@kuindji/typed-sql 0.2.0 → 0.4.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 (48) hide show
  1. package/README.md +11 -3
  2. package/dist/columns.d.ts +11 -3
  3. package/dist/columns.d.ts.map +1 -1
  4. package/dist/expressions.d.ts +84 -13
  5. package/dist/expressions.d.ts.map +1 -1
  6. package/dist/parsing/extract.d.ts +13 -9
  7. package/dist/parsing/extract.d.ts.map +1 -1
  8. package/dist/parsing/normalize.d.ts +9 -3
  9. package/dist/parsing/normalize.d.ts.map +1 -1
  10. package/dist/parsing/pg-literals.d.ts +10 -2
  11. package/dist/parsing/pg-literals.d.ts.map +1 -1
  12. package/dist/parsing/split.d.ts +27 -3
  13. package/dist/parsing/split.d.ts.map +1 -1
  14. package/dist/parsing/string-utils.d.ts +2 -4
  15. package/dist/parsing/string-utils.d.ts.map +1 -1
  16. package/dist/parsing/tokenize.d.ts +27 -17
  17. package/dist/parsing/tokenize.d.ts.map +1 -1
  18. package/dist/partial.d.ts +6 -6
  19. package/dist/partial.d.ts.map +1 -1
  20. package/dist/tables.d.ts +58 -13
  21. package/dist/tables.d.ts.map +1 -1
  22. package/dist/validation/dispatch.d.ts +7 -5
  23. package/dist/validation/dispatch.d.ts.map +1 -1
  24. package/dist/validation/joins.d.ts +3 -3
  25. package/dist/validation/joins.d.ts.map +1 -1
  26. package/dist/validation/return-derived.d.ts +8 -4
  27. package/dist/validation/return-derived.d.ts.map +1 -1
  28. package/dist/validation/return-derived.js.map +1 -1
  29. package/dist/validation/return-types.d.ts +1 -1
  30. package/dist/validation/return-types.d.ts.map +1 -1
  31. package/dist/validation/validate-columns.d.ts +27 -16
  32. package/dist/validation/validate-columns.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/columns.ts +168 -32
  35. package/src/expressions.ts +589 -63
  36. package/src/parsing/extract.ts +72 -32
  37. package/src/parsing/normalize.ts +114 -10
  38. package/src/parsing/pg-literals.ts +32 -10
  39. package/src/parsing/split.ts +236 -72
  40. package/src/parsing/string-utils.ts +15 -15
  41. package/src/parsing/tokenize.ts +224 -146
  42. package/src/partial.ts +9 -15
  43. package/src/tables.ts +546 -183
  44. package/src/validation/dispatch.ts +58 -52
  45. package/src/validation/joins.ts +15 -19
  46. package/src/validation/return-derived.ts +60 -4
  47. package/src/validation/return-types.ts +9 -3
  48. package/src/validation/validate-columns.ts +161 -67
@@ -36,28 +36,40 @@ export type FirstTopLevelReturningTail<S extends string> =
36
36
  ? FirstTopLevelReturningTailWalk<S>
37
37
  : S extends `${string} returning ${infer After}` ? After : "";
38
38
 
39
+ // Quote-jump twin of `HasReturningQuoteAwareWalk` (validation/dispatch.ts):
40
+ // find the leftmost ` returning `; if no quote opens before it, its tail is the
41
+ // answer. Otherwise jump the leftmost quote span whole (`'…'` or `"…"`,
42
+ // whichever opens first — the other quote kind inside the span is data) and
43
+ // re-test the remainder. O(quote spans) instead of O(chars); an unterminated
44
+ // quote swallows the rest, exactly like the old walk-to-EOF inside a literal.
39
45
  type FirstTopLevelReturningTailWalk<
40
46
  S extends string,
41
- InString extends boolean = false,
42
- InDString extends boolean = false,
43
47
  Steps extends any[] = []
44
48
  > = string extends S
45
49
  ? ""
46
- : Steps["length"] extends 1200
50
+ : Steps["length"] extends 400
47
51
  ? S extends `${string} returning ${infer After}` ? After : ""
48
- : InString extends true
49
- ? S extends `${infer C}${infer Rest}`
50
- ? FirstTopLevelReturningTailWalk<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
52
+ : S extends `${infer Before} returning ${infer After}`
53
+ ? Before extends `${string}'${string}` | `${string}"${string}`
54
+ ? FtrtQuoteJump<S, Steps>
55
+ : After
56
+ : "";
57
+
58
+ type FtrtQuoteJump<S extends string, Steps extends any[]> =
59
+ S extends `${infer P}'${infer R}`
60
+ ? P extends `${string}"${string}`
61
+ ? FtrtDQuoteJump<S, Steps>
62
+ : R extends `${string}'${infer R2}`
63
+ ? FirstTopLevelReturningTailWalk<R2, [any, ...Steps]>
51
64
  : ""
52
- : InDString extends true
53
- ? S extends `${infer C}${infer Rest}`
54
- ? FirstTopLevelReturningTailWalk<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
55
- : ""
56
- : S extends ` returning ${infer After}`
57
- ? After
58
- : S extends `${infer C}${infer Rest}`
59
- ? FirstTopLevelReturningTailWalk<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
60
- : "";
65
+ : FtrtDQuoteJump<S, Steps>;
66
+
67
+ type FtrtDQuoteJump<S extends string, Steps extends any[]> =
68
+ S extends `${string}"${infer R}`
69
+ ? R extends `${string}"${infer R2}`
70
+ ? FirstTopLevelReturningTailWalk<R2, [any, ...Steps]>
71
+ : ""
72
+ : "";
61
73
 
62
74
  // Given a string whose first non-skipped char is `(`, consume the first
63
75
  // balanced parenthesised group (quote-aware) and return its inner content plus
@@ -72,6 +84,15 @@ type FirstTopLevelReturningTailWalk<
72
84
  // every CHUNK steps and a driver that re-invokes it with a fresh step counter —
73
85
  // resetting TS's per-chain tail-recursion count, so arbitrarily long groups split
74
86
  // losslessly. Mirrors the `SplitTopLevel` worker/driver above.
87
+ //
88
+ // Struct-jump, not per-char (the old walk minted one growing-`Acc` string PER
89
+ // CHARACTER across entire CTE/subquery bodies). Each step advances to the
90
+ // LEFTMOST of the three state chars `'` `(` `)` (double quotes were never
91
+ // tracked here — preserved), copying the whole run before it in a single mint;
92
+ // inside a `'…'` literal it jumps straight to the closing quote (`''` escapes
93
+ // exit+re-enter across two jumps; an unterminated quote at EOF copies the rest
94
+ // verbatim). The `Steps` cap counts JUMPS and yields `{ __c: [...] }` to the
95
+ // driver, so arbitrarily paren-dense inputs still complete losslessly.
75
96
  type SplitBalancedParenWorker<
76
97
  S extends string,
77
98
  Depth extends any[] = [],
@@ -80,23 +101,42 @@ type SplitBalancedParenWorker<
80
101
  Steps extends any[] = []
81
102
  > = Steps["length"] extends 350
82
103
  ? { __c: [S, Depth, Acc, InString] }
83
- : S extends `${infer C}${infer Rest}`
84
- ? C extends "'"
85
- ? SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString extends true ? false : true, [any, ...Steps]>
86
- : InString extends true
87
- ? SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString, [any, ...Steps]>
88
- : C extends "("
89
- ? Depth["length"] extends 0
90
- ? SplitBalancedParenWorker<Rest, [any], Acc, InString, [any, ...Steps]>
91
- : SplitBalancedParenWorker<Rest, [any, ...Depth], `${Acc}${C}`, InString, [any, ...Steps]>
92
- : C extends ")"
93
- ? Depth extends [any, ...infer D extends any[]]
94
- ? D["length"] extends 0
95
- ? { inner: Acc; rest: Rest }
96
- : SplitBalancedParenWorker<Rest, D, `${Acc}${C}`, InString, [any, ...Steps]>
97
- : { inner: Acc; rest: Rest }
98
- : SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString, [any, ...Steps]>
99
- : { inner: Acc; rest: "" };
104
+ : InString extends true
105
+ ? S extends `${infer P}'${infer R}`
106
+ ? SplitBalancedParenWorker<R, Depth, `${Acc}${P}'`, false, [any, ...Steps]>
107
+ : { inner: `${Acc}${S}`; rest: "" }
108
+ : S extends `${infer P}'${infer R}`
109
+ ? P extends `${string}(${string}` | `${string})${string}`
110
+ ? SbpParenJump<S, Depth, Acc, Steps>
111
+ : SplitBalancedParenWorker<R, Depth, `${Acc}${P}'`, true, [any, ...Steps]>
112
+ : SbpParenJump<S, Depth, Acc, Steps>;
113
+
114
+ // Leftmost of `(` / `)` (the caller guarantees no `'` occurs before either).
115
+ type SbpParenJump<
116
+ S extends string,
117
+ Depth extends any[],
118
+ Acc extends string,
119
+ Steps extends any[]
120
+ > = S extends `${infer P}(${infer R}`
121
+ ? P extends `${string})${string}`
122
+ ? SbpCloseJump<S, Depth, Acc, Steps>
123
+ : Depth["length"] extends 0
124
+ // the group-opening `(` itself is consumed, not copied into `inner`
125
+ ? SplitBalancedParenWorker<R, [any], `${Acc}${P}`, false, [any, ...Steps]>
126
+ : SplitBalancedParenWorker<R, [any, ...Depth], `${Acc}${P}(`, false, [any, ...Steps]>
127
+ : SbpCloseJump<S, Depth, Acc, Steps>;
128
+
129
+ type SbpCloseJump<
130
+ S extends string,
131
+ Depth extends any[],
132
+ Acc extends string,
133
+ Steps extends any[]
134
+ > = S extends `${infer P})${infer R}`
135
+ ? Depth extends [any, any, ...infer D extends any[]]
136
+ ? SplitBalancedParenWorker<R, [any, ...D], `${Acc}${P})`, false, [any, ...Steps]>
137
+ // depth ≤ 1: this `)` closes the group (or is an unmatched top-level `)`)
138
+ : { inner: `${Acc}${P}`; rest: R }
139
+ : { inner: `${Acc}${S}`; rest: "" };
100
140
 
101
141
  export type SplitBalancedParen<S extends string> =
102
142
  SplitBalancedParenDrive<SplitBalancedParenWorker<S>>;
@@ -24,7 +24,65 @@ import type { NeutralizePgLiterals, RewriteExtractCall, StripComments } from "./
24
24
  // length, so the "collapse first to fit the lowercaser under its cap" rationale no
25
25
  // longer requires a second collapse afterwards.
26
26
  export type NormalizeQuery<S extends string> =
27
- RewriteExtractCall<Trim<RemoveTrailingSemicolon<LowercaseOutsideQuotes<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>;
27
+ RewriteExtractCall<Trim<RemoveTrailingSemicolon<CollapseDotSpaces<LowercaseOutsideQuotes<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>>;
28
+
29
+ // Collapse whitespace around the `.` qualifier separator: `X .Y` / `X. Y` /
30
+ // `X . Y` -> `X.Y`. PostgreSQL allows whitespace around the qualifier dot
31
+ // (`tbl . col` === `tbl.col`); the normalizer's ReplaceWhitespace+CollapseSpaces
32
+ // turns a multi-line qualified ref (`tbl\n .col`) into `tbl .col`, which would
33
+ // otherwise tokenize as the orphan `.col` plus a bare `tbl` that the
34
+ // unqualified-column check then rejects as a (non-existent) column. Rejoining the
35
+ // dot lets `tbl.col` resolve normally.
36
+ //
37
+ // CHEAP PRE-GATE: the overwhelmingly common query has no spaced dot, so a single
38
+ // `" ."` / `". "` membership test short-circuits to identity in ~1 instantiation.
39
+ export type CollapseDotSpaces<S extends string> =
40
+ S extends `${string} .${string}`
41
+ ? MaybeCollapseDotSpaces<S>
42
+ : S extends `${string}. ${string}`
43
+ ? MaybeCollapseDotSpaces<S>
44
+ : S;
45
+
46
+ // Single-quoted literals are already blanked to '' by NeutralizePgLiterals, so
47
+ // only a double-quoted identifier could host a spaced dot (`"a . b"`). With no
48
+ // `"` present the quote-unaware ladder is provably safe; otherwise defer to a
49
+ // quote-aware walk that collapses dots only OUTSIDE double-quoted spans.
50
+ type MaybeCollapseDotSpaces<S extends string> =
51
+ S extends `${string}"${string}`
52
+ ? CollapseDotSpacesQuoteAware<S, false, "", []>
53
+ : CollapseDotSpacesWalk<S>;
54
+
55
+ // O(spaced dots), not O(chars): `${infer A}` jumps a whole non-matching run per
56
+ // step, so a string with no further spaced dot exits in one instantiation. Step
57
+ // cap is a runaway backstop, far under TS's recursion ceiling.
58
+ type CollapseDotSpacesWalk<S extends string, Steps extends any[] = []> =
59
+ Steps["length"] extends 400
60
+ ? S
61
+ : S extends `${infer A} . ${infer B}`
62
+ ? CollapseDotSpacesWalk<`${A}.${B}`, [any, ...Steps]>
63
+ : S extends `${infer A} .${infer B}`
64
+ ? CollapseDotSpacesWalk<`${A}.${B}`, [any, ...Steps]>
65
+ : S extends `${infer A}. ${infer B}`
66
+ ? CollapseDotSpacesWalk<`${A}.${B}`, [any, ...Steps]>
67
+ : S;
68
+
69
+ // Quote-aware tier: copy `"..."` identifier spans verbatim, collapse spaced dots
70
+ // only in the runs OUTSIDE them, so a column literally named `"a . b"` is never
71
+ // corrupted. Depth is O(double-quote boundaries).
72
+ type CollapseDotSpacesQuoteAware<
73
+ S extends string,
74
+ InDQ extends boolean,
75
+ Acc extends string,
76
+ Steps extends any[]
77
+ > = Steps["length"] extends 400
78
+ ? `${Acc}${S}`
79
+ : InDQ extends true
80
+ ? S extends `${infer P}"${infer R}`
81
+ ? CollapseDotSpacesQuoteAware<R, false, `${Acc}${P}"`, [any, ...Steps]>
82
+ : `${Acc}${S}`
83
+ : S extends `${infer P}"${infer R}`
84
+ ? CollapseDotSpacesQuoteAware<R, true, `${Acc}${CollapseDotSpacesWalk<P>}"`, [any, ...Steps]>
85
+ : `${Acc}${CollapseDotSpacesWalk<S>}`;
28
86
 
29
87
  // Quote-aware lowercasing: SQL keywords/identifiers are case-insensitive, but
30
88
  // single-quoted string literals and double-quoted identifiers keep their exact
@@ -221,7 +279,7 @@ type LcKeepQuoteJump<
221
279
  // NormalizeQuery variant that preserves `:name` param case — used by the
222
280
  // write/raw builder param extraction (ExtractParams) only.
223
281
  export type NormalizeQueryKeepParams<S extends string> =
224
- RewriteExtractCall<Trim<RemoveTrailingSemicolon<LowercaseOutsideQuotesKeepParams<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>;
282
+ RewriteExtractCall<Trim<RemoveTrailingSemicolon<CollapseDotSpaces<LowercaseOutsideQuotesKeepParams<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>>;
225
283
 
226
284
  // Convert every `\n` / `\t` / `\r` to a space. OCCURRENCE-based (like
227
285
  // `CollapseSpaces`): each step splits at the FIRST remaining line break, so the
@@ -237,18 +295,64 @@ export type ReplaceWhitespace<S extends string> =
237
295
  ? ReplaceWhitespaceRuns<S>
238
296
  : S;
239
297
 
240
- type ReplaceWhitespaceRuns<S extends string, Steps extends any[] = []> =
298
+ // Acc-carrying worker: the finished prefix moves into `Acc` and is NEVER
299
+ // rescanned or reminted (the old form rebuilt the FULL string once per line
300
+ // break and re-matched it from the start). Because consumed segments leave the
301
+ // scan, the worker must take the LEFTMOST of `\n`/`\t`/`\r` each step — the
302
+ // old rebuild-and-rescan form got that for free by always re-matching `\n`
303
+ // first over the whole string. Same pairwise-narrowing cascade as
304
+ // `MtcStructJump`. The step cap counts whitespace RUNS (each consumed whole by
305
+ // `ConsumeWsRun`), so it covers at least as much input as the old
306
+ // per-occurrence cap; on cap the remainder is left as-is, as before.
307
+ type ReplaceWhitespaceRuns<S extends string, Acc extends string = "", Steps extends any[] = []> =
241
308
  string extends S
242
309
  ? S
243
310
  : Steps["length"] extends 1500
244
- ? S
311
+ ? `${Acc}${S}`
245
312
  : S extends `${infer A}\n${infer B}`
246
- ? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
247
- : S extends `${infer A}\t${infer B}`
248
- ? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
249
- : S extends `${infer A}\r${infer B}`
250
- ? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
251
- : S;
313
+ ? A extends `${string}\t${string}` | `${string}\r${string}`
314
+ ? RwrTabCr<S, Acc, Steps>
315
+ : ReplaceWhitespaceRuns<ConsumeWsRun<B>, `${Acc}${A} `, [any, ...Steps]>
316
+ : RwrTabCr<S, Acc, Steps>;
317
+
318
+ // Leftmost of `\t` / `\r` (caller ruled out an earlier `\n`).
319
+ type RwrTabCr<S extends string, Acc extends string, Steps extends any[]> =
320
+ S extends `${infer A}\t${infer B}`
321
+ ? A extends `${string}\r${string}`
322
+ ? S extends `${infer A2}\r${infer B2}`
323
+ ? ReplaceWhitespaceRuns<ConsumeWsRun<B2>, `${Acc}${A2} `, [any, ...Steps]>
324
+ : `${Acc}${S}`
325
+ : ReplaceWhitespaceRuns<ConsumeWsRun<B>, `${Acc}${A} `, [any, ...Steps]>
326
+ : S extends `${infer A2}\r${infer B2}`
327
+ ? ReplaceWhitespaceRuns<ConsumeWsRun<B2>, `${Acc}${A2} `, [any, ...Steps]>
328
+ : `${Acc}${S}`;
329
+
330
+ // Eat the whole whitespace run FOLLOWING a consumed line break before the full
331
+ // string is rebuilt. A formatted query's `\n␣␣␣␣` indentation otherwise survives
332
+ // as a multi-space run that costs `ReplaceWhitespaceRuns` extra full-string
333
+ // remints (one per `\n` of a blank line) plus one more full remint per run in
334
+ // `CollapseSpaces`. Peeling here works on the TAIL only — far cheaper mints —
335
+ // and leaves `CollapseSpaces` a no-op for these runs. Equivalence: both forms
336
+ // reduce every whitespace run that touches a line break to a single space, and
337
+ // runs NOT touching a line break are still collapsed by the unchanged
338
+ // `CollapseSpaces` pass. A capped-out leftover (`\t`/`\n`/`\r` beyond the
339
+ // budget) is still caught by the outer loop's own branches.
340
+ type ConsumeWsRun<S extends string, Steps extends any[] = []> =
341
+ Steps["length"] extends 64
342
+ ? S
343
+ : S extends ` ${infer R}` // 16 spaces
344
+ ? ConsumeWsRun<R, [any, ...Steps]>
345
+ : S extends ` ${infer R}` // 4 spaces
346
+ ? ConsumeWsRun<R, [any, ...Steps]>
347
+ : S extends ` ${infer R}`
348
+ ? ConsumeWsRun<R, [any, ...Steps]>
349
+ : S extends `\t${infer R}`
350
+ ? ConsumeWsRun<R, [any, ...Steps]>
351
+ : S extends `\n${infer R}`
352
+ ? ConsumeWsRun<R, [any, ...Steps]>
353
+ : S extends `\r${infer R}`
354
+ ? ConsumeWsRun<R, [any, ...Steps]>
355
+ : S;
252
356
 
253
357
  // Cheap "is this string longer than ~500 chars" check: drop 10 chars per step
254
358
  // for up to 50 steps. If content survives all 50 drops the string exceeds the
@@ -170,16 +170,38 @@ export type RewriteExtractCall<S extends string> =
170
170
  : RewriteExtractWalk<S>
171
171
  : S;
172
172
 
173
- export type RewriteExtractWalk<S extends string, Steps extends any[] = []> =
174
- Steps["length"] extends 24
175
- ? S
176
- : S extends `${infer Pre} extract(${infer AfterOpen}`
177
- ? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
178
- ? Inner extends `${infer _Field} from ${infer Source}`
179
- ? `${RewriteExtractWalk<Pre, [any, ...Steps]>} extract(${Trim<Source>})${RewriteExtractWalk<Rest, [any, ...Steps]>}`
180
- : `${RewriteExtractWalk<Pre, [any, ...Steps]>} extract(${Inner})${RewriteExtractWalk<Rest, [any, ...Steps]>}`
181
- : S
182
- : S;
173
+ // Tail-recursive accumulator walk + chunked driver. Template matching is
174
+ // LEFTMOST, so `Pre` can never contain another ` extract(` — it is appended to
175
+ // `Acc` verbatim and only `Rest` is recursed. (The previous version recursed
176
+ // into BOTH `Pre` and `Rest` building a nested template, so its step cap had to
177
+ // stay tiny 24 and a 50-projection report query with 25+ EXTRACTs bailed
178
+ // half-rewritten: the surviving inner ` from ` tokens then fed the tables
179
+ // collector bogus sources like `min(ua.col`, flipping ValidateSQL to a false
180
+ // rejection.) The driver re-invokes the worker with a fresh step counter every
181
+ // 64 rewrites, so any realistic number of EXTRACTs completes losslessly.
182
+ export type RewriteExtractWalk<S extends string> =
183
+ RewExDrive<RewExWorker<S>>;
184
+
185
+ type RewExDrive<R> =
186
+ [R] extends [never]
187
+ ? never
188
+ : R extends { __c: [infer S extends string, infer Acc extends string] }
189
+ ? RewExDrive<RewExWorker<S, Acc, []>>
190
+ : R;
191
+
192
+ type RewExWorker<
193
+ S extends string,
194
+ Acc extends string = "",
195
+ Steps extends any[] = []
196
+ > = Steps["length"] extends 64
197
+ ? { __c: [S, Acc] }
198
+ : S extends `${infer Pre} extract(${infer AfterOpen}`
199
+ ? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
200
+ ? Inner extends `${infer _Field} from ${infer Source}`
201
+ ? RewExWorker<Rest, `${Acc}${Pre} extract(${Trim<Source>})`, [any, ...Steps]>
202
+ : RewExWorker<Rest, `${Acc}${Pre} extract(${Inner})`, [any, ...Steps]>
203
+ : `${Acc}${S}`
204
+ : `${Acc}${S}`;
183
205
 
184
206
  // As `RewriteExtractWalk`, but an ` extract(` whose prefix has an odd number of
185
207
  // single quotes sits INSIDE a string literal and is left verbatim (the literal's