@minnowdb/core 0.8.0 → 0.9.1
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 +8 -2
- package/dist/engine/client.js +106 -22
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2044 -1674
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +29 -0
- 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 +54 -16
- package/dist/engine/live.d.ts +2 -0
- package/dist/engine/live.js +109 -108
- package/dist/engine/query-cache.js +17 -2
- package/dist/engine/query.d.ts +1 -2
- package/dist/engine/query.js +4 -7
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.js +15 -14
- 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/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 +128 -40
- 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/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-aggregate.d.ts +0 -12
- 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/query-generations.d.ts +0 -8
- package/dist/engine/query-identity.d.ts +0 -3
- 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 -97
- 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,4 +1,4 @@
|
|
|
1
|
-
import { type LiveQueryPatchOptions } from "./live-patch.js";
|
|
1
|
+
import { type LiveQueryPatch, type LiveQueryPatchOptions } from "./live-patch.js";
|
|
2
2
|
import { type CompactionJobRecord, type GarbageCollectionJobRecord, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult } from "../storage/types.js";
|
|
3
3
|
import { type BatchRow } from "./batch.js";
|
|
4
4
|
import type { Catalog } from "./catalog.js";
|
|
@@ -47,6 +47,8 @@ export interface MinnowDatabaseClientOptions<TSchema extends AnySchema = Untyped
|
|
|
47
47
|
store?: StoreDescriptor;
|
|
48
48
|
/** Cloneable database options applied when the worker constructs the database. */
|
|
49
49
|
databaseOptions?: WireDatabaseOptions;
|
|
50
|
+
/** Maximum response wait, including initialization; defaults to 60 seconds. */
|
|
51
|
+
requestTimeoutMs?: number;
|
|
50
52
|
}
|
|
51
53
|
export interface ClientLiveQueryOptions {
|
|
52
54
|
/** BroadcastChannel name the worker uses to exchange cross-tab commit hints. */
|
|
@@ -56,6 +58,8 @@ export interface ClientLiveQueryOptions {
|
|
|
56
58
|
export interface CloseClientOptions {
|
|
57
59
|
/** Also terminate the worker after disposing; only meaningful when the transport can. */
|
|
58
60
|
terminateWorker?: boolean;
|
|
61
|
+
/** Grace allowed for disposal before closing the transport; defaults to 5 seconds. */
|
|
62
|
+
timeoutMs?: number;
|
|
59
63
|
}
|
|
60
64
|
export interface ClientMigrationResult {
|
|
61
65
|
createdTables: string[];
|
|
@@ -67,6 +71,7 @@ export interface ClientMigrationResult {
|
|
|
67
71
|
}
|
|
68
72
|
interface EventRoute {
|
|
69
73
|
onChange?: (result: QueryResult, delivery: LiveQueryDelivery) => void;
|
|
74
|
+
onPatch?: (patch: LiveQueryPatch, delivery: LiveQueryDelivery) => void;
|
|
70
75
|
onInvalidate?: (invalidation: LiveQueryInvalidation) => void;
|
|
71
76
|
onError?: (error: unknown) => void;
|
|
72
77
|
onComplete?: () => void;
|
|
@@ -75,6 +80,7 @@ interface EventRoute {
|
|
|
75
80
|
onStats?: (stats: QueryExecutionStats) => void;
|
|
76
81
|
}
|
|
77
82
|
interface RpcCallControls {
|
|
83
|
+
timeoutMs?: number;
|
|
78
84
|
signal?: AbortSignal | undefined;
|
|
79
85
|
onStats?: ((stats: QueryExecutionStats) => void) | undefined;
|
|
80
86
|
}
|
|
@@ -265,7 +271,7 @@ export declare class ClientLiveQuerySet {
|
|
|
265
271
|
constructor(client: MinnowDatabaseClient, handleId: string, created: Promise<unknown>);
|
|
266
272
|
/** Registers a query (SQL or a compiled-plan envelope) and re-runs it on relevant changes. */
|
|
267
273
|
subscribe(query: LiveQueryInput, options: LiveQuerySubscribeOptions): Promise<ClientLiveSubscription>;
|
|
268
|
-
/**
|
|
274
|
+
/** Transfers resets followed by changed row payloads and retained positions across the worker. */
|
|
269
275
|
subscribePatches(query: LiveQueryInput, options: LiveQueryPatchOptions): Promise<ClientLiveSubscription>;
|
|
270
276
|
/** Registers dependency observation while leaving execution/result mapping to an adapter. */
|
|
271
277
|
observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<ClientLiveSubscription>;
|
package/dist/engine/client.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
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, OpfsUncertainOutcomeError } from "../storage/types.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";
|
|
3
3
|
import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
|
|
4
4
|
import { parseRpcResponse, MAX_DATABASE_RPC_IN_FLIGHT, protocolVersion } from "../worker-protocol/index.js";
|
|
5
5
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
6
|
-
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";
|
|
7
7
|
import { QueryMemoryBudgetError } from "./memory.js";
|
|
8
8
|
import { decodeQueryResult } from "./result-wire.js";
|
|
9
9
|
import { serializeSchema } from "./schema-wire.js";
|
|
@@ -33,6 +33,9 @@ const errorRegistry = new Map([
|
|
|
33
33
|
CompactionJobCancelledError,
|
|
34
34
|
MaintenanceBacklogError,
|
|
35
35
|
DatabaseReadBacklogError,
|
|
36
|
+
TransactionExpiredError,
|
|
37
|
+
DatabaseWorkerTimeoutError,
|
|
38
|
+
DatabaseWorkerOutcomeUnknownError,
|
|
36
39
|
LiveQueryLimitError,
|
|
37
40
|
SqlCompileError,
|
|
38
41
|
QueryMemoryBudgetError,
|
|
@@ -59,6 +62,8 @@ const errorRegistry = new Map([
|
|
|
59
62
|
PostingBuildConflictError,
|
|
60
63
|
StorageCorruptionError,
|
|
61
64
|
StorageFormatVersionError,
|
|
65
|
+
OpfsCoordinationError,
|
|
66
|
+
OpfsDatabaseInUseError,
|
|
62
67
|
OpfsUncertainOutcomeError
|
|
63
68
|
].map((constructor) => [constructor.name, constructor]));
|
|
64
69
|
function rehydrateError(serialized) {
|
|
@@ -98,6 +103,8 @@ class MinnowDatabaseClient {
|
|
|
98
103
|
return this;
|
|
99
104
|
}
|
|
100
105
|
#transport;
|
|
106
|
+
#requestTimeoutMs;
|
|
107
|
+
#closePromise;
|
|
101
108
|
#pending = /* @__PURE__ */ new Map();
|
|
102
109
|
#events = /* @__PURE__ */ new Map();
|
|
103
110
|
#ready;
|
|
@@ -114,6 +121,7 @@ class MinnowDatabaseClient {
|
|
|
114
121
|
this.#fail(new Error("A database worker message could not be deserialized"));
|
|
115
122
|
};
|
|
116
123
|
constructor(transport, options = {}) {
|
|
124
|
+
this.#requestTimeoutMs = clientDeadline(options.requestTimeoutMs ?? 6e4);
|
|
117
125
|
this.#schema = options.schema;
|
|
118
126
|
this.#transport = transport;
|
|
119
127
|
transport.addEventListener("message", this.#onMessage);
|
|
@@ -289,10 +297,10 @@ class MinnowDatabaseClient {
|
|
|
289
297
|
try {
|
|
290
298
|
const session = {
|
|
291
299
|
version: opened.version,
|
|
292
|
-
query: (sql, options = {}) =>
|
|
293
|
-
...options
|
|
294
|
-
|
|
295
|
-
}
|
|
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
|
+
}
|
|
296
304
|
};
|
|
297
305
|
return await action(session);
|
|
298
306
|
} finally {
|
|
@@ -301,18 +309,33 @@ class MinnowDatabaseClient {
|
|
|
301
309
|
}
|
|
302
310
|
async write(action) {
|
|
303
311
|
const opened = await this.#call("writeOpen", []);
|
|
304
|
-
|
|
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", [
|
|
305
328
|
op,
|
|
306
329
|
tableName,
|
|
307
330
|
input,
|
|
308
331
|
options
|
|
309
|
-
]);
|
|
332
|
+
]));
|
|
310
333
|
const session = {
|
|
311
|
-
query:
|
|
334
|
+
query: (sql, options = {}) => enqueue(async () => {
|
|
312
335
|
const { signal, onStats, ...wireOptions } = options;
|
|
313
336
|
return decodeQueryResult(await this._invokeControlled(opened.handleId, "query", [sql, wireOptions, onStats !== void 0], { signal, onStats }));
|
|
314
|
-
},
|
|
315
|
-
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])),
|
|
316
339
|
insertBatch: (tableName, input) => stage("insertBatch", tableName, input),
|
|
317
340
|
upsertBatch: (tableName, input, options) => stage("upsertBatch", tableName, input, options),
|
|
318
341
|
updateBatch: (tableName, input) => stage("updateBatch", tableName, input),
|
|
@@ -320,9 +343,13 @@ class MinnowDatabaseClient {
|
|
|
320
343
|
};
|
|
321
344
|
try {
|
|
322
345
|
const result = await action(session);
|
|
346
|
+
accepting = false;
|
|
347
|
+
await tail;
|
|
323
348
|
const committed = await this._invoke(opened.handleId, "commit", []);
|
|
324
349
|
return { result, version: committed.version };
|
|
325
350
|
} catch (error) {
|
|
351
|
+
accepting = false;
|
|
352
|
+
await tail;
|
|
326
353
|
await this._invoke(opened.handleId, "abort", []).catch(() => void 0);
|
|
327
354
|
throw error;
|
|
328
355
|
}
|
|
@@ -470,17 +497,22 @@ class MinnowDatabaseClient {
|
|
|
470
497
|
async listGarbageCollectionJobs() {
|
|
471
498
|
return await this.#call("listGarbageCollectionJobs", []);
|
|
472
499
|
}
|
|
473
|
-
|
|
474
|
-
if (this.#
|
|
475
|
-
return;
|
|
500
|
+
close(options = {}) {
|
|
501
|
+
if (this.#closePromise !== void 0)
|
|
502
|
+
return this.#closePromise;
|
|
503
|
+
const timeoutMs = clientDeadline(options.timeoutMs ?? 5e3);
|
|
476
504
|
this.#closed = true;
|
|
505
|
+
this.#closePromise = this.#closeTransport(options, timeoutMs);
|
|
506
|
+
return this.#closePromise;
|
|
507
|
+
}
|
|
508
|
+
async #closeTransport(options, timeoutMs) {
|
|
477
509
|
if (this.#onVisibilityChange !== void 0 && typeof document !== "undefined") {
|
|
478
510
|
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
479
511
|
}
|
|
480
512
|
try {
|
|
481
|
-
await this.#post("rpc-call", null, "dispose", []);
|
|
513
|
+
await this.#post("rpc-call", null, "dispose", [], void 0, true, { timeoutMs });
|
|
482
514
|
} finally {
|
|
483
|
-
this.#
|
|
515
|
+
this.#fail(new Error("Database client is closed"));
|
|
484
516
|
this.#transport.removeEventListener?.("message", this.#onMessage);
|
|
485
517
|
this.#transport.removeEventListener?.("error", this.#onError);
|
|
486
518
|
this.#transport.removeEventListener?.("messageerror", this.#onMessageError);
|
|
@@ -517,14 +549,29 @@ class MinnowDatabaseClient {
|
|
|
517
549
|
throw new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`);
|
|
518
550
|
}
|
|
519
551
|
const requestId = crypto.randomUUID();
|
|
552
|
+
const timeoutMs = controls.timeoutMs ?? this.#requestTimeoutMs;
|
|
553
|
+
const mayPublish = rpcMayPublish(method, args);
|
|
520
554
|
return new Promise((resolve, reject) => {
|
|
555
|
+
const timer = setTimeout(() => this.#fail(new DatabaseWorkerTimeoutError(method, timeoutMs)), timeoutMs);
|
|
556
|
+
timer.unref?.();
|
|
521
557
|
const onAbort = () => {
|
|
522
558
|
try {
|
|
523
559
|
this.#transport.postMessage({ version: protocolVersion, requestId, kind: "rpc-cancel" });
|
|
524
560
|
} catch {
|
|
525
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);
|
|
526
572
|
};
|
|
527
573
|
const cleanup = () => {
|
|
574
|
+
clearTimeout(timer);
|
|
528
575
|
controls.signal?.removeEventListener("abort", onAbort);
|
|
529
576
|
if (controls.onStats !== void 0)
|
|
530
577
|
this.#events.delete(requestId);
|
|
@@ -533,7 +580,7 @@ class MinnowDatabaseClient {
|
|
|
533
580
|
if (controls.onStats !== void 0) {
|
|
534
581
|
this.#events.set(requestId, { onStats: controls.onStats });
|
|
535
582
|
}
|
|
536
|
-
this.#pending.set(requestId, { resolve, reject, cleanup });
|
|
583
|
+
this.#pending.set(requestId, { resolve, reject, cleanup, method, requestId, mayPublish });
|
|
537
584
|
try {
|
|
538
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 });
|
|
539
586
|
} catch (error) {
|
|
@@ -560,6 +607,24 @@ class MinnowDatabaseClient {
|
|
|
560
607
|
if (response.event === "change") {
|
|
561
608
|
const { result, delivery } = response.payload;
|
|
562
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);
|
|
563
628
|
} else if (response.event === "invalidate") {
|
|
564
629
|
route.onInvalidate?.(response.payload);
|
|
565
630
|
} else if (response.event === "error") {
|
|
@@ -597,7 +662,7 @@ class MinnowDatabaseClient {
|
|
|
597
662
|
}
|
|
598
663
|
this.#pending.delete(requestId);
|
|
599
664
|
pending.cleanup?.();
|
|
600
|
-
pending.reject(error);
|
|
665
|
+
pending.reject(pending.mayPublish ? new DatabaseWorkerOutcomeUnknownError(pending.method, requestId, { cause: error }) : error);
|
|
601
666
|
}
|
|
602
667
|
#fail(error) {
|
|
603
668
|
this.#fatal = error;
|
|
@@ -606,10 +671,20 @@ class MinnowDatabaseClient {
|
|
|
606
671
|
this.#events.clear();
|
|
607
672
|
for (const call of pending) {
|
|
608
673
|
call.cleanup?.();
|
|
609
|
-
call.reject(error);
|
|
674
|
+
call.reject(call.mayPublish ? new DatabaseWorkerOutcomeUnknownError(call.method, call.requestId, { cause: error }) : error);
|
|
610
675
|
}
|
|
611
676
|
}
|
|
612
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
|
+
}
|
|
613
688
|
class ClientBufferedWriter {
|
|
614
689
|
client;
|
|
615
690
|
handleId;
|
|
@@ -660,11 +735,17 @@ class ClientLiveQuerySet {
|
|
|
660
735
|
}
|
|
661
736
|
#created;
|
|
662
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);
|
|
663
743
|
await this.#created;
|
|
664
744
|
const subscriptionId = crypto.randomUUID();
|
|
665
745
|
const state = { completed: false };
|
|
666
746
|
this.client._routeEvents(subscriptionId, {
|
|
667
747
|
onChange: options.onChange.bind(options),
|
|
748
|
+
...onPatch === void 0 ? {} : { onPatch },
|
|
668
749
|
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
669
750
|
onComplete: () => {
|
|
670
751
|
state.completed = true;
|
|
@@ -675,7 +756,8 @@ class ClientLiveQuerySet {
|
|
|
675
756
|
try {
|
|
676
757
|
const created = await this.client._invoke(this.handleId, "subscribe", [
|
|
677
758
|
subscriptionId,
|
|
678
|
-
query
|
|
759
|
+
query,
|
|
760
|
+
...onPatch === void 0 ? [] : [{ patches: true }]
|
|
679
761
|
]);
|
|
680
762
|
this.#subscriptionIds.add(subscriptionId);
|
|
681
763
|
return new ClientLiveSubscription(this.client, subscriptionId, created.dependencyTableIds, () => this.#subscriptionIds.delete(subscriptionId), state);
|
|
@@ -685,13 +767,15 @@ class ClientLiveQuerySet {
|
|
|
685
767
|
}
|
|
686
768
|
}
|
|
687
769
|
subscribePatches(query, options) {
|
|
688
|
-
return this
|
|
770
|
+
return this.#subscribe(query, {
|
|
689
771
|
onChange: (result, delivery) => options.onPatch(createLiveQueryPatch(result, delivery), delivery),
|
|
690
772
|
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
691
773
|
...options.onComplete === void 0 ? {} : { onComplete: options.onComplete.bind(options) }
|
|
692
|
-
});
|
|
774
|
+
}, options.onPatch.bind(options));
|
|
693
775
|
}
|
|
694
776
|
async observe(query, options) {
|
|
777
|
+
if (typeof query !== "string")
|
|
778
|
+
query = structuredClone(query);
|
|
695
779
|
await this.#created;
|
|
696
780
|
const subscriptionId = crypto.randomUUID();
|
|
697
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
|
/**
|