1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
import type { InStatement } from '@libsql/client';
import { createClient } from '@libsql/client';
import { type DBTables } from '../core/types.js';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql';
import { drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy';
import { type SQLiteTable } from 'drizzle-orm/sqlite-core';
import { z } from 'zod';
import { getTableName } from 'drizzle-orm';
const isWebContainer = !!process.versions?.webcontainer;
interface LocalDatabaseClient extends LibSQLDatabase, Disposable {}
export async function createLocalDatabaseClient({
tables,
dbUrl,
seeding,
}: {
dbUrl: string;
tables: DBTables;
seeding: boolean;
}): Promise<LocalDatabaseClient> {
const url = isWebContainer ? 'file:content.db' : dbUrl;
const client = createClient({ url });
const db = Object.assign(drizzleLibsql(client), {
[Symbol.dispose || Symbol.for('Symbol.dispose')]() {
client.close();
},
});
if (seeding) return db;
const { insert: drizzleInsert, update: drizzleUpdate, delete: drizzleDelete } = db;
return Object.assign(db, {
insert(Table: SQLiteTable) {
checkIfModificationIsAllowed(tables, Table);
return drizzleInsert.call(this, Table);
},
update(Table: SQLiteTable) {
checkIfModificationIsAllowed(tables, Table);
return drizzleUpdate.call(this, Table);
},
delete(Table: SQLiteTable) {
checkIfModificationIsAllowed(tables, Table);
return drizzleDelete.call(this, Table);
},
});
}
function checkIfModificationIsAllowed(tables: DBTables, Table: SQLiteTable) {
const tableName = getTableName(Table);
const collection = tables[tableName];
if (!collection.writable) {
throw new Error(`The [${tableName}] collection is read-only.`);
}
}
export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string) {
const url = new URL('/db/query', remoteDbURL);
const db = drizzleProxy(async (sql, parameters, method) => {
const requestBody: InStatement = { sql, args: parameters };
// eslint-disable-next-line no-console
console.info(JSON.stringify(requestBody));
const res = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${appToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!res.ok) {
throw new Error(
`Failed to execute query.\nQuery: ${sql}\nFull error: ${res.status} ${await res.text()}}`
);
}
const queryResultSchema = z.object({
rows: z.array(z.unknown()),
});
let rows: unknown[];
try {
const json = await res.json();
rows = queryResultSchema.parse(json).rows;
} catch (e) {
throw new Error(
`Failed to execute query.\nQuery: ${sql}\nFull error: Unexpected JSON response. ${
e instanceof Error ? e.message : String(e)
}`
);
}
// Drizzle expects each row as an array of its values
const rowValues: unknown[][] = [];
for (const row of rows) {
if (row != null && typeof row === 'object') {
rowValues.push(Object.values(row));
}
}
if (method === 'get') {
return { rows: rowValues[0] };
}
return { rows: rowValues };
});
return db;
}
|