@korajs/store 0.4.0 → 0.6.0

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 (69) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.cjs +410 -17
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +23 -5
  6. package/dist/adapters/indexeddb.d.ts +23 -5
  7. package/dist/adapters/indexeddb.js +163 -18
  8. package/dist/adapters/indexeddb.js.map +1 -1
  9. package/dist/adapters/sqlite-wasm-shared-host.cjs +68 -0
  10. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -0
  11. package/dist/adapters/sqlite-wasm-shared-host.d.cts +2 -0
  12. package/dist/adapters/sqlite-wasm-shared-host.d.ts +2 -0
  13. package/dist/adapters/sqlite-wasm-shared-host.js +66 -0
  14. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -0
  15. package/dist/adapters/sqlite-wasm-worker.cjs +34 -5
  16. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm-worker.js +34 -5
  18. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  19. package/dist/adapters/sqlite-wasm.cjs +249 -2
  20. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  21. package/dist/adapters/sqlite-wasm.d.cts +15 -3
  22. package/dist/adapters/sqlite-wasm.d.ts +15 -3
  23. package/dist/adapters/sqlite-wasm.js +2 -2
  24. package/dist/audit-trace-store-AM4EWQNZ.js +15 -0
  25. package/dist/backup-JN3LB4YT.js +14 -0
  26. package/dist/backup-JN3LB4YT.js.map +1 -0
  27. package/dist/chunk-56LL4XP6.js +205 -0
  28. package/dist/chunk-56LL4XP6.js.map +1 -0
  29. package/dist/chunk-6AHYGQQT.js +425 -0
  30. package/dist/chunk-6AHYGQQT.js.map +1 -0
  31. package/dist/chunk-7BPETY5Y.js +570 -0
  32. package/dist/chunk-7BPETY5Y.js.map +1 -0
  33. package/dist/chunk-AGN3CIPB.js +188 -0
  34. package/dist/chunk-AGN3CIPB.js.map +1 -0
  35. package/dist/chunk-EZSTD2XC.js +380 -0
  36. package/dist/chunk-EZSTD2XC.js.map +1 -0
  37. package/dist/chunk-MYSJN3DF.js +117 -0
  38. package/dist/chunk-MYSJN3DF.js.map +1 -0
  39. package/dist/{chunk-DXKLAQ6P.js → chunk-XO4LMPAH.js} +1 -1
  40. package/dist/chunk-XO4LMPAH.js.map +1 -0
  41. package/dist/export-audit-5GV6VRDZ.js +15 -0
  42. package/dist/export-audit-5GV6VRDZ.js.map +1 -0
  43. package/dist/index.cjs +2662 -577
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +565 -187
  46. package/dist/index.d.ts +565 -187
  47. package/dist/index.js +1381 -739
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal.cjs +789 -0
  50. package/dist/internal.cjs.map +1 -0
  51. package/dist/internal.d.cts +154 -0
  52. package/dist/internal.d.ts +154 -0
  53. package/dist/internal.js +48 -0
  54. package/dist/internal.js.map +1 -0
  55. package/dist/{sqlite-wasm-channel-46AOWNPM.js → sqlite-wasm-channel-HDDW6UBH.js} +2 -2
  56. package/dist/sqlite-wasm-channel-HDDW6UBH.js.map +1 -0
  57. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.cts → sqlite-wasm-channel-I9y_jObg.d.cts} +1 -0
  58. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.ts → sqlite-wasm-channel-I9y_jObg.d.ts} +1 -0
  59. package/dist/types-BMyHmwvn.d.ts +163 -0
  60. package/dist/types-CTr00g_d.d.cts +163 -0
  61. package/dist/types-DepnRDk_.d.cts +212 -0
  62. package/dist/types-DepnRDk_.d.ts +212 -0
  63. package/package.json +23 -2
  64. package/dist/chunk-DXKLAQ6P.js.map +0 -1
  65. package/dist/chunk-ZP5AXQ3Z.js +0 -179
  66. package/dist/chunk-ZP5AXQ3Z.js.map +0 -1
  67. package/dist/types-DF-KDSK1.d.cts +0 -106
  68. package/dist/types-DF-KDSK1.d.ts +0 -106
  69. /package/dist/{sqlite-wasm-channel-46AOWNPM.js.map → audit-trace-store-AM4EWQNZ.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1,6 +1,25 @@
1
- import { Q as QueryDescriptor, a as SubscriptionCallback, C as CollectionRecord, S as StorageAdapter, W as WhereClause, O as OrderByDirection, b as StoreConfig, A as ApplyResult, T as Transaction } from './types-DF-KDSK1.cjs';
2
- export { M as MigrationPlan, c as OrderByClause, d as WhereOperators } from './types-DF-KDSK1.cjs';
3
- import { KoraError, Operation, CollectionDefinition, SchemaDefinition, SequenceConfig, HybridLogicalClock, OperationLog, VersionVector, OnDeleteAction, RelationDefinition, StateMachineDefinition } from '@korajs/core';
1
+ import { C as CollectionRecord, S as StorageAdapter, Q as QueryDescriptor, c as SubscriptionCallback, W as WhereClause, d as OrderByDirection, L as LocalMutationHandler, e as StoreConfig, A as ApplyRemoteOptions, f as MaterializedRowSnapshot } from './types-DepnRDk_.cjs';
2
+ export { M as MigrationPlan, g as OrderByClause, h as StoreIsolation, T as Transaction, i as TransactionBufferedEntry, j as TransactionCommitBatch, k as TransactionCommitResult, l as WhereOperators } from './types-DepnRDk_.cjs';
3
+ import { Operation, KoraError, VersionVector, SchemaDefinition, MergeTrace, CollectionDefinition, SequenceConfig, HybridLogicalClock, CausalTracker, OperationLog, ApplyResult, StateMachineDefinition, KoraEvent } from '@korajs/core';
4
+ export { ApplyResult } from '@korajs/core';
5
+ import { R as RelationEnforcer, L as LocalMutationContext } from './types-CTr00g_d.cjs';
6
+ export { C as Collection } from './types-CTr00g_d.cjs';
7
+ import * as yjs from 'yjs';
8
+
9
+ /**
10
+ * In-memory materialized state at a causal cut in the operation log.
11
+ * Read-only — does not mutate the live store.
12
+ */
13
+ interface ReplaySnapshot {
14
+ /** The operation whose causal past was replayed (inclusive). */
15
+ targetOperation: Operation;
16
+ /** Operations applied in causal order (ancestors + target). */
17
+ operationsApplied: Operation[];
18
+ /** Non-deleted records per collection after replay. */
19
+ collections: Record<string, CollectionRecord[]>;
20
+ /** Look up a single record at the replay cut. Returns null if deleted or missing. */
21
+ findRecord(collection: string, recordId: string): CollectionRecord | null;
22
+ }
4
23
 
5
24
  /**
6
25
  * Thrown when a query is invalid (bad field names, invalid operators, etc.).
@@ -45,6 +64,205 @@ declare class PersistenceError extends KoraError {
45
64
  constructor(message: string, context?: Record<string, unknown>);
46
65
  }
47
66
 
67
+ /**
68
+ * How the local operation log may be compacted.
69
+ * Materialized collection rows are always kept as the convergence baseline.
70
+ */
71
+ type CompactionStrategy = {
72
+ mode: 'never';
73
+ } | {
74
+ mode: 'after-ack';
75
+ /** Server version vector; defaults to persisted last-acked vector on the store. */
76
+ serverVector?: VersionVector;
77
+ } | {
78
+ mode: 'after-days';
79
+ /** Delete only ops older than this many days (still requires ack watermark). */
80
+ days: number;
81
+ serverVector?: VersionVector;
82
+ };
83
+ /**
84
+ * Result of a compaction run.
85
+ */
86
+ interface CompactionResult {
87
+ /** Number of operation log rows removed. */
88
+ deletedCount: number;
89
+ /** Per-node sequence ceiling used for deletion (from server ack vector). */
90
+ watermark: VersionVector;
91
+ }
92
+ declare const COMPACTION_BASELINE_META_KEY = "compaction_baseline_at";
93
+
94
+ /**
95
+ * Compute per-node sequence watermarks safe to compact (server has acknowledged these ops).
96
+ */
97
+ declare function computeAckCompactionWatermark(serverVector: VersionVector): VersionVector;
98
+ /**
99
+ * Remove acknowledged, materialized operation log entries from `_kora_ops_*` tables.
100
+ * Does not modify collection rows — they are the compaction baseline snapshot.
101
+ */
102
+ declare function compactOperationLog(adapter: StorageAdapter, schema: SchemaDefinition, strategy: CompactionStrategy, serverVector: VersionVector): Promise<CompactionResult>;
103
+
104
+ declare const LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
105
+ /**
106
+ * Serialize a version vector for `_kora_meta` storage.
107
+ */
108
+ declare function serializeVersionVectorToMeta(vector: VersionVector): string;
109
+ /**
110
+ * Deserialize a version vector from `_kora_meta`.
111
+ */
112
+ declare function deserializeVersionVectorFromMeta(value: string): VersionVector;
113
+ /**
114
+ * Merge two version vectors, keeping the maximum sequence per node.
115
+ */
116
+ declare function mergeVersionVectors(a: VersionVector, b: VersionVector): VersionVector;
117
+ /**
118
+ * Operations present locally but not yet on the server (per server version vector).
119
+ */
120
+ declare function collectOperationsAheadOfServer(localVector: VersionVector, serverVector: VersionVector, fetchRange: (nodeId: string, fromSeq: number, toSeq: number) => Promise<Operation[]>): Promise<Operation[]>;
121
+
122
+ /**
123
+ * A merge or constraint decision persisted for enterprise audit export.
124
+ */
125
+ interface PersistedAuditTrace {
126
+ /** Unique row id (UUID v7). */
127
+ id: string;
128
+ /** Wall-clock time when the trace was recorded (epoch ms). */
129
+ recordedAt: number;
130
+ /** Source event type from the Kora event bus. */
131
+ eventType: 'merge:completed' | 'merge:conflict' | 'constraint:violated';
132
+ /** Constraint name when {@link eventType} is `constraint:violated`. */
133
+ constraint?: string;
134
+ /** Full merge trace including conflicting operations and resolution details. */
135
+ trace: MergeTrace;
136
+ }
137
+ /**
138
+ * Manifest embedded in audit export files.
139
+ */
140
+ interface AuditExportManifest {
141
+ /** Audit export format version */
142
+ version: 1;
143
+ /** When the export was created (epoch ms) */
144
+ exportedAt: number;
145
+ /** Node ID of the originating device */
146
+ nodeId: string;
147
+ /** Schema version at export time */
148
+ schemaVersion: number;
149
+ /** Number of operations included */
150
+ operationCount: number;
151
+ /** Number of merge traces included */
152
+ mergeTraceCount: number;
153
+ /** SHA-256 hex checksum of content sections */
154
+ checksum: string;
155
+ }
156
+ /**
157
+ * Decoded audit export payload.
158
+ */
159
+ interface AuditExportPayload {
160
+ manifest: AuditExportManifest;
161
+ operations: Operation[];
162
+ mergeTraces: PersistedAuditTrace[];
163
+ }
164
+ /**
165
+ * Progress reported during audit export.
166
+ */
167
+ interface AuditExportProgress {
168
+ phase: 'reading' | 'writing' | 'verifying';
169
+ /** 0-1 progress ratio */
170
+ progress: number;
171
+ message: string;
172
+ }
173
+ /**
174
+ * Options for {@link exportAudit}.
175
+ */
176
+ interface AuditExportOptions {
177
+ /** Subset of collections for operations and traces. All if omitted. */
178
+ collections?: string[];
179
+ /** Include only traces recorded at or after this timestamp (epoch ms). */
180
+ since?: number;
181
+ /** Include only traces recorded at or before this timestamp (epoch ms). */
182
+ until?: number;
183
+ /** Progress callback */
184
+ onProgress?: (progress: AuditExportProgress) => void;
185
+ }
186
+ /**
187
+ * Query filters for reading persisted audit traces.
188
+ */
189
+ interface AuditTraceQuery {
190
+ collections?: string[];
191
+ since?: number;
192
+ until?: number;
193
+ }
194
+
195
+ /**
196
+ * Manifest embedded in every backup file.
197
+ */
198
+ interface BackupManifest {
199
+ /** Backup format version */
200
+ version: 1;
201
+ /** When the backup was created (epoch ms) */
202
+ createdAt: number;
203
+ /** Node ID of the originating device */
204
+ nodeId: string;
205
+ /** Schema version at backup time */
206
+ schemaVersion: number;
207
+ /** Total number of operations in the backup */
208
+ operationCount: number;
209
+ /** Collection names included in the backup */
210
+ collections: string[];
211
+ /** Whether materialized records are included */
212
+ includesRecords: boolean;
213
+ /** SHA-256 hex checksum of all content sections */
214
+ checksum: string;
215
+ }
216
+ /**
217
+ * Progress reported during backup/restore operations.
218
+ */
219
+ interface BackupProgress {
220
+ phase: 'reading' | 'writing' | 'verifying' | 'restoring';
221
+ /** 0-1 progress ratio */
222
+ progress: number;
223
+ message: string;
224
+ }
225
+ /**
226
+ * Result of a restore operation.
227
+ */
228
+ interface RestoreResult {
229
+ /** Number of operations restored */
230
+ operationsRestored: number;
231
+ /** Number of records restored */
232
+ recordsRestored: number;
233
+ /** Whether the restore completed successfully */
234
+ success: boolean;
235
+ /** Error message if failed */
236
+ error?: string;
237
+ /** Duration in ms */
238
+ duration: number;
239
+ }
240
+ /**
241
+ * Options for exporting a backup.
242
+ */
243
+ interface BackupOptions {
244
+ /** Include materialized record snapshots (default: true) */
245
+ includeRecords?: boolean;
246
+ /** Subset of collections to backup. All if omitted. */
247
+ collections?: string[];
248
+ /** Progress callback */
249
+ onProgress?: (progress: BackupProgress) => void;
250
+ }
251
+ /**
252
+ * Options for restoring from a backup.
253
+ */
254
+ interface RestoreOptions {
255
+ /** Subset of collections to restore. All if omitted. */
256
+ collections?: string[];
257
+ /** Progress callback */
258
+ onProgress?: (progress: BackupProgress) => void;
259
+ /**
260
+ * If true, merge operations with existing data (replay through applyRemoteOperation).
261
+ * If false, clear and replace all data (default: false).
262
+ */
263
+ merge?: boolean;
264
+ }
265
+
48
266
  /**
49
267
  * Configuration options for the SubscriptionManager.
50
268
  */
@@ -66,6 +284,8 @@ interface SubscriptionManagerOptions {
66
284
  * @default 0.01
67
285
  */
68
286
  bloomFalsePositiveRate?: number;
287
+ /** Called when a query subscription is registered (e.g. to register sync query subsets). */
288
+ onQuerySubscribed?: (descriptor: QueryDescriptor) => () => void;
69
289
  }
70
290
  /**
71
291
  * Performance statistics for monitoring subscription checking efficiency.
@@ -106,6 +326,7 @@ declare class SubscriptionManager {
106
326
  private subscriptions;
107
327
  private pendingCollections;
108
328
  private flushScheduled;
329
+ private readonly onQuerySubscribed?;
109
330
  private bloomFilter;
110
331
  private bloomDirty;
111
332
  private readonly bloomThreshold;
@@ -179,8 +400,9 @@ declare class SubscriptionManager {
179
400
  private rebuildBloomFilter;
180
401
  private scheduleFlush;
181
402
  /**
182
- * Compare two result sets. Uses length check + JSON comparison as pragmatic approach.
183
- * Sufficient for typical query results. Can be optimized to id-based diffing if profiling shows need.
403
+ * Compare two result sets. Uses ID-based comparison first (same length, same IDs),
404
+ * then deep comparison of remaining fields when IDs match.
405
+ * Avoids JSON.stringify which is O(n) and fragile to field ordering differences.
184
406
  */
185
407
  private resultsEqual;
186
408
  }
@@ -334,6 +556,20 @@ declare class SequenceManager {
334
556
  }): Promise<void>;
335
557
  }
336
558
 
559
+ /**
560
+ * Allocates monotonic sequence numbers for a single open transaction.
561
+ * Loads the current DB watermark once, then increments in memory until commit.
562
+ */
563
+ declare class TransactionSequenceAllocator {
564
+ private readonly adapter;
565
+ private readonly nodeId;
566
+ private loaded;
567
+ private watermark;
568
+ constructor(adapter: StorageAdapter, nodeId: string);
569
+ allocate(): Promise<number>;
570
+ getHighWaterMark(): number;
571
+ }
572
+
337
573
  /**
338
574
  * Internal configuration for creating a TransactionContext.
339
575
  * Passed from Store to avoid exposing Store internals publicly.
@@ -343,7 +579,10 @@ interface TransactionContextConfig {
343
579
  adapter: StorageAdapter;
344
580
  clock: HybridLogicalClock;
345
581
  nodeId: string;
346
- getSequenceNumber: () => number;
582
+ sequenceAllocator: TransactionSequenceAllocator;
583
+ relationEnforcer: RelationEnforcer | null;
584
+ causalTracker: CausalTracker | null;
585
+ localMutationHandler: LocalMutationHandler | null;
347
586
  }
348
587
  /**
349
588
  * A collection accessor within a transaction.
@@ -447,7 +686,12 @@ declare class Store implements OperationLog {
447
686
  private readonly schema;
448
687
  private readonly adapter;
449
688
  private readonly configNodeId;
689
+ private readonly dbName;
690
+ private readonly isolation;
450
691
  private readonly emitter;
692
+ private localMutationHandler;
693
+ private relationEnforcer;
694
+ private causalTracker;
451
695
  constructor(config: StoreConfig);
452
696
  /**
453
697
  * Open the store: initialize the database, load or generate a node ID,
@@ -477,20 +721,101 @@ declare class Store implements OperationLog {
477
721
  * Checks for duplicates, applies to the data table, persists the operation,
478
722
  * and updates the version vector.
479
723
  */
480
- applyRemoteOperation(op: Operation): Promise<ApplyResult>;
724
+ applyRemoteOperation(op: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
481
725
  /**
482
726
  * Get operations from a node within a sequence number range.
483
727
  * Implements the OperationLog interface for computeDelta.
484
728
  */
485
- getRange(nodeId: string, fromSeq: number, toSeq: number): Operation[];
729
+ getRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]>;
486
730
  /**
487
- * Async version of getRange for use by the sync layer.
731
+ * Get operations from a node within a sequence number range.
488
732
  */
489
733
  getOperationRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]>;
734
+ /**
735
+ * Load every operation from the local append-only log across all collections.
736
+ * Used by sync delta computation, backup export, and time-travel replay.
737
+ */
738
+ getAllOperations(): Promise<Operation[]>;
739
+ /**
740
+ * Rebuild an in-memory snapshot of materialized state at a causal cut in the op log.
741
+ * Does not mutate the live store — intended for DevTools time-travel inspection.
742
+ *
743
+ * @param operationId - Content-addressed id of the operation to replay through (inclusive)
744
+ * @throws {OperationError} When the operation id is not present in the local log
745
+ */
746
+ replayTo(operationId: string): Promise<ReplaySnapshot>;
747
+ /**
748
+ * Persist a merge trace to the durable audit log.
749
+ */
750
+ appendAuditTrace(trace: PersistedAuditTrace): Promise<void>;
751
+ /**
752
+ * Read persisted audit traces with optional filters.
753
+ */
754
+ getAuditTraces(query?: AuditTraceQuery): Promise<PersistedAuditTrace[]>;
755
+ /**
756
+ * Export operations and merge traces as a portable audit bundle.
757
+ */
758
+ exportAudit(options?: AuditExportOptions): Promise<Uint8Array>;
490
759
  /**
491
760
  * Get the schema definition.
492
761
  */
493
762
  getSchema(): SchemaDefinition;
763
+ /**
764
+ * Route local CRUD through the unified apply pipeline (korajs ApplyPipeline).
765
+ */
766
+ setLocalMutationHandler(handler: LocalMutationHandler | null): void;
767
+ /**
768
+ * Build mutation context for a collection (used by ApplyPipeline side effects).
769
+ */
770
+ createMutationContext(collection: string, options?: {
771
+ inTransaction?: boolean;
772
+ extraCausalDeps?: string[];
773
+ }): LocalMutationContext;
774
+ /**
775
+ * Load a materialized row by ID, including soft-deleted tombstones.
776
+ */
777
+ findMaterializedRow(collection: string, recordId: string): Promise<MaterializedRowSnapshot | null>;
778
+ /**
779
+ * Latest operation from this device for a record (used for delete-vs-update merge on sync).
780
+ */
781
+ /**
782
+ * Load the last server version vector acknowledged by this client (persisted in `_kora_meta`).
783
+ */
784
+ loadLastAckedServerVector(): Promise<VersionVector>;
785
+ /**
786
+ * Persist the last server version vector this client believes the server has applied.
787
+ */
788
+ saveLastAckedServerVector(vector: VersionVector): Promise<void>;
789
+ /**
790
+ * Load persisted delta cursor for resuming paginated initial sync.
791
+ */
792
+ loadDeltaCursor(): Promise<string | null>;
793
+ /**
794
+ * Persist or clear the delta cursor for paginated initial sync resume.
795
+ */
796
+ saveDeltaCursor(cursor: string | null): Promise<void>;
797
+ /**
798
+ * Local operations not yet reflected on the server version vector.
799
+ */
800
+ getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]>;
801
+ /**
802
+ * Count of local operations ahead of the server version vector.
803
+ */
804
+ countUnsyncedOperations(serverVector: VersionVector): Promise<number>;
805
+ /**
806
+ * Compact the local operation log using materialized rows as the baseline.
807
+ * Only removes ops the server has acknowledged (per {@link CompactionStrategy}).
808
+ */
809
+ compact(strategy: CompactionStrategy): Promise<CompactionResult>;
810
+ /**
811
+ * Merge session remote vector with persisted last-acked vector (max per node).
812
+ */
813
+ mergeServerVectors(sessionVector: VersionVector, persistedVector: VersionVector): VersionVector;
814
+ getLatestLocalOperationForRecord(collection: string, recordId: string): Promise<Operation | null>;
815
+ /**
816
+ * Latest operation for a record from any node (for 3-way merge when local op log is empty).
817
+ */
818
+ getLatestOperationForRecord(collection: string, recordId: string): Promise<Operation | null>;
494
819
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
495
820
  getSubscriptionManager(): SubscriptionManager;
496
821
  /**
@@ -517,7 +842,31 @@ declare class Store implements OperationLog {
517
842
  * @returns The operations that were committed
518
843
  */
519
844
  transaction(fn: (tx: TransactionContext) => Promise<void>): Promise<Operation[]>;
520
- private nextSequenceNumber;
845
+ /**
846
+ * Export all data as a portable backup binary.
847
+ * Includes operations, version vector, metadata, and optionally materialized records.
848
+ *
849
+ * @param options - Backup options (includeRecords, collections, onProgress)
850
+ * @returns Backup as a Uint8Array
851
+ */
852
+ exportBackup(options?: BackupOptions): Promise<Uint8Array>;
853
+ /**
854
+ * Restore data from a backup binary.
855
+ *
856
+ * @param data - The backup data
857
+ * @param options - Restore options (merge, collections, onProgress)
858
+ * @returns Result of the restore operation
859
+ */
860
+ importBackup(data: Uint8Array, options?: RestoreOptions): Promise<RestoreResult>;
861
+ /**
862
+ * Read backup manifest without loading the entire backup.
863
+ *
864
+ * @param data - The raw backup data
865
+ * @returns The backup manifest
866
+ */
867
+ static readBackupManifest(data: Uint8Array): BackupManifest;
868
+ private recordOperationSequence;
869
+ private allocateSequenceNumber;
521
870
  /**
522
871
  * Check the stored schema version and run any pending migrations.
523
872
  * Migrations are applied in version order within a transaction.
@@ -548,6 +897,62 @@ interface CollectionAccessor {
548
897
  where(conditions: Record<string, unknown>): QueryBuilder;
549
898
  }
550
899
 
900
+ /**
901
+ * Bridges the async QueryBuilder.subscribe() API with synchronous snapshot reads
902
+ * required by React useSyncExternalStore, Vue composables, and Svelte stores.
903
+ *
904
+ * Uses lazy subscription: the underlying query subscription starts when the first
905
+ * listener attaches and stops when the last listener detaches.
906
+ */
907
+ declare class QueryStore<T = CollectionRecord> {
908
+ private snapshot;
909
+ private listeners;
910
+ private unsubscribeQuery;
911
+ private active;
912
+ private readonly queryBuilder;
913
+ constructor(queryBuilder: QueryBuilder<T>);
914
+ /**
915
+ * Subscribe to snapshot changes.
916
+ *
917
+ * @returns Unsubscribe function
918
+ */
919
+ subscribe: (onStoreChange: () => void) => (() => void);
920
+ /** Synchronous read of the latest query results. */
921
+ getSnapshot: () => readonly T[];
922
+ /** Tear down listeners and the underlying query subscription. */
923
+ destroy(): void;
924
+ private startSubscription;
925
+ private stopSubscription;
926
+ private notifyListeners;
927
+ }
928
+
929
+ /**
930
+ * Ensures a query builder is backed by an open store (not a legacy pending placeholder).
931
+ */
932
+ declare function assertQueryReady(query: QueryBuilder<unknown>): void;
933
+
934
+ /**
935
+ * Reference-counted cache of {@link QueryStore} instances keyed by query descriptor.
936
+ * Identical queries from different components share one underlying subscription.
937
+ */
938
+ declare class QueryStoreCache {
939
+ private readonly scopeKey;
940
+ private entries;
941
+ constructor(scopeKey?: string);
942
+ getOrCreate<T>(queryBuilder: QueryBuilder<T>): QueryStore<T>;
943
+ release(queryBuilder: QueryBuilder<unknown>): void;
944
+ clear(): void;
945
+ get size(): number;
946
+ private getKey;
947
+ }
948
+ /**
949
+ * Process-wide shared query store cache for framework bindings.
950
+ *
951
+ * @deprecated Prefer {@link KoraApp.getQueryStoreCache} via `KoraProvider` context
952
+ * so each app instance owns an isolated cache.
953
+ */
954
+ declare function getSharedQueryStoreCache(): QueryStoreCache;
955
+
551
956
  /**
552
957
  * Bloom filter for subscription dependency tracking.
553
958
  *
@@ -638,182 +1043,6 @@ declare class SubscriptionBloomFilter {
638
1043
  private getPositions;
639
1044
  }
640
1045
 
641
- /**
642
- * A resolved relation reference: describes a relation that points TO a given collection.
643
- * When a record in `targetCollection` is deleted, we must handle records in
644
- * `sourceCollection` that reference it via `foreignKeyField`.
645
- */
646
- interface IncomingRelation {
647
- /** Name of the relation in the schema */
648
- relationName: string;
649
- /** Collection that holds the foreign key (the "from" side) */
650
- sourceCollection: string;
651
- /** Field in the source collection that references the target */
652
- foreignKeyField: string;
653
- /** What to do when the referenced record is deleted */
654
- onDelete: OnDeleteAction;
655
- /** The full relation definition */
656
- relation: RelationDefinition;
657
- }
658
-
659
- /**
660
- * Configuration for the RelationEnforcer.
661
- */
662
- interface RelationEnforcerConfig {
663
- schema: SchemaDefinition;
664
- adapter: StorageAdapter;
665
- clock: HybridLogicalClock;
666
- nodeId: string;
667
- getSequenceNumber: () => number;
668
- }
669
- /**
670
- * Result of enforcing referential integrity on a delete operation.
671
- * Contains all additional operations that were created as side effects
672
- * (cascaded deletes and set-null updates).
673
- */
674
- interface EnforcementResult {
675
- /** Additional operations created by cascading deletes and set-null updates */
676
- operations: Operation[];
677
- }
678
- /**
679
- * Enforces referential integrity constraints during local delete operations.
680
- *
681
- * When a record is deleted, this enforcer checks all relations that reference
682
- * the deleted record's collection and applies the appropriate onDelete policy:
683
- *
684
- * - **cascade**: Recursively deletes all referencing records
685
- * - **set-null**: Sets the foreign key to null on all referencing records
686
- * - **restrict**: Throws a ReferentialIntegrityError if any references exist
687
- * - **no-action**: Does nothing (the foreign key is left dangling)
688
- *
689
- * The enforcer operates within a provided transaction to ensure atomicity.
690
- * All generated operations (cascaded deletes, set-null updates) share a
691
- * causal dependency chain through the original delete operation.
692
- *
693
- * @example
694
- * ```typescript
695
- * const enforcer = new RelationEnforcer({
696
- * schema, adapter, clock, nodeId,
697
- * getSequenceNumber: () => ++seq,
698
- * })
699
- * const result = await enforcer.enforceDelete(
700
- * 'projects', 'proj-1', tx, ['delete-op-id']
701
- * )
702
- * // result.operations contains any cascaded delete/update ops
703
- * ```
704
- */
705
- declare class RelationEnforcer {
706
- private readonly lookup;
707
- private readonly schema;
708
- private readonly adapter;
709
- private readonly clock;
710
- private readonly nodeId;
711
- private readonly getSequenceNumber;
712
- constructor(config: RelationEnforcerConfig);
713
- /**
714
- * Enforce referential integrity after deleting a record.
715
- *
716
- * Must be called within a transaction. The transaction handle is used
717
- * for all cascaded writes to ensure atomicity.
718
- *
719
- * @param collection - The collection the deleted record belongs to
720
- * @param recordId - The ID of the deleted record
721
- * @param tx - The active transaction handle
722
- * @param causalDeps - Causal dependencies for generated operations
723
- * @returns All additional operations created as side effects
724
- * @throws {ReferentialIntegrityError} If a 'restrict' policy is violated
725
- */
726
- enforceDelete(collection: string, recordId: string, tx: Transaction, causalDeps: string[]): Promise<EnforcementResult>;
727
- /**
728
- * Enforce a single relation's onDelete policy.
729
- */
730
- private enforceRelation;
731
- /**
732
- * Cascade: delete all records in the source collection that reference
733
- * the deleted record, then recursively cascade those deletes.
734
- */
735
- private enforceCascade;
736
- /**
737
- * Set-null: update all referencing records to set the foreign key to null.
738
- */
739
- private enforceSetNull;
740
- /**
741
- * Restrict: refuse the delete if any referencing records exist.
742
- */
743
- private enforceRestrict;
744
- /**
745
- * Get the relation lookup map for external use (e.g., by the merge engine).
746
- */
747
- getRelationLookup(): Map<string, IncomingRelation[]>;
748
- }
749
-
750
- /**
751
- * Callback invoked after a mutation so the Store can notify subscriptions.
752
- */
753
- type MutationCallback = (collection: string, operation: Operation) => void;
754
- /**
755
- * Collection provides CRUD operations on a single schema collection.
756
- * Each mutation creates an Operation and persists both the data and the operation atomically.
757
- *
758
- * When a RelationEnforcer is provided, delete operations enforce referential integrity
759
- * policies (cascade, set-null, restrict) defined in the schema's relations.
760
- */
761
- declare class Collection {
762
- private readonly name;
763
- private readonly definition;
764
- private readonly schema;
765
- private readonly adapter;
766
- private readonly clock;
767
- private readonly nodeId;
768
- private readonly getSequenceNumber;
769
- private readonly onMutation;
770
- private readonly relationEnforcer;
771
- constructor(name: string, definition: CollectionDefinition, schema: SchemaDefinition, adapter: StorageAdapter, clock: HybridLogicalClock, nodeId: string, getSequenceNumber: () => number, onMutation: MutationCallback, relationEnforcer?: RelationEnforcer);
772
- /**
773
- * Insert a new record into the collection.
774
- * Generates a UUID v7 for the id, validates data, and persists atomically.
775
- *
776
- * @param data - The record data (auto fields and defaults are applied automatically)
777
- * @returns The inserted record with id, createdAt, updatedAt
778
- */
779
- insert(data: Record<string, unknown>): Promise<CollectionRecord>;
780
- /**
781
- * Find a record by its ID. Returns null if not found or soft-deleted.
782
- */
783
- findById(id: string): Promise<CollectionRecord | null>;
784
- /**
785
- * Update an existing record. Only the provided fields are changed.
786
- *
787
- * @param id - The record ID to update
788
- * @param data - Partial data with only the fields to change
789
- * @returns The updated record
790
- * @throws {RecordNotFoundError} If the record doesn't exist or is deleted
791
- */
792
- update(id: string, data: Record<string, unknown>): Promise<CollectionRecord>;
793
- /**
794
- * Soft-delete a record by its ID.
795
- *
796
- * When a RelationEnforcer is configured, referential integrity policies
797
- * are enforced within the same transaction:
798
- * - **cascade**: Recursively deletes all referencing records
799
- * - **set-null**: Sets foreign keys to null on referencing records
800
- * - **restrict**: Throws ReferentialIntegrityError if references exist
801
- * - **no-action**: Does nothing (dangling references are left)
802
- *
803
- * All cascaded operations are created atomically within the same transaction
804
- * and share causal dependencies with the original delete.
805
- *
806
- * @param id - The record ID to delete
807
- * @throws {RecordNotFoundError} If the record doesn't exist or is already deleted
808
- * @throws {ReferentialIntegrityError} If a 'restrict' policy is violated
809
- */
810
- delete(id: string): Promise<void>;
811
- /** Get the collection name */
812
- getName(): string;
813
- /** Get the collection definition */
814
- getDefinition(): CollectionDefinition;
815
- }
816
-
817
1046
  /**
818
1047
  * Error thrown when a local mutation attempts an invalid state transition.
819
1048
  *
@@ -868,6 +1097,91 @@ declare function validateStateTransition(collectionName: string, recordId: strin
868
1097
  */
869
1098
  declare function validateUpdateStateMachine(collectionName: string, recordId: string, collectionDef: CollectionDefinition, currentRecord: Record<string, unknown>, updateData: Record<string, unknown>): Record<string, unknown>;
870
1099
 
1100
+ /** User identity for collaborative cursor presence. */
1101
+ interface RichTextAwarenessUser {
1102
+ name: string;
1103
+ color: string;
1104
+ avatar?: string;
1105
+ }
1106
+ /** Remote collaborator cursor for a richtext field. */
1107
+ interface RichTextCursorInfo {
1108
+ clientId: number;
1109
+ userName: string;
1110
+ color: string;
1111
+ anchor: number;
1112
+ head: number;
1113
+ }
1114
+ /** Minimal sync surface required for richtext collaboration. */
1115
+ interface RichTextSyncEngine {
1116
+ getRichtextDocChannel?(): RichTextDocChannel;
1117
+ getAwarenessManager(): RichTextAwarenessManager;
1118
+ }
1119
+ interface RichTextDocChannel {
1120
+ shouldUseChannel(snapshotBytes: number, useDocChannel?: boolean): boolean;
1121
+ send(collection: string, recordId: string, field: string, update: Uint8Array): void;
1122
+ subscribe(collection: string, recordId: string, field: string, listener: (update: Uint8Array) => void): () => void;
1123
+ }
1124
+ interface RichTextAwarenessManager {
1125
+ clientId: number;
1126
+ getLocalState(): RichTextAwarenessState | null;
1127
+ setLocalState(state: RichTextAwarenessState): void;
1128
+ getStates(): Map<number, RichTextAwarenessState>;
1129
+ on(event: 'change', listener: () => void): () => void;
1130
+ }
1131
+ interface RichTextAwarenessState {
1132
+ user: RichTextAwarenessUser;
1133
+ cursor?: {
1134
+ collection: string;
1135
+ recordId: string;
1136
+ field: string;
1137
+ anchor: number;
1138
+ head: number;
1139
+ };
1140
+ }
1141
+ interface CreateRichTextControllerOptions {
1142
+ collection: CollectionAccessor;
1143
+ collectionName: string;
1144
+ recordId: string;
1145
+ fieldName: string;
1146
+ store: {
1147
+ collection(name: string): CollectionAccessor;
1148
+ };
1149
+ syncEngine?: RichTextSyncEngine | null;
1150
+ useDocChannel?: boolean;
1151
+ user?: RichTextAwarenessUser;
1152
+ }
1153
+ interface RichTextControllerSnapshot {
1154
+ ready: boolean;
1155
+ error: Error | null;
1156
+ canUndo: boolean;
1157
+ canRedo: boolean;
1158
+ cursors: readonly RichTextCursorInfo[];
1159
+ }
1160
+ interface RichTextController {
1161
+ readonly doc: yjs.Doc;
1162
+ readonly text: yjs.Text;
1163
+ getSnapshot(): RichTextControllerSnapshot;
1164
+ subscribe(listener: () => void): () => void;
1165
+ undo(): void;
1166
+ redo(): void;
1167
+ setCursor(anchor: number, head: number): void;
1168
+ clearCursor(): void;
1169
+ setUser(user: RichTextAwarenessUser | undefined): void;
1170
+ destroy(): void;
1171
+ }
1172
+
1173
+ /**
1174
+ * Framework-agnostic richtext binding: Yjs document lifecycle, persistence,
1175
+ * incremental doc channel, and awareness cursors.
1176
+ */
1177
+ declare function createRichTextController(options: CreateRichTextControllerOptions): RichTextController;
1178
+
1179
+ /**
1180
+ * Narrows a sync engine reference to the richtext controller surface.
1181
+ * SyncEngine satisfies this interface at runtime; types live in different packages.
1182
+ */
1183
+ declare function asRichTextSyncEngine(engine: unknown): RichTextSyncEngine | null;
1184
+
871
1185
  type RichtextInput = string | Uint8Array | ArrayBuffer | null | undefined;
872
1186
  /**
873
1187
  * Encodes richtext values into Yjs document updates.
@@ -881,6 +1195,11 @@ declare function decodeRichtext(value: unknown): Uint8Array | null;
881
1195
  * Reads plain text from a richtext Yjs state update.
882
1196
  */
883
1197
  declare function richtextToPlainText(value: RichtextInput): string;
1198
+ /**
1199
+ * Compares two richtext values by decoded plain text.
1200
+ * Yjs update bytes are not stable across separate encodes of the same string.
1201
+ */
1202
+ declare function richtextStatesEqual(a: unknown, b: unknown): boolean;
884
1203
 
885
1204
  /**
886
1205
  * Minimal pluralization/singularization utilities for relation name resolution.
@@ -909,4 +1228,63 @@ declare function pluralize(word: string): string;
909
1228
  */
910
1229
  declare function singularize(word: string): string;
911
1230
 
912
- export { AdapterError, ApplyResult, Collection, type CollectionAccessor, CollectionRecord, InvalidStateTransitionError, OrderByDirection, PersistenceError, QueryBuilder, QueryDescriptor, QueryError, RecordNotFoundError, SequenceManager, StorageAdapter, Store, StoreConfig, StoreNotOpenError, SubscriptionBloomFilter, SubscriptionCallback, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionStats, Transaction, type TransactionCollectionAccessor, TransactionContext, type TransactionContextConfig, WhereClause, WorkerInitError, WorkerTimeoutError, decodeRichtext, encodeRichtext, pluralize, richtextToPlainText, singularize, validateStateTransition, validateUpdateStateMachine };
1231
+ /**
1232
+ * Export a backup from a store.
1233
+ *
1234
+ * Reads all data via the adapter and produces a portable binary backup.
1235
+ *
1236
+ * @param adapter - The storage adapter
1237
+ * @param schema - The schema definition
1238
+ * @param nodeId - Current node ID
1239
+ * @param schemaVersion - Schema version
1240
+ * @param options - Backup options
1241
+ * @returns Backup as a Uint8Array
1242
+ */
1243
+ declare function exportBackup(adapter: StorageAdapter, schema: SchemaDefinition, nodeId: string, schemaVersion: number, options?: BackupOptions): Promise<Uint8Array>;
1244
+ /**
1245
+ * Read backup manifest without loading the entire backup.
1246
+ * Parses only the first section (which is always the manifest).
1247
+ *
1248
+ * @param data - The raw backup data
1249
+ * @returns The backup manifest
1250
+ */
1251
+ declare function readBackupManifest(data: Uint8Array): BackupManifest;
1252
+ /**
1253
+ * Validate the backup checksum.
1254
+ *
1255
+ * @param data - The raw backup data
1256
+ * @returns True if the checksum is valid
1257
+ */
1258
+ declare function verifyBackupChecksum(data: Uint8Array): Promise<boolean>;
1259
+ /**
1260
+ * Restore a backup into a store.
1261
+ *
1262
+ * @param adapter - The storage adapter
1263
+ * @param schema - The schema definition
1264
+ * @param data - The backup data
1265
+ * @param options - Restore options
1266
+ * @returns Result of the restore operation
1267
+ */
1268
+ declare function restoreBackup(adapter: StorageAdapter, schema: SchemaDefinition, data: Uint8Array, options?: RestoreOptions): Promise<RestoreResult>;
1269
+
1270
+ /**
1271
+ * Build a {@link PersistedAuditTrace} from a merge-related Kora event.
1272
+ */
1273
+ declare function persistedAuditTraceFromEvent(event: Extract<KoraEvent, {
1274
+ type: 'merge:completed' | 'merge:conflict' | 'constraint:violated';
1275
+ }>): PersistedAuditTrace;
1276
+
1277
+ /**
1278
+ * Decode an audit export binary into structured data.
1279
+ */
1280
+ declare function decodeAuditExport(data: Uint8Array): AuditExportPayload;
1281
+ /**
1282
+ * Read only the manifest from an audit export without decoding all sections.
1283
+ */
1284
+ declare function readAuditExportManifest(data: Uint8Array): AuditExportManifest;
1285
+ /**
1286
+ * Verify the checksum of an audit export file.
1287
+ */
1288
+ declare function verifyAuditExportChecksum(data: Uint8Array): Promise<boolean>;
1289
+
1290
+ export { AdapterError, ApplyRemoteOptions, type AuditExportManifest, type AuditExportOptions, type AuditExportPayload, type AuditExportProgress, type AuditTraceQuery, type BackupManifest, type BackupOptions, type BackupProgress, COMPACTION_BASELINE_META_KEY, type CollectionAccessor, CollectionRecord, type CompactionResult, type CompactionStrategy, type CreateRichTextControllerOptions, InvalidStateTransitionError, LAST_ACKED_SERVER_VECTOR_META_KEY, LocalMutationHandler, MaterializedRowSnapshot, OrderByDirection, type PersistedAuditTrace, PersistenceError, QueryBuilder, QueryDescriptor, QueryError, QueryStore, QueryStoreCache, RecordNotFoundError, type ReplaySnapshot, type RestoreOptions, type RestoreResult, type RichTextAwarenessUser, type RichTextController, type RichTextControllerSnapshot, type RichTextCursorInfo, type RichTextSyncEngine, SequenceManager, StorageAdapter, Store, StoreConfig, StoreNotOpenError, SubscriptionBloomFilter, SubscriptionCallback, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionStats, type TransactionCollectionAccessor, TransactionContext, type TransactionContextConfig, WhereClause, WorkerInitError, WorkerTimeoutError, asRichTextSyncEngine, assertQueryReady, collectOperationsAheadOfServer, compactOperationLog, computeAckCompactionWatermark, createRichTextController, decodeAuditExport, decodeRichtext, deserializeVersionVectorFromMeta, encodeRichtext, exportBackup, getSharedQueryStoreCache, mergeVersionVectors, persistedAuditTraceFromEvent, pluralize, readAuditExportManifest, readBackupManifest, restoreBackup, richtextStatesEqual, richtextToPlainText, serializeVersionVectorToMeta, singularize, validateStateTransition, validateUpdateStateMachine, verifyAuditExportChecksum, verifyBackupChecksum };