@korajs/store 1.0.0-beta.12 → 1.0.0-beta.14
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/README.md +29 -37
- package/dist/adapters/better-sqlite3.cjs +90 -4
- package/dist/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.d.cts +21 -12
- package/dist/adapters/better-sqlite3.d.ts +21 -12
- package/dist/adapters/better-sqlite3.js +69 -5
- package/dist/adapters/better-sqlite3.js.map +1 -1
- package/dist/adapters/indexeddb.cjs +1316 -610
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +29 -3
- package/dist/adapters/indexeddb.d.ts +29 -3
- package/dist/adapters/indexeddb.js +247 -221
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +856 -63
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +529 -63
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +1032 -344
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +239 -18
- package/dist/adapters/sqlite-wasm.d.ts +239 -18
- package/dist/adapters/sqlite-wasm.js +111 -5
- package/dist/adapters/sqlite-wasm.js.map +1 -1
- package/dist/backup-IHKVMUAM.js +25 -0
- package/dist/chunk-5AQSGMJV.js +5471 -0
- package/dist/chunk-5AQSGMJV.js.map +1 -0
- package/dist/chunk-5JMGMPUE.js +591 -0
- package/dist/chunk-5JMGMPUE.js.map +1 -0
- package/dist/{chunk-CYO3YKFL.js → chunk-5ZRRF6R2.js} +2 -2
- package/dist/chunk-A6GFH5IG.js +36 -0
- package/dist/chunk-A6GFH5IG.js.map +1 -0
- package/dist/chunk-BILSA6XZ.js +608 -0
- package/dist/chunk-BILSA6XZ.js.map +1 -0
- package/dist/chunk-BQSIL6TT.js +1095 -0
- package/dist/chunk-BQSIL6TT.js.map +1 -0
- package/dist/{chunk-GSJ7EEUC.js → chunk-J5AGXF3D.js} +58 -9
- package/dist/chunk-J5AGXF3D.js.map +1 -0
- package/dist/chunk-KK3GBONL.js +112 -0
- package/dist/chunk-KK3GBONL.js.map +1 -0
- package/dist/chunk-KTVNANUH.js +876 -0
- package/dist/chunk-KTVNANUH.js.map +1 -0
- package/dist/chunk-QW4UU7NG.js +1003 -0
- package/dist/chunk-QW4UU7NG.js.map +1 -0
- package/dist/chunk-SRHDJF5B.js +186 -0
- package/dist/chunk-SRHDJF5B.js.map +1 -0
- package/dist/chunk-UC2LGFPV.js +810 -0
- package/dist/chunk-UC2LGFPV.js.map +1 -0
- package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
- package/dist/index.cjs +9533 -4246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1143 -110
- package/dist/index.d.ts +1143 -110
- package/dist/index.js +298 -3191
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +581 -264
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +15 -85
- package/dist/internal.d.ts +15 -85
- package/dist/internal.js +10 -8
- package/dist/restore-MNDPR557.js +299 -0
- package/dist/restore-MNDPR557.js.map +1 -0
- package/dist/sql-builder-D0tC3sv9.d.cts +615 -0
- package/dist/sql-builder-D5UMzHEo.d.ts +615 -0
- package/dist/sqlite-wasm-channel-DVX93Uiq.d.cts +141 -0
- package/dist/sqlite-wasm-channel-DVX93Uiq.d.ts +141 -0
- package/dist/sqlite-wasm-channel-QCCNBYCU.js +12 -0
- package/dist/store-HNGX4KTT.js +16 -0
- package/dist/store-HNGX4KTT.js.map +1 -0
- package/dist/{types-BslDOTk0.d.ts → types-Dh-xyUjP.d.cts} +99 -26
- package/dist/{types-BslDOTk0.d.cts → types-Dh-xyUjP.d.ts} +99 -26
- package/package.json +3 -2
- package/dist/backup-5S7K2Z35.js +0 -14
- package/dist/chunk-GSJ7EEUC.js.map +0 -1
- package/dist/chunk-HS6DBMST.js +0 -383
- package/dist/chunk-HS6DBMST.js.map +0 -1
- package/dist/chunk-RJIEKEN4.js +0 -751
- package/dist/chunk-RJIEKEN4.js.map +0 -1
- package/dist/chunk-TUR7RGEP.js +0 -84
- package/dist/chunk-TUR7RGEP.js.map +0 -1
- package/dist/chunk-VDBJQ75K.js +0 -78
- package/dist/chunk-VDBJQ75K.js.map +0 -1
- package/dist/chunk-YT7MRFCP.js +0 -687
- package/dist/chunk-YT7MRFCP.js.map +0 -1
- package/dist/sqlite-wasm-channel-GGHA2BXU.js +0 -12
- package/dist/sqlite-wasm-channel-L6o3bWQK.d.cts +0 -92
- package/dist/sqlite-wasm-channel-L6o3bWQK.d.ts +0 -92
- package/dist/types-BOO9EPt1.d.ts +0 -170
- package/dist/types-C_GDavG-.d.cts +0 -170
- /package/dist/{backup-5S7K2Z35.js.map → backup-IHKVMUAM.js.map} +0 -0
- /package/dist/{chunk-CYO3YKFL.js.map → chunk-5ZRRF6R2.js.map} +0 -0
- /package/dist/{export-audit-ODGQTQIK.js.map → export-audit-J2QXRHD2.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-GGHA2BXU.js.map → sqlite-wasm-channel-QCCNBYCU.js.map} +0 -0
|
@@ -1,6 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
STORED_SCHEMA_VERSION_SQL,
|
|
3
|
+
parseSchemaCeiling,
|
|
4
|
+
schemaAheadMessage,
|
|
5
|
+
storedSchemaVersion
|
|
6
|
+
} from "../chunk-A6GFH5IG.js";
|
|
7
|
+
import {
|
|
8
|
+
LEGACY_OPFS_POOL_NAME,
|
|
9
|
+
TransactionSerializingWorkerBridge,
|
|
10
|
+
opfsPoolDirectory,
|
|
11
|
+
opfsPoolLockName,
|
|
12
|
+
opfsPoolNameFor,
|
|
13
|
+
opfsPoolPath,
|
|
14
|
+
startWorkerRpcRelay
|
|
15
|
+
} from "../chunk-5JMGMPUE.js";
|
|
16
|
+
import {
|
|
17
|
+
WorkerInitError
|
|
18
|
+
} from "../chunk-SRHDJF5B.js";
|
|
19
|
+
|
|
20
|
+
// src/adapters/opfs-pool-ownership.ts
|
|
21
|
+
var OPFS_POOL_HEADROOM = 2;
|
|
22
|
+
var BLOCKED_REPORT_AFTER_MS = 750;
|
|
23
|
+
var INSTALL_RACE_ATTEMPTS = 20;
|
|
3
24
|
var OPFS_INIT_TIMEOUT_MS = 1e4;
|
|
25
|
+
var OPAQUE_DIR_NAME = ".opaque";
|
|
26
|
+
var PoolUnavailableError = class extends Error {
|
|
27
|
+
constructor(message, reason) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
this.name = "PoolUnavailableError";
|
|
31
|
+
}
|
|
32
|
+
reason;
|
|
33
|
+
};
|
|
34
|
+
function opfsSahSupported(sqlite3) {
|
|
35
|
+
const g = globalThis;
|
|
36
|
+
const fileHandle = g.FileSystemFileHandle;
|
|
37
|
+
return typeof sqlite3.installOpfsSAHPoolVfs === "function" && typeof g.FileSystemDirectoryHandle !== "undefined" && typeof fileHandle?.prototype?.createSyncAccessHandle === "function" && typeof navigator !== "undefined" && typeof navigator.storage?.getDirectory === "function";
|
|
38
|
+
}
|
|
39
|
+
function sleep(ms) {
|
|
40
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
+
}
|
|
4
42
|
async function withTimeout(promise, timeoutMs, label) {
|
|
5
43
|
let timer;
|
|
6
44
|
try {
|
|
@@ -8,7 +46,7 @@ async function withTimeout(promise, timeoutMs, label) {
|
|
|
8
46
|
promise,
|
|
9
47
|
new Promise((_, reject) => {
|
|
10
48
|
timer = setTimeout(
|
|
11
|
-
() => reject(new
|
|
49
|
+
() => reject(new PoolUnavailableError(`${label} timed out after ${timeoutMs}ms`, "timeout")),
|
|
12
50
|
timeoutMs
|
|
13
51
|
);
|
|
14
52
|
})
|
|
@@ -17,25 +55,253 @@ async function withTimeout(promise, timeoutMs, label) {
|
|
|
17
55
|
if (timer !== void 0) clearTimeout(timer);
|
|
18
56
|
}
|
|
19
57
|
}
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
|
|
58
|
+
async function holdExclusiveLock(name, reporter) {
|
|
59
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
60
|
+
if (!locks || typeof locks.request !== "function") {
|
|
61
|
+
return () => {
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const grab = (ifAvailable) => new Promise((resolve, reject) => {
|
|
65
|
+
locks.request(name, { mode: "exclusive", ifAvailable }, (lock) => {
|
|
66
|
+
if (lock === null) {
|
|
67
|
+
resolve(null);
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
return new Promise((release2) => {
|
|
71
|
+
resolve(() => release2());
|
|
72
|
+
});
|
|
73
|
+
}).catch(reject);
|
|
74
|
+
});
|
|
75
|
+
const immediate = await grab(true);
|
|
76
|
+
if (immediate) return immediate;
|
|
77
|
+
const start = Date.now();
|
|
78
|
+
reporter?.blocked();
|
|
79
|
+
const release = await grab(false);
|
|
80
|
+
if (!release) {
|
|
81
|
+
throw new PoolUnavailableError(`Web Lock "${name}" was not granted`, "lock-conflict");
|
|
82
|
+
}
|
|
83
|
+
reporter?.unblocked(Date.now() - start);
|
|
84
|
+
return release;
|
|
85
|
+
}
|
|
86
|
+
async function opfsRoot() {
|
|
87
|
+
return await navigator.storage.getDirectory();
|
|
88
|
+
}
|
|
89
|
+
async function childDirectory(parent, name) {
|
|
90
|
+
try {
|
|
91
|
+
return await parent.getDirectoryHandle(name);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function poolDirectoryExists(poolName) {
|
|
97
|
+
return await childDirectory(await opfsRoot(), opfsPoolDirectory(poolName)) !== null;
|
|
98
|
+
}
|
|
99
|
+
function isHandleBusy(error) {
|
|
100
|
+
const name = error?.name;
|
|
101
|
+
return name === "NoModificationAllowedError" || name === "InvalidStateError";
|
|
102
|
+
}
|
|
103
|
+
async function poolHandlesBusy(poolName) {
|
|
104
|
+
const dir = await childDirectory(await opfsRoot(), opfsPoolDirectory(poolName));
|
|
105
|
+
if (!dir) return false;
|
|
106
|
+
const opaque = await childDirectory(dir, OPAQUE_DIR_NAME);
|
|
107
|
+
if (!opaque) return false;
|
|
108
|
+
for await (const [, handle] of opaque.entries()) {
|
|
109
|
+
if (handle.kind !== "file") continue;
|
|
110
|
+
try {
|
|
111
|
+
const access = await handle.createSyncAccessHandle();
|
|
112
|
+
access.close();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (isHandleBusy(error)) return true;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
async function waitForPoolHandles(poolName, reporter) {
|
|
121
|
+
const start = Date.now();
|
|
122
|
+
let reported = false;
|
|
123
|
+
let delay = 15;
|
|
124
|
+
while (await poolHandlesBusy(poolName)) {
|
|
125
|
+
if (!reported && Date.now() - start >= BLOCKED_REPORT_AFTER_MS) {
|
|
126
|
+
reported = true;
|
|
127
|
+
reporter?.blocked();
|
|
128
|
+
}
|
|
129
|
+
await sleep(delay);
|
|
130
|
+
delay = Math.min(delay * 2, 500);
|
|
131
|
+
}
|
|
132
|
+
if (reported) reporter?.unblocked(Date.now() - start);
|
|
133
|
+
}
|
|
134
|
+
async function installPool(sqlite3, poolName, reporter) {
|
|
135
|
+
const install = sqlite3.installOpfsSAHPoolVfs;
|
|
136
|
+
if (!install || !opfsSahSupported(sqlite3)) {
|
|
137
|
+
throw new PoolUnavailableError("OPFS SyncAccessHandle pool is not supported", "unsupported");
|
|
138
|
+
}
|
|
139
|
+
let lastError = null;
|
|
140
|
+
for (let attempt = 0; attempt < INSTALL_RACE_ATTEMPTS; attempt += 1) {
|
|
141
|
+
await waitForPoolHandles(poolName, reporter);
|
|
142
|
+
try {
|
|
143
|
+
const pool = await withTimeout(
|
|
144
|
+
install({ name: poolName, forceReinitIfPreviouslyFailed: true }),
|
|
145
|
+
OPFS_INIT_TIMEOUT_MS,
|
|
146
|
+
"OPFS VFS install"
|
|
147
|
+
);
|
|
148
|
+
if (pool.isPaused()) {
|
|
149
|
+
await pool.unpauseVfs();
|
|
150
|
+
}
|
|
151
|
+
return pool;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof PoolUnavailableError) throw error;
|
|
154
|
+
lastError = error;
|
|
155
|
+
if (!isHandleBusy(error)) break;
|
|
156
|
+
await sleep(Math.min(25 * 2 ** attempt, 250));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
160
|
+
if (isHandleBusy(lastError)) {
|
|
161
|
+
throw new PoolUnavailableError(
|
|
162
|
+
`OPFS pool "${poolName}" stayed busy: ${message}`,
|
|
163
|
+
"lock-conflict"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
throw new PoolUnavailableError(
|
|
167
|
+
`OPFS pool "${poolName}" could not be installed: ${message}`,
|
|
168
|
+
"unsupported"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
async function ensurePoolCapacity(pool) {
|
|
172
|
+
await pool.reserveMinimumCapacity(pool.getFileCount() + 2 + OPFS_POOL_HEADROOM);
|
|
23
173
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
174
|
+
function assertDatabaseHealthy(db, label) {
|
|
175
|
+
const rows = db.exec({
|
|
176
|
+
sql: "PRAGMA quick_check",
|
|
177
|
+
returnValue: "resultRows",
|
|
178
|
+
rowMode: "array"
|
|
179
|
+
});
|
|
180
|
+
const verdict = rows?.[0]?.[0];
|
|
181
|
+
if (verdict !== "ok") {
|
|
182
|
+
throw new Error(`${label} failed integrity check: ${String(verdict)}`);
|
|
28
183
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)) {
|
|
32
|
-
|
|
184
|
+
}
|
|
185
|
+
function pausePool(pool) {
|
|
186
|
+
if (!pool.isPaused()) {
|
|
187
|
+
pool.pauseVfs();
|
|
33
188
|
}
|
|
34
|
-
return "unsupported";
|
|
35
189
|
}
|
|
190
|
+
async function migrateFromLegacyPool(sqlite3, target, poolPath, reporter) {
|
|
191
|
+
if (!await poolDirectoryExists(LEGACY_OPFS_POOL_NAME)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
const releaseLegacyLock = await holdExclusiveLock(
|
|
195
|
+
opfsPoolLockName(LEGACY_OPFS_POOL_NAME),
|
|
196
|
+
reporter
|
|
197
|
+
);
|
|
198
|
+
try {
|
|
199
|
+
if (!await poolDirectoryExists(LEGACY_OPFS_POOL_NAME)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const legacy = await installPool(sqlite3, LEGACY_OPFS_POOL_NAME, reporter);
|
|
203
|
+
try {
|
|
204
|
+
if (!legacy.getFileNames().includes(poolPath)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
const legacyDb = new legacy.OpfsSAHPoolDb(poolPath);
|
|
208
|
+
try {
|
|
209
|
+
assertDatabaseHealthy(legacyDb, `Legacy database ${poolPath}`);
|
|
210
|
+
} finally {
|
|
211
|
+
legacyDb.close();
|
|
212
|
+
}
|
|
213
|
+
const bytes = legacy.exportFile(poolPath);
|
|
214
|
+
if (bytes.byteLength === 0) {
|
|
215
|
+
legacy.unlink(poolPath);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
await ensurePoolCapacity(target);
|
|
219
|
+
await target.importDb(poolPath, bytes);
|
|
220
|
+
let migratedDb = null;
|
|
221
|
+
try {
|
|
222
|
+
migratedDb = new target.OpfsSAHPoolDb(poolPath);
|
|
223
|
+
assertDatabaseHealthy(migratedDb, `Migrated database ${poolPath}`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
migratedDb?.close();
|
|
226
|
+
migratedDb = null;
|
|
227
|
+
target.unlink(poolPath);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
migratedDb.close();
|
|
231
|
+
legacy.unlink(poolPath);
|
|
232
|
+
legacy.unlink(`${poolPath}-journal`);
|
|
233
|
+
return true;
|
|
234
|
+
} finally {
|
|
235
|
+
if (legacy.getFileCount() === 0) {
|
|
236
|
+
await legacy.removeVfs();
|
|
237
|
+
} else {
|
|
238
|
+
pausePool(legacy);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
releaseLegacyLock();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/adapters/sqlite-wasm-loader.ts
|
|
247
|
+
var SQLITE_INIT_TIMEOUT_MS = 6e4;
|
|
248
|
+
async function loadSqliteModule(init, options = {}) {
|
|
249
|
+
const timeoutMs = options.timeoutMs ?? SQLITE_INIT_TIMEOUT_MS;
|
|
250
|
+
const source = options.unhandledRejections ?? globalUnhandledRejections;
|
|
251
|
+
let timer;
|
|
252
|
+
let unsubscribe;
|
|
253
|
+
try {
|
|
254
|
+
return await Promise.race([
|
|
255
|
+
init(options.initOptions),
|
|
256
|
+
new Promise((_, reject) => {
|
|
257
|
+
unsubscribe = source((reason) => {
|
|
258
|
+
if (!isWasmLoadFailure(reason)) return;
|
|
259
|
+
reject(
|
|
260
|
+
new WorkerInitError(
|
|
261
|
+
`could not load the SQLite WebAssembly binary (sqlite3.wasm): ${describe(reason)}. The device may be offline before the app was cached, or the file is not served (check the URL and the application/wasm MIME type).`,
|
|
262
|
+
{ cause: describe(reason) }
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
timer = setTimeout(
|
|
267
|
+
() => reject(
|
|
268
|
+
new WorkerInitError(`SQLite3 module init timed out after ${String(timeoutMs)}ms`)
|
|
269
|
+
),
|
|
270
|
+
timeoutMs
|
|
271
|
+
);
|
|
272
|
+
})
|
|
273
|
+
]);
|
|
274
|
+
} finally {
|
|
275
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
276
|
+
unsubscribe?.();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function isWasmLoadFailure(reason) {
|
|
280
|
+
if (reason instanceof TypeError) return true;
|
|
281
|
+
const wasm = globalThis.WebAssembly;
|
|
282
|
+
if (!wasm) return false;
|
|
283
|
+
for (const name of ["CompileError", "LinkError", "RuntimeError"]) {
|
|
284
|
+
const ctor = wasm[name];
|
|
285
|
+
if (typeof ctor === "function" && reason instanceof ctor) return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
function describe(reason) {
|
|
290
|
+
return reason instanceof Error ? reason.message : String(reason);
|
|
291
|
+
}
|
|
292
|
+
var globalUnhandledRejections = (listener) => {
|
|
293
|
+
const scope = globalThis;
|
|
294
|
+
if (typeof scope.addEventListener !== "function") return () => {
|
|
295
|
+
};
|
|
296
|
+
const handler = (event) => listener(event.reason);
|
|
297
|
+
scope.addEventListener("unhandledrejection", handler);
|
|
298
|
+
return () => scope.removeEventListener?.("unhandledrejection", handler);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// src/adapters/sqlite-wasm-worker-core.ts
|
|
302
|
+
var PROGRESS_HEARTBEAT_MS = 500;
|
|
303
|
+
var PROGRESS_HANDLER_OPS = 2e4;
|
|
36
304
|
var sqlite3Promise = null;
|
|
37
|
-
var opfsPoolPromise = null;
|
|
38
|
-
var opfsFallbackReason;
|
|
39
305
|
async function loadSqlite3() {
|
|
40
306
|
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
41
307
|
const wasmUrl = globalThis.__KORA_SQLITE_WASM_URL;
|
|
@@ -43,72 +309,225 @@ async function loadSqlite3() {
|
|
|
43
309
|
locateFile: (file) => file.endsWith(".wasm") ? wasmUrl : file
|
|
44
310
|
} : void 0;
|
|
45
311
|
const initFn = sqlite3InitModule;
|
|
46
|
-
return
|
|
312
|
+
return loadSqliteModule(initFn, initOptions ? { initOptions } : {});
|
|
47
313
|
}
|
|
48
314
|
function getSqlite3() {
|
|
49
315
|
if (!sqlite3Promise) {
|
|
50
|
-
|
|
316
|
+
const loading = loadSqlite3();
|
|
317
|
+
sqlite3Promise = loading;
|
|
318
|
+
loading.catch(() => {
|
|
319
|
+
if (sqlite3Promise === loading) sqlite3Promise = null;
|
|
320
|
+
});
|
|
51
321
|
}
|
|
52
322
|
return sqlite3Promise;
|
|
53
323
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
324
|
+
function createSqliteWasmCore(options = {}) {
|
|
325
|
+
let db = null;
|
|
326
|
+
let sqlite3Api = null;
|
|
327
|
+
let persistent = false;
|
|
328
|
+
let fallbackReason;
|
|
329
|
+
let pool = null;
|
|
330
|
+
let poolName = null;
|
|
331
|
+
let releasePoolLock = null;
|
|
332
|
+
let openInfo = {
|
|
333
|
+
created: false,
|
|
334
|
+
migratedFromLegacy: false
|
|
335
|
+
};
|
|
336
|
+
let lastProgressBeat = 0;
|
|
337
|
+
function reporterFor(resource, name) {
|
|
338
|
+
return {
|
|
339
|
+
blocked: () => options.postEvent?.({ kind: "storage-blocked", resource, poolName: name }),
|
|
340
|
+
unblocked: (waitedMs) => options.postEvent?.({ kind: "storage-unblocked", resource, poolName: name, waitedMs })
|
|
341
|
+
};
|
|
58
342
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
343
|
+
async function releaseStorage(mode) {
|
|
344
|
+
if (db) {
|
|
345
|
+
detachProgressHandler(db);
|
|
346
|
+
try {
|
|
347
|
+
db.close();
|
|
348
|
+
} finally {
|
|
349
|
+
db = null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (pool) {
|
|
353
|
+
if (mode === "remove") {
|
|
354
|
+
await pool.removeVfs();
|
|
355
|
+
} else {
|
|
356
|
+
pausePool(pool);
|
|
357
|
+
}
|
|
358
|
+
pool = null;
|
|
359
|
+
}
|
|
360
|
+
releasePoolLock?.();
|
|
361
|
+
releasePoolLock = null;
|
|
362
|
+
}
|
|
363
|
+
function attachProgressHandler(target) {
|
|
364
|
+
const register = sqlite3Api?.capi?.sqlite3_progress_handler;
|
|
365
|
+
if (!options.onProgress || typeof register !== "function" || !target.pointer) return;
|
|
366
|
+
register(
|
|
367
|
+
target.pointer,
|
|
368
|
+
PROGRESS_HANDLER_OPS,
|
|
369
|
+
() => {
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
if (now - lastProgressBeat >= PROGRESS_HEARTBEAT_MS) {
|
|
372
|
+
lastProgressBeat = now;
|
|
373
|
+
options.onProgress?.();
|
|
374
|
+
}
|
|
375
|
+
return 0;
|
|
376
|
+
},
|
|
377
|
+
0
|
|
64
378
|
);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
opfsFallbackReason = classifyOpfsFailure(error);
|
|
67
|
-
return null;
|
|
68
379
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
380
|
+
function detachProgressHandler(target) {
|
|
381
|
+
const register = sqlite3Api?.capi?.sqlite3_progress_handler;
|
|
382
|
+
if (typeof register !== "function" || !target.pointer) return;
|
|
383
|
+
try {
|
|
384
|
+
register(target.pointer, 0, 0, 0);
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
73
387
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
388
|
+
async function openOpfs(sqlite3, dbName, mustExist) {
|
|
389
|
+
const name = opfsPoolNameFor(dbName);
|
|
390
|
+
const path = opfsPoolPath(dbName);
|
|
391
|
+
poolName = name;
|
|
392
|
+
releasePoolLock = await holdExclusiveLock(opfsPoolLockName(name), reporterFor("pool", name));
|
|
393
|
+
pool = await installPool(sqlite3, name, reporterFor("pool", name));
|
|
394
|
+
let migratedFromLegacy = false;
|
|
395
|
+
if (!pool.getFileNames().includes(path)) {
|
|
396
|
+
migratedFromLegacy = await migrateFromLegacyPool(
|
|
397
|
+
sqlite3,
|
|
398
|
+
pool,
|
|
399
|
+
path,
|
|
400
|
+
reporterFor("legacy-pool", name)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const existedBefore = pool.getFileNames().includes(path);
|
|
404
|
+
if (!existedBefore && mustExist) {
|
|
405
|
+
await releaseStorage(pool.getFileCount() === 0 ? "remove" : "pause");
|
|
406
|
+
return { id: 0, type: "error", message: `Database file ${path} not found`, code: "NOT_FOUND" };
|
|
407
|
+
}
|
|
408
|
+
await ensurePoolCapacity(pool);
|
|
81
409
|
try {
|
|
82
|
-
|
|
410
|
+
db = new pool.OpfsSAHPoolDb(path);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (!existedBefore) pool.unlink(path);
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
persistent = true;
|
|
416
|
+
openInfo = { created: !existedBefore, migratedFromLegacy };
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
async function open(id, ddlStatements, dbName, storage, mustExist) {
|
|
420
|
+
if (db) {
|
|
421
|
+
try {
|
|
83
422
|
applyDdl(db, ddlStatements);
|
|
84
423
|
return { id, type: "success", data: buildOpenData() };
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return { id, type: "error", message: error.message, code: "INIT_ERROR" };
|
|
85
426
|
}
|
|
427
|
+
}
|
|
428
|
+
let createdPath = null;
|
|
429
|
+
try {
|
|
86
430
|
const sqlite3 = await getSqlite3();
|
|
87
431
|
sqlite3Api = sqlite3;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
432
|
+
if (storage === "opfs" && opfsSahSupported(sqlite3)) {
|
|
433
|
+
try {
|
|
434
|
+
const early = await openOpfs(sqlite3, dbName, mustExist);
|
|
435
|
+
if (early) return { ...early, id };
|
|
436
|
+
if (openInfo.created) createdPath = opfsPoolPath(dbName);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (!(error instanceof PoolUnavailableError) || error.reason === "lock-conflict") {
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
await releaseStorage("pause");
|
|
442
|
+
fallbackReason = error.reason;
|
|
443
|
+
}
|
|
92
444
|
} else {
|
|
445
|
+
fallbackReason = "unsupported";
|
|
446
|
+
}
|
|
447
|
+
if (!db) {
|
|
93
448
|
db = new sqlite3.oo1.DB({ filename: ":memory:" });
|
|
94
449
|
persistent = false;
|
|
95
450
|
}
|
|
96
|
-
db.exec({ sql: "PRAGMA journal_mode = WAL" });
|
|
97
451
|
db.exec({ sql: "PRAGMA foreign_keys = ON" });
|
|
98
452
|
applyDdl(db, ddlStatements);
|
|
453
|
+
attachProgressHandler(db);
|
|
99
454
|
return { id, type: "success", data: buildOpenData() };
|
|
100
455
|
} catch (error) {
|
|
101
|
-
|
|
456
|
+
const currentPool = pool;
|
|
457
|
+
if (db) {
|
|
458
|
+
try {
|
|
459
|
+
db.close();
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
db = null;
|
|
463
|
+
}
|
|
464
|
+
if (createdPath && currentPool) {
|
|
465
|
+
try {
|
|
466
|
+
currentPool.unlink(createdPath);
|
|
467
|
+
currentPool.unlink(`${createdPath}-journal`);
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
await releaseStorage("pause");
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
const reason = error instanceof PoolUnavailableError ? error.reason : void 0;
|
|
476
|
+
return {
|
|
477
|
+
id,
|
|
478
|
+
type: "error",
|
|
479
|
+
message: error.message,
|
|
480
|
+
code: "INIT_ERROR",
|
|
481
|
+
context: {
|
|
482
|
+
...reason ? { reason } : {},
|
|
483
|
+
...createdPath ? { removedCreatedFile: createdPath } : {}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function readJournalMode() {
|
|
489
|
+
if (!db) return void 0;
|
|
490
|
+
try {
|
|
491
|
+
let mode;
|
|
492
|
+
db.exec({
|
|
493
|
+
sql: "PRAGMA journal_mode",
|
|
494
|
+
rowMode: "object",
|
|
495
|
+
callback: (row) => {
|
|
496
|
+
if (typeof row.journal_mode === "string") mode = row.journal_mode;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
return mode;
|
|
500
|
+
} catch {
|
|
501
|
+
return void 0;
|
|
102
502
|
}
|
|
103
503
|
}
|
|
104
504
|
function buildOpenData() {
|
|
505
|
+
const journalMode = readJournalMode();
|
|
105
506
|
return {
|
|
507
|
+
...journalMode ? { journalMode } : {},
|
|
106
508
|
persistent,
|
|
107
|
-
...persistent ? {} : { fallbackReason:
|
|
509
|
+
...persistent ? {} : { fallbackReason: fallbackReason ?? "unsupported" },
|
|
510
|
+
...poolName && persistent ? { poolName } : {},
|
|
511
|
+
created: openInfo.created,
|
|
512
|
+
migratedFromLegacy: openInfo.migratedFromLegacy
|
|
108
513
|
};
|
|
109
514
|
}
|
|
110
515
|
function applyDdl(target, ddlStatements) {
|
|
111
516
|
for (const sql of ddlStatements) {
|
|
517
|
+
const ceiling = parseSchemaCeiling(sql);
|
|
518
|
+
if (ceiling !== null) {
|
|
519
|
+
const rows = [];
|
|
520
|
+
target.exec({
|
|
521
|
+
sql: STORED_SCHEMA_VERSION_SQL,
|
|
522
|
+
rowMode: "object",
|
|
523
|
+
callback: (row) => {
|
|
524
|
+
rows.push({ value: row.value });
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
const stored = storedSchemaVersion(rows);
|
|
528
|
+
if (stored > ceiling) throw new Error(schemaAheadMessage(stored, ceiling));
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
112
531
|
if (sql.startsWith("--kora:safe-alter")) {
|
|
113
532
|
try {
|
|
114
533
|
target.exec({ sql: sql.replace("--kora:safe-alter\n", "") });
|
|
@@ -153,12 +572,13 @@ function createSqliteWasmCore() {
|
|
|
153
572
|
return { id, type: "error", message: error.message, code: "QUERY_ERROR" };
|
|
154
573
|
}
|
|
155
574
|
}
|
|
156
|
-
function close(id) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
575
|
+
async function close(id, mode) {
|
|
576
|
+
try {
|
|
577
|
+
await releaseStorage(mode);
|
|
578
|
+
return { id, type: "success" };
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return { id, type: "error", message: error.message, code: "CLOSE_ERROR" };
|
|
160
581
|
}
|
|
161
|
-
return { id, type: "success" };
|
|
162
582
|
}
|
|
163
583
|
function migrate(id, statements) {
|
|
164
584
|
if (!db) {
|
|
@@ -177,10 +597,9 @@ function createSqliteWasmCore() {
|
|
|
177
597
|
if (!db) {
|
|
178
598
|
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
179
599
|
}
|
|
180
|
-
|
|
181
|
-
if (typeof dbWithDeserialize.deserialize === "function") {
|
|
600
|
+
if (typeof db.deserialize === "function") {
|
|
182
601
|
try {
|
|
183
|
-
|
|
602
|
+
db.deserialize(data);
|
|
184
603
|
return { id, type: "success" };
|
|
185
604
|
} catch (error) {
|
|
186
605
|
return { id, type: "error", message: error.message, code: "IMPORT_ERROR" };
|
|
@@ -205,9 +624,17 @@ function createSqliteWasmCore() {
|
|
|
205
624
|
try {
|
|
206
625
|
switch (request.type) {
|
|
207
626
|
case "open":
|
|
208
|
-
return await open(
|
|
627
|
+
return await open(
|
|
628
|
+
request.id,
|
|
629
|
+
request.ddlStatements,
|
|
630
|
+
request.dbName ?? "kora-db",
|
|
631
|
+
request.storage ?? "opfs",
|
|
632
|
+
request.mustExist === true
|
|
633
|
+
);
|
|
209
634
|
case "close":
|
|
210
|
-
return close(request.id);
|
|
635
|
+
return await close(request.id, "pause");
|
|
636
|
+
case "destroy":
|
|
637
|
+
return await close(request.id, "remove");
|
|
211
638
|
case "execute":
|
|
212
639
|
return execute(request.id, request.sql, request.params);
|
|
213
640
|
case "query":
|
|
@@ -229,6 +656,8 @@ function createSqliteWasmCore() {
|
|
|
229
656
|
message: "Export not yet supported in browser worker",
|
|
230
657
|
code: "EXPORT_NOT_SUPPORTED"
|
|
231
658
|
};
|
|
659
|
+
case "serve":
|
|
660
|
+
return { id: request.id, type: "success" };
|
|
232
661
|
default:
|
|
233
662
|
return {
|
|
234
663
|
id: request.id,
|
|
@@ -250,10 +679,47 @@ function createSqliteWasmCore() {
|
|
|
250
679
|
}
|
|
251
680
|
|
|
252
681
|
// src/adapters/sqlite-wasm-worker.ts
|
|
253
|
-
var
|
|
682
|
+
var relay = null;
|
|
683
|
+
var core = createSqliteWasmCore({
|
|
684
|
+
postEvent: (event) => {
|
|
685
|
+
const message = { id: -1, type: "event", event };
|
|
686
|
+
self.postMessage(message);
|
|
687
|
+
},
|
|
688
|
+
// Keep followers' watchdogs fed while a long statement blocks this worker.
|
|
689
|
+
onProgress: () => relay?.beat()
|
|
690
|
+
});
|
|
691
|
+
var serializer = new TransactionSerializingWorkerBridge({
|
|
692
|
+
send: (request) => core.handle(request),
|
|
693
|
+
terminate: () => {
|
|
694
|
+
}
|
|
695
|
+
});
|
|
254
696
|
self.onmessage = (event) => {
|
|
255
|
-
|
|
697
|
+
const { clientId, ...request } = event.data;
|
|
698
|
+
if (request.type === "serve") {
|
|
699
|
+
if (!relay) {
|
|
700
|
+
relay = startWorkerRpcRelay(request.channelName, serializer);
|
|
701
|
+
}
|
|
702
|
+
const response = { id: request.id, type: "success" };
|
|
256
703
|
self.postMessage(response);
|
|
257
|
-
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
void serializer.send(request, clientId).then(
|
|
707
|
+
(response) => {
|
|
708
|
+
if (request.type === "close" || request.type === "destroy") {
|
|
709
|
+
relay?.stop();
|
|
710
|
+
relay = null;
|
|
711
|
+
}
|
|
712
|
+
self.postMessage(response);
|
|
713
|
+
},
|
|
714
|
+
(error) => {
|
|
715
|
+
const response = {
|
|
716
|
+
id: request.id,
|
|
717
|
+
type: "error",
|
|
718
|
+
message: error instanceof Error ? error.message : String(error),
|
|
719
|
+
code: "WORKER_ERROR"
|
|
720
|
+
};
|
|
721
|
+
self.postMessage(response);
|
|
722
|
+
}
|
|
723
|
+
);
|
|
258
724
|
};
|
|
259
725
|
//# sourceMappingURL=sqlite-wasm-worker.js.map
|