@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.
- package/dist/engine/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +9 -0
- package/dist/engine/client.js +112 -20
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2199 -1675
- package/dist/engine/errors.d.ts +21 -2
- package/dist/engine/errors.js +30 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/live-accept.js +13 -0
- package/dist/engine/live-aggregate.js +264 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +14 -0
- package/dist/engine/live.js +146 -142
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +19 -15
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +7 -13
- package/dist/engine/query.js +298 -435
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.js +28 -20
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +387 -0
- package/dist/engine/worker-server.d.ts +1 -1
- package/dist/engine/worker-server.js +28 -15
- package/dist/engine/write-coordinator.js +54 -0
- package/dist/plan/model.d.ts +1 -1
- package/dist/storage/indexeddb.js +134 -89
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -1
- package/dist/storage/opfs/leader.js +20 -9
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +2 -0
- package/dist/storage/opfs/store.js +119 -43
- package/dist/storage/toolkit/record-core.js +2 -1
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.js +21 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.js +4 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +2 -2
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
- package/dist/date-value.d.ts +0 -20
- package/dist/engine/artifact-cache.d.ts +0 -29
- package/dist/engine/byte-estimates.d.ts +0 -11
- package/dist/engine/cancellation.d.ts +0 -2
- package/dist/engine/defaults.d.ts +0 -29
- package/dist/engine/group-index.d.ts +0 -33
- package/dist/engine/join-index.d.ts +0 -10
- package/dist/engine/live-equal.d.ts +0 -7
- package/dist/engine/point-read.d.ts +0 -59
- package/dist/engine/query-cache.d.ts +0 -21
- package/dist/engine/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -93
- package/dist/engine/sql-functions.d.ts +0 -11
- package/dist/engine/sql-json.d.ts +0 -40
- package/dist/engine/sql-semantics.d.ts +0 -66
- package/dist/engine/worker-store-indexeddb.d.ts +0 -2
- package/dist/engine/worker-store-memory.d.ts +0 -2
- package/dist/engine/worker-store-opfs.d.ts +0 -2
- package/dist/engine/write-block-planner.d.ts +0 -19
- package/dist/storage/opfs/leader.d.ts +0 -460
- package/dist/storage/opfs/rpc.d.ts +0 -82
- package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
|
@@ -52,11 +52,12 @@ class ArtifactCache {
|
|
|
52
52
|
return entry.payload;
|
|
53
53
|
}
|
|
54
54
|
put(key, payload, bytes) {
|
|
55
|
-
if (!this.enabled || bytes > this.#limitBytes)
|
|
56
|
-
return;
|
|
57
55
|
if (!Number.isSafeInteger(bytes) || bytes < 0) {
|
|
58
56
|
throw new RangeError("Artifact cache entry bytes must be a non-negative whole number");
|
|
59
57
|
}
|
|
58
|
+
bytes += 96 + key.length * 2;
|
|
59
|
+
if (!this.enabled || !Number.isSafeInteger(bytes) || bytes > this.#limitBytes)
|
|
60
|
+
return;
|
|
60
61
|
const existing = this.#entries.get(key);
|
|
61
62
|
if (existing !== void 0) {
|
|
62
63
|
this.#usedBytes -= existing.bytes;
|
|
@@ -47,6 +47,16 @@ class BufferedTableWriter {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
async flush() {
|
|
50
|
+
const adds = this.#addTail;
|
|
51
|
+
await adds;
|
|
52
|
+
let result;
|
|
53
|
+
if (this.#inFlight !== void 0)
|
|
54
|
+
result = await this.#inFlight;
|
|
55
|
+
if (this.#rows.length > 0)
|
|
56
|
+
result = await this.#flushBatch();
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
async #flushBatch() {
|
|
50
60
|
if (this.#inFlight !== void 0)
|
|
51
61
|
return this.#inFlight;
|
|
52
62
|
if (this.#rows.length === 0)
|
|
@@ -102,7 +112,7 @@ class BufferedTableWriter {
|
|
|
102
112
|
if (this.#inFlight !== void 0)
|
|
103
113
|
await this.#inFlight;
|
|
104
114
|
else
|
|
105
|
-
await this
|
|
115
|
+
await this.#flushBatch();
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
118
|
async #addSerial(row) {
|
|
@@ -115,7 +125,7 @@ class BufferedTableWriter {
|
|
|
115
125
|
}
|
|
116
126
|
if (this.#inFlight !== void 0)
|
|
117
127
|
await this.#inFlight;
|
|
118
|
-
return this
|
|
128
|
+
return this.#flushBatch();
|
|
119
129
|
}
|
|
120
130
|
#scheduleAgeFlush() {
|
|
121
131
|
if (this.#timer !== void 0 || this.#rows.length === 0 || this.#closed)
|
package/dist/engine/client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LiveQueryPatch, type LiveQueryPatchOptions } from "./live-patch.js";
|
|
1
2
|
import { type CompactionJobRecord, type GarbageCollectionJobRecord, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult } from "../storage/types.js";
|
|
2
3
|
import { type BatchRow } from "./batch.js";
|
|
3
4
|
import type { Catalog } from "./catalog.js";
|
|
@@ -46,6 +47,8 @@ export interface MinnowDatabaseClientOptions<TSchema extends AnySchema = Untyped
|
|
|
46
47
|
store?: StoreDescriptor;
|
|
47
48
|
/** Cloneable database options applied when the worker constructs the database. */
|
|
48
49
|
databaseOptions?: WireDatabaseOptions;
|
|
50
|
+
/** Maximum response wait, including initialization; defaults to 60 seconds. */
|
|
51
|
+
requestTimeoutMs?: number;
|
|
49
52
|
}
|
|
50
53
|
export interface ClientLiveQueryOptions {
|
|
51
54
|
/** BroadcastChannel name the worker uses to exchange cross-tab commit hints. */
|
|
@@ -55,6 +58,8 @@ export interface ClientLiveQueryOptions {
|
|
|
55
58
|
export interface CloseClientOptions {
|
|
56
59
|
/** Also terminate the worker after disposing; only meaningful when the transport can. */
|
|
57
60
|
terminateWorker?: boolean;
|
|
61
|
+
/** Grace allowed for disposal before closing the transport; defaults to 5 seconds. */
|
|
62
|
+
timeoutMs?: number;
|
|
58
63
|
}
|
|
59
64
|
export interface ClientMigrationResult {
|
|
60
65
|
createdTables: string[];
|
|
@@ -66,6 +71,7 @@ export interface ClientMigrationResult {
|
|
|
66
71
|
}
|
|
67
72
|
interface EventRoute {
|
|
68
73
|
onChange?: (result: QueryResult, delivery: LiveQueryDelivery) => void;
|
|
74
|
+
onPatch?: (patch: LiveQueryPatch, delivery: LiveQueryDelivery) => void;
|
|
69
75
|
onInvalidate?: (invalidation: LiveQueryInvalidation) => void;
|
|
70
76
|
onError?: (error: unknown) => void;
|
|
71
77
|
onComplete?: () => void;
|
|
@@ -74,6 +80,7 @@ interface EventRoute {
|
|
|
74
80
|
onStats?: (stats: QueryExecutionStats) => void;
|
|
75
81
|
}
|
|
76
82
|
interface RpcCallControls {
|
|
83
|
+
timeoutMs?: number;
|
|
77
84
|
signal?: AbortSignal | undefined;
|
|
78
85
|
onStats?: ((stats: QueryExecutionStats) => void) | undefined;
|
|
79
86
|
}
|
|
@@ -264,6 +271,8 @@ export declare class ClientLiveQuerySet {
|
|
|
264
271
|
constructor(client: MinnowDatabaseClient, handleId: string, created: Promise<unknown>);
|
|
265
272
|
/** Registers a query (SQL or a compiled-plan envelope) and re-runs it on relevant changes. */
|
|
266
273
|
subscribe(query: LiveQueryInput, options: LiveQuerySubscribeOptions): Promise<ClientLiveSubscription>;
|
|
274
|
+
/** Transfers resets followed by changed row payloads and retained positions across the worker. */
|
|
275
|
+
subscribePatches(query: LiveQueryInput, options: LiveQueryPatchOptions): Promise<ClientLiveSubscription>;
|
|
267
276
|
/** Registers dependency observation while leaving execution/result mapping to an adapter. */
|
|
268
277
|
observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<ClientLiveSubscription>;
|
|
269
278
|
stats(): Promise<LiveQueryStats>;
|
package/dist/engine/client.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createLiveQueryPatch } from "./live-patch.js";
|
|
2
|
+
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, PostingBuildConflictError, SnapshotManifestMissingError, SnapshotImportConflictError, SchemaConflictError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, StorageCorruptionError, StorageFormatVersionError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError } from "../storage/types.js";
|
|
2
3
|
import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
|
|
3
4
|
import { parseRpcResponse, MAX_DATABASE_RPC_IN_FLIGHT, protocolVersion } from "../worker-protocol/index.js";
|
|
4
5
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
5
|
-
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, DatabaseReadBacklogError, LiveQueryLimitError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
6
|
+
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, DatabaseReadBacklogError, TransactionExpiredError, DatabaseWorkerTimeoutError, DatabaseWorkerOutcomeUnknownError, LiveQueryLimitError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
6
7
|
import { QueryMemoryBudgetError } from "./memory.js";
|
|
7
8
|
import { decodeQueryResult } from "./result-wire.js";
|
|
8
9
|
import { serializeSchema } from "./schema-wire.js";
|
|
@@ -32,6 +33,9 @@ const errorRegistry = new Map([
|
|
|
32
33
|
CompactionJobCancelledError,
|
|
33
34
|
MaintenanceBacklogError,
|
|
34
35
|
DatabaseReadBacklogError,
|
|
36
|
+
TransactionExpiredError,
|
|
37
|
+
DatabaseWorkerTimeoutError,
|
|
38
|
+
DatabaseWorkerOutcomeUnknownError,
|
|
35
39
|
LiveQueryLimitError,
|
|
36
40
|
SqlCompileError,
|
|
37
41
|
QueryMemoryBudgetError,
|
|
@@ -58,6 +62,8 @@ const errorRegistry = new Map([
|
|
|
58
62
|
PostingBuildConflictError,
|
|
59
63
|
StorageCorruptionError,
|
|
60
64
|
StorageFormatVersionError,
|
|
65
|
+
OpfsCoordinationError,
|
|
66
|
+
OpfsDatabaseInUseError,
|
|
61
67
|
OpfsUncertainOutcomeError
|
|
62
68
|
].map((constructor) => [constructor.name, constructor]));
|
|
63
69
|
function rehydrateError(serialized) {
|
|
@@ -97,6 +103,8 @@ class MinnowDatabaseClient {
|
|
|
97
103
|
return this;
|
|
98
104
|
}
|
|
99
105
|
#transport;
|
|
106
|
+
#requestTimeoutMs;
|
|
107
|
+
#closePromise;
|
|
100
108
|
#pending = /* @__PURE__ */ new Map();
|
|
101
109
|
#events = /* @__PURE__ */ new Map();
|
|
102
110
|
#ready;
|
|
@@ -113,6 +121,7 @@ class MinnowDatabaseClient {
|
|
|
113
121
|
this.#fail(new Error("A database worker message could not be deserialized"));
|
|
114
122
|
};
|
|
115
123
|
constructor(transport, options = {}) {
|
|
124
|
+
this.#requestTimeoutMs = clientDeadline(options.requestTimeoutMs ?? 6e4);
|
|
116
125
|
this.#schema = options.schema;
|
|
117
126
|
this.#transport = transport;
|
|
118
127
|
transport.addEventListener("message", this.#onMessage);
|
|
@@ -288,10 +297,10 @@ class MinnowDatabaseClient {
|
|
|
288
297
|
try {
|
|
289
298
|
const session = {
|
|
290
299
|
version: opened.version,
|
|
291
|
-
query: (sql, options = {}) =>
|
|
292
|
-
...options
|
|
293
|
-
|
|
294
|
-
}
|
|
300
|
+
query: async (sql, options = {}) => {
|
|
301
|
+
const { signal, onStats, ...wireOptions } = options;
|
|
302
|
+
return decodeQueryResult(await this._invokeControlled(opened.handleId, "query", [sql, wireOptions, onStats !== void 0], { signal, onStats }));
|
|
303
|
+
}
|
|
295
304
|
};
|
|
296
305
|
return await action(session);
|
|
297
306
|
} finally {
|
|
@@ -300,18 +309,33 @@ class MinnowDatabaseClient {
|
|
|
300
309
|
}
|
|
301
310
|
async write(action) {
|
|
302
311
|
const opened = await this.#call("writeOpen", []);
|
|
303
|
-
|
|
312
|
+
let tail = Promise.resolve();
|
|
313
|
+
let accepting = true;
|
|
314
|
+
let pending = 0;
|
|
315
|
+
const enqueue = (run) => {
|
|
316
|
+
if (!accepting)
|
|
317
|
+
return Promise.reject(new Error("The write scope has ended"));
|
|
318
|
+
if (pending >= MAX_DATABASE_RPC_IN_FLIGHT)
|
|
319
|
+
return Promise.reject(new RangeError("Too many pending write scope calls; await a statement"));
|
|
320
|
+
pending += 1;
|
|
321
|
+
const task = tail.then(run);
|
|
322
|
+
tail = task.then(() => void 0, () => void 0);
|
|
323
|
+
return task.finally(() => {
|
|
324
|
+
pending -= 1;
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
const stage = (op, tableName, input, options) => enqueue(() => this._invoke(opened.handleId, "stage", [
|
|
304
328
|
op,
|
|
305
329
|
tableName,
|
|
306
330
|
input,
|
|
307
331
|
options
|
|
308
|
-
]);
|
|
332
|
+
]));
|
|
309
333
|
const session = {
|
|
310
|
-
query:
|
|
334
|
+
query: (sql, options = {}) => enqueue(async () => {
|
|
311
335
|
const { signal, onStats, ...wireOptions } = options;
|
|
312
336
|
return decodeQueryResult(await this._invokeControlled(opened.handleId, "query", [sql, wireOptions, onStats !== void 0], { signal, onStats }));
|
|
313
|
-
},
|
|
314
|
-
execute: (sql, params) => this._invoke(opened.handleId, "execute", params === void 0 ? [sql] : [sql, params]),
|
|
337
|
+
}),
|
|
338
|
+
execute: (sql, params) => enqueue(() => this._invoke(opened.handleId, "execute", params === void 0 ? [sql] : [sql, params])),
|
|
315
339
|
insertBatch: (tableName, input) => stage("insertBatch", tableName, input),
|
|
316
340
|
upsertBatch: (tableName, input, options) => stage("upsertBatch", tableName, input, options),
|
|
317
341
|
updateBatch: (tableName, input) => stage("updateBatch", tableName, input),
|
|
@@ -319,9 +343,13 @@ class MinnowDatabaseClient {
|
|
|
319
343
|
};
|
|
320
344
|
try {
|
|
321
345
|
const result = await action(session);
|
|
346
|
+
accepting = false;
|
|
347
|
+
await tail;
|
|
322
348
|
const committed = await this._invoke(opened.handleId, "commit", []);
|
|
323
349
|
return { result, version: committed.version };
|
|
324
350
|
} catch (error) {
|
|
351
|
+
accepting = false;
|
|
352
|
+
await tail;
|
|
325
353
|
await this._invoke(opened.handleId, "abort", []).catch(() => void 0);
|
|
326
354
|
throw error;
|
|
327
355
|
}
|
|
@@ -469,17 +497,22 @@ class MinnowDatabaseClient {
|
|
|
469
497
|
async listGarbageCollectionJobs() {
|
|
470
498
|
return await this.#call("listGarbageCollectionJobs", []);
|
|
471
499
|
}
|
|
472
|
-
|
|
473
|
-
if (this.#
|
|
474
|
-
return;
|
|
500
|
+
close(options = {}) {
|
|
501
|
+
if (this.#closePromise !== void 0)
|
|
502
|
+
return this.#closePromise;
|
|
503
|
+
const timeoutMs = clientDeadline(options.timeoutMs ?? 5e3);
|
|
475
504
|
this.#closed = true;
|
|
505
|
+
this.#closePromise = this.#closeTransport(options, timeoutMs);
|
|
506
|
+
return this.#closePromise;
|
|
507
|
+
}
|
|
508
|
+
async #closeTransport(options, timeoutMs) {
|
|
476
509
|
if (this.#onVisibilityChange !== void 0 && typeof document !== "undefined") {
|
|
477
510
|
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
478
511
|
}
|
|
479
512
|
try {
|
|
480
|
-
await this.#post("rpc-call", null, "dispose", []);
|
|
513
|
+
await this.#post("rpc-call", null, "dispose", [], void 0, true, { timeoutMs });
|
|
481
514
|
} finally {
|
|
482
|
-
this.#
|
|
515
|
+
this.#fail(new Error("Database client is closed"));
|
|
483
516
|
this.#transport.removeEventListener?.("message", this.#onMessage);
|
|
484
517
|
this.#transport.removeEventListener?.("error", this.#onError);
|
|
485
518
|
this.#transport.removeEventListener?.("messageerror", this.#onMessageError);
|
|
@@ -516,14 +549,29 @@ class MinnowDatabaseClient {
|
|
|
516
549
|
throw new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`);
|
|
517
550
|
}
|
|
518
551
|
const requestId = crypto.randomUUID();
|
|
552
|
+
const timeoutMs = controls.timeoutMs ?? this.#requestTimeoutMs;
|
|
553
|
+
const mayPublish = rpcMayPublish(method, args);
|
|
519
554
|
return new Promise((resolve, reject) => {
|
|
555
|
+
const timer = setTimeout(() => this.#fail(new DatabaseWorkerTimeoutError(method, timeoutMs)), timeoutMs);
|
|
556
|
+
timer.unref?.();
|
|
520
557
|
const onAbort = () => {
|
|
521
558
|
try {
|
|
522
559
|
this.#transport.postMessage({ version: protocolVersion, requestId, kind: "rpc-cancel" });
|
|
523
560
|
} catch {
|
|
524
561
|
}
|
|
562
|
+
const call = this.#pending.get(requestId);
|
|
563
|
+
if (call === void 0)
|
|
564
|
+
return;
|
|
565
|
+
this.#pending.delete(requestId);
|
|
566
|
+
call.cleanup?.();
|
|
567
|
+
const error = new Error("Database request was cancelled", {
|
|
568
|
+
cause: controls.signal?.reason
|
|
569
|
+
});
|
|
570
|
+
error.name = "AbortError";
|
|
571
|
+
call.reject(mayPublish ? new DatabaseWorkerOutcomeUnknownError(method, requestId, { cause: error }) : error);
|
|
525
572
|
};
|
|
526
573
|
const cleanup = () => {
|
|
574
|
+
clearTimeout(timer);
|
|
527
575
|
controls.signal?.removeEventListener("abort", onAbort);
|
|
528
576
|
if (controls.onStats !== void 0)
|
|
529
577
|
this.#events.delete(requestId);
|
|
@@ -532,7 +580,7 @@ class MinnowDatabaseClient {
|
|
|
532
580
|
if (controls.onStats !== void 0) {
|
|
533
581
|
this.#events.set(requestId, { onStats: controls.onStats });
|
|
534
582
|
}
|
|
535
|
-
this.#pending.set(requestId, { resolve, reject, cleanup });
|
|
583
|
+
this.#pending.set(requestId, { resolve, reject, cleanup, method, requestId, mayPublish });
|
|
536
584
|
try {
|
|
537
585
|
this.#transport.postMessage(kind === "rpc-init" ? { version: protocolVersion, requestId, kind, payload: args[0] } : { version: protocolVersion, requestId, kind, handleId, method, args }, transfer === void 0 ? void 0 : { transfer });
|
|
538
586
|
} catch (error) {
|
|
@@ -559,6 +607,24 @@ class MinnowDatabaseClient {
|
|
|
559
607
|
if (response.event === "change") {
|
|
560
608
|
const { result, delivery } = response.payload;
|
|
561
609
|
route.onChange?.(decodeQueryResult(result), delivery);
|
|
610
|
+
} else if (response.event === "patch") {
|
|
611
|
+
const { result: payload, delivery } = response.payload;
|
|
612
|
+
const result = decodeQueryResult(payload);
|
|
613
|
+
const retained = delivery.retained;
|
|
614
|
+
if (!(retained instanceof Int32Array))
|
|
615
|
+
throw new TypeError("Live patch is missing retained positions");
|
|
616
|
+
const changedRows = [];
|
|
617
|
+
for (let index = 0; index < retained.length; index += 1) {
|
|
618
|
+
if ((retained[index] ?? -1) >= 0)
|
|
619
|
+
continue;
|
|
620
|
+
const row = result.rows[changedRows.length];
|
|
621
|
+
if (row === void 0)
|
|
622
|
+
throw new TypeError("Live patch is missing a changed row");
|
|
623
|
+
changedRows.push({ index, row });
|
|
624
|
+
}
|
|
625
|
+
if (changedRows.length !== result.rows.length)
|
|
626
|
+
throw new TypeError("Live patch has excess rows");
|
|
627
|
+
route.onPatch?.({ type: "patch", retained, changedRows }, delivery);
|
|
562
628
|
} else if (response.event === "invalidate") {
|
|
563
629
|
route.onInvalidate?.(response.payload);
|
|
564
630
|
} else if (response.event === "error") {
|
|
@@ -596,7 +662,7 @@ class MinnowDatabaseClient {
|
|
|
596
662
|
}
|
|
597
663
|
this.#pending.delete(requestId);
|
|
598
664
|
pending.cleanup?.();
|
|
599
|
-
pending.reject(error);
|
|
665
|
+
pending.reject(pending.mayPublish ? new DatabaseWorkerOutcomeUnknownError(pending.method, requestId, { cause: error }) : error);
|
|
600
666
|
}
|
|
601
667
|
#fail(error) {
|
|
602
668
|
this.#fatal = error;
|
|
@@ -605,10 +671,20 @@ class MinnowDatabaseClient {
|
|
|
605
671
|
this.#events.clear();
|
|
606
672
|
for (const call of pending) {
|
|
607
673
|
call.cleanup?.();
|
|
608
|
-
call.reject(error);
|
|
674
|
+
call.reject(call.mayPublish ? new DatabaseWorkerOutcomeUnknownError(call.method, call.requestId, { cause: error }) : error);
|
|
609
675
|
}
|
|
610
676
|
}
|
|
611
677
|
}
|
|
678
|
+
function clientDeadline(value) {
|
|
679
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647)
|
|
680
|
+
throw new RangeError("Worker timeout must be a positive timer interval");
|
|
681
|
+
return value;
|
|
682
|
+
}
|
|
683
|
+
function rpcMayPublish(method, args) {
|
|
684
|
+
if (method === "execute" && typeof args[0] === "string" && /^\s*SELECT\b/iu.test(args[0]))
|
|
685
|
+
return false;
|
|
686
|
+
return /^(?:insert|upsert|update|delete|create|drop|migrate|commit|flush|execute|runStatement|import|finish|add|close|dispose)/u.test(method);
|
|
687
|
+
}
|
|
612
688
|
class ClientBufferedWriter {
|
|
613
689
|
client;
|
|
614
690
|
handleId;
|
|
@@ -659,11 +735,17 @@ class ClientLiveQuerySet {
|
|
|
659
735
|
}
|
|
660
736
|
#created;
|
|
661
737
|
async subscribe(query, options) {
|
|
738
|
+
return this.#subscribe(query, options);
|
|
739
|
+
}
|
|
740
|
+
async #subscribe(query, options, onPatch) {
|
|
741
|
+
if (typeof query !== "string")
|
|
742
|
+
query = structuredClone(query);
|
|
662
743
|
await this.#created;
|
|
663
744
|
const subscriptionId = crypto.randomUUID();
|
|
664
745
|
const state = { completed: false };
|
|
665
746
|
this.client._routeEvents(subscriptionId, {
|
|
666
747
|
onChange: options.onChange.bind(options),
|
|
748
|
+
...onPatch === void 0 ? {} : { onPatch },
|
|
667
749
|
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
668
750
|
onComplete: () => {
|
|
669
751
|
state.completed = true;
|
|
@@ -674,7 +756,8 @@ class ClientLiveQuerySet {
|
|
|
674
756
|
try {
|
|
675
757
|
const created = await this.client._invoke(this.handleId, "subscribe", [
|
|
676
758
|
subscriptionId,
|
|
677
|
-
query
|
|
759
|
+
query,
|
|
760
|
+
...onPatch === void 0 ? [] : [{ patches: true }]
|
|
678
761
|
]);
|
|
679
762
|
this.#subscriptionIds.add(subscriptionId);
|
|
680
763
|
return new ClientLiveSubscription(this.client, subscriptionId, created.dependencyTableIds, () => this.#subscriptionIds.delete(subscriptionId), state);
|
|
@@ -683,7 +766,16 @@ class ClientLiveQuerySet {
|
|
|
683
766
|
throw error;
|
|
684
767
|
}
|
|
685
768
|
}
|
|
769
|
+
subscribePatches(query, options) {
|
|
770
|
+
return this.#subscribe(query, {
|
|
771
|
+
onChange: (result, delivery) => options.onPatch(createLiveQueryPatch(result, delivery), delivery),
|
|
772
|
+
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
773
|
+
...options.onComplete === void 0 ? {} : { onComplete: options.onComplete.bind(options) }
|
|
774
|
+
}, options.onPatch.bind(options));
|
|
775
|
+
}
|
|
686
776
|
async observe(query, options) {
|
|
777
|
+
if (typeof query !== "string")
|
|
778
|
+
query = structuredClone(query);
|
|
687
779
|
await this.#created;
|
|
688
780
|
const subscriptionId = crypto.randomUUID();
|
|
689
781
|
const state = { completed: false };
|
|
@@ -257,7 +257,7 @@ export interface QueryOptions {
|
|
|
257
257
|
* data with the default on measures cache lookups, not query execution.
|
|
258
258
|
*/
|
|
259
259
|
memoize?: boolean;
|
|
260
|
-
readonly version?: number;
|
|
260
|
+
readonly version?: number | null;
|
|
261
261
|
/**
|
|
262
262
|
* Values for the statement's `?`/`$n` placeholders, in order. Required exactly when the
|
|
263
263
|
* statement has placeholders; the compiled plan is cached on the SQL text and re-bound per
|
|
@@ -472,6 +472,8 @@ export interface MinnowDatabaseOptions<TSchema extends AnySchema = UntypedSchema
|
|
|
472
472
|
targetBlockBytes?: number;
|
|
473
473
|
rowsPerBlock?: number;
|
|
474
474
|
maxCommitRetries?: number;
|
|
475
|
+
/** Coordinate autocommit writers across instances and, with Web Locks, tabs. Default true. */
|
|
476
|
+
coordinateWrites?: boolean;
|
|
475
477
|
now?: () => Date;
|
|
476
478
|
createId?: () => string;
|
|
477
479
|
/** Durable spill-owner lease lifetime; renewed while a spilling query runs. */
|
|
@@ -812,8 +814,8 @@ export declare class MinnowDatabase<TSchema extends AnySchema = UntypedSchema> {
|
|
|
812
814
|
* Creates a live-query set over this database. Local commits hint it directly, an optional
|
|
813
815
|
* channel carries cross-tab hints, and an optional poll interval bounds staleness without any
|
|
814
816
|
* hint; every hint path converges on the durable manifest version, so missed messages delay a
|
|
815
|
-
* refresh but never produce a stale result. Equal subscriptions retain one private
|
|
816
|
-
*
|
|
817
|
+
* refresh but never produce a stale result. Equal subscriptions retain one private result
|
|
818
|
+
* snapshot, compared exactly before delivery.
|
|
817
819
|
*/
|
|
818
820
|
liveQueries(options?: LiveQuerySetOptions): LiveQuerySet;
|
|
819
821
|
/**
|