@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,745 @@
1
+ import type { DatabaseSchema, ColumnTypeFromTableKey, RowTypeForTable, RowTypeForTables } from "./schema.js";
2
+ import type {
3
+ ColumnRef,
4
+ ColumnRefValidLooseWith,
5
+ ParseColumnRef,
6
+ QualifiedColumnRefs,
7
+ ResolveTableKey,
8
+ UnqualifiedColumnRefs,
9
+ UnqualifiedColumnValid
10
+ } from "./columns.js";
11
+ import type { AliasesInQuery, TablesInQuery } from "./tables.js";
12
+ import type {
13
+ CleanExpr,
14
+ CleanIdent,
15
+ ExtractAlias,
16
+ ExtractAliasResult,
17
+ ExtractBefore,
18
+ ExtractBeforeFromTopLevel,
19
+ IsIdentifier,
20
+ IsParamPlaceholder,
21
+ IsRuntimeStringFragment,
22
+ IsSqlConstant,
23
+ SqlConstantType,
24
+ SplitBalancedParen,
25
+ SplitTopLevel,
26
+ TokenizeLoose,
27
+ Trim
28
+ } from "./parsing.js";
29
+ import type { AllTrue } from "./utils.js";
30
+
31
+ // Expression parsing & types
32
+
33
+ export type IsIgnorableRuntimeExpr<E extends string> =
34
+ IsRuntimeStringFragment<E> extends true
35
+ ? true
36
+ : [E] extends [" "]
37
+ ? true
38
+ : false;
39
+
40
+ export type ExprToObject<
41
+ E extends string,
42
+ Tables extends string,
43
+ Aliases extends string,
44
+ S extends DatabaseSchema,
45
+ Nullable extends string = never
46
+ > =
47
+ IsIgnorableRuntimeExpr<E> extends true
48
+ ? {}
49
+ : ExtractAliasResult<E> extends { expr: infer RawExpr extends string; alias: infer Alias }
50
+ ? IsIgnorableRuntimeExpr<RawExpr> extends true
51
+ ? [Alias] extends [never]
52
+ ? {}
53
+ : Alias extends string
54
+ ? { [K in Alias]: unknown }
55
+ : {}
56
+ : [Alias] extends [never]
57
+ ? CleanIdent<RawExpr> extends "*"
58
+ ? RowTypeForTables<Tables, S>
59
+ : CleanIdent<RawExpr> extends `${infer T}.*`
60
+ ? MaybeNullableRow<RowTypeForTable<ResolveTableKey<CleanIdent<T>, Tables, Aliases, S>, S>, T, Nullable>
61
+ : ExprKey<E, Tables, Aliases, S> extends infer Key extends string | never
62
+ ? Key extends string
63
+ ? { [K in Key]: ApplyProjectionNull<ExprType<RawExpr, Tables, Aliases, S>, RawExpr, Tables, Aliases, S, Nullable> }
64
+ : Record<string, unknown>
65
+ : Record<string, unknown>
66
+ : Alias extends string
67
+ ? { [K in Alias]: ApplyProjectionNull<ExprType<RawExpr, Tables, Aliases, S>, RawExpr, Tables, Aliases, S, Nullable> }
68
+ : Record<string, unknown>
69
+ : Record<string, unknown>;
70
+
71
+ // Outer-join nullability for a directly-projected column. `Nullable` is the set
72
+ // of reference qualifiers (aliases / table names) that are nullable due to an
73
+ // outer join (see `NullableRelations`). When the projected expression is a plain
74
+ // column ref (optionally wrapped in a cast) qualified by one of them, union
75
+ // `| null` onto its type. Function calls, concatenations, literals, and `*` are
76
+ // not plain qualified column refs, so they keep their computed type. The
77
+ // `[Nullable] extends [never]` guard makes join-free queries pay zero extra cost.
78
+ export type ApplyJoinNull<
79
+ T,
80
+ E extends string,
81
+ Nullable extends string
82
+ > =
83
+ [Nullable] extends [never]
84
+ ? T
85
+ : RefQualifier<E> extends infer Q extends string
86
+ ? [Q] extends [never]
87
+ ? T
88
+ : Q extends Nullable
89
+ ? T | null
90
+ : T
91
+ : T;
92
+
93
+ // The qualifier of a plain column ref (`tr."name"` -> `tr`), after stripping an
94
+ // outer cast (`tms."currency"::text` -> `tms`). `never` when the expression has
95
+ // no qualifier (bare column) or is not a plain column ref.
96
+ export type RefQualifier<E extends string> =
97
+ StripOuterCast<E> extends infer Inner extends string
98
+ ? CleanExpr<Inner> extends `${infer Q}.${string}`
99
+ ? CleanIdent<Q>
100
+ : never
101
+ : never;
102
+
103
+ export type StripOuterCast<E extends string> =
104
+ CleanExpr<E> extends `${infer Inner}::${string}`
105
+ ? Inner
106
+ : CleanExpr<E> extends `cast(${infer Inner} as ${string})`
107
+ ? Inner
108
+ : CleanExpr<E> extends `cast (${infer Inner} as ${string})`
109
+ ? Inner
110
+ : E;
111
+
112
+ // Projection-level nullability dispatcher. Plain column refs go through
113
+ // `ApplyJoinNull` (qualifier-in-nullable-set check). A `coalesce(...)` projection
114
+ // (optionally wrapped in an outer cast like `coalesce(...)::text`) hides its column
115
+ // refs from `RefQualifier`, so `ApplyJoinNull` can never see them — we handle it
116
+ // here instead. SQL: `coalesce(a, b, c)` is NULL only when EVERY argument is NULL,
117
+ // so the projection gains `| null` only when all args are nullable (an outer-join
118
+ // nullable qualifier OR an already-nullable base type). A non-null literal
119
+ // (`coalesce(o.x, '')`) keeps the result non-null. `T` is the already-computed
120
+ // projection type (for the cast case, the cast's target type); we only add `| null`.
121
+ export type ApplyProjectionNull<
122
+ T,
123
+ E extends string,
124
+ Tables extends string,
125
+ Aliases extends string,
126
+ S extends DatabaseSchema,
127
+ Nullable extends string
128
+ > =
129
+ CleanExpr<StripOuterCast<E>> extends `coalesce(${infer Args})`
130
+ ? CoalesceAllArgsNullable<SplitTopLevel<Args>, Tables, Aliases, S, Nullable> extends true
131
+ ? T | null
132
+ : T
133
+ : ApplyJoinNull<T, E, Nullable>;
134
+
135
+ // True only when every coalesce argument is nullable. An empty/exhausted list is
136
+ // vacuously `true`, but the wrapper above only reaches this for a real coalesce call
137
+ // (at least one arg). Recursion is depth-capped like the other arg walkers.
138
+ export type CoalesceAllArgsNullable<
139
+ Args extends string[],
140
+ Tables extends string,
141
+ Aliases extends string,
142
+ S extends DatabaseSchema,
143
+ Nullable extends string,
144
+ Steps extends any[] = []
145
+ > = Steps["length"] extends 30
146
+ ? true
147
+ : Args extends [infer H extends string, ...infer Rest extends string[]]
148
+ ? CoalesceArgNullable<H, Tables, Aliases, S, Nullable> extends true
149
+ ? CoalesceAllArgsNullable<Rest, Tables, Aliases, S, Nullable, [any, ...Steps]>
150
+ : false
151
+ : true;
152
+
153
+ // A single coalesce argument is nullable when its qualifier is in the outer-join
154
+ // nullable set (`ri.sku` under `left join ... ri`), or — failing that — when its
155
+ // base type already admits `null` (a base-nullable column, or an unresolved
156
+ // `unknown` arg). A non-null literal resolves to a non-null base type -> `false`.
157
+ export type CoalesceArgNullable<
158
+ Arg extends string,
159
+ Tables extends string,
160
+ Aliases extends string,
161
+ S extends DatabaseSchema,
162
+ Nullable extends string
163
+ > =
164
+ RefQualifier<Arg> extends infer Q extends string
165
+ ? [Q] extends [never]
166
+ ? null extends ExprType<Arg, Tables, Aliases, S>
167
+ ? true
168
+ : false
169
+ : Q extends Nullable
170
+ ? true
171
+ : null extends ExprType<Arg, Tables, Aliases, S>
172
+ ? true
173
+ : false
174
+ : null extends ExprType<Arg, Tables, Aliases, S>
175
+ ? true
176
+ : false;
177
+
178
+ // Nullablize every column of a `*`-expanded row when its qualifier is nullable.
179
+ export type MaybeNullableRow<Row, Qualifier extends string, Nullable extends string> =
180
+ [Nullable] extends [never]
181
+ ? Row
182
+ : CleanIdent<Qualifier> extends Nullable
183
+ ? { [K in keyof Row]: Row[K] | null }
184
+ : Row;
185
+
186
+ export type ExprKey<E extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
187
+ CleanIdent<E> extends "*" ? never :
188
+ CleanIdent<E> extends `${infer T}.*` ? never :
189
+ CleanExpr<E> extends `${infer Inner}::${string}`
190
+ ? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
191
+ : CleanExpr<E> extends `cast(${infer Inner} as ${string})`
192
+ ? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
193
+ : CleanExpr<E> extends `cast (${infer Inner} as ${string})`
194
+ ? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
195
+ : [ColumnKeyFromExpr<E, Tables, Aliases, S>] extends [never]
196
+ ? FunctionKeyFromExpr<E>
197
+ : ColumnKeyFromExpr<E, Tables, Aliases, S>;
198
+
199
+ export type ColumnKeyFromExpr<E extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
200
+ ParseColumnRef<CleanExpr<E>, Tables, Aliases, S> extends infer Ref
201
+ ? Ref extends ColumnRef<any, any>
202
+ ? Ref["column"]
203
+ : never
204
+ : never;
205
+
206
+ export type FunctionKeyFromExpr<E extends string> =
207
+ CleanExpr<E> extends `case ${string}` ? "case" :
208
+ CleanExpr<E> extends `${infer Func}(${string}`
209
+ ? CleanIdent<Func>
210
+ : CleanExpr<E> extends `${infer Func} (${string}`
211
+ ? CleanIdent<Func>
212
+ : never;
213
+
214
+ // A projected expression whose top-level operator is a comparison
215
+ // (`<`, `>`, `<=`, `>=`, `<>`, `!=`, `=`) yields `boolean`. CASE expressions are
216
+ // excluded: their `when … > …` comparison sits at top level but the expression's
217
+ // type is the THEN/ELSE branch, not boolean. A `case` wrapped in parens (e.g.
218
+ // `(case … end)`) is already protected — its inner comparison is below depth 0.
219
+ export type IsBoolExpr<CE extends string> =
220
+ CE extends `case ${string}`
221
+ ? false
222
+ : HasTopLevelCompare<CE>;
223
+
224
+ // Scans for a comparison operator outside parens and outside `'…'`/`"…"` quotes.
225
+ // `->>`, `#>>` and `::` are consumed as units so their `>`/`:` are not mistaken
226
+ // for comparisons. Modelled on the char-walker in `SplitTopLevel` (parsing.ts).
227
+ export type HasTopLevelCompare<
228
+ S extends string,
229
+ Depth extends any[] = [],
230
+ Steps extends any[] = [],
231
+ InQ extends boolean = false,
232
+ InDQ extends boolean = false
233
+ > = Steps["length"] extends 400
234
+ ? false
235
+ : S extends `${infer C}${infer Rest}`
236
+ ? InQ extends true
237
+ ? HasTopLevelCompare<Rest, Depth, [any, ...Steps], C extends "'" ? false : true, InDQ>
238
+ : InDQ extends true
239
+ ? HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, C extends `"` ? false : true>
240
+ : C extends "'"
241
+ ? HasTopLevelCompare<Rest, Depth, [any, ...Steps], true, InDQ>
242
+ : C extends `"`
243
+ ? HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, true>
244
+ : C extends "("
245
+ ? HasTopLevelCompare<Rest, [any, ...Depth], [any, ...Steps], InQ, InDQ>
246
+ : C extends ")"
247
+ ? HasTopLevelCompare<Rest, Depth extends [any, ...infer D] ? D : [], [any, ...Steps], InQ, InDQ>
248
+ : Depth["length"] extends 0
249
+ // Consume multi-char operators whose `<`/`>`/`:` are NOT comparisons:
250
+ // JSON access (`->`, `->>`, `#>`, `#>>`), containment (`@>`, `<@`),
251
+ // cast (`::`), and bit-shift (`<<`, `>>`). Longer forms first.
252
+ ? S extends `->>${infer R}`
253
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
254
+ : S extends `->${infer R}`
255
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
256
+ : S extends `#>>${infer R}`
257
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
258
+ : S extends `#>${infer R}`
259
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
260
+ : S extends `@>${infer R}`
261
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
262
+ : S extends `<@${infer R}`
263
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
264
+ : S extends `::${infer R}`
265
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
266
+ : S extends `<<${infer R}`
267
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
268
+ : S extends `>>${infer R}`
269
+ ? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
270
+ : C extends "<" | ">" | "=" | "!"
271
+ ? true
272
+ : HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, InDQ>
273
+ : HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, InDQ>
274
+ : false;
275
+
276
+ // Strip a redundant fully-wrapping paren pair, repeatedly: `((expr))` and
277
+ // `((case ...)::text)` -> the inner expression whose parens wrap the WHOLE
278
+ // thing. Without this an outer operator hidden by a redundant wrap (e.g. the
279
+ // `::text` in `((case ...)::text)`) is not seen as top-level, so the cast is
280
+ // missed and the expression misparses (empty-function) to `unknown`.
281
+ // - A subquery `(select ...)` KEEPS its parens — its detection relies on them.
282
+ // - A trailing operator after the matching close (`(a)::int`, `(a) + b`) means
283
+ // the parens do NOT wrap the whole expression (`rest != ""`), so it is left
284
+ // as-is and the real outer operator is handled normally.
285
+ type UnwrapRedundantParens<E extends string, Steps extends any[] = []> =
286
+ Steps["length"] extends 12
287
+ ? E
288
+ : E extends `(select ${string})`
289
+ ? E
290
+ : E extends `(${string}`
291
+ ? SplitBalancedParen<E> extends { inner: infer Inner extends string; rest: "" }
292
+ ? UnwrapRedundantParens<Trim<Inner>, [any, ...Steps]>
293
+ : E
294
+ : E;
295
+
296
+ export type ExprType<
297
+ E extends string,
298
+ Tables extends string,
299
+ Aliases extends string,
300
+ S extends DatabaseSchema,
301
+ Steps extends any[] = []
302
+ > =
303
+ Steps["length"] extends 25
304
+ ? unknown
305
+ : IsIgnorableRuntimeExpr<E> extends true
306
+ ? unknown
307
+ : UnwrapRedundantParens<CleanExpr<E>> extends infer CE extends string
308
+ ? IsRuntimeStringFragment<CE> extends true
309
+ ? unknown
310
+ : CE extends "*"
311
+ ? RowTypeForTables<Tables, S>
312
+ : CE extends `${infer T}.*`
313
+ ? RowTypeForTable<ResolveTableKey<CleanIdent<T>, Tables, Aliases, S>, S>
314
+ : CE extends `(select ${infer SubBody})`
315
+ ? ScalarSubqueryType<SubBody, S, [any, ...Steps]>
316
+ : IsBoolExpr<CE> extends true
317
+ ? boolean
318
+ // A CASE expression is always `unknown` by design (the
319
+ // THEN/ELSE branch type is not inferred). Short-circuit here
320
+ // BEFORE the function/operator cascade so a large
321
+ // `(case when count(distinct <big expr>) ... end)` is not
322
+ // fully resolved (incl. its aggregate args) just to arrive at
323
+ // `unknown` — that resolution is pure cost that starves the
324
+ // per-query instantiation budget on wide SELECTs. A CASE with
325
+ // an OUTER cast (`(case ...)::text`) does NOT match here (it
326
+ // ends in the type name, not `)`), so it still takes its cast
327
+ // type via the branch below.
328
+ : IsCaseExpr<CE> extends true
329
+ ? unknown
330
+ : [OuterCastName<CE>] extends [never]
331
+ // No TOP-LEVEL `::` cast: any `::` present is nested
332
+ // inside a function/paren arg (e.g. `f(a::int)`, or the
333
+ // inner casts of `sum(g(x::numeric))::float8`), so the
334
+ // cast is not the outer operator — fall through to the
335
+ // cast(...)/function/operator cascade below.
336
+ ? CE extends `cast(${infer Inner} as ${infer CastTypeName})`
337
+ ? ExprType<Inner, Tables, Aliases, S, [any, ...Steps]> extends never
338
+ ? never
339
+ : SqlTypeToTs<CastTypeName>
340
+ : CE extends `cast (${infer Inner} as ${infer CastTypeName})`
341
+ ? ExprType<Inner, Tables, Aliases, S, [any, ...Steps]> extends never
342
+ ? never
343
+ : SqlTypeToTs<CastTypeName>
344
+ : CE extends `${infer Func}(${infer Args})`
345
+ ? FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
346
+ : CE extends `${infer Func} (${infer Args})`
347
+ ? FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
348
+ : CE extends `${string}||${string}`
349
+ ? string
350
+ : CE extends `${infer JBase}->>${string}`
351
+ ? ExprType<JBase, Tables, Aliases, S, [any, ...Steps]> extends never
352
+ ? never
353
+ : string
354
+ : CE extends `${infer JBase}#>>${string}`
355
+ ? ExprType<JBase, Tables, Aliases, S, [any, ...Steps]> extends never
356
+ ? never
357
+ : string
358
+ : CE extends "null"
359
+ ? null
360
+ : CE extends `'${infer L}'`
361
+ ? string
362
+ : CE extends `${number}`
363
+ ? number
364
+ : CE extends "true"
365
+ ? boolean
366
+ : CE extends "false"
367
+ ? boolean
368
+ : IsSqlConstant<CE> extends true
369
+ ? SqlConstantType<CE>
370
+ : IsParamPlaceholder<CE> extends true
371
+ ? unknown
372
+ : [ParseColumnRef<CE, Tables, Aliases, S>] extends [infer Ref]
373
+ ? [Ref] extends [never]
374
+ ? IsIdentifier<CE> extends true
375
+ ? never
376
+ : unknown
377
+ : Ref extends ColumnRef<infer TableKey extends string, infer Column extends string>
378
+ ? ColumnTypeFromTableKey<TableKey, Column, S>
379
+ : IsIdentifier<CE> extends true
380
+ ? never
381
+ : unknown
382
+ : unknown
383
+ // A genuine TOP-LEVEL `::T` cast. As with the JSON-text
384
+ // operators, a `->>` / `#>>` to the right of the cast type
385
+ // name means the cast is NOT the outermost operator — the
386
+ // JSON text extraction is, yielding `string`.
387
+ : OuterCastName<CE> extends `${string}->>${string}`
388
+ ? string
389
+ : OuterCastName<CE> extends `${string}#>>${string}`
390
+ ? string
391
+ // The `extends never` guard exists only to surface a
392
+ // BARE invalid column (`not_a_col::text` -> never).
393
+ // Run it only when the cast's inner is a simple ref;
394
+ // for a COMPOUND inner (`sum(...)::float8`,
395
+ // `(bool_and(...) ...)::boolean`) take the cast type
396
+ // directly — fully type-resolving such inners just to
397
+ // check `never` is a large, needless instantiation
398
+ // cost (it starves the per-query budget so later
399
+ // projections in a wide SELECT bail to `never`), and
400
+ // hidden bad refs are caught by the SELECT-list
401
+ // validator, not here.
402
+ : CastInnerIsSimpleRef<OuterCastInner<CE>> extends true
403
+ ? ExprType<OuterCastInner<CE>, Tables, Aliases, S, [any, ...Steps]> extends never
404
+ ? never
405
+ : SqlTypeToTs<OuterCastName<CE>>
406
+ : SqlTypeToTs<OuterCastName<CE>>
407
+ : unknown;
408
+
409
+ // Scalar subquery in an expression position -> the type of its single
410
+ // projected column. `SubBody` is everything after `(select `, e.g.
411
+ // "count(*) from payments p where p.order_id = o.id". We extract the inner
412
+ // select list (paren/quote-aware, stopping at the inner top-level FROM), take
413
+ // its first projected expression, and type it against the SUBQUERY's own
414
+ // tables/aliases so correlated column refs (e.g. max(amount)) resolve. Inner
415
+ // column refs against the OUTER query (correlation) fall back to unknown, which
416
+ // is acceptable for a scalar result type.
417
+ export type ScalarSubqueryType<
418
+ SubBody extends string,
419
+ S extends DatabaseSchema,
420
+ Steps extends any[]
421
+ > =
422
+ ExtractBeforeFromTopLevel<SubBody> extends infer SL extends string
423
+ ? SplitTopLevel<SL> extends [infer First extends string, ...infer _Rest]
424
+ ? First extends string
425
+ ? `select ${SubBody}` extends infer SubQuery extends string
426
+ ? TablesInQuery<SubQuery, S> extends infer SubTables extends string
427
+ ? AliasesInQuery<SubQuery, S> extends infer SubAliases extends string
428
+ ? ExtractAliasResult<First> extends { expr: infer RawExpr extends string }
429
+ ? ExprType<RawExpr, SubTables, SubAliases, S, Steps>
430
+ : unknown
431
+ : unknown
432
+ : unknown
433
+ : unknown
434
+ : unknown
435
+ : unknown
436
+ : unknown;
437
+
438
+ // Function returns
439
+
440
+ export type FunctionReturn<
441
+ Func extends string,
442
+ Args extends string,
443
+ Tables extends string,
444
+ Aliases extends string,
445
+ S extends DatabaseSchema,
446
+ Steps extends any[] = []
447
+ > =
448
+ Steps["length"] extends 25
449
+ ? unknown
450
+ : ArgsValid<Args, Tables, Aliases, S, Steps> extends false
451
+ ? never
452
+ : Func extends "count"
453
+ ? number
454
+ : Func extends "sum" | "avg"
455
+ ? number
456
+ : Func extends "min" | "max"
457
+ ? FirstArgType<Args, Tables, Aliases, S, Steps>
458
+ : Func extends "upper" | "lower" | "concat"
459
+ ? string
460
+ : Func extends "coalesce"
461
+ ? UnionArgTypes<Args, Tables, Aliases, S, Steps>
462
+ : unknown;
463
+
464
+ // Expression validation
465
+
466
+ export type ExprsValid<Exprs extends string[], N extends string, S extends DatabaseSchema> =
467
+ TablesInQuery<N, S> extends infer Tables extends string
468
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
469
+ ? ExprsValidList<Exprs, Tables, Aliases, S>
470
+ : true
471
+ : true;
472
+
473
+ export type ExprValid<
474
+ E extends string,
475
+ Tables extends string,
476
+ Aliases extends string,
477
+ S extends DatabaseSchema
478
+ > =
479
+ IsIgnorableRuntimeExpr<E> extends true
480
+ ? true
481
+ : ExtractAlias<E> extends { expr: infer RawExpr extends string }
482
+ ? IsIgnorableRuntimeExpr<RawExpr> extends true
483
+ ? true
484
+ : ExprType<RawExpr, Tables, Aliases, S> extends never
485
+ ? false
486
+ : NeedsTokenRefValidation<RawExpr> extends true
487
+ ? ExprColumnRefsValid<RawExpr, Tables, Aliases, S>
488
+ : FuncCompoundArgsValid<RawExpr, Tables, Aliases, S>
489
+ : true;
490
+
491
+ // A function-call (or cast) projection skips the token ref-scan above
492
+ // (`NeedsTokenRefValidation` is false for `${fn}(${args})`), which is why an
493
+ // invalid column hidden inside an aggregate/function argument — `sum(price +
494
+ // bogus_col)`, `date_trunc('day', bogus_col)`, `array_agg(bogus_col ORDER BY
495
+ // created_at)` — escapes validation while the same ref written OUTSIDE a function
496
+ // is caught. Recover that case here, in the SELECT-list validation path ONLY (not
497
+ // `ExprType`, so the return-type/`QueryResult` path and its instantiation cost are
498
+ // untouched).
499
+ //
500
+ // We extract the call's argument list and run each argument through the same
501
+ // loose column-ref scan the rest of the query uses (`ExprColumnRefsValid`), which
502
+ // already skips string literals, numbers, params, operators, and SQL keywords —
503
+ // so an aggregate-local `ORDER BY` (`array_agg(id ORDER BY created_at)`) and
504
+ // keyword-style args resolve their genuine column surfaces while non-columns stay
505
+ // lenient. The `EXTRACT(field FROM source)` date-part keyword grammar is handled
506
+ // upstream by `RewriteExtractCall` (rewritten to `extract(source)`) so the field
507
+ // token is never seen here. The `extends false` guard rejects only on a DEFINITE
508
+ // invalid column.
509
+ export type FuncCompoundArgsValid<
510
+ E extends string,
511
+ Tables extends string,
512
+ Aliases extends string,
513
+ S extends DatabaseSchema
514
+ > =
515
+ ExtractFuncArgList<CleanExpr<E>> extends infer Args extends string
516
+ ? [Args] extends [never]
517
+ ? true
518
+ : ArgsArithRefsValid<SplitTopLevel<Args>, Tables, Aliases, S>
519
+ : true;
520
+
521
+ // The inner argument list of the FIRST `(...)` group, or `never` when the
522
+ // expression is not a call (e.g. a cast like `x::int`, which must stay lenient).
523
+ export type ExtractFuncArgList<E extends string> =
524
+ E extends `${infer _Func}(${infer AfterOpen}`
525
+ ? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string }
526
+ ? Inner
527
+ : never
528
+ : never;
529
+
530
+ export type ArgsArithRefsValid<
531
+ Args extends string[],
532
+ Tables extends string,
533
+ Aliases extends string,
534
+ S extends DatabaseSchema,
535
+ Steps extends any[] = []
536
+ > = Steps["length"] extends 30
537
+ ? true
538
+ : Args extends [infer H extends string, ...infer Rest extends string[]]
539
+ // The loose ref-scan inside `ExprColumnRefsValid` tokenizes (padding
540
+ // operators itself) and only surfaces genuine column candidates, so each
541
+ // argument — bare column, arithmetic, CASE, or aggregate-local `ORDER BY`
542
+ // — is validated while literals/params/keywords stay lenient.
543
+ ? Trim<H> extends infer TH extends string
544
+ ? ExprColumnRefsValid<TH, Tables, Aliases, S> extends false
545
+ ? false
546
+ : ArgsArithRefsValid<Rest, Tables, Aliases, S, [any, ...Steps]>
547
+ : ArgsArithRefsValid<Rest, Tables, Aliases, S, [any, ...Steps]>
548
+ : true;
549
+
550
+ export type NeedsTokenRefValidation<E extends string> =
551
+ CleanExpr<E> extends `${string}::${string}` ? false :
552
+ CleanExpr<E> extends `cast(${string} as ${string})` ? false :
553
+ CleanExpr<E> extends `cast (${string} as ${string})` ? false :
554
+ CleanExpr<E> extends `${string}(${string}` ? false :
555
+ CleanExpr<E> extends `${string} (${string}` ? false :
556
+ true;
557
+
558
+ export type ExprColumnRefsValid<
559
+ E extends string,
560
+ Tables extends string,
561
+ Aliases extends string,
562
+ S extends DatabaseSchema
563
+ > = ExprQualifiedRefsValid<E, Tables, Aliases, S> extends true
564
+ ? ExprUnqualifiedRefsValid<E, Tables, Aliases, S>
565
+ : false;
566
+
567
+ export type ExprQualifiedRefsValid<
568
+ E extends string,
569
+ Tables extends string,
570
+ Aliases extends string,
571
+ S extends DatabaseSchema
572
+ > = QualifiedColumnRefs<TokenizeLoose<E>, S, Tables, Aliases> extends infer Cols
573
+ ? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
574
+ : true;
575
+
576
+ export type ExprUnqualifiedRefsValid<
577
+ E extends string,
578
+ Tables extends string,
579
+ Aliases extends string,
580
+ S extends DatabaseSchema
581
+ > = UnqualifiedColumnRefs<TokenizeLoose<E>, S, Tables, Aliases> extends infer Cols
582
+ ? AllTrue<Cols extends string ? UnqualifiedColumnValid<Cols, Tables, Aliases, S> : true>
583
+ : true;
584
+
585
+ export type ExprsValidList<
586
+ Exprs extends string[],
587
+ Tables extends string,
588
+ Aliases extends string,
589
+ S extends DatabaseSchema,
590
+ Steps extends any[] = []
591
+ > = Steps["length"] extends 100
592
+ ? true
593
+ : Exprs extends [infer H extends string, ...infer Rest extends string[]]
594
+ ? ExprValid<H, Tables, Aliases, S> extends true
595
+ ? ExprsValidList<Rest, Tables, Aliases, S, [any, ...Steps]>
596
+ : false
597
+ : true;
598
+
599
+ // Argument parsing
600
+
601
+ export type FirstArgType<
602
+ Args extends string,
603
+ Tables extends string,
604
+ Aliases extends string,
605
+ S extends DatabaseSchema,
606
+ Steps extends any[]
607
+ > =
608
+ SplitTopLevel<Args> extends [infer First extends string, ...infer _]
609
+ ? ExprType<First, Tables, Aliases, S, Steps>
610
+ : unknown;
611
+
612
+ export type UnionArgTypes<
613
+ Args extends string,
614
+ Tables extends string,
615
+ Aliases extends string,
616
+ S extends DatabaseSchema,
617
+ Steps extends any[]
618
+ > =
619
+ SplitTopLevel<Args> extends infer Parts extends string[]
620
+ ? Parts[number] extends infer P extends string
621
+ ? ExprType<P, Tables, Aliases, S, Steps>
622
+ : unknown
623
+ : unknown;
624
+
625
+ export type ArgsValid<
626
+ Args extends string,
627
+ Tables extends string,
628
+ Aliases extends string,
629
+ S extends DatabaseSchema,
630
+ Steps extends any[]
631
+ > =
632
+ Trim<Args> extends ""
633
+ ? true
634
+ : SplitTopLevel<Args> extends infer Parts extends string[]
635
+ ? AllTrue<Parts[number] extends infer P extends string ? (ExprType<P, Tables, Aliases, S, Steps> extends never ? false : true) : true>
636
+ : true;
637
+
638
+ // SQL type mapping
639
+
640
+ // Maps a SQL cast target type name to its TypeScript type.
641
+ // Handles, in order: chained casts (`a::int::text` -> last type wins), array
642
+ // suffixes (`int[]` -> number[]), and finally a flat scalar mapping.
643
+ export type SqlTypeToTs<T extends string> =
644
+ Trim<T> extends `${string}::${infer Rest}`
645
+ ? SqlTypeToTs<Rest>
646
+ : Trim<T> extends `${infer Base}[]`
647
+ ? SqlScalarToTs<NormalizeTypeName<Base>>[]
648
+ : SqlScalarToTs<NormalizeTypeName<T>>;
649
+
650
+ export type SqlScalarToTs<N extends string> =
651
+ N extends "int" | "integer" | "bigint" | "smallint" | "numeric" | "decimal" | "real" | "double" | "float"
652
+ | "int2" | "int4" | "int8" | "float4" | "float8"
653
+ ? number
654
+ : N extends "bool" | "boolean"
655
+ ? boolean
656
+ : N extends "text" | "varchar" | "char" | "character" | "uuid"
657
+ ? string
658
+ : N extends "date" | "time" | "timestamp" | "timestamptz"
659
+ ? string
660
+ : N extends "json" | "jsonb"
661
+ ? unknown
662
+ : N extends "bytea" | "blob"
663
+ ? Uint8Array
664
+ : unknown;
665
+
666
+ export type NormalizeTypeName<S extends string> =
667
+ CleanIdent<ExtractBefore<Trim<S>, "(">>;
668
+
669
+ // The TS type implied by an expression's OUTER cast (`expr::int`,
670
+ // `cast(expr as int)`), if any. Mirrors `ExprType`'s cast detection but is
671
+ // self-contained — used where the full `ExprType` context isn't available
672
+ // (e.g. typing an outer projection over a derived subquery, whose column refs
673
+ // resolve against the subquery row, not the schema). Returns `unknown` when
674
+ // there is no outer cast or the cast target doesn't map to a known scalar, so
675
+ // callers that fall back to `unknown` are never made worse.
676
+ export type OuterCastTs<E extends string> =
677
+ CleanExpr<E> extends `${string}::${infer CastName}`
678
+ ? SqlTypeToTs<CastName>
679
+ : CleanExpr<E> extends `cast(${string} as ${infer CastName})`
680
+ ? SqlTypeToTs<CastName>
681
+ : CleanExpr<E> extends `cast (${string} as ${infer CastName})`
682
+ ? SqlTypeToTs<CastName>
683
+ : unknown;
684
+
685
+ // --- Top-level (outer) `::` cast detection --------------------------------
686
+ // A `::T` cast is the OUTER operator of an expression only when it sits at paren
687
+ // depth 0. Splitting at the LEFTMOST `::` (the naive `${infer Inner}::${infer T}`)
688
+ // is wrong for casts nested in call args: `sum(g(x::numeric))::float8` would split
689
+ // at `::numeric`, leaving the unbalanced `sum(g(x` as the inner expr (which types
690
+ // to `never`) and poisoning the whole projection. Cheap top-level signal: scanning
691
+ // `::` left-to-right, the type-name part of a TOP-LEVEL cast contains no `)` — a
692
+ // `)` after the `::` means an unclosed `(` precedes it, so that `::` is nested.
693
+ //
694
+ // `OuterCastName<E>` -> the outer cast's type-name (possibly chained, e.g.
695
+ // `int::text`, which `SqlTypeToTs` resolves last-wins), or `never` when there is
696
+ // no top-level cast. `OuterCastInner<E>` -> `E` with that outer cast stripped, with
697
+ // any inner casts preserved.
698
+ //
699
+ // A `::` is NESTED (not the outer operator) when the text to its right has an
700
+ // UNMATCHED closing paren — a `)` with no `(` before it (the `)` closes a `(` that
701
+ // opened to the LEFT of the `::`). A parameterized type name such as
702
+ // `numeric(10,2)` has its `(` BEFORE the `)`, so it is NOT flagged and the cast
703
+ // stays top-level.
704
+ type CastAfterIsNested<After extends string> =
705
+ After extends `${infer P})${string}`
706
+ ? P extends `${string}(${string}`
707
+ ? false
708
+ : true
709
+ : false;
710
+
711
+ export type OuterCastName<E extends string> =
712
+ E extends `${string}::${infer After}`
713
+ ? CastAfterIsNested<After> extends true
714
+ ? OuterCastName<After>
715
+ : After
716
+ : never;
717
+
718
+ export type OuterCastInner<E extends string, Acc extends string = ""> =
719
+ E extends `${infer A}::${infer After}`
720
+ ? CastAfterIsNested<After> extends true
721
+ ? OuterCastInner<After, `${Acc}${A}::`>
722
+ : `${Acc}${A}`
723
+ : E;
724
+
725
+ // A cast's inner expression is a "simple ref" — a bare (optionally qualified /
726
+ // quoted) column or identifier — when, after trimming, it contains no `(` (no
727
+ // call / paren group) and no space (no operator / compound expression). Only then
728
+ // is the invalid-bare-column `extends never` guard meaningful (and cheap); a
729
+ // compound inner takes its cast type directly.
730
+ export type CastInnerIsSimpleRef<I extends string> =
731
+ Trim<I> extends `${string}(${string}`
732
+ ? false
733
+ : Trim<I> extends `${string} ${string}`
734
+ ? false
735
+ : true;
736
+
737
+ // A top-level CASE expression — `case ...`, optionally wrapped in balanced parens
738
+ // (`(case ... end)`). Used to short-circuit CASE typing to `unknown` (its design
739
+ // result) without resolving the branches/aggregate args.
740
+ export type IsCaseExpr<E extends string> =
741
+ Trim<E> extends `case ${string}`
742
+ ? true
743
+ : Trim<E> extends `(${infer Inner})`
744
+ ? IsCaseExpr<Inner>
745
+ : false;