@minnowdb/core 0.7.8 → 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.
@@ -2,7 +2,7 @@ import { type CompactionJobRecord, type GarbageCollectionJobRecord, type Storage
2
2
  import { type BatchRow } from "./batch.js";
3
3
  import type { Catalog } from "./catalog.js";
4
4
  import type { BufferPoolStats, StagedWriteResult, StagedUpsertResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, MigrateOptions, DeleteBatchResult, ExecuteOptions, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchResult, UpsertBatchResult, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
5
- import type { LiveQueryInput, LiveQueryInvalidation, LiveQueryObserveOptions, LiveQueryStats, LiveQuerySubscribeOptions } from "./live.js";
5
+ import type { LiveQueryDelivery, LiveQueryInput, LiveQueryInvalidation, LiveQueryObserveOptions, LiveQueryStats, LiveQuerySubscribeOptions } from "./live.js";
6
6
  import type { CompiledQuery, CompiledStatement, QueryResult, QueryValue } from "./query.js";
7
7
  import type { AnySchema, UntypedSchema, AnyTable, BatchColumnName, BatchDeleteInput, BatchInsertInput, BatchInsertRow, BatchKeyValue, BatchReadOptions, BatchReadRow, BatchUpdateChanges, BatchUpdateInput, BatchUpsertOptions, SchemaDefinition, TableName } from "./schema.js";
8
8
  import { type WireMigrationStep } from "./schema-wire.js";
@@ -65,7 +65,7 @@ export interface ClientMigrationResult {
65
65
  steps: WireMigrationStep[];
66
66
  }
67
67
  interface EventRoute {
68
- onChange?: (result: QueryResult) => void;
68
+ onChange?: (result: QueryResult, delivery: LiveQueryDelivery) => void;
69
69
  onInvalidate?: (invalidation: LiveQueryInvalidation) => void;
70
70
  onError?: (error: unknown) => void;
71
71
  onComplete?: () => void;
@@ -556,9 +556,10 @@ class MinnowDatabaseClient {
556
556
  const route = this.#events.get(response.handleId);
557
557
  if (route === void 0)
558
558
  return;
559
- if (response.event === "change")
560
- route.onChange?.(decodeQueryResult(response.payload));
561
- else if (response.event === "invalidate") {
559
+ if (response.event === "change") {
560
+ const { result, delivery } = response.payload;
561
+ route.onChange?.(decodeQueryResult(result), delivery);
562
+ } else if (response.event === "invalidate") {
562
563
  route.onInvalidate?.(response.payload);
563
564
  } else if (response.event === "error") {
564
565
  route.onError?.(rehydrateResponseError(response.payload));
@@ -698,7 +699,8 @@ class ClientLiveQuerySet {
698
699
  try {
699
700
  const created = await this.client._invoke(this.handleId, "observe", [
700
701
  subscriptionId,
701
- query
702
+ query,
703
+ { suppressUnchanged: options.suppressUnchanged === true }
702
704
  ]);
703
705
  this.#subscriptionIds.add(subscriptionId);
704
706
  return new ClientLiveSubscription(this.client, subscriptionId, created.dependencyTableIds, () => this.#subscriptionIds.delete(subscriptionId), state);
@@ -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";
@@ -1124,11 +1124,11 @@ class MinnowDatabase {
1124
1124
  this.#activeReadReservations -= 1;
1125
1125
  }
1126
1126
  }
1127
- async #queryWithReadReservation(sql, options) {
1127
+ async #queryWithReadReservation(sql, options, probe, storeMemo = true) {
1128
1128
  throwIfAborted(options.signal);
1129
1129
  await this.#settleExpiredStatementTransaction();
1130
1130
  throwIfAborted(options.signal);
1131
- 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);
1132
1132
  }
1133
1133
  async #assistAutomaticCollection() {
1134
1134
  if (!this.#autoCollect && !this.#manualCollectionDebtInitialized) {
@@ -2857,7 +2857,7 @@ class MinnowDatabase {
2857
2857
  async query(sql, options = {}) {
2858
2858
  return this.#queryWithReadReservation(sql, options);
2859
2859
  }
2860
- async #queryUnreserved(sql, options) {
2860
+ async #queryUnreserved(sql, options, probe, storeMemo = true) {
2861
2861
  throwIfAborted(options.signal);
2862
2862
  const open = this.#openTransaction;
2863
2863
  if (open !== void 0) {
@@ -2883,9 +2883,9 @@ class MinnowDatabase {
2883
2883
  }
2884
2884
  }
2885
2885
  const plan = bindPlanParameters(compiled, options.params);
2886
- const probe = this.store.getCatalogProbe.bind(this.store);
2886
+ const readProbe = this.store.getCatalogProbe.bind(this.store);
2887
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;
2888
- 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);
2889
2889
  throwIfAborted(options.signal);
2890
2890
  return externalizeQueryResult(result);
2891
2891
  }
@@ -2974,9 +2974,9 @@ class MinnowDatabase {
2974
2974
  });
2975
2975
  }
2976
2976
  }
2977
- async #memoizedQuery(plan, key, options, probe) {
2977
+ async #memoizedQuery(plan, key, options, probe, before, store = true) {
2978
2978
  throwIfAborted(options.signal);
2979
- const before = await probe();
2979
+ before ??= await probe();
2980
2980
  throwIfAborted(options.signal);
2981
2981
  const cached = this.#cacheGet(`${key}${String(before.catalogEpoch)}`);
2982
2982
  if (cached !== void 0) {
@@ -2985,6 +2985,8 @@ class MinnowDatabase {
2985
2985
  }
2986
2986
  const result = await this.#queryCompiled(plan, options, before);
2987
2987
  throwIfAborted(options.signal);
2988
+ if (!store)
2989
+ return result;
2988
2990
  const bytes = queryResultRetainedBytes(result);
2989
2991
  if (bytes <= RESULT_MEMO_MAX_BYTES) {
2990
2992
  const after = await probe();
@@ -3729,19 +3731,15 @@ class MinnowDatabase {
3729
3731
  return this.store.getCatalogProbe();
3730
3732
  },
3731
3733
  manifestPage: (afterVersion, limit) => this.store.listManifestPage(afterVersion, limit),
3732
- dependencyTableIds: async (query) => {
3734
+ dependencyTableIds: async (query, probe) => {
3733
3735
  const compiled = compileLiveQuery(query);
3734
- const plan = await this.#applyCatalogRewrites(compiled);
3736
+ const plan = await this.#applyCatalogRewrites(compiled, probe);
3735
3737
  const tables = await this.#findRealBlockTables(plan);
3736
3738
  return new Set([...tables.values()].map((record) => record.id));
3737
3739
  },
3738
- execute: async (query) => {
3739
- if (typeof query === "string")
3740
- return this.query(query);
3741
- if (query.kind === "typed-query")
3742
- return this.#queryCompiled(query.plan);
3743
- return this.query(query.sql, { params: query.params });
3744
- },
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),
3745
3743
  changeCanAffect: (query, tableIds, after, until) => this.#liveChangeCanAffect(query, tableIds, after, until)
3746
3744
  }, {
3747
3745
  ...options,
@@ -3754,6 +3752,205 @@ class MinnowDatabase {
3754
3752
  this.#liveSets.add(set);
3755
3753
  return set;
3756
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
+ }
3757
3954
  async #liveChangeCanAffect(query, tableIds, after, until) {
3758
3955
  const plan = typeof query === "string" ? this.#compileCached(query) : query.kind === "typed-query" ? query.plan : bindPlanParameters(this.#compileCached(query.sql), query.params);
3759
3956
  if (planContainsFts(plan))
@@ -3855,6 +4052,51 @@ class MinnowDatabase {
3855
4052
  }
3856
4053
  return context;
3857
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
+ }
3858
4100
  #liveProofTable(context, tableId) {
3859
4101
  const cached = context.tables.get(tableId);
3860
4102
  if (cached !== void 0)
@@ -3875,7 +4117,7 @@ class MinnowDatabase {
3875
4117
  coveredVersions.add(committed);
3876
4118
  windowSegments.push(segment);
3877
4119
  }
3878
- return { table, windowSegments, coveredVersions };
4120
+ return { table, windowSegments, coveredVersions, changedKeys: /* @__PURE__ */ new Map() };
3879
4121
  })();
3880
4122
  context.tables.set(tableId, entry);
3881
4123
  return entry;
@@ -14189,6 +14431,203 @@ function searchableFtsColumns(table) {
14189
14431
  return void 0;
14190
14432
  return visibleTableColumns(table).filter((column) => column.type !== "boolean").map((column) => column.name);
14191
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
+ }
14192
14631
  function getUniqueKeyColumn(table) {
14193
14632
  if (table.uniqueKeyColumnId === void 0)
14194
14633
  return void 0;