@minnowdb/core 0.7.10 → 0.9.0

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 (75) hide show
  1. package/dist/engine/artifact-cache.js +3 -2
  2. package/dist/engine/buffered-writer.js +12 -2
  3. package/dist/engine/client.d.ts +9 -0
  4. package/dist/engine/client.js +112 -20
  5. package/dist/engine/database.d.ts +5 -3
  6. package/dist/engine/database.js +2199 -1675
  7. package/dist/engine/errors.d.ts +21 -2
  8. package/dist/engine/errors.js +30 -1
  9. package/dist/engine/index.d.ts +1 -0
  10. package/dist/engine/index.js +4 -0
  11. package/dist/engine/live-accept.js +13 -0
  12. package/dist/engine/live-aggregate.js +264 -0
  13. package/dist/engine/live-patch.d.ts +21 -0
  14. package/dist/engine/live-patch.js +31 -0
  15. package/dist/engine/live.d.ts +14 -0
  16. package/dist/engine/live.js +146 -142
  17. package/dist/engine/optimizer.js +11 -6
  18. package/dist/engine/query-cache.js +19 -15
  19. package/dist/engine/query-generations.js +61 -0
  20. package/dist/engine/query-identity.js +41 -0
  21. package/dist/engine/query.d.ts +7 -13
  22. package/dist/engine/query.js +298 -435
  23. package/dist/engine/result-state.d.ts +7 -0
  24. package/dist/engine/result-state.js +15 -0
  25. package/dist/engine/sql-domains.js +121 -0
  26. package/dist/engine/sql-semantics.js +7 -4
  27. package/dist/engine/typed-live.js +28 -20
  28. package/dist/engine/vector.d.ts +4 -0
  29. package/dist/engine/vector.js +14 -14
  30. package/dist/engine/windows.d.ts +12 -0
  31. package/dist/engine/windows.js +387 -0
  32. package/dist/engine/worker-server.d.ts +1 -1
  33. package/dist/engine/worker-server.js +28 -15
  34. package/dist/engine/write-coordinator.js +54 -0
  35. package/dist/plan/model.d.ts +1 -1
  36. package/dist/storage/indexeddb.js +134 -89
  37. package/dist/storage/opfs/index.d.ts +1 -1
  38. package/dist/storage/opfs/index.js +3 -1
  39. package/dist/storage/opfs/leader.js +20 -9
  40. package/dist/storage/opfs/rpc.js +3 -1
  41. package/dist/storage/opfs/store.d.ts +2 -0
  42. package/dist/storage/opfs/store.js +119 -43
  43. package/dist/storage/toolkit/record-core.js +2 -1
  44. package/dist/storage/types.d.ts +14 -0
  45. package/dist/storage/types.js +21 -0
  46. package/dist/transactions/index.d.ts +3 -0
  47. package/dist/transactions/index.js +4 -1
  48. package/dist/worker-protocol/index.d.ts +1 -1
  49. package/dist/worker-protocol/index.js +1 -1
  50. package/package.json +2 -2
  51. package/postgres-feature-profile.json +6 -1
  52. package/sql-feature-matrix.json +20 -27
  53. package/dist/date-value.d.ts +0 -20
  54. package/dist/engine/artifact-cache.d.ts +0 -29
  55. package/dist/engine/byte-estimates.d.ts +0 -11
  56. package/dist/engine/cancellation.d.ts +0 -2
  57. package/dist/engine/defaults.d.ts +0 -29
  58. package/dist/engine/group-index.d.ts +0 -33
  59. package/dist/engine/join-index.d.ts +0 -10
  60. package/dist/engine/live-equal.d.ts +0 -7
  61. package/dist/engine/point-read.d.ts +0 -59
  62. package/dist/engine/query-cache.d.ts +0 -21
  63. package/dist/engine/result-wire.d.ts +0 -70
  64. package/dist/engine/sort-keys.d.ts +0 -73
  65. package/dist/engine/sql-domains.d.ts +0 -93
  66. package/dist/engine/sql-functions.d.ts +0 -11
  67. package/dist/engine/sql-json.d.ts +0 -40
  68. package/dist/engine/sql-semantics.d.ts +0 -66
  69. package/dist/engine/worker-store-indexeddb.d.ts +0 -2
  70. package/dist/engine/worker-store-memory.d.ts +0 -2
  71. package/dist/engine/worker-store-opfs.d.ts +0 -2
  72. package/dist/engine/write-block-planner.d.ts +0 -19
  73. package/dist/storage/opfs/leader.d.ts +0 -460
  74. package/dist/storage/opfs/rpc.d.ts +0 -82
  75. package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
@@ -0,0 +1,7 @@
1
+ import type { QueryResult } from "../plan/model.js";
2
+ /** Shared boundary state; result copies must not need the SQL compiler or evaluators. */
3
+ export declare const alreadyExternalResults: WeakSet<QueryResult>;
4
+ /** Internal ownership boundary for results assembled entirely from already-public row values. */
5
+ export declare function markQueryResultExternal(result: QueryResult): QueryResult;
6
+ /** Carries the internal no-conversion proof across a defensive result copy. */
7
+ export declare function copyQueryResultExternalization(source: QueryResult, copy: QueryResult): QueryResult;
@@ -0,0 +1,15 @@
1
+ const alreadyExternalResults = /* @__PURE__ */ new WeakSet();
2
+ function markQueryResultExternal(result) {
3
+ alreadyExternalResults.add(result);
4
+ return result;
5
+ }
6
+ function copyQueryResultExternalization(source, copy) {
7
+ if (alreadyExternalResults.has(source))
8
+ alreadyExternalResults.add(copy);
9
+ return copy;
10
+ }
11
+ export {
12
+ alreadyExternalResults,
13
+ copyQueryResultExternalization,
14
+ markQueryResultExternal
15
+ };
@@ -722,6 +722,125 @@ function intervalDomainCompare(left, right) {
722
722
  function isSqlDomainValue(value) {
723
723
  return typeof value === "string" && value.startsWith(PREFIX);
724
724
  }
725
+ function temporalDomainPart(field, value) {
726
+ if (typeof value !== "string")
727
+ return void 0;
728
+ let months = 0;
729
+ let days = 0;
730
+ let usecs;
731
+ const interval = value.startsWith(INTERVAL_VALUE);
732
+ if (interval) {
733
+ [months, days, usecs] = JSON.parse(value.slice(INTERVAL_VALUE.length));
734
+ } else if (value.startsWith(TIME_VALUE)) {
735
+ const [hour = "0", minute = "0", second = "0"] = value.slice(TIME_VALUE.length).split(":");
736
+ usecs = Number(hour) * 36e8 + Number(minute) * 6e7 + Math.round(Number(second) * 1e6);
737
+ } else
738
+ return void 0;
739
+ switch (field) {
740
+ case "hour":
741
+ return Math.trunc(usecs / 36e8);
742
+ case "minute":
743
+ return Math.trunc(usecs / 6e7) % 60;
744
+ case "second":
745
+ return usecs % 6e7 / 1e6;
746
+ case "milliseconds":
747
+ return usecs % 6e7 / 1e3;
748
+ case "microseconds":
749
+ return usecs % 6e7;
750
+ case "epoch":
751
+ return interval ? (Math.trunc(months / 12) * 365.25 + months % 12 * 30 + days) * 86400 + usecs / 1e6 : usecs / 1e6;
752
+ }
753
+ if (interval) {
754
+ const years = Math.trunc(months / 12);
755
+ switch (field) {
756
+ case "year":
757
+ return years;
758
+ case "month":
759
+ return months % 12;
760
+ case "day":
761
+ return days;
762
+ case "quarter":
763
+ return Math.trunc(months % 12 / 3) + 1;
764
+ case "decade":
765
+ return Math.trunc(years / 10);
766
+ case "century":
767
+ return Math.trunc(years / 100);
768
+ case "millennium":
769
+ return Math.trunc(years / 1e3);
770
+ }
771
+ }
772
+ throw new TypeError(`Cannot extract ${field} from ${interval ? "INTERVAL" : "TIME"}`);
773
+ }
774
+ function structuredDomainCompare(left, right) {
775
+ if (typeof left !== "string" || typeof right !== "string")
776
+ return void 0;
777
+ const array = left.startsWith(ARRAY_VALUE) && right.startsWith(ARRAY_VALUE);
778
+ const jsonb = left.startsWith(JSONB_VALUE) && right.startsWith(JSONB_VALUE);
779
+ if (!array && !jsonb)
780
+ return void 0;
781
+ if (left === right)
782
+ return 0;
783
+ const prefix = array ? ARRAY_VALUE : JSONB_VALUE;
784
+ const a = JSON.parse(left.slice(prefix.length));
785
+ const b = JSON.parse(right.slice(prefix.length));
786
+ if (array && Array.isArray(a) && Array.isArray(b)) {
787
+ for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
788
+ const x = a[index];
789
+ const y = b[index];
790
+ const order = x === null || y === null ? x === y ? 0 : x === null ? 1 : -1 : compareJsonStructure(x, y);
791
+ if (order !== 0)
792
+ return order;
793
+ }
794
+ return a.length - b.length;
795
+ }
796
+ if (Array.isArray(a) && a.length === 0)
797
+ return Array.isArray(b) && b.length === 0 ? 0 : -1;
798
+ if (Array.isArray(b) && b.length === 0)
799
+ return 1;
800
+ return compareJsonStructure(a, b);
801
+ }
802
+ function compareJsonStructure(left, right) {
803
+ if (left === right)
804
+ return 0;
805
+ const rank = (value) => value === null ? 0 : typeof value === "string" ? 1 : typeof value === "number" ? 2 : typeof value === "boolean" ? 3 : Array.isArray(value) ? 4 : 5;
806
+ const difference = rank(left) - rank(right);
807
+ if (difference !== 0)
808
+ return difference;
809
+ if (typeof left === "number" && typeof right === "number")
810
+ return left - right;
811
+ if (typeof left === "string" && typeof right === "string")
812
+ return left < right ? -1 : 1;
813
+ if (typeof left === "boolean" && typeof right === "boolean")
814
+ return Number(left) - Number(right);
815
+ if (Array.isArray(left) && Array.isArray(right)) {
816
+ if (left.length !== right.length)
817
+ return left.length - right.length;
818
+ for (let index = 0; index < left.length; index += 1) {
819
+ const order = compareJsonStructure(left[index], right[index]);
820
+ if (order !== 0)
821
+ return order;
822
+ }
823
+ return 0;
824
+ }
825
+ const a = left;
826
+ const b = right;
827
+ const encoder = new TextEncoder();
828
+ const keyOrder = (x, y) => encoder.encode(x).length - encoder.encode(y).length || (x === y ? 0 : x < y ? -1 : 1);
829
+ const keysA = Object.keys(a).sort(keyOrder);
830
+ const keysB = Object.keys(b).sort(keyOrder);
831
+ if (keysA.length !== keysB.length)
832
+ return keysA.length - keysB.length;
833
+ for (let index = 0; index < keysA.length; index += 1) {
834
+ const x = keysA[index] ?? "";
835
+ const y = keysB[index] ?? "";
836
+ if (x !== y)
837
+ return x < y ? -1 : 1;
838
+ const order = compareJsonStructure(a[x], b[y]);
839
+ if (order !== 0)
840
+ return order;
841
+ }
842
+ return 0;
843
+ }
725
844
  export {
726
845
  arrayDomainValue,
727
846
  boundedJsonText,
@@ -751,6 +870,8 @@ export {
751
870
  normalizeSqlDomainValue,
752
871
  preservedJsonDomainValue,
753
872
  protectedSqlTextValue,
873
+ structuredDomainCompare,
874
+ temporalDomainPart,
754
875
  timeDomainValue,
755
876
  uuidDomainValue
756
877
  };
@@ -1,7 +1,7 @@
1
1
  import { dateMilliseconds } from "../date-value.js";
2
2
  import { assertWellFormedString } from "../block-format/unicode.js";
3
3
  import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS } from "./cache-limits.js";
4
- import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, intervalDomainCompare, isDateDomainValue, isSqlDomainValue } from "./sql-domains.js";
4
+ import { structuredDomainCompare, collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, intervalDomainCompare, isDateDomainValue, isSqlDomainValue } from "./sql-domains.js";
5
5
  const SQL_TIMESTAMP_TEXT = /^(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?))?(Z|[+-]\d{2}:?\d{2})?$/;
6
6
  function parseSqlTimestampText(text) {
7
7
  const match = SQL_TIMESTAMP_TEXT.exec(text.trim());
@@ -33,9 +33,9 @@ function readUntypedText(type, text) {
33
33
  }
34
34
  if (type === "boolean") {
35
35
  const lowered = text.trim().toLowerCase();
36
- if (lowered === "t" || lowered === "true" || lowered === "1")
36
+ if (/^(?:t(?:r(?:u(?:e)?)?)?|y(?:e(?:s)?)?|on|1)$/.test(lowered))
37
37
  return true;
38
- if (lowered === "f" || lowered === "false" || lowered === "0")
38
+ if (/^(?:f(?:a(?:l(?:s(?:e)?)?)?)?|n(?:o)?|of(?:f)?|0)$/.test(lowered))
39
39
  return false;
40
40
  }
41
41
  return text;
@@ -72,6 +72,9 @@ function compareSqlValues(left, right) {
72
72
  }
73
73
  return compareSqlStrings(plainLeft, plainRight);
74
74
  }
75
+ const structured = structuredDomainCompare(left, right);
76
+ if (structured !== void 0)
77
+ return structured;
75
78
  const collated = collatedDomainCompare(left, right);
76
79
  if (collated !== void 0)
77
80
  return collated;
@@ -347,7 +350,7 @@ function generalLikeMatcher(tokens, caseInsensitive) {
347
350
  }
348
351
  function compileLikePattern(pattern, caseInsensitive = false, escape) {
349
352
  assertBoundedPattern(pattern, "LIKE pattern");
350
- const exactEscape = escape === void 0 ? void 0 : exactSingleCharacter(escape, "LIKE escape");
353
+ const exactEscape = escape === void 0 || escape === "" ? void 0 : exactSingleCharacter(escape, "LIKE escape");
351
354
  const key = JSON.stringify([caseInsensitive, exactEscape ?? null, pattern]);
352
355
  const cached = cachedPattern(likeCache, key);
353
356
  if (cached !== void 0)
@@ -6,7 +6,7 @@ function reconcileRows(previous, next, retained) {
6
6
  for (let index = 0; index < next.length; index += 1) {
7
7
  const row = next[index];
8
8
  const was = provenance?.[index] ?? -1;
9
- if (was >= 0 && was < previous.length) {
9
+ if (was >= 0 && was < previous.length && sameLiveValue(previous[was], row)) {
10
10
  rows[index] = previous[was];
11
11
  if (was !== index)
12
12
  changed = true;
@@ -38,6 +38,7 @@ class LiveQuery {
38
38
  #executionAbort;
39
39
  #invalidationSequence = 0;
40
40
  #closed = false;
41
+ #refreshLeases = 0;
41
42
  constructor(backend, source, onClose) {
42
43
  this.#backend = backend;
43
44
  this.#source = source;
@@ -57,32 +58,39 @@ class LiveQuery {
57
58
  return;
58
59
  subscribed = false;
59
60
  this.#listeners.delete(registered);
60
- if (this.#listeners.size === 0)
61
+ if (this.#listeners.size === 0 && this.#refreshLeases === 0)
61
62
  this.#stopObservation();
62
63
  };
63
64
  };
64
65
  async refresh() {
65
66
  if (this.#closed)
66
67
  throw new Error("Live query is closed");
67
- const sequence = this.#invalidationSequence;
68
- if (this.#listeners.size > 0) {
69
- if (this.#subscription === void 0)
70
- this.#startObservation();
71
- const opening = this.#subscription;
72
- if (opening !== void 0)
73
- await opening.catch(() => void 0);
74
- await this.#backend.refresh();
75
- }
76
- if (this.#invalidationSequence === sequence) {
77
- const version = this.#snapshot.status === "loading" ? null : this.#snapshot.version;
78
- if (this.#decodes()) {
79
- const last = this.#lastDelivered;
80
- if (this.#snapshot.status === "error" && last !== void 0)
81
- this.#schedule(last);
82
- } else
83
- this.#schedule({ manifestVersion: version, catalogEpoch: 0, initial: false });
68
+ this.#refreshLeases += 1;
69
+ try {
70
+ const sequence = this.#invalidationSequence;
71
+ if (this.#listeners.size > 0 || this.#decodes()) {
72
+ if (this.#subscription === void 0)
73
+ this.#startObservation();
74
+ const opening = this.#subscription;
75
+ if (opening !== void 0)
76
+ await opening.catch(() => void 0);
77
+ await this.#backend.refresh();
78
+ }
79
+ if (this.#invalidationSequence === sequence) {
80
+ const version = this.#snapshot.status === "loading" ? null : this.#snapshot.version;
81
+ if (this.#decodes()) {
82
+ const last = this.#lastDelivered;
83
+ if (this.#snapshot.status === "error" && last !== void 0)
84
+ this.#schedule(last);
85
+ } else
86
+ this.#schedule({ manifestVersion: version, catalogEpoch: 0, initial: false });
87
+ }
88
+ await this.#waitForIdle();
89
+ } finally {
90
+ this.#refreshLeases -= 1;
91
+ if (this.#refreshLeases === 0 && this.#listeners.size === 0)
92
+ this.#stopObservation();
84
93
  }
85
- await this.#waitForIdle();
86
94
  }
87
95
  #decodes() {
88
96
  return this.#source.decode !== void 0 && this.#backend.subscribe !== void 0;
@@ -12,6 +12,8 @@ export interface VectorWindow {
12
12
  readonly length: number;
13
13
  }
14
14
  interface VectorBase {
15
+ /** Present on immutable block vectors, so keyed reads need no decoded-block lookup. */
16
+ readonly nullCount?: number;
15
17
  readonly validity: Uint8Array;
16
18
  readonly length: number;
17
19
  readonly window?: VectorWindow;
@@ -110,5 +112,7 @@ interface PrepareVectorQueryOptions {
110
112
  export declare function createColumnarTable(name: string, columns: ReadonlyMap<string, ColumnarColumnInput>, uniqueKey?: string): ColumnarTable;
111
113
  export declare function columnarTableFromRows(name: string, rows: readonly DatabaseRow[], projectedColumnNames?: readonly string[], protectText?: boolean): ColumnarTable;
112
114
  export declare function prepareVectorQuery(plan: CompiledQuery, inputTables: ReadonlyMap<string, ColumnarTable>, options?: PrepareVectorQueryOptions): PreparedVectorQuery;
115
+ /** Builds directly from a row/column reader without retaining an intermediate value array. */
116
+ export declare function createColumnVector(type: VectorType, length: number, valueAt: (index: number) => unknown): ColumnVector;
113
117
  export declare function vectorValue(vector: ColumnVector, rowIndex: number): QueryValue;
114
118
  export {};
@@ -90,7 +90,7 @@ function createColumnarTable(name, columns, uniqueKey) {
90
90
  if (column.values.length !== rowCount) {
91
91
  throw new Error(`Column row count mismatch: ${name}.${columnName}`);
92
92
  }
93
- vectors.set(columnName, createVector(column));
93
+ vectors.set(columnName, createColumnVector(column.type, rowCount, (index) => column.values[index]));
94
94
  }
95
95
  if (uniqueKey !== void 0 && !vectors.has(uniqueKey)) {
96
96
  throw new Error(`Unique-key vector is missing: ${name}.${uniqueKey}`);
@@ -205,21 +205,20 @@ function prepareVectorQuery(plan, inputTables, options = {}) {
205
205
  throw error;
206
206
  }
207
207
  }
208
- function createVector(input) {
209
- validateVectorType(input.type);
210
- const length = input.values.length;
208
+ function createColumnVector(type, length, valueAt) {
209
+ validateVectorType(type);
211
210
  const validity = new Uint8Array(Math.ceil(length / 8));
212
- if (input.type === "string") {
211
+ if (type === "string") {
213
212
  const dictionary = [];
214
213
  const dictionaryIndex = /* @__PURE__ */ new Map();
215
214
  const codes = new Uint32Array(length);
216
215
  codes.fill(NULL_STRING_CODE);
217
216
  for (let index = 0; index < length; index += 1) {
218
- const value = input.values[index];
217
+ const value = valueAt(index);
219
218
  if (value === null)
220
219
  continue;
221
220
  if (typeof value !== "string")
222
- throw vectorTypeError(input.type, value);
221
+ throw vectorTypeError(type, value);
223
222
  setValid(validity, index);
224
223
  let code = dictionaryIndex.get(value);
225
224
  if (code === void 0) {
@@ -231,14 +230,14 @@ function createVector(input) {
231
230
  }
232
231
  return { kind: "string", length, validity, codes, dictionary };
233
232
  }
234
- if (input.type === "boolean") {
233
+ if (type === "boolean") {
235
234
  const values2 = new Uint8Array(length);
236
235
  for (let index = 0; index < length; index += 1) {
237
- const value = input.values[index];
236
+ const value = valueAt(index);
238
237
  if (value === null)
239
238
  continue;
240
239
  if (typeof value !== "boolean")
241
- throw vectorTypeError(input.type, value);
240
+ throw vectorTypeError(type, value);
242
241
  setValid(validity, index);
243
242
  values2[index] = value ? 1 : 0;
244
243
  }
@@ -246,16 +245,16 @@ function createVector(input) {
246
245
  }
247
246
  const values = new Float64Array(length);
248
247
  for (let index = 0; index < length; index += 1) {
249
- const value = input.values[index];
248
+ const value = valueAt(index);
250
249
  if (value === null)
251
250
  continue;
252
- const numericValue = input.type === "datetime" ? value instanceof Date ? dateMilliseconds(value) : Number.NaN : typeof value === "number" ? value : Number.NaN;
251
+ const numericValue = type === "datetime" ? value instanceof Date ? dateMilliseconds(value) : Number.NaN : typeof value === "number" ? value : Number.NaN;
253
252
  if (!Number.isFinite(numericValue))
254
- throw vectorTypeError(input.type, value);
253
+ throw vectorTypeError(type, value);
255
254
  setValid(validity, index);
256
255
  values[index] = numericValue;
257
256
  }
258
- return input.type === "datetime" ? { kind: "datetime", length, validity, values } : { kind: "number", length, validity, values };
257
+ return type === "datetime" ? { kind: "datetime", length, validity, values } : { kind: "number", length, validity, values };
259
258
  }
260
259
  function validateVectorType(type) {
261
260
  if (type !== "boolean" && type !== "number" && type !== "string" && type !== "datetime") {
@@ -4876,6 +4875,7 @@ function safeMemoryProduct(left, right, label) {
4876
4875
  }
4877
4876
  export {
4878
4877
  columnarTableFromRows,
4878
+ createColumnVector,
4879
4879
  createColumnarTable,
4880
4880
  prepareVectorQuery,
4881
4881
  vectorValue
@@ -0,0 +1,12 @@
1
+ import type { QueryResult, WindowSpec } from "../plan/model.js";
2
+ import { QueryMemoryContext } from "./memory.js";
3
+ interface WindowOptions {
4
+ copyRows?: boolean;
5
+ memoryContext?: QueryMemoryContext;
6
+ signal?: AbortSignal;
7
+ }
8
+ /** Synchronous executor used by standalone queries; shares every kernel with asynchronous reads. */
9
+ export declare function applyWindowFunctions(result: QueryResult, windows: readonly WindowSpec[], options?: WindowOptions): QueryResult;
10
+ /** Cooperatively runs window passes so cancellation can arrive while a large partition is active. */
11
+ export declare function applyWindowFunctionsAsync(result: QueryResult, windows: readonly WindowSpec[], options?: WindowOptions): Promise<QueryResult>;
12
+ export {};