@kuindji/typed-sql 0.1.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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/builder/assemble.d.ts +13 -0
  4. package/dist/builder/assemble.d.ts.map +1 -0
  5. package/dist/builder/assemble.js +86 -0
  6. package/dist/builder/assemble.js.map +1 -0
  7. package/dist/builder/condition-tree.d.ts +27 -0
  8. package/dist/builder/condition-tree.d.ts.map +1 -0
  9. package/dist/builder/condition-tree.js +91 -0
  10. package/dist/builder/condition-tree.js.map +1 -0
  11. package/dist/builder/conditional-sql.d.ts +80 -0
  12. package/dist/builder/conditional-sql.d.ts.map +1 -0
  13. package/dist/builder/conditional-sql.js +88 -0
  14. package/dist/builder/conditional-sql.js.map +1 -0
  15. package/dist/builder/db.d.ts +76 -0
  16. package/dist/builder/db.d.ts.map +1 -0
  17. package/dist/builder/db.js +12 -0
  18. package/dist/builder/db.js.map +1 -0
  19. package/dist/builder/delete.d.ts +39 -0
  20. package/dist/builder/delete.d.ts.map +1 -0
  21. package/dist/builder/delete.js +33 -0
  22. package/dist/builder/delete.js.map +1 -0
  23. package/dist/builder/extract-params.d.ts +97 -0
  24. package/dist/builder/extract-params.d.ts.map +1 -0
  25. package/dist/builder/extract-params.js +2 -0
  26. package/dist/builder/extract-params.js.map +1 -0
  27. package/dist/builder/index.d.ts +23 -0
  28. package/dist/builder/index.d.ts.map +1 -0
  29. package/dist/builder/index.js +14 -0
  30. package/dist/builder/index.js.map +1 -0
  31. package/dist/builder/insert.d.ts +51 -0
  32. package/dist/builder/insert.d.ts.map +1 -0
  33. package/dist/builder/insert.js +39 -0
  34. package/dist/builder/insert.js.map +1 -0
  35. package/dist/builder/mutate.d.ts +28 -0
  36. package/dist/builder/mutate.d.ts.map +1 -0
  37. package/dist/builder/mutate.js +17 -0
  38. package/dist/builder/mutate.js.map +1 -0
  39. package/dist/builder/params.d.ts +22 -0
  40. package/dist/builder/params.d.ts.map +1 -0
  41. package/dist/builder/params.js +65 -0
  42. package/dist/builder/params.js.map +1 -0
  43. package/dist/builder/return-type.d.ts +39 -0
  44. package/dist/builder/return-type.d.ts.map +1 -0
  45. package/dist/builder/return-type.js +2 -0
  46. package/dist/builder/return-type.js.map +1 -0
  47. package/dist/builder/scanner.d.ts +49 -0
  48. package/dist/builder/scanner.d.ts.map +1 -0
  49. package/dist/builder/scanner.js +240 -0
  50. package/dist/builder/scanner.js.map +1 -0
  51. package/dist/builder/select.d.ts +76 -0
  52. package/dist/builder/select.d.ts.map +1 -0
  53. package/dist/builder/select.js +240 -0
  54. package/dist/builder/select.js.map +1 -0
  55. package/dist/builder/sql-tag.d.ts +319 -0
  56. package/dist/builder/sql-tag.d.ts.map +1 -0
  57. package/dist/builder/sql-tag.js +3 -0
  58. package/dist/builder/sql-tag.js.map +1 -0
  59. package/dist/builder/sql.d.ts +17 -0
  60. package/dist/builder/sql.d.ts.map +1 -0
  61. package/dist/builder/sql.js +36 -0
  62. package/dist/builder/sql.js.map +1 -0
  63. package/dist/builder/state.d.ts +53 -0
  64. package/dist/builder/state.d.ts.map +1 -0
  65. package/dist/builder/state.js +18 -0
  66. package/dist/builder/state.js.map +1 -0
  67. package/dist/builder/update.d.ts +60 -0
  68. package/dist/builder/update.d.ts.map +1 -0
  69. package/dist/builder/update.js +40 -0
  70. package/dist/builder/update.js.map +1 -0
  71. package/dist/builder/write-assemble.d.ts +5 -0
  72. package/dist/builder/write-assemble.d.ts.map +1 -0
  73. package/dist/builder/write-assemble.js +57 -0
  74. package/dist/builder/write-assemble.js.map +1 -0
  75. package/dist/builder/write-state.d.ts +39 -0
  76. package/dist/builder/write-state.d.ts.map +1 -0
  77. package/dist/builder/write-state.js +6 -0
  78. package/dist/builder/write-state.js.map +1 -0
  79. package/dist/builder/write-tag.d.ts +91 -0
  80. package/dist/builder/write-tag.d.ts.map +1 -0
  81. package/dist/builder/write-tag.js +2 -0
  82. package/dist/builder/write-tag.js.map +1 -0
  83. package/dist/columns.d.ts +33 -0
  84. package/dist/columns.d.ts.map +1 -0
  85. package/dist/columns.js +2 -0
  86. package/dist/columns.js.map +1 -0
  87. package/dist/expressions.d.ts +71 -0
  88. package/dist/expressions.d.ts.map +1 -0
  89. package/dist/expressions.js +2 -0
  90. package/dist/expressions.js.map +1 -0
  91. package/dist/index.d.ts +22 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +5 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/parsing/extract.d.ts +47 -0
  96. package/dist/parsing/extract.d.ts.map +1 -0
  97. package/dist/parsing/extract.js +2 -0
  98. package/dist/parsing/extract.js.map +1 -0
  99. package/dist/parsing/normalize.d.ts +44 -0
  100. package/dist/parsing/normalize.d.ts.map +1 -0
  101. package/dist/parsing/normalize.js +2 -0
  102. package/dist/parsing/normalize.js.map +1 -0
  103. package/dist/parsing/pg-literals.d.ts +37 -0
  104. package/dist/parsing/pg-literals.d.ts.map +1 -0
  105. package/dist/parsing/pg-literals.js +2 -0
  106. package/dist/parsing/pg-literals.js.map +1 -0
  107. package/dist/parsing/split.d.ts +100 -0
  108. package/dist/parsing/split.d.ts.map +1 -0
  109. package/dist/parsing/split.js +2 -0
  110. package/dist/parsing/split.js.map +1 -0
  111. package/dist/parsing/string-utils.d.ts +29 -0
  112. package/dist/parsing/string-utils.d.ts.map +1 -0
  113. package/dist/parsing/string-utils.js +2 -0
  114. package/dist/parsing/string-utils.js.map +1 -0
  115. package/dist/parsing/tokenize.d.ts +27 -0
  116. package/dist/parsing/tokenize.d.ts.map +1 -0
  117. package/dist/parsing/tokenize.js +2 -0
  118. package/dist/parsing/tokenize.js.map +1 -0
  119. package/dist/parsing.d.ts +7 -0
  120. package/dist/parsing.d.ts.map +1 -0
  121. package/dist/parsing.js +9 -0
  122. package/dist/parsing.js.map +1 -0
  123. package/dist/partial.d.ts +30 -0
  124. package/dist/partial.d.ts.map +1 -0
  125. package/dist/partial.js +10 -0
  126. package/dist/partial.js.map +1 -0
  127. package/dist/schema.d.ts +28 -0
  128. package/dist/schema.d.ts.map +1 -0
  129. package/dist/schema.js +2 -0
  130. package/dist/schema.js.map +1 -0
  131. package/dist/tables.d.ts +34 -0
  132. package/dist/tables.d.ts.map +1 -0
  133. package/dist/tables.js +2 -0
  134. package/dist/tables.js.map +1 -0
  135. package/dist/utils.d.ts +13 -0
  136. package/dist/utils.d.ts.map +1 -0
  137. package/dist/utils.js +3 -0
  138. package/dist/utils.js.map +1 -0
  139. package/dist/validation/cte.d.ts +54 -0
  140. package/dist/validation/cte.d.ts.map +1 -0
  141. package/dist/validation/cte.js +2 -0
  142. package/dist/validation/cte.js.map +1 -0
  143. package/dist/validation/dispatch.d.ts +31 -0
  144. package/dist/validation/dispatch.d.ts.map +1 -0
  145. package/dist/validation/dispatch.js +2 -0
  146. package/dist/validation/dispatch.js.map +1 -0
  147. package/dist/validation/joins.d.ts +16 -0
  148. package/dist/validation/joins.d.ts.map +1 -0
  149. package/dist/validation/joins.js +2 -0
  150. package/dist/validation/joins.js.map +1 -0
  151. package/dist/validation/return-derived.d.ts +67 -0
  152. package/dist/validation/return-derived.d.ts.map +1 -0
  153. package/dist/validation/return-derived.js +5 -0
  154. package/dist/validation/return-derived.js.map +1 -0
  155. package/dist/validation/return-types.d.ts +41 -0
  156. package/dist/validation/return-types.d.ts.map +1 -0
  157. package/dist/validation/return-types.js +2 -0
  158. package/dist/validation/return-types.js.map +1 -0
  159. package/dist/validation/validate-columns.d.ts +63 -0
  160. package/dist/validation/validate-columns.d.ts.map +1 -0
  161. package/dist/validation/validate-columns.js +2 -0
  162. package/dist/validation/validate-columns.js.map +1 -0
  163. package/dist/validation.d.ts +7 -0
  164. package/dist/validation.d.ts.map +1 -0
  165. package/dist/validation.js +9 -0
  166. package/dist/validation.js.map +1 -0
  167. package/package.json +64 -0
  168. package/src/builder/assemble.ts +100 -0
  169. package/src/builder/condition-tree.ts +162 -0
  170. package/src/builder/conditional-sql.ts +325 -0
  171. package/src/builder/db.ts +281 -0
  172. package/src/builder/delete.ts +57 -0
  173. package/src/builder/extract-params.ts +507 -0
  174. package/src/builder/index.ts +58 -0
  175. package/src/builder/insert.ts +75 -0
  176. package/src/builder/mutate.ts +55 -0
  177. package/src/builder/params.ts +95 -0
  178. package/src/builder/return-type.ts +66 -0
  179. package/src/builder/scanner.ts +254 -0
  180. package/src/builder/select.ts +470 -0
  181. package/src/builder/sql-tag.ts +422 -0
  182. package/src/builder/sql.ts +51 -0
  183. package/src/builder/state.ts +55 -0
  184. package/src/builder/update.ts +77 -0
  185. package/src/builder/write-assemble.ts +52 -0
  186. package/src/builder/write-state.ts +43 -0
  187. package/src/builder/write-tag.ts +119 -0
  188. package/src/columns.ts +336 -0
  189. package/src/expressions.ts +745 -0
  190. package/src/index.ts +81 -0
  191. package/src/parsing/extract.ts +260 -0
  192. package/src/parsing/normalize.ts +243 -0
  193. package/src/parsing/pg-literals.ts +289 -0
  194. package/src/parsing/split.ts +288 -0
  195. package/src/parsing/string-utils.ts +172 -0
  196. package/src/parsing/tokenize.ts +321 -0
  197. package/src/parsing.ts +8 -0
  198. package/src/partial.ts +241 -0
  199. package/src/schema.ts +130 -0
  200. package/src/tables.ts +278 -0
  201. package/src/utils.ts +43 -0
  202. package/src/validation/cte.ts +198 -0
  203. package/src/validation/dispatch.ts +312 -0
  204. package/src/validation/joins.ts +198 -0
  205. package/src/validation/return-derived.ts +253 -0
  206. package/src/validation/return-types.ts +271 -0
  207. package/src/validation/validate-columns.ts +489 -0
  208. package/src/validation.ts +8 -0
@@ -0,0 +1,489 @@
1
+ // Table/column existence + qualified/unqualified ref + scope-shape validation.
2
+ import type { AliasesInQuery, InsertTargetTable, TableKeyValid, TablesInQuery, UpdateTargetTable } from "../tables.js";
3
+ import type { AllTrue, And, StartsWith } from "../utils.js";
4
+ import type { CleanIdent, DQuoteSpaceSentinel, ExceedsLengthBudget, ExtractAliasResult, ExtractBefore, ExtractConflictColumns, ExtractConflictUpdateExcludedCols, ExtractConflictUpdateSetColumns, ExtractInsertColumns, ExtractLastWhere, ExtractReturningList, ExtractSelectList, ExtractUpdateSetColumns, ReplaceAll, SplitSelectList, StripSubqueries, TokenizeLoose, Trim } from "../parsing.js";
5
+ import type { ColumnRefValidLooseWith, IsSimpleRefPart, QualifiedColumnRefs, ResolveAlias, TableKeysByName, UnqualifiedColumnRefs, UnqualifiedColumnValid } from "../columns.js";
6
+ import type { ColumnsExistInTable, RefScanBeforeOrderBy, RefScanOrderBy, RefScanSegment, SelectAliasesInQuery, SelectAliasSet } from "./return-types.js";
7
+ import type { CteRow, SingleCteMatch } from "./cte.js";
8
+ import type { DatabaseSchema } from "../schema.js";
9
+ import type { DerivedRenamedRow, DerivedTableMatch } from "./return-derived.js";
10
+ import type { ExprsValidList } from "../expressions.js";
11
+ import type { HasReturning, QueryKind, ValidateSQLNormalized } from "./dispatch.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // CTE / derived-table VALIDATION (scope surface).
15
+ //
16
+ // A CTE or derived table exposes ONLY its projected output row to the outer
17
+ // query — never the base tables in its body. Result inference already computes
18
+ // that row (`CteRow` / `DerivedSubRow`, including `t(a,b)` renaming); validation
19
+ // reuses it. A single-CTE / leading-derived SELECT is valid when:
20
+ // 1. the body is itself a valid query, AND
21
+ // 2. every outer projection references a column the body actually exposes
22
+ // (qualified by the CTE/derived relation name, or unqualified).
23
+ // This both ACCEPTS the projected/renamed reads and REJECTS reads of a column
24
+ // the body did not project (or whose name a `t(cols)` list renamed away).
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export type ValidateCteShape<N extends string, S extends DatabaseSchema> =
28
+ SingleCteMatch<N> extends {
29
+ body: infer Body extends string;
30
+ outer: infer Outer extends string;
31
+ name: infer Name extends string;
32
+ cols: infer Cols extends string[];
33
+ }
34
+ ? CteRow<Body, Cols, S> extends infer Row
35
+ ? And<
36
+ ValidateSQLNormalized<Body, S>,
37
+ OuterProjectionInRow<SplitSelectList<ExtractSelectList<Outer>>, Name, Row, S>,
38
+ // Outer WHERE refs also see only the CTE's exposed row.
39
+ OuterWhereRefsInRow<Outer, Name, Row, S>,
40
+ // ORDER BY / GROUP BY / HAVING are outer clauses too.
41
+ OuterTailClauseRefsInRow<Outer, Name, Row, S>,
42
+ true
43
+ >
44
+ : true
45
+ : true;
46
+
47
+ export type ValidateDerivedShape<N extends string, S extends DatabaseSchema> =
48
+ DerivedTableMatch<N> extends { body: infer Body extends string; alias: infer DAlias extends string; cols: infer Cols extends string[] }
49
+ ? DerivedRenamedRow<Body, Cols, S> extends infer Row
50
+ ? And<
51
+ ValidateSQLNormalized<Body, S>,
52
+ OuterProjectionInRow<SplitSelectList<ExtractSelectList<N>>, DAlias, Row, S>,
53
+ // Outer WHERE refs see only the derived row; the body (and its own
54
+ // WHERE) is stripped first and validated by the recursive body check.
55
+ OuterWhereRefsInRow<N, DAlias, Row, S>,
56
+ // ORDER BY / GROUP BY / HAVING are outer clauses too.
57
+ OuterTailClauseRefsInRow<N, DAlias, Row, S>,
58
+ true
59
+ >
60
+ : true
61
+ : true;
62
+
63
+ // Every projected expression of the outer query must reference a column the
64
+ // derived/CTE relation exposes. Functions, literals, `*`, and `<rel>.*` are
65
+ // left unchecked (always valid); only plain column reads are constrained.
66
+ export type OuterProjectionInRow<Exprs extends string[], Name extends string, Row, S extends DatabaseSchema> =
67
+ AllTrue<
68
+ Exprs[number] extends infer E
69
+ ? E extends string
70
+ ? OuterProjInRow<E, Name, Row, S>
71
+ : true
72
+ : true
73
+ >;
74
+
75
+ export type OuterProjInRow<E extends string, Name extends string, Row, S extends DatabaseSchema> =
76
+ ExtractAliasResult<E> extends { expr: infer Raw extends string }
77
+ ? ProjRefInRow<Trim<Raw>, Name, Row, S>
78
+ : ProjRefInRow<Trim<E>, Name, Row, S>;
79
+
80
+ export type ProjRefInRow<Raw extends string, Name extends string, Row, S extends DatabaseSchema> =
81
+ Raw extends "*" ? true :
82
+ Raw extends `${Name}.*` ? true :
83
+ Raw extends `${number}` ? true :
84
+ Raw extends `'${string}` ? true :
85
+ Raw extends `"${string}` ? true :
86
+ // A call / parenthesised expression (`upper(status)`, `upper(dt.status)`):
87
+ // the wrapper must not smuggle in a column the relation never exposed, so
88
+ // validate the refs INSIDE the parens against the exposed row rather than
89
+ // accepting the whole expression as a non-simple ref.
90
+ Raw extends `${string}(${infer Inner})${string}`
91
+ ? SegRefsInRow<Inner, Name, Row, S>
92
+ : Raw extends `${infer Q}.${infer Col}`
93
+ ? IsSimpleRefPart<Q> extends true
94
+ ? IsSimpleRefPart<Col> extends true
95
+ ? CleanIdent<Q> extends Name
96
+ ? KeyInRow<CleanIdent<Col>, Row>
97
+ : false
98
+ : true
99
+ : true
100
+ : IsSimpleRefPart<Raw> extends true
101
+ ? KeyInRow<CleanIdent<Raw>, Row>
102
+ : true;
103
+
104
+ export type KeyInRow<K extends string, Row> = [K] extends [keyof Row] ? true : false;
105
+
106
+ // Validate every column-reference candidate in an arbitrary text segment (an
107
+ // outer WHERE predicate, or a function call's argument list) against the
108
+ // CTE/derived relation's exposed `Row`. Reuses the same token walkers the core
109
+ // validator uses to surface refs, then checks each via `ProjRefInRow` (qualifier
110
+ // must equal the relation `Name`, column must be a key of `Row`). `Tables`/
111
+ // `Aliases` are `never`: the walkers only consult them to EXCLUDE table/alias
112
+ // tokens, and here the relation name is already excluded by its `from`/`join`/`)`
113
+ // predecessor and output aliases by the walker's `Prev extends "as"` guard. An
114
+ // empty/whitespace segment yields no refs → `true` (no-op).
115
+ export type SegRefsInRow<Seg extends string, Name extends string, Row, S extends DatabaseSchema> =
116
+ Trim<Seg> extends ""
117
+ ? true
118
+ : TokenizeLoose<Seg> extends infer Toks extends string[]
119
+ ? And<
120
+ AllTrue<
121
+ QualifiedColumnRefs<Toks, S, never, never> extends infer R
122
+ ? R extends string ? ProjRefInRow<R, Name, Row, S> : true
123
+ : true
124
+ >,
125
+ AllTrue<
126
+ UnqualifiedColumnRefs<Toks, S, never, never> extends infer R
127
+ ? R extends string ? ProjRefInRow<R, Name, Row, S> : true
128
+ : true
129
+ >,
130
+ true,
131
+ true
132
+ >
133
+ : true;
134
+
135
+ // The outer query's WHERE predicate, scoped to the CTE/derived relation's exposed
136
+ // row. Strip subquery bodies FIRST so the derived/CTE body's own WHERE is never
137
+ // scanned here (it is validated recursively), then scan ONLY when a real outer
138
+ // WHERE survives — `ExtractLastWhere` returns the whole string when there is no
139
+ // ` where `, so the explicit guard keeps the no-WHERE case a true no-op.
140
+ export type OuterWhereRefsInRow<OuterText extends string, Name extends string, Row, S extends DatabaseSchema> =
141
+ StripSubqueries<OuterText> extends infer Stripped extends string
142
+ ? Stripped extends `${string} where ${string}`
143
+ ? SegRefsInRow<ExtractLastWhere<Stripped>, Name, Row, S>
144
+ : true
145
+ : true;
146
+
147
+ // ORDER BY / GROUP BY / HAVING are outer clauses with the same exposed-row scope
148
+ // as WHERE: they may read only the columns the CTE/derived relation projected,
149
+ // never an unprojected base-table column from its body. Subquery bodies are
150
+ // stripped first (a derived body's own trailing clauses live inside its parens),
151
+ // then each clause's expression segment is scanned against the exposed `Row`.
152
+ // Output aliases from the outer SELECT list are blessed — Postgres permits an
153
+ // output column name in GROUP BY / ORDER BY — so referencing one is never a
154
+ // false rejection (HAVING is treated leniently the same way).
155
+ export type OuterTailClauseRefsInRow<OuterText extends string, Name extends string, Row, S extends DatabaseSchema> =
156
+ StripSubqueries<OuterText> extends infer Stripped extends string
157
+ ? SelectAliasesInQuery<Stripped> extends infer SelAliases extends string
158
+ ? And<
159
+ SegRefsInRowWithAliases<ExtractGroupByExpr<Stripped>, Name, Row, S, SelAliases>,
160
+ SegRefsInRowWithAliases<ExtractHavingExpr<Stripped>, Name, Row, S, SelAliases>,
161
+ SegRefsInRowWithAliases<ExtractOrderByExpr<Stripped>, Name, Row, S, SelAliases>,
162
+ true,
163
+ true
164
+ >
165
+ : true
166
+ : true;
167
+
168
+ // Trailing-clause expression extractors. Each yields the clause's expression
169
+ // text bounded by the clauses that may follow it (clause order is
170
+ // GROUP BY → HAVING → ORDER BY → LIMIT/OFFSET/UNION), or "" when absent.
171
+ type StopAtLimitTail<S extends string> =
172
+ ExtractBefore<ExtractBefore<ExtractBefore<S, " limit ">, " offset ">, " union ">;
173
+
174
+ type ExtractGroupByExpr<S extends string> =
175
+ S extends `${string} group by ${infer R}`
176
+ ? StopAtLimitTail<ExtractBefore<ExtractBefore<R, " having ">, " order by ">>
177
+ : "";
178
+
179
+ type ExtractHavingExpr<S extends string> =
180
+ S extends `${string} having ${infer R}`
181
+ ? StopAtLimitTail<ExtractBefore<R, " order by ">>
182
+ : "";
183
+
184
+ type ExtractOrderByExpr<S extends string> =
185
+ S extends `${string} order by ${infer R}`
186
+ ? StopAtLimitTail<R>
187
+ : "";
188
+
189
+ // As `SegRefsInRow`, but additionally blesses a set of outer SELECT-list output
190
+ // aliases (an unqualified ref equal to one is accepted without consulting `Row`).
191
+ export type SegRefsInRowWithAliases<Seg extends string, Name extends string, Row, S extends DatabaseSchema, SelAliases extends string> =
192
+ Trim<Seg> extends ""
193
+ ? true
194
+ : TokenizeLoose<Seg> extends infer Toks extends string[]
195
+ ? And<
196
+ AllTrue<
197
+ QualifiedColumnRefs<Toks, S, never, never> extends infer R
198
+ ? R extends string ? RefInRowOrAlias<R, Name, Row, S, SelAliases> : true
199
+ : true
200
+ >,
201
+ AllTrue<
202
+ UnqualifiedColumnRefs<Toks, S, never, never> extends infer R
203
+ ? R extends string ? RefInRowOrAlias<R, Name, Row, S, SelAliases> : true
204
+ : true
205
+ >,
206
+ true,
207
+ true
208
+ >
209
+ : true;
210
+
211
+ export type RefInRowOrAlias<R extends string, Name extends string, Row, S extends DatabaseSchema, SelAliases extends string> =
212
+ [SelAliases] extends [never]
213
+ ? ProjRefInRow<R, Name, Row, S>
214
+ : CleanIdent<R> extends SelAliases
215
+ ? true
216
+ : ProjRefInRow<R, Name, Row, S>;
217
+
218
+
219
+ // Table and alias extraction
220
+
221
+ export type AllTablesValid<N extends string, S extends DatabaseSchema> =
222
+ TablesInQuery<N, S> extends infer Tables extends string
223
+ ? AllTablesValidFor<Tables, S>
224
+ : true;
225
+
226
+ export type AllTablesValidFor<Tables extends string, S extends DatabaseSchema> =
227
+ AllTrue<Tables extends string ? TableKeyValid<Tables, S> : true>;
228
+
229
+ // Column validation
230
+
231
+ export type AllColumnsValid<N extends string, S extends DatabaseSchema> =
232
+ TablesInQuery<N, S> extends infer Tables extends string
233
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
234
+ ? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
235
+ ? AllColumnsValidFor<N, S, Tables, Aliases, LooseTokens>
236
+ : false
237
+ : false
238
+ : false;
239
+
240
+ export type AllColumnsValidFor<
241
+ N extends string,
242
+ S extends DatabaseSchema,
243
+ Tables extends string,
244
+ Aliases extends string,
245
+ LooseTokens extends string[]
246
+ > = SelectAliasSet<N> extends infer SelectAliases extends string
247
+ ? QueryKind<N> extends "update"
248
+ // A normal (non-high-complexity) UPDATE has no subquery/CASE SET, so its
249
+ // SET-RHS and top-level WHERE column refs are safe to scan the same way a
250
+ // SELECT's are. This catches invalid columns hidden in `SET x = bogus` or
251
+ // `WHERE bogus = 1` that the SET-target-only check (`ColumnsValidInUpdate`)
252
+ // misses. There are no SELECT-list aliases in an UPDATE, so `never`.
253
+ ? And<
254
+ ColumnsValidInUpdate<N, S>,
255
+ ColumnsValidInInsert<N, S>,
256
+ ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>,
257
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>,
258
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens, never>
259
+ >
260
+ : And<
261
+ ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>,
262
+ ColumnsValidInInsert<N, S>,
263
+ ColumnsValidInUpdate<N, S>,
264
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>,
265
+ // A SELECT-list alias is only resolvable in ORDER BY — NOT in
266
+ // WHERE/GROUP/HAVING. So the unqualified ref-scan blesses the alias
267
+ // set ONLY for the ORDER BY token slice; the FROM..(pre-order-by)
268
+ // slice (WHERE/GROUP/HAVING/JOIN-ON) is validated against real
269
+ // columns with `never` aliases. When there is no ORDER BY this is
270
+ // equivalent to the old single-pass `never`-alias scan.
271
+ SelectUnqualifiedRefsScoped<N, S, Tables, Aliases, SelectAliases>
272
+ >
273
+ : false;
274
+
275
+ // Validate the SELECT's unqualified column refs with ORDER-BY-scoped alias
276
+ // resolution. The pre-ORDER-BY ref segment (WHERE/GROUP/HAVING/ON) is validated
277
+ // against real columns only; the ORDER-BY segment additionally accepts the
278
+ // SELECT-list output aliases.
279
+ export type SelectUnqualifiedRefsScoped<
280
+ N extends string,
281
+ S extends DatabaseSchema,
282
+ Tables extends string,
283
+ Aliases extends string,
284
+ SelectAliases extends string
285
+ > =
286
+ [SelectAliases] extends [never]
287
+ ? UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanSegment<N>>, never>
288
+ : And<
289
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanBeforeOrderBy<N>>, never>,
290
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanOrderBy<N>>, SelectAliases>,
291
+ true,
292
+ true,
293
+ true
294
+ >;
295
+
296
+ export type ColumnsValidInSelectOrReturning<N extends string, S extends DatabaseSchema> =
297
+ TablesInQuery<N, S> extends infer Tables extends string
298
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
299
+ ? ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>
300
+ : true
301
+ : true;
302
+
303
+ export type ColumnsValidInSelectOrReturningFor<
304
+ N extends string,
305
+ S extends DatabaseSchema,
306
+ Tables extends string,
307
+ Aliases extends string
308
+ > =
309
+ HasReturning<N> extends true
310
+ ? [Tables] extends [never]
311
+ ? true
312
+ : ExprsValidList<SplitSelectList<ReplaceAll<ExtractReturningList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
313
+ : QueryKind<N> extends "select"
314
+ ? [Tables] extends [never]
315
+ ? true
316
+ // The projection list is extracted from the validation scan view,
317
+ // whose double-quoted-identifier spaces are masked with a sentinel.
318
+ // The alias set restores those spaces, so restore them here too or a
319
+ // qualifier through a space-bearing quoted alias (`"user alias".id`)
320
+ // would never match its registered alias (round-12 regression).
321
+ : ExprsValidList<SplitSelectList<ReplaceAll<ExtractSelectList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
322
+ : true;
323
+
324
+ // insert
325
+
326
+ export type ColumnsValidInInsert<N extends string, S extends DatabaseSchema> =
327
+ QueryKind<N> extends "insert"
328
+ ? InsertTargetTable<N, S> extends infer TableKey extends string
329
+ ? TableKey extends never
330
+ ? true
331
+ : And<
332
+ ColumnsExistInTable<ExtractInsertColumns<N>, TableKey, S>,
333
+ ColumnsExistInTable<ExtractConflictColumns<N>, TableKey, S>,
334
+ ColumnsExistInTable<ExtractConflictUpdateSetColumns<N>, TableKey, S>,
335
+ // RHS `excluded.<col>` refs must name a column of the target
336
+ // table (the `excluded` pseudo-row mirrors it).
337
+ ColumnsExistInTable<ExtractConflictUpdateExcludedCols<N>, TableKey, S>,
338
+ true
339
+ >
340
+ : true
341
+ : true;
342
+
343
+ // update
344
+
345
+ export type ColumnsValidInUpdate<N extends string, S extends DatabaseSchema> =
346
+ QueryKind<N> extends "update"
347
+ ? UpdateTargetTable<N, S> extends infer TableKey extends string
348
+ ? TableKey extends never
349
+ ? true
350
+ : ColumnsExistInTable<ExtractUpdateSetColumns<N>, TableKey, S>
351
+ : true
352
+ : true;
353
+
354
+ // qualified refs across query (best-effort)
355
+
356
+ export type QualifiedColumnRefsValid<N extends string, S extends DatabaseSchema> =
357
+ TablesInQuery<N, S> extends infer Tables extends string
358
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
359
+ ? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
360
+ ? QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>
361
+ : true
362
+ : true
363
+ : true;
364
+
365
+ export type QualifiedColumnRefsValidFor<
366
+ N extends string,
367
+ S extends DatabaseSchema,
368
+ Tables extends string,
369
+ Aliases extends string,
370
+ LooseTokens extends string[]
371
+ > = QualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
372
+ ? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
373
+ : true;
374
+
375
+ // Once a table is given a range alias (`FROM products p`), PostgreSQL hides the
376
+ // original table name as a correlation name for that query level — `products.id`
377
+ // is then invalid; only `p.id` works. The lenient qualified-ref check accepts
378
+ // the base-name qualifier (it still resolves to a real column), so reject it
379
+ // explicitly: any qualified ref whose qualifier is the BASE NAME of a table that
380
+ // carries a range alias (and is not itself an alias) is invalid. Scans the whole
381
+ // query (the offending ref can sit in the SELECT list, not just the ref segment).
382
+ export type AliasedTableKeys<Aliases extends string> =
383
+ Aliases extends `${string}=>${infer T}` ? T : never;
384
+
385
+ export type QualifierShadowedByAlias<
386
+ Q extends string,
387
+ Tables extends string,
388
+ Aliases extends string,
389
+ S extends DatabaseSchema
390
+ > =
391
+ [ResolveAlias<CleanIdent<Q>, Aliases>] extends [never]
392
+ ? [Extract<TableKeysByName<CleanIdent<Q>, Tables>, AliasedTableKeys<Aliases>>] extends [never]
393
+ ? false
394
+ : true
395
+ : false;
396
+
397
+ export type NoAliasShadowedQualifiers<
398
+ N extends string,
399
+ S extends DatabaseSchema,
400
+ Tables extends string,
401
+ Aliases extends string
402
+ > =
403
+ [AliasedTableKeys<Aliases>] extends [never]
404
+ ? true
405
+ : AllTrue<
406
+ QualifiedColumnRefs<TokenizeLoose<N>, S, Tables, Aliases> extends infer R
407
+ ? R extends `${infer Q}.${string}`
408
+ ? QualifierShadowedByAlias<Q, Tables, Aliases, S> extends true
409
+ ? false
410
+ : true
411
+ : true
412
+ : true
413
+ >;
414
+
415
+ // A table introduced INSIDE a subquery is in scope only there — it must not
416
+ // satisfy an UNQUALIFIED column reference in the OUTER query. The whole-query
417
+ // table scan flattens every relation into one set, so an outer `email` resolves
418
+ // against a `users` table that only exists inside an `EXISTS (...)`. Re-validate
419
+ // the outer scope in isolation: strip every parenthesised group (excising
420
+ // subquery bodies), collect only the depth-0 FROM/JOIN relations, and require
421
+ // each outer unqualified ref to resolve against THOSE. Correlated subquery refs
422
+ // live inside the stripped parens, so they are never checked against the outer
423
+ // tables here (the core whole-query scan still validates them). Gated to plain
424
+ // SELECTs whose FROM clause has no derived/subquery source, and skipped for
425
+ // report-scale queries to bound the char-walk.
426
+ export type OuterScopeUnqualifiedValid<N extends string, S extends DatabaseSchema> =
427
+ StartsWith<N, "select "> extends true
428
+ // Cheap pre-gate: `StripSubqueries` only excises parens that contain a
429
+ // `select`, so unless the query has a `(` followed somewhere by `select`
430
+ // (a parenthesised subquery), the body validates the whole query — already
431
+ // done by the core scan — and is necessarily `true`. Tightening the old
432
+ // "any `(`" gate skips `StripSubqueries` on queries whose only parens are
433
+ // function/grouping. A subquery's `select` is always preceded by its own
434
+ // `(`, so this superset never misses a real scope leak (incl. `( select`).
435
+ ? Lowercase<N> extends `${string}(${string}select${string}`
436
+ ? ExceedsLengthBudget<N> extends true
437
+ ? true
438
+ : StripSubqueries<N> extends infer Stripped extends string
439
+ ? TablesInQuery<Stripped, S> extends infer OT extends string
440
+ ? [OT] extends [never]
441
+ ? true
442
+ // Only trust the depth-0 view when every relation it
443
+ // recovers is a real base table — a JOINed derived
444
+ // source survives stripping as a bare alias token and
445
+ // would otherwise look like a missing table.
446
+ : AllTablesValidFor<OT, S> extends true
447
+ ? AliasesInQuery<Stripped, S> extends infer OA extends string
448
+ ? UnqualifiedColumnRefsValidFor<
449
+ Stripped,
450
+ S,
451
+ OT,
452
+ OA,
453
+ TokenizeLoose<RefScanSegment<Stripped>>,
454
+ SelectAliasesInQuery<Stripped>
455
+ >
456
+ : true
457
+ : true
458
+ : true
459
+ : true
460
+ : true
461
+ : true;
462
+
463
+ export type UnqualifiedColumnRefsValid<N extends string, S extends DatabaseSchema> =
464
+ TablesInQuery<N, S> extends infer Tables extends string
465
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
466
+ ? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
467
+ ? SelectAliasesInQuery<N> extends infer SelectAliases extends string
468
+ ? UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens, SelectAliases>
469
+ : true
470
+ : true
471
+ : true
472
+ : true;
473
+
474
+ export type UnqualifiedColumnRefsValidFor<
475
+ N extends string,
476
+ S extends DatabaseSchema,
477
+ Tables extends string,
478
+ Aliases extends string,
479
+ LooseTokens extends string[],
480
+ SelectAliases extends string
481
+ > = UnqualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
482
+ ? AllTrue<
483
+ Cols extends string
484
+ ? CleanIdent<Cols> extends SelectAliases
485
+ ? true
486
+ : UnqualifiedColumnValid<Cols, Tables, Aliases, S>
487
+ : true
488
+ >
489
+ : true;
@@ -0,0 +1,8 @@
1
+ // Barrel for type-level SQL validation + result inference. Real declarations
2
+ // live in ./validation/*. Importers keep using `from "./validation.js"`.
3
+ export * from "./validation/dispatch.js";
4
+ export * from "./validation/joins.js";
5
+ export * from "./validation/return-types.js";
6
+ export * from "./validation/return-derived.js";
7
+ export * from "./validation/cte.js";
8
+ export * from "./validation/validate-columns.js";