@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,172 @@
1
+ // Low-level string primitives, predicates, and token cleaners.
2
+ import type { SqlConstant, OperatorToken } from "./tokenize.js";
3
+
4
+ export type ReplaceAll<S extends string, From extends string, To extends string> =
5
+ From extends ""
6
+ ? S
7
+ : ReplaceAllImpl<S, From, To>;
8
+
9
+ export type ReplaceAllImpl<
10
+ S extends string,
11
+ From extends string,
12
+ To extends string,
13
+ Steps extends any[] = []
14
+ > = Steps["length"] extends 250
15
+ ? S
16
+ : S extends `${infer Head}${From}${infer Tail}`
17
+ ? `${Head}${To}${ReplaceAllImpl<Tail, From, To, [any, ...Steps]>}`
18
+ : S;
19
+
20
+ // Collapse runs of consecutive spaces to a single space. A ladder of multi-space
21
+ // patterns removes up to 15 spaces per recursion step, so a deeply-indented
22
+ // report-scale query collapses in O(runs) rather than O(spaces) steps — keeping the
23
+ // downstream `Split<N, " ">` token walk well under TypeScript's tail-recursion
24
+ // limit. (A query whose normalized form carried 1000+ spaces previously overflowed
25
+ // `Split` with TS2589.) Tail-recursive; cap high enough for multi-thousand-space
26
+ // analytics queries.
27
+ export type CollapseSpaces<S extends string, Steps extends any[] = []> =
28
+ Steps["length"] extends 800
29
+ ? S
30
+ : S extends `${infer A} ${infer B}` // 16 spaces -> 1
31
+ ? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
32
+ : S extends `${infer A} ${infer B}` // 4 spaces -> 1
33
+ ? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
34
+ : S extends `${infer A} ${infer B}` // 2 spaces -> 1
35
+ ? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
36
+ : S;
37
+
38
+ export type TrimLeft<S extends string> = S extends `${Whitespace}${infer R}` ? TrimLeft<R> : S;
39
+
40
+ export type TrimRight<S extends string> = S extends `${infer R}${Whitespace}` ? TrimRight<R> : S;
41
+
42
+ export type Trim<S extends string> = TrimLeft<TrimRight<S>>;
43
+
44
+ export type Whitespace = " " | "\n" | "\t" | "\r";
45
+
46
+ // Clean identifiers
47
+
48
+ export type CleanIdent<S extends string> = Lowercase<Unquote<TrimPunctuation<Trim<S>>>>;
49
+
50
+ export type CleanExpr<S extends string> = Trim<S> extends infer T extends string ? T : S;
51
+
52
+ export type IsIdentifier<S extends string> =
53
+ HasSpecial<S> extends true ? false : true;
54
+
55
+ export type IsRuntimeStringFragment<S extends string> =
56
+ string extends S
57
+ ? true
58
+ : `${Lowercase<string>}` extends S
59
+ ? true
60
+ : string extends CleanIdent<S>
61
+ ? true
62
+ : false;
63
+
64
+ export type HasSpecial<S extends string> =
65
+ S extends `${string} ${string}` ? true :
66
+ S extends `${string}(${string}` ? true :
67
+ S extends `${string})${string}` ? true :
68
+ S extends `${string}+${string}` ? true :
69
+ S extends `${string}-${string}` ? true :
70
+ S extends `${string}*${string}` ? true :
71
+ S extends `${string}/${string}` ? true :
72
+ S extends `${string}=${string}` ? true :
73
+ S extends `${string}<${string}` ? true :
74
+ S extends `${string}>${string}` ? true :
75
+ S extends `${string},${string}` ? true :
76
+ S extends `${string}::${string}` ? true :
77
+ S extends `${string}||${string}` ? true :
78
+ false;
79
+
80
+ export type IsParamPlaceholder<S extends string> =
81
+ S extends `$${string}` ? true :
82
+ S extends `:${string}` ? true :
83
+ S extends "?" ? true :
84
+ false;
85
+
86
+ export type IsQualifiedRefCandidate<S extends string> =
87
+ S extends `'${string}'` ? false :
88
+ S extends `${number}.${number}` ? false :
89
+ true;
90
+
91
+ export type IsSqlConstant<S extends string> =
92
+ CleanIdent<S> extends SqlConstant ? true : false;
93
+
94
+ export type SqlConstantType<S extends string> =
95
+ CleanIdent<S> extends "current_date" ? string :
96
+ CleanIdent<S> extends "current_time" ? string :
97
+ CleanIdent<S> extends "current_timestamp" ? string :
98
+ CleanIdent<S> extends "localtime" ? string :
99
+ CleanIdent<S> extends "localtimestamp" ? string :
100
+ CleanIdent<S> extends "current_user" ? string :
101
+ CleanIdent<S> extends "session_user" ? string :
102
+ CleanIdent<S> extends "current_schema" ? string :
103
+ string;
104
+
105
+ export type Unquote<S extends string> =
106
+ S extends `"${infer R}"` ? R :
107
+ S extends `\`${infer R}\`` ? R :
108
+ S;
109
+
110
+ export type TrimPunctuation<S extends string> =
111
+ S extends `${Punct}${infer R}` ? TrimPunctuation<R> :
112
+ S extends `${infer R}${Punct}` ? TrimPunctuation<R> :
113
+ S;
114
+
115
+ export type Punct = "," | ";" | "(" | ")";
116
+
117
+ // Split helpers
118
+
119
+ export type Split<
120
+ S extends string,
121
+ Delim extends string,
122
+ Acc extends string[] = [],
123
+ Steps extends any[] = []
124
+ > = Steps["length"] extends 2000
125
+ ? [...Acc, S]
126
+ : S extends `${infer Head}${Delim}${infer Tail}`
127
+ ? Split<Tail, Delim, [...Acc, Head], [any, ...Steps]>
128
+ : [...Acc, S];
129
+
130
+ export type SplitLast<S extends string, Delim extends string> =
131
+ S extends `${infer Head}${Delim}${infer Tail}`
132
+ ? Tail extends `${string}${Delim}${string}`
133
+ ? SplitLast<Tail, Delim> extends [infer H2 extends string, infer T2 extends string]
134
+ ? [`${Head}${Delim}${H2}`, T2]
135
+ : [Head, Tail]
136
+ : [Head, Tail]
137
+ : [S, ""];
138
+
139
+ export type SplitOnDot<S extends string> =
140
+ S extends `${infer A}.${infer B}` ? [A, ...SplitOnDot<B>] : [S];
141
+
142
+ export type SplitOnDotClean<S extends string> =
143
+ SplitOnDot<S> extends [infer A extends string, infer B extends string, infer C extends string]
144
+ ? [CleanIdent<A>, CleanIdent<B>, CleanIdent<C>]
145
+ : SplitOnDot<S> extends [infer A extends string, infer B extends string]
146
+ ? [CleanIdent<A>, CleanIdent<B>]
147
+ : SplitOnDot<S> extends [infer A extends string]
148
+ ? [CleanIdent<A>]
149
+ : [];
150
+
151
+ export type MapClean<Tokens extends string[], Acc extends string[] = []> =
152
+ Tokens extends [infer H extends string, ...infer R extends string[]]
153
+ ? MapClean<R, [...Acc, CleanIdent<H> extends "" ? "" : TrimPunctuation<Trim<H>>]>
154
+ : Acc;
155
+
156
+
157
+ export type MapCleanLoose<Tokens extends string[], Acc extends string[] = []> =
158
+ Tokens extends [infer H extends string, ...infer R extends string[]]
159
+ ? MapCleanLoose<R, [...Acc, CleanLooseToken<H>]>
160
+ : Acc;
161
+
162
+ export type CleanLooseToken<S extends string> =
163
+ S extends OperatorToken
164
+ ? S
165
+ : CleanIdent<S> extends ""
166
+ ? ""
167
+ : CleanIdent<S>;
168
+
169
+ export type FilterEmpty<Tokens extends string[], Acc extends string[] = []> =
170
+ Tokens extends [infer H extends string, ...infer R extends string[]]
171
+ ? H extends "" ? FilterEmpty<R, Acc> : FilterEmpty<R, [...Acc, H]>
172
+ : Acc;
@@ -0,0 +1,321 @@
1
+ // Tokenization, sentinels, operators, and SQL keyword sets.
2
+ import type { CollapseSpaces, FilterEmpty, MapClean, MapCleanLoose, ReplaceAll, Split } from "./string-utils.js";
3
+ import type { ExceedsLengthBudget, HasLineBreaks } from "./normalize.js";
4
+ // Tokenization & parsing helpers
5
+
6
+ export type Tokenize<N extends string> = FilterEmpty<MapClean<Split<N, " ">>>;
7
+
8
+ // Sentinel token standing in for a TOP-LEVEL comma. It survives `MapClean`
9
+ // (no stripped punctuation, non-empty identifier) whereas a bare `,` does not,
10
+ // so it cleanly distinguishes a FROM-source separator from a comma nested in
11
+ // parens / a string literal — which must still be dropped as before.
12
+ export type CommaSep = "__tsqlcomma__";
13
+
14
+ // Replace only TOP-LEVEL commas (paren depth 0, outside single OR double quotes)
15
+ // with the `CommaSep` sentinel (space-padded so it tokenizes on its own). Commas
16
+ // nested inside parens (`count(a, b)`, FROM subqueries, `insert (x, y)`, value
17
+ // tuples), string literals, or quoted identifiers (`users as "u,1"`) are left
18
+ // verbatim and get stripped by `MapClean` as today. The `InDString` arm tracks
19
+ // double-quoted identifiers so a comma inside a quoted table/column alias is not
20
+ // mistaken for a FROM-source separator. Char-walk mirrors `SplitTopLevel` /
21
+ // `StripComments`; step-bounded.
22
+ export type MarkTopLevelCommas<
23
+ S extends string,
24
+ Depth extends any[] = [],
25
+ InString extends boolean = false,
26
+ Acc extends string = "",
27
+ Steps extends any[] = [],
28
+ InDString extends boolean = false
29
+ > = string extends S
30
+ ? S
31
+ : Steps["length"] extends 1500
32
+ ? `${Acc}${S}`
33
+ : S extends `${infer C}${infer Rest}`
34
+ ? InDString extends true
35
+ ? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], C extends `"` ? false : true>
36
+ : C extends "'"
37
+ ? MarkTopLevelCommas<Rest, Depth, InString extends true ? false : true, `${Acc}${C}`, [any, ...Steps], InDString>
38
+ : InString extends true
39
+ ? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
40
+ : C extends `"`
41
+ ? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], true>
42
+ : C extends "("
43
+ ? MarkTopLevelCommas<Rest, [any, ...Depth], InString, `${Acc}${C}`, [any, ...Steps], InDString>
44
+ : C extends ")"
45
+ ? MarkTopLevelCommas<Rest, Depth extends [any, ...infer D] ? D : [], InString, `${Acc}${C}`, [any, ...Steps], InDString>
46
+ : C extends ","
47
+ ? Depth["length"] extends 0
48
+ ? MarkTopLevelCommas<Rest, Depth, InString, `${Acc} ${CommaSep} `, [any, ...Steps], InDString>
49
+ : MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
50
+ : MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
51
+ : Acc;
52
+
53
+ // Token stream for the table/alias collectors: identical to `Tokenize` except
54
+ // top-level commas survive as `CommaSep` tokens (so `from a, b` exposes its
55
+ // source boundary). Used ONLY by `TablesInQuery` / `AliasesInQuery`.
56
+ //
57
+ // Report-scale queries (multi-line, or very long) skip the comma-marking
58
+ // char-walk and fall back to plain `Tokenize` — the same big-query light path
59
+ // `ValidateSQLNormalizedLightSelect` already takes. A comma cross-join in such a
60
+ // query is negligibly rare, and avoiding the extra instantiation depth keeps the
61
+ // largest analytics queries under the TS recursion limit.
62
+ export type TokenizeTables<N extends string> =
63
+ HasLineBreaks<N> extends true
64
+ ? Tokenize<N>
65
+ : ExceedsLengthBudget<N> extends true
66
+ ? Tokenize<N>
67
+ : FilterEmpty<MapClean<RestoreDQuotedSpaces<Split<MaybeMarkDQuotedSpaces<MarkTopLevelCommas<N>>, " ">>>>;
68
+
69
+ export type TokenizeLoose<N extends string> =
70
+ FilterEmpty<MapCleanLoose<RestoreDQuotedSpaces<
71
+ Split<CollapseSpaces<RestoreWildcards<PadOperators<ProtectWildcards<MaybeMarkDQuotedSpaces<MaybeStripDQuotedPunct<N>>>>>>, " ">
72
+ >>> extends infer Toks extends string[]
73
+ ? N extends `${string}distinct ${string}`
74
+ ? DropDistinctFrom<Toks>
75
+ : Toks
76
+ : [];
77
+
78
+ // `IS [NOT] DISTINCT FROM` is a comparison operator: its `from` is operator text,
79
+ // NOT a FROM clause / table-source boundary. The column ref-scanner skips a token
80
+ // whose `Prev` is `from` (treating it as a table source), so the RHS expression of
81
+ // the operator (`price IS DISTINCT FROM bogus_col`) escapes validation entirely
82
+ // (round-13 D1/D2). Drop the operator `from` — the one directly preceded by
83
+ // `distinct` — from the token list so the RHS's `Prev` becomes `distinct`, which
84
+ // `CanPrecedeColumn` already blesses, and the column is validated like any other.
85
+ // `distinct` is immediately followed by the bare token `from` ONLY in this
86
+ // operator, so the rewrite is unambiguous. The real FROM-clause `from` is untouched.
87
+ export type DropDistinctFrom<
88
+ Tokens extends string[],
89
+ Acc extends string[] = [],
90
+ Prev extends string = "",
91
+ Steps extends any[] = []
92
+ > = Steps["length"] extends 400
93
+ ? [...Acc, ...Tokens]
94
+ : Tokens extends [infer H extends string, ...infer R extends string[]]
95
+ ? H extends "from"
96
+ ? Prev extends "distinct"
97
+ ? DropDistinctFrom<R, Acc, "from", [any, ...Steps]>
98
+ : DropDistinctFrom<R, [...Acc, H], H, [any, ...Steps]>
99
+ : DropDistinctFrom<R, [...Acc, H], H, [any, ...Steps]>
100
+ : Acc;
101
+
102
+ // Operator/comma characters that `PadOperators` would split on. Inside a
103
+ // double-quoted identifier (`"u,1"`) these are part of the identifier, not
104
+ // structure, so splitting on them leaks bogus tokens (`u`, `1`) into the column
105
+ // ref-scan and falsely rejects an otherwise valid query. We drop them from inside
106
+ // double-quoted spans before padding so the identifier stays a single token.
107
+ export type DQuotedPunct =
108
+ "(" | ")" | "," | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "|" | "&" | "!" | "?";
109
+
110
+ // Only pay for the char-walk when there is actually a double quote to handle —
111
+ // the overwhelmingly common no-quote query short-circuits to identity.
112
+ export type MaybeStripDQuotedPunct<S extends string> =
113
+ S extends `${string}"${string}` ? StripDQuotedPunct<S> : S;
114
+
115
+ // Quote-aware walk that removes `DQuotedPunct` characters located INSIDE a
116
+ // double-quoted span while leaving the quote characters and everything outside
117
+ // the quotes untouched. `"u,1"` -> `"u1"`; `"u1".id` (no inner punctuation) is
118
+ // unchanged. Step-bounded against runaway.
119
+ export type StripDQuotedPunct<
120
+ S extends string,
121
+ InDQ extends boolean = false,
122
+ Acc extends string = "",
123
+ Steps extends any[] = []
124
+ > = string extends S
125
+ ? S
126
+ : Steps["length"] extends 1500
127
+ ? `${Acc}${S}`
128
+ : S extends `${infer C}${infer Rest}`
129
+ ? C extends `"`
130
+ ? StripDQuotedPunct<Rest, InDQ extends true ? false : true, `${Acc}${C}`, [any, ...Steps]>
131
+ : InDQ extends true
132
+ ? C extends DQuotedPunct
133
+ ? StripDQuotedPunct<Rest, InDQ, Acc, [any, ...Steps]>
134
+ : StripDQuotedPunct<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
135
+ : StripDQuotedPunct<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
136
+ : Acc;
137
+
138
+ // Sentinel standing in for a SPACE located INSIDE a double-quoted identifier.
139
+ // `Split<_, " ">` would otherwise break a quoted identifier that contains spaces
140
+ // (`"Order ID"`, `"user alias"`) into several tokens, so a quoted ORDER BY alias
141
+ // fails to resolve and a quoted table alias is mistaken for multiple table-source
142
+ // tokens. Marking the inner spaces keeps the identifier a single token through
143
+ // the space-split; `RestoreDQuotedSpaces` turns each sentinel back into a real
144
+ // space per-token before `CleanIdent`/`MapClean` runs. Mirrors `StripDQuotedPunct`.
145
+ export type DQuoteSpaceSentinel = "__tsqldqsp__";
146
+
147
+ // Only pay for the char-walk when there is actually a double quote present — the
148
+ // overwhelmingly common no-quote query short-circuits to identity.
149
+ export type MaybeMarkDQuotedSpaces<S extends string> =
150
+ S extends `${string}"${string}` ? MarkDQuotedSpaces<S> : S;
151
+
152
+ export type MarkDQuotedSpaces<
153
+ S extends string,
154
+ InDQ extends boolean = false,
155
+ Acc extends string = "",
156
+ Steps extends any[] = []
157
+ > = string extends S
158
+ ? S
159
+ : Steps["length"] extends 1500
160
+ ? `${Acc}${S}`
161
+ : S extends `${infer C}${infer Rest}`
162
+ ? C extends `"`
163
+ ? MarkDQuotedSpaces<Rest, InDQ extends true ? false : true, `${Acc}${C}`, [any, ...Steps]>
164
+ : InDQ extends true
165
+ ? C extends " "
166
+ ? MarkDQuotedSpaces<Rest, InDQ, `${Acc}${DQuoteSpaceSentinel}`, [any, ...Steps]>
167
+ : MarkDQuotedSpaces<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
168
+ : MarkDQuotedSpaces<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
169
+ : Acc;
170
+
171
+ // Restore the space sentinel to a real space in each token of a token list, so a
172
+ // quoted identifier that survived the space-split as one token (`"Order ID"`,
173
+ // `"user alias".id`) is cleaned to its true value (`order id`, `"user alias".id`).
174
+ export type RestoreDQuotedSpaces<Tokens extends string[], Acc extends string[] = []> =
175
+ Tokens extends [infer H extends string, ...infer R extends string[]]
176
+ ? RestoreDQuotedSpaces<R, [...Acc, ReplaceAll<H, DQuoteSpaceSentinel, " ">]>
177
+ : Acc;
178
+
179
+ // A validation-only view of a query: blank the CONTENTS of every single-quoted
180
+ // string literal (`'anything'` -> `''`) and mask the interior spaces of every
181
+ // double-quoted identifier. Used SOLELY on the ValidateSQL path and computed once
182
+ // at the top (before dispatch), so the char-walk completes to a concrete string
183
+ // and never compounds with validation's own instantiation depth. The result path
184
+ // is untouched, so literal value types still infer from the original text (E1).
185
+ //
186
+ // String literal contents are never column/table references, yet their interior
187
+ // words survive tokenization as bare tokens once `PadOperators` splits on the
188
+ // `(`/`)`/`,` they may contain (`' over (bogus_col)'` -> ... `bogus_col` ...),
189
+ // and the raw space-anchored clause-marker scans (` over (` / ` filter (` /
190
+ // ` within group (` / ` distinct on (` / ` using (`) likewise match inside them.
191
+ // Blanking the literal removes both problems at once (round-12 S1–S5). Masking
192
+ // double-quoted spaces stops the same markers matching inside a quoted output
193
+ // alias (round-12 A1) while leaving the identifier intact for ref validation
194
+ // (`TokenizeLoose` restores the sentinel). The caller gates this behind a quote
195
+ // and within-budget pre-check so report-scale queries never run the walk.
196
+ export type ValidationScanView<S extends string> =
197
+ S extends `${string}'${string}`
198
+ ? MaybeMarkDQuotedSpaces<BlankSingleQuotedLiterals<S>>
199
+ : MaybeMarkDQuotedSpaces<S>;
200
+
201
+ export type BlankSingleQuotedLiterals<
202
+ S extends string,
203
+ InString extends boolean = false,
204
+ Acc extends string = "",
205
+ Steps extends any[] = []
206
+ > = string extends S
207
+ ? S
208
+ : Steps["length"] extends 600
209
+ ? `${Acc}${S}`
210
+ : InString extends true
211
+ ? S extends `${infer C}${infer R}`
212
+ ? C extends "'"
213
+ ? BlankSingleQuotedLiterals<R, false, `${Acc}'`, [any, ...Steps]>
214
+ : BlankSingleQuotedLiterals<R, true, Acc, [any, ...Steps]>
215
+ : `${Acc}'`
216
+ : S extends `${infer C}${infer R}`
217
+ ? C extends "'"
218
+ ? BlankSingleQuotedLiterals<R, true, `${Acc}'`, [any, ...Steps]>
219
+ : BlankSingleQuotedLiterals<R, false, `${Acc}${C}`, [any, ...Steps]>
220
+ : Acc;
221
+
222
+ export type OperatorToken =
223
+ | "(" | ")" | "," | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "|" | "&" | "!" | "?"
224
+ // `~` / `!~` are PostgreSQL regex-match operators; `[` / `]` delimit array
225
+ // literals/subscripts. Treating them as operators makes `CanPrecedeColumn`
226
+ // bless the RHS expression so a column ref there is validated (e.g.
227
+ // `title ~ bogus_col`, `tags @> array[bogus_col]`), and keeps the operator
228
+ // tokens themselves from being mistaken for columns.
229
+ | "~" | "[" | "]";
230
+
231
+ export type PadOperator<S extends string, Op extends string> =
232
+ ReplaceAll<S, Op, ` ${Op} `>;
233
+
234
+ export type ProtectWildcards<S extends string> =
235
+ ReplaceAll<S, ".*", ".__wildcard__">;
236
+
237
+ export type RestoreWildcards<S extends string> =
238
+ ReplaceAll<S, ".__wildcard__", ".*">;
239
+
240
+ export type PadOperators<S extends string> =
241
+ PadOperator<
242
+ PadOperator<
243
+ PadOperator<
244
+ PadOperator<
245
+ PadOperator<
246
+ PadOperator<
247
+ PadOperator<
248
+ PadOperator<
249
+ PadOperator<
250
+ PadOperator<
251
+ PadOperator<
252
+ PadOperator<
253
+ PadOperator<
254
+ PadOperator<
255
+ PadOperator<
256
+ PadOperator<S, "(">,
257
+ ")">,
258
+ ",">,
259
+ "=">,
260
+ "<">,
261
+ ">">,
262
+ "+">,
263
+ "-">,
264
+ "*">,
265
+ "/">,
266
+ "|">,
267
+ "&">,
268
+ "!">,
269
+ // Split array literal/subscript brackets so the contents
270
+ // (`array[bogus_col]`) tokenize and inner column refs are validated.
271
+ "[">,
272
+ "]">,
273
+ "?">;
274
+
275
+ // SQL keywords (minimal)
276
+
277
+ export type SqlKeyword =
278
+ | "as" | "on" | "where" | "join" | "left" | "right" | "inner" | "outer" | "full" | "cross"
279
+ | "group" | "order" | "by" | "having" | "limit" | "offset" | "union" | "select" | "from"
280
+ | "update" | "insert" | "into" | "values" | "set" | "delete" | "returning" | "distinct";
281
+
282
+ export type SqlReserved =
283
+ | SqlKeyword
284
+ | "and" | "or" | "not" | "is" | "null" | "true" | "false"
285
+ | "like" | "ilike" | "in" | "between" | "exists"
286
+ // Pattern-matching keywords: `SIMILAR TO <pattern>` and `LIKE ... ESCAPE
287
+ // <char>`. The keywords themselves are never column references.
288
+ | "similar" | "to" | "escape"
289
+ | "case" | "when" | "then" | "else" | "end"
290
+ // `ARRAY` in an array literal (`ARRAY[1, 2]`) is a constructor keyword, never
291
+ // a column reference, so it must not be flagged as an invalid column.
292
+ | "array"
293
+ | "asc" | "desc" | "all"
294
+ | "interval" | "nulls" | "first" | "last"
295
+ // Window / frame clause keywords (inside OVER(...) / FILTER(...)): these are
296
+ // never column references, so they must not be flagged as invalid columns.
297
+ | "over" | "filter" | "partition" | "window" | "within"
298
+ | "range" | "rows" | "groups" | "preceding" | "following" | "unbounded";
299
+
300
+ export type SqlConstant =
301
+ | "current_date"
302
+ | "current_time"
303
+ | "current_timestamp"
304
+ | "localtime"
305
+ | "localtimestamp"
306
+ | "current_user"
307
+ | "session_user"
308
+ | "current_schema";
309
+
310
+ export type CanPrecedeColumn<Token extends string> =
311
+ Token extends "" ? true :
312
+ Token extends OperatorToken ? (Token extends ")" ? false : true) :
313
+ Token extends "select" | "where" | "on" | "and" | "or" | "by" | "having" | "set" | "values"
314
+ | "returning" | "distinct" | "case" | "when" | "then" | "else" | "not" | "is" | "in"
315
+ | "between" | "like" | "ilike"
316
+ // RHS of `SIMILAR TO <pattern>` (the `to`) and `... ESCAPE <char>`
317
+ // (the `escape`) are column-bearing expression positions, so an unknown
318
+ // bareword there must be validated like any other RHS column.
319
+ | "to" | "escape"
320
+ ? true
321
+ : false;
package/src/parsing.ts ADDED
@@ -0,0 +1,8 @@
1
+ // Barrel for the type-level SQL parser. Real declarations live in ./parsing/*.
2
+ // Importers keep using `from "./parsing.js"`; this file freezes that path.
3
+ export * from "./parsing/string-utils.js";
4
+ export * from "./parsing/pg-literals.js";
5
+ export * from "./parsing/normalize.js";
6
+ export * from "./parsing/split.js";
7
+ export * from "./parsing/extract.js";
8
+ export * from "./parsing/tokenize.js";