@minnowdb/core 0.10.0 → 0.10.2
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/auto-store.d.ts +15 -3
- package/dist/engine/auto-store.js +48 -6
- package/dist/engine/client.js +23 -7
- package/dist/engine/database.js +347 -862
- package/dist/engine/index-terms.js +627 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +2 -1
- package/dist/engine/live-maintenance.js +220 -0
- package/dist/engine/schema.js +2 -1
- package/dist/engine/sql-functions.js +3 -2
- package/dist/engine/sql-quote.js +6 -0
- package/dist/engine/vector.js +1 -3
- package/dist/engine/worker-host.js +2 -0
- package/dist/engine/worker-server.js +4 -5
- package/dist/engine/worker-store-auto.js +2 -2
- package/dist/engine/write-coordinator.js +21 -1
- package/dist/storage/indexeddb.d.ts +18 -0
- package/dist/storage/indexeddb.js +293 -211
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +44 -5
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +12 -0
- package/dist/storage/opfs/store.js +59 -12
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.js +3 -3
- package/dist/storage/types.d.ts +35 -4
- package/dist/storage/types.js +17 -4
- package/dist/testing/block-store-conformance.js +40 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +9 -0
- package/dist/testing/interaction-simulator.d.ts +319 -0
- package/dist/testing/interaction-simulator.js +1631 -0
- package/dist/transactions/index.d.ts +7 -0
- package/dist/transactions/index.js +17 -3
- package/package.json +4 -1
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the composition root knows that this module must not import: whether an OPFS database
|
|
3
|
+
* directory of a name exists. The OPFS adapter provides it (`opfsDatabaseExists`); a root that
|
|
4
|
+
* cannot bundle OPFS leaves it out, and OPFS is then never chosen for an unknown name anyway.
|
|
5
|
+
*/
|
|
6
|
+
export interface AutoStoreProbes {
|
|
7
|
+
opfsDatabaseExists?: (name: string) => Promise<boolean>;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* The `{ kind: "auto" }` store: OPFS where this context can hold synchronous access handles,
|
|
3
11
|
* IndexedDB where it cannot (Safari's private browsing, a page context, an older build).
|
|
@@ -11,6 +19,8 @@ export type AutoStoreKind = "opfs" | "indexeddb";
|
|
|
11
19
|
/** Test seams: the OPFS probe and the IndexedDB factory that keeps the choices. */
|
|
12
20
|
export declare const autoStoreTestHooks: {
|
|
13
21
|
opfsAvailable?: () => Promise<boolean>;
|
|
22
|
+
/** Whether an OPFS database directory of this name exists; Node has no OPFS to ask. */
|
|
23
|
+
opfsDatabaseExists?: (name: string) => Promise<boolean>;
|
|
14
24
|
indexedDB?: IDBFactory;
|
|
15
25
|
};
|
|
16
26
|
/**
|
|
@@ -24,15 +34,17 @@ export declare function opfsAvailable(): Promise<boolean>;
|
|
|
24
34
|
* instant in contexts that disagree still end up on the one store the record names; the
|
|
25
35
|
* caller reports through `settleAutoStoreChoice` whether that reservation produced a database.
|
|
26
36
|
*/
|
|
27
|
-
export declare function resolveAutoStoreKind(name: string): Promise<{
|
|
37
|
+
export declare function resolveAutoStoreKind(name: string, probes?: AutoStoreProbes): Promise<{
|
|
28
38
|
kind: AutoStoreKind;
|
|
29
39
|
reserved: boolean;
|
|
30
40
|
}>;
|
|
31
41
|
/**
|
|
32
42
|
* Opens the store `auto` resolves to. A reservation that produced no database is released
|
|
33
|
-
* again, so a first open that failed does not pin the name to a store that never held data
|
|
43
|
+
* again, so a first open that failed does not pin the name to a store that never held data —
|
|
44
|
+
* but only when the store really holds none: another connection may have opened the same
|
|
45
|
+
* reservation successfully in the meantime, and its database must keep its memory.
|
|
34
46
|
*/
|
|
35
|
-
export declare function openAutoStore<Store>(name: string, open: (kind: AutoStoreKind) => Promise<Store
|
|
47
|
+
export declare function openAutoStore<Store>(name: string, open: (kind: AutoStoreKind) => Promise<Store>, probes?: AutoStoreProbes): Promise<{
|
|
36
48
|
store: Store;
|
|
37
49
|
kind: AutoStoreKind;
|
|
38
50
|
}>;
|
|
@@ -19,7 +19,7 @@ async function opfsAvailable() {
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
async function resolveAutoStoreKind(name) {
|
|
22
|
+
async function resolveAutoStoreKind(name, probes = {}) {
|
|
23
23
|
const remembered = await readChoice(name);
|
|
24
24
|
if (remembered === "indexeddb")
|
|
25
25
|
return { kind: "indexeddb", reserved: false };
|
|
@@ -28,22 +28,64 @@ async function resolveAutoStoreKind(name) {
|
|
|
28
28
|
return { kind: "opfs", reserved: false };
|
|
29
29
|
throw new DatabaseStoreUnavailableError("opfs", name, `Database "${name}" lives on the OPFS store, which this context cannot open; it is not reopened on IndexedDB, where it would be empty`);
|
|
30
30
|
}
|
|
31
|
-
const
|
|
31
|
+
const existing = await existingDatabaseStore(name, probes);
|
|
32
|
+
let kind;
|
|
33
|
+
if (existing === "opfs" && !await opfsAvailable()) {
|
|
34
|
+
throw new DatabaseStoreUnavailableError("opfs", name, `Database "${name}" lives on the OPFS store, which this context cannot open; it is not reopened on IndexedDB, where it would be empty`);
|
|
35
|
+
} else if (existing !== void 0)
|
|
36
|
+
kind = existing;
|
|
37
|
+
else
|
|
38
|
+
kind = await opfsAvailable() ? "opfs" : "indexeddb";
|
|
32
39
|
const reserved = await reserveChoice(name, kind);
|
|
33
40
|
if (reserved)
|
|
34
41
|
return { kind, reserved: true };
|
|
35
|
-
return resolveAutoStoreKind(name);
|
|
42
|
+
return resolveAutoStoreKind(name, probes);
|
|
36
43
|
}
|
|
37
|
-
async function openAutoStore(name, open) {
|
|
38
|
-
const { kind, reserved } = await resolveAutoStoreKind(name);
|
|
44
|
+
async function openAutoStore(name, open, probes = {}) {
|
|
45
|
+
const { kind, reserved } = await resolveAutoStoreKind(name, probes);
|
|
39
46
|
try {
|
|
40
47
|
return { store: await open(kind), kind };
|
|
41
48
|
} catch (error) {
|
|
42
|
-
if (reserved)
|
|
49
|
+
if (reserved && await existingDatabaseStore(name, probes).catch(() => kind) !== kind) {
|
|
43
50
|
await forgetStoreChoice(name).catch(() => void 0);
|
|
51
|
+
}
|
|
44
52
|
throw error;
|
|
45
53
|
}
|
|
46
54
|
}
|
|
55
|
+
async function existingDatabaseStore(name, probes) {
|
|
56
|
+
const opfsExists = autoStoreTestHooks.opfsDatabaseExists ?? probes.opfsDatabaseExists;
|
|
57
|
+
if (opfsExists !== void 0 && await opfsExists(name))
|
|
58
|
+
return "opfs";
|
|
59
|
+
if (await indexedDbDatabaseExists(name))
|
|
60
|
+
return "indexeddb";
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
async function indexedDbDatabaseExists(name) {
|
|
64
|
+
const factory = choiceFactory();
|
|
65
|
+
if (factory === void 0)
|
|
66
|
+
return false;
|
|
67
|
+
const list = factory.databases;
|
|
68
|
+
if (typeof list === "function") {
|
|
69
|
+
try {
|
|
70
|
+
return (await list.call(factory)).some((entry) => entry.name === name);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const request = factory.open(name);
|
|
76
|
+
let created = false;
|
|
77
|
+
request.addEventListener("upgradeneeded", () => {
|
|
78
|
+
created = true;
|
|
79
|
+
request.transaction?.abort();
|
|
80
|
+
});
|
|
81
|
+
request.addEventListener("success", () => {
|
|
82
|
+
request.result.close();
|
|
83
|
+
resolve(!created);
|
|
84
|
+
});
|
|
85
|
+
request.addEventListener("error", () => resolve(false));
|
|
86
|
+
request.addEventListener("blocked", () => resolve(true));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
47
89
|
async function forgetStoreChoice(name) {
|
|
48
90
|
const database = await openChoices();
|
|
49
91
|
if (database === void 0)
|
package/dist/engine/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLiveQueryPatch } from "./live-patch.js";
|
|
2
|
-
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, ConnectionLostError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SchemaConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueIndexCoverageError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UnknownOutcomeError, WriteConflictError } from "../storage/types.js";
|
|
2
|
+
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, ConnectionLostError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SchemaConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageUnresponsiveError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueIndexCoverageError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UnknownOutcomeError, WriteConflictError } from "../storage/types.js";
|
|
3
3
|
import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
|
|
4
4
|
import { MAX_DATABASE_RPC_IN_FLIGHT, WORKER_DIAGNOSTIC_HANDLE_ID, isWorkerErrorReport, parseRpcResponse, MIN_WORKER_KEEPALIVE_INTERVAL_MS, WORKER_KEEPALIVE_INTERVAL_MS, protocolVersion, rehydrateError as rehydrateSerializedError } from "../worker-protocol/index.js";
|
|
5
5
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
@@ -65,6 +65,7 @@ const errorRegistry = new Map([
|
|
|
65
65
|
PostingBuildConflictError,
|
|
66
66
|
StorageCorruptionError,
|
|
67
67
|
StorageFormatVersionError,
|
|
68
|
+
StorageUnresponsiveError,
|
|
68
69
|
OpfsCoordinationError,
|
|
69
70
|
OpfsDatabaseInUseError,
|
|
70
71
|
OpfsUncertainOutcomeError,
|
|
@@ -188,6 +189,9 @@ class MinnowDatabaseClient {
|
|
|
188
189
|
this.#events.clear();
|
|
189
190
|
this.#transport = next;
|
|
190
191
|
this.#ready = this.#attach(next);
|
|
192
|
+
if (this.#onVisibilityChange !== void 0 && typeof document !== "undefined") {
|
|
193
|
+
document.addEventListener("visibilitychange", this.#onVisibilityChange);
|
|
194
|
+
}
|
|
191
195
|
this.#onVisibilityChange?.();
|
|
192
196
|
await this.#ready;
|
|
193
197
|
}
|
|
@@ -578,7 +582,7 @@ class MinnowDatabaseClient {
|
|
|
578
582
|
try {
|
|
579
583
|
await this.#post("rpc-call", null, "dispose", [], void 0, true, { timeoutMs });
|
|
580
584
|
} finally {
|
|
581
|
-
this.#fail(new Error("Database client is closed"));
|
|
585
|
+
this.#fail(new Error("Database client is closed"), true);
|
|
582
586
|
this.#transport.removeEventListener?.("message", this.#onMessage);
|
|
583
587
|
this.#transport.removeEventListener?.("error", this.#onError);
|
|
584
588
|
this.#transport.removeEventListener?.("messageerror", this.#onMessageError);
|
|
@@ -750,25 +754,37 @@ class MinnowDatabaseClient {
|
|
|
750
754
|
}
|
|
751
755
|
#rejectUnreadable(message, cause) {
|
|
752
756
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
753
|
-
const
|
|
754
|
-
cause
|
|
755
|
-
});
|
|
757
|
+
const text = `The database worker sent a frame this client cannot read: ${reason}`;
|
|
756
758
|
const requestId = message.requestId;
|
|
757
759
|
const pending = typeof requestId === "string" ? this.#pending.get(requestId) : void 0;
|
|
758
760
|
if (pending === void 0 || typeof requestId !== "string") {
|
|
759
|
-
this.#fail(
|
|
761
|
+
this.#fail(new DatabaseWorkerFailedError("messageerror", text, { cause }));
|
|
760
762
|
return;
|
|
761
763
|
}
|
|
764
|
+
const error = new Error(text, { cause });
|
|
762
765
|
this.#pending.delete(requestId);
|
|
763
766
|
pending.cleanup?.();
|
|
764
767
|
pending.reject(pending.mayPublish ? new DatabaseWorkerOutcomeUnknownError(pending.method, requestId, { cause: error }) : error);
|
|
765
768
|
}
|
|
766
|
-
#fail(error) {
|
|
769
|
+
#fail(error, closing = false) {
|
|
767
770
|
const first = this.#fatal === void 0;
|
|
768
771
|
this.#fatal = error;
|
|
769
772
|
const pending = [...this.#pending.values()];
|
|
770
773
|
this.#pending.clear();
|
|
774
|
+
const routes = [...this.#events.values()];
|
|
771
775
|
this.#events.clear();
|
|
776
|
+
for (const route of routes) {
|
|
777
|
+
if (!closing) {
|
|
778
|
+
try {
|
|
779
|
+
route.onError?.(error);
|
|
780
|
+
} catch {
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
route.onComplete?.();
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
772
788
|
for (const call of pending) {
|
|
773
789
|
call.cleanup?.();
|
|
774
790
|
call.reject(call.mayPublish ? new DatabaseWorkerOutcomeUnknownError(call.method, call.requestId, { cause: error }) : error);
|