@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.
@@ -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: InsertBatchInput): ColumnarBatch;
48
+ export declare function toColumnarBatch(input: InsertBatchInputLike): ColumnarBatch;
@@ -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
- return input;
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 { BatchValue } from "./batch.js";
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
- /** Batches row-oriented writes by row count, estimated bytes, or age. */
30
- export declare class BufferedTableWriter {
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: Readonly<Record<string, BatchValue>>): Promise<BufferedFlushResult | undefined>;
40
+ add(row: TRow): Promise<BufferedFlushResult | undefined>;
38
41
  flush(): Promise<BufferedFlushResult | undefined>;
39
42
  requestFlush(): void;
40
43
  close(): Promise<BufferedFlushResult | undefined>;
@@ -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, type InsertBatchInput } from "./batch.js";
2
+ import { type BatchRow } from "./batch.js";
3
3
  import type { Catalog } from "./catalog.js";
4
- import type { BatchValue, BufferPoolStats, StagedWriteResult, StagedUpsertResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, DatabaseRow, MigrateOptions, DeleteBatchInput, DeleteBatchResult, ExecuteOptions, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, ReadTableOptions, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchInput, UpdateBatchResult, UpsertBatchResult, UpsertOptions, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
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
- migrate(definition: SchemaDefinition<readonly AnyTable[]>, options?: MigrateOptions): Promise<ClientMigrationResult>;
107
- insertBatch(tableName: string, input: InsertBatchInput): Promise<InsertBatchResult>;
108
- insert(tableName: string, row: BatchRow): Promise<InsertBatchResult>;
109
- upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<UpsertBatchResult>;
110
- upsert(tableName: string, row: BatchRow, options?: UpsertOptions): Promise<UpsertBatchResult>;
111
- updateBatch(tableName: string, input: UpdateBatchInput): Promise<UpdateBatchResult>;
112
- update(tableName: string, key: Exclude<BatchValue, null>, changes: Readonly<Record<string, BatchValue>>): Promise<UpdateBatchResult>;
113
- deleteBatch(tableName: string, input: DeleteBatchInput): Promise<DeleteBatchResult>;
114
- delete(tableName: string, key: Exclude<BatchValue, null>): Promise<DeleteBatchResult>;
115
- bufferedWriter(tableName: string, options?: BufferedWriterOptions): ClientBufferedWriter;
116
- readTable(tableName: string, versionOrOptions?: number | ReadTableOptions): Promise<DatabaseRow[]>;
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: string, input: InsertBatchInput): Promise<StagedWriteResult>;
213
- upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<StagedUpsertResult>;
214
- updateBatch(tableName: string, input: UpdateBatchInput): Promise<StagedWriteResult>;
215
- deleteBatch(tableName: string, input: DeleteBatchInput): Promise<StagedWriteResult>;
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: Readonly<Record<string, BatchValue>>): Promise<BufferedFlushResult | undefined>;
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;
@@ -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
- return await this.#call("updateBatch", [tableName, input]);
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
- return await this.#call("update", [tableName, key, changes]);
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 BatchRow, type BatchValue, type InsertBatchInput } from "./batch.js";
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: string, input: InsertBatchInput): Promise<StagedWriteResult>;
201
- upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<StagedUpsertResult>;
202
- updateBatch(tableName: string, input: UpdateBatchInput): Promise<StagedWriteResult>;
203
- deleteBatch(tableName: string, input: DeleteBatchInput): Promise<StagedWriteResult>;
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: string, input: InsertBatchInput): Promise<InsertBatchResult>;
762
- insert(tableName: string, row: BatchRow): Promise<InsertBatchResult>;
763
- upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<UpsertBatchResult>;
764
- upsert(tableName: string, row: BatchRow, options?: UpsertOptions): Promise<UpsertBatchResult>;
765
- updateBatch(tableName: string, input: UpdateBatchInput): Promise<UpdateBatchResult>;
766
- update(tableName: string, key: Exclude<BatchValue, null>, changes: Readonly<Record<string, BatchValue>>): Promise<UpdateBatchResult>;
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: string, key: Exclude<BatchValue, null>): Promise<DeleteBatchResult>;
769
- deleteBatch(tableName: string, input: DeleteBatchInput): Promise<DeleteBatchResult>;
770
- bufferedWriter(tableName: string, options?: BufferedWriterOptions): BufferedTableWriter;
771
- readTable(tableName: string, versionOrOptions?: number | ReadTableOptions): Promise<DatabaseRow[]>;
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
- migrate(definition: SchemaDefinition<readonly AnyTable[]>, options?: MigrateOptions): Promise<MigrateResult>;
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.
@@ -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).map(([name, value]) => [name, [value]]))
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
@@ -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: unknown;
317
- readonly input: unknown;
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;
@@ -1,4 +1,4 @@
1
- export declare const protocolVersion: 3;
1
+ export declare const protocolVersion: 4;
2
2
  /** Outstanding request/response pairs retained by either side of one database RPC connection. */
3
3
  export declare const MAX_DATABASE_RPC_IN_FLIGHT = 256;
4
4
  export type RpcRequest = {
@@ -1,4 +1,4 @@
1
- const protocolVersion = 3;
1
+ const protocolVersion = 4;
2
2
  const MAX_DATABASE_RPC_IN_FLIGHT = 256;
3
3
  function serializeError(error) {
4
4
  if (!(error instanceof Error)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnowdb/core",
3
- "version": "0.7.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",