@korajs/store 1.0.0-beta.12 → 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 +9371 -4240
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1053 -110
- package/dist/index.d.ts +1053 -110
- package/dist/index.js +249 -3158
- 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
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as CollectionRecord, S as StorageAdapter, Q as QueryDescriptor, d as SubscriptionCallback,
|
|
2
|
-
export { M as MigrationPlan,
|
|
3
|
-
import { Operation, KoraError, VersionVector, SchemaDefinition, HLCTimestamp, MergeTrace, CollectionDefinition, SequenceConfig, HybridLogicalClock, CausalTracker, OperationLog, ApplyResult, StateMachineDefinition, KoraBytesValue, KoraEvent, BlobRefMetadata, BlobRef } from '@korajs/core';
|
|
1
|
+
import { C as CollectionRecord, S as StorageAdapter, Q as QueryDescriptor, d as QuerySubscriptionError, e as SubscriptionCallback, f as SubscribeOptions, W as WhereClause, g as OrderByDirection, h as StoreConfig, A as ApplyRemoteOptions, L as LocalMutationHandler, i as MaterializedRowSnapshot, j as RowVersionState } from './types-Dh-xyUjP.js';
|
|
2
|
+
export { M as MigrationPlan, k as OrderByClause, l as QueryErrorPhase, m as StorageFallbackReason, a as StorageOpenState, n as StoreIsolation, T as Transaction, o as WhereOperators } from './types-Dh-xyUjP.js';
|
|
3
|
+
import { Operation, KoraError, VersionVector, SchemaDefinition, HLCTimestamp, MergeTrace, CollectionDefinition, SequenceConfig, HybridLogicalClock, CausalTracker, SecretKeyProvider, OperationLog, OperationTransform, ApplyResult, FoldState, KoraEventEmitter, StateMachineDefinition, KoraBytesValue, KoraEvent, BlobRefMetadata, BlobRef } from '@korajs/core';
|
|
4
4
|
export { ApplyResult } from '@korajs/core';
|
|
5
|
-
import { R as RelationEnforcer, L as LocalMutationContext } from './
|
|
6
|
-
export { C as Collection } from './
|
|
5
|
+
import { R as RelationEnforcer, j as LocalDeleteHook, k as RecordFolder, L as LocalMutationContext, A as AdoptionSchedule, T as TerminalRejection, l as LocalNodeRecord } from './sql-builder-CGckdBDL.js';
|
|
6
|
+
export { C as Collection, P as ParkedAdoption, V as VIRTUAL_TIMESTAMP_FIELDS, m as VIRTUAL_TIMESTAMP_FIELD_NAMES, n as VirtualTimestampField, o as mergeYjsUpdates } from './sql-builder-CGckdBDL.js';
|
|
7
7
|
import * as yjs from 'yjs';
|
|
8
8
|
import { C as ContentAddressedBlobStore, M as MemoryBlobStore } from './content-addressed-blob-store-Dcj3SB9r.js';
|
|
9
9
|
export { B as BlobIntegrityError } from './content-addressed-blob-store-Dcj3SB9r.js';
|
|
@@ -53,6 +53,34 @@ declare class OptimisticLockError extends KoraError {
|
|
|
53
53
|
declare class AdapterError extends KoraError {
|
|
54
54
|
constructor(message: string, context?: Record<string, unknown>);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when a write reaches a storage adapter that could not obtain durable
|
|
58
|
+
* storage (it opened, or was promoted to leader, on a non-persistent in-memory
|
|
59
|
+
* database). Kora refuses such writes instead of accepting data that would be
|
|
60
|
+
* lost on reload. Reads still work.
|
|
61
|
+
*
|
|
62
|
+
* Fix: close other tabs or apps using the same origin's OPFS storage and reopen,
|
|
63
|
+
* or opt in explicitly with `allowNonDurable: true` if in-memory storage is
|
|
64
|
+
* acceptable for this app.
|
|
65
|
+
*/
|
|
66
|
+
declare class StorageDurabilityError extends KoraError {
|
|
67
|
+
constructor(dbName: string, phase: 'open' | 'promotion', reason: string);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown when the store opens a database a NEWER build of the app already migrated: its
|
|
71
|
+
* stored schema version is above this code's `schema.version` (RT-109). Running older
|
|
72
|
+
* code against it would write operations of the old schema into the new one, or fail
|
|
73
|
+
* part-way, so the store refuses before changing anything and emits `store:schema-ahead`.
|
|
74
|
+
*
|
|
75
|
+
* Fix: run the newer build (reload while online, or accept the app's update prompt).
|
|
76
|
+
* Never lower `schema.version` in a deployed app.
|
|
77
|
+
*/
|
|
78
|
+
declare class SchemaVersionAheadError extends KoraError {
|
|
79
|
+
readonly dbName: string;
|
|
80
|
+
readonly storedVersion: number;
|
|
81
|
+
readonly codeVersion: number;
|
|
82
|
+
constructor(dbName: string, storedVersion: number, codeVersion: number);
|
|
83
|
+
}
|
|
56
84
|
/**
|
|
57
85
|
* Thrown when an operation is attempted on a store that has not been opened.
|
|
58
86
|
*/
|
|
@@ -78,7 +106,50 @@ declare class WorkerTimeoutError extends KoraError {
|
|
|
78
106
|
* leader instead of waiting out the full RPC timeout.
|
|
79
107
|
*/
|
|
80
108
|
declare class NoLeaderError extends KoraError {
|
|
81
|
-
constructor(operation: string);
|
|
109
|
+
constructor(operation: string, message?: string, code?: string, context?: Record<string, unknown>);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Thrown to a follower tab's pending storage requests when the leader tab that
|
|
113
|
+
* was answering stopped sending heartbeats (hung, frozen, or suspended). The
|
|
114
|
+
* request may or may not have been applied: retry it with the same `requestId`
|
|
115
|
+
* (in the error context) and the leader de-duplicates it. Retriable.
|
|
116
|
+
*/
|
|
117
|
+
declare class LeaderUnresponsiveError extends NoLeaderError {
|
|
118
|
+
constructor(operation: string, silentMs: number, requestId: string);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Thrown to pending storage requests when their bridge was torn down, typically
|
|
122
|
+
* because this tab was just promoted to leader or the leader handed over.
|
|
123
|
+
* Retriable: the next attempt goes to the new leader.
|
|
124
|
+
*/
|
|
125
|
+
declare class BridgeTerminatedError extends KoraError {
|
|
126
|
+
constructor(operation: string, reason: string);
|
|
127
|
+
}
|
|
128
|
+
/** Thrown when a storage request is cancelled through its `AbortSignal`. */
|
|
129
|
+
declare class RequestAbortedError extends KoraError {
|
|
130
|
+
constructor(operation: string, requestId: string);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Thrown by `deleteDatabase()` when the database is open in some tab or worker
|
|
134
|
+
* on this origin. Close it everywhere (or call `app.close()`) and retry.
|
|
135
|
+
*/
|
|
136
|
+
declare class StorageInUseError extends KoraError {
|
|
137
|
+
constructor(dbName: string);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Thrown by `deleteDatabase()` when the database still holds operations that
|
|
141
|
+
* never reached the server. Sync first, or pass `{ force: true }` to discard them.
|
|
142
|
+
*/
|
|
143
|
+
declare class UnsyncedDataError extends KoraError {
|
|
144
|
+
constructor(dbName: string);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Thrown when a database's data lives in a storage backend this runtime cannot
|
|
148
|
+
* read (for example it was written to OPFS, and OPFS is unavailable now). Kora
|
|
149
|
+
* refuses to start an empty, disjoint copy rather than hide the existing data.
|
|
150
|
+
*/
|
|
151
|
+
declare class StorageBackendMismatchError extends KoraError {
|
|
152
|
+
constructor(dbName: string, recorded: string, requested: string, reason: string);
|
|
82
153
|
}
|
|
83
154
|
/**
|
|
84
155
|
* Thrown when IndexedDB persistence operations fail (serialize/deserialize).
|
|
@@ -124,6 +195,27 @@ declare function computeAckCompactionWatermark(serverVector: VersionVector): Ver
|
|
|
124
195
|
*/
|
|
125
196
|
declare function compactOperationLog(adapter: StorageAdapter, schema: SchemaDefinition, strategy: CompactionStrategy, serverVector: VersionVector): Promise<CompactionResult>;
|
|
126
197
|
|
|
198
|
+
/**
|
|
199
|
+
* How a database is re-materialized on its first open with the W7 fold:
|
|
200
|
+
*
|
|
201
|
+
* - `'log'`: the log is clean (W8 integrity scan): every record is rebuilt with the
|
|
202
|
+
* fold from its operations. This also repairs replicas that diverged under the
|
|
203
|
+
* pairwise merge of beta.12/13.
|
|
204
|
+
* - `'snapshot+log'`: the log is not complete (it was compacted, or has sequence
|
|
205
|
+
* gaps): each existing row becomes the record's row snapshot (its values at
|
|
206
|
+
* their `_field_versions`, exact where a stored fold state still has them), and
|
|
207
|
+
* the remaining operations are folded on top.
|
|
208
|
+
* - `'kept'`: the log has quarantined rows (W8). Only the records that own a
|
|
209
|
+
* quarantined row are kept exactly as they are (their row becomes their snapshot,
|
|
210
|
+
* nothing is rebuilt from their log); every other record is rebuilt as in `'log'`
|
|
211
|
+
* or `'snapshot+log'` (RT-68).
|
|
212
|
+
*
|
|
213
|
+
* Row snapshots are approximate (see `createSnapshotState`); records that fold
|
|
214
|
+
* against one are listed in `_kora_fold_snapshot` until their history is complete
|
|
215
|
+
* again (a scope entry with the server's fold state, or a full resync).
|
|
216
|
+
*/
|
|
217
|
+
type RematerializationMode = 'log' | 'snapshot+log' | 'kept';
|
|
218
|
+
|
|
127
219
|
/**
|
|
128
220
|
* Result of re-stamping unsynced operations after a clock correction.
|
|
129
221
|
*/
|
|
@@ -138,23 +230,39 @@ interface ClockRebaseResult {
|
|
|
138
230
|
newMaxTimestamp: HLCTimestamp | null;
|
|
139
231
|
}
|
|
140
232
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
233
|
+
/** Result of {@link rotateUnsyncedOperationsInLog}. */
|
|
234
|
+
interface NodeRotationResult {
|
|
235
|
+
/** The node id the device uses from now on. */
|
|
236
|
+
nodeId: string;
|
|
237
|
+
/** The rewritten operations under the new node id, in their original causal order. */
|
|
238
|
+
operations: Operation[];
|
|
239
|
+
/** Maps each old operation id to its new content-addressed id. */
|
|
240
|
+
idMapping: Record<string, string>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Result of `Store.resequenceOperation` (RT-35 renumbering). */
|
|
244
|
+
interface ResequenceResult {
|
|
245
|
+
/** The renumbered operation (a version-2 one under a new id). */
|
|
246
|
+
operation: Operation;
|
|
247
|
+
/** Never-sent dependents rewritten to name the new id (re-hashed), in sequence order. */
|
|
248
|
+
dependents: Operation[];
|
|
249
|
+
/** Old id -> new id, for the operation and every re-hashed dependent. */
|
|
250
|
+
idMapping: Record<string, string>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** A delivered operation the client did not apply, as stored in `_kora_unapplied_ops`. */
|
|
254
|
+
interface UnappliedOperation {
|
|
255
|
+
/** The operation as delivered (still encrypted when decryption failed). */
|
|
256
|
+
operation: Operation;
|
|
257
|
+
/** Delivery sequence of the batch that carried it, or null for a legacy batch. */
|
|
258
|
+
deliverySequence: number | null;
|
|
259
|
+
/** Machine-readable reason. */
|
|
260
|
+
code: string;
|
|
261
|
+
/** Human-readable explanation. */
|
|
262
|
+
message: string;
|
|
263
|
+
/** Wall-clock time (ms) it was quarantined. Display only. */
|
|
264
|
+
quarantinedAt: number;
|
|
265
|
+
}
|
|
158
266
|
|
|
159
267
|
/**
|
|
160
268
|
* A merge or constraint decision persisted for enterprise audit export.
|
|
@@ -233,11 +341,14 @@ interface AuditTraceQuery {
|
|
|
233
341
|
* Manifest embedded in every backup file.
|
|
234
342
|
*/
|
|
235
343
|
interface BackupManifest {
|
|
236
|
-
/** Backup format version */
|
|
237
|
-
version:
|
|
344
|
+
/** Backup format version (2 since STORE-5; version 1 files need `convertBackupV1`) */
|
|
345
|
+
version: number;
|
|
238
346
|
/** When the backup was created (epoch ms) */
|
|
239
347
|
createdAt: number;
|
|
240
|
-
/**
|
|
348
|
+
/**
|
|
349
|
+
* Node ID of the originating device. Informational only: a restore never copies a
|
|
350
|
+
* device identity into the importing database.
|
|
351
|
+
*/
|
|
241
352
|
nodeId: string;
|
|
242
353
|
/** Schema version at backup time */
|
|
243
354
|
schemaVersion: number;
|
|
@@ -247,6 +358,20 @@ interface BackupManifest {
|
|
|
247
358
|
collections: string[];
|
|
248
359
|
/** Whether materialized records are included */
|
|
249
360
|
includesRecords: boolean;
|
|
361
|
+
/** Whether the records include deleted rows (tombstones). False for converted v1 files. */
|
|
362
|
+
includesTombstones?: boolean;
|
|
363
|
+
/** Set on a file produced by `convertBackupV1`. */
|
|
364
|
+
convertedFrom?: number;
|
|
365
|
+
/**
|
|
366
|
+
* The file carries the W7 fold's per-record base states (`fold_base`), row
|
|
367
|
+
* snapshots (`fold_snapshot`) and the compacted prefixes (`compacted_through`), so
|
|
368
|
+
* a compacted device's history survives a restore on another device (RT-66).
|
|
369
|
+
* Absent on files from earlier releases: their log may be incomplete (compacted),
|
|
370
|
+
* so a restore rebuilds their records on top of the backup's rows.
|
|
371
|
+
*/
|
|
372
|
+
includesFoldState?: boolean;
|
|
373
|
+
/** The exporting database had compacted its log (it then needs `fold_base`). */
|
|
374
|
+
compacted?: boolean;
|
|
250
375
|
/** SHA-256 hex checksum of all content sections */
|
|
251
376
|
checksum: string;
|
|
252
377
|
}
|
|
@@ -271,6 +396,19 @@ interface RestoreResult {
|
|
|
271
396
|
success: boolean;
|
|
272
397
|
/** Error message if failed */
|
|
273
398
|
error?: string;
|
|
399
|
+
/** Error code if failed (`BACKUP_FORMAT_OUTDATED`, `BACKUP_CHECKSUM_MISMATCH`, ...) */
|
|
400
|
+
errorCode?: string;
|
|
401
|
+
/**
|
|
402
|
+
* Replace mode: this device's own writes the sync server had not acknowledged,
|
|
403
|
+
* re-applied on top of the restored data (see `RestoreOptions.keepUnsyncedWrites`).
|
|
404
|
+
*/
|
|
405
|
+
unsyncedWritesKept?: number;
|
|
406
|
+
/**
|
|
407
|
+
* Merge mode: operations of server-authority nodes (`kora:` nodes and the explicit
|
|
408
|
+
* server authorities this device learned) the file held and the import left out.
|
|
409
|
+
* Server decisions reach a device only from the sync server.
|
|
410
|
+
*/
|
|
411
|
+
serverOperationsSkipped?: number;
|
|
274
412
|
/** Duration in ms */
|
|
275
413
|
duration: number;
|
|
276
414
|
}
|
|
@@ -294,12 +432,118 @@ interface RestoreOptions {
|
|
|
294
432
|
/** Progress callback */
|
|
295
433
|
onProgress?: (progress: BackupProgress) => void;
|
|
296
434
|
/**
|
|
297
|
-
* If true, merge
|
|
298
|
-
*
|
|
435
|
+
* If true, merge the backup into the existing data: every operation is applied like
|
|
436
|
+
* one received from sync (deduplicated by id, merged per field), the version vector
|
|
437
|
+
* advances by MAX, and nothing of the exporting device's identity or sync state is
|
|
438
|
+
* imported. If false (default), replace the local data with the backup's.
|
|
299
439
|
*/
|
|
300
440
|
merge?: boolean;
|
|
441
|
+
/**
|
|
442
|
+
* Replace mode only: keep this device's own writes that the sync server has not
|
|
443
|
+
* acknowledged, re-applied on top of the backup, so a restore never discards writes
|
|
444
|
+
* that exist nowhere else. Writes of a node the server has accepted are always kept
|
|
445
|
+
* (they are in flight). Default: true for `store.importBackup`; `app.importBackup`
|
|
446
|
+
* defaults it to whether sync is configured, so a local-only app gets an exact
|
|
447
|
+
* replace.
|
|
448
|
+
*/
|
|
449
|
+
keepUnsyncedWrites?: boolean;
|
|
301
450
|
}
|
|
302
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Log-integrity scan (W8 step 0).
|
|
454
|
+
*
|
|
455
|
+
* Every later fold of the operation log (W7 re-materialization, replay, compaction)
|
|
456
|
+
* trusts that each stored row is a canonical operation. Two earlier releases could
|
|
457
|
+
* break that:
|
|
458
|
+
*
|
|
459
|
+
* - beta.12's backup restore wrote `JSON.stringify(timestamp)` into the timestamp
|
|
460
|
+
* column, which `HybridLogicalClock.deserialize` reads as `wallTime: NaN` (a `null`
|
|
461
|
+
* once re-exported), and a second restore of such a backup nested the damage;
|
|
462
|
+
* - compaction deletes acknowledged operations, leaving holes in a node's sequence.
|
|
463
|
+
*
|
|
464
|
+
* The scan detects rows that do not round-trip through `deserializeOperation` /
|
|
465
|
+
* `serializeOperation`, repairs the ones whose original value is recoverable from the
|
|
466
|
+
* row itself (the JSON-encoded timestamp), and moves the rest to
|
|
467
|
+
* {@link LOG_QUARANTINE_TABLE} (never deleted), so no fold ever reads them. It also
|
|
468
|
+
* reports sequence gaps in this database's own nodes. Remote nodes' gaps are normal
|
|
469
|
+
* (scoped sync only delivers part of another device's log) and are not reported.
|
|
470
|
+
*/
|
|
471
|
+
/** Rows that could not be repaired, kept verbatim. Never cleared automatically. */
|
|
472
|
+
declare const LOG_QUARANTINE_TABLE = "_kora_log_quarantine";
|
|
473
|
+
/** What is wrong with an operation row. */
|
|
474
|
+
type LogRowProblem =
|
|
475
|
+
/** The timestamp is a JSON-encoded HLC (beta.12 backup restore). Repairable. */
|
|
476
|
+
'timestamp-json'
|
|
477
|
+
/** The timestamp cannot be read as an HLC and is not recoverable. */
|
|
478
|
+
| 'timestamp-malformed'
|
|
479
|
+
/** `id`, `node_id` or `record_id` is missing. */
|
|
480
|
+
| 'identity-malformed'
|
|
481
|
+
/** `type` is not insert, update or delete. */
|
|
482
|
+
| 'type-invalid'
|
|
483
|
+
/** `sequence_number` is not a positive integer. */
|
|
484
|
+
| 'sequence-invalid'
|
|
485
|
+
/** `data` or `previous_data` is not a JSON object. */
|
|
486
|
+
| 'data-malformed'
|
|
487
|
+
/** `causal_deps` is not a JSON array of operation ids. */
|
|
488
|
+
| 'causal-deps-malformed'
|
|
489
|
+
/** The row cannot be turned into an operation and back. */
|
|
490
|
+
| 'round-trip-failed';
|
|
491
|
+
/** One operation row the scan repaired or quarantined. */
|
|
492
|
+
interface LogIntegrityRow {
|
|
493
|
+
collection: string;
|
|
494
|
+
operationId: string;
|
|
495
|
+
nodeId: string | null;
|
|
496
|
+
sequenceNumber: number | null;
|
|
497
|
+
problem: LogRowProblem;
|
|
498
|
+
/** Human-readable detail: the bad value, or how it was repaired. */
|
|
499
|
+
detail: string;
|
|
500
|
+
}
|
|
501
|
+
/** Sequence numbers missing from one of this database's own nodes. */
|
|
502
|
+
interface LogSequenceGap {
|
|
503
|
+
nodeId: string;
|
|
504
|
+
/** First missing sequence number (inclusive). */
|
|
505
|
+
from: number;
|
|
506
|
+
/** Last missing sequence number (inclusive). */
|
|
507
|
+
to: number;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Result of {@link scanLogIntegrity} / `Store.verifyLogIntegrity()`.
|
|
511
|
+
*
|
|
512
|
+
* `clean` is the precondition for rebuilding state from the log (W7): every row is
|
|
513
|
+
* canonical, nothing is quarantined, and this database's own nodes have no holes
|
|
514
|
+
* below their highest stored sequence number.
|
|
515
|
+
*/
|
|
516
|
+
interface LogIntegrityReport {
|
|
517
|
+
/** True when the log can be folded as-is (no quarantined rows, no own-node gaps). */
|
|
518
|
+
clean: boolean;
|
|
519
|
+
/** `full`: every row was checked in JS. `quick`: SQL prefilter, suspects checked in JS. */
|
|
520
|
+
mode: 'full' | 'quick';
|
|
521
|
+
/** Rows checked in JS (all rows in full mode, suspects in quick mode). */
|
|
522
|
+
checkedRows: number;
|
|
523
|
+
/** Rows this scan repaired in place. */
|
|
524
|
+
repaired: LogIntegrityRow[];
|
|
525
|
+
/** Rows this scan moved to the quarantine table. */
|
|
526
|
+
newlyQuarantined: LogIntegrityRow[];
|
|
527
|
+
/** Every row in the quarantine table (this scan's and earlier ones'). */
|
|
528
|
+
quarantined: LogIntegrityRow[];
|
|
529
|
+
/** Holes in this database's own nodes' sequences (compaction, a lost tail). */
|
|
530
|
+
gaps: LogSequenceGap[];
|
|
531
|
+
/** When the log was last compacted (epoch ms), or null when never. */
|
|
532
|
+
compactedAt: number | null;
|
|
533
|
+
/** False when the scan only reported (`repair: false`). */
|
|
534
|
+
repairApplied: boolean;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Compares two query result sets; true means subscribers need not be notified. */
|
|
538
|
+
type ResultsEqual = (prev: readonly CollectionRecord[], next: readonly CollectionRecord[]) => boolean;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Per-subscription options accepted by `register` / `registerAndFetch`.
|
|
542
|
+
*/
|
|
543
|
+
interface RegisterOptions extends SubscribeOptions {
|
|
544
|
+
/** Result-set comparator; defaults to structural comparison of every value. */
|
|
545
|
+
resultsEqual?: ResultsEqual;
|
|
546
|
+
}
|
|
303
547
|
/**
|
|
304
548
|
* Configuration options for the SubscriptionManager.
|
|
305
549
|
*/
|
|
@@ -323,7 +567,29 @@ interface SubscriptionManagerOptions {
|
|
|
323
567
|
bloomFalsePositiveRate?: number;
|
|
324
568
|
/** Called when a query subscription is registered (e.g. to register sync query subsets). */
|
|
325
569
|
onQuerySubscribed?: (descriptor: QueryDescriptor) => () => void;
|
|
570
|
+
/**
|
|
571
|
+
* Called for every subscription failure (the store emits `query:error`), in
|
|
572
|
+
* addition to the subscriber's own `onError`.
|
|
573
|
+
*/
|
|
574
|
+
onQueryError?: (failure: QuerySubscriptionError) => void;
|
|
326
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* A committed change to the rows of one collection that can affect query results
|
|
578
|
+
* (RT-98). Every invalidation the store makes produces one: local writes and remote
|
|
579
|
+
* applies (with the operation), and changes made without a new operation (rejection
|
|
580
|
+
* re-folds, scope retraction, cascade settling, rematerialization, backup restore,
|
|
581
|
+
* held/discarded writes).
|
|
582
|
+
*/
|
|
583
|
+
interface RecordsChange {
|
|
584
|
+
/** Collection whose rows changed */
|
|
585
|
+
readonly collection: string;
|
|
586
|
+
/** Changed record ids, or null when unknown (treat the whole collection as changed) */
|
|
587
|
+
readonly ids: readonly string[] | null;
|
|
588
|
+
/** The operation that caused the change, when there is one */
|
|
589
|
+
readonly operation?: Operation;
|
|
590
|
+
}
|
|
591
|
+
/** Listener for {@link RecordsChange}s made by this runtime (not those relayed from peers). */
|
|
592
|
+
type RecordsChangeListener = (change: RecordsChange) => void;
|
|
327
593
|
/**
|
|
328
594
|
* Performance statistics for monitoring subscription checking efficiency.
|
|
329
595
|
*/
|
|
@@ -364,6 +630,14 @@ declare class SubscriptionManager {
|
|
|
364
630
|
private pendingCollections;
|
|
365
631
|
private flushScheduled;
|
|
366
632
|
private readonly onQuerySubscribed?;
|
|
633
|
+
private readonly onQueryError?;
|
|
634
|
+
private readonly changeListeners;
|
|
635
|
+
/**
|
|
636
|
+
* Collections changed before the first change listener attached (a store's open:
|
|
637
|
+
* rematerialization, migration backfills). Replayed to that listener, so other tabs
|
|
638
|
+
* hear about changes made while this tab was still opening. Bounded by the schema.
|
|
639
|
+
*/
|
|
640
|
+
private unheardCollections;
|
|
367
641
|
private bloomFilter;
|
|
368
642
|
private bloomDirty;
|
|
369
643
|
private readonly bloomThreshold;
|
|
@@ -383,22 +657,55 @@ declare class SubscriptionManager {
|
|
|
383
657
|
* @param executeFn - Function to re-execute the query and get current results
|
|
384
658
|
* @returns An unsubscribe function
|
|
385
659
|
*/
|
|
386
|
-
register(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]
|
|
660
|
+
register(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>, options?: RegisterOptions): () => void;
|
|
387
661
|
/**
|
|
388
662
|
* Register a subscription and immediately execute the query.
|
|
389
663
|
* The initial results are stored as lastResults so subsequent flushes
|
|
390
|
-
* correctly diff against the initial state.
|
|
664
|
+
* correctly diff against the initial state. A failing initial run is reported
|
|
665
|
+
* through `options.onError` and the `query:error` hook, never as an unhandled
|
|
666
|
+
* rejection (STORE-12).
|
|
391
667
|
*
|
|
392
668
|
* @returns An unsubscribe function
|
|
393
669
|
*/
|
|
394
|
-
registerAndFetch(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]
|
|
670
|
+
registerAndFetch(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>, options?: RegisterOptions): () => void;
|
|
671
|
+
private createSubscription;
|
|
672
|
+
private track;
|
|
673
|
+
/**
|
|
674
|
+
* Run a subscription's query and deliver the result when it changed. Never
|
|
675
|
+
* throws: failures go to the error channel. Runs are numbered so a slow,
|
|
676
|
+
* older run never overwrites the result of a newer one.
|
|
677
|
+
*/
|
|
678
|
+
private run;
|
|
679
|
+
private isCurrent;
|
|
680
|
+
private reportError;
|
|
395
681
|
/**
|
|
396
682
|
* Notify the manager that a mutation occurred on a collection.
|
|
397
683
|
* Schedules a microtask flush to batch multiple mutations in the same tick.
|
|
398
684
|
*/
|
|
399
|
-
notify(collection: string,
|
|
400
|
-
/**
|
|
401
|
-
|
|
685
|
+
notify(collection: string, operation: Operation): void;
|
|
686
|
+
/**
|
|
687
|
+
* Invalidate a collection after a local-view change that has no domain operation
|
|
688
|
+
* (a re-fold, a retraction, a restore). Pass the record ids when known.
|
|
689
|
+
*/
|
|
690
|
+
invalidate(collection: string, ids?: readonly string[] | null): void;
|
|
691
|
+
/**
|
|
692
|
+
* The single entry point for a committed change made by this runtime (RT-98): it
|
|
693
|
+
* schedules the affected queries and tells every change listener (the cross-tab
|
|
694
|
+
* bus), so no invalidation path can reach this tab's queries without reaching the
|
|
695
|
+
* other tabs' too.
|
|
696
|
+
*/
|
|
697
|
+
recordsChanged(change: RecordsChange): void;
|
|
698
|
+
/**
|
|
699
|
+
* Invalidate a collection for a change another runtime made to the shared
|
|
700
|
+
* database. Not relayed to change listeners (the peer already broadcast it).
|
|
701
|
+
*/
|
|
702
|
+
invalidateFromPeer(collection: string): void;
|
|
703
|
+
/**
|
|
704
|
+
* Listen to every committed change this runtime makes.
|
|
705
|
+
*
|
|
706
|
+
* @returns An unsubscribe function
|
|
707
|
+
*/
|
|
708
|
+
onRecordsChanged(listener: RecordsChangeListener): () => void;
|
|
402
709
|
/**
|
|
403
710
|
* Immediately flush all pending notifications.
|
|
404
711
|
* Useful for testing. In production, flushing happens via microtask.
|
|
@@ -438,12 +745,6 @@ declare class SubscriptionManager {
|
|
|
438
745
|
*/
|
|
439
746
|
private rebuildBloomFilter;
|
|
440
747
|
private scheduleFlush;
|
|
441
|
-
/**
|
|
442
|
-
* Compare two result sets. Uses ID-based comparison first (same length, same IDs),
|
|
443
|
-
* then deep comparison of remaining fields when IDs match.
|
|
444
|
-
* Avoids JSON.stringify which is O(n) and fragile to field ordering differences.
|
|
445
|
-
*/
|
|
446
|
-
private resultsEqual;
|
|
447
748
|
}
|
|
448
749
|
|
|
449
750
|
/**
|
|
@@ -472,6 +773,10 @@ declare class QueryBuilder<T = CollectionRecord> {
|
|
|
472
773
|
constructor(collectionName: string, definition: CollectionDefinition, adapter: StorageAdapter, subscriptionManager: SubscriptionManager, initialWhere?: WhereClause, schema?: SchemaDefinition | undefined);
|
|
473
774
|
/**
|
|
474
775
|
* Add WHERE conditions (AND semantics, merged with existing conditions).
|
|
776
|
+
*
|
|
777
|
+
* A field whose value is `undefined` adds no condition (it does not remove an
|
|
778
|
+
* earlier one); `null` matches missing values (`IS NULL`). Non-finite numbers are
|
|
779
|
+
* refused. See {@link normalizeWhere} (RT-102).
|
|
475
780
|
*/
|
|
476
781
|
where(conditions: WhereClause): QueryBuilder<T>;
|
|
477
782
|
/**
|
|
@@ -516,7 +821,9 @@ declare class QueryBuilder<T = CollectionRecord> {
|
|
|
516
821
|
*
|
|
517
822
|
* @returns An unsubscribe function
|
|
518
823
|
*/
|
|
519
|
-
subscribe(callback: SubscriptionCallback<T
|
|
824
|
+
subscribe(callback: SubscriptionCallback<T>, options?: SubscribeOptions): () => void;
|
|
825
|
+
/** Comparator built once per collection definition and shared by its queries. */
|
|
826
|
+
private getResultsEqual;
|
|
520
827
|
/** Get the internal descriptor (for testing/debugging) */
|
|
521
828
|
getDescriptor(): QueryDescriptor;
|
|
522
829
|
private clone;
|
|
@@ -595,20 +902,6 @@ declare class SequenceManager {
|
|
|
595
902
|
}): Promise<void>;
|
|
596
903
|
}
|
|
597
904
|
|
|
598
|
-
/**
|
|
599
|
-
* Allocates monotonic sequence numbers for a single open transaction.
|
|
600
|
-
* Loads the current DB watermark once, then increments in memory until commit.
|
|
601
|
-
*/
|
|
602
|
-
declare class TransactionSequenceAllocator {
|
|
603
|
-
private readonly adapter;
|
|
604
|
-
private readonly nodeId;
|
|
605
|
-
private loaded;
|
|
606
|
-
private watermark;
|
|
607
|
-
constructor(adapter: StorageAdapter, nodeId: string);
|
|
608
|
-
allocate(): Promise<number>;
|
|
609
|
-
getHighWaterMark(): number;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
905
|
/**
|
|
613
906
|
* Internal configuration for creating a TransactionContext.
|
|
614
907
|
* Passed from Store to avoid exposing Store internals publicly.
|
|
@@ -618,10 +911,19 @@ interface TransactionContextConfig {
|
|
|
618
911
|
adapter: StorageAdapter;
|
|
619
912
|
clock: HybridLogicalClock;
|
|
620
913
|
nodeId: string;
|
|
621
|
-
sequenceAllocator: TransactionSequenceAllocator;
|
|
622
914
|
relationEnforcer: RelationEnforcer | null;
|
|
623
915
|
causalTracker: CausalTracker | null;
|
|
624
|
-
|
|
916
|
+
/**
|
|
917
|
+
* Key provider for `t.secret()` encrypted fields. Transaction writes transform
|
|
918
|
+
* secret fields through the same helper as single-record writes (STORE-4).
|
|
919
|
+
*/
|
|
920
|
+
secretKeyProvider?: SecretKeyProvider;
|
|
921
|
+
/** Referential hook run for every local delete inside the commit (see {@link LocalDeleteHook}). */
|
|
922
|
+
beforeLocalDelete?: LocalDeleteHook;
|
|
923
|
+
/** Called when the commit's storage transaction fails, before the error propagates. */
|
|
924
|
+
onStorageError?: (error: unknown) => void;
|
|
925
|
+
/** The W7 record fold (see `WriteEnv.fold`). */
|
|
926
|
+
fold?: RecordFolder;
|
|
625
927
|
}
|
|
626
928
|
/**
|
|
627
929
|
* A collection accessor within a transaction.
|
|
@@ -636,8 +938,10 @@ interface TransactionCollectionAccessor {
|
|
|
636
938
|
/**
|
|
637
939
|
* TransactionContext provides atomic multi-collection operations.
|
|
638
940
|
*
|
|
639
|
-
*
|
|
640
|
-
* call
|
|
941
|
+
* Mutations are validated and buffered when called (so errors surface at the
|
|
942
|
+
* call site and `insert` returns the new id immediately), then written in a
|
|
943
|
+
* single `StorageAdapter.transaction()` at commit. All operations share the
|
|
944
|
+
* same transactionId (UUID v7) and one contiguous block of sequence numbers.
|
|
641
945
|
*
|
|
642
946
|
* Subscription notifications are deferred until after commit.
|
|
643
947
|
*
|
|
@@ -672,7 +976,13 @@ declare class TransactionContext {
|
|
|
672
976
|
*/
|
|
673
977
|
collection(name: string): TransactionCollectionAccessor;
|
|
674
978
|
/**
|
|
675
|
-
* Commit all buffered
|
|
979
|
+
* Commit all buffered mutations atomically.
|
|
980
|
+
*
|
|
981
|
+
* Every entry is written, in call order, inside ONE storage transaction through
|
|
982
|
+
* the single local write path: sequence numbers are reserved inside the
|
|
983
|
+
* transaction (one contiguous block covering referential side effects too) and
|
|
984
|
+
* each operation is built only after its number is reserved.
|
|
985
|
+
*
|
|
676
986
|
* Returns the list of operations and affected collections for subscription notification.
|
|
677
987
|
*/
|
|
678
988
|
commit(): Promise<{
|
|
@@ -687,32 +997,35 @@ declare class TransactionContext {
|
|
|
687
997
|
* Get the transaction ID shared by all operations in this transaction.
|
|
688
998
|
*/
|
|
689
999
|
getTransactionId(): string;
|
|
1000
|
+
private writeEnv;
|
|
690
1001
|
private ensureActive;
|
|
691
1002
|
private insert;
|
|
692
1003
|
private update;
|
|
693
1004
|
private deleteRecord;
|
|
694
|
-
private findById;
|
|
695
1005
|
/**
|
|
696
|
-
*
|
|
697
|
-
*
|
|
1006
|
+
* The values an update will write, resolved against `current` (the effective
|
|
1007
|
+
* record) for read-your-writes inside the transaction. Secret fields are shown
|
|
1008
|
+
* in their at-rest form, like the record a committed update returns.
|
|
1009
|
+
*/
|
|
1010
|
+
private previewUpdate;
|
|
1011
|
+
/**
|
|
1012
|
+
* The effective state of a record: the committed row overlaid with this
|
|
1013
|
+
* transaction's buffered mutations, in call order.
|
|
698
1014
|
*/
|
|
699
1015
|
private getEffectiveRecord;
|
|
700
1016
|
}
|
|
701
1017
|
|
|
702
|
-
/**
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
* await store.close()
|
|
714
|
-
* ```
|
|
715
|
-
*/
|
|
1018
|
+
/** Result of {@link Store.bindPrincipal} (RT-42). */
|
|
1019
|
+
interface PrincipalBinding {
|
|
1020
|
+
/** The node id local writes use from now on. */
|
|
1021
|
+
nodeId: string;
|
|
1022
|
+
/** The node id in use before the call. */
|
|
1023
|
+
previousNodeId: string;
|
|
1024
|
+
/** The store moved to another node id. */
|
|
1025
|
+
switched: boolean;
|
|
1026
|
+
/** The node id is pinned and belongs to another user: nothing may upload it now. */
|
|
1027
|
+
conflict: boolean;
|
|
1028
|
+
}
|
|
716
1029
|
declare class Store implements OperationLog {
|
|
717
1030
|
private opened;
|
|
718
1031
|
private nodeId;
|
|
@@ -732,6 +1045,18 @@ declare class Store implements OperationLog {
|
|
|
732
1045
|
private relationEnforcer;
|
|
733
1046
|
private causalTracker;
|
|
734
1047
|
private readonly secretKeyProvider;
|
|
1048
|
+
/** Releases the per-tab node lock (RT-40); null when none is held. */
|
|
1049
|
+
private releaseNodeLock;
|
|
1050
|
+
/** How rows are materialized ({@link StoreConfig.materialization}). */
|
|
1051
|
+
private readonly materialization;
|
|
1052
|
+
/** The W7 record fold; null under legacy materialization. */
|
|
1053
|
+
private readonly folder;
|
|
1054
|
+
/** True once every row of the database is a materialization of its fold state. */
|
|
1055
|
+
private foldActive;
|
|
1056
|
+
/** Schema transforms applied at fold time (RT-84); never to stored operations. */
|
|
1057
|
+
private readonly operationTransforms;
|
|
1058
|
+
/** Largest operation a local write may produce (RT-86); default in the write path. */
|
|
1059
|
+
private readonly maxOperationBytes;
|
|
735
1060
|
constructor(config: StoreConfig);
|
|
736
1061
|
/**
|
|
737
1062
|
* Open the store: initialize the database, load or generate a node ID,
|
|
@@ -742,6 +1067,25 @@ declare class Store implements OperationLog {
|
|
|
742
1067
|
* Close the store: clear subscriptions and close the adapter.
|
|
743
1068
|
*/
|
|
744
1069
|
close(): Promise<void>;
|
|
1070
|
+
/**
|
|
1071
|
+
* Check the operation log (W8 step 0): every row must round-trip through the
|
|
1072
|
+
* canonical operation serializer. Rows an earlier release damaged in a recoverable
|
|
1073
|
+
* way (a JSON-encoded timestamp written by a beta.12 backup restore) are repaired;
|
|
1074
|
+
* unrecoverable rows move to the quarantine table, where no fold reads them. Also
|
|
1075
|
+
* reports sequence gaps in this database's own nodes (compaction, a lost tail).
|
|
1076
|
+
*
|
|
1077
|
+
* The store runs a quick variant (SQL prefilter) on every open. Rebuilding state from
|
|
1078
|
+
* the log must only run on a `clean` report.
|
|
1079
|
+
*
|
|
1080
|
+
* @param options - `repair: false` only reports; `mode: 'quick'` checks only the rows
|
|
1081
|
+
* a SQL prefilter flags (default `'full'`: every row)
|
|
1082
|
+
* @returns The integrity report; emits `store:log-integrity` when rows changed
|
|
1083
|
+
*/
|
|
1084
|
+
verifyLogIntegrity(options?: {
|
|
1085
|
+
repair?: boolean;
|
|
1086
|
+
mode?: 'full' | 'quick';
|
|
1087
|
+
}): Promise<LogIntegrityReport>;
|
|
1088
|
+
private scanLog;
|
|
745
1089
|
/**
|
|
746
1090
|
* Get a Collection instance for CRUD operations.
|
|
747
1091
|
* @throws {StoreNotOpenError} If the store is not open
|
|
@@ -762,12 +1106,143 @@ declare class Store implements OperationLog {
|
|
|
762
1106
|
*/
|
|
763
1107
|
setClockReferenceOffset(offsetMs: number): void;
|
|
764
1108
|
getNodeId(): string;
|
|
1109
|
+
/** The schema transforms the fold applies (transforms at fold time, RT-84). */
|
|
1110
|
+
getOperationTransforms(): readonly OperationTransform[];
|
|
765
1111
|
/**
|
|
766
1112
|
* Apply a remote operation received from sync.
|
|
767
1113
|
* Checks for duplicates, applies to the data table, persists the operation,
|
|
768
1114
|
* and updates the version vector.
|
|
769
1115
|
*/
|
|
770
|
-
applyRemoteOperation(
|
|
1116
|
+
applyRemoteOperation(stored: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
|
|
1117
|
+
/**
|
|
1118
|
+
* W7 remote apply: append the operation, then merge it into its record's fold
|
|
1119
|
+
* state, which re-materializes the row, all in one write transaction. There is no
|
|
1120
|
+
* read-decide-write race to guard: the state is read and written inside the
|
|
1121
|
+
* transaction, and the merge does not depend on the order operations arrive in.
|
|
1122
|
+
*/
|
|
1123
|
+
private applyRemoteFolded;
|
|
1124
|
+
/** DevTools: every fold decision that was a conflict, plus one completion per merge. */
|
|
1125
|
+
private emitMergeTraces;
|
|
1126
|
+
/** The record fold, once the database's rows are fold materializations. */
|
|
1127
|
+
private activeFold;
|
|
1128
|
+
/**
|
|
1129
|
+
* The record's W7 fold state (null under legacy materialization or when the
|
|
1130
|
+
* record has none). For DevTools, tests and the server-comparison harness.
|
|
1131
|
+
*/
|
|
1132
|
+
getFoldState(collection: string, recordId: string): Promise<FoldState | null>;
|
|
1133
|
+
/** Whether rows are materialized by the W7 fold (false under `materialization: 'legacy'`). */
|
|
1134
|
+
isFoldMaterialized(): boolean;
|
|
1135
|
+
private readMeta;
|
|
1136
|
+
private writeMeta;
|
|
1137
|
+
/**
|
|
1138
|
+
* Write every genuine beta.12 clear of the op log into its logged body, once per
|
|
1139
|
+
* database (RT-83, RT-85): the fold folds bodies as written, so the clear must be in
|
|
1140
|
+
* the body. Records already materialized by the fold are re-folded.
|
|
1141
|
+
*/
|
|
1142
|
+
private ensureLegacyBodiesCanonical;
|
|
1143
|
+
/**
|
|
1144
|
+
* Make every row a materialization of its record's fold state (W7), once per
|
|
1145
|
+
* database and fold-state version. Runs after the W8 log-integrity scan: a
|
|
1146
|
+
* clean log is rebuilt with the fold; an incomplete (compacted) log rebuilds on
|
|
1147
|
+
* top of the rows as base snapshots; a log with quarantined rows is never
|
|
1148
|
+
* rebuilt from (rows are kept and become base snapshots), and
|
|
1149
|
+
* `store:rematerialized` reports which happened.
|
|
1150
|
+
*/
|
|
1151
|
+
private ensureMaterialization;
|
|
1152
|
+
/**
|
|
1153
|
+
* Refuse to open when a schema version in the local operation log has no transform
|
|
1154
|
+
* path to the current schema (RT-103). Operations of a NEWER schema (authored by an
|
|
1155
|
+
* upgraded peer) are not checked: they are kept aside until this device upgrades.
|
|
1156
|
+
*/
|
|
1157
|
+
private assertTransformCoverage;
|
|
1158
|
+
/**
|
|
1159
|
+
* Re-fold the records of every collection whose fold plan changed since the rows
|
|
1160
|
+
* were materialized (RT-63): a field that became `merge('counter')`, an array that
|
|
1161
|
+
* became append-only, a new or edited resolver. Their stored states hold fields of
|
|
1162
|
+
* the old kind, which the fold refuses (`FoldStateError`). Each record is rebuilt
|
|
1163
|
+
* from its log with the new plan; compacted bases and row snapshots are adapted
|
|
1164
|
+
* (a re-planned field restarts from its materialized value at its version). The
|
|
1165
|
+
* client's equivalent of the server stores' `fold_plan_fingerprint`.
|
|
1166
|
+
*/
|
|
1167
|
+
private ensureFoldPlan;
|
|
1168
|
+
/**
|
|
1169
|
+
* Row snapshots were created (RT-68): ask the sync server for a full resync (the
|
|
1170
|
+
* delivery watermarks restart at 0, as after a restore), so each such record gets
|
|
1171
|
+
* its complete history back; {@link settleSnapshotRecords} then drops the
|
|
1172
|
+
* snapshots once the resync has caught up.
|
|
1173
|
+
*/
|
|
1174
|
+
private requestSnapshotResync;
|
|
1175
|
+
/**
|
|
1176
|
+
* Records whose state still folds against a row snapshot (approximate: a late
|
|
1177
|
+
* concurrent write older than a field's version is not folded, RT-68).
|
|
1178
|
+
*/
|
|
1179
|
+
getSnapshotRecords(): Promise<Array<{
|
|
1180
|
+
collection: string;
|
|
1181
|
+
recordId: string;
|
|
1182
|
+
}>>;
|
|
1183
|
+
/**
|
|
1184
|
+
* Called by sync when a delivery stream has caught up (its final batch fully
|
|
1185
|
+
* applied). After the full resync {@link requestSnapshotResync} asked for, every
|
|
1186
|
+
* row-snapshot record whose insert is back in the log is re-folded from its
|
|
1187
|
+
* complete history and stops being approximate (RT-68). Also retires the
|
|
1188
|
+
* provisional cascades of remote deletes (RT-69): the server's own copies were
|
|
1189
|
+
* delivered by now, and any it did not derive must not stay applied locally.
|
|
1190
|
+
*
|
|
1191
|
+
* With `provisionalOnly` only the provisional cascades are retired (RT-93: a stream
|
|
1192
|
+
* batch from a server that derives no cascades, which is not the end of a resync).
|
|
1193
|
+
*
|
|
1194
|
+
* @param options - `provisionalOnly`: leave row snapshots for the next catch-up
|
|
1195
|
+
* @returns How many snapshots were dropped
|
|
1196
|
+
*/
|
|
1197
|
+
settleAfterCatchUp(options?: {
|
|
1198
|
+
provisionalOnly?: boolean;
|
|
1199
|
+
}): Promise<number>;
|
|
1200
|
+
/**
|
|
1201
|
+
* Apply the cascades / set-nulls of a REMOTE delete as local-only provisional
|
|
1202
|
+
* effects (RT-69): folded into the children like operations, but never logged,
|
|
1203
|
+
* sequenced or queued for upload. The server derives and relays its own copy;
|
|
1204
|
+
* when it arrives (same parent, same record) the provisional effect is retired.
|
|
1205
|
+
*
|
|
1206
|
+
* @param parent - The remote delete
|
|
1207
|
+
* @param effects - Its side effects, as the referential check derived them
|
|
1208
|
+
*/
|
|
1209
|
+
applyProvisionalSideEffects(parent: Operation, effects: ReadonlyArray<{
|
|
1210
|
+
type: 'delete' | 'update';
|
|
1211
|
+
collection: string;
|
|
1212
|
+
recordId: string;
|
|
1213
|
+
data: Record<string, unknown> | null;
|
|
1214
|
+
previousData: Record<string, unknown> | null;
|
|
1215
|
+
ruleId: string;
|
|
1216
|
+
}>): Promise<number>;
|
|
1217
|
+
/**
|
|
1218
|
+
* Node ids whose writes win `merge('server-authoritative')` fields (the server's,
|
|
1219
|
+
* from the sync handshake). Every `kora:server:` id is authoritative by prefix, so
|
|
1220
|
+
* only explicit ids (legacy server node ids, configured extras) are kept: the device
|
|
1221
|
+
* persists the UNION of every explicit id it has learned and never drops one (RT-75).
|
|
1222
|
+
* Handshakes differ per server instance (each lists what it knows), so a list that
|
|
1223
|
+
* omits an id is not a revocation.
|
|
1224
|
+
*
|
|
1225
|
+
* Revocation is explicit (RT-81): ids a handshake lists in `revokedAuthoritativeNodeIds`
|
|
1226
|
+
* are removed from the union, persisted as revoked, and never learned again. Records
|
|
1227
|
+
* of collections with such fields are re-folded only when the explicit set changes
|
|
1228
|
+
* (grows, or loses a revoked id).
|
|
1229
|
+
*
|
|
1230
|
+
* @param nodeIds - The server's authoritative node ids (one handshake's list)
|
|
1231
|
+
* @param revokedNodeIds - Explicit ids the server revoked (one handshake's list)
|
|
1232
|
+
*/
|
|
1233
|
+
setAuthoritativeNodeIds(nodeIds: readonly string[], revokedNodeIds?: readonly string[]): Promise<void>;
|
|
1234
|
+
/**
|
|
1235
|
+
* Re-fold the records of these operations from base + log (W7 exclusion: an
|
|
1236
|
+
* operation with a terminal-rejection marker is left out).
|
|
1237
|
+
*/
|
|
1238
|
+
private refoldRecordsOf;
|
|
1239
|
+
/**
|
|
1240
|
+
* Append a remote operation to the log. If another operation of the same node
|
|
1241
|
+
* already holds its sequence number (a beta.12 device could produce such
|
|
1242
|
+
* pairs), the log keeps the existing row and this one is recorded in the
|
|
1243
|
+
* sequence-conflicts table, where dedup and record folds still see it.
|
|
1244
|
+
*/
|
|
1245
|
+
private appendRemoteOperationRow;
|
|
771
1246
|
/**
|
|
772
1247
|
* Hide a record from this client's authorized materialized view. This writes no
|
|
773
1248
|
* replicated operation and therefore cannot be mistaken for a domain deletion.
|
|
@@ -779,6 +1254,26 @@ declare class Store implements OperationLog {
|
|
|
779
1254
|
collection: string;
|
|
780
1255
|
recordId: string;
|
|
781
1256
|
}>>;
|
|
1257
|
+
/**
|
|
1258
|
+
* Materialize a server scope-entry insert (RT-19) whose fields each carry their
|
|
1259
|
+
* own version (RT-27). The entry restates the record's current server values; a
|
|
1260
|
+
* single whole-row stamp would let a field the server last wrote long ago
|
|
1261
|
+
* overwrite this device's newer unsynced edit of it, after which the device's own
|
|
1262
|
+
* operation uploads and wins on the server (divergence). So every field is resolved
|
|
1263
|
+
* on its own, by last-write-wins against this row's `_field_versions`:
|
|
1264
|
+
*
|
|
1265
|
+
* - no row: inserted with each field stamped at its own version, `_created_at`
|
|
1266
|
+
* at the record's creation (the entry's `timestamp`), then any orphaned
|
|
1267
|
+
* updates/deletes are folded in as for any insert;
|
|
1268
|
+
* - live row, or a row hidden by a scope retraction: per-field LWW (a retracted
|
|
1269
|
+
* row is shown again; `_created_at` is kept);
|
|
1270
|
+
* - domain tombstone: revived only when the entry's newest field version is
|
|
1271
|
+
* newer than the tombstone, then per-field LWW.
|
|
1272
|
+
*
|
|
1273
|
+
* Deterministic and idempotent: the outcome depends only on the stored and
|
|
1274
|
+
* incoming per-field versions, and a re-applied entry wins no field.
|
|
1275
|
+
*/
|
|
1276
|
+
private applyFieldVersionedInsert;
|
|
782
1277
|
/**
|
|
783
1278
|
* Materialize update/delete operations that were logged for a record BEFORE
|
|
784
1279
|
* its insert arrived (reordered delivery). Runs inside the insert's write
|
|
@@ -842,11 +1337,34 @@ declare class Store implements OperationLog {
|
|
|
842
1337
|
*/
|
|
843
1338
|
notifyExternalOperation(operation: Operation): void;
|
|
844
1339
|
/**
|
|
845
|
-
*
|
|
1340
|
+
* Notify this store that another same-origin runtime changed rows of a collection
|
|
1341
|
+
* in the shared local database without an operation this tab can see (a re-fold,
|
|
1342
|
+
* a scope retraction, a restore; RT-98). Only re-runs the affected live queries.
|
|
1343
|
+
*
|
|
1344
|
+
* @param collection - The collection whose rows changed
|
|
1345
|
+
*/
|
|
1346
|
+
notifyExternalChange(collection: string): void;
|
|
1347
|
+
/**
|
|
1348
|
+
* Listen to every committed change this store makes to collection rows: local
|
|
1349
|
+
* writes, remote applies, and changes without a new operation (re-folds, scope
|
|
1350
|
+
* retraction, cascade settling, rematerialization, restore). The cross-tab bus
|
|
1351
|
+
* relays these to other tabs on the same database (RT-98).
|
|
1352
|
+
*
|
|
1353
|
+
* @returns An unsubscribe function
|
|
1354
|
+
*/
|
|
1355
|
+
onRecordsChanged(listener: RecordsChangeListener): () => void;
|
|
1356
|
+
/**
|
|
1357
|
+
* Build the local write context for a collection: everything the single local
|
|
1358
|
+
* write path needs (used by Collection writes and by ApplyPipeline).
|
|
846
1359
|
*/
|
|
847
1360
|
createMutationContext(collection: string, options?: {
|
|
848
|
-
inTransaction?: boolean;
|
|
849
1361
|
extraCausalDeps?: string[];
|
|
1362
|
+
/**
|
|
1363
|
+
* Stamp the writes with this clock instead of the store's (it must be this
|
|
1364
|
+
* node's). Used for the side effects of a remote delete, stamped right after
|
|
1365
|
+
* the delete like the server's copy (seam 5), never with the device's "now".
|
|
1366
|
+
*/
|
|
1367
|
+
clock?: HybridLogicalClock;
|
|
850
1368
|
}): LocalMutationContext;
|
|
851
1369
|
/**
|
|
852
1370
|
* Load a materialized row by ID, including soft-deleted tombstones.
|
|
@@ -871,6 +1389,25 @@ declare class Store implements OperationLog {
|
|
|
871
1389
|
* Persist the last server version vector this client believes the server has applied.
|
|
872
1390
|
*/
|
|
873
1391
|
saveLastAckedServerVector(vector: VersionVector): Promise<void>;
|
|
1392
|
+
/**
|
|
1393
|
+
* Load the per-device node token the sync server issued for this node id (RT-12).
|
|
1394
|
+
*/
|
|
1395
|
+
loadNodeToken(nodeId?: string): Promise<string | null>;
|
|
1396
|
+
/**
|
|
1397
|
+
* Persist the per-device node token under its node id (the current one by default).
|
|
1398
|
+
*/
|
|
1399
|
+
saveNodeToken(token: string, nodeId?: string): Promise<void>;
|
|
1400
|
+
/**
|
|
1401
|
+
* Load the node ids the sync server named authoritative (protocol v2), or null when
|
|
1402
|
+
* no protocol-2 server ever answered.
|
|
1403
|
+
*/
|
|
1404
|
+
loadAuthoritativeNodeIds(): Promise<string[] | null>;
|
|
1405
|
+
/**
|
|
1406
|
+
* Persist the node ids the sync server named authoritative (protocol v2). Same as
|
|
1407
|
+
* {@link setAuthoritativeNodeIds}: merged into the union of learned explicit ids,
|
|
1408
|
+
* and the fold re-folds affected records only when that set grows.
|
|
1409
|
+
*/
|
|
1410
|
+
saveAuthoritativeNodeIds(nodeIds: string[]): Promise<void>;
|
|
874
1411
|
/**
|
|
875
1412
|
* Load persisted delta cursor for resuming paginated initial sync.
|
|
876
1413
|
*/
|
|
@@ -896,6 +1433,31 @@ declare class Store implements OperationLog {
|
|
|
896
1433
|
* Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
|
|
897
1434
|
*/
|
|
898
1435
|
deleteDeliveryWatermark(signature: string): Promise<void>;
|
|
1436
|
+
/**
|
|
1437
|
+
* Record delivered operations the sync engine deliberately did not apply (inbound
|
|
1438
|
+
* quarantine, W4) and, when given, advance a view's delivery watermark in the same
|
|
1439
|
+
* transaction, so the watermark never passes an operation that is neither applied nor
|
|
1440
|
+
* recorded.
|
|
1441
|
+
*/
|
|
1442
|
+
saveInboundQuarantine(entries: UnappliedOperation[], watermark?: {
|
|
1443
|
+
signature: string;
|
|
1444
|
+
watermark: number;
|
|
1445
|
+
}): Promise<void>;
|
|
1446
|
+
/** Every quarantined inbound operation, oldest delivery first. */
|
|
1447
|
+
loadInboundQuarantine(): Promise<UnappliedOperation[]>;
|
|
1448
|
+
/** Remove quarantined inbound operations (applied on replay, or reconciled). */
|
|
1449
|
+
removeInboundQuarantine(operationIds: string[]): Promise<void>;
|
|
1450
|
+
/**
|
|
1451
|
+
* The contiguous acknowledged prefix of this device's own operations for a node id,
|
|
1452
|
+
* or null when none was recorded under that contract (W3).
|
|
1453
|
+
*/
|
|
1454
|
+
loadOwnAckedThrough(nodeId: string): Promise<number | null>;
|
|
1455
|
+
/** Persist the contiguous acknowledged own-operation prefix for a node id. */
|
|
1456
|
+
saveOwnAckedThrough(nodeId: string, sequence: number): Promise<void>;
|
|
1457
|
+
/** The downlink scope the sync server last accepted (null when none). */
|
|
1458
|
+
loadAcceptedDownlinkScope(): Promise<Record<string, Record<string, unknown>> | null>;
|
|
1459
|
+
/** Persist (or clear) the downlink scope the sync server last accepted. */
|
|
1460
|
+
saveAcceptedDownlinkScope(scope: Record<string, Record<string, unknown>> | null): Promise<void>;
|
|
899
1461
|
/**
|
|
900
1462
|
* Local operations not yet reflected on the server version vector.
|
|
901
1463
|
*/
|
|
@@ -911,14 +1473,155 @@ declare class Store implements OperationLog {
|
|
|
911
1473
|
* No data-change notifications are emitted: materialized values are
|
|
912
1474
|
* unchanged, only version stamps move.
|
|
913
1475
|
*/
|
|
1476
|
+
/**
|
|
1477
|
+
* Move this device to a fresh node id, re-authoring its never-acknowledged
|
|
1478
|
+
* operations under it (RT-21). Used when the sync server refuses the current node
|
|
1479
|
+
* id (`NODE_ID_CLAIMED`): the device keeps every local write and uploads it under
|
|
1480
|
+
* the new id. Acknowledged history stays under the old id. Persisted atomically;
|
|
1481
|
+
* later writes use the new id.
|
|
1482
|
+
*
|
|
1483
|
+
* @param unsyncedOpIds - Ids of this node's operations the server never acknowledged
|
|
1484
|
+
* @returns The new node id and the rewritten operations (for the outbound queue)
|
|
1485
|
+
* @throws {KoraError} When the node id is pinned (`StoreConfig.nodeId`, or per-tab isolation)
|
|
1486
|
+
*/
|
|
1487
|
+
rotateNodeId(unsyncedOpIds: string[]): Promise<NodeRotationResult>;
|
|
1488
|
+
/**
|
|
1489
|
+
* Move this database back to a node id it authored under before (RT-38): after the
|
|
1490
|
+
* sync server refused the current node, the device tries a node a returning user
|
|
1491
|
+
* owns. Nothing is rewritten; later writes use `nodeId`.
|
|
1492
|
+
*
|
|
1493
|
+
* @throws {KoraError} When the node id is pinned, or `nodeId` is not a local node
|
|
1494
|
+
*/
|
|
1495
|
+
switchNodeId(nodeId: string): Promise<void>;
|
|
1496
|
+
/** Point the clock, sequence counter and collections at `nodeId` (already persisted). */
|
|
1497
|
+
private rebindToNode;
|
|
1498
|
+
/**
|
|
1499
|
+
* Bind this database's writes to the signed-in user (RT-42). Call it whenever the
|
|
1500
|
+
* signed-in user is known or changes, BEFORE the next local write (`createApp` does
|
|
1501
|
+
* so at start and on every auth change of `sync.authClient`).
|
|
1502
|
+
*
|
|
1503
|
+
* - The current node belongs to `principal`: nothing changes.
|
|
1504
|
+
* - The current node is unbound and has no history (no write, never synced): it is
|
|
1505
|
+
* bound to `principal` (every write under it from now on is theirs).
|
|
1506
|
+
* - The current node is unbound but has history (a database from before RT-42, or
|
|
1507
|
+
* writes made while nobody was signed in): its owner is NOT guessed (RT-50). The
|
|
1508
|
+
* store moves to `principal`'s own node, and the old node's owner is learned from
|
|
1509
|
+
* the sync server (the first accepted handshake as it binds it; a refusal rules
|
|
1510
|
+
* that user out). A node that never synced cannot be attributed by anyone: its
|
|
1511
|
+
* unsynced writes are held for the app to assign or discard (`app.sync.assignHeld`,
|
|
1512
|
+
* `app.sync.discardHeld`).
|
|
1513
|
+
* - It belongs to another user: the store moves to `principal`'s own local node (the
|
|
1514
|
+
* most recent one, not held), or to a fresh node bound to `principal`. Nothing is
|
|
1515
|
+
* rewritten: the other user's unsynced writes stay under their node, and sync never
|
|
1516
|
+
* uploads them on this user's session (they count as `heldOperations`).
|
|
1517
|
+
*
|
|
1518
|
+
* A pinned node id (`StoreConfig.nodeId`, such as an auth device id) cannot move:
|
|
1519
|
+
* `conflict` is then true when it belongs to another user, and sync refuses to
|
|
1520
|
+
* upload it under `principal`; an unbound pinned node with history stays unbound
|
|
1521
|
+
* until the server accepts it for a user. Under `per-tab` isolation the tab moves to
|
|
1522
|
+
* a fresh per-tab node.
|
|
1523
|
+
*
|
|
1524
|
+
* @param principal - The signed-in user id
|
|
1525
|
+
* @returns The node now in use and whether it changed
|
|
1526
|
+
*/
|
|
1527
|
+
bindPrincipal(principal: string): Promise<PrincipalBinding>;
|
|
1528
|
+
/**
|
|
1529
|
+
* A sync handshake as `nodeId` was accepted for `principal` (RT-50): bind an unbound
|
|
1530
|
+
* node (or replace a guessed binding) from the server's answer.
|
|
1531
|
+
*/
|
|
1532
|
+
confirmNodePrincipal(nodeId: string, principal: string): Promise<void>;
|
|
1533
|
+
/**
|
|
1534
|
+
* The sync server refused `nodeId` for `principal` (RT-50): clear a guessed binding,
|
|
1535
|
+
* and remember that an unbound node is not theirs.
|
|
1536
|
+
*/
|
|
1537
|
+
recordNodeRefusedFor(nodeId: string, principal: string): Promise<void>;
|
|
1538
|
+
/**
|
|
1539
|
+
* Assign a held, unbound local node's writes to `principal` (RT-50): an explicit app
|
|
1540
|
+
* decision for writes nobody can attribute (a database that never synced).
|
|
1541
|
+
*
|
|
1542
|
+
* @returns false when the node is unknown, the store's own, already bound, or was
|
|
1543
|
+
* refused for `principal` by the server
|
|
1544
|
+
*/
|
|
1545
|
+
assignNodePrincipal(nodeId: string, principal: string): Promise<boolean>;
|
|
1546
|
+
/**
|
|
1547
|
+
* Forget a local node other than the store's own whatever it still holds (RT-50: the
|
|
1548
|
+
* app discarded its held writes, which were marked never to upload first).
|
|
1549
|
+
*/
|
|
1550
|
+
dropLocalNode(nodeId: string): Promise<void>;
|
|
1551
|
+
/** The adoption schedule of this database's local nodes (RT-46). */
|
|
1552
|
+
loadAdoptionSchedule(): Promise<AdoptionSchedule>;
|
|
1553
|
+
/** Persist the adoption schedule (RT-46). */
|
|
1554
|
+
saveAdoptionSchedule(schedule: AdoptionSchedule): Promise<void>;
|
|
1555
|
+
/**
|
|
1556
|
+
* Raise a local node's sequence counter to at least `floor`, in a transaction (the
|
|
1557
|
+
* same MAX every reservation uses, W6), so the next write never reuses a number the
|
|
1558
|
+
* server already holds (RT-35: the device lost the tail of its log, for example on a
|
|
1559
|
+
* reload inside the IndexedDB snapshot window or after restoring an older copy).
|
|
1560
|
+
*
|
|
1561
|
+
* @returns Whether the counter moved
|
|
1562
|
+
*/
|
|
1563
|
+
raiseSequenceFloor(nodeId: string, floor: number): Promise<boolean>;
|
|
1564
|
+
/**
|
|
1565
|
+
* Give one of a local node's operations a fresh sequence number above `floor`
|
|
1566
|
+
* (RT-35): the server refused it with `SEQUENCE_CONFLICT` because it holds another
|
|
1567
|
+
* operation of this node under that number (one this device lost). A version-1
|
|
1568
|
+
* operation keeps its id (its hash does not cover the sequence number), exactly like
|
|
1569
|
+
* the W6 sequence repair; a version-2 operation (protocol v2) is re-hashed under the
|
|
1570
|
+
* new number, so it carries a new id. The old identity is recorded in
|
|
1571
|
+
* `_kora_seq_conflicts` (`reemitted_as`).
|
|
1572
|
+
*
|
|
1573
|
+
* A new id would leave later operations naming the old one in `causalDeps`: those in
|
|
1574
|
+
* `rewritableDependents` (operations the server never stored: never sent) are
|
|
1575
|
+
* rewritten to name the new id, and re-hashed, transitively, in the same
|
|
1576
|
+
* transaction. Dependents already sent keep their ids (the server may hold them);
|
|
1577
|
+
* their dep resolves through the `_kora_seq_conflicts` record. Every touched record
|
|
1578
|
+
* is re-folded (fold stamps carry operation ids).
|
|
1579
|
+
*
|
|
1580
|
+
* @param rewritableDependents - Ids of this node's operations that were never sent
|
|
1581
|
+
* @returns The renumbered operation and the rewritten dependents, or null when the
|
|
1582
|
+
* operation is not in the log
|
|
1583
|
+
*/
|
|
1584
|
+
resequenceOperation(operationId: string, nodeId: string, floor: number, rewritableDependents?: readonly string[]): Promise<ResequenceResult | null>;
|
|
1585
|
+
/**
|
|
1586
|
+
* Durability barrier (RT-35): resolves once every write committed so far is durable
|
|
1587
|
+
* on this device. Sync awaits it before an operation leaves the device.
|
|
1588
|
+
*/
|
|
1589
|
+
ensureDurable(): Promise<void>;
|
|
1590
|
+
/** Record operations the server refused for good (RT-36). Never cleared. */
|
|
1591
|
+
recordTerminalRejections(entries: TerminalRejection[]): Promise<void>;
|
|
1592
|
+
/** Which of these operation ids the server refused for good (RT-36). */
|
|
1593
|
+
findTerminalRejections(operationIds: string[]): Promise<Set<string>>;
|
|
1594
|
+
/** Every node id this database authored operations under (RT-38, RT-40). */
|
|
1595
|
+
listLocalNodes(): Promise<LocalNodeRecord[]>;
|
|
1596
|
+
/** Forget a drained local node other than this store's own (RT-40). */
|
|
1597
|
+
forgetLocalNode(nodeId: string): Promise<void>;
|
|
1598
|
+
/** The current refusal cycle: the count of accepted sync handshakes (RT-38). */
|
|
1599
|
+
loadAcceptedCycle(): Promise<number>;
|
|
1600
|
+
/** Record an accepted sync handshake as `nodeId` (RT-38). */
|
|
1601
|
+
markLocalNodeAccepted(nodeId: string): Promise<void>;
|
|
1602
|
+
/** Record that the sync server refused `nodeId`; `held` holds its writes (RT-38). */
|
|
1603
|
+
markLocalNodeRefused(nodeId: string, held: boolean): Promise<void>;
|
|
1604
|
+
/**
|
|
1605
|
+
* Take over another local node's unsynced writes (RT-40): resolves to a release
|
|
1606
|
+
* function when no live tab uses `nodeId` (its lock is free), or null when one does.
|
|
1607
|
+
* Without Web Locks (Node, old browsers) the node is assumed free.
|
|
1608
|
+
*/
|
|
1609
|
+
claimLocalNode(nodeId: string): Promise<(() => void) | null>;
|
|
1610
|
+
/** Whether a live tab holds `nodeId` (per-tab isolation, RT-40). */
|
|
1611
|
+
isLocalNodeLive(nodeId: string): Promise<boolean>;
|
|
914
1612
|
rebaseUnsyncedOperations(unsyncedOpIds: string[], correctedNowMs: number): Promise<ClockRebaseResult>;
|
|
915
1613
|
/**
|
|
916
1614
|
* Count of local operations ahead of the server version vector.
|
|
917
1615
|
*/
|
|
918
1616
|
countUnsyncedOperations(serverVector: VersionVector): Promise<number>;
|
|
919
1617
|
/**
|
|
920
|
-
* Compact the local operation log
|
|
921
|
-
*
|
|
1618
|
+
* Compact the local operation log. Only removes ops the server has acknowledged
|
|
1619
|
+
* (per {@link CompactionStrategy}). With the W7 fold (STORE-14), the effect of the
|
|
1620
|
+
* removed operations is first joined into each record's base fold state, so a
|
|
1621
|
+
* re-fold (rejection exclusion, a later re-materialization) and the merge of a
|
|
1622
|
+
* late operation stay exact; delete, atomic and custom-resolver operations are
|
|
1623
|
+
* kept, and a re-delivered compacted id is a duplicate by its node's compacted
|
|
1624
|
+
* prefix.
|
|
922
1625
|
*/
|
|
923
1626
|
compact(strategy: CompactionStrategy): Promise<CompactionResult>;
|
|
924
1627
|
/**
|
|
@@ -971,13 +1674,46 @@ declare class Store implements OperationLog {
|
|
|
971
1674
|
*/
|
|
972
1675
|
exportBackup(options?: BackupOptions): Promise<Uint8Array>;
|
|
973
1676
|
/**
|
|
974
|
-
* Restore data from a backup binary.
|
|
1677
|
+
* Restore data from a backup binary (format version 2, STORE-5).
|
|
1678
|
+
*
|
|
1679
|
+
* - `merge: true`: every backup operation is applied through the remote-apply path
|
|
1680
|
+
* (deduplicated by id, merged per field), the version vector advances by MAX, and
|
|
1681
|
+
* nothing of the exporting device's identity or sync state is imported.
|
|
1682
|
+
* - Replace (default): the data is replaced by the backup's while this device keeps
|
|
1683
|
+
* its node id, local nodes and principal bindings, its own sequence counters never
|
|
1684
|
+
* move backwards, and unsynced own writes are kept (see
|
|
1685
|
+
* `RestoreOptions.keepUnsyncedWrites`). Then {@link reloadFromDisk} runs.
|
|
1686
|
+
*
|
|
1687
|
+
* A version-1 file is refused (`errorCode: 'BACKUP_FORMAT_OUTDATED'`); convert it with
|
|
1688
|
+
* `convertBackupV1`.
|
|
975
1689
|
*
|
|
976
1690
|
* @param data - The backup data
|
|
977
|
-
* @param options - Restore options (merge, collections, onProgress)
|
|
1691
|
+
* @param options - Restore options (merge, collections, keepUnsyncedWrites, onProgress)
|
|
1692
|
+
* @param internal - `applyOperation`: the app's remote-apply path (defaults to
|
|
1693
|
+
* {@link applyRemoteOperation}); `createApp` passes the merge-aware pipeline sync uses
|
|
978
1694
|
* @returns Result of the restore operation
|
|
979
1695
|
*/
|
|
980
|
-
importBackup(data: Uint8Array, options?: RestoreOptions
|
|
1696
|
+
importBackup(data: Uint8Array, options?: RestoreOptions, internal?: {
|
|
1697
|
+
applyOperation?: (operation: Operation) => Promise<ApplyResult>;
|
|
1698
|
+
}): Promise<RestoreResult>;
|
|
1699
|
+
/**
|
|
1700
|
+
* Merge mode (RT-66): join the backup's compacted base states and row snapshots
|
|
1701
|
+
* into this database's, and re-fold those records, before the backup's operations
|
|
1702
|
+
* are applied. The join is the fold's state-based merge, so the result equals
|
|
1703
|
+
* folding both devices' histories together. Compacted prefixes advance by MAX
|
|
1704
|
+
* (unfiltered restores only: the prefix is per node, across collections).
|
|
1705
|
+
*/
|
|
1706
|
+
private mergeBackupFoldState;
|
|
1707
|
+
/**
|
|
1708
|
+
* Re-read this store's state from the database after it changed underneath it (a
|
|
1709
|
+
* backup restore): the node id, version vector and own sequence counter, the clock
|
|
1710
|
+
* (advanced past every timestamp in the log), and a fresh causal tracker. Every live
|
|
1711
|
+
* query re-runs. The local node registry and terminal rejections are always read
|
|
1712
|
+
* from the database, so they need no reload.
|
|
1713
|
+
*/
|
|
1714
|
+
reloadFromDisk(): Promise<void>;
|
|
1715
|
+
/** The greatest HLC timestamp in the operation log (canonical strings sort by HLC). */
|
|
1716
|
+
private loadNewestLogTimestamp;
|
|
981
1717
|
/**
|
|
982
1718
|
* Read backup manifest without loading the entire backup.
|
|
983
1719
|
*
|
|
@@ -986,21 +1722,22 @@ declare class Store implements OperationLog {
|
|
|
986
1722
|
*/
|
|
987
1723
|
static readBackupManifest(data: Uint8Array): BackupManifest;
|
|
988
1724
|
private recordOperationSequence;
|
|
989
|
-
|
|
1725
|
+
/** Post-commit publication of a committed local operation. */
|
|
1726
|
+
private publishLocalOperation;
|
|
990
1727
|
/**
|
|
991
|
-
*
|
|
992
|
-
*
|
|
1728
|
+
* Map an out-of-space storage failure to `store:quota-exceeded` (STORE-15).
|
|
1729
|
+
* The error itself still propagates to the caller; this only makes the
|
|
1730
|
+
* condition observable on every adapter, not just IndexedDB.
|
|
993
1731
|
*/
|
|
994
|
-
private
|
|
1732
|
+
private reportStorageError;
|
|
1733
|
+
/** Report a database a newer build migrated (`store:schema-ahead`) and build the error. */
|
|
1734
|
+
private schemaAhead;
|
|
995
1735
|
/**
|
|
996
|
-
*
|
|
1736
|
+
* Run pending schema migrations (STORE-13, NEW-STORE-1): one transaction per version
|
|
1737
|
+
* (DDL, backfills, `schema_version`); backfills write operations through the local
|
|
1738
|
+
* write path so they sync, unless declared `localOnly`.
|
|
997
1739
|
*/
|
|
998
|
-
private
|
|
999
|
-
/**
|
|
1000
|
-
* Run a backfill transform on all records in a collection.
|
|
1001
|
-
* Reads all rows, applies the transform, and updates changed fields.
|
|
1002
|
-
*/
|
|
1003
|
-
private runBackfill;
|
|
1740
|
+
private runMigrationsIfNeeded;
|
|
1004
1741
|
private loadOrGenerateNodeId;
|
|
1005
1742
|
private loadSequenceNumber;
|
|
1006
1743
|
private loadVersionVector;
|
|
@@ -1017,6 +1754,147 @@ interface CollectionAccessor {
|
|
|
1017
1754
|
where(conditions: Record<string, unknown>): QueryBuilder;
|
|
1018
1755
|
}
|
|
1019
1756
|
|
|
1757
|
+
declare const LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
|
|
1758
|
+
/**
|
|
1759
|
+
* Serialize a version vector for `_kora_meta` storage.
|
|
1760
|
+
*/
|
|
1761
|
+
declare function serializeVersionVectorToMeta(vector: VersionVector): string;
|
|
1762
|
+
/**
|
|
1763
|
+
* Deserialize a version vector from `_kora_meta`.
|
|
1764
|
+
*/
|
|
1765
|
+
declare function deserializeVersionVectorFromMeta(value: string): VersionVector;
|
|
1766
|
+
/**
|
|
1767
|
+
* Merge two version vectors, keeping the maximum sequence per node.
|
|
1768
|
+
*/
|
|
1769
|
+
declare function mergeVersionVectors(a: VersionVector, b: VersionVector): VersionVector;
|
|
1770
|
+
/**
|
|
1771
|
+
* Operations present locally but not yet on the server (per server version vector).
|
|
1772
|
+
*/
|
|
1773
|
+
declare function collectOperationsAheadOfServer(localVector: VersionVector, serverVector: VersionVector, fetchRange: (nodeId: string, fromSeq: number, toSeq: number) => Promise<Operation[]>): Promise<Operation[]>;
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* Normalise a `where` clause to its canonical form (RT-102). This is the ONE place that
|
|
1777
|
+
* decides what a where value means, so the SQL the store runs, the descriptor and the
|
|
1778
|
+
* query key (store cache, React/Vue/Svelte bindings) always agree:
|
|
1779
|
+
*
|
|
1780
|
+
* - `undefined` means "no condition": `where({ projectId: undefined })` is `where({})`
|
|
1781
|
+
* (the usual "nothing selected yet" pattern), and an operator whose operand is
|
|
1782
|
+
* `undefined` (`{ $gt: undefined }`) is dropped; a field left with no operator is
|
|
1783
|
+
* dropped. A later `.where({ x: undefined })` therefore does not remove an earlier
|
|
1784
|
+
* condition on `x`.
|
|
1785
|
+
* - `null` means SQL `IS NULL` (`$ne: null` means `IS NOT NULL`).
|
|
1786
|
+
* - Non-finite numbers (`NaN`, `Infinity`) have no stored representation: refused.
|
|
1787
|
+
* - `undefined` inside a `$in` list is refused (ambiguous: no condition cannot be one
|
|
1788
|
+
* element of a list).
|
|
1789
|
+
*
|
|
1790
|
+
* @param where - The clause as the developer wrote it
|
|
1791
|
+
* @returns A new clause with the rules above applied
|
|
1792
|
+
* @throws {QueryError} For a non-finite number or an `undefined` `$in` element
|
|
1793
|
+
*/
|
|
1794
|
+
declare function normalizeWhere(where: WhereClause): WhereClause;
|
|
1795
|
+
/**
|
|
1796
|
+
* The canonical identity of a query: equal keys if and only if the descriptors run the
|
|
1797
|
+
* same query (RT-102). Used by the store's query-store cache and by every framework
|
|
1798
|
+
* binding to decide when to share a store or re-subscribe.
|
|
1799
|
+
*
|
|
1800
|
+
* Unlike `JSON.stringify`, nothing is dropped or conflated: `where` keys are sorted
|
|
1801
|
+
* (their order does not change the query), `undefined` is dropped exactly as
|
|
1802
|
+
* {@link normalizeWhere} drops it, and `NaN`, `Date` and byte values keep distinct,
|
|
1803
|
+
* typed encodings.
|
|
1804
|
+
*
|
|
1805
|
+
* @param descriptor - The query descriptor (`queryBuilder.getDescriptor()`)
|
|
1806
|
+
* @returns A string key
|
|
1807
|
+
*/
|
|
1808
|
+
declare function queryKey(descriptor: QueryDescriptor): string;
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* Durable-storage state of this origin, as far as Kora knows (NEW-STORE-4).
|
|
1812
|
+
* - `unknown`: not checked yet (the boot check runs in the background).
|
|
1813
|
+
* - `persisted`: the browser granted persistent storage; it will not evict this
|
|
1814
|
+
* origin's data under storage pressure.
|
|
1815
|
+
* - `best-effort`: storage may be evicted under pressure (the browser default).
|
|
1816
|
+
* - `unsupported`: the runtime has no StorageManager persistence API (Node,
|
|
1817
|
+
* older browsers, some WebViews).
|
|
1818
|
+
* - `error`: the check or request threw; `lastError` says why.
|
|
1819
|
+
*/
|
|
1820
|
+
type StoragePersistenceState = 'unknown' | 'persisted' | 'best-effort' | 'unsupported' | 'error';
|
|
1821
|
+
/** Snapshot returned by `app.storage.persistence.status()`. */
|
|
1822
|
+
interface StoragePersistenceStatus {
|
|
1823
|
+
state: StoragePersistenceState;
|
|
1824
|
+
/** True only when the browser reported persistent storage. */
|
|
1825
|
+
persisted: boolean;
|
|
1826
|
+
/** True once `persist()` was asked for (explicitly or by an automatic trigger). */
|
|
1827
|
+
requested: boolean;
|
|
1828
|
+
/** Message of the last failure, when `state` is `error`. */
|
|
1829
|
+
lastError?: string;
|
|
1830
|
+
}
|
|
1831
|
+
/** Why an automatic (background) persistence request was made. */
|
|
1832
|
+
type PersistenceRequestReason = 'explicit' | 'sign-in' | 'first-write' | 'installed-app';
|
|
1833
|
+
/** The subset of `navigator.storage` used here. */
|
|
1834
|
+
interface PersistenceStorageManager {
|
|
1835
|
+
persist?: () => Promise<boolean>;
|
|
1836
|
+
persisted?: () => Promise<boolean>;
|
|
1837
|
+
}
|
|
1838
|
+
/** Options for {@link StoragePersistence}. */
|
|
1839
|
+
interface StoragePersistenceOptions {
|
|
1840
|
+
/** Receives `storage:persistence` events. */
|
|
1841
|
+
emitter?: KoraEventEmitter | null;
|
|
1842
|
+
/**
|
|
1843
|
+
* The StorageManager to use. Defaults to `globalThis.navigator.storage`,
|
|
1844
|
+
* resolved on each call so tests and late polyfills are honoured.
|
|
1845
|
+
*/
|
|
1846
|
+
storage?: PersistenceStorageManager | null;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Tracks and requests durable storage (`navigator.storage.persist()`) without ever
|
|
1850
|
+
* putting it on the startup path (NEW-STORE-4).
|
|
1851
|
+
*
|
|
1852
|
+
* In Firefox `persist()` opens a permission prompt and its promise stays pending
|
|
1853
|
+
* until the user answers, so awaiting it inside startup could hang `app.ready`
|
|
1854
|
+
* forever. Kora therefore:
|
|
1855
|
+
* - checks `persisted()` at boot, which never prompts, in the background;
|
|
1856
|
+
* - requests persistence only on an explicit `request()` or, without awaiting,
|
|
1857
|
+
* after a meaningful moment (sign-in, the first local write, running as an
|
|
1858
|
+
* installed app);
|
|
1859
|
+
* - surfaces every result through `status()` and the `storage:persistence` event,
|
|
1860
|
+
* never by blocking.
|
|
1861
|
+
*/
|
|
1862
|
+
declare class StoragePersistence {
|
|
1863
|
+
private current;
|
|
1864
|
+
private inFlightRequest;
|
|
1865
|
+
private readonly emitter;
|
|
1866
|
+
private readonly storageOverride;
|
|
1867
|
+
constructor(options?: StoragePersistenceOptions);
|
|
1868
|
+
/** Synchronous snapshot of the last known state. Never prompts. */
|
|
1869
|
+
status(): StoragePersistenceStatus;
|
|
1870
|
+
/**
|
|
1871
|
+
* Read whether storage is already persistent (`navigator.storage.persisted()`).
|
|
1872
|
+
* Never prompts and never throws: a failure becomes `state: 'error'`.
|
|
1873
|
+
*/
|
|
1874
|
+
check(): Promise<StoragePersistenceStatus>;
|
|
1875
|
+
/**
|
|
1876
|
+
* Ask the browser for persistent storage (`navigator.storage.persist()`).
|
|
1877
|
+
* May show a permission prompt (Firefox), so call it from a user gesture or a
|
|
1878
|
+
* meaningful moment, and do not block rendering on it. Concurrent calls share
|
|
1879
|
+
* one request. Never throws: a failure becomes `state: 'error'`.
|
|
1880
|
+
*
|
|
1881
|
+
* @returns The state after the browser answered
|
|
1882
|
+
*/
|
|
1883
|
+
request(): Promise<StoragePersistenceStatus>;
|
|
1884
|
+
/**
|
|
1885
|
+
* Fire-and-forget `request()` for an automatic trigger. Never awaited by Kora,
|
|
1886
|
+
* so a pending prompt cannot block anything; the outcome arrives as a
|
|
1887
|
+
* `storage:persistence` event. Skipped when storage is already persistent or a
|
|
1888
|
+
* request was already made this session.
|
|
1889
|
+
*
|
|
1890
|
+
* @param _reason - Which trigger fired (kept for tracing at call sites)
|
|
1891
|
+
*/
|
|
1892
|
+
requestInBackground(_reason: PersistenceRequestReason): void;
|
|
1893
|
+
private storage;
|
|
1894
|
+
private update;
|
|
1895
|
+
private emit;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1020
1898
|
/**
|
|
1021
1899
|
* Bridges the async QueryBuilder.subscribe() API with synchronous snapshot reads
|
|
1022
1900
|
* required by React useSyncExternalStore, Vue composables, and Svelte stores.
|
|
@@ -1030,6 +1908,7 @@ declare class QueryStore<T = CollectionRecord> {
|
|
|
1030
1908
|
private unsubscribeQuery;
|
|
1031
1909
|
private active;
|
|
1032
1910
|
private hasEmittedSnapshot;
|
|
1911
|
+
private error;
|
|
1033
1912
|
private readonly queryBuilder;
|
|
1034
1913
|
constructor(queryBuilder: QueryBuilder<T>);
|
|
1035
1914
|
/**
|
|
@@ -1045,6 +1924,12 @@ declare class QueryStore<T = CollectionRecord> {
|
|
|
1045
1924
|
* authoritative result for the current subscription lifecycle.
|
|
1046
1925
|
*/
|
|
1047
1926
|
hasSnapshot: () => boolean;
|
|
1927
|
+
/**
|
|
1928
|
+
* The latest query failure, or null. Set when the query (or a re-run after a
|
|
1929
|
+
* write) fails; cleared by the next successful delivery. Listeners are notified
|
|
1930
|
+
* on both transitions, so bindings read it next to `getSnapshot()`.
|
|
1931
|
+
*/
|
|
1932
|
+
getError: () => Error | null;
|
|
1048
1933
|
/** Tear down listeners and the underlying query subscription. */
|
|
1049
1934
|
destroy(): void;
|
|
1050
1935
|
private startSubscription;
|
|
@@ -1360,14 +2245,22 @@ declare function pluralize(word: string): string;
|
|
|
1360
2245
|
*/
|
|
1361
2246
|
declare function singularize(word: string): string;
|
|
1362
2247
|
|
|
2248
|
+
/** Current backup format version. */
|
|
2249
|
+
declare const BACKUP_VERSION = 2;
|
|
2250
|
+
/** A backup file this release cannot restore as-is. */
|
|
2251
|
+
declare class BackupFormatError extends KoraError {
|
|
2252
|
+
constructor(message: string, code: string, context?: Record<string, unknown>);
|
|
2253
|
+
}
|
|
1363
2254
|
/**
|
|
1364
|
-
* Export a backup from
|
|
2255
|
+
* Export a backup (format version 2) from an opened database.
|
|
1365
2256
|
*
|
|
1366
|
-
*
|
|
2257
|
+
* Everything is read inside one transaction, so the backup is a consistent snapshot.
|
|
2258
|
+
* Operations are exported in canonical form (exactly what `deserializeOperation` reads
|
|
2259
|
+
* back), including tombstones, so a restore rebuilds the same log.
|
|
1367
2260
|
*
|
|
1368
2261
|
* @param adapter - The storage adapter
|
|
1369
2262
|
* @param schema - The schema definition
|
|
1370
|
-
* @param nodeId -
|
|
2263
|
+
* @param nodeId - Node id of the exporting device (informational only; never imported)
|
|
1371
2264
|
* @param schemaVersion - Schema version
|
|
1372
2265
|
* @param options - Backup options
|
|
1373
2266
|
* @returns Backup as a Uint8Array
|
|
@@ -1375,7 +2268,6 @@ declare function singularize(word: string): string;
|
|
|
1375
2268
|
declare function exportBackup(adapter: StorageAdapter, schema: SchemaDefinition, nodeId: string, schemaVersion: number, options?: BackupOptions): Promise<Uint8Array>;
|
|
1376
2269
|
/**
|
|
1377
2270
|
* Read backup manifest without loading the entire backup.
|
|
1378
|
-
* Parses only the first section (which is always the manifest).
|
|
1379
2271
|
*
|
|
1380
2272
|
* @param data - The raw backup data
|
|
1381
2273
|
* @returns The backup manifest
|
|
@@ -1388,14 +2280,41 @@ declare function readBackupManifest(data: Uint8Array): BackupManifest;
|
|
|
1388
2280
|
* @returns True if the checksum is valid
|
|
1389
2281
|
*/
|
|
1390
2282
|
declare function verifyBackupChecksum(data: Uint8Array): Promise<boolean>;
|
|
2283
|
+
/** Options for {@link convertBackupV1}. */
|
|
2284
|
+
interface ConvertBackupV1Options {
|
|
2285
|
+
/**
|
|
2286
|
+
* Drop operations whose timestamp cannot be recovered instead of refusing the file.
|
|
2287
|
+
* Default false.
|
|
2288
|
+
*/
|
|
2289
|
+
dropUnrecoverable?: boolean;
|
|
2290
|
+
}
|
|
1391
2291
|
/**
|
|
1392
|
-
*
|
|
2292
|
+
* Convert a version-1 backup (Kora 1.0.0-beta.12 and earlier) to version 2.
|
|
1393
2293
|
*
|
|
1394
|
-
*
|
|
2294
|
+
* - Operations: a v1 file exported from a database that a v1 restore had damaged holds
|
|
2295
|
+
* misread timestamps (`wallTime: null`); they are recovered from the misread values
|
|
2296
|
+
* (the same rule as the log-integrity scan). An unrecoverable one refuses the file
|
|
2297
|
+
* unless `dropUnrecoverable` is set.
|
|
2298
|
+
* - The `meta` section (the exporting device's node id, node tokens, sync state) is
|
|
2299
|
+
* dropped: it must never be imported into another database.
|
|
2300
|
+
* - Record rows are kept; v1 exported live rows only, so the manifest records
|
|
2301
|
+
* `includesTombstones: false`. Bytes that v1 serialized as `{"0":..,"1":..}` objects
|
|
2302
|
+
* are decoded on restore.
|
|
2303
|
+
*
|
|
2304
|
+
* @throws {BackupFormatError} When the input is not a valid version-1 backup
|
|
2305
|
+
*/
|
|
2306
|
+
declare function convertBackupV1(data: Uint8Array, options?: ConvertBackupV1Options): Promise<Uint8Array>;
|
|
2307
|
+
/**
|
|
2308
|
+
* Restore a backup into the database behind `adapter`.
|
|
2309
|
+
*
|
|
2310
|
+
* @deprecated Use `store.importBackup()` / `app.importBackup()`: restoring needs the
|
|
2311
|
+
* store (the device's identity, version vector, clock and live queries). This entry
|
|
2312
|
+
* point opens a temporary `Store` on `adapter`, imports, and CLOSES the adapter.
|
|
2313
|
+
*
|
|
2314
|
+
* @param adapter - A storage adapter no store is using
|
|
1395
2315
|
* @param schema - The schema definition
|
|
1396
2316
|
* @param data - The backup data
|
|
1397
2317
|
* @param options - Restore options
|
|
1398
|
-
* @returns Result of the restore operation
|
|
1399
2318
|
*/
|
|
1400
2319
|
declare function restoreBackup(adapter: StorageAdapter, schema: SchemaDefinition, data: Uint8Array, options?: RestoreOptions): Promise<RestoreResult>;
|
|
1401
2320
|
|
|
@@ -1471,8 +2390,8 @@ declare class OpfsBlobStore implements ContentAddressedBlobStore {
|
|
|
1471
2390
|
}
|
|
1472
2391
|
/**
|
|
1473
2392
|
* Create an {@link OpfsBlobDirectory} backed by the browser's Origin Private
|
|
1474
|
-
* File System, rooted at a named subdirectory.
|
|
1475
|
-
* storage
|
|
2393
|
+
* File System, rooted at a named subdirectory. It does not request persistent
|
|
2394
|
+
* storage: that is `app.storage.persistence`, kept off the startup path.
|
|
1476
2395
|
*
|
|
1477
2396
|
* @param rootDirName - The OPFS subdirectory to store blobs under (default `kora-blobs`)
|
|
1478
2397
|
* @throws {Error} If OPFS is unavailable (e.g. called outside a browser)
|
|
@@ -1759,6 +2678,13 @@ interface ChunkResponseMessage {
|
|
|
1759
2678
|
requestId: string;
|
|
1760
2679
|
/** The chunk bytes, or null when the responder does not hold that hash. */
|
|
1761
2680
|
bytes: Uint8Array | null;
|
|
2681
|
+
/**
|
|
2682
|
+
* The server refused the request for rate, not because the chunk is missing
|
|
2683
|
+
* (RT-24). The requester retries after `retryAfterMs` instead of failing.
|
|
2684
|
+
*/
|
|
2685
|
+
throttled?: boolean;
|
|
2686
|
+
/** With `throttled`: how long to wait before retrying, in ms. */
|
|
2687
|
+
retryAfterMs?: number;
|
|
1762
2688
|
}
|
|
1763
2689
|
type ChunkMessage = ChunkRequestMessage | ChunkResponseMessage;
|
|
1764
2690
|
/**
|
|
@@ -1776,18 +2702,35 @@ interface RemoteChunkProvider extends ChunkProvider {
|
|
|
1776
2702
|
/** Number of requests still awaiting a response (for diagnostics/tests). */
|
|
1777
2703
|
pendingCount(): number;
|
|
1778
2704
|
}
|
|
2705
|
+
/** Options for {@link createRemoteChunkProvider}. */
|
|
2706
|
+
interface RemoteChunkProviderOptions {
|
|
2707
|
+
/** Per-attempt timeout in ms (default 30s). */
|
|
2708
|
+
timeoutMs?: number;
|
|
2709
|
+
/**
|
|
2710
|
+
* Longest total time one chunk may spend waiting on "throttled" answers before
|
|
2711
|
+
* the request fails (default 10 minutes). The transfer is resumable, so a failed
|
|
2712
|
+
* request can simply be retried later.
|
|
2713
|
+
*/
|
|
2714
|
+
maxThrottleWaitMs?: number;
|
|
2715
|
+
/** Smallest back-off after a throttled answer, in ms (default 250). */
|
|
2716
|
+
minThrottleDelayMs?: number;
|
|
2717
|
+
/** Largest single back-off after a throttled answer, in ms (default 30s). */
|
|
2718
|
+
maxThrottleDelayMs?: number;
|
|
2719
|
+
}
|
|
1779
2720
|
/**
|
|
1780
2721
|
* Create a {@link ChunkProvider} that requests chunks over a message port,
|
|
1781
2722
|
* correlating each answer to its request and timing out a stalled request so a
|
|
1782
2723
|
* dropped response cannot hang a transfer forever (the transfer is resumable, so
|
|
1783
2724
|
* a timed-out request can simply be retried).
|
|
1784
2725
|
*
|
|
2726
|
+
* A "throttled" answer (the server's blob request budget is spent, RT-24) is not a
|
|
2727
|
+
* failure: the request is re-sent after the server's `retryAfterMs` (bounded, with
|
|
2728
|
+
* exponential back-off when absent), until `maxThrottleWaitMs` has elapsed.
|
|
2729
|
+
*
|
|
1785
2730
|
* @param port - The message port to the peer that holds the blob
|
|
1786
|
-
* @param options
|
|
2731
|
+
* @param options - Timeouts and throttle back-off bounds
|
|
1787
2732
|
*/
|
|
1788
|
-
declare function createRemoteChunkProvider(port: ChunkMessagePort, options?:
|
|
1789
|
-
timeoutMs?: number;
|
|
1790
|
-
}): RemoteChunkProvider;
|
|
2733
|
+
declare function createRemoteChunkProvider(port: ChunkMessagePort, options?: RemoteChunkProviderOptions): RemoteChunkProvider;
|
|
1791
2734
|
/**
|
|
1792
2735
|
* Serve blob chunk requests arriving on a message port from a blob store. Answers
|
|
1793
2736
|
* each `blob-chunk-request` with the bytes for that hash, or null when the store
|
|
@@ -1807,4 +2750,4 @@ declare function createChunkPortPair(): {
|
|
|
1807
2750
|
b: ChunkMessagePort;
|
|
1808
2751
|
};
|
|
1809
2752
|
|
|
1810
|
-
export { AdapterError, ApplyRemoteOptions, type AuditExportManifest, type AuditExportOptions, type AuditExportPayload, type AuditExportProgress, type AuditTraceQuery, type BackupManifest, type BackupOptions, type BackupProgress, type BlobGcOptions, type BlobGcResult, type BlobManifest, type BlobManifestMetadata, COMPACTION_BASELINE_META_KEY, type ChunkMessage, type ChunkMessagePort, type ChunkProvider, type ChunkRequestMessage, type ChunkResponseMessage, type ClockRebaseResult, type CollectionAccessor, CollectionRecord, type CompactionResult, type CompactionStrategy, ContentAddressedBlobStore, type CreateRichTextControllerOptions, DEFAULT_CHUNK_SIZE, InvalidStateTransitionError, LAST_ACKED_SERVER_VECTOR_META_KEY, LocalMutationHandler, MaterializedRowSnapshot, MemoryBlobStore, NoLeaderError, type OpfsBlobDirectory, OpfsBlobStore, OptimisticLockError, OrderByDirection, type PersistedAuditTrace, PersistenceError, QueryBuilder, QueryDescriptor, QueryError, QueryStore, QueryStoreCache, type ReceiveBlobResult, type ReceiveBlobStores, RecordNotFoundError, type RemoteChunkProvider, type ReplaySnapshot, type RestoreOptions, type RestoreResult, type RichTextAwarenessUser, type RichTextController, type RichTextControllerSnapshot, type RichTextCursorInfo, type RichTextSyncEngine, RowVersionState, SequenceManager, type ServerBlobCallbacks, StorageAdapter, Store, StoreConfig, StoreNotOpenError, SubscriptionBloomFilter, SubscriptionCallback, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionStats, type TransactionCollectionAccessor, TransactionContext, type TransactionContextConfig, WhereClause, WorkerInitError, WorkerTimeoutError, asRichTextSyncEngine, assertQueryReady, chunkBlob, collectBlobGarbage, collectOperationsAheadOfServer, compactOperationLog, computeAckCompactionWatermark, createChunkPortPair, createMemoryServerBlobStore, createOpfsBlobDirectory, createOpfsBlobStore, createRemoteChunkProvider, createRichTextController, decodeAuditExport, decodeRichtext, deserializeVersionVectorFromMeta, encodeRichtext, exportBackup, extractBlobRefs, fetchBlobManifest, getSharedQueryStoreCache, mergeVersionVectors, parseBlobManifest, persistedAuditTraceFromEvent, pluralize, prepareBlobForSend, putBlobForTransfer, readAuditExportManifest, readBackupManifest, reassembleBlob, receiveBlob, resolveBlobManifest, restoreBackup, richtextStatesEqual, richtextToPlainText, serializeBlobManifest, serializeVersionVectorToMeta, serveBlobChunks, singularize, toServerBlobCallbacks, validateStateTransition, validateUpdateStateMachine, verifyAuditExportChecksum, verifyBackupChecksum };
|
|
2753
|
+
export { AdapterError, AdoptionSchedule, ApplyRemoteOptions, type AuditExportManifest, type AuditExportOptions, type AuditExportPayload, type AuditExportProgress, type AuditTraceQuery, BACKUP_VERSION, BackupFormatError, type BackupManifest, type BackupOptions, type BackupProgress, type BlobGcOptions, type BlobGcResult, type BlobManifest, type BlobManifestMetadata, BridgeTerminatedError, COMPACTION_BASELINE_META_KEY, type ChunkMessage, type ChunkMessagePort, type ChunkProvider, type ChunkRequestMessage, type ChunkResponseMessage, type ClockRebaseResult, type CollectionAccessor, CollectionRecord, type CompactionResult, type CompactionStrategy, ContentAddressedBlobStore, type ConvertBackupV1Options, type CreateRichTextControllerOptions, DEFAULT_CHUNK_SIZE, InvalidStateTransitionError, LAST_ACKED_SERVER_VECTOR_META_KEY, LOG_QUARANTINE_TABLE, LeaderUnresponsiveError, LocalMutationHandler, LocalNodeRecord, type LogIntegrityReport, type LogIntegrityRow, type LogRowProblem, type LogSequenceGap, MaterializedRowSnapshot, MemoryBlobStore, NoLeaderError, type NodeRotationResult, type OpfsBlobDirectory, OpfsBlobStore, OptimisticLockError, OrderByDirection, type PersistedAuditTrace, PersistenceError, type PersistenceRequestReason, type PersistenceStorageManager, type PrincipalBinding, QueryBuilder, QueryDescriptor, QueryError, QueryStore, QueryStoreCache, QuerySubscriptionError, type ReceiveBlobResult, type ReceiveBlobStores, RecordNotFoundError, type RecordsChange, type RecordsChangeListener, type RegisterOptions, type RematerializationMode, type RemoteChunkProvider, type RemoteChunkProviderOptions, type ReplaySnapshot, RequestAbortedError, type ResequenceResult, type RestoreOptions, type RestoreResult, type ResultsEqual, type RichTextAwarenessUser, type RichTextController, type RichTextControllerSnapshot, type RichTextCursorInfo, type RichTextSyncEngine, RowVersionState, SchemaVersionAheadError, SequenceManager, type ServerBlobCallbacks, StorageAdapter, StorageBackendMismatchError, StorageDurabilityError, StorageInUseError, StoragePersistence, type StoragePersistenceOptions, type StoragePersistenceState, type StoragePersistenceStatus, Store, StoreConfig, StoreNotOpenError, SubscribeOptions, SubscriptionBloomFilter, SubscriptionCallback, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionStats, TerminalRejection, type TransactionCollectionAccessor, TransactionContext, type TransactionContextConfig, type UnappliedOperation, UnsyncedDataError, WhereClause, WorkerInitError, WorkerTimeoutError, asRichTextSyncEngine, assertQueryReady, chunkBlob, collectBlobGarbage, collectOperationsAheadOfServer, compactOperationLog, computeAckCompactionWatermark, convertBackupV1, createChunkPortPair, createMemoryServerBlobStore, createOpfsBlobDirectory, createOpfsBlobStore, createRemoteChunkProvider, createRichTextController, decodeAuditExport, decodeRichtext, deserializeVersionVectorFromMeta, encodeRichtext, exportBackup, extractBlobRefs, fetchBlobManifest, getSharedQueryStoreCache, mergeVersionVectors, normalizeWhere, parseBlobManifest, persistedAuditTraceFromEvent, pluralize, prepareBlobForSend, putBlobForTransfer, queryKey, readAuditExportManifest, readBackupManifest, reassembleBlob, receiveBlob, resolveBlobManifest, restoreBackup, richtextStatesEqual, richtextToPlainText, serializeBlobManifest, serializeVersionVectorToMeta, serveBlobChunks, singularize, toServerBlobCallbacks, validateStateTransition, validateUpdateStateMachine, verifyAuditExportChecksum, verifyBackupChecksum };
|