rivetkit 0.0.0-stack-chore-sync-engine-changes-from-enterprise-mszropyy.677d526 → 0.0.0-sync-sqlite.527e186
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/dist/browser/client.d.ts +25 -0
- package/dist/browser/client.js +17 -5
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +1 -1
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/agent-os/index.cjs +145 -3
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +40 -2
- package/dist/tsup/agent-os/index.d.ts +40 -2
- package/dist/tsup/agent-os/index.js +144 -2
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-GPD77M3M.js → chunk-2UJVBM5X.js} +2 -2
- package/dist/tsup/chunk-2UJVBM5X.js.map +1 -0
- package/dist/tsup/{chunk-OWSXJF7Z.js → chunk-6DC5T27N.js} +20 -8
- package/dist/tsup/chunk-6DC5T27N.js.map +1 -0
- package/dist/tsup/{chunk-XPT7SGBW.js → chunk-6QHFQDW4.js} +4 -4
- package/dist/tsup/{chunk-ZXV3CG4Y.cjs → chunk-7KYMUJOU.cjs} +7 -7
- package/dist/tsup/{chunk-ZXV3CG4Y.cjs.map → chunk-7KYMUJOU.cjs.map} +1 -1
- package/dist/tsup/{chunk-M4RSGECU.js → chunk-7XKSZBR7.js} +2 -2
- package/dist/tsup/{chunk-VXJ26VPL.cjs → chunk-BPBPNCCY.cjs} +10 -10
- package/dist/tsup/{chunk-VXJ26VPL.cjs.map → chunk-BPBPNCCY.cjs.map} +1 -1
- package/dist/tsup/{chunk-YHYNYVNZ.js → chunk-CV5KMH2U.js} +3 -3
- package/dist/tsup/{chunk-FSD52J3H.cjs → chunk-D72VXRS4.cjs} +3 -3
- package/dist/tsup/{chunk-FSD52J3H.cjs.map → chunk-D72VXRS4.cjs.map} +1 -1
- package/dist/tsup/{chunk-L6DNN72S.js → chunk-GE2H2DHD.js} +2 -2
- package/dist/tsup/{chunk-UBGN2UE2.cjs → chunk-GNKZNRNM.cjs} +3 -3
- package/dist/tsup/{chunk-UBGN2UE2.cjs.map → chunk-GNKZNRNM.cjs.map} +1 -1
- package/dist/tsup/{chunk-TFACXNOP.cjs → chunk-W5MTJGSD.cjs} +121 -10
- package/dist/tsup/chunk-W5MTJGSD.cjs.map +1 -0
- package/dist/tsup/{chunk-JTHHCZCZ.js → chunk-WCEJTJHO.js} +34 -1
- package/dist/tsup/chunk-WCEJTJHO.js.map +1 -0
- package/dist/tsup/{chunk-7ZPRFCTV.cjs → chunk-X5N4RMK4.cjs} +156 -144
- package/dist/tsup/chunk-X5N4RMK4.cjs.map +1 -0
- package/dist/tsup/{chunk-LHT3TRDM.js → chunk-XGCQ4JOX.js} +115 -4
- package/dist/tsup/chunk-XGCQ4JOX.js.map +1 -0
- package/dist/tsup/{chunk-KVFIONKQ.cjs → chunk-XMNL3AE2.cjs} +35 -2
- package/dist/tsup/chunk-XMNL3AE2.cjs.map +1 -0
- package/dist/tsup/{chunk-ZYHZOZUQ.js → chunk-XW3HQQDX.js} +2 -2
- package/dist/tsup/{chunk-XNLSA7TO.cjs → chunk-Y4YVQXM7.cjs} +4 -4
- package/dist/tsup/{chunk-XNLSA7TO.cjs.map → chunk-Y4YVQXM7.cjs.map} +1 -1
- package/dist/tsup/{chunk-2TAUHMAC.cjs → chunk-YHTH6K2K.cjs} +2 -2
- package/dist/tsup/chunk-YHTH6K2K.cjs.map +1 -0
- package/dist/tsup/client/mod.cjs +6 -6
- package/dist/tsup/client/mod.d.cts +3 -3
- package/dist/tsup/client/mod.d.ts +3 -3
- package/dist/tsup/client/mod.js +5 -5
- package/dist/tsup/common/log.cjs +2 -2
- package/dist/tsup/common/log.js +1 -1
- package/dist/tsup/common/websocket.cjs +3 -3
- package/dist/tsup/common/websocket.js +2 -2
- package/dist/tsup/{config-ey2I2zz5.d.cts → config-B0q37XhG.d.cts} +31 -1
- package/dist/tsup/{config-ey2I2zz5.d.ts → config-B0q37XhG.d.ts} +31 -1
- package/dist/tsup/{config-BGTanB_3.d.ts → config-BPJQ0mDo.d.ts} +9 -1
- package/dist/tsup/{config-NHz6oMb9.d.cts → config-CGUJyT5L.d.cts} +9 -1
- package/dist/tsup/{context-SHomdVV8.d.ts → context-DZ_lu12i.d.ts} +2 -2
- package/dist/tsup/{context-DB8zG_Ib.d.cts → context-WLBsDXrQ.d.cts} +2 -2
- package/dist/tsup/db/drizzle.cjs +113 -10
- package/dist/tsup/db/drizzle.cjs.map +1 -1
- package/dist/tsup/db/drizzle.d.cts +2 -2
- package/dist/tsup/db/drizzle.d.ts +2 -2
- package/dist/tsup/db/drizzle.js +106 -3
- package/dist/tsup/db/drizzle.js.map +1 -1
- package/dist/tsup/db/mod.cjs +4 -4
- package/dist/tsup/db/mod.d.cts +4 -4
- package/dist/tsup/db/mod.d.ts +4 -4
- package/dist/tsup/db/mod.js +3 -3
- package/dist/tsup/dynamic/mod.d.cts +2 -2
- package/dist/tsup/dynamic/mod.d.ts +2 -2
- package/dist/tsup/inspector/mod.cjs +5 -5
- package/dist/tsup/inspector/mod.js +4 -4
- package/dist/tsup/inspector/workflow.cjs +4 -4
- package/dist/tsup/inspector/workflow.js +3 -3
- package/dist/tsup/inspector-tab/mod.d.cts +3 -3
- package/dist/tsup/inspector-tab/mod.d.ts +3 -3
- package/dist/tsup/mod.cjs +296 -88
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +4 -4
- package/dist/tsup/mod.d.ts +4 -4
- package/dist/tsup/mod.js +225 -17
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +7 -7
- package/dist/tsup/test/mod.d.cts +2 -2
- package/dist/tsup/test/mod.d.ts +2 -2
- package/dist/tsup/test/mod.js +5 -5
- package/dist/tsup/unstable/migrations.d.cts +1 -1
- package/dist/tsup/unstable/migrations.d.ts +1 -1
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/dist/tsup/workflow/mod.cjs +7 -7
- package/dist/tsup/workflow/mod.d.cts +4 -4
- package/dist/tsup/workflow/mod.d.ts +4 -4
- package/dist/tsup/workflow/mod.js +4 -4
- package/package.json +8 -8
- package/src/agent-os/actor/index.ts +6 -3
- package/src/agent-os/actor/preview.ts +5 -2
- package/src/client/actor-handle.ts +15 -8
- package/src/client/raw-utils.ts +14 -0
- package/src/common/database/config.ts +54 -0
- package/src/common/database/mod.test.ts +162 -1
- package/src/common/database/mod.ts +154 -6
- package/src/common/database/native-database.test.ts +81 -2
- package/src/common/database/native-database.ts +172 -3
- package/src/common/database/shared.ts +49 -1
- package/src/db/drizzle.test.ts +82 -1
- package/src/db/drizzle.ts +136 -6
- package/src/db/mod.ts +2 -0
- package/src/registry/napi-runtime.ts +71 -0
- package/src/registry/native.ts +48 -14
- package/src/registry/runtime.ts +46 -0
- package/src/registry/wasm-runtime.ts +63 -0
- package/dist/tsup/chunk-2TAUHMAC.cjs.map +0 -1
- package/dist/tsup/chunk-7ZPRFCTV.cjs.map +0 -1
- package/dist/tsup/chunk-GPD77M3M.js.map +0 -1
- package/dist/tsup/chunk-JTHHCZCZ.js.map +0 -1
- package/dist/tsup/chunk-KVFIONKQ.cjs.map +0 -1
- package/dist/tsup/chunk-LHT3TRDM.js.map +0 -1
- package/dist/tsup/chunk-OWSXJF7Z.js.map +0 -1
- package/dist/tsup/chunk-TFACXNOP.cjs.map +0 -1
- /package/dist/tsup/{chunk-XPT7SGBW.js.map → chunk-6QHFQDW4.js.map} +0 -0
- /package/dist/tsup/{chunk-M4RSGECU.js.map → chunk-7XKSZBR7.js.map} +0 -0
- /package/dist/tsup/{chunk-YHYNYVNZ.js.map → chunk-CV5KMH2U.js.map} +0 -0
- /package/dist/tsup/{chunk-L6DNN72S.js.map → chunk-GE2H2DHD.js.map} +0 -0
- /package/dist/tsup/{chunk-ZYHZOZUQ.js.map → chunk-XW3HQQDX.js.map} +0 -0
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
SqliteDatabase,
|
|
8
8
|
SqliteExecuteResult,
|
|
9
9
|
SqliteTransactionDatabase,
|
|
10
|
+
SynchronousSqliteTransactionDatabase,
|
|
10
11
|
} from "./config";
|
|
11
12
|
import { db, registerNativeStateTransactionOpener } from "./mod";
|
|
12
13
|
|
|
@@ -19,7 +20,20 @@ class FakeSqliteDatabase implements SqliteDatabase {
|
|
|
19
20
|
transactionTimeouts: Array<number | undefined> = [];
|
|
20
21
|
transactionNames: Array<string | undefined> = [];
|
|
21
22
|
|
|
22
|
-
async exec(
|
|
23
|
+
async exec(
|
|
24
|
+
sql: string,
|
|
25
|
+
callback?: (row: unknown[], columns: string[]) => void,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
this.execSync(sql, callback);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
execSync(
|
|
31
|
+
sql: string,
|
|
32
|
+
callback?: (row: unknown[], columns: string[]) => void,
|
|
33
|
+
): void {
|
|
34
|
+
this.record(sql);
|
|
35
|
+
callback?.([1], ["value"]);
|
|
36
|
+
}
|
|
23
37
|
|
|
24
38
|
async execute(
|
|
25
39
|
sql: string,
|
|
@@ -29,21 +43,40 @@ class FakeSqliteDatabase implements SqliteDatabase {
|
|
|
29
43
|
return emptyResult();
|
|
30
44
|
}
|
|
31
45
|
|
|
46
|
+
executeSync(sql: string, params?: SqliteBindings): SqliteExecuteResult {
|
|
47
|
+
this.record(sql, params);
|
|
48
|
+
return emptyResult();
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
async beginTransaction(
|
|
33
52
|
timeoutMs?: number,
|
|
34
53
|
name?: string,
|
|
35
54
|
): Promise<SqliteTransactionDatabase> {
|
|
55
|
+
return this.beginTransactionSync(timeoutMs, name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
beginTransactionSync(
|
|
59
|
+
timeoutMs?: number,
|
|
60
|
+
name?: string,
|
|
61
|
+
): SynchronousSqliteTransactionDatabase {
|
|
36
62
|
this.transactionTimeouts.push(timeoutMs);
|
|
37
63
|
this.transactionNames.push(name);
|
|
38
64
|
this.record("BEGIN");
|
|
39
65
|
return {
|
|
40
66
|
exec: async () => {},
|
|
67
|
+
execSync: () => {},
|
|
41
68
|
execute: async (sql, params) => {
|
|
42
69
|
this.record(sql, params);
|
|
43
70
|
return emptyResult();
|
|
44
71
|
},
|
|
72
|
+
executeSync: (sql, params) => {
|
|
73
|
+
this.record(sql, params);
|
|
74
|
+
return emptyResult();
|
|
75
|
+
},
|
|
45
76
|
commit: async () => this.record("COMMIT"),
|
|
77
|
+
commitSync: () => this.record("COMMIT"),
|
|
46
78
|
rollback: async () => this.record("ROLLBACK"),
|
|
79
|
+
rollbackSync: () => this.record("ROLLBACK"),
|
|
47
80
|
};
|
|
48
81
|
}
|
|
49
82
|
async beginStateTransaction(
|
|
@@ -53,10 +86,15 @@ class FakeSqliteDatabase implements SqliteDatabase {
|
|
|
53
86
|
this.record("BEGIN_STATE");
|
|
54
87
|
return {
|
|
55
88
|
exec: async () => {},
|
|
89
|
+
execSync: () => {},
|
|
56
90
|
execute: async (sql, params) => {
|
|
57
91
|
this.record(sql, params);
|
|
58
92
|
return emptyResult();
|
|
59
93
|
},
|
|
94
|
+
executeSync: (sql, params) => {
|
|
95
|
+
this.record(sql, params);
|
|
96
|
+
return emptyResult();
|
|
97
|
+
},
|
|
60
98
|
commit: async () => this.record("COMMIT"),
|
|
61
99
|
rollback: async () => this.record("ROLLBACK"),
|
|
62
100
|
};
|
|
@@ -173,6 +211,129 @@ describe("db", () => {
|
|
|
173
211
|
});
|
|
174
212
|
});
|
|
175
213
|
|
|
214
|
+
test("exposes synchronous raw queries on the built-in client", async () => {
|
|
215
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
216
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
217
|
+
|
|
218
|
+
client.executeSync("SELECT ?", 42);
|
|
219
|
+
expect(
|
|
220
|
+
client.executeSync<{ value: number }>("SELECT 1; SELECT 2"),
|
|
221
|
+
).toEqual([{ value: 1 }]);
|
|
222
|
+
|
|
223
|
+
expect(nativeDb.executeCalls).toEqual([
|
|
224
|
+
{
|
|
225
|
+
sql: "SELECT ?",
|
|
226
|
+
params: [42],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
sql: "SELECT 1; SELECT 2",
|
|
230
|
+
params: undefined,
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("commits synchronous transaction work before returning", async () => {
|
|
236
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
237
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
238
|
+
|
|
239
|
+
const value = client.transactionSync(
|
|
240
|
+
(tx) => {
|
|
241
|
+
tx.executeSync("INSERT INTO items(value) VALUES (?)", "inside");
|
|
242
|
+
return 42;
|
|
243
|
+
},
|
|
244
|
+
{ name: "sync-insert", timeout: 120_000 },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(value).toBe(42);
|
|
248
|
+
expect(nativeDb.transactionTimeouts).toEqual([120_000]);
|
|
249
|
+
expect(nativeDb.transactionNames).toEqual(["sync-insert"]);
|
|
250
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
251
|
+
"BEGIN",
|
|
252
|
+
"INSERT INTO items(value) VALUES (?)",
|
|
253
|
+
"COMMIT",
|
|
254
|
+
]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("rolls back synchronous transactions on callback and commit errors", async () => {
|
|
258
|
+
const callbackDb = new FakeSqliteDatabase();
|
|
259
|
+
const callbackClient = await db().createClient(
|
|
260
|
+
testProviderContext(callbackDb),
|
|
261
|
+
);
|
|
262
|
+
expect(() =>
|
|
263
|
+
callbackClient.transactionSync(() => {
|
|
264
|
+
throw new Error("callback failed");
|
|
265
|
+
}),
|
|
266
|
+
).toThrow("callback failed");
|
|
267
|
+
expect(callbackDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
268
|
+
"BEGIN",
|
|
269
|
+
"ROLLBACK",
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
const commitDb = new FakeSqliteDatabase();
|
|
273
|
+
commitDb.failSql.set("COMMIT", new Error("commit failed"));
|
|
274
|
+
const commitClient = await db().createClient(
|
|
275
|
+
testProviderContext(commitDb),
|
|
276
|
+
);
|
|
277
|
+
expect(() => commitClient.transactionSync(() => 1)).toThrow(
|
|
278
|
+
"commit failed",
|
|
279
|
+
);
|
|
280
|
+
expect(commitDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
281
|
+
"BEGIN",
|
|
282
|
+
"COMMIT",
|
|
283
|
+
"ROLLBACK",
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("rejects async synchronous-transaction callbacks and outer-client queries", async () => {
|
|
288
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
289
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
290
|
+
|
|
291
|
+
expect(() => client.transactionSync(async () => undefined)).toThrow(
|
|
292
|
+
"must not return a promise",
|
|
293
|
+
);
|
|
294
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
295
|
+
"BEGIN",
|
|
296
|
+
"ROLLBACK",
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
nativeDb.executeCalls = [];
|
|
300
|
+
let outerQuery: Promise<Record<string, unknown>[]> | undefined;
|
|
301
|
+
client.transactionSync((tx) => {
|
|
302
|
+
expect(Object.keys(tx)).toEqual(["executeSync"]);
|
|
303
|
+
expect(() => client.executeSync("SELECT 1")).toThrow(
|
|
304
|
+
"transaction callback's tx value",
|
|
305
|
+
);
|
|
306
|
+
outerQuery = client.execute("SELECT 1");
|
|
307
|
+
expect(() => client.transactionSync(() => undefined)).toThrow(
|
|
308
|
+
"Nested synchronous SQLite transactions",
|
|
309
|
+
);
|
|
310
|
+
tx.executeSync("SELECT 2");
|
|
311
|
+
});
|
|
312
|
+
await expect(outerQuery).rejects.toThrow(
|
|
313
|
+
"transaction callback's tx value",
|
|
314
|
+
);
|
|
315
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
316
|
+
"BEGIN",
|
|
317
|
+
"SELECT 2",
|
|
318
|
+
"COMMIT",
|
|
319
|
+
]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("validates synchronous transaction options before beginning", async () => {
|
|
323
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
324
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
325
|
+
|
|
326
|
+
for (const timeout of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
327
|
+
expect(() =>
|
|
328
|
+
client.transactionSync(() => undefined, { timeout }),
|
|
329
|
+
).toThrow("positive finite");
|
|
330
|
+
}
|
|
331
|
+
expect(() =>
|
|
332
|
+
client.transactionSync(() => undefined, { name: "" }),
|
|
333
|
+
).toThrow("must not be empty");
|
|
334
|
+
expect(nativeDb.executeCalls).toEqual([]);
|
|
335
|
+
});
|
|
336
|
+
|
|
176
337
|
test("rolls back migrations when onMigrate fails", async () => {
|
|
177
338
|
const nativeDb = new FakeSqliteDatabase();
|
|
178
339
|
const provider = db({
|
|
@@ -7,17 +7,20 @@ import type {
|
|
|
7
7
|
SqliteProfilingOptions,
|
|
8
8
|
SqliteTransactionDatabase,
|
|
9
9
|
SqliteTransactionOptions,
|
|
10
|
+
SynchronousRawAccess,
|
|
11
|
+
SynchronousTransactionAccess,
|
|
10
12
|
} from "./config";
|
|
11
13
|
import {
|
|
12
14
|
isManualTransactionControl,
|
|
13
15
|
isSqliteBindingObject,
|
|
14
16
|
MIGRATION_TRANSACTION_TIMEOUT_MS,
|
|
17
|
+
runSqliteTransactionSync,
|
|
15
18
|
toSqliteBindings,
|
|
16
19
|
validateTransactionName,
|
|
17
20
|
validateTransactionTimeout,
|
|
18
21
|
} from "./shared";
|
|
19
22
|
|
|
20
|
-
export type { RawAccess } from "./config";
|
|
23
|
+
export type { RawAccess, SynchronousRawAccess } from "./config";
|
|
21
24
|
|
|
22
25
|
export interface DatabaseFactoryConfig {
|
|
23
26
|
onMigrate?: (db: RawAccess) => Promise<void> | void;
|
|
@@ -84,8 +87,8 @@ export function db({
|
|
|
84
87
|
onMigrate,
|
|
85
88
|
warnOnManualTransactions = true,
|
|
86
89
|
profiling,
|
|
87
|
-
}: DatabaseFactoryConfig = {}): DatabaseProvider<
|
|
88
|
-
const provider: DatabaseProvider<
|
|
90
|
+
}: DatabaseFactoryConfig = {}): DatabaseProvider<SynchronousRawAccess> {
|
|
91
|
+
const provider: DatabaseProvider<SynchronousRawAccess> = {
|
|
89
92
|
sqliteProfiling: profiling,
|
|
90
93
|
createClient: async (ctx) => {
|
|
91
94
|
const nativeDatabaseProvider = ctx.nativeDatabaseProvider;
|
|
@@ -98,6 +101,7 @@ export function db({
|
|
|
98
101
|
const db = await nativeDatabaseProvider.open(ctx.actorId);
|
|
99
102
|
let closed = false;
|
|
100
103
|
let manualTransactionWarned = false;
|
|
104
|
+
let synchronousTransactionActive = false;
|
|
101
105
|
const ensureOpen = () => {
|
|
102
106
|
if (closed) {
|
|
103
107
|
throw new Error(
|
|
@@ -105,13 +109,22 @@ export function db({
|
|
|
105
109
|
);
|
|
106
110
|
}
|
|
107
111
|
};
|
|
112
|
+
const ensureSynchronousTransactionClient = (
|
|
113
|
+
transactionScoped: boolean,
|
|
114
|
+
) => {
|
|
115
|
+
if (!transactionScoped && synchronousTransactionActive) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Use the transaction callback's tx value for queries inside db.transactionSync().",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
108
121
|
|
|
109
122
|
const createClient = (
|
|
110
123
|
target: SqliteDatabase | SqliteTransactionDatabase,
|
|
111
124
|
transactionScoped = false,
|
|
112
125
|
stateTransactionContext?: NativeStateTransactionContext,
|
|
113
|
-
):
|
|
114
|
-
const client:
|
|
126
|
+
): SynchronousRawAccess => {
|
|
127
|
+
const client: SynchronousRawAccess = {
|
|
115
128
|
execute: async <
|
|
116
129
|
TRow extends Record<string, unknown> = Record<
|
|
117
130
|
string,
|
|
@@ -122,6 +135,7 @@ export function db({
|
|
|
122
135
|
...args: unknown[]
|
|
123
136
|
): Promise<TRow[]> => {
|
|
124
137
|
ensureOpen();
|
|
138
|
+
ensureSynchronousTransactionClient(transactionScoped);
|
|
125
139
|
if (
|
|
126
140
|
!transactionScoped &&
|
|
127
141
|
warnOnManualTransactions &&
|
|
@@ -132,7 +146,7 @@ export function db({
|
|
|
132
146
|
manualTransactionWarned = true;
|
|
133
147
|
getLogger("database").warn(
|
|
134
148
|
{ actorId: ctx.actorId },
|
|
135
|
-
"Manual cross-call SQLite transactions can interleave with other actor work. Use db.transaction() for coordinated transactions. Set warnOnManualTransactions: false in your db(...) configuration to disable this warning.",
|
|
149
|
+
"Manual cross-call SQLite transactions can interleave with other actor work. Use db.transaction() or db.transactionSync() for coordinated transactions. Set warnOnManualTransactions: false in your db(...) configuration to disable this warning.",
|
|
136
150
|
);
|
|
137
151
|
}
|
|
138
152
|
|
|
@@ -188,10 +202,91 @@ export function db({
|
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
204
|
},
|
|
205
|
+
executeSync: <
|
|
206
|
+
TRow extends Record<string, unknown> = Record<
|
|
207
|
+
string,
|
|
208
|
+
unknown
|
|
209
|
+
>,
|
|
210
|
+
>(
|
|
211
|
+
query: string,
|
|
212
|
+
...args: unknown[]
|
|
213
|
+
): TRow[] => {
|
|
214
|
+
ensureOpen();
|
|
215
|
+
ensureSynchronousTransactionClient(transactionScoped);
|
|
216
|
+
if (!target.executeSync) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"Synchronous SQLite queries are only available in the Node.js native runtime.",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
!transactionScoped &&
|
|
223
|
+
warnOnManualTransactions &&
|
|
224
|
+
!manualTransactionWarned &&
|
|
225
|
+
!hasMultipleStatements(query) &&
|
|
226
|
+
isManualTransactionControl(query)
|
|
227
|
+
) {
|
|
228
|
+
manualTransactionWarned = true;
|
|
229
|
+
getLogger("database").warn(
|
|
230
|
+
{ actorId: ctx.actorId },
|
|
231
|
+
"Manual cross-call SQLite transactions can interleave with other actor work. Use db.transaction() or db.transactionSync() for coordinated transactions. Set warnOnManualTransactions: false in your db(...) configuration to disable this warning.",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const kvReadsBefore = ctx.metrics?.totalKvReads ?? 0;
|
|
236
|
+
const kvWritesBefore = ctx.metrics?.totalKvWrites ?? 0;
|
|
237
|
+
const start = performance.now();
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
if (args.length > 0) {
|
|
241
|
+
const bindings =
|
|
242
|
+
args.length === 1 &&
|
|
243
|
+
isSqliteBindingObject(args[0])
|
|
244
|
+
? toSqliteBindings(args[0])
|
|
245
|
+
: toSqliteBindings(args);
|
|
246
|
+
const { rows, columns } = target.executeSync(
|
|
247
|
+
query,
|
|
248
|
+
bindings,
|
|
249
|
+
);
|
|
250
|
+
return rows.map((row) =>
|
|
251
|
+
rowToObject<TRow>(row, columns),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!hasMultipleStatements(query)) {
|
|
256
|
+
const { rows, columns } = target.executeSync(
|
|
257
|
+
query,
|
|
258
|
+
undefined,
|
|
259
|
+
);
|
|
260
|
+
return rows.map((row) =>
|
|
261
|
+
rowToObject<TRow>(row, columns),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return execMultiStatementSync<TRow>(target, query);
|
|
266
|
+
} finally {
|
|
267
|
+
const durationMs = performance.now() - start;
|
|
268
|
+
ctx.metrics?.trackSql(query, durationMs);
|
|
269
|
+
if (ctx.metrics) {
|
|
270
|
+
const kvReads =
|
|
271
|
+
ctx.metrics.totalKvReads - kvReadsBefore;
|
|
272
|
+
const kvWrites =
|
|
273
|
+
ctx.metrics.totalKvWrites - kvWritesBefore;
|
|
274
|
+
ctx.log?.debug({
|
|
275
|
+
msg: "sql query",
|
|
276
|
+
query: query.slice(0, 120),
|
|
277
|
+
durationMs,
|
|
278
|
+
kvReads,
|
|
279
|
+
kvWrites,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
191
284
|
transaction: async <T>(
|
|
192
285
|
callback: (tx: RawAccess) => Promise<T> | T,
|
|
193
286
|
options?: SqliteTransactionOptions,
|
|
194
287
|
): Promise<T> => {
|
|
288
|
+
ensureOpen();
|
|
289
|
+
ensureSynchronousTransactionClient(transactionScoped);
|
|
195
290
|
validateTransactionTimeout(options?.timeout);
|
|
196
291
|
validateTransactionName(options?.name);
|
|
197
292
|
if (
|
|
@@ -249,6 +344,39 @@ export function db({
|
|
|
249
344
|
}
|
|
250
345
|
}
|
|
251
346
|
},
|
|
347
|
+
transactionSync: <T>(
|
|
348
|
+
callback: (tx: SynchronousTransactionAccess) => T,
|
|
349
|
+
options?: Omit<
|
|
350
|
+
SqliteTransactionOptions,
|
|
351
|
+
"experimental"
|
|
352
|
+
>,
|
|
353
|
+
): T => {
|
|
354
|
+
ensureOpen();
|
|
355
|
+
if (transactionScoped || synchronousTransactionActive) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
"Nested synchronous SQLite transactions are not supported.",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return runSqliteTransactionSync(
|
|
361
|
+
db,
|
|
362
|
+
(transaction) => {
|
|
363
|
+
const transactionClient = createClient(
|
|
364
|
+
transaction,
|
|
365
|
+
true,
|
|
366
|
+
);
|
|
367
|
+
const tx: SynchronousTransactionAccess = {
|
|
368
|
+
executeSync: transactionClient.executeSync,
|
|
369
|
+
};
|
|
370
|
+
synchronousTransactionActive = true;
|
|
371
|
+
try {
|
|
372
|
+
return callback(tx);
|
|
373
|
+
} finally {
|
|
374
|
+
synchronousTransactionActive = false;
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
options,
|
|
378
|
+
);
|
|
379
|
+
},
|
|
252
380
|
close: async () => {
|
|
253
381
|
if (!closed) {
|
|
254
382
|
closed = true;
|
|
@@ -304,6 +432,26 @@ async function execMultiStatement<TRow extends Record<string, unknown>>(
|
|
|
304
432
|
return results as TRow[];
|
|
305
433
|
}
|
|
306
434
|
|
|
435
|
+
function execMultiStatementSync<TRow extends Record<string, unknown>>(
|
|
436
|
+
db: Pick<SqliteDatabase, "execSync">,
|
|
437
|
+
query: string,
|
|
438
|
+
): TRow[] {
|
|
439
|
+
if (!db.execSync) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"Synchronous SQLite queries are only available in the Node.js native runtime.",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
const results: Record<string, unknown>[] = [];
|
|
445
|
+
let columnNames: string[] | null = null;
|
|
446
|
+
db.execSync(query, (row: unknown[], columns: string[]) => {
|
|
447
|
+
if (!columnNames) {
|
|
448
|
+
columnNames = columns;
|
|
449
|
+
}
|
|
450
|
+
results.push(rowToObject(row, columnNames));
|
|
451
|
+
});
|
|
452
|
+
return results as TRow[];
|
|
453
|
+
}
|
|
454
|
+
|
|
307
455
|
async function withMigrationSavepoint<T>(
|
|
308
456
|
client: RawAccess,
|
|
309
457
|
callback: (leased: RawAccess) => Promise<T> | T,
|
|
@@ -20,28 +20,66 @@ function deferred<T>() {
|
|
|
20
20
|
|
|
21
21
|
class FakeNativeDatabase implements JsNativeDatabaseLike {
|
|
22
22
|
async beginTransaction() {
|
|
23
|
+
this.transactionEvents.push("BEGIN");
|
|
24
|
+
return this.#transaction();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
beginTransactionSync() {
|
|
28
|
+
this.transactionEvents.push("BEGIN_SYNC");
|
|
29
|
+
return this.#transaction();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#transaction() {
|
|
23
33
|
return {
|
|
24
34
|
exec: async (_sql: string) => this.exec(),
|
|
35
|
+
execSync: (_sql: string) => this.execSync(),
|
|
25
36
|
execute: async (sql: string, params?: NativeParams) =>
|
|
26
37
|
this.execute(sql, params),
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
executeSync: (sql: string, params?: NativeParams) =>
|
|
39
|
+
this.executeSync(sql, params),
|
|
40
|
+
commit: async () => {
|
|
41
|
+
this.transactionEvents.push("COMMIT");
|
|
42
|
+
},
|
|
43
|
+
commitSync: () => {
|
|
44
|
+
this.transactionEvents.push("COMMIT_SYNC");
|
|
45
|
+
},
|
|
46
|
+
rollback: async () => {
|
|
47
|
+
this.transactionEvents.push("ROLLBACK");
|
|
48
|
+
},
|
|
49
|
+
rollbackSync: () => {
|
|
50
|
+
this.transactionEvents.push("ROLLBACK_SYNC");
|
|
51
|
+
},
|
|
29
52
|
};
|
|
30
53
|
}
|
|
31
54
|
active = 0;
|
|
32
55
|
maxActive = 0;
|
|
33
56
|
closed = false;
|
|
34
57
|
executeCalls: { sql: string; params?: NativeParams; write: boolean }[] = [];
|
|
58
|
+
transactionEvents: string[] = [];
|
|
35
59
|
#pending: ReturnType<typeof deferred<NativeExecuteResult>>[] = [];
|
|
36
60
|
|
|
37
61
|
async exec() {
|
|
38
62
|
return { columns: [], rows: [] };
|
|
39
63
|
}
|
|
40
64
|
|
|
65
|
+
execSync() {
|
|
66
|
+
return { columns: ["value"], rows: [[1], [2]] };
|
|
67
|
+
}
|
|
68
|
+
|
|
41
69
|
async execute(sql: string, params?: NativeParams) {
|
|
42
70
|
return await this.#startExecute(sql, params, false);
|
|
43
71
|
}
|
|
44
72
|
|
|
73
|
+
executeSync(sql: string, params?: NativeParams): NativeExecuteResult {
|
|
74
|
+
this.executeCalls.push({ sql, params, write: false });
|
|
75
|
+
return {
|
|
76
|
+
columns: ["value"],
|
|
77
|
+
rows: [[1]],
|
|
78
|
+
changes: 0,
|
|
79
|
+
lastInsertRowId: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
async query(sql: string, params?: NativeParams) {
|
|
46
84
|
const { columns, rows } = await this.execute(sql, params);
|
|
47
85
|
return { columns, rows };
|
|
@@ -155,6 +193,47 @@ describe("wrapJsNativeDatabase", () => {
|
|
|
155
193
|
});
|
|
156
194
|
});
|
|
157
195
|
|
|
196
|
+
test("executes synchronously with normalized bindings", () => {
|
|
197
|
+
const native = new FakeNativeDatabase();
|
|
198
|
+
const db = wrapJsNativeDatabase(native);
|
|
199
|
+
|
|
200
|
+
const result = db.executeSync?.("SELECT ?, ?", [true, "text"]);
|
|
201
|
+
const execRows: unknown[][] = [];
|
|
202
|
+
db.execSync?.("SELECT 1; SELECT 2", (row) => execRows.push(row));
|
|
203
|
+
|
|
204
|
+
expect(native.executeCalls[0]?.params).toEqual([
|
|
205
|
+
{ kind: "int", intValue: 1 },
|
|
206
|
+
{ kind: "text", textValue: "text" },
|
|
207
|
+
]);
|
|
208
|
+
expect(result).toMatchObject({
|
|
209
|
+
columns: ["value"],
|
|
210
|
+
rows: [[1]],
|
|
211
|
+
});
|
|
212
|
+
expect(execRows).toEqual([[1], [2]]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("wraps synchronous transaction lifecycle methods", () => {
|
|
216
|
+
const native = new FakeNativeDatabase();
|
|
217
|
+
const db = wrapJsNativeDatabase(native);
|
|
218
|
+
|
|
219
|
+
const committed = db.beginTransactionSync?.(1_000, "commit");
|
|
220
|
+
if (!committed)
|
|
221
|
+
throw new Error("missing synchronous transaction support");
|
|
222
|
+
committed.executeSync("SELECT 1");
|
|
223
|
+
committed.commitSync();
|
|
224
|
+
const rolledBack = db.beginTransactionSync?.(1_000, "rollback");
|
|
225
|
+
if (!rolledBack)
|
|
226
|
+
throw new Error("missing synchronous transaction support");
|
|
227
|
+
rolledBack.rollbackSync();
|
|
228
|
+
|
|
229
|
+
expect(native.transactionEvents).toEqual([
|
|
230
|
+
"BEGIN_SYNC",
|
|
231
|
+
"COMMIT_SYNC",
|
|
232
|
+
"BEGIN_SYNC",
|
|
233
|
+
"ROLLBACK_SYNC",
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
|
|
158
237
|
test("returns native execute metadata", async () => {
|
|
159
238
|
const native = new FakeNativeDatabase();
|
|
160
239
|
const db = wrapJsNativeDatabase(native);
|