@vibeorm/sql 2.5.0 → 4.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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ast.d.ts +339 -12
  3. package/dist/codecs.d.ts +57 -0
  4. package/dist/dialect.d.ts +54 -0
  5. package/dist/dialects/postgres-index-keys.d.ts +1 -0
  6. package/dist/dialects/postgres-physical-catalog.d.ts +7 -1
  7. package/dist/dialects/sqlite-fts.d.ts +28 -0
  8. package/dist/index.d.ts +19 -7
  9. package/dist/index.js +2100 -983
  10. package/dist/index.js.map +26 -20
  11. package/dist/json-list-parameter.d.ts +37 -0
  12. package/dist/parameter-snapshot.d.ts +6 -0
  13. package/dist/params.d.ts +7 -8
  14. package/dist/pg-array.d.ts +13 -0
  15. package/dist/query/expressions.d.ts +70 -0
  16. package/dist/query/index.d.ts +6 -0
  17. package/dist/query/index.js +1078 -0
  18. package/dist/query/index.js.map +16 -0
  19. package/dist/query/mutations.d.ts +15 -0
  20. package/dist/query/reusable.d.ts +17 -0
  21. package/dist/query/select.d.ts +11 -0
  22. package/dist/query/sources.d.ts +37 -0
  23. package/dist/query/state.d.ts +73 -0
  24. package/dist/query/types.d.ts +188 -0
  25. package/dist/query-plan.d.ts +97 -0
  26. package/dist/render.d.ts +11 -1
  27. package/dist/result-decoder.d.ts +41 -10
  28. package/dist/sql-bindings.d.ts +84 -0
  29. package/dist/sql-input.d.ts +98 -0
  30. package/package.json +9 -3
  31. package/dist/ast.d.ts.map +0 -1
  32. package/dist/capabilities.d.ts.map +0 -1
  33. package/dist/codecs.d.ts.map +0 -1
  34. package/dist/composable.d.ts.map +0 -1
  35. package/dist/dialect.d.ts.map +0 -1
  36. package/dist/dialects/constraint-owned-index.d.ts.map +0 -1
  37. package/dist/dialects/diagnostics.d.ts.map +0 -1
  38. package/dist/dialects/external-dependents.d.ts.map +0 -1
  39. package/dist/dialects/external-ownership.d.ts.map +0 -1
  40. package/dist/dialects/migration-history.d.ts.map +0 -1
  41. package/dist/dialects/migration-inventory.d.ts.map +0 -1
  42. package/dist/dialects/migration-objects.d.ts.map +0 -1
  43. package/dist/dialects/migration-replay-coverage.d.ts.map +0 -1
  44. package/dist/dialects/migration-shadow.d.ts.map +0 -1
  45. package/dist/dialects/migration-visibility.d.ts.map +0 -1
  46. package/dist/dialects/module-dependency.d.ts.map +0 -1
  47. package/dist/dialects/mysql-check-probe.d.ts.map +0 -1
  48. package/dist/dialects/mysql.d.ts.map +0 -1
  49. package/dist/dialects/postgres-default-identity.d.ts.map +0 -1
  50. package/dist/dialects/postgres-diagnostics.d.ts.map +0 -1
  51. package/dist/dialects/postgres-index-keys.d.ts.map +0 -1
  52. package/dist/dialects/postgres-migration.d.ts.map +0 -1
  53. package/dist/dialects/postgres-physical-catalog.d.ts.map +0 -1
  54. package/dist/dialects/postgres-rls-readiness.d.ts.map +0 -1
  55. package/dist/dialects/postgres-rls.d.ts.map +0 -1
  56. package/dist/dialects/postgres-type-identity.d.ts.map +0 -1
  57. package/dist/dialects/postgres.d.ts.map +0 -1
  58. package/dist/dialects/registry.d.ts.map +0 -1
  59. package/dist/dialects/sqlite-constraints.d.ts.map +0 -1
  60. package/dist/dialects/sqlite-session.d.ts.map +0 -1
  61. package/dist/dialects/sqlite-table-definition.d.ts.map +0 -1
  62. package/dist/dialects/sqlite.d.ts.map +0 -1
  63. package/dist/exclusion.d.ts.map +0 -1
  64. package/dist/identifiers.d.ts.map +0 -1
  65. package/dist/index-columns.d.ts.map +0 -1
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/json-transport.d.ts.map +0 -1
  68. package/dist/like.d.ts.map +0 -1
  69. package/dist/locks.d.ts.map +0 -1
  70. package/dist/module-journal.d.ts.map +0 -1
  71. package/dist/native-enum-parameter.d.ts.map +0 -1
  72. package/dist/params.d.ts.map +0 -1
  73. package/dist/pg-array.d.ts.map +0 -1
  74. package/dist/predicate.d.ts.map +0 -1
  75. package/dist/render.d.ts.map +0 -1
  76. 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,4 +1,5 @@
1
1
  import type { NativeEnumArrayParameter } from "./native-enum-parameter.ts";
2
+ import type { JsonListParameter } from "./json-list-parameter.ts";
2
3
  import type { SqlFragment } from "./composable.ts";
3
4
  import type { RlsReadinessQuery } from "./dialects/postgres-rls-readiness.ts";
4
5
  /**
@@ -15,13 +16,20 @@ export type ColRef = {
15
16
  readonly table?: string;
16
17
  readonly name: string;
17
18
  /**
18
- * Projection-only wire transform, postgres lateral joins only (SQL review
19
- * B3): raw `to_jsonb` would ship BigInt/Decimal as float64 JSON numbers and
20
- * Bytes as `\x` hex text, so the subselect casts instead — `castText`
21
- * renders `"col"::text AS "col"`, `encodeBase64` renders
22
- * `encode("col", 'base64') AS "col"`. Never set on filter/order refs.
19
+ * Projection-only wire transform. `jsonText` uses the dialect's JSON text
20
+ * projection to preserve SQL NULL versus the JSON document `null` before
21
+ * driver parsing, without converting a postgres `json` column to `jsonb`.
22
+ * The other transforms are postgres only. `castText` renders
23
+ * `"col"::text AS "col"`, `encodeBase64` renders
24
+ * `encode("col", 'base64') AS "col"`. Two users: the lateral subselect (SQL
25
+ * review B3 — raw `to_jsonb` would ship BigInt/Decimal as float64 JSON
26
+ * numbers and Bytes as `\x` hex text), and every row projection of a
27
+ * `Float[]`/`Json[]` column (bun:sql's array parser refuses float arrays
28
+ * printed with an exponent and json arrays holding integers beyond int32;
29
+ * the runtime parses the array literal text itself). Never set on
30
+ * filter/order refs.
23
31
  */
24
- readonly transform?: "castText" | "encodeBase64";
32
+ readonly transform?: "castText" | "encodeBase64" | "jsonText";
25
33
  /**
26
34
  * Comparison/order/aggregate cast, sqlite Decimal only (SQL review B4):
27
35
  * Decimal stores as exact TEXT there and text comparison is lexicographic —
@@ -30,6 +38,14 @@ export type ColRef = {
30
38
  * storage and read-back stay the exact text.
31
39
  */
32
40
  readonly castReal?: boolean;
41
+ /**
42
+ * Order/distinct/compare cast, postgres `json` columns only (`Json @db.Json`,
43
+ * release-3.0 D3): `json` has no ordering or equality operator, so ORDER BY,
44
+ * DISTINCT ON and comparison sites render `"col"::jsonb` (`"col"::jsonb[]`
45
+ * for a `json[]` list column) — the order and distinctness of a jsonb
46
+ * column. Never set on projections, so read-back keeps the stored text.
47
+ */
48
+ readonly castJsonb?: "jsonb" | "jsonb[]";
33
49
  /**
34
50
  * Projection alias: renders `ref AS "alias"`. PROJECTION-ONLY — filter,
35
51
  * order and correlation refs never set it (the renderer ignores it outside
@@ -93,6 +109,11 @@ export type SqlExpr = {
93
109
  readonly column: ColRef;
94
110
  readonly values: readonly unknown[];
95
111
  readonly negate?: boolean;
112
+ /**
113
+ * The column holds a time of day (`@db.Time(n)` / `@db.Timetz(n)`): the
114
+ * dialect renders the membership per `SqlDialect.timeOfDayInStrategy`.
115
+ */
116
+ readonly timeOfDay?: boolean;
96
117
  }
97
118
  /**
98
119
  * ORDERED-TUPLE membership: `("a", "b") IN ((?, ?), (?, ?))`. The composite
@@ -183,13 +204,13 @@ export type SqlExpr = {
183
204
  | {
184
205
  readonly kind: "arrayContainsAll";
185
206
  readonly column: ColRef;
186
- readonly values: readonly unknown[] | string | NativeEnumArrayParameter;
207
+ readonly values: readonly unknown[] | string | NativeEnumArrayParameter | JsonListParameter;
187
208
  }
188
209
  /** Scalar-list overlap: `"col" && $n` (one array param) — Prisma's `hasSome`. Postgres only. */
189
210
  | {
190
211
  readonly kind: "arrayOverlaps";
191
212
  readonly column: ColRef;
192
- readonly values: readonly unknown[] | string | NativeEnumArrayParameter;
213
+ readonly values: readonly unknown[] | string | NativeEnumArrayParameter | JsonListParameter;
193
214
  }
194
215
  /**
195
216
  * Scalar-list emptiness via `array_length("col", 1) IS [NOT] NULL` — the v1
@@ -216,6 +237,15 @@ export type SqlExpr = {
216
237
  readonly value: unknown;
217
238
  readonly insensitive?: boolean;
218
239
  readonly negate?: boolean;
240
+ /**
241
+ * The column's postgres type: absent (or `jsonb`) for the default
242
+ * `jsonb`, `json` for `Json @db.Json`. Postgres defines `=`, `<>` and
243
+ * `@>` for jsonb only, so on a `json` column every op casts the
244
+ * EXTRACTED value to jsonb (the string ops then read it as text), which
245
+ * gives a jsonb column's answer. Sqlite and mysql have one JSON type
246
+ * and ignore it.
247
+ */
248
+ readonly storage?: "jsonb" | "json";
219
249
  }
220
250
  /**
221
251
  * A bound extension fragment: literal text, bound parameters and quoted
@@ -369,6 +399,24 @@ export type LateralJoin = {
369
399
  /** The correlated child select — may reference outer columns via table-qualified refs. */
370
400
  readonly select: SelectStatement;
371
401
  };
402
+ /**
403
+ * One correlated scalar `COUNT(*)` projected into a select list:
404
+ * `(SELECT COUNT(*) FROM … WHERE <correlation>) AS "alias"` — the relation
405
+ * `_count` of the round-trip campaign's R-03b, which saves the separate grouped
406
+ * count statement. Portable: postgres, sqlite and mysql all evaluate a
407
+ * correlated scalar subquery once per output row; the runtime wraps a paged
408
+ * root in a derived table first, so the subqueries run only for the page.
409
+ * The select must be ONE ungrouped `COUNT(*)` (`columns: []`, `selectExpr`
410
+ * countAll, no grouping, paging, distinct or lock) — anything else is refused
411
+ * at render. Its parameters join the statement's single sequence in text order
412
+ * (the select list renders before FROM).
413
+ */
414
+ export type CountSubquery = {
415
+ /** Output column the count is exposed under (`__vibe_count_0`). */
416
+ readonly alias: string;
417
+ /** The correlated count select — references outer columns via table-qualified refs. */
418
+ readonly select: SelectStatement;
419
+ };
372
420
  /**
373
421
  * One `INNER JOIN "table" AS alias ON left = right [AND …]` clause — plain
374
422
  * equality joins only, portable across all dialects. Added for the
@@ -384,6 +432,75 @@ export type InnerJoin = {
384
432
  readonly right: ColRef;
385
433
  }[];
386
434
  };
435
+ /**
436
+ * How one member of a {@link KeyTableJoin} tuple binds inside the derived
437
+ * table. `value` binds the parameter as it is: it stays COERCIBLE, so a text
438
+ * column's OWN collation decides `=` — the equality `col IN (?, …)` uses.
439
+ * `integer` and `decimal` cast the parameter into the column's numeric family:
440
+ * BigInt and Decimal values travel as strings, and a derived-table string
441
+ * compared with a numeric column goes through a double (measured on mysql:
442
+ * `9007199254740993` matched `9007199254740992`).
443
+ *
444
+ * `integer` renders `CAST(? AS SIGNED)`. `decimal` renders
445
+ * `CAST(? AS DECIMAL(65, scale))` with the tested column's own scale: 65 is
446
+ * MySQL's maximum precision, so the cast holds every value a
447
+ * `DECIMAL(p, scale)` column can hold. A fixed `DECIMAL(65,30)` held only 35
448
+ * integer digits and clipped a `Decimal(65, 0)` key onto its maximum, which
449
+ * matched nothing (review finding 1); the column's own precision would clip a
450
+ * wider value onto the column's maximum and pair it with a row it does not
451
+ * equal (measured: `CAST('100000' AS DECIMAL(5,0))` is `99999`). `scale` is
452
+ * 0–30, MySQL's range.
453
+ *
454
+ * `citext` (postgres only) renders `CAST($n AS citext)`: a postgres `citext`
455
+ * key compares case-insensitively, and a bare VALUES parameter would be
456
+ * `text`, whose `=` against a citext column is the case-sensitive text
457
+ * operator. A VALUES cell has no column to infer a type from, so every member
458
+ * of a postgres key table binds typed: `citext`, `text` (`CAST($n AS text)`,
459
+ * a text or varchar column) or `integer` (`CAST($n AS bigint)`, exact against
460
+ * any integer column) — any other member refuses on postgres. `text` is
461
+ * postgres-only; mysql binds text members as `value`.
462
+ */
463
+ export type KeyTableMember = {
464
+ readonly kind: "value";
465
+ } | {
466
+ readonly kind: "integer";
467
+ } | {
468
+ readonly kind: "decimal";
469
+ readonly scale: number;
470
+ } | {
471
+ readonly kind: "citext";
472
+ } | {
473
+ readonly kind: "text";
474
+ };
475
+ /**
476
+ * The batch's key tuples as a derived table, each tagged with its ORDINAL
477
+ * (row `i` carries `i`), INNER JOINed on the tested columns — mysql:
478
+ * `INNER JOIN (VALUES ROW(?, 0), ROW(?, 1)) AS \`alias\` (\`__vibe_k0\`, \`ordinal\`)
479
+ * ON col = \`alias\`.\`__vibe_k0\``; postgres only over `citext` members
480
+ * (`(VALUES (CAST($1 AS citext), 0), …)`, release 3.0 nested writes).
481
+ * Member columns are named `__vibe_k<i>` (a reserved prefix, so unqualified
482
+ * child columns never collide with them).
483
+ *
484
+ * Why it exists (EPIC R): a relation load used to stitch children to parents
485
+ * by comparing decoded keys in JavaScript, while mysql's collations equate
486
+ * spellings (`acme` = `Acme` = `ACME`, `cafe` = `Café`, `x ` = `x`) that
487
+ * JavaScript keeps apart — the foreign key and the `IN` filter accepted the
488
+ * child, the stitch dropped it. Joined this way the DATABASE pairs every child
489
+ * row with every batch key it considers equal, and the projected ordinal names
490
+ * the key; the join is also the membership filter.
491
+ */
492
+ export type KeyTableJoin = {
493
+ /** Derived-table alias (`__vibe_keys`); the ordinal projects as `alias.ordinalAlias`. */
494
+ readonly alias: string;
495
+ /** The statement's columns, compared member by member (`=`), in tuple order. */
496
+ readonly columns: readonly ColRef[];
497
+ /** How each member binds, in tuple order — see {@link KeyTableMember}. */
498
+ readonly members: readonly KeyTableMember[];
499
+ /** Encoded key tuples; never empty, each as long as `columns`. */
500
+ readonly rows: readonly (readonly unknown[])[];
501
+ /** Output name of the ordinal column. */
502
+ readonly ordinalAlias: string;
503
+ };
387
504
  /**
388
505
  * A `ROW_NUMBER() OVER (PARTITION BY … [ORDER BY …]) AS "alias"` projection,
389
506
  * appended after `columns` — the distinct emulation on dialects without
@@ -413,6 +530,8 @@ export type SelectStatement = {
413
530
  * combined with `selectExpr`.
414
531
  */
415
532
  readonly aggregates?: readonly AggSelect[];
533
+ /** Correlated `COUNT(*)` subqueries, rendered after `aggregates` — see {@link CountSubquery}. */
534
+ readonly countSubqueries?: readonly CountSubquery[];
416
535
  /**
417
536
  * `SELECT DISTINCT ON (cols)` — postgres only (`capabilities.distinctOn`;
418
537
  * other dialects refuse at render time and the runtime emulates client-side
@@ -430,6 +549,8 @@ export type SelectStatement = {
430
549
  readonly lateralJoins?: readonly LateralJoin[];
431
550
  /** Plain equality INNER JOINs — portable, see {@link InnerJoin}. */
432
551
  readonly innerJoins?: readonly InnerJoin[];
552
+ /** Key-table join, rendered after `innerJoins` — mysql only, see {@link KeyTableJoin}. */
553
+ readonly keyTable?: KeyTableJoin;
433
554
  /** ROW_NUMBER window projection — see {@link RowNumberProjection}. */
434
555
  readonly rowNumber?: RowNumberProjection;
435
556
  readonly where?: SqlExpr;
@@ -482,14 +603,21 @@ export declare function isSqlDefaultCell(value: unknown): boolean;
482
603
  * Sqlite/mysql render the bare placeholder — their Json transport is already
483
604
  * plain text. The COLLECTED parameter is the plain text either way: this
484
605
  * wrapper never crosses the wire.
606
+ *
607
+ * `storage: "json"` marks a postgres `json` column (`Json @db.Json`): the cell
608
+ * renders `$n::text::json`, so the column stores the text as written (key
609
+ * order and spelling kept) instead of jsonb's normalized text (release-3.0
610
+ * D3). Absent = jsonb; dialects without a param cast ignore it.
485
611
  */
486
612
  export type JsonParamCell = {
487
613
  readonly __jsonParam: true;
488
614
  readonly text: string;
615
+ readonly storage?: "json";
489
616
  };
490
617
  /** Wrap a Json column's encoded (stringified) parameter for cast-site rendering. */
491
618
  export declare function jsonParamCell(params: {
492
619
  text: string;
620
+ storage?: "json";
493
621
  }): JsonParamCell;
494
622
  /** Is this VALUES/SET cell a {@link JsonParamCell}? */
495
623
  export declare function isJsonParamCell(value: unknown): value is JsonParamCell;
@@ -511,6 +639,21 @@ export declare function excludedRef(params: {
511
639
  }): ExcludedRef;
512
640
  /** Is this SET cell an {@link ExcludedRef}? */
513
641
  export declare function isExcludedRef(value: unknown): value is ExcludedRef;
642
+ /**
643
+ * One RETURNING column: a bare column name renders `"col"`; `castText` renders
644
+ * `"col"::text AS "col"` (postgres only) for `Float[]`/`Json[]`. `jsonText`
645
+ * applies the dialect's JSON text projection, as {@link ColRef.transform} does
646
+ * for SELECT. Dialects without RETURNING still omit the clause and flag it.
647
+ */
648
+ export type ReturningColumn = string | {
649
+ readonly name: string;
650
+ readonly castText: true;
651
+ } | {
652
+ readonly name: string;
653
+ readonly transform: "jsonText";
654
+ };
655
+ /** The column name a {@link ReturningColumn} exposes in the result row. */
656
+ export declare function returningColumnName(column: ReturningColumn): string;
514
657
  export type InsertStatement = {
515
658
  readonly kind: "insert";
516
659
  readonly table: TableRef;
@@ -520,7 +663,7 @@ export type InsertStatement = {
520
663
  /** Upsert clause: ON CONFLICT (pg/sqlite) or ON DUPLICATE KEY (mysql). */
521
664
  readonly onConflict?: OnConflictClause;
522
665
  /** Rendered as RETURNING where the dialect supports it, else omitted+flagged. */
523
- readonly returning?: readonly string[];
666
+ readonly returning?: readonly ReturningColumn[];
524
667
  };
525
668
  /** Conflicting rows are updated with these column values. */
526
669
  export type OnConflictUpdate = {
@@ -577,13 +720,13 @@ export type UpdateStatement = {
577
720
  /** Column → parameter value, or an atomic {@link SetOperation}. */
578
721
  readonly set: Readonly<Record<string, unknown>>;
579
722
  readonly where?: SqlExpr;
580
- readonly returning?: readonly string[];
723
+ readonly returning?: readonly ReturningColumn[];
581
724
  };
582
725
  export type DeleteStatement = {
583
726
  readonly kind: "delete";
584
727
  readonly table: TableRef;
585
728
  readonly where?: SqlExpr;
586
- readonly returning?: readonly string[];
729
+ readonly returning?: readonly ReturningColumn[];
587
730
  };
588
731
  /** Native RLS DDL, kept distinct from ordinary row-query expressions. */
589
732
  export type RlsEnableStatement = {
@@ -663,7 +806,191 @@ export type AdvisoryLockStatement = {
663
806
  };
664
807
  /** The column an advisory try-lock projects its verdict into. */
665
808
  export declare const ADVISORY_LOCK_RESULT_COLUMN: string;
666
- export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement;
809
+ /**
810
+ * A cell of an {@link InsertSelectStatement} that reads a column of an EARLIER
811
+ * chain step: `"step"."column"`. A column reference, not a parameter — the
812
+ * value comes from the step's rows inside the same statement (a child row
813
+ * taking its parent's key from the parent step, round-trip campaign EPIC F).
814
+ * The step must be named in the insert's `from` list.
815
+ */
816
+ export type StepColumnRef = {
817
+ readonly __stepColumn: true;
818
+ readonly step: string;
819
+ readonly column: string;
820
+ };
821
+ /** Reference `column` of the earlier chain step `step` from an INSERT … SELECT cell. */
822
+ export declare function stepColumnRef(params: {
823
+ step: string;
824
+ column: string;
825
+ }): StepColumnRef;
826
+ /** Is this INSERT … SELECT cell a {@link StepColumnRef}? */
827
+ export declare function isStepColumnRef(value: unknown): value is StepColumnRef;
828
+ /**
829
+ * `INSERT INTO t (cols) SELECT <cells> [FROM "step", …] [WHERE <expr>]
830
+ * [RETURNING …]` — an INSERT whose ONE row is a SELECT list instead of a
831
+ * VALUES row, so a WHERE can veto the write inside the statement and cells can
832
+ * read earlier steps. Valid ONLY as a {@link ChainStep} statement.
833
+ *
834
+ * Cells take the same forms as a VALUES cell — a codec-encoded value, a
835
+ * {@link JsonParamCell}, a {@link DbNowMarker} — plus {@link StepColumnRef}.
836
+ * The {@link SQL_DEFAULT_CELL} is refused: `DEFAULT` is not allowed in a SELECT
837
+ * list; an omitted column takes its default by being absent from `columns`.
838
+ *
839
+ * Parameter typing (O-03, probed on PostgreSQL): a bare parameter in this
840
+ * one-row SELECT list takes the TARGET column's type, exactly as in VALUES.
841
+ */
842
+ export type InsertSelectStatement = {
843
+ readonly kind: "insertSelect";
844
+ readonly table: TableRef;
845
+ readonly columns: readonly string[];
846
+ /** One cell per column, aligned to `columns` — deterministic parameter order. */
847
+ readonly cells: readonly unknown[];
848
+ /** Earlier chain steps the SELECT reads FROM, in order (absent: a one-row SELECT without FROM). */
849
+ readonly from?: readonly string[];
850
+ /**
851
+ * Several source rows (round-trip campaign EPIC F): the SELECT reads them
852
+ * from a derived table `alias`, joined after the `from` steps, and the
853
+ * `cells` read its columns through `stepColumnRef({ step: alias, column })`.
854
+ * See {@link ChainRowSource} for how the rows are typed.
855
+ */
856
+ readonly source?: ChainRowSource;
857
+ readonly where?: SqlExpr;
858
+ readonly returning?: readonly ReturningColumn[];
859
+ };
860
+ /**
861
+ * Rows an {@link InsertSelectStatement} inserts, as ONE derived table:
862
+ * `(SELECT <cols> FROM (VALUES (<typed NULL row>), ($1, $2), …) AS "alias"
863
+ * (<cols>) OFFSET 1) AS "alias"`.
864
+ *
865
+ * Why the typed NULL row (review O-03): a bare parameter outside the INSERT's
866
+ * own VALUES has no type, and PostgreSQL resolves an all-parameter column of a
867
+ * VALUES list or UNION in FROM as `text` ("column is of type integer but
868
+ * expression is of type text"). The first row is one `(SELECT "col" FROM
869
+ * <typedBy> WHERE FALSE)` per column — a NULL of the TARGET column's exact
870
+ * type (enums, `numeric(p,s)`, `varchar(n)`, domains and arrays included) —
871
+ * so every parameter below takes that type, with no cast map anywhere;
872
+ * `OFFSET 1` drops the row. (A `UNION ALL` of one-row SELECTs types the same
873
+ * way but costs ~10× the planning time at 1000 rows — EPIC F shape gate.)
874
+ *
875
+ * Cells take the VALUES forms: a codec-encoded value, a {@link JsonParamCell}
876
+ * or a {@link DbNowMarker}; the DEFAULT cell is refused (a homogeneous row set
877
+ * omits the column instead). A cell carrying its OWN type (the Json cast, the
878
+ * clock) must agree with the column's type under VALUES' common-type rules,
879
+ * which are stricter than INSERT's assignment casts — the builder keeps such
880
+ * columns out (EPIC F eligibility). Rows are read in list order, which is the
881
+ * order the INSERT consumes them.
882
+ */
883
+ export type ChainRowSource = {
884
+ readonly alias: string;
885
+ /** The table whose columns type the rows — normally the INSERT's own table. */
886
+ readonly typedBy: TableRef;
887
+ readonly columns: readonly string[];
888
+ /** At least one row, each aligned to `columns`. */
889
+ readonly rows: readonly (readonly unknown[])[];
890
+ };
891
+ /** What one chain step may be: a read, or a data-modifying statement with RETURNING. */
892
+ export type ChainStepStatement = SelectStatement | InsertStatement | InsertSelectStatement | UpdateStatement | DeleteStatement;
893
+ /**
894
+ * One named step: rendered `"name" AS (<statement>)`. Later steps and the
895
+ * result read it as a table (`FROM "name"`, `EXISTS (SELECT 1 FROM "name" …)`,
896
+ * a {@link StepColumnRef}). A data-modifying step contributes the rows its
897
+ * RETURNING list names.
898
+ */
899
+ export type ChainStep = {
900
+ readonly name: string;
901
+ readonly statement: ChainStepStatement;
902
+ };
903
+ /**
904
+ * The chain's final SELECT: the rows of `rowsFrom` (in that order, `UNION
905
+ * ALL`), each projecting `columns`.
906
+ *
907
+ * `verdict` adds one column `alias` = `expr` to every row, AND guarantees a
908
+ * row: when the `rowsFrom` steps return nothing, the result is exactly ONE
909
+ * verdict row whose `columns` are all NULL. A caller recognizes it by NULL in
910
+ * a column the steps never return as NULL (a primary key). Accounting
911
+ * consequence: such a statement returns one row per written row OR one
912
+ * verdict row — a caller that counts returned rows as row changes must undo a
913
+ * verdict-row outcome (the upsert fold always rolls it back).
914
+ */
915
+ export type ChainResult = {
916
+ readonly rowsFrom: readonly string[];
917
+ readonly columns: readonly string[];
918
+ readonly verdict?: {
919
+ readonly alias: string;
920
+ readonly expr: SqlExpr;
921
+ };
922
+ /**
923
+ * In-statement vetoes (round-trip campaign EPIC F), checked in order: when
924
+ * `step` returned no row (fewer than `minRows`, EPIC W), the STATEMENT FAILS — so the database undoes every
925
+ * effect of every step, trigger writes included — with an error whose
926
+ * message carries `__vibe_veto:<token>:<nonce>:` ({@link CHAIN_VETO_PREFIX},
927
+ * {@link chainVetoToken}). Requires `tally`: the veto is the tally column's
928
+ * guard, so it is evaluated exactly once, on the one guaranteed result row.
929
+ */
930
+ readonly veto?: readonly ChainVeto[];
931
+ /**
932
+ * One column `alias` on every result row: the number of rows the listed
933
+ * data-modifying steps returned (their RETURNING rows = their direct
934
+ * effects). Guarantees one row like `verdict`. Row-change accounting reads
935
+ * it instead of counting result rows (effect `writeTally`).
936
+ */
937
+ readonly tally?: ChainTally;
938
+ };
939
+ /**
940
+ * Fail the statement when `step` returned no row; `token` names the veto in the
941
+ * error. `nonce` is bound as a PARAMETER into the marker (never SQL text): a
942
+ * per-call random value the caller requires back in the error, so a user value
943
+ * that merely looks like a marker is never read as a veto.
944
+ *
945
+ * `minRows` (EPIC W) makes the veto count-sensitive: the statement fails when
946
+ * `step` returned FEWER rows than that (a multi-row INSERT a trigger shortened).
947
+ * Omitted or 1 = "no row". A positive safe integer, rendered as SQL text.
948
+ */
949
+ export type ChainVeto = {
950
+ readonly step: string;
951
+ readonly token: string;
952
+ readonly nonce: string;
953
+ readonly minRows?: number;
954
+ };
955
+ /** Count the RETURNING rows of `steps` into the result column `alias`. */
956
+ export type ChainTally = {
957
+ readonly alias: string;
958
+ readonly steps: readonly string[];
959
+ };
960
+ /** Marker every chain veto error message carries, followed by `<token>:<nonce>:`. */
961
+ export declare const CHAIN_VETO_PREFIX: string;
962
+ /** Allowed veto tokens: lower-case words — they are rendered into SQL text. */
963
+ export declare const CHAIN_VETO_TOKEN_PATTERN: RegExp;
964
+ /**
965
+ * The veto token a database error message carries for this `nonce`, or
966
+ * `undefined`. Matches the marker anywhere in the text, so a localized server
967
+ * message (`lc_messages`) still yields it: the marker is the invalid VALUE the
968
+ * message quotes. A marker without the caller's nonce is not a veto.
969
+ */
970
+ export declare function chainVetoToken(params: {
971
+ message: string;
972
+ nonce: string;
973
+ }): string | undefined;
974
+ /**
975
+ * ONE statement made of an ordered chain of named steps — reads (a locked
976
+ * probe) and data-modifying statements with RETURNING — ending in a final
977
+ * SELECT over them (round-trip campaign EPIC 4, reused by EPIC F). Renders as
978
+ * `WITH "a" AS (…), "b" AS (…) SELECT …` on a dialect whose
979
+ * `statementChain` strategy names the form (postgres); every other dialect
980
+ * refuses at render time (`VIBE_UNSUPPORTED_CAPABILITY`) — callers gate on
981
+ * the strategy first and keep their multi-statement path there.
982
+ *
983
+ * PostgreSQL semantics the builder relies on: every step sees ONE snapshot
984
+ * (a step never sees another step's writes — only its RETURNING rows), steps
985
+ * that reference a step run after it, and the statement is atomic: an error in
986
+ * any step undoes every step's effects, trigger writes included.
987
+ */
988
+ export type ChainStatement = {
989
+ readonly kind: "chain";
990
+ readonly steps: readonly ChainStep[];
991
+ readonly result: ChainResult;
992
+ };
993
+ export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | ChainStatement;
667
994
  /** Readiness statements remain distinct from application queries. */
668
995
  export type RlsReadinessStatement = {
669
996
  readonly kind: "rlsReadiness";
package/dist/codecs.d.ts CHANGED
@@ -113,6 +113,63 @@ 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
+ * Parse stored JSON text at the centralized codec boundary. A supplied context
127
+ * marks a bound field: its error contains only that context, never the document
128
+ * or the native parser cause. Without context, legacy codec errors are unchanged.
129
+ */
130
+ export declare function parseJsonText(params: {
131
+ text: string;
132
+ context?: Readonly<Record<string, unknown>>;
133
+ }): unknown;
134
+ /**
135
+ * The SHORTEST decimal that round-trips through float32 to the same value as
136
+ * `value` — what postgres itself prints for a `real` (`0.1`, `3.4028235e+38`).
137
+ *
138
+ * A driver that reads a `real` in BINARY format (bun:sql with named
139
+ * statements) hands back the float32 widened to float64:
140
+ * `0.10000000149011612` for a stored `0.1`. Text-format drivers already give
141
+ * the short form, and for those this is the identity — the shortest form of a
142
+ * short value is itself — so it is safe on every driver. Smallest `p` in 1..9
143
+ * with `fround(toPrecision(p)) === fround(value)`; nine significant digits
144
+ * always identify a float32. `±0`, NaN and ±Infinity pass through.
145
+ */
146
+ export declare function shortestFloat32(params: {
147
+ value: number;
148
+ }): number;
149
+ /**
150
+ * `Float @db.Real` — a refinement of the Float codec keyed by the native type,
151
+ * not by the scalar type, so it is not a table row: the runtime routes a
152
+ * field with `nativeType: "Real"` here. Decode = {@link numberCodec} then
153
+ * {@link shortestFloat32}. Real aggregates that postgres returns as `real`
154
+ * (`min`/`max`/`sum`) use it too; `avg` returns double precision and must not.
155
+ */
156
+ export declare const float32Codec: ScalarCodec;
157
+ /**
158
+ * Element decoders for a postgres array that arrived as its TEXT literal
159
+ * (`{1e-07,NULL}`, `{"{\"a\": 1}"}`) — the runtime projects `Float[]`/`Json[]`
160
+ * as `::text`, and drivers return arrays whose type OID they do not know
161
+ * (`uuid[]` on bun:sql; `xml[]`, `bit[]`, `citext[]` on node-postgres) as the
162
+ * literal. Each element is postgres's text output for its type:
163
+ *
164
+ * - Boolean `t`/`f`, Bytes `\x` hex and Json text need their own conversion;
165
+ * - every other scalar's regular postgres codec already accepts the text form
166
+ * (`Number("1e-07")`, `Number("NaN")`, `Number("-0")`, BigInt/Decimal text,
167
+ * `parseDateTimeText` for `2024-06-01 12:34:56.789+00`), so `null` means
168
+ * "decode with the field's own codec".
169
+ */
170
+ export declare function postgresArrayElementFromText(params: {
171
+ scalarType: ScalarType;
172
+ }): ((text: string) => unknown) | null;
116
173
  /** postgres (and pglite — same wire behaviour). */
117
174
  export declare const POSTGRES_CODECS: CodecTable;
118
175
  /** 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. */
@@ -167,6 +198,8 @@ export type SqlDialect = {
167
198
  * {@link JsonParamCell} cells and the json-filter operands.
168
199
  */
169
200
  readonly jsonParamCast: string;
201
+ /** Physical types for standalone validated scalar values, without column inference. */
202
+ readonly valueCastTypes: Readonly<Record<ScalarType, string>>;
170
203
  /**
171
204
  * Statement that reads back the id generated by the previous INSERT, for
172
205
  * dialects without RETURNING. `null` where RETURNING covers it. The single
@@ -197,6 +230,19 @@ export type SqlDialect = {
197
230
  * rows are grouped by column set into separate statements there (M4).
198
231
  */
199
232
  readonly rowDefaultKeyword: boolean;
233
+ /**
234
+ * How a {@link ChainStatement} (an ordered chain of read and data-modifying
235
+ * steps ending in one SELECT — round-trip campaign EPIC 4/F) renders, or
236
+ * `null` where the dialect cannot run one as a single statement.
237
+ * `dataModifyingCte` is PostgreSQL's `WITH "step" AS (INSERT/UPDATE/DELETE …
238
+ * RETURNING …) SELECT …` (PGlite included). SQLite and MySQL accept only
239
+ * SELECT inside WITH, so they are `null`: the runtime keeps its
240
+ * multi-statement path there (an optimisation, not a feature — plan D4) and
241
+ * the renderer refuses a chain with `VIBE_UNSUPPORTED_CAPABILITY`. An
242
+ * internal SQL-shape strategy, deliberately NOT in the public capability
243
+ * table (review O-11).
244
+ */
245
+ readonly statementChain: "dataModifyingCte" | null;
200
246
  /**
201
247
  * Conservative bound-parameter budget per statement (M7). Hard limits:
202
248
  * sqlite errors past SQLITE_MAX_VARIABLE_NUMBER (32766 by default), mysql
@@ -239,6 +285,14 @@ export type SqlDialect = {
239
285
  placeholder(params: {
240
286
  index: number;
241
287
  }): string;
288
+ /**
289
+ * Project a JSON document as text while preserving SQL NULL. PostgreSQL
290
+ * casts to text, MySQL casts to CHAR and SQLite keeps its stored text. The
291
+ * expression is already rendered SQL, not an identifier or a bound value.
292
+ */
293
+ jsonTextProjection(params: {
294
+ expression: string;
295
+ }): string;
242
296
  /**
243
297
  * The database-clock expression for `DbNowMarker` sites: `now()` on
244
298
  * postgres, `NOW(6)` on mysql, and on sqlite an ISO-8601 `strftime` whose
@@ -5,5 +5,6 @@ import type { RenderedQuery } from "../render.ts";
5
5
  export declare function renderPostgresIndexKeys(params: {
6
6
  dialect: SqlDialect;
7
7
  table?: QualifiedSqlName;
8
+ namespaces?: readonly string[];
8
9
  }): RenderedQuery;
9
10
  //# sourceMappingURL=postgres-index-keys.d.ts.map