@vibeorm/sql 2.4.0 → 3.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ast.d.ts +334 -11
  3. package/dist/codecs.d.ts +48 -0
  4. package/dist/dialect.d.ts +44 -0
  5. package/dist/dialects/external-dependents.d.ts +28 -0
  6. package/dist/dialects/external-ownership.d.ts +8 -0
  7. package/dist/dialects/migration-shadow.d.ts +5 -0
  8. package/dist/dialects/postgres-index-keys.d.ts +10 -0
  9. package/dist/dialects/postgres-physical-catalog.d.ts +17 -0
  10. package/dist/dialects/sqlite-fts.d.ts +28 -0
  11. package/dist/exclusion.d.ts +2 -0
  12. package/dist/index-columns.d.ts +15 -0
  13. package/dist/index.d.ts +20 -5
  14. package/dist/index.js +1023 -273
  15. package/dist/index.js.map +28 -20
  16. package/dist/json-list-parameter.d.ts +37 -0
  17. package/dist/native-enum-parameter.d.ts +17 -0
  18. package/dist/pg-array.d.ts +13 -0
  19. package/dist/render.d.ts +11 -1
  20. package/package.json +5 -3
  21. package/dist/ast.d.ts.map +0 -1
  22. package/dist/capabilities.d.ts.map +0 -1
  23. package/dist/codecs.d.ts.map +0 -1
  24. package/dist/composable.d.ts.map +0 -1
  25. package/dist/dialect.d.ts.map +0 -1
  26. package/dist/dialects/constraint-owned-index.d.ts.map +0 -1
  27. package/dist/dialects/diagnostics.d.ts.map +0 -1
  28. package/dist/dialects/migration-history.d.ts.map +0 -1
  29. package/dist/dialects/migration-inventory.d.ts.map +0 -1
  30. package/dist/dialects/migration-objects.d.ts.map +0 -1
  31. package/dist/dialects/migration-replay-coverage.d.ts.map +0 -1
  32. package/dist/dialects/migration-shadow.d.ts.map +0 -1
  33. package/dist/dialects/migration-visibility.d.ts.map +0 -1
  34. package/dist/dialects/module-dependency.d.ts.map +0 -1
  35. package/dist/dialects/mysql-check-probe.d.ts.map +0 -1
  36. package/dist/dialects/mysql.d.ts.map +0 -1
  37. package/dist/dialects/postgres-default-identity.d.ts.map +0 -1
  38. package/dist/dialects/postgres-diagnostics.d.ts.map +0 -1
  39. package/dist/dialects/postgres-migration.d.ts.map +0 -1
  40. package/dist/dialects/postgres-rls-readiness.d.ts.map +0 -1
  41. package/dist/dialects/postgres-rls.d.ts.map +0 -1
  42. package/dist/dialects/postgres-type-identity.d.ts.map +0 -1
  43. package/dist/dialects/postgres.d.ts.map +0 -1
  44. package/dist/dialects/registry.d.ts.map +0 -1
  45. package/dist/dialects/sqlite-constraints.d.ts.map +0 -1
  46. package/dist/dialects/sqlite-session.d.ts.map +0 -1
  47. package/dist/dialects/sqlite-table-definition.d.ts.map +0 -1
  48. package/dist/dialects/sqlite.d.ts.map +0 -1
  49. package/dist/exclusion.d.ts.map +0 -1
  50. package/dist/identifiers.d.ts.map +0 -1
  51. package/dist/index.d.ts.map +0 -1
  52. package/dist/json-transport.d.ts.map +0 -1
  53. package/dist/like.d.ts.map +0 -1
  54. package/dist/locks.d.ts.map +0 -1
  55. package/dist/module-journal.d.ts.map +0 -1
  56. package/dist/params.d.ts.map +0 -1
  57. package/dist/pg-array.d.ts.map +0 -1
  58. package/dist/predicate.d.ts.map +0 -1
  59. package/dist/render.d.ts.map +0 -1
  60. package/dist/result-decoder.d.ts.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VibeORM contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/ast.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { NativeEnumArrayParameter } from "./native-enum-parameter.ts";
2
+ import type { JsonListParameter } from "./json-list-parameter.ts";
1
3
  import type { SqlFragment } from "./composable.ts";
2
4
  import type { RlsReadinessQuery } from "./dialects/postgres-rls-readiness.ts";
3
5
  /**
@@ -14,11 +16,15 @@ export type ColRef = {
14
16
  readonly table?: string;
15
17
  readonly name: string;
16
18
  /**
17
- * Projection-only wire transform, postgres lateral joins only (SQL review
18
- * B3): raw `to_jsonb` would ship BigInt/Decimal as float64 JSON numbers and
19
- * Bytes as `\x` hex text, so the subselect casts instead — `castText`
20
- * renders `"col"::text AS "col"`, `encodeBase64` renders
21
- * `encode("col", 'base64') AS "col"`. Never set on filter/order refs.
19
+ * Projection-only wire transform, postgres only. `castText` renders
20
+ * `"col"::text AS "col"`, `encodeBase64` renders
21
+ * `encode("col", 'base64') AS "col"`. Two users: the lateral subselect (SQL
22
+ * review B3 — raw `to_jsonb` would ship BigInt/Decimal as float64 JSON
23
+ * numbers and Bytes as `\x` hex text), and every row projection of a
24
+ * `Float[]`/`Json[]` column (bun:sql's array parser refuses float arrays
25
+ * printed with an exponent and json arrays holding integers beyond int32;
26
+ * the runtime parses the array literal text itself). Never set on
27
+ * filter/order refs.
22
28
  */
23
29
  readonly transform?: "castText" | "encodeBase64";
24
30
  /**
@@ -29,6 +35,14 @@ export type ColRef = {
29
35
  * storage and read-back stay the exact text.
30
36
  */
31
37
  readonly castReal?: boolean;
38
+ /**
39
+ * Order/distinct/compare cast, postgres `json` columns only (`Json @db.Json`,
40
+ * release-3.0 D3): `json` has no ordering or equality operator, so ORDER BY,
41
+ * DISTINCT ON and comparison sites render `"col"::jsonb` (`"col"::jsonb[]`
42
+ * for a `json[]` list column) — the order and distinctness of a jsonb
43
+ * column. Never set on projections, so read-back keeps the stored text.
44
+ */
45
+ readonly castJsonb?: "jsonb" | "jsonb[]";
32
46
  /**
33
47
  * Projection alias: renders `ref AS "alias"`. PROJECTION-ONLY — filter,
34
48
  * order and correlation refs never set it (the renderer ignores it outside
@@ -39,6 +53,8 @@ export type ColRef = {
39
53
  };
40
54
  export type TableRef = {
41
55
  readonly name: string;
56
+ /** Explicit fixed PostgreSQL placement; absence preserves existing SQL. */
57
+ readonly namespace?: string;
42
58
  readonly alias?: string;
43
59
  };
44
60
  /** Aggregate functions applied to one column. */
@@ -90,6 +106,11 @@ export type SqlExpr = {
90
106
  readonly column: ColRef;
91
107
  readonly values: readonly unknown[];
92
108
  readonly negate?: boolean;
109
+ /**
110
+ * The column holds a time of day (`@db.Time(n)` / `@db.Timetz(n)`): the
111
+ * dialect renders the membership per `SqlDialect.timeOfDayInStrategy`.
112
+ */
113
+ readonly timeOfDay?: boolean;
93
114
  }
94
115
  /**
95
116
  * ORDERED-TUPLE membership: `("a", "b") IN ((?, ?), (?, ?))`. The composite
@@ -180,13 +201,13 @@ export type SqlExpr = {
180
201
  | {
181
202
  readonly kind: "arrayContainsAll";
182
203
  readonly column: ColRef;
183
- readonly values: readonly unknown[];
204
+ readonly values: readonly unknown[] | string | NativeEnumArrayParameter | JsonListParameter;
184
205
  }
185
206
  /** Scalar-list overlap: `"col" && $n` (one array param) — Prisma's `hasSome`. Postgres only. */
186
207
  | {
187
208
  readonly kind: "arrayOverlaps";
188
209
  readonly column: ColRef;
189
- readonly values: readonly unknown[];
210
+ readonly values: readonly unknown[] | string | NativeEnumArrayParameter | JsonListParameter;
190
211
  }
191
212
  /**
192
213
  * Scalar-list emptiness via `array_length("col", 1) IS [NOT] NULL` — the v1
@@ -213,6 +234,15 @@ export type SqlExpr = {
213
234
  readonly value: unknown;
214
235
  readonly insensitive?: boolean;
215
236
  readonly negate?: boolean;
237
+ /**
238
+ * The column's postgres type: absent (or `jsonb`) for the default
239
+ * `jsonb`, `json` for `Json @db.Json`. Postgres defines `=`, `<>` and
240
+ * `@>` for jsonb only, so on a `json` column every op casts the
241
+ * EXTRACTED value to jsonb (the string ops then read it as text), which
242
+ * gives a jsonb column's answer. Sqlite and mysql have one JSON type
243
+ * and ignore it.
244
+ */
245
+ readonly storage?: "jsonb" | "json";
216
246
  }
217
247
  /**
218
248
  * A bound extension fragment: literal text, bound parameters and quoted
@@ -366,6 +396,24 @@ export type LateralJoin = {
366
396
  /** The correlated child select — may reference outer columns via table-qualified refs. */
367
397
  readonly select: SelectStatement;
368
398
  };
399
+ /**
400
+ * One correlated scalar `COUNT(*)` projected into a select list:
401
+ * `(SELECT COUNT(*) FROM … WHERE <correlation>) AS "alias"` — the relation
402
+ * `_count` of the round-trip campaign's R-03b, which saves the separate grouped
403
+ * count statement. Portable: postgres, sqlite and mysql all evaluate a
404
+ * correlated scalar subquery once per output row; the runtime wraps a paged
405
+ * root in a derived table first, so the subqueries run only for the page.
406
+ * The select must be ONE ungrouped `COUNT(*)` (`columns: []`, `selectExpr`
407
+ * countAll, no grouping, paging, distinct or lock) — anything else is refused
408
+ * at render. Its parameters join the statement's single sequence in text order
409
+ * (the select list renders before FROM).
410
+ */
411
+ export type CountSubquery = {
412
+ /** Output column the count is exposed under (`__vibe_count_0`). */
413
+ readonly alias: string;
414
+ /** The correlated count select — references outer columns via table-qualified refs. */
415
+ readonly select: SelectStatement;
416
+ };
369
417
  /**
370
418
  * One `INNER JOIN "table" AS alias ON left = right [AND …]` clause — plain
371
419
  * equality joins only, portable across all dialects. Added for the
@@ -381,6 +429,75 @@ export type InnerJoin = {
381
429
  readonly right: ColRef;
382
430
  }[];
383
431
  };
432
+ /**
433
+ * How one member of a {@link KeyTableJoin} tuple binds inside the derived
434
+ * table. `value` binds the parameter as it is: it stays COERCIBLE, so a text
435
+ * column's OWN collation decides `=` — the equality `col IN (?, …)` uses.
436
+ * `integer` and `decimal` cast the parameter into the column's numeric family:
437
+ * BigInt and Decimal values travel as strings, and a derived-table string
438
+ * compared with a numeric column goes through a double (measured on mysql:
439
+ * `9007199254740993` matched `9007199254740992`).
440
+ *
441
+ * `integer` renders `CAST(? AS SIGNED)`. `decimal` renders
442
+ * `CAST(? AS DECIMAL(65, scale))` with the tested column's own scale: 65 is
443
+ * MySQL's maximum precision, so the cast holds every value a
444
+ * `DECIMAL(p, scale)` column can hold. A fixed `DECIMAL(65,30)` held only 35
445
+ * integer digits and clipped a `Decimal(65, 0)` key onto its maximum, which
446
+ * matched nothing (review finding 1); the column's own precision would clip a
447
+ * wider value onto the column's maximum and pair it with a row it does not
448
+ * equal (measured: `CAST('100000' AS DECIMAL(5,0))` is `99999`). `scale` is
449
+ * 0–30, MySQL's range.
450
+ *
451
+ * `citext` (postgres only) renders `CAST($n AS citext)`: a postgres `citext`
452
+ * key compares case-insensitively, and a bare VALUES parameter would be
453
+ * `text`, whose `=` against a citext column is the case-sensitive text
454
+ * operator. A VALUES cell has no column to infer a type from, so every member
455
+ * of a postgres key table binds typed: `citext`, `text` (`CAST($n AS text)`,
456
+ * a text or varchar column) or `integer` (`CAST($n AS bigint)`, exact against
457
+ * any integer column) — any other member refuses on postgres. `text` is
458
+ * postgres-only; mysql binds text members as `value`.
459
+ */
460
+ export type KeyTableMember = {
461
+ readonly kind: "value";
462
+ } | {
463
+ readonly kind: "integer";
464
+ } | {
465
+ readonly kind: "decimal";
466
+ readonly scale: number;
467
+ } | {
468
+ readonly kind: "citext";
469
+ } | {
470
+ readonly kind: "text";
471
+ };
472
+ /**
473
+ * The batch's key tuples as a derived table, each tagged with its ORDINAL
474
+ * (row `i` carries `i`), INNER JOINed on the tested columns — mysql:
475
+ * `INNER JOIN (VALUES ROW(?, 0), ROW(?, 1)) AS \`alias\` (\`__vibe_k0\`, \`ordinal\`)
476
+ * ON col = \`alias\`.\`__vibe_k0\``; postgres only over `citext` members
477
+ * (`(VALUES (CAST($1 AS citext), 0), …)`, release 3.0 nested writes).
478
+ * Member columns are named `__vibe_k<i>` (a reserved prefix, so unqualified
479
+ * child columns never collide with them).
480
+ *
481
+ * Why it exists (EPIC R): a relation load used to stitch children to parents
482
+ * by comparing decoded keys in JavaScript, while mysql's collations equate
483
+ * spellings (`acme` = `Acme` = `ACME`, `cafe` = `Café`, `x ` = `x`) that
484
+ * JavaScript keeps apart — the foreign key and the `IN` filter accepted the
485
+ * child, the stitch dropped it. Joined this way the DATABASE pairs every child
486
+ * row with every batch key it considers equal, and the projected ordinal names
487
+ * the key; the join is also the membership filter.
488
+ */
489
+ export type KeyTableJoin = {
490
+ /** Derived-table alias (`__vibe_keys`); the ordinal projects as `alias.ordinalAlias`. */
491
+ readonly alias: string;
492
+ /** The statement's columns, compared member by member (`=`), in tuple order. */
493
+ readonly columns: readonly ColRef[];
494
+ /** How each member binds, in tuple order — see {@link KeyTableMember}. */
495
+ readonly members: readonly KeyTableMember[];
496
+ /** Encoded key tuples; never empty, each as long as `columns`. */
497
+ readonly rows: readonly (readonly unknown[])[];
498
+ /** Output name of the ordinal column. */
499
+ readonly ordinalAlias: string;
500
+ };
384
501
  /**
385
502
  * A `ROW_NUMBER() OVER (PARTITION BY … [ORDER BY …]) AS "alias"` projection,
386
503
  * appended after `columns` — the distinct emulation on dialects without
@@ -410,6 +527,8 @@ export type SelectStatement = {
410
527
  * combined with `selectExpr`.
411
528
  */
412
529
  readonly aggregates?: readonly AggSelect[];
530
+ /** Correlated `COUNT(*)` subqueries, rendered after `aggregates` — see {@link CountSubquery}. */
531
+ readonly countSubqueries?: readonly CountSubquery[];
413
532
  /**
414
533
  * `SELECT DISTINCT ON (cols)` — postgres only (`capabilities.distinctOn`;
415
534
  * other dialects refuse at render time and the runtime emulates client-side
@@ -427,6 +546,8 @@ export type SelectStatement = {
427
546
  readonly lateralJoins?: readonly LateralJoin[];
428
547
  /** Plain equality INNER JOINs — portable, see {@link InnerJoin}. */
429
548
  readonly innerJoins?: readonly InnerJoin[];
549
+ /** Key-table join, rendered after `innerJoins` — mysql only, see {@link KeyTableJoin}. */
550
+ readonly keyTable?: KeyTableJoin;
430
551
  /** ROW_NUMBER window projection — see {@link RowNumberProjection}. */
431
552
  readonly rowNumber?: RowNumberProjection;
432
553
  readonly where?: SqlExpr;
@@ -479,14 +600,21 @@ export declare function isSqlDefaultCell(value: unknown): boolean;
479
600
  * Sqlite/mysql render the bare placeholder — their Json transport is already
480
601
  * plain text. The COLLECTED parameter is the plain text either way: this
481
602
  * wrapper never crosses the wire.
603
+ *
604
+ * `storage: "json"` marks a postgres `json` column (`Json @db.Json`): the cell
605
+ * renders `$n::text::json`, so the column stores the text as written (key
606
+ * order and spelling kept) instead of jsonb's normalized text (release-3.0
607
+ * D3). Absent = jsonb; dialects without a param cast ignore it.
482
608
  */
483
609
  export type JsonParamCell = {
484
610
  readonly __jsonParam: true;
485
611
  readonly text: string;
612
+ readonly storage?: "json";
486
613
  };
487
614
  /** Wrap a Json column's encoded (stringified) parameter for cast-site rendering. */
488
615
  export declare function jsonParamCell(params: {
489
616
  text: string;
617
+ storage?: "json";
490
618
  }): JsonParamCell;
491
619
  /** Is this VALUES/SET cell a {@link JsonParamCell}? */
492
620
  export declare function isJsonParamCell(value: unknown): value is JsonParamCell;
@@ -508,6 +636,17 @@ export declare function excludedRef(params: {
508
636
  }): ExcludedRef;
509
637
  /** Is this SET cell an {@link ExcludedRef}? */
510
638
  export declare function isExcludedRef(value: unknown): value is ExcludedRef;
639
+ /**
640
+ * One RETURNING column: a bare column name renders `"col"`; the object form
641
+ * renders `"col"::text AS "col"` (postgres only) — the `Float[]`/`Json[]`
642
+ * projection rule {@link ColRef.transform} documents, applied to RETURNING.
643
+ */
644
+ export type ReturningColumn = string | {
645
+ readonly name: string;
646
+ readonly castText: true;
647
+ };
648
+ /** The column name a {@link ReturningColumn} exposes in the result row. */
649
+ export declare function returningColumnName(column: ReturningColumn): string;
511
650
  export type InsertStatement = {
512
651
  readonly kind: "insert";
513
652
  readonly table: TableRef;
@@ -517,7 +656,7 @@ export type InsertStatement = {
517
656
  /** Upsert clause: ON CONFLICT (pg/sqlite) or ON DUPLICATE KEY (mysql). */
518
657
  readonly onConflict?: OnConflictClause;
519
658
  /** Rendered as RETURNING where the dialect supports it, else omitted+flagged. */
520
- readonly returning?: readonly string[];
659
+ readonly returning?: readonly ReturningColumn[];
521
660
  };
522
661
  /** Conflicting rows are updated with these column values. */
523
662
  export type OnConflictUpdate = {
@@ -574,13 +713,13 @@ export type UpdateStatement = {
574
713
  /** Column → parameter value, or an atomic {@link SetOperation}. */
575
714
  readonly set: Readonly<Record<string, unknown>>;
576
715
  readonly where?: SqlExpr;
577
- readonly returning?: readonly string[];
716
+ readonly returning?: readonly ReturningColumn[];
578
717
  };
579
718
  export type DeleteStatement = {
580
719
  readonly kind: "delete";
581
720
  readonly table: TableRef;
582
721
  readonly where?: SqlExpr;
583
- readonly returning?: readonly string[];
722
+ readonly returning?: readonly ReturningColumn[];
584
723
  };
585
724
  /** Native RLS DDL, kept distinct from ordinary row-query expressions. */
586
725
  export type RlsEnableStatement = {
@@ -660,7 +799,191 @@ export type AdvisoryLockStatement = {
660
799
  };
661
800
  /** The column an advisory try-lock projects its verdict into. */
662
801
  export declare const ADVISORY_LOCK_RESULT_COLUMN: string;
663
- export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement;
802
+ /**
803
+ * A cell of an {@link InsertSelectStatement} that reads a column of an EARLIER
804
+ * chain step: `"step"."column"`. A column reference, not a parameter — the
805
+ * value comes from the step's rows inside the same statement (a child row
806
+ * taking its parent's key from the parent step, round-trip campaign EPIC F).
807
+ * The step must be named in the insert's `from` list.
808
+ */
809
+ export type StepColumnRef = {
810
+ readonly __stepColumn: true;
811
+ readonly step: string;
812
+ readonly column: string;
813
+ };
814
+ /** Reference `column` of the earlier chain step `step` from an INSERT … SELECT cell. */
815
+ export declare function stepColumnRef(params: {
816
+ step: string;
817
+ column: string;
818
+ }): StepColumnRef;
819
+ /** Is this INSERT … SELECT cell a {@link StepColumnRef}? */
820
+ export declare function isStepColumnRef(value: unknown): value is StepColumnRef;
821
+ /**
822
+ * `INSERT INTO t (cols) SELECT <cells> [FROM "step", …] [WHERE <expr>]
823
+ * [RETURNING …]` — an INSERT whose ONE row is a SELECT list instead of a
824
+ * VALUES row, so a WHERE can veto the write inside the statement and cells can
825
+ * read earlier steps. Valid ONLY as a {@link ChainStep} statement.
826
+ *
827
+ * Cells take the same forms as a VALUES cell — a codec-encoded value, a
828
+ * {@link JsonParamCell}, a {@link DbNowMarker} — plus {@link StepColumnRef}.
829
+ * The {@link SQL_DEFAULT_CELL} is refused: `DEFAULT` is not allowed in a SELECT
830
+ * list; an omitted column takes its default by being absent from `columns`.
831
+ *
832
+ * Parameter typing (O-03, probed on PostgreSQL): a bare parameter in this
833
+ * one-row SELECT list takes the TARGET column's type, exactly as in VALUES.
834
+ */
835
+ export type InsertSelectStatement = {
836
+ readonly kind: "insertSelect";
837
+ readonly table: TableRef;
838
+ readonly columns: readonly string[];
839
+ /** One cell per column, aligned to `columns` — deterministic parameter order. */
840
+ readonly cells: readonly unknown[];
841
+ /** Earlier chain steps the SELECT reads FROM, in order (absent: a one-row SELECT without FROM). */
842
+ readonly from?: readonly string[];
843
+ /**
844
+ * Several source rows (round-trip campaign EPIC F): the SELECT reads them
845
+ * from a derived table `alias`, joined after the `from` steps, and the
846
+ * `cells` read its columns through `stepColumnRef({ step: alias, column })`.
847
+ * See {@link ChainRowSource} for how the rows are typed.
848
+ */
849
+ readonly source?: ChainRowSource;
850
+ readonly where?: SqlExpr;
851
+ readonly returning?: readonly ReturningColumn[];
852
+ };
853
+ /**
854
+ * Rows an {@link InsertSelectStatement} inserts, as ONE derived table:
855
+ * `(SELECT <cols> FROM (VALUES (<typed NULL row>), ($1, $2), …) AS "alias"
856
+ * (<cols>) OFFSET 1) AS "alias"`.
857
+ *
858
+ * Why the typed NULL row (review O-03): a bare parameter outside the INSERT's
859
+ * own VALUES has no type, and PostgreSQL resolves an all-parameter column of a
860
+ * VALUES list or UNION in FROM as `text` ("column is of type integer but
861
+ * expression is of type text"). The first row is one `(SELECT "col" FROM
862
+ * <typedBy> WHERE FALSE)` per column — a NULL of the TARGET column's exact
863
+ * type (enums, `numeric(p,s)`, `varchar(n)`, domains and arrays included) —
864
+ * so every parameter below takes that type, with no cast map anywhere;
865
+ * `OFFSET 1` drops the row. (A `UNION ALL` of one-row SELECTs types the same
866
+ * way but costs ~10× the planning time at 1000 rows — EPIC F shape gate.)
867
+ *
868
+ * Cells take the VALUES forms: a codec-encoded value, a {@link JsonParamCell}
869
+ * or a {@link DbNowMarker}; the DEFAULT cell is refused (a homogeneous row set
870
+ * omits the column instead). A cell carrying its OWN type (the Json cast, the
871
+ * clock) must agree with the column's type under VALUES' common-type rules,
872
+ * which are stricter than INSERT's assignment casts — the builder keeps such
873
+ * columns out (EPIC F eligibility). Rows are read in list order, which is the
874
+ * order the INSERT consumes them.
875
+ */
876
+ export type ChainRowSource = {
877
+ readonly alias: string;
878
+ /** The table whose columns type the rows — normally the INSERT's own table. */
879
+ readonly typedBy: TableRef;
880
+ readonly columns: readonly string[];
881
+ /** At least one row, each aligned to `columns`. */
882
+ readonly rows: readonly (readonly unknown[])[];
883
+ };
884
+ /** What one chain step may be: a read, or a data-modifying statement with RETURNING. */
885
+ export type ChainStepStatement = SelectStatement | InsertStatement | InsertSelectStatement | UpdateStatement | DeleteStatement;
886
+ /**
887
+ * One named step: rendered `"name" AS (<statement>)`. Later steps and the
888
+ * result read it as a table (`FROM "name"`, `EXISTS (SELECT 1 FROM "name" …)`,
889
+ * a {@link StepColumnRef}). A data-modifying step contributes the rows its
890
+ * RETURNING list names.
891
+ */
892
+ export type ChainStep = {
893
+ readonly name: string;
894
+ readonly statement: ChainStepStatement;
895
+ };
896
+ /**
897
+ * The chain's final SELECT: the rows of `rowsFrom` (in that order, `UNION
898
+ * ALL`), each projecting `columns`.
899
+ *
900
+ * `verdict` adds one column `alias` = `expr` to every row, AND guarantees a
901
+ * row: when the `rowsFrom` steps return nothing, the result is exactly ONE
902
+ * verdict row whose `columns` are all NULL. A caller recognizes it by NULL in
903
+ * a column the steps never return as NULL (a primary key). Accounting
904
+ * consequence: such a statement returns one row per written row OR one
905
+ * verdict row — a caller that counts returned rows as row changes must undo a
906
+ * verdict-row outcome (the upsert fold always rolls it back).
907
+ */
908
+ export type ChainResult = {
909
+ readonly rowsFrom: readonly string[];
910
+ readonly columns: readonly string[];
911
+ readonly verdict?: {
912
+ readonly alias: string;
913
+ readonly expr: SqlExpr;
914
+ };
915
+ /**
916
+ * In-statement vetoes (round-trip campaign EPIC F), checked in order: when
917
+ * `step` returned no row (fewer than `minRows`, EPIC W), the STATEMENT FAILS — so the database undoes every
918
+ * effect of every step, trigger writes included — with an error whose
919
+ * message carries `__vibe_veto:<token>:<nonce>:` ({@link CHAIN_VETO_PREFIX},
920
+ * {@link chainVetoToken}). Requires `tally`: the veto is the tally column's
921
+ * guard, so it is evaluated exactly once, on the one guaranteed result row.
922
+ */
923
+ readonly veto?: readonly ChainVeto[];
924
+ /**
925
+ * One column `alias` on every result row: the number of rows the listed
926
+ * data-modifying steps returned (their RETURNING rows = their direct
927
+ * effects). Guarantees one row like `verdict`. Row-change accounting reads
928
+ * it instead of counting result rows (effect `writeTally`).
929
+ */
930
+ readonly tally?: ChainTally;
931
+ };
932
+ /**
933
+ * Fail the statement when `step` returned no row; `token` names the veto in the
934
+ * error. `nonce` is bound as a PARAMETER into the marker (never SQL text): a
935
+ * per-call random value the caller requires back in the error, so a user value
936
+ * that merely looks like a marker is never read as a veto.
937
+ *
938
+ * `minRows` (EPIC W) makes the veto count-sensitive: the statement fails when
939
+ * `step` returned FEWER rows than that (a multi-row INSERT a trigger shortened).
940
+ * Omitted or 1 = "no row". A positive safe integer, rendered as SQL text.
941
+ */
942
+ export type ChainVeto = {
943
+ readonly step: string;
944
+ readonly token: string;
945
+ readonly nonce: string;
946
+ readonly minRows?: number;
947
+ };
948
+ /** Count the RETURNING rows of `steps` into the result column `alias`. */
949
+ export type ChainTally = {
950
+ readonly alias: string;
951
+ readonly steps: readonly string[];
952
+ };
953
+ /** Marker every chain veto error message carries, followed by `<token>:<nonce>:`. */
954
+ export declare const CHAIN_VETO_PREFIX: string;
955
+ /** Allowed veto tokens: lower-case words — they are rendered into SQL text. */
956
+ export declare const CHAIN_VETO_TOKEN_PATTERN: RegExp;
957
+ /**
958
+ * The veto token a database error message carries for this `nonce`, or
959
+ * `undefined`. Matches the marker anywhere in the text, so a localized server
960
+ * message (`lc_messages`) still yields it: the marker is the invalid VALUE the
961
+ * message quotes. A marker without the caller's nonce is not a veto.
962
+ */
963
+ export declare function chainVetoToken(params: {
964
+ message: string;
965
+ nonce: string;
966
+ }): string | undefined;
967
+ /**
968
+ * ONE statement made of an ordered chain of named steps — reads (a locked
969
+ * probe) and data-modifying statements with RETURNING — ending in a final
970
+ * SELECT over them (round-trip campaign EPIC 4, reused by EPIC F). Renders as
971
+ * `WITH "a" AS (…), "b" AS (…) SELECT …` on a dialect whose
972
+ * `statementChain` strategy names the form (postgres); every other dialect
973
+ * refuses at render time (`VIBE_UNSUPPORTED_CAPABILITY`) — callers gate on
974
+ * the strategy first and keep their multi-statement path there.
975
+ *
976
+ * PostgreSQL semantics the builder relies on: every step sees ONE snapshot
977
+ * (a step never sees another step's writes — only its RETURNING rows), steps
978
+ * that reference a step run after it, and the statement is atomic: an error in
979
+ * any step undoes every step's effects, trigger writes included.
980
+ */
981
+ export type ChainStatement = {
982
+ readonly kind: "chain";
983
+ readonly steps: readonly ChainStep[];
984
+ readonly result: ChainResult;
985
+ };
986
+ export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | ChainStatement;
664
987
  /** Readiness statements remain distinct from application queries. */
665
988
  export type RlsReadinessStatement = {
666
989
  readonly kind: "rlsReadiness";
package/dist/codecs.d.ts CHANGED
@@ -113,6 +113,54 @@ export declare function decodeIsNoop(params: {
113
113
  export declare function parseDateTimeText(params: {
114
114
  text: string;
115
115
  }): Date | undefined;
116
+ /**
117
+ * The time-of-day codec for a DateTime field's native type, or `undefined`
118
+ * when the native type is not `time`/`timetz` (the DateTime table row
119
+ * applies). The one native-type rule is `timeOfDayType` in @vibeorm/schema,
120
+ * shared with the dialect refusal in schema validation.
121
+ */
122
+ export declare function timeOfDayCodec(params: {
123
+ nativeType: string | undefined;
124
+ }): ScalarCodec | undefined;
125
+ /**
126
+ * The SHORTEST decimal that round-trips through float32 to the same value as
127
+ * `value` — what postgres itself prints for a `real` (`0.1`, `3.4028235e+38`).
128
+ *
129
+ * A driver that reads a `real` in BINARY format (bun:sql with named
130
+ * statements) hands back the float32 widened to float64:
131
+ * `0.10000000149011612` for a stored `0.1`. Text-format drivers already give
132
+ * the short form, and for those this is the identity — the shortest form of a
133
+ * short value is itself — so it is safe on every driver. Smallest `p` in 1..9
134
+ * with `fround(toPrecision(p)) === fround(value)`; nine significant digits
135
+ * always identify a float32. `±0`, NaN and ±Infinity pass through.
136
+ */
137
+ export declare function shortestFloat32(params: {
138
+ value: number;
139
+ }): number;
140
+ /**
141
+ * `Float @db.Real` — a refinement of the Float codec keyed by the native type,
142
+ * not by the scalar type, so it is not a table row: the runtime routes a
143
+ * field with `nativeType: "Real"` here. Decode = {@link numberCodec} then
144
+ * {@link shortestFloat32}. Real aggregates that postgres returns as `real`
145
+ * (`min`/`max`/`sum`) use it too; `avg` returns double precision and must not.
146
+ */
147
+ export declare const float32Codec: ScalarCodec;
148
+ /**
149
+ * Element decoders for a postgres array that arrived as its TEXT literal
150
+ * (`{1e-07,NULL}`, `{"{\"a\": 1}"}`) — the runtime projects `Float[]`/`Json[]`
151
+ * as `::text`, and drivers return arrays whose type OID they do not know
152
+ * (`uuid[]` on bun:sql; `xml[]`, `bit[]`, `citext[]` on node-postgres) as the
153
+ * literal. Each element is postgres's text output for its type:
154
+ *
155
+ * - Boolean `t`/`f`, Bytes `\x` hex and Json text need their own conversion;
156
+ * - every other scalar's regular postgres codec already accepts the text form
157
+ * (`Number("1e-07")`, `Number("NaN")`, `Number("-0")`, BigInt/Decimal text,
158
+ * `parseDateTimeText` for `2024-06-01 12:34:56.789+00`), so `null` means
159
+ * "decode with the field's own codec".
160
+ */
161
+ export declare function postgresArrayElementFromText(params: {
162
+ scalarType: ScalarType;
163
+ }): ((text: string) => unknown) | null;
116
164
  /** postgres (and pglite — same wire behaviour). */
117
165
  export declare const POSTGRES_CODECS: CodecTable;
118
166
  /** sqlite — no boolean, date or json types; everything rides TEXT/INTEGER. */
package/dist/dialect.d.ts CHANGED
@@ -72,6 +72,14 @@ export type ConflictKeyEquality = {
72
72
  * created otherwise, and sqlite's default collating sequence is BINARY);
73
73
  * mysql does not, because its server default `utf8mb4_0900_ai_ci` ignores
74
74
  * case and accents.
75
+ *
76
+ * Consumers: the batched upsert's duplicate-key check
77
+ * (packages/runtime/src/bulk-upsert.ts) and the relation loader
78
+ * (packages/runtime/src/relation-loader.ts, `associatesInDatabase`). Where
79
+ * this is false, a relation key with a `String` member is paired with its
80
+ * parents by the DATABASE through a key table (`SelectStatement.keyTable`,
81
+ * mysql only) instead of by JavaScript string equality, which would drop
82
+ * children whose key the collation equates (`acme` under `Acme`).
75
83
  */
76
84
  readonly textIsBinaryByDefault: boolean;
77
85
  /** Character capacity of the default String column, if bounded. Also consumed by migrate. */
@@ -100,6 +108,15 @@ export type SqlDialect = {
100
108
  * member, so composite membership always chunks against `paramBudget`.
101
109
  */
102
110
  readonly tupleInStrategy: "rowValue" | "expand";
111
+ /**
112
+ * Membership on a time-of-day column (`inArray` with `timeOfDay`): `in`
113
+ * renders like any other membership; `equalities` renders an OR of `=`
114
+ * comparisons. MySQL 8.4 types a `?` inside a multi-value IN list against a
115
+ * TIME(n) column as TIME(6) and matches nothing unless n is 6 (live on
116
+ * 8.4.11; 26.7 matches); `=` converts the parameter to the column's TIME on
117
+ * every version.
118
+ */
119
+ readonly timeOfDayInStrategy: "in" | "equalities";
103
120
  /** Upsert clause form. */
104
121
  readonly upsertForm: "onConflict" | "onDuplicateKey";
105
122
  /** Which equality decides a duplicate conflict key here (batched upsert). */
@@ -136,6 +153,20 @@ export type SqlDialect = {
136
153
  * non-native dialects — documented in docs/dialect-notes.md.
137
154
  */
138
155
  readonly strictMatchStrategy: "native" | "glob" | "binary";
156
+ /**
157
+ * `sql.greatest` (NULL-ignoring two-operand maximum): `native` renders
158
+ * `GREATEST(l, r)`, whose PostgreSQL semantics already ignore NULL; `frame`
159
+ * binds both operands once in a correlated derived table and compares the
160
+ * bound columns with CASE (MySQL's GREATEST returns NULL on any NULL, and
161
+ * SQLite has none). `wrapped-frame` is the frame with each operand in its own
162
+ * scalar subquery: MySQL 8.4 refuses a derived-table select list that holds a
163
+ * nested frame beside a direct outer column reference (ER_ILLEGAL_REFERENCE
164
+ * 1247). In both frames a bare NULL value operand renders as the NULL
165
+ * literal. Only a node with a nested greatest operand uses a frame; a node
166
+ * without one keeps the 2.x CASE form (2.x semantics, types and aggregate
167
+ * scope), which repeats its two operands, so nesting stays linear.
168
+ */
169
+ readonly greatestForm: "native" | "frame" | "wrapped-frame";
139
170
  /** ORDER BY NULLS FIRST/LAST: native clause or `col IS NULL` emulation term. */
140
171
  readonly orderByNullsMode: "native" | "emulate";
141
172
  /** Dialects where OFFSET requires a LIMIT clause declare the filler literal. */
@@ -197,6 +228,19 @@ export type SqlDialect = {
197
228
  * rows are grouped by column set into separate statements there (M4).
198
229
  */
199
230
  readonly rowDefaultKeyword: boolean;
231
+ /**
232
+ * How a {@link ChainStatement} (an ordered chain of read and data-modifying
233
+ * steps ending in one SELECT — round-trip campaign EPIC 4/F) renders, or
234
+ * `null` where the dialect cannot run one as a single statement.
235
+ * `dataModifyingCte` is PostgreSQL's `WITH "step" AS (INSERT/UPDATE/DELETE …
236
+ * RETURNING …) SELECT …` (PGlite included). SQLite and MySQL accept only
237
+ * SELECT inside WITH, so they are `null`: the runtime keeps its
238
+ * multi-statement path there (an optimisation, not a feature — plan D4) and
239
+ * the renderer refuses a chain with `VIBE_UNSUPPORTED_CAPABILITY`. An
240
+ * internal SQL-shape strategy, deliberately NOT in the public capability
241
+ * table (review O-11).
242
+ */
243
+ readonly statementChain: "dataModifyingCte" | null;
200
244
  /**
201
245
  * Conservative bound-parameter budget per statement (M7). Hard limits:
202
246
  * sqlite errors past SQLITE_MAX_VARIABLE_NUMBER (32766 by default), mysql
@@ -0,0 +1,28 @@
1
+ import type { SqlDialect } from "../dialect.ts";
2
+ import type { RenderedQuery } from "../render.ts";
3
+ /** An application type, relation or column whose planned drop or retype must not break another owner's objects. */
4
+ export type ExternalDependentTarget = {
5
+ readonly kind: "type" | "relation";
6
+ readonly namespace: string;
7
+ readonly name: string;
8
+ } | {
9
+ readonly kind: "column";
10
+ readonly namespace: string;
11
+ readonly name: string;
12
+ readonly column: string;
13
+ };
14
+ /**
15
+ * Normal (`deptype = 'n'`) catalog dependents of each target, answered per
16
+ * target position. A target covers its array type; a relation also covers its
17
+ * row type, and a relation or column also covers the sequences it owns
18
+ * (identity and serial), whose dependents are what a drop would break. Each
19
+ * dependent is anchored to its relation (columns, defaults, view rules,
20
+ * triggers, table constraints, policies, composite types) or to its own
21
+ * namespace and name (routines, domains), so ownership can be decided outside
22
+ * SQL. Read-only.
23
+ */
24
+ export declare function renderExternalDependents(params: {
25
+ dialect: SqlDialect;
26
+ targets: readonly ExternalDependentTarget[];
27
+ }): RenderedQuery;
28
+ //# sourceMappingURL=external-dependents.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { SqlDialect } from "../dialect.ts";
2
+ import type { RenderedQuery } from "../render.ts";
3
+ /** Complete catalog ownership edges, including endpoints outside the migration namespace. */
4
+ export declare function renderExternalOwnershipCatalog(params: {
5
+ dialect: SqlDialect;
6
+ operation: "namespaces" | "inheritance" | "ownedSequences" | "foreignKeys";
7
+ }): RenderedQuery;
8
+ //# sourceMappingURL=external-ownership.d.ts.map
@@ -28,6 +28,11 @@ export type ShadowOperation = {
28
28
  readonly insert: boolean;
29
29
  } | {
30
30
  readonly kind: "resetPostgres";
31
+ }
32
+ /** `ownedNamespaces` must come from a validated dedicated-shadow marker or declared package configuration for db reset, never from discovery. */
33
+ | {
34
+ readonly kind: "resetDedicatedPostgres";
35
+ readonly ownedNamespaces: readonly string[];
31
36
  } | {
32
37
  readonly kind: "mysqlForeignKeys";
33
38
  readonly enabled: boolean;