@xnetjs/sqlite 0.0.2 → 0.0.3
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/{adapter-I7yAV6iu.d.ts → adapter-t2aqQ__n.d.ts} +27 -1
- package/dist/adapters/electron.d.ts +7 -3
- package/dist/adapters/electron.js +50 -6
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +27 -6
- package/dist/adapters/memory.d.ts +2 -2
- package/dist/adapters/memory.js +1 -1
- package/dist/adapters/web-proxy.d.ts +19 -3
- package/dist/adapters/web-proxy.js +118 -6
- package/dist/adapters/web-worker.d.ts +12 -1
- package/dist/adapters/web-worker.js +27 -3
- package/dist/adapters/web.d.ts +11 -3
- package/dist/adapters/web.js +7 -4
- package/dist/browser-support.d.ts +56 -1
- package/dist/browser-support.js +11 -3
- package/dist/{chunk-ZRR5D2OD.js → chunk-2OK46ZBU.js} +118 -0
- package/dist/chunk-CBU263LI.js +30 -0
- package/dist/chunk-DCY4LAUD.js +494 -0
- package/dist/chunk-NVD7G7DZ.js +406 -0
- package/dist/index.d.ts +26 -7
- package/dist/index.js +188 -128
- package/dist/schema-BEgIA-rZ.d.ts +35 -0
- package/dist/types-DMrj3K4o.d.ts +132 -0
- package/package.json +1 -1
- package/dist/chunk-BXYZU3OL.js +0 -245
- package/dist/chunk-HIREU5S5.js +0 -193
- package/dist/schema-CjkXTqxn.d.ts +0 -35
- package/dist/types-C_aHfRDF.d.ts +0 -42
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSQLiteCorruptionError
|
|
3
|
+
} from "./chunk-CBU263LI.js";
|
|
4
|
+
import {
|
|
5
|
+
SCHEMA_DDL,
|
|
6
|
+
SCHEMA_VERSION
|
|
7
|
+
} from "./chunk-NVD7G7DZ.js";
|
|
8
|
+
|
|
9
|
+
// src/adapters/opfs-retry.ts
|
|
10
|
+
function isOpfsLockError(err) {
|
|
11
|
+
const e = err;
|
|
12
|
+
if (!e) return false;
|
|
13
|
+
if (e.name === "NoModificationAllowedError") return true;
|
|
14
|
+
return /Access Handles cannot be created|createSyncAccessHandle/i.test(e.message ?? "");
|
|
15
|
+
}
|
|
16
|
+
async function withOpfsLockRetry(fn, options = {}) {
|
|
17
|
+
const attempts = options.attempts ?? 5;
|
|
18
|
+
const baseDelayMs = options.baseDelayMs ?? 150;
|
|
19
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
20
|
+
let lastErr;
|
|
21
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
22
|
+
try {
|
|
23
|
+
return await fn();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
lastErr = err;
|
|
26
|
+
if (!isOpfsLockError(err) || attempt === attempts) throw err;
|
|
27
|
+
options.onRetry?.(attempt, err);
|
|
28
|
+
await sleep(baseDelayMs * attempt);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw lastErr;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/adapters/web.ts
|
|
35
|
+
var WEB_SQLITE_VFS_NAME = "opfs-sahpool";
|
|
36
|
+
var WEB_SQLITE_VFS_DIRECTORY = ".xnet-sqlite";
|
|
37
|
+
var WEB_SQLITE_INITIAL_CAPACITY = 10;
|
|
38
|
+
function isDebugEnabled() {
|
|
39
|
+
return typeof self !== "undefined" && typeof localStorage !== "undefined" && localStorage.getItem("xnet:sqlite:debug") === "true";
|
|
40
|
+
}
|
|
41
|
+
function log(...args) {
|
|
42
|
+
if (isDebugEnabled()) {
|
|
43
|
+
console.log(...args);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getDatabasePath(config) {
|
|
47
|
+
return config.path.startsWith("/") ? config.path : `/${config.path}`;
|
|
48
|
+
}
|
|
49
|
+
async function removeOPFSDirectory(directory) {
|
|
50
|
+
const storage = globalThis.navigator?.storage;
|
|
51
|
+
if (typeof storage?.getDirectory !== "function") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const root = await storage.getDirectory();
|
|
56
|
+
await root.removeEntry(directory.replace(/^\/+/, ""), { recursive: true });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err instanceof DOMException && err.name === "NotFoundError") {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function resetWebSQLiteOpfsStorage(config) {
|
|
65
|
+
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
66
|
+
const sqlite3 = await sqlite3InitModule();
|
|
67
|
+
const dbPath = getDatabasePath(config);
|
|
68
|
+
try {
|
|
69
|
+
const poolUtil = await sqlite3.installOpfsSAHPoolVfs({
|
|
70
|
+
name: WEB_SQLITE_VFS_NAME,
|
|
71
|
+
directory: WEB_SQLITE_VFS_DIRECTORY,
|
|
72
|
+
initialCapacity: WEB_SQLITE_INITIAL_CAPACITY,
|
|
73
|
+
clearOnInit: false
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
poolUtil.unlink(dbPath);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
await poolUtil.wipeFiles();
|
|
80
|
+
await poolUtil.removeVfs();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.warn("[WebSQLiteAdapter] SAH-pool reset failed, removing OPFS directory:", err);
|
|
83
|
+
await removeOPFSDirectory(WEB_SQLITE_VFS_DIRECTORY);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
var WebSQLiteAdapter = class {
|
|
87
|
+
sqlite3 = null;
|
|
88
|
+
db = null;
|
|
89
|
+
poolUtil = null;
|
|
90
|
+
_config = null;
|
|
91
|
+
inTransaction = false;
|
|
92
|
+
storageMode = "memory";
|
|
93
|
+
async open(config) {
|
|
94
|
+
if (this.db !== null) {
|
|
95
|
+
throw new Error("Database already open. Call close() first.");
|
|
96
|
+
}
|
|
97
|
+
log("[WebSQLiteAdapter] Starting open()...");
|
|
98
|
+
log("[WebSQLiteAdapter] Importing sqlite-wasm...");
|
|
99
|
+
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
100
|
+
log("[WebSQLiteAdapter] sqlite-wasm imported");
|
|
101
|
+
log("[WebSQLiteAdapter] Initializing sqlite3 module...");
|
|
102
|
+
this.sqlite3 = await sqlite3InitModule();
|
|
103
|
+
log("[WebSQLiteAdapter] sqlite3 module initialized");
|
|
104
|
+
try {
|
|
105
|
+
await withOpfsLockRetry(
|
|
106
|
+
async () => {
|
|
107
|
+
log("[WebSQLiteAdapter] Installing OPFS-SAHPool VFS...");
|
|
108
|
+
this.poolUtil = await this.sqlite3.installOpfsSAHPoolVfs({
|
|
109
|
+
name: WEB_SQLITE_VFS_NAME,
|
|
110
|
+
directory: WEB_SQLITE_VFS_DIRECTORY,
|
|
111
|
+
initialCapacity: WEB_SQLITE_INITIAL_CAPACITY,
|
|
112
|
+
// Support ~3-4 databases with journals
|
|
113
|
+
clearOnInit: false
|
|
114
|
+
});
|
|
115
|
+
log("[WebSQLiteAdapter] OPFS-SAHPool VFS installed");
|
|
116
|
+
log("[WebSQLiteAdapter] Reserving capacity...");
|
|
117
|
+
await this.poolUtil.reserveMinimumCapacity(10);
|
|
118
|
+
log("[WebSQLiteAdapter] Capacity reserved");
|
|
119
|
+
const dbPath = getDatabasePath(config);
|
|
120
|
+
log("[WebSQLiteAdapter] Opening database at", dbPath);
|
|
121
|
+
this.db = new this.poolUtil.OpfsSAHPoolDb(dbPath, "c");
|
|
122
|
+
this.storageMode = "opfs";
|
|
123
|
+
log("[WebSQLiteAdapter] Database opened with OPFS-SAHPool");
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
onRetry: (attempt, retryErr) => {
|
|
127
|
+
console.warn(
|
|
128
|
+
`[WebSQLiteAdapter] OPFS access handles are busy (attempt ${attempt}) \u2014 a previous tab/worker is likely still releasing them; retrying before any in-memory fallback.`,
|
|
129
|
+
retryErr
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn("[WebSQLiteAdapter] OPFS-SAHPool not available, trying OPFS direct mode:", err);
|
|
136
|
+
const dbPath = getDatabasePath(config);
|
|
137
|
+
const opfsDbCtor = this.sqlite3?.oo1?.OpfsDb;
|
|
138
|
+
if (typeof opfsDbCtor === "function") {
|
|
139
|
+
try {
|
|
140
|
+
this.db = new opfsDbCtor(dbPath, "c");
|
|
141
|
+
this.storageMode = "opfs";
|
|
142
|
+
log("[WebSQLiteAdapter] Database opened with OPFS direct mode");
|
|
143
|
+
} catch (opfsErr) {
|
|
144
|
+
log("[WebSQLiteAdapter] OPFS direct mode not available:", opfsErr);
|
|
145
|
+
this.db = new this.sqlite3.oo1.DB(":memory:", "c");
|
|
146
|
+
this.storageMode = "memory";
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
this.db = new this.sqlite3.oo1.DB(":memory:", "c");
|
|
150
|
+
this.storageMode = "memory";
|
|
151
|
+
}
|
|
152
|
+
if (this.storageMode === "memory") {
|
|
153
|
+
const cause = isOpfsLockError(err) ? "another xNet tab/worker is holding the local database" : "OPFS is unavailable in this browser context";
|
|
154
|
+
console.error(
|
|
155
|
+
`[WebSQLiteAdapter] Using an IN-MEMORY database (${cause}). Local data will NOT persist across reloads and the workspace will appear empty until it re-syncs from the hub. Close other xNet tabs and reload to restore persistent storage.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this._config = config;
|
|
160
|
+
if (config.foreignKeys !== false) {
|
|
161
|
+
this.execSync("PRAGMA foreign_keys = ON");
|
|
162
|
+
}
|
|
163
|
+
if (config.busyTimeout) {
|
|
164
|
+
this.execSync(`PRAGMA busy_timeout = ${config.busyTimeout}`);
|
|
165
|
+
} else {
|
|
166
|
+
this.execSync("PRAGMA busy_timeout = 5000");
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
this.execSync("PRAGMA page_size = 8192");
|
|
170
|
+
} catch (err) {
|
|
171
|
+
log("[WebSQLiteAdapter] page_size pragma not applied:", err);
|
|
172
|
+
}
|
|
173
|
+
this.execSync("PRAGMA synchronous = NORMAL");
|
|
174
|
+
this.execSync("PRAGMA cache_size = -262144");
|
|
175
|
+
this.execSync("PRAGMA temp_store = MEMORY");
|
|
176
|
+
try {
|
|
177
|
+
this.execSync("PRAGMA journal_mode = TRUNCATE");
|
|
178
|
+
} catch (err) {
|
|
179
|
+
log("[WebSQLiteAdapter] journal_mode pragma not applied:", err);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async close() {
|
|
183
|
+
if (this.db) {
|
|
184
|
+
try {
|
|
185
|
+
this.execSync("PRAGMA optimize");
|
|
186
|
+
} catch (err) {
|
|
187
|
+
log("[WebSQLiteAdapter] optimize on close skipped:", err);
|
|
188
|
+
}
|
|
189
|
+
this.db.close();
|
|
190
|
+
this.db = null;
|
|
191
|
+
}
|
|
192
|
+
this.sqlite3 = null;
|
|
193
|
+
this.poolUtil = null;
|
|
194
|
+
this._config = null;
|
|
195
|
+
}
|
|
196
|
+
isOpen() {
|
|
197
|
+
return this.db !== null;
|
|
198
|
+
}
|
|
199
|
+
getStorageMode() {
|
|
200
|
+
return this.storageMode;
|
|
201
|
+
}
|
|
202
|
+
async query(sql, params) {
|
|
203
|
+
this.ensureOpen();
|
|
204
|
+
const rows = [];
|
|
205
|
+
this.db.exec({
|
|
206
|
+
sql,
|
|
207
|
+
bind: params,
|
|
208
|
+
rowMode: "object",
|
|
209
|
+
callback: (row) => {
|
|
210
|
+
rows.push(row);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return rows;
|
|
214
|
+
}
|
|
215
|
+
async queryOne(sql, params) {
|
|
216
|
+
const rows = await this.query(sql, params);
|
|
217
|
+
return rows[0] ?? null;
|
|
218
|
+
}
|
|
219
|
+
async run(sql, params) {
|
|
220
|
+
this.ensureOpen();
|
|
221
|
+
this.db.exec({
|
|
222
|
+
sql,
|
|
223
|
+
bind: params
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
changes: this.sqlite3.capi.sqlite3_changes(this.db.pointer),
|
|
227
|
+
lastInsertRowid: this.sqlite3.capi.sqlite3_last_insert_rowid(this.db.pointer)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async exec(sql) {
|
|
231
|
+
this.ensureOpen();
|
|
232
|
+
this.execSync(sql);
|
|
233
|
+
}
|
|
234
|
+
execSync(sql) {
|
|
235
|
+
this.db.exec({ sql });
|
|
236
|
+
}
|
|
237
|
+
async transaction(fn) {
|
|
238
|
+
await this.beginTransaction();
|
|
239
|
+
try {
|
|
240
|
+
const result = await fn();
|
|
241
|
+
await this.commit();
|
|
242
|
+
return result;
|
|
243
|
+
} catch (err) {
|
|
244
|
+
if (this.inTransaction) {
|
|
245
|
+
try {
|
|
246
|
+
await this.rollback();
|
|
247
|
+
} catch (rollbackErr) {
|
|
248
|
+
if (isSQLiteCorruptionError(rollbackErr) && !isSQLiteCorruptionError(err)) {
|
|
249
|
+
throw rollbackErr;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async applyNodeBatch(input) {
|
|
257
|
+
await this.transaction(async () => {
|
|
258
|
+
for (const node of input.nodes) {
|
|
259
|
+
await this.run(
|
|
260
|
+
`INSERT INTO nodes (id, schema_id, created_at, updated_at, created_by, deleted_at)
|
|
261
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
262
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
263
|
+
schema_id = excluded.schema_id,
|
|
264
|
+
updated_at = excluded.updated_at,
|
|
265
|
+
deleted_at = excluded.deleted_at`,
|
|
266
|
+
[node.id, node.schemaId, node.createdAt, node.updatedAt, node.createdBy, node.deletedAt]
|
|
267
|
+
);
|
|
268
|
+
if (node.propertyKeys.length === 0) {
|
|
269
|
+
await this.run("DELETE FROM node_properties WHERE node_id = ?", [node.id]);
|
|
270
|
+
} else {
|
|
271
|
+
await this.run(
|
|
272
|
+
`DELETE FROM node_properties
|
|
273
|
+
WHERE node_id = ? AND property_key NOT IN (${node.propertyKeys.map(() => "?").join(", ")})`,
|
|
274
|
+
[node.id, ...node.propertyKeys]
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const property of input.properties) {
|
|
279
|
+
await this.run(
|
|
280
|
+
`INSERT INTO node_properties
|
|
281
|
+
(node_id, property_key, value, lamport_time, updated_by, updated_at)
|
|
282
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
283
|
+
ON CONFLICT(node_id, property_key) DO UPDATE SET
|
|
284
|
+
value = excluded.value,
|
|
285
|
+
lamport_time = excluded.lamport_time,
|
|
286
|
+
updated_by = excluded.updated_by,
|
|
287
|
+
updated_at = excluded.updated_at
|
|
288
|
+
WHERE excluded.lamport_time > node_properties.lamport_time`,
|
|
289
|
+
[
|
|
290
|
+
property.nodeId,
|
|
291
|
+
property.propertyKey,
|
|
292
|
+
property.value,
|
|
293
|
+
property.lamportTime,
|
|
294
|
+
property.updatedBy,
|
|
295
|
+
property.updatedAt
|
|
296
|
+
]
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (input.indexMode !== "defer-schema") {
|
|
300
|
+
for (const node of input.nodes) {
|
|
301
|
+
await this.run("DELETE FROM node_property_scalars WHERE node_id = ?", [node.id]);
|
|
302
|
+
}
|
|
303
|
+
for (const row of input.scalarIndexRows) {
|
|
304
|
+
await this.run(
|
|
305
|
+
`INSERT INTO node_property_scalars
|
|
306
|
+
(
|
|
307
|
+
node_id,
|
|
308
|
+
schema_id,
|
|
309
|
+
property_key,
|
|
310
|
+
value_type,
|
|
311
|
+
value_text,
|
|
312
|
+
value_number,
|
|
313
|
+
value_boolean,
|
|
314
|
+
value_hash,
|
|
315
|
+
updated_at,
|
|
316
|
+
lamport_time
|
|
317
|
+
)
|
|
318
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
319
|
+
[
|
|
320
|
+
row.nodeId,
|
|
321
|
+
row.schemaId,
|
|
322
|
+
row.propertyKey,
|
|
323
|
+
row.valueType,
|
|
324
|
+
row.valueText,
|
|
325
|
+
row.valueNumber,
|
|
326
|
+
row.valueBoolean,
|
|
327
|
+
row.valueHash,
|
|
328
|
+
row.updatedAt,
|
|
329
|
+
row.lamportTime
|
|
330
|
+
]
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
for (const nodeId of input.ftsNodeIds) {
|
|
334
|
+
await this.run("DELETE FROM nodes_fts WHERE node_id = ?", [nodeId]);
|
|
335
|
+
}
|
|
336
|
+
for (const row of input.ftsRows) {
|
|
337
|
+
await this.run("INSERT INTO nodes_fts (node_id, title, content) VALUES (?, ?, ?)", [
|
|
338
|
+
row.nodeId,
|
|
339
|
+
row.title,
|
|
340
|
+
row.content
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const change of input.changes) {
|
|
345
|
+
await this.run(
|
|
346
|
+
`INSERT OR IGNORE INTO changes
|
|
347
|
+
(hash, node_id, payload, lamport_time, lamport_peer, wall_time, author, parent_hash, batch_id, signature)
|
|
348
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
349
|
+
[
|
|
350
|
+
change.hash,
|
|
351
|
+
change.nodeId,
|
|
352
|
+
change.payload,
|
|
353
|
+
change.lamportTime,
|
|
354
|
+
change.lamportPeer,
|
|
355
|
+
change.wallTime,
|
|
356
|
+
change.author,
|
|
357
|
+
change.parentHash,
|
|
358
|
+
change.batchId,
|
|
359
|
+
change.signature
|
|
360
|
+
]
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
await this.run(
|
|
364
|
+
`INSERT INTO sync_state (key, value) VALUES ('lastLamportTime', ?)
|
|
365
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
366
|
+
[String(input.lastLamportTime)]
|
|
367
|
+
);
|
|
368
|
+
if (input.indexMode !== "defer-schema") {
|
|
369
|
+
const invalidatedAt = Date.now();
|
|
370
|
+
for (const schemaId of input.affectedSchemaIds) {
|
|
371
|
+
await this.run(
|
|
372
|
+
`UPDATE node_query_materializations
|
|
373
|
+
SET invalidated_at = ?
|
|
374
|
+
WHERE schema_id = ? AND invalidated_at IS NULL`,
|
|
375
|
+
[invalidatedAt, schemaId]
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return {
|
|
381
|
+
nodeRowsWritten: input.nodes.length,
|
|
382
|
+
propertyRowsWritten: input.properties.length,
|
|
383
|
+
changeRowsWritten: input.changes.length,
|
|
384
|
+
scalarRowsWritten: input.scalarIndexRows.length,
|
|
385
|
+
ftsRowsWritten: input.ftsRows.length
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async beginTransaction() {
|
|
389
|
+
if (this.inTransaction) {
|
|
390
|
+
throw new Error("Transaction already in progress");
|
|
391
|
+
}
|
|
392
|
+
this.execSync("BEGIN IMMEDIATE");
|
|
393
|
+
this.inTransaction = true;
|
|
394
|
+
}
|
|
395
|
+
async commit() {
|
|
396
|
+
if (!this.inTransaction) {
|
|
397
|
+
throw new Error("No transaction in progress");
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
this.execSync("COMMIT");
|
|
401
|
+
this.inTransaction = false;
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (isSQLiteCorruptionError(err)) {
|
|
404
|
+
this.inTransaction = false;
|
|
405
|
+
}
|
|
406
|
+
throw err;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async rollback() {
|
|
410
|
+
if (!this.inTransaction) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
this.execSync("ROLLBACK");
|
|
415
|
+
} finally {
|
|
416
|
+
this.inTransaction = false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async prepare(sql) {
|
|
420
|
+
return {
|
|
421
|
+
query: async (params) => {
|
|
422
|
+
return this.query(sql, params);
|
|
423
|
+
},
|
|
424
|
+
queryOne: async (params) => {
|
|
425
|
+
return this.queryOne(sql, params);
|
|
426
|
+
},
|
|
427
|
+
run: async (params) => {
|
|
428
|
+
return this.run(sql, params);
|
|
429
|
+
},
|
|
430
|
+
finalize: async () => {
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async getSchemaVersion() {
|
|
435
|
+
try {
|
|
436
|
+
const row = await this.queryOne(
|
|
437
|
+
"SELECT version FROM _schema_version ORDER BY version DESC LIMIT 1"
|
|
438
|
+
);
|
|
439
|
+
return row?.version ?? 0;
|
|
440
|
+
} catch {
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async setSchemaVersion(version) {
|
|
445
|
+
await this.run("INSERT INTO _schema_version (version, applied_at) VALUES (?, ?)", [
|
|
446
|
+
version,
|
|
447
|
+
Date.now()
|
|
448
|
+
]);
|
|
449
|
+
}
|
|
450
|
+
async applySchema(version, sql) {
|
|
451
|
+
const currentVersion = await this.getSchemaVersion();
|
|
452
|
+
if (currentVersion >= version) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
await this.transaction(async () => {
|
|
456
|
+
await this.exec(sql);
|
|
457
|
+
await this.setSchemaVersion(version);
|
|
458
|
+
});
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
async getDatabaseSize() {
|
|
462
|
+
try {
|
|
463
|
+
const row = await this.queryOne(
|
|
464
|
+
"SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"
|
|
465
|
+
);
|
|
466
|
+
return row?.size ?? 0;
|
|
467
|
+
} catch {
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async vacuum() {
|
|
472
|
+
await this.exec("VACUUM");
|
|
473
|
+
}
|
|
474
|
+
async checkpoint() {
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
477
|
+
ensureOpen() {
|
|
478
|
+
if (!this.db || !this.sqlite3) {
|
|
479
|
+
throw new Error("Database not open. Call open() first.");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
async function createWebSQLiteAdapter(config) {
|
|
484
|
+
const adapter = new WebSQLiteAdapter();
|
|
485
|
+
await adapter.open(config);
|
|
486
|
+
await adapter.applySchema(SCHEMA_VERSION, SCHEMA_DDL);
|
|
487
|
+
return adapter;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export {
|
|
491
|
+
resetWebSQLiteOpfsStorage,
|
|
492
|
+
WebSQLiteAdapter,
|
|
493
|
+
createWebSQLiteAdapter
|
|
494
|
+
};
|