@kuindji/typed-sql 0.1.0 → 0.2.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 (47) hide show
  1. package/README.md +7 -0
  2. package/dist/builder/assemble.d.ts.map +1 -1
  3. package/dist/builder/assemble.js +9 -10
  4. package/dist/builder/assemble.js.map +1 -1
  5. package/dist/builder/conditional-sql.d.ts +7 -2
  6. package/dist/builder/conditional-sql.d.ts.map +1 -1
  7. package/dist/builder/conditional-sql.js +9 -22
  8. package/dist/builder/conditional-sql.js.map +1 -1
  9. package/dist/builder/params.d.ts +2 -1
  10. package/dist/builder/params.d.ts.map +1 -1
  11. package/dist/builder/params.js +34 -27
  12. package/dist/builder/params.js.map +1 -1
  13. package/dist/builder/select.d.ts +7 -0
  14. package/dist/builder/select.d.ts.map +1 -1
  15. package/dist/builder/select.js +7 -2
  16. package/dist/builder/select.js.map +1 -1
  17. package/dist/builder/state.d.ts +3 -6
  18. package/dist/builder/state.d.ts.map +1 -1
  19. package/dist/builder/state.js +1 -2
  20. package/dist/builder/state.js.map +1 -1
  21. package/dist/expressions.d.ts +1 -1
  22. package/dist/expressions.d.ts.map +1 -1
  23. package/dist/parsing/extract.d.ts +2 -1
  24. package/dist/parsing/extract.d.ts.map +1 -1
  25. package/dist/parsing/normalize.d.ts +4 -3
  26. package/dist/parsing/normalize.d.ts.map +1 -1
  27. package/dist/parsing/pg-literals.d.ts +2 -2
  28. package/dist/parsing/pg-literals.d.ts.map +1 -1
  29. package/dist/parsing/tokenize.d.ts +8 -6
  30. package/dist/parsing/tokenize.d.ts.map +1 -1
  31. package/dist/validation/dispatch.d.ts +3 -1
  32. package/dist/validation/dispatch.d.ts.map +1 -1
  33. package/dist/validation/validate-columns.d.ts +1 -1
  34. package/dist/validation/validate-columns.d.ts.map +1 -1
  35. package/package.json +3 -2
  36. package/src/builder/assemble.ts +9 -13
  37. package/src/builder/conditional-sql.ts +9 -27
  38. package/src/builder/params.ts +33 -27
  39. package/src/builder/select.ts +19 -2
  40. package/src/builder/state.ts +4 -6
  41. package/src/expressions.ts +8 -1
  42. package/src/parsing/extract.ts +18 -4
  43. package/src/parsing/normalize.ts +83 -46
  44. package/src/parsing/pg-literals.ts +23 -12
  45. package/src/parsing/tokenize.ts +56 -23
  46. package/src/validation/dispatch.ts +21 -3
  47. package/src/validation/validate-columns.ts +13 -7
@@ -1,9 +1,9 @@
1
1
  // Tokenization, sentinels, operators, and SQL keyword sets.
2
- import type { CollapseSpaces, FilterEmpty, MapClean, MapCleanLoose, ReplaceAll, Split } from "./string-utils.js";
2
+ import type { CleanIdent, CleanLooseToken, CollapseSpaces, ReplaceAll, Split, Trim, TrimPunctuation } from "./string-utils.js";
3
3
  import type { ExceedsLengthBudget, HasLineBreaks } from "./normalize.js";
4
4
  // Tokenization & parsing helpers
5
5
 
6
- export type Tokenize<N extends string> = FilterEmpty<MapClean<Split<N, " ">>>;
6
+ export type Tokenize<N extends string> = CleanFilterTokens<Split<N, " ">>;
7
7
 
8
8
  // Sentinel token standing in for a TOP-LEVEL comma. It survives `MapClean`
9
9
  // (no stripped punctuation, non-empty identifier) whereas a bare `,` does not,
@@ -64,12 +64,12 @@ export type TokenizeTables<N extends string> =
64
64
  ? Tokenize<N>
65
65
  : ExceedsLengthBudget<N> extends true
66
66
  ? Tokenize<N>
67
- : FilterEmpty<MapClean<RestoreDQuotedSpaces<Split<MaybeMarkDQuotedSpaces<MarkTopLevelCommas<N>>, " ">>>>;
67
+ : RestoreCleanFilterTokens<Split<MaybeMarkDQuotedSpaces<MarkTopLevelCommas<N>>, " ">>;
68
68
 
69
69
  export type TokenizeLoose<N extends string> =
70
- FilterEmpty<MapCleanLoose<RestoreDQuotedSpaces<
70
+ RestoreCleanLooseFilterTokens<
71
71
  Split<CollapseSpaces<RestoreWildcards<PadOperators<ProtectWildcards<MaybeMarkDQuotedSpaces<MaybeStripDQuotedPunct<N>>>>>>, " ">
72
- >>> extends infer Toks extends string[]
72
+ > extends infer Toks extends string[]
73
73
  ? N extends `${string}distinct ${string}`
74
74
  ? DropDistinctFrom<Toks>
75
75
  : Toks
@@ -168,12 +168,45 @@ export type MarkDQuotedSpaces<
168
168
  : MarkDQuotedSpaces<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
169
169
  : Acc;
170
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[] = []> =
171
+ // Fused token post-passes: one walk instead of the old
172
+ // `FilterEmpty<MapClean<RestoreDQuotedSpaces<…>>>` three-walk chain. Each pass was
173
+ // an independent element-wise map/filter, so composing them per token yields the
174
+ // identical list (ordering preserved) while building the result spine once.
175
+ //
176
+ // The DQuote-space sentinel restore (`ReplaceAll<H, DQuoteSpaceSentinel, " ">`) lets
177
+ // a quoted identifier that survived the space-split as one token (`"Order ID"`,
178
+ // `"user alias".id`) clean to its true value. `CleanFilterTokens` is the no-restore
179
+ // variant (plain `Tokenize`, which never marks sentinels).
180
+ //
181
+ // MapClean maps each token to `CleanIdent<H> extends "" ? "" : TrimPunctuation<Trim<H>>`
182
+ // and FilterEmpty drops the `""`s. Since `CleanIdent = Lowercase<Unquote<TrimPunctuation<
183
+ // Trim<S>>>>`, a non-empty `CleanIdent<H>` guarantees a non-empty `TrimPunctuation<Trim<H>>`,
184
+ // so the kept value is never empty — the empty-token filter collapses to the single
185
+ // `CleanIdent<H> extends ""` test. (The loose variant keeps an explicit empty filter
186
+ // because `CleanLooseToken` can return `""` for a non-operator empty ident.)
187
+ export type CleanFilterTokens<Tokens extends string[], Acc extends string[] = []> =
175
188
  Tokens extends [infer H extends string, ...infer R extends string[]]
176
- ? RestoreDQuotedSpaces<R, [...Acc, ReplaceAll<H, DQuoteSpaceSentinel, " ">]>
189
+ ? CleanIdent<H> extends ""
190
+ ? CleanFilterTokens<R, Acc>
191
+ : CleanFilterTokens<R, [...Acc, TrimPunctuation<Trim<H>>]>
192
+ : Acc;
193
+
194
+ export type RestoreCleanFilterTokens<Tokens extends string[], Acc extends string[] = []> =
195
+ Tokens extends [infer H0 extends string, ...infer R extends string[]]
196
+ ? ReplaceAll<H0, DQuoteSpaceSentinel, " "> extends infer H extends string
197
+ ? CleanIdent<H> extends ""
198
+ ? RestoreCleanFilterTokens<R, Acc>
199
+ : RestoreCleanFilterTokens<R, [...Acc, TrimPunctuation<Trim<H>>]>
200
+ : never
201
+ : Acc;
202
+
203
+ export type RestoreCleanLooseFilterTokens<Tokens extends string[], Acc extends string[] = []> =
204
+ Tokens extends [infer H0 extends string, ...infer R extends string[]]
205
+ ? CleanLooseToken<ReplaceAll<H0, DQuoteSpaceSentinel, " ">> extends infer M extends string
206
+ ? M extends ""
207
+ ? RestoreCleanLooseFilterTokens<R, Acc>
208
+ : RestoreCleanLooseFilterTokens<R, [...Acc, M]>
209
+ : never
177
210
  : Acc;
178
211
 
179
212
  // A validation-only view of a query: blank the CONTENTS of every single-quoted
@@ -198,26 +231,26 @@ export type ValidationScanView<S extends string> =
198
231
  ? MaybeMarkDQuotedSpaces<BlankSingleQuotedLiterals<S>>
199
232
  : MaybeMarkDQuotedSpaces<S>;
200
233
 
234
+ // Pairwise marker-jump: hop to the opening `'`, then to its closing `'`, emitting
235
+ // the verbatim prefix plus a blanked body `''`, and recurse on the tail. The `''`
236
+ // SQL escape pairs LEFTMOST exactly as the old per-char toggle did (`'it''s'` →
237
+ // `''''`); an UNTERMINATED opener (no closing `'`) is closed off with an appended
238
+ // `'`, matching the old EOF-in-string branch (`${Acc}'`), so `…'xyz` → `…''`. Depth
239
+ // is now the NUMBER OF LITERALS (a handful), not the string length (≤600 before).
240
+ // Step cap retained purely as a runaway backstop for a pathological quote storm.
201
241
  export type BlankSingleQuotedLiterals<
202
242
  S extends string,
203
- InString extends boolean = false,
204
243
  Acc extends string = "",
205
244
  Steps extends any[] = []
206
245
  > = string extends S
207
246
  ? S
208
- : Steps["length"] extends 600
247
+ : Steps["length"] extends 300
209
248
  ? `${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;
249
+ : S extends `${infer Pre}'${infer Rest}`
250
+ ? Rest extends `${infer _Lit}'${infer After}`
251
+ ? BlankSingleQuotedLiterals<After, `${Acc}${Pre}''`, [any, ...Steps]>
252
+ : `${Acc}${Pre}''`
253
+ : `${Acc}${S}`;
221
254
 
222
255
  export type OperatorToken =
223
256
  | "(" | ")" | "," | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "|" | "&" | "!" | "?"
@@ -288,25 +288,43 @@ export type HasReturning<N extends string> =
288
288
  ? false
289
289
  : HasReturningQuoteAware<N>;
290
290
 
291
+ // Quote-free fast-path: a query with no `'` and no `"` has no place for a
292
+ // ` returning ` to hide, so every occurrence is top-level — a single pattern test
293
+ // is exact and skips the ~1200-step char-walk (these run on every DML). Only
294
+ // quote-bearing queries pay for the walk below. The fast-path pattern matches the
295
+ // step-cap fallback this walk already uses, so it is consistent with prior behavior.
291
296
  export type HasReturningQuoteAware<
292
297
  S extends string,
293
298
  InString extends boolean = false,
294
299
  InDString extends boolean = false,
295
300
  Steps extends any[] = []
301
+ > = string extends S
302
+ ? false
303
+ : S extends `${string}'${string}`
304
+ ? HasReturningQuoteAwareWalk<S, InString, InDString, Steps>
305
+ : S extends `${string}"${string}`
306
+ ? HasReturningQuoteAwareWalk<S, InString, InDString, Steps>
307
+ : S extends `${string} returning ${string}` ? true : false;
308
+
309
+ type HasReturningQuoteAwareWalk<
310
+ S extends string,
311
+ InString extends boolean = false,
312
+ InDString extends boolean = false,
313
+ Steps extends any[] = []
296
314
  > = string extends S
297
315
  ? false
298
316
  : Steps["length"] extends 1200
299
317
  ? S extends `${string} returning ${string}` ? true : false
300
318
  : InString extends true
301
319
  ? S extends `${infer C}${infer Rest}`
302
- ? HasReturningQuoteAware<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
320
+ ? HasReturningQuoteAwareWalk<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
303
321
  : false
304
322
  : InDString extends true
305
323
  ? S extends `${infer C}${infer Rest}`
306
- ? HasReturningQuoteAware<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
324
+ ? HasReturningQuoteAwareWalk<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
307
325
  : false
308
326
  : S extends ` returning ${string}`
309
327
  ? true
310
328
  : S extends `${infer C}${infer Rest}`
311
- ? HasReturningQuoteAware<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
329
+ ? HasReturningQuoteAwareWalk<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
312
330
  : false;
@@ -402,15 +402,21 @@ export type NoAliasShadowedQualifiers<
402
402
  > =
403
403
  [AliasedTableKeys<Aliases>] extends [never]
404
404
  ? true
405
- : AllTrue<
406
- QualifiedColumnRefs<TokenizeLoose<N>, S, Tables, Aliases> extends infer R
407
- ? R extends `${infer Q}.${string}`
408
- ? QualifierShadowedByAlias<Q, Tables, Aliases, S> extends true
409
- ? false
405
+ // A shadowable qualifier is a `qualifier.column` token, which requires a
406
+ // `.`. With no `.` anywhere, `QualifiedColumnRefs` accumulates `never` and
407
+ // `AllTrue<never>` is `true` — so skip the whole-query `TokenizeLoose<N>`
408
+ // re-walk (computed nowhere else) on dot-free queries. Exact-equivalent.
409
+ : N extends `${string}.${string}`
410
+ ? AllTrue<
411
+ QualifiedColumnRefs<TokenizeLoose<N>, S, Tables, Aliases> extends infer R
412
+ ? R extends `${infer Q}.${string}`
413
+ ? QualifierShadowedByAlias<Q, Tables, Aliases, S> extends true
414
+ ? false
415
+ : true
410
416
  : true
411
417
  : true
412
- : true
413
- >;
418
+ >
419
+ : true;
414
420
 
415
421
  // A table introduced INSIDE a subquery is in scope only there — it must not
416
422
  // satisfy an UNQUALIFIED column reference in the OUTER query. The whole-query