@minnowdb/core 0.1.1 → 0.2.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 (119) hide show
  1. package/README.md +12 -9
  2. package/dist/engine/client.d.ts +10 -1
  3. package/dist/engine/client.d.ts.map +1 -1
  4. package/dist/engine/client.js +30 -5
  5. package/dist/engine/client.js.map +1 -1
  6. package/dist/engine/database.d.ts +55 -6
  7. package/dist/engine/database.d.ts.map +1 -1
  8. package/dist/engine/database.js +2689 -898
  9. package/dist/engine/database.js.map +1 -1
  10. package/dist/engine/live.d.ts +6 -2
  11. package/dist/engine/live.d.ts.map +1 -1
  12. package/dist/engine/live.js +35 -11
  13. package/dist/engine/live.js.map +1 -1
  14. package/dist/engine/query-cache.d.ts +13 -1
  15. package/dist/engine/query-cache.d.ts.map +1 -1
  16. package/dist/engine/query-cache.js +34 -5
  17. package/dist/engine/query-cache.js.map +1 -1
  18. package/dist/engine/query.d.ts +13 -1
  19. package/dist/engine/query.d.ts.map +1 -1
  20. package/dist/engine/query.js +73 -46
  21. package/dist/engine/query.js.map +1 -1
  22. package/dist/engine/result-wire.d.ts +68 -0
  23. package/dist/engine/result-wire.d.ts.map +1 -0
  24. package/dist/engine/result-wire.js +264 -0
  25. package/dist/engine/result-wire.js.map +1 -0
  26. package/dist/engine/sort-keys.d.ts +50 -12
  27. package/dist/engine/sort-keys.d.ts.map +1 -1
  28. package/dist/engine/sort-keys.js +371 -25
  29. package/dist/engine/sort-keys.js.map +1 -1
  30. package/dist/engine/vector.d.ts +11 -0
  31. package/dist/engine/vector.d.ts.map +1 -1
  32. package/dist/engine/vector.js +99 -203
  33. package/dist/engine/vector.js.map +1 -1
  34. package/dist/engine/worker-host.d.ts +17 -1
  35. package/dist/engine/worker-host.d.ts.map +1 -1
  36. package/dist/engine/worker-host.js +87 -13
  37. package/dist/engine/worker-host.js.map +1 -1
  38. package/dist/storage/index.d.ts +13 -0
  39. package/dist/storage/index.d.ts.map +1 -1
  40. package/dist/storage/index.js +13 -0
  41. package/dist/storage/index.js.map +1 -1
  42. package/dist/storage/indexeddb.d.ts +5 -1
  43. package/dist/storage/indexeddb.d.ts.map +1 -1
  44. package/dist/storage/indexeddb.js +484 -402
  45. package/dist/storage/indexeddb.js.map +1 -1
  46. package/dist/storage/memory.d.ts +24 -21
  47. package/dist/storage/memory.d.ts.map +1 -1
  48. package/dist/storage/memory.js +124 -1199
  49. package/dist/storage/memory.js.map +1 -1
  50. package/dist/storage/opfs/files.d.ts +64 -0
  51. package/dist/storage/opfs/files.d.ts.map +1 -0
  52. package/dist/storage/opfs/files.js +243 -0
  53. package/dist/storage/opfs/files.js.map +1 -0
  54. package/dist/storage/opfs/index.d.ts +3 -0
  55. package/dist/storage/opfs/index.d.ts.map +1 -0
  56. package/dist/storage/opfs/index.js +3 -0
  57. package/dist/storage/opfs/index.js.map +1 -0
  58. package/dist/storage/opfs/leader.d.ts +130 -0
  59. package/dist/storage/opfs/leader.d.ts.map +1 -0
  60. package/dist/storage/opfs/leader.js +1104 -0
  61. package/dist/storage/opfs/leader.js.map +1 -0
  62. package/dist/storage/opfs/rpc.d.ts +70 -0
  63. package/dist/storage/opfs/rpc.d.ts.map +1 -0
  64. package/dist/storage/opfs/rpc.js +58 -0
  65. package/dist/storage/opfs/rpc.js.map +1 -0
  66. package/dist/storage/opfs/store.d.ts +154 -0
  67. package/dist/storage/opfs/store.d.ts.map +1 -0
  68. package/dist/storage/opfs/store.js +958 -0
  69. package/dist/storage/opfs/store.js.map +1 -0
  70. package/dist/storage/toolkit/extents.d.ts +69 -0
  71. package/dist/storage/toolkit/extents.d.ts.map +1 -0
  72. package/dist/storage/toolkit/extents.js +175 -0
  73. package/dist/storage/toolkit/extents.js.map +1 -0
  74. package/dist/storage/toolkit/index.d.ts +30 -0
  75. package/dist/storage/toolkit/index.d.ts.map +1 -0
  76. package/dist/storage/toolkit/index.js +5 -0
  77. package/dist/storage/toolkit/index.js.map +1 -0
  78. package/dist/storage/toolkit/record-core.d.ts +252 -0
  79. package/dist/storage/toolkit/record-core.d.ts.map +1 -0
  80. package/dist/storage/toolkit/record-core.js +1670 -0
  81. package/dist/storage/toolkit/record-core.js.map +1 -0
  82. package/dist/storage/toolkit/sync-file.d.ts +29 -0
  83. package/dist/storage/toolkit/sync-file.d.ts.map +1 -0
  84. package/dist/storage/toolkit/sync-file.js +2 -0
  85. package/dist/storage/toolkit/sync-file.js.map +1 -0
  86. package/dist/storage/toolkit/wal.d.ts +22 -0
  87. package/dist/storage/toolkit/wal.d.ts.map +1 -0
  88. package/dist/storage/toolkit/wal.js +79 -0
  89. package/dist/storage/toolkit/wal.js.map +1 -0
  90. package/dist/storage/toolkit/wire.d.ts +25 -0
  91. package/dist/storage/toolkit/wire.d.ts.map +1 -0
  92. package/dist/storage/toolkit/wire.js +100 -0
  93. package/dist/storage/toolkit/wire.js.map +1 -0
  94. package/dist/storage/types.d.ts +343 -59
  95. package/dist/storage/types.d.ts.map +1 -1
  96. package/dist/storage/types.js +112 -11
  97. package/dist/storage/types.js.map +1 -1
  98. package/dist/testing/block-store-conformance.d.ts +51 -0
  99. package/dist/testing/block-store-conformance.d.ts.map +1 -0
  100. package/dist/testing/block-store-conformance.js +745 -0
  101. package/dist/testing/block-store-conformance.js.map +1 -0
  102. package/dist/testing/index.d.ts +3 -0
  103. package/dist/testing/index.d.ts.map +1 -1
  104. package/dist/testing/index.js +5 -0
  105. package/dist/testing/index.js.map +1 -1
  106. package/dist/testing/opfs-shim.d.ts +35 -0
  107. package/dist/testing/opfs-shim.d.ts.map +1 -0
  108. package/dist/testing/opfs-shim.js +295 -0
  109. package/dist/testing/opfs-shim.js.map +1 -0
  110. package/dist/transactions/index.d.ts +42 -1
  111. package/dist/transactions/index.d.ts.map +1 -1
  112. package/dist/transactions/index.js +219 -13
  113. package/dist/transactions/index.js.map +1 -1
  114. package/dist/worker-protocol/index.d.ts +2 -2
  115. package/dist/worker-protocol/index.d.ts.map +1 -1
  116. package/dist/worker-protocol/index.js +2 -1
  117. package/dist/worker-protocol/index.js.map +1 -1
  118. package/package.json +10 -2
  119. package/sql-feature-matrix.json +8 -0
@@ -1,3 +1,4 @@
1
+ import type { DatabaseSnapshot, SnapshotLoadProgress } from "./snapshot.js";
1
2
  export declare const storeNames: readonly ["catalog", "manifests", "segments", "blocks", "transactions", "leases", "statistics", "temp", "gc"];
2
3
  /** The manifest fields every commit publishes; `Manifest` adds the resolved block list. */
3
4
  export interface ManifestSummary {
@@ -21,8 +22,8 @@ export interface Manifest extends ManifestSummary {
21
22
  * carries only this commit's added and removed ids plus its distance from the checkpoint below
22
23
  * it. Reads resolve a version by walking back to the nearest checkpoint and applying deltas
23
24
  * forward, so publishing a commit writes O(changed blocks) instead of rewriting every live
24
- * block id. Records are never physically deleted (pruning tombstones them via `prunedAt`), so a
25
- * chain below any readable version always resolves.
25
+ * block id. Pruning first tombstones records; maintenance later deletes only the obsolete prefix
26
+ * below the checkpoint that the earliest readable version needs.
26
27
  */
27
28
  export interface StoredManifestRecord extends ManifestSummary {
28
29
  blockIds?: string[];
@@ -123,7 +124,6 @@ export interface TableRecord {
123
124
  columns: TableColumnRecord[];
124
125
  uniqueKeyColumnId?: string;
125
126
  uniqueKeyLookupReady?: boolean;
126
- uniqueKeyStorage?: "chunks-v1" | "chunks-v2";
127
127
  /** Full-text index state per column ID. Writers that see this emit commit deltas. */
128
128
  ftsColumns?: Record<string, FtsColumnIndexRecord>;
129
129
  /** AFTER triggers on this table, fired by the committing writer inside its transaction. */
@@ -299,6 +299,8 @@ export interface RechunkCompactionRewritePlan {
299
299
  readonly columns: readonly RechunkCompactionSourceColumn[];
300
300
  /** Shared row windows, emitted in output-window-major then column order. */
301
301
  readonly outputs: readonly RechunkCompactionOutputWindow[];
302
+ /** Optional level-one publication layout; partitions tile the output without splitting windows. */
303
+ readonly partitions?: readonly MergeOutputPartition[];
302
304
  }
303
305
  export interface MergeCompactionSourceBlock {
304
306
  readonly blockId: string;
@@ -341,6 +343,17 @@ export interface MergeCompactionOutputColumn {
341
343
  readonly type: SimpleDataType;
342
344
  readonly sourceRanges: readonly MergeCompactionOutputSourceRange[];
343
345
  }
346
+ /**
347
+ * One output segment of a partitioned physical rewrite: a contiguous run of the canonical
348
+ * output, published as its own level-one partition under its own logical order.
349
+ */
350
+ export interface MergeOutputPartition {
351
+ /** Row offset within the canonical merged output. */
352
+ readonly rowStart: number;
353
+ readonly rowCount: number;
354
+ /** The finite, non-negative published order; strictly increasing across partitions. */
355
+ readonly logicalOrder: number;
356
+ }
344
357
  /** An immutable logical replay result followed by a physical, output-driven rewrite. */
345
358
  export interface MergeCompactionRewritePlan {
346
359
  readonly kind: "merge-v1";
@@ -356,6 +369,13 @@ export interface MergeCompactionRewritePlan {
356
369
  readonly sourceSegments: readonly MergeCompactionSourceSegment[];
357
370
  readonly columns: readonly MergeCompactionOutputColumn[];
358
371
  readonly outputs: readonly RechunkCompactionOutputWindow[];
372
+ /**
373
+ * How the output is split into published segments. Missing on plans that publish the whole
374
+ * output as one segment under `logicalOrder`. When present, the partitions tile the output
375
+ * contiguously, every output window lies inside one partition, and partition `i` publishes
376
+ * as the job's output segment ID for `i === 0` and `${outputSegmentId}/${i}` after that.
377
+ */
378
+ readonly partitions?: readonly MergeOutputPartition[];
359
379
  }
360
380
  export type CompactionRewritePlan = CopyCompactionRewritePlan | RechunkCompactionRewritePlan | MergeCompactionRewritePlan;
361
381
  /**
@@ -443,16 +463,29 @@ export declare class CompactionJobConflictError extends Error {
443
463
  }
444
464
  export declare const garbageCollectionJobStates: readonly ["planned", "running", "completed"];
445
465
  export type GarbageCollectionJobState = (typeof garbageCollectionJobStates)[number];
466
+ /**
467
+ * How far a job has examined each of its candidate lists, in the order a step works through
468
+ * them: manifests, then segments, then blocks, then transactions. Transactions come last so a
469
+ * segment the same job reclaims has already released the transaction that wrote it.
470
+ */
446
471
  export interface GarbageCollectionCursor {
447
472
  manifestIndex: number;
448
473
  segmentIndex: number;
449
474
  blockIndex: number;
475
+ transactionIndex: number;
450
476
  }
451
477
  export interface CreateGarbageCollectionJobInput {
452
478
  id: string;
453
479
  candidateManifestVersions: readonly number[];
454
480
  candidateSegmentIds: readonly string[];
455
481
  candidateBlockIds: readonly string[];
482
+ /**
483
+ * Transaction records the planner believes nothing needs any more: committed records below
484
+ * the retained window with no segment owner, or aborted records after their pending artifacts
485
+ * are gone. Optional, so a caller that only reclaims artifacts need not mention them. The
486
+ * store decides for itself at step time.
487
+ */
488
+ candidateTransactionIds?: readonly string[];
456
489
  /** Fixed cutoff used to decide which persisted leases protect a manifest for this job. */
457
490
  leaseCutoff: string;
458
491
  createdAt: string;
@@ -462,6 +495,7 @@ export interface GarbageCollectionJobRecord {
462
495
  candidateManifestVersions: number[];
463
496
  candidateSegmentIds: string[];
464
497
  candidateBlockIds: string[];
498
+ candidateTransactionIds: string[];
465
499
  cursor: GarbageCollectionCursor;
466
500
  prunedManifestCount: number;
467
501
  alreadyPrunedManifestCount: number;
@@ -474,6 +508,9 @@ export interface GarbageCollectionJobRecord {
474
508
  retainedBlockCount: number;
475
509
  missingBlockCount: number;
476
510
  reclaimedBlockBytes: number;
511
+ reclaimedTransactionCount: number;
512
+ retainedTransactionCount: number;
513
+ missingTransactionCount: number;
477
514
  state: GarbageCollectionJobState;
478
515
  revision: number;
479
516
  leaseCutoff: string;
@@ -499,6 +536,9 @@ export interface GarbageCollectionStepResult {
499
536
  retainedBlockIds: string[];
500
537
  missingBlockIds: string[];
501
538
  reclaimedBlockBytes: number;
539
+ reclaimedTransactionIds: string[];
540
+ retainedTransactionIds: string[];
541
+ missingTransactionIds: string[];
502
542
  }
503
543
  export interface StoragePage<T, Cursor> {
504
544
  records: T[];
@@ -531,6 +571,10 @@ export interface GarbageCollectionStepAccounting {
531
571
  retainedBlockCount: number;
532
572
  missingBlockCount: number;
533
573
  reclaimedBlockBytes: number;
574
+ examinedTransactionCount: number;
575
+ reclaimedTransactionCount: number;
576
+ retainedTransactionCount: number;
577
+ missingTransactionCount: number;
534
578
  updatedAt: string;
535
579
  }
536
580
  export declare class LeaseConflictError extends Error {
@@ -612,12 +656,34 @@ export interface CommitTransactionInput {
612
656
  ftsChanges?: readonly FtsChanges[];
613
657
  committedAt: string;
614
658
  }
659
+ /**
660
+ * The single-shot write: stage these blocks and segments and commit them, in one atomic storage
661
+ * transaction. Carries the same commit change as `CommitTransactionInput`; the artifacts become
662
+ * the transaction's journaled pending ids on the way through.
663
+ */
664
+ export interface WriteTransactionInput extends Omit<CommitTransactionInput, "transactionId" | "expectedTransactionRevision"> {
665
+ /**
666
+ * Which transaction publishes. `{ id, expectedRevision }` names an active transaction begun
667
+ * earlier (typically with `beginTransaction`, when the artifacts depend on a row-id or
668
+ * auto-increment reservation); its journal is extended and its revision compare-and-swapped
669
+ * (`TransactionRecordConflictError`). `{ record }` begins the transaction in the same step —
670
+ * a fresh record (revision 0, empty journal) the store pins at `expectedManifestVersion` —
671
+ * so a write that needed no reservation costs one round trip in total.
672
+ */
673
+ transaction: {
674
+ id: string;
675
+ expectedRevision: number;
676
+ } | {
677
+ record: Omit<TransactionRecord, "snapshotVersion">;
678
+ };
679
+ blocks: readonly BlockWrite[];
680
+ segments: readonly SegmentRecord[];
681
+ }
615
682
  export interface UniqueKeyChanges {
616
683
  tableId: string;
617
684
  keyTokens: readonly string[];
618
685
  requireAbsent: boolean;
619
686
  remove?: boolean;
620
- storageMode?: "chunks-v1" | "chunks-v2";
621
687
  }
622
688
  /** One term's postings within a commit delta or base chunk: parallel rowId/tf arrays. */
623
689
  export interface FtsPosting {
@@ -740,16 +806,47 @@ export interface CatalogProbe {
740
806
  manifestVersion: number | null;
741
807
  catalogEpoch: number;
742
808
  }
743
- export interface BlockStore {
809
+ /**
810
+ * Bulk payload storage: immutable, opaque byte blobs keyed by structured ids.
811
+ *
812
+ * Blocks are write-once. `addBlock`/`addBlocks` MUST reject an id that already exists, and a
813
+ * batch containing any duplicate MUST write nothing at all. Ids contain `/` separators
814
+ * (`table/<uuid>/segment/<uuid>/part/000001`) and sort lexically; treat them as opaque keys.
815
+ * Reads MUST return bytes the caller may mutate freely (a fresh copy or freshly deserialized
816
+ * buffer), and writes MUST NOT alias the caller's buffer — the engine may reuse it.
817
+ *
818
+ * Published blocks are retired by superseding them in a commit, never by `removeBlock`; the
819
+ * lease-aware collector deletes the bytes once no reader can be pinned to them.
820
+ */
821
+ export interface BlockPayloadStore {
744
822
  addBlock(id: string, bytes: Uint8Array): Promise<void>;
823
+ /** All or nothing: an internal or existing duplicate id fails the whole batch unwritten. */
745
824
  addBlocks(blocks: readonly BlockWrite[]): Promise<void>;
746
825
  getBlock(id: string): Promise<Uint8Array | undefined>;
826
+ /** Positional per requested id; undefined where a block does not exist. */
747
827
  getBlocks(ids: readonly string[]): Promise<Array<Uint8Array | undefined>>;
828
+ /** Deleting a missing block is not an error. */
748
829
  removeBlock(id: string): Promise<void>;
830
+ /** Every stored block id, sorted lexically. A cold path — tools and tests, not queries. */
749
831
  listBlockIds(): Promise<string[]>;
832
+ }
833
+ /**
834
+ * The table catalog: schema records, the counters that keep writes collision-free, and
835
+ * unique-key membership.
836
+ *
837
+ * Table mutations are compare-and-swap on the record's `revision` and MUST fail with
838
+ * `TableRecordConflictError` — the exact exported class — on a mismatch. Catalog mutations
839
+ * advance the catalog epoch (see `CatalogProbe`). Counter reservations (`reserveRowIds`,
840
+ * `reserveAutoIncrement`) MUST be atomic and durable: two racing callers may never receive
841
+ * overlapping ranges, across connections and across crashes. Reservations are never returned;
842
+ * aborted transactions leave gaps.
843
+ */
844
+ export interface CatalogStore {
845
+ /** Fails on a duplicate id or name. Advances the catalog epoch. */
750
846
  addTable(record: TableRecord): Promise<void>;
751
847
  getTable(id: string): Promise<TableRecord | undefined>;
752
848
  getTableByName(name: string): Promise<TableRecord | undefined>;
849
+ /** Sorted by table name. */
753
850
  listTables(): Promise<TableRecord[]>;
754
851
  updateTable(id: string, expectedRevision: number, update: {
755
852
  columns?: TableColumnRecord[];
@@ -770,6 +867,132 @@ export interface BlockStore {
770
867
  * pinned reader may still be reading.
771
868
  */
772
869
  removeTable(id: string, expectedRevision: number): Promise<void>;
870
+ reserveRowIds(tableId: string, count: number): Promise<RowIdRange>;
871
+ /**
872
+ * Atomically reserves `count` auto-increment values for the column, first bumping the
873
+ * counter to at least `atLeast`. `count` may be 0 for a pure bump past explicit values.
874
+ */
875
+ reserveAutoIncrement(tableId: string, columnId: string, count: number, atLeast?: bigint): Promise<RowIdRange>;
876
+ /** Which of the given key tokens already exist for the table, deduplicated and sorted. */
877
+ getExistingUniqueKeys(tableId: string, keyTokens: readonly string[]): Promise<string[]>;
878
+ }
879
+ /**
880
+ * Versions and visibility: manifests (the set of live block ids at each version), segments
881
+ * (which blocks belong to which table and rows), and the transaction records that stage and
882
+ * publish them.
883
+ *
884
+ * This is where the whole consistency story lives. `commitTransaction` is THE atomic step of
885
+ * the database: in one durable, all-or-nothing action it validates the transaction record's
886
+ * revision and active status, compare-and-swaps the current manifest version, publishes the
887
+ * next manifest, finalizes the transaction's segments, applies unique-key changes (failing
888
+ * with `UniqueKeyConflictError` on a `requireAbsent` violation), applies full-text deltas,
889
+ * and flips the transaction record to committed. No intermediate state may ever be
890
+ * observable, including after a crash at any moment. Version conflicts MUST be
891
+ * `WriteConflictError` and revision conflicts `TransactionRecordConflictError` — the exact
892
+ * exported classes; the engine's retry and rebase loops match on them, and the worker client
893
+ * rehydrates them by name across the thread boundary.
894
+ */
895
+ export interface TransactionStore {
896
+ getCurrentManifest(): Promise<Manifest | undefined>;
897
+ /** The current version alone, without materializing the manifest's block list. */
898
+ getCurrentManifestVersion(): Promise<number | null>;
899
+ getManifest(version: number): Promise<Manifest | undefined>;
900
+ listManifests(): Promise<Manifest[]>;
901
+ listManifestPage(afterVersion: number | null, limit: number): Promise<StoragePage<Manifest, number>>;
902
+ /**
903
+ * Publishes the next version directly from a full block-id list, compare-and-swapping on
904
+ * `expectedVersion` (`WriteConflictError` on a mismatch). Every id must exist. The engine
905
+ * commits through transactions instead; this is the lower-level tool underneath.
906
+ */
907
+ publishManifest(input: PublishManifestInput): Promise<Manifest>;
908
+ /** Fails on a duplicate id; the record's snapshot version and pending ids must be valid. */
909
+ createTransaction(record: TransactionRecord): Promise<void>;
910
+ getTransaction(id: string): Promise<TransactionRecord | undefined>;
911
+ /** Positional per requested id; undefined where a record does not exist. */
912
+ getTransactions(ids: readonly string[]): Promise<Array<TransactionRecord | undefined>>;
913
+ /** Sorted by startedAt, then id. */
914
+ listTransactions(): Promise<TransactionRecord[]>;
915
+ listTransactionPage(afterId: string | null, limit: number): Promise<StoragePage<TransactionRecord, string>>;
916
+ /**
917
+ * Compare-and-swap on `expectedRevision` (`TransactionRecordConflictError` on a mismatch).
918
+ * Only active transactions may be updated, and only `commitTransaction` may mark one
919
+ * committed.
920
+ */
921
+ updateTransaction(id: string, expectedRevision: number, update: TransactionRecordUpdate): Promise<TransactionRecord>;
922
+ /** Publishes the next version; the summary omits the block list, which commits never need. */
923
+ commitTransaction(input: CommitTransactionInput): Promise<ManifestSummary>;
924
+ addSegment(record: SegmentRecord): Promise<void>;
925
+ getSegment(id: string): Promise<SegmentRecord | undefined>;
926
+ /** Sorted by id; `tableId` filters. */
927
+ listSegments(tableId?: string): Promise<SegmentRecord[]>;
928
+ removeSegment(id: string): Promise<void>;
929
+ }
930
+ /**
931
+ * Reader pins. A lease is a stored record with an expiry that protects one manifest version
932
+ * (and every block it references) from garbage collection while a reader may still be using
933
+ * it. Renewals are compare-and-swap on `revision` and fail with `LeaseConflictError`; an
934
+ * expired lease simply stops protecting — there is no callback, which is what makes dead
935
+ * tabs safe.
936
+ */
937
+ export interface LeaseStore {
938
+ /** Fails on a duplicate id, or when the pinned version's manifest is unavailable. */
939
+ createLease(record: LeaseRecord): Promise<void>;
940
+ getLease(id: string): Promise<LeaseRecord | undefined>;
941
+ /** Sorted by id. */
942
+ listLeases(): Promise<LeaseRecord[]>;
943
+ renewLease(id: string, expectedRevision: number, expiresAt: string): Promise<LeaseRecord>;
944
+ /** True when removed; false (without removing) when the lease has not yet expired. */
945
+ removeLeaseIfExpired(id: string, expectedRevision: number, expiresAtCutoff: string): Promise<boolean>;
946
+ removeLease(id: string): Promise<void>;
947
+ }
948
+ /**
949
+ * Background-maintenance bookkeeping: the resumable job records that let compaction and
950
+ * garbage collection survive a tab being closed, throttled, or killed mid-step. Job updates
951
+ * are compare-and-swap on `revision` and fail with `CompactionJobConflictError` /
952
+ * `GarbageCollectionJobConflictError`.
953
+ *
954
+ * `runGarbageCollectionStep` does real deletion and MUST be atomic: it re-verifies lease and
955
+ * transaction pins inside the same storage transaction that prunes manifests and deletes
956
+ * segments and blocks, advancing the job's cursors so an interrupted collection resumes
957
+ * rather than restarts. Reclaimed manifests are first tombstoned (`prunedAt`);
958
+ * `removePrunedManifestRecords` may then delete only the old prefix no readable delta chain
959
+ * needs.
960
+ */
961
+ export interface MaintenanceStore {
962
+ createCompactionJob(record: CompactionJobRecord): Promise<void>;
963
+ getCompactionJob(id: string): Promise<CompactionJobRecord | undefined>;
964
+ /** Sorted by createdAt, then id; `tableId` filters. */
965
+ listCompactionJobs(tableId?: string): Promise<CompactionJobRecord[]>;
966
+ listCompactionJobPage(afterId: string | null, limit: number): Promise<StoragePage<CompactionJobRecord, string>>;
967
+ updateCompactionJob(id: string, expectedRevision: number, update: CompactionJobRecordUpdate): Promise<CompactionJobRecord>;
968
+ /**
969
+ * Resolves a job that may be racing its own publication: already-terminal jobs return
970
+ * unchanged, a job whose transaction committed is marked published, anything else is
971
+ * cancelled and its active transaction aborted — atomically.
972
+ */
973
+ cancelCompactionJob(id: string, expectedRevision: number, cancelledAt: string): Promise<CompactionJobRecord>;
974
+ removeCompactionJob(id: string): Promise<void>;
975
+ /** Validates candidate provenance against persisted records before accepting the job. */
976
+ createGarbageCollectionJob(input: CreateGarbageCollectionJobInput): Promise<GarbageCollectionJobRecord>;
977
+ getGarbageCollectionJob(id: string): Promise<GarbageCollectionJobRecord | undefined>;
978
+ /** Sorted by createdAt, then id. */
979
+ listGarbageCollectionJobs(): Promise<GarbageCollectionJobRecord[]>;
980
+ runGarbageCollectionStep(input: RunGarbageCollectionStepInput): Promise<GarbageCollectionStepResult>;
981
+ /**
982
+ * Deletes obsolete tombstones only after their garbage blocks are gone, while retaining the
983
+ * checkpoint prefix readable deltas need. A tombstone is the collector's durable discovery
984
+ * record between bounded passes.
985
+ */
986
+ removePrunedManifestRecords(): Promise<number>;
987
+ removeGarbageCollectionJob(id: string): Promise<void>;
988
+ }
989
+ /**
990
+ * Full-text index persistence: per-column base chunks plus the per-commit deltas that
991
+ * `commitTransaction` applies. The index is a pruning accelerator the scan re-verifies, so
992
+ * losing a base costs a rebuild, never a wrong answer — which is why snapshots may restore
993
+ * indexed columns as `invalid`.
994
+ */
995
+ export interface FtsIndexStore {
773
996
  /**
774
997
  * Replaces one column's full-text base chunks (term-range partitioned, term-sorted within
775
998
  * each chunk) and deletes commit deltas the new base covers. The caller flips the catalog
@@ -784,8 +1007,8 @@ export interface BlockStore {
784
1007
  /**
785
1008
  * Per-term candidate row IDs from the base chunks plus every commit delta at or below
786
1009
  * `upToVersion`, with the column's merged token total for exact BM25 statistics. Prefix
787
- * terms match the term range [term, term + "￿"). Reports the merged delta-chunk count so
788
- * callers can schedule a rebuild when the tail grows, and the base's covered version —
1010
+ * terms match the term range [term, term + "\uffff"). Reports the merged delta-chunk count
1011
+ * so callers can schedule a rebuild when the tail grows, and the base's covered version —
789
1012
  * a concurrent rebuild can publish a base ahead of a reader's snapshot, and a caller
790
1013
  * needing snapshot-exact statistics must detect `coversVersion > upToVersion` and fall
791
1014
  * back (candidates stay a safe superset either way).
@@ -798,21 +1021,77 @@ export interface BlockStore {
798
1021
  totalTokens: number;
799
1022
  coversVersion: number;
800
1023
  }>;
801
- addSegment(record: SegmentRecord): Promise<void>;
802
- getSegment(id: string): Promise<SegmentRecord | undefined>;
803
- listSegments(tableId?: string): Promise<SegmentRecord[]>;
804
- removeSegment(id: string): Promise<void>;
805
- reserveRowIds(tableId: string, count: number): Promise<RowIdRange>;
1024
+ }
1025
+ /**
1026
+ * Query spill: scratch pages a bounded-memory query writes when it exceeds its budget, plus
1027
+ * the owner leases that let any connection reclaim a dead owner's pages. Pages carry no
1028
+ * durability requirement whatsoever — losing them costs a query, never data — but owner
1029
+ * records are real records with the usual compare-and-swap (`TempOwnerConflictError`).
1030
+ */
1031
+ export interface TempSpillStore {
1032
+ putTempRunPage(page: TempRunPage): Promise<void>;
806
1033
  /**
807
- * Atomically reserves `count` auto-increment values for the column, first bumping the
808
- * counter to at least `atLeast`. `count` may be 0 for a pure bump past explicit values.
809
- * Reservations are never returned; aborted transactions leave gaps.
1034
+ * Optional: writes a batch of pages in one storage round trip. Callers fall back to
1035
+ * per-page writes when absent; implement it where per-call overhead is real (the IndexedDB
1036
+ * adapter pays one transaction per page otherwise).
810
1037
  */
811
- reserveAutoIncrement(tableId: string, columnId: string, count: number, atLeast?: bigint): Promise<RowIdRange>;
812
- getExistingUniqueKeys(tableId: string, keyTokens: readonly string[]): Promise<string[]>;
813
- getCurrentManifest(): Promise<Manifest | undefined>;
814
- /** The current version alone, without materializing the manifest's block list. */
815
- getCurrentManifestVersion(): Promise<number | null>;
1038
+ putTempRunPages?(pages: readonly TempRunPage[]): Promise<void>;
1039
+ getTempRunPage(ownerId: string, runId: string, pageIndex: number): Promise<Uint8Array | undefined>;
1040
+ removeTempRun(ownerId: string, runId: string): Promise<void>;
1041
+ /** Removes the owner record and every page under the owner. */
1042
+ removeTempOwner(ownerId: string): Promise<void>;
1043
+ createTempOwner(record: TempOwnerRecord): Promise<void>;
1044
+ getTempOwner(ownerId: string): Promise<TempOwnerRecord | undefined>;
1045
+ renewTempOwner(ownerId: string, expectedRevision: number, expiresAt: string): Promise<TempOwnerRecord>;
1046
+ /** Sweeps pages too when it removes; owners found only via orphaned pages count as expired. */
1047
+ removeTempOwnerIfExpired(ownerId: string, expiresAtCutoff: string): Promise<boolean>;
1048
+ /** Owner ids from records and from orphaned pages alike, deduplicated, sorted, paged. */
1049
+ listTempOwnerIdsPage(afterOwnerId: string | null, limit: number): Promise<StoragePage<string, string>>;
1050
+ }
1051
+ /**
1052
+ * The complete storage contract: a database is `MinnowDatabase` plus one implementation of
1053
+ * this interface. The engine holds exactly one and talks to nothing else persistent, so
1054
+ * implementing it against a new substrate — React Native storage, an object store like R2,
1055
+ * the Node filesystem — yields a working database with no engine changes. The capability
1056
+ * interfaces above split the surface by concern; implement all of them (this type), and see
1057
+ * `/docs/storage/custom` for the guide and `runBlockStoreConformance` from
1058
+ * `@minnowdb/core/testing` for the referee.
1059
+ *
1060
+ * The rules every implementation must honor — the conformance kit checks each of them:
1061
+ *
1062
+ * - **Atomicity.** Every method is all-or-nothing, including after a crash at any moment.
1063
+ * `commitTransaction` and `runGarbageCollectionStep` mutate several record families in one
1064
+ * durable step. A method that resolves has happened; a method that rejects has not
1065
+ * (observably) happened.
1066
+ * - **Conflicts are typed, by exact class.** Compare-and-swap failures throw the exported
1067
+ * error classes (`WriteConflictError`, `TransactionRecordConflictError`,
1068
+ * `TableRecordConflictError`, `LeaseConflictError`, `CompactionJobConflictError`,
1069
+ * `GarbageCollectionJobConflictError`, `TempOwnerConflictError`, `UniqueKeyConflictError`,
1070
+ * `SnapshotManifestMissingError`) — not subclasses, not wrappers. The engine's rebase loops
1071
+ * match on them and the worker client rehydrates them by constructor name.
1072
+ * - **Platform errors pass through.** A quota refusal must escape as the browser's own
1073
+ * `QuotaExceededError` `DOMException`, unwrapped, with everything committed beforehand
1074
+ * intact and the same write succeeding once space frees — no reopen, no repair step.
1075
+ * - **Nothing is shared.** Returned records and bytes must be safe for the caller to mutate;
1076
+ * received records and bytes must be copied or serialized before the call resolves.
1077
+ * - **Deterministic ordering.** List methods sort as documented on each capability interface;
1078
+ * pagination cursors are stable under concurrent writes.
1079
+ * - **Optional means atomic.** The optional methods exist so an adapter that can do something
1080
+ * in one atomic step may say so; callers trust a present method completely and fall back to
1081
+ * the sequential calls when it is absent. Never implement one as the sequential calls in a
1082
+ * trench coat.
1083
+ * - **Multiple connections are normal.** Several instances (tabs) may open one database.
1084
+ * Readers must never block writers; competing writers must resolve through the typed
1085
+ * conflicts. How is the adapter's business — storage transactions, a write-ahead log behind
1086
+ * a leader, anything that keeps these rules true.
1087
+ * - **Records carry no adapter fields.** These types are the whole vocabulary between engine
1088
+ * and store. Anything an adapter needs to remember about its own layout — key partitioning,
1089
+ * format generations, file placements — lives in the adapter's own storage space, keyed
1090
+ * however it likes, never as extra fields on the records it hands back.
1091
+ * - **Bigints are data.** Row ids, counters, and full-text posting ids are `bigint`; an
1092
+ * adapter that serializes records needs an encoding for them.
1093
+ */
1094
+ export interface BlockStore extends BlockPayloadStore, CatalogStore, TransactionStore, LeaseStore, MaintenanceStore, FtsIndexStore, TempSpillStore {
816
1095
  /**
817
1096
  * Optional: the current manifest version and catalog epoch in one atomic read. This is the
818
1097
  * freshness probe: an unchanged pair proves any cached catalog state is still exactly what
@@ -825,22 +1104,12 @@ export interface BlockStore {
825
1104
  * would; callers fall back to those calls when this is absent.
826
1105
  */
827
1106
  getQueryCatalogState?(tableNames: readonly string[]): Promise<QueryCatalogState>;
828
- getManifest(version: number): Promise<Manifest | undefined>;
829
- listManifests(): Promise<Manifest[]>;
830
- listManifestPage(afterVersion: number | null, limit: number): Promise<StoragePage<Manifest, number>>;
831
- publishManifest(input: PublishManifestInput): Promise<Manifest>;
832
- createTransaction(record: TransactionRecord): Promise<void>;
833
1107
  /**
834
1108
  * Optional: reads the current manifest version, creates the transaction record pinned to it,
835
1109
  * and optionally reserves row ids, all in one atomic storage transaction — one round trip
836
1110
  * instead of three. Callers fall back to the individual calls when this is absent.
837
1111
  */
838
1112
  beginTransaction?(input: BeginTransactionInput): Promise<BeginTransactionResult>;
839
- getTransaction(id: string): Promise<TransactionRecord | undefined>;
840
- getTransactions(ids: readonly string[]): Promise<Array<TransactionRecord | undefined>>;
841
- listTransactions(): Promise<TransactionRecord[]>;
842
- listTransactionPage(afterId: string | null, limit: number): Promise<StoragePage<TransactionRecord, string>>;
843
- updateTransaction(id: string, expectedRevision: number, update: TransactionRecordUpdate): Promise<TransactionRecord>;
844
1113
  /**
845
1114
  * Optional: stages blocks and segments and journals them on the transaction record in one
846
1115
  * atomic storage transaction. Must be equivalent to addBlocks + addSegment(s) + one
@@ -848,35 +1117,50 @@ export interface BlockStore {
848
1117
  * crash. Callers fall back to those calls when this is absent.
849
1118
  */
850
1119
  stageTransactionArtifacts?(input: StageTransactionArtifactsInput): Promise<TransactionRecord>;
851
- /** Publishes the next version; the summary omits the block list, which commits never need. */
852
- commitTransaction(input: CommitTransactionInput): Promise<ManifestSummary>;
853
- createLease(record: LeaseRecord): Promise<void>;
854
- getLease(id: string): Promise<LeaseRecord | undefined>;
855
- listLeases(): Promise<LeaseRecord[]>;
856
- renewLease(id: string, expectedRevision: number, expiresAt: string): Promise<LeaseRecord>;
857
- removeLeaseIfExpired(id: string, expectedRevision: number, expiresAtCutoff: string): Promise<boolean>;
858
- removeLease(id: string): Promise<void>;
859
- createCompactionJob(record: CompactionJobRecord): Promise<void>;
860
- getCompactionJob(id: string): Promise<CompactionJobRecord | undefined>;
861
- listCompactionJobs(tableId?: string): Promise<CompactionJobRecord[]>;
862
- listCompactionJobPage(afterId: string | null, limit: number): Promise<StoragePage<CompactionJobRecord, string>>;
863
- updateCompactionJob(id: string, expectedRevision: number, update: CompactionJobRecordUpdate): Promise<CompactionJobRecord>;
864
- cancelCompactionJob(id: string, expectedRevision: number, cancelledAt: string): Promise<CompactionJobRecord>;
865
- removeCompactionJob(id: string): Promise<void>;
866
- createGarbageCollectionJob(input: CreateGarbageCollectionJobInput): Promise<GarbageCollectionJobRecord>;
867
- getGarbageCollectionJob(id: string): Promise<GarbageCollectionJobRecord | undefined>;
868
- listGarbageCollectionJobs(): Promise<GarbageCollectionJobRecord[]>;
869
- runGarbageCollectionStep(input: RunGarbageCollectionStepInput): Promise<GarbageCollectionStepResult>;
870
- removeGarbageCollectionJob(id: string): Promise<void>;
871
- putTempRunPage(page: TempRunPage): Promise<void>;
872
- getTempRunPage(ownerId: string, runId: string, pageIndex: number): Promise<Uint8Array | undefined>;
873
- removeTempRun(ownerId: string, runId: string): Promise<void>;
874
- removeTempOwner(ownerId: string): Promise<void>;
875
- createTempOwner(record: TempOwnerRecord): Promise<void>;
876
- getTempOwner(ownerId: string): Promise<TempOwnerRecord | undefined>;
877
- renewTempOwner(ownerId: string, expectedRevision: number, expiresAt: string): Promise<TempOwnerRecord>;
878
- removeTempOwnerIfExpired(ownerId: string, expiresAtCutoff: string): Promise<boolean>;
879
- listTempOwnerIdsPage(afterOwnerId: string | null, limit: number): Promise<StoragePage<string, string>>;
1120
+ /**
1121
+ * Optional: the single-shot write — begin (or continue) a transaction, stage its blocks and
1122
+ * segments, and commit, all in one atomic storage transaction. Must be exactly equivalent to
1123
+ * `stageTransactionArtifacts` followed by `commitTransaction` (preceded by `createTransaction`
1124
+ * at `expectedManifestVersion` when the input carries a fresh record): the same validation,
1125
+ * the same typed conflicts (`WriteConflictError`, `TransactionRecordConflictError`,
1126
+ * `UniqueKeyConflictError`), the same finalized records afterwards — and nothing at all
1127
+ * written when any part refuses, including the fresh record. This is what lets a simple
1128
+ * write cost one durable storage commit instead of three; callers fall back to the sequence
1129
+ * when it is absent.
1130
+ */
1131
+ writeTransaction?(input: WriteTransactionInput): Promise<ManifestSummary>;
1132
+ /**
1133
+ * Optional: re-pins a lease to another manifest version and renews it, in one atomic step —
1134
+ * `createLease` at the new version plus `removeLease` of the old pin, as one round trip that
1135
+ * keeps the record and its id. Compare-and-swap on `expectedRevision` (`LeaseConflictError`);
1136
+ * the target version's manifest must be available (`SnapshotManifestMissingError`), and a
1137
+ * refused move leaves the lease exactly as it was. The engine uses it to carry its shared
1138
+ * reader pin forward after each commit; callers fall back to create + remove when absent.
1139
+ */
1140
+ moveLease?(id: string, expectedRevision: number, manifestVersion: number | null, expiresAt: string): Promise<LeaseRecord>;
1141
+ /**
1142
+ * Optional: one committed version copied out as a portable snapshot — see
1143
+ * `/docs/storage/snapshots` for what it carries, drops, and guarantees. A store without it
1144
+ * still works; `MinnowDatabase` reports the capability as missing rather than failing.
1145
+ */
1146
+ exportSnapshot?(): Promise<DatabaseSnapshot>;
1147
+ /**
1148
+ * Optional: loads a snapshot into this store, which must be empty. Pairs with
1149
+ * `exportSnapshot` — implement both or neither.
1150
+ */
1151
+ importSnapshot?(snapshot: DatabaseSnapshot, options?: {
1152
+ onProgress?: (progress: SnapshotLoadProgress) => void;
1153
+ }): Promise<void>;
1154
+ /**
1155
+ * Optional: what this database's data occupies in its substrate, in bytes — the number an
1156
+ * application shows a user next to the quota, and what the benchmarks report.
1157
+ */
1158
+ getLogicalStorageBytes?(): Promise<number>;
1159
+ /**
1160
+ * Releases whatever the connection holds (open handles, channels, timers) without flushing
1161
+ * or deleting anything. Synchronous; safe to call twice. Data durability must never depend
1162
+ * on close being called — tabs die without warning.
1163
+ */
880
1164
  close(): void;
881
1165
  }
882
1166
  export declare function createManifest(input: PublishManifestInput): Manifest;