@korajs/store 1.0.0-beta.12 → 1.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +29 -37
  2. package/dist/adapters/better-sqlite3.cjs +90 -4
  3. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  4. package/dist/adapters/better-sqlite3.d.cts +21 -12
  5. package/dist/adapters/better-sqlite3.d.ts +21 -12
  6. package/dist/adapters/better-sqlite3.js +69 -5
  7. package/dist/adapters/better-sqlite3.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +1316 -610
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.cts +29 -3
  11. package/dist/adapters/indexeddb.d.ts +29 -3
  12. package/dist/adapters/indexeddb.js +247 -221
  13. package/dist/adapters/indexeddb.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm-worker.cjs +856 -63
  15. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm-worker.js +529 -63
  17. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  18. package/dist/adapters/sqlite-wasm.cjs +1032 -344
  19. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  20. package/dist/adapters/sqlite-wasm.d.cts +239 -18
  21. package/dist/adapters/sqlite-wasm.d.ts +239 -18
  22. package/dist/adapters/sqlite-wasm.js +111 -5
  23. package/dist/adapters/sqlite-wasm.js.map +1 -1
  24. package/dist/backup-IHKVMUAM.js +25 -0
  25. package/dist/chunk-5AQSGMJV.js +5471 -0
  26. package/dist/chunk-5AQSGMJV.js.map +1 -0
  27. package/dist/chunk-5JMGMPUE.js +591 -0
  28. package/dist/chunk-5JMGMPUE.js.map +1 -0
  29. package/dist/{chunk-CYO3YKFL.js → chunk-5ZRRF6R2.js} +2 -2
  30. package/dist/chunk-A6GFH5IG.js +36 -0
  31. package/dist/chunk-A6GFH5IG.js.map +1 -0
  32. package/dist/chunk-BILSA6XZ.js +608 -0
  33. package/dist/chunk-BILSA6XZ.js.map +1 -0
  34. package/dist/chunk-BQSIL6TT.js +1095 -0
  35. package/dist/chunk-BQSIL6TT.js.map +1 -0
  36. package/dist/{chunk-GSJ7EEUC.js → chunk-J5AGXF3D.js} +58 -9
  37. package/dist/chunk-J5AGXF3D.js.map +1 -0
  38. package/dist/chunk-KK3GBONL.js +112 -0
  39. package/dist/chunk-KK3GBONL.js.map +1 -0
  40. package/dist/chunk-KTVNANUH.js +876 -0
  41. package/dist/chunk-KTVNANUH.js.map +1 -0
  42. package/dist/chunk-QW4UU7NG.js +1003 -0
  43. package/dist/chunk-QW4UU7NG.js.map +1 -0
  44. package/dist/chunk-SRHDJF5B.js +186 -0
  45. package/dist/chunk-SRHDJF5B.js.map +1 -0
  46. package/dist/chunk-UC2LGFPV.js +810 -0
  47. package/dist/chunk-UC2LGFPV.js.map +1 -0
  48. package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
  49. package/dist/index.cjs +9533 -4246
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +1143 -110
  52. package/dist/index.d.ts +1143 -110
  53. package/dist/index.js +298 -3191
  54. package/dist/index.js.map +1 -1
  55. package/dist/internal.cjs +581 -264
  56. package/dist/internal.cjs.map +1 -1
  57. package/dist/internal.d.cts +15 -85
  58. package/dist/internal.d.ts +15 -85
  59. package/dist/internal.js +10 -8
  60. package/dist/restore-MNDPR557.js +299 -0
  61. package/dist/restore-MNDPR557.js.map +1 -0
  62. package/dist/sql-builder-D0tC3sv9.d.cts +615 -0
  63. package/dist/sql-builder-D5UMzHEo.d.ts +615 -0
  64. package/dist/sqlite-wasm-channel-DVX93Uiq.d.cts +141 -0
  65. package/dist/sqlite-wasm-channel-DVX93Uiq.d.ts +141 -0
  66. package/dist/sqlite-wasm-channel-QCCNBYCU.js +12 -0
  67. package/dist/store-HNGX4KTT.js +16 -0
  68. package/dist/store-HNGX4KTT.js.map +1 -0
  69. package/dist/{types-BslDOTk0.d.ts → types-Dh-xyUjP.d.cts} +99 -26
  70. package/dist/{types-BslDOTk0.d.cts → types-Dh-xyUjP.d.ts} +99 -26
  71. package/package.json +3 -2
  72. package/dist/backup-5S7K2Z35.js +0 -14
  73. package/dist/chunk-GSJ7EEUC.js.map +0 -1
  74. package/dist/chunk-HS6DBMST.js +0 -383
  75. package/dist/chunk-HS6DBMST.js.map +0 -1
  76. package/dist/chunk-RJIEKEN4.js +0 -751
  77. package/dist/chunk-RJIEKEN4.js.map +0 -1
  78. package/dist/chunk-TUR7RGEP.js +0 -84
  79. package/dist/chunk-TUR7RGEP.js.map +0 -1
  80. package/dist/chunk-VDBJQ75K.js +0 -78
  81. package/dist/chunk-VDBJQ75K.js.map +0 -1
  82. package/dist/chunk-YT7MRFCP.js +0 -687
  83. package/dist/chunk-YT7MRFCP.js.map +0 -1
  84. package/dist/sqlite-wasm-channel-GGHA2BXU.js +0 -12
  85. package/dist/sqlite-wasm-channel-L6o3bWQK.d.cts +0 -92
  86. package/dist/sqlite-wasm-channel-L6o3bWQK.d.ts +0 -92
  87. package/dist/types-BOO9EPt1.d.ts +0 -170
  88. package/dist/types-C_GDavG-.d.cts +0 -170
  89. /package/dist/{backup-5S7K2Z35.js.map → backup-IHKVMUAM.js.map} +0 -0
  90. /package/dist/{chunk-CYO3YKFL.js.map → chunk-5ZRRF6R2.js.map} +0 -0
  91. /package/dist/{export-audit-ODGQTQIK.js.map → export-audit-J2QXRHD2.js.map} +0 -0
  92. /package/dist/{sqlite-wasm-channel-GGHA2BXU.js.map → sqlite-wasm-channel-QCCNBYCU.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, W as WhereClause, e as OrderByDirection, L as LocalMutationHandler, f as StoreConfig, A as ApplyRemoteOptions, g as MaterializedRowSnapshot, h as RowVersionState } from './types-BslDOTk0.js';
2
- export { M as MigrationPlan, i as OrderByClause, j as StorageFallbackReason, a as StorageOpenState, k as StoreIsolation, T as Transaction, l as TransactionBufferedEntry, m as TransactionCommitBatch, n as TransactionCommitResult, o as WhereOperators } from './types-BslDOTk0.js';
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 './types-BOO9EPt1.js';
6
- export { C as Collection } from './types-BOO9EPt1.js';
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-D5UMzHEo.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-D5UMzHEo.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).
@@ -86,6 +157,14 @@ declare class NoLeaderError extends KoraError {
86
157
  declare class PersistenceError extends KoraError {
87
158
  constructor(message: string, context?: Record<string, unknown>);
88
159
  }
160
+ /**
161
+ * Thrown by a local write while the store's pinned node id (an auth device id) belongs
162
+ * to another user than the one signed in (F9): a browser two users shared. The write
163
+ * could only be stored under the owner's node and would later upload as the owner's.
164
+ */
165
+ declare class NodeOwnedByAnotherUserError extends KoraError {
166
+ constructor(nodeId: string, principal: string | null);
167
+ }
89
168
 
90
169
  /**
91
170
  * How the local operation log may be compacted.
@@ -124,6 +203,27 @@ declare function computeAckCompactionWatermark(serverVector: VersionVector): Ver
124
203
  */
125
204
  declare function compactOperationLog(adapter: StorageAdapter, schema: SchemaDefinition, strategy: CompactionStrategy, serverVector: VersionVector): Promise<CompactionResult>;
126
205
 
206
+ /**
207
+ * How a database is re-materialized on its first open with the W7 fold:
208
+ *
209
+ * - `'log'`: the log is clean (W8 integrity scan): every record is rebuilt with the
210
+ * fold from its operations. This also repairs replicas that diverged under the
211
+ * pairwise merge of beta.12/13.
212
+ * - `'snapshot+log'`: the log is not complete (it was compacted, or has sequence
213
+ * gaps): each existing row becomes the record's row snapshot (its values at
214
+ * their `_field_versions`, exact where a stored fold state still has them), and
215
+ * the remaining operations are folded on top.
216
+ * - `'kept'`: the log has quarantined rows (W8). Only the records that own a
217
+ * quarantined row are kept exactly as they are (their row becomes their snapshot,
218
+ * nothing is rebuilt from their log); every other record is rebuilt as in `'log'`
219
+ * or `'snapshot+log'` (RT-68).
220
+ *
221
+ * Row snapshots are approximate (see `createSnapshotState`); records that fold
222
+ * against one are listed in `_kora_fold_snapshot` until their history is complete
223
+ * again (a scope entry with the server's fold state, or a full resync).
224
+ */
225
+ type RematerializationMode = 'log' | 'snapshot+log' | 'kept';
226
+
127
227
  /**
128
228
  * Result of re-stamping unsynced operations after a clock correction.
129
229
  */
@@ -138,23 +238,39 @@ interface ClockRebaseResult {
138
238
  newMaxTimestamp: HLCTimestamp | null;
139
239
  }
140
240
 
141
- declare const LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
142
- /**
143
- * Serialize a version vector for `_kora_meta` storage.
144
- */
145
- declare function serializeVersionVectorToMeta(vector: VersionVector): string;
146
- /**
147
- * Deserialize a version vector from `_kora_meta`.
148
- */
149
- declare function deserializeVersionVectorFromMeta(value: string): VersionVector;
150
- /**
151
- * Merge two version vectors, keeping the maximum sequence per node.
152
- */
153
- declare function mergeVersionVectors(a: VersionVector, b: VersionVector): VersionVector;
154
- /**
155
- * Operations present locally but not yet on the server (per server version vector).
156
- */
157
- declare function collectOperationsAheadOfServer(localVector: VersionVector, serverVector: VersionVector, fetchRange: (nodeId: string, fromSeq: number, toSeq: number) => Promise<Operation[]>): Promise<Operation[]>;
241
+ /** Result of {@link rotateUnsyncedOperationsInLog}. */
242
+ interface NodeRotationResult {
243
+ /** The node id the device uses from now on. */
244
+ nodeId: string;
245
+ /** The rewritten operations under the new node id, in their original causal order. */
246
+ operations: Operation[];
247
+ /** Maps each old operation id to its new content-addressed id. */
248
+ idMapping: Record<string, string>;
249
+ }
250
+
251
+ /** Result of `Store.resequenceOperation` (RT-35 renumbering). */
252
+ interface ResequenceResult {
253
+ /** The renumbered operation (a version-2 one under a new id). */
254
+ operation: Operation;
255
+ /** Never-sent dependents rewritten to name the new id (re-hashed), in sequence order. */
256
+ dependents: Operation[];
257
+ /** Old id -> new id, for the operation and every re-hashed dependent. */
258
+ idMapping: Record<string, string>;
259
+ }
260
+
261
+ /** A delivered operation the client did not apply, as stored in `_kora_unapplied_ops`. */
262
+ interface UnappliedOperation {
263
+ /** The operation as delivered (still encrypted when decryption failed). */
264
+ operation: Operation;
265
+ /** Delivery sequence of the batch that carried it, or null for a legacy batch. */
266
+ deliverySequence: number | null;
267
+ /** Machine-readable reason. */
268
+ code: string;
269
+ /** Human-readable explanation. */
270
+ message: string;
271
+ /** Wall-clock time (ms) it was quarantined. Display only. */
272
+ quarantinedAt: number;
273
+ }
158
274
 
159
275
  /**
160
276
  * A merge or constraint decision persisted for enterprise audit export.
@@ -233,11 +349,14 @@ interface AuditTraceQuery {
233
349
  * Manifest embedded in every backup file.
234
350
  */
235
351
  interface BackupManifest {
236
- /** Backup format version */
237
- version: 1;
352
+ /** Backup format version (2 since STORE-5; version 1 files need `convertBackupV1`) */
353
+ version: number;
238
354
  /** When the backup was created (epoch ms) */
239
355
  createdAt: number;
240
- /** Node ID of the originating device */
356
+ /**
357
+ * Node ID of the originating device. Informational only: a restore never copies a
358
+ * device identity into the importing database.
359
+ */
241
360
  nodeId: string;
242
361
  /** Schema version at backup time */
243
362
  schemaVersion: number;
@@ -247,6 +366,20 @@ interface BackupManifest {
247
366
  collections: string[];
248
367
  /** Whether materialized records are included */
249
368
  includesRecords: boolean;
369
+ /** Whether the records include deleted rows (tombstones). False for converted v1 files. */
370
+ includesTombstones?: boolean;
371
+ /** Set on a file produced by `convertBackupV1`. */
372
+ convertedFrom?: number;
373
+ /**
374
+ * The file carries the W7 fold's per-record base states (`fold_base`), row
375
+ * snapshots (`fold_snapshot`) and the compacted prefixes (`compacted_through`), so
376
+ * a compacted device's history survives a restore on another device (RT-66).
377
+ * Absent on files from earlier releases: their log may be incomplete (compacted),
378
+ * so a restore rebuilds their records on top of the backup's rows.
379
+ */
380
+ includesFoldState?: boolean;
381
+ /** The exporting database had compacted its log (it then needs `fold_base`). */
382
+ compacted?: boolean;
250
383
  /** SHA-256 hex checksum of all content sections */
251
384
  checksum: string;
252
385
  }
@@ -271,6 +404,24 @@ interface RestoreResult {
271
404
  success: boolean;
272
405
  /** Error message if failed */
273
406
  error?: string;
407
+ /** Error code if failed (`BACKUP_CHECKSUM_MISMATCH`, `BACKUP_OPERATION_INVALID`, ...) */
408
+ errorCode?: string;
409
+ /**
410
+ * Set when the file was a version-1 backup (Kora 1.0.0-beta.12 and earlier) that the
411
+ * import converted first, as `convertBackupV1` does.
412
+ */
413
+ convertedFromVersion?: 1;
414
+ /**
415
+ * Replace mode: this device's own writes the sync server had not acknowledged,
416
+ * re-applied on top of the restored data (see `RestoreOptions.keepUnsyncedWrites`).
417
+ */
418
+ unsyncedWritesKept?: number;
419
+ /**
420
+ * Merge mode: operations of server-authority nodes (`kora:` nodes and the explicit
421
+ * server authorities this device learned) the file held and the import left out.
422
+ * Server decisions reach a device only from the sync server.
423
+ */
424
+ serverOperationsSkipped?: number;
274
425
  /** Duration in ms */
275
426
  duration: number;
276
427
  }
@@ -294,12 +445,118 @@ interface RestoreOptions {
294
445
  /** Progress callback */
295
446
  onProgress?: (progress: BackupProgress) => void;
296
447
  /**
297
- * If true, merge operations with existing data (replay through applyRemoteOperation).
298
- * If false, clear and replace all data (default: false).
448
+ * If true, merge the backup into the existing data: every operation is applied like
449
+ * one received from sync (deduplicated by id, merged per field), the version vector
450
+ * advances by MAX, and nothing of the exporting device's identity or sync state is
451
+ * imported. If false (default), replace the local data with the backup's.
299
452
  */
300
453
  merge?: boolean;
454
+ /**
455
+ * Replace mode only: keep this device's own writes that the sync server has not
456
+ * acknowledged, re-applied on top of the backup, so a restore never discards writes
457
+ * that exist nowhere else. Writes of a node the server has accepted are always kept
458
+ * (they are in flight). Default: true for `store.importBackup`; `app.importBackup`
459
+ * defaults it to whether sync is configured, so a local-only app gets an exact
460
+ * replace.
461
+ */
462
+ keepUnsyncedWrites?: boolean;
301
463
  }
302
464
 
465
+ /**
466
+ * Log-integrity scan (W8 step 0).
467
+ *
468
+ * Every later fold of the operation log (W7 re-materialization, replay, compaction)
469
+ * trusts that each stored row is a canonical operation. Two earlier releases could
470
+ * break that:
471
+ *
472
+ * - beta.12's backup restore wrote `JSON.stringify(timestamp)` into the timestamp
473
+ * column, which `HybridLogicalClock.deserialize` reads as `wallTime: NaN` (a `null`
474
+ * once re-exported), and a second restore of such a backup nested the damage;
475
+ * - compaction deletes acknowledged operations, leaving holes in a node's sequence.
476
+ *
477
+ * The scan detects rows that do not round-trip through `deserializeOperation` /
478
+ * `serializeOperation`, repairs the ones whose original value is recoverable from the
479
+ * row itself (the JSON-encoded timestamp), and moves the rest to
480
+ * {@link LOG_QUARANTINE_TABLE} (never deleted), so no fold ever reads them. It also
481
+ * reports sequence gaps in this database's own nodes. Remote nodes' gaps are normal
482
+ * (scoped sync only delivers part of another device's log) and are not reported.
483
+ */
484
+ /** Rows that could not be repaired, kept verbatim. Never cleared automatically. */
485
+ declare const LOG_QUARANTINE_TABLE = "_kora_log_quarantine";
486
+ /** What is wrong with an operation row. */
487
+ type LogRowProblem =
488
+ /** The timestamp is a JSON-encoded HLC (beta.12 backup restore). Repairable. */
489
+ 'timestamp-json'
490
+ /** The timestamp cannot be read as an HLC and is not recoverable. */
491
+ | 'timestamp-malformed'
492
+ /** `id`, `node_id` or `record_id` is missing. */
493
+ | 'identity-malformed'
494
+ /** `type` is not insert, update or delete. */
495
+ | 'type-invalid'
496
+ /** `sequence_number` is not a positive integer. */
497
+ | 'sequence-invalid'
498
+ /** `data` or `previous_data` is not a JSON object. */
499
+ | 'data-malformed'
500
+ /** `causal_deps` is not a JSON array of operation ids. */
501
+ | 'causal-deps-malformed'
502
+ /** The row cannot be turned into an operation and back. */
503
+ | 'round-trip-failed';
504
+ /** One operation row the scan repaired or quarantined. */
505
+ interface LogIntegrityRow {
506
+ collection: string;
507
+ operationId: string;
508
+ nodeId: string | null;
509
+ sequenceNumber: number | null;
510
+ problem: LogRowProblem;
511
+ /** Human-readable detail: the bad value, or how it was repaired. */
512
+ detail: string;
513
+ }
514
+ /** Sequence numbers missing from one of this database's own nodes. */
515
+ interface LogSequenceGap {
516
+ nodeId: string;
517
+ /** First missing sequence number (inclusive). */
518
+ from: number;
519
+ /** Last missing sequence number (inclusive). */
520
+ to: number;
521
+ }
522
+ /**
523
+ * Result of {@link scanLogIntegrity} / `Store.verifyLogIntegrity()`.
524
+ *
525
+ * `clean` is the precondition for rebuilding state from the log (W7): every row is
526
+ * canonical, nothing is quarantined, and this database's own nodes have no holes
527
+ * below their highest stored sequence number.
528
+ */
529
+ interface LogIntegrityReport {
530
+ /** True when the log can be folded as-is (no quarantined rows, no own-node gaps). */
531
+ clean: boolean;
532
+ /** `full`: every row was checked in JS. `quick`: SQL prefilter, suspects checked in JS. */
533
+ mode: 'full' | 'quick';
534
+ /** Rows checked in JS (all rows in full mode, suspects in quick mode). */
535
+ checkedRows: number;
536
+ /** Rows this scan repaired in place. */
537
+ repaired: LogIntegrityRow[];
538
+ /** Rows this scan moved to the quarantine table. */
539
+ newlyQuarantined: LogIntegrityRow[];
540
+ /** Every row in the quarantine table (this scan's and earlier ones'). */
541
+ quarantined: LogIntegrityRow[];
542
+ /** Holes in this database's own nodes' sequences (compaction, a lost tail). */
543
+ gaps: LogSequenceGap[];
544
+ /** When the log was last compacted (epoch ms), or null when never. */
545
+ compactedAt: number | null;
546
+ /** False when the scan only reported (`repair: false`). */
547
+ repairApplied: boolean;
548
+ }
549
+
550
+ /** Compares two query result sets; true means subscribers need not be notified. */
551
+ type ResultsEqual = (prev: readonly CollectionRecord[], next: readonly CollectionRecord[]) => boolean;
552
+
553
+ /**
554
+ * Per-subscription options accepted by `register` / `registerAndFetch`.
555
+ */
556
+ interface RegisterOptions extends SubscribeOptions {
557
+ /** Result-set comparator; defaults to structural comparison of every value. */
558
+ resultsEqual?: ResultsEqual;
559
+ }
303
560
  /**
304
561
  * Configuration options for the SubscriptionManager.
305
562
  */
@@ -323,7 +580,29 @@ interface SubscriptionManagerOptions {
323
580
  bloomFalsePositiveRate?: number;
324
581
  /** Called when a query subscription is registered (e.g. to register sync query subsets). */
325
582
  onQuerySubscribed?: (descriptor: QueryDescriptor) => () => void;
583
+ /**
584
+ * Called for every subscription failure (the store emits `query:error`), in
585
+ * addition to the subscriber's own `onError`.
586
+ */
587
+ onQueryError?: (failure: QuerySubscriptionError) => void;
588
+ }
589
+ /**
590
+ * A committed change to the rows of one collection that can affect query results
591
+ * (RT-98). Every invalidation the store makes produces one: local writes and remote
592
+ * applies (with the operation), and changes made without a new operation (rejection
593
+ * re-folds, scope retraction, cascade settling, rematerialization, backup restore,
594
+ * held/discarded writes).
595
+ */
596
+ interface RecordsChange {
597
+ /** Collection whose rows changed */
598
+ readonly collection: string;
599
+ /** Changed record ids, or null when unknown (treat the whole collection as changed) */
600
+ readonly ids: readonly string[] | null;
601
+ /** The operation that caused the change, when there is one */
602
+ readonly operation?: Operation;
326
603
  }
604
+ /** Listener for {@link RecordsChange}s made by this runtime (not those relayed from peers). */
605
+ type RecordsChangeListener = (change: RecordsChange) => void;
327
606
  /**
328
607
  * Performance statistics for monitoring subscription checking efficiency.
329
608
  */
@@ -364,6 +643,14 @@ declare class SubscriptionManager {
364
643
  private pendingCollections;
365
644
  private flushScheduled;
366
645
  private readonly onQuerySubscribed?;
646
+ private readonly onQueryError?;
647
+ private readonly changeListeners;
648
+ /**
649
+ * Collections changed before the first change listener attached (a store's open:
650
+ * rematerialization, migration backfills). Replayed to that listener, so other tabs
651
+ * hear about changes made while this tab was still opening. Bounded by the schema.
652
+ */
653
+ private unheardCollections;
367
654
  private bloomFilter;
368
655
  private bloomDirty;
369
656
  private readonly bloomThreshold;
@@ -383,22 +670,55 @@ declare class SubscriptionManager {
383
670
  * @param executeFn - Function to re-execute the query and get current results
384
671
  * @returns An unsubscribe function
385
672
  */
386
- register(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>): () => void;
673
+ register(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>, options?: RegisterOptions): () => void;
387
674
  /**
388
675
  * Register a subscription and immediately execute the query.
389
676
  * The initial results are stored as lastResults so subsequent flushes
390
- * correctly diff against the initial state.
677
+ * correctly diff against the initial state. A failing initial run is reported
678
+ * through `options.onError` and the `query:error` hook, never as an unhandled
679
+ * rejection (STORE-12).
391
680
  *
392
681
  * @returns An unsubscribe function
393
682
  */
394
- registerAndFetch(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>): () => void;
683
+ registerAndFetch(descriptor: QueryDescriptor, callback: SubscriptionCallback<CollectionRecord>, executeFn: () => Promise<CollectionRecord[]>, options?: RegisterOptions): () => void;
684
+ private createSubscription;
685
+ private track;
686
+ /**
687
+ * Run a subscription's query and deliver the result when it changed. Never
688
+ * throws: failures go to the error channel. Runs are numbered so a slow,
689
+ * older run never overwrites the result of a newer one.
690
+ */
691
+ private run;
692
+ private isCurrent;
693
+ private reportError;
395
694
  /**
396
695
  * Notify the manager that a mutation occurred on a collection.
397
696
  * Schedules a microtask flush to batch multiple mutations in the same tick.
398
697
  */
399
- notify(collection: string, _operation: Operation): void;
400
- /** Invalidate a collection after a local-view change that has no domain operation. */
401
- invalidate(collection: string): void;
698
+ notify(collection: string, operation: Operation): void;
699
+ /**
700
+ * Invalidate a collection after a local-view change that has no domain operation
701
+ * (a re-fold, a retraction, a restore). Pass the record ids when known.
702
+ */
703
+ invalidate(collection: string, ids?: readonly string[] | null): void;
704
+ /**
705
+ * The single entry point for a committed change made by this runtime (RT-98): it
706
+ * schedules the affected queries and tells every change listener (the cross-tab
707
+ * bus), so no invalidation path can reach this tab's queries without reaching the
708
+ * other tabs' too.
709
+ */
710
+ recordsChanged(change: RecordsChange): void;
711
+ /**
712
+ * Invalidate a collection for a change another runtime made to the shared
713
+ * database. Not relayed to change listeners (the peer already broadcast it).
714
+ */
715
+ invalidateFromPeer(collection: string): void;
716
+ /**
717
+ * Listen to every committed change this runtime makes.
718
+ *
719
+ * @returns An unsubscribe function
720
+ */
721
+ onRecordsChanged(listener: RecordsChangeListener): () => void;
402
722
  /**
403
723
  * Immediately flush all pending notifications.
404
724
  * Useful for testing. In production, flushing happens via microtask.
@@ -438,12 +758,6 @@ declare class SubscriptionManager {
438
758
  */
439
759
  private rebuildBloomFilter;
440
760
  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
761
  }
448
762
 
449
763
  /**
@@ -472,6 +786,10 @@ declare class QueryBuilder<T = CollectionRecord> {
472
786
  constructor(collectionName: string, definition: CollectionDefinition, adapter: StorageAdapter, subscriptionManager: SubscriptionManager, initialWhere?: WhereClause, schema?: SchemaDefinition | undefined);
473
787
  /**
474
788
  * Add WHERE conditions (AND semantics, merged with existing conditions).
789
+ *
790
+ * A field whose value is `undefined` adds no condition (it does not remove an
791
+ * earlier one); `null` matches missing values (`IS NULL`). Non-finite numbers are
792
+ * refused. See {@link normalizeWhere} (RT-102).
475
793
  */
476
794
  where(conditions: WhereClause): QueryBuilder<T>;
477
795
  /**
@@ -516,7 +834,9 @@ declare class QueryBuilder<T = CollectionRecord> {
516
834
  *
517
835
  * @returns An unsubscribe function
518
836
  */
519
- subscribe(callback: SubscriptionCallback<T>): () => void;
837
+ subscribe(callback: SubscriptionCallback<T>, options?: SubscribeOptions): () => void;
838
+ /** Comparator built once per collection definition and shared by its queries. */
839
+ private getResultsEqual;
520
840
  /** Get the internal descriptor (for testing/debugging) */
521
841
  getDescriptor(): QueryDescriptor;
522
842
  private clone;
@@ -595,20 +915,6 @@ declare class SequenceManager {
595
915
  }): Promise<void>;
596
916
  }
597
917
 
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
918
  /**
613
919
  * Internal configuration for creating a TransactionContext.
614
920
  * Passed from Store to avoid exposing Store internals publicly.
@@ -618,10 +924,23 @@ interface TransactionContextConfig {
618
924
  adapter: StorageAdapter;
619
925
  clock: HybridLogicalClock;
620
926
  nodeId: string;
621
- sequenceAllocator: TransactionSequenceAllocator;
622
927
  relationEnforcer: RelationEnforcer | null;
623
928
  causalTracker: CausalTracker | null;
624
- localMutationHandler: LocalMutationHandler | null;
929
+ /**
930
+ * Key provider for `t.secret()` encrypted fields. Transaction writes transform
931
+ * secret fields through the same helper as single-record writes (STORE-4).
932
+ */
933
+ secretKeyProvider?: SecretKeyProvider;
934
+ /** Referential hook run for every local delete inside the commit (see {@link LocalDeleteHook}). */
935
+ beforeLocalDelete?: LocalDeleteHook;
936
+ /** Called when the commit's storage transaction fails, before the error propagates. */
937
+ onStorageError?: (error: unknown) => void;
938
+ /** The W7 record fold (see `WriteEnv.fold`). */
939
+ fold?: RecordFolder;
940
+ /** See `WriteEnv.maxOperationBytes`. */
941
+ maxOperationBytes?: number;
942
+ /** See `WriteEnv.assertLocalWriteAllowed`. */
943
+ assertLocalWriteAllowed?: () => void;
625
944
  }
626
945
  /**
627
946
  * A collection accessor within a transaction.
@@ -636,8 +955,10 @@ interface TransactionCollectionAccessor {
636
955
  /**
637
956
  * TransactionContext provides atomic multi-collection operations.
638
957
  *
639
- * All mutations are buffered and committed in a single StorageAdapter.transaction()
640
- * call. All operations share the same transactionId (UUID v7).
958
+ * Mutations are validated and buffered when called (so errors surface at the
959
+ * call site and `insert` returns the new id immediately), then written in a
960
+ * single `StorageAdapter.transaction()` at commit. All operations share the
961
+ * same transactionId (UUID v7) and one contiguous block of sequence numbers.
641
962
  *
642
963
  * Subscription notifications are deferred until after commit.
643
964
  *
@@ -672,7 +993,13 @@ declare class TransactionContext {
672
993
  */
673
994
  collection(name: string): TransactionCollectionAccessor;
674
995
  /**
675
- * Commit all buffered operations atomically.
996
+ * Commit all buffered mutations atomically.
997
+ *
998
+ * Every entry is written, in call order, inside ONE storage transaction through
999
+ * the single local write path: sequence numbers are reserved inside the
1000
+ * transaction (one contiguous block covering referential side effects too) and
1001
+ * each operation is built only after its number is reserved.
1002
+ *
676
1003
  * Returns the list of operations and affected collections for subscription notification.
677
1004
  */
678
1005
  commit(): Promise<{
@@ -687,32 +1014,35 @@ declare class TransactionContext {
687
1014
  * Get the transaction ID shared by all operations in this transaction.
688
1015
  */
689
1016
  getTransactionId(): string;
1017
+ private writeEnv;
690
1018
  private ensureActive;
691
1019
  private insert;
692
1020
  private update;
693
1021
  private deleteRecord;
694
- private findById;
695
1022
  /**
696
- * Get the effective state of a record, considering both the database and buffered operations.
697
- * Buffered inserts/updates take precedence over the database state.
1023
+ * The values an update will write, resolved against `current` (the effective
1024
+ * record) for read-your-writes inside the transaction. Secret fields are shown
1025
+ * in their at-rest form, like the record a committed update returns.
1026
+ */
1027
+ private previewUpdate;
1028
+ /**
1029
+ * The effective state of a record: the committed row overlaid with this
1030
+ * transaction's buffered mutations, in call order.
698
1031
  */
699
1032
  private getEffectiveRecord;
700
1033
  }
701
1034
 
702
- /**
703
- * Store is the main orchestrator. It owns a schema, a storage adapter,
704
- * a clock, and a subscription manager. It creates Collection instances
705
- * for each schema collection, and provides the sync contract via
706
- * applyRemoteOperation and getOperationRange.
707
- *
708
- * @example
709
- * ```typescript
710
- * const store = new Store({ schema, adapter })
711
- * await store.open()
712
- * const todo = await store.collection('todos').insert({ title: 'Hello' })
713
- * await store.close()
714
- * ```
715
- */
1035
+ /** Result of {@link Store.bindPrincipal} (RT-42). */
1036
+ interface PrincipalBinding {
1037
+ /** The node id local writes use from now on. */
1038
+ nodeId: string;
1039
+ /** The node id in use before the call. */
1040
+ previousNodeId: string;
1041
+ /** The store moved to another node id. */
1042
+ switched: boolean;
1043
+ /** The node id is pinned and belongs to another user: nothing may upload it now. */
1044
+ conflict: boolean;
1045
+ }
716
1046
  declare class Store implements OperationLog {
717
1047
  private opened;
718
1048
  private nodeId;
@@ -725,6 +1055,12 @@ declare class Store implements OperationLog {
725
1055
  private readonly schema;
726
1056
  private readonly adapter;
727
1057
  private readonly configNodeId;
1058
+ /** The owner of the pinned node while it belongs to another user than the signed-in one (F9). */
1059
+ private pinnedNodeOwnedBy;
1060
+ /** The sync server refused the pinned node as another user's (F9). */
1061
+ private pinnedNodeRefusedByServer;
1062
+ /** The user of the last {@link bindPrincipal}. */
1063
+ private signedInPrincipal;
728
1064
  private readonly dbName;
729
1065
  private readonly isolation;
730
1066
  private readonly emitter;
@@ -732,6 +1068,18 @@ declare class Store implements OperationLog {
732
1068
  private relationEnforcer;
733
1069
  private causalTracker;
734
1070
  private readonly secretKeyProvider;
1071
+ /** Releases the per-tab node lock (RT-40); null when none is held. */
1072
+ private releaseNodeLock;
1073
+ /** How rows are materialized ({@link StoreConfig.materialization}). */
1074
+ private readonly materialization;
1075
+ /** The W7 record fold; null under legacy materialization. */
1076
+ private readonly folder;
1077
+ /** True once every row of the database is a materialization of its fold state. */
1078
+ private foldActive;
1079
+ /** Schema transforms applied at fold time (RT-84); never to stored operations. */
1080
+ private readonly operationTransforms;
1081
+ /** Largest operation a local write may produce (RT-86); default in the write path. */
1082
+ private readonly maxOperationBytes;
735
1083
  constructor(config: StoreConfig);
736
1084
  /**
737
1085
  * Open the store: initialize the database, load or generate a node ID,
@@ -742,6 +1090,25 @@ declare class Store implements OperationLog {
742
1090
  * Close the store: clear subscriptions and close the adapter.
743
1091
  */
744
1092
  close(): Promise<void>;
1093
+ /**
1094
+ * Check the operation log (W8 step 0): every row must round-trip through the
1095
+ * canonical operation serializer. Rows an earlier release damaged in a recoverable
1096
+ * way (a JSON-encoded timestamp written by a beta.12 backup restore) are repaired;
1097
+ * unrecoverable rows move to the quarantine table, where no fold reads them. Also
1098
+ * reports sequence gaps in this database's own nodes (compaction, a lost tail).
1099
+ *
1100
+ * The store runs a quick variant (SQL prefilter) on every open. Rebuilding state from
1101
+ * the log must only run on a `clean` report.
1102
+ *
1103
+ * @param options - `repair: false` only reports; `mode: 'quick'` checks only the rows
1104
+ * a SQL prefilter flags (default `'full'`: every row)
1105
+ * @returns The integrity report; emits `store:log-integrity` when rows changed
1106
+ */
1107
+ verifyLogIntegrity(options?: {
1108
+ repair?: boolean;
1109
+ mode?: 'full' | 'quick';
1110
+ }): Promise<LogIntegrityReport>;
1111
+ private scanLog;
745
1112
  /**
746
1113
  * Get a Collection instance for CRUD operations.
747
1114
  * @throws {StoreNotOpenError} If the store is not open
@@ -762,12 +1129,143 @@ declare class Store implements OperationLog {
762
1129
  */
763
1130
  setClockReferenceOffset(offsetMs: number): void;
764
1131
  getNodeId(): string;
1132
+ /** The schema transforms the fold applies (transforms at fold time, RT-84). */
1133
+ getOperationTransforms(): readonly OperationTransform[];
765
1134
  /**
766
1135
  * Apply a remote operation received from sync.
767
1136
  * Checks for duplicates, applies to the data table, persists the operation,
768
1137
  * and updates the version vector.
769
1138
  */
770
- applyRemoteOperation(op: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
1139
+ applyRemoteOperation(stored: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
1140
+ /**
1141
+ * W7 remote apply: append the operation, then merge it into its record's fold
1142
+ * state, which re-materializes the row, all in one write transaction. There is no
1143
+ * read-decide-write race to guard: the state is read and written inside the
1144
+ * transaction, and the merge does not depend on the order operations arrive in.
1145
+ */
1146
+ private applyRemoteFolded;
1147
+ /** DevTools: every fold decision that was a conflict, plus one completion per merge. */
1148
+ private emitMergeTraces;
1149
+ /** The record fold, once the database's rows are fold materializations. */
1150
+ private activeFold;
1151
+ /**
1152
+ * The record's W7 fold state (null under legacy materialization or when the
1153
+ * record has none). For DevTools, tests and the server-comparison harness.
1154
+ */
1155
+ getFoldState(collection: string, recordId: string): Promise<FoldState | null>;
1156
+ /** Whether rows are materialized by the W7 fold (false under `materialization: 'legacy'`). */
1157
+ isFoldMaterialized(): boolean;
1158
+ private readMeta;
1159
+ private writeMeta;
1160
+ /**
1161
+ * Write every genuine beta.12 clear of the op log into its logged body, once per
1162
+ * database (RT-83, RT-85): the fold folds bodies as written, so the clear must be in
1163
+ * the body. Records already materialized by the fold are re-folded.
1164
+ */
1165
+ private ensureLegacyBodiesCanonical;
1166
+ /**
1167
+ * Make every row a materialization of its record's fold state (W7), once per
1168
+ * database and fold-state version. Runs after the W8 log-integrity scan: a
1169
+ * clean log is rebuilt with the fold; an incomplete (compacted) log rebuilds on
1170
+ * top of the rows as base snapshots; a log with quarantined rows is never
1171
+ * rebuilt from (rows are kept and become base snapshots), and
1172
+ * `store:rematerialized` reports which happened.
1173
+ */
1174
+ private ensureMaterialization;
1175
+ /**
1176
+ * Refuse to open when a schema version in the local operation log has no transform
1177
+ * path to the current schema (RT-103). Operations of a NEWER schema (authored by an
1178
+ * upgraded peer) are not checked: they are kept aside until this device upgrades.
1179
+ */
1180
+ private assertTransformCoverage;
1181
+ /**
1182
+ * Re-fold the records of every collection whose fold plan changed since the rows
1183
+ * were materialized (RT-63): a field that became `merge('counter')`, an array that
1184
+ * became append-only, a new or edited resolver. Their stored states hold fields of
1185
+ * the old kind, which the fold refuses (`FoldStateError`). Each record is rebuilt
1186
+ * from its log with the new plan; compacted bases and row snapshots are adapted
1187
+ * (a re-planned field restarts from its materialized value at its version). The
1188
+ * client's equivalent of the server stores' `fold_plan_fingerprint`.
1189
+ */
1190
+ private ensureFoldPlan;
1191
+ /**
1192
+ * Row snapshots were created (RT-68): ask the sync server for a full resync (the
1193
+ * delivery watermarks restart at 0, as after a restore), so each such record gets
1194
+ * its complete history back; {@link settleSnapshotRecords} then drops the
1195
+ * snapshots once the resync has caught up.
1196
+ */
1197
+ private requestSnapshotResync;
1198
+ /**
1199
+ * Records whose state still folds against a row snapshot (approximate: a late
1200
+ * concurrent write older than a field's version is not folded, RT-68).
1201
+ */
1202
+ getSnapshotRecords(): Promise<Array<{
1203
+ collection: string;
1204
+ recordId: string;
1205
+ }>>;
1206
+ /**
1207
+ * Called by sync when a delivery stream has caught up (its final batch fully
1208
+ * applied). After the full resync {@link requestSnapshotResync} asked for, every
1209
+ * row-snapshot record whose insert is back in the log is re-folded from its
1210
+ * complete history and stops being approximate (RT-68). Also retires the
1211
+ * provisional cascades of remote deletes (RT-69): the server's own copies were
1212
+ * delivered by now, and any it did not derive must not stay applied locally.
1213
+ *
1214
+ * With `provisionalOnly` only the provisional cascades are retired (RT-93: a stream
1215
+ * batch from a server that derives no cascades, which is not the end of a resync).
1216
+ *
1217
+ * @param options - `provisionalOnly`: leave row snapshots for the next catch-up
1218
+ * @returns How many snapshots were dropped
1219
+ */
1220
+ settleAfterCatchUp(options?: {
1221
+ provisionalOnly?: boolean;
1222
+ }): Promise<number>;
1223
+ /**
1224
+ * Apply the cascades / set-nulls of a REMOTE delete as local-only provisional
1225
+ * effects (RT-69): folded into the children like operations, but never logged,
1226
+ * sequenced or queued for upload. The server derives and relays its own copy;
1227
+ * when it arrives (same parent, same record) the provisional effect is retired.
1228
+ *
1229
+ * @param parent - The remote delete
1230
+ * @param effects - Its side effects, as the referential check derived them
1231
+ */
1232
+ applyProvisionalSideEffects(parent: Operation, effects: ReadonlyArray<{
1233
+ type: 'delete' | 'update';
1234
+ collection: string;
1235
+ recordId: string;
1236
+ data: Record<string, unknown> | null;
1237
+ previousData: Record<string, unknown> | null;
1238
+ ruleId: string;
1239
+ }>): Promise<number>;
1240
+ /**
1241
+ * Node ids whose writes win `merge('server-authoritative')` fields (the server's,
1242
+ * from the sync handshake). Every `kora:server:` id is authoritative by prefix, so
1243
+ * only explicit ids (legacy server node ids, configured extras) are kept: the device
1244
+ * persists the UNION of every explicit id it has learned and never drops one (RT-75).
1245
+ * Handshakes differ per server instance (each lists what it knows), so a list that
1246
+ * omits an id is not a revocation.
1247
+ *
1248
+ * Revocation is explicit (RT-81): ids a handshake lists in `revokedAuthoritativeNodeIds`
1249
+ * are removed from the union, persisted as revoked, and never learned again. Records
1250
+ * of collections with such fields are re-folded only when the explicit set changes
1251
+ * (grows, or loses a revoked id).
1252
+ *
1253
+ * @param nodeIds - The server's authoritative node ids (one handshake's list)
1254
+ * @param revokedNodeIds - Explicit ids the server revoked (one handshake's list)
1255
+ */
1256
+ setAuthoritativeNodeIds(nodeIds: readonly string[], revokedNodeIds?: readonly string[]): Promise<void>;
1257
+ /**
1258
+ * Re-fold the records of these operations from base + log (W7 exclusion: an
1259
+ * operation with a terminal-rejection marker is left out).
1260
+ */
1261
+ private refoldRecordsOf;
1262
+ /**
1263
+ * Append a remote operation to the log. If another operation of the same node
1264
+ * already holds its sequence number (a beta.12 device could produce such
1265
+ * pairs), the log keeps the existing row and this one is recorded in the
1266
+ * sequence-conflicts table, where dedup and record folds still see it.
1267
+ */
1268
+ private appendRemoteOperationRow;
771
1269
  /**
772
1270
  * Hide a record from this client's authorized materialized view. This writes no
773
1271
  * replicated operation and therefore cannot be mistaken for a domain deletion.
@@ -779,6 +1277,26 @@ declare class Store implements OperationLog {
779
1277
  collection: string;
780
1278
  recordId: string;
781
1279
  }>>;
1280
+ /**
1281
+ * Materialize a server scope-entry insert (RT-19) whose fields each carry their
1282
+ * own version (RT-27). The entry restates the record's current server values; a
1283
+ * single whole-row stamp would let a field the server last wrote long ago
1284
+ * overwrite this device's newer unsynced edit of it, after which the device's own
1285
+ * operation uploads and wins on the server (divergence). So every field is resolved
1286
+ * on its own, by last-write-wins against this row's `_field_versions`:
1287
+ *
1288
+ * - no row: inserted with each field stamped at its own version, `_created_at`
1289
+ * at the record's creation (the entry's `timestamp`), then any orphaned
1290
+ * updates/deletes are folded in as for any insert;
1291
+ * - live row, or a row hidden by a scope retraction: per-field LWW (a retracted
1292
+ * row is shown again; `_created_at` is kept);
1293
+ * - domain tombstone: revived only when the entry's newest field version is
1294
+ * newer than the tombstone, then per-field LWW.
1295
+ *
1296
+ * Deterministic and idempotent: the outcome depends only on the stored and
1297
+ * incoming per-field versions, and a re-applied entry wins no field.
1298
+ */
1299
+ private applyFieldVersionedInsert;
782
1300
  /**
783
1301
  * Materialize update/delete operations that were logged for a record BEFORE
784
1302
  * its insert arrived (reordered delivery). Runs inside the insert's write
@@ -842,11 +1360,34 @@ declare class Store implements OperationLog {
842
1360
  */
843
1361
  notifyExternalOperation(operation: Operation): void;
844
1362
  /**
845
- * Build mutation context for a collection (used by ApplyPipeline side effects).
1363
+ * Notify this store that another same-origin runtime changed rows of a collection
1364
+ * in the shared local database without an operation this tab can see (a re-fold,
1365
+ * a scope retraction, a restore; RT-98). Only re-runs the affected live queries.
1366
+ *
1367
+ * @param collection - The collection whose rows changed
1368
+ */
1369
+ notifyExternalChange(collection: string): void;
1370
+ /**
1371
+ * Listen to every committed change this store makes to collection rows: local
1372
+ * writes, remote applies, and changes without a new operation (re-folds, scope
1373
+ * retraction, cascade settling, rematerialization, restore). The cross-tab bus
1374
+ * relays these to other tabs on the same database (RT-98).
1375
+ *
1376
+ * @returns An unsubscribe function
1377
+ */
1378
+ onRecordsChanged(listener: RecordsChangeListener): () => void;
1379
+ /**
1380
+ * Build the local write context for a collection: everything the single local
1381
+ * write path needs (used by Collection writes and by ApplyPipeline).
846
1382
  */
847
1383
  createMutationContext(collection: string, options?: {
848
- inTransaction?: boolean;
849
1384
  extraCausalDeps?: string[];
1385
+ /**
1386
+ * Stamp the writes with this clock instead of the store's (it must be this
1387
+ * node's). Used for the side effects of a remote delete, stamped right after
1388
+ * the delete like the server's copy (seam 5), never with the device's "now".
1389
+ */
1390
+ clock?: HybridLogicalClock;
850
1391
  }): LocalMutationContext;
851
1392
  /**
852
1393
  * Load a materialized row by ID, including soft-deleted tombstones.
@@ -871,6 +1412,25 @@ declare class Store implements OperationLog {
871
1412
  * Persist the last server version vector this client believes the server has applied.
872
1413
  */
873
1414
  saveLastAckedServerVector(vector: VersionVector): Promise<void>;
1415
+ /**
1416
+ * Load the per-device node token the sync server issued for this node id (RT-12).
1417
+ */
1418
+ loadNodeToken(nodeId?: string): Promise<string | null>;
1419
+ /**
1420
+ * Persist the per-device node token under its node id (the current one by default).
1421
+ */
1422
+ saveNodeToken(token: string, nodeId?: string): Promise<void>;
1423
+ /**
1424
+ * Load the node ids the sync server named authoritative (protocol v2), or null when
1425
+ * no protocol-2 server ever answered.
1426
+ */
1427
+ loadAuthoritativeNodeIds(): Promise<string[] | null>;
1428
+ /**
1429
+ * Persist the node ids the sync server named authoritative (protocol v2). Same as
1430
+ * {@link setAuthoritativeNodeIds}: merged into the union of learned explicit ids,
1431
+ * and the fold re-folds affected records only when that set grows.
1432
+ */
1433
+ saveAuthoritativeNodeIds(nodeIds: string[]): Promise<void>;
874
1434
  /**
875
1435
  * Load persisted delta cursor for resuming paginated initial sync.
876
1436
  */
@@ -896,6 +1456,31 @@ declare class Store implements OperationLog {
896
1456
  * Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
897
1457
  */
898
1458
  deleteDeliveryWatermark(signature: string): Promise<void>;
1459
+ /**
1460
+ * Record delivered operations the sync engine deliberately did not apply (inbound
1461
+ * quarantine, W4) and, when given, advance a view's delivery watermark in the same
1462
+ * transaction, so the watermark never passes an operation that is neither applied nor
1463
+ * recorded.
1464
+ */
1465
+ saveInboundQuarantine(entries: UnappliedOperation[], watermark?: {
1466
+ signature: string;
1467
+ watermark: number;
1468
+ }): Promise<void>;
1469
+ /** Every quarantined inbound operation, oldest delivery first. */
1470
+ loadInboundQuarantine(): Promise<UnappliedOperation[]>;
1471
+ /** Remove quarantined inbound operations (applied on replay, or reconciled). */
1472
+ removeInboundQuarantine(operationIds: string[]): Promise<void>;
1473
+ /**
1474
+ * The contiguous acknowledged prefix of this device's own operations for a node id,
1475
+ * or null when none was recorded under that contract (W3).
1476
+ */
1477
+ loadOwnAckedThrough(nodeId: string): Promise<number | null>;
1478
+ /** Persist the contiguous acknowledged own-operation prefix for a node id. */
1479
+ saveOwnAckedThrough(nodeId: string, sequence: number): Promise<void>;
1480
+ /** The downlink scope the sync server last accepted (null when none). */
1481
+ loadAcceptedDownlinkScope(): Promise<Record<string, Record<string, unknown>> | null>;
1482
+ /** Persist (or clear) the downlink scope the sync server last accepted. */
1483
+ saveAcceptedDownlinkScope(scope: Record<string, Record<string, unknown>> | null): Promise<void>;
899
1484
  /**
900
1485
  * Local operations not yet reflected on the server version vector.
901
1486
  */
@@ -911,14 +1496,185 @@ declare class Store implements OperationLog {
911
1496
  * No data-change notifications are emitted: materialized values are
912
1497
  * unchanged, only version stamps move.
913
1498
  */
1499
+ /**
1500
+ * Move this device to a fresh node id, re-authoring its never-acknowledged
1501
+ * operations under it (RT-21). Used when the sync server refuses the current node
1502
+ * id (`NODE_ID_CLAIMED`): the device keeps every local write and uploads it under
1503
+ * the new id. Acknowledged history stays under the old id. Persisted atomically;
1504
+ * later writes use the new id.
1505
+ *
1506
+ * @param unsyncedOpIds - Ids of this node's operations the server never acknowledged
1507
+ * @returns The new node id and the rewritten operations (for the outbound queue)
1508
+ * @throws {KoraError} When the node id is pinned (`StoreConfig.nodeId`, or per-tab isolation)
1509
+ */
1510
+ rotateNodeId(unsyncedOpIds: string[]): Promise<NodeRotationResult>;
1511
+ /**
1512
+ * Re-author another local node's never-acknowledged operations under a fresh node id
1513
+ * bound to `principal` (the user they belong to). Used when the sync server refuses an
1514
+ * adopted node whose writes belong to the signed-in user (the app assigned them, or
1515
+ * the node was created for them) but which no claims-aware server ever accepted: a
1516
+ * beta.12 database's node with history the server recorded no owner for. The store's
1517
+ * own node id, clock and counters are untouched; the fresh node is registered, so the
1518
+ * sync engine adopts it like any other local node.
1519
+ *
1520
+ * @param fromNodeId - The refused local node (never the store's own)
1521
+ * @param unsyncedOpIds - Ids of its operations the server never acknowledged
1522
+ * @param principal - The user the writes belong to (the fresh node's binding)
1523
+ * @returns The fresh node id and the rewritten operations (for the outbound queue)
1524
+ * @throws {KoraError} `NODE_ID_UNKNOWN` when `fromNodeId` is the store's own node or
1525
+ * not a local node
1526
+ */
1527
+ reauthorLocalNode(fromNodeId: string, unsyncedOpIds: string[], principal: string): Promise<NodeRotationResult>;
1528
+ /**
1529
+ * Move this database back to a node id it authored under before (RT-38): after the
1530
+ * sync server refused the current node, the device tries a node a returning user
1531
+ * owns. Nothing is rewritten; later writes use `nodeId`.
1532
+ *
1533
+ * @throws {KoraError} When the node id is pinned, or `nodeId` is not a local node
1534
+ */
1535
+ switchNodeId(nodeId: string): Promise<void>;
1536
+ /** Point the clock, sequence counter and collections at `nodeId` (already persisted). */
1537
+ private rebindToNode;
1538
+ /**
1539
+ * Bind this database's writes to the signed-in user (RT-42). Call it whenever the
1540
+ * signed-in user is known or changes, BEFORE the next local write (`createApp` does
1541
+ * so at start and on every auth change of `sync.authClient`).
1542
+ *
1543
+ * - The current node belongs to `principal`: nothing changes.
1544
+ * - The current node is unbound and has no history (no write, never synced): it is
1545
+ * bound to `principal` (every write under it from now on is theirs).
1546
+ * - The current node is unbound but has history (a database from before RT-42, or
1547
+ * writes made while nobody was signed in): its owner is NOT guessed (RT-50). The
1548
+ * store moves to `principal`'s own node, and the old node's owner is learned from
1549
+ * the sync server (the first accepted handshake as it binds it; a refusal rules
1550
+ * that user out). A node that never synced cannot be attributed by anyone: its
1551
+ * unsynced writes are held for the app to assign or discard (`app.sync.assignHeld`,
1552
+ * `app.sync.discardHeld`).
1553
+ * - It belongs to another user: the store moves to `principal`'s own local node (the
1554
+ * most recent one, not held), or to a fresh node bound to `principal`. Nothing is
1555
+ * rewritten: the other user's unsynced writes stay under their node, and sync never
1556
+ * uploads them on this user's session (they count as `heldOperations`).
1557
+ *
1558
+ * A pinned node id (`StoreConfig.nodeId`, such as an auth device id) cannot move:
1559
+ * `conflict` is then true when it belongs to another user, and sync refuses to
1560
+ * upload it under `principal`; an unbound pinned node with history stays unbound
1561
+ * until the server accepts it for a user. Under `per-tab` isolation the tab moves to
1562
+ * a fresh per-tab node.
1563
+ *
1564
+ * @param principal - The signed-in user id
1565
+ * @returns The node now in use and whether it changed
1566
+ */
1567
+ bindPrincipal(principal: string): Promise<PrincipalBinding>;
1568
+ /**
1569
+ * The sync server refused this store's pinned node id because another user owns it
1570
+ * (`owned: true`), or later accepted it (`false`). A pinned node cannot move to a fresh
1571
+ * id, so writes made under it could only ever upload as its owner: while the server
1572
+ * says so, local writes are refused (F9). A no-op for a store whose node is not pinned
1573
+ * (it moves to a fresh node instead).
1574
+ *
1575
+ * @param owned - Whether the server reported the node owned by another user
1576
+ */
1577
+ setPinnedNodeOwnedElsewhere(owned: boolean): void;
1578
+ /** Refuse a local write while the pinned node belongs to another user (F9). */
1579
+ private assertLocalWriteAllowed;
1580
+ private bindPrincipalToNode;
1581
+ /**
1582
+ * A sync handshake as `nodeId` was accepted for `principal` (RT-50): bind an unbound
1583
+ * node (or replace a guessed binding) from the server's answer.
1584
+ */
1585
+ confirmNodePrincipal(nodeId: string, principal: string): Promise<void>;
1586
+ /**
1587
+ * The sync server refused `nodeId` for `principal` (RT-50): clear a guessed binding,
1588
+ * and remember that an unbound node is not theirs.
1589
+ */
1590
+ recordNodeRefusedFor(nodeId: string, principal: string): Promise<void>;
1591
+ /**
1592
+ * Assign a held, unbound local node's writes to `principal` (RT-50): an explicit app
1593
+ * decision for writes nobody can attribute (a database that never synced).
1594
+ *
1595
+ * @returns false when the node is unknown, the store's own, already bound, or was
1596
+ * refused for `principal` by the server
1597
+ */
1598
+ assignNodePrincipal(nodeId: string, principal: string): Promise<boolean>;
1599
+ /**
1600
+ * Forget a local node other than the store's own whatever it still holds (RT-50: the
1601
+ * app discarded its held writes, which were marked never to upload first).
1602
+ */
1603
+ dropLocalNode(nodeId: string): Promise<void>;
1604
+ /** The adoption schedule of this database's local nodes (RT-46). */
1605
+ loadAdoptionSchedule(): Promise<AdoptionSchedule>;
1606
+ /** Persist the adoption schedule (RT-46). */
1607
+ saveAdoptionSchedule(schedule: AdoptionSchedule): Promise<void>;
1608
+ /**
1609
+ * Raise a local node's sequence counter to at least `floor`, in a transaction (the
1610
+ * same MAX every reservation uses, W6), so the next write never reuses a number the
1611
+ * server already holds (RT-35: the device lost the tail of its log, for example on a
1612
+ * reload inside the IndexedDB snapshot window or after restoring an older copy).
1613
+ *
1614
+ * @returns Whether the counter moved
1615
+ */
1616
+ raiseSequenceFloor(nodeId: string, floor: number): Promise<boolean>;
1617
+ /**
1618
+ * Give one of a local node's operations a fresh sequence number above `floor`
1619
+ * (RT-35): the server refused it with `SEQUENCE_CONFLICT` because it holds another
1620
+ * operation of this node under that number (one this device lost). A version-1
1621
+ * operation keeps its id (its hash does not cover the sequence number), exactly like
1622
+ * the W6 sequence repair; a version-2 operation (protocol v2) is re-hashed under the
1623
+ * new number, so it carries a new id. The old identity is recorded in
1624
+ * `_kora_seq_conflicts` (`reemitted_as`).
1625
+ *
1626
+ * A new id would leave later operations naming the old one in `causalDeps`: those in
1627
+ * `rewritableDependents` (operations the server never stored: never sent) are
1628
+ * rewritten to name the new id, and re-hashed, transitively, in the same
1629
+ * transaction. Dependents already sent keep their ids (the server may hold them);
1630
+ * their dep resolves through the `_kora_seq_conflicts` record. Every touched record
1631
+ * is re-folded (fold stamps carry operation ids).
1632
+ *
1633
+ * @param rewritableDependents - Ids of this node's operations that were never sent
1634
+ * @returns The renumbered operation and the rewritten dependents, or null when the
1635
+ * operation is not in the log
1636
+ */
1637
+ resequenceOperation(operationId: string, nodeId: string, floor: number, rewritableDependents?: readonly string[]): Promise<ResequenceResult | null>;
1638
+ /**
1639
+ * Durability barrier (RT-35): resolves once every write committed so far is durable
1640
+ * on this device. Sync awaits it before an operation leaves the device.
1641
+ */
1642
+ ensureDurable(): Promise<void>;
1643
+ /** Record operations the server refused for good (RT-36). Never cleared. */
1644
+ recordTerminalRejections(entries: TerminalRejection[]): Promise<void>;
1645
+ /** Which of these operation ids the server refused for good (RT-36). */
1646
+ findTerminalRejections(operationIds: string[]): Promise<Set<string>>;
1647
+ /** Every node id this database authored operations under (RT-38, RT-40). */
1648
+ listLocalNodes(): Promise<LocalNodeRecord[]>;
1649
+ /** Forget a drained local node other than this store's own (RT-40). */
1650
+ forgetLocalNode(nodeId: string): Promise<void>;
1651
+ /** The current refusal cycle: the count of accepted sync handshakes (RT-38). */
1652
+ loadAcceptedCycle(): Promise<number>;
1653
+ /** Record an accepted sync handshake as `nodeId` (RT-38). */
1654
+ markLocalNodeAccepted(nodeId: string): Promise<void>;
1655
+ /** Record that the sync server refused `nodeId`; `held` holds its writes (RT-38). */
1656
+ markLocalNodeRefused(nodeId: string, held: boolean): Promise<void>;
1657
+ /**
1658
+ * Take over another local node's unsynced writes (RT-40): resolves to a release
1659
+ * function when no live tab uses `nodeId` (its lock is free), or null when one does.
1660
+ * Without Web Locks (Node, old browsers) the node is assumed free.
1661
+ */
1662
+ claimLocalNode(nodeId: string): Promise<(() => void) | null>;
1663
+ /** Whether a live tab holds `nodeId` (per-tab isolation, RT-40). */
1664
+ isLocalNodeLive(nodeId: string): Promise<boolean>;
914
1665
  rebaseUnsyncedOperations(unsyncedOpIds: string[], correctedNowMs: number): Promise<ClockRebaseResult>;
915
1666
  /**
916
1667
  * Count of local operations ahead of the server version vector.
917
1668
  */
918
1669
  countUnsyncedOperations(serverVector: VersionVector): Promise<number>;
919
1670
  /**
920
- * Compact the local operation log using materialized rows as the baseline.
921
- * Only removes ops the server has acknowledged (per {@link CompactionStrategy}).
1671
+ * Compact the local operation log. Only removes ops the server has acknowledged
1672
+ * (per {@link CompactionStrategy}). With the W7 fold (STORE-14), the effect of the
1673
+ * removed operations is first joined into each record's base fold state, so a
1674
+ * re-fold (rejection exclusion, a later re-materialization) and the merge of a
1675
+ * late operation stay exact; delete, atomic and custom-resolver operations are
1676
+ * kept, and a re-delivered compacted id is a duplicate by its node's compacted
1677
+ * prefix.
922
1678
  */
923
1679
  compact(strategy: CompactionStrategy): Promise<CompactionResult>;
924
1680
  /**
@@ -971,13 +1727,47 @@ declare class Store implements OperationLog {
971
1727
  */
972
1728
  exportBackup(options?: BackupOptions): Promise<Uint8Array>;
973
1729
  /**
974
- * Restore data from a backup binary.
1730
+ * Restore data from a backup binary (format version 2, STORE-5).
1731
+ *
1732
+ * - `merge: true`: every backup operation is applied through the remote-apply path
1733
+ * (deduplicated by id, merged per field), the version vector advances by MAX, and
1734
+ * nothing of the exporting device's identity or sync state is imported.
1735
+ * - Replace (default): the data is replaced by the backup's while this device keeps
1736
+ * its node id, local nodes and principal bindings, its own sequence counters never
1737
+ * move backwards, and unsynced own writes are kept (see
1738
+ * `RestoreOptions.keepUnsyncedWrites`). Then {@link reloadFromDisk} runs.
1739
+ *
1740
+ * A version-1 file (Kora 1.0.0-beta.12 and earlier) is converted first, exactly as
1741
+ * `convertBackupV1` does (`result.convertedFromVersion: 1`); one holding an operation
1742
+ * whose timestamp cannot be recovered is refused (`BACKUP_OPERATION_INVALID`).
975
1743
  *
976
1744
  * @param data - The backup data
977
- * @param options - Restore options (merge, collections, onProgress)
1745
+ * @param options - Restore options (merge, collections, keepUnsyncedWrites, onProgress)
1746
+ * @param internal - `applyOperation`: the app's remote-apply path (defaults to
1747
+ * {@link applyRemoteOperation}); `createApp` passes the merge-aware pipeline sync uses
978
1748
  * @returns Result of the restore operation
979
1749
  */
980
- importBackup(data: Uint8Array, options?: RestoreOptions): Promise<RestoreResult>;
1750
+ importBackup(data: Uint8Array, options?: RestoreOptions, internal?: {
1751
+ applyOperation?: (operation: Operation) => Promise<ApplyResult>;
1752
+ }): Promise<RestoreResult>;
1753
+ /**
1754
+ * Merge mode (RT-66): join the backup's compacted base states and row snapshots
1755
+ * into this database's, and re-fold those records, before the backup's operations
1756
+ * are applied. The join is the fold's state-based merge, so the result equals
1757
+ * folding both devices' histories together. Compacted prefixes advance by MAX
1758
+ * (unfiltered restores only: the prefix is per node, across collections).
1759
+ */
1760
+ private mergeBackupFoldState;
1761
+ /**
1762
+ * Re-read this store's state from the database after it changed underneath it (a
1763
+ * backup restore): the node id, version vector and own sequence counter, the clock
1764
+ * (advanced past every timestamp in the log), and a fresh causal tracker. Every live
1765
+ * query re-runs. The local node registry and terminal rejections are always read
1766
+ * from the database, so they need no reload.
1767
+ */
1768
+ reloadFromDisk(): Promise<void>;
1769
+ /** The greatest HLC timestamp in the operation log (canonical strings sort by HLC). */
1770
+ private loadNewestLogTimestamp;
981
1771
  /**
982
1772
  * Read backup manifest without loading the entire backup.
983
1773
  *
@@ -986,21 +1776,35 @@ declare class Store implements OperationLog {
986
1776
  */
987
1777
  static readBackupManifest(data: Uint8Array): BackupManifest;
988
1778
  private recordOperationSequence;
989
- private allocateSequenceNumber;
990
- /**
991
- * Check the stored schema version and run any pending migrations.
992
- * Migrations are applied in version order within a transaction.
993
- */
994
- private runMigrationsIfNeeded;
1779
+ /** Post-commit publication of a committed local operation. */
1780
+ private publishLocalOperation;
995
1781
  /**
996
- * Get the stored schema version from _kora_meta. Returns 0 if not set.
1782
+ * Map an out-of-space storage failure to `store:quota-exceeded` (STORE-15).
1783
+ * The error itself still propagates to the caller; this only makes the
1784
+ * condition observable on every adapter, not just IndexedDB.
997
1785
  */
998
- private getStoredSchemaVersion;
1786
+ private reportStorageError;
1787
+ /** Report a database a newer build migrated (`store:schema-ahead`) and build the error. */
1788
+ private schemaAhead;
999
1789
  /**
1000
- * Run a backfill transform on all records in a collection.
1001
- * Reads all rows, applies the transform, and updates changed fields.
1790
+ * Run pending schema migrations (STORE-13, NEW-STORE-1): one transaction per version
1791
+ * (DDL, backfills, `schema_version`); backfills write operations through the local
1792
+ * write path so they sync, unless declared `localOnly`.
1002
1793
  */
1003
- private runBackfill;
1794
+ private runMigrationsIfNeeded;
1795
+ /** The node id the database persisted before this open, if any. */
1796
+ private readPersistedNodeId;
1797
+ /**
1798
+ * Register node ids this database authored under that the registry does not know
1799
+ * yet: the node id a pinned `nodeId` (the auth device id) just replaced, and the
1800
+ * authors of queued operations. A beta.12 database had no registry, and a template
1801
+ * app created before its user signed in authored under a random node; once the
1802
+ * store is pinned to the device id, that node's queued writes would otherwise sit in
1803
+ * the queue forever, neither uploaded nor reported. Registered, they are adopted or
1804
+ * held (`status.heldNodes`) like any other local node's writes. Only the outbound
1805
+ * queue is read: received operations never enter it.
1806
+ */
1807
+ private registerUnregisteredAuthors;
1004
1808
  private loadOrGenerateNodeId;
1005
1809
  private loadSequenceNumber;
1006
1810
  private loadVersionVector;
@@ -1017,6 +1821,147 @@ interface CollectionAccessor {
1017
1821
  where(conditions: Record<string, unknown>): QueryBuilder;
1018
1822
  }
1019
1823
 
1824
+ declare const LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
1825
+ /**
1826
+ * Serialize a version vector for `_kora_meta` storage.
1827
+ */
1828
+ declare function serializeVersionVectorToMeta(vector: VersionVector): string;
1829
+ /**
1830
+ * Deserialize a version vector from `_kora_meta`.
1831
+ */
1832
+ declare function deserializeVersionVectorFromMeta(value: string): VersionVector;
1833
+ /**
1834
+ * Merge two version vectors, keeping the maximum sequence per node.
1835
+ */
1836
+ declare function mergeVersionVectors(a: VersionVector, b: VersionVector): VersionVector;
1837
+ /**
1838
+ * Operations present locally but not yet on the server (per server version vector).
1839
+ */
1840
+ declare function collectOperationsAheadOfServer(localVector: VersionVector, serverVector: VersionVector, fetchRange: (nodeId: string, fromSeq: number, toSeq: number) => Promise<Operation[]>): Promise<Operation[]>;
1841
+
1842
+ /**
1843
+ * Normalise a `where` clause to its canonical form (RT-102). This is the ONE place that
1844
+ * decides what a where value means, so the SQL the store runs, the descriptor and the
1845
+ * query key (store cache, React/Vue/Svelte bindings) always agree:
1846
+ *
1847
+ * - `undefined` means "no condition": `where({ projectId: undefined })` is `where({})`
1848
+ * (the usual "nothing selected yet" pattern), and an operator whose operand is
1849
+ * `undefined` (`{ $gt: undefined }`) is dropped; a field left with no operator is
1850
+ * dropped. A later `.where({ x: undefined })` therefore does not remove an earlier
1851
+ * condition on `x`.
1852
+ * - `null` means SQL `IS NULL` (`$ne: null` means `IS NOT NULL`).
1853
+ * - Non-finite numbers (`NaN`, `Infinity`) have no stored representation: refused.
1854
+ * - `undefined` inside a `$in` list is refused (ambiguous: no condition cannot be one
1855
+ * element of a list).
1856
+ *
1857
+ * @param where - The clause as the developer wrote it
1858
+ * @returns A new clause with the rules above applied
1859
+ * @throws {QueryError} For a non-finite number or an `undefined` `$in` element
1860
+ */
1861
+ declare function normalizeWhere(where: WhereClause): WhereClause;
1862
+ /**
1863
+ * The canonical identity of a query: equal keys if and only if the descriptors run the
1864
+ * same query (RT-102). Used by the store's query-store cache and by every framework
1865
+ * binding to decide when to share a store or re-subscribe.
1866
+ *
1867
+ * Unlike `JSON.stringify`, nothing is dropped or conflated: `where` keys are sorted
1868
+ * (their order does not change the query), `undefined` is dropped exactly as
1869
+ * {@link normalizeWhere} drops it, and `NaN`, `Date` and byte values keep distinct,
1870
+ * typed encodings.
1871
+ *
1872
+ * @param descriptor - The query descriptor (`queryBuilder.getDescriptor()`)
1873
+ * @returns A string key
1874
+ */
1875
+ declare function queryKey(descriptor: QueryDescriptor): string;
1876
+
1877
+ /**
1878
+ * Durable-storage state of this origin, as far as Kora knows (NEW-STORE-4).
1879
+ * - `unknown`: not checked yet (the boot check runs in the background).
1880
+ * - `persisted`: the browser granted persistent storage; it will not evict this
1881
+ * origin's data under storage pressure.
1882
+ * - `best-effort`: storage may be evicted under pressure (the browser default).
1883
+ * - `unsupported`: the runtime has no StorageManager persistence API (Node,
1884
+ * older browsers, some WebViews).
1885
+ * - `error`: the check or request threw; `lastError` says why.
1886
+ */
1887
+ type StoragePersistenceState = 'unknown' | 'persisted' | 'best-effort' | 'unsupported' | 'error';
1888
+ /** Snapshot returned by `app.storage.persistence.status()`. */
1889
+ interface StoragePersistenceStatus {
1890
+ state: StoragePersistenceState;
1891
+ /** True only when the browser reported persistent storage. */
1892
+ persisted: boolean;
1893
+ /** True once `persist()` was asked for (explicitly or by an automatic trigger). */
1894
+ requested: boolean;
1895
+ /** Message of the last failure, when `state` is `error`. */
1896
+ lastError?: string;
1897
+ }
1898
+ /** Why an automatic (background) persistence request was made. */
1899
+ type PersistenceRequestReason = 'explicit' | 'sign-in' | 'first-write' | 'installed-app';
1900
+ /** The subset of `navigator.storage` used here. */
1901
+ interface PersistenceStorageManager {
1902
+ persist?: () => Promise<boolean>;
1903
+ persisted?: () => Promise<boolean>;
1904
+ }
1905
+ /** Options for {@link StoragePersistence}. */
1906
+ interface StoragePersistenceOptions {
1907
+ /** Receives `storage:persistence` events. */
1908
+ emitter?: KoraEventEmitter | null;
1909
+ /**
1910
+ * The StorageManager to use. Defaults to `globalThis.navigator.storage`,
1911
+ * resolved on each call so tests and late polyfills are honoured.
1912
+ */
1913
+ storage?: PersistenceStorageManager | null;
1914
+ }
1915
+ /**
1916
+ * Tracks and requests durable storage (`navigator.storage.persist()`) without ever
1917
+ * putting it on the startup path (NEW-STORE-4).
1918
+ *
1919
+ * In Firefox `persist()` opens a permission prompt and its promise stays pending
1920
+ * until the user answers, so awaiting it inside startup could hang `app.ready`
1921
+ * forever. Kora therefore:
1922
+ * - checks `persisted()` at boot, which never prompts, in the background;
1923
+ * - requests persistence only on an explicit `request()` or, without awaiting,
1924
+ * after a meaningful moment (sign-in, the first local write, running as an
1925
+ * installed app);
1926
+ * - surfaces every result through `status()` and the `storage:persistence` event,
1927
+ * never by blocking.
1928
+ */
1929
+ declare class StoragePersistence {
1930
+ private current;
1931
+ private inFlightRequest;
1932
+ private readonly emitter;
1933
+ private readonly storageOverride;
1934
+ constructor(options?: StoragePersistenceOptions);
1935
+ /** Synchronous snapshot of the last known state. Never prompts. */
1936
+ status(): StoragePersistenceStatus;
1937
+ /**
1938
+ * Read whether storage is already persistent (`navigator.storage.persisted()`).
1939
+ * Never prompts and never throws: a failure becomes `state: 'error'`.
1940
+ */
1941
+ check(): Promise<StoragePersistenceStatus>;
1942
+ /**
1943
+ * Ask the browser for persistent storage (`navigator.storage.persist()`).
1944
+ * May show a permission prompt (Firefox), so call it from a user gesture or a
1945
+ * meaningful moment, and do not block rendering on it. Concurrent calls share
1946
+ * one request. Never throws: a failure becomes `state: 'error'`.
1947
+ *
1948
+ * @returns The state after the browser answered
1949
+ */
1950
+ request(): Promise<StoragePersistenceStatus>;
1951
+ /**
1952
+ * Fire-and-forget `request()` for an automatic trigger. Never awaited by Kora,
1953
+ * so a pending prompt cannot block anything; the outcome arrives as a
1954
+ * `storage:persistence` event. Skipped when storage is already persistent or a
1955
+ * request was already made this session.
1956
+ *
1957
+ * @param _reason - Which trigger fired (kept for tracing at call sites)
1958
+ */
1959
+ requestInBackground(_reason: PersistenceRequestReason): void;
1960
+ private storage;
1961
+ private update;
1962
+ private emit;
1963
+ }
1964
+
1020
1965
  /**
1021
1966
  * Bridges the async QueryBuilder.subscribe() API with synchronous snapshot reads
1022
1967
  * required by React useSyncExternalStore, Vue composables, and Svelte stores.
@@ -1030,6 +1975,7 @@ declare class QueryStore<T = CollectionRecord> {
1030
1975
  private unsubscribeQuery;
1031
1976
  private active;
1032
1977
  private hasEmittedSnapshot;
1978
+ private error;
1033
1979
  private readonly queryBuilder;
1034
1980
  constructor(queryBuilder: QueryBuilder<T>);
1035
1981
  /**
@@ -1045,6 +1991,12 @@ declare class QueryStore<T = CollectionRecord> {
1045
1991
  * authoritative result for the current subscription lifecycle.
1046
1992
  */
1047
1993
  hasSnapshot: () => boolean;
1994
+ /**
1995
+ * The latest query failure, or null. Set when the query (or a re-run after a
1996
+ * write) fails; cleared by the next successful delivery. Listeners are notified
1997
+ * on both transitions, so bindings read it next to `getSnapshot()`.
1998
+ */
1999
+ getError: () => Error | null;
1048
2000
  /** Tear down listeners and the underlying query subscription. */
1049
2001
  destroy(): void;
1050
2002
  private startSubscription;
@@ -1282,6 +2234,12 @@ interface RichTextControllerSnapshot {
1282
2234
  canUndo: boolean;
1283
2235
  canRedo: boolean;
1284
2236
  cursors: readonly RichTextCursorInfo[];
2237
+ /**
2238
+ * True while local edits are not in a successful save: a save is pending, in
2239
+ * flight, or was refused (see `error`). The edits stay in the live document and
2240
+ * are saved by the next edit or `retrySave()`.
2241
+ */
2242
+ hasUnsavedChanges: boolean;
1285
2243
  }
1286
2244
  interface RichTextController {
1287
2245
  readonly doc: yjs.Doc;
@@ -1293,12 +2251,29 @@ interface RichTextController {
1293
2251
  setCursor(anchor: number, head: number): void;
1294
2252
  clearCursor(): void;
1295
2253
  setUser(user: RichTextAwarenessUser | undefined): void;
2254
+ /**
2255
+ * Save the live document now (for example after a refused save, once the cause
2256
+ * is fixed). Resolves when the save settled; its outcome is in the snapshot.
2257
+ */
2258
+ retrySave(): Promise<void>;
2259
+ /**
2260
+ * The full document state (a Yjs update) while local edits are unsaved, else
2261
+ * null. Keep it as a recovery copy when saves keep failing.
2262
+ */
2263
+ getUnsavedState(): Uint8Array | null;
2264
+ /** Stop syncing; edits still waiting for the debounced save are saved first. */
1296
2265
  destroy(): void;
1297
2266
  }
1298
2267
 
1299
2268
  /**
1300
2269
  * Framework-agnostic richtext binding: Yjs document lifecycle, persistence,
1301
2270
  * incremental doc channel, and awareness cursors.
2271
+ *
2272
+ * Every save writes the full state of the live Y.Doc, which holds every local edit
2273
+ * and every merged remote change, so a stored change arriving while a save waits
2274
+ * cannot drop local typing. A refused save keeps the edits unsaved
2275
+ * (`hasUnsavedChanges`, `getUnsavedState()`) and the next edit or `retrySave()`
2276
+ * saves them again; the first successful save clears `error`.
1302
2277
  */
1303
2278
  declare function createRichTextController(options: CreateRichTextControllerOptions): RichTextController;
1304
2279
 
@@ -1360,14 +2335,22 @@ declare function pluralize(word: string): string;
1360
2335
  */
1361
2336
  declare function singularize(word: string): string;
1362
2337
 
2338
+ /** Current backup format version. */
2339
+ declare const BACKUP_VERSION = 2;
2340
+ /** A backup file this release cannot restore as-is. */
2341
+ declare class BackupFormatError extends KoraError {
2342
+ constructor(message: string, code: string, context?: Record<string, unknown>);
2343
+ }
1363
2344
  /**
1364
- * Export a backup from a store.
2345
+ * Export a backup (format version 2) from an opened database.
1365
2346
  *
1366
- * Reads all data via the adapter and produces a portable binary backup.
2347
+ * Everything is read inside one transaction, so the backup is a consistent snapshot.
2348
+ * Operations are exported in canonical form (exactly what `deserializeOperation` reads
2349
+ * back), including tombstones, so a restore rebuilds the same log.
1367
2350
  *
1368
2351
  * @param adapter - The storage adapter
1369
2352
  * @param schema - The schema definition
1370
- * @param nodeId - Current node ID
2353
+ * @param nodeId - Node id of the exporting device (informational only; never imported)
1371
2354
  * @param schemaVersion - Schema version
1372
2355
  * @param options - Backup options
1373
2356
  * @returns Backup as a Uint8Array
@@ -1375,7 +2358,6 @@ declare function singularize(word: string): string;
1375
2358
  declare function exportBackup(adapter: StorageAdapter, schema: SchemaDefinition, nodeId: string, schemaVersion: number, options?: BackupOptions): Promise<Uint8Array>;
1376
2359
  /**
1377
2360
  * Read backup manifest without loading the entire backup.
1378
- * Parses only the first section (which is always the manifest).
1379
2361
  *
1380
2362
  * @param data - The raw backup data
1381
2363
  * @returns The backup manifest
@@ -1388,14 +2370,41 @@ declare function readBackupManifest(data: Uint8Array): BackupManifest;
1388
2370
  * @returns True if the checksum is valid
1389
2371
  */
1390
2372
  declare function verifyBackupChecksum(data: Uint8Array): Promise<boolean>;
2373
+ /** Options for {@link convertBackupV1}. */
2374
+ interface ConvertBackupV1Options {
2375
+ /**
2376
+ * Drop operations whose timestamp cannot be recovered instead of refusing the file.
2377
+ * Default false.
2378
+ */
2379
+ dropUnrecoverable?: boolean;
2380
+ }
1391
2381
  /**
1392
- * Restore a backup into a store.
2382
+ * Convert a version-1 backup (Kora 1.0.0-beta.12 and earlier) to version 2.
1393
2383
  *
1394
- * @param adapter - The storage adapter
2384
+ * - Operations: a v1 file exported from a database that a v1 restore had damaged holds
2385
+ * misread timestamps (`wallTime: null`); they are recovered from the misread values
2386
+ * (the same rule as the log-integrity scan). An unrecoverable one refuses the file
2387
+ * unless `dropUnrecoverable` is set.
2388
+ * - The `meta` section (the exporting device's node id, node tokens, sync state) is
2389
+ * dropped: it must never be imported into another database.
2390
+ * - Record rows are kept; v1 exported live rows only, so the manifest records
2391
+ * `includesTombstones: false`. Bytes that v1 serialized as `{"0":..,"1":..}` objects
2392
+ * are decoded on restore.
2393
+ *
2394
+ * @throws {BackupFormatError} When the input is not a valid version-1 backup
2395
+ */
2396
+ declare function convertBackupV1(data: Uint8Array, options?: ConvertBackupV1Options): Promise<Uint8Array>;
2397
+ /**
2398
+ * Restore a backup into the database behind `adapter`.
2399
+ *
2400
+ * @deprecated Use `store.importBackup()` / `app.importBackup()`: restoring needs the
2401
+ * store (the device's identity, version vector, clock and live queries). This entry
2402
+ * point opens a temporary `Store` on `adapter`, imports, and CLOSES the adapter.
2403
+ *
2404
+ * @param adapter - A storage adapter no store is using
1395
2405
  * @param schema - The schema definition
1396
2406
  * @param data - The backup data
1397
2407
  * @param options - Restore options
1398
- * @returns Result of the restore operation
1399
2408
  */
1400
2409
  declare function restoreBackup(adapter: StorageAdapter, schema: SchemaDefinition, data: Uint8Array, options?: RestoreOptions): Promise<RestoreResult>;
1401
2410
 
@@ -1471,8 +2480,8 @@ declare class OpfsBlobStore implements ContentAddressedBlobStore {
1471
2480
  }
1472
2481
  /**
1473
2482
  * Create an {@link OpfsBlobDirectory} backed by the browser's Origin Private
1474
- * File System, rooted at a named subdirectory. Best-effort requests persistent
1475
- * storage so the browser is less likely to evict blobs under storage pressure.
2483
+ * File System, rooted at a named subdirectory. It does not request persistent
2484
+ * storage: that is `app.storage.persistence`, kept off the startup path.
1476
2485
  *
1477
2486
  * @param rootDirName - The OPFS subdirectory to store blobs under (default `kora-blobs`)
1478
2487
  * @throws {Error} If OPFS is unavailable (e.g. called outside a browser)
@@ -1759,6 +2768,13 @@ interface ChunkResponseMessage {
1759
2768
  requestId: string;
1760
2769
  /** The chunk bytes, or null when the responder does not hold that hash. */
1761
2770
  bytes: Uint8Array | null;
2771
+ /**
2772
+ * The server refused the request for rate, not because the chunk is missing
2773
+ * (RT-24). The requester retries after `retryAfterMs` instead of failing.
2774
+ */
2775
+ throttled?: boolean;
2776
+ /** With `throttled`: how long to wait before retrying, in ms. */
2777
+ retryAfterMs?: number;
1762
2778
  }
1763
2779
  type ChunkMessage = ChunkRequestMessage | ChunkResponseMessage;
1764
2780
  /**
@@ -1776,18 +2792,35 @@ interface RemoteChunkProvider extends ChunkProvider {
1776
2792
  /** Number of requests still awaiting a response (for diagnostics/tests). */
1777
2793
  pendingCount(): number;
1778
2794
  }
2795
+ /** Options for {@link createRemoteChunkProvider}. */
2796
+ interface RemoteChunkProviderOptions {
2797
+ /** Per-attempt timeout in ms (default 30s). */
2798
+ timeoutMs?: number;
2799
+ /**
2800
+ * Longest total time one chunk may spend waiting on "throttled" answers before
2801
+ * the request fails (default 10 minutes). The transfer is resumable, so a failed
2802
+ * request can simply be retried later.
2803
+ */
2804
+ maxThrottleWaitMs?: number;
2805
+ /** Smallest back-off after a throttled answer, in ms (default 250). */
2806
+ minThrottleDelayMs?: number;
2807
+ /** Largest single back-off after a throttled answer, in ms (default 30s). */
2808
+ maxThrottleDelayMs?: number;
2809
+ }
1779
2810
  /**
1780
2811
  * Create a {@link ChunkProvider} that requests chunks over a message port,
1781
2812
  * correlating each answer to its request and timing out a stalled request so a
1782
2813
  * dropped response cannot hang a transfer forever (the transfer is resumable, so
1783
2814
  * a timed-out request can simply be retried).
1784
2815
  *
2816
+ * A "throttled" answer (the server's blob request budget is spent, RT-24) is not a
2817
+ * failure: the request is re-sent after the server's `retryAfterMs` (bounded, with
2818
+ * exponential back-off when absent), until `maxThrottleWaitMs` has elapsed.
2819
+ *
1785
2820
  * @param port - The message port to the peer that holds the blob
1786
- * @param options.timeoutMs - Per-request timeout (default 30s)
2821
+ * @param options - Timeouts and throttle back-off bounds
1787
2822
  */
1788
- declare function createRemoteChunkProvider(port: ChunkMessagePort, options?: {
1789
- timeoutMs?: number;
1790
- }): RemoteChunkProvider;
2823
+ declare function createRemoteChunkProvider(port: ChunkMessagePort, options?: RemoteChunkProviderOptions): RemoteChunkProvider;
1791
2824
  /**
1792
2825
  * Serve blob chunk requests arriving on a message port from a blob store. Answers
1793
2826
  * each `blob-chunk-request` with the bytes for that hash, or null when the store
@@ -1807,4 +2840,4 @@ declare function createChunkPortPair(): {
1807
2840
  b: ChunkMessagePort;
1808
2841
  };
1809
2842
 
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 };
2843
+ 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, NodeOwnedByAnotherUserError, 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 };