@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
@@ -3,6 +3,25 @@
3
3
  * them (name, fields, instanceof) without bundling the executor.
4
4
  */
5
5
  type ErrorValue = boolean | number | string | Date;
6
+ /** An idle SQL transaction rolled back; later statements must not become autocommits. */
7
+ export declare class TransactionExpiredError extends Error {
8
+ readonly name = "TransactionExpiredError";
9
+ constructor();
10
+ }
11
+ /** The worker stopped responding within the configured request deadline. */
12
+ export declare class DatabaseWorkerTimeoutError extends Error {
13
+ readonly method: string;
14
+ readonly timeoutMs: number;
15
+ readonly name = "DatabaseWorkerTimeoutError";
16
+ constructor(method: string, timeoutMs: number);
17
+ }
18
+ /** A transport failure cannot prove whether this operation published before its reply was lost. */
19
+ export declare class DatabaseWorkerOutcomeUnknownError extends Error {
20
+ readonly method: string;
21
+ readonly requestId: string;
22
+ readonly name = "DatabaseWorkerOutcomeUnknownError";
23
+ constructor(method: string, requestId: string, options?: ErrorOptions);
24
+ }
6
25
  /** A database operation named a table that is not present in the current catalog. */
7
26
  export declare class UnknownTableError extends TypeError {
8
27
  readonly tableName: string;
@@ -17,10 +36,10 @@ export declare class DatabaseReadBacklogError extends Error {
17
36
  }
18
37
  /** A live-query owner reached one of its documented resident-resource ceilings. */
19
38
  export declare class LiveQueryLimitError extends Error {
20
- readonly resource: "set" | "group" | "subscription";
39
+ readonly resource: "set" | "group" | "subscription" | "byte";
21
40
  readonly limit: number;
22
41
  readonly name = "LiveQueryLimitError";
23
- constructor(resource: "set" | "group" | "subscription", limit: number);
42
+ constructor(resource: "set" | "group" | "subscription" | "byte", limit: number);
24
43
  }
25
44
  export declare class UniqueConstraintError extends Error {
26
45
  readonly tableName: string;
@@ -1,3 +1,29 @@
1
+ class TransactionExpiredError extends Error {
2
+ name = "TransactionExpiredError";
3
+ constructor() {
4
+ super("The SQL transaction expired and rolled back; issue ROLLBACK or BEGIN before continuing");
5
+ }
6
+ }
7
+ class DatabaseWorkerTimeoutError extends Error {
8
+ method;
9
+ timeoutMs;
10
+ name = "DatabaseWorkerTimeoutError";
11
+ constructor(method, timeoutMs) {
12
+ super(`Database worker ${method} did not respond within ${String(timeoutMs)}ms`);
13
+ this.method = method;
14
+ this.timeoutMs = timeoutMs;
15
+ }
16
+ }
17
+ class DatabaseWorkerOutcomeUnknownError extends Error {
18
+ method;
19
+ requestId;
20
+ name = "DatabaseWorkerOutcomeUnknownError";
21
+ constructor(method, requestId, options) {
22
+ super(`Database worker outcome is unknown for ${method}; reconcile durable application IDs before retrying`, options);
23
+ this.method = method;
24
+ this.requestId = requestId;
25
+ }
26
+ }
1
27
  function formatValue(value) {
2
28
  return value instanceof Date ? dateIsoString(value) : String(value);
3
29
  }
@@ -22,7 +48,7 @@ class LiveQueryLimitError extends Error {
22
48
  limit;
23
49
  name = "LiveQueryLimitError";
24
50
  constructor(resource, limit) {
25
- super(resource === "set" ? `A database cannot retain more than ${String(limit)} live-query sets` : `A live-query set cannot retain more than ${String(limit)} ${resource} records`);
51
+ super(resource === "set" ? `A database cannot retain more than ${String(limit)} live-query sets` : resource === "byte" ? `A live-query set cannot retain more than ${String(limit)} modeled bytes` : `A live-query set cannot retain more than ${String(limit)} ${resource} records`);
26
52
  this.resource = resource;
27
53
  this.limit = limit;
28
54
  }
@@ -117,10 +143,13 @@ export {
117
143
  CompactionMemoryBudgetError,
118
144
  CompactionWriteAmplificationError,
119
145
  DatabaseReadBacklogError,
146
+ DatabaseWorkerOutcomeUnknownError,
147
+ DatabaseWorkerTimeoutError,
120
148
  LiveQueryLimitError,
121
149
  MaintenanceBacklogError,
122
150
  MissingKeyError,
123
151
  SqlCompileError,
152
+ TransactionExpiredError,
124
153
  UniqueConstraintError,
125
154
  UnknownTableError,
126
155
  VisibleSegmentCursorStaleError
@@ -17,3 +17,4 @@ export type * from "./schema.js";
17
17
  export type { InsertValue, CompiledQuery, CompiledStatement, QueryResult, QueryRow, QueryExecutionOptions, QueryValue, SqlColumnSchema, SqlColumnType, } from "./query.js";
18
18
  export type { AsyncQueryExecutionOptions, QueryBatchExecutionOptions, QuerySpillStore, } from "./vector.js";
19
19
  export type { MinnowSqlDriver, MinnowSqlExecutor } from "./sql-driver.js";
20
+ export { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, } from "../storage/types.js";
@@ -4,6 +4,7 @@ export * from "./errors.js";
4
4
  import { QueryMemoryBudgetError } from "./memory.js";
5
5
  import { MAX_SQL_NESTING_DEPTH, MAX_SQL_NUMERIC_DIGITS, MAX_SQL_PARAMETERS, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_STRUCTURED_VALUE_DEPTH, MAX_SQL_STRUCTURED_VALUE_ITEMS, MAX_SQL_TEXT_CHARACTERS, MAX_SQL_TOKENS } from "./cache-limits.js";
6
6
  import { column, foreignKeyName, isDestructiveStep, planMigration, schema, table, view } from "./schema.js";
7
+ import { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError } from "../storage/types.js";
7
8
  export {
8
9
  MAX_SQL_NESTING_DEPTH,
9
10
  MAX_SQL_NUMERIC_DIGITS,
@@ -15,6 +16,9 @@ export {
15
16
  MAX_SQL_STRUCTURED_VALUE_ITEMS,
16
17
  MAX_SQL_TEXT_CHARACTERS,
17
18
  MAX_SQL_TOKENS,
19
+ OpfsCoordinationError,
20
+ OpfsDatabaseInUseError,
21
+ OpfsUncertainOutcomeError,
18
22
  QueryMemoryBudgetError,
19
23
  column,
20
24
  foreignKeyName,
@@ -0,0 +1,13 @@
1
+ const publications = /* @__PURE__ */ new WeakMap();
2
+ function stageLiveExecution(execution, publish) {
3
+ publications.set(execution, publish);
4
+ return execution;
5
+ }
6
+ function acceptLiveExecution(execution) {
7
+ publications.get(execution)?.();
8
+ publications.delete(execution);
9
+ }
10
+ export {
11
+ acceptLiveExecution,
12
+ stageLiveExecution
13
+ };
@@ -0,0 +1,264 @@
1
+ import { childExpressions, hasAggregate, mapChildExpressions, executeRowQueryInternal, inferResultColumnDomains, externalizeQueryResult } from "./query.js";
2
+ import { encodeQueryIdentity } from "./query-identity.js";
3
+ import { exactNumericBinary, exactNumericValue, externalSqlDomainValue, isExactNumeric } from "./sql-domains.js";
4
+ import { encodeSqlEqualityValue } from "./sql-semantics.js";
5
+ function contributionBytes(key, row) {
6
+ return 64 + key.length * 2 + row.group.length * 2 + row.values.reduce((sum, value) => sum + (typeof value === "string" ? value.length * 2 + 16 : 16), 0);
7
+ }
8
+ function groupBytes(key, group) {
9
+ return 96 + key.length * 2 + group.sums.reduce((sum, value) => sum + value.length * 2 + 24, 0);
10
+ }
11
+ class LiveAggregate {
12
+ inputPlan;
13
+ #outputPlan;
14
+ #aggregates;
15
+ #groupAliases;
16
+ #contributions;
17
+ #revision;
18
+ #pending;
19
+ #bytes;
20
+ #groups;
21
+ #domains;
22
+ keyAlias;
23
+ #schema;
24
+ constructor(inputPlan, outputPlan, aggregates, groupAliases, keyAlias, contributions = { rows: /* @__PURE__ */ new Map(), revision: 0 }, groups = /* @__PURE__ */ new Map(), domains = [], schema = [], bytes = 256 + encodeQueryIdentity(inputPlan).length * 2 + encodeQueryIdentity(outputPlan).length * 2, pending) {
25
+ this.inputPlan = inputPlan;
26
+ this.#outputPlan = outputPlan;
27
+ this.#aggregates = aggregates;
28
+ this.#groupAliases = groupAliases;
29
+ this.keyAlias = keyAlias;
30
+ this.#contributions = contributions;
31
+ this.#revision = contributions.revision;
32
+ this.#pending = pending;
33
+ this.#bytes = bytes;
34
+ this.#groups = groups;
35
+ this.#domains = domains;
36
+ this.#schema = schema;
37
+ }
38
+ static plan(plan, qualifiedKey) {
39
+ const aggregates = [];
40
+ const groupAliases = plan.groupBy.map((_, index) => `__minnow_live_group_${String(index)}`);
41
+ const groupKeys = new Map(plan.groupBy.map((expression, index) => [
42
+ encodeQueryIdentity(expression),
43
+ groupAliases[index] ?? ""
44
+ ]));
45
+ const rowLocal = (expression) => !hasAggregate(expression) && !["subquery", "exists", "window", "parameter", "wildcard"].includes(expression.kind) && childExpressions(expression).every(rowLocal);
46
+ if (!plan.groupBy.every(rowLocal))
47
+ return void 0;
48
+ const rewrite = (expression) => {
49
+ const grouped = groupKeys.get(encodeQueryIdentity(expression));
50
+ if (grouped !== void 0)
51
+ return { kind: "column", reference: grouped };
52
+ if (expression.kind === "call" && hasAggregate(expression)) {
53
+ if (!["COUNT", "SUM", "AVG"].includes(expression.name) || expression.arguments.length !== 1 || expression.distinct === true)
54
+ throw new TypeError("Not an additive live aggregate");
55
+ const argument = expression.arguments[0];
56
+ if (argument === void 0 || !(rowLocal(argument) || expression.name === "COUNT" && argument.kind === "wildcard"))
57
+ throw new TypeError("Not a row-local aggregate argument");
58
+ const alias = `__minnow_live_value_${String(aggregates.length)}`;
59
+ aggregates.push({
60
+ name: expression.name,
61
+ alias,
62
+ argument: argument.kind === "wildcard" ? { kind: "literal", value: 1 } : argument
63
+ });
64
+ return { kind: "column", reference: alias };
65
+ }
66
+ if (expression.kind === "column" || expression.kind === "window" || expression.kind === "subquery" || expression.kind === "exists")
67
+ throw new TypeError("Not a grouped live expression");
68
+ return mapChildExpressions(expression, rewrite);
69
+ };
70
+ try {
71
+ const select = plan.select.map((item) => ({ ...item, expression: rewrite(item.expression) }));
72
+ const having = plan.having.map((predicate) => ({
73
+ ...predicate,
74
+ left: rewrite(predicate.left),
75
+ right: rewrite(predicate.right)
76
+ }));
77
+ const outputAliases = new Set(select.map((item) => item.alias));
78
+ const orderBy = plan.orderBy.map((term) => ({
79
+ ...term,
80
+ expression: term.expression.kind === "column" && outputAliases.has(term.expression.reference) ? term.expression : rewrite(term.expression)
81
+ }));
82
+ if (aggregates.length === 0)
83
+ return void 0;
84
+ const keyAlias = "__minnow_live_aggregate_key";
85
+ const inputPlan = {
86
+ ...plan,
87
+ select: [
88
+ { alias: keyAlias, expression: { kind: "column", reference: qualifiedKey } },
89
+ ...plan.groupBy.map((expression, index) => ({
90
+ alias: groupAliases[index] ?? "",
91
+ expression
92
+ })),
93
+ ...aggregates.map(({ alias, argument }) => ({ alias, expression: argument }))
94
+ ],
95
+ groupBy: [],
96
+ having: [],
97
+ orderBy: []
98
+ };
99
+ delete inputPlan.limit;
100
+ delete inputPlan.offset;
101
+ const outputPlan = {
102
+ ...plan,
103
+ base: { table: "__minnow_live_groups", alias: "__minnow_live_groups" },
104
+ joins: [],
105
+ select,
106
+ predicates: having,
107
+ groupBy: [],
108
+ having: [],
109
+ orderBy
110
+ };
111
+ return new LiveAggregate(inputPlan, outputPlan, aggregates, groupAliases, keyAlias);
112
+ } catch {
113
+ return void 0;
114
+ }
115
+ }
116
+ patch(result, changed, token) {
117
+ if (this.#pending !== void 0 || this.#revision !== this.#contributions.revision)
118
+ throw new Error("Unaccepted or stale aggregate");
119
+ const rows = this.#contributions.rows;
120
+ const pending = /* @__PURE__ */ new Map();
121
+ let bytes = this.#bytes;
122
+ const groups = new Map(this.#groups);
123
+ const touched = /* @__PURE__ */ new Set();
124
+ const groupFor = (key, keys = []) => {
125
+ let group = groups.get(key);
126
+ if (group !== void 0 && !touched.has(key))
127
+ bytes -= groupBytes(key, group);
128
+ if (group === void 0) {
129
+ group = {
130
+ keys,
131
+ members: 0,
132
+ counts: this.#aggregates.map(() => 0),
133
+ sums: this.#aggregates.map(() => exactNumericValue(0) ?? ""),
134
+ absolute: this.#aggregates.map(() => 0)
135
+ };
136
+ groups.set(key, group);
137
+ touched.add(key);
138
+ } else if (!touched.has(key)) {
139
+ group = {
140
+ ...group,
141
+ counts: [...group.counts],
142
+ sums: [...group.sums],
143
+ absolute: [...group.absolute]
144
+ };
145
+ groups.set(key, group);
146
+ touched.add(key);
147
+ }
148
+ return group;
149
+ };
150
+ const apply = (group, values, sign) => {
151
+ group.members += sign;
152
+ for (const [index, aggregate] of this.#aggregates.entries()) {
153
+ const value = values[index] ?? null;
154
+ if (value === null)
155
+ continue;
156
+ group.counts[index] = (group.counts[index] ?? 0) + sign;
157
+ if (aggregate.name !== "COUNT") {
158
+ if (typeof value !== "number" && !isExactNumeric(value))
159
+ throw new TypeError("Live SUM requires numeric values");
160
+ if (typeof value === "number") {
161
+ if (!Number.isSafeInteger(value))
162
+ throw new TypeError("Floating-point aggregates require full execution");
163
+ group.absolute[index] = (group.absolute[index] ?? 0) + sign * Math.abs(value);
164
+ if (!Number.isSafeInteger(group.absolute[index]))
165
+ throw new TypeError("Aggregate sum may round; requires full execution");
166
+ }
167
+ const sum = exactNumericBinary(sign === 1 ? "+" : "-", group.sums[index] ?? exactNumericValue(0), exactNumericValue(value));
168
+ if (sum === null || sum === void 0)
169
+ throw new TypeError("Invalid numeric aggregate contribution");
170
+ group.sums[index] = sum;
171
+ }
172
+ }
173
+ };
174
+ for (const key of changed) {
175
+ const old = rows.get(key);
176
+ if (old === void 0)
177
+ continue;
178
+ apply(groupFor(old.group), old.values, -1);
179
+ pending.set(key, void 0);
180
+ bytes -= contributionBytes(key, old);
181
+ }
182
+ for (const row of result.rows) {
183
+ const key = token(row[this.keyAlias] ?? null);
184
+ if (pending.get(key) !== void 0 || rows.has(key) && !changed.has(key))
185
+ throw new TypeError("Duplicate live input key");
186
+ const keys = this.#groupAliases.map((alias) => row[alias] ?? null);
187
+ const group = JSON.stringify(keys.map(encodeSqlEqualityValue));
188
+ const values = this.#aggregates.map(({ alias }) => row[alias] ?? null);
189
+ apply(groupFor(group, keys), values, 1);
190
+ const contribution = { group, values };
191
+ pending.set(key, contribution);
192
+ bytes += contributionBytes(key, contribution);
193
+ }
194
+ if (this.#groupAliases.length === 0)
195
+ groupFor("[]");
196
+ else
197
+ for (const key of touched)
198
+ if (groups.get(key)?.members === 0)
199
+ groups.delete(key);
200
+ for (const key of touched) {
201
+ const group = groups.get(key);
202
+ if (group !== void 0)
203
+ bytes += groupBytes(key, group);
204
+ }
205
+ const domains = this.#aggregates.map(({ alias }, index) => result.columnDomains[result.columns.indexOf(alias)] ?? this.#domains[index] ?? null);
206
+ return new LiveAggregate(this.inputPlan, this.#outputPlan, this.#aggregates, this.#groupAliases, this.keyAlias, this.#contributions, groups, domains, result.columns.map((name, index) => {
207
+ const domain = result.columnDomains[index];
208
+ const value = result.rows.find((row) => row[name] !== null)?.[name];
209
+ const aggregate = this.#aggregates.find((item) => item.alias === name);
210
+ return {
211
+ name,
212
+ type: aggregate?.name === "COUNT" ? "number" : domain !== null && domain !== void 0 ? "string" : typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : value instanceof Date ? "datetime" : value === void 0 || value === null ? this.#schema[index]?.type ?? "string" : "string",
213
+ ...domain === null || domain === void 0 || aggregate?.name === "COUNT" ? {} : { sqlDomain: domain }
214
+ };
215
+ }), bytes, pending);
216
+ }
217
+ accept() {
218
+ const pending = this.#pending;
219
+ if (pending === void 0)
220
+ return;
221
+ if (this.#revision !== this.#contributions.revision)
222
+ throw new Error("Stale aggregate");
223
+ for (const [key, contribution] of pending) {
224
+ if (contribution === void 0)
225
+ this.#contributions.rows.delete(key);
226
+ else
227
+ this.#contributions.rows.set(key, contribution);
228
+ }
229
+ this.#revision = ++this.#contributions.revision;
230
+ this.#pending = void 0;
231
+ }
232
+ result() {
233
+ const rows = [];
234
+ for (const group of this.#groups.values()) {
235
+ const row = {};
236
+ for (const [index, alias] of this.#groupAliases.entries())
237
+ row[alias] = group.keys[index] ?? null;
238
+ for (const [index, aggregate] of this.#aggregates.entries()) {
239
+ const count = group.counts[index] ?? 0;
240
+ const domain = this.#domains[index];
241
+ let value = aggregate.name === "COUNT" ? count : count === 0 ? null : group.sums[index] ?? null;
242
+ if (value !== null && aggregate.name !== "COUNT") {
243
+ if (domain?.kind !== "numeric") {
244
+ value = Number(externalSqlDomainValue(value));
245
+ if (aggregate.name === "AVG")
246
+ value /= count;
247
+ } else if (aggregate.name === "AVG")
248
+ value = exactNumericBinary("/", value, count, domain.scale) ?? null;
249
+ }
250
+ row[aggregate.alias] = value;
251
+ }
252
+ rows.push(row);
253
+ }
254
+ const result = executeRowQueryInternal(this.#outputPlan, /* @__PURE__ */ new Map([["__minnow_live_groups", rows]]));
255
+ result.columnDomains = inferResultColumnDomains(this.#outputPlan, /* @__PURE__ */ new Map([["__minnow_live_groups", this.#schema]]));
256
+ return externalizeQueryResult(result);
257
+ }
258
+ get retainedBytes() {
259
+ return this.#bytes;
260
+ }
261
+ }
262
+ export {
263
+ LiveAggregate
264
+ };
@@ -0,0 +1,21 @@
1
+ import type { QueryResult, QueryRow } from "../plan/model.js";
2
+ import type { LiveQueryDelivery } from "./live.js";
3
+ export type LiveQueryPatch = {
4
+ readonly type: "reset";
5
+ readonly result: QueryResult;
6
+ } | {
7
+ readonly type: "patch";
8
+ /** Each next position's previous position, or -1 for a changed/new row. */
9
+ readonly retained: Int32Array;
10
+ readonly changedRows: ReadonlyArray<{
11
+ readonly index: number;
12
+ readonly row: QueryRow;
13
+ }>;
14
+ };
15
+ export interface LiveQueryPatchOptions {
16
+ onPatch(patch: LiveQueryPatch, delivery: LiveQueryDelivery): void;
17
+ onError?(error: unknown): void;
18
+ onComplete?(): void;
19
+ }
20
+ /** Copy only changed payloads when provenance is available; resets establish a fresh baseline. */
21
+ export declare function createLiveQueryPatch(result: QueryResult, delivery: LiveQueryDelivery): LiveQueryPatch;
@@ -0,0 +1,31 @@
1
+ import { copyDate } from "../date-value.js";
2
+ function copyRow(row) {
3
+ const copy = { ...row };
4
+ for (const key of Object.keys(copy)) {
5
+ const value = copy[key];
6
+ if (value instanceof Date)
7
+ copy[key] = copyDate(value);
8
+ }
9
+ return copy;
10
+ }
11
+ function createLiveQueryPatch(result, delivery) {
12
+ if (delivery.retained === void 0 || delivery.initial)
13
+ return {
14
+ type: "reset",
15
+ result: {
16
+ columns: [...result.columns],
17
+ columnDomains: structuredClone(result.columnDomains),
18
+ rows: result.rows.map(copyRow)
19
+ }
20
+ };
21
+ const changedRows = [];
22
+ for (let index = 0; index < result.rows.length; index += 1) {
23
+ const row = result.rows[index];
24
+ if (row !== void 0 && (delivery.retained[index] ?? -1) < 0)
25
+ changedRows.push({ index, row: copyRow(row) });
26
+ }
27
+ return { type: "patch", retained: new Int32Array(delivery.retained), changedRows };
28
+ }
29
+ export {
30
+ createLiveQueryPatch
31
+ };
@@ -1,3 +1,5 @@
1
+ import { type LiveQueryPatchOptions } from "./live-patch.js";
2
+ export type { LiveQueryPatch, LiveQueryPatchOptions } from "./live-patch.js";
1
3
  import { type CatalogProbe, type Manifest, type StoragePage } from "../storage/types.js";
2
4
  import { type CompiledQuery, type QueryResult, type QueryValue } from "./query.js";
3
5
  /**
@@ -27,6 +29,8 @@ export interface LiveQuerySetOptions {
27
29
  readonly maxGroups?: number;
28
30
  /** Maximum result/observer subscriptions retained by this set. Defaults to 1,024. */
29
31
  readonly maxSubscriptions?: number;
32
+ /** Maximum modeled resident result and maintenance bytes per set. Defaults to 64 MiB. */
33
+ readonly maxRetainedBytes?: number;
30
34
  /**
31
35
  * Hand `onChange` the set's retained result instead of a private copy. The result is shared
32
36
  * with every equal subscription and with the next change comparison, so a subscriber must
@@ -64,6 +68,8 @@ export interface LiveQueryDelivery {
64
68
  readonly retained?: Int32Array;
65
69
  }
66
70
  export interface LiveQuerySubscribeOptions {
71
+ /** Borrow the read-only retained result for this subscription instead of copying it. */
72
+ readonly sharedResults?: boolean;
67
73
  onChange(result: QueryResult, delivery: LiveQueryDelivery): void;
68
74
  onError?(error: unknown): void;
69
75
  /** Called once when the subscription ends because the subscription or its set closed. */
@@ -114,6 +120,8 @@ export interface LiveQueryStats {
114
120
  * bounded by the engine at 64 rows and is not included.
115
121
  */
116
122
  retainedRows: number;
123
+ /** Modeled resident result and incremental maintenance bytes, counted once per group. */
124
+ retainedBytes: number;
117
125
  /** Work avoided because equal statements shared one query group or in-flight execution. */
118
126
  sharedExecutions: number;
119
127
  lastSweepMs: number;
@@ -146,6 +154,8 @@ export interface LiveQueryExecuteContext {
146
154
  export interface LiveMaintainedExecution {
147
155
  readonly result: QueryResult;
148
156
  readonly state: unknown;
157
+ /** Modeled total bytes retained by the result and opaque maintenance state. */
158
+ readonly retainedBytes?: number;
149
159
  }
150
160
  export interface LiveMaintainedChange extends LiveMaintainedExecution {
151
161
  readonly changed: boolean;
@@ -159,6 +169,8 @@ export interface LiveQueryHost {
159
169
  dependencyTableIds(query: LiveQueryInput, probe?: CatalogProbe): Promise<Set<string>>;
160
170
  /** Executes the statement; the returned result belongs to the set and is never shared. */
161
171
  execute(query: LiveQueryInput, context?: LiveQueryExecuteContext): Promise<QueryResult>;
172
+ /** Optionally publishes an accepted result for adapters that re-execute after invalidation. */
173
+ memoize?(query: LiveQueryInput, result: QueryResult, probe: CatalogProbe): Promise<void>;
162
174
  /**
163
175
  * Executes a statement the host can later maintain incrementally, or returns undefined when
164
176
  * the statement's shape rules that out; the set then executes it in full from then on.
@@ -178,6 +190,8 @@ export declare class LiveQuerySet {
178
190
  get stats(): LiveQueryStats;
179
191
  /** Registers a query, delivers its current result, and shares work with equal statements. */
180
192
  subscribe(query: LiveQueryInput, options: LiveQuerySubscribeOptions): Promise<LiveQuerySubscription>;
193
+ /** Delivers resets or changed row payloads without constructing a private full row array per patch. */
194
+ subscribePatches(query: LiveQueryInput, options: LiveQueryPatchOptions): Promise<LiveQuerySubscription>;
181
195
  /** Observes invalidation; the statement executes inside the set only when asked to compare. */
182
196
  observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<LiveQuerySubscription>;
183
197
  /** Called by the owning database after each local write commit; also hints other tabs. */