@minnowdb/core 0.6.5 → 0.6.7

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 (57) hide show
  1. package/dist/block-format/codecs.d.ts +0 -8
  2. package/dist/block-format/codecs.js +1 -1
  3. package/dist/engine/artifact-cache.d.ts +2 -1
  4. package/dist/engine/batch.d.ts +0 -1
  5. package/dist/engine/batch.js +1 -1
  6. package/dist/engine/buffered-writer.js +1 -12
  7. package/dist/engine/byte-estimates.d.ts +11 -0
  8. package/dist/engine/byte-estimates.js +25 -0
  9. package/dist/engine/database.js +58 -34
  10. package/dist/engine/fts.d.ts +0 -1
  11. package/dist/engine/fts.js +1 -1
  12. package/dist/engine/group-index.d.ts +2 -0
  13. package/dist/engine/group-index.js +4 -8
  14. package/dist/engine/join-index.d.ts +0 -1
  15. package/dist/engine/join-index.js +1 -15
  16. package/dist/engine/keyed-live.js +2 -36
  17. package/dist/engine/live-equal.d.ts +7 -0
  18. package/dist/engine/live-equal.js +42 -0
  19. package/dist/engine/optimizer.js +11 -16
  20. package/dist/engine/point-read.d.ts +1 -1
  21. package/dist/engine/point-read.js +3 -2
  22. package/dist/engine/query-cache.js +1 -12
  23. package/dist/engine/query.d.ts +20 -59
  24. package/dist/engine/query.js +286 -54
  25. package/dist/engine/result-wire.d.ts +2 -1
  26. package/dist/engine/schema.d.ts +15 -11
  27. package/dist/engine/schema.js +11 -19
  28. package/dist/engine/sort-keys.d.ts +2 -3
  29. package/dist/engine/sort-keys.js +1 -1
  30. package/dist/engine/sql-domains.d.ts +27 -2
  31. package/dist/engine/sql-domains.js +120 -6
  32. package/dist/engine/sql-json.d.ts +0 -2
  33. package/dist/engine/sql-json.js +1 -1
  34. package/dist/engine/sql-semantics.d.ts +2 -1
  35. package/dist/engine/typed-live.js +3 -41
  36. package/dist/engine/vector.d.ts +7 -7
  37. package/dist/engine/vector.js +11 -30
  38. package/dist/engine/write-block-planner.d.ts +2 -1
  39. package/dist/plan/model.d.ts +22 -0
  40. package/dist/plan/model.js +29 -1
  41. package/dist/storage/indexeddb.js +13 -23
  42. package/dist/storage/opfs/leader.d.ts +0 -4
  43. package/dist/storage/opfs/leader.js +5 -14
  44. package/dist/storage/opfs/snapshot-ledger.d.ts +3 -2
  45. package/dist/storage/toolkit/index.d.ts +1 -1
  46. package/dist/storage/toolkit/record-core.d.ts +0 -1
  47. package/dist/storage/toolkit/record-core.js +3 -19
  48. package/dist/storage/types.d.ts +4 -3
  49. package/dist/storage/types.js +13 -2
  50. package/dist/testing/block-store-conformance.js +28 -0
  51. package/dist/testing/opfs-shim.d.ts +2 -1
  52. package/dist/testing/simulator.js +3 -0
  53. package/dist/worker-protocol/index.d.ts +0 -32
  54. package/dist/worker-protocol/index.js +0 -40
  55. package/package.json +2 -1
  56. package/postgres-feature-profile.json +8 -3
  57. package/sql-feature-matrix.json +71 -11
@@ -1 +1,29 @@
1
- export {};
1
+ /**
2
+ * A cross join rides the nested-loop inner-join path with a condition every row pair satisfies:
3
+ * 1 = 1 over null literal key expressions. Producers build the shape with crossJoinPlan and
4
+ * consumers recognize it with isCrossJoinPlan, so the encoding lives in exactly one place.
5
+ */
6
+ export function crossJoinPlan(source) {
7
+ return {
8
+ ...source,
9
+ kind: "inner",
10
+ left: { kind: "literal", value: null },
11
+ right: { kind: "literal", value: null },
12
+ on: {
13
+ kind: "condition",
14
+ operator: "=",
15
+ left: { kind: "literal", value: 1 },
16
+ right: { kind: "literal", value: 1 },
17
+ },
18
+ };
19
+ }
20
+ export function isCrossJoinPlan(join) {
21
+ const condition = join.on;
22
+ return (join.kind === "inner" &&
23
+ condition?.kind === "condition" &&
24
+ condition.operator === "=" &&
25
+ condition.left.kind === "literal" &&
26
+ condition.left.value === 1 &&
27
+ condition.right.kind === "literal" &&
28
+ condition.right.value === 1);
29
+ }
@@ -1,4 +1,4 @@
1
- import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, validateTableForeignKey, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsOrderedReadLimits, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError, } from "./types.js";
1
+ import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, validateTableForeignKey, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError, } from "./types.js";
2
2
  import { crc32, verifyStoredBlock } from "../block-format/index.js";
3
3
  import { dateIsoString } from "../date-value.js";
4
4
  import { decodeSnapshotMetadataItems, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity, } from "./snapshot-stream.js";
@@ -1769,11 +1769,8 @@ export class IndexedDbBlockStore {
1769
1769
  validateId(tableId, "Table ID");
1770
1770
  validateId(columnId, "Column ID");
1771
1771
  validateFtsPostingQueries(terms);
1772
- if (!Number.isSafeInteger(maxRowIds) ||
1773
- maxRowIds < 1 ||
1774
- maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
1775
- throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
1776
- }
1772
+ validateFtsReadVersion(upToVersion);
1773
+ validateFtsCandidateLimit(maxRowIds);
1777
1774
  const transaction = this.#transaction("catalog", "readonly");
1778
1775
  const store = transaction.objectStore("catalog");
1779
1776
  const [rawToc, rawDeltaIndex] = await Promise.all([
@@ -1870,9 +1867,7 @@ export class IndexedDbBlockStore {
1870
1867
  async readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
1871
1868
  validateId(tableId, "Table ID");
1872
1869
  validateId(columnId, "Column ID");
1873
- if (!Number.isSafeInteger(upToVersion) || upToVersion < 0) {
1874
- throw new RangeError("Full-text snapshot version must be a non-negative safe integer");
1875
- }
1870
+ validateFtsReadVersion(upToVersion);
1876
1871
  validateFtsOrderedReadLimits(maxRowIds, maxRetainedBytes);
1877
1872
  const transaction = this.#transaction("catalog", "readonly");
1878
1873
  const store = transaction.objectStore("catalog");
@@ -6983,20 +6978,15 @@ function createCurrentIndexedDbSchema(database, upgrade) {
6983
6978
  for (const storeName of storeNames)
6984
6979
  database.createObjectStore(storeName);
6985
6980
  database.createObjectStore(SNAPSHOT_HEADER_STORE);
6986
- upgrade.objectStore("segments").createIndex(SEGMENT_TABLE_INDEX, "tableId");
6987
- upgrade.objectStore("leases").createIndex(LEASE_EXPIRY_INDEX, ["expiresAt", "id"]);
6988
- upgrade.objectStore("transactions").createIndex(TRANSACTION_STATUS_INDEX, "status");
6989
- upgrade.objectStore("temp").createIndex(TEMP_OWNER_EXPIRY_INDEX, ["expiresAt", "ownerId"]);
6990
- upgrade.objectStore("catalog").createIndex(CATALOG_FTS_BUILD_UPDATED_INDEX, "updatedAt");
6991
- upgrade.objectStore("catalog").createIndex(CATALOG_FTS_BUILD_EXPIRY_INDEX, "ftsBuildExpiry");
6992
- upgrade
6993
- .objectStore("catalog")
6994
- .createIndex(CATALOG_FTS_RETIREMENT_UPDATED_INDEX, "retirementUpdatedAt");
6995
- upgrade.objectStore("catalog").createIndex(UNIQUE_KEY_BUILD_ACTIVE_INDEX, "activeBuildState");
6996
- upgrade.objectStore("catalog").createIndex(UNIQUE_KEY_BUILD_EXPIRY_INDEX, "activeExpiry");
6997
- upgrade.objectStore("catalog").createIndex(MANIFEST_BLOCK_ID_INDEX, "blockId", {
6998
- unique: true,
6999
- });
6981
+ // Creation is driven by the same declaration validateCurrentIndexedDbSchema verifies
6982
+ // against, so the two can never disagree about what the current schema is.
6983
+ for (const [storeName, indexes] of Object.entries(indexedDbIndexSchema)) {
6984
+ const store = upgrade.objectStore(storeName);
6985
+ for (const index of indexes) {
6986
+ const keyPath = typeof index.keyPath === "string" ? index.keyPath : [...index.keyPath];
6987
+ store.createIndex(index.name, keyPath, { unique: index.unique ?? false });
6988
+ }
6989
+ }
7000
6990
  upgrade.objectStore("gc").add(emptyMaintenanceQuota(), MAINTENANCE_QUOTA_KEY);
7001
6991
  upgrade.objectStore("statistics").add(emptyResourceLedger(), RESOURCE_LEDGER_KEY);
7002
6992
  upgrade.objectStore("statistics").add(emptyCatalogResourceLedger(), CATALOG_RESOURCE_LEDGER_KEY);
@@ -4,8 +4,6 @@ import { type Placement } from "../toolkit/extents.js";
4
4
  /** A failed checkpoint may defer compaction, but the recovery log itself stays bounded. */
5
5
  export declare const MAX_OPFS_WAL_BYTES: number;
6
6
  export declare const MAX_OPFS_CHECKPOINT_BYTES: number;
7
- /** Failed physical reclamation cannot permit byte-growing work forever. */
8
- export declare const MAX_OPFS_CLEANUP_DEBT_BYTES: number;
9
7
  interface IdPlacement {
10
8
  id: string;
11
9
  placement: Placement;
@@ -459,6 +457,4 @@ export declare class OpfsLeader {
459
457
  export declare function assertBlockReadBatchByteLimit(placements: ReadonlyArray<{
460
458
  length: number;
461
459
  } | undefined>): void;
462
- /** Conservative retained-heap model for one decoded postings chunk and its nested arrays. */
463
- export declare function modeledFtsChunkBytes(chunk: readonly FtsPosting[]): number;
464
460
  export {};
@@ -1,4 +1,4 @@
1
- import { activePostingStorageColumnIds, assertStorageBulkReadItems, assertTempRunPageBatchLimits, BlockReadBatchTooLargeError, collectFtsPostingsBounded, ftsPostingQueryMatches, MAX_BLOCK_READ_BATCH_BYTES, MAX_FTS_BASE_CHUNKS, MAX_POSTING_BUILD_TTL_MS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_ORDERED_READ_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_SEGMENTS, SnapshotImportConflictError, PostingBuildConflictError, StorageResourceLimitError, StorageCorruptionError, StorageFormatVersionError, validateFtsPostingQueries, validateFtsOrderedReadLimits, uniqueKeyBuildChunkRetainedBytes, } from "../types.js";
1
+ import { activePostingStorageColumnIds, assertStorageBulkReadItems, assertTempRunPageBatchLimits, BlockReadBatchTooLargeError, collectFtsPostingsBounded, ftsPostingQueryMatches, MAX_BLOCK_READ_BATCH_BYTES, MAX_FTS_BASE_CHUNKS, MAX_POSTING_BUILD_TTL_MS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_ORDERED_READ_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_SEGMENTS, SnapshotImportConflictError, PostingBuildConflictError, StorageResourceLimitError, StorageCorruptionError, StorageFormatVersionError, validateFtsPostingQueries, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, uniqueKeyBuildChunkRetainedBytes, } from "../types.js";
2
2
  import { dateIsoString } from "../../date-value.js";
3
3
  import { decodeSnapshotMetadataItems, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity, } from "../snapshot-stream.js";
4
4
  import { RecordCore, validateBeginTransactionInput, validateBlockWriteBytes, validateFtsBaseInput, validateFtsPostingChunks, validateId, validateTempRunPage, validateTempRunPageIdentity, } from "../toolkit/record-core.js";
@@ -17,7 +17,7 @@ const CHECKPOINT_ENTRIES = 1024;
17
17
  export const MAX_OPFS_WAL_BYTES = 256 * 1024 * 1024;
18
18
  export const MAX_OPFS_CHECKPOINT_BYTES = 256 * 1024 * 1024;
19
19
  /** Failed physical reclamation cannot permit byte-growing work forever. */
20
- export const MAX_OPFS_CLEANUP_DEBT_BYTES = 64 * 1024 * 1024;
20
+ const MAX_OPFS_CLEANUP_DEBT_BYTES = 64 * 1024 * 1024;
21
21
  /** Decoded full-text base cache: bounded primarily by modeled retained heap, count secondarily. */
22
22
  const FTS_CHUNK_CACHE_BYTES = 16 * 1024 * 1024;
23
23
  const FTS_CHUNK_CACHE_SIZE = 64;
@@ -2485,9 +2485,7 @@ export class OpfsLeader {
2485
2485
  validateId(tableId);
2486
2486
  validateId(columnId);
2487
2487
  validateFtsPostingQueries(terms);
2488
- if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
2489
- throw new RangeError("Full-text query version must be a safe integer at least -1");
2490
- }
2488
+ validateFtsReadVersion(upToVersion);
2491
2489
  validateFtsCandidateLimit(maxRowIds);
2492
2490
  return this.#run(async () => {
2493
2491
  // Unlike immutable table blocks, derived-index extents are not protected by reader leases:
@@ -2553,9 +2551,7 @@ export class OpfsLeader {
2553
2551
  async readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
2554
2552
  validateId(tableId);
2555
2553
  validateId(columnId);
2556
- if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
2557
- throw new RangeError("Full-text query version must be a safe integer at least -1");
2558
- }
2554
+ validateFtsReadVersion(upToVersion);
2559
2555
  validateFtsOrderedReadLimits(maxRowIds, maxRetainedBytes);
2560
2556
  return this.#run(async () => {
2561
2557
  const key = postingStorageKey(tableId, columnId);
@@ -4041,11 +4037,6 @@ function requireCoverageVersion(value, label) {
4041
4037
  if (!Number.isSafeInteger(value) || value < -1)
4042
4038
  throw new Error(`Invalid ${label}`);
4043
4039
  }
4044
- function validateFtsCandidateLimit(value) {
4045
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_FTS_CANDIDATE_ROW_IDS) {
4046
- throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
4047
- }
4048
- }
4049
4040
  function requirePositiveInteger(value, label) {
4050
4041
  if (!Number.isSafeInteger(value) || value < 1)
4051
4042
  throw new Error(`Invalid ${label}`);
@@ -5035,7 +5026,7 @@ function samePlacement(left, right) {
5035
5026
  left.checksum === right.checksum);
5036
5027
  }
5037
5028
  /** Conservative retained-heap model for one decoded postings chunk and its nested arrays. */
5038
- export function modeledFtsChunkBytes(chunk) {
5029
+ function modeledFtsChunkBytes(chunk) {
5039
5030
  let bytes = 64;
5040
5031
  for (const posting of chunk) {
5041
5032
  bytes += 64 + posting.term.length * 2 + posting.rowIds.length * 40;
@@ -1,8 +1,8 @@
1
1
  import { type SnapshotFrame, type SnapshotFrameKind } from "../types.js";
2
2
  import { type Placement } from "../toolkit/extents.js";
3
3
  import { OpfsTree } from "./files.js";
4
- export type SnapshotLedgerKind = "export" | "import" | "completed";
5
- export interface SnapshotLedgerRecord {
4
+ type SnapshotLedgerKind = "export" | "import" | "completed";
5
+ interface SnapshotLedgerRecord {
6
6
  readonly sequence: number;
7
7
  readonly kind: SnapshotFrameKind;
8
8
  readonly itemCount: number;
@@ -38,3 +38,4 @@ export declare class SnapshotFrameLedger {
38
38
  adoptLength(length: number): void;
39
39
  close(): void;
40
40
  }
41
+ export {};
@@ -26,4 +26,4 @@ export { readFully, writeFully, type SyncFileHandle } from "./sync-file.js";
26
26
  export { RecordCore, type PhysicalBlocks, type RecordCoreState } from "./record-core.js";
27
27
  export { MAX_WAL_FRAME_BYTES, WalWriter, iterateWalFrames, replayWalFrames, type ReplayedWalFrame, } from "./wal.js";
28
28
  export { ExtentPool, assertValidExtentMeta, assertValidPlacement, extentPath, validPlacement, type ExtentBatchMark, type ExtentFiles, type ExtentMeta, type ExtentPoolOptions, type Placement, } from "./extents.js";
29
- export { LOG_FORMAT_VERSION, decodeChunk, decodePostingChunk, decodeRecordJson, decodeSyncCheckpoint, encodeChunk, encodePostingChunk, encodeRecordJson, encodeSyncCheckpoint, } from "./wire.js";
29
+ export { LOG_FORMAT_VERSION, decodeChunk, decodePostingChunk, decodeRecordJson, decodeSyncCheckpoint, encodeChunk, encodePostingChunk, encodeRecordJson, encodeSyncCheckpoint, type PostingChunkEntry, } from "./wire.js";
@@ -373,7 +373,6 @@ export declare function validateFtsPostingChunks(chunks: unknown, label: string)
373
373
  export declare function validateSegmentRuntimeRecord(value: unknown, label: string): SegmentRecord;
374
374
  export declare function validateAutoIncrementReservation(count: number, atLeast: bigint | undefined): void;
375
375
  export declare function validateBeginTransactionInput(input: BeginTransactionInput): void;
376
- export declare function validateLeaseExpiration(expiresAt: string): void;
377
376
  export declare function validateTempRunPage(page: TempRunPage): void;
378
377
  export declare function validateTempRunPageIdentity(ownerId: string, runId: string, pageIndex: number): void;
379
378
  export {};
@@ -1,4 +1,4 @@
1
- import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
1
+ import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, validateFtsCandidateLimit, validateFtsReadVersion, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
2
2
  import { dateIsoString } from "../../date-value.js";
3
3
  import { assertWellFormedString, crc32, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../../block-format/index.js";
4
4
  /**
@@ -1729,9 +1729,7 @@ export class RecordCore {
1729
1729
  validateId(tableId);
1730
1730
  validateId(columnId);
1731
1731
  validateFtsPostingQueries(terms);
1732
- if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
1733
- throw new RangeError("Full-text query version must be a safe integer at least -1");
1734
- }
1732
+ validateFtsReadVersion(upToVersion);
1735
1733
  validateFtsCandidateLimit(maxRowIds);
1736
1734
  const key = `${tableId}/${columnId}`;
1737
1735
  const base = this.#ftsBases.get(key);
@@ -1834,9 +1832,7 @@ export class RecordCore {
1834
1832
  readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes) {
1835
1833
  validateId(tableId);
1836
1834
  validateId(columnId);
1837
- if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
1838
- throw new RangeError("Full-text query version must be a safe integer at least -1");
1839
- }
1835
+ validateFtsReadVersion(upToVersion);
1840
1836
  const base = this.#ftsBases.get(`${tableId}/${columnId}`);
1841
1837
  const coversVersion = base?.coversVersion ?? -1;
1842
1838
  const delta = this.readFtsDeltas(tableId, columnId, coversVersion, upToVersion);
@@ -6382,13 +6378,6 @@ function validateTempOwnerRecord(record) {
6382
6378
  throw new RangeError("Temp owner record must be created at revision zero");
6383
6379
  }
6384
6380
  }
6385
- export function validateLeaseExpiration(expiresAt) {
6386
- if (typeof expiresAt !== "string" ||
6387
- expiresAt.length === 0 ||
6388
- !Number.isFinite(Date.parse(expiresAt))) {
6389
- throw new TypeError("Lease expiration must be valid");
6390
- }
6391
- }
6392
6381
  function isCanonicalTimestamp(value) {
6393
6382
  const timestamp = Date.parse(value);
6394
6383
  return Number.isFinite(timestamp) && dateIsoString(new Date(timestamp)) === value;
@@ -6496,11 +6485,6 @@ function sortedVersionInInterval(versions, addedVersion, removedVersion) {
6496
6485
  const version = versions[low];
6497
6486
  return version !== undefined && (removedVersion === null || version < removedVersion);
6498
6487
  }
6499
- function validateFtsCandidateLimit(value) {
6500
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_FTS_CANDIDATE_ROW_IDS) {
6501
- throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
6502
- }
6503
- }
6504
6488
  function varuintByteLength(value) {
6505
6489
  let length = 1;
6506
6490
  while (value >= 0x80n) {
@@ -534,8 +534,6 @@ export interface CompactionJobCursor {
534
534
  sourceSegmentIndex: number;
535
535
  sourceBlockIndex: number;
536
536
  }
537
- export declare const compactionRewritePlanKinds: readonly ["copy-v1", "rechunk-v1", "merge-v1"];
538
- export type CompactionRewritePlanKind = (typeof compactionRewritePlanKinds)[number];
539
537
  export interface CopyCompactionRewritePlan {
540
538
  readonly kind: "copy-v1";
541
539
  }
@@ -1669,6 +1667,10 @@ export declare function ftsPostingQueryMatches(term: string, query: FtsPostingQu
1669
1667
  * never drift between backends — pruning would silently differ per store.
1670
1668
  */
1671
1669
  export declare function collectFtsCandidates(chunkLists: Iterable<readonly FtsPosting[]>, terms: readonly FtsPostingQuery[], maxRowIds?: number): FtsCandidates;
1670
+ /** Validates the snapshot bound of one full-text read; -1 selects the base view alone. */
1671
+ export declare function validateFtsReadVersion(upToVersion: number): void;
1672
+ /** Validates the candidate row-ID ceiling accepted by one full-text candidate read. */
1673
+ export declare function validateFtsCandidateLimit(maxRowIds: number): void;
1672
1674
  /** Validates the fixed memory ceilings accepted by one ordered postings read. */
1673
1675
  export declare function validateFtsOrderedReadLimits(maxRowIds?: number, maxRetainedBytes?: number): void;
1674
1676
  /**
@@ -2422,7 +2424,6 @@ export declare function createManifest(input: CreateManifestInput): Manifest;
2422
2424
  export declare function normalizeSegmentRecord(record: SegmentRecord): SegmentRecord;
2423
2425
  export declare function updateTransactionRecord(record: TransactionRecord, update: TransactionRecordUpdate): TransactionRecord;
2424
2426
  export declare function createGarbageCollectionJobRecord(input: CreateGarbageCollectionJobInput): GarbageCollectionJobRecord;
2425
- export declare function normalizeGarbageCollectionDiscovery(discovery: GarbageCollectionDiscovery): GarbageCollectionDiscovery;
2426
2427
  export declare function updateGarbageCollectionPlanningRecord(record: GarbageCollectionJobRecord, input: UpdateGarbageCollectionPlanningInput): GarbageCollectionJobRecord;
2427
2428
  export declare function normalizeGarbageCollectionJobRecord(record: GarbageCollectionJobRecord): GarbageCollectionJobRecord;
2428
2429
  export declare function advanceGarbageCollectionJobRecord(record: GarbageCollectionJobRecord, accounting: GarbageCollectionStepAccounting): GarbageCollectionJobRecord;
@@ -726,7 +726,6 @@ export const compactionJobStates = [
726
726
  "cancelled",
727
727
  "aborted",
728
728
  ];
729
- export const compactionRewritePlanKinds = ["copy-v1", "rechunk-v1", "merge-v1"];
730
729
  export const compactionOutputCompressions = ["raw", "gzip"];
731
730
  /** Every segment ID owned by a compaction job, including merge partition outputs. */
732
731
  export function compactionOutputSegmentIds(job) {
@@ -1427,6 +1426,18 @@ export function collectFtsCandidates(chunkLists, terms, maxRowIds = MAX_FTS_CAND
1427
1426
  overflow: false,
1428
1427
  };
1429
1428
  }
1429
+ /** Validates the snapshot bound of one full-text read; -1 selects the base view alone. */
1430
+ export function validateFtsReadVersion(upToVersion) {
1431
+ if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
1432
+ throw new RangeError("Full-text query version must be a safe integer at least -1");
1433
+ }
1434
+ }
1435
+ /** Validates the candidate row-ID ceiling accepted by one full-text candidate read. */
1436
+ export function validateFtsCandidateLimit(maxRowIds) {
1437
+ if (!Number.isSafeInteger(maxRowIds) || maxRowIds < 1 || maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
1438
+ throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
1439
+ }
1440
+ }
1430
1441
  /** Validates the fixed memory ceilings accepted by one ordered postings read. */
1431
1442
  export function validateFtsOrderedReadLimits(maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
1432
1443
  if (!Number.isSafeInteger(maxRowIds) || maxRowIds < 1 || maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
@@ -1940,7 +1951,7 @@ export function createGarbageCollectionJobRecord(input) {
1940
1951
  ...(discovery === undefined ? {} : { discovery }),
1941
1952
  };
1942
1953
  }
1943
- export function normalizeGarbageCollectionDiscovery(discovery) {
1954
+ function normalizeGarbageCollectionDiscovery(discovery) {
1944
1955
  const runtime = discovery;
1945
1956
  if (typeof runtime !== "object" || runtime === null) {
1946
1957
  throw new TypeError("Garbage collection discovery must be an object");
@@ -1008,6 +1008,34 @@ export function blockStoreConformanceCases() {
1008
1008
  { term: "minnows", rowIds: [2n], tf: [1] },
1009
1009
  { term: "shark", rowIds: [4n], tf: [1] },
1010
1010
  ], "ordered postings must merge into canonical term and row-ID order");
1011
+ // The read-argument contract is uniform across adapters: -1 selects the base view
1012
+ // alone, anything below it (or non-integral) is refused, and the candidate limit is
1013
+ // validated on every read path.
1014
+ const baseOnly = await store.readFtsPostings("table-t", "col-v", -1);
1015
+ check(baseOnly.hasBase && baseOnly.postings.length === 3, "an upToVersion of -1 must serve the base view alone");
1016
+ for (const version of [-2, Number.NaN, 1.5]) {
1017
+ try {
1018
+ await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], version);
1019
+ throw new Error(`candidate read accepted invalid version ${String(version)}`);
1020
+ }
1021
+ catch (error) {
1022
+ check(error instanceof RangeError, "invalid candidate versions must throw RangeError");
1023
+ }
1024
+ try {
1025
+ await store.readFtsPostings("table-t", "col-v", version);
1026
+ throw new Error(`postings read accepted invalid version ${String(version)}`);
1027
+ }
1028
+ catch (error) {
1029
+ check(error instanceof RangeError, "invalid postings versions must throw RangeError");
1030
+ }
1031
+ }
1032
+ try {
1033
+ await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10, 0);
1034
+ throw new Error("candidate read accepted a zero row-ID limit");
1035
+ }
1036
+ catch (error) {
1037
+ check(error instanceof RangeError, "invalid candidate limits must throw RangeError");
1038
+ }
1011
1039
  store.close();
1012
1040
  },
1013
1041
  },
@@ -21,7 +21,7 @@
21
21
  * toolkit's `SyncFileHandle` surface, in plain vitest, with crashes and quota failures on tap.
22
22
  */
23
23
  export type WriteFault = (path: string, phase: "create" | "write" | "flush") => void;
24
- export type DeleteFault = (path: string) => void;
24
+ type DeleteFault = (path: string) => void;
25
25
  export type TransferLimit = (path: string, operation: "read" | "write", requestedBytes: number, at: number) => number | undefined;
26
26
  export declare class MemoryOpfs {
27
27
  #private;
@@ -43,3 +43,4 @@ export declare class MemoryOpfs {
43
43
  /** Test-side in-place corruption: existing sync handles observe the changed byte. */
44
44
  corruptFileByte(path: string, offset: number): void;
45
45
  }
46
+ export {};
@@ -572,6 +572,9 @@ function summarizeCollection(result) {
572
572
  retainedTransactions: result.retainedTransactionCount,
573
573
  };
574
574
  }
575
+ // Stream-identical copy of `mulberry32` in ./seeds.ts. This module ships in the tarball and
576
+ // seeds.ts does not (it reads MINNOW_SEED and the unpublished regression-seeds.json), so the
577
+ // published simulator cannot import the canonical copy.
575
578
  function mulberry32(seed) {
576
579
  let state = seed >>> 0;
577
580
  return () => {
@@ -1,38 +1,6 @@
1
1
  export declare const protocolVersion: 3;
2
2
  /** Outstanding request/response pairs retained by either side of one database RPC connection. */
3
3
  export declare const MAX_DATABASE_RPC_IN_FLIGHT = 256;
4
- export type WorkerOperation = "benchmark" | "cancelBenchmark" | "memorySample" | "datasetList" | "datasetCreate" | "datasetDelete" | "runQuery" | "suiteReference" | "suiteWrite" | "suiteFeatureMatrix" | "suiteLive";
5
- export interface WorkerRequest<T = unknown> {
6
- version: typeof protocolVersion;
7
- requestId: string;
8
- operation: WorkerOperation;
9
- payload: T;
10
- }
11
- export interface SuccessResponse<T = unknown> {
12
- version: typeof protocolVersion;
13
- requestId: string;
14
- kind: "success";
15
- result: T;
16
- }
17
- export interface FailureResponse {
18
- version: typeof protocolVersion;
19
- requestId: string;
20
- kind: "failure";
21
- error: {
22
- name: string;
23
- message: string;
24
- };
25
- }
26
- export interface ProgressResponse<T = unknown> {
27
- version: typeof protocolVersion;
28
- requestId: string;
29
- kind: "progress";
30
- progress: T;
31
- }
32
- export type WorkerResponse<T = unknown> = SuccessResponse<T> | FailureResponse | ProgressResponse;
33
- export declare function parseRequest(value: unknown): WorkerRequest;
34
- export declare function success<T>(requestId: string, result: T): SuccessResponse<T>;
35
- export declare function failure(requestId: string, error: unknown): FailureResponse;
36
4
  export type RpcRequest = {
37
5
  version: typeof protocolVersion;
38
6
  requestId: string;
@@ -1,31 +1,6 @@
1
1
  export const protocolVersion = 3;
2
2
  /** Outstanding request/response pairs retained by either side of one database RPC connection. */
3
3
  export const MAX_DATABASE_RPC_IN_FLIGHT = 256;
4
- export function parseRequest(value) {
5
- if (typeof value !== "object" || value === null)
6
- throw new TypeError("Request must be an object");
7
- const candidate = value;
8
- if (candidate.version !== protocolVersion)
9
- throw new Error("Unsupported protocol version");
10
- if (typeof candidate.requestId !== "string" || candidate.requestId.length === 0) {
11
- throw new TypeError("Request ID must be a non-empty string");
12
- }
13
- if (!isOperation(candidate.operation))
14
- throw new Error("Unsupported worker operation");
15
- return candidate;
16
- }
17
- export function success(requestId, result) {
18
- return { version: protocolVersion, requestId, kind: "success", result };
19
- }
20
- export function failure(requestId, error) {
21
- const normalized = error instanceof Error ? error : new Error(String(error));
22
- return {
23
- version: protocolVersion,
24
- requestId,
25
- kind: "failure",
26
- error: { name: normalized.name, message: normalized.message },
27
- };
28
- }
29
4
  export function serializeError(error) {
30
5
  if (!(error instanceof Error)) {
31
6
  return { name: "Error", message: String(error) };
@@ -101,18 +76,3 @@ export function parseRpcResponse(value) {
101
76
  throw new Error("Unsupported protocol version");
102
77
  return candidate;
103
78
  }
104
- function isOperation(value) {
105
- return [
106
- "benchmark",
107
- "cancelBenchmark",
108
- "memorySample",
109
- "datasetList",
110
- "datasetCreate",
111
- "datasetDelete",
112
- "runQuery",
113
- "suiteReference",
114
- "suiteWrite",
115
- "suiteFeatureMatrix",
116
- "suiteLive",
117
- ].includes(String(value));
118
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnowdb/core",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
5
5
  "license": "MIT",
6
6
  "author": "Eric Wilhite",
@@ -125,6 +125,7 @@
125
125
  "!dist/storage/fixture-shape.*",
126
126
  "!dist/engine/storage-test-helpers.*",
127
127
  "!dist/testing/seeds.*",
128
+ "!dist/testing/oracle.*",
128
129
  "!dist/*.tsbuildinfo",
129
130
  "sql-feature-matrix.json",
130
131
  "postgres-feature-profile.json"
@@ -111,7 +111,7 @@
111
111
  {
112
112
  "id": "function.string-extended",
113
113
  "classification": "different",
114
- "reason": "The bundled form includes INSTR; PostgreSQL spells that function strpos with reversed arguments."
114
+ "reason": "The bundled form includes INSTR; PostgreSQL has no INSTR and spells the same (string, substring) lookup STRPOS, with the arguments in the same order."
115
115
  },
116
116
  {
117
117
  "id": "function.trim-multi-character",
@@ -134,10 +134,15 @@
134
134
  "classification": "different",
135
135
  "reason": "Both engines accept the correlated JSON aggregate and agree on its JSON value, but Minnow returns JSON text while PostgreSQL returns a native JSON value."
136
136
  },
137
+ {
138
+ "id": "literal.scientific",
139
+ "classification": "different",
140
+ "reason": "PostgreSQL types every scientific-notation constant NUMERIC and renders it as text. Minnow evaluates it exactly but returns a number whenever the value reads back identically from one, keeping ordinary constants number-typed at the JavaScript boundary; a constant that stays exact renders fully expanded, as PostgreSQL renders it."
141
+ },
137
142
  {
138
143
  "id": "type.exact-numeric",
139
144
  "classification": "different",
140
- "reason": "Minnow preserves exact decimals at the JavaScript boundary as strings; PGlite's default decoder returns this NUMERIC value as a number. A declared scale renders at exactly that scale as PostgreSQL does; a bare NUMERIC column, a derived arithmetic result, and a value cast or concatenated to text render canonically, without the trailing fractional zeros PostgreSQL preserves. A non-terminating AVG quotient renders at Minnow's internal precision, which keeps more fractional digits than PostgreSQL's rounding; the value agrees to every digit PostgreSQL renders."
145
+ "reason": "Minnow preserves exact decimals at the JavaScript boundary as strings, as PGlite's default decoder also does. A declared scale renders at exactly that scale as PostgreSQL does; a bare NUMERIC column, a derived arithmetic result, and a value cast or concatenated to text render canonically, without the trailing fractional zeros PostgreSQL preserves. Division and AVG select their result scale the way PostgreSQL does, so quotient digits agree — including AVG over a column whose declared scale exceeds the selection. The canonical encoding does drop a stored value's display scale, so an explicit arithmetic quotient (such as SUM(v) / COUNT(v)) over a column declared with more than about twenty fractional digits can carry fewer digits than PostgreSQL, which floors the selection at the operand's display scale. An arithmetic or comparison expression mixing a float column with a constant Float64 cannot represent stays exact, where PostgreSQL casts the constant to float8 and rounds it before evaluating."
141
146
  },
142
147
  {
143
148
  "id": "type.json-jsonb",
@@ -182,7 +187,7 @@
182
187
  {
183
188
  "id": "json.array",
184
189
  "classification": "different",
185
- "reason": "The JSON value agrees, but Minnow returns compact JSON text while PostgreSQL's text rendering includes spaces."
190
+ "reason": "Minnow defaults JSON_ARRAY to NULL ON NULL, so a NULL argument becomes a JSON null where PostgreSQL's ABSENT ON NULL default drops it; Minnow also returns compact JSON text while PostgreSQL's rendering includes spaces."
186
191
  },
187
192
  {
188
193
  "id": "trigger.create-after",