@voltro/database 0.41.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/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. */
@@ -2970,6 +3025,8 @@ export declare const expires: () => MixinDefinition<{
2970
3025
  */
2971
3026
  export declare const externalChangeEvent: (event: ChangeEvent, attribution?: Partial<ChangeEventMeta>) => ChangeEvent;
2972
3027
 
3028
+ export declare const extractDbCause: (err: unknown) => Record<string, unknown>;
3029
+
2973
3030
  /** The subset of `@voltro/logger`'s logger this module needs. Structural so
2974
3031
  * this package keeps its browser-safe import surface. */
2975
3032
  export declare interface FallbackLogger {
@@ -3518,6 +3575,15 @@ export declare const isFieldDecryptionError: (e: unknown) => e is FieldDecryptio
3518
3575
  /** Type-guard for the discovery walker. */
3519
3576
  export declare const isFileMigration: (value: unknown) => value is FileMigration;
3520
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
+
3521
3587
  /** True when `name` is a live table the framework / its runtime engines create
3522
3588
  * but no user schema declares — never plan a drop for it. */
3523
3589
  export declare const isFrameworkOwnedLiveTable: (name: string) => boolean;
@@ -3531,6 +3597,18 @@ export declare const isLocalFirst: (table: LocalFirstMarked) => boolean;
3531
3597
 
3532
3598
  export declare const isMigrationDefinition: (value: unknown) => value is MigrationDefinition;
3533
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
+
3534
3612
  /**
3535
3613
  * True when the connection string points at a Neon serverless-postgres host
3536
3614
  * (`*.neon.tech` / `*.neon.build`, including the `-pooler` endpoints). Neon's
@@ -3546,6 +3624,15 @@ export declare const isNull: <RowOf = Record<string, unknown>, K extends keyof R
3546
3624
 
3547
3625
  export declare const isPrimaryKeyConflictError: (err: unknown) => err is PrimaryKeyConflictError;
3548
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
+
3549
3636
  /** Is this ciphertext in the OLD raw encoding? Used by `voltro db encrypt-column`
3550
3637
  * to normalise a column without touching rows that are already current. Answers
3551
3638
  * `undefined` when the two forms coincide (a numeric column, or a value that