@minnowdb/core 0.7.7 → 0.7.8
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/batch.d.ts +14 -1
- package/dist/engine/batch.js +23 -2
- package/dist/engine/buffered-writer.d.ts +7 -4
- package/dist/engine/client.d.ts +42 -25
- package/dist/engine/client.js +16 -6
- package/dist/engine/database.d.ts +40 -22
- package/dist/engine/database.js +32 -20
- package/dist/engine/schema.d.ts +85 -2
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +1 -1
package/dist/engine/batch.d.ts
CHANGED
|
@@ -31,5 +31,18 @@ export interface ColumnarBatch {
|
|
|
31
31
|
}
|
|
32
32
|
/** What `insertBatch` and `upsertBatch` take: rows, or columns for a bulk load. */
|
|
33
33
|
export type InsertBatchInput = readonly BatchRow[] | ColumnarBatch;
|
|
34
|
+
/**
|
|
35
|
+
* `ColumnarBatch` as the schema-typed overloads hand it to the erased implementation: a column
|
|
36
|
+
* vector or omission mask may be `undefined`, meaning that column is not in the batch. Only the
|
|
37
|
+
* type is looser — `toColumnarBatch` drops such entries, so nothing downstream sees them.
|
|
38
|
+
*/
|
|
39
|
+
export interface ColumnarBatchLike {
|
|
40
|
+
readonly columns: Readonly<Record<string, readonly BatchValue[] | undefined>>;
|
|
41
|
+
readonly omitted?: Readonly<Record<string, readonly boolean[] | undefined>>;
|
|
42
|
+
readonly rowCount?: number;
|
|
43
|
+
}
|
|
44
|
+
export type InsertBatchInputLike = readonly BatchRow[] | ColumnarBatchLike;
|
|
45
|
+
/** The entries of a per-column record whose vector is present. */
|
|
46
|
+
export declare function definedVectors<T>(vectors: Readonly<Record<string, T | undefined>>): Record<string, T>;
|
|
34
47
|
/** Pivots rows into the engine's columnar form; a columnar batch passes straight through. */
|
|
35
|
-
export declare function toColumnarBatch(input:
|
|
48
|
+
export declare function toColumnarBatch(input: InsertBatchInputLike): ColumnarBatch;
|
package/dist/engine/batch.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
function definedVectors(vectors) {
|
|
2
|
+
const defined = {};
|
|
3
|
+
for (const [name, vector] of Object.entries(vectors)) {
|
|
4
|
+
if (vector !== void 0)
|
|
5
|
+
defined[name] = vector;
|
|
6
|
+
}
|
|
7
|
+
return defined;
|
|
8
|
+
}
|
|
9
|
+
function hasUndefinedVector(vectors) {
|
|
10
|
+
return Object.values(vectors).includes(void 0);
|
|
11
|
+
}
|
|
1
12
|
function isColumnarBatch(input) {
|
|
2
13
|
return !Array.isArray(input);
|
|
3
14
|
}
|
|
4
15
|
function toColumnarBatch(input) {
|
|
5
|
-
if (isColumnarBatch(input))
|
|
6
|
-
|
|
16
|
+
if (isColumnarBatch(input)) {
|
|
17
|
+
const columnsComplete = !hasUndefinedVector(input.columns);
|
|
18
|
+
const omittedComplete = input.omitted === void 0 || !hasUndefinedVector(input.omitted);
|
|
19
|
+
if (columnsComplete && omittedComplete)
|
|
20
|
+
return input;
|
|
21
|
+
return {
|
|
22
|
+
columns: definedVectors(input.columns),
|
|
23
|
+
...input.omitted === void 0 ? {} : { omitted: definedVectors(input.omitted) },
|
|
24
|
+
...input.rowCount === void 0 ? {} : { rowCount: input.rowCount }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
7
27
|
if (input.length === 0)
|
|
8
28
|
throw new TypeError("A batch needs at least one row");
|
|
9
29
|
const columnsByName = /* @__PURE__ */ new Map();
|
|
@@ -36,5 +56,6 @@ function toColumnarBatch(input) {
|
|
|
36
56
|
};
|
|
37
57
|
}
|
|
38
58
|
export {
|
|
59
|
+
definedVectors,
|
|
39
60
|
toColumnarBatch
|
|
40
61
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BatchRow } from "./batch.js";
|
|
2
2
|
import type { InsertBatchResult, MinnowDatabase, UpsertBatchResult } from "./database.js";
|
|
3
3
|
export interface BufferedWriterOptions {
|
|
4
4
|
mode?: "insert" | "upsert";
|
|
@@ -26,15 +26,18 @@ export interface LifecycleFlushOptions {
|
|
|
26
26
|
}
|
|
27
27
|
/** Maximum accepted `add()` calls that have not completed. Callers must await for backpressure. */
|
|
28
28
|
export declare const MAX_BUFFERED_WRITER_PENDING_ADDS = 64;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Batches row-oriented writes by row count, estimated bytes, or age. `TRow` is the declared
|
|
31
|
+
* table's insert row when the database that opened the writer carries a schema.
|
|
32
|
+
*/
|
|
33
|
+
export declare class BufferedTableWriter<TRow extends BatchRow = BatchRow> {
|
|
31
34
|
#private;
|
|
32
35
|
private readonly database;
|
|
33
36
|
private readonly tableName;
|
|
34
37
|
constructor(database: MinnowDatabase, tableName: string, options?: BufferedWriterOptions);
|
|
35
38
|
get pendingRowCount(): number;
|
|
36
39
|
get estimatedBytes(): number;
|
|
37
|
-
add(row:
|
|
40
|
+
add(row: TRow): Promise<BufferedFlushResult | undefined>;
|
|
38
41
|
flush(): Promise<BufferedFlushResult | undefined>;
|
|
39
42
|
requestFlush(): void;
|
|
40
43
|
close(): Promise<BufferedFlushResult | undefined>;
|
package/dist/engine/client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type CompactionJobRecord, type GarbageCollectionJobRecord, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult } from "../storage/types.js";
|
|
2
|
-
import { type BatchRow
|
|
2
|
+
import { type BatchRow } from "./batch.js";
|
|
3
3
|
import type { Catalog } from "./catalog.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { BufferPoolStats, StagedWriteResult, StagedUpsertResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, MigrateOptions, DeleteBatchResult, ExecuteOptions, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchResult, UpsertBatchResult, 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
|
-
import type { AnyTable, SchemaDefinition } from "./schema.js";
|
|
7
|
+
import type { AnySchema, UntypedSchema, AnyTable, BatchColumnName, BatchDeleteInput, BatchInsertInput, BatchInsertRow, BatchKeyValue, BatchReadOptions, BatchReadRow, BatchUpdateChanges, BatchUpdateInput, BatchUpsertOptions, SchemaDefinition, TableName } from "./schema.js";
|
|
8
8
|
import { type WireMigrationStep } from "./schema-wire.js";
|
|
9
9
|
import type { StoreDescriptor, WireDatabaseOptions } from "./worker-host.js";
|
|
10
10
|
/**
|
|
@@ -31,7 +31,13 @@ export interface ClientTransport {
|
|
|
31
31
|
removeEventListener?(type: "error" | "messageerror", listener: () => void): void;
|
|
32
32
|
terminate?(): void;
|
|
33
33
|
}
|
|
34
|
-
export interface MinnowDatabaseClientOptions {
|
|
34
|
+
export interface MinnowDatabaseClientOptions<TSchema extends AnySchema = UntypedSchema> {
|
|
35
|
+
/**
|
|
36
|
+
* The schema this database is declared against. It types every batch method by table name and
|
|
37
|
+
* is what a bare `migrate()` applies. It stays on the main thread: the worker learns the
|
|
38
|
+
* schema from `migrate()`, not from construction.
|
|
39
|
+
*/
|
|
40
|
+
schema?: TSchema;
|
|
35
41
|
/**
|
|
36
42
|
* Defaults to `{ kind: "indexeddb", name: "minnow" }`. The `opfs` kind selects
|
|
37
43
|
* `OpfsBlockStore`, which needs the worker to be a dedicated worker (it always is with
|
|
@@ -71,9 +77,9 @@ interface RpcCallControls {
|
|
|
71
77
|
signal?: AbortSignal | undefined;
|
|
72
78
|
onStats?: ((stats: QueryExecutionStats) => void) | undefined;
|
|
73
79
|
}
|
|
74
|
-
export declare class MinnowDatabaseClient {
|
|
80
|
+
export declare class MinnowDatabaseClient<TSchema extends AnySchema = UntypedSchema> {
|
|
75
81
|
#private;
|
|
76
|
-
constructor(transport: ClientTransport, options?: MinnowDatabaseClientOptions);
|
|
82
|
+
constructor(transport: ClientTransport, options?: MinnowDatabaseClientOptions<TSchema>);
|
|
77
83
|
/** Resolves once the worker has opened the store and constructed the database. */
|
|
78
84
|
ready(): Promise<void>;
|
|
79
85
|
createTable(input: CreateTableInput): Promise<void>;
|
|
@@ -103,17 +109,28 @@ export declare class MinnowDatabaseClient {
|
|
|
103
109
|
/** The published catalog; see `MinnowDatabase.introspect()`. */
|
|
104
110
|
introspect(): Promise<Catalog>;
|
|
105
111
|
listTables(): Promise<TableDefinition[]>;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Brings storage in line with a schema declaration. With no argument it applies the schema the
|
|
114
|
+
* client was constructed with.
|
|
115
|
+
*/
|
|
116
|
+
migrate(definition?: SchemaDefinition<readonly AnyTable[]> | undefined, options?: MigrateOptions): Promise<ClientMigrationResult>;
|
|
117
|
+
insertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>): Promise<InsertBatchResult>;
|
|
118
|
+
insert<TName extends TableName<TSchema>>(tableName: TName, row: BatchInsertRow<TSchema, TName>): Promise<InsertBatchResult>;
|
|
119
|
+
upsertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<UpsertBatchResult>;
|
|
120
|
+
upsert<TName extends TableName<TSchema>>(tableName: TName, row: BatchInsertRow<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<UpsertBatchResult>;
|
|
121
|
+
updateBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchUpdateInput<TSchema, TName>): Promise<UpdateBatchResult>;
|
|
122
|
+
/**
|
|
123
|
+
* Changes one row by the table's unique key. An explicitly `undefined` change leaves that
|
|
124
|
+
* column untouched, so a patch spread from optional fields needs no filtering first.
|
|
125
|
+
*/
|
|
126
|
+
update<TName extends TableName<TSchema>>(tableName: TName, key: BatchKeyValue<TSchema, TName>, changes: BatchUpdateChanges<TSchema, TName>): Promise<UpdateBatchResult>;
|
|
127
|
+
deleteBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchDeleteInput<TSchema, TName>): Promise<DeleteBatchResult>;
|
|
128
|
+
delete<TName extends TableName<TSchema>>(tableName: TName, key: BatchKeyValue<TSchema, TName>): Promise<DeleteBatchResult>;
|
|
129
|
+
bufferedWriter<TName extends TableName<TSchema>>(tableName: TName, options?: BufferedWriterOptions): ClientBufferedWriter<BatchInsertRow<TSchema, TName>>;
|
|
130
|
+
readTable<TName extends TableName<TSchema>, const TColumns extends ReadonlyArray<BatchColumnName<TSchema, TName>>>(tableName: TName, options: BatchReadOptions<TSchema, TName, TColumns> & {
|
|
131
|
+
readonly columns: TColumns;
|
|
132
|
+
}): Promise<Array<Pick<BatchReadRow<TSchema, TName>, TColumns[number]>>>;
|
|
133
|
+
readTable<TName extends TableName<TSchema>>(tableName: TName, versionOrOptions?: number | BatchReadOptions<TSchema, TName>): Promise<Array<BatchReadRow<TSchema, TName>>>;
|
|
117
134
|
/**
|
|
118
135
|
* Results cross the channel as one array per column (typed arrays for numbers, booleans, and
|
|
119
136
|
* datetimes) and are rebuilt into row objects here; see `result-wire.ts`.
|
|
@@ -142,7 +159,7 @@ export declare class MinnowDatabaseClient {
|
|
|
142
159
|
* transaction and publishes as one atomic commit when the callback returns; an error
|
|
143
160
|
* aborts the scope with nothing published.
|
|
144
161
|
*/
|
|
145
|
-
write<T>(action: (session: ClientWriteSession) => Promise<T>): Promise<{
|
|
162
|
+
write<T>(action: (session: ClientWriteSession<TSchema>) => Promise<T>): Promise<{
|
|
146
163
|
result: T;
|
|
147
164
|
version: number | null;
|
|
148
165
|
}>;
|
|
@@ -205,14 +222,14 @@ export declare class MinnowDatabaseClient {
|
|
|
205
222
|
* consistent with each other for the lifetime of the callback.
|
|
206
223
|
*/
|
|
207
224
|
/** The scope handed to the client `write()`; mirrors the in-worker WriteSession. */
|
|
208
|
-
export interface ClientWriteSession {
|
|
225
|
+
export interface ClientWriteSession<TSchema extends AnySchema = UntypedSchema> {
|
|
209
226
|
/** Read-your-writes: observes the pre-scope snapshot plus everything staged so far. */
|
|
210
227
|
query(sql: string, options?: QueryOptions): Promise<QueryResult>;
|
|
211
228
|
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
212
|
-
insertBatch(tableName:
|
|
213
|
-
upsertBatch(tableName:
|
|
214
|
-
updateBatch(tableName:
|
|
215
|
-
deleteBatch(tableName:
|
|
229
|
+
insertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
230
|
+
upsertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<StagedUpsertResult>;
|
|
231
|
+
updateBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchUpdateInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
232
|
+
deleteBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchDeleteInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
216
233
|
}
|
|
217
234
|
export interface ClientSnapshotSession {
|
|
218
235
|
/** The pinned manifest version; null only on a database with no commits yet. */
|
|
@@ -223,12 +240,12 @@ export interface ClientSnapshotSession {
|
|
|
223
240
|
* Proxy of a worker-side BufferedTableWriter. The age timer runs on the worker's clock, and
|
|
224
241
|
* onError fires for background flush failures exactly as in-worker — delivered as an event.
|
|
225
242
|
*/
|
|
226
|
-
export declare class ClientBufferedWriter {
|
|
243
|
+
export declare class ClientBufferedWriter<TRow extends BatchRow = BatchRow> {
|
|
227
244
|
#private;
|
|
228
245
|
private readonly client;
|
|
229
246
|
private readonly handleId;
|
|
230
247
|
constructor(client: MinnowDatabaseClient, handleId: string, created: Promise<unknown>);
|
|
231
|
-
add(row:
|
|
248
|
+
add(row: TRow): Promise<BufferedFlushResult | undefined>;
|
|
232
249
|
flush(): Promise<BufferedFlushResult | undefined>;
|
|
233
250
|
/** Fire-and-forget: flush failures surface through onError, matching the in-worker contract. */
|
|
234
251
|
requestFlush(): void;
|
package/dist/engine/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
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
|
-
import { toColumnarBatch } from "./batch.js";
|
|
4
|
+
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
5
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";
|
|
@@ -92,6 +92,10 @@ function rehydrateResponseError(payload) {
|
|
|
92
92
|
return rehydrateError(candidate);
|
|
93
93
|
}
|
|
94
94
|
class MinnowDatabaseClient {
|
|
95
|
+
#schema;
|
|
96
|
+
get #erased() {
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
95
99
|
#transport;
|
|
96
100
|
#pending = /* @__PURE__ */ new Map();
|
|
97
101
|
#events = /* @__PURE__ */ new Map();
|
|
@@ -109,6 +113,7 @@ class MinnowDatabaseClient {
|
|
|
109
113
|
this.#fail(new Error("A database worker message could not be deserialized"));
|
|
110
114
|
};
|
|
111
115
|
constructor(transport, options = {}) {
|
|
116
|
+
this.#schema = options.schema;
|
|
112
117
|
this.#transport = transport;
|
|
113
118
|
transport.addEventListener("message", this.#onMessage);
|
|
114
119
|
transport.addEventListener("error", this.#onError);
|
|
@@ -165,7 +170,10 @@ class MinnowDatabaseClient {
|
|
|
165
170
|
async listTables() {
|
|
166
171
|
return await this.#call("listTables", []);
|
|
167
172
|
}
|
|
168
|
-
async migrate(definition, options = {}) {
|
|
173
|
+
async migrate(definition = this.#schema, options = {}) {
|
|
174
|
+
if (definition === void 0) {
|
|
175
|
+
throw new TypeError("migrate() needs a schema: pass a definition, or construct the client with { schema }");
|
|
176
|
+
}
|
|
169
177
|
return await this.#call("migrate", [
|
|
170
178
|
serializeSchema(definition),
|
|
171
179
|
options
|
|
@@ -186,10 +194,12 @@ class MinnowDatabaseClient {
|
|
|
186
194
|
return await this.#call("upsert", [tableName, row, options]);
|
|
187
195
|
}
|
|
188
196
|
async updateBatch(tableName, input) {
|
|
189
|
-
|
|
197
|
+
const wire = { keys: input.keys, changes: definedVectors(input.changes) };
|
|
198
|
+
return await this.#call("updateBatch", [tableName, wire]);
|
|
190
199
|
}
|
|
191
200
|
async update(tableName, key, changes) {
|
|
192
|
-
|
|
201
|
+
const present = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== void 0));
|
|
202
|
+
return await this.#call("update", [tableName, key, present]);
|
|
193
203
|
}
|
|
194
204
|
async deleteBatch(tableName, input) {
|
|
195
205
|
return await this.#call("deleteBatch", [tableName, input]);
|
|
@@ -204,7 +214,7 @@ class MinnowDatabaseClient {
|
|
|
204
214
|
...onError === void 0 ? {} : { onError }
|
|
205
215
|
});
|
|
206
216
|
const created = this.#call("bufferedWriter", [handleId, tableName, wireOptions]);
|
|
207
|
-
return new ClientBufferedWriter(this, handleId, created);
|
|
217
|
+
return new ClientBufferedWriter(this.#erased, handleId, created);
|
|
208
218
|
}
|
|
209
219
|
async readTable(tableName, versionOrOptions) {
|
|
210
220
|
return decodeQueryResult(await this.#call("readTable", versionOrOptions === void 0 ? [tableName] : [tableName, versionOrOptions])).rows;
|
|
@@ -424,7 +434,7 @@ class MinnowDatabaseClient {
|
|
|
424
434
|
liveQueries(options = {}) {
|
|
425
435
|
const handleId = crypto.randomUUID();
|
|
426
436
|
const created = this.#call("liveQueries", [handleId, options]);
|
|
427
|
-
return new ClientLiveQuerySet(this, handleId, created);
|
|
437
|
+
return new ClientLiveQuerySet(this.#erased, handleId, created);
|
|
428
438
|
}
|
|
429
439
|
async listVisibleSegmentPage(tableName, options) {
|
|
430
440
|
return await this.#call("listVisibleSegmentPage", options === void 0 ? [tableName] : [tableName, options]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type BatchValue } 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
4
|
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
@@ -9,7 +9,7 @@ import type { SnapshotExportProgress, SnapshotLoadProgress } from "../storage/sn
|
|
|
9
9
|
import { type ComparisonOperator, type CompiledQuery, type CompiledStatement, type ForeignKeyDefinition, type QueryResult, type QueryRow, type QueryValue, type UniqueConstraintDefinition } from "./query.js";
|
|
10
10
|
import { LiveQuerySet, type LiveQuerySetOptions } from "./live.js";
|
|
11
11
|
import { type Catalog } from "./catalog.js";
|
|
12
|
-
import { type AnyTable, type MigrationStep, type SchemaDefinition } from "./schema.js";
|
|
12
|
+
import { type AnySchema, type UntypedSchema, type AnyTable, type BatchColumnName, type BatchDeleteInput, type BatchInsertInput, type BatchInsertRow, type BatchKeyValue, type BatchReadOptions, type BatchReadRow, type BatchUpdateChanges, type BatchUpdateInput, type BatchUpsertOptions, type MigrationStep, type SchemaDefinition, type TableName } from "./schema.js";
|
|
13
13
|
/** Nested SQL savepoints and their total cloned transaction state are both hard bounded. */
|
|
14
14
|
export declare const MAX_TRANSACTION_SAVEPOINTS = 64;
|
|
15
15
|
export declare const MAX_TRANSACTION_SAVEPOINT_BYTES: number;
|
|
@@ -95,6 +95,7 @@ export interface UpsertBatchResult extends Omit<InsertBatchResult, "segmentId">
|
|
|
95
95
|
requestedRowCount: number;
|
|
96
96
|
insertedRowCount: number;
|
|
97
97
|
updatedRowCount: number;
|
|
98
|
+
/** Input rows rejected by `conflictWhere`; always 0 without it. */
|
|
98
99
|
skippedRowCount: number;
|
|
99
100
|
}
|
|
100
101
|
export interface UpsertConflictWhere {
|
|
@@ -189,7 +190,7 @@ export interface StagedUpsertResult extends StagedWriteResult {
|
|
|
189
190
|
* caught the original error. A mutation that fails validation before registering anything
|
|
190
191
|
* leaves the scope usable.
|
|
191
192
|
*/
|
|
192
|
-
export interface WriteSession {
|
|
193
|
+
export interface WriteSession<TSchema extends AnySchema = UntypedSchema> {
|
|
193
194
|
/**
|
|
194
195
|
* Read-your-writes: the query observes the pre-scope snapshot PLUS everything this scope
|
|
195
196
|
* has staged so far, ordered after all committed data — without publishing anything.
|
|
@@ -197,10 +198,10 @@ export interface WriteSession {
|
|
|
197
198
|
query(sql: string, options?: QueryOptions): Promise<QueryResult>;
|
|
198
199
|
/** Runs a SELECT, INSERT, UPDATE, or DELETE inside this write scope. */
|
|
199
200
|
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
200
|
-
insertBatch(tableName:
|
|
201
|
-
upsertBatch(tableName:
|
|
202
|
-
updateBatch(tableName:
|
|
203
|
-
deleteBatch(tableName:
|
|
201
|
+
insertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
202
|
+
upsertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<StagedUpsertResult>;
|
|
203
|
+
updateBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchUpdateInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
204
|
+
deleteBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchDeleteInput<TSchema, TName>): Promise<StagedWriteResult>;
|
|
204
205
|
}
|
|
205
206
|
/** What one statement's execution cost, reported by the engine that ran it. */
|
|
206
207
|
export interface QueryExecutionStats {
|
|
@@ -444,7 +445,13 @@ export interface TableDefinition {
|
|
|
444
445
|
}
|
|
445
446
|
export type DatabaseRow = Record<string, Exclude<BatchValue, null> | null>;
|
|
446
447
|
export { CompactionBacklogError, CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, TableInUseError, UniqueConstraintError, VisibleSegmentCursorStaleError, };
|
|
447
|
-
export interface MinnowDatabaseOptions {
|
|
448
|
+
export interface MinnowDatabaseOptions<TSchema extends AnySchema = UntypedSchema> {
|
|
449
|
+
/**
|
|
450
|
+
* The schema this database is declared against. It types every batch method by table name —
|
|
451
|
+
* rows, keys, update changes, `conflictWhere`, `readTable` results — and is what a bare
|
|
452
|
+
* `migrate()` applies. Without it the batch API addresses tables by plain string, as SQL does.
|
|
453
|
+
*/
|
|
454
|
+
schema?: TSchema;
|
|
448
455
|
/**
|
|
449
456
|
* Block codec for newly written blocks; defaults to "gzip", which is also what compaction
|
|
450
457
|
* rewrites to, so a table's blocks are encoded the same way however they got there.
|
|
@@ -703,10 +710,10 @@ export interface SnapshotImportOptions {
|
|
|
703
710
|
/** Cancels transfer and atomically removes this caller's staged import. */
|
|
704
711
|
signal?: AbortSignal;
|
|
705
712
|
}
|
|
706
|
-
export declare class MinnowDatabase {
|
|
713
|
+
export declare class MinnowDatabase<TSchema extends AnySchema = UntypedSchema> {
|
|
707
714
|
#private;
|
|
708
715
|
private readonly store;
|
|
709
|
-
constructor(store: BlockStore, options?: MinnowDatabaseOptions);
|
|
716
|
+
constructor(store: BlockStore, options?: MinnowDatabaseOptions<TSchema>);
|
|
710
717
|
/**
|
|
711
718
|
* Stops timers and background scheduling, rolls back an abandoned statement transaction,
|
|
712
719
|
* closes live-query resources, releases the engine's reader lease, and drops resident caches.
|
|
@@ -758,17 +765,24 @@ export declare class MinnowDatabase {
|
|
|
758
765
|
dropTable(tableName: string, options?: {
|
|
759
766
|
ifExists?: boolean;
|
|
760
767
|
}): Promise<boolean>;
|
|
761
|
-
insertBatch(tableName:
|
|
762
|
-
insert(tableName:
|
|
763
|
-
upsertBatch(tableName:
|
|
764
|
-
upsert(tableName:
|
|
765
|
-
updateBatch(tableName:
|
|
766
|
-
|
|
768
|
+
insertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>): Promise<InsertBatchResult>;
|
|
769
|
+
insert<TName extends TableName<TSchema>>(tableName: TName, row: BatchInsertRow<TSchema, TName>): Promise<InsertBatchResult>;
|
|
770
|
+
upsertBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchInsertInput<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<UpsertBatchResult>;
|
|
771
|
+
upsert<TName extends TableName<TSchema>>(tableName: TName, row: BatchInsertRow<TSchema, TName>, options?: BatchUpsertOptions<TSchema, TName>): Promise<UpsertBatchResult>;
|
|
772
|
+
updateBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchUpdateInput<TSchema, TName>): Promise<UpdateBatchResult>;
|
|
773
|
+
/**
|
|
774
|
+
* Changes one row by the table's unique key. An explicitly `undefined` change leaves that
|
|
775
|
+
* column untouched, so a patch spread from optional fields needs no filtering first.
|
|
776
|
+
*/
|
|
777
|
+
update<TName extends TableName<TSchema>>(tableName: TName, key: BatchKeyValue<TSchema, TName>, changes: BatchUpdateChanges<TSchema, TName>): Promise<UpdateBatchResult>;
|
|
767
778
|
/** Deletes one row by the table's unique key. */
|
|
768
|
-
delete(tableName:
|
|
769
|
-
deleteBatch(tableName:
|
|
770
|
-
bufferedWriter(tableName:
|
|
771
|
-
readTable(tableName:
|
|
779
|
+
delete<TName extends TableName<TSchema>>(tableName: TName, key: BatchKeyValue<TSchema, TName>): Promise<DeleteBatchResult>;
|
|
780
|
+
deleteBatch<TName extends TableName<TSchema>>(tableName: TName, input: BatchDeleteInput<TSchema, TName>): Promise<DeleteBatchResult>;
|
|
781
|
+
bufferedWriter<TName extends TableName<TSchema>>(tableName: TName, options?: BufferedWriterOptions): BufferedTableWriter<BatchInsertRow<TSchema, TName>>;
|
|
782
|
+
readTable<TName extends TableName<TSchema>, const TColumns extends ReadonlyArray<BatchColumnName<TSchema, TName>>>(tableName: TName, options: BatchReadOptions<TSchema, TName, TColumns> & {
|
|
783
|
+
readonly columns: TColumns;
|
|
784
|
+
}): Promise<Array<Pick<BatchReadRow<TSchema, TName>, TColumns[number]>>>;
|
|
785
|
+
readTable<TName extends TableName<TSchema>>(tableName: TName, versionOrOptions?: number | BatchReadOptions<TSchema, TName>): Promise<Array<BatchReadRow<TSchema, TName>>>;
|
|
772
786
|
/**
|
|
773
787
|
* Runs the callback against one pinned manifest version: every query inside the scope
|
|
774
788
|
* observes the same committed state, however many commits land meanwhile. This is the
|
|
@@ -811,7 +825,7 @@ export declare class MinnowDatabase {
|
|
|
811
825
|
* compaction landing mid-scope never fails it. An error thrown by the callback aborts the
|
|
812
826
|
* scope with nothing published. A scope that stages nothing publishes nothing.
|
|
813
827
|
*/
|
|
814
|
-
write<T>(action: (session: WriteSession) => Promise<T>): Promise<{
|
|
828
|
+
write<T>(action: (session: WriteSession<TSchema>) => Promise<T>): Promise<{
|
|
815
829
|
result: T;
|
|
816
830
|
version: number | null;
|
|
817
831
|
}>;
|
|
@@ -828,7 +842,11 @@ export declare class MinnowDatabase {
|
|
|
828
842
|
* steps — and every catalog alteration is one atomic compare-and-swap, so a concurrent
|
|
829
843
|
* migrator fails explicitly with a conflict instead of interleaving.
|
|
830
844
|
*/
|
|
831
|
-
|
|
845
|
+
/**
|
|
846
|
+
* Brings storage in line with a schema declaration. With no argument it applies the schema the
|
|
847
|
+
* database was constructed with.
|
|
848
|
+
*/
|
|
849
|
+
migrate(definition?: SchemaDefinition<readonly AnyTable[]> | undefined, options?: MigrateOptions): Promise<MigrateResult>;
|
|
832
850
|
/**
|
|
833
851
|
* Renders the optimized logical plan for a SELECT statement plus the physical strategy notes
|
|
834
852
|
* the prepared execution would choose, without executing it.
|
package/dist/engine/database.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { crossJoinPlan } from "../plan/model.js";
|
|
2
|
-
import { toColumnarBatch } from "./batch.js";
|
|
2
|
+
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
3
3
|
import { ArtifactCache } from "./artifact-cache.js";
|
|
4
4
|
import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
|
|
5
5
|
import { throwIfAborted } from "./cancellation.js";
|
|
@@ -182,6 +182,9 @@ function migrationResult(steps) {
|
|
|
182
182
|
steps: [...steps]
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
|
+
function compactUpdateBatchInput(input) {
|
|
186
|
+
return Object.values(input.changes).includes(void 0) ? { keys: input.keys, changes: definedVectors(input.changes) } : input;
|
|
187
|
+
}
|
|
185
188
|
const MAX_GARBAGE_COLLECTION_RETAIN_RECENT_VERSIONS = 1024;
|
|
186
189
|
const REFERENTIAL_CASCADES = 8;
|
|
187
190
|
class TransactionRollback extends Error {
|
|
@@ -548,6 +551,10 @@ async function* singleSnapshotChunk(bytes) {
|
|
|
548
551
|
}
|
|
549
552
|
class MinnowDatabase {
|
|
550
553
|
store;
|
|
554
|
+
#schema;
|
|
555
|
+
get #erased() {
|
|
556
|
+
return this;
|
|
557
|
+
}
|
|
551
558
|
#closed = false;
|
|
552
559
|
#closePromise;
|
|
553
560
|
#transactions;
|
|
@@ -625,6 +632,7 @@ class MinnowDatabase {
|
|
|
625
632
|
#preparedCatalogStates = /* @__PURE__ */ new WeakMap();
|
|
626
633
|
constructor(store, options = {}) {
|
|
627
634
|
this.store = store;
|
|
635
|
+
this.#schema = options.schema;
|
|
628
636
|
this.#compression = options.compression ?? "gzip";
|
|
629
637
|
this.#rowsPerBlock = options.rowsPerBlock ?? 65536;
|
|
630
638
|
if (!Number.isSafeInteger(this.#rowsPerBlock) || this.#rowsPerBlock <= 0 || this.#rowsPerBlock > MAX_BLOCK_ROW_COUNT) {
|
|
@@ -1589,7 +1597,7 @@ class MinnowDatabase {
|
|
|
1589
1597
|
return { ...statement, rows };
|
|
1590
1598
|
}
|
|
1591
1599
|
async insert(tableName, row) {
|
|
1592
|
-
return this.insertBatch(tableName, [row]);
|
|
1600
|
+
return this.#erased.insertBatch(tableName, [row]);
|
|
1593
1601
|
}
|
|
1594
1602
|
upsertBatch(tableName, input, options = {}) {
|
|
1595
1603
|
return this.#withWriteReservation(() => this.#upsertBatchReserved(tableName, input, options));
|
|
@@ -1631,10 +1639,10 @@ class MinnowDatabase {
|
|
|
1631
1639
|
};
|
|
1632
1640
|
}
|
|
1633
1641
|
async upsert(tableName, row, options = {}) {
|
|
1634
|
-
return this.upsertBatch(tableName, [row], options);
|
|
1642
|
+
return this.#erased.upsertBatch(tableName, [row], options);
|
|
1635
1643
|
}
|
|
1636
1644
|
updateBatch(tableName, input) {
|
|
1637
|
-
return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, input));
|
|
1645
|
+
return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, compactUpdateBatchInput(input)));
|
|
1638
1646
|
}
|
|
1639
1647
|
async #updateBatchReserved(tableName, input) {
|
|
1640
1648
|
return this.#runWrite(async () => {
|
|
@@ -1657,13 +1665,13 @@ class MinnowDatabase {
|
|
|
1657
1665
|
});
|
|
1658
1666
|
}
|
|
1659
1667
|
async update(tableName, key, changes) {
|
|
1660
|
-
return this.updateBatch(tableName, {
|
|
1668
|
+
return this.#erased.updateBatch(tableName, {
|
|
1661
1669
|
keys: [key],
|
|
1662
|
-
changes: Object.fromEntries(Object.entries(changes).
|
|
1670
|
+
changes: Object.fromEntries(Object.entries(changes).flatMap(([name, value]) => value === void 0 ? [] : [[name, [value]]]))
|
|
1663
1671
|
});
|
|
1664
1672
|
}
|
|
1665
1673
|
async delete(tableName, key) {
|
|
1666
|
-
return this.deleteBatch(tableName, { keys: [key] });
|
|
1674
|
+
return this.#erased.deleteBatch(tableName, { keys: [key] });
|
|
1667
1675
|
}
|
|
1668
1676
|
deleteBatch(tableName, input) {
|
|
1669
1677
|
return this.#withWriteReservation(() => this.#deleteBatchReserved(tableName, input));
|
|
@@ -1678,7 +1686,7 @@ class MinnowDatabase {
|
|
|
1678
1686
|
if (dependents.length === 0)
|
|
1679
1687
|
return this.#deleteBatchOnce(tableName, normalizedInput);
|
|
1680
1688
|
const started = performance.now();
|
|
1681
|
-
const { result, version } = await this.write(async (session) => {
|
|
1689
|
+
const { result, version } = await this.#erased.write(async (session) => {
|
|
1682
1690
|
await this.#applyReferentialActions(table, [...normalizedInput.keys], session, REFERENTIAL_CASCADES);
|
|
1683
1691
|
return session.deleteBatch(tableName, normalizedInput);
|
|
1684
1692
|
});
|
|
@@ -1882,7 +1890,7 @@ class MinnowDatabase {
|
|
|
1882
1890
|
}
|
|
1883
1891
|
}
|
|
1884
1892
|
bufferedWriter(tableName, options = {}) {
|
|
1885
|
-
return new BufferedTableWriter(this, tableName, options);
|
|
1893
|
+
return new BufferedTableWriter(this.#erased, tableName, options);
|
|
1886
1894
|
}
|
|
1887
1895
|
async #writeUpdateBatch(table, keyColumn, input, keys) {
|
|
1888
1896
|
await this.#assertCompactionCapacity(table);
|
|
@@ -4880,6 +4888,11 @@ class MinnowDatabase {
|
|
|
4880
4888
|
}
|
|
4881
4889
|
async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
|
|
4882
4890
|
const table = await this.#findTable(tableName);
|
|
4891
|
+
const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
|
|
4892
|
+
const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
|
|
4893
|
+
if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
|
|
4894
|
+
throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
|
|
4895
|
+
}
|
|
4883
4896
|
const filled = await this.#fillDefaults(table, input);
|
|
4884
4897
|
const { generated, autoIncrement } = filled;
|
|
4885
4898
|
let batch = filled.batch;
|
|
@@ -4892,11 +4905,6 @@ class MinnowDatabase {
|
|
|
4892
4905
|
validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
|
|
4893
4906
|
}
|
|
4894
4907
|
}
|
|
4895
|
-
const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
|
|
4896
|
-
const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
|
|
4897
|
-
if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
|
|
4898
|
-
throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
|
|
4899
|
-
}
|
|
4900
4908
|
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
|
|
4901
4909
|
let skippedRowCount = 0;
|
|
4902
4910
|
if (normalizedConflictWhere !== void 0) {
|
|
@@ -4912,6 +4920,7 @@ class MinnowDatabase {
|
|
|
4912
4920
|
rowCount = filtered.rowCount;
|
|
4913
4921
|
sessionUpsertFirings = filtered.firings;
|
|
4914
4922
|
if (rowCount === 0) {
|
|
4923
|
+
collectAutoIncrementGenerated(batch, generated, autoIncrement);
|
|
4915
4924
|
return {
|
|
4916
4925
|
tableName: table.name,
|
|
4917
4926
|
segmentId: null,
|
|
@@ -5178,7 +5187,10 @@ class MinnowDatabase {
|
|
|
5178
5187
|
}
|
|
5179
5188
|
return (await this.#memoizedQuery(query.plan, `typed ${planMemoKey(query.plan)}`, {}, probe)).rows;
|
|
5180
5189
|
}
|
|
5181
|
-
async migrate(definition, options = {}) {
|
|
5190
|
+
async migrate(definition = this.#schema, options = {}) {
|
|
5191
|
+
if (definition === void 0) {
|
|
5192
|
+
throw new TypeError("migrate() needs a schema: pass a definition, or construct the database with { schema }");
|
|
5193
|
+
}
|
|
5182
5194
|
let originalSteps;
|
|
5183
5195
|
for (let attempt = 0; ; attempt += 1) {
|
|
5184
5196
|
try {
|
|
@@ -6079,7 +6091,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6079
6091
|
let outcome;
|
|
6080
6092
|
let version;
|
|
6081
6093
|
if (options.writer === void 0) {
|
|
6082
|
-
const completed = await this.write(apply);
|
|
6094
|
+
const completed = await this.#erased.write(apply);
|
|
6083
6095
|
outcome = completed.result;
|
|
6084
6096
|
version = completed.version;
|
|
6085
6097
|
} else {
|
|
@@ -6184,7 +6196,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6184
6196
|
}
|
|
6185
6197
|
const sourceKey = mergeSourceKeyExpression(statement, keyColumn.name);
|
|
6186
6198
|
const sourceSql = mergeSourceSql(statement);
|
|
6187
|
-
const { result: applied, version } = await this.write(async (session) => {
|
|
6199
|
+
const { result: applied, version } = await this.#erased.write(async (session) => {
|
|
6188
6200
|
const sourceRows = (await session.query(sourceSql)).rows;
|
|
6189
6201
|
const keys = sourceRows.map((row) => storedSqlValueFromExecution(keyColumn, evaluateJoinedRowExpression(sourceKey, { [statement.source.alias]: row })));
|
|
6190
6202
|
const present = /* @__PURE__ */ new Map();
|
|
@@ -6765,7 +6777,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6765
6777
|
}
|
|
6766
6778
|
await this.#assertStagedInsertKeysFree(table2, stagedKeyColumn, input, writer);
|
|
6767
6779
|
}
|
|
6768
|
-
const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.insertBatch(statement.table, input));
|
|
6780
|
+
const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.#erased.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.#erased.insertBatch(statement.table, input));
|
|
6769
6781
|
const generated = "generatedColumns" in result ? result.generatedColumns ?? {} : {};
|
|
6770
6782
|
return {
|
|
6771
6783
|
kind: "insert",
|
|
@@ -6897,7 +6909,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6897
6909
|
...returnedRows2 === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows2)
|
|
6898
6910
|
};
|
|
6899
6911
|
}
|
|
6900
|
-
const deleted = options.writer === void 0 ? await this.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
|
|
6912
|
+
const deleted = options.writer === void 0 ? await this.#erased.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
|
|
6901
6913
|
return {
|
|
6902
6914
|
kind: "delete",
|
|
6903
6915
|
table: table.name,
|
|
@@ -6934,7 +6946,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6934
6946
|
returnedChanges[assignment.column] = executionValues;
|
|
6935
6947
|
}
|
|
6936
6948
|
}
|
|
6937
|
-
const updated = options.writer === void 0 ? await this.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
|
|
6949
|
+
const updated = options.writer === void 0 ? await this.#erased.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
|
|
6938
6950
|
const returnedRows = returningColumns === void 0 ? void 0 : rows.map((row, index) => Object.fromEntries(returningColumns.map((name) => [
|
|
6939
6951
|
name,
|
|
6940
6952
|
returnedChanges !== void 0 && name in returnedChanges ? returnedChanges[name]?.[index] ?? null : updated.generatedColumns?.[name] !== void 0 ? updated.generatedColumns[name][index] ?? null : row[name] ?? null
|
package/dist/engine/schema.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ColumnDefault, type SqlDomain, type TableColumnRecord, type TableRecord } from "../storage/types.js";
|
|
2
|
+
import type { ComparisonOperator } from "../plan/model.js";
|
|
3
|
+
import type { BatchRow, BatchValue, InsertBatchInput } from "./batch.js";
|
|
2
4
|
import { type Catalog, type CatalogColumn, type CatalogTable } from "./catalog.js";
|
|
5
|
+
import type { DatabaseRow, DeleteBatchInput, UpdateBatchInput, UpsertOptions } from "./database.js";
|
|
3
6
|
import { type Expression, type QueryValue } from "./query.js";
|
|
4
7
|
/**
|
|
5
8
|
* Typed schema DSL and catalog migration planning. Column builders carry compile-time value and
|
|
@@ -313,8 +316,8 @@ export declare function schema<TTables extends readonly AnyTable[], TViews exten
|
|
|
313
316
|
}): SchemaDefinition<TTables, TViews>;
|
|
314
317
|
type ColumnTypeMetadata<TColumn> = TColumn extends {
|
|
315
318
|
readonly "~types"?: infer TMetadata extends {
|
|
316
|
-
readonly select:
|
|
317
|
-
readonly input:
|
|
319
|
+
readonly select: SchemaValue;
|
|
320
|
+
readonly input: SchemaValue;
|
|
318
321
|
};
|
|
319
322
|
} ? NonNullable<TMetadata> : never;
|
|
320
323
|
type ColumnValue<TColumn extends AnyColumn> = TColumn["isNullable"] extends true ? ColumnTypeMetadata<TColumn>["select"] | null : ColumnTypeMetadata<TColumn>["select"];
|
|
@@ -359,6 +362,86 @@ export type PrimaryKeyKeys<TTable extends AnyTable> = {
|
|
|
359
362
|
export type InferUpdateChanges<TTable extends AnyTable> = {
|
|
360
363
|
[K in keyof TTable["columns"] as K extends PrimaryKeyKeys<TTable> | GeneratedKeys<TTable> ? never : K]?: ColumnInputValue<TTable["columns"][K]> | undefined;
|
|
361
364
|
};
|
|
365
|
+
/** The bound every schema type parameter carries. */
|
|
366
|
+
export type AnySchema = SchemaDefinition<readonly AnyTable[]>;
|
|
367
|
+
/**
|
|
368
|
+
* The schema type parameter's default: no declaration, so the batch API addresses tables by
|
|
369
|
+
* plain string. It is `any` rather than `AnySchema` so that a database declared against a schema
|
|
370
|
+
* remains assignable to the undeclared class — `MinnowDatabase<typeof mine>` is a
|
|
371
|
+
* `MinnowDatabase` — which no ordinary type argument can express once table names are checked.
|
|
372
|
+
* Every type below resolves to its erased branch for it, so nothing else about it is `any`.
|
|
373
|
+
*/
|
|
374
|
+
export type UntypedSchema = any;
|
|
375
|
+
type SchemaTable<TSchema extends AnySchema> = TSchema["tables"][number];
|
|
376
|
+
/** True when the schema carries no literal table names — the untyped default. */
|
|
377
|
+
type ErasedSchema<TSchema extends AnySchema> = string extends SchemaTable<TSchema>["name"] ? true : false;
|
|
378
|
+
/** The declared table names; `string` for an untyped database. */
|
|
379
|
+
export type TableName<TSchema extends AnySchema> = SchemaTable<TSchema>["name"];
|
|
380
|
+
/** The declaration behind one table name. */
|
|
381
|
+
export type TableOf<TSchema extends AnySchema, TName extends TableName<TSchema>> = Extract<SchemaTable<TSchema>, {
|
|
382
|
+
readonly name: TName;
|
|
383
|
+
}>;
|
|
384
|
+
/** A declared table's column names; `string` for an untyped database. */
|
|
385
|
+
export type BatchColumnName<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? string : keyof TableOf<TSchema, TName>["columns"] & string;
|
|
386
|
+
/** The columnar form of `InferInsertRow`: one vector per column, optional where the row is. */
|
|
387
|
+
export interface ColumnarInsertBatch<TTable extends AnyTable> {
|
|
388
|
+
readonly columns: {
|
|
389
|
+
readonly [K in RequiredInsertKeys<TTable>]: ReadonlyArray<ColumnInputValue<TTable["columns"][K]>>;
|
|
390
|
+
} & {
|
|
391
|
+
readonly [K in OptionalInsertKeys<TTable>]?: ReadonlyArray<ColumnInputValue<TTable["columns"][K]>>;
|
|
392
|
+
};
|
|
393
|
+
/** Per row, whether the column was omitted (takes its default) rather than set to NULL. */
|
|
394
|
+
readonly omitted?: Partial<Readonly<Record<OptionalInsertKeys<TTable>, readonly boolean[]>>>;
|
|
395
|
+
readonly rowCount?: number;
|
|
396
|
+
}
|
|
397
|
+
/** What `insertBatch` and `upsertBatch` accept for one table: typed rows, or a typed columnar batch. */
|
|
398
|
+
export type BatchInsertInput<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? InsertBatchInput : ReadonlyArray<InferInsertRow<TableOf<TSchema, TName>>> | ColumnarInsertBatch<TableOf<TSchema, TName>>;
|
|
399
|
+
/** One row for `insert`, `upsert`, or a buffered writer's `add`. */
|
|
400
|
+
export type BatchInsertRow<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? BatchRow : InferInsertRow<TableOf<TSchema, TName>>;
|
|
401
|
+
/** What `readTable` returns per row. */
|
|
402
|
+
export type BatchReadRow<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? DatabaseRow : InferRow<TableOf<TSchema, TName>>;
|
|
403
|
+
/**
|
|
404
|
+
* The key a batch write addresses a row by: the `.unique()` column or a one-column table-level
|
|
405
|
+
* primary key. A composite primary key is addressed through its hidden locator, which the batch
|
|
406
|
+
* API does not expose, so those tables get `never` here and are written through SQL instead.
|
|
407
|
+
*/
|
|
408
|
+
type BatchKey<TTable extends AnyTable> = TTable["primaryKey"] extends readonly [
|
|
409
|
+
unknown,
|
|
410
|
+
unknown,
|
|
411
|
+
...unknown[]
|
|
412
|
+
] ? never : TTable["primaryKey"] extends readonly [infer TOnly extends keyof TTable["columns"]] ? NonNullable<ColumnValue<TTable["columns"][TOnly]>> : NonNullable<ScalarUniqueKeyValue<TTable>>;
|
|
413
|
+
/** One key value for `update` and `delete`. */
|
|
414
|
+
export type BatchKeyValue<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? Exclude<BatchValue, null> : BatchKey<TableOf<TSchema, TName>>;
|
|
415
|
+
/** The changes object `update` takes: every non-key, non-generated column, each optional. */
|
|
416
|
+
export type BatchUpdateChanges<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? Readonly<Record<string, BatchValue>> : InferUpdateChanges<TableOf<TSchema, TName>>;
|
|
417
|
+
/** What `updateBatch` takes: typed keys and one vector per changed column. */
|
|
418
|
+
export type BatchUpdateInput<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? UpdateBatchInput : {
|
|
419
|
+
readonly keys: ReadonlyArray<BatchKey<TableOf<TSchema, TName>>>;
|
|
420
|
+
readonly changes: {
|
|
421
|
+
readonly [K in keyof InferUpdateChanges<TableOf<TSchema, TName>>]?: ReadonlyArray<Exclude<InferUpdateChanges<TableOf<TSchema, TName>>[K], undefined>>;
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
/** What `deleteBatch` takes. */
|
|
425
|
+
export type BatchDeleteInput<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? DeleteBatchInput : {
|
|
426
|
+
readonly keys: ReadonlyArray<BatchKey<TableOf<TSchema, TName>>>;
|
|
427
|
+
};
|
|
428
|
+
/** A `conflictWhere` guard whose `value` follows the column it names. */
|
|
429
|
+
export type BatchConflictWhere<TTable extends AnyTable> = {
|
|
430
|
+
[K in keyof TTable["columns"] & string]: {
|
|
431
|
+
readonly column: K;
|
|
432
|
+
readonly operator: ComparisonOperator;
|
|
433
|
+
readonly value: ColumnInputValue<TTable["columns"][K]>;
|
|
434
|
+
};
|
|
435
|
+
}[keyof TTable["columns"] & string];
|
|
436
|
+
/** `upsertBatch`/`upsert` options, with the guard typed against the table. */
|
|
437
|
+
export type BatchUpsertOptions<TSchema extends AnySchema, TName extends TableName<TSchema>> = ErasedSchema<TSchema> extends true ? UpsertOptions : {
|
|
438
|
+
readonly conflictWhere?: BatchConflictWhere<TableOf<TSchema, TName>>;
|
|
439
|
+
};
|
|
440
|
+
/** `readTable` options; naming `columns` narrows the returned rows to those columns. */
|
|
441
|
+
export interface BatchReadOptions<TSchema extends AnySchema, TName extends TableName<TSchema>, TColumns extends ReadonlyArray<BatchColumnName<TSchema, TName>> = ReadonlyArray<BatchColumnName<TSchema, TName>>> {
|
|
442
|
+
readonly version?: number;
|
|
443
|
+
readonly columns?: TColumns;
|
|
444
|
+
}
|
|
362
445
|
export type MigrationStep = {
|
|
363
446
|
kind: "create-table";
|
|
364
447
|
table: AnyTable;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Wilhite",
|