@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
|
|
2
|
-
import { S as StorageAdapter,
|
|
3
|
-
import { W as WorkerBridge } from '../sqlite-wasm-channel-
|
|
4
|
-
export { a as WebWorkerBridge, b as WorkerRequest, c as WorkerResponse } from '../sqlite-wasm-channel-
|
|
2
|
+
import { S as StorageAdapter, T as Transaction, a as StorageOpenState, M as MigrationPlan } from '../types-Dh-xyUjP.cjs';
|
|
3
|
+
import { W as WorkerBridge } from '../sqlite-wasm-channel-DVX93Uiq.cjs';
|
|
4
|
+
export { a as WebWorkerBridge, b as WorkerRequest, c as WorkerResponse, d as WorkerSendOptions, e as WorkerStatusEvent } from '../sqlite-wasm-channel-DVX93Uiq.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Async mutex for serializing access to a resource across `await` boundaries.
|
|
@@ -26,6 +26,19 @@ declare class Mutex {
|
|
|
26
26
|
private createRelease;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Per-request options for {@link SqliteWasmAdapter.execute} and `query`. */
|
|
30
|
+
interface StorageRequestOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Stop waiting for the response. The leader may still apply a write that
|
|
33
|
+
* already reached it; retry with the same `requestId` to stay idempotent.
|
|
34
|
+
*/
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
/**
|
|
37
|
+
* Stable id for this request. A follower tab's retry with the same id is
|
|
38
|
+
* answered from the leader's response cache instead of being applied twice.
|
|
39
|
+
*/
|
|
40
|
+
requestId?: string;
|
|
41
|
+
}
|
|
29
42
|
/**
|
|
30
43
|
* Options for creating a SqliteWasmAdapter.
|
|
31
44
|
*/
|
|
@@ -36,7 +49,7 @@ interface SqliteWasmAdapterOptions {
|
|
|
36
49
|
*/
|
|
37
50
|
bridge?: WorkerBridge;
|
|
38
51
|
/**
|
|
39
|
-
* Database name for persistence.
|
|
52
|
+
* Database name for persistence. Each database gets its own OPFS pool.
|
|
40
53
|
*/
|
|
41
54
|
dbName?: string;
|
|
42
55
|
/**
|
|
@@ -58,18 +71,66 @@ interface SqliteWasmAdapterOptions {
|
|
|
58
71
|
*/
|
|
59
72
|
emitNonPersistentDiagnostic?: boolean;
|
|
60
73
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `store:
|
|
64
|
-
*
|
|
74
|
+
* Accept writes when durable OPFS storage could not be obtained and the
|
|
75
|
+
* database runs in memory. Defaults to false: a non-durable open or promotion
|
|
76
|
+
* emits the blocking `store:durability-lost` event and every write is refused
|
|
77
|
+
* with {@link StorageDurabilityError}, so data is never accepted and then lost
|
|
78
|
+
* on reload. Set true only for apps that can tolerate losing local writes.
|
|
79
|
+
*/
|
|
80
|
+
allowNonDurable?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* For `createApp()`: skip the durability check at open only, because the caller
|
|
83
|
+
* inspects {@link SqliteWasmAdapter.getStorageOpenState} right after open and
|
|
84
|
+
* switches to a durable backend itself. A later leader promotion that loses
|
|
85
|
+
* durable storage is still enforced.
|
|
86
|
+
*/
|
|
87
|
+
deferOpenDurabilityCheck?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Release storage ownership when the page is frozen or hidden into the
|
|
90
|
+
* back/forward cache (`freeze` / `pagehide`), so a visible tab can take over
|
|
91
|
+
* instead of waiting on a tab that cannot run; ownership is re-acquired on
|
|
92
|
+
* `resume` / `pageshow`. Defaults to true.
|
|
93
|
+
*/
|
|
94
|
+
releaseOnFreeze?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* When set, storage diagnostics are emitted here: `store:opfs-unavailable`,
|
|
97
|
+
* `store:durability-lost`, `store:storage-blocked` (another holder has the
|
|
98
|
+
* storage; the open waits), `store:storage-migrated` and
|
|
99
|
+
* `store:db-name-collision`.
|
|
65
100
|
*/
|
|
66
101
|
emitter?: KoraEventEmitter;
|
|
102
|
+
/**
|
|
103
|
+
* @internal Used by the IndexedDB adapter: open an in-memory database that
|
|
104
|
+
* never touches OPFS (the IndexedDB adapter persists snapshots itself).
|
|
105
|
+
*/
|
|
106
|
+
storage?: 'opfs' | 'memory';
|
|
107
|
+
/**
|
|
108
|
+
* @internal Do not read or write the storage manifest (the IndexedDB adapter's
|
|
109
|
+
* inner database and its one-shot OPFS reader manage the manifest themselves).
|
|
110
|
+
*/
|
|
111
|
+
skipBackendRecord?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* @internal Used by the IndexedDB adapter (STORE-6): runs in one transaction on this
|
|
114
|
+
* tab's OWN worker right after it opened the database as storage leader (first open
|
|
115
|
+
* or promotion), before the worker serves any other tab. The IndexedDB adapter
|
|
116
|
+
* restores its persisted snapshot here, so a snapshot is only ever restored into a
|
|
117
|
+
* freshly created worker database and never over a leader's live one. Throwing fails
|
|
118
|
+
* the open (or the promotion, which then refuses writes).
|
|
119
|
+
*/
|
|
120
|
+
onLeaderWorkerOpened?: (tx: Transaction) => Promise<void>;
|
|
67
121
|
}
|
|
68
122
|
/**
|
|
69
123
|
* SQLite WASM adapter that communicates with a SQLite instance through a WorkerBridge.
|
|
70
124
|
*
|
|
71
|
-
* In browsers,
|
|
72
|
-
*
|
|
125
|
+
* In browsers, one tab per database (the leader) runs a dedicated worker that
|
|
126
|
+
* owns the database's OPFS pool; other tabs reach that worker over a
|
|
127
|
+
* BroadcastChannel. In Node.js tests, the bridge is backed by MockWorkerBridge
|
|
128
|
+
* wrapping better-sqlite3.
|
|
129
|
+
*
|
|
130
|
+
* Storage is durable or loud, never silently in memory (W8a): another holder of
|
|
131
|
+
* the storage makes the open wait (reported with `store:storage-blocked`); only a
|
|
132
|
+
* runtime without OPFS SAH support gets a non-durable database, which refuses
|
|
133
|
+
* writes unless the app opted in with `allowNonDurable`.
|
|
73
134
|
*
|
|
74
135
|
* @example
|
|
75
136
|
* ```typescript
|
|
@@ -83,6 +144,8 @@ interface SqliteWasmAdapterOptions {
|
|
|
83
144
|
*/
|
|
84
145
|
declare class SqliteWasmAdapter implements StorageAdapter {
|
|
85
146
|
private bridge;
|
|
147
|
+
/** The dedicated worker this tab runs while it is the database's leader. */
|
|
148
|
+
private ownWorker;
|
|
86
149
|
private opened;
|
|
87
150
|
private readonly mutex;
|
|
88
151
|
private readonly injectedBridge;
|
|
@@ -97,10 +160,45 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
97
160
|
/** Retained so a follower promoted to leader can re-open its own worker. */
|
|
98
161
|
private schema;
|
|
99
162
|
private promoting;
|
|
163
|
+
private closing;
|
|
164
|
+
private suspended;
|
|
165
|
+
/** Requests wait here while leadership changes hands (promotion, resume). */
|
|
166
|
+
private gate;
|
|
167
|
+
private removeLifecycleListeners;
|
|
168
|
+
private readonly allowNonDurable;
|
|
169
|
+
private readonly deferOpenDurabilityCheck;
|
|
170
|
+
private readonly releaseOnFreeze;
|
|
171
|
+
private readonly storage;
|
|
172
|
+
private readonly skipBackendRecord;
|
|
173
|
+
/** Set when this adapter lost (or never had) durable storage; writes are refused. */
|
|
174
|
+
private durabilityLoss;
|
|
175
|
+
private readonly onLeaderWorkerOpened;
|
|
100
176
|
constructor(options?: SqliteWasmAdapterOptions);
|
|
101
177
|
open(schema: SchemaDefinition): Promise<void>;
|
|
102
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Join the database as leader (own worker) or follower (RPC to the leader's
|
|
180
|
+
* worker), then open it. The open waits, without a timeout, while another
|
|
181
|
+
* holder still has the storage; the worker reports that as a blocking state.
|
|
182
|
+
*/
|
|
183
|
+
private attachAndOpen;
|
|
184
|
+
/** Open the database in this tab's own worker and start serving followers. */
|
|
185
|
+
private openLeaderWorker;
|
|
186
|
+
/**
|
|
187
|
+
* Whether this runtime owns the database's worker: the storage leader in a browser,
|
|
188
|
+
* or any adapter with an injected bridge (tests, Node). A follower tab relays every
|
|
189
|
+
* request to the leader's worker (STORE-6).
|
|
190
|
+
*/
|
|
191
|
+
isLeader(): boolean;
|
|
192
|
+
private checkedOpen;
|
|
103
193
|
private openCurrentBridge;
|
|
194
|
+
private onWorkerEvent;
|
|
195
|
+
/**
|
|
196
|
+
* Leader-only bookkeeping after a durable open: report a legacy-pool
|
|
197
|
+
* migration, move an IndexedDB-held copy into OPFS explicitly (never two
|
|
198
|
+
* disjoint copies), and record OPFS as this database's backend.
|
|
199
|
+
*/
|
|
200
|
+
private afterLeaderOpen;
|
|
201
|
+
private applyDumpThroughWorker;
|
|
104
202
|
/**
|
|
105
203
|
* Surface a `store:opfs-unavailable` diagnostic when the worker reported that
|
|
106
204
|
* persistence silently degraded to an in-memory database, so a data-loss
|
|
@@ -109,18 +207,58 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
109
207
|
*/
|
|
110
208
|
private reportStorageMode;
|
|
111
209
|
getStorageOpenState(): StorageOpenState | null;
|
|
210
|
+
/**
|
|
211
|
+
* Durable or loud, never silent (NEW-STORE-6). When the open or a promotion
|
|
212
|
+
* ended on non-persistent storage and the app did not opt into that, emit the
|
|
213
|
+
* blocking `store:durability-lost` event and refuse every later write. A
|
|
214
|
+
* bridge that reports no mode (the Node mock) is not judged.
|
|
215
|
+
*/
|
|
216
|
+
private enforceDurability;
|
|
217
|
+
private guardDurableWrite;
|
|
112
218
|
private warnSharedWorkerDeprecated;
|
|
219
|
+
/**
|
|
220
|
+
* Close the database. A leader closes the database file and releases the OPFS
|
|
221
|
+
* pool inside its worker, terminates the worker, and only then releases the
|
|
222
|
+
* tab leader lock, so the next leader never races a still-running worker for
|
|
223
|
+
* the file handles (NEW-STORE-10). A follower just leaves.
|
|
224
|
+
*/
|
|
113
225
|
close(): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Release everything this adapter holds, in ownership order: lifecycle
|
|
228
|
+
* listeners, then the worker (which holds the OPFS pool and its Web Lock),
|
|
229
|
+
* then the tab leader lock or queued promotion request.
|
|
230
|
+
*/
|
|
231
|
+
private teardown;
|
|
114
232
|
/**
|
|
115
233
|
* Promotes a follower to leader after the previous leader released the storage
|
|
116
|
-
* lock (its tab closed or
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
234
|
+
* lock (its tab closed, crashed, or suspended on freeze). The new worker waits
|
|
235
|
+
* on the pool's Web Lock and handles, so it never races the previous worker.
|
|
236
|
+
* Requests issued during the hand-off wait for it; requests that were in
|
|
237
|
+
* flight to the old leader fail with a retriable `BridgeTerminatedError`.
|
|
120
238
|
*/
|
|
121
239
|
private promoteToLeader;
|
|
122
|
-
|
|
123
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Page Lifecycle: a frozen or bfcached page cannot run, so it must not keep
|
|
242
|
+
* other tabs waiting on its storage. Leaders terminate their worker (freeing
|
|
243
|
+
* the OPFS pool) and then release the leader lock, so a visible tab is
|
|
244
|
+
* promoted; followers withdraw their queued lock request so a frozen tab is
|
|
245
|
+
* never granted leadership it cannot use. Never uses Web Locks `steal`.
|
|
246
|
+
*/
|
|
247
|
+
private installLifecycleListeners;
|
|
248
|
+
/** Synchronous by necessity: a `freeze` handler gets no further turns. */
|
|
249
|
+
private suspend;
|
|
250
|
+
private resume;
|
|
251
|
+
/**
|
|
252
|
+
* Non-transactional write. Takes the same mutex as {@link transaction}, so it
|
|
253
|
+
* never lands inside this tab's open transaction and is never rolled back
|
|
254
|
+
* with it (STORE-8). Inside a transaction callback, use the `tx` handle.
|
|
255
|
+
*/
|
|
256
|
+
execute(sql: string, params?: unknown[], options?: StorageRequestOptions): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Non-transactional read. Waits for this tab's open transaction to finish, so
|
|
259
|
+
* it never observes uncommitted (possibly rolled-back) rows (STORE-8).
|
|
260
|
+
*/
|
|
261
|
+
query<T>(sql: string, params?: unknown[], options?: StorageRequestOptions): Promise<T[]>;
|
|
124
262
|
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
|
|
125
263
|
migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
|
|
126
264
|
/**
|
|
@@ -137,4 +275,87 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
137
275
|
private sendChecked;
|
|
138
276
|
}
|
|
139
277
|
|
|
140
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Kora-owned manifest of the local databases this origin created (W8a).
|
|
280
|
+
*
|
|
281
|
+
* One record per database name, kept in its own IndexedDB database so it is
|
|
282
|
+
* readable from any tab without opening the OPFS pools. It records which backend
|
|
283
|
+
* holds the database's data, which is how Kora keeps OPFS and IndexedDB from
|
|
284
|
+
* ever holding two disjoint copies: a backend switch is a recorded, explicit
|
|
285
|
+
* migration, never a silent fresh start (NEW-STORE-5, LMS-5c).
|
|
286
|
+
*
|
|
287
|
+
* The manifest is advisory for listing and authoritative for the backend
|
|
288
|
+
* choice. Nothing is ever evicted automatically; deletion is the explicit
|
|
289
|
+
* `deleteDatabase()` API.
|
|
290
|
+
*/
|
|
291
|
+
/** Where a database's data lives. */
|
|
292
|
+
type LocalDatabaseBackend = 'opfs' | 'indexeddb';
|
|
293
|
+
/** One manifest entry, as returned by `listDatabases()`. */
|
|
294
|
+
interface LocalDatabaseRecord {
|
|
295
|
+
/** The store name (`store.name`, plus the per-user suffix if namespaced). */
|
|
296
|
+
dbName: string;
|
|
297
|
+
/** The backend that holds the authoritative copy. */
|
|
298
|
+
backend: LocalDatabaseBackend;
|
|
299
|
+
/** OPFS pool holding the file (opfs backend only). */
|
|
300
|
+
poolName?: string;
|
|
301
|
+
/** Wall-clock milliseconds; informational only, never used for ordering. */
|
|
302
|
+
createdAt: number;
|
|
303
|
+
/** Wall-clock milliseconds of the last durable open; informational only. */
|
|
304
|
+
lastOpenedAt: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Explicit management of the local databases on this origin (W8a, NEW-STORE-7).
|
|
309
|
+
*
|
|
310
|
+
* Kora never evicts a database on its own: a full pool is prevented by
|
|
311
|
+
* per-database pools and capacity reservation, and removing data is always an
|
|
312
|
+
* explicit call that refuses while the database is open or still holds
|
|
313
|
+
* operations the server has not acknowledged.
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
/** Read-only access to a database handed to the unsynced-data check. */
|
|
317
|
+
interface LocalDatabaseReader {
|
|
318
|
+
dbName: string;
|
|
319
|
+
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
320
|
+
}
|
|
321
|
+
/** Options for {@link deleteLocalDatabase}. */
|
|
322
|
+
interface DeleteLocalDatabaseOptions {
|
|
323
|
+
/** URL of the Kora SQLite worker (the same one the app's store uses). */
|
|
324
|
+
workerUrl: string | URL;
|
|
325
|
+
/**
|
|
326
|
+
* Decides whether the database still holds operations the server never
|
|
327
|
+
* acknowledged. Injected by the app layer, which knows its sync setup.
|
|
328
|
+
* When it returns true the delete is refused with {@link UnsyncedDataError}.
|
|
329
|
+
*/
|
|
330
|
+
hasUnsyncedOperations?: (db: LocalDatabaseReader) => Promise<boolean>;
|
|
331
|
+
/** Delete even when unsynced operations exist (they are lost). */
|
|
332
|
+
force?: boolean;
|
|
333
|
+
/** Worker response timeout. Defaults to 30000ms. */
|
|
334
|
+
workerResponseTimeoutMs?: number;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Databases Kora has recorded on this origin (created or opened since W8a).
|
|
338
|
+
* A database still in the pre-W8a shared pool appears after its first open.
|
|
339
|
+
*/
|
|
340
|
+
declare function listLocalDatabases(): Promise<LocalDatabaseRecord[]>;
|
|
341
|
+
/**
|
|
342
|
+
* Permanently delete a local database and its OPFS pool or IndexedDB copy.
|
|
343
|
+
*
|
|
344
|
+
* Refuses with {@link StorageInUseError} while any tab has the database open,
|
|
345
|
+
* and with {@link UnsyncedDataError} while `hasUnsyncedOperations` reports
|
|
346
|
+
* unsynced operations (unless `force`). Never touches any other database.
|
|
347
|
+
*
|
|
348
|
+
* @returns true when something was deleted, false when the database did not exist
|
|
349
|
+
*/
|
|
350
|
+
declare function deleteLocalDatabase(dbName: string, options: DeleteLocalDatabaseOptions): Promise<boolean>;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The OPFS SAH pool that owns `dbName`. Database names that are already safe
|
|
354
|
+
* map to `kora-opfs-<dbName>`; anything sanitized or truncated gets a hash suffix
|
|
355
|
+
* so two distinct names can never share a pool.
|
|
356
|
+
*
|
|
357
|
+
* @example opfsPoolNameFor('school__user_alice') === 'kora-opfs-school__user_alice'
|
|
358
|
+
*/
|
|
359
|
+
declare function opfsPoolNameFor(dbName: string): string;
|
|
360
|
+
|
|
361
|
+
export { type DeleteLocalDatabaseOptions, type LocalDatabaseBackend, type LocalDatabaseReader, type LocalDatabaseRecord, Mutex, SqliteWasmAdapter, type SqliteWasmAdapterOptions, type StorageRequestOptions, WorkerBridge, deleteLocalDatabase, listLocalDatabases, opfsPoolNameFor };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
|
|
2
|
-
import { S as StorageAdapter,
|
|
3
|
-
import { W as WorkerBridge } from '../sqlite-wasm-channel-
|
|
4
|
-
export { a as WebWorkerBridge, b as WorkerRequest, c as WorkerResponse } from '../sqlite-wasm-channel-
|
|
2
|
+
import { S as StorageAdapter, T as Transaction, a as StorageOpenState, M as MigrationPlan } from '../types-Dh-xyUjP.js';
|
|
3
|
+
import { W as WorkerBridge } from '../sqlite-wasm-channel-DVX93Uiq.js';
|
|
4
|
+
export { a as WebWorkerBridge, b as WorkerRequest, c as WorkerResponse, d as WorkerSendOptions, e as WorkerStatusEvent } from '../sqlite-wasm-channel-DVX93Uiq.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Async mutex for serializing access to a resource across `await` boundaries.
|
|
@@ -26,6 +26,19 @@ declare class Mutex {
|
|
|
26
26
|
private createRelease;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Per-request options for {@link SqliteWasmAdapter.execute} and `query`. */
|
|
30
|
+
interface StorageRequestOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Stop waiting for the response. The leader may still apply a write that
|
|
33
|
+
* already reached it; retry with the same `requestId` to stay idempotent.
|
|
34
|
+
*/
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
/**
|
|
37
|
+
* Stable id for this request. A follower tab's retry with the same id is
|
|
38
|
+
* answered from the leader's response cache instead of being applied twice.
|
|
39
|
+
*/
|
|
40
|
+
requestId?: string;
|
|
41
|
+
}
|
|
29
42
|
/**
|
|
30
43
|
* Options for creating a SqliteWasmAdapter.
|
|
31
44
|
*/
|
|
@@ -36,7 +49,7 @@ interface SqliteWasmAdapterOptions {
|
|
|
36
49
|
*/
|
|
37
50
|
bridge?: WorkerBridge;
|
|
38
51
|
/**
|
|
39
|
-
* Database name for persistence.
|
|
52
|
+
* Database name for persistence. Each database gets its own OPFS pool.
|
|
40
53
|
*/
|
|
41
54
|
dbName?: string;
|
|
42
55
|
/**
|
|
@@ -58,18 +71,66 @@ interface SqliteWasmAdapterOptions {
|
|
|
58
71
|
*/
|
|
59
72
|
emitNonPersistentDiagnostic?: boolean;
|
|
60
73
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `store:
|
|
64
|
-
*
|
|
74
|
+
* Accept writes when durable OPFS storage could not be obtained and the
|
|
75
|
+
* database runs in memory. Defaults to false: a non-durable open or promotion
|
|
76
|
+
* emits the blocking `store:durability-lost` event and every write is refused
|
|
77
|
+
* with {@link StorageDurabilityError}, so data is never accepted and then lost
|
|
78
|
+
* on reload. Set true only for apps that can tolerate losing local writes.
|
|
79
|
+
*/
|
|
80
|
+
allowNonDurable?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* For `createApp()`: skip the durability check at open only, because the caller
|
|
83
|
+
* inspects {@link SqliteWasmAdapter.getStorageOpenState} right after open and
|
|
84
|
+
* switches to a durable backend itself. A later leader promotion that loses
|
|
85
|
+
* durable storage is still enforced.
|
|
86
|
+
*/
|
|
87
|
+
deferOpenDurabilityCheck?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Release storage ownership when the page is frozen or hidden into the
|
|
90
|
+
* back/forward cache (`freeze` / `pagehide`), so a visible tab can take over
|
|
91
|
+
* instead of waiting on a tab that cannot run; ownership is re-acquired on
|
|
92
|
+
* `resume` / `pageshow`. Defaults to true.
|
|
93
|
+
*/
|
|
94
|
+
releaseOnFreeze?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* When set, storage diagnostics are emitted here: `store:opfs-unavailable`,
|
|
97
|
+
* `store:durability-lost`, `store:storage-blocked` (another holder has the
|
|
98
|
+
* storage; the open waits), `store:storage-migrated` and
|
|
99
|
+
* `store:db-name-collision`.
|
|
65
100
|
*/
|
|
66
101
|
emitter?: KoraEventEmitter;
|
|
102
|
+
/**
|
|
103
|
+
* @internal Used by the IndexedDB adapter: open an in-memory database that
|
|
104
|
+
* never touches OPFS (the IndexedDB adapter persists snapshots itself).
|
|
105
|
+
*/
|
|
106
|
+
storage?: 'opfs' | 'memory';
|
|
107
|
+
/**
|
|
108
|
+
* @internal Do not read or write the storage manifest (the IndexedDB adapter's
|
|
109
|
+
* inner database and its one-shot OPFS reader manage the manifest themselves).
|
|
110
|
+
*/
|
|
111
|
+
skipBackendRecord?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* @internal Used by the IndexedDB adapter (STORE-6): runs in one transaction on this
|
|
114
|
+
* tab's OWN worker right after it opened the database as storage leader (first open
|
|
115
|
+
* or promotion), before the worker serves any other tab. The IndexedDB adapter
|
|
116
|
+
* restores its persisted snapshot here, so a snapshot is only ever restored into a
|
|
117
|
+
* freshly created worker database and never over a leader's live one. Throwing fails
|
|
118
|
+
* the open (or the promotion, which then refuses writes).
|
|
119
|
+
*/
|
|
120
|
+
onLeaderWorkerOpened?: (tx: Transaction) => Promise<void>;
|
|
67
121
|
}
|
|
68
122
|
/**
|
|
69
123
|
* SQLite WASM adapter that communicates with a SQLite instance through a WorkerBridge.
|
|
70
124
|
*
|
|
71
|
-
* In browsers,
|
|
72
|
-
*
|
|
125
|
+
* In browsers, one tab per database (the leader) runs a dedicated worker that
|
|
126
|
+
* owns the database's OPFS pool; other tabs reach that worker over a
|
|
127
|
+
* BroadcastChannel. In Node.js tests, the bridge is backed by MockWorkerBridge
|
|
128
|
+
* wrapping better-sqlite3.
|
|
129
|
+
*
|
|
130
|
+
* Storage is durable or loud, never silently in memory (W8a): another holder of
|
|
131
|
+
* the storage makes the open wait (reported with `store:storage-blocked`); only a
|
|
132
|
+
* runtime without OPFS SAH support gets a non-durable database, which refuses
|
|
133
|
+
* writes unless the app opted in with `allowNonDurable`.
|
|
73
134
|
*
|
|
74
135
|
* @example
|
|
75
136
|
* ```typescript
|
|
@@ -83,6 +144,8 @@ interface SqliteWasmAdapterOptions {
|
|
|
83
144
|
*/
|
|
84
145
|
declare class SqliteWasmAdapter implements StorageAdapter {
|
|
85
146
|
private bridge;
|
|
147
|
+
/** The dedicated worker this tab runs while it is the database's leader. */
|
|
148
|
+
private ownWorker;
|
|
86
149
|
private opened;
|
|
87
150
|
private readonly mutex;
|
|
88
151
|
private readonly injectedBridge;
|
|
@@ -97,10 +160,45 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
97
160
|
/** Retained so a follower promoted to leader can re-open its own worker. */
|
|
98
161
|
private schema;
|
|
99
162
|
private promoting;
|
|
163
|
+
private closing;
|
|
164
|
+
private suspended;
|
|
165
|
+
/** Requests wait here while leadership changes hands (promotion, resume). */
|
|
166
|
+
private gate;
|
|
167
|
+
private removeLifecycleListeners;
|
|
168
|
+
private readonly allowNonDurable;
|
|
169
|
+
private readonly deferOpenDurabilityCheck;
|
|
170
|
+
private readonly releaseOnFreeze;
|
|
171
|
+
private readonly storage;
|
|
172
|
+
private readonly skipBackendRecord;
|
|
173
|
+
/** Set when this adapter lost (or never had) durable storage; writes are refused. */
|
|
174
|
+
private durabilityLoss;
|
|
175
|
+
private readonly onLeaderWorkerOpened;
|
|
100
176
|
constructor(options?: SqliteWasmAdapterOptions);
|
|
101
177
|
open(schema: SchemaDefinition): Promise<void>;
|
|
102
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Join the database as leader (own worker) or follower (RPC to the leader's
|
|
180
|
+
* worker), then open it. The open waits, without a timeout, while another
|
|
181
|
+
* holder still has the storage; the worker reports that as a blocking state.
|
|
182
|
+
*/
|
|
183
|
+
private attachAndOpen;
|
|
184
|
+
/** Open the database in this tab's own worker and start serving followers. */
|
|
185
|
+
private openLeaderWorker;
|
|
186
|
+
/**
|
|
187
|
+
* Whether this runtime owns the database's worker: the storage leader in a browser,
|
|
188
|
+
* or any adapter with an injected bridge (tests, Node). A follower tab relays every
|
|
189
|
+
* request to the leader's worker (STORE-6).
|
|
190
|
+
*/
|
|
191
|
+
isLeader(): boolean;
|
|
192
|
+
private checkedOpen;
|
|
103
193
|
private openCurrentBridge;
|
|
194
|
+
private onWorkerEvent;
|
|
195
|
+
/**
|
|
196
|
+
* Leader-only bookkeeping after a durable open: report a legacy-pool
|
|
197
|
+
* migration, move an IndexedDB-held copy into OPFS explicitly (never two
|
|
198
|
+
* disjoint copies), and record OPFS as this database's backend.
|
|
199
|
+
*/
|
|
200
|
+
private afterLeaderOpen;
|
|
201
|
+
private applyDumpThroughWorker;
|
|
104
202
|
/**
|
|
105
203
|
* Surface a `store:opfs-unavailable` diagnostic when the worker reported that
|
|
106
204
|
* persistence silently degraded to an in-memory database, so a data-loss
|
|
@@ -109,18 +207,58 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
109
207
|
*/
|
|
110
208
|
private reportStorageMode;
|
|
111
209
|
getStorageOpenState(): StorageOpenState | null;
|
|
210
|
+
/**
|
|
211
|
+
* Durable or loud, never silent (NEW-STORE-6). When the open or a promotion
|
|
212
|
+
* ended on non-persistent storage and the app did not opt into that, emit the
|
|
213
|
+
* blocking `store:durability-lost` event and refuse every later write. A
|
|
214
|
+
* bridge that reports no mode (the Node mock) is not judged.
|
|
215
|
+
*/
|
|
216
|
+
private enforceDurability;
|
|
217
|
+
private guardDurableWrite;
|
|
112
218
|
private warnSharedWorkerDeprecated;
|
|
219
|
+
/**
|
|
220
|
+
* Close the database. A leader closes the database file and releases the OPFS
|
|
221
|
+
* pool inside its worker, terminates the worker, and only then releases the
|
|
222
|
+
* tab leader lock, so the next leader never races a still-running worker for
|
|
223
|
+
* the file handles (NEW-STORE-10). A follower just leaves.
|
|
224
|
+
*/
|
|
113
225
|
close(): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Release everything this adapter holds, in ownership order: lifecycle
|
|
228
|
+
* listeners, then the worker (which holds the OPFS pool and its Web Lock),
|
|
229
|
+
* then the tab leader lock or queued promotion request.
|
|
230
|
+
*/
|
|
231
|
+
private teardown;
|
|
114
232
|
/**
|
|
115
233
|
* Promotes a follower to leader after the previous leader released the storage
|
|
116
|
-
* lock (its tab closed or
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
234
|
+
* lock (its tab closed, crashed, or suspended on freeze). The new worker waits
|
|
235
|
+
* on the pool's Web Lock and handles, so it never races the previous worker.
|
|
236
|
+
* Requests issued during the hand-off wait for it; requests that were in
|
|
237
|
+
* flight to the old leader fail with a retriable `BridgeTerminatedError`.
|
|
120
238
|
*/
|
|
121
239
|
private promoteToLeader;
|
|
122
|
-
|
|
123
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Page Lifecycle: a frozen or bfcached page cannot run, so it must not keep
|
|
242
|
+
* other tabs waiting on its storage. Leaders terminate their worker (freeing
|
|
243
|
+
* the OPFS pool) and then release the leader lock, so a visible tab is
|
|
244
|
+
* promoted; followers withdraw their queued lock request so a frozen tab is
|
|
245
|
+
* never granted leadership it cannot use. Never uses Web Locks `steal`.
|
|
246
|
+
*/
|
|
247
|
+
private installLifecycleListeners;
|
|
248
|
+
/** Synchronous by necessity: a `freeze` handler gets no further turns. */
|
|
249
|
+
private suspend;
|
|
250
|
+
private resume;
|
|
251
|
+
/**
|
|
252
|
+
* Non-transactional write. Takes the same mutex as {@link transaction}, so it
|
|
253
|
+
* never lands inside this tab's open transaction and is never rolled back
|
|
254
|
+
* with it (STORE-8). Inside a transaction callback, use the `tx` handle.
|
|
255
|
+
*/
|
|
256
|
+
execute(sql: string, params?: unknown[], options?: StorageRequestOptions): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Non-transactional read. Waits for this tab's open transaction to finish, so
|
|
259
|
+
* it never observes uncommitted (possibly rolled-back) rows (STORE-8).
|
|
260
|
+
*/
|
|
261
|
+
query<T>(sql: string, params?: unknown[], options?: StorageRequestOptions): Promise<T[]>;
|
|
124
262
|
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
|
|
125
263
|
migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
|
|
126
264
|
/**
|
|
@@ -137,4 +275,87 @@ declare class SqliteWasmAdapter implements StorageAdapter {
|
|
|
137
275
|
private sendChecked;
|
|
138
276
|
}
|
|
139
277
|
|
|
140
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Kora-owned manifest of the local databases this origin created (W8a).
|
|
280
|
+
*
|
|
281
|
+
* One record per database name, kept in its own IndexedDB database so it is
|
|
282
|
+
* readable from any tab without opening the OPFS pools. It records which backend
|
|
283
|
+
* holds the database's data, which is how Kora keeps OPFS and IndexedDB from
|
|
284
|
+
* ever holding two disjoint copies: a backend switch is a recorded, explicit
|
|
285
|
+
* migration, never a silent fresh start (NEW-STORE-5, LMS-5c).
|
|
286
|
+
*
|
|
287
|
+
* The manifest is advisory for listing and authoritative for the backend
|
|
288
|
+
* choice. Nothing is ever evicted automatically; deletion is the explicit
|
|
289
|
+
* `deleteDatabase()` API.
|
|
290
|
+
*/
|
|
291
|
+
/** Where a database's data lives. */
|
|
292
|
+
type LocalDatabaseBackend = 'opfs' | 'indexeddb';
|
|
293
|
+
/** One manifest entry, as returned by `listDatabases()`. */
|
|
294
|
+
interface LocalDatabaseRecord {
|
|
295
|
+
/** The store name (`store.name`, plus the per-user suffix if namespaced). */
|
|
296
|
+
dbName: string;
|
|
297
|
+
/** The backend that holds the authoritative copy. */
|
|
298
|
+
backend: LocalDatabaseBackend;
|
|
299
|
+
/** OPFS pool holding the file (opfs backend only). */
|
|
300
|
+
poolName?: string;
|
|
301
|
+
/** Wall-clock milliseconds; informational only, never used for ordering. */
|
|
302
|
+
createdAt: number;
|
|
303
|
+
/** Wall-clock milliseconds of the last durable open; informational only. */
|
|
304
|
+
lastOpenedAt: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Explicit management of the local databases on this origin (W8a, NEW-STORE-7).
|
|
309
|
+
*
|
|
310
|
+
* Kora never evicts a database on its own: a full pool is prevented by
|
|
311
|
+
* per-database pools and capacity reservation, and removing data is always an
|
|
312
|
+
* explicit call that refuses while the database is open or still holds
|
|
313
|
+
* operations the server has not acknowledged.
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
/** Read-only access to a database handed to the unsynced-data check. */
|
|
317
|
+
interface LocalDatabaseReader {
|
|
318
|
+
dbName: string;
|
|
319
|
+
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
320
|
+
}
|
|
321
|
+
/** Options for {@link deleteLocalDatabase}. */
|
|
322
|
+
interface DeleteLocalDatabaseOptions {
|
|
323
|
+
/** URL of the Kora SQLite worker (the same one the app's store uses). */
|
|
324
|
+
workerUrl: string | URL;
|
|
325
|
+
/**
|
|
326
|
+
* Decides whether the database still holds operations the server never
|
|
327
|
+
* acknowledged. Injected by the app layer, which knows its sync setup.
|
|
328
|
+
* When it returns true the delete is refused with {@link UnsyncedDataError}.
|
|
329
|
+
*/
|
|
330
|
+
hasUnsyncedOperations?: (db: LocalDatabaseReader) => Promise<boolean>;
|
|
331
|
+
/** Delete even when unsynced operations exist (they are lost). */
|
|
332
|
+
force?: boolean;
|
|
333
|
+
/** Worker response timeout. Defaults to 30000ms. */
|
|
334
|
+
workerResponseTimeoutMs?: number;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Databases Kora has recorded on this origin (created or opened since W8a).
|
|
338
|
+
* A database still in the pre-W8a shared pool appears after its first open.
|
|
339
|
+
*/
|
|
340
|
+
declare function listLocalDatabases(): Promise<LocalDatabaseRecord[]>;
|
|
341
|
+
/**
|
|
342
|
+
* Permanently delete a local database and its OPFS pool or IndexedDB copy.
|
|
343
|
+
*
|
|
344
|
+
* Refuses with {@link StorageInUseError} while any tab has the database open,
|
|
345
|
+
* and with {@link UnsyncedDataError} while `hasUnsyncedOperations` reports
|
|
346
|
+
* unsynced operations (unless `force`). Never touches any other database.
|
|
347
|
+
*
|
|
348
|
+
* @returns true when something was deleted, false when the database did not exist
|
|
349
|
+
*/
|
|
350
|
+
declare function deleteLocalDatabase(dbName: string, options: DeleteLocalDatabaseOptions): Promise<boolean>;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The OPFS SAH pool that owns `dbName`. Database names that are already safe
|
|
354
|
+
* map to `kora-opfs-<dbName>`; anything sanitized or truncated gets a hash suffix
|
|
355
|
+
* so two distinct names can never share a pool.
|
|
356
|
+
*
|
|
357
|
+
* @example opfsPoolNameFor('school__user_alice') === 'kora-opfs-school__user_alice'
|
|
358
|
+
*/
|
|
359
|
+
declare function opfsPoolNameFor(dbName: string): string;
|
|
360
|
+
|
|
361
|
+
export { type DeleteLocalDatabaseOptions, type LocalDatabaseBackend, type LocalDatabaseReader, type LocalDatabaseRecord, Mutex, SqliteWasmAdapter, type SqliteWasmAdapterOptions, type StorageRequestOptions, WorkerBridge, deleteLocalDatabase, listLocalDatabases, opfsPoolNameFor };
|