rivetkit 2.3.4 → 2.3.5-rc.1
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 +157 -9
- package/dist/browser/client.js +518 -542
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.d.ts +101 -2
- package/dist/browser/inspector/client.js +1245 -227
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/agent-os/index.cjs +103 -36
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +159 -11
- package/dist/tsup/agent-os/index.d.ts +159 -11
- package/dist/tsup/agent-os/index.js +103 -36
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-7X4M7ZRA.cjs → chunk-4XKED5P5.cjs} +146 -177
- package/dist/tsup/chunk-4XKED5P5.cjs.map +1 -0
- package/dist/tsup/{chunk-Q7NBMTF3.cjs → chunk-6HQCLWAW.cjs} +384 -4
- package/dist/tsup/chunk-6HQCLWAW.cjs.map +1 -0
- package/dist/tsup/{chunk-JZ7TWV65.js → chunk-7AFZMFPQ.js} +15 -1
- package/dist/tsup/chunk-7AFZMFPQ.js.map +1 -0
- package/dist/tsup/{chunk-M7DCRMOZ.js → chunk-B4IZU537.js} +382 -2
- package/dist/tsup/chunk-B4IZU537.js.map +1 -0
- package/dist/tsup/{chunk-3MHDOUD7.cjs → chunk-G3CCRCTK.cjs} +14 -9
- package/dist/tsup/chunk-G3CCRCTK.cjs.map +1 -0
- package/dist/tsup/{chunk-CMPFZ724.js → chunk-GAV4PKFT.js} +16 -47
- package/dist/tsup/{chunk-CMPFZ724.js.map → chunk-GAV4PKFT.js.map} +1 -1
- package/dist/tsup/{chunk-HXUEHHJF.js → chunk-JI6GZ2C2.js} +2 -11
- package/dist/tsup/{chunk-HXUEHHJF.js.map → chunk-JI6GZ2C2.js.map} +1 -1
- package/dist/tsup/{chunk-3SNFA373.cjs → chunk-M3FCXL3Q.cjs} +3 -3
- package/dist/tsup/{chunk-3SNFA373.cjs.map → chunk-M3FCXL3Q.cjs.map} +1 -1
- package/dist/tsup/{chunk-GXW3VFJQ.js → chunk-MGTSVSUC.js} +2 -2
- package/dist/tsup/{chunk-VE2X4KMG.cjs → chunk-MYWZCDML.cjs} +2 -11
- package/dist/tsup/chunk-MYWZCDML.cjs.map +1 -0
- package/dist/tsup/{chunk-M5TAM4JV.js → chunk-NIC3QLKL.js} +3 -3
- package/dist/tsup/{chunk-H4XOUVGP.js → chunk-P4OGQ4JL.js} +5 -7
- package/dist/tsup/{chunk-H4XOUVGP.js.map → chunk-P4OGQ4JL.js.map} +1 -1
- package/dist/tsup/{chunk-EOSGY3DC.js → chunk-PAG6G46L.js} +2 -2
- package/dist/tsup/{chunk-XIX5DOZN.js → chunk-SHBE77IT.js} +14 -9
- package/dist/tsup/chunk-SHBE77IT.js.map +1 -0
- package/dist/tsup/{chunk-SRT2DPN3.cjs → chunk-TNIWIUNV.cjs} +9 -11
- package/dist/tsup/chunk-TNIWIUNV.cjs.map +1 -0
- package/dist/tsup/{chunk-UPOCJPEQ.cjs → chunk-TR5NLR5V.cjs} +4 -4
- package/dist/tsup/{chunk-UPOCJPEQ.cjs.map → chunk-TR5NLR5V.cjs.map} +1 -1
- package/dist/tsup/{chunk-ZA7FLHKH.cjs → chunk-UEULFXUI.cjs} +16 -2
- package/dist/tsup/chunk-UEULFXUI.cjs.map +1 -0
- package/dist/tsup/{chunk-T4MURX4P.cjs → chunk-UJ2FQY63.cjs} +7 -7
- package/dist/tsup/{chunk-T4MURX4P.cjs.map → chunk-UJ2FQY63.cjs.map} +1 -1
- package/dist/tsup/client/mod.cjs +7 -8
- package/dist/tsup/client/mod.cjs.map +1 -1
- package/dist/tsup/client/mod.d.cts +5 -4
- package/dist/tsup/client/mod.d.ts +5 -4
- package/dist/tsup/client/mod.js +6 -7
- 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-D49x8NpL.d.cts → config-BxOimoGX.d.cts} +16 -0
- package/dist/tsup/{config-D49x8NpL.d.ts → config-BxOimoGX.d.ts} +16 -0
- package/dist/tsup/{config-ZkIsRFsN.d.ts → config-BzdqbLAP.d.ts} +164 -27
- package/dist/tsup/{config-BfBhjEbk.d.cts → config-CPPEwn_e.d.cts} +164 -27
- package/dist/tsup/{context-sZ9GlaBK.d.cts → context-DSA9gFXB.d.cts} +2 -2
- package/dist/tsup/{context-B7-84u9K.d.ts → context-wWv-K2K_.d.ts} +2 -2
- package/dist/tsup/db/drizzle.cjs +120 -68
- package/dist/tsup/db/drizzle.cjs.map +1 -1
- package/dist/tsup/db/drizzle.d.cts +8 -3
- package/dist/tsup/db/drizzle.d.ts +8 -3
- package/dist/tsup/db/drizzle.js +120 -68
- package/dist/tsup/db/drizzle.js.map +1 -1
- package/dist/tsup/db/mod.cjs +74 -22
- package/dist/tsup/db/mod.cjs.map +1 -1
- package/dist/tsup/db/mod.d.cts +4 -3
- package/dist/tsup/db/mod.d.ts +4 -3
- package/dist/tsup/db/mod.js +74 -22
- package/dist/tsup/db/mod.js.map +1 -1
- package/dist/tsup/dynamic/mod.cjs +2 -2
- package/dist/tsup/dynamic/mod.d.cts +3 -3
- package/dist/tsup/dynamic/mod.d.ts +3 -3
- package/dist/tsup/dynamic/mod.js +1 -1
- package/dist/tsup/inspector/mod.cjs +4 -5
- package/dist/tsup/inspector/mod.cjs.map +1 -1
- package/dist/tsup/inspector/mod.js +3 -4
- package/dist/tsup/inspector/mod.js.map +1 -1
- package/dist/tsup/inspector-tab/mod.cjs +2 -2
- package/dist/tsup/inspector-tab/mod.cjs.map +1 -1
- package/dist/tsup/inspector-tab/mod.d.cts +5 -5
- package/dist/tsup/inspector-tab/mod.d.ts +5 -5
- package/dist/tsup/inspector-tab/mod.js +1 -1
- package/dist/tsup/inspector-tab/mod.js.map +1 -1
- package/dist/tsup/mod.cjs +605 -158
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +5 -5
- package/dist/tsup/mod.d.ts +5 -5
- package/dist/tsup/mod.js +528 -81
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +10 -11
- package/dist/tsup/test/mod.cjs.map +1 -1
- package/dist/tsup/test/mod.d.cts +3 -3
- package/dist/tsup/test/mod.d.ts +3 -3
- package/dist/tsup/test/mod.js +6 -7
- package/dist/tsup/test/mod.js.map +1 -1
- package/dist/tsup/utils-CV0akuDD.d.ts +162 -0
- package/dist/tsup/utils-DQDCphrl.d.cts +162 -0
- package/dist/tsup/utils.cjs +6 -2
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +3 -146
- package/dist/tsup/utils.d.ts +3 -146
- package/dist/tsup/utils.js +5 -1
- package/dist/tsup/workflow/mod.cjs +198 -151
- package/dist/tsup/workflow/mod.cjs.map +1 -1
- package/dist/tsup/workflow/mod.d.cts +5 -5
- package/dist/tsup/workflow/mod.d.ts +5 -5
- package/dist/tsup/workflow/mod.js +187 -140
- package/dist/tsup/workflow/mod.js.map +1 -1
- package/package.json +8 -8
- package/src/actor/config.test.ts +14 -0
- package/src/actor/config.ts +104 -16
- package/src/actor/definition.ts +18 -7
- package/src/agent-os/actor/index.ts +2 -2
- package/src/common/bare/generated/actor-runtime-socket-protocol/v1.ts +848 -0
- package/src/common/bare/generated/inspector/v6.ts +1367 -0
- package/src/common/database/config.ts +24 -0
- package/src/common/database/mod.test.ts +148 -59
- package/src/common/database/mod.ts +93 -23
- package/src/common/database/native-database.test.ts +9 -0
- package/src/common/database/native-database.ts +151 -0
- package/src/common/database/shared.ts +15 -0
- package/src/common/encoding.ts +28 -21
- package/src/db/drizzle.test.ts +157 -0
- package/src/db/drizzle.ts +170 -79
- package/src/dynamic/isolate-runtime.ts +0 -2
- package/src/engine-client/driver.ts +0 -15
- package/src/engine-client/mod.ts +0 -41
- package/src/inspector/client.browser.ts +69 -8
- package/src/inspector-tab/mod.ts +1 -0
- package/src/registry/config/index.ts +1 -35
- package/src/registry/napi-runtime.ts +204 -16
- package/src/registry/native.ts +200 -30
- package/src/registry/runtime.ts +146 -3
- package/src/registry/wasm-runtime.ts +217 -6
- package/src/utils.ts +3 -0
- package/src/workflow/context.ts +5 -27
- package/src/workflow/driver.test.ts +62 -0
- package/src/workflow/driver.ts +231 -113
- package/src/workflow/mod.ts +3 -3
- package/dist/tsup/chunk-3MHDOUD7.cjs.map +0 -1
- package/dist/tsup/chunk-7X4M7ZRA.cjs.map +0 -1
- package/dist/tsup/chunk-JZ7TWV65.js.map +0 -1
- package/dist/tsup/chunk-M7DCRMOZ.js.map +0 -1
- package/dist/tsup/chunk-Q7NBMTF3.cjs.map +0 -1
- package/dist/tsup/chunk-SRT2DPN3.cjs.map +0 -1
- package/dist/tsup/chunk-VE2X4KMG.cjs.map +0 -1
- package/dist/tsup/chunk-W6WPKZ6I.js +0 -382
- package/dist/tsup/chunk-W6WPKZ6I.js.map +0 -1
- package/dist/tsup/chunk-XIX5DOZN.js.map +0 -1
- package/dist/tsup/chunk-Z7PSQWMC.cjs +0 -382
- package/dist/tsup/chunk-Z7PSQWMC.cjs.map +0 -1
- package/dist/tsup/chunk-ZA7FLHKH.cjs.map +0 -1
- package/src/actor/driver.ts +0 -141
- package/src/actor/instance/mod.ts +0 -2484
- package/src/drivers/engine/actor-driver.ts +0 -2473
- /package/dist/tsup/{chunk-GXW3VFJQ.js.map → chunk-MGTSVSUC.js.map} +0 -0
- /package/dist/tsup/{chunk-M5TAM4JV.js.map → chunk-NIC3QLKL.js.map} +0 -0
- /package/dist/tsup/{chunk-EOSGY3DC.js.map → chunk-PAG6G46L.js.map} +0 -0
|
@@ -23,6 +23,11 @@ export interface SqliteExecuteResult extends SqliteQueryResult {
|
|
|
23
23
|
lastInsertRowId?: number | null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export interface SqliteBatchStatement {
|
|
27
|
+
sql: string;
|
|
28
|
+
params?: SqliteBindings;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
export interface SqliteNativeMetrics {
|
|
27
32
|
requestBuildNs: number;
|
|
28
33
|
serializeNs: number;
|
|
@@ -43,6 +48,10 @@ export interface SqliteDatabase {
|
|
|
43
48
|
callback?: (row: unknown[], columns: string[]) => void,
|
|
44
49
|
): Promise<void>;
|
|
45
50
|
execute(sql: string, params?: SqliteBindings): Promise<SqliteExecuteResult>;
|
|
51
|
+
executeBatch(
|
|
52
|
+
statements: SqliteBatchStatement[],
|
|
53
|
+
): Promise<SqliteExecuteResult[]>;
|
|
54
|
+
beginTransaction(timeoutMs?: number): Promise<SqliteTransactionDatabase>;
|
|
46
55
|
run(sql: string, params?: SqliteBindings): Promise<void>;
|
|
47
56
|
query(sql: string, params?: SqliteBindings): Promise<SqliteQueryResult>;
|
|
48
57
|
nativeMetrics?():
|
|
@@ -52,6 +61,16 @@ export interface SqliteDatabase {
|
|
|
52
61
|
close(): Promise<void>;
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
export interface SqliteTransactionDatabase {
|
|
65
|
+
exec(
|
|
66
|
+
sql: string,
|
|
67
|
+
callback?: (row: unknown[], columns: string[]) => void,
|
|
68
|
+
): Promise<void>;
|
|
69
|
+
execute(sql: string, params?: SqliteBindings): Promise<SqliteExecuteResult>;
|
|
70
|
+
commit(): Promise<void>;
|
|
71
|
+
rollback(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Provider for opening native databases from the active runtime.
|
|
57
76
|
*/
|
|
@@ -143,6 +162,11 @@ export type RawAccess = {
|
|
|
143
162
|
* Executes a raw SQL query.
|
|
144
163
|
*/
|
|
145
164
|
execute: ExecuteFunction;
|
|
165
|
+
/** Runs a callback in an isolated SQLite transaction. */
|
|
166
|
+
transaction: <T>(
|
|
167
|
+
callback: (tx: RawAccess) => Promise<T> | T,
|
|
168
|
+
options?: { timeout?: number },
|
|
169
|
+
) => Promise<T>;
|
|
146
170
|
/**
|
|
147
171
|
* Returns native SQLite metrics when the active runtime supports them.
|
|
148
172
|
*/
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pino } from "pino";
|
|
2
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
3
|
+
import { configureBaseLogger } from "@/common/log";
|
|
2
4
|
import type {
|
|
3
5
|
DatabaseProviderContext,
|
|
4
6
|
SqliteBindings,
|
|
5
7
|
SqliteDatabase,
|
|
6
8
|
SqliteExecuteResult,
|
|
9
|
+
SqliteTransactionDatabase,
|
|
7
10
|
} from "./config";
|
|
8
11
|
import { db } from "./mod";
|
|
9
12
|
|
|
13
|
+
let logLines: string[];
|
|
14
|
+
|
|
10
15
|
class FakeSqliteDatabase implements SqliteDatabase {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}[] = [];
|
|
16
|
+
failSql = new Map<string, Error>();
|
|
17
|
+
executeCalls: { sql: string; params?: SqliteBindings }[] = [];
|
|
18
|
+
transactionTimeouts: Array<number | undefined> = [];
|
|
15
19
|
|
|
16
20
|
async exec(): Promise<void> {}
|
|
17
21
|
|
|
@@ -19,18 +23,36 @@ class FakeSqliteDatabase implements SqliteDatabase {
|
|
|
19
23
|
sql: string,
|
|
20
24
|
params?: SqliteBindings,
|
|
21
25
|
): Promise<SqliteExecuteResult> {
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
this.record(sql, params);
|
|
27
|
+
return emptyResult();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async beginTransaction(
|
|
31
|
+
timeoutMs?: number,
|
|
32
|
+
): Promise<SqliteTransactionDatabase> {
|
|
33
|
+
this.transactionTimeouts.push(timeoutMs);
|
|
34
|
+
this.record("BEGIN");
|
|
26
35
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
exec: async () => {},
|
|
37
|
+
execute: async (sql, params) => {
|
|
38
|
+
this.record(sql, params);
|
|
39
|
+
return emptyResult();
|
|
40
|
+
},
|
|
41
|
+
commit: async () => this.record("COMMIT"),
|
|
42
|
+
rollback: async () => this.record("ROLLBACK"),
|
|
31
43
|
};
|
|
32
44
|
}
|
|
33
45
|
|
|
46
|
+
async executeBatch(
|
|
47
|
+
statements: Array<{ sql: string; params?: SqliteBindings }>,
|
|
48
|
+
): Promise<SqliteExecuteResult[]> {
|
|
49
|
+
const results: SqliteExecuteResult[] = [];
|
|
50
|
+
for (const statement of statements) {
|
|
51
|
+
results.push(await this.execute(statement.sql, statement.params));
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
async run(sql: string, params?: SqliteBindings): Promise<void> {
|
|
35
57
|
await this.execute(sql, params);
|
|
36
58
|
}
|
|
@@ -41,6 +63,21 @@ class FakeSqliteDatabase implements SqliteDatabase {
|
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
async close(): Promise<void> {}
|
|
66
|
+
|
|
67
|
+
private record(sql: string, params?: SqliteBindings): void {
|
|
68
|
+
this.executeCalls.push({ sql, params });
|
|
69
|
+
const error = this.failSql.get(sql);
|
|
70
|
+
if (error) throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function emptyResult(): SqliteExecuteResult {
|
|
75
|
+
return {
|
|
76
|
+
columns: [],
|
|
77
|
+
rows: [],
|
|
78
|
+
changes: 0,
|
|
79
|
+
lastInsertRowId: null,
|
|
80
|
+
};
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
function testProviderContext(
|
|
@@ -54,84 +91,136 @@ function testProviderContext(
|
|
|
54
91
|
batchDelete: async () => {},
|
|
55
92
|
deleteRange: async () => {},
|
|
56
93
|
},
|
|
57
|
-
nativeDatabaseProvider: {
|
|
58
|
-
open: async () => database,
|
|
59
|
-
},
|
|
94
|
+
nativeDatabaseProvider: { open: async () => database },
|
|
60
95
|
};
|
|
61
96
|
}
|
|
62
97
|
|
|
63
98
|
describe("db", () => {
|
|
64
|
-
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
logLines = [];
|
|
101
|
+
configureBaseLogger(
|
|
102
|
+
pino(
|
|
103
|
+
{ level: "warn", base: {}, timestamp: false },
|
|
104
|
+
{ write: (line: string) => logLines.push(line) },
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("runs onMigrate through the shared transaction handle", async () => {
|
|
65
110
|
const nativeDb = new FakeSqliteDatabase();
|
|
66
111
|
const provider = db({
|
|
67
112
|
onMigrate: async (client) => {
|
|
68
113
|
await client.execute(
|
|
69
|
-
"CREATE TABLE items(id INTEGER PRIMARY KEY
|
|
114
|
+
"CREATE TABLE items(id INTEGER PRIMARY KEY)",
|
|
70
115
|
);
|
|
71
|
-
await client.execute("SELECT COUNT(*) AS count FROM items");
|
|
72
116
|
},
|
|
73
117
|
});
|
|
74
118
|
const client = await provider.createClient(
|
|
75
119
|
testProviderContext(nativeDb),
|
|
76
120
|
);
|
|
77
|
-
|
|
78
121
|
await provider.onMigrate(client);
|
|
79
122
|
|
|
80
|
-
expect(nativeDb.executeCalls).toEqual([
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
sql: "CREATE TABLE items(id INTEGER PRIMARY KEY, value TEXT)",
|
|
87
|
-
params: undefined,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
sql: "SELECT COUNT(*) AS count FROM items",
|
|
91
|
-
params: undefined,
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
sql: "RELEASE SAVEPOINT __rivet_on_migrate",
|
|
95
|
-
params: undefined,
|
|
96
|
-
},
|
|
123
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
124
|
+
"BEGIN",
|
|
125
|
+
"SAVEPOINT __rivet_on_migrate",
|
|
126
|
+
"CREATE TABLE items(id INTEGER PRIMARY KEY)",
|
|
127
|
+
"RELEASE SAVEPOINT __rivet_on_migrate",
|
|
128
|
+
"COMMIT",
|
|
97
129
|
]);
|
|
130
|
+
expect(nativeDb.transactionTimeouts).toEqual([300_000]);
|
|
98
131
|
});
|
|
99
132
|
|
|
100
|
-
test("rolls back
|
|
133
|
+
test("rolls back migrations when onMigrate fails", async () => {
|
|
101
134
|
const nativeDb = new FakeSqliteDatabase();
|
|
102
135
|
const provider = db({
|
|
103
|
-
onMigrate: async (
|
|
104
|
-
await client.execute(
|
|
105
|
-
"CREATE TABLE items(id INTEGER PRIMARY KEY, value TEXT)",
|
|
106
|
-
);
|
|
136
|
+
onMigrate: async () => {
|
|
107
137
|
throw new Error("migration failed");
|
|
108
138
|
},
|
|
109
139
|
});
|
|
110
140
|
const client = await provider.createClient(
|
|
111
141
|
testProviderContext(nativeDb),
|
|
112
142
|
);
|
|
113
|
-
|
|
114
143
|
await expect(provider.onMigrate(client)).rejects.toThrow(
|
|
115
144
|
"migration failed",
|
|
116
145
|
);
|
|
146
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
147
|
+
"BEGIN",
|
|
148
|
+
"SAVEPOINT __rivet_on_migrate",
|
|
149
|
+
"ROLLBACK TO SAVEPOINT __rivet_on_migrate",
|
|
150
|
+
"RELEASE SAVEPOINT __rivet_on_migrate",
|
|
151
|
+
"ROLLBACK",
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
117
154
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
sql: "ROLLBACK TO SAVEPOINT __rivet_on_migrate",
|
|
129
|
-
params: undefined,
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
sql: "RELEASE SAVEPOINT __rivet_on_migrate",
|
|
133
|
-
params: undefined,
|
|
155
|
+
test("commits transaction work and forwards timeout", async () => {
|
|
156
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
157
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
158
|
+
const value = await client.transaction(
|
|
159
|
+
async (tx) => {
|
|
160
|
+
await tx.execute(
|
|
161
|
+
"INSERT INTO items(value) VALUES (?)",
|
|
162
|
+
"inside",
|
|
163
|
+
);
|
|
164
|
+
return 42;
|
|
134
165
|
},
|
|
166
|
+
{ timeout: 120_000 },
|
|
167
|
+
);
|
|
168
|
+
expect(value).toBe(42);
|
|
169
|
+
expect(nativeDb.transactionTimeouts).toEqual([120_000]);
|
|
170
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
171
|
+
"BEGIN",
|
|
172
|
+
"INSERT INTO items(value) VALUES (?)",
|
|
173
|
+
"COMMIT",
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("rolls back a transaction when the callback throws", async () => {
|
|
178
|
+
const nativeDb = new FakeSqliteDatabase();
|
|
179
|
+
const client = await db().createClient(testProviderContext(nativeDb));
|
|
180
|
+
await expect(
|
|
181
|
+
client.transaction(async (tx) => {
|
|
182
|
+
await tx.execute(
|
|
183
|
+
"INSERT INTO items(value) VALUES (?)",
|
|
184
|
+
"inside",
|
|
185
|
+
);
|
|
186
|
+
throw new Error("callback failed");
|
|
187
|
+
}),
|
|
188
|
+
).rejects.toThrow("callback failed");
|
|
189
|
+
expect(nativeDb.executeCalls.map(({ sql }) => sql)).toEqual([
|
|
190
|
+
"BEGIN",
|
|
191
|
+
"INSERT INTO items(value) VALUES (?)",
|
|
192
|
+
"ROLLBACK",
|
|
135
193
|
]);
|
|
136
194
|
});
|
|
195
|
+
|
|
196
|
+
test("validates transaction timeouts", async () => {
|
|
197
|
+
const client = await db().createClient(
|
|
198
|
+
testProviderContext(new FakeSqliteDatabase()),
|
|
199
|
+
);
|
|
200
|
+
for (const timeout of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
201
|
+
await expect(
|
|
202
|
+
client.transaction(async () => {}, { timeout }),
|
|
203
|
+
).rejects.toThrow("positive finite");
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("warns once for manual cross-call transactions and names the opt-out", async () => {
|
|
208
|
+
const client = await db().createClient(
|
|
209
|
+
testProviderContext(new FakeSqliteDatabase()),
|
|
210
|
+
);
|
|
211
|
+
await client.execute("BEGIN");
|
|
212
|
+
await client.execute("COMMIT");
|
|
213
|
+
expect(logLines).toHaveLength(1);
|
|
214
|
+
expect(JSON.parse(logLines[0] ?? "{}").msg).toContain(
|
|
215
|
+
"Set warnOnManualTransactions: false",
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("can suppress the manual transaction warning", async () => {
|
|
220
|
+
const client = await db({
|
|
221
|
+
warnOnManualTransactions: false,
|
|
222
|
+
}).createClient(testProviderContext(new FakeSqliteDatabase()));
|
|
223
|
+
await client.execute("BEGIN");
|
|
224
|
+
expect(logLines).toHaveLength(0);
|
|
225
|
+
});
|
|
137
226
|
});
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { getLogger } from "@/common/log";
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseProvider,
|
|
4
|
+
RawAccess,
|
|
5
|
+
SqliteDatabase,
|
|
6
|
+
SqliteTransactionDatabase,
|
|
7
|
+
} from "./config";
|
|
8
|
+
import {
|
|
9
|
+
isManualTransactionControl,
|
|
10
|
+
isSqliteBindingObject,
|
|
11
|
+
MIGRATION_TRANSACTION_TIMEOUT_MS,
|
|
12
|
+
toSqliteBindings,
|
|
13
|
+
validateTransactionTimeout,
|
|
14
|
+
} from "./shared";
|
|
3
15
|
|
|
4
16
|
export type { RawAccess } from "./config";
|
|
5
17
|
|
|
6
18
|
interface DatabaseFactoryConfig {
|
|
7
19
|
onMigrate?: (db: RawAccess) => Promise<void> | void;
|
|
20
|
+
warnOnManualTransactions?: boolean;
|
|
8
21
|
}
|
|
9
22
|
|
|
10
23
|
function hasMultipleStatements(query: string): boolean {
|
|
@@ -14,6 +27,7 @@ function hasMultipleStatements(query: string): boolean {
|
|
|
14
27
|
|
|
15
28
|
export function db({
|
|
16
29
|
onMigrate,
|
|
30
|
+
warnOnManualTransactions = true,
|
|
17
31
|
}: DatabaseFactoryConfig = {}): DatabaseProvider<RawAccess> {
|
|
18
32
|
return {
|
|
19
33
|
createClient: async (ctx) => {
|
|
@@ -26,6 +40,7 @@ export function db({
|
|
|
26
40
|
|
|
27
41
|
const db = await nativeDatabaseProvider.open(ctx.actorId);
|
|
28
42
|
let closed = false;
|
|
43
|
+
let manualTransactionWarned = false;
|
|
29
44
|
const ensureOpen = () => {
|
|
30
45
|
if (closed) {
|
|
31
46
|
throw new Error(
|
|
@@ -34,7 +49,10 @@ export function db({
|
|
|
34
49
|
}
|
|
35
50
|
};
|
|
36
51
|
|
|
37
|
-
const
|
|
52
|
+
const createClient = (
|
|
53
|
+
target: SqliteDatabase | SqliteTransactionDatabase,
|
|
54
|
+
transactionScoped = false,
|
|
55
|
+
): RawAccess => ({
|
|
38
56
|
execute: async <
|
|
39
57
|
TRow extends Record<string, unknown> = Record<
|
|
40
58
|
string,
|
|
@@ -45,6 +63,19 @@ export function db({
|
|
|
45
63
|
...args: unknown[]
|
|
46
64
|
): Promise<TRow[]> => {
|
|
47
65
|
ensureOpen();
|
|
66
|
+
if (
|
|
67
|
+
!transactionScoped &&
|
|
68
|
+
warnOnManualTransactions &&
|
|
69
|
+
!manualTransactionWarned &&
|
|
70
|
+
!hasMultipleStatements(query) &&
|
|
71
|
+
isManualTransactionControl(query)
|
|
72
|
+
) {
|
|
73
|
+
manualTransactionWarned = true;
|
|
74
|
+
getLogger("database").warn(
|
|
75
|
+
{ actorId: ctx.actorId },
|
|
76
|
+
"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.",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
48
79
|
|
|
49
80
|
const kvReadsBefore = ctx.metrics?.totalKvReads ?? 0;
|
|
50
81
|
const kvWritesBefore = ctx.metrics?.totalKvWrites ?? 0;
|
|
@@ -57,7 +88,7 @@ export function db({
|
|
|
57
88
|
isSqliteBindingObject(args[0])
|
|
58
89
|
? toSqliteBindings(args[0])
|
|
59
90
|
: toSqliteBindings(args);
|
|
60
|
-
const { rows, columns } = await
|
|
91
|
+
const { rows, columns } = await target.execute(
|
|
61
92
|
query,
|
|
62
93
|
bindings,
|
|
63
94
|
);
|
|
@@ -67,13 +98,16 @@ export function db({
|
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
if (!hasMultipleStatements(query)) {
|
|
70
|
-
const { rows, columns } = await
|
|
101
|
+
const { rows, columns } = await target.execute(
|
|
102
|
+
query,
|
|
103
|
+
undefined,
|
|
104
|
+
);
|
|
71
105
|
return rows.map((row) =>
|
|
72
106
|
rowToObject<TRow>(row, columns),
|
|
73
107
|
);
|
|
74
108
|
}
|
|
75
109
|
|
|
76
|
-
return await execMultiStatement<TRow>(
|
|
110
|
+
return await execMultiStatement<TRow>(target, query);
|
|
77
111
|
} finally {
|
|
78
112
|
const durationMs = performance.now() - start;
|
|
79
113
|
ctx.metrics?.trackSql(query, durationMs);
|
|
@@ -92,6 +126,28 @@ export function db({
|
|
|
92
126
|
}
|
|
93
127
|
}
|
|
94
128
|
},
|
|
129
|
+
transaction: async <T>(
|
|
130
|
+
callback: (tx: RawAccess) => Promise<T> | T,
|
|
131
|
+
options?: { timeout?: number },
|
|
132
|
+
): Promise<T> => {
|
|
133
|
+
validateTransactionTimeout(options?.timeout);
|
|
134
|
+
const transaction = await db.beginTransaction(
|
|
135
|
+
options?.timeout,
|
|
136
|
+
);
|
|
137
|
+
const tx = createClient(transaction, true);
|
|
138
|
+
try {
|
|
139
|
+
const result = await callback(tx);
|
|
140
|
+
await transaction.commit();
|
|
141
|
+
return result;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
try {
|
|
144
|
+
await transaction.rollback();
|
|
145
|
+
} catch {
|
|
146
|
+
// Preserve the callback or commit error after expiry cleanup.
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
95
151
|
close: async () => {
|
|
96
152
|
if (!closed) {
|
|
97
153
|
closed = true;
|
|
@@ -99,12 +155,15 @@ export function db({
|
|
|
99
155
|
}
|
|
100
156
|
},
|
|
101
157
|
nativeMetrics: () => db.nativeMetrics?.() ?? null,
|
|
102
|
-
};
|
|
158
|
+
});
|
|
159
|
+
const client = createClient(db);
|
|
103
160
|
return client;
|
|
104
161
|
},
|
|
105
162
|
onMigrate: async (client) => {
|
|
106
163
|
if (onMigrate) {
|
|
107
|
-
await withMigrationSavepoint(client, () =>
|
|
164
|
+
await withMigrationSavepoint(client, (leased) =>
|
|
165
|
+
onMigrate(leased),
|
|
166
|
+
);
|
|
108
167
|
}
|
|
109
168
|
},
|
|
110
169
|
};
|
|
@@ -122,7 +181,7 @@ function rowToObject<TRow extends Record<string, unknown>>(
|
|
|
122
181
|
}
|
|
123
182
|
|
|
124
183
|
async function execMultiStatement<TRow extends Record<string, unknown>>(
|
|
125
|
-
db: SqliteDatabase,
|
|
184
|
+
db: Pick<SqliteDatabase, "exec">,
|
|
126
185
|
query: string,
|
|
127
186
|
): Promise<TRow[]> {
|
|
128
187
|
const results: Record<string, unknown>[] = [];
|
|
@@ -138,19 +197,30 @@ async function execMultiStatement<TRow extends Record<string, unknown>>(
|
|
|
138
197
|
|
|
139
198
|
async function withMigrationSavepoint<T>(
|
|
140
199
|
client: RawAccess,
|
|
141
|
-
callback: () => Promise<T> | T,
|
|
200
|
+
callback: (leased: RawAccess) => Promise<T> | T,
|
|
142
201
|
): Promise<T> {
|
|
143
|
-
await client.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
202
|
+
return await client.transaction(
|
|
203
|
+
async (transaction) => {
|
|
204
|
+
await transaction.execute("SAVEPOINT __rivet_on_migrate");
|
|
205
|
+
try {
|
|
206
|
+
const result = await callback(transaction);
|
|
207
|
+
await transaction.execute(
|
|
208
|
+
"RELEASE SAVEPOINT __rivet_on_migrate",
|
|
209
|
+
);
|
|
210
|
+
return result;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
try {
|
|
213
|
+
await transaction.execute(
|
|
214
|
+
"ROLLBACK TO SAVEPOINT __rivet_on_migrate",
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
await transaction.execute(
|
|
218
|
+
"RELEASE SAVEPOINT __rivet_on_migrate",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{ timeout: MIGRATION_TRANSACTION_TIMEOUT_MS },
|
|
225
|
+
);
|
|
156
226
|
}
|
|
@@ -19,6 +19,15 @@ function deferred<T>() {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
class FakeNativeDatabase implements JsNativeDatabaseLike {
|
|
22
|
+
async beginTransaction() {
|
|
23
|
+
return {
|
|
24
|
+
exec: async (_sql: string) => this.exec(),
|
|
25
|
+
execute: async (sql: string, params?: NativeParams) =>
|
|
26
|
+
this.execute(sql, params),
|
|
27
|
+
commit: async () => {},
|
|
28
|
+
rollback: async () => {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
22
31
|
active = 0;
|
|
23
32
|
maxActive = 0;
|
|
24
33
|
closed = false;
|