@kuindji/typed-sql 0.1.0 → 0.3.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 (57) 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 +12 -6
  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 +10 -5
  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/tables.d.ts +2 -2
  32. package/dist/tables.d.ts.map +1 -1
  33. package/dist/validation/dispatch.d.ts +3 -1
  34. package/dist/validation/dispatch.d.ts.map +1 -1
  35. package/dist/validation/return-derived.d.ts +8 -4
  36. package/dist/validation/return-derived.d.ts.map +1 -1
  37. package/dist/validation/return-derived.js.map +1 -1
  38. package/dist/validation/return-types.d.ts +1 -1
  39. package/dist/validation/return-types.d.ts.map +1 -1
  40. package/dist/validation/validate-columns.d.ts +17 -6
  41. package/dist/validation/validate-columns.d.ts.map +1 -1
  42. package/package.json +3 -2
  43. package/src/builder/assemble.ts +9 -13
  44. package/src/builder/conditional-sql.ts +9 -27
  45. package/src/builder/params.ts +33 -27
  46. package/src/builder/select.ts +19 -2
  47. package/src/builder/state.ts +4 -6
  48. package/src/expressions.ts +47 -7
  49. package/src/parsing/extract.ts +18 -4
  50. package/src/parsing/normalize.ts +143 -48
  51. package/src/parsing/pg-literals.ts +23 -12
  52. package/src/parsing/tokenize.ts +56 -23
  53. package/src/tables.ts +35 -4
  54. package/src/validation/dispatch.ts +21 -3
  55. package/src/validation/return-derived.ts +60 -4
  56. package/src/validation/return-types.ts +9 -3
  57. package/src/validation/validate-columns.ts +123 -13
@@ -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;
@@ -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
 
@@ -4,9 +4,9 @@ import type { AllTrue, And, StartsWith } from "../utils.js";
4
4
  import type { CleanIdent, DQuoteSpaceSentinel, ExceedsLengthBudget, ExtractAliasResult, ExtractBefore, ExtractConflictColumns, ExtractConflictUpdateExcludedCols, ExtractConflictUpdateSetColumns, ExtractInsertColumns, ExtractLastWhere, ExtractReturningList, ExtractSelectList, ExtractUpdateSetColumns, ReplaceAll, SplitSelectList, StripSubqueries, TokenizeLoose, Trim } from "../parsing.js";
5
5
  import type { ColumnRefValidLooseWith, IsSimpleRefPart, QualifiedColumnRefs, ResolveAlias, TableKeysByName, UnqualifiedColumnRefs, UnqualifiedColumnValid } from "../columns.js";
6
6
  import type { ColumnsExistInTable, RefScanBeforeOrderBy, RefScanOrderBy, RefScanSegment, SelectAliasesInQuery, SelectAliasSet } from "./return-types.js";
7
- import type { CteRow, SingleCteMatch } from "./cte.js";
7
+ import type { CteNames, CteRow, SingleCteMatch } from "./cte.js";
8
8
  import type { DatabaseSchema } from "../schema.js";
9
- import type { DerivedRenamedRow, DerivedTableMatch } from "./return-derived.js";
9
+ import type { AliasHasNoSpace, DerivedRenamedRow, DerivedTableMatch } from "./return-derived.js";
10
10
  import type { ExprsValidList } from "../expressions.js";
11
11
  import type { HasReturning, QueryKind, ValidateSQLNormalized } from "./dispatch.js";
12
12
 
@@ -309,7 +309,7 @@ export type ColumnsValidInSelectOrReturningFor<
309
309
  HasReturning<N> extends true
310
310
  ? [Tables] extends [never]
311
311
  ? true
312
- : ExprsValidList<SplitSelectList<ReplaceAll<ExtractReturningList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
312
+ : ExprsValidList<SplitSelectList<ReplaceAll<ExtractReturningList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S, [], SelectLocalRels<N>>
313
313
  : QueryKind<N> extends "select"
314
314
  ? [Tables] extends [never]
315
315
  ? true
@@ -318,9 +318,18 @@ export type ColumnsValidInSelectOrReturningFor<
318
318
  // The alias set restores those spaces, so restore them here too or a
319
319
  // qualifier through a space-bearing quoted alias (`"user alias".id`)
320
320
  // would never match its registered alias (round-12 regression).
321
- : ExprsValidList<SplitSelectList<ReplaceAll<ExtractSelectList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
321
+ : ExprsValidList<SplitSelectList<ReplaceAll<ExtractSelectList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S, [], SelectLocalRels<N>>
322
322
  : true;
323
323
 
324
+ // Query-local relation names whose qualified refs the projection-list validator
325
+ // must bless: a `WITH`-query's CTE names. (A CTE that reaches the core validator
326
+ // — e.g. `WITH ... SELECT ... JOIN ...` — is collected into `TablesInQuery` as a
327
+ // bogus base table for the ref-resolution, so `cte.col` in the SELECT list would
328
+ // otherwise resolve `never` and falsely reject.) Gated on the `with ` prefix so
329
+ // CTE-free queries pay nothing.
330
+ type SelectLocalRels<N extends string> =
331
+ N extends `with ${string}` ? CteNames<N> : never;
332
+
324
333
  // insert
325
334
 
326
335
  export type ColumnsValidInInsert<N extends string, S extends DatabaseSchema> =
@@ -368,10 +377,66 @@ export type QualifiedColumnRefsValidFor<
368
377
  Tables extends string,
369
378
  Aliases extends string,
370
379
  LooseTokens extends string[]
380
+ > =
381
+ // Common path: with no CTE and no parenthesised FROM source there is no local
382
+ // relation that could qualify a ref, so skip the (per-ref) local-relation
383
+ // blessing entirely — exact prior behavior, zero added cost.
384
+ HasLocalRelations<N> extends true
385
+ ? QualifiedRefsValidWithLocal<N, S, Tables, Aliases, LooseTokens, CteNames<N>>
386
+ : QualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
387
+ ? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
388
+ : true;
389
+
390
+ // A "local relation" is a query-local name that is NOT a base table: a CTE name, or
391
+ // the alias bound to a derived / VALUES / subquery FROM source (`from (…) [as]
392
+ // x[(cols)]`). Its columns are not modeled in the schema, so a ref qualified by it
393
+ // must be accepted leniently rather than resolved against a (non-existent) base
394
+ // table. Blessing only ever turns a reject into an accept — never the reverse —
395
+ // consistent with the lenient-parser contract.
396
+ type HasLocalRelations<N extends string> =
397
+ N extends `with ${string}` ? true :
398
+ N extends `${string} from (${string}` ? true :
399
+ N extends `${string} join (${string}` ? true :
400
+ false;
401
+
402
+ type QualifiedRefsValidWithLocal<
403
+ N extends string,
404
+ S extends DatabaseSchema,
405
+ Tables extends string,
406
+ Aliases extends string,
407
+ LooseTokens extends string[],
408
+ Ctes extends string
371
409
  > = QualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
372
- ? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
410
+ ? AllTrue<
411
+ Cols extends string
412
+ ? IsLocalRelation<RefQualifierOf<Cols>, Ctes, N> extends true
413
+ ? true
414
+ : ColumnRefValidLooseWith<Cols, Tables, Aliases, S>
415
+ : true
416
+ >
373
417
  : true;
374
418
 
419
+ // The qualifier (text before the first `.`) of a qualified column ref.
420
+ type RefQualifierOf<Col extends string> =
421
+ Col extends `${infer Q}.${string}` ? CleanIdent<Q> : never;
422
+
423
+ type IsLocalRelation<Q extends string, Ctes extends string, N extends string> =
424
+ [Q] extends [never] ? false :
425
+ Q extends Ctes ? true :
426
+ IsDerivedSourceAlias<Q, N>;
427
+
428
+ // Detect `… ) [as] q …` / `… ) [as] q( …` — q is the alias bound to a parenthesised
429
+ // (derived / VALUES / subquery) FROM source. N is already normalized (lowercase
430
+ // outside quotes); q is the cleaned, lowercased qualifier.
431
+ type IsDerivedSourceAlias<Q extends string, N extends string> =
432
+ N extends `${string}) as ${Q} ${string}` ? true :
433
+ N extends `${string}) as ${Q}(${string}` ? true :
434
+ N extends `${string}) as ${Q}` ? true :
435
+ N extends `${string}) ${Q} ${string}` ? true :
436
+ N extends `${string}) ${Q}(${string}` ? true :
437
+ N extends `${string}) ${Q}` ? true :
438
+ false;
439
+
375
440
  // Once a table is given a range alias (`FROM products p`), PostgreSQL hides the
376
441
  // original table name as a correlation name for that query level — `products.id`
377
442
  // is then invalid; only `p.id` works. The lenient qualified-ref check accepts
@@ -402,15 +467,21 @@ export type NoAliasShadowedQualifiers<
402
467
  > =
403
468
  [AliasedTableKeys<Aliases>] extends [never]
404
469
  ? 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
470
+ // A shadowable qualifier is a `qualifier.column` token, which requires a
471
+ // `.`. With no `.` anywhere, `QualifiedColumnRefs` accumulates `never` and
472
+ // `AllTrue<never>` is `true` — so skip the whole-query `TokenizeLoose<N>`
473
+ // re-walk (computed nowhere else) on dot-free queries. Exact-equivalent.
474
+ : N extends `${string}.${string}`
475
+ ? AllTrue<
476
+ QualifiedColumnRefs<TokenizeLoose<N>, S, Tables, Aliases> extends infer R
477
+ ? R extends `${infer Q}.${string}`
478
+ ? QualifierShadowedByAlias<Q, Tables, Aliases, S> extends true
479
+ ? false
480
+ : true
410
481
  : true
411
482
  : true
412
- : true
413
- >;
483
+ >
484
+ : true;
414
485
 
415
486
  // A table introduced INSIDE a subquery is in scope only there — it must not
416
487
  // satisfy an UNQUALIFIED column reference in the OUTER query. The whole-query
@@ -483,7 +554,46 @@ export type UnqualifiedColumnRefsValidFor<
483
554
  Cols extends string
484
555
  ? CleanIdent<Cols> extends SelectAliases
485
556
  ? true
486
- : UnqualifiedColumnValid<Cols, Tables, Aliases, S>
557
+ : UnqualifiedColumnValid<Cols, Tables, Aliases, S> extends true
558
+ ? true
559
+ // A derived/VALUES source's column-alias list (`... ) as
560
+ // src(id, t)`) survives tokenization as bare identifier tokens,
561
+ // and `t` is not a schema column anywhere — bless names that
562
+ // belong to such a list. Checked ONLY after normal resolution
563
+ // fails, so valid queries never pay for the extraction, and
564
+ // blessing can only turn a reject into an accept (lenient
565
+ // contract).
566
+ : CleanIdent<Cols> extends SourceAliasListCols<N>
567
+ ? true
568
+ : false
487
569
  : true
488
570
  >
489
571
  : true;
572
+
573
+ // Every column name bound by a derived-source column-alias list — `) as p(a, b)`
574
+ // / `) p(a, b)` — plus CTE column-alias lists (`with c(x, y) as (`). Collected
575
+ // across ALL occurrences in the (normalized, single-line) query. The alias word
576
+ // itself must be space-free, mirroring `DerivedColsAfterAlias`'s guard, so a
577
+ // trailing `WHERE ... IN (...)` paren group is not misread as a column list.
578
+ type SourceAliasListCols<N extends string> =
579
+ N extends `with ${infer CteHead}(${infer CteCols}) as (${infer Rest}`
580
+ ? AliasHasNoSpace<Trim<CteHead>> extends true
581
+ ? ColNamesFromList<CteCols> | DerivedAliasListCols<Rest>
582
+ : DerivedAliasListCols<N>
583
+ : DerivedAliasListCols<N>;
584
+
585
+ type DerivedAliasListCols<N extends string, Steps extends any[] = []> =
586
+ Steps["length"] extends 15
587
+ ? never
588
+ : N extends `${string}) as ${infer Rest}`
589
+ ? Rest extends `${infer Alias}(${infer Cols})${infer Tail}`
590
+ ? AliasHasNoSpace<Trim<Alias>> extends true
591
+ ? ColNamesFromList<Cols> | DerivedAliasListCols<Tail, [any, ...Steps]>
592
+ : DerivedAliasListCols<Tail, [any, ...Steps]>
593
+ : never
594
+ : never;
595
+
596
+ type ColNamesFromList<L extends string> =
597
+ L extends `${infer A},${infer R}`
598
+ ? CleanIdent<A> | ColNamesFromList<R>
599
+ : CleanIdent<L>;