@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
@@ -6,7 +6,7 @@ import type { CteNames, NonCteTables, SingleCteMatch } from "./cte.js";
6
6
  import type { DatabaseSchema } from "../schema.js";
7
7
  import type { DerivedAliasName, DerivedFirstWord, DerivedTableMatch } from "./return-derived.js";
8
8
  import type { DistinctOnColsValid, JoinUsingColsValid, WindowFilterColsValid } from "./joins.js";
9
- import type { ExceedsLengthBudget, ExtractBefore, ExtractLastWhere, HasLineBreaks, TokenizeLoose, Trim, ValidationScanView } from "../parsing.js";
9
+ import type { ExceedsLengthBudget, ExtractBefore, ExtractLastWhere, HasLineBreaks, Trim, ValidationScanView } from "../parsing.js";
10
10
  import type { RefScanSegment } from "./return-types.js";
11
11
  // Core validation / inference
12
12
 
@@ -126,16 +126,13 @@ export type WhereColsValidForUpdate<
126
126
  TargetKey extends string,
127
127
  AliasEntry extends string,
128
128
  S extends DatabaseSchema
129
- > =
130
- TokenizeLoose<W> extends infer WT extends string[]
131
- ? And<
132
- QualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, WT>,
133
- UnqualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, WT, never>,
134
- true,
135
- true,
136
- true
137
- >
138
- : true;
129
+ > = And<
130
+ QualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, W>,
131
+ UnqualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, W, never>,
132
+ true,
133
+ true,
134
+ true
135
+ >;
139
136
 
140
137
  // "Light" validator for high-complexity SELECTs: validate the cheap, bounded
141
138
  // parts (every referenced table exists, and the select/returning list resolves)
@@ -217,21 +214,19 @@ export type LightSelectTablesAndList<N extends string, S extends DatabaseSchema>
217
214
  export type ValidateSQLNormalizedCore<N extends string, S extends DatabaseSchema> =
218
215
  TablesInQuery<N, S> extends infer Tables extends string
219
216
  ? AliasesInQuery<N, S> extends infer Aliases extends string
220
- ? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
221
- ? AllTablesValidFor<NonCteTables<N, S, Tables>, S> extends true
222
- ? AllColumnsValidFor<N, S, Tables, Aliases, LooseTokens> extends true
223
- ? NoAliasShadowedQualifiers<N, S, Tables, Aliases> extends true
224
- ? OuterScopeUnqualifiedValid<N, S> extends true
225
- ? WindowFilterColsValid<N, S, Tables, Aliases> extends true
226
- ? JoinUsingColsValid<N, S, Tables> extends true
227
- ? DistinctOnColsValid<N, S, Tables, Aliases> extends true
228
- ? true
229
- : false
217
+ ? AllTablesValidFor<NonCteTables<N, S, Tables>, S> extends true
218
+ ? AllColumnsValidFor<N, S, Tables, Aliases, RefScanSegment<N>> extends true
219
+ ? NoAliasShadowedQualifiers<N, S, Tables, Aliases> extends true
220
+ ? OuterScopeUnqualifiedValid<N, S> extends true
221
+ ? WindowFilterColsValid<N, S, Tables, Aliases> extends true
222
+ ? JoinUsingColsValid<N, S, Tables> extends true
223
+ ? DistinctOnColsValid<N, S, Tables, Aliases> extends true
224
+ ? true
230
225
  : false
231
226
  : false
232
227
  : false
233
- : false
234
228
  : false
229
+ : false
235
230
  : false
236
231
  : false
237
232
  : false
@@ -290,41 +285,52 @@ export type HasReturning<N extends string> =
290
285
 
291
286
  // Quote-free fast-path: a query with no `'` and no `"` has no place for a
292
287
  // ` 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.
296
- export type HasReturningQuoteAware<
297
- S extends string,
298
- InString extends boolean = false,
299
- InDString extends boolean = false,
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;
288
+ // is exact and skips the walk (these run on every DML). Only quote-bearing
289
+ // queries pay for the walk below. The fast-path pattern matches the step-cap
290
+ // fallback this walk already uses, so it is consistent with prior behavior.
291
+ export type HasReturningQuoteAware<S extends string> =
292
+ string extends S
293
+ ? false
294
+ : S extends `${string}'${string}`
295
+ ? HasReturningQuoteAwareWalk<S>
296
+ : S extends `${string}"${string}`
297
+ ? HasReturningQuoteAwareWalk<S>
298
+ : S extends `${string} returning ${string}` ? true : false;
308
299
 
300
+ // Quote-jump, not per-char (the old walk minted the tail string PER CHARACTER on
301
+ // every quote-bearing DML). Find the leftmost ` returning `; if no quote opens
302
+ // before it, it is top-level — answer found. Otherwise jump the leftmost quote
303
+ // span whole (`'…'` or `"…"`, whichever opens first — a quote of the other kind
304
+ // inside the span is data, mirroring the old InString/InDString suppression) and
305
+ // re-test the remainder. O(quote spans) instead of O(chars); an unterminated
306
+ // quote swallows the rest, exactly like the old walk-to-EOF inside a literal.
309
307
  type HasReturningQuoteAwareWalk<
310
308
  S extends string,
311
- InString extends boolean = false,
312
- InDString extends boolean = false,
313
309
  Steps extends any[] = []
314
310
  > = string extends S
315
311
  ? false
316
- : Steps["length"] extends 1200
312
+ : Steps["length"] extends 400
317
313
  ? S extends `${string} returning ${string}` ? true : false
318
- : InString extends true
319
- ? S extends `${infer C}${infer Rest}`
320
- ? HasReturningQuoteAwareWalk<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
314
+ : S extends `${infer Before} returning ${string}`
315
+ ? Before extends `${string}'${string}` | `${string}"${string}`
316
+ ? HrqaQuoteJump<S, Steps>
317
+ : true
318
+ : false;
319
+
320
+ // Leftmost of `'` / `"` (the caller guarantees at least one occurs before the
321
+ // first ` returning `): skip its whole span, resume after the closing quote.
322
+ type HrqaQuoteJump<S extends string, Steps extends any[]> =
323
+ S extends `${infer P}'${infer R}`
324
+ ? P extends `${string}"${string}`
325
+ ? HrqaDQuoteJump<S, Steps>
326
+ : R extends `${string}'${infer R2}`
327
+ ? HasReturningQuoteAwareWalk<R2, [any, ...Steps]>
321
328
  : false
322
- : InDString extends true
323
- ? S extends `${infer C}${infer Rest}`
324
- ? HasReturningQuoteAwareWalk<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
325
- : false
326
- : S extends ` returning ${string}`
327
- ? true
328
- : S extends `${infer C}${infer Rest}`
329
- ? HasReturningQuoteAwareWalk<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
330
- : false;
329
+ : HrqaDQuoteJump<S, Steps>;
330
+
331
+ type HrqaDQuoteJump<S extends string, Steps extends any[]> =
332
+ S extends `${string}"${infer R}`
333
+ ? R extends `${string}"${infer R2}`
334
+ ? HasReturningQuoteAwareWalk<R2, [any, ...Steps]>
335
+ : false
336
+ : false;
@@ -1,6 +1,6 @@
1
1
  // JOIN USING / window-filter / DISTINCT ON column validation.
2
2
  import type { AllTrue, And, IsUnion } from "../utils.js";
3
- import type { CleanIdent, ExtractCallParenBodies, SplitCommaSimple, TokenizeLoose, Trim } from "../parsing.js";
3
+ import type { CleanIdent, ExtractCallParenBodies, SplitCommaSimple, Trim } from "../parsing.js";
4
4
  import type { ColumnExists, DatabaseSchema } from "../schema.js";
5
5
  import type { QualifiedColumnRefsValidFor, UnqualifiedColumnRefsValidFor } from "./validate-columns.js";
6
6
  import type { QueryKind } from "./dispatch.js";
@@ -151,15 +151,13 @@ export type WindowFilterColsValid<
151
151
  ? `${ExtractCallParenBodies<N, " over (">} ${ExtractCallParenBodies<N, " over(">} ${ExtractCallParenBodies<N, " filter (">} ${ExtractCallParenBodies<N, " filter(">} ${ExtractCallParenBodies<N, " within group (">} ${ExtractCallParenBodies<N, " within group(">}` extends infer Seg extends string
152
152
  ? Trim<Seg> extends ""
153
153
  ? true
154
- : TokenizeLoose<Seg> extends infer Toks extends string[]
155
- ? And<
156
- QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks>,
157
- UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks, never>,
158
- true,
159
- true,
160
- true
161
- >
162
- : true
154
+ : And<
155
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg>,
156
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg, never>,
157
+ true,
158
+ true,
159
+ true
160
+ >
163
161
  : true
164
162
  : true;
165
163
 
@@ -183,15 +181,13 @@ export type DistinctOnColsValid<
183
181
  ? `${ExtractCallParenBodies<N, " distinct on (">} ${ExtractCallParenBodies<N, " distinct on(">}` extends infer Seg extends string
184
182
  ? Trim<Seg> extends ""
185
183
  ? true
186
- : TokenizeLoose<Seg> extends infer Toks extends string[]
187
- ? And<
188
- QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks>,
189
- UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Toks, never>,
190
- true,
191
- true,
192
- true
193
- >
194
- : true
184
+ : And<
185
+ QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg>,
186
+ UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg, never>,
187
+ true,
188
+ true,
189
+ true
190
+ >
195
191
  : true
196
192
  : true
197
193
  : true;
@@ -1,7 +1,7 @@
1
1
  // Derived-table & JOIN LATERAL / joined-derived result inference.
2
- import type { AliasesInQuery, TablesInQuery } from "../tables.js";
2
+ import type { AliasesInQuery, NullableRelations, TablesInQuery } from "../tables.js";
3
3
  import type { ApplyJoinNull, OuterCastTs, RefQualifier } from "../expressions.js";
4
- import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, SplitBalancedParen, SplitCommaSimple, SplitSelectList, Trim, TrimLeft } from "../parsing.js";
4
+ import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, SplitBalancedParen, SplitCommaSimple, SplitSelectList, StripSubqueries, Trim, TrimLeft } from "../parsing.js";
5
5
  import type { DatabaseSchema } from "../schema.js";
6
6
  import type { MergeRow, SelectReturnWith } from "./return-types.js";
7
7
  import type { Simplify, UnionToIntersection } from "../utils.js";
@@ -107,7 +107,30 @@ export type DerivedBodyColType<E extends string, BaseRow> =
107
107
  export type DerivedTableReturn<N extends string, S extends DatabaseSchema> =
108
108
  DerivedTableMatch<N> extends { body: infer Body extends string; alias: infer DAlias extends string; cols: infer Cols extends string[] }
109
109
  ? DerivedRenamedRow<Body, Cols, S> extends infer SubRow
110
- ? BuildDerivedReturn<SplitSelectList<ExtractSelectList<N>>, DAlias, SubRow>
110
+ ? Simplify<MergeRow<
111
+ DerivedJoinedBaseRow<N, S>,
112
+ BuildDerivedReturn<SplitSelectList<ExtractSelectList<N>>, DAlias, SubRow>
113
+ >>
114
+ : {}
115
+ : {};
116
+
117
+ // Tables JOINed onto the leading derived source (`FROM (<subquery>) d JOIN base
118
+ // b ...`) are real outer relations, so projected refs to them (`b.col`,
119
+ // `b.col AS x`) must resolve against the schema — not leak as a dotted-literal
120
+ // key (`{"b.col": unknown}`) or an `unknown` type. Excise the subquery body
121
+ // (`StripSubqueries`) to recover just those outer relations, then resolve the
122
+ // outer SELECT list against them with outer-join nullability. Refs to the derived
123
+ // alias (`d.col`) don't resolve here (it isn't a real table); the derived-alias
124
+ // path supplies those and `MergeRow` lets it win on overlap. Gated on an actual
125
+ // JOIN so a plain `FROM (<subquery>) d` pays nothing extra.
126
+ export type DerivedJoinedBaseRow<N extends string, S extends DatabaseSchema> =
127
+ StripSubqueries<N> extends infer Outer extends string
128
+ ? Outer extends `${string} join ${string}`
129
+ ? TablesInQuery<Outer, S> extends infer OT extends string
130
+ ? AliasesInQuery<Outer, S> extends infer OA extends string
131
+ ? SelectReturnWith<ExtractSelectList<N>, OT, OA, S, NullableRelations<N, S>>
132
+ : {}
133
+ : {}
111
134
  : {}
112
135
  : {};
113
136
 
@@ -123,6 +146,37 @@ export type BuildDerivedReturn<
123
146
  ? BuildDerivedReturn<Rest, DAlias, SubRow, MergeRow<Acc, DerivedExprToObject<H, DAlias, SubRow>>, [any, ...Steps]>
124
147
  : Simplify<Acc>;
125
148
 
149
+ // A "bare identifier": a simple unquoted name with no operator/paren/space/dot
150
+ // chars. Lets us tell a *plain* qualified column ref (`ip.entity_id`) apart from
151
+ // an expression that merely contains a dot (`sum(t.x)`, `(array_agg(a.b))[1]`).
152
+ export type IsBareIdent<S extends string> =
153
+ S extends "" ? false
154
+ : S extends `${string}${
155
+ | " " | "(" | ")" | "+" | "-" | "*" | "/" | ":" | "'" | "\"" | "["
156
+ | "]" | "," | "." | "=" | "<" | ">" | "|" | "%" | "!" | "~" | "@"
157
+ }${string}`
158
+ ? false
159
+ : true;
160
+
161
+ // The qualifier of a *simple* qualified column ref `Q.col` (both parts bare),
162
+ // else `never` (functions/expressions/quoted refs don't qualify).
163
+ export type SimpleQualRef<E extends string> =
164
+ CleanIdent<E> extends `${infer Q}.${infer Col}`
165
+ ? IsBareIdent<Q> extends true
166
+ ? IsBareIdent<Col> extends true ? Q : never
167
+ : never
168
+ : never;
169
+
170
+ // True when `RawExpr` is a simple qualified column ref pointing at something
171
+ // OTHER than the derived alias — i.e. a JOINed base table (`ip.entity_id`,
172
+ // `t.name`). `DerivedTableReturn` resolves those against the schema separately,
173
+ // so the derived-alias path skips them rather than emitting a bogus dotted key.
174
+ export type IsForeignSimpleRef<RawExpr extends string, DAlias extends string> =
175
+ SimpleQualRef<RawExpr> extends infer Q extends string
176
+ ? [Q] extends [never] ? false
177
+ : Q extends DAlias ? (DAlias extends Q ? false : true) : true
178
+ : false;
179
+
126
180
  // `Nullable` carries the outer-join nullable-qualifier set. When the derived
127
181
  // table is the nullable side of an outer join (`LEFT JOIN LATERAL (...) d`), its
128
182
  // exposed columns must gain `| null` (Postgres: the whole derived row is NULL when
@@ -130,7 +184,9 @@ export type BuildDerivedReturn<
130
184
  // don't pass it are unaffected.
131
185
  export type DerivedExprToObject<E extends string, DAlias extends string, SubRow, Nullable extends string = never> =
132
186
  ExtractAliasResult<E> extends { expr: infer RawExpr extends string; alias: infer OutAlias }
133
- ? [OutAlias] extends [never]
187
+ ? IsForeignSimpleRef<RawExpr, DAlias> extends true
188
+ ? {}
189
+ : [OutAlias] extends [never]
134
190
  ? CleanIdent<RawExpr> extends "*"
135
191
  ? SubRow
136
192
  : CleanIdent<RawExpr> extends `${DAlias}.*`
@@ -259,9 +259,15 @@ export type SelectAliases<
259
259
  ? Acc
260
260
  : Exprs extends [infer H extends string, ...infer Rest extends string[]]
261
261
  ? ExtractAlias<H> extends { alias: infer Alias }
262
- ? Alias extends string
263
- ? SelectAliases<Rest, Acc | Alias, [any, ...Steps]>
264
- : SelectAliases<Rest, Acc, [any, ...Steps]>
262
+ // A non-aliased projection yields `alias: never`. Guard it in tuple
263
+ // position: a NAKED `never extends string ?` arm collapses the whole
264
+ // conditional (and with it the entire recursion) to `never`, silently
265
+ // dropping every alias the list DOES define.
266
+ ? [Alias] extends [never]
267
+ ? SelectAliases<Rest, Acc, [any, ...Steps]>
268
+ : Alias extends string
269
+ ? SelectAliases<Rest, Acc | Alias, [any, ...Steps]>
270
+ : SelectAliases<Rest, Acc, [any, ...Steps]>
265
271
  : SelectAliases<Rest, Acc, [any, ...Steps]>
266
272
  : Acc;
267
273