@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,198 @@
1
+ // JOIN USING / window-filter / DISTINCT ON column validation.
2
+ import type { AllTrue, And, IsUnion } from "../utils.js";
3
+ import type { CleanIdent, ExtractCallParenBodies, SplitCommaSimple, TokenizeLoose, Trim } from "../parsing.js";
4
+ import type { ColumnExists, DatabaseSchema } from "../schema.js";
5
+ import type { QualifiedColumnRefsValidFor, UnqualifiedColumnRefsValidFor } from "./validate-columns.js";
6
+ import type { QueryKind } from "./dispatch.js";
7
+ import type { TableKeyFromToken, TableKeyValid } from "../tables.js";
8
+ import type { TablesWithColumn } from "../columns.js";
9
+ // `JOIN ... USING (col)` requires `col` to exist on BOTH joined tables. The
10
+ // loose ref-scan only checks that an unqualified column resolves to SOME table,
11
+ // so a column present on just one side (e.g. `users JOIN orders USING (user_id)`
12
+ // where only `orders` has `user_id`) is wrongly accepted. Surface every
13
+ // ` using (cols)` body (spaced and no-space) and require each listed column to
14
+ // exist on at least TWO of the query's tables — a cheap proxy for "both sides of
15
+ // the join" that is correct for the common single-USING-join case and never
16
+ // false-rejects a column genuinely shared across the join. SELECT-only; a DELETE
17
+ // `... USING t` table source has no parenthesised column list so it never
18
+ // matches the ` using (` marker. A no-op (`true`) for queries without USING.
19
+ export type JoinUsingColsValid<
20
+ N extends string,
21
+ S extends DatabaseSchema,
22
+ Tables extends string
23
+ > =
24
+ QueryKind<N> extends "select"
25
+ // Cheap keyword pre-gate: skip the balanced-paren extraction walk entirely
26
+ // unless a ` using (` marker is actually present. `Lowercase<N>` only ever
27
+ // matches MORE strings than the lowercase-marker body, so a no-match here
28
+ // provably coincides with the `Trim<Seg> extends ""` -> `true` arm below.
29
+ ? Lowercase<N> extends `${string} using (${string}` | `${string} using(${string}`
30
+ ? `${ExtractCallParenBodies<N, " using (">} ${ExtractCallParenBodies<N, " using(">}` extends infer Seg extends string
31
+ ? Trim<Seg> extends ""
32
+ ? true
33
+ : And<
34
+ // Cheap "shared on >=2 tables" proxy (the left side approximation).
35
+ UsingColsInTwoTables<SplitCommaSimple<Seg>, Tables, S>,
36
+ // Precise right-side check: each USING column must exist on the
37
+ // specific table being joined, so an unrelated later table can no
38
+ // longer mask an invalid USING pair (adversarial round-9 J1).
39
+ JoinUsingRightSideValid<N, S>,
40
+ true,
41
+ true,
42
+ true
43
+ >
44
+ : true
45
+ : true
46
+ : true;
47
+
48
+ // Walk the query join-by-join, pairing each ` using (cols)` with the table named
49
+ // immediately before it (the join's RIGHT side) and requiring every listed column
50
+ // to exist on that table. When a ` join ` head is NOT immediately followed by its
51
+ // own ` using (` (the join source itself still contains a later ` join `), the
52
+ // USING belongs to a deeper join — skip this head and continue. A right side that
53
+ // resolves to something other than a real base table (alias / derived table) is
54
+ // left to the cheap shared-column proxy rather than risk a false reject.
55
+ export type JoinUsingRightSideValid<
56
+ S extends string,
57
+ Sch extends DatabaseSchema,
58
+ Steps extends any[] = []
59
+ > = Steps["length"] extends 40
60
+ ? true
61
+ : S extends `${infer _Head} join ${infer AfterJoin}`
62
+ ? AfterJoin extends `${infer Src} using (${infer Body})${infer Rest}`
63
+ ? Src extends `${string} join ${string}`
64
+ ? JoinUsingRightSideValid<AfterJoin, Sch, [any, ...Steps]>
65
+ : And<
66
+ UsingColsOnRightTable<SplitCommaSimple<Body>, JoinSrcFirstWord<Src>, Sch>,
67
+ JoinUsingRightSideValid<Rest, Sch, [any, ...Steps]>,
68
+ true, true, true
69
+ >
70
+ : AfterJoin extends `${infer Src2} using(${infer Body2})${infer Rest2}`
71
+ ? Src2 extends `${string} join ${string}`
72
+ ? JoinUsingRightSideValid<AfterJoin, Sch, [any, ...Steps]>
73
+ : And<
74
+ UsingColsOnRightTable<SplitCommaSimple<Body2>, JoinSrcFirstWord<Src2>, Sch>,
75
+ JoinUsingRightSideValid<Rest2, Sch, [any, ...Steps]>,
76
+ true, true, true
77
+ >
78
+ : JoinUsingRightSideValid<AfterJoin, Sch, [any, ...Steps]>
79
+ : true;
80
+
81
+ // The first whitespace-delimited token of a join source (`users`, `users u`,
82
+ // `public.users u` -> `users` / `public.users`). Left un-cleaned so
83
+ // `TableKeyFromToken` can parse a schema-qualified `schema.table` token.
84
+ export type JoinSrcFirstWord<Src extends string> =
85
+ Trim<Src> extends `${infer W} ${string}` ? W : Trim<Src>;
86
+
87
+ // Each USING column must exist on the joined (right-side) table. If the source
88
+ // token does not resolve to a real base table, defer to the shared-column proxy.
89
+ export type UsingColsOnRightTable<
90
+ Cols extends string[],
91
+ SrcWord extends string,
92
+ S extends DatabaseSchema
93
+ > = TableKeyFromToken<SrcWord, S> extends infer RK extends string
94
+ ? TableKeyValid<RK, S> extends true
95
+ ? AllTrue<
96
+ Cols[number] extends infer C extends string
97
+ ? CleanIdent<C> extends ""
98
+ ? true
99
+ : ColumnExists<RK, CleanIdent<C>, S>
100
+ : true
101
+ >
102
+ : true
103
+ : true;
104
+
105
+ export type UsingColsInTwoTables<
106
+ Cols extends string[],
107
+ Tables extends string,
108
+ S extends DatabaseSchema
109
+ > = AllTrue<
110
+ Cols[number] extends infer C extends string
111
+ ? CleanIdent<C> extends ""
112
+ ? true
113
+ : UsingColOnBothSides<CleanIdent<C>, Tables, S>
114
+ : true
115
+ >;
116
+
117
+ export type UsingColOnBothSides<
118
+ Col extends string,
119
+ Tables extends string,
120
+ S extends DatabaseSchema
121
+ > = TablesWithColumn<Tables, Col, S> extends infer Owners
122
+ ? [Owners] extends [never]
123
+ ? false
124
+ : IsUnion<Owners> extends true
125
+ ? true
126
+ : false
127
+ : false;
128
+
129
+ // Columns inside `over (...)` / `filter (...)` / `within group (...)` clauses
130
+ // live in the SELECT list (before the top-level FROM), so the from-FROM-onward
131
+ // loose ref-scan never sees them and the select-list treats `fn() over (...)` /
132
+ // `fn() within group (...)` as a plain function call. We surface those clause
133
+ // bodies explicitly and validate their column refs the same way as the rest of
134
+ // the query. `WITHIN GROUP (ORDER BY <expr>)` is the ordered-set aggregate's sort
135
+ // body — its columns must be validated like a window's. A no-op (`true`) for
136
+ // queries without these clauses.
137
+ export type WindowFilterColsValid<
138
+ N extends string,
139
+ S extends DatabaseSchema,
140
+ Tables extends string,
141
+ Aliases extends string
142
+ > =
143
+ // Cheap keyword pre-gate: only run the (six) balanced-paren extraction walks
144
+ // when an ` over (` / ` filter (` / ` within group (` marker is actually
145
+ // present. `Lowercase<N>` matches a superset of the body's lowercase markers,
146
+ // so a no-match coincides with the `Trim<Seg> extends ""` -> `true` arm.
147
+ Lowercase<N> extends
148
+ | `${string} over (${string}` | `${string} over(${string}`
149
+ | `${string} filter (${string}` | `${string} filter(${string}`
150
+ | `${string} within group (${string}` | `${string} within group(${string}`
151
+ ? `${ExtractCallParenBodies<N, " over (">} ${ExtractCallParenBodies<N, " over(">} ${ExtractCallParenBodies<N, " filter (">} ${ExtractCallParenBodies<N, " filter(">} ${ExtractCallParenBodies<N, " within group (">} ${ExtractCallParenBodies<N, " within group(">}` extends infer Seg extends string
152
+ ? Trim<Seg> extends ""
153
+ ? true
154
+ : TokenizeLoose<Seg> extends infer Toks extends string[]
155
+ ? And<
156
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks>,
157
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks, never>,
158
+ true,
159
+ true,
160
+ true
161
+ >
162
+ : true
163
+ : true
164
+ : true;
165
+
166
+ // `DISTINCT ON (exprs)` columns are part of the SELECT scope but `StripDistinct`
167
+ // removes the whole ON-list before the projection / FROM-onward ref-scan runs, so
168
+ // `SELECT DISTINCT ON (bogus_col) id ...` escaped validation entirely. Surface the
169
+ // ON-list body explicitly (spaced and no-space variants) and validate its column
170
+ // refs against the query's tables/aliases exactly like `WindowFilterColsValid`.
171
+ // SELECT-only; a no-op (`true`) for queries without DISTINCT ON.
172
+ export type DistinctOnColsValid<
173
+ N extends string,
174
+ S extends DatabaseSchema,
175
+ Tables extends string,
176
+ Aliases extends string
177
+ > =
178
+ QueryKind<N> extends "select"
179
+ // Cheap keyword pre-gate: skip the extraction walk unless a ` distinct on (`
180
+ // marker is present. Superset match -> no-match coincides with the empty-Seg
181
+ // `true` arm.
182
+ ? Lowercase<N> extends `${string} distinct on (${string}` | `${string} distinct on(${string}`
183
+ ? `${ExtractCallParenBodies<N, " distinct on (">} ${ExtractCallParenBodies<N, " distinct on(">}` extends infer Seg extends string
184
+ ? Trim<Seg> extends ""
185
+ ? true
186
+ : TokenizeLoose<Seg> extends infer Toks extends string[]
187
+ ? And<
188
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks>,
189
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks, never>,
190
+ true,
191
+ true,
192
+ true
193
+ >
194
+ : true
195
+ : true
196
+ : true
197
+ : true;
198
+
@@ -0,0 +1,253 @@
1
+ // Derived-table & JOIN LATERAL / joined-derived result inference.
2
+ import type { AliasesInQuery, TablesInQuery } from "../tables.js";
3
+ import type { ApplyJoinNull, OuterCastTs, RefQualifier } from "../expressions.js";
4
+ import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, SplitBalancedParen, SplitCommaSimple, SplitSelectList, Trim, TrimLeft } from "../parsing.js";
5
+ import type { DatabaseSchema } from "../schema.js";
6
+ import type { MergeRow, SelectReturnWith } from "./return-types.js";
7
+ import type { Simplify, UnionToIntersection } from "../utils.js";
8
+ // The outer columns come from the subquery's projection rather than a real
9
+ // table, so the normal table/alias machinery yields `never`. Detect a single
10
+ // derived-table FROM, compute the subquery's row type, and resolve the outer
11
+ // select list against it.
12
+
13
+ export type DerivedTableMatch<N extends string> =
14
+ Trim<ExtractFromClause<N>> extends `(${string}`
15
+ ? SplitBalancedParen<Trim<ExtractFromClause<N>>> extends { inner: infer Body extends string; rest: infer Rest extends string }
16
+ ? Trim<Body> extends `select ${string}`
17
+ ? { body: Trim<Body>; alias: DerivedAliasName<Trim<Rest>>; cols: DerivedColsList<Trim<Rest>> }
18
+ : never
19
+ : never
20
+ : never;
21
+
22
+ export type DerivedAliasName<S extends string> =
23
+ Trim<S> extends `as ${infer R}` ? DerivedFirstWord<Trim<R>> : DerivedFirstWord<Trim<S>>;
24
+
25
+ // First token after the subquery paren — the alias name. Breaks on a space OR on
26
+ // a `(` so a column-alias list (`p(c1, c2)`) doesn't bleed into the alias.
27
+ export type DerivedFirstWord<S extends string> =
28
+ S extends `${infer W} ${string}`
29
+ ? DerivedFirstWord<W>
30
+ : S extends `${infer W}(${string}`
31
+ ? CleanIdent<W>
32
+ : CleanIdent<S>;
33
+
34
+ // Column-alias list on a derived table: `(<subquery>) AS p(c1, c2)`. The list
35
+ // positionally renames the subquery's exposed columns. We only treat the FIRST
36
+ // `(...)` as the list when the text before it is a single identifier (the alias);
37
+ // otherwise the `(` belongs to a later clause (`p where f(x) = 1`) and there is
38
+ // no list.
39
+ export type DerivedColsList<S extends string> =
40
+ Trim<S> extends `as ${infer R}` ? DerivedColsAfterAlias<Trim<R>> : DerivedColsAfterAlias<Trim<S>>;
41
+
42
+ export type DerivedColsAfterAlias<S extends string> =
43
+ S extends `${infer A}(${infer Cols})${string}`
44
+ ? Trim<A> extends "" ? [] :
45
+ AliasHasNoSpace<Trim<A>> extends true
46
+ ? FilterDerivedCols<SplitCommaSimple<Cols>>
47
+ : []
48
+ : [];
49
+
50
+ export type AliasHasNoSpace<S extends string> =
51
+ S extends `${string} ${string}` ? false : true;
52
+
53
+ export type FilterDerivedCols<Cols extends string[], Acc extends string[] = []> =
54
+ Cols extends [infer C extends string, ...infer Rest extends string[]]
55
+ ? CleanIdent<C> extends ""
56
+ ? FilterDerivedCols<Rest, Acc>
57
+ : FilterDerivedCols<Rest, [...Acc, CleanIdent<C>]>
58
+ : Acc;
59
+
60
+ // The subquery's projected row.
61
+ export type DerivedSubRow<Body extends string, S extends DatabaseSchema> =
62
+ TablesInQuery<Body, S> extends infer SubTables extends string
63
+ ? AliasesInQuery<Body, S> extends infer SubAliases extends string
64
+ ? SelectReturnWith<ExtractSelectList<Body>, SubTables, SubAliases, S>
65
+ : {}
66
+ : {};
67
+
68
+ // The derived table's exposed row after applying an optional `p(c1, c2)`
69
+ // column-alias list. Without a list it is just the body's projection. With a
70
+ // list the body columns are renamed positionally; a *partial* list renames the
71
+ // leading columns and leaves the trailing ones under their original names
72
+ // (Postgres allows fewer aliases than columns).
73
+ export type DerivedRenamedRow<Body extends string, Cols extends string[], S extends DatabaseSchema> =
74
+ DerivedSubRow<Body, S> extends infer BaseRow
75
+ ? Cols extends []
76
+ ? BaseRow
77
+ : RenamePartialRow<SplitSelectList<ExtractSelectList<Body>>, Cols, BaseRow>
78
+ : {};
79
+
80
+ // Pair the body's i-th projected expression with `Cols[i]` when supplied, else
81
+ // keep its own name. Shallow mapped type over the body exprs (no recursive
82
+ // intersection accumulation) to stay within TS's instantiation budget.
83
+ export type RenamePartialRow<BodyExprs extends string[], Cols extends string[], BaseRow> =
84
+ Simplify<UnionToIntersection<
85
+ {
86
+ [I in keyof BodyExprs]: BodyExprs[I] extends string
87
+ ? { [P in DerivedRenameKey<I, Cols, BodyExprs[I]> & string]: DerivedBodyColType<BodyExprs[I], BaseRow> }
88
+ : {}
89
+ }[number]
90
+ >>;
91
+
92
+ export type DerivedRenameKey<I, Cols extends string[], BodyExpr extends string> =
93
+ I extends keyof Cols
94
+ ? Cols[I] extends string ? Cols[I] : DerivedBodyColKey<BodyExpr>
95
+ : DerivedBodyColKey<BodyExpr>;
96
+
97
+ export type DerivedBodyColKey<E extends string> =
98
+ ExtractAliasResult<E> extends { expr: infer Raw extends string; alias: infer A extends string }
99
+ ? [A] extends [never] ? CleanIdent<Raw> : A
100
+ : CleanIdent<E>;
101
+
102
+ export type DerivedBodyColType<E extends string, BaseRow> =
103
+ DerivedBodyColKey<E> extends infer K extends string
104
+ ? K extends keyof BaseRow ? BaseRow[K] : unknown
105
+ : unknown;
106
+
107
+ export type DerivedTableReturn<N extends string, S extends DatabaseSchema> =
108
+ DerivedTableMatch<N> extends { body: infer Body extends string; alias: infer DAlias extends string; cols: infer Cols extends string[] }
109
+ ? DerivedRenamedRow<Body, Cols, S> extends infer SubRow
110
+ ? BuildDerivedReturn<SplitSelectList<ExtractSelectList<N>>, DAlias, SubRow>
111
+ : {}
112
+ : {};
113
+
114
+ export type BuildDerivedReturn<
115
+ Exprs extends string[],
116
+ DAlias extends string,
117
+ SubRow,
118
+ Acc = {},
119
+ Steps extends any[] = []
120
+ > = Steps["length"] extends 100
121
+ ? Simplify<Acc>
122
+ : Exprs extends [infer H extends string, ...infer Rest extends string[]]
123
+ ? BuildDerivedReturn<Rest, DAlias, SubRow, MergeRow<Acc, DerivedExprToObject<H, DAlias, SubRow>>, [any, ...Steps]>
124
+ : Simplify<Acc>;
125
+
126
+ // `Nullable` carries the outer-join nullable-qualifier set. When the derived
127
+ // table is the nullable side of an outer join (`LEFT JOIN LATERAL (...) d`), its
128
+ // exposed columns must gain `| null` (Postgres: the whole derived row is NULL when
129
+ // the join doesn't match). Defaulted to `never` so the leading-derived callers that
130
+ // don't pass it are unaffected.
131
+ export type DerivedExprToObject<E extends string, DAlias extends string, SubRow, Nullable extends string = never> =
132
+ ExtractAliasResult<E> extends { expr: infer RawExpr extends string; alias: infer OutAlias }
133
+ ? [OutAlias] extends [never]
134
+ ? CleanIdent<RawExpr> extends "*"
135
+ ? SubRow
136
+ : CleanIdent<RawExpr> extends `${DAlias}.*`
137
+ ? SubRow
138
+ : DerivedColKey<RawExpr, DAlias> extends infer K extends string
139
+ ? { [P in K]: ApplyJoinNull<DerivedProjType<RawExpr, K, SubRow>, RawExpr, Nullable> }
140
+ : Record<string, unknown>
141
+ : OutAlias extends string
142
+ ? { [P in OutAlias]: ApplyJoinNull<DerivedProjType<RawExpr, DerivedColKey<RawExpr, DAlias>, SubRow>, RawExpr, Nullable> }
143
+ : Record<string, unknown>
144
+ : Record<string, unknown>;
145
+
146
+ export type DerivedColKey<RawExpr extends string, DAlias extends string> =
147
+ CleanIdent<RawExpr> extends `${DAlias}.${infer Col}` ? Col : CleanIdent<RawExpr>;
148
+
149
+ // Type of an outer projection over a derived subquery. A plain column ref takes
150
+ // its type from the subquery row; any other expression isn't a derived column,
151
+ // so we recover its type from an outer cast (`extract(...)::int`) when present,
152
+ // otherwise `unknown` (the conservative default for unmodeled expressions).
153
+ export type DerivedProjType<RawExpr extends string, Col extends string, SubRow> =
154
+ Col extends keyof SubRow ? SubRow[Col] : OuterCastTs<RawExpr>;
155
+
156
+ export type DerivedColType<Col extends string, SubRow> =
157
+ Col extends keyof SubRow ? SubRow[Col] : unknown;
158
+
159
+ // A derived subquery can also appear as a JOIN source — `... JOIN [LATERAL]
160
+ // (<subquery>) <alias> ON ...` — not only as a leading `FROM (...)`. In that case
161
+ // the normal table/alias machinery never registers `<alias>`, so a projected
162
+ // `<alias>.col` resolves to nothing and is dropped from the row. These helpers
163
+ // expose such a JOINed derived row under its alias and apply outer-join nullability.
164
+
165
+ // Drop a leading `LATERAL` modifier so the derived source `(...)` is reachable.
166
+ export type StripLeadingLateral<S extends string> =
167
+ TrimLeft<S> extends `lateral ${infer R}` ? R : TrimLeft<S>;
168
+
169
+ // Find the subquery body of a derived table JOINed under `Alias`, scanning each
170
+ // ` join ` head. Returns `never` when no JOINed derived source carries that alias.
171
+ export type JoinedDerivedBody<N extends string, Alias extends string, Steps extends any[] = []> =
172
+ Steps["length"] extends 30
173
+ ? never
174
+ : N extends `${infer _Before} join ${infer After}`
175
+ ? StripLeadingLateral<After> extends infer Src extends string
176
+ ? Trim<Src> extends `(${string}`
177
+ ? SplitBalancedParen<Trim<Src>> extends { inner: infer Body extends string; rest: infer Rest extends string }
178
+ ? Trim<Body> extends `select ${string}`
179
+ ? DerivedAliasName<Trim<Rest>> extends Alias
180
+ ? Trim<Body>
181
+ : JoinedDerivedBody<After, Alias, [any, ...Steps]>
182
+ : JoinedDerivedBody<After, Alias, [any, ...Steps]>
183
+ : JoinedDerivedBody<After, Alias, [any, ...Steps]>
184
+ : JoinedDerivedBody<After, Alias, [any, ...Steps]>
185
+ : never
186
+ : never;
187
+
188
+ // Whether the JOIN introducing the derived `Alias` makes its row nullable — i.e.
189
+ // the source sits on the nullable side of a LEFT or FULL [OUTER] JOIN. Returns
190
+ // `Alias` (the nullable-qualifier set for `ApplyJoinNull`) when so, else `never`.
191
+ // The token-based `NullableRelations` can't be used here: in its inlined token
192
+ // view the derived alias is separated from its `join` by the whole subquery body,
193
+ // so the alias is mis-attributed to the `lateral` modifier.
194
+ export type JoinedDerivedNullable<N extends string, Alias extends string, Steps extends any[] = []> =
195
+ Steps["length"] extends 30
196
+ ? never
197
+ : N extends `${infer Before} join ${infer After}`
198
+ ? StripLeadingLateral<After> extends infer Src extends string
199
+ ? Trim<Src> extends `(${string}`
200
+ ? SplitBalancedParen<Trim<Src>> extends { inner: infer Body extends string; rest: infer Rest extends string }
201
+ ? Trim<Body> extends `select ${string}`
202
+ ? DerivedAliasName<Trim<Rest>> extends Alias
203
+ ? JoinModNullable<Before> extends true ? Alias : never
204
+ : JoinedDerivedNullable<After, Alias, [any, ...Steps]>
205
+ : JoinedDerivedNullable<After, Alias, [any, ...Steps]>
206
+ : JoinedDerivedNullable<After, Alias, [any, ...Steps]>
207
+ : JoinedDerivedNullable<After, Alias, [any, ...Steps]>
208
+ : never
209
+ : never;
210
+
211
+ // The join-keyword chain immediately precedes the matched ` join `. A trailing
212
+ // `left` / `full` (optionally `... outer`) marks the right-hand source nullable.
213
+ export type JoinModNullable<Before extends string> =
214
+ StripTrailingOuter<Lowercase<Trim<Before>>> extends `${string} left` | "left" | `${string} full` | "full"
215
+ ? true
216
+ : false;
217
+
218
+ export type StripTrailingOuter<S extends string> =
219
+ Trim<S> extends `${infer H} outer` ? Trim<H> : Trim<S>;
220
+
221
+ // Contribution of select-list exprs that reference a JOINed derived subquery,
222
+ // with outer-join nullability applied. Gated on the presence of a `(` after a
223
+ // ` join ` so ordinary queries pay nothing.
224
+ export type JoinedDerivedReturn<N extends string, S extends DatabaseSchema, _Nullable extends string> =
225
+ N extends `${string} join ${string}(${string}`
226
+ ? BuildJoinedDerivedReturn<SplitSelectList<ExtractSelectList<N>>, N, S>
227
+ : {};
228
+
229
+ export type BuildJoinedDerivedReturn<
230
+ Exprs extends string[],
231
+ N extends string,
232
+ S extends DatabaseSchema,
233
+ Acc = {},
234
+ Steps extends any[] = []
235
+ > = Steps["length"] extends 50
236
+ ? Simplify<Acc>
237
+ : Exprs extends [infer H extends string, ...infer Rest extends string[]]
238
+ ? ExtractAliasResult<H> extends { expr: infer RawExpr extends string; alias: infer _OutAlias }
239
+ ? RefQualifier<RawExpr> extends infer Q extends string
240
+ ? [Q] extends [never]
241
+ ? BuildJoinedDerivedReturn<Rest, N, S, Acc, [any, ...Steps]>
242
+ : [JoinedDerivedBody<N, Q>] extends [infer Body extends string]
243
+ ? [Body] extends [never]
244
+ ? BuildJoinedDerivedReturn<Rest, N, S, Acc, [any, ...Steps]>
245
+ : BuildJoinedDerivedReturn<Rest, N, S, MergeRow<Acc, DerivedExprToObject<H, Q, DerivedSubRow<Body, S>, JoinedDerivedNullable<N, Q>>>, [any, ...Steps]>
246
+ : BuildJoinedDerivedReturn<Rest, N, S, Acc, [any, ...Steps]>
247
+ : BuildJoinedDerivedReturn<Rest, N, S, Acc, [any, ...Steps]>
248
+ : BuildJoinedDerivedReturn<Rest, N, S, Acc, [any, ...Steps]>
249
+ : Simplify<Acc>;
250
+
251
+ // Common-table expression (single CTE): `WITH [RECURSIVE] <name>[(<cols>)] AS
252
+ // (<body>) SELECT <outer> FROM <name> ...`. The previous result path leaked the
253
+ // INNER CTE select list (greedy `with ${string} select` match), dropping the