Skip to content

Commit c3091c0

Browse files
committed
fix(rivetkit): surface native sqlite kv errors to callers
1 parent 7825238 commit c3091c0

2 files changed

Lines changed: 97 additions & 3 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, expect, test } from "vitest";
2+
import { wrapJsNativeDatabase, type JsNativeDatabaseLike } from "./native-database";
3+
4+
function createDatabase(
5+
overrides: Partial<JsNativeDatabaseLike> = {},
6+
): JsNativeDatabaseLike {
7+
return {
8+
async exec() {
9+
return { columns: [], rows: [] };
10+
},
11+
async query() {
12+
return { columns: [], rows: [] };
13+
},
14+
async run() {
15+
return { changes: 0 };
16+
},
17+
async close() {},
18+
...overrides,
19+
};
20+
}
21+
22+
describe("wrapJsNativeDatabase", () => {
23+
test("appends native sqlite kv errors to generic sqlite I/O failures", async () => {
24+
const db = wrapJsNativeDatabase(
25+
createDatabase({
26+
async run() {
27+
throw new Error(
28+
"failed to execute sqlite statement: disk I/O error",
29+
);
30+
},
31+
takeLastKvError() {
32+
return "envoy channel closed while writing sqlite page";
33+
},
34+
}),
35+
);
36+
37+
await expect(db.run("INSERT INTO foo VALUES (1)")).rejects.toThrow(
38+
"failed to execute sqlite statement: disk I/O error (native sqlite kv error: envoy channel closed while writing sqlite page)",
39+
);
40+
});
41+
42+
test("does not attach native sqlite kv errors to unrelated sqlite failures", async () => {
43+
const db = wrapJsNativeDatabase(
44+
createDatabase({
45+
async run() {
46+
throw new Error(
47+
"failed to execute sqlite statement: no such table: foo",
48+
);
49+
},
50+
takeLastKvError() {
51+
return "envoy channel closed while writing sqlite page";
52+
},
53+
}),
54+
);
55+
56+
await expect(db.run("INSERT INTO foo VALUES (1)")).rejects.toThrow(
57+
"failed to execute sqlite statement: no such table: foo",
58+
);
59+
});
60+
});

rivetkit-typescript/packages/rivetkit/src/db/native-database.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,30 @@ export interface JsNativeDatabaseLike {
2626
exec(sql: string): Promise<NativeExecResult>;
2727
query(sql: string, params?: NativeBindParam[] | null): Promise<NativeQueryResult>;
2828
run(sql: string, params?: NativeBindParam[] | null): Promise<NativeRunResult>;
29+
takeLastKvError?(): string | null;
2930
close(): Promise<void>;
3031
}
3132

33+
function shouldAttachNativeKvError(message: string): boolean {
34+
return /i\/o error|unable to open database file/i.test(message);
35+
}
36+
37+
function enrichNativeDatabaseError(
38+
database: JsNativeDatabaseLike,
39+
error: unknown,
40+
): never {
41+
const kvError = database.takeLastKvError?.();
42+
if (
43+
error instanceof Error &&
44+
kvError &&
45+
shouldAttachNativeKvError(error.message) &&
46+
!error.message.includes(kvError)
47+
) {
48+
error.message = `${error.message} (native sqlite kv error: ${kvError})`;
49+
}
50+
throw error;
51+
}
52+
3253
function toNativeBinding(arg: unknown): NativeBindParam {
3354
if (arg === null || arg === undefined) {
3455
return { kind: "null" };
@@ -126,7 +147,12 @@ export function wrapJsNativeDatabase(
126147
sql: string,
127148
callback?: (row: unknown[], columns: string[]) => void,
128149
): Promise<void> {
129-
const result = await database.exec(sql);
150+
let result: NativeExecResult;
151+
try {
152+
result = await database.exec(sql);
153+
} catch (error) {
154+
enrichNativeDatabaseError(database, error);
155+
}
130156
if (!callback) {
131157
return;
132158
}
@@ -135,10 +161,18 @@ export function wrapJsNativeDatabase(
135161
}
136162
},
137163
async run(sql: string, params?: SqliteBindings): Promise<void> {
138-
await database.run(sql, toNativeBindings(sql, params));
164+
try {
165+
await database.run(sql, toNativeBindings(sql, params));
166+
} catch (error) {
167+
enrichNativeDatabaseError(database, error);
168+
}
139169
},
140170
async query(sql: string, params?: SqliteBindings) {
141-
return await database.query(sql, toNativeBindings(sql, params));
171+
try {
172+
return await database.query(sql, toNativeBindings(sql, params));
173+
} catch (error) {
174+
enrichNativeDatabaseError(database, error);
175+
}
142176
},
143177
async close(): Promise<void> {
144178
await database.close();

0 commit comments

Comments
 (0)