@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.
- 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 +23 -8
- package/dist/engine/database.d.ts +44 -22
- package/dist/engine/database.js +285 -103
- package/dist/engine/schema.d.ts +85 -2
- package/dist/engine/worker-server.js +4 -1
- package/dist/storage/indexeddb.js +13 -8
- package/dist/storage/toolkit/record-core.js +11 -6
- package/dist/storage/types.d.ts +11 -0
- 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/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;
|
|
@@ -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,
|
|
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,
|
|
9725
|
+
async function assertGarbageCollectionCandidateProvenanceInTransaction(transaction, candidates) {
|
|
9721
9726
|
const manifestStore = transaction.objectStore("manifests");
|
|
9722
|
-
for (const version of
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
5665
|
-
for (const version of
|
|
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
|
|
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 =
|
|
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 =
|
|
5685
|
+
const unprovenSegmentId = candidates.candidateSegmentIds.find((id) => {
|
|
5681
5686
|
return !segments.has(id);
|
|
5682
5687
|
});
|
|
5683
5688
|
if (unprovenSegmentId !== void 0) {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -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[];
|
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",
|