@voltro/database 0.71.0 → 0.72.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 +435 -0
- package/THIRD-PARTY-NOTICES.md +29 -1
- package/dist/frameworkLiveTables-C1WjQ_uI.js +1641 -0
- package/dist/index.d.ts +661 -30
- package/dist/index.js +832 -1061
- package/dist/sql.d.ts +3027 -711
- package/dist/sql.js +2014 -1236
- package/package.json +3 -2
- package/dist/frameworkLiveTables-D53uncZH.js +0 -1308
package/dist/index.d.ts
CHANGED
|
@@ -1791,6 +1791,11 @@ export declare interface ColumnDefinition<TsType, Type extends ColumnType = Colu
|
|
|
1791
1791
|
* `boolean` does not distribute. */
|
|
1792
1792
|
declare type ColumnHasDefault<D> = D extends ColumnDefinition<unknown, ColumnType, infer HD> ? ([HD] extends [true] ? true : false) : false;
|
|
1793
1793
|
|
|
1794
|
+
/** A column as the DSL builds it — `text().nullable()`, `integer().default(0)` — or its definition. */
|
|
1795
|
+
export declare type ColumnInput = {
|
|
1796
|
+
__definition(): ColumnDefinition<unknown>;
|
|
1797
|
+
} | ColumnDefinition<unknown>;
|
|
1798
|
+
|
|
1794
1799
|
/** A column may be omitted from an insert when it is auto-filled, has a default,
|
|
1795
1800
|
* or is nullable. */
|
|
1796
1801
|
declare type ColumnOptionalForInsert<K extends PropertyKey, D> = K extends AutoFilledColumn ? true : ColumnHasDefault<D> extends true ? true : null extends InferColumn<D> ? true : false;
|
|
@@ -1811,6 +1816,232 @@ declare type ColumnOptionalForInsert<K extends PropertyKey, D> = K extends AutoF
|
|
|
1811
1816
|
*/
|
|
1812
1817
|
export declare const columnSchema: <TsType, C extends ColumnType>(def: ColumnDefinition<TsType, C>) => Schema.Schema.Any;
|
|
1813
1818
|
|
|
1819
|
+
/**
|
|
1820
|
+
* A column as seen by the planner: either declared in code (extracted
|
|
1821
|
+
* from `ColumnDefinition`) or live in the DB (extracted via introspection).
|
|
1822
|
+
*
|
|
1823
|
+
* The shape is intentionally minimal — only the bits that affect diff
|
|
1824
|
+
* classification land here. Things like `idScheme`, `computed`,
|
|
1825
|
+
* `defaultFactory` etc. live in the runtime layer and don't survive
|
|
1826
|
+
* the round-trip to `information_schema`.
|
|
1827
|
+
*/
|
|
1828
|
+
declare interface ColumnSnapshot {
|
|
1829
|
+
readonly name: string;
|
|
1830
|
+
readonly type: ColumnType;
|
|
1831
|
+
readonly nullable: boolean;
|
|
1832
|
+
readonly unique: boolean;
|
|
1833
|
+
readonly hasDefault: boolean;
|
|
1834
|
+
readonly defaultValue?: unknown;
|
|
1835
|
+
/**
|
|
1836
|
+
* Type-narrowing acknowledgement from `.narrowedFrom(from, { using })`.
|
|
1837
|
+
* Only ever set on the DECLARED side; introspection leaves it
|
|
1838
|
+
* undefined (the live DB has no notion of a prior type). When
|
|
1839
|
+
* `from` equals the live column type the planner downgrades the
|
|
1840
|
+
* otherwise-blocked type change to `needs-backfill` and threads
|
|
1841
|
+
* `using` into the `ALTER COLUMN … TYPE … USING <using>` cast. */
|
|
1842
|
+
readonly narrowedFrom?: {
|
|
1843
|
+
readonly from: ColumnType;
|
|
1844
|
+
readonly using?: string;
|
|
1845
|
+
};
|
|
1846
|
+
/**
|
|
1847
|
+
* Bounded text length from `text().maxLength(n)` — carried on BOTH sides.
|
|
1848
|
+
*
|
|
1849
|
+
* DECLARED: from the column definition. LIVE: from introspection, and ONLY when
|
|
1850
|
+
* the dialect materialises a length. An unbounded text family member
|
|
1851
|
+
* (`text`/`longtext`/`mediumtext`/`clob`) leaves this `undefined` rather than
|
|
1852
|
+
* reporting the type's theoretical maximum — mysql answers 4294967295 for
|
|
1853
|
+
* `longtext`, and comparing that against a declared `undefined` would re-emit an
|
|
1854
|
+
* ALTER on every boot forever.
|
|
1855
|
+
*
|
|
1856
|
+
* WHY IT IS HERE AT ALL. It was absent from the snapshot entirely: the value
|
|
1857
|
+
* lived on the column definition, was read only when rendering CREATE DDL, and
|
|
1858
|
+
* never reached the comparison. So the differ was not comparing lengths wrongly
|
|
1859
|
+
* — it could not see them. Adding `.maxLength(n)` to an EXISTING column planned
|
|
1860
|
+
* zero operations and reported "schema is up to date" while the live column
|
|
1861
|
+
* stayed `longtext`. A deployment hit that trying to apply the documented remedy
|
|
1862
|
+
* for MariaDB's hash long-unique, and their contrast is what pinned it: a column
|
|
1863
|
+
* bounded AT CREATION was `varchar(64)` (DDL path, fine), one bounded afterwards
|
|
1864
|
+
* stayed `longtext` (diff path, blind).
|
|
1865
|
+
*
|
|
1866
|
+
* NOT compared on sqlite: it has no length-enforced type, so the declared value
|
|
1867
|
+
* is real and the live side can never report one. Comparing them there would
|
|
1868
|
+
* re-emit forever. See `sameColumnShape`.
|
|
1869
|
+
*/
|
|
1870
|
+
readonly maxLength?: number;
|
|
1871
|
+
readonly references?: {
|
|
1872
|
+
readonly table: string;
|
|
1873
|
+
readonly column: string;
|
|
1874
|
+
readonly onDelete?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
1875
|
+
readonly onUpdate?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
1876
|
+
/**
|
|
1877
|
+
* Orphan-handling policy for adding this FK to a POPULATED column
|
|
1878
|
+
* (`reference(() => t, { orphanPolicy })`). Only ever set on the
|
|
1879
|
+
* DECLARED side; introspection leaves it undefined (the live DB has
|
|
1880
|
+
* no notion of it). `normRef` strips it before comparison so it never
|
|
1881
|
+
* causes FK churn — the planner reads it directly to classify +
|
|
1882
|
+
* drive the applier's orphan pre-step. See the references-diff in
|
|
1883
|
+
* `classifyColumnChange`.
|
|
1884
|
+
*/
|
|
1885
|
+
readonly orphanPolicy?: 'fail' | 'null' | 'delete';
|
|
1886
|
+
/**
|
|
1887
|
+
* The constraint's catalog name, on the LIVE side only. A reader reports
|
|
1888
|
+
* it (postgres, the mysql family, mssql; sqlite names nothing) so a drop
|
|
1889
|
+
* can name the constraint that is actually there rather than assume the
|
|
1890
|
+
* `<table>_<column>_fkey` convention the framework uses when IT creates
|
|
1891
|
+
* one. `normRef` strips it before comparison and before hashing: a name
|
|
1892
|
+
* is a catalog identity, not schema.
|
|
1893
|
+
*/
|
|
1894
|
+
readonly constraintName?: string;
|
|
1895
|
+
};
|
|
1896
|
+
/** Physical representation for `reference()`; defaults to `text`. */
|
|
1897
|
+
readonly referenceStorage?: 'text' | 'numeric';
|
|
1898
|
+
/** `oneOf(['a','b'])` → CHECK constraint values. */
|
|
1899
|
+
readonly oneOf?: ReadonlyArray<string>;
|
|
1900
|
+
/** Required/observed JSON validity on SQL Server. Unverified means no
|
|
1901
|
+
* recognized enabled and trusted check proves the existing data valid. */
|
|
1902
|
+
readonly jsonValidation?: 'value' | 'container' | 'unverified';
|
|
1903
|
+
/** Live SQL Server JSON checks recognized by complete predicate shape.
|
|
1904
|
+
* Names are catalog identities for DDL, not portable schema fingerprints. */
|
|
1905
|
+
readonly jsonChecks?: ReadonlyArray<{
|
|
1906
|
+
readonly name: string;
|
|
1907
|
+
readonly validation: 'value' | 'container';
|
|
1908
|
+
readonly enabled: boolean;
|
|
1909
|
+
readonly trusted: boolean;
|
|
1910
|
+
}>;
|
|
1911
|
+
/**
|
|
1912
|
+
* Fixed-point precision + scale for a `decimal(p,s)` / `numeric(p,s)`
|
|
1913
|
+
* column (`type === 'decimal'`). Drives the emitted `NUMERIC(p,s)` /
|
|
1914
|
+
* `DECIMAL(p,s)` DDL. Set on the DECLARED side; introspection leaves them
|
|
1915
|
+
* undefined (the planner compares the `decimal` type structurally, not the
|
|
1916
|
+
* exact precision — an ALTER of precision alone is not auto-classified, same
|
|
1917
|
+
* posture as `generatedAs`). See `snapshotPgType` / `snapshotMysqlType`.
|
|
1918
|
+
*/
|
|
1919
|
+
readonly precision?: number;
|
|
1920
|
+
readonly scale?: number;
|
|
1921
|
+
/**
|
|
1922
|
+
* Id-generation scheme for `type: 'id'` columns. Mirrors
|
|
1923
|
+
* `ColumnDefinition.idScheme.kind` so the applier can tell a numeric
|
|
1924
|
+
* auto-increment PK (`kind: 'numeric'`) from a typeid/string PK and
|
|
1925
|
+
* emit the dialect-correct auto-increment DDL (BIGSERIAL / BIGINT
|
|
1926
|
+
* AUTO_INCREMENT / IDENTITY / INTEGER AUTOINCREMENT). Only ever set on
|
|
1927
|
+
* the DECLARED side; introspection leaves it undefined (the live DB
|
|
1928
|
+
* doesn't report a scheme — a numeric PK is detectable there from the
|
|
1929
|
+
* integer type + auto_increment, which the diff doesn't need).
|
|
1930
|
+
*/
|
|
1931
|
+
readonly idScheme?: {
|
|
1932
|
+
readonly kind: string;
|
|
1933
|
+
};
|
|
1934
|
+
/**
|
|
1935
|
+
* What the `id` KIND is made of, on the LIVE side.
|
|
1936
|
+
*
|
|
1937
|
+
* Introspection cannot see a DSL spelling, only a column, and it reports the
|
|
1938
|
+
* `id` kind for ANY sole primary key named `id` — a NAME-based inference. So
|
|
1939
|
+
* the kind alone erases the one distinction that matters when the two sides
|
|
1940
|
+
* disagree: a text key and a sequence are both reported as `id`, and a
|
|
1941
|
+
* declaration that changed from one to the other compared EQUAL. That
|
|
1942
|
+
* approximation predates the `text()` spelling and got wider with it.
|
|
1943
|
+
*
|
|
1944
|
+
* This carries the storage the kind was inferred over, derived from the same
|
|
1945
|
+
* raw type the introspector already read. Set by all four dialect readers
|
|
1946
|
+
* wherever they apply the inference, and derived for the DECLARED side from
|
|
1947
|
+
* `idScheme` — so the comparison pairs like with like instead of pairing on
|
|
1948
|
+
* the name.
|
|
1949
|
+
*/
|
|
1950
|
+
readonly idStorage?: 'text' | 'numeric';
|
|
1951
|
+
/**
|
|
1952
|
+
* What this column's declared KIND is stored AS, when the two differ and no
|
|
1953
|
+
* reader can report the kind back.
|
|
1954
|
+
*
|
|
1955
|
+
* An `array()` is `<element>[]` on postgres and JSON / NVARCHAR(MAX) / TEXT
|
|
1956
|
+
* elsewhere, and every reader answers with the STORAGE — so the declared
|
|
1957
|
+
* kind and the live one never matched and the table hashed apart from its own
|
|
1958
|
+
* database forever. The planner already collapses this per dialect; the
|
|
1959
|
+
* fingerprint has no dialect, so the DECLARED side states the answer here at
|
|
1960
|
+
* snapshot time (`declaredSnapshot` takes the dialect) and the hash reads it.
|
|
1961
|
+
* Set on the declared side only; a live snapshot needs none, because its type
|
|
1962
|
+
* IS the storage.
|
|
1963
|
+
*/
|
|
1964
|
+
readonly storageType?: ColumnType;
|
|
1965
|
+
/**
|
|
1966
|
+
* Verbatim DDL fragment for a `raw(ddl)` column (`type === 'raw'`).
|
|
1967
|
+
* Emitted unchanged as the column's SQL type on every dialect — the
|
|
1968
|
+
* applier and the CREATE-TABLE emitter both defer to it. Only ever set
|
|
1969
|
+
* on the declared side; the live DB reports a concrete type, not the
|
|
1970
|
+
* original fragment, so introspection leaves it undefined.
|
|
1971
|
+
*/
|
|
1972
|
+
readonly rawDdl?: string;
|
|
1973
|
+
/**
|
|
1974
|
+
* `.generatedAs(expr, { stored })` — a DB-computed column. When set the
|
|
1975
|
+
* applier emits `GENERATED ALWAYS AS (<expr>) STORED|VIRTUAL` (mssql:
|
|
1976
|
+
* `AS (<expr>) PERSISTED`) and SUPPRESSES NOT NULL / DEFAULT / inline
|
|
1977
|
+
* UNIQUE / REFERENCES (the value is computed; the DB rejects those
|
|
1978
|
+
* alongside a generation clause). Only ever set on the DECLARED side —
|
|
1979
|
+
* introspection does NOT read generation expressions, so the live snapshot
|
|
1980
|
+
* leaves it undefined. The planner's `sameColumnShape` deliberately does
|
|
1981
|
+
* NOT compare this field, so a declared-generated column never diffs
|
|
1982
|
+
* against its introspected-as-plain live form (no churn). Consequence:
|
|
1983
|
+
* the declarative engine creates generated columns correctly but does NOT
|
|
1984
|
+
* detect a CHANGE to the expression after the fact — recreate the table
|
|
1985
|
+
* (drop + re-migrate) to alter a generation expression.
|
|
1986
|
+
*/
|
|
1987
|
+
readonly generatedAs?: {
|
|
1988
|
+
readonly expr: string;
|
|
1989
|
+
readonly stored: boolean;
|
|
1990
|
+
};
|
|
1991
|
+
/**
|
|
1992
|
+
* Data-sensitivity classification from `.sensitive(class)` (or implied by
|
|
1993
|
+
* `.encrypted()`). DECLARED-side only — introspection never reports it. Read
|
|
1994
|
+
* by the export masker to pseudonymise/anonymise the column when copying to a
|
|
1995
|
+
* lower environment; the planner ignores it (no DDL, no churn). See `safe`.
|
|
1996
|
+
*/
|
|
1997
|
+
readonly sensitive?: {
|
|
1998
|
+
readonly class: string;
|
|
1999
|
+
};
|
|
2000
|
+
/**
|
|
2001
|
+
* Reviewed-safe marker from `.safe()`. DECLARED-side only. Lets the export
|
|
2002
|
+
* masker be fail-closed: a column that is neither `sensitive` nor `safe`
|
|
2003
|
+
* blocks a masking export until classified.
|
|
2004
|
+
*/
|
|
2005
|
+
readonly safe?: boolean;
|
|
2006
|
+
/**
|
|
2007
|
+
* Type PARAMETERS for the three `ColumnType`s whose DDL is not determined by
|
|
2008
|
+
* the type tag alone — `vector(n)`, `array(of)`, `enum(name, values)`.
|
|
2009
|
+
* DECLARED-side only (introspection reports a concrete SQL type, not the
|
|
2010
|
+
* declaration that produced it).
|
|
2011
|
+
*
|
|
2012
|
+
* These exist because the applier renders its DDL from a ColumnSnapshot, not
|
|
2013
|
+
* from the `ColumnDefinition` that `migrate.ts`'s canonical `sqlType` reads.
|
|
2014
|
+
* Without them the snapshot renderers had nothing to render and collapsed all
|
|
2015
|
+
* three to `text` — which is not a smaller mistake than it looks. A declared
|
|
2016
|
+
* `vector(1536)` against a live `text` column planned an `alter-column-type`
|
|
2017
|
+
* that emitted `ALTER COLUMN … TYPE text`: valid SQL, applied successfully,
|
|
2018
|
+
* changed nothing. The plan re-emitted it on every boot, the applier reported
|
|
2019
|
+
* success every time, and the schema never converged. (Reported from a live
|
|
2020
|
+
* pod: "applied 31 op(s)" with none of the 31 present in the database.)
|
|
2021
|
+
*
|
|
2022
|
+
* The rule for anything added later: if the applier has to RENDER it, the
|
|
2023
|
+
* snapshot has to CARRY it — a snapshot renderer must never invent a type it
|
|
2024
|
+
* wasn't given.
|
|
2025
|
+
*/
|
|
2026
|
+
readonly vectorDim?: number;
|
|
2027
|
+
readonly vectorPrecision?: 'float32' | 'half';
|
|
2028
|
+
readonly arrayElement?: ColumnType;
|
|
2029
|
+
readonly enumName?: string;
|
|
2030
|
+
readonly enumValues?: ReadonlyArray<string>;
|
|
2031
|
+
/**
|
|
2032
|
+
* PostGIS `geography(kind, srid)` / `geometry(kind, srid)` parameters. Same
|
|
2033
|
+
* reason as the three above: a spatial column declares `type: 'text'`, so
|
|
2034
|
+
* WITHOUT this the applier's `add-column` created a plain `text` column and
|
|
2035
|
+
* introspection (`USER-DEFINED` → `text`) agreed with it — no churn, no
|
|
2036
|
+
* error, and no spatial column. DECLARED-side only.
|
|
2037
|
+
*/
|
|
2038
|
+
readonly spatial?: {
|
|
2039
|
+
readonly kind: 'geography' | 'geometry';
|
|
2040
|
+
readonly geomKind: string;
|
|
2041
|
+
readonly srid: number;
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1814
2045
|
/**
|
|
1815
2046
|
* How many bind parameters one row costs.
|
|
1816
2047
|
*
|
|
@@ -2871,6 +3102,15 @@ export declare const decodeFieldValue: (cipher: FieldCipher, ciphertext: string,
|
|
|
2871
3102
|
|
|
2872
3103
|
export declare type DecoderDialectId = 'postgres' | 'mysql' | 'mariadb' | 'sqlite' | 'mssql' | 'turso';
|
|
2873
3104
|
|
|
3105
|
+
/**
|
|
3106
|
+
* The same decode, for a caller that HOLDS the table definition rather than
|
|
3107
|
+
* a name to look up — the migration applier reads rows of a table it was
|
|
3108
|
+
* handed as a declaration, on a process where nothing registered it.
|
|
3109
|
+
*/
|
|
3110
|
+
export declare const decodeRowsForTable: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, table: {
|
|
3111
|
+
readonly fields: Record<string, unknown>;
|
|
3112
|
+
}, dialect: DecoderDialectId) => ReadonlyArray<T>;
|
|
3113
|
+
|
|
2874
3114
|
/**
|
|
2875
3115
|
* Decode every row of a query result against the table's registered
|
|
2876
3116
|
* column types + the dialect's read quirks. Returns a fresh array of
|
|
@@ -2960,6 +3200,9 @@ export declare const defineMixin: <const Input extends FieldsInput>(options: Mix
|
|
|
2960
3200
|
|
|
2961
3201
|
export declare const defineSeed: (input: SeedDefinitionInput) => SeedDefinition;
|
|
2962
3202
|
|
|
3203
|
+
/** Forget the selection. A no-op when there is none. */
|
|
3204
|
+
export declare const deleteTenantSelection: (store: DataStore, identity: TenantSelectionRowIdentity) => Promise<void>;
|
|
3205
|
+
|
|
2963
3206
|
export declare const denseRank: (alias?: string) => WindowBuilder;
|
|
2964
3207
|
|
|
2965
3208
|
/**
|
|
@@ -3209,6 +3452,18 @@ export declare const drainIdentifierWarnings: () => ReadonlyArray<string>;
|
|
|
3209
3452
|
*/
|
|
3210
3453
|
export declare const dropped: () => ColumnBuilder<unknown, "text">;
|
|
3211
3454
|
|
|
3455
|
+
export declare interface EachOptions {
|
|
3456
|
+
/** Rows per page. Default 1000. Pages are keyed by the `id` column. */
|
|
3457
|
+
readonly batchSize?: number;
|
|
3458
|
+
/** Milliseconds to sleep between pages, to share the database with live traffic. Default 0. */
|
|
3459
|
+
readonly sleepMs?: number;
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
export declare interface EachReport {
|
|
3463
|
+
readonly scanned: number;
|
|
3464
|
+
readonly pages: number;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3212
3467
|
/**
|
|
3213
3468
|
* Raised when a relation declared `one` matches MORE than one row for the same
|
|
3214
3469
|
* parent key — the mis-declaration that silently hands the app an arbitrary
|
|
@@ -3660,26 +3915,72 @@ export declare interface FileMigration {
|
|
|
3660
3915
|
/** Human description that surfaces in `voltro db plans` + the
|
|
3661
3916
|
* history timeline. Keep it short — what the migration does. */
|
|
3662
3917
|
readonly description: string;
|
|
3663
|
-
/**
|
|
3664
|
-
*
|
|
3665
|
-
*
|
|
3918
|
+
/**
|
|
3919
|
+
* Run `up` and the ledger row that records it inside ONE transaction, on
|
|
3920
|
+
* every dialect. **The default.** A body that fails or crashes leaves
|
|
3921
|
+
* nothing behind — not half its rows, and not a ledger row for work that did
|
|
3922
|
+
* not finish. `transactional: false` is one unit per statement with the
|
|
3923
|
+
* ledger row written after the body: a crash in the middle leaves the rows
|
|
3924
|
+
* already written AND no record, so the body runs again on the next boot
|
|
3925
|
+
* over half-converted data, and has to be written to survive that.
|
|
3926
|
+
*
|
|
3927
|
+
* The decision is per MIGRATION, not per dialect, because the engine
|
|
3928
|
+
* property that matters is about DDL: MySQL, MariaDB and Turso cannot hold
|
|
3929
|
+
* DDL inside a transaction — the MySQL family commits everything before it
|
|
3930
|
+
* on every `ALTER`. A transactional migration there is data only. The
|
|
3931
|
+
* runner MEASURES this rather than trusting it: a body that broke its own
|
|
3932
|
+
* transaction is recorded, so it does not run twice, and the run then fails
|
|
3933
|
+
* with the migration's name and the fix (`transactional: false`, or the
|
|
3934
|
+
* schema change through `ctx.schema` in a migration of its own).
|
|
3935
|
+
*/
|
|
3936
|
+
readonly transactional?: boolean;
|
|
3937
|
+
/** The forward body. */
|
|
3666
3938
|
readonly up: (ctx: FileMigrationContext) => Promise<void> | Effect.Effect<void, unknown, never>;
|
|
3667
|
-
/**
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
3670
|
-
*
|
|
3939
|
+
/**
|
|
3940
|
+
* The reverse body — required, so that the decision is written down. A
|
|
3941
|
+
* migration that repairs a shape has no inverse: say so with
|
|
3942
|
+
* `down: irreversible('…')`, and `voltro db rollback-file` refuses with
|
|
3943
|
+
* that reason instead of running a body that pretends.
|
|
3944
|
+
*/
|
|
3671
3945
|
readonly down: (ctx: FileMigrationContext) => Promise<void> | Effect.Effect<void, unknown, never>;
|
|
3672
3946
|
}
|
|
3673
3947
|
|
|
3674
3948
|
/**
|
|
3675
3949
|
* The runtime context a file-based migration's `up` / `down` body
|
|
3676
3950
|
* receives.
|
|
3951
|
+
*
|
|
3952
|
+
* `store` is the same typed data store the application runs on — the same
|
|
3953
|
+
* column codecs, the same dialect, the same query builder — bound to the
|
|
3954
|
+
* database this migration is running against. A row read through it has its
|
|
3955
|
+
* JSON parsed and its timestamps as `Date`s on every dialect; a row written
|
|
3956
|
+
* through it is encoded the same way. `sql` stays beside it for what the
|
|
3957
|
+
* builder does not express.
|
|
3677
3958
|
*/
|
|
3678
3959
|
export declare interface FileMigrationContext {
|
|
3679
|
-
/**
|
|
3680
|
-
*
|
|
3681
|
-
*
|
|
3682
|
-
|
|
3960
|
+
/**
|
|
3961
|
+
* The escape hatch, and it asks for a reason. `raw('why')` returns the
|
|
3962
|
+
* `@effect/sql` client — `sql.unsafe(...)`, `sql\`...\``,
|
|
3963
|
+
* `sql.onDialectOrElse({...})` — for the one thing neither `store` nor
|
|
3964
|
+
* `schema` expresses. The reason is required (an empty one throws), it is
|
|
3965
|
+
* what `voltro doctor` lists beside every migration that reaches for raw
|
|
3966
|
+
* SQL, and it is the sentence a reviewer reads first. A migration written
|
|
3967
|
+
* against the raw client is bound to the dialect it was written on; the
|
|
3968
|
+
* rest of this context is not.
|
|
3969
|
+
*/
|
|
3970
|
+
readonly raw: (reason: string) => SqlClient.SqlClient;
|
|
3971
|
+
/**
|
|
3972
|
+
* The application's data store over this database. Typed reads through a
|
|
3973
|
+
* descriptor (`store.query(database.vacations.descriptor)`) are checked
|
|
3974
|
+
* against the CURRENT declaration at compile time — a column renamed since
|
|
3975
|
+
* this migration was written fails the build, not the deploy. Reads by
|
|
3976
|
+
* table name (`store.query({ table: 'vacations', … })`) go to the database
|
|
3977
|
+
* as it is, decoded through whatever the current declaration says about
|
|
3978
|
+
* the columns it still knows; a column it no longer declares passes
|
|
3979
|
+
* through as the driver returned it.
|
|
3980
|
+
*/
|
|
3981
|
+
readonly store: DataStore;
|
|
3982
|
+
/** The dialect this migration is running against. */
|
|
3983
|
+
readonly dialect: DialectId;
|
|
3683
3984
|
/**
|
|
3684
3985
|
* Lightweight log surface scoped to the migration. Lands in
|
|
3685
3986
|
* `voltro logs --filter 'migrations:file:<id>'` for any apply
|
|
@@ -3692,6 +3993,63 @@ export declare interface FileMigrationContext {
|
|
|
3692
3993
|
/** ISO-8601 timestamp when this migration started (deterministic
|
|
3693
3994
|
* across the up/down pair via the applied_at column). */
|
|
3694
3995
|
readonly appliedAt: string;
|
|
3996
|
+
/** Schema questions and dialect-aware schema operations. */
|
|
3997
|
+
readonly schema: MigrationSchemaOps;
|
|
3998
|
+
/**
|
|
3999
|
+
* Visit every row of `table` in pages keyed by `id`. `fn` may return a
|
|
4000
|
+
* Promise or an Effect. The table needs an `id` column, which every
|
|
4001
|
+
* `table()` with `id()` has.
|
|
4002
|
+
*/
|
|
4003
|
+
readonly each: <R extends Row = Row>(table: string, fn: (row: R) => Promise<void> | Effect.Effect<void, unknown, never>, options?: EachOptions) => Effect.Effect<EachReport, unknown>;
|
|
4004
|
+
/**
|
|
4005
|
+
* Read rows in pages, compute a patch per row, write it back under an
|
|
4006
|
+
* optimistic guard, and repeat until a pass changes nothing.
|
|
4007
|
+
*
|
|
4008
|
+
* The guard is the row's own values: the UPDATE carries `WHERE id = ? AND
|
|
4009
|
+
* <each patched column> = <the value just read>` (plus `options.guard`), so
|
|
4010
|
+
* a row another process changed between the read and the write is not
|
|
4011
|
+
* overwritten — it is counted as contended and read again on the next
|
|
4012
|
+
* pass. That is the guard a migration needs while the previous deploy is
|
|
4013
|
+
* still writing, without a hashing function a dialect may not have.
|
|
4014
|
+
*
|
|
4015
|
+
* `fn` returns the patch, or `undefined` for a row that is already in
|
|
4016
|
+
* shape. A pass in which every row returns `undefined` and nothing was
|
|
4017
|
+
* contended is the stable state that ends the rewrite.
|
|
4018
|
+
*/
|
|
4019
|
+
readonly rewrite: <R extends Row = Row>(table: string, fn: (row: R) => Partial<R> | undefined | Promise<Partial<R> | undefined>, options?: RewriteOptions) => Effect.Effect<RewriteReport, unknown>;
|
|
4020
|
+
/**
|
|
4021
|
+
* A post-condition. Registered during `up`, evaluated after it — every one
|
|
4022
|
+
* must hold, or the migration FAILS and is not recorded, so the next run
|
|
4023
|
+
* tries again. This is the "read it back and check" every data migration
|
|
4024
|
+
* ends with, as a contract the runner enforces instead of a pattern each
|
|
4025
|
+
* body repeats.
|
|
4026
|
+
*/
|
|
4027
|
+
readonly expect: (description: string, check: () => Promise<boolean> | Effect.Effect<boolean, unknown, never>) => void;
|
|
4028
|
+
/**
|
|
4029
|
+
* A precondition. When `condition` is false the body stops HERE, the
|
|
4030
|
+
* migration is recorded as applied with `skipped: <reason>` in its notes,
|
|
4031
|
+
* and it never runs again on this database — the same answer Liquibase's
|
|
4032
|
+
* `MARK_RAN` gives. For a migration that must not be recorded when its
|
|
4033
|
+
* precondition fails, fail it instead.
|
|
4034
|
+
*
|
|
4035
|
+
* yield* ctx.skipUnless(ctx.schema.tableExists('vacations'), 'no vacations table on this database')
|
|
4036
|
+
*/
|
|
4037
|
+
readonly skipUnless: (condition: boolean | Promise<boolean> | Effect.Effect<boolean, unknown, never>, reason: string) => Effect.Effect<void, unknown>;
|
|
4038
|
+
/**
|
|
4039
|
+
* Run `work` inside ONE database transaction: the `store` it receives is
|
|
4040
|
+
* the transaction's view, and an Effect it returns is executed on the
|
|
4041
|
+
* transaction's connection, so `sql` statements yielded inside it are part
|
|
4042
|
+
* of the same unit. A migration that is transactional as a whole declares
|
|
4043
|
+
* `transactional: true` instead and gets the ledger row in the same unit.
|
|
4044
|
+
*
|
|
4045
|
+
* What the engine cannot do, this cannot do either: on MySQL and MariaDB a
|
|
4046
|
+
* DDL statement inside the unit commits everything before it. A
|
|
4047
|
+
* transactional scope is for data.
|
|
4048
|
+
*/
|
|
4049
|
+
readonly transactional: <T>(work: (tx: {
|
|
4050
|
+
readonly store: DataStore;
|
|
4051
|
+
readonly raw: (reason: string) => SqlClient.SqlClient;
|
|
4052
|
+
}) => Promise<T> | Effect.Effect<T, unknown, SqlClient.SqlClient>) => Effect.Effect<T, unknown>;
|
|
3695
4053
|
}
|
|
3696
4054
|
|
|
3697
4055
|
/**
|
|
@@ -4041,6 +4399,32 @@ export declare interface IndexKindOptions {
|
|
|
4041
4399
|
};
|
|
4042
4400
|
}
|
|
4043
4401
|
|
|
4402
|
+
declare interface IndexSnapshot {
|
|
4403
|
+
readonly name: string;
|
|
4404
|
+
readonly table: string;
|
|
4405
|
+
readonly columns: ReadonlyArray<string>;
|
|
4406
|
+
readonly unique: boolean;
|
|
4407
|
+
/** Physical access method; absent means btree. Catalogs may name extension methods. */
|
|
4408
|
+
readonly kind?: string;
|
|
4409
|
+
/** Resolved HNSW build parameters and operator class, not a query-time metric. */
|
|
4410
|
+
readonly kindOptions?: IndexKindOptions;
|
|
4411
|
+
/** SQL predicate for a partial/filtered index; never a full UNIQUE constraint. */
|
|
4412
|
+
readonly where?: string;
|
|
4413
|
+
/** False for an invalid PostgreSQL index left by an interrupted online build. */
|
|
4414
|
+
readonly valid?: boolean;
|
|
4415
|
+
/**
|
|
4416
|
+
* Expression / json-path index (`expressionIndex(...)` / `jsonIndex(...)`),
|
|
4417
|
+
* i.e. at least one key is a SQL expression rather than a bare column. The
|
|
4418
|
+
* DB normalises the expression text on introspection (`(lower("email"))` →
|
|
4419
|
+
* `lower(email)`), so it can NEVER round-trip byte-for-byte. The planner
|
|
4420
|
+
* therefore matches such indexes by NAME + uniqueness only (see
|
|
4421
|
+
* `sameIndexShape`) — without this they re-emit on every `db plan` forever
|
|
4422
|
+
* (postgres pre-fix: they were INVISIBLE to introspect — an `add` with no
|
|
4423
|
+
* `drop` that never converged to "up to date").
|
|
4424
|
+
*/
|
|
4425
|
+
readonly expression?: boolean;
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4044
4428
|
export declare type InferColumn<C> = C extends ColumnDefinition<infer T, ColumnType> ? T : never;
|
|
4045
4429
|
|
|
4046
4430
|
/**
|
|
@@ -4209,6 +4593,25 @@ export declare const intersect: (...queries: ReadonlyArray<QueryWithDescriptorAn
|
|
|
4209
4593
|
*/
|
|
4210
4594
|
export declare const interval: () => ColumnBuilder<string, "interval", boolean>;
|
|
4211
4595
|
|
|
4596
|
+
/**
|
|
4597
|
+
* A `down` that says there is no inverse — with the reason, which is what a
|
|
4598
|
+
* rollback prints. A data migration that repairs a broken shape HAS no
|
|
4599
|
+
* inverse (its inverse would be the defect), and a `down` that only logs
|
|
4600
|
+
* "nothing to undo" and returns success lets `voltro db rollback-file`
|
|
4601
|
+
* delete the ledger row for a change that is still in the database.
|
|
4602
|
+
*/
|
|
4603
|
+
export declare const irreversible: (reason: string) => FileMigration["down"];
|
|
4604
|
+
|
|
4605
|
+
export declare class IrreversibleMigrationError extends Error {
|
|
4606
|
+
readonly migrationId: string;
|
|
4607
|
+
readonly reason: string;
|
|
4608
|
+
readonly _tag = "IrreversibleMigrationError";
|
|
4609
|
+
constructor(migrationId: string, reason: string);
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
/** The reason a `down` was declared irreversible with, or `undefined` for an ordinary body. */
|
|
4613
|
+
export declare const irreversibleReason: (down: FileMigration["down"]) => string | undefined;
|
|
4614
|
+
|
|
4212
4615
|
/** Is this value a BOUND ciphertext (`enc:v2:`)? An unbound `enc:v1:` value
|
|
4213
4616
|
* reads fine and is what `voltro db encrypt-column` re-binds. */
|
|
4214
4617
|
export declare const isBoundCiphertext: (value: unknown) => value is string;
|
|
@@ -4689,16 +5092,13 @@ export declare type MergeMixinFields<Mixins extends ReadonlyArray<AnyMixin>> = M
|
|
|
4689
5092
|
* export default migration({
|
|
4690
5093
|
* id: '20260415_120000_split_address_out',
|
|
4691
5094
|
* description: 'Move users.address* into a separate addresses table.',
|
|
4692
|
-
* up:
|
|
4693
|
-
*
|
|
4694
|
-
*
|
|
4695
|
-
*
|
|
4696
|
-
* log.info('migrated address fields to new table')
|
|
4697
|
-
* },
|
|
4698
|
-
* down:
|
|
4699
|
-
* await sql.unsafe(`ALTER TABLE users ADD COLUMN addressStreet text`)
|
|
4700
|
-
* // ... etc — see file-based docs for the full pattern
|
|
4701
|
-
* },
|
|
5095
|
+
* up: ({ sql, store, log }) => Effect.gen(function* () {
|
|
5096
|
+
* yield* sql.unsafe(`CREATE TABLE addresses ( ... )`)
|
|
5097
|
+
* yield* sql.unsafe(`INSERT INTO addresses (...) SELECT (...) FROM users`)
|
|
5098
|
+
* yield* sql.unsafe(`ALTER TABLE users DROP COLUMN addressStreet`)
|
|
5099
|
+
* yield* log.info('migrated address fields to new table')
|
|
5100
|
+
* }),
|
|
5101
|
+
* down: irreversible('the address rows have no column to go back to'),
|
|
4702
5102
|
* })
|
|
4703
5103
|
*/
|
|
4704
5104
|
export declare const migration: (spec: FileMigration) => FileMigration;
|
|
@@ -4735,6 +5135,109 @@ export declare interface MigrationDefinitionInput {
|
|
|
4735
5135
|
*/
|
|
4736
5136
|
export declare type MigrationEffect = Effect.Effect<unknown, unknown, SqlClient.SqlClient>;
|
|
4737
5137
|
|
|
5138
|
+
/** One or more `ctx.expect` post-conditions did not hold after `up`. */
|
|
5139
|
+
export declare class MigrationExpectationFailed extends Error {
|
|
5140
|
+
readonly migrationId: string;
|
|
5141
|
+
readonly failed: ReadonlyArray<string>;
|
|
5142
|
+
readonly _tag = "MigrationExpectationFailed";
|
|
5143
|
+
constructor(migrationId: string, failed: ReadonlyArray<string>);
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
/**
|
|
5147
|
+
* Schema questions and schema operations a migration body can ask of the
|
|
5148
|
+
* database it is running against, spelled once for every dialect.
|
|
5149
|
+
*
|
|
5150
|
+
* Every operation is idempotent by INSPECTION, not by error tolerance: it
|
|
5151
|
+
* reads the live schema first and answers `already-absent` /
|
|
5152
|
+
* `already-present` when there is nothing to do. That is the portable
|
|
5153
|
+
* `IF EXISTS` — MySQL has no such clause for a foreign key, and its two
|
|
5154
|
+
* "already gone" error codes are not the kind of thing a migration should
|
|
5155
|
+
* be matching on.
|
|
5156
|
+
*
|
|
5157
|
+
* On SQLite the dropping operations are table rebuilds (the dialect has no
|
|
5158
|
+
* `DROP CONSTRAINT`), built from the live table minus the change — the same
|
|
5159
|
+
* rebuild the planner's applier performs, with foreign-key enforcement off
|
|
5160
|
+
* for its duration and checked before the connection is handed back.
|
|
5161
|
+
*/
|
|
5162
|
+
export declare interface MigrationSchemaOps {
|
|
5163
|
+
/** The live schema, read fresh on every call — an operation above may have changed it. */
|
|
5164
|
+
readonly snapshot: () => Effect.Effect<SchemaSnapshot, unknown>;
|
|
5165
|
+
readonly tableExists: (table: string) => Effect.Effect<boolean, unknown>;
|
|
5166
|
+
readonly columnExists: (table: string, column: string) => Effect.Effect<boolean, unknown>;
|
|
5167
|
+
readonly indexExists: (table: string, index: string) => Effect.Effect<boolean, unknown>;
|
|
5168
|
+
/** Whether `table.column` currently carries a FOREIGN KEY. */
|
|
5169
|
+
readonly foreignKeyExists: (table: string, column: string) => Effect.Effect<boolean, unknown>;
|
|
5170
|
+
/** Drop the FOREIGN KEY on `table.column`, under the name the catalog reports for it. */
|
|
5171
|
+
readonly dropForeignKey: (table: string, column: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5172
|
+
readonly dropIndex: (table: string, index: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5173
|
+
/** Drop a column — and first the FOREIGN KEY it carries, if any, on the
|
|
5174
|
+
* dialects that refuse to drop a column a constraint still holds. */
|
|
5175
|
+
readonly dropColumn: (table: string, column: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5176
|
+
/** Rename a column. `already-present` when `to` exists and `from` does not:
|
|
5177
|
+
* a re-run after a crash that landed the rename. */
|
|
5178
|
+
readonly renameColumn: (table: string, from: string, to: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5179
|
+
/** Create a table from a declaration. `already-present` when a table of that name exists. */
|
|
5180
|
+
readonly createTable: (table: TableLike) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5181
|
+
/** Drop a table and everything in it. Data loss is the caller's explicit intent here. */
|
|
5182
|
+
readonly dropTable: (table: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5183
|
+
/** Rename a table. `already-present` when `to` exists and `from` does not. */
|
|
5184
|
+
readonly renameTable: (from: string, to: string) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5185
|
+
/**
|
|
5186
|
+
* Add a column. A NOT NULL column without a default cannot be added to a
|
|
5187
|
+
* table that has rows on any dialect: add it nullable or with a default,
|
|
5188
|
+
* fill it (`rewrite`), then `alterColumn(table, name, { nullable: false })`.
|
|
5189
|
+
*/
|
|
5190
|
+
readonly addColumn: (table: string, name: string, column: ColumnInput) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5191
|
+
/**
|
|
5192
|
+
* Change a column's nullability and/or default. `default: null` removes the
|
|
5193
|
+
* default; a type change goes through `evolve`, where the planner decides
|
|
5194
|
+
* whether it is lossy.
|
|
5195
|
+
*/
|
|
5196
|
+
readonly alterColumn: (table: string, name: string, change: {
|
|
5197
|
+
readonly nullable?: boolean;
|
|
5198
|
+
readonly default?: unknown;
|
|
5199
|
+
}) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5200
|
+
/** Add an index over `columns`; the name defaults to `<table>_<cols>_idx`. */
|
|
5201
|
+
readonly addIndex: (table: string, columns: ReadonlyArray<string>, options?: {
|
|
5202
|
+
readonly unique?: boolean;
|
|
5203
|
+
readonly name?: string;
|
|
5204
|
+
}) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5205
|
+
/** Add a FOREIGN KEY from `table.column` to `target.table`'s `target.column` (default `id`). */
|
|
5206
|
+
readonly addForeignKey: (table: string, column: string, target: {
|
|
5207
|
+
readonly table: string;
|
|
5208
|
+
readonly column?: string;
|
|
5209
|
+
}, options?: {
|
|
5210
|
+
readonly onDelete?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
5211
|
+
readonly onUpdate?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
5212
|
+
}) => Effect.Effect<SchemaOpOutcome, unknown>;
|
|
5213
|
+
/** Remove every row. `DELETE FROM` where the dialect has no TRUNCATE inside a unit. */
|
|
5214
|
+
readonly truncate: (table: string) => Effect.Effect<void, unknown>;
|
|
5215
|
+
/**
|
|
5216
|
+
* The Voltro way to change a table's shape in a migration: DECLARE the
|
|
5217
|
+
* table as it should be from here on, and the same planner that drives
|
|
5218
|
+
* `voltro db apply` diffs it against the live table and applies the
|
|
5219
|
+
* difference — add, drop, rename, retype, index, constraint — with the same
|
|
5220
|
+
* per-dialect statements, the same three-step backfills, and the same
|
|
5221
|
+
* convergence proof (an `evolve` whose re-plan is not empty fails). A drop
|
|
5222
|
+
* is allowed without a `dropped()` marker: calling `evolve` from a
|
|
5223
|
+
* migration IS the explicit intent the marker stands for elsewhere.
|
|
5224
|
+
*/
|
|
5225
|
+
readonly evolve: (table: TableLike) => Effect.Effect<{
|
|
5226
|
+
readonly applied: number;
|
|
5227
|
+
}, unknown>;
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
/**
|
|
5231
|
+
* The signal `ctx.skipUnless` raises. The runner treats it as an OUTCOME,
|
|
5232
|
+
* not a failure: the migration is recorded with the reason and never runs
|
|
5233
|
+
* again on this database.
|
|
5234
|
+
*/
|
|
5235
|
+
export declare class MigrationSkipped {
|
|
5236
|
+
readonly reason: string;
|
|
5237
|
+
readonly _tag = "MigrationSkipped";
|
|
5238
|
+
constructor(reason: string);
|
|
5239
|
+
}
|
|
5240
|
+
|
|
4738
5241
|
export declare interface MigrationStep {
|
|
4739
5242
|
readonly name: string;
|
|
4740
5243
|
readonly apply: MigrationEffect;
|
|
@@ -6157,6 +6660,9 @@ export declare interface ReadableByColumn {
|
|
|
6157
6660
|
*/
|
|
6158
6661
|
export declare const readableByColumns: (table: TableLike) => ReadonlyArray<ReadableByColumn>;
|
|
6159
6662
|
|
|
6663
|
+
/** The stored tenant, or null when the subject has no selection. */
|
|
6664
|
+
export declare const readTenantSelection: (store: DataStore, identity: TenantSelectionRowIdentity) => Promise<string | null>;
|
|
6665
|
+
|
|
6160
6666
|
/** Floating-point number. Postgres `DOUBLE PRECISION`, mysql/mariadb `DOUBLE`,
|
|
6161
6667
|
* mssql `FLOAT`, sqlite `REAL`. Drivers return JS numbers — no codec. Use for
|
|
6162
6668
|
* fractional values (ratings, percentages, measurements) where `integer()`
|
|
@@ -6609,6 +7115,39 @@ export declare const retentionTtlMsFromEnv: (envValue: string | undefined, defau
|
|
|
6609
7115
|
|
|
6610
7116
|
export declare type RetryDecision = 'retry' | 'noRetry';
|
|
6611
7117
|
|
|
7118
|
+
export declare interface RewriteOptions extends EachOptions {
|
|
7119
|
+
/**
|
|
7120
|
+
* Columns whose CURRENT value must still hold when the patch is written, in
|
|
7121
|
+
* addition to the columns the patch itself changes. A JSON column cannot be
|
|
7122
|
+
* compared by value on every dialect, so a rewrite of one guards on what
|
|
7123
|
+
* you name here — an `updatedAt`, a `version` — rather than on the document.
|
|
7124
|
+
*/
|
|
7125
|
+
readonly guard?: ReadonlyArray<string>;
|
|
7126
|
+
/**
|
|
7127
|
+
* How many passes over the table before contention is a failure. A pass
|
|
7128
|
+
* that rewrites nothing and contends on nothing is STABLE and ends the
|
|
7129
|
+
* rewrite; a row that changed under the migration between its read and its
|
|
7130
|
+
* write is contended and is read again on the next pass. Default 10.
|
|
7131
|
+
*/
|
|
7132
|
+
readonly maxPasses?: number;
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
export declare interface RewriteReport {
|
|
7136
|
+
readonly passes: number;
|
|
7137
|
+
readonly scanned: number;
|
|
7138
|
+
readonly rewritten: number;
|
|
7139
|
+
/** Writes lost to a concurrent change and retried on a later pass. */
|
|
7140
|
+
readonly contended: number;
|
|
7141
|
+
}
|
|
7142
|
+
|
|
7143
|
+
/** A `ctx.rewrite` that was still contended after `maxPasses`. */
|
|
7144
|
+
export declare class RewriteUnstableError extends Error {
|
|
7145
|
+
readonly table: string;
|
|
7146
|
+
readonly report: RewriteReport;
|
|
7147
|
+
readonly _tag = "RewriteUnstableError";
|
|
7148
|
+
constructor(table: string, report: RewriteReport);
|
|
7149
|
+
}
|
|
7150
|
+
|
|
6612
7151
|
export declare type Row = Readonly<Record<string, unknown>>;
|
|
6613
7152
|
|
|
6614
7153
|
declare type Row_2 = Readonly<Record<string, unknown>>;
|
|
@@ -6805,6 +7344,18 @@ export declare interface SchemaChangeSeedEvent {
|
|
|
6805
7344
|
|
|
6806
7345
|
export declare type SchemaChangeSeedHook = (event: SchemaChangeSeedEvent) => Promise<void>;
|
|
6807
7346
|
|
|
7347
|
+
/** What a schema operation on the migration context found to do. */
|
|
7348
|
+
export declare type SchemaOpOutcome = 'applied' | 'already-absent' | 'already-present';
|
|
7349
|
+
|
|
7350
|
+
/**
|
|
7351
|
+
* The full schema as the planner sees it on one side of the diff.
|
|
7352
|
+
* Both the declared side and the introspected side conform to this
|
|
7353
|
+
* shape so the diff algorithm is symmetric.
|
|
7354
|
+
*/
|
|
7355
|
+
declare interface SchemaSnapshot {
|
|
7356
|
+
readonly tables: ReadonlyArray<TableSnapshot>;
|
|
7357
|
+
}
|
|
7358
|
+
|
|
6808
7359
|
/**
|
|
6809
7360
|
* The PUBLIC structural view of a built table — name, columns, and applied
|
|
6810
7361
|
* indexes — with NONE of the fluent builder methods.
|
|
@@ -8049,6 +8600,36 @@ export declare interface TableRule {
|
|
|
8049
8600
|
readonly severity: RuleSeverity;
|
|
8050
8601
|
}
|
|
8051
8602
|
|
|
8603
|
+
declare interface TableSnapshot {
|
|
8604
|
+
readonly name: string;
|
|
8605
|
+
readonly columns: ReadonlyArray<ColumnSnapshot>;
|
|
8606
|
+
readonly indexes: ReadonlyArray<IndexSnapshot>;
|
|
8607
|
+
/**
|
|
8608
|
+
* Composite PRIMARY KEY column set (`.primaryKey(['a','b'])`). Set on the
|
|
8609
|
+
* DECLARED side only — it drives the `PRIMARY KEY (a, b)` line in the
|
|
8610
|
+
* applier's create-table emission. Absent (the default) means the single
|
|
8611
|
+
* `id()` column carries the PK inline. Introspection leaves it undefined;
|
|
8612
|
+
* the live PK is compared via the synthesised `<table>_pkey` index.
|
|
8613
|
+
*/
|
|
8614
|
+
readonly primaryKey?: ReadonlyArray<string>;
|
|
8615
|
+
/**
|
|
8616
|
+
* Approximate row count from the live DB's planner stats — used by
|
|
8617
|
+
* the migration planner to promote `safe` ops to `online-required`
|
|
8618
|
+
* when the table exceeds the configured threshold (default 50_000).
|
|
8619
|
+
* Pulled from `pg_class.reltuples` on postgres; equivalent stats
|
|
8620
|
+
* on other dialects. Absent on the declared side of the diff (no
|
|
8621
|
+
* data exists yet), present from introspection.
|
|
8622
|
+
*/
|
|
8623
|
+
readonly rowCount?: number;
|
|
8624
|
+
/**
|
|
8625
|
+
* The name this table previously had (`table(name, fields, { renamedFrom })`).
|
|
8626
|
+
* DECLARED side only — introspection has no way to know. The planner pairs it
|
|
8627
|
+
* against a live table of that name and folds what would otherwise be
|
|
8628
|
+
* DROP + CREATE into a `rename-table`.
|
|
8629
|
+
*/
|
|
8630
|
+
readonly renamedFrom?: string;
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8052
8633
|
/** A page read failed while streaming a table. Carries the underlying store
|
|
8053
8634
|
* error as `cause` so callers can inspect the transient-ness for retry. */
|
|
8054
8635
|
export declare class TableStreamError extends TableStreamError_base<{
|
|
@@ -8076,6 +8657,8 @@ export declare interface TableUnique {
|
|
|
8076
8657
|
*/
|
|
8077
8658
|
export declare const teardownBranch: (branchId: string, mechanism: BranchMechanism, executor: BranchExecutor) => Promise<ReadonlyArray<BranchStep>>;
|
|
8078
8659
|
|
|
8660
|
+
export declare const TENANT_SELECTIONS_TABLE: string;
|
|
8661
|
+
|
|
8079
8662
|
/** What a tenant-create firing knows: which namespace was just provisioned. */
|
|
8080
8663
|
export declare interface TenantCreateSeedEvent {
|
|
8081
8664
|
/** The namespace (postgres schema / mysql database / attached sqlite file)
|
|
@@ -8137,6 +8720,19 @@ export declare class TenantResidencyUnresolved extends Error {
|
|
|
8137
8720
|
constructor(message: string);
|
|
8138
8721
|
}
|
|
8139
8722
|
|
|
8723
|
+
/** The row id a subject's selection lives under: a hash of the identity, so
|
|
8724
|
+
* the key is bounded on every dialect and a second selection for one subject
|
|
8725
|
+
* is the same row, never a second one. */
|
|
8726
|
+
export declare const tenantSelectionId: (identity: TenantSelectionRowIdentity) => string;
|
|
8727
|
+
|
|
8728
|
+
/** The two fields that name a subject across credentials. Structurally the
|
|
8729
|
+
* protocol's `TenantSelectionIdentity`; typed loosely here because this
|
|
8730
|
+
* package does not depend on the protocol. */
|
|
8731
|
+
export declare interface TenantSelectionRowIdentity {
|
|
8732
|
+
readonly type: string;
|
|
8733
|
+
readonly id: string;
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8140
8736
|
export declare const text: () => ColumnBuilder<string, "text", boolean>;
|
|
8141
8737
|
|
|
8142
8738
|
export declare const timestamp: () => ColumnBuilder<Date, "timestamp", boolean>;
|
|
@@ -8191,6 +8787,21 @@ export declare interface TransactionalViewHandle {
|
|
|
8191
8787
|
commitEvents(): void;
|
|
8192
8788
|
}
|
|
8193
8789
|
|
|
8790
|
+
/**
|
|
8791
|
+
* A `transactional` migration whose body committed the transaction from
|
|
8792
|
+
* inside — DDL on the MySQL family, or an explicit COMMIT. The body's work
|
|
8793
|
+
* IS in the database and the migration IS recorded (running it again would
|
|
8794
|
+
* repeat the committed half); what failed is the guarantee the migration
|
|
8795
|
+
* asked for, and the run stops so nobody deploys on top of an assumption
|
|
8796
|
+
* that did not hold.
|
|
8797
|
+
*/
|
|
8798
|
+
export declare class TransactionBrokenByBodyError extends Error {
|
|
8799
|
+
readonly migrationId: string;
|
|
8800
|
+
readonly dialect: DialectId;
|
|
8801
|
+
readonly _tag = "TransactionBrokenByBodyError";
|
|
8802
|
+
constructor(migrationId: string, dialect: DialectId);
|
|
8803
|
+
}
|
|
8804
|
+
|
|
8194
8805
|
/**
|
|
8195
8806
|
* The `[Connection, depth]` tuple `@effect/sql` provides inside
|
|
8196
8807
|
* `withTransaction`. Every statement that must run on the transaction's OWN
|
|
@@ -8679,6 +9290,18 @@ export declare const _voltroCdcOffsetsTable: TableLike;
|
|
|
8679
9290
|
*/
|
|
8680
9291
|
export declare const _voltroDataTransfersTable: TableLike;
|
|
8681
9292
|
|
|
9293
|
+
/**
|
|
9294
|
+
* `_voltro_devices` — device identity beside `_voltro_api_keys`: a machine
|
|
9295
|
+
* caller with a tenant whose credential is short-lived and rotated. Only
|
|
9296
|
+
* hashes: the one-time pairing code, the current access token, the current
|
|
9297
|
+
* refresh token and the refresh token rotated OUT last (a replay of it revokes
|
|
9298
|
+
* the device). `generation` counts rotations. Hash columns are indexed, not
|
|
9299
|
+
* unique: they are nullable, and a UNIQUE over NULLs is the partial-unique
|
|
9300
|
+
* trap on MariaDB. Created for every sql app (empty unless `devices` is
|
|
9301
|
+
* enabled in app.config). See `@voltro/runtime`'s `DeviceService`.
|
|
9302
|
+
*/
|
|
9303
|
+
export declare const _voltroDevicesTable: SchemaTable;
|
|
9304
|
+
|
|
8682
9305
|
/**
|
|
8683
9306
|
* `_voltro_idempotency` — one row per (scope, key) for HTTP idempotency
|
|
8684
9307
|
* (the `Idempotency-Key` header on REST routes). `scope` namespaces the key
|
|
@@ -8701,15 +9324,6 @@ export declare const _voltroDataTransfersTable: TableLike;
|
|
|
8701
9324
|
*/
|
|
8702
9325
|
export declare const _voltroIdempotencyTable: SchemaTable;
|
|
8703
9326
|
|
|
8704
|
-
/**
|
|
8705
|
-
* `_voltro_kv` — the durable key-value store behind `ctx.kv` / the `Kv`
|
|
8706
|
-
* service when `KV_BACKEND=database` (the default for sql apps). One row per
|
|
8707
|
-
* key; `key` is UNIQUE and already carries any namespace the facade layered on
|
|
8708
|
-
* (e.g. a per-tenant prefix). `value` is opaque JSON; `expiresAt` drives the
|
|
8709
|
-
* optional TTL (NULL = never). Distinct from the cache: entries are NEVER
|
|
8710
|
-
* evicted for capacity — they live until deleted or their TTL lapses. Created
|
|
8711
|
-
* for every sql app (empty unless `ctx.kv` is used).
|
|
8712
|
-
*/
|
|
8713
9327
|
export declare const _voltroKvTable: SchemaTable;
|
|
8714
9328
|
|
|
8715
9329
|
/**
|
|
@@ -8795,6 +9409,19 @@ export declare const _voltroReplicaObservationsTable: SchemaTable;
|
|
|
8795
9409
|
*/
|
|
8796
9410
|
export declare const _voltroSeedsTable: SchemaTable;
|
|
8797
9411
|
|
|
9412
|
+
/**
|
|
9413
|
+
* `_voltro_tenant_selections` — the ACTIVE tenant a subject chose among the
|
|
9414
|
+
* tenants it belongs to, stored beside the subject rather than inside a
|
|
9415
|
+
* credential so it survives the credential: the next JWT from an external
|
|
9416
|
+
* IdP, the next API-key call, finds it. One row per subject identity —
|
|
9417
|
+
* `id` is `sha256(type, id)` (`tenantSelectionId` in `@voltro/runtime`), so
|
|
9418
|
+
* a `text` primary key stays bounded on every dialect. Verified against the
|
|
9419
|
+
* app's memberships on every request it applies to and cleared when the
|
|
9420
|
+
* membership is gone. Created for every sql app (empty unless `auth.memberships`
|
|
9421
|
+
* is configured and someone switches).
|
|
9422
|
+
*/
|
|
9423
|
+
export declare const _voltroTenantSelectionsTable: SchemaTable;
|
|
9424
|
+
|
|
8798
9425
|
export declare const walkLeaves: (predicate: Predicate, conjunctive?: boolean) => Generator<PredicateLeafWithContext>;
|
|
8799
9426
|
|
|
8800
9427
|
/** Three chars from the floor — at this point a typical next-column
|
|
@@ -8940,4 +9567,8 @@ export declare type WriteRecorder = (port: TxnRecorderPort, write: RecordedWrite
|
|
|
8940
9567
|
/** Are recorders suspended right here? Exported so a caller can SAY so. */
|
|
8941
9568
|
export declare const writeRecordersSuspended: () => boolean;
|
|
8942
9569
|
|
|
9570
|
+
/** Store the selection — an upsert on the derived id, so it is a replace. The
|
|
9571
|
+
* caller has verified the membership; this writes, and only writes. */
|
|
9572
|
+
export declare const writeTenantSelection: (store: DataStore, identity: TenantSelectionRowIdentity, tenantId: string) => Promise<void>;
|
|
9573
|
+
|
|
8943
9574
|
export { }
|