@minnowdb/core 0.7.7 → 0.7.10

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.
@@ -1,5 +1,5 @@
1
1
  import { crossJoinPlan } from "../plan/model.js";
2
- import { toColumnarBatch } from "./batch.js";
2
+ import { definedVectors, toColumnarBatch } from "./batch.js";
3
3
  import { ArtifactCache } from "./artifact-cache.js";
4
4
  import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
5
5
  import { throwIfAborted } from "./cancellation.js";
@@ -21,7 +21,7 @@ import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBy
21
21
  import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES } from "./memory.js";
22
22
  import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE } from "./live.js";
23
23
  import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan } from "./optimizer.js";
24
- import { encodeSqlEqualityValue, readUntypedText } from "./sql-semantics.js";
24
+ import { encodeSqlEqualityValue, readUntypedText, compareSqlValues } from "./sql-semantics.js";
25
25
  import { exactNumericAsNumber, exactNumericValue, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
26
26
  import { toCatalog } from "./catalog.js";
27
27
  import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration } from "./schema.js";
@@ -182,6 +182,9 @@ function migrationResult(steps) {
182
182
  steps: [...steps]
183
183
  };
184
184
  }
185
+ function compactUpdateBatchInput(input) {
186
+ return Object.values(input.changes).includes(void 0) ? { keys: input.keys, changes: definedVectors(input.changes) } : input;
187
+ }
185
188
  const MAX_GARBAGE_COLLECTION_RETAIN_RECENT_VERSIONS = 1024;
186
189
  const REFERENTIAL_CASCADES = 8;
187
190
  class TransactionRollback extends Error {
@@ -548,6 +551,10 @@ async function* singleSnapshotChunk(bytes) {
548
551
  }
549
552
  class MinnowDatabase {
550
553
  store;
554
+ #schema;
555
+ get #erased() {
556
+ return this;
557
+ }
551
558
  #closed = false;
552
559
  #closePromise;
553
560
  #transactions;
@@ -625,6 +632,7 @@ class MinnowDatabase {
625
632
  #preparedCatalogStates = /* @__PURE__ */ new WeakMap();
626
633
  constructor(store, options = {}) {
627
634
  this.store = store;
635
+ this.#schema = options.schema;
628
636
  this.#compression = options.compression ?? "gzip";
629
637
  this.#rowsPerBlock = options.rowsPerBlock ?? 65536;
630
638
  if (!Number.isSafeInteger(this.#rowsPerBlock) || this.#rowsPerBlock <= 0 || this.#rowsPerBlock > MAX_BLOCK_ROW_COUNT) {
@@ -1116,11 +1124,11 @@ class MinnowDatabase {
1116
1124
  this.#activeReadReservations -= 1;
1117
1125
  }
1118
1126
  }
1119
- async #queryWithReadReservation(sql, options) {
1127
+ async #queryWithReadReservation(sql, options, probe, storeMemo = true) {
1120
1128
  throwIfAborted(options.signal);
1121
1129
  await this.#settleExpiredStatementTransaction();
1122
1130
  throwIfAborted(options.signal);
1123
- return this.#openTransaction === void 0 ? this.#withReadReservation(() => this.#queryUnreserved(sql, options)) : this.#queryUnreserved(sql, options);
1131
+ return this.#openTransaction === void 0 ? this.#withReadReservation(() => this.#queryUnreserved(sql, options, probe, storeMemo)) : this.#queryUnreserved(sql, options, probe, storeMemo);
1124
1132
  }
1125
1133
  async #assistAutomaticCollection() {
1126
1134
  if (!this.#autoCollect && !this.#manualCollectionDebtInitialized) {
@@ -1589,7 +1597,7 @@ class MinnowDatabase {
1589
1597
  return { ...statement, rows };
1590
1598
  }
1591
1599
  async insert(tableName, row) {
1592
- return this.insertBatch(tableName, [row]);
1600
+ return this.#erased.insertBatch(tableName, [row]);
1593
1601
  }
1594
1602
  upsertBatch(tableName, input, options = {}) {
1595
1603
  return this.#withWriteReservation(() => this.#upsertBatchReserved(tableName, input, options));
@@ -1631,10 +1639,10 @@ class MinnowDatabase {
1631
1639
  };
1632
1640
  }
1633
1641
  async upsert(tableName, row, options = {}) {
1634
- return this.upsertBatch(tableName, [row], options);
1642
+ return this.#erased.upsertBatch(tableName, [row], options);
1635
1643
  }
1636
1644
  updateBatch(tableName, input) {
1637
- return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, input));
1645
+ return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, compactUpdateBatchInput(input)));
1638
1646
  }
1639
1647
  async #updateBatchReserved(tableName, input) {
1640
1648
  return this.#runWrite(async () => {
@@ -1657,13 +1665,13 @@ class MinnowDatabase {
1657
1665
  });
1658
1666
  }
1659
1667
  async update(tableName, key, changes) {
1660
- return this.updateBatch(tableName, {
1668
+ return this.#erased.updateBatch(tableName, {
1661
1669
  keys: [key],
1662
- changes: Object.fromEntries(Object.entries(changes).map(([name, value]) => [name, [value]]))
1670
+ changes: Object.fromEntries(Object.entries(changes).flatMap(([name, value]) => value === void 0 ? [] : [[name, [value]]]))
1663
1671
  });
1664
1672
  }
1665
1673
  async delete(tableName, key) {
1666
- return this.deleteBatch(tableName, { keys: [key] });
1674
+ return this.#erased.deleteBatch(tableName, { keys: [key] });
1667
1675
  }
1668
1676
  deleteBatch(tableName, input) {
1669
1677
  return this.#withWriteReservation(() => this.#deleteBatchReserved(tableName, input));
@@ -1678,7 +1686,7 @@ class MinnowDatabase {
1678
1686
  if (dependents.length === 0)
1679
1687
  return this.#deleteBatchOnce(tableName, normalizedInput);
1680
1688
  const started = performance.now();
1681
- const { result, version } = await this.write(async (session) => {
1689
+ const { result, version } = await this.#erased.write(async (session) => {
1682
1690
  await this.#applyReferentialActions(table, [...normalizedInput.keys], session, REFERENTIAL_CASCADES);
1683
1691
  return session.deleteBatch(tableName, normalizedInput);
1684
1692
  });
@@ -1882,7 +1890,7 @@ class MinnowDatabase {
1882
1890
  }
1883
1891
  }
1884
1892
  bufferedWriter(tableName, options = {}) {
1885
- return new BufferedTableWriter(this, tableName, options);
1893
+ return new BufferedTableWriter(this.#erased, tableName, options);
1886
1894
  }
1887
1895
  async #writeUpdateBatch(table, keyColumn, input, keys) {
1888
1896
  await this.#assertCompactionCapacity(table);
@@ -2849,7 +2857,7 @@ class MinnowDatabase {
2849
2857
  async query(sql, options = {}) {
2850
2858
  return this.#queryWithReadReservation(sql, options);
2851
2859
  }
2852
- async #queryUnreserved(sql, options) {
2860
+ async #queryUnreserved(sql, options, probe, storeMemo = true) {
2853
2861
  throwIfAborted(options.signal);
2854
2862
  const open = this.#openTransaction;
2855
2863
  if (open !== void 0) {
@@ -2875,9 +2883,9 @@ class MinnowDatabase {
2875
2883
  }
2876
2884
  }
2877
2885
  const plan = bindPlanParameters(compiled, options.params);
2878
- const probe = this.store.getCatalogProbe.bind(this.store);
2886
+ const readProbe = this.store.getCatalogProbe.bind(this.store);
2879
2887
  const memoizable = options.memoize !== false && cacheableQueryInput(sql, options.params ?? []) && plan.usesStatementDatetime !== true && plan.usesVolatileFunctions !== true && plan.usesSequenceCalls !== true && options.version === void 0 && options.executionMemoryBudgetBytes === void 0 && options.spillToStorage === void 0 && options.spillPageRows === void 0;
2880
- const result = !memoizable ? await this.#queryCompiled(plan, options) : await this.#memoizedQuery(plan, `res ${queryResultMemoKey(sql, options.params ?? [])}`, options, probe);
2888
+ const result = !memoizable ? await this.#queryCompiled(plan, options, probe) : await this.#memoizedQuery(plan, `res ${queryResultMemoKey(sql, options.params ?? [])}`, options, readProbe, probe, storeMemo);
2881
2889
  throwIfAborted(options.signal);
2882
2890
  return externalizeQueryResult(result);
2883
2891
  }
@@ -2966,9 +2974,9 @@ class MinnowDatabase {
2966
2974
  });
2967
2975
  }
2968
2976
  }
2969
- async #memoizedQuery(plan, key, options, probe) {
2977
+ async #memoizedQuery(plan, key, options, probe, before, store = true) {
2970
2978
  throwIfAborted(options.signal);
2971
- const before = await probe();
2979
+ before ??= await probe();
2972
2980
  throwIfAborted(options.signal);
2973
2981
  const cached = this.#cacheGet(`${key}${String(before.catalogEpoch)}`);
2974
2982
  if (cached !== void 0) {
@@ -2977,6 +2985,8 @@ class MinnowDatabase {
2977
2985
  }
2978
2986
  const result = await this.#queryCompiled(plan, options, before);
2979
2987
  throwIfAborted(options.signal);
2988
+ if (!store)
2989
+ return result;
2980
2990
  const bytes = queryResultRetainedBytes(result);
2981
2991
  if (bytes <= RESULT_MEMO_MAX_BYTES) {
2982
2992
  const after = await probe();
@@ -3721,19 +3731,15 @@ class MinnowDatabase {
3721
3731
  return this.store.getCatalogProbe();
3722
3732
  },
3723
3733
  manifestPage: (afterVersion, limit) => this.store.listManifestPage(afterVersion, limit),
3724
- dependencyTableIds: async (query) => {
3734
+ dependencyTableIds: async (query, probe) => {
3725
3735
  const compiled = compileLiveQuery(query);
3726
- const plan = await this.#applyCatalogRewrites(compiled);
3736
+ const plan = await this.#applyCatalogRewrites(compiled, probe);
3727
3737
  const tables = await this.#findRealBlockTables(plan);
3728
3738
  return new Set([...tables.values()].map((record) => record.id));
3729
3739
  },
3730
- execute: async (query) => {
3731
- if (typeof query === "string")
3732
- return this.query(query);
3733
- if (query.kind === "typed-query")
3734
- return this.#queryCompiled(query.plan);
3735
- return this.query(query.sql, { params: query.params });
3736
- },
3740
+ execute: (query, context) => this.#liveExecute(query, context),
3741
+ executeMaintainable: (query, context) => this.#liveExecuteMaintainable(compileLiveQuery(query), context),
3742
+ maintain: (_query, result, state, tableIds, after, until, probe) => this.#liveMaintain(result, state, tableIds, after, until, probe),
3737
3743
  changeCanAffect: (query, tableIds, after, until) => this.#liveChangeCanAffect(query, tableIds, after, until)
3738
3744
  }, {
3739
3745
  ...options,
@@ -3746,6 +3752,205 @@ class MinnowDatabase {
3746
3752
  this.#liveSets.add(set);
3747
3753
  return set;
3748
3754
  }
3755
+ async #liveExecute(query, context) {
3756
+ if (context === void 0) {
3757
+ if (typeof query === "string")
3758
+ return this.query(query);
3759
+ if (query.kind === "typed-query")
3760
+ return this.#queryCompiled(query.plan);
3761
+ return this.query(query.sql, { params: query.params });
3762
+ }
3763
+ const { probe, memoize } = context;
3764
+ if (typeof query !== "string" && query.kind === "typed-query") {
3765
+ const plan = query.plan;
3766
+ const memoizable = plan.usesStatementDatetime !== true && plan.usesVolatileFunctions !== true && plan.usesSequenceCalls !== true;
3767
+ return this.#withReadReservation(() => memoizable ? this.#memoizedQuery(plan, `typed ${planMemoKey(plan)}`, {}, this.store.getCatalogProbe.bind(this.store), probe, memoize) : this.#queryCompiled(plan, {}, probe));
3768
+ }
3769
+ if (typeof query === "string")
3770
+ return this.#queryWithReadReservation(query, {}, probe, memoize);
3771
+ return this.#queryWithReadReservation(query.sql, { params: query.params }, probe, memoize);
3772
+ }
3773
+ async #liveMaintenancePlan(compiled, probe) {
3774
+ if (compiled.usesStatementDatetime === true || compiled.usesVolatileFunctions === true || compiled.usesSequenceCalls === true) {
3775
+ return void 0;
3776
+ }
3777
+ const plan = await this.#applyCatalogRewrites(compiled, probe);
3778
+ const base = plan.base;
3779
+ if (plan.joins.length > 0 || base.derived !== void 0 || base.union !== void 0 || base.recursive !== void 0 || base.windowed !== void 0 || plan.pendingSelectShape !== void 0 || plan.distinctWildcard === true || plan.groupBy.length > 0 || plan.having.length > 0 || plan.limitParameter !== void 0 || plan.offsetParameter !== void 0 || planReadsBeyondSingleScan(plan) || planContainsFts(plan)) {
3780
+ return void 0;
3781
+ }
3782
+ const rowLocal = (expression) => {
3783
+ if (expression.kind === "subquery" || expression.kind === "exists" || expression.kind === "window" || expression.kind === "wildcard" || expression.kind === "parameter" || hasAggregate(expression)) {
3784
+ return false;
3785
+ }
3786
+ return childExpressions(expression).every(rowLocal);
3787
+ };
3788
+ if (!plan.select.every((item) => rowLocal(item.expression)))
3789
+ return void 0;
3790
+ if (!plan.predicates.every(({ left, right }) => rowLocal(left) && rowLocal(right))) {
3791
+ return void 0;
3792
+ }
3793
+ if (!plan.orderBy.every((term) => rowLocal(term.expression)))
3794
+ return void 0;
3795
+ const table = await this.#findTable(base.table);
3796
+ const keyColumn = getUniqueKeyColumn(table);
3797
+ if (keyColumn === void 0 || keyColumn.hidden === true || keyColumn.sqlDomain !== void 0) {
3798
+ return void 0;
3799
+ }
3800
+ const publicColumns = plan.select.map((item) => item.alias);
3801
+ if (publicColumns.some((name) => name.startsWith(LIVE_HIDDEN_PREFIX)))
3802
+ return void 0;
3803
+ const fullPlan = clonePlanTree(plan);
3804
+ const qualifiedKey = `${base.alias}.${keyColumn.name}`;
3805
+ fullPlan.select.push({
3806
+ expression: { kind: "column", reference: qualifiedKey },
3807
+ alias: LIVE_KEY_ALIAS
3808
+ });
3809
+ const orderTerms = [];
3810
+ for (const [index, term] of plan.orderBy.entries()) {
3811
+ let alias;
3812
+ if (term.expression.kind === "column" && !term.expression.reference.includes(".")) {
3813
+ const output = term.expression.reference;
3814
+ if (publicColumns.includes(output))
3815
+ alias = output;
3816
+ } else if (term.expression.kind === "literal" && typeof term.expression.value === "number") {
3817
+ const position = term.expression.value;
3818
+ if (!Number.isInteger(position) || position < 1 || position > publicColumns.length) {
3819
+ return void 0;
3820
+ }
3821
+ alias = publicColumns[position - 1];
3822
+ }
3823
+ if (alias === void 0) {
3824
+ alias = `${LIVE_ORDER_ALIAS}${String(index)}`;
3825
+ fullPlan.select.push({ expression: clonePlanTree(term.expression), alias });
3826
+ }
3827
+ orderTerms.push({ alias, descending: term.direction === "desc", nulls: term.nulls });
3828
+ }
3829
+ const deltaPlan = clonePlanTree(fullPlan);
3830
+ deltaPlan.orderBy = [];
3831
+ delete deltaPlan.limit;
3832
+ delete deltaPlan.offset;
3833
+ const margin = plan.limit === void 0 || orderTerms.length === 0 ? 0 : Math.max(LIVE_WINDOW_MARGIN_MIN, Math.min(plan.limit, LIVE_WINDOW_MARGIN_MAX));
3834
+ if (margin > 0)
3835
+ fullPlan.limit = (plan.limit ?? 0) + margin;
3836
+ return {
3837
+ tableId: table.id,
3838
+ keyColumnId: keyColumn.id,
3839
+ qualifiedKey,
3840
+ fullPlan,
3841
+ deltaPlan,
3842
+ publicColumns,
3843
+ columnDomains: [],
3844
+ orderTerms,
3845
+ limit: plan.limit,
3846
+ offset: plan.offset ?? 0,
3847
+ margin,
3848
+ complete: true,
3849
+ rows: [],
3850
+ keys: [],
3851
+ order: []
3852
+ };
3853
+ }
3854
+ async #liveExecuteMaintainable(compiled, context) {
3855
+ const probe = context?.probe ?? await this.store.getCatalogProbe();
3856
+ const state = await this.#liveMaintenancePlan(compiled, probe);
3857
+ if (state === void 0)
3858
+ return void 0;
3859
+ let executed;
3860
+ try {
3861
+ executed = externalizeQueryResult(await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe)));
3862
+ } catch {
3863
+ return void 0;
3864
+ }
3865
+ const split = splitLiveHiddenColumns(executed, state);
3866
+ return liveMaintainedOutcome({
3867
+ ...state,
3868
+ columnDomains: split.result.columnDomains,
3869
+ complete: state.fullPlan.limit === void 0 || split.result.rows.length < state.fullPlan.limit,
3870
+ rows: split.result.rows,
3871
+ keys: split.keys,
3872
+ order: split.order
3873
+ }, void 0);
3874
+ }
3875
+ async #liveMaintain(retained, state, tableIds, after, until, probe) {
3876
+ if (tableIds.length !== 1 || tableIds[0] !== state.tableId)
3877
+ return void 0;
3878
+ const context = await this.#liveProofContext(after, until);
3879
+ const entry = await this.#liveProofTable(context, state.tableId);
3880
+ if (entry === void 0)
3881
+ return void 0;
3882
+ for (const version of context.changedVersions.get(state.tableId) ?? []) {
3883
+ if (!entry.coveredVersions.has(version))
3884
+ return void 0;
3885
+ }
3886
+ const changed = await this.#liveChangedKeys(entry, state.keyColumnId);
3887
+ if (changed === void 0)
3888
+ return void 0;
3889
+ const { changedKeys, existingKeys } = changed;
3890
+ if (changedKeys.size === 0)
3891
+ return { result: retained, state, changed: false };
3892
+ const affected = /* @__PURE__ */ new Set();
3893
+ const retainedTokens = /* @__PURE__ */ new Set();
3894
+ for (const [index, key] of state.keys.entries()) {
3895
+ const token = liveKeyToken(key);
3896
+ retainedTokens.add(token);
3897
+ if (changedKeys.has(token))
3898
+ affected.add(index);
3899
+ }
3900
+ const fullLimit = state.limit === void 0 ? void 0 : state.limit + state.margin;
3901
+ const truncated = !state.complete;
3902
+ if (truncated && affected.size > 0 && state.orderTerms.length === 0)
3903
+ return void 0;
3904
+ if (state.offset > 0) {
3905
+ for (const token of existingKeys)
3906
+ if (!retainedTokens.has(token))
3907
+ return void 0;
3908
+ }
3909
+ const deltaPlan = {
3910
+ ...state.deltaPlan,
3911
+ predicates: [
3912
+ ...state.deltaPlan.predicates,
3913
+ {
3914
+ left: { kind: "column", reference: state.qualifiedKey },
3915
+ operator: "IN",
3916
+ right: {
3917
+ kind: "list",
3918
+ items: [...changedKeys.values()].map((value) => ({ kind: "literal", value }))
3919
+ }
3920
+ }
3921
+ ]
3922
+ };
3923
+ const delta = splitLiveHiddenColumns(externalizeQueryResult(await this.#withReadReservation(() => this.#queryCompiled(deltaPlan, {}, probe))), state);
3924
+ const compare = liveOrderComparator(state.orderTerms);
3925
+ const added = {
3926
+ rows: delta.result.rows,
3927
+ keys: delta.keys,
3928
+ order: delta.order,
3929
+ previousIndex: new Int32Array(delta.result.rows.length).fill(-1)
3930
+ };
3931
+ let merged;
3932
+ try {
3933
+ const kept = filterLiveRows(state, (index) => !affected.has(index));
3934
+ if (state.offset > 0 && added.rows.length > 0) {
3935
+ if (kept.rows.length === 0 || added.rows.some((_, index) => compare(added.order, index, kept.order, 0) < 0)) {
3936
+ return void 0;
3937
+ }
3938
+ }
3939
+ merged = mergeLiveRows(kept, added, compare, state.orderTerms.length > 0);
3940
+ } catch {
3941
+ return void 0;
3942
+ }
3943
+ if (fullLimit !== void 0 && merged.rows.length > fullLimit)
3944
+ merged = trimLiveRows(merged, fullLimit);
3945
+ if (truncated && affected.size > 0 && state.limit !== void 0) {
3946
+ const oldEdge = state.rows.length - 1;
3947
+ const visibleEdge = Math.min(merged.rows.length, state.limit) - 1;
3948
+ if (merged.rows.length < state.limit || oldEdge < 0 || visibleEdge < 0 || compare(merged.order, visibleEdge, state.order, oldEdge) > 0) {
3949
+ return void 0;
3950
+ }
3951
+ }
3952
+ return liveMaintainedOutcome({ ...state, rows: merged.rows, keys: merged.keys, order: merged.order }, retained, merged.previousIndex);
3953
+ }
3749
3954
  async #liveChangeCanAffect(query, tableIds, after, until) {
3750
3955
  const plan = typeof query === "string" ? this.#compileCached(query) : query.kind === "typed-query" ? query.plan : bindPlanParameters(this.#compileCached(query.sql), query.params);
3751
3956
  if (planContainsFts(plan))
@@ -3847,6 +4052,51 @@ class MinnowDatabase {
3847
4052
  }
3848
4053
  return context;
3849
4054
  }
4055
+ #liveChangedKeys(entry, keyColumnId) {
4056
+ const cached = entry.changedKeys.get(keyColumnId);
4057
+ if (cached !== void 0)
4058
+ return cached;
4059
+ const pending = (async () => {
4060
+ const blockIds = [];
4061
+ const existingBlockIds = /* @__PURE__ */ new Set();
4062
+ let deltaRows = 0;
4063
+ for (const segment of entry.windowSegments) {
4064
+ if (segment.kind === "base")
4065
+ continue;
4066
+ const ids = segment.columnBlockIds[keyColumnId];
4067
+ if (ids === void 0)
4068
+ return void 0;
4069
+ deltaRows += segment.rowCount;
4070
+ if (deltaRows > LIVE_MAINTENANCE_MAX_DELTA_ROWS)
4071
+ return void 0;
4072
+ blockIds.push(...ids);
4073
+ if (segment.kind !== "insert")
4074
+ for (const id of ids)
4075
+ existingBlockIds.add(id);
4076
+ }
4077
+ const changedKeys = /* @__PURE__ */ new Map();
4078
+ const existingKeys = /* @__PURE__ */ new Set();
4079
+ if (blockIds.length > 0) {
4080
+ const decoded = await this.#loadDecodedBlocks([...new Set(blockIds)]);
4081
+ for (const blockId of blockIds) {
4082
+ const column = decoded.get(blockId);
4083
+ if (column === void 0)
4084
+ return void 0;
4085
+ for (const value of column.values) {
4086
+ if (value === null)
4087
+ continue;
4088
+ const token = liveKeyToken(value);
4089
+ changedKeys.set(token, value);
4090
+ if (existingBlockIds.has(blockId))
4091
+ existingKeys.add(token);
4092
+ }
4093
+ }
4094
+ }
4095
+ return { changedKeys, existingKeys };
4096
+ })();
4097
+ entry.changedKeys.set(keyColumnId, pending);
4098
+ return pending;
4099
+ }
3850
4100
  #liveProofTable(context, tableId) {
3851
4101
  const cached = context.tables.get(tableId);
3852
4102
  if (cached !== void 0)
@@ -3867,7 +4117,7 @@ class MinnowDatabase {
3867
4117
  coveredVersions.add(committed);
3868
4118
  windowSegments.push(segment);
3869
4119
  }
3870
- return { table, windowSegments, coveredVersions };
4120
+ return { table, windowSegments, coveredVersions, changedKeys: /* @__PURE__ */ new Map() };
3871
4121
  })();
3872
4122
  context.tables.set(tableId, entry);
3873
4123
  return entry;
@@ -4880,6 +5130,11 @@ class MinnowDatabase {
4880
5130
  }
4881
5131
  async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
4882
5132
  const table = await this.#findTable(tableName);
5133
+ const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
5134
+ const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
5135
+ if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
5136
+ throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
5137
+ }
4883
5138
  const filled = await this.#fillDefaults(table, input);
4884
5139
  const { generated, autoIncrement } = filled;
4885
5140
  let batch = filled.batch;
@@ -4892,11 +5147,6 @@ class MinnowDatabase {
4892
5147
  validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
4893
5148
  }
4894
5149
  }
4895
- const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
4896
- const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
4897
- if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
4898
- throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
4899
- }
4900
5150
  let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
4901
5151
  let skippedRowCount = 0;
4902
5152
  if (normalizedConflictWhere !== void 0) {
@@ -4912,6 +5162,7 @@ class MinnowDatabase {
4912
5162
  rowCount = filtered.rowCount;
4913
5163
  sessionUpsertFirings = filtered.firings;
4914
5164
  if (rowCount === 0) {
5165
+ collectAutoIncrementGenerated(batch, generated, autoIncrement);
4915
5166
  return {
4916
5167
  tableName: table.name,
4917
5168
  segmentId: null,
@@ -5178,7 +5429,10 @@ class MinnowDatabase {
5178
5429
  }
5179
5430
  return (await this.#memoizedQuery(query.plan, `typed ${planMemoKey(query.plan)}`, {}, probe)).rows;
5180
5431
  }
5181
- async migrate(definition, options = {}) {
5432
+ async migrate(definition = this.#schema, options = {}) {
5433
+ if (definition === void 0) {
5434
+ throw new TypeError("migrate() needs a schema: pass a definition, or construct the database with { schema }");
5435
+ }
5182
5436
  let originalSteps;
5183
5437
  for (let attempt = 0; ; attempt += 1) {
5184
5438
  try {
@@ -6079,7 +6333,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6079
6333
  let outcome;
6080
6334
  let version;
6081
6335
  if (options.writer === void 0) {
6082
- const completed = await this.write(apply);
6336
+ const completed = await this.#erased.write(apply);
6083
6337
  outcome = completed.result;
6084
6338
  version = completed.version;
6085
6339
  } else {
@@ -6184,7 +6438,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6184
6438
  }
6185
6439
  const sourceKey = mergeSourceKeyExpression(statement, keyColumn.name);
6186
6440
  const sourceSql = mergeSourceSql(statement);
6187
- const { result: applied, version } = await this.write(async (session) => {
6441
+ const { result: applied, version } = await this.#erased.write(async (session) => {
6188
6442
  const sourceRows = (await session.query(sourceSql)).rows;
6189
6443
  const keys = sourceRows.map((row) => storedSqlValueFromExecution(keyColumn, evaluateJoinedRowExpression(sourceKey, { [statement.source.alias]: row })));
6190
6444
  const present = /* @__PURE__ */ new Map();
@@ -6765,7 +7019,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6765
7019
  }
6766
7020
  await this.#assertStagedInsertKeysFree(table2, stagedKeyColumn, input, writer);
6767
7021
  }
6768
- const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.insertBatch(statement.table, input));
7022
+ const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.#erased.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.#erased.insertBatch(statement.table, input));
6769
7023
  const generated = "generatedColumns" in result ? result.generatedColumns ?? {} : {};
6770
7024
  return {
6771
7025
  kind: "insert",
@@ -6897,7 +7151,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6897
7151
  ...returnedRows2 === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows2)
6898
7152
  };
6899
7153
  }
6900
- const deleted = options.writer === void 0 ? await this.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
7154
+ const deleted = options.writer === void 0 ? await this.#erased.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
6901
7155
  return {
6902
7156
  kind: "delete",
6903
7157
  table: table.name,
@@ -6934,7 +7188,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6934
7188
  returnedChanges[assignment.column] = executionValues;
6935
7189
  }
6936
7190
  }
6937
- const updated = options.writer === void 0 ? await this.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
7191
+ const updated = options.writer === void 0 ? await this.#erased.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
6938
7192
  const returnedRows = returningColumns === void 0 ? void 0 : rows.map((row, index) => Object.fromEntries(returningColumns.map((name) => [
6939
7193
  name,
6940
7194
  returnedChanges !== void 0 && name in returnedChanges ? returnedChanges[name]?.[index] ?? null : updated.generatedColumns?.[name] !== void 0 ? updated.generatedColumns[name][index] ?? null : row[name] ?? null
@@ -14177,6 +14431,203 @@ function searchableFtsColumns(table) {
14177
14431
  return void 0;
14178
14432
  return visibleTableColumns(table).filter((column) => column.type !== "boolean").map((column) => column.name);
14179
14433
  }
14434
+ const LIVE_HIDDEN_PREFIX = "__minnow_live_";
14435
+ const LIVE_KEY_ALIAS = `${LIVE_HIDDEN_PREFIX}key`;
14436
+ const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
14437
+ const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
14438
+ const LIVE_WINDOW_MARGIN_MIN = 16;
14439
+ const LIVE_WINDOW_MARGIN_MAX = 64;
14440
+ function sameLiveRow(left, right, columns) {
14441
+ for (const column of columns) {
14442
+ const a = left[column] ?? null;
14443
+ const b = right[column] ?? null;
14444
+ if (a instanceof Date || b instanceof Date) {
14445
+ if (!(a instanceof Date && b instanceof Date) || !Object.is(dateMilliseconds(a), dateMilliseconds(b))) {
14446
+ return false;
14447
+ }
14448
+ } else if (!Object.is(a, b))
14449
+ return false;
14450
+ }
14451
+ return true;
14452
+ }
14453
+ function liveKeyToken(value) {
14454
+ if (typeof value === "number")
14455
+ return `n:${String(value)}`;
14456
+ if (typeof value === "string")
14457
+ return `s:${value}`;
14458
+ if (typeof value === "boolean")
14459
+ return value ? "b:1" : "b:0";
14460
+ if (value instanceof Date)
14461
+ return `d:${String(dateMilliseconds(value))}`;
14462
+ return "z";
14463
+ }
14464
+ function splitLiveHiddenColumns(executed, state) {
14465
+ const publicCount = state.publicColumns.length;
14466
+ const hidden = executed.columns.slice(publicCount);
14467
+ const count = executed.rows.length;
14468
+ const keys = new Array(count);
14469
+ const order = state.orderTerms.map(() => new Array(count));
14470
+ for (let index = 0; index < count; index += 1) {
14471
+ const row = executed.rows[index] ?? {};
14472
+ keys[index] = row[LIVE_KEY_ALIAS] ?? null;
14473
+ for (const [term, { alias }] of state.orderTerms.entries()) {
14474
+ const values = order[term];
14475
+ if (values !== void 0)
14476
+ values[index] = row[alias] ?? null;
14477
+ }
14478
+ for (const column of hidden)
14479
+ Reflect.deleteProperty(row, column);
14480
+ }
14481
+ return {
14482
+ result: {
14483
+ columns: executed.columns.slice(0, publicCount),
14484
+ columnDomains: executed.columnDomains.slice(0, publicCount),
14485
+ rows: executed.rows
14486
+ },
14487
+ keys,
14488
+ order
14489
+ };
14490
+ }
14491
+ function liveMaintainedOutcome(state, previous, previousIndex) {
14492
+ const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
14493
+ const visible = state.rows.slice(0, visibleCount);
14494
+ if (previous === void 0) {
14495
+ return {
14496
+ result: {
14497
+ columns: [...state.publicColumns],
14498
+ columnDomains: [...state.columnDomains],
14499
+ rows: visible
14500
+ },
14501
+ state,
14502
+ changed: true
14503
+ };
14504
+ }
14505
+ const previousCount = previous.rows.length;
14506
+ const retained = new Int32Array(visibleCount);
14507
+ let same = visibleCount === previousCount;
14508
+ let kept = 0;
14509
+ for (let index = 0; index < visibleCount; index += 1) {
14510
+ const was = previousIndex?.[index] ?? -1;
14511
+ if (was >= 0 && was < previousCount) {
14512
+ retained[index] = was;
14513
+ kept += 1;
14514
+ if (was !== index)
14515
+ same = false;
14516
+ continue;
14517
+ }
14518
+ retained[index] = -1;
14519
+ const before = previous.rows[index];
14520
+ const now = visible[index];
14521
+ if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
14522
+ visible[index] = before;
14523
+ retained[index] = index;
14524
+ kept += 1;
14525
+ } else
14526
+ same = false;
14527
+ }
14528
+ if (same)
14529
+ return { result: previous, state, changed: false };
14530
+ return {
14531
+ result: {
14532
+ columns: [...state.publicColumns],
14533
+ columnDomains: [...state.columnDomains],
14534
+ rows: visible
14535
+ },
14536
+ state,
14537
+ changed: true,
14538
+ ...kept > 0 ? { retained } : {}
14539
+ };
14540
+ }
14541
+ function filterLiveRows(state, keep) {
14542
+ const rows = [];
14543
+ const keys = [];
14544
+ const order = state.order.map(() => new Array());
14545
+ const previous = [];
14546
+ for (let index = 0; index < state.rows.length; index += 1) {
14547
+ const row = state.rows[index];
14548
+ if (row === void 0 || !keep(index))
14549
+ continue;
14550
+ rows.push(row);
14551
+ keys.push(state.keys[index] ?? null);
14552
+ for (const [term, values] of state.order.entries()) {
14553
+ order[term]?.push(values[index] ?? null);
14554
+ }
14555
+ previous.push(index);
14556
+ }
14557
+ return { rows, keys, order, previousIndex: Int32Array.from(previous) };
14558
+ }
14559
+ function trimLiveRows(rows, count) {
14560
+ return {
14561
+ rows: rows.rows.slice(0, count),
14562
+ keys: rows.keys.slice(0, count),
14563
+ order: rows.order.map((values) => values.slice(0, count)),
14564
+ previousIndex: rows.previousIndex.slice(0, count)
14565
+ };
14566
+ }
14567
+ function liveOrderComparator(terms) {
14568
+ return (leftOrder, leftIndex, rightOrder, rightIndex) => {
14569
+ for (const [term, { descending, nulls }] of terms.entries()) {
14570
+ const a = leftOrder[term]?.[leftIndex] ?? null;
14571
+ const b = rightOrder[term]?.[rightIndex] ?? null;
14572
+ if (a === null || b === null) {
14573
+ if (a === null && b === null)
14574
+ continue;
14575
+ const nullsFirst = nulls === "first" || nulls === void 0 && descending;
14576
+ return a === null ? nullsFirst ? -1 : 1 : nullsFirst ? 1 : -1;
14577
+ }
14578
+ let comparison = compareSqlValues(a, b);
14579
+ if (descending)
14580
+ comparison = -comparison;
14581
+ if (comparison !== 0)
14582
+ return comparison;
14583
+ }
14584
+ return 0;
14585
+ };
14586
+ }
14587
+ function mergeLiveRows(kept, added, compare, ordered) {
14588
+ if (added.rows.length === 0)
14589
+ return kept;
14590
+ const terms = kept.order.length;
14591
+ const total = kept.rows.length + added.rows.length;
14592
+ const rows = new Array(total);
14593
+ const keys = new Array(total);
14594
+ const order = Array.from({ length: terms }, () => new Array(total));
14595
+ const previousIndex = new Int32Array(total);
14596
+ const take = (source, from, to) => {
14597
+ rows[to] = source.rows[from] ?? {};
14598
+ keys[to] = source.keys[from] ?? null;
14599
+ for (let term = 0; term < terms; term += 1) {
14600
+ const values = order[term];
14601
+ if (values !== void 0)
14602
+ values[to] = source.order[term]?.[from] ?? null;
14603
+ }
14604
+ previousIndex[to] = source.previousIndex[from] ?? -1;
14605
+ };
14606
+ if (!ordered) {
14607
+ for (let index = 0; index < kept.rows.length; index += 1)
14608
+ take(kept, index, index);
14609
+ for (let index = 0; index < added.rows.length; index += 1) {
14610
+ take(added, index, kept.rows.length + index);
14611
+ }
14612
+ return { rows, keys, order, previousIndex };
14613
+ }
14614
+ const addedIndexes = added.rows.map((_, index) => index);
14615
+ addedIndexes.sort((left, right) => compare(added.order, left, added.order, right));
14616
+ let keptIndex = 0;
14617
+ let addedPosition = 0;
14618
+ for (let to = 0; to < total; to += 1) {
14619
+ const addedIndex = addedIndexes[addedPosition];
14620
+ const takeAdded = addedIndex !== void 0 && (keptIndex >= kept.rows.length || compare(added.order, addedIndex, kept.order, keptIndex) < 0);
14621
+ if (takeAdded) {
14622
+ take(added, addedIndex, to);
14623
+ addedPosition += 1;
14624
+ } else {
14625
+ take(kept, keptIndex, to);
14626
+ keptIndex += 1;
14627
+ }
14628
+ }
14629
+ return { rows, keys, order, previousIndex };
14630
+ }
14180
14631
  function getUniqueKeyColumn(table) {
14181
14632
  if (table.uniqueKeyColumnId === void 0)
14182
14633
  return void 0;