@korajs/store 0.6.0 → 1.0.0-beta.1

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/better-sqlite3.cjs +94 -40
  3. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  4. package/dist/adapters/better-sqlite3.d.cts +9 -1
  5. package/dist/adapters/better-sqlite3.d.ts +9 -1
  6. package/dist/adapters/better-sqlite3.js +62 -41
  7. package/dist/adapters/better-sqlite3.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +24 -13
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.cts +2 -2
  11. package/dist/adapters/indexeddb.d.ts +2 -2
  12. package/dist/adapters/indexeddb.js +4 -3
  13. package/dist/adapters/indexeddb.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm.cjs +24 -13
  15. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm.d.cts +27 -4
  17. package/dist/adapters/sqlite-wasm.d.ts +27 -4
  18. package/dist/adapters/sqlite-wasm.js +6 -4
  19. package/dist/{backup-JN3LB4YT.js → backup-ZV4Y424B.js} +3 -3
  20. package/dist/blob-fs.cjs +166 -0
  21. package/dist/blob-fs.cjs.map +1 -0
  22. package/dist/blob-fs.d.cts +29 -0
  23. package/dist/blob-fs.d.ts +29 -0
  24. package/dist/blob-fs.js +128 -0
  25. package/dist/blob-fs.js.map +1 -0
  26. package/dist/{chunk-56LL4XP6.js → chunk-6NKLDBTY.js} +52 -1
  27. package/dist/chunk-6NKLDBTY.js.map +1 -0
  28. package/dist/{chunk-AGN3CIPB.js → chunk-FFYA7J5H.js} +2 -2
  29. package/dist/{chunk-XO4LMPAH.js → chunk-IOEFNHHP.js} +2 -35
  30. package/dist/chunk-IOEFNHHP.js.map +1 -0
  31. package/dist/{chunk-LAWV6CFH.js → chunk-QL2XVZRN.js} +12 -1
  32. package/dist/chunk-QL2XVZRN.js.map +1 -0
  33. package/dist/{chunk-6AHYGQQT.js → chunk-RBPVEAVM.js} +4 -4
  34. package/dist/chunk-TVJEC5S2.js +40 -0
  35. package/dist/chunk-TVJEC5S2.js.map +1 -0
  36. package/dist/chunk-W4MTTNEZ.js +55 -0
  37. package/dist/chunk-W4MTTNEZ.js.map +1 -0
  38. package/dist/{chunk-EZSTD2XC.js → chunk-X57MWW35.js} +2 -2
  39. package/dist/{chunk-7BPETY5Y.js → chunk-ZLWAYQEQ.js} +128 -15
  40. package/dist/chunk-ZLWAYQEQ.js.map +1 -0
  41. package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
  42. package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
  43. package/dist/{export-audit-5GV6VRDZ.js → export-audit-AJ7RORNK.js} +3 -3
  44. package/dist/index.cjs +1245 -113
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +468 -7
  47. package/dist/index.d.ts +468 -7
  48. package/dist/index.js +975 -61
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal.cjs +123 -33
  51. package/dist/internal.cjs.map +1 -1
  52. package/dist/internal.d.cts +3 -3
  53. package/dist/internal.d.ts +3 -3
  54. package/dist/internal.js +3 -3
  55. package/dist/{sqlite-wasm-channel-I9y_jObg.d.cts → sqlite-wasm-channel-DUDPclTE.d.cts} +2 -15
  56. package/dist/{sqlite-wasm-channel-I9y_jObg.d.ts → sqlite-wasm-channel-DUDPclTE.d.ts} +2 -15
  57. package/dist/sqlite-wasm-channel-H43KEZLM.js +12 -0
  58. package/dist/{types-CTr00g_d.d.cts → types-5h4Hqcwc.d.ts} +10 -3
  59. package/dist/{types-BMyHmwvn.d.ts → types-DfPiPwKr.d.cts} +10 -3
  60. package/dist/{types-DepnRDk_.d.cts → types-k6AZgiH4.d.cts} +48 -2
  61. package/dist/{types-DepnRDk_.d.ts → types-k6AZgiH4.d.ts} +48 -2
  62. package/package.json +15 -2
  63. package/dist/chunk-56LL4XP6.js.map +0 -1
  64. package/dist/chunk-7BPETY5Y.js.map +0 -1
  65. package/dist/chunk-LAWV6CFH.js.map +0 -1
  66. package/dist/chunk-XO4LMPAH.js.map +0 -1
  67. package/dist/sqlite-wasm-channel-HDDW6UBH.js +0 -10
  68. /package/dist/{backup-JN3LB4YT.js.map → backup-ZV4Y424B.js.map} +0 -0
  69. /package/dist/{chunk-AGN3CIPB.js.map → chunk-FFYA7J5H.js.map} +0 -0
  70. /package/dist/{chunk-6AHYGQQT.js.map → chunk-RBPVEAVM.js.map} +0 -0
  71. /package/dist/{chunk-EZSTD2XC.js.map → chunk-X57MWW35.js.map} +0 -0
  72. /package/dist/{export-audit-5GV6VRDZ.js.map → export-audit-AJ7RORNK.js.map} +0 -0
  73. /package/dist/{sqlite-wasm-channel-HDDW6UBH.js.map → sqlite-wasm-channel-H43KEZLM.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1,10 +1,12 @@
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';
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, g as RowVersionState } from './types-k6AZgiH4.cjs';
2
+ export { M as MigrationPlan, h as OrderByClause, i as StoreIsolation, T as Transaction, j as TransactionBufferedEntry, k as TransactionCommitBatch, l as TransactionCommitResult, m as WhereOperators } from './types-k6AZgiH4.cjs';
3
+ import { Operation, KoraError, VersionVector, SchemaDefinition, HLCTimestamp, MergeTrace, CollectionDefinition, SequenceConfig, HybridLogicalClock, CausalTracker, OperationLog, ApplyResult, 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-CTr00g_d.cjs';
6
- export { C as Collection } from './types-CTr00g_d.cjs';
5
+ import { R as RelationEnforcer, L as LocalMutationContext } from './types-DfPiPwKr.cjs';
6
+ export { C as Collection } from './types-DfPiPwKr.cjs';
7
7
  import * as yjs from 'yjs';
8
+ import { C as ContentAddressedBlobStore, M as MemoryBlobStore } from './content-addressed-blob-store-Dcj3SB9r.cjs';
9
+ export { B as BlobIntegrityError } from './content-addressed-blob-store-Dcj3SB9r.cjs';
8
10
 
9
11
  /**
10
12
  * In-memory materialized state at a causal cut in the operation log.
@@ -33,6 +35,18 @@ declare class QueryError extends KoraError {
33
35
  declare class RecordNotFoundError extends KoraError {
34
36
  constructor(collection: string, recordId: string);
35
37
  }
38
+ /**
39
+ * Thrown inside `applyRemoteOperation` when the row's version state changed
40
+ * between the caller's snapshot read and the guarded write (a concurrent local
41
+ * mutation landed in the window). The transaction is rolled back with nothing
42
+ * written; the caller re-reads fresh state, recomputes its merge, and retries.
43
+ * This is the optimistic-concurrency guard that keeps merge-engine results
44
+ * (richtext, add-wins arrays, constraint resolutions) from clobbering newer
45
+ * local edits they never saw.
46
+ */
47
+ declare class OptimisticLockError extends KoraError {
48
+ constructor(collection: string, recordId: string);
49
+ }
36
50
  /**
37
51
  * Thrown when a storage adapter operation fails.
38
52
  */
@@ -101,6 +115,20 @@ declare function computeAckCompactionWatermark(serverVector: VersionVector): Ver
101
115
  */
102
116
  declare function compactOperationLog(adapter: StorageAdapter, schema: SchemaDefinition, strategy: CompactionStrategy, serverVector: VersionVector): Promise<CompactionResult>;
103
117
 
118
+ /**
119
+ * Result of re-stamping unsynced operations after a clock correction.
120
+ */
121
+ interface ClockRebaseResult {
122
+ /** The rewritten operations, in causal order (original HLC total order preserved). */
123
+ operations: Operation[];
124
+ /** Maps each old (pre-rebase) operation id to its new content-addressed id. */
125
+ idMapping: Record<string, string>;
126
+ /** Number of operations that were re-stamped. */
127
+ rebasedCount: number;
128
+ /** Highest timestamp assigned during the rebase, or null when nothing was rebased. */
129
+ newMaxTimestamp: HLCTimestamp | null;
130
+ }
131
+
104
132
  declare const LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
105
133
  /**
106
134
  * Serialize a version vector for `_kora_meta` storage.
@@ -692,6 +720,7 @@ declare class Store implements OperationLog {
692
720
  private localMutationHandler;
693
721
  private relationEnforcer;
694
722
  private causalTracker;
723
+ private readonly secretKeyProvider;
695
724
  constructor(config: StoreConfig);
696
725
  /**
697
726
  * Open the store: initialize the database, load or generate a node ID,
@@ -715,6 +744,12 @@ declare class Store implements OperationLog {
715
744
  /**
716
745
  * Get the node ID for this store instance.
717
746
  */
747
+ /**
748
+ * Records the offset between server time and this device's clock
749
+ * (serverTime - localTime), learned at sync handshake. Lets the store's HLC
750
+ * validate remote timestamps correctly even when the local clock is wrong.
751
+ */
752
+ setClockReferenceOffset(offsetMs: number): void;
718
753
  getNodeId(): string;
719
754
  /**
720
755
  * Apply a remote operation received from sync.
@@ -722,6 +757,18 @@ declare class Store implements OperationLog {
722
757
  * and updates the version vector.
723
758
  */
724
759
  applyRemoteOperation(op: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
760
+ /**
761
+ * Materialize update/delete operations that were logged for a record BEFORE
762
+ * its insert arrived (reordered delivery). Runs inside the insert's write
763
+ * transaction, folding each orphan in timestamp order through the exact
764
+ * per-field LWW / LWW-delete rules the normal apply paths use, so the final
765
+ * row state equals what a device that received the operations in causal
766
+ * order computed. Called only when the insert created a previously-absent
767
+ * row — in that case every logged update/delete for the record is
768
+ * necessarily an orphan (rows are soft-deleted, never removed, so a row
769
+ * that is absent now was never materialized).
770
+ */
771
+ private foldOrphanedOperations;
725
772
  /**
726
773
  * Get operations from a node within a sequence number range.
727
774
  * Implements the OperationLog interface for computeDelta.
@@ -775,6 +822,14 @@ declare class Store implements OperationLog {
775
822
  * Load a materialized row by ID, including soft-deleted tombstones.
776
823
  */
777
824
  findMaterializedRow(collection: string, recordId: string): Promise<MaterializedRowSnapshot | null>;
825
+ /**
826
+ * Raw version state of a materialized row, for optimistic-concurrency guarded
827
+ * applies: capture this snapshot before computing a merge, pass it as
828
+ * `guardRowState`, and the apply refuses to write if the row changed since.
829
+ * Returns null when no row exists (which is itself a valid guard state — the
830
+ * apply then requires the row to still be absent).
831
+ */
832
+ getRowVersionState(collection: string, recordId: string): Promise<RowVersionState | null>;
778
833
  /**
779
834
  * Latest operation from this device for a record (used for delete-vs-update merge on sync).
780
835
  */
@@ -798,6 +853,18 @@ declare class Store implements OperationLog {
798
853
  * Local operations not yet reflected on the server version vector.
799
854
  */
800
855
  getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]>;
856
+ /**
857
+ * Re-stamp never-acknowledged local operations after a fast device clock was
858
+ * corrected, so sync can resume immediately. Safe because unacknowledged
859
+ * operations are private to this device (like unpushed git commits);
860
+ * acknowledged/shared operations are immutable and never touched.
861
+ *
862
+ * After the rewrite the store's HLC is advanced past the highest new
863
+ * timestamp so subsequent local writes keep sorting after the rebased ops.
864
+ * No data-change notifications are emitted: materialized values are
865
+ * unchanged, only version stamps move.
866
+ */
867
+ rebaseUnsyncedOperations(unsyncedOpIds: string[], correctedNowMs: number): Promise<ClockRebaseResult>;
801
868
  /**
802
869
  * Count of local operations ahead of the server version vector.
803
870
  */
@@ -1185,8 +1252,14 @@ declare function asRichTextSyncEngine(engine: unknown): RichTextSyncEngine | nul
1185
1252
  type RichtextInput = string | Uint8Array | ArrayBuffer | null | undefined;
1186
1253
  /**
1187
1254
  * Encodes richtext values into Yjs document updates.
1255
+ *
1256
+ * Also accepts the tagged `{ $koraBytes }` form that binary richtext values
1257
+ * take inside `op.data` (and, for old dev databases, the pre-fix numeric-key
1258
+ * object shape), so every consumer that writes op-data values to columns
1259
+ * inherits the decoding. Plain strings keep their exact historical behavior:
1260
+ * encoded as a fresh Yjs document containing the string.
1188
1261
  */
1189
- declare function encodeRichtext(value: RichtextInput): Uint8Array | null;
1262
+ declare function encodeRichtext(value: RichtextInput | KoraBytesValue): Uint8Array | null;
1190
1263
  /**
1191
1264
  * Decodes driver-provided richtext values into Uint8Array.
1192
1265
  */
@@ -1287,4 +1360,392 @@ declare function readAuditExportManifest(data: Uint8Array): AuditExportManifest;
1287
1360
  */
1288
1361
  declare function verifyAuditExportChecksum(data: Uint8Array): Promise<boolean>;
1289
1362
 
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 };
1363
+ /**
1364
+ * The minimal directory abstraction an {@link OpfsBlobStore} needs. Blobs are
1365
+ * laid out as `<shard>/<name>` (shard = first two hex chars of the hash) so a
1366
+ * single directory never holds millions of entries.
1367
+ *
1368
+ * The real backend ({@link createOpfsBlobDirectory}) is the browser Origin
1369
+ * Private File System; tests supply an in-memory implementation. Keeping this
1370
+ * port small means the content-addressed logic (hashing, integrity, dedup,
1371
+ * sharding) is fully testable without a browser.
1372
+ */
1373
+ interface OpfsBlobDirectory {
1374
+ /** Read the bytes at `<shard>/<name>`, or null if absent. */
1375
+ read(shard: string, name: string): Promise<Uint8Array | null>;
1376
+ /**
1377
+ * Write bytes at `<shard>/<name>`, creating the shard directory as needed.
1378
+ * Must be atomic: a crash mid-write may not leave a partially-written file
1379
+ * that a later read would trust.
1380
+ */
1381
+ write(shard: string, name: string, bytes: Uint8Array): Promise<void>;
1382
+ /** Remove `<shard>/<name>`. Returns whether anything was removed. */
1383
+ remove(shard: string, name: string): Promise<boolean>;
1384
+ /** Whether `<shard>/<name>` exists. */
1385
+ has(shard: string, name: string): Promise<boolean>;
1386
+ /** Count of stored blobs across all shards (temp files excluded). */
1387
+ count(): Promise<number>;
1388
+ /** Every stored blob name (hash) across all shards (temp files excluded). */
1389
+ keys(): Promise<string[]>;
1390
+ }
1391
+ /**
1392
+ * A persistent {@link ContentAddressedBlobStore} for the browser, backed by the
1393
+ * Origin Private File System (OPFS) — the same durable storage the SQLite WASM
1394
+ * adapter uses. Blobs survive reloads, are sharded by hash prefix, deduplicated,
1395
+ * and integrity-verified on read (the returned bytes are checked to hash to the
1396
+ * requested key). Writes commit atomically so a torn write cannot surface as
1397
+ * trusted data.
1398
+ *
1399
+ * The store itself is environment-neutral: it talks to an {@link OpfsBlobDirectory}.
1400
+ * Use {@link createOpfsBlobStore} in a browser to get one backed by real OPFS.
1401
+ */
1402
+ declare class OpfsBlobStore implements ContentAddressedBlobStore {
1403
+ private readonly dir;
1404
+ constructor(dir: OpfsBlobDirectory);
1405
+ private shardFor;
1406
+ put(bytes: Uint8Array, metadata?: BlobRefMetadata): Promise<BlobRef>;
1407
+ get(hash: string): Promise<Uint8Array | null>;
1408
+ has(hash: string): Promise<boolean>;
1409
+ delete(hash: string): Promise<boolean>;
1410
+ size(): Promise<number>;
1411
+ list(): Promise<string[]>;
1412
+ }
1413
+ /**
1414
+ * Create an {@link OpfsBlobDirectory} backed by the browser's Origin Private
1415
+ * File System, rooted at a named subdirectory. Best-effort requests persistent
1416
+ * storage so the browser is less likely to evict blobs under storage pressure.
1417
+ *
1418
+ * @param rootDirName - The OPFS subdirectory to store blobs under (default `kora-blobs`)
1419
+ * @throws {Error} If OPFS is unavailable (e.g. called outside a browser)
1420
+ */
1421
+ declare function createOpfsBlobDirectory(rootDirName?: string): Promise<OpfsBlobDirectory>;
1422
+ /**
1423
+ * Create a persistent {@link OpfsBlobStore} backed by real browser OPFS.
1424
+ *
1425
+ * @param rootDirName - The OPFS subdirectory to store blobs under (default `kora-blobs`)
1426
+ * @throws {Error} If OPFS is unavailable (e.g. called outside a browser)
1427
+ */
1428
+ declare function createOpfsBlobStore(rootDirName?: string): Promise<OpfsBlobStore>;
1429
+
1430
+ /** Default chunk size for out-of-band blob transfer (256 KiB). */
1431
+ declare const DEFAULT_CHUNK_SIZE: number;
1432
+ /**
1433
+ * A manifest describing how a blob is split into content-addressed chunks.
1434
+ *
1435
+ * The manifest is small and travels with the operation stream (or alongside it);
1436
+ * the chunk bytes travel out of band. Because every chunk is content-addressed,
1437
+ * identical chunks across blobs are stored and transferred once, and a peer only
1438
+ * ever requests chunk hashes it does not already hold.
1439
+ */
1440
+ interface BlobManifest {
1441
+ /** Hex SHA-256 hash of the full reassembled blob (its content address). */
1442
+ blobHash: string;
1443
+ /** Total size of the full blob in bytes. */
1444
+ size: number;
1445
+ /** The chunk size used to split the blob. */
1446
+ chunkSize: number;
1447
+ /** Ordered hashes of each chunk. Repeats when identical chunks recur. */
1448
+ chunkHashes: string[];
1449
+ mimeType?: string;
1450
+ filename?: string;
1451
+ }
1452
+ /** Optional metadata carried on a manifest. */
1453
+ interface BlobManifestMetadata {
1454
+ mimeType?: string;
1455
+ filename?: string;
1456
+ }
1457
+ /**
1458
+ * Split a blob into content-addressed chunks and produce its manifest.
1459
+ *
1460
+ * @param bytes - The full blob content
1461
+ * @param chunkSize - Bytes per chunk (defaults to {@link DEFAULT_CHUNK_SIZE})
1462
+ * @param metadata - Optional mime type / filename to record on the manifest
1463
+ * @returns The manifest and a map of chunk hash to chunk bytes (deduplicated)
1464
+ */
1465
+ declare function chunkBlob(bytes: Uint8Array, chunkSize?: number, metadata?: BlobManifestMetadata): Promise<{
1466
+ manifest: BlobManifest;
1467
+ chunks: Map<string, Uint8Array>;
1468
+ }>;
1469
+ /**
1470
+ * Reassemble a blob from chunks held in a content-addressed store, verifying
1471
+ * integrity at both levels: each chunk is verified by the store on read, and the
1472
+ * reassembled whole is verified against the manifest's blob hash.
1473
+ *
1474
+ * @param manifest - The blob manifest
1475
+ * @param chunkStore - A store holding the chunk bytes keyed by chunk hash
1476
+ * @returns The full reassembled blob bytes
1477
+ * @throws {BlobIntegrityError} If the reassembled bytes do not match blobHash
1478
+ * @throws {Error} If a chunk named in the manifest is missing from the store
1479
+ */
1480
+ declare function reassembleBlob(manifest: BlobManifest, chunkStore: ContentAddressedBlobStore): Promise<Uint8Array>;
1481
+
1482
+ /**
1483
+ * A source of chunk bytes on the sending side of a transfer. In production this
1484
+ * is backed by the sync connection (the receiver requests a chunk hash, the peer
1485
+ * that holds the blob answers with the bytes); in tests it is backed by a map.
1486
+ */
1487
+ interface ChunkProvider {
1488
+ /** Fetch the bytes for a chunk hash, or null if the provider does not have it. */
1489
+ getChunk(hash: string): Promise<Uint8Array | null>;
1490
+ }
1491
+ /** Result of receiving a blob out of band. */
1492
+ interface ReceiveBlobResult {
1493
+ /** The content-addressed reference to the reassembled blob. */
1494
+ ref: BlobRef;
1495
+ /** Number of chunks actually fetched from the provider. */
1496
+ chunksFetched: number;
1497
+ /** Number of chunks skipped because they were already held locally. */
1498
+ chunksSkipped: number;
1499
+ }
1500
+ /** Stores used while receiving a blob. */
1501
+ interface ReceiveBlobStores {
1502
+ /**
1503
+ * Where fetched chunks are staged, keyed by chunk hash. A persistent chunk
1504
+ * store makes a transfer resumable: an interrupted transfer leaves its chunks
1505
+ * here, and a resumed transfer skips them.
1506
+ */
1507
+ chunkStore: ContentAddressedBlobStore;
1508
+ /** Where the fully reassembled, verified blob is written. */
1509
+ blobStore: ContentAddressedBlobStore;
1510
+ }
1511
+ /**
1512
+ * Receive a blob out of band, chunk by chunk, resumably and with integrity
1513
+ * verification at every step.
1514
+ *
1515
+ * For each chunk named in the manifest: if the chunk store already holds it (a
1516
+ * prior, possibly interrupted transfer), it is skipped; otherwise it is fetched
1517
+ * from the provider and verified to hash to the expected chunk hash before being
1518
+ * staged. Once every chunk is present, the blob is reassembled, verified against
1519
+ * the manifest's blob hash, and written to the blob store (which deduplicates).
1520
+ *
1521
+ * The transfer is idempotent: running it again after completion fetches nothing
1522
+ * and returns the same reference.
1523
+ *
1524
+ * @param manifest - The manifest of the blob to receive
1525
+ * @param provider - The source of chunk bytes
1526
+ * @param stores - The chunk staging store and the destination blob store
1527
+ * @returns The reference plus how many chunks were fetched versus skipped
1528
+ * @throws {BlobIntegrityError} If a fetched chunk or the whole blob fails verification
1529
+ * @throws {Error} If the provider cannot supply a required chunk
1530
+ */
1531
+ declare function receiveBlob(manifest: BlobManifest, provider: ChunkProvider, stores: ReceiveBlobStores): Promise<ReceiveBlobResult>;
1532
+ /**
1533
+ * Prepare a blob for out-of-band sending: split it into chunks, stage those
1534
+ * chunks in a content-addressed store, and return the manifest plus a
1535
+ * {@link ChunkProvider} that serves the chunks from that store.
1536
+ *
1537
+ * @param bytes - The full blob content
1538
+ * @param chunkStore - Where to stage the chunks (served to receivers)
1539
+ * @param options - Optional chunk size and metadata
1540
+ * @returns The manifest and a provider that serves the staged chunks
1541
+ */
1542
+ declare function prepareBlobForSend(bytes: Uint8Array, chunkStore: ContentAddressedBlobStore, options?: {
1543
+ chunkSize?: number;
1544
+ mimeType?: string;
1545
+ filename?: string;
1546
+ }): Promise<{
1547
+ manifest: BlobManifest;
1548
+ provider: ChunkProvider;
1549
+ }>;
1550
+
1551
+ /**
1552
+ * Serialize a {@link BlobManifest} to canonical bytes for content addressing.
1553
+ *
1554
+ * Fields are emitted in a fixed order so the same manifest always produces the
1555
+ * same bytes, which is what lets the manifest be stored and fetched under its own
1556
+ * SHA-256 hash. Optional fields are omitted when absent.
1557
+ */
1558
+ declare function serializeBlobManifest(manifest: BlobManifest): Uint8Array;
1559
+ /**
1560
+ * Parse canonical manifest bytes back into a {@link BlobManifest}, validating the
1561
+ * shape. Throws on malformed input rather than returning a partial manifest.
1562
+ */
1563
+ declare function parseBlobManifest(bytes: Uint8Array): BlobManifest;
1564
+ /**
1565
+ * Store a blob for out-of-band transfer, addressed entirely by content.
1566
+ *
1567
+ * Stages the blob's chunks (so this device can serve them), stores the full blob
1568
+ * (so local reads by hash are O(1)), and stores the blob's manifest as its own
1569
+ * content-addressed object. The returned {@link BlobRef} carries `manifestHash`,
1570
+ * so a peer that receives the reference in a synced record can pull the bytes
1571
+ * knowing only the reference — no separate manifest hand-off.
1572
+ *
1573
+ * @param store - The content-addressed store to stage chunks, blob, and manifest in
1574
+ * @param bytes - The full blob content
1575
+ * @param options - Optional chunk size and metadata
1576
+ * @returns The reference (including `manifestHash`) and the manifest
1577
+ */
1578
+ declare function putBlobForTransfer(store: ContentAddressedBlobStore, bytes: Uint8Array, options?: {
1579
+ chunkSize?: number;
1580
+ } & BlobRefMetadata): Promise<{
1581
+ ref: BlobRef;
1582
+ manifest: BlobManifest;
1583
+ }>;
1584
+ /**
1585
+ * Fetch and integrity-verify a blob's manifest from a {@link ChunkProvider}.
1586
+ *
1587
+ * The manifest is a content-addressed object like a chunk, so it is requested by
1588
+ * its hash, verified to hash to that value, and parsed. Used to resolve a
1589
+ * {@link BlobRef}'s manifest before pulling the blob's chunks.
1590
+ *
1591
+ * @param provider - The source to fetch the manifest bytes from
1592
+ * @param manifestHash - The manifest's content hash (from `BlobRef.manifestHash`)
1593
+ * @throws {BlobIntegrityError} If the fetched bytes do not hash to `manifestHash`
1594
+ * @throws {Error} If the provider cannot supply the manifest
1595
+ */
1596
+ declare function fetchBlobManifest(provider: ChunkProvider, manifestHash: string): Promise<BlobManifest>;
1597
+ /**
1598
+ * Resolve the manifest for a {@link BlobRef} that was stored via
1599
+ * {@link putBlobForTransfer}. Fetches and verifies the manifest by the ref's
1600
+ * `manifestHash`.
1601
+ *
1602
+ * @throws {Error} If the ref has no `manifestHash` (it was not stored for transfer)
1603
+ */
1604
+ declare function resolveBlobManifest(provider: ChunkProvider, ref: BlobRef): Promise<BlobManifest>;
1605
+
1606
+ /**
1607
+ * The server-side blob callbacks a `KoraSyncServer` accepts: read a chunk by
1608
+ * hash (to serve it) and persist an uploaded chunk by hash (to store it).
1609
+ */
1610
+ interface ServerBlobCallbacks {
1611
+ resolveBlobChunk: (hash: string) => Promise<Uint8Array | null>;
1612
+ persistBlobChunk: (hash: string, bytes: Uint8Array) => Promise<void>;
1613
+ }
1614
+ /**
1615
+ * Adapt a {@link ContentAddressedBlobStore} into the read/persist callbacks a
1616
+ * `KoraSyncServer` needs for central blob storage. The server package depends
1617
+ * only on these plain function signatures, so a self-hosted server can back its
1618
+ * blob storage with any store (for example a `FilesystemBlobStore`) without the
1619
+ * server package taking a dependency on `@korajs/store`.
1620
+ *
1621
+ * @example
1622
+ * ```typescript
1623
+ * const server = new KoraSyncServer({
1624
+ * store,
1625
+ * ...toServerBlobCallbacks(new FilesystemBlobStore('/var/kora/blobs')),
1626
+ * })
1627
+ * ```
1628
+ */
1629
+ declare function toServerBlobCallbacks(store: ContentAddressedBlobStore): ServerBlobCallbacks;
1630
+ /**
1631
+ * Convenience: an in-memory central blob store plus its server callbacks. For
1632
+ * tests and single-process deployments. Persistent deployments should back
1633
+ * {@link toServerBlobCallbacks} with a durable store instead.
1634
+ */
1635
+ declare function createMemoryServerBlobStore(): {
1636
+ store: MemoryBlobStore;
1637
+ callbacks: ServerBlobCallbacks;
1638
+ };
1639
+
1640
+ /** Outcome of a blob garbage-collection sweep. */
1641
+ interface BlobGcResult {
1642
+ /** Total distinct blobs held before the sweep. */
1643
+ scanned: number;
1644
+ /** Distinct hashes kept because a live reference points to them (blob, manifest, or chunk). */
1645
+ live: number;
1646
+ /** Number of blobs deleted (or, with `dryRun`, that would be deleted). */
1647
+ collected: number;
1648
+ /** The hashes deleted (or, with `dryRun`, that would be deleted). */
1649
+ collectedHashes: string[];
1650
+ }
1651
+ /** Options for {@link collectBlobGarbage}. */
1652
+ interface BlobGcOptions {
1653
+ /** Compute what would be collected without deleting anything. */
1654
+ dryRun?: boolean;
1655
+ }
1656
+ /**
1657
+ * Extract the {@link BlobRef}s referenced by a materialized record. Scans the
1658
+ * record's top-level field values, so it works without the schema; a `blob`
1659
+ * field simply holds a `BlobRef`.
1660
+ */
1661
+ declare function extractBlobRefs(record: Record<string, unknown>): BlobRef[];
1662
+ /**
1663
+ * Garbage-collect a content-addressed blob store by mark-and-sweep: keep every
1664
+ * blob reachable from a live reference, delete the rest.
1665
+ *
1666
+ * The live set is closed over the reference graph — for each live {@link BlobRef}
1667
+ * it retains the blob hash, the manifest hash, and every chunk hash named in that
1668
+ * manifest. Because chunks (and blobs) are shared by content address, a chunk
1669
+ * referenced by any surviving blob is retained even if another blob that used it
1670
+ * was collected. This makes GC safe under deduplication.
1671
+ *
1672
+ * Mark-and-sweep (rather than reference counting) is deliberate: counts are
1673
+ * fragile under concurrent edits and CRDT merges, whereas a sweep against the
1674
+ * current live set is always correct for the state it observes. Run it
1675
+ * periodically or on demand.
1676
+ *
1677
+ * A manifest that cannot be read or parsed (corruption) contributes no chunk
1678
+ * hashes; its own hash still counts as live. The blob it describes is already
1679
+ * unusable, so its chunks becoming collectable is acceptable, and GC never
1680
+ * throws on a single bad manifest.
1681
+ *
1682
+ * @param store - The blob store to sweep
1683
+ * @param liveRefs - Every reference still reachable from live records
1684
+ * @param options - `dryRun` to report without deleting
1685
+ */
1686
+ declare function collectBlobGarbage(store: ContentAddressedBlobStore, liveRefs: Iterable<BlobRef>, options?: BlobGcOptions): Promise<BlobGcResult>;
1687
+
1688
+ /**
1689
+ * Blob chunk exchange messages. These ride any message transport (the sync
1690
+ * WebSocket in production, an in-memory pair in tests). A receiver requests a
1691
+ * chunk by hash and correlates the answer by `requestId`.
1692
+ */
1693
+ interface ChunkRequestMessage {
1694
+ type: 'blob-chunk-request';
1695
+ requestId: string;
1696
+ hash: string;
1697
+ }
1698
+ interface ChunkResponseMessage {
1699
+ type: 'blob-chunk-response';
1700
+ requestId: string;
1701
+ /** The chunk bytes, or null when the responder does not hold that hash. */
1702
+ bytes: Uint8Array | null;
1703
+ }
1704
+ type ChunkMessage = ChunkRequestMessage | ChunkResponseMessage;
1705
+ /**
1706
+ * A bidirectional message port carrying {@link ChunkMessage}s. Implemented over
1707
+ * the sync connection in production; `createChunkPortPair` provides an in-memory
1708
+ * duplex pair for tests.
1709
+ */
1710
+ interface ChunkMessagePort {
1711
+ send(message: ChunkMessage): void;
1712
+ /** Register a handler for incoming messages. Multiple handlers are allowed. */
1713
+ onMessage(handler: (message: ChunkMessage) => void): void;
1714
+ }
1715
+ /** A {@link ChunkProvider} that fetches chunks over a message port. */
1716
+ interface RemoteChunkProvider extends ChunkProvider {
1717
+ /** Number of requests still awaiting a response (for diagnostics/tests). */
1718
+ pendingCount(): number;
1719
+ }
1720
+ /**
1721
+ * Create a {@link ChunkProvider} that requests chunks over a message port,
1722
+ * correlating each answer to its request and timing out a stalled request so a
1723
+ * dropped response cannot hang a transfer forever (the transfer is resumable, so
1724
+ * a timed-out request can simply be retried).
1725
+ *
1726
+ * @param port - The message port to the peer that holds the blob
1727
+ * @param options.timeoutMs - Per-request timeout (default 30s)
1728
+ */
1729
+ declare function createRemoteChunkProvider(port: ChunkMessagePort, options?: {
1730
+ timeoutMs?: number;
1731
+ }): RemoteChunkProvider;
1732
+ /**
1733
+ * Serve blob chunk requests arriving on a message port from a blob store. Answers
1734
+ * each `blob-chunk-request` with the bytes for that hash, or null when the store
1735
+ * does not hold it. Returns nothing; attach it once per connection.
1736
+ *
1737
+ * @param port - The message port from the peer requesting chunks
1738
+ * @param blobStore - The store to serve chunks from (keyed by chunk hash)
1739
+ */
1740
+ declare function serveBlobChunks(port: ChunkMessagePort, blobStore: ContentAddressedBlobStore): void;
1741
+ /**
1742
+ * Create a connected in-memory pair of {@link ChunkMessagePort}s. Messages sent
1743
+ * on one arrive on the other, asynchronously (next microtask) so the pair models
1744
+ * a real transport rather than re-entrant synchronous delivery. For tests.
1745
+ */
1746
+ declare function createChunkPortPair(): {
1747
+ a: ChunkMessagePort;
1748
+ b: ChunkMessagePort;
1749
+ };
1750
+
1751
+ 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, 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 };