@voltro/database 0.40.0 → 0.42.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 +171 -0
- package/dist/{frameworkLiveTables-BS6FuivX.js → frameworkLiveTables-XPFqj8n3.js} +247 -247
- package/dist/index.d.ts +162 -0
- package/dist/index.js +815 -657
- package/dist/sql.d.ts +44 -4
- package/dist/sql.js +854 -796
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -765,6 +765,19 @@ export declare const chunkRowsForInsert: <T extends Record<string, unknown>>(row
|
|
|
765
765
|
*/
|
|
766
766
|
export declare const claimPendingAttribution: (key: string) => WriteAttribution | undefined;
|
|
767
767
|
|
|
768
|
+
/**
|
|
769
|
+
* Classify a driver failure — already flattened by `extractDbCause` — into the
|
|
770
|
+
* safe facts above, or `undefined` when it is not a constraint violation at all
|
|
771
|
+
* (a deadlock, a dead connection, a timeout, a syntax error).
|
|
772
|
+
*
|
|
773
|
+
* `operation` is the write the framework was performing. It is consulted for
|
|
774
|
+
* ONE thing: sqlite reports both FK directions as the same bare
|
|
775
|
+
* `FOREIGN KEY constraint failed` with no name and no column, so a delete that
|
|
776
|
+
* trips it is `foreignKeyInUse` and an insert is `foreignKey`. Every other
|
|
777
|
+
* dialect states the direction itself and the operation is not consulted.
|
|
778
|
+
*/
|
|
779
|
+
export declare const classifyConstraintViolation: (dbCause: Record<string, unknown>, operation?: string) => ConstraintFacts | undefined;
|
|
780
|
+
|
|
768
781
|
/** Wipe — used by tests + the dev-loop hot-reload. */
|
|
769
782
|
export declare const clearEnumRenames: () => void;
|
|
770
783
|
|
|
@@ -1865,6 +1878,48 @@ export declare interface ConnectionIdentityInput {
|
|
|
1865
1878
|
readonly database?: string | undefined;
|
|
1866
1879
|
}
|
|
1867
1880
|
|
|
1881
|
+
/**
|
|
1882
|
+
* The SAFE facts about a constraint violation: which rule, and the NAME of the
|
|
1883
|
+
* constraint / column it fired on. Never a value, and never the driver's
|
|
1884
|
+
* sentence.
|
|
1885
|
+
*
|
|
1886
|
+
* That exclusion is the whole design, and it is measured, not cautious:
|
|
1887
|
+
*
|
|
1888
|
+
* - postgres attaches `detail: "Failing row contains (t4, l1, null, null,
|
|
1889
|
+
* null)."` to a not-null and a check violation — **the entire row**, every
|
|
1890
|
+
* column, including anything a table marked `.sensitive()`.
|
|
1891
|
+
* - mysql/mariadb: `Duplicate entry 's1' for key 'tasks.slug'`.
|
|
1892
|
+
* - mssql: `The duplicate key value is (s1).` and, on truncation,
|
|
1893
|
+
* `Truncated value: '…'`.
|
|
1894
|
+
*
|
|
1895
|
+
* So "just pass the driver message through" is not a smaller version of this —
|
|
1896
|
+
* it is a different feature, one that ships row contents to whoever provoked
|
|
1897
|
+
* the error. A constraint NAME is schema, the same class of fact the framework
|
|
1898
|
+
* already puts on the wire in `TableValidationFailed.table`; a row is data.
|
|
1899
|
+
*
|
|
1900
|
+
* Everything here is captured as an anchored identifier group out of the
|
|
1901
|
+
* driver text — never a slice of it — so a regex that fails to match yields
|
|
1902
|
+
* `undefined` rather than a sentence that happens to contain a value.
|
|
1903
|
+
*/
|
|
1904
|
+
export declare interface ConstraintFacts {
|
|
1905
|
+
readonly kind: ConstraintKind;
|
|
1906
|
+
/** The constraint / index name, when the dialect names one. */
|
|
1907
|
+
readonly constraint?: string;
|
|
1908
|
+
/** The column, when the dialect names one instead of (or beside) a constraint. */
|
|
1909
|
+
readonly column?: string;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* Which integrity rule the database refused on.
|
|
1914
|
+
*
|
|
1915
|
+
* `foreignKey` and `foreignKeyInUse` are the two DIRECTIONS of one constraint
|
|
1916
|
+
* and mean opposite things to a caller — "the row you pointed at does not
|
|
1917
|
+
* exist" vs "you may not remove this row, others still point at it". A UI
|
|
1918
|
+
* branches differently on each, so they are separate members rather than one
|
|
1919
|
+
* member plus a string a caller would have to grep.
|
|
1920
|
+
*/
|
|
1921
|
+
export declare type ConstraintKind = 'foreignKey' | 'foreignKeyInUse' | 'unique' | 'notNull' | 'check';
|
|
1922
|
+
|
|
1868
1923
|
/** Case-insensitive substring match on a text column. Non-indexable —
|
|
1869
1924
|
* always evaluated as an unindexed leaf (the matcher's pre-filter only
|
|
1870
1925
|
* recognizes eq/in/range). Lowers to `ILIKE '%…%'` on Postgres. */
|
|
@@ -2343,6 +2398,46 @@ export declare const decimal: (precision: number, scale?: number) => ColumnBuild
|
|
|
2343
2398
|
*/
|
|
2344
2399
|
export declare const declareEnumRename: (enumName: string, rename: EnumValueRename) => void;
|
|
2345
2400
|
|
|
2401
|
+
/**
|
|
2402
|
+
* Read an encrypted field, in EITHER encoding.
|
|
2403
|
+
*
|
|
2404
|
+
* ── Why this reads two forms when there is only one writer ─────────────────
|
|
2405
|
+
*
|
|
2406
|
+
* Because the old form is already on somebody's production disk, and a fix that
|
|
2407
|
+
* requires rewriting it before the app works is not a fix — it is an outage with
|
|
2408
|
+
* a migration attached. "No back-compat" is about not keeping old CODE paths; a
|
|
2409
|
+
* row written last month is data, and data is read where it is.
|
|
2410
|
+
*
|
|
2411
|
+
* ── Why this is deterministic and not a heuristic ──────────────────────────
|
|
2412
|
+
*
|
|
2413
|
+
* The two forms are `encrypt(JSON.stringify(v))` and `encrypt(v)`. After
|
|
2414
|
+
* decrypting, they are told apart by what the plaintext PARSES to, against the
|
|
2415
|
+
* column's declared type:
|
|
2416
|
+
*
|
|
2417
|
+
* - parse THROWS → raw form. A JSON encoding always parses.
|
|
2418
|
+
* - parse yields a STRING → JSON form. `JSON.stringify` of any string
|
|
2419
|
+
* yields a quoted string, and a raw secret is
|
|
2420
|
+
* not quoted (see the one exception below).
|
|
2421
|
+
* - parse yields a NON-string → depends on the column:
|
|
2422
|
+
* · a TEXT-like column can only hold a string, so a number/object here is
|
|
2423
|
+
* the raw form being parsed by accident (`"12345"` → `12345`) → raw.
|
|
2424
|
+
* · a number / boolean / json column stores exactly that, so the parsed
|
|
2425
|
+
* value IS the value → JSON form.
|
|
2426
|
+
*
|
|
2427
|
+
* No branch guesses. `expected` is the column's declared type; omit it (the
|
|
2428
|
+
* `string → string` escape hatch) and the text-like rule applies, which is
|
|
2429
|
+
* correct for a caller that can only have written a string.
|
|
2430
|
+
*
|
|
2431
|
+
* ── The one case nothing can separate, stated rather than hidden ───────────
|
|
2432
|
+
*
|
|
2433
|
+
* A raw secret whose literal text is `"abc"` — quotes included as characters —
|
|
2434
|
+
* is byte-identical to the JSON encoding of `abc`. No algorithm can tell them
|
|
2435
|
+
* apart, because they are the same bytes. A token or key never looks like that;
|
|
2436
|
+
* it is written here so the next reader does not go looking for the branch that
|
|
2437
|
+
* handles it.
|
|
2438
|
+
*/
|
|
2439
|
+
export declare const decodeFieldValue: (cipher: FieldCipher, ciphertext: string, expected?: string) => unknown;
|
|
2440
|
+
|
|
2346
2441
|
export declare type DecoderDialectId = 'postgres' | 'mysql' | 'mariadb' | 'sqlite' | 'mssql' | 'turso';
|
|
2347
2442
|
|
|
2348
2443
|
/**
|
|
@@ -2753,6 +2848,35 @@ export declare interface EagerRootPlan {
|
|
|
2753
2848
|
|
|
2754
2849
|
declare type EmptyMerge = unknown;
|
|
2755
2850
|
|
|
2851
|
+
/**
|
|
2852
|
+
* THE encoding for an encrypted field. One definition, called by every writer
|
|
2853
|
+
* and every reader — the store middleware, the raw-SQL escape hatch
|
|
2854
|
+
* (`encryptField` / `decryptField` in `@voltro/runtime`), and the
|
|
2855
|
+
* `voltro db encrypt-column` backfill.
|
|
2856
|
+
*
|
|
2857
|
+
* ── Why this is one function and not three copies ──────────────────────────
|
|
2858
|
+
*
|
|
2859
|
+
* It was three. The store wrote `encrypt(JSON.stringify(v))`, the escape hatch
|
|
2860
|
+
* wrote `encrypt(v)`, and the backfill wrote `encrypt(v)`. All three produce the
|
|
2861
|
+
* `enc:v1:` envelope and NOTHING distinguishes them. A consumer wrote session
|
|
2862
|
+
* rows through the escape hatch and read them through the store, which threw
|
|
2863
|
+
* `FieldDecryptionError` blaming the key — the key was right; the ENCODING was
|
|
2864
|
+
* not. Eight of their ten session rows, and the failure looked like a key
|
|
2865
|
+
* rotation the whole time.
|
|
2866
|
+
*
|
|
2867
|
+
* The JSON form wins because it is the only one that can carry a non-string: an
|
|
2868
|
+
* `.encrypted()` column may be a number or a json column, and `encrypt(v)` for
|
|
2869
|
+
* those was already lossy. The escape hatch's `string → string` signature is
|
|
2870
|
+
* unchanged by it — a stringified string parses back to the same string.
|
|
2871
|
+
*
|
|
2872
|
+
* ── The distinction the error has to make ──────────────────────────────────
|
|
2873
|
+
*
|
|
2874
|
+
* A GCM failure and a JSON failure are different diagnoses with different
|
|
2875
|
+
* remedies, and collapsing them is what cost the consumer a day. `decodeFieldValue`
|
|
2876
|
+
* therefore decrypts and decodes as two separate steps and says which one failed.
|
|
2877
|
+
*/
|
|
2878
|
+
export declare const encodeFieldValue: (cipher: FieldCipher, value: unknown) => string;
|
|
2879
|
+
|
|
2756
2880
|
/**
|
|
2757
2881
|
* Serialize a row's json()/array() cells to JSON strings so they can be
|
|
2758
2882
|
* bound as scalar SQL parameters. Symmetric to `decodeRowsFromSchema`.
|
|
@@ -2901,6 +3025,8 @@ export declare const expires: () => MixinDefinition<{
|
|
|
2901
3025
|
*/
|
|
2902
3026
|
export declare const externalChangeEvent: (event: ChangeEvent, attribution?: Partial<ChangeEventMeta>) => ChangeEvent;
|
|
2903
3027
|
|
|
3028
|
+
export declare const extractDbCause: (err: unknown) => Record<string, unknown>;
|
|
3029
|
+
|
|
2904
3030
|
/** The subset of `@voltro/logger`'s logger this module needs. Structural so
|
|
2905
3031
|
* this package keeps its browser-safe import surface. */
|
|
2906
3032
|
export declare interface FallbackLogger {
|
|
@@ -3449,6 +3575,15 @@ export declare const isFieldDecryptionError: (e: unknown) => e is FieldDecryptio
|
|
|
3449
3575
|
/** Type-guard for the discovery walker. */
|
|
3450
3576
|
export declare const isFileMigration: (value: unknown) => value is FileMigration;
|
|
3451
3577
|
|
|
3578
|
+
/**
|
|
3579
|
+
* Foreign-key violation, either direction, across every dialect. Derived from
|
|
3580
|
+
* `classifyConstraintViolation` rather than re-listing the codes: this
|
|
3581
|
+
* predicate and the classifier disagreeing is invisible until a tenant-FK
|
|
3582
|
+
* failure quietly stops being typed, and mssql already spent a release in
|
|
3583
|
+
* exactly that state (547 was read off `.code`, where it never appears).
|
|
3584
|
+
*/
|
|
3585
|
+
export declare const isForeignKeyViolation: (dbCause: Record<string, unknown>) => boolean;
|
|
3586
|
+
|
|
3452
3587
|
/** True when `name` is a live table the framework / its runtime engines create
|
|
3453
3588
|
* but no user schema declares — never plan a drop for it. */
|
|
3454
3589
|
export declare const isFrameworkOwnedLiveTable: (name: string) => boolean;
|
|
@@ -3462,6 +3597,18 @@ export declare const isLocalFirst: (table: LocalFirstMarked) => boolean;
|
|
|
3462
3597
|
|
|
3463
3598
|
export declare const isMigrationDefinition: (value: unknown) => value is MigrationDefinition;
|
|
3464
3599
|
|
|
3600
|
+
/**
|
|
3601
|
+
* MySQL and MariaDB. They share a driver, a quoting style and most of a
|
|
3602
|
+
* grammar — and they are NOT interchangeable in DDL.
|
|
3603
|
+
*
|
|
3604
|
+
* The one that has already cost us: **MySQL/InnoDB silently discards a
|
|
3605
|
+
* column-inline `REFERENCES` clause** (parsed, thrown away, no warning, CREATE
|
|
3606
|
+
* succeeds), while MariaDB honours it and creates the constraint. Measured on
|
|
3607
|
+
* mysql 8.4 and mariadb 11. Anything referential that this family emits has to
|
|
3608
|
+
* be table-level to work on both.
|
|
3609
|
+
*/
|
|
3610
|
+
export declare const isMysqlFamily: (dialect: DialectId) => boolean;
|
|
3611
|
+
|
|
3465
3612
|
/**
|
|
3466
3613
|
* True when the connection string points at a Neon serverless-postgres host
|
|
3467
3614
|
* (`*.neon.tech` / `*.neon.build`, including the `-pooler` endpoints). Neon's
|
|
@@ -3477,6 +3624,21 @@ export declare const isNull: <RowOf = Record<string, unknown>, K extends keyof R
|
|
|
3477
3624
|
|
|
3478
3625
|
export declare const isPrimaryKeyConflictError: (err: unknown) => err is PrimaryKeyConflictError;
|
|
3479
3626
|
|
|
3627
|
+
/** A query cancelled for exceeding the `statementTimeoutMs` deadline, across
|
|
3628
|
+
* dialects: pg `57014` (query_canceled — what `statement_timeout` raises),
|
|
3629
|
+
* MySQL `3024` (ER_QUERY_TIMEOUT), MariaDB `1969` (ER_STATEMENT_TIMEOUT), mssql
|
|
3630
|
+
* `ETIMEOUT` (tedious request timeout), sqlite `SQLITE_INTERRUPT`. NOT a
|
|
3631
|
+
* transient error — a re-run just repeats the runaway, so it must not retry
|
|
3632
|
+
* (`servePipeline`'s transient set deliberately excludes it). Lets a consumer /
|
|
3633
|
+
* observability layer name the failure instead of reading an opaque SqlError. */
|
|
3634
|
+
export declare const isQueryTimeout: (dbCause: Record<string, unknown>) => boolean;
|
|
3635
|
+
|
|
3636
|
+
/** Is this ciphertext in the OLD raw encoding? Used by `voltro db encrypt-column`
|
|
3637
|
+
* to normalise a column without touching rows that are already current. Answers
|
|
3638
|
+
* `undefined` when the two forms coincide (a numeric column, or a value that
|
|
3639
|
+
* parses to itself) — nothing to do either way. */
|
|
3640
|
+
export declare const isRawEncoding: (cipher: FieldCipher, ciphertext: string, expected?: string) => boolean | undefined;
|
|
3641
|
+
|
|
3480
3642
|
/** Can THIS deployment serve the region? */
|
|
3481
3643
|
export declare const isRegionServable: (region: string, config: ResidencyConfig) => boolean;
|
|
3482
3644
|
|