or3-provider-sqlite 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -19
- package/dist/module.json +1 -1
- package/dist/runtime/server/admin/adapters/sync-sqlite.js +90 -16
- package/dist/runtime/server/admin/stores/sqlite-store.js +7 -8
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.d.ts +5 -0
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.js +368 -40
- package/dist/runtime/server/connect/sqlite-connect-store.js +19 -5
- package/dist/runtime/server/db/d1-dialect.d.ts +39 -0
- package/dist/runtime/server/db/d1-dialect.js +70 -0
- package/dist/runtime/server/db/d1.d.ts +13 -0
- package/dist/runtime/server/db/d1.js +30 -0
- package/dist/runtime/server/db/kysely.d.ts +53 -15
- package/dist/runtime/server/db/kysely.js +374 -16
- package/dist/runtime/server/db/migrate.js +3 -1
- package/dist/runtime/server/db/migrations/016_connect_runtime_binding.d.ts +4 -0
- package/dist/runtime/server/db/migrations/016_connect_runtime_binding.js +28 -0
- package/dist/runtime/server/db/schema.d.ts +3 -0
- package/dist/runtime/server/plugins/register.js +33 -13
- package/dist/runtime/server/rate-limit/sqlite-provider.d.ts +2 -0
- package/dist/runtime/server/rate-limit/sqlite-provider.js +70 -1
- package/dist/runtime/server/sync/maintenance-state.d.ts +26 -0
- package/dist/runtime/server/sync/maintenance-state.js +66 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +15 -4
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +780 -21
- package/dist/runtime/server/webhooks/sqlite-webhook-store.d.ts +3 -2
- package/dist/runtime/server/webhooks/sqlite-webhook-store.js +90 -39
- package/package.json +10 -6
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getD1Database } from "./kysely.js";
|
|
2
|
+
function assertSuccess(result) {
|
|
3
|
+
if (result.success === false) {
|
|
4
|
+
throw new Error(result.error ?? "Cloudflare D1 statement failed.");
|
|
5
|
+
}
|
|
6
|
+
if (result.error) throw new Error(result.error);
|
|
7
|
+
}
|
|
8
|
+
export async function d1All(sql, ...parameters) {
|
|
9
|
+
const result = await getD1Database().prepare(sql).bind(...parameters).all();
|
|
10
|
+
assertSuccess(result);
|
|
11
|
+
return result.results ?? [];
|
|
12
|
+
}
|
|
13
|
+
export async function d1Run(sql, ...parameters) {
|
|
14
|
+
const result = await getD1Database().prepare(sql).bind(...parameters).run();
|
|
15
|
+
assertSuccess(result);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
export async function d1Batch(statements) {
|
|
19
|
+
const database = getD1Database();
|
|
20
|
+
if (!database.batch) {
|
|
21
|
+
throw new Error("The configured D1 binding does not support batch().");
|
|
22
|
+
}
|
|
23
|
+
const results = await database.batch(
|
|
24
|
+
statements.map(
|
|
25
|
+
({ sql, parameters = [] }) => database.prepare(sql).bind(...parameters)
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
for (const result of results) assertSuccess(result);
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
@@ -1,32 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared database initialization for the SQLite provider.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The default remains better-sqlite3. Bun and Turso expose the same
|
|
5
|
+
* synchronous SQLite shape, while Cloudflare D1 uses its native async binding.
|
|
6
6
|
*/
|
|
7
7
|
import { Kysely } from 'kysely';
|
|
8
|
-
import
|
|
8
|
+
import { type D1Database } from './d1-dialect.js';
|
|
9
9
|
import type { Or3SqliteDb } from './schema.js';
|
|
10
|
+
export type SqliteDriver = 'better-sqlite3' | 'bun' | 'turso' | 'd1';
|
|
11
|
+
export interface SqliteRawStatement {
|
|
12
|
+
readonly reader?: boolean;
|
|
13
|
+
all(...parameters: unknown[]): unknown[];
|
|
14
|
+
get(...parameters: unknown[]): unknown;
|
|
15
|
+
run(...parameters: unknown[]): {
|
|
16
|
+
changes: number;
|
|
17
|
+
lastInsertRowid?: number | bigint;
|
|
18
|
+
};
|
|
19
|
+
iterate(...parameters: unknown[]): IterableIterator<unknown>;
|
|
20
|
+
}
|
|
21
|
+
type SqliteTransaction<T> = ((...args: unknown[]) => T) & {
|
|
22
|
+
immediate: (...args: unknown[]) => T;
|
|
23
|
+
};
|
|
24
|
+
export interface SqliteRawDatabase {
|
|
25
|
+
close(): void;
|
|
26
|
+
prepare(sql: string): SqliteRawStatement;
|
|
27
|
+
pragma?(source: string): unknown;
|
|
28
|
+
exec(source: string): unknown;
|
|
29
|
+
transaction<T>(fn: (...args: unknown[]) => T): SqliteTransaction<T>;
|
|
30
|
+
}
|
|
10
31
|
export interface SqliteDbOptions {
|
|
11
|
-
path
|
|
32
|
+
/** SQLite file path for better-sqlite3 and Bun. */
|
|
33
|
+
path?: string;
|
|
34
|
+
/** Explicitly selects a native runtime. Defaults to better-sqlite3. */
|
|
35
|
+
driver?: SqliteDriver | string;
|
|
12
36
|
journalMode?: string;
|
|
13
37
|
synchronous?: string;
|
|
38
|
+
/** Turso/libSQL database URL. */
|
|
39
|
+
tursoUrl?: string;
|
|
40
|
+
/** Turso/libSQL auth token. */
|
|
41
|
+
tursoAuthToken?: string;
|
|
42
|
+
/** Cloudflare D1 binding name. Defaults to DB. */
|
|
43
|
+
d1Binding?: string;
|
|
44
|
+
/** Allows integrations and tests to provide a D1 binding directly. */
|
|
45
|
+
d1Database?: D1Database;
|
|
14
46
|
}
|
|
15
47
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
48
|
+
* Initialize the singleton once during server startup. This is async because
|
|
49
|
+
* all native runtimes are loaded lazily, keeping Workers bundles Node-free.
|
|
18
50
|
*/
|
|
19
|
-
export declare function
|
|
51
|
+
export declare function initializeSqliteDb(options?: SqliteDbOptions): Promise<Kysely<Or3SqliteDb>>;
|
|
20
52
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Only available after getSqliteDb() has been called.
|
|
53
|
+
* Return the initialized Kysely singleton.
|
|
23
54
|
*/
|
|
24
|
-
export declare function
|
|
55
|
+
export declare function getSqliteDb(): Kysely<Or3SqliteDb>;
|
|
56
|
+
/** The selected driver, resolving the current environment before initialization. */
|
|
57
|
+
export declare function getSqliteDriver(): SqliteDriver;
|
|
58
|
+
export declare function isD1Driver(): boolean;
|
|
25
59
|
/**
|
|
26
|
-
*
|
|
60
|
+
* Get the native local SQLite connection for synchronous compatibility paths.
|
|
61
|
+
* D1 intentionally has no synchronous raw connection.
|
|
27
62
|
*/
|
|
63
|
+
export declare function getRawDb(): SqliteRawDatabase;
|
|
64
|
+
/** Return the initialized D1 binding when the D1 driver is selected. */
|
|
65
|
+
export declare function getD1Database(): D1Database;
|
|
66
|
+
/** Destroy the current connection (primarily for tests). */
|
|
28
67
|
export declare function destroySqliteDb(): Promise<void>;
|
|
29
|
-
/**
|
|
30
|
-
* Reset the module-level singleton (for tests only).
|
|
31
|
-
*/
|
|
68
|
+
/** Reset module state for tests. */
|
|
32
69
|
export declare function _resetForTest(): void;
|
|
70
|
+
export {};
|
|
@@ -1,14 +1,46 @@
|
|
|
1
1
|
import { Kysely, SqliteDialect } from "kysely";
|
|
2
|
-
import
|
|
2
|
+
import { D1Dialect } from "./d1-dialect.js";
|
|
3
3
|
let instance = null;
|
|
4
4
|
let rawDb = null;
|
|
5
|
+
let d1Db = null;
|
|
6
|
+
let selectedDriver = null;
|
|
7
|
+
let initialization = null;
|
|
5
8
|
function envFlag(value) {
|
|
6
9
|
if (!value) return false;
|
|
7
10
|
const normalized = value.trim().toLowerCase();
|
|
8
11
|
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
9
12
|
}
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
function normalizeDriver(value) {
|
|
14
|
+
switch (value?.trim().toLowerCase()) {
|
|
15
|
+
case void 0:
|
|
16
|
+
case "":
|
|
17
|
+
case "better-sqlite3":
|
|
18
|
+
case "better":
|
|
19
|
+
case "sqlite":
|
|
20
|
+
return "better-sqlite3";
|
|
21
|
+
case "bun":
|
|
22
|
+
case "bun:sqlite":
|
|
23
|
+
return "bun";
|
|
24
|
+
case "turso":
|
|
25
|
+
case "libsql":
|
|
26
|
+
return "turso";
|
|
27
|
+
case "d1":
|
|
28
|
+
case "cloudflare-d1":
|
|
29
|
+
case "cloudflare":
|
|
30
|
+
return "d1";
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Unsupported OR3_SQLITE_DRIVER value "${value}". Use better-sqlite3, bun, turso, or d1.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function resolveDriver(options) {
|
|
38
|
+
const configured = options?.driver ?? process.env.OR3_SQLITE_DRIVER;
|
|
39
|
+
if (configured) return normalizeDriver(configured);
|
|
40
|
+
if (options?.d1Database) return "d1";
|
|
41
|
+
return "better-sqlite3";
|
|
42
|
+
}
|
|
43
|
+
function resolveLocalPath(options) {
|
|
12
44
|
const isTestEnv = process.env.NODE_ENV === "test" || envFlag(process.env.VITEST);
|
|
13
45
|
const allowInMemory = envFlag(process.env.OR3_SQLITE_ALLOW_IN_MEMORY);
|
|
14
46
|
const strictMode = envFlag(process.env.OR3_SQLITE_STRICT);
|
|
@@ -34,29 +66,355 @@ export function getSqliteDb(options) {
|
|
|
34
66
|
"[or3-sqlite] OR3_SQLITE_ALLOW_IN_MEMORY=true enabled. Data will be lost on process restart."
|
|
35
67
|
);
|
|
36
68
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
return path;
|
|
70
|
+
}
|
|
71
|
+
function normalizeParameters(parameters) {
|
|
72
|
+
return parameters.length === 1 && Array.isArray(parameters[0]) ? parameters[0] : parameters;
|
|
73
|
+
}
|
|
74
|
+
function isReaderStatement(sql) {
|
|
75
|
+
const normalized = sql.trim().toLowerCase();
|
|
76
|
+
return /^(select|pragma|explain|with)\b/.test(normalized) || /\breturning\b/.test(normalized);
|
|
77
|
+
}
|
|
78
|
+
class BunStatementAdapter {
|
|
79
|
+
constructor(statement, sql) {
|
|
80
|
+
this.statement = statement;
|
|
81
|
+
this.sql = sql;
|
|
82
|
+
}
|
|
83
|
+
get reader() {
|
|
84
|
+
return this.statement.columnNames ? this.statement.columnNames.length > 0 : isReaderStatement(this.sql);
|
|
85
|
+
}
|
|
86
|
+
all(...parameters) {
|
|
87
|
+
return this.statement.all(...normalizeParameters(parameters));
|
|
88
|
+
}
|
|
89
|
+
get(...parameters) {
|
|
90
|
+
return this.statement.get(...normalizeParameters(parameters));
|
|
91
|
+
}
|
|
92
|
+
run(...parameters) {
|
|
93
|
+
const result = this.statement.run(...normalizeParameters(parameters));
|
|
94
|
+
return {
|
|
95
|
+
changes: result.changes ?? 0,
|
|
96
|
+
lastInsertRowid: result.lastInsertRowid
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
iterate(...parameters) {
|
|
100
|
+
const values = normalizeParameters(parameters);
|
|
101
|
+
return this.statement.iterate ? this.statement.iterate(...values) : this.statement.all(...values)[Symbol.iterator]();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
class BunDatabaseAdapter {
|
|
105
|
+
constructor(database) {
|
|
106
|
+
this.database = database;
|
|
107
|
+
}
|
|
108
|
+
close() {
|
|
109
|
+
this.database.close();
|
|
110
|
+
}
|
|
111
|
+
prepare(sql) {
|
|
112
|
+
return new BunStatementAdapter(this.database.prepare(sql), sql);
|
|
113
|
+
}
|
|
114
|
+
pragma(source) {
|
|
115
|
+
if (this.database.run) return this.database.run(`PRAGMA ${source}`);
|
|
116
|
+
return this.database.exec?.(`PRAGMA ${source}`);
|
|
117
|
+
}
|
|
118
|
+
exec(source) {
|
|
119
|
+
return this.database.exec?.(source);
|
|
120
|
+
}
|
|
121
|
+
transaction(fn) {
|
|
122
|
+
if (!this.database.transaction) {
|
|
123
|
+
throw new Error("bun:sqlite does not expose transaction() in this Bun runtime.");
|
|
124
|
+
}
|
|
125
|
+
const transaction = this.database.transaction(fn);
|
|
126
|
+
if (!transaction.immediate) {
|
|
127
|
+
transaction.immediate = (...args) => transaction(...args);
|
|
128
|
+
}
|
|
129
|
+
return transaction;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function stripLibsqlMetadata(value) {
|
|
133
|
+
if (!isRecord(value) || !("_metadata" in value)) return value;
|
|
134
|
+
const { _metadata: _ignored, ...row } = value;
|
|
135
|
+
return row;
|
|
136
|
+
}
|
|
137
|
+
class TursoStatementAdapter {
|
|
138
|
+
constructor(statement) {
|
|
139
|
+
this.statement = statement;
|
|
140
|
+
}
|
|
141
|
+
get reader() {
|
|
142
|
+
return this.statement.reader;
|
|
143
|
+
}
|
|
144
|
+
all(...parameters) {
|
|
145
|
+
return this.statement.all(...parameters).map(stripLibsqlMetadata);
|
|
146
|
+
}
|
|
147
|
+
get(...parameters) {
|
|
148
|
+
return stripLibsqlMetadata(this.statement.get(...parameters));
|
|
149
|
+
}
|
|
150
|
+
run(...parameters) {
|
|
151
|
+
return stripLibsqlMetadata(
|
|
152
|
+
this.statement.run(...parameters)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
iterate(...parameters) {
|
|
156
|
+
return Array.from(
|
|
157
|
+
this.statement.iterate(...parameters),
|
|
158
|
+
stripLibsqlMetadata
|
|
159
|
+
)[Symbol.iterator]();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
class TursoDatabaseAdapter {
|
|
163
|
+
constructor(database) {
|
|
164
|
+
this.database = database;
|
|
165
|
+
}
|
|
166
|
+
close() {
|
|
167
|
+
this.database.close();
|
|
168
|
+
}
|
|
169
|
+
prepare(sql) {
|
|
170
|
+
return new TursoStatementAdapter(this.database.prepare(sql));
|
|
171
|
+
}
|
|
172
|
+
pragma(source) {
|
|
173
|
+
return this.database.pragma?.(source);
|
|
174
|
+
}
|
|
175
|
+
exec(source) {
|
|
176
|
+
return this.database.exec(source);
|
|
177
|
+
}
|
|
178
|
+
transaction(fn) {
|
|
179
|
+
return this.database.transaction(fn);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
class KyselyStatementAdapter {
|
|
183
|
+
constructor(statement, sql) {
|
|
184
|
+
this.statement = statement;
|
|
185
|
+
this.sql = sql;
|
|
186
|
+
}
|
|
187
|
+
get reader() {
|
|
188
|
+
return this.statement.reader ?? isReaderStatement(this.sql);
|
|
189
|
+
}
|
|
190
|
+
all(parameters) {
|
|
191
|
+
return this.statement.all(parameters);
|
|
192
|
+
}
|
|
193
|
+
run(parameters) {
|
|
194
|
+
const result = this.statement.run(parameters);
|
|
195
|
+
return {
|
|
196
|
+
changes: result.changes,
|
|
197
|
+
lastInsertRowid: result.lastInsertRowid ?? 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
iterate(parameters) {
|
|
201
|
+
return this.statement.iterate(parameters);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
class KyselyDatabaseAdapter {
|
|
205
|
+
constructor(database) {
|
|
206
|
+
this.database = database;
|
|
207
|
+
}
|
|
208
|
+
close() {
|
|
209
|
+
this.database.close();
|
|
210
|
+
}
|
|
211
|
+
prepare(sql) {
|
|
212
|
+
return new KyselyStatementAdapter(this.database.prepare(sql), sql);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function validatePragmaValue(name, value) {
|
|
216
|
+
const normalized = value.trim();
|
|
217
|
+
if (!/^[a-z0-9_]+$/i.test(normalized)) {
|
|
218
|
+
throw new Error(`Invalid ${name} pragma value.`);
|
|
219
|
+
}
|
|
220
|
+
return normalized;
|
|
221
|
+
}
|
|
222
|
+
function applyLocalPragmas(db, options) {
|
|
223
|
+
const journalMode = validatePragmaValue(
|
|
224
|
+
"journal_mode",
|
|
225
|
+
options?.journalMode ?? process.env.OR3_SQLITE_PRAGMA_JOURNAL_MODE ?? "WAL"
|
|
226
|
+
);
|
|
227
|
+
const synchronous = validatePragmaValue(
|
|
228
|
+
"synchronous",
|
|
229
|
+
options?.synchronous ?? process.env.OR3_SQLITE_PRAGMA_SYNCHRONOUS ?? "NORMAL"
|
|
230
|
+
);
|
|
231
|
+
db.pragma?.(`journal_mode = ${journalMode}`);
|
|
232
|
+
db.pragma?.(`synchronous = ${synchronous}`);
|
|
233
|
+
db.pragma?.("foreign_keys = ON");
|
|
234
|
+
}
|
|
235
|
+
async function importRuntimeModule(moduleName) {
|
|
236
|
+
const imported = await import(
|
|
237
|
+
/* @vite-ignore */
|
|
238
|
+
moduleName
|
|
239
|
+
);
|
|
240
|
+
if (!isRecord(imported)) {
|
|
241
|
+
throw new Error(`Runtime module "${moduleName}" did not export an object.`);
|
|
242
|
+
}
|
|
243
|
+
return imported;
|
|
244
|
+
}
|
|
245
|
+
async function importCloudflareWorkersModule() {
|
|
246
|
+
const imported = await import(
|
|
247
|
+
/* @vite-ignore */
|
|
248
|
+
"cloudflare:workers"
|
|
249
|
+
);
|
|
250
|
+
if (!isRecord(imported)) {
|
|
251
|
+
throw new Error("cloudflare:workers did not export an object.");
|
|
252
|
+
}
|
|
253
|
+
return imported;
|
|
254
|
+
}
|
|
255
|
+
function isRecord(value) {
|
|
256
|
+
return typeof value === "object" && value !== null;
|
|
257
|
+
}
|
|
258
|
+
function asDatabaseConstructor(value) {
|
|
259
|
+
return typeof value === "function" ? value : null;
|
|
260
|
+
}
|
|
261
|
+
async function createBetterSqliteDatabase(path) {
|
|
262
|
+
let imported;
|
|
263
|
+
try {
|
|
264
|
+
imported = await importRuntimeModule("better-sqlite3");
|
|
265
|
+
} catch (error) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"Unable to load better-sqlite3. Install it or set OR3_SQLITE_DRIVER to bun, turso, or d1.",
|
|
268
|
+
{ cause: error }
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
const Database = asDatabaseConstructor(imported.default);
|
|
272
|
+
if (!Database) {
|
|
273
|
+
throw new Error("better-sqlite3 did not provide a Database constructor.");
|
|
274
|
+
}
|
|
275
|
+
return new Database(path);
|
|
276
|
+
}
|
|
277
|
+
async function createBunSqliteDatabase(path) {
|
|
278
|
+
let imported;
|
|
279
|
+
try {
|
|
280
|
+
imported = await import(
|
|
281
|
+
/* @vite-ignore */
|
|
282
|
+
"bun:sqlite"
|
|
283
|
+
);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"OR3_SQLITE_DRIVER=bun requires Bun and its built-in bun:sqlite module.",
|
|
287
|
+
{ cause: error }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const Database = asDatabaseConstructor(
|
|
291
|
+
imported.Database ?? imported.default
|
|
292
|
+
);
|
|
293
|
+
if (!Database) {
|
|
294
|
+
throw new Error("bun:sqlite did not provide a Database constructor.");
|
|
295
|
+
}
|
|
296
|
+
return new BunDatabaseAdapter(new Database(path));
|
|
297
|
+
}
|
|
298
|
+
async function createTursoDatabase(options) {
|
|
299
|
+
const url = options?.tursoUrl ?? process.env.OR3_SQLITE_TURSO_URL;
|
|
300
|
+
const authToken = options?.tursoAuthToken ?? process.env.OR3_SQLITE_TURSO_AUTH_TOKEN;
|
|
301
|
+
if (!url?.trim()) {
|
|
302
|
+
throw new Error("OR3_SQLITE_TURSO_URL is required when OR3_SQLITE_DRIVER=turso.");
|
|
303
|
+
}
|
|
304
|
+
if (!authToken?.trim()) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
"OR3_SQLITE_TURSO_AUTH_TOKEN is required when OR3_SQLITE_DRIVER=turso."
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
let imported;
|
|
310
|
+
try {
|
|
311
|
+
imported = await importRuntimeModule("libsql");
|
|
312
|
+
} catch (error) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
"Unable to load libsql for Turso. Install the libsql package and try again.",
|
|
315
|
+
{ cause: error }
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const Database = asDatabaseConstructor(
|
|
319
|
+
imported.default ?? imported.Database
|
|
320
|
+
);
|
|
321
|
+
if (!Database) {
|
|
322
|
+
throw new Error("libsql did not provide a Database constructor.");
|
|
323
|
+
}
|
|
324
|
+
return new TursoDatabaseAdapter(new Database(url, { authToken }));
|
|
325
|
+
}
|
|
326
|
+
async function resolveD1Database(options) {
|
|
327
|
+
if (options?.d1Database) return options.d1Database;
|
|
328
|
+
const binding = (options?.d1Binding ?? process.env.OR3_SQLITE_D1_BINDING ?? "DB").trim();
|
|
329
|
+
if (!binding) {
|
|
330
|
+
throw new Error("OR3_SQLITE_D1_BINDING must name a Cloudflare D1 binding.");
|
|
331
|
+
}
|
|
332
|
+
let imported;
|
|
333
|
+
try {
|
|
334
|
+
imported = await importCloudflareWorkersModule();
|
|
335
|
+
} catch (error) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
"OR3_SQLITE_DRIVER=d1 requires a Cloudflare Workers runtime with a D1 binding.",
|
|
338
|
+
{ cause: error }
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const env = imported.env;
|
|
342
|
+
const database = isRecord(env) ? env[binding] : void 0;
|
|
343
|
+
if (!isD1Database(database)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Cloudflare D1 binding "${binding}" was not found. Set OR3_SQLITE_D1_BINDING to the binding name from wrangler.jsonc.`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return database;
|
|
349
|
+
}
|
|
350
|
+
function isD1Database(value) {
|
|
351
|
+
return isRecord(value) && typeof value.prepare === "function";
|
|
352
|
+
}
|
|
353
|
+
async function initialize(options) {
|
|
354
|
+
const driver = resolveDriver(options);
|
|
355
|
+
selectedDriver = driver;
|
|
356
|
+
if (driver === "d1") {
|
|
357
|
+
d1Db = await resolveD1Database(options);
|
|
358
|
+
instance = new Kysely({
|
|
359
|
+
dialect: new D1Dialect(d1Db)
|
|
360
|
+
});
|
|
361
|
+
return instance;
|
|
362
|
+
}
|
|
363
|
+
const path = resolveLocalPath(options);
|
|
364
|
+
rawDb = driver === "bun" ? await createBunSqliteDatabase(path) : driver === "turso" ? await createTursoDatabase(options) : await createBetterSqliteDatabase(path);
|
|
365
|
+
if (driver !== "turso") applyLocalPragmas(rawDb, options);
|
|
43
366
|
instance = new Kysely({
|
|
44
|
-
dialect: new SqliteDialect({ database: rawDb })
|
|
367
|
+
dialect: new SqliteDialect({ database: new KyselyDatabaseAdapter(rawDb) })
|
|
45
368
|
});
|
|
46
369
|
return instance;
|
|
47
370
|
}
|
|
371
|
+
export async function initializeSqliteDb(options) {
|
|
372
|
+
if (instance) return instance;
|
|
373
|
+
if (!initialization) {
|
|
374
|
+
initialization = initialize(options).finally(() => {
|
|
375
|
+
initialization = null;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return initialization;
|
|
379
|
+
}
|
|
380
|
+
export function getSqliteDb() {
|
|
381
|
+
if (!instance) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
"SQLite DB not initialized \u2014 await initializeSqliteDb() before calling getSqliteDb()."
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return instance;
|
|
387
|
+
}
|
|
388
|
+
export function getSqliteDriver() {
|
|
389
|
+
return selectedDriver ?? resolveDriver(void 0);
|
|
390
|
+
}
|
|
391
|
+
export function isD1Driver() {
|
|
392
|
+
return getSqliteDriver() === "d1";
|
|
393
|
+
}
|
|
48
394
|
export function getRawDb() {
|
|
49
|
-
if (!rawDb)
|
|
395
|
+
if (!rawDb) {
|
|
396
|
+
const suffix = isD1Driver() ? " Cloudflare D1 uses an async native binding." : "";
|
|
397
|
+
throw new Error(`SQLite raw DB not initialized.${suffix}`);
|
|
398
|
+
}
|
|
50
399
|
return rawDb;
|
|
51
400
|
}
|
|
52
|
-
export
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
instance = null;
|
|
56
|
-
rawDb = null;
|
|
401
|
+
export function getD1Database() {
|
|
402
|
+
if (!d1Db) {
|
|
403
|
+
throw new Error("Cloudflare D1 is not initialized \u2014 call initializeSqliteDb() first.");
|
|
57
404
|
}
|
|
405
|
+
return d1Db;
|
|
406
|
+
}
|
|
407
|
+
export async function destroySqliteDb() {
|
|
408
|
+
if (instance) await instance.destroy();
|
|
409
|
+
instance = null;
|
|
410
|
+
rawDb = null;
|
|
411
|
+
d1Db = null;
|
|
412
|
+
selectedDriver = null;
|
|
58
413
|
}
|
|
59
414
|
export function _resetForTest() {
|
|
60
415
|
instance = null;
|
|
61
416
|
rawDb = null;
|
|
417
|
+
d1Db = null;
|
|
418
|
+
selectedDriver = null;
|
|
419
|
+
initialization = null;
|
|
62
420
|
}
|
|
@@ -14,6 +14,7 @@ import * as m012 from "./migrations/012_connect_credential_redelivery.js";
|
|
|
14
14
|
import * as m013 from "./migrations/013_connect_environment_lifecycle.js";
|
|
15
15
|
import * as m014 from "./migrations/014_connect_retention_activation.js";
|
|
16
16
|
import * as m015 from "./migrations/015_rate_limits.js";
|
|
17
|
+
import * as m016 from "./migrations/016_connect_runtime_binding.js";
|
|
17
18
|
const REQUIRED_SCHEMA_TABLES = [
|
|
18
19
|
"users",
|
|
19
20
|
"auth_accounts",
|
|
@@ -55,7 +56,8 @@ const migrations = {
|
|
|
55
56
|
"012_connect_credential_redelivery": m012,
|
|
56
57
|
"013_connect_environment_lifecycle": m013,
|
|
57
58
|
"014_connect_retention_activation": m014,
|
|
58
|
-
"015_rate_limits": m015
|
|
59
|
+
"015_rate_limits": m015,
|
|
60
|
+
"016_connect_runtime_binding": m016
|
|
59
61
|
};
|
|
60
62
|
class StaticMigrationProvider {
|
|
61
63
|
async getMigrations() {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
const result = await sql`
|
|
4
|
+
PRAGMA table_info(connect_environments)
|
|
5
|
+
`.execute(db);
|
|
6
|
+
const columns = new Set(result.rows.map((column) => column.name));
|
|
7
|
+
if (!columns.has("driver")) {
|
|
8
|
+
await sql`
|
|
9
|
+
ALTER TABLE connect_environments
|
|
10
|
+
ADD COLUMN driver TEXT
|
|
11
|
+
`.execute(db);
|
|
12
|
+
}
|
|
13
|
+
if (!columns.has("runtime")) {
|
|
14
|
+
await sql`
|
|
15
|
+
ALTER TABLE connect_environments
|
|
16
|
+
ADD COLUMN runtime TEXT
|
|
17
|
+
`.execute(db);
|
|
18
|
+
}
|
|
19
|
+
if (!columns.has("base_path")) {
|
|
20
|
+
await sql`
|
|
21
|
+
ALTER TABLE connect_environments
|
|
22
|
+
ADD COLUMN base_path TEXT
|
|
23
|
+
`.execute(db);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function down(db) {
|
|
27
|
+
void db;
|
|
28
|
+
}
|
|
@@ -164,6 +164,9 @@ export interface ConnectEnvironmentsTable {
|
|
|
164
164
|
control_token_hash: string;
|
|
165
165
|
access_credential_ciphertext: string;
|
|
166
166
|
tunnel_secret_ciphertext: string | null;
|
|
167
|
+
driver: 'intern' | 'runs' | null;
|
|
168
|
+
runtime: 'intern' | 'openclaw' | 'hermes' | null;
|
|
169
|
+
base_path: '/' | '/or3/' | null;
|
|
167
170
|
status: 'provisioning' | 'active' | 'revoking' | 'revoked' | 'error';
|
|
168
171
|
lifecycle_attempts: number;
|
|
169
172
|
lifecycle_next_attempt_at: number;
|
|
@@ -16,18 +16,24 @@ import {
|
|
|
16
16
|
} from "../admin/stores/sqlite-store.js";
|
|
17
17
|
import { sqliteSyncAdminAdapter } from "../admin/adapters/sync-sqlite.js";
|
|
18
18
|
import { createSqliteConnectStore } from "../connect/sqlite-connect-store.js";
|
|
19
|
-
import {
|
|
19
|
+
import { getSqliteDriver, initializeSqliteDb } from "../db/kysely.js";
|
|
20
20
|
import { runMigrations } from "../db/migrate.js";
|
|
21
21
|
import { sqliteRateLimitProvider } from "../rate-limit/sqlite-provider.js";
|
|
22
22
|
import { useRuntimeConfig } from "#imports";
|
|
23
23
|
const SQLITE_PROVIDER_ID = "sqlite";
|
|
24
|
-
|
|
24
|
+
let d1RuntimeInitialization;
|
|
25
|
+
export default defineNitroPlugin(async (nitroApp) => {
|
|
25
26
|
const config = useRuntimeConfig();
|
|
26
27
|
if (!config.auth?.enabled) return;
|
|
27
28
|
const syncSelected = config.sync?.enabled === true && config.sync?.provider === SQLITE_PROVIDER_ID;
|
|
28
29
|
const connectSelected = config.connect?.enabled === true && config.connect?.provider === SQLITE_PROVIDER_ID;
|
|
29
30
|
if (!syncSelected && !connectSelected) return;
|
|
30
|
-
const
|
|
31
|
+
const driver = getSqliteDriver();
|
|
32
|
+
if (driver === "d1" && connectSelected) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"[or3-sqlite] Cloudflare D1 supports Auth and Sync, but OR3 Connect still requires a synchronous SQLite runtime. Set OR3_CONNECT_ENABLED=false or choose better-sqlite3, Bun, or Turso."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
31
37
|
registerAuthWorkspaceStore({
|
|
32
38
|
id: SQLITE_PROVIDER_ID,
|
|
33
39
|
order: 100,
|
|
@@ -48,24 +54,38 @@ export default defineNitroPlugin(async () => {
|
|
|
48
54
|
create: createSqliteConnectStore
|
|
49
55
|
});
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
if (driver !== "d1") {
|
|
58
|
+
registerWebhookStore({
|
|
59
|
+
id: SQLITE_PROVIDER_ID,
|
|
60
|
+
order: 100,
|
|
61
|
+
create: createSqliteWebhookStore
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
console.warn(
|
|
65
|
+
"[or3-sqlite] Webhook persistence is unavailable with Cloudflare D1."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
56
68
|
registerAdminStoreProvider({
|
|
57
69
|
id: SQLITE_PROVIDER_ID,
|
|
58
70
|
createWorkspaceAccessStore: createSqliteWorkspaceAccessStore,
|
|
59
71
|
createWorkspaceSettingsStore: createSqliteWorkspaceSettingsStore,
|
|
60
72
|
createAdminUserStore: createSqliteAdminUserStore,
|
|
61
73
|
getCapabilities: () => ({
|
|
62
|
-
supportsServerSideAdmin:
|
|
63
|
-
supportsUserSearch:
|
|
64
|
-
supportsWorkspaceList:
|
|
65
|
-
supportsWorkspaceManagement:
|
|
66
|
-
supportsDeploymentAdminGrants:
|
|
74
|
+
supportsServerSideAdmin: driver !== "d1",
|
|
75
|
+
supportsUserSearch: driver !== "d1",
|
|
76
|
+
supportsWorkspaceList: driver !== "d1",
|
|
77
|
+
supportsWorkspaceManagement: driver !== "d1",
|
|
78
|
+
supportsDeploymentAdminGrants: driver !== "d1"
|
|
67
79
|
})
|
|
68
80
|
});
|
|
69
81
|
registerProviderAdminAdapter(sqliteSyncAdminAdapter);
|
|
82
|
+
if (driver === "d1") {
|
|
83
|
+
nitroApp.hooks.hook("request", async () => {
|
|
84
|
+
d1RuntimeInitialization ??= initializeSqliteDb().then(runMigrations);
|
|
85
|
+
await d1RuntimeInitialization;
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const db = await initializeSqliteDb();
|
|
70
90
|
await runMigrations(db);
|
|
71
91
|
});
|
|
@@ -3,5 +3,7 @@ export declare class SqliteRateLimitProvider implements RateLimitProvider {
|
|
|
3
3
|
readonly name = "sqlite";
|
|
4
4
|
checkAndRecord(key: string, config: RateLimitConfig): Promise<RateLimitResult>;
|
|
5
5
|
getStats(key: string, config: RateLimitConfig): Promise<RateLimitStats | null>;
|
|
6
|
+
private checkAndRecordInD1;
|
|
7
|
+
private getStatsInD1;
|
|
6
8
|
}
|
|
7
9
|
export declare const sqliteRateLimitProvider: SqliteRateLimitProvider;
|