@korajs/store 1.0.0-beta.11 → 1.0.0-beta.13
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 +803 -62
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +480 -62
- 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-4NUPDEMB.js +25 -0
- package/dist/chunk-32EDKWMI.js +876 -0
- package/dist/chunk-32EDKWMI.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-DUKTWVZW.js +175 -0
- package/dist/chunk-DUKTWVZW.js.map +1 -0
- package/dist/chunk-HZTDBLDB.js +607 -0
- package/dist/chunk-HZTDBLDB.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-MCQKEHOD.js +112 -0
- package/dist/chunk-MCQKEHOD.js.map +1 -0
- package/dist/chunk-P4V375FY.js +810 -0
- package/dist/chunk-P4V375FY.js.map +1 -0
- package/dist/chunk-QDUSJMFV.js +1095 -0
- package/dist/chunk-QDUSJMFV.js.map +1 -0
- package/dist/chunk-SJP2HTYZ.js +1003 -0
- package/dist/chunk-SJP2HTYZ.js.map +1 -0
- package/dist/chunk-W5F2LWG6.js +591 -0
- package/dist/chunk-W5F2LWG6.js.map +1 -0
- package/dist/chunk-Y5LRH7KI.js +5341 -0
- package/dist/chunk-Y5LRH7KI.js.map +1 -0
- package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
- package/dist/index.cjs +9354 -4157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1064 -108
- package/dist/index.d.ts +1064 -108
- package/dist/index.js +249 -3092
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +580 -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-FR7XIKT2.js +299 -0
- package/dist/restore-FR7XIKT2.js.map +1 -0
- package/dist/sql-builder-C4u55jKW.d.cts +608 -0
- package/dist/sql-builder-CGckdBDL.d.ts +608 -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-MLHLKEED.js +12 -0
- package/dist/store-IMWKZLWB.js +16 -0
- package/dist/store-IMWKZLWB.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-4NUPDEMB.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-MLHLKEED.js.map} +0 -0
|
@@ -22,9 +22,368 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
|
|
25
|
-
// src/adapters/
|
|
26
|
-
var
|
|
25
|
+
// src/adapters/opfs-names.ts
|
|
26
|
+
var LEGACY_OPFS_POOL_NAME = "kora-opfs";
|
|
27
|
+
var OPFS_POOL_PREFIX = "kora-opfs-";
|
|
28
|
+
var MAX_POOL_SLUG_LENGTH = 48;
|
|
29
|
+
function fnv1a32Hex(value) {
|
|
30
|
+
let hash = 2166136261;
|
|
31
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
32
|
+
hash ^= value.charCodeAt(i);
|
|
33
|
+
hash = Math.imul(hash, 16777619);
|
|
34
|
+
}
|
|
35
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
36
|
+
}
|
|
37
|
+
function opfsPoolNameFor(dbName) {
|
|
38
|
+
const sanitized = dbName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
39
|
+
const slug = sanitized.slice(0, MAX_POOL_SLUG_LENGTH);
|
|
40
|
+
if (slug === dbName) {
|
|
41
|
+
return `${OPFS_POOL_PREFIX}${slug}`;
|
|
42
|
+
}
|
|
43
|
+
return `${OPFS_POOL_PREFIX}${slug}-${fnv1a32Hex(dbName)}`;
|
|
44
|
+
}
|
|
45
|
+
function opfsPoolDirectory(poolName) {
|
|
46
|
+
return `.${poolName}`;
|
|
47
|
+
}
|
|
48
|
+
function opfsPoolLockName(poolName) {
|
|
49
|
+
return `kora-opfs-pool:${poolName}`;
|
|
50
|
+
}
|
|
51
|
+
function opfsDatabaseFilename(dbName) {
|
|
52
|
+
const base = dbName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
53
|
+
return base.endsWith(".db") ? base : `${base}.db`;
|
|
54
|
+
}
|
|
55
|
+
function opfsPoolPath(dbName) {
|
|
56
|
+
return `/${opfsDatabaseFilename(dbName)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/errors.ts
|
|
60
|
+
var import_core = require("@korajs/core");
|
|
61
|
+
var BridgeTerminatedError = class extends import_core.KoraError {
|
|
62
|
+
constructor(operation, reason) {
|
|
63
|
+
super(
|
|
64
|
+
`Storage request "${operation}" was interrupted (${reason}); retry it against the new storage leader.`,
|
|
65
|
+
"BRIDGE_TERMINATED",
|
|
66
|
+
{ operation, reason, retriable: true }
|
|
67
|
+
);
|
|
68
|
+
this.name = "BridgeTerminatedError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/multi-tab/tab-storage.ts
|
|
73
|
+
var RPC_REQUEST = "kora-worker-request";
|
|
74
|
+
var RPC_RESPONSE = "kora-worker-response";
|
|
75
|
+
var CLIENT_LEAVE = "kora-client-leave";
|
|
76
|
+
var LEADER_PING = "kora-leader-ping";
|
|
77
|
+
var LEADER_PONG = "kora-leader-pong";
|
|
78
|
+
var LEADER_HEARTBEAT = "kora-leader-heartbeat";
|
|
79
|
+
var DEFAULT_HEARTBEAT_MS = 1e3;
|
|
80
|
+
var DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS = 1e4;
|
|
81
|
+
var DEDUP_CAPACITY = 512;
|
|
82
|
+
function createRequestDeduper() {
|
|
83
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
84
|
+
const completed = /* @__PURE__ */ new Map();
|
|
85
|
+
return {
|
|
86
|
+
run(requestId, start) {
|
|
87
|
+
const done = completed.get(requestId);
|
|
88
|
+
if (done) return Promise.resolve(done);
|
|
89
|
+
if (inflight.has(requestId)) return null;
|
|
90
|
+
const promise = start().then((response) => {
|
|
91
|
+
inflight.delete(requestId);
|
|
92
|
+
completed.set(requestId, response);
|
|
93
|
+
if (completed.size > DEDUP_CAPACITY) {
|
|
94
|
+
const oldest = completed.keys().next().value;
|
|
95
|
+
if (oldest !== void 0) completed.delete(oldest);
|
|
96
|
+
}
|
|
97
|
+
return response;
|
|
98
|
+
});
|
|
99
|
+
inflight.set(requestId, promise);
|
|
100
|
+
return promise;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function serveChannel(channel, bridge, source, heartbeatMs) {
|
|
105
|
+
const epoch = createClientId();
|
|
106
|
+
const dedup = createRequestDeduper();
|
|
107
|
+
const beat = () => {
|
|
108
|
+
const message = { type: LEADER_HEARTBEAT, epoch, source };
|
|
109
|
+
channel.postMessage(message);
|
|
110
|
+
};
|
|
111
|
+
const respond = (requestId, response) => {
|
|
112
|
+
const msg = { type: RPC_RESPONSE, requestId, response };
|
|
113
|
+
channel.postMessage(msg);
|
|
114
|
+
};
|
|
115
|
+
const onMessage = (event) => {
|
|
116
|
+
const data = event.data;
|
|
117
|
+
if (data?.type === LEADER_PING) {
|
|
118
|
+
channel.postMessage({ type: LEADER_PONG });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (data?.type === CLIENT_LEAVE) {
|
|
122
|
+
if (hasReclaimClient(bridge)) {
|
|
123
|
+
bridge.reclaimClient(data.clientId, "client-left");
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (data?.type !== RPC_REQUEST) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (data.request.type === "close" || data.request.type === "destroy" || data.request.type === "serve") {
|
|
131
|
+
respond(data.requestId, { id: data.request.id, type: "success" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const pending = dedup.run(
|
|
135
|
+
data.requestId,
|
|
136
|
+
() => bridge.send(data.request, data.clientId).catch(
|
|
137
|
+
(error) => ({
|
|
138
|
+
id: data.request.id,
|
|
139
|
+
type: "error",
|
|
140
|
+
message: error instanceof Error ? error.message : "Worker RPC failed",
|
|
141
|
+
code: "LEADER_RPC_ERROR"
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
void pending?.then((response) => respond(data.requestId, response));
|
|
146
|
+
};
|
|
147
|
+
channel.addEventListener("message", onMessage);
|
|
148
|
+
const interval = heartbeatMs > 0 ? setInterval(beat, heartbeatMs) : void 0;
|
|
149
|
+
if (heartbeatMs > 0) beat();
|
|
150
|
+
return {
|
|
151
|
+
beat,
|
|
152
|
+
stop: () => {
|
|
153
|
+
if (interval !== void 0) clearInterval(interval);
|
|
154
|
+
channel.removeEventListener("message", onMessage);
|
|
155
|
+
channel.close();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function startWorkerRpcRelay(channelName, bridge, options = {}) {
|
|
160
|
+
return serveChannel(
|
|
161
|
+
new BroadcastChannel(channelName),
|
|
162
|
+
bridge,
|
|
163
|
+
"worker",
|
|
164
|
+
options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
var TransactionSerializingWorkerBridge = class {
|
|
168
|
+
inner;
|
|
169
|
+
leaderClientId = createClientId();
|
|
170
|
+
transactionIdleTimeoutMs;
|
|
171
|
+
queue = [];
|
|
172
|
+
activeTransactionClient = null;
|
|
173
|
+
abortedClients = /* @__PURE__ */ new Set();
|
|
174
|
+
transactionIdleTimer = null;
|
|
175
|
+
reclaiming = false;
|
|
176
|
+
processing = false;
|
|
177
|
+
terminated = false;
|
|
178
|
+
constructor(inner, transactionIdleTimeoutMs = DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS) {
|
|
179
|
+
this.inner = inner;
|
|
180
|
+
this.transactionIdleTimeoutMs = transactionIdleTimeoutMs;
|
|
181
|
+
}
|
|
182
|
+
send(request, clientId = this.leaderClientId) {
|
|
183
|
+
if (this.terminated) {
|
|
184
|
+
return Promise.resolve({
|
|
185
|
+
id: request.id,
|
|
186
|
+
type: "error",
|
|
187
|
+
message: "Worker has been terminated",
|
|
188
|
+
code: "WORKER_TERMINATED"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (this.abortedClients.has(clientId)) {
|
|
192
|
+
if (request.type === "begin") {
|
|
193
|
+
this.abortedClients.delete(clientId);
|
|
194
|
+
} else {
|
|
195
|
+
return Promise.resolve({
|
|
196
|
+
id: request.id,
|
|
197
|
+
type: "error",
|
|
198
|
+
message: "Previous transaction was aborted because the client stopped sending requests.",
|
|
199
|
+
code: "TRANSACTION_ABORTED"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
this.queue.push({ clientId, request, resolve, reject });
|
|
205
|
+
void this.processQueue();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
terminate() {
|
|
209
|
+
if (this.terminated) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.terminated = true;
|
|
213
|
+
this.clearTransactionIdleTimer();
|
|
214
|
+
this.inner.terminate();
|
|
215
|
+
const pending = this.queue.splice(0);
|
|
216
|
+
for (const entry of pending) {
|
|
217
|
+
entry.reject(new BridgeTerminatedError(entry.request.type, "worker terminated"));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
reclaimClient(clientId, reason) {
|
|
221
|
+
if (this.terminated) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
void this.reclaimClientNow(clientId, reason);
|
|
225
|
+
}
|
|
226
|
+
async processQueue() {
|
|
227
|
+
if (this.processing || this.reclaiming) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.processing = true;
|
|
231
|
+
try {
|
|
232
|
+
while (!this.terminated) {
|
|
233
|
+
const index = this.nextRunnableIndex();
|
|
234
|
+
if (index === -1) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const [entry] = this.queue.splice(index, 1);
|
|
238
|
+
if (!entry) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const response = await this.inner.send(entry.request);
|
|
243
|
+
this.recordTransactionState(entry, response);
|
|
244
|
+
entry.resolve(response);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
this.resetFailedTransaction(entry);
|
|
247
|
+
entry.reject(error instanceof Error ? error : new Error(String(error)));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} finally {
|
|
251
|
+
this.processing = false;
|
|
252
|
+
if (!this.terminated && !this.reclaiming && this.nextRunnableIndex() !== -1) {
|
|
253
|
+
void this.processQueue();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
nextRunnableIndex() {
|
|
258
|
+
if (this.queue.length === 0) {
|
|
259
|
+
return -1;
|
|
260
|
+
}
|
|
261
|
+
if (this.activeTransactionClient === null) {
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
return this.queue.findIndex((entry) => entry.clientId === this.activeTransactionClient);
|
|
265
|
+
}
|
|
266
|
+
recordTransactionState(entry, response) {
|
|
267
|
+
if (response.type === "error") {
|
|
268
|
+
this.resetFailedTransaction(entry);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (entry.request.type === "begin") {
|
|
272
|
+
this.activeTransactionClient = entry.clientId;
|
|
273
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
277
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
278
|
+
}
|
|
279
|
+
if (this.activeTransactionClient === entry.clientId && (entry.request.type === "commit" || entry.request.type === "rollback")) {
|
|
280
|
+
this.activeTransactionClient = null;
|
|
281
|
+
this.clearTransactionIdleTimer();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
resetFailedTransaction(entry) {
|
|
285
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
286
|
+
this.activeTransactionClient = null;
|
|
287
|
+
this.clearTransactionIdleTimer();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
armTransactionIdleTimer(clientId) {
|
|
291
|
+
this.clearTransactionIdleTimer();
|
|
292
|
+
this.transactionIdleTimer = setTimeout(() => {
|
|
293
|
+
void this.reclaimClientNow(clientId, "transaction-idle-timeout");
|
|
294
|
+
}, this.transactionIdleTimeoutMs);
|
|
295
|
+
}
|
|
296
|
+
clearTransactionIdleTimer() {
|
|
297
|
+
if (this.transactionIdleTimer) {
|
|
298
|
+
clearTimeout(this.transactionIdleTimer);
|
|
299
|
+
this.transactionIdleTimer = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async reclaimClientNow(clientId, reason) {
|
|
303
|
+
if (this.reclaiming || this.activeTransactionClient !== clientId) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this.reclaiming = true;
|
|
307
|
+
this.clearTransactionIdleTimer();
|
|
308
|
+
this.activeTransactionClient = null;
|
|
309
|
+
this.abortedClients.add(clientId);
|
|
310
|
+
this.rejectQueuedClientRequests(clientId, reason);
|
|
311
|
+
try {
|
|
312
|
+
const response = await this.inner.send({ id: 0, type: "rollback" });
|
|
313
|
+
if (response.type === "error") {
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
} finally {
|
|
317
|
+
this.reclaiming = false;
|
|
318
|
+
if (!this.terminated && this.nextRunnableIndex() !== -1) {
|
|
319
|
+
void this.processQueue();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
rejectQueuedClientRequests(clientId, reason) {
|
|
324
|
+
const keep = [];
|
|
325
|
+
for (const entry of this.queue) {
|
|
326
|
+
if (entry.clientId === clientId) {
|
|
327
|
+
entry.resolve({
|
|
328
|
+
id: entry.request.id,
|
|
329
|
+
type: "error",
|
|
330
|
+
message: `Transaction client was reclaimed: ${reason}`,
|
|
331
|
+
code: "TRANSACTION_ABORTED"
|
|
332
|
+
});
|
|
333
|
+
} else {
|
|
334
|
+
keep.push(entry);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
this.queue = keep;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
function hasReclaimClient(bridge) {
|
|
341
|
+
return typeof bridge.reclaimClient === "function";
|
|
342
|
+
}
|
|
343
|
+
function createClientId() {
|
|
344
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
345
|
+
return crypto.randomUUID();
|
|
346
|
+
}
|
|
347
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/migrations/schema-ceiling.ts
|
|
351
|
+
var DIRECTIVE = /^--kora:schema-ceiling (\d+)$/;
|
|
352
|
+
function parseSchemaCeiling(sql) {
|
|
353
|
+
const match = DIRECTIVE.exec(sql.trim());
|
|
354
|
+
return match ? Number(match[1]) : null;
|
|
355
|
+
}
|
|
356
|
+
function storedSchemaVersion(rows) {
|
|
357
|
+
const value = Number(rows[0]?.value ?? 0);
|
|
358
|
+
return Number.isFinite(value) ? value : 0;
|
|
359
|
+
}
|
|
360
|
+
function schemaAheadMessage(stored, code) {
|
|
361
|
+
return `KORA_SCHEMA_VERSION_AHEAD stored=${stored} code=${code}: the database was migrated by a newer build (schema version ${stored}); this build's schema version is ${code}.`;
|
|
362
|
+
}
|
|
363
|
+
var STORED_SCHEMA_VERSION_SQL = "SELECT value FROM _kora_meta WHERE key = 'schema_version'";
|
|
364
|
+
|
|
365
|
+
// src/adapters/opfs-pool-ownership.ts
|
|
366
|
+
var OPFS_POOL_HEADROOM = 2;
|
|
367
|
+
var BLOCKED_REPORT_AFTER_MS = 750;
|
|
368
|
+
var INSTALL_RACE_ATTEMPTS = 20;
|
|
27
369
|
var OPFS_INIT_TIMEOUT_MS = 1e4;
|
|
370
|
+
var OPAQUE_DIR_NAME = ".opaque";
|
|
371
|
+
var PoolUnavailableError = class extends Error {
|
|
372
|
+
constructor(message, reason) {
|
|
373
|
+
super(message);
|
|
374
|
+
this.reason = reason;
|
|
375
|
+
this.name = "PoolUnavailableError";
|
|
376
|
+
}
|
|
377
|
+
reason;
|
|
378
|
+
};
|
|
379
|
+
function opfsSahSupported(sqlite3) {
|
|
380
|
+
const g = globalThis;
|
|
381
|
+
const fileHandle = g.FileSystemFileHandle;
|
|
382
|
+
return typeof sqlite3.installOpfsSAHPoolVfs === "function" && typeof g.FileSystemDirectoryHandle !== "undefined" && typeof fileHandle?.prototype?.createSyncAccessHandle === "function" && typeof navigator !== "undefined" && typeof navigator.storage?.getDirectory === "function";
|
|
383
|
+
}
|
|
384
|
+
function sleep(ms) {
|
|
385
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
386
|
+
}
|
|
28
387
|
async function withTimeout(promise, timeoutMs, label) {
|
|
29
388
|
let timer;
|
|
30
389
|
try {
|
|
@@ -32,7 +391,7 @@ async function withTimeout(promise, timeoutMs, label) {
|
|
|
32
391
|
promise,
|
|
33
392
|
new Promise((_, reject) => {
|
|
34
393
|
timer = setTimeout(
|
|
35
|
-
() => reject(new
|
|
394
|
+
() => reject(new PoolUnavailableError(`${label} timed out after ${timeoutMs}ms`, "timeout")),
|
|
36
395
|
timeoutMs
|
|
37
396
|
);
|
|
38
397
|
})
|
|
@@ -41,25 +400,198 @@ async function withTimeout(promise, timeoutMs, label) {
|
|
|
41
400
|
if (timer !== void 0) clearTimeout(timer);
|
|
42
401
|
}
|
|
43
402
|
}
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
403
|
+
async function holdExclusiveLock(name, reporter) {
|
|
404
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
405
|
+
if (!locks || typeof locks.request !== "function") {
|
|
406
|
+
return () => {
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const grab = (ifAvailable) => new Promise((resolve, reject) => {
|
|
410
|
+
locks.request(name, { mode: "exclusive", ifAvailable }, (lock) => {
|
|
411
|
+
if (lock === null) {
|
|
412
|
+
resolve(null);
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
return new Promise((release2) => {
|
|
416
|
+
resolve(() => release2());
|
|
417
|
+
});
|
|
418
|
+
}).catch(reject);
|
|
419
|
+
});
|
|
420
|
+
const immediate = await grab(true);
|
|
421
|
+
if (immediate) return immediate;
|
|
422
|
+
const start = Date.now();
|
|
423
|
+
reporter?.blocked();
|
|
424
|
+
const release = await grab(false);
|
|
425
|
+
if (!release) {
|
|
426
|
+
throw new PoolUnavailableError(`Web Lock "${name}" was not granted`, "lock-conflict");
|
|
427
|
+
}
|
|
428
|
+
reporter?.unblocked(Date.now() - start);
|
|
429
|
+
return release;
|
|
430
|
+
}
|
|
431
|
+
async function opfsRoot() {
|
|
432
|
+
return await navigator.storage.getDirectory();
|
|
433
|
+
}
|
|
434
|
+
async function childDirectory(parent, name) {
|
|
435
|
+
try {
|
|
436
|
+
return await parent.getDirectoryHandle(name);
|
|
437
|
+
} catch {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function poolDirectoryExists(poolName) {
|
|
442
|
+
return await childDirectory(await opfsRoot(), opfsPoolDirectory(poolName)) !== null;
|
|
443
|
+
}
|
|
444
|
+
function isHandleBusy(error) {
|
|
445
|
+
const name = error?.name;
|
|
446
|
+
return name === "NoModificationAllowedError" || name === "InvalidStateError";
|
|
447
|
+
}
|
|
448
|
+
async function poolHandlesBusy(poolName) {
|
|
449
|
+
const dir = await childDirectory(await opfsRoot(), opfsPoolDirectory(poolName));
|
|
450
|
+
if (!dir) return false;
|
|
451
|
+
const opaque = await childDirectory(dir, OPAQUE_DIR_NAME);
|
|
452
|
+
if (!opaque) return false;
|
|
453
|
+
for await (const [, handle] of opaque.entries()) {
|
|
454
|
+
if (handle.kind !== "file") continue;
|
|
455
|
+
try {
|
|
456
|
+
const access = await handle.createSyncAccessHandle();
|
|
457
|
+
access.close();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (isHandleBusy(error)) return true;
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
async function waitForPoolHandles(poolName, reporter) {
|
|
466
|
+
const start = Date.now();
|
|
467
|
+
let reported = false;
|
|
468
|
+
let delay = 15;
|
|
469
|
+
while (await poolHandlesBusy(poolName)) {
|
|
470
|
+
if (!reported && Date.now() - start >= BLOCKED_REPORT_AFTER_MS) {
|
|
471
|
+
reported = true;
|
|
472
|
+
reporter?.blocked();
|
|
473
|
+
}
|
|
474
|
+
await sleep(delay);
|
|
475
|
+
delay = Math.min(delay * 2, 500);
|
|
476
|
+
}
|
|
477
|
+
if (reported) reporter?.unblocked(Date.now() - start);
|
|
478
|
+
}
|
|
479
|
+
async function installPool(sqlite3, poolName, reporter) {
|
|
480
|
+
const install = sqlite3.installOpfsSAHPoolVfs;
|
|
481
|
+
if (!install || !opfsSahSupported(sqlite3)) {
|
|
482
|
+
throw new PoolUnavailableError("OPFS SyncAccessHandle pool is not supported", "unsupported");
|
|
483
|
+
}
|
|
484
|
+
let lastError = null;
|
|
485
|
+
for (let attempt = 0; attempt < INSTALL_RACE_ATTEMPTS; attempt += 1) {
|
|
486
|
+
await waitForPoolHandles(poolName, reporter);
|
|
487
|
+
try {
|
|
488
|
+
const pool = await withTimeout(
|
|
489
|
+
install({ name: poolName, forceReinitIfPreviouslyFailed: true }),
|
|
490
|
+
OPFS_INIT_TIMEOUT_MS,
|
|
491
|
+
"OPFS VFS install"
|
|
492
|
+
);
|
|
493
|
+
if (pool.isPaused()) {
|
|
494
|
+
await pool.unpauseVfs();
|
|
495
|
+
}
|
|
496
|
+
return pool;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (error instanceof PoolUnavailableError) throw error;
|
|
499
|
+
lastError = error;
|
|
500
|
+
if (!isHandleBusy(error)) break;
|
|
501
|
+
await sleep(Math.min(25 * 2 ** attempt, 250));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
505
|
+
if (isHandleBusy(lastError)) {
|
|
506
|
+
throw new PoolUnavailableError(
|
|
507
|
+
`OPFS pool "${poolName}" stayed busy: ${message}`,
|
|
508
|
+
"lock-conflict"
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
throw new PoolUnavailableError(
|
|
512
|
+
`OPFS pool "${poolName}" could not be installed: ${message}`,
|
|
513
|
+
"unsupported"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
async function ensurePoolCapacity(pool) {
|
|
517
|
+
await pool.reserveMinimumCapacity(pool.getFileCount() + 2 + OPFS_POOL_HEADROOM);
|
|
47
518
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
519
|
+
function assertDatabaseHealthy(db, label) {
|
|
520
|
+
const rows = db.exec({
|
|
521
|
+
sql: "PRAGMA quick_check",
|
|
522
|
+
returnValue: "resultRows",
|
|
523
|
+
rowMode: "array"
|
|
524
|
+
});
|
|
525
|
+
const verdict = rows?.[0]?.[0];
|
|
526
|
+
if (verdict !== "ok") {
|
|
527
|
+
throw new Error(`${label} failed integrity check: ${String(verdict)}`);
|
|
52
528
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)) {
|
|
56
|
-
|
|
529
|
+
}
|
|
530
|
+
function pausePool(pool) {
|
|
531
|
+
if (!pool.isPaused()) {
|
|
532
|
+
pool.pauseVfs();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function migrateFromLegacyPool(sqlite3, target, poolPath, reporter) {
|
|
536
|
+
if (!await poolDirectoryExists(LEGACY_OPFS_POOL_NAME)) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
const releaseLegacyLock = await holdExclusiveLock(
|
|
540
|
+
opfsPoolLockName(LEGACY_OPFS_POOL_NAME),
|
|
541
|
+
reporter
|
|
542
|
+
);
|
|
543
|
+
try {
|
|
544
|
+
if (!await poolDirectoryExists(LEGACY_OPFS_POOL_NAME)) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
const legacy = await installPool(sqlite3, LEGACY_OPFS_POOL_NAME, reporter);
|
|
548
|
+
try {
|
|
549
|
+
if (!legacy.getFileNames().includes(poolPath)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const legacyDb = new legacy.OpfsSAHPoolDb(poolPath);
|
|
553
|
+
try {
|
|
554
|
+
assertDatabaseHealthy(legacyDb, `Legacy database ${poolPath}`);
|
|
555
|
+
} finally {
|
|
556
|
+
legacyDb.close();
|
|
557
|
+
}
|
|
558
|
+
const bytes = legacy.exportFile(poolPath);
|
|
559
|
+
if (bytes.byteLength === 0) {
|
|
560
|
+
legacy.unlink(poolPath);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
await ensurePoolCapacity(target);
|
|
564
|
+
await target.importDb(poolPath, bytes);
|
|
565
|
+
let migratedDb = null;
|
|
566
|
+
try {
|
|
567
|
+
migratedDb = new target.OpfsSAHPoolDb(poolPath);
|
|
568
|
+
assertDatabaseHealthy(migratedDb, `Migrated database ${poolPath}`);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
migratedDb?.close();
|
|
571
|
+
migratedDb = null;
|
|
572
|
+
target.unlink(poolPath);
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
migratedDb.close();
|
|
576
|
+
legacy.unlink(poolPath);
|
|
577
|
+
legacy.unlink(`${poolPath}-journal`);
|
|
578
|
+
return true;
|
|
579
|
+
} finally {
|
|
580
|
+
if (legacy.getFileCount() === 0) {
|
|
581
|
+
await legacy.removeVfs();
|
|
582
|
+
} else {
|
|
583
|
+
pausePool(legacy);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} finally {
|
|
587
|
+
releaseLegacyLock();
|
|
57
588
|
}
|
|
58
|
-
return "unsupported";
|
|
59
589
|
}
|
|
590
|
+
|
|
591
|
+
// src/adapters/sqlite-wasm-worker-core.ts
|
|
592
|
+
var PROGRESS_HEARTBEAT_MS = 500;
|
|
593
|
+
var PROGRESS_HANDLER_OPS = 2e4;
|
|
60
594
|
var sqlite3Promise = null;
|
|
61
|
-
var opfsPoolPromise = null;
|
|
62
|
-
var opfsFallbackReason;
|
|
63
595
|
async function loadSqlite3() {
|
|
64
596
|
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
65
597
|
const wasmUrl = globalThis.__KORA_SQLITE_WASM_URL;
|
|
@@ -67,7 +599,20 @@ async function loadSqlite3() {
|
|
|
67
599
|
locateFile: (file) => file.endsWith(".wasm") ? wasmUrl : file
|
|
68
600
|
} : void 0;
|
|
69
601
|
const initFn = sqlite3InitModule;
|
|
70
|
-
|
|
602
|
+
let timer;
|
|
603
|
+
try {
|
|
604
|
+
return await Promise.race([
|
|
605
|
+
initFn(initOptions),
|
|
606
|
+
new Promise((_, reject) => {
|
|
607
|
+
timer = setTimeout(
|
|
608
|
+
() => reject(new Error("SQLite3 module init timed out after 60000ms")),
|
|
609
|
+
6e4
|
|
610
|
+
);
|
|
611
|
+
})
|
|
612
|
+
]);
|
|
613
|
+
} finally {
|
|
614
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
615
|
+
}
|
|
71
616
|
}
|
|
72
617
|
function getSqlite3() {
|
|
73
618
|
if (!sqlite3Promise) {
|
|
@@ -75,64 +620,213 @@ function getSqlite3() {
|
|
|
75
620
|
}
|
|
76
621
|
return sqlite3Promise;
|
|
77
622
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
623
|
+
function createSqliteWasmCore(options = {}) {
|
|
624
|
+
let db = null;
|
|
625
|
+
let sqlite3Api = null;
|
|
626
|
+
let persistent = false;
|
|
627
|
+
let fallbackReason;
|
|
628
|
+
let pool = null;
|
|
629
|
+
let poolName = null;
|
|
630
|
+
let releasePoolLock = null;
|
|
631
|
+
let openInfo = {
|
|
632
|
+
created: false,
|
|
633
|
+
migratedFromLegacy: false
|
|
634
|
+
};
|
|
635
|
+
let lastProgressBeat = 0;
|
|
636
|
+
function reporterFor(resource, name) {
|
|
637
|
+
return {
|
|
638
|
+
blocked: () => options.postEvent?.({ kind: "storage-blocked", resource, poolName: name }),
|
|
639
|
+
unblocked: (waitedMs) => options.postEvent?.({ kind: "storage-unblocked", resource, poolName: name, waitedMs })
|
|
640
|
+
};
|
|
82
641
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
642
|
+
async function releaseStorage(mode) {
|
|
643
|
+
if (db) {
|
|
644
|
+
detachProgressHandler(db);
|
|
645
|
+
try {
|
|
646
|
+
db.close();
|
|
647
|
+
} finally {
|
|
648
|
+
db = null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (pool) {
|
|
652
|
+
if (mode === "remove") {
|
|
653
|
+
await pool.removeVfs();
|
|
654
|
+
} else {
|
|
655
|
+
pausePool(pool);
|
|
656
|
+
}
|
|
657
|
+
pool = null;
|
|
658
|
+
}
|
|
659
|
+
releasePoolLock?.();
|
|
660
|
+
releasePoolLock = null;
|
|
661
|
+
}
|
|
662
|
+
function attachProgressHandler(target) {
|
|
663
|
+
const register = sqlite3Api?.capi?.sqlite3_progress_handler;
|
|
664
|
+
if (!options.onProgress || typeof register !== "function" || !target.pointer) return;
|
|
665
|
+
register(
|
|
666
|
+
target.pointer,
|
|
667
|
+
PROGRESS_HANDLER_OPS,
|
|
668
|
+
() => {
|
|
669
|
+
const now = Date.now();
|
|
670
|
+
if (now - lastProgressBeat >= PROGRESS_HEARTBEAT_MS) {
|
|
671
|
+
lastProgressBeat = now;
|
|
672
|
+
options.onProgress?.();
|
|
673
|
+
}
|
|
674
|
+
return 0;
|
|
675
|
+
},
|
|
676
|
+
0
|
|
88
677
|
);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
opfsFallbackReason = classifyOpfsFailure(error);
|
|
91
|
-
return null;
|
|
92
678
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
679
|
+
function detachProgressHandler(target) {
|
|
680
|
+
const register = sqlite3Api?.capi?.sqlite3_progress_handler;
|
|
681
|
+
if (typeof register !== "function" || !target.pointer) return;
|
|
682
|
+
try {
|
|
683
|
+
register(target.pointer, 0, 0, 0);
|
|
684
|
+
} catch {
|
|
685
|
+
}
|
|
97
686
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
687
|
+
async function openOpfs(sqlite3, dbName, mustExist) {
|
|
688
|
+
const name = opfsPoolNameFor(dbName);
|
|
689
|
+
const path = opfsPoolPath(dbName);
|
|
690
|
+
poolName = name;
|
|
691
|
+
releasePoolLock = await holdExclusiveLock(opfsPoolLockName(name), reporterFor("pool", name));
|
|
692
|
+
pool = await installPool(sqlite3, name, reporterFor("pool", name));
|
|
693
|
+
let migratedFromLegacy = false;
|
|
694
|
+
if (!pool.getFileNames().includes(path)) {
|
|
695
|
+
migratedFromLegacy = await migrateFromLegacyPool(
|
|
696
|
+
sqlite3,
|
|
697
|
+
pool,
|
|
698
|
+
path,
|
|
699
|
+
reporterFor("legacy-pool", name)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const existedBefore = pool.getFileNames().includes(path);
|
|
703
|
+
if (!existedBefore && mustExist) {
|
|
704
|
+
await releaseStorage(pool.getFileCount() === 0 ? "remove" : "pause");
|
|
705
|
+
return { id: 0, type: "error", message: `Database file ${path} not found`, code: "NOT_FOUND" };
|
|
706
|
+
}
|
|
707
|
+
await ensurePoolCapacity(pool);
|
|
105
708
|
try {
|
|
106
|
-
|
|
709
|
+
db = new pool.OpfsSAHPoolDb(path);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
if (!existedBefore) pool.unlink(path);
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
persistent = true;
|
|
715
|
+
openInfo = { created: !existedBefore, migratedFromLegacy };
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
async function open(id, ddlStatements, dbName, storage, mustExist) {
|
|
719
|
+
if (db) {
|
|
720
|
+
try {
|
|
107
721
|
applyDdl(db, ddlStatements);
|
|
108
722
|
return { id, type: "success", data: buildOpenData() };
|
|
723
|
+
} catch (error) {
|
|
724
|
+
return { id, type: "error", message: error.message, code: "INIT_ERROR" };
|
|
109
725
|
}
|
|
726
|
+
}
|
|
727
|
+
let createdPath = null;
|
|
728
|
+
try {
|
|
110
729
|
const sqlite3 = await getSqlite3();
|
|
111
730
|
sqlite3Api = sqlite3;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
731
|
+
if (storage === "opfs" && opfsSahSupported(sqlite3)) {
|
|
732
|
+
try {
|
|
733
|
+
const early = await openOpfs(sqlite3, dbName, mustExist);
|
|
734
|
+
if (early) return { ...early, id };
|
|
735
|
+
if (openInfo.created) createdPath = opfsPoolPath(dbName);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
if (!(error instanceof PoolUnavailableError) || error.reason === "lock-conflict") {
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
await releaseStorage("pause");
|
|
741
|
+
fallbackReason = error.reason;
|
|
742
|
+
}
|
|
116
743
|
} else {
|
|
744
|
+
fallbackReason = "unsupported";
|
|
745
|
+
}
|
|
746
|
+
if (!db) {
|
|
117
747
|
db = new sqlite3.oo1.DB({ filename: ":memory:" });
|
|
118
748
|
persistent = false;
|
|
119
749
|
}
|
|
120
|
-
db.exec({ sql: "PRAGMA journal_mode = WAL" });
|
|
121
750
|
db.exec({ sql: "PRAGMA foreign_keys = ON" });
|
|
122
751
|
applyDdl(db, ddlStatements);
|
|
752
|
+
attachProgressHandler(db);
|
|
123
753
|
return { id, type: "success", data: buildOpenData() };
|
|
124
754
|
} catch (error) {
|
|
125
|
-
|
|
755
|
+
const currentPool = pool;
|
|
756
|
+
if (db) {
|
|
757
|
+
try {
|
|
758
|
+
db.close();
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
db = null;
|
|
762
|
+
}
|
|
763
|
+
if (createdPath && currentPool) {
|
|
764
|
+
try {
|
|
765
|
+
currentPool.unlink(createdPath);
|
|
766
|
+
currentPool.unlink(`${createdPath}-journal`);
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
await releaseStorage("pause");
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
const reason = error instanceof PoolUnavailableError ? error.reason : void 0;
|
|
775
|
+
return {
|
|
776
|
+
id,
|
|
777
|
+
type: "error",
|
|
778
|
+
message: error.message,
|
|
779
|
+
code: "INIT_ERROR",
|
|
780
|
+
context: {
|
|
781
|
+
...reason ? { reason } : {},
|
|
782
|
+
...createdPath ? { removedCreatedFile: createdPath } : {}
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function readJournalMode() {
|
|
788
|
+
if (!db) return void 0;
|
|
789
|
+
try {
|
|
790
|
+
let mode;
|
|
791
|
+
db.exec({
|
|
792
|
+
sql: "PRAGMA journal_mode",
|
|
793
|
+
rowMode: "object",
|
|
794
|
+
callback: (row) => {
|
|
795
|
+
if (typeof row.journal_mode === "string") mode = row.journal_mode;
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
return mode;
|
|
799
|
+
} catch {
|
|
800
|
+
return void 0;
|
|
126
801
|
}
|
|
127
802
|
}
|
|
128
803
|
function buildOpenData() {
|
|
804
|
+
const journalMode = readJournalMode();
|
|
129
805
|
return {
|
|
806
|
+
...journalMode ? { journalMode } : {},
|
|
130
807
|
persistent,
|
|
131
|
-
...persistent ? {} : { fallbackReason:
|
|
808
|
+
...persistent ? {} : { fallbackReason: fallbackReason ?? "unsupported" },
|
|
809
|
+
...poolName && persistent ? { poolName } : {},
|
|
810
|
+
created: openInfo.created,
|
|
811
|
+
migratedFromLegacy: openInfo.migratedFromLegacy
|
|
132
812
|
};
|
|
133
813
|
}
|
|
134
814
|
function applyDdl(target, ddlStatements) {
|
|
135
815
|
for (const sql of ddlStatements) {
|
|
816
|
+
const ceiling = parseSchemaCeiling(sql);
|
|
817
|
+
if (ceiling !== null) {
|
|
818
|
+
const rows = [];
|
|
819
|
+
target.exec({
|
|
820
|
+
sql: STORED_SCHEMA_VERSION_SQL,
|
|
821
|
+
rowMode: "object",
|
|
822
|
+
callback: (row) => {
|
|
823
|
+
rows.push({ value: row.value });
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
const stored = storedSchemaVersion(rows);
|
|
827
|
+
if (stored > ceiling) throw new Error(schemaAheadMessage(stored, ceiling));
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
136
830
|
if (sql.startsWith("--kora:safe-alter")) {
|
|
137
831
|
try {
|
|
138
832
|
target.exec({ sql: sql.replace("--kora:safe-alter\n", "") });
|
|
@@ -177,12 +871,13 @@ function createSqliteWasmCore() {
|
|
|
177
871
|
return { id, type: "error", message: error.message, code: "QUERY_ERROR" };
|
|
178
872
|
}
|
|
179
873
|
}
|
|
180
|
-
function close(id) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
874
|
+
async function close(id, mode) {
|
|
875
|
+
try {
|
|
876
|
+
await releaseStorage(mode);
|
|
877
|
+
return { id, type: "success" };
|
|
878
|
+
} catch (error) {
|
|
879
|
+
return { id, type: "error", message: error.message, code: "CLOSE_ERROR" };
|
|
184
880
|
}
|
|
185
|
-
return { id, type: "success" };
|
|
186
881
|
}
|
|
187
882
|
function migrate(id, statements) {
|
|
188
883
|
if (!db) {
|
|
@@ -201,10 +896,9 @@ function createSqliteWasmCore() {
|
|
|
201
896
|
if (!db) {
|
|
202
897
|
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
203
898
|
}
|
|
204
|
-
|
|
205
|
-
if (typeof dbWithDeserialize.deserialize === "function") {
|
|
899
|
+
if (typeof db.deserialize === "function") {
|
|
206
900
|
try {
|
|
207
|
-
|
|
901
|
+
db.deserialize(data);
|
|
208
902
|
return { id, type: "success" };
|
|
209
903
|
} catch (error) {
|
|
210
904
|
return { id, type: "error", message: error.message, code: "IMPORT_ERROR" };
|
|
@@ -229,9 +923,17 @@ function createSqliteWasmCore() {
|
|
|
229
923
|
try {
|
|
230
924
|
switch (request.type) {
|
|
231
925
|
case "open":
|
|
232
|
-
return await open(
|
|
926
|
+
return await open(
|
|
927
|
+
request.id,
|
|
928
|
+
request.ddlStatements,
|
|
929
|
+
request.dbName ?? "kora-db",
|
|
930
|
+
request.storage ?? "opfs",
|
|
931
|
+
request.mustExist === true
|
|
932
|
+
);
|
|
233
933
|
case "close":
|
|
234
|
-
return close(request.id);
|
|
934
|
+
return await close(request.id, "pause");
|
|
935
|
+
case "destroy":
|
|
936
|
+
return await close(request.id, "remove");
|
|
235
937
|
case "execute":
|
|
236
938
|
return execute(request.id, request.sql, request.params);
|
|
237
939
|
case "query":
|
|
@@ -253,6 +955,8 @@ function createSqliteWasmCore() {
|
|
|
253
955
|
message: "Export not yet supported in browser worker",
|
|
254
956
|
code: "EXPORT_NOT_SUPPORTED"
|
|
255
957
|
};
|
|
958
|
+
case "serve":
|
|
959
|
+
return { id: request.id, type: "success" };
|
|
256
960
|
default:
|
|
257
961
|
return {
|
|
258
962
|
id: request.id,
|
|
@@ -274,10 +978,47 @@ function createSqliteWasmCore() {
|
|
|
274
978
|
}
|
|
275
979
|
|
|
276
980
|
// src/adapters/sqlite-wasm-worker.ts
|
|
277
|
-
var
|
|
981
|
+
var relay = null;
|
|
982
|
+
var core = createSqliteWasmCore({
|
|
983
|
+
postEvent: (event) => {
|
|
984
|
+
const message = { id: -1, type: "event", event };
|
|
985
|
+
self.postMessage(message);
|
|
986
|
+
},
|
|
987
|
+
// Keep followers' watchdogs fed while a long statement blocks this worker.
|
|
988
|
+
onProgress: () => relay?.beat()
|
|
989
|
+
});
|
|
990
|
+
var serializer = new TransactionSerializingWorkerBridge({
|
|
991
|
+
send: (request) => core.handle(request),
|
|
992
|
+
terminate: () => {
|
|
993
|
+
}
|
|
994
|
+
});
|
|
278
995
|
self.onmessage = (event) => {
|
|
279
|
-
|
|
996
|
+
const { clientId, ...request } = event.data;
|
|
997
|
+
if (request.type === "serve") {
|
|
998
|
+
if (!relay) {
|
|
999
|
+
relay = startWorkerRpcRelay(request.channelName, serializer);
|
|
1000
|
+
}
|
|
1001
|
+
const response = { id: request.id, type: "success" };
|
|
280
1002
|
self.postMessage(response);
|
|
281
|
-
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
void serializer.send(request, clientId).then(
|
|
1006
|
+
(response) => {
|
|
1007
|
+
if (request.type === "close" || request.type === "destroy") {
|
|
1008
|
+
relay?.stop();
|
|
1009
|
+
relay = null;
|
|
1010
|
+
}
|
|
1011
|
+
self.postMessage(response);
|
|
1012
|
+
},
|
|
1013
|
+
(error) => {
|
|
1014
|
+
const response = {
|
|
1015
|
+
id: request.id,
|
|
1016
|
+
type: "error",
|
|
1017
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1018
|
+
code: "WORKER_ERROR"
|
|
1019
|
+
};
|
|
1020
|
+
self.postMessage(response);
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
282
1023
|
};
|
|
283
1024
|
//# sourceMappingURL=sqlite-wasm-worker.cjs.map
|