@minnowdb/core 0.7.5 → 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.
@@ -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;
@@ -554,10 +554,13 @@ class DatabaseRpcServer {
554
554
  return handle.session.execute(sql, params);
555
555
  }
556
556
  if (method === "stage") {
557
- const [op, tableName, input] = args;
557
+ const [op, tableName, input, options] = args;
558
558
  if (!isStageOp(op)) {
559
559
  throw new Error(`Unsupported write stage operation: ${String(op)}`);
560
560
  }
561
+ if (op === "upsertBatch") {
562
+ return handle.session.upsertBatch(tableName, input, options);
563
+ }
561
564
  return handle.session[op](tableName, input);
562
565
  }
563
566
  if (method === "commit")
@@ -3919,7 +3919,12 @@ class IndexedDbBlockStore {
3919
3919
  await assertActiveGarbageCollectionMarker(gcStore, current);
3920
3920
  }
3921
3921
  const updated = updateGarbageCollectionPlanningRecord(current, input);
3922
- await assertGarbageCollectionCandidateProvenanceInTransaction(transaction, updated);
3922
+ await assertGarbageCollectionCandidateProvenanceInTransaction(transaction, {
3923
+ candidateManifestVersions: input.candidateManifestVersions ?? [],
3924
+ candidateSegmentIds: input.candidateSegmentIds ?? [],
3925
+ candidateBlockIds: input.candidateBlockIds ?? [],
3926
+ candidateTransactionIds: input.candidateTransactionIds ?? []
3927
+ });
3923
3928
  gcStore.put(garbageCollectionJobEnvelope(updated), key);
3924
3929
  await transactionDone(transaction);
3925
3930
  return structuredClone(updated);
@@ -9717,15 +9722,15 @@ function assertGenericTransactionUpdateAllowed(record, update) {
9717
9722
  throw new TypeError("Only commitTransaction can set a committed transaction version");
9718
9723
  }
9719
9724
  }
9720
- async function assertGarbageCollectionCandidateProvenanceInTransaction(transaction, job) {
9725
+ async function assertGarbageCollectionCandidateProvenanceInTransaction(transaction, candidates) {
9721
9726
  const manifestStore = transaction.objectStore("manifests");
9722
- for (const version of job.candidateManifestVersions) {
9727
+ for (const version of candidates.candidateManifestVersions) {
9723
9728
  const value = await requestResult(manifestStore.get(version));
9724
9729
  if (value === void 0) {
9725
9730
  throw new Error(`Garbage collection candidate manifest is missing: ${String(version)}`);
9726
9731
  }
9727
9732
  }
9728
- for (const id of job.candidateTransactionIds) {
9733
+ for (const id of candidates.candidateTransactionIds) {
9729
9734
  const value = await requestResult(transaction.objectStore("transactions").get(id));
9730
9735
  const record = value === void 0 ? void 0 : asTransactionRecord(value);
9731
9736
  if (record === void 0 || record.status !== "aborted" && (record.status !== "committed" || record.committedVersion === null)) {
@@ -9733,10 +9738,10 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
9733
9738
  }
9734
9739
  }
9735
9740
  const manifestProvenBlockIds = /* @__PURE__ */ new Set();
9736
- const manifestValues = await Promise.all(job.candidateBlockIds.map((id) => requestResult(transaction.objectStore("catalog").get(manifestBlockKey(id)))));
9741
+ const manifestValues = await Promise.all(candidates.candidateBlockIds.map((id) => requestResult(transaction.objectStore("catalog").get(manifestBlockKey(id)))));
9737
9742
  for (const [index, value] of manifestValues.entries()) {
9738
9743
  if (value !== void 0) {
9739
- const id = job.candidateBlockIds[index] ?? "";
9744
+ const id = candidates.candidateBlockIds[index] ?? "";
9740
9745
  asManifestBlockRecord(value, id);
9741
9746
  manifestProvenBlockIds.add(id);
9742
9747
  }
@@ -9757,12 +9762,12 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
9757
9762
  return isTerminalCompactionJob(record) && (record.sourceBlockIds.includes(id) || record.outputBlockIds.includes(id));
9758
9763
  });
9759
9764
  };
9760
- for (const id of job.candidateBlockIds) {
9765
+ for (const id of candidates.candidateBlockIds) {
9761
9766
  if (await blockHasProvenance(id))
9762
9767
  continue;
9763
9768
  throw new Error(`Garbage collection block candidate has no persisted provenance: ${id}`);
9764
9769
  }
9765
- for (const id of job.candidateSegmentIds) {
9770
+ for (const id of candidates.candidateSegmentIds) {
9766
9771
  const segmentValue = await requestResult(transaction.objectStore("segments").get(id));
9767
9772
  if (segmentValue !== void 0)
9768
9773
  continue;
@@ -3567,7 +3567,12 @@ class RecordCore {
3567
3567
  throw new GarbageCollectionJobConflictError(input.jobId, input.expectedRevision, current?.revision ?? null);
3568
3568
  }
3569
3569
  const updated = updateGarbageCollectionPlanningRecord(current, input);
3570
- assertGarbageCollectionCandidateProvenance(updated, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
3570
+ assertGarbageCollectionCandidateProvenance({
3571
+ candidateManifestVersions: input.candidateManifestVersions ?? [],
3572
+ candidateSegmentIds: input.candidateSegmentIds ?? [],
3573
+ candidateBlockIds: input.candidateBlockIds ?? [],
3574
+ candidateTransactionIds: input.candidateTransactionIds ?? []
3575
+ }, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
3571
3576
  this.#garbageCollectionJobs.set(updated.id, updated);
3572
3577
  return cloneRecord(updated);
3573
3578
  }
@@ -5661,23 +5666,23 @@ function assertPendingArtifactsAvailable(transaction, physical, segments, valida
5661
5666
  }
5662
5667
  }
5663
5668
  }
5664
- function assertGarbageCollectionCandidateProvenance(job, manifests, manifestBlocks, segments, transactions, roots) {
5665
- for (const version of job.candidateManifestVersions) {
5669
+ function assertGarbageCollectionCandidateProvenance(candidates, manifests, manifestBlocks, segments, transactions, roots) {
5670
+ for (const version of candidates.candidateManifestVersions) {
5666
5671
  if (!manifests.has(version)) {
5667
5672
  throw new Error(`Garbage collection candidate manifest is missing: ${String(version)}`);
5668
5673
  }
5669
5674
  }
5670
- for (const id of job.candidateTransactionIds) {
5675
+ for (const id of candidates.candidateTransactionIds) {
5671
5676
  const transaction = transactions.get(id);
5672
5677
  if (transaction === void 0 || transaction.status !== "aborted" && (transaction.status !== "committed" || transaction.committedVersion === null)) {
5673
5678
  throw new Error(`Garbage collection transaction candidate is not terminal: ${id}`);
5674
5679
  }
5675
5680
  }
5676
- const unprovenBlockId = job.candidateBlockIds.find((id) => !manifestBlocks.has(id) && roots.abortedTransactionBlockCount(id) === 0 && roots.terminalJobBlockCount(id) === 0);
5681
+ const unprovenBlockId = candidates.candidateBlockIds.find((id) => !manifestBlocks.has(id) && roots.abortedTransactionBlockCount(id) === 0 && roots.terminalJobBlockCount(id) === 0);
5677
5682
  if (unprovenBlockId !== void 0) {
5678
5683
  throw new Error(`Garbage collection block candidate has no persisted provenance: ${unprovenBlockId}`);
5679
5684
  }
5680
- const unprovenSegmentId = job.candidateSegmentIds.find((id) => {
5685
+ const unprovenSegmentId = candidates.candidateSegmentIds.find((id) => {
5681
5686
  return !segments.has(id);
5682
5687
  });
5683
5688
  if (unprovenSegmentId !== void 0) {
@@ -815,6 +815,17 @@ export interface UpdateGarbageCollectionPlanningInput {
815
815
  discovery: GarbageCollectionDiscovery;
816
816
  updatedAt: string;
817
817
  }
818
+ /**
819
+ * One page's (or one job's full) proposed garbage-collection candidates, checked for persisted
820
+ * provenance before being accepted. Shared by every adapter's provenance assertion so a candidate
821
+ * kind added to one cannot be forgotten in another.
822
+ */
823
+ export interface GarbageCollectionCandidateSet {
824
+ readonly candidateManifestVersions: readonly number[];
825
+ readonly candidateSegmentIds: readonly string[];
826
+ readonly candidateBlockIds: readonly string[];
827
+ readonly candidateTransactionIds: readonly string[];
828
+ }
818
829
  export interface GarbageCollectionJobRecord {
819
830
  id: string;
820
831
  candidateManifestVersions: number[];
@@ -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.5",
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",