@voltro/database 0.77.1 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/dist/index.d.ts +124 -16
- package/dist/index.js +528 -528
- package/dist/{replaceInProgress-DdjHyYFT.js → replaceInProgress-BmSjNTp0.js} +467 -458
- package/dist/sql.d.ts +435 -14
- package/dist/sql.js +1665 -1293
- package/package.json +2 -2
package/dist/sql.d.ts
CHANGED
|
@@ -603,6 +603,12 @@ declare interface ChangeStreamGap {
|
|
|
603
603
|
readonly downForMs?: number;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
/** A live table's columns that carry a character set, with it. */
|
|
607
|
+
export declare const charsetColumnsOf: (table: TableSnapshot) => ReadonlyArray<{
|
|
608
|
+
readonly column: string;
|
|
609
|
+
readonly charset: string;
|
|
610
|
+
}>;
|
|
611
|
+
|
|
606
612
|
/** Partition into ≤`n`-sized chunks (disjoint, complete). Exported for the
|
|
607
613
|
* batching regression test — the introspection's correctness reduces to this
|
|
608
614
|
* producing a complete, non-overlapping partition of the table list. */
|
|
@@ -1685,6 +1691,25 @@ export declare interface ColumnSnapshot {
|
|
|
1685
1691
|
* blocks a masking export until classified.
|
|
1686
1692
|
*/
|
|
1687
1693
|
readonly safe?: boolean;
|
|
1694
|
+
/**
|
|
1695
|
+
* The character set a STRING column is stored in — MySQL / MariaDB only, and
|
|
1696
|
+
* only on columns the engine gives one (text, varchar, enum; never json,
|
|
1697
|
+
* which is `utf8mb4` on MariaDB and charset-less on MySQL by construction,
|
|
1698
|
+
* and never a number, a date or bytes).
|
|
1699
|
+
*
|
|
1700
|
+
* Live side: what `information_schema.columns.character_set_name` reports.
|
|
1701
|
+
* Declared side: the store's declared charset (`store: { dialect, charset }`
|
|
1702
|
+
* in `app.config.ts`), stamped on every string column — ABSENT when the app
|
|
1703
|
+
* declares none. The planner compares it only when BOTH sides carry one, so
|
|
1704
|
+
* an undeclared app never plans a conversion; and the fingerprint never
|
|
1705
|
+
* hashes it, because a live reading against an absent declaration would part
|
|
1706
|
+
* every table's two hashes forever. `storeCharset.ts` carries the reason.
|
|
1707
|
+
*
|
|
1708
|
+
* A `string`, not `MysqlCharset`: the reader reports whatever the column is
|
|
1709
|
+
* in (`latin1` is real), and a conversion FROM it is exactly what the
|
|
1710
|
+
* declaration is for. Only the declared side is limited to the two.
|
|
1711
|
+
*/
|
|
1712
|
+
readonly charset?: string;
|
|
1688
1713
|
/**
|
|
1689
1714
|
* Type PARAMETERS for the three `ColumnType`s whose DDL is not determined by
|
|
1690
1715
|
* the type tag alone — `vector(n)`, `array(of)`, `enum(name, values)`.
|
|
@@ -1724,10 +1749,39 @@ export declare interface ColumnSnapshot {
|
|
|
1724
1749
|
};
|
|
1725
1750
|
}
|
|
1726
1751
|
|
|
1752
|
+
/**
|
|
1753
|
+
* Does the declared column render a STRING type on the mysql family — the
|
|
1754
|
+
* columns a declared charset is stamped on? Mirrors `sqlType(_, 'mariadb')`:
|
|
1755
|
+
* text (LONGTEXT / VARCHAR / TEXT), enum (`ENUM(...)`), a typeid/ulid `id`
|
|
1756
|
+
* (VARCHAR(64)) and a text-stored `reference` (VARCHAR(64)). A numeric id or
|
|
1757
|
+
* reference is BIGINT; json / array are JSON; bytes is LONGBLOB — none carry a
|
|
1758
|
+
* character set. `raw` is the user's own DDL, which the framework does not
|
|
1759
|
+
* read, so it is left alone on both sides.
|
|
1760
|
+
*/
|
|
1761
|
+
export declare const columnTakesCharset: (col: Pick<ColumnSnapshot, "type" | "idScheme" | "idStorage" | "referenceStorage">) => boolean;
|
|
1762
|
+
|
|
1727
1763
|
declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigint' | 'boolean' | 'timestamp' | 'date' | 'json' | 'bytes' | 'reference' | 'vector' | 'enum' | 'array' | 'interval' | 'raw';
|
|
1728
1764
|
|
|
1729
1765
|
declare type ColumnType_2 = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigint' | 'boolean' | 'timestamp' | 'date' | 'json' | 'bytes' | 'reference' | 'vector' | 'enum' | 'array' | 'interval' | 'raw';
|
|
1730
1766
|
|
|
1767
|
+
/** A live foreign key a charset conversion has to carry across the rewrite. */
|
|
1768
|
+
export declare interface ConvertCharsetForeignKey {
|
|
1769
|
+
/** Catalog name of the constraint. */
|
|
1770
|
+
readonly name: string;
|
|
1771
|
+
/** The child (referencing) side. */
|
|
1772
|
+
readonly table: string;
|
|
1773
|
+
readonly column: string;
|
|
1774
|
+
readonly targetTable: string;
|
|
1775
|
+
readonly targetColumn: string;
|
|
1776
|
+
readonly onDelete?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
1777
|
+
readonly onUpdate?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
1778
|
+
/** Which operation re-creates it after the rewrite: this table's, or the
|
|
1779
|
+
* other endpoint's (`'peer'` — that table converts in the same plan and
|
|
1780
|
+
* sorts after this one). Dropping is done by whichever runs first; both
|
|
1781
|
+
* tolerate an already-absent constraint. */
|
|
1782
|
+
readonly restore: 'here' | 'peer';
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1731
1785
|
/** The tables a plan CREATEs — the set every same-plan operation is checked against. */
|
|
1732
1786
|
export declare const createdTablesOf: (operations: ReadonlyArray<PlannedOperation>) => ReadonlySet<string>;
|
|
1733
1787
|
|
|
@@ -2222,15 +2276,62 @@ declare interface DataStore {
|
|
|
2222
2276
|
* a way to hand a store somebody else's connection.
|
|
2223
2277
|
*/
|
|
2224
2278
|
run?<A, E>(effect: Effect.Effect<A, E, SqlClient.SqlClient>): Promise<A>;
|
|
2279
|
+
/**
|
|
2280
|
+
* What this store can say about its OWN shape without a catalog — the
|
|
2281
|
+
* tables it holds and the columns their rows carry.
|
|
2282
|
+
*
|
|
2283
|
+
* The inverse capability of {@link DataStore.run}. A SQL store answers a
|
|
2284
|
+
* schema question by asking the server's catalog, which is authoritative
|
|
2285
|
+
* about far more than this (types, indexes, constraints) and is therefore
|
|
2286
|
+
* the right source whenever there is a connection. A store that IS its data
|
|
2287
|
+
* has no catalog — and is not thereby ignorant: it knows exactly which
|
|
2288
|
+
* tables it holds, because holding them is all a table is there. That much
|
|
2289
|
+
* is answerable honestly, and nothing beyond it is.
|
|
2290
|
+
*
|
|
2291
|
+
* So the contract is deliberately narrow, and the narrowness is the point:
|
|
2292
|
+
*
|
|
2293
|
+
* - `table` is unqualified by whatever namespace the view is bound to, so
|
|
2294
|
+
* a tenant view reports the tenant's tables under their bare names.
|
|
2295
|
+
* - `columns` is the UNION of the keys the held rows carry. An empty
|
|
2296
|
+
* table therefore reports NO columns, and that is not a claim that it
|
|
2297
|
+
* has none — a caller must read `rowCount` before treating an absent
|
|
2298
|
+
* column as absent.
|
|
2299
|
+
* - `rowCount` is how many rows are held, so "the table is empty and says
|
|
2300
|
+
* nothing about its columns" is distinguishable from "the table has
|
|
2301
|
+
* rows and none of them carries that column".
|
|
2302
|
+
*
|
|
2303
|
+
* OPTIONAL on the interface: the in-memory store implements it (it is the
|
|
2304
|
+
* only store whose data IS its schema); SQL stores omit it, because the
|
|
2305
|
+
* catalog answers the same questions better and a second, weaker source of
|
|
2306
|
+
* truth beside it is how the two drift. Callers guard with
|
|
2307
|
+
* `if (store.storedTables)`.
|
|
2308
|
+
*
|
|
2309
|
+
* Read by the file-migration context built WITHOUT a `sql` client
|
|
2310
|
+
* (`makeFileMigrationContext`), so a migration body that opens with
|
|
2311
|
+
* `ctx.skipUnless(ctx.schema.tableExists('…'), …)` — the guard the file
|
|
2312
|
+
* migration docs ask for — runs unchanged in a database-free test.
|
|
2313
|
+
*/
|
|
2314
|
+
storedTables?(): ReadonlyArray<StoredTableShape>;
|
|
2225
2315
|
}
|
|
2226
2316
|
|
|
2317
|
+
export declare const declaredSnapshot: (tables: ReadonlyArray<TableLike>, dialect?: DialectId, options?: DeclaredSnapshotOptions) => SchemaSnapshot;
|
|
2318
|
+
|
|
2227
2319
|
/**
|
|
2228
2320
|
* Build a `SchemaSnapshot` from an array of `TableLike` declarations.
|
|
2229
2321
|
* Caller is responsible for passing the FULL set of tables (user-
|
|
2230
2322
|
* declared + framework). The fingerprint over this snapshot is what the
|
|
2231
2323
|
* boot-time prod check compares against `_voltro_migration_plans`.
|
|
2232
2324
|
*/
|
|
2233
|
-
export declare
|
|
2325
|
+
export declare interface DeclaredSnapshotOptions {
|
|
2326
|
+
/**
|
|
2327
|
+
* The store's declared character set (`store: { dialect, charset }`) —
|
|
2328
|
+
* stamped on every string column so the planner can compare it with what the
|
|
2329
|
+
* database reports. MySQL / MariaDB only: on any other dialect a charset is a
|
|
2330
|
+
* configuration error and is refused here rather than ignored, so a
|
|
2331
|
+
* declaration that cannot take effect never reads as one that did.
|
|
2332
|
+
*/
|
|
2333
|
+
readonly charset?: MysqlCharset | undefined;
|
|
2334
|
+
}
|
|
2234
2335
|
|
|
2235
2336
|
/** The NOTIFY channel a schema emits on when none is configured. */
|
|
2236
2337
|
export declare const DEFAULT_CDC_CHANNEL = "framework_changes";
|
|
@@ -2286,6 +2387,12 @@ export declare interface DeployLedgerRow {
|
|
|
2286
2387
|
|
|
2287
2388
|
export declare const describeOutcome: (outcome: BootMigrationOutcome, dialectId: string) => string;
|
|
2288
2389
|
|
|
2390
|
+
/** `bf_links`, `bf_links.ticket_key` — the spelling a message uses. */
|
|
2391
|
+
export declare const describeProbe: (probe: SchemaProbe) => string;
|
|
2392
|
+
|
|
2393
|
+
/** One line per finding, for the CLI and the doctor. */
|
|
2394
|
+
export declare const describeStoreCharsetFinding: (f: StoreCharsetFinding) => string;
|
|
2395
|
+
|
|
2289
2396
|
/**
|
|
2290
2397
|
* Parse `VOLTRO_DESTRUCTIVE_OK`.
|
|
2291
2398
|
*
|
|
@@ -2496,6 +2603,31 @@ export declare const emitAddIndexMysql: (op: Extract<MigrationOperation, {
|
|
|
2496
2603
|
kind: "add-index";
|
|
2497
2604
|
}>, declared: ReadonlyArray<TableLike>, engine?: MysqlEngine) => string;
|
|
2498
2605
|
|
|
2606
|
+
/**
|
|
2607
|
+
* The statements of a `convert-charset` operation, in order:
|
|
2608
|
+
*
|
|
2609
|
+
* 1. drop every foreign key that touches the table (either end) — the
|
|
2610
|
+
* engines refuse to change the charset of a column a constraint holds;
|
|
2611
|
+
* 2. ONE `ALTER TABLE` that MODIFYs each declared string column to its
|
|
2612
|
+
* declared definition plus `CHARACTER SET`, and sets the table default —
|
|
2613
|
+
* a single rewrite, not one per column, and never the json columns;
|
|
2614
|
+
* 3. re-create the constraints this operation owns (`restore: 'here'`); the
|
|
2615
|
+
* ones between two converting tables come back with the later table.
|
|
2616
|
+
*
|
|
2617
|
+
* Every column is rendered from the DECLARATION (`emitModifyColumnMysql` is
|
|
2618
|
+
* the same renderer the type and nullability operations use), because MODIFY
|
|
2619
|
+
* redefines the column and the declaration is the shape the plan converges
|
|
2620
|
+
* to. A generated column keeps its expression — MODIFY without it would drop
|
|
2621
|
+
* the generation.
|
|
2622
|
+
*/
|
|
2623
|
+
export declare const emitConvertCharsetMysql: (op: Extract<MigrationOperation, {
|
|
2624
|
+
kind: "convert-charset";
|
|
2625
|
+
}>, declared: ReadonlyArray<TableLike>) => {
|
|
2626
|
+
readonly drops: ReadonlyArray<string>;
|
|
2627
|
+
readonly rewrite: string;
|
|
2628
|
+
readonly restores: ReadonlyArray<string>;
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2499
2631
|
export declare const emitCreateTableMssql: (op: Extract<MigrationOperation, {
|
|
2500
2632
|
kind: "create-table";
|
|
2501
2633
|
}>) => ReadonlyArray<string>;
|
|
@@ -2574,7 +2706,7 @@ cdcChannel?: string) => string;
|
|
|
2574
2706
|
* containers are a deliberate non-goal (each tenant namespace is
|
|
2575
2707
|
* self-contained for its own tables).
|
|
2576
2708
|
*/
|
|
2577
|
-
export declare const emitNamespacedSchemaSql: (tables: ReadonlyArray<AnyTable>, dialect: DialectId, namespace: string, sqliteFile?: string) => string;
|
|
2709
|
+
export declare const emitNamespacedSchemaSql: (tables: ReadonlyArray<AnyTable>, dialect: DialectId, namespace: string, sqliteFile?: string, options?: SchemaEmitOptions) => string;
|
|
2578
2710
|
|
|
2579
2711
|
/**
|
|
2580
2712
|
* DDL that creates a tenant's namespace CONTAINER, per dialect:
|
|
@@ -2590,12 +2722,17 @@ export declare const emitNamespacedSchemaSql: (tables: ReadonlyArray<AnyTable>,
|
|
|
2590
2722
|
* errors if the alias is already attached, which the migrator treats as
|
|
2591
2723
|
* "already provisioned".
|
|
2592
2724
|
*/
|
|
2593
|
-
export declare const emitNamespaceProvisionDdl: (namespace: string, dialect: DialectId, sqliteFile?: string
|
|
2725
|
+
export declare const emitNamespaceProvisionDdl: (namespace: string, dialect: DialectId, sqliteFile?: string,
|
|
2726
|
+
/** The declared store charset (mysql family) — the new database's default,
|
|
2727
|
+
* so its tables inherit the declaration rather than the server default.
|
|
2728
|
+
* `IF NOT EXISTS` leaves an existing database's default alone (measured on
|
|
2729
|
+
* both engines); the tables' own column charsets still land. */
|
|
2730
|
+
charset?: MysqlCharset) => string;
|
|
2594
2731
|
|
|
2595
2732
|
export declare const emitSchemaSql: (entities: ReadonlyArray<AnyTable | AnyView>, dialect: DialectId,
|
|
2596
2733
|
/** NOTIFY channel for the reactive triggers. Must match the store's
|
|
2597
2734
|
* `cdcChannel`, or writes emit where nothing is listening. */
|
|
2598
|
-
cdcChannel?: string) => string;
|
|
2735
|
+
cdcChannel?: string, options?: SchemaEmitOptions) => string;
|
|
2599
2736
|
|
|
2600
2737
|
declare type EmptyMerge = unknown;
|
|
2601
2738
|
|
|
@@ -2757,6 +2894,31 @@ export declare interface FileMigration {
|
|
|
2757
2894
|
* schema change through `ctx.schema` in a migration of its own).
|
|
2758
2895
|
*/
|
|
2759
2896
|
readonly transactional?: boolean;
|
|
2897
|
+
/**
|
|
2898
|
+
* WHERE in a migrate run this body belongs, relative to the declarative
|
|
2899
|
+
* schema apply. Required, and there is no default, because the answer cannot
|
|
2900
|
+
* be inferred from the body and getting it wrong is silent.
|
|
2901
|
+
*
|
|
2902
|
+
* - **`'before-schema'`** — runs FIRST, before the planner diffs declared
|
|
2903
|
+
* against live. This is the phase for everything a state diff cannot infer
|
|
2904
|
+
* and must not be computed against: a table split, a merge, a foreign key
|
|
2905
|
+
* dropped so the planner does not trip over it, a type change with a USING
|
|
2906
|
+
* expression. The plan is computed AFTER this body, against the shape it
|
|
2907
|
+
* left behind.
|
|
2908
|
+
* - **`'after-schema'`** — runs LAST, after the plan has been applied and the
|
|
2909
|
+
* plugin steps and change triggers have converged. This is the phase for a
|
|
2910
|
+
* body that NEEDS the new shape: a back-fill into a table the same deploy
|
|
2911
|
+
* creates, a column filled from its neighbours. It runs in the same job,
|
|
2912
|
+
* under the same failure gate, and — on a dialect that holds a deploy in
|
|
2913
|
+
* one transaction — inside the same unit as the schema it depends on.
|
|
2914
|
+
*
|
|
2915
|
+
* A back-fill declared `'before-schema'` is the mistake this field exists to
|
|
2916
|
+
* make unavailable: on the deploy that creates its table the body can only
|
|
2917
|
+
* take its own `skipUnless` guard, and the skip is recorded, so it never gets
|
|
2918
|
+
* a second chance. The runner names that case when it can see it, but a
|
|
2919
|
+
* warning after the fact is the weaker half — the phase is the answer.
|
|
2920
|
+
*/
|
|
2921
|
+
readonly phase: MigrationPhase;
|
|
2760
2922
|
/** The forward body. */
|
|
2761
2923
|
readonly up: (ctx: FileMigrationContext) => Promise<void> | Effect.Effect<void, unknown, never>;
|
|
2762
2924
|
/**
|
|
@@ -2878,9 +3040,12 @@ export declare interface FileMigrationContext {
|
|
|
2878
3040
|
/**
|
|
2879
3041
|
* A body driven over a store that has no SQL underneath — the in-memory
|
|
2880
3042
|
* store a unit test uses. No client: `each`, `rewrite`, `expect`,
|
|
2881
|
-
* `skipUnless`, `store`, `log
|
|
2882
|
-
*
|
|
2883
|
-
* under `transactional`
|
|
3043
|
+
* `skipUnless`, `store`, `log`, the two read-only probes the store can answer
|
|
3044
|
+
* about itself (`schema.tableExists`, `schema.columnExists`) and a Promise
|
|
3045
|
+
* body under `transactional` run against the store; `raw()`, an Effect body
|
|
3046
|
+
* under `transactional` and every `schema` member that needs a CATALOG —
|
|
3047
|
+
* `snapshot`, `indexExists`, `foreignKeyExists` and all the DDL — fail with
|
|
3048
|
+
* `MigrationNeedsSqlClient`, each naming what to do instead. A store that
|
|
2884
3049
|
* CAN run SQL (`run` present) is not accepted here: its migration context
|
|
2885
3050
|
* needs the client, and the type says so before the runtime check does.
|
|
2886
3051
|
*/
|
|
@@ -2928,7 +3093,21 @@ export declare interface FileMigrationRunResult {
|
|
|
2928
3093
|
id: string;
|
|
2929
3094
|
durationMs: number;
|
|
2930
3095
|
}>;
|
|
3096
|
+
/** Ids this pass did NOT run because they are already recorded. */
|
|
2931
3097
|
readonly skipped: ReadonlyArray<string>;
|
|
3098
|
+
/**
|
|
3099
|
+
* The migrations whose body stopped at its own `skipUnless`, with the schema
|
|
3100
|
+
* objects it found absent. A caller holding the plan that follows can say
|
|
3101
|
+
* whether the skip was permanent; nobody else can.
|
|
3102
|
+
*/
|
|
3103
|
+
readonly skips: ReadonlyArray<FileMigrationSkip>;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
/** One migration that skipped, and what its body found missing. */
|
|
3107
|
+
export declare interface FileMigrationSkip {
|
|
3108
|
+
readonly id: string;
|
|
3109
|
+
readonly reason: string;
|
|
3110
|
+
readonly absent: ReadonlyArray<SchemaProbe>;
|
|
2932
3111
|
}
|
|
2933
3112
|
|
|
2934
3113
|
/**
|
|
@@ -2946,6 +3125,8 @@ export declare const fileMigrationStatus: (sql: SqlClient.SqlClient, projectRoot
|
|
|
2946
3125
|
/** One row of `voltro db status` for a `migrations/<timestamp>_<slug>.ts` file. */
|
|
2947
3126
|
export declare interface FileMigrationStatusRow {
|
|
2948
3127
|
readonly id: string;
|
|
3128
|
+
/** Where in a run this body executes — `voltro db status` says which. */
|
|
3129
|
+
readonly phase: MigrationPhase;
|
|
2949
3130
|
readonly applied: boolean;
|
|
2950
3131
|
/** The file no longer hashes to what was recorded when it ran. Always false
|
|
2951
3132
|
* for a row applied before hashes were recorded — see `appliedFileMigrations`. */
|
|
@@ -3232,6 +3413,8 @@ export declare const isAlreadySatisfied: (err: unknown, rules: ReadonlyArray<Alr
|
|
|
3232
3413
|
/** Type-guard for the discovery walker. */
|
|
3233
3414
|
export declare const isFileMigration: (value: unknown) => value is FileMigration;
|
|
3234
3415
|
|
|
3416
|
+
export declare const isMysqlCharset: (value: unknown) => value is MysqlCharset;
|
|
3417
|
+
|
|
3235
3418
|
/** Type guard — narrows an unknown value to a {@link RawSqlFragment}. */
|
|
3236
3419
|
export declare const isRawSqlFragment: (value: unknown) => value is RawSqlFragment;
|
|
3237
3420
|
|
|
@@ -3305,6 +3488,7 @@ declare type MergeMixinFields<Mixins extends ReadonlyArray<AnyMixin>> = Mixins e
|
|
|
3305
3488
|
* export default migration({
|
|
3306
3489
|
* id: '20260415_120000_split_address_out',
|
|
3307
3490
|
* description: 'Move users.address* into a separate addresses table.',
|
|
3491
|
+
* phase: 'before-schema',
|
|
3308
3492
|
* up: ({ sql, store, log }) => Effect.gen(function* () {
|
|
3309
3493
|
* yield* sql.unsafe(`CREATE TABLE addresses ( ... )`)
|
|
3310
3494
|
* yield* sql.unsafe(`INSERT INTO addresses (...) SELECT (...) FROM users`)
|
|
@@ -3326,6 +3510,9 @@ export declare const MIGRATION_ATOMICITY: Readonly<Record<DialectId, MigrationAt
|
|
|
3326
3510
|
*/
|
|
3327
3511
|
export declare const MIGRATION_NEEDS_TWO_CONNECTIONS: string;
|
|
3328
3512
|
|
|
3513
|
+
/** Both phases, in the order a run executes them. The one definition; readers derive. */
|
|
3514
|
+
export declare const MIGRATION_PHASES: readonly [MigrationPhase, MigrationPhase];
|
|
3515
|
+
|
|
3329
3516
|
/**
|
|
3330
3517
|
* How much of a plan a dialect can apply atomically.
|
|
3331
3518
|
*
|
|
@@ -3403,10 +3590,13 @@ export declare interface MigrationLockScope {
|
|
|
3403
3590
|
/**
|
|
3404
3591
|
* A migration context was built without a SQL client — over the in-memory
|
|
3405
3592
|
* store, the shape a unit test uses — and the body reached a surface that
|
|
3406
|
-
* needs a live connection: `raw()`,
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3409
|
-
*
|
|
3593
|
+
* needs a live connection: `raw()`, an Effect body under `ctx.transactional`
|
|
3594
|
+
* (which runs ON the connection), or a `ctx.schema` member that can only be
|
|
3595
|
+
* answered from a CATALOG — `snapshot`, `indexExists`, `foreignKeyExists` and
|
|
3596
|
+
* every schema CHANGE. `each`, `rewrite`, `expect`, `skipUnless`, `store`,
|
|
3597
|
+
* `log` and the two probes the store can answer about its own shape
|
|
3598
|
+
* (`tableExists`, `columnExists`) work without one; each refusal names what to
|
|
3599
|
+
* do instead. Also raised at construction when what was handed in as `sql` is not
|
|
3410
3600
|
* an `@effect/sql` client, or when a SQL-backed store arrives without the
|
|
3411
3601
|
* client it executes on — both are refused before a body can run rather
|
|
3412
3602
|
* than surfacing as a TypeError from inside the first helper that touches
|
|
@@ -3553,8 +3743,44 @@ export declare type MigrationOperation = {
|
|
|
3553
3743
|
readonly validation: 'value' | 'container';
|
|
3554
3744
|
readonly previousValidation: 'value' | 'container' | 'unverified' | 'absent';
|
|
3555
3745
|
readonly dropConstraints: ReadonlyArray<string>;
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Bring every string column of ONE table to the declared character set —
|
|
3749
|
+
* MySQL / MariaDB only. A table REWRITE: each named column is `MODIFY`ed to
|
|
3750
|
+
* its declared definition plus `CHARACTER SET`, and the table's default is
|
|
3751
|
+
* set so a later `ADD COLUMN` inherits it. json columns are never in
|
|
3752
|
+
* `columns` (their charset is the engine's, not the declaration's).
|
|
3753
|
+
*
|
|
3754
|
+
* `foreignKeys` are the constraints that touch this table — either end —
|
|
3755
|
+
* because neither engine changes the character set of a column a foreign
|
|
3756
|
+
* key holds (MariaDB 1832/1833, MySQL 3780). Each is dropped before the
|
|
3757
|
+
* rewrite and re-created after it; `restore` says which endpoint's operation
|
|
3758
|
+
* re-creates it, so a constraint between two converting tables comes back
|
|
3759
|
+
* only once both sides are at the target (the later of the two, by name).
|
|
3760
|
+
*/
|
|
3761
|
+
| {
|
|
3762
|
+
readonly kind: 'convert-charset';
|
|
3763
|
+
readonly table: string;
|
|
3764
|
+
readonly charset: MysqlCharset;
|
|
3765
|
+
/** The string columns to rewrite, in declaration order. */
|
|
3766
|
+
readonly columns: ReadonlyArray<string>;
|
|
3767
|
+
/** The character sets those columns are in today (for the plan line). */
|
|
3768
|
+
readonly from: ReadonlyArray<string>;
|
|
3769
|
+
readonly foreignKeys: ReadonlyArray<ConvertCharsetForeignKey>;
|
|
3556
3770
|
};
|
|
3557
3771
|
|
|
3772
|
+
/**
|
|
3773
|
+
* The two points in a migrate run at which a file-based body can execute.
|
|
3774
|
+
*
|
|
3775
|
+
* The ORDER of `'before-schema'` is what every file migration has always had
|
|
3776
|
+
* and is not negotiable: a body that drops a constraint the planner would trip
|
|
3777
|
+
* over has to run before the plan is computed, and `voltro db apply --plan`
|
|
3778
|
+
* refuses a pending one for exactly that reason. `'after-schema'` is the point
|
|
3779
|
+
* that did not exist — everything the declarative apply produces is in place,
|
|
3780
|
+
* so a body may depend on it.
|
|
3781
|
+
*/
|
|
3782
|
+
export declare type MigrationPhase = 'before-schema' | 'after-schema';
|
|
3783
|
+
|
|
3558
3784
|
export declare interface MigrationPlan {
|
|
3559
3785
|
readonly operations: ReadonlyArray<PlannedOperation>;
|
|
3560
3786
|
/** Pre-state fingerprint (live DB at plan time). */
|
|
@@ -3779,6 +4005,16 @@ declare interface MixinIndex {
|
|
|
3779
4005
|
readonly kindOptions?: IndexKindOptions;
|
|
3780
4006
|
}
|
|
3781
4007
|
|
|
4008
|
+
export declare const MYSQL_CHARSETS: ReadonlyArray<MysqlCharset>;
|
|
4009
|
+
|
|
4010
|
+
/**
|
|
4011
|
+
* The character sets a store can declare for its string columns on MySQL /
|
|
4012
|
+
* MariaDB. `utf8mb4` holds every Unicode scalar; `utf8mb3` (the engines' old
|
|
4013
|
+
* `utf8`) stops at three bytes and refuses emoji and most supplementary-plane
|
|
4014
|
+
* characters with errno 1366.
|
|
4015
|
+
*/
|
|
4016
|
+
export declare type MysqlCharset = 'utf8mb3' | 'utf8mb4';
|
|
4017
|
+
|
|
3782
4018
|
/** Which engine a `mysql`-dialect connection is actually talking to. */
|
|
3783
4019
|
export declare type MysqlEngine = 'mysql' | 'mariadb';
|
|
3784
4020
|
|
|
@@ -3801,6 +4037,11 @@ export declare type MysqlEngine = 'mysql' | 'mariadb';
|
|
|
3801
4037
|
*/
|
|
3802
4038
|
export declare const mysqlIndexPrefixFor: (col: ColumnSnapshot | undefined) => number | undefined;
|
|
3803
4039
|
|
|
4040
|
+
export declare interface NamespaceCharsetReport {
|
|
4041
|
+
readonly namespace: string;
|
|
4042
|
+
readonly findings: ReadonlyArray<StoreCharsetFinding>;
|
|
4043
|
+
}
|
|
4044
|
+
|
|
3804
4045
|
/**
|
|
3805
4046
|
* Type-narrowing migration marker. Tells the planner that this column
|
|
3806
4047
|
* previously had type `from` and is now being narrowed/converted to its
|
|
@@ -3962,7 +4203,9 @@ export declare const parseEnumCheck: (clause: string) => {
|
|
|
3962
4203
|
* decision needs the list (to name it in the refusal) and must not have run
|
|
3963
4204
|
* anything to get it.
|
|
3964
4205
|
*/
|
|
3965
|
-
export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string
|
|
4206
|
+
export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string,
|
|
4207
|
+
/** Absent: BOTH phases — a gate that asks "is anything pending at all". */
|
|
4208
|
+
phase?: MigrationPhase) => Effect.Effect<ReadonlyArray<string>, unknown>;
|
|
3966
4209
|
|
|
3967
4210
|
/**
|
|
3968
4211
|
* What `runFileBasedMigrations` would execute next, with the one fact a deploy
|
|
@@ -3971,11 +4214,33 @@ export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, project
|
|
|
3971
4214
|
* every pending migration except those, so they are named in the question
|
|
3972
4215
|
* the operator answers, not discovered in the report afterwards.
|
|
3973
4216
|
*/
|
|
3974
|
-
export declare const pendingFileMigrationSummary: (sql: SqlClient.SqlClient, projectRoot: string
|
|
4217
|
+
export declare const pendingFileMigrationSummary: (sql: SqlClient.SqlClient, projectRoot: string,
|
|
4218
|
+
/** Absent: BOTH phases. */
|
|
4219
|
+
phase?: MigrationPhase) => Effect.Effect<ReadonlyArray<{
|
|
3975
4220
|
readonly id: string;
|
|
4221
|
+
readonly phase: MigrationPhase;
|
|
3976
4222
|
readonly irreversible: string | undefined;
|
|
3977
4223
|
}>, unknown>;
|
|
3978
4224
|
|
|
4225
|
+
/** A skip the plan that follows makes permanent, with the objects that prove it. */
|
|
4226
|
+
export declare interface PermanentSkip {
|
|
4227
|
+
readonly id: string;
|
|
4228
|
+
readonly reason: string;
|
|
4229
|
+
/** The probed objects this plan creates — what makes the skip permanent. */
|
|
4230
|
+
readonly created: ReadonlyArray<SchemaProbe>;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
/**
|
|
4234
|
+
* The skips this plan turns into permanent ones.
|
|
4235
|
+
*
|
|
4236
|
+
* A `before-schema` body that probed for an object and found it absent, where
|
|
4237
|
+
* the plan computed immediately afterwards CREATES that object, could not have
|
|
4238
|
+
* taken any other outcome on this deploy — and the skip was recorded, so it
|
|
4239
|
+
* will not be offered again. Both facts are in hand at that moment; neither is
|
|
4240
|
+
* on its own.
|
|
4241
|
+
*/
|
|
4242
|
+
export declare const permanentSkips: (skips: ReadonlyArray<FileMigrationSkip>, plan: MigrationPlan) => ReadonlyArray<PermanentSkip>;
|
|
4243
|
+
|
|
3979
4244
|
export declare interface PlanInput {
|
|
3980
4245
|
readonly declared: ReadonlyArray<TableLike>;
|
|
3981
4246
|
readonly live: SchemaSnapshot;
|
|
@@ -4002,6 +4267,16 @@ export declare interface PlanInput {
|
|
|
4002
4267
|
* CLI populates it from `VOLTRO_DB_IGNORE_TABLES` (comma-separated names).
|
|
4003
4268
|
*/
|
|
4004
4269
|
readonly ignoreTables?: ReadonlyArray<string>;
|
|
4270
|
+
/**
|
|
4271
|
+
* The store's declared character set — `store: { dialect, charset }` in
|
|
4272
|
+
* `app.config.ts`. MySQL / MariaDB only; `declaredSnapshot` refuses it for
|
|
4273
|
+
* any other dialect. Declared, every table the app declares is CREATED with
|
|
4274
|
+
* it and, when a live table's string columns are stored otherwise, CONVERTED
|
|
4275
|
+
* to it (`convert-charset`, a table rewrite that carries the table's foreign
|
|
4276
|
+
* keys across). Absent, nothing is planned about character sets — see
|
|
4277
|
+
* `storeCharset.ts` for why that is the default and what is reported instead.
|
|
4278
|
+
*/
|
|
4279
|
+
readonly charset?: MysqlCharset | undefined;
|
|
4005
4280
|
}
|
|
4006
4281
|
|
|
4007
4282
|
/**
|
|
@@ -4028,6 +4303,16 @@ export declare const plannableLiveTables: <T extends {
|
|
|
4028
4303
|
readonly name: string;
|
|
4029
4304
|
}>(tables: ReadonlyArray<T>, declaredNames: ReadonlySet<string>) => ReadonlyArray<T>;
|
|
4030
4305
|
|
|
4306
|
+
/**
|
|
4307
|
+
* What a planned operation CREATES — the objects that do not exist before the
|
|
4308
|
+
* plan runs and do after it.
|
|
4309
|
+
*
|
|
4310
|
+
* Only creations. An operation that renames or drops is not what a body probing
|
|
4311
|
+
* for absence is waiting for, and reporting one would send an operator looking
|
|
4312
|
+
* for a relationship that is not there.
|
|
4313
|
+
*/
|
|
4314
|
+
export declare const plannedCreations: (plan: MigrationPlan) => ReadonlyArray<SchemaProbe>;
|
|
4315
|
+
|
|
4031
4316
|
/**
|
|
4032
4317
|
* One row in a `MigrationPlan` — a concrete operation paired with the
|
|
4033
4318
|
* planner's classification + any blocking reason.
|
|
@@ -5086,6 +5371,13 @@ export declare interface RunFileBasedMigrationsCtx {
|
|
|
5086
5371
|
/** Schema scope for the migration advisory lock (see `migrations/lock.ts`).
|
|
5087
5372
|
* Absent → resolved from configuration (`DB_SCHEMA`). */
|
|
5088
5373
|
readonly lockSchema?: string;
|
|
5374
|
+
/**
|
|
5375
|
+
* WHICH phase this pass runs. Required, not defaulted: a run applies the two
|
|
5376
|
+
* phases as two passes around the declarative apply, and a caller that could
|
|
5377
|
+
* leave this out would run one of them at whichever point it happened to be
|
|
5378
|
+
* called — which is the defect the phase exists to remove, moved up a layer.
|
|
5379
|
+
*/
|
|
5380
|
+
readonly phase: MigrationPhase;
|
|
5089
5381
|
}
|
|
5090
5382
|
|
|
5091
5383
|
/**
|
|
@@ -5137,6 +5429,9 @@ export declare interface RunPlannedMigrationsCtx {
|
|
|
5137
5429
|
/** How long a soft-drop snapshot may sit before the boot warns about it
|
|
5138
5430
|
* (`database.softDropRetentionDays`; default 7). */
|
|
5139
5431
|
readonly softDropRetentionDays?: number;
|
|
5432
|
+
/** The store's declared character set (`store: { dialect, charset }`),
|
|
5433
|
+
* MySQL / MariaDB only — threaded into every plan this boot computes. */
|
|
5434
|
+
readonly charset?: MysqlCharset | undefined;
|
|
5140
5435
|
}
|
|
5141
5436
|
|
|
5142
5437
|
/**
|
|
@@ -5152,6 +5447,9 @@ export declare interface RunPlannedMigrationsCtx {
|
|
|
5152
5447
|
*/
|
|
5153
5448
|
export declare const runSchemaOp: (sql: SqlClient.SqlClient, op: MigrationSchemaOp, live: SchemaSnapshot) => Effect.Effect<void, SqlError_2>;
|
|
5154
5449
|
|
|
5450
|
+
/** Runs one Effect over the connection — the shape `ManagedRuntime.runPromise` and a store's `run` share. */
|
|
5451
|
+
export declare type RunSql = <A>(effect: Effect.Effect<A, SqlError_2, SqlClient.SqlClient>) => Promise<A>;
|
|
5452
|
+
|
|
5155
5453
|
/**
|
|
5156
5454
|
* Effect that applies the emitted DDL to the provided SqlClient. Splits
|
|
5157
5455
|
* on `;` only outside dollar-quoted blocks (the postgres NOTIFY trigger
|
|
@@ -5177,14 +5475,50 @@ export declare const runSchemaOp: (sql: SqlClient.SqlClient, op: MigrationSchema
|
|
|
5177
5475
|
* absent → `DB_SCHEMA` from the environment, the same configuration the
|
|
5178
5476
|
* connection's `search_path` pin reads.
|
|
5179
5477
|
*/
|
|
5180
|
-
export declare interface SchemaApplyOptions {
|
|
5478
|
+
export declare interface SchemaApplyOptions extends SchemaEmitOptions {
|
|
5181
5479
|
readonly retryFilter?: (error: unknown) => RetryDecision;
|
|
5182
5480
|
readonly lockSchema?: string;
|
|
5183
5481
|
}
|
|
5184
5482
|
|
|
5483
|
+
export declare interface SchemaEmitOptions {
|
|
5484
|
+
/**
|
|
5485
|
+
* The store's declared character set — MySQL / MariaDB only. Every CREATE
|
|
5486
|
+
* TABLE carries it as the table default, so the bring-up path creates the
|
|
5487
|
+
* same tables the planner's `create-table` does. Refused for any other
|
|
5488
|
+
* dialect (a declaration that cannot take effect must not read as one that
|
|
5489
|
+
* did).
|
|
5490
|
+
*/
|
|
5491
|
+
readonly charset?: MysqlCharset | undefined;
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5185
5494
|
/** What a schema operation on the migration context found to do. */
|
|
5186
5495
|
export declare type SchemaOpOutcome = 'applied' | 'already-absent' | 'already-present';
|
|
5187
5496
|
|
|
5497
|
+
/** A schema object a body asked about, in the vocabulary a user writes. */
|
|
5498
|
+
export declare type SchemaProbe = {
|
|
5499
|
+
readonly kind: 'table';
|
|
5500
|
+
readonly table: string;
|
|
5501
|
+
} | {
|
|
5502
|
+
readonly kind: 'column';
|
|
5503
|
+
readonly table: string;
|
|
5504
|
+
readonly name: string;
|
|
5505
|
+
} | {
|
|
5506
|
+
readonly kind: 'index';
|
|
5507
|
+
readonly table: string;
|
|
5508
|
+
readonly name: string;
|
|
5509
|
+
} | {
|
|
5510
|
+
readonly kind: 'foreign-key';
|
|
5511
|
+
readonly table: string;
|
|
5512
|
+
readonly name: string;
|
|
5513
|
+
};
|
|
5514
|
+
|
|
5515
|
+
export declare interface SchemaProbeRecorder {
|
|
5516
|
+
/** The context to hand the body — `ctx` with a recording `schema`. */
|
|
5517
|
+
readonly ctx: FileMigrationContext;
|
|
5518
|
+
/** Every existence question that answered ABSENT, in the order it was asked. */
|
|
5519
|
+
readonly absent: () => ReadonlyArray<SchemaProbe>;
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5188
5522
|
/**
|
|
5189
5523
|
* The full schema as the planner sees it on one side of the diff.
|
|
5190
5524
|
* Both the declared side and the introspected side conform to this
|
|
@@ -5350,6 +5684,67 @@ export declare interface SquashResult {
|
|
|
5350
5684
|
readonly snapshotId: string;
|
|
5351
5685
|
}
|
|
5352
5686
|
|
|
5687
|
+
/** The declared snapshot's tables with the store charset stamped on every string column. */
|
|
5688
|
+
export declare const stampDeclaredCharset: (tables: ReadonlyArray<TableSnapshot>, charset: MysqlCharset) => ReadonlyArray<TableSnapshot>;
|
|
5689
|
+
|
|
5690
|
+
/**
|
|
5691
|
+
* What to do about the findings, once. Names the real config field and the
|
|
5692
|
+
* command that applies it; nothing here is an env var.
|
|
5693
|
+
*/
|
|
5694
|
+
export declare const STORE_CHARSET_REMEDY: string;
|
|
5695
|
+
|
|
5696
|
+
/**
|
|
5697
|
+
* One table the store's character set is wrong about.
|
|
5698
|
+
*
|
|
5699
|
+
* - `disagrees-with-declaration` — the app declares a charset and these
|
|
5700
|
+
* columns are stored in another one. The plan carries the conversion; the
|
|
5701
|
+
* doctor names it for the operator who has not run `voltro db plan`.
|
|
5702
|
+
* - `json-outruns-text` — no declaration, and the table has a json column
|
|
5703
|
+
* (which holds every Unicode character on both engines) next to string
|
|
5704
|
+
* columns that do not. The reported symptom, as a line.
|
|
5705
|
+
* - `mixed` — no declaration, no json, and the table's string columns are not
|
|
5706
|
+
* all in one charset: a column converted by hand, or a column added under a
|
|
5707
|
+
* different database default. Two columns of one declaration disagree about
|
|
5708
|
+
* what a string is.
|
|
5709
|
+
*/
|
|
5710
|
+
export declare interface StoreCharsetFinding {
|
|
5711
|
+
readonly table: string;
|
|
5712
|
+
readonly kind: 'disagrees-with-declaration' | 'json-outruns-text' | 'mixed';
|
|
5713
|
+
/** The columns that are not where they should be, with the charset they are in. */
|
|
5714
|
+
readonly columns: ReadonlyArray<{
|
|
5715
|
+
readonly column: string;
|
|
5716
|
+
readonly charset: string;
|
|
5717
|
+
}>;
|
|
5718
|
+
/** What the finding measures against: the declaration, or `utf8mb4` for json. */
|
|
5719
|
+
readonly expected: string;
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
/**
|
|
5723
|
+
* The tables whose string columns cannot hold what they should. Pure — over a
|
|
5724
|
+
* live snapshot the caller has already read — so `voltro db plan`, `voltro
|
|
5725
|
+
* doctor` and a test can ask the same question of the same reading.
|
|
5726
|
+
*
|
|
5727
|
+
* Zero findings on a healthy database is the contract: a `utf8mb4` database
|
|
5728
|
+
* with no declaration reports nothing, and so does any database whose every
|
|
5729
|
+
* string column matches its declaration. Measured against both engines in
|
|
5730
|
+
* `storeCharset.integration.test.ts`.
|
|
5731
|
+
*/
|
|
5732
|
+
export declare const storeCharsetFindings: (input: {
|
|
5733
|
+
readonly live: SchemaSnapshot;
|
|
5734
|
+
readonly dialect: DialectId;
|
|
5735
|
+
readonly charset?: MysqlCharset | undefined;
|
|
5736
|
+
}) => ReadonlyArray<StoreCharsetFinding>;
|
|
5737
|
+
|
|
5738
|
+
/** One table as a store that has no catalog can describe it. See {@link DataStore.storedTables}. */
|
|
5739
|
+
declare interface StoredTableShape {
|
|
5740
|
+
/** The table's name, unqualified by any namespace the reporting view is bound to. */
|
|
5741
|
+
readonly table: string;
|
|
5742
|
+
/** The union of the column names the held rows carry. Empty for a table with no rows. */
|
|
5743
|
+
readonly columns: ReadonlyArray<string>;
|
|
5744
|
+
/** How many rows the store holds for this table. `0` means `columns` carries no evidence. */
|
|
5745
|
+
readonly rowCount: number;
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5353
5748
|
/**
|
|
5354
5749
|
* `col IN (SELECT col FROM ...)` / `col NOT IN (SELECT ...)` — the
|
|
5355
5750
|
* RHS is a sub-query descriptor. The sub-query MUST project a
|
|
@@ -5981,6 +6376,20 @@ declare interface TableUnique {
|
|
|
5981
6376
|
};
|
|
5982
6377
|
}
|
|
5983
6378
|
|
|
6379
|
+
/**
|
|
6380
|
+
* The tenant databases whose string columns are stored otherwise than the
|
|
6381
|
+
* declaration — or, undeclared, otherwise than their json columns. Only the
|
|
6382
|
+
* charset facts are read (type kind, charset), which is all the question
|
|
6383
|
+
* needs; a full introspection per tenant would be a catalog scan per tenant.
|
|
6384
|
+
*
|
|
6385
|
+
* Empty on a server with no tenant database, and empty when every tenant
|
|
6386
|
+
* agrees. Namespaces come out sorted.
|
|
6387
|
+
*/
|
|
6388
|
+
export declare const tenantNamespaceCharsetFindings: (run: RunSql, options: {
|
|
6389
|
+
readonly charset?: MysqlCharset | undefined;
|
|
6390
|
+
readonly dialect?: "mysql" | "mariadb";
|
|
6391
|
+
}) => Promise<ReadonlyArray<NamespaceCharsetReport>>;
|
|
6392
|
+
|
|
5984
6393
|
/**
|
|
5985
6394
|
* A `transactional` migration whose body committed the transaction from
|
|
5986
6395
|
* inside — DDL on the MySQL family, or an explicit COMMIT. The body's work
|
|
@@ -6164,6 +6573,18 @@ declare interface WindowSpec_2 {
|
|
|
6164
6573
|
|
|
6165
6574
|
export declare const withMigrationLock: <A, E, R = never>(sql: SqlClient.SqlClient, work: Effect.Effect<A, E, R>, scope?: MigrationLockScope) => Effect.Effect<A, E | SqlError_2, R>;
|
|
6166
6575
|
|
|
6576
|
+
/**
|
|
6577
|
+
* Wrap a migration context so its four existence questions are recorded when —
|
|
6578
|
+
* and only when — they answer absent.
|
|
6579
|
+
*
|
|
6580
|
+
* The four are exactly `MigrationSchemaOps`'s `*Exists` members, and the map
|
|
6581
|
+
* below is typed over them, so a fifth existence question added there fails to
|
|
6582
|
+
* compile until it has been decided whether an absent answer is evidence of the
|
|
6583
|
+
* same thing. An existence probe nobody recorded reads exactly like a body that
|
|
6584
|
+
* never asked.
|
|
6585
|
+
*/
|
|
6586
|
+
export declare const withSchemaProbeRecorder: (ctx: FileMigrationContext) => SchemaProbeRecorder;
|
|
6587
|
+
|
|
6167
6588
|
/** Tree of relations to eager-load. */
|
|
6168
6589
|
declare type WithSpec = Readonly<Record<string, true | EagerLoadSpec>>;
|
|
6169
6590
|
|