@absurd-sqlite/sdk 0.2.0 → 0.2.1-alpha.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.
@@ -1,5 +1,8 @@
1
1
  import sqlite from "better-sqlite3";
2
- import { describe, expect, it } from "vitest";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
3
6
 
4
7
  import { SqliteConnection } from "../src/sqlite";
5
8
  import type { SQLiteDatabase } from "../src/sqlite-types";
@@ -82,4 +85,77 @@ describe("SqliteConnection", () => {
82
85
  expect(rows[0]?.created_at.getTime()).toBe(now);
83
86
  db.close();
84
87
  });
88
+
89
+ it("retries when SQLite reports the database is busy", async () => {
90
+ const tempDir = mkdtempSync(join(tmpdir(), "absurd-sqlite-busy-"));
91
+ const dbPath = join(tempDir, "busy.db");
92
+ const primary = new sqlite(dbPath) as unknown as SQLiteDatabase;
93
+ (primary as any).pragma("busy_timeout = 1");
94
+ const conn = new SqliteConnection(primary);
95
+ await conn.exec("CREATE TABLE t_busy (id INTEGER PRIMARY KEY, value TEXT)");
96
+
97
+ const blocker = new sqlite(dbPath);
98
+ blocker.pragma("busy_timeout = 1");
99
+ blocker.exec("BEGIN EXCLUSIVE");
100
+
101
+ let released = false;
102
+ const releaseLock = () => {
103
+ if (released) return;
104
+ released = true;
105
+ try {
106
+ blocker.exec("COMMIT");
107
+ } catch (err) {
108
+ // Ignore if the transaction was already closed.
109
+ }
110
+ blocker.close();
111
+ };
112
+ const timer = setTimeout(releaseLock, 50);
113
+
114
+ try {
115
+ await conn.exec("INSERT INTO t_busy (value) VALUES ($1)", ["alpha"]);
116
+ const { rows } = await conn.query<{ value: string }>(
117
+ "SELECT value FROM t_busy"
118
+ );
119
+ expect(rows[0]?.value).toBe("alpha");
120
+ } finally {
121
+ clearTimeout(timer);
122
+ releaseLock();
123
+ primary.close();
124
+ rmSync(tempDir, { recursive: true, force: true });
125
+ }
126
+ });
127
+
128
+ it("retries on locked error codes from SQLite", async () => {
129
+ const lockedError = new Error("SQLITE_LOCKED: mock lock") as any;
130
+ lockedError.code = "SQLITE_LOCKED_SHAREDCACHE";
131
+ lockedError.errno = 6;
132
+
133
+ let attempts = 0;
134
+ const statement = {
135
+ readonly: false,
136
+ columns: vi.fn().mockReturnValue([]),
137
+ all: vi.fn(),
138
+ run: vi.fn(() => {
139
+ attempts++;
140
+ if (attempts === 1) {
141
+ throw lockedError;
142
+ }
143
+ return 1;
144
+ }),
145
+ };
146
+
147
+ const prepareSpy = vi.fn().mockReturnValue(statement as any);
148
+ const db: SQLiteDatabase = {
149
+ prepare: prepareSpy as any,
150
+ close: vi.fn(),
151
+ loadExtension: vi.fn(),
152
+ };
153
+ const conn = new SqliteConnection(db);
154
+
155
+ await expect(
156
+ conn.exec("UPDATE locked_table SET value = $1 WHERE id = $2", [1, 1])
157
+ ).resolves.toBeUndefined();
158
+ expect(statement.run).toHaveBeenCalledTimes(2);
159
+ expect(prepareSpy).toHaveBeenCalledTimes(1);
160
+ });
85
161
  });