@minnowdb/core 0.4.1 → 0.6.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/README.md +5 -5
- package/dist/engine/cancellation.d.ts +2 -0
- package/dist/engine/cancellation.js +4 -0
- package/dist/engine/catalog.d.ts +5 -1
- package/dist/engine/catalog.js +5 -1
- package/dist/engine/client.d.ts +34 -6
- package/dist/engine/client.js +87 -19
- package/dist/engine/database.d.ts +43 -20
- package/dist/engine/database.js +823 -164
- package/dist/engine/defaults.js +11 -0
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +31 -0
- package/dist/engine/fts.d.ts +2 -15
- package/dist/engine/live.d.ts +1 -7
- package/dist/engine/live.js +12 -13
- package/dist/engine/optimizer.js +546 -39
- package/dist/engine/query-cache.js +1 -0
- package/dist/engine/query.d.ts +16 -278
- package/dist/engine/query.js +260 -74
- package/dist/engine/result-wire.d.ts +2 -0
- package/dist/engine/result-wire.js +21 -5
- package/dist/engine/schema-wire.d.ts +14 -1
- package/dist/engine/schema-wire.js +7 -1
- package/dist/engine/schema.d.ts +83 -32
- package/dist/engine/schema.js +180 -14
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +65 -1
- package/dist/engine/sql-json.js +22 -3
- package/dist/engine/sql-semantics.js +21 -3
- package/dist/engine/vector.d.ts +2 -2
- package/dist/engine/vector.js +328 -79
- package/dist/engine/worker-host.js +119 -44
- package/dist/plan/index.d.ts +5 -4
- package/dist/plan/index.js +3 -3
- package/dist/plan/model.d.ts +218 -0
- package/dist/plan/model.js +1 -0
- package/dist/storage/indexeddb.js +4 -12
- package/dist/storage/toolkit/record-core.js +7 -22
- package/dist/storage/types.d.ts +26 -8
- package/dist/storage/types.js +85 -0
- package/dist/transactions/index.d.ts +5 -3
- package/dist/transactions/index.js +58 -8
- package/dist/worker-protocol/index.d.ts +6 -1
- package/dist/worker-protocol/index.js +5 -2
- package/package.json +1 -1
- package/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +75 -19
package/README.md
CHANGED
|
@@ -9,15 +9,15 @@ npm install @minnowdb/core
|
|
|
9
9
|
|
|
10
10
|
- Direct SQL through `MinnowDatabase.query()` and `execute()`.
|
|
11
11
|
- Joins, CTEs, window functions, grouping sets, upserts, `RETURNING`, triggers, exact decimals,
|
|
12
|
-
JSON/JSONB, arrays, enums, sequences, and savepoints.
|
|
12
|
+
nested JSON/JSONB, stored generated columns, zoneless DATE, arrays, enums, sequences, and savepoints.
|
|
13
13
|
- Compressed column storage, secondary indexes, full-text search, and snapshot reads.
|
|
14
14
|
- Atomic writes across tabs through IndexedDB or OPFS, strict durability by default, and explicit
|
|
15
15
|
origin-eviction persistence policy.
|
|
16
16
|
- A ready-made worker client with the same everyday database API.
|
|
17
|
-
- TypeScript schema declarations and metadata-only migrations, including SQL domains
|
|
18
|
-
composite primary/foreign keys.
|
|
19
|
-
-
|
|
20
|
-
|
|
17
|
+
- TypeScript schema declarations and metadata-only migrations, including SQL domains,
|
|
18
|
+
composite primary/foreign keys, and informational relationships.
|
|
19
|
+
- Guarded columnar upserts, result-column SQL domains, typed catalog errors, and batch writes.
|
|
20
|
+
- Pull-driven query cursors, live queries, snapshots, compaction, and configurable query memory.
|
|
21
21
|
|
|
22
22
|
Use [the PostgreSQL compatibility page](https://minnowdb.com/docs/sql/feature-matrix/) for the
|
|
23
23
|
exact SQL surface. Use [the documentation](https://minnowdb.com/docs/) for installation, storage,
|
package/dist/engine/catalog.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnDefault, type SecondaryIndexState, type SimpleDataType, type SqlDomain, type TableRecord } from "../storage/types.js";
|
|
1
|
+
import { type ColumnDefault, type ColumnGenerated, type SecondaryIndexState, type SimpleDataType, type SqlDomain, type TableRecord } from "../storage/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* The published shape of the catalog: everything a schema tool needs to diff a live database
|
|
4
4
|
* against a desired schema, without reading engine internals.
|
|
@@ -20,6 +20,8 @@ export interface CatalogColumn {
|
|
|
20
20
|
readonly nullable: boolean;
|
|
21
21
|
/** Filled for omitted or SQL DEFAULT insert slots; never applied at read time. */
|
|
22
22
|
readonly defaultValue?: ColumnDefault;
|
|
23
|
+
/** Stored expression maintained by the engine; callers cannot assign this column. */
|
|
24
|
+
readonly generatedValue?: ColumnGenerated;
|
|
23
25
|
/** String columns only: the closed set of values writes may draw from. */
|
|
24
26
|
readonly enumValues?: readonly string[];
|
|
25
27
|
/** Derived from the default spec, because a planner should not have to decode one. */
|
|
@@ -33,6 +35,8 @@ export interface CatalogForeignKey {
|
|
|
33
35
|
readonly parentTable: string;
|
|
34
36
|
readonly parentColumns: readonly string[];
|
|
35
37
|
readonly onDelete: "restrict" | "cascade" | "set null";
|
|
38
|
+
/** Whether writes and parent deletes enforce this relationship. */
|
|
39
|
+
readonly enforced: boolean;
|
|
36
40
|
}
|
|
37
41
|
export interface CatalogCheck {
|
|
38
42
|
readonly name: string;
|
package/dist/engine/catalog.js
CHANGED
|
@@ -9,6 +9,7 @@ function toCatalogColumn(column) {
|
|
|
9
9
|
...(column.sqlDomain === undefined ? {} : { sqlDomain: structuredClone(column.sqlDomain) }),
|
|
10
10
|
nullable: column.nullable,
|
|
11
11
|
...(column.defaultValue === undefined ? {} : { defaultValue: column.defaultValue }),
|
|
12
|
+
...(column.generatedValue === undefined ? {} : { generatedValue: column.generatedValue }),
|
|
12
13
|
...(column.enumValues === undefined ? {} : { enumValues: [...column.enumValues] }),
|
|
13
14
|
...(column.backfill === undefined
|
|
14
15
|
? {}
|
|
@@ -45,7 +46,10 @@ export function toCatalog(records) {
|
|
|
45
46
|
...(record.primaryKeyColumnIds === undefined
|
|
46
47
|
? {}
|
|
47
48
|
: { primaryKeyColumnIds: [...record.primaryKeyColumnIds] }),
|
|
48
|
-
foreignKeys: (record.foreignKeys ?? []).map((key) => ({
|
|
49
|
+
foreignKeys: (record.foreignKeys ?? []).map((key) => ({
|
|
50
|
+
...key,
|
|
51
|
+
enforced: key.enforced !== false,
|
|
52
|
+
})),
|
|
49
53
|
checks: (record.checks ?? []).map((check) => ({ ...check })),
|
|
50
54
|
...(record.secondaryIndexes === undefined
|
|
51
55
|
? {}
|
package/dist/engine/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CompactionJobRecord, type GarbageCollectionJobRecord, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult } from "../storage/types.js";
|
|
2
2
|
import { type BatchRow, type InsertBatchInput } from "./batch.js";
|
|
3
3
|
import type { Catalog } from "./catalog.js";
|
|
4
|
-
import type { BatchValue, BufferPoolStats, StagedWriteResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, DatabaseRow, MigrateOptions, DeleteBatchInput, DeleteBatchResult, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, ReadTableOptions, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchInput, UpdateBatchResult, UpsertBatchResult, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
|
|
4
|
+
import type { BatchValue, BufferPoolStats, StagedWriteResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, DatabaseRow, MigrateOptions, DeleteBatchInput, DeleteBatchResult, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, ReadTableOptions, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchInput, UpdateBatchResult, UpsertBatchResult, UpsertOptions, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
|
|
5
5
|
import type { LiveQueryInput, LiveQueryInvalidation, LiveQueryObserveOptions, LiveQueryStats, LiveQuerySubscribeOptions } from "./live.js";
|
|
6
6
|
import type { CompiledQuery, CompiledStatement, QueryResult, QueryValue } from "./query.js";
|
|
7
7
|
import type { AnyTable, SchemaDefinition } from "./schema.js";
|
|
@@ -65,6 +65,11 @@ interface EventRoute {
|
|
|
65
65
|
onComplete?: () => void;
|
|
66
66
|
/** Payload shape is the handle's own; only snapshot loads emit these today. */
|
|
67
67
|
onProgress?: (progress: unknown) => void;
|
|
68
|
+
onStats?: (stats: QueryExecutionStats) => void;
|
|
69
|
+
}
|
|
70
|
+
interface RpcCallControls {
|
|
71
|
+
signal?: AbortSignal | undefined;
|
|
72
|
+
onStats?: ((stats: QueryExecutionStats) => void) | undefined;
|
|
68
73
|
}
|
|
69
74
|
export declare class MinnowDatabaseClient {
|
|
70
75
|
#private;
|
|
@@ -72,14 +77,37 @@ export declare class MinnowDatabaseClient {
|
|
|
72
77
|
/** Resolves once the worker has opened the store and constructed the database. */
|
|
73
78
|
ready(): Promise<void>;
|
|
74
79
|
createTable(input: CreateTableInput): Promise<void>;
|
|
80
|
+
createView(name: string, sql: string, options?: {
|
|
81
|
+
orReplace?: boolean;
|
|
82
|
+
managed?: boolean;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
dropView(name: string, options?: {
|
|
85
|
+
ifExists?: boolean;
|
|
86
|
+
}): Promise<boolean>;
|
|
87
|
+
dropColumn(tableName: string, columnName: string, options?: {
|
|
88
|
+
ifExists?: boolean;
|
|
89
|
+
}): Promise<boolean>;
|
|
90
|
+
dropTable(tableName: string, options?: {
|
|
91
|
+
ifExists?: boolean;
|
|
92
|
+
}): Promise<boolean>;
|
|
93
|
+
createIndex(indexName: string, tableName: string, requestedColumns: string | ReadonlyArray<{
|
|
94
|
+
name: string;
|
|
95
|
+
direction: "asc" | "desc";
|
|
96
|
+
}>, options?: {
|
|
97
|
+
unique?: boolean;
|
|
98
|
+
}): Promise<void>;
|
|
99
|
+
dropIndex(indexName: string, options?: {
|
|
100
|
+
ifExists?: boolean;
|
|
101
|
+
}): Promise<boolean>;
|
|
102
|
+
buildFtsIndex(tableName: string, columnName: string): Promise<void>;
|
|
75
103
|
/** The published catalog; see `MinnowDatabase.introspect()`. */
|
|
76
104
|
introspect(): Promise<Catalog>;
|
|
77
105
|
listTables(): Promise<TableDefinition[]>;
|
|
78
106
|
migrate(definition: SchemaDefinition<readonly AnyTable[]>, options?: MigrateOptions): Promise<ClientMigrationResult>;
|
|
79
107
|
insertBatch(tableName: string, input: InsertBatchInput): Promise<InsertBatchResult>;
|
|
80
108
|
insert(tableName: string, row: BatchRow): Promise<InsertBatchResult>;
|
|
81
|
-
upsertBatch(tableName: string, input: InsertBatchInput): Promise<UpsertBatchResult>;
|
|
82
|
-
upsert(tableName: string, row: BatchRow): Promise<UpsertBatchResult>;
|
|
109
|
+
upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<UpsertBatchResult>;
|
|
110
|
+
upsert(tableName: string, row: BatchRow, options?: UpsertOptions): Promise<UpsertBatchResult>;
|
|
83
111
|
updateBatch(tableName: string, input: UpdateBatchInput): Promise<UpdateBatchResult>;
|
|
84
112
|
update(tableName: string, key: Exclude<BatchValue, null>, changes: Readonly<Record<string, BatchValue>>): Promise<UpdateBatchResult>;
|
|
85
113
|
deleteBatch(tableName: string, input: DeleteBatchInput): Promise<DeleteBatchResult>;
|
|
@@ -166,6 +194,8 @@ export declare class MinnowDatabaseClient {
|
|
|
166
194
|
/** @internal */
|
|
167
195
|
_invoke(handleId: string, method: string, args: unknown[]): Promise<unknown>;
|
|
168
196
|
/** @internal */
|
|
197
|
+
_invokeControlled(handleId: string, method: string, args: unknown[], controls: RpcCallControls): Promise<unknown>;
|
|
198
|
+
/** @internal */
|
|
169
199
|
_routeEvents(handleId: string, route: EventRoute): void;
|
|
170
200
|
/** @internal */
|
|
171
201
|
_unrouteEvents(handleId: string): void;
|
|
@@ -177,9 +207,7 @@ export declare class MinnowDatabaseClient {
|
|
|
177
207
|
/** The scope handed to the client `write()`; mirrors the in-worker WriteSession. */
|
|
178
208
|
export interface ClientWriteSession {
|
|
179
209
|
/** Read-your-writes: observes the pre-scope snapshot plus everything staged so far. */
|
|
180
|
-
query(sql: string, options?:
|
|
181
|
-
params?: QueryValue[];
|
|
182
|
-
}): Promise<QueryResult>;
|
|
210
|
+
query(sql: string, options?: QueryOptions): Promise<QueryResult>;
|
|
183
211
|
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
184
212
|
insertBatch(tableName: string, input: InsertBatchInput): Promise<StagedWriteResult>;
|
|
185
213
|
upsertBatch(tableName: string, input: InsertBatchInput): Promise<StagedWriteResult>;
|
package/dist/engine/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConfl
|
|
|
2
2
|
import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
|
|
3
3
|
import { parseRpcResponse, MAX_DATABASE_RPC_IN_FLIGHT, protocolVersion, } from "../worker-protocol/index.js";
|
|
4
4
|
import { toColumnarBatch } from "./batch.js";
|
|
5
|
-
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UniqueConstraintError, VisibleSegmentCursorStaleError, } from "./errors.js";
|
|
5
|
+
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, DatabaseReadBacklogError, LiveQueryLimitError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError, } from "./errors.js";
|
|
6
6
|
import { QueryMemoryBudgetError } from "./memory.js";
|
|
7
7
|
import { decodeQueryResult } from "./result-wire.js";
|
|
8
8
|
import { serializeSchema } from "./schema-wire.js";
|
|
@@ -30,11 +30,14 @@ function throwIfClientSnapshotAborted(signal) {
|
|
|
30
30
|
const errorRegistry = new Map([
|
|
31
31
|
UniqueConstraintError,
|
|
32
32
|
MissingKeyError,
|
|
33
|
+
UnknownTableError,
|
|
33
34
|
CompactionBacklogError,
|
|
34
35
|
CompactionMemoryBudgetError,
|
|
35
36
|
CompactionWriteAmplificationError,
|
|
36
37
|
CompactionJobCancelledError,
|
|
37
38
|
MaintenanceBacklogError,
|
|
39
|
+
DatabaseReadBacklogError,
|
|
40
|
+
LiveQueryLimitError,
|
|
38
41
|
SqlCompileError,
|
|
39
42
|
QueryMemoryBudgetError,
|
|
40
43
|
VisibleSegmentCursorStaleError,
|
|
@@ -141,6 +144,27 @@ export class MinnowDatabaseClient {
|
|
|
141
144
|
async createTable(input) {
|
|
142
145
|
await this.#call("createTable", [input]);
|
|
143
146
|
}
|
|
147
|
+
async createView(name, sql, options = {}) {
|
|
148
|
+
await this.#call("createView", [name, sql, options]);
|
|
149
|
+
}
|
|
150
|
+
async dropView(name, options = {}) {
|
|
151
|
+
return (await this.#call("dropView", [name, options]));
|
|
152
|
+
}
|
|
153
|
+
async dropColumn(tableName, columnName, options = {}) {
|
|
154
|
+
return (await this.#call("dropColumn", [tableName, columnName, options]));
|
|
155
|
+
}
|
|
156
|
+
async dropTable(tableName, options = {}) {
|
|
157
|
+
return (await this.#call("dropTable", [tableName, options]));
|
|
158
|
+
}
|
|
159
|
+
async createIndex(indexName, tableName, requestedColumns, options = {}) {
|
|
160
|
+
await this.#call("createIndex", [indexName, tableName, requestedColumns, options]);
|
|
161
|
+
}
|
|
162
|
+
async dropIndex(indexName, options = {}) {
|
|
163
|
+
return (await this.#call("dropIndex", [indexName, options]));
|
|
164
|
+
}
|
|
165
|
+
async buildFtsIndex(tableName, columnName) {
|
|
166
|
+
await this.#call("buildFtsIndex", [tableName, columnName]);
|
|
167
|
+
}
|
|
144
168
|
/** The published catalog; see `MinnowDatabase.introspect()`. */
|
|
145
169
|
async introspect() {
|
|
146
170
|
return (await this.#call("introspect", []));
|
|
@@ -163,12 +187,12 @@ export class MinnowDatabaseClient {
|
|
|
163
187
|
async insert(tableName, row) {
|
|
164
188
|
return (await this.#call("insert", [tableName, row]));
|
|
165
189
|
}
|
|
166
|
-
async upsertBatch(tableName, input) {
|
|
190
|
+
async upsertBatch(tableName, input, options = {}) {
|
|
167
191
|
const batch = toColumnarBatch(input);
|
|
168
|
-
return (await this.#call("upsertBatch", [tableName, batch]));
|
|
192
|
+
return (await this.#call("upsertBatch", [tableName, batch, options]));
|
|
169
193
|
}
|
|
170
|
-
async upsert(tableName, row) {
|
|
171
|
-
return (await this.#call("upsert", [tableName, row]));
|
|
194
|
+
async upsert(tableName, row, options = {}) {
|
|
195
|
+
return (await this.#call("upsert", [tableName, row, options]));
|
|
172
196
|
}
|
|
173
197
|
async updateBatch(tableName, input) {
|
|
174
198
|
return (await this.#call("updateBatch", [tableName, input]));
|
|
@@ -200,17 +224,17 @@ export class MinnowDatabaseClient {
|
|
|
200
224
|
* datetimes) and are rebuilt into row objects here; see `result-wire.ts`.
|
|
201
225
|
*/
|
|
202
226
|
async query(sql, options) {
|
|
203
|
-
|
|
227
|
+
const { signal, onStats, ...wireOptions } = options ?? {};
|
|
228
|
+
return decodeQueryResult(await this.#call("query", [sql, wireOptions, onStats !== undefined], { signal, onStats }));
|
|
204
229
|
}
|
|
205
230
|
/** Pulls one transferred columnar page at a time, preserving worker backpressure. */
|
|
206
231
|
queryCursor(sql, options = {}) {
|
|
207
232
|
const call = this.#call.bind(this);
|
|
208
233
|
const invoke = this._invoke.bind(this);
|
|
234
|
+
const routeEvents = this._routeEvents.bind(this);
|
|
235
|
+
const unrouteEvents = this._unrouteEvents.bind(this);
|
|
209
236
|
const handleId = crypto.randomUUID();
|
|
210
237
|
const { signal, onStats, ...wireOptions } = options;
|
|
211
|
-
if (onStats !== undefined) {
|
|
212
|
-
throw new TypeError("Query cursor onStats callbacks are not available across a worker");
|
|
213
|
-
}
|
|
214
238
|
async function* batches() {
|
|
215
239
|
let opened = false;
|
|
216
240
|
let closing;
|
|
@@ -224,9 +248,11 @@ export class MinnowDatabaseClient {
|
|
|
224
248
|
void close();
|
|
225
249
|
};
|
|
226
250
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
251
|
+
if (onStats !== undefined)
|
|
252
|
+
routeEvents(handleId, { onStats });
|
|
227
253
|
try {
|
|
228
254
|
signal?.throwIfAborted();
|
|
229
|
-
await call("queryCursorOpen", [handleId, sql, wireOptions]);
|
|
255
|
+
await call("queryCursorOpen", [handleId, sql, wireOptions, onStats !== undefined]);
|
|
230
256
|
opened = true;
|
|
231
257
|
if (signal?.aborted === true) {
|
|
232
258
|
await close();
|
|
@@ -242,6 +268,7 @@ export class MinnowDatabaseClient {
|
|
|
242
268
|
finally {
|
|
243
269
|
signal?.removeEventListener("abort", onAbort);
|
|
244
270
|
await close();
|
|
271
|
+
unrouteEvents(handleId);
|
|
245
272
|
}
|
|
246
273
|
}
|
|
247
274
|
return batches();
|
|
@@ -290,7 +317,10 @@ export class MinnowDatabaseClient {
|
|
|
290
317
|
const opened = (await this.#call("writeOpen", []));
|
|
291
318
|
const stage = (op, tableName, input) => this._invoke(opened.handleId, "stage", [op, tableName, input]);
|
|
292
319
|
const session = {
|
|
293
|
-
query: async (sql, options) =>
|
|
320
|
+
query: async (sql, options = {}) => {
|
|
321
|
+
const { signal, onStats, ...wireOptions } = options;
|
|
322
|
+
return decodeQueryResult(await this._invokeControlled(opened.handleId, "query", [sql, wireOptions, onStats !== undefined], { signal, onStats }));
|
|
323
|
+
},
|
|
294
324
|
execute: (sql, params) => this._invoke(opened.handleId, "execute", params === undefined ? [sql] : [sql, params]),
|
|
295
325
|
insertBatch: (tableName, input) => stage("insertBatch", tableName, input),
|
|
296
326
|
upsertBatch: (tableName, input) => stage("upsertBatch", tableName, input),
|
|
@@ -501,6 +531,12 @@ export class MinnowDatabaseClient {
|
|
|
501
531
|
return this.#post("rpc-call", handleId, method, args);
|
|
502
532
|
}
|
|
503
533
|
/** @internal */
|
|
534
|
+
async _invokeControlled(handleId, method, args, controls) {
|
|
535
|
+
if (this.#closed)
|
|
536
|
+
throw new Error("Database client is closed");
|
|
537
|
+
return this.#post("rpc-call", handleId, method, args, undefined, false, controls);
|
|
538
|
+
}
|
|
539
|
+
/** @internal */
|
|
504
540
|
_routeEvents(handleId, route) {
|
|
505
541
|
this.#events.set(handleId, route);
|
|
506
542
|
}
|
|
@@ -508,23 +544,49 @@ export class MinnowDatabaseClient {
|
|
|
508
544
|
_unrouteEvents(handleId) {
|
|
509
545
|
this.#events.delete(handleId);
|
|
510
546
|
}
|
|
511
|
-
async #call(method, args) {
|
|
547
|
+
async #call(method, args, controls = {}) {
|
|
512
548
|
if (this.#closed)
|
|
513
549
|
throw new Error("Database client is closed");
|
|
514
|
-
return this.#post("rpc-call", null, method, args);
|
|
550
|
+
return this.#post("rpc-call", null, method, args, undefined, false, controls);
|
|
515
551
|
}
|
|
516
|
-
async #post(kind, handleId, method, args, transfer, bypassLimit = kind === "rpc-init" || method === "dispose") {
|
|
552
|
+
async #post(kind, handleId, method, args, transfer, bypassLimit = kind === "rpc-init" || method === "dispose", controls = {}) {
|
|
517
553
|
if (this.#fatal !== undefined)
|
|
518
554
|
throw this.#fatal;
|
|
555
|
+
controls.signal?.throwIfAborted();
|
|
519
556
|
if (!bypassLimit && this.#pending.size >= MAX_DATABASE_RPC_IN_FLIGHT) {
|
|
520
557
|
throw new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`);
|
|
521
558
|
}
|
|
522
559
|
const requestId = crypto.randomUUID();
|
|
523
560
|
return new Promise((resolve, reject) => {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
561
|
+
const onAbort = () => {
|
|
562
|
+
try {
|
|
563
|
+
this.#transport.postMessage({ version: protocolVersion, requestId, kind: "rpc-cancel" });
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// The original RPC still owns completion. A transport failure also reaches #fail via
|
|
567
|
+
// its error event; throwing from an AbortSignal listener would only be unhandled noise.
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
const cleanup = () => {
|
|
571
|
+
controls.signal?.removeEventListener("abort", onAbort);
|
|
572
|
+
if (controls.onStats !== undefined)
|
|
573
|
+
this.#events.delete(requestId);
|
|
574
|
+
};
|
|
575
|
+
controls.signal?.addEventListener("abort", onAbort, { once: true });
|
|
576
|
+
if (controls.onStats !== undefined) {
|
|
577
|
+
this.#events.set(requestId, { onStats: controls.onStats });
|
|
578
|
+
}
|
|
579
|
+
this.#pending.set(requestId, { resolve, reject, cleanup });
|
|
580
|
+
try {
|
|
581
|
+
this.#transport.postMessage(kind === "rpc-init"
|
|
582
|
+
? { version: protocolVersion, requestId, kind, payload: args[0] }
|
|
583
|
+
: { version: protocolVersion, requestId, kind, handleId, method, args }, transfer === undefined ? undefined : { transfer });
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
this.#pending.delete(requestId);
|
|
587
|
+
cleanup();
|
|
588
|
+
reject(error instanceof Error ? error : new Error("Database request failed", { cause: error }));
|
|
589
|
+
}
|
|
528
590
|
});
|
|
529
591
|
}
|
|
530
592
|
#receive(message) {
|
|
@@ -545,6 +607,9 @@ export class MinnowDatabaseClient {
|
|
|
545
607
|
}
|
|
546
608
|
else if (response.event === "progress")
|
|
547
609
|
route.onProgress?.(response.payload);
|
|
610
|
+
else if (response.event === "stats") {
|
|
611
|
+
route.onStats?.(response.payload);
|
|
612
|
+
}
|
|
548
613
|
else if (response.event === "complete") {
|
|
549
614
|
this.#events.delete(response.handleId);
|
|
550
615
|
route.onComplete?.();
|
|
@@ -555,6 +620,7 @@ export class MinnowDatabaseClient {
|
|
|
555
620
|
if (pending === undefined)
|
|
556
621
|
return;
|
|
557
622
|
this.#pending.delete(response.requestId);
|
|
623
|
+
pending.cleanup?.();
|
|
558
624
|
if (response.kind === "rpc-result")
|
|
559
625
|
pending.resolve(response.result);
|
|
560
626
|
else
|
|
@@ -565,8 +631,10 @@ export class MinnowDatabaseClient {
|
|
|
565
631
|
const pending = [...this.#pending.values()];
|
|
566
632
|
this.#pending.clear();
|
|
567
633
|
this.#events.clear();
|
|
568
|
-
for (const call of pending)
|
|
634
|
+
for (const call of pending) {
|
|
635
|
+
call.cleanup?.();
|
|
569
636
|
call.reject(error);
|
|
637
|
+
}
|
|
570
638
|
}
|
|
571
639
|
}
|
|
572
640
|
/**
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type BatchRow, type BatchValue, type InsertBatchInput } from "./batch.js";
|
|
2
2
|
import { BufferedTableWriter, type BufferedWriterOptions } from "./buffered-writer.js";
|
|
3
3
|
export { attachLifecycleFlush, BufferedTableWriter, MAX_BUFFERED_WRITER_PENDING_ADDS, type BufferedFlushResult, type BufferedWriterOptions, type LifecycleDocumentTarget, type LifecycleFlushOptions, type LifecycleFlushRequester, type LifecyclePageTarget, } from "./buffered-writer.js";
|
|
4
|
-
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
4
|
+
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
5
|
+
export { DatabaseReadBacklogError } from "./errors.js";
|
|
5
6
|
import { type Compression } from "../block-format/index.js";
|
|
6
|
-
import { type BlockStore, type ColumnDefault, CompactionBacklogError, type CompactionJobRecord, type CompactionJobState, type GarbageCollectionJobRecord, type GarbageCollectionJobState, type SimpleDataType, type SqlDomain, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult, TableInUseError } from "../storage/types.js";
|
|
7
|
+
import { type BlockStore, type ColumnDefault, type ColumnGenerated, CompactionBacklogError, type CompactionJobRecord, type CompactionJobState, type GarbageCollectionJobRecord, type GarbageCollectionJobState, type SimpleDataType, type SqlDomain, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult, TableInUseError } from "../storage/types.js";
|
|
7
8
|
import type { SnapshotExportProgress, SnapshotLoadProgress } from "../storage/snapshot.js";
|
|
8
|
-
import { type CompiledQuery, type CompiledStatement, type ForeignKeyDefinition, type QueryResult, type QueryRow, type QueryValue, type UniqueConstraintDefinition } from "./query.js";
|
|
9
|
+
import { type ComparisonOperator, type CompiledQuery, type CompiledStatement, type ForeignKeyDefinition, type QueryResult, type QueryRow, type QueryValue, type UniqueConstraintDefinition } from "./query.js";
|
|
9
10
|
import { LiveQuerySet, type LiveQuerySetOptions } from "./live.js";
|
|
10
11
|
import { type Catalog } from "./catalog.js";
|
|
11
12
|
import { type AnyTable, type MigrationStep, type SchemaDefinition } from "./schema.js";
|
|
@@ -16,11 +17,6 @@ export declare const MAX_TRANSACTION_SAVEPOINT_BYTES: number;
|
|
|
16
17
|
export declare const MAX_DATABASE_PENDING_WRITES = 64;
|
|
17
18
|
/** Concurrent direct reads retained by one database instance. Reads still execute in parallel. */
|
|
18
19
|
export declare const MAX_DATABASE_ACTIVE_READS = 256;
|
|
19
|
-
export declare class DatabaseReadBacklogError extends Error {
|
|
20
|
-
readonly limit: number;
|
|
21
|
-
readonly name = "DatabaseReadBacklogError";
|
|
22
|
-
constructor(limit?: number);
|
|
23
|
-
}
|
|
24
20
|
export interface ColumnDefinition {
|
|
25
21
|
name: string;
|
|
26
22
|
type: SimpleDataType;
|
|
@@ -31,6 +27,8 @@ export interface ColumnDefinition {
|
|
|
31
27
|
nullable?: boolean;
|
|
32
28
|
/** Fills omitted or SQL DEFAULT slots at insert time; never applied at read time. */
|
|
33
29
|
defaultValue?: ColumnDefault;
|
|
30
|
+
/** Stored expression over sibling columns; callers cannot assign this column. */
|
|
31
|
+
generatedValue?: ColumnGenerated;
|
|
34
32
|
/** String columns only: the closed set of values writes must draw from. */
|
|
35
33
|
enumValues?: readonly string[];
|
|
36
34
|
/** What rows written before this column existed read as, instead of NULL. */
|
|
@@ -91,9 +89,23 @@ export interface InsertBatchResult {
|
|
|
91
89
|
*/
|
|
92
90
|
generatedColumns?: Record<string, BatchValue[]>;
|
|
93
91
|
}
|
|
94
|
-
export interface UpsertBatchResult extends InsertBatchResult {
|
|
92
|
+
export interface UpsertBatchResult extends Omit<InsertBatchResult, "segmentId"> {
|
|
93
|
+
segmentId: string | null;
|
|
94
|
+
/** Input rows considered by the statement, including conflicts rejected by `conflictWhere`. */
|
|
95
|
+
requestedRowCount: number;
|
|
95
96
|
insertedRowCount: number;
|
|
96
97
|
updatedRowCount: number;
|
|
98
|
+
skippedRowCount: number;
|
|
99
|
+
}
|
|
100
|
+
export interface UpsertConflictWhere {
|
|
101
|
+
/** Column of the existing conflicting row to test. */
|
|
102
|
+
column: string;
|
|
103
|
+
operator: ComparisonOperator;
|
|
104
|
+
value: BatchValue;
|
|
105
|
+
}
|
|
106
|
+
export interface UpsertOptions {
|
|
107
|
+
/** SQL `ON CONFLICT DO UPDATE ... WHERE` semantics over the existing target row. */
|
|
108
|
+
conflictWhere?: UpsertConflictWhere;
|
|
97
109
|
}
|
|
98
110
|
export interface UpdateBatchInput {
|
|
99
111
|
/** Stable ordinary arrays; do not mutate or expose changing accessors until the write settles. */
|
|
@@ -111,6 +123,8 @@ export interface UpdateBatchResult {
|
|
|
111
123
|
storedBytes: number;
|
|
112
124
|
version: number;
|
|
113
125
|
metrics: WriteMetrics;
|
|
126
|
+
/** Recomputed generated-column vectors in input key order. */
|
|
127
|
+
generatedColumns?: Record<string, BatchValue[]>;
|
|
114
128
|
}
|
|
115
129
|
export interface WriteMetrics {
|
|
116
130
|
logicalBytes: number;
|
|
@@ -216,6 +230,11 @@ export interface MaintenanceStatus {
|
|
|
216
230
|
} | null;
|
|
217
231
|
}
|
|
218
232
|
export interface QueryOptions {
|
|
233
|
+
/**
|
|
234
|
+
* Stops a read between bounded execution or storage batches. An abort never returns a partial
|
|
235
|
+
* result and releases any reader lease and temporary spill owner before the promise rejects.
|
|
236
|
+
*/
|
|
237
|
+
readonly signal?: AbortSignal;
|
|
219
238
|
/**
|
|
220
239
|
* Called once with what this execution cost, before the result is returned. Additive and
|
|
221
240
|
* optional: the engine can report its own memory because it reserves before it allocates,
|
|
@@ -254,8 +273,6 @@ export interface QueryOptions {
|
|
|
254
273
|
export interface QueryCursorOptions extends QueryOptions {
|
|
255
274
|
/** Maximum rows in one yielded result batch. Defaults to the vector scan batch size. */
|
|
256
275
|
readonly batchRows?: number;
|
|
257
|
-
/** Cancels the scan and releases its snapshot lease. */
|
|
258
|
-
readonly signal?: AbortSignal;
|
|
259
276
|
}
|
|
260
277
|
export interface DeleteBatchInput {
|
|
261
278
|
/** Stable ordinary array; do not mutate or expose changing accessors until the write settles. */
|
|
@@ -415,7 +432,7 @@ export interface TableDefinition {
|
|
|
415
432
|
uniqueKey?: string;
|
|
416
433
|
}
|
|
417
434
|
export type DatabaseRow = Record<string, Exclude<BatchValue, null> | null>;
|
|
418
|
-
export { CompactionBacklogError, CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, TableInUseError, UniqueConstraintError, VisibleSegmentCursorStaleError, };
|
|
435
|
+
export { CompactionBacklogError, CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, TableInUseError, UniqueConstraintError, VisibleSegmentCursorStaleError, };
|
|
419
436
|
export interface MinnowDatabaseOptions {
|
|
420
437
|
/**
|
|
421
438
|
* Block codec for newly written blocks; defaults to "gzip", which is also what compaction
|
|
@@ -570,26 +587,30 @@ export type ExecuteResult = {
|
|
|
570
587
|
} | {
|
|
571
588
|
kind: "drop-trigger";
|
|
572
589
|
name: string;
|
|
573
|
-
} | {
|
|
590
|
+
} | ({
|
|
574
591
|
kind: "insert";
|
|
575
592
|
table: string;
|
|
576
593
|
rowCount: number;
|
|
577
594
|
/** Absent when the statement affected no rows (an INSERT ... SELECT of an empty set). */
|
|
578
595
|
version?: number;
|
|
579
|
-
|
|
580
|
-
} | {
|
|
596
|
+
} & ReturningExecuteFields) | ({
|
|
581
597
|
kind: "update";
|
|
582
598
|
table: string;
|
|
583
599
|
rowCount: number;
|
|
584
600
|
version?: number;
|
|
585
|
-
|
|
586
|
-
} | {
|
|
601
|
+
} & ReturningExecuteFields) | ({
|
|
587
602
|
kind: "delete";
|
|
588
603
|
table: string;
|
|
589
604
|
rowCount: number;
|
|
590
605
|
version?: number | null;
|
|
606
|
+
} & ReturningExecuteFields);
|
|
607
|
+
interface ReturningExecuteFields {
|
|
591
608
|
returnedRows?: QueryRow[];
|
|
592
|
-
|
|
609
|
+
/** RETURNING projection order; present with returnedRows, including for an empty result. */
|
|
610
|
+
returnedColumns?: string[];
|
|
611
|
+
/** Logical domains aligned with returnedColumns. */
|
|
612
|
+
returnedColumnDomains?: Array<SqlDomain | null>;
|
|
613
|
+
}
|
|
593
614
|
export interface RunStatementOptions {
|
|
594
615
|
/**
|
|
595
616
|
* Projects the affected rows back: column names, or "*" for every table column. Inserts echo
|
|
@@ -722,10 +743,12 @@ export declare class MinnowDatabase {
|
|
|
722
743
|
}): Promise<boolean>;
|
|
723
744
|
insertBatch(tableName: string, input: InsertBatchInput): Promise<InsertBatchResult>;
|
|
724
745
|
insert(tableName: string, row: BatchRow): Promise<InsertBatchResult>;
|
|
725
|
-
upsertBatch(tableName: string, input: InsertBatchInput): Promise<UpsertBatchResult>;
|
|
726
|
-
upsert(tableName: string, row: BatchRow): Promise<UpsertBatchResult>;
|
|
746
|
+
upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<UpsertBatchResult>;
|
|
747
|
+
upsert(tableName: string, row: BatchRow, options?: UpsertOptions): Promise<UpsertBatchResult>;
|
|
727
748
|
updateBatch(tableName: string, input: UpdateBatchInput): Promise<UpdateBatchResult>;
|
|
728
749
|
update(tableName: string, key: Exclude<BatchValue, null>, changes: Readonly<Record<string, BatchValue>>): Promise<UpdateBatchResult>;
|
|
750
|
+
/** Deletes one row by the table's unique key. */
|
|
751
|
+
delete(tableName: string, key: Exclude<BatchValue, null>): Promise<DeleteBatchResult>;
|
|
729
752
|
deleteBatch(tableName: string, input: DeleteBatchInput): Promise<DeleteBatchResult>;
|
|
730
753
|
bufferedWriter(tableName: string, options?: BufferedWriterOptions): BufferedTableWriter;
|
|
731
754
|
readTable(tableName: string, versionOrOptions?: number | ReadTableOptions): Promise<DatabaseRow[]>;
|