@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,507 @@
1
+ // src/builder/extract-params.ts
2
+ import type { DatabaseSchema } from "../schema.js";
3
+ import type { ColumnTypeFromTableKey, RowTypeForTable } from "../schema.js";
4
+ import type { NormalizeQuery, NormalizeQueryKeepParams } from "../parsing.js";
5
+ import type {
6
+ ExtractInsertColumns, ExtractReturningList, ExtractLastWhere,
7
+ ExtractBefore, SplitCommaSimple, SplitTopLevel, Trim, CleanIdent,
8
+ ExceedsLengthBudget,
9
+ } from "../parsing.js";
10
+ import type {
11
+ InsertTargetTable, UpdateTargetTable, DeleteTargetTable,
12
+ } from "../tables.js";
13
+ import type { Simplify } from "../utils.js";
14
+ import type { GetReturnType } from "../index.js";
15
+ import type { DriverParamValue } from "./scanner.js";
16
+
17
+ // ---- :name detection ----
18
+ export type ParamName<Token extends string> =
19
+ Trim<Token> extends `:${infer Name}` ? CleanParamIdent<Name> : never;
20
+
21
+ // A `:name` identifier ends at the first terminator char. Use the deterministic
22
+ // left-to-right `ReadName` walk (NameStop set) rather than a single template with
23
+ // a *union* of separators: `${infer Head}${")" | "," | " "}${string}` infers a
24
+ // DIFFERENT Head per union member and yields a *union* result — so a param like
25
+ // `:userId` trailed by `\n order by "pseName", …` (an un-stripped ORDER BY leaking
26
+ // into the WHERE block) produced `"userId\n" | "userId\n order by \"pseName\""`
27
+ // instead of `"userId"`. ReadName stops at the first NameStop (incl. `\n`,`\t`,`:`
28
+ // so `::cast` suffixes drop too), so it returns a single clean name.
29
+ type CleanParamIdent<S extends string> = ReadName<S>;
30
+
31
+ // Column name for a (possibly qualified, possibly quoted) ref. Quotes are
32
+ // stripped via CleanIdent so the case-insensitive schema lookup matches; a raw
33
+ // `"shopperId"` qualifier would otherwise miss the column and bind `never`.
34
+ type ColOf<S extends string> =
35
+ FirstToken<Trim<S>> extends infer T extends string
36
+ ? T extends `${infer _A}.${infer C}` ? CleanIdent<C> : CleanIdent<T> : never;
37
+ type FirstToken<S extends string> = S extends `${infer A} ${infer _}` ? A : S;
38
+
39
+ // ---- INSERT ----
40
+ export type ExtractInsertValues<N extends string> =
41
+ N extends `${string} values (${infer V})${string}` ? SplitCommaSimple<V>
42
+ : N extends `${string} values(${infer V2})${string}` ? SplitCommaSimple<V2>
43
+ : [];
44
+
45
+ type ZipInsert<
46
+ Cols extends readonly string[], Vals extends readonly string[],
47
+ Table extends string, S extends DatabaseSchema, Acc = {},
48
+ > = Cols extends readonly [infer C extends string, ...infer CR extends string[]]
49
+ ? Vals extends readonly [infer V extends string, ...infer VR extends string[]]
50
+ ? ParamName<V> extends infer P
51
+ // not exactly `:name` → extract any inner placeholders loose (spec §6)
52
+ ? [P] extends [never] ? ZipInsert<CR, VR, Table, S, Acc & LooseParams<V>>
53
+ : P extends string
54
+ ? ZipInsert<CR, VR, Table, S, Acc & { [K in P]: ColumnTypeFromTableKey<Table, CleanIdent<C>, S> }>
55
+ : ZipInsert<CR, VR, Table, S, Acc>
56
+ : Acc
57
+ : Acc
58
+ : Acc;
59
+
60
+ type ConflictSetBlock<N extends string> =
61
+ N extends `${string} do update set ${infer Rest}`
62
+ ? ExtractBefore<ExtractBefore<Rest, " where ">, " returning "> : "";
63
+
64
+ // ---- multi-row VALUES detection (spec §3) ----
65
+ type IsMultiRowInsert<N extends string> =
66
+ N extends `${string} values ${infer After}` ? HasTopLevelTupleSep<After> : false;
67
+
68
+ // Walk After: skip single-quoted literals AND dollar-quoted strings; track paren
69
+ // depth. When the FIRST top-level tuple closes (depth returns to 0), it is a
70
+ // multi-row INSERT iff the next non-whitespace char is a comma (another tuple
71
+ // follows). Any other trailing clause — `on conflict (id)`, `returning …` —
72
+ // has no top-level `),` so it stays single-row. Step-capped; widens to false on
73
+ // overrun. Comments are already stripped by NormalizeQuery, so no comment arm.
74
+ type HasTopLevelTupleSep<
75
+ S extends string, Depth extends any[] = [], Steps extends any[] = [],
76
+ > = Steps["length"] extends 400 ? false
77
+ // single-quoted literal: `''` escape first, then a whole literal
78
+ : S extends `''${infer R}` ? HasTopLevelTupleSep<R, Depth, [any, ...Steps]>
79
+ : S extends `'${infer _Q}'${infer R}` ? HasTopLevelTupleSep<R, Depth, [any, ...Steps]>
80
+ // dollar-quoted string: `$tag$ … $tag$` (tag may be empty → `$$ … $$`).
81
+ : S extends `$${infer Tag}$${infer Rest}`
82
+ ? Rest extends `${infer _Body}$${Tag}$${infer After}`
83
+ ? HasTopLevelTupleSep<After, Depth, [any, ...Steps]>
84
+ : false // unterminated dollar-quote → stop (not multi-row)
85
+ : S extends `(${infer R}` ? HasTopLevelTupleSep<R, [any, ...Depth], [any, ...Steps]>
86
+ : S extends `)${infer R}`
87
+ ? Depth extends [any, ...infer Rest extends any[]]
88
+ // top-level tuple just closed → another tuple iff next non-space is ","
89
+ ? Rest extends [] ? AfterTupleIsComma<R>
90
+ : HasTopLevelTupleSep<R, Rest, [any, ...Steps]>
91
+ : HasTopLevelTupleSep<R, [], [any, ...Steps]> // unbalanced ")" — ignore
92
+ : S extends `${infer _C}${infer R}` ? HasTopLevelTupleSep<R, Depth, [any, ...Steps]>
93
+ : false;
94
+
95
+ // True iff the next non-whitespace char begins another VALUES tuple separator.
96
+ type AfterTupleIsComma<S extends string> =
97
+ S extends `${" " | "\t" | "\n"}${infer R}` ? AfterTupleIsComma<R>
98
+ : S extends `,${string}` ? true
99
+ : false;
100
+
101
+ type InsertParams<N extends string, S extends DatabaseSchema> =
102
+ IsMultiRowInsert<N> extends true
103
+ ? { __error: true; message: "[SQL Error] multi-row VALUES not supported in the typed path; use the untyped driver call" }
104
+ : InsertTargetTable<N, S> extends infer Table extends string
105
+ ? ZipInsert<ExtractInsertColumns<N>, ExtractInsertValues<N>, Table, S>
106
+ & SetParams<SplitTopLevel<ConflictSetBlock<N>>, Table, S>
107
+ & WhereParamsFor<N, Table, S>
108
+ : {};
109
+
110
+ // ---- UPDATE SET ----
111
+ type ExtractSetBlock<N extends string> =
112
+ N extends `${string} set ${infer Rest}`
113
+ ? ExtractBefore<ExtractBefore<Rest, " where ">, " returning "> : "";
114
+
115
+ type SetParams<
116
+ Pairs extends readonly string[], Table extends string,
117
+ S extends DatabaseSchema, Acc = {},
118
+ > = Pairs extends readonly [infer P extends string, ...infer R extends string[]]
119
+ ? P extends `${infer Left}=${infer Right}`
120
+ ? ParamName<Right> extends infer Name
121
+ // not exactly `:name` → extract any inner placeholders loose (spec §6)
122
+ ? [Name] extends [never] ? SetParams<R, Table, S, Acc & LooseParams<Right>>
123
+ : Name extends string
124
+ ? SetParams<R, Table, S, Acc & { [K in Name]: ColumnTypeFromTableKey<Table, CleanIdent<Left>, S> }>
125
+ : SetParams<R, Table, S, Acc>
126
+ : Acc
127
+ : SetParams<R, Table, S, Acc>
128
+ : Acc;
129
+
130
+ // ---- WHERE / USING ----
131
+ type WhereBlock<N extends string> =
132
+ N extends `${string} where ${string}` ? ExtractLastWhere<N> : "";
133
+
134
+ // Split on top-level " and "/" or ", but keep a `between X and Y` range intact:
135
+ // when a part ends with a dangling `between ... ` (no closing operand yet),
136
+ // re-glue it with the following part.
137
+ type SplitConds<S extends string> =
138
+ Reglue<SplitOn<S, " and ">> extends infer A extends string[]
139
+ ? FlatSplit<A, " or "> : [];
140
+ type SplitOn<S extends string, D extends string> =
141
+ S extends `${infer H}${D}${infer T}` ? [H, ...SplitOn<T, D>] : [S];
142
+ type FlatSplit<Parts extends readonly string[], D extends string, Acc extends string[] = []> =
143
+ Parts extends readonly [infer H extends string, ...infer R extends string[]]
144
+ ? FlatSplit<R, D, [...Acc, ...SplitOn<H, D>]> : Acc;
145
+
146
+ type Reglue<Parts extends readonly string[], Acc extends string[] = []> =
147
+ Parts extends readonly [infer H extends string, infer N extends string, ...infer R extends string[]]
148
+ ? EndsWithBetween<H> extends true
149
+ ? Reglue<[`${H} and ${N}`, ...R], Acc>
150
+ : Reglue<[N, ...R], [...Acc, H]>
151
+ : Parts extends readonly [infer L extends string]
152
+ ? [...Acc, L]
153
+ : Acc;
154
+
155
+ type EndsWithBetween<S extends string> =
156
+ Lowercase<Trim<S>> extends `${string} between ${infer Rest}`
157
+ ? Rest extends `${string} and ${string}` ? false : true
158
+ : false;
159
+
160
+ // Extract EVERY placeholder name in a fragment and type each DriverParamValue.
161
+ // Used as the loose fallback (spec §6.5) — present, not dropped, not column-typed.
162
+ // Delegates to the colon-jumping `AllLooseParams`: `${infer _Pre}:${infer Rest}`
163
+ // matches the LEFTMOST colon (probe-confirmed — the old "greedy → LAST colon"
164
+ // rationale for the per-char walk was a misconception), so this scans colon-to-colon
165
+ // (O(colons), NOT O(chars)) yet yields the identical param set. Between colons the
166
+ // old per-char walk only consumed chars without touching Acc, so jumping straight to
167
+ // the next colon is equivalent; `::cast` skip / empty-name skip / colons inside an
168
+ // un-stripped single-quoted literal (`'a:b'` → `:b`) all match exactly. The 64
169
+ // colon-event cap is lenient and strictly safer than the implicitly TS2589-bounded
170
+ // per-char walk it replaces (design contract: widen/lenient on overrun).
171
+ type LooseParams<S extends string, Acc = {}> = AllLooseParams<S, Acc>;
172
+
173
+ // Whole-query placeholder sweep for the select/with path (step-capped). The select
174
+ // path types params precisely from the *last* WHERE only, so placeholders elsewhere
175
+ // — in the SELECT projection (`:currency::text` inside a function call) or in an
176
+ // *earlier* WHERE of a UNION — are otherwise dropped, and withParams then rejects
177
+ // those keys. This sweep captures EVERY `:name` as DriverParamValue (= unknown);
178
+ // intersecting it with the precise WHERE bindings is a no-op on shared keys
179
+ // (`unknown & T = T`), so precise column types still win.
180
+ //
181
+ // Single-quoted string literals must be skipped, or a literal like `'draft:team:'`
182
+ // or `'team:' || …` would have its inner colons misread as `:team` / `:user`. Rather
183
+ // than pre-build a literal-stripped copy of the whole (~7.6k-char) query and re-walk
184
+ // it — the old `StripSingleQuoted` rebuilt the full string via `\`${Pre} ${After}\``
185
+ // once PER literal (~30 big-string interns on the hot SELECTs) — this FUSES the two
186
+ // passes: jump to the next quote in one instantiation, colon-scan the quote-free
187
+ // prefix (`AllLooseParams<Pre>` — colons there are provably not inside a literal),
188
+ // skip the literal body, then recurse on the suffix, never interning the full string.
189
+ // Behaviour matches the old strip→scan exactly: an unterminated quote drops its
190
+ // dangling tail (lenient), and escaped `''` degrades identically (treated as two
191
+ // literal boundaries, never yielding a param).
192
+ type LooseParamsSkipLit<S extends string, Acc = {}, Steps extends any[] = []> =
193
+ Steps["length"] extends 64 ? Acc
194
+ : S extends `${infer Pre}'${infer Rest}`
195
+ ? Rest extends `${infer _Lit}'${infer After}`
196
+ ? LooseParamsSkipLit<After, AllLooseParams<Pre, Acc>, [any, ...Steps]>
197
+ : AllLooseParams<Pre, Acc>
198
+ : AllLooseParams<S, Acc>;
199
+
200
+ // Jumps colon-to-colon via template inference (`${infer _Pre}:${infer Rest}`
201
+ // matches up to the LEFTMOST colon in ONE instantiation), so recursion depth is
202
+ // the number of colons in the query — a handful — NOT its character length. A
203
+ // per-char walk capped near ~1000 instead *causes* TS2589 (design contract), so
204
+ // this scans cheaply and the step cap is a small colon count, not a length cap.
205
+ // `::cast` is detected by `Rest` starting with a second colon and skipped.
206
+ // Caller passes a quote-free segment (see LooseParamsSkipLit), so a colon here is
207
+ // never inside a single-quoted string literal.
208
+ type AllLooseParams<S extends string, Acc = {}, Steps extends any[] = []> =
209
+ Steps["length"] extends 64 ? Acc
210
+ : S extends `${infer _Pre}:${infer Rest}`
211
+ ? Rest extends `:${infer R2}` ? AllLooseParams<R2, Acc, [any, ...Steps]>
212
+ : ReadName<Rest> extends infer Nm extends string
213
+ ? Nm extends "" ? AllLooseParams<Rest, Acc, [any, ...Steps]>
214
+ : AllLooseParams<DropName<Rest>, Acc & { [K in Nm]: DriverParamValue }, [any, ...Steps]>
215
+ : Acc
216
+ : Acc;
217
+
218
+ // Chars that terminate a `:name` identifier in a SQL fragment.
219
+ type NameStop =
220
+ | " " | "\t" | "\n" | "," | ";" | ")" | "(" | "'" | '"' | ":" | "."
221
+ | "=" | "+" | "-" | "*" | "/" | "|" | "%" | ">" | "<" | "!" | "~"
222
+ | "@" | "#" | "&" | "^" | "[" | "]" | "{" | "}";
223
+ type ReadName<S extends string, Acc extends string = ""> =
224
+ S extends `${infer C}${infer R}`
225
+ ? C extends NameStop ? Acc : ReadName<R, `${Acc}${C}`>
226
+ : Acc;
227
+ type DropName<S extends string> =
228
+ S extends `${infer C}${infer R}` ? C extends NameStop ? S : DropName<R> : S;
229
+
230
+ type WhereParam<Cond extends string, Alias extends string, Table extends string, S extends DatabaseSchema> =
231
+ // col between :lo and :hi (keywords lowercased post-normalize)
232
+ Trim<Cond> extends `${infer Lhs} between ${infer Lo} and ${infer Hi}`
233
+ ? BetweenParams<Lhs, Lo, Hi, Alias, Table, S>
234
+ // col is [not] distinct from :p
235
+ : Trim<Cond> extends `${infer Lhs} is not distinct from ${infer Rhs}`
236
+ ? DistinctParam<Lhs, Rhs, Alias, Table, S>
237
+ : Trim<Cond> extends `${infer Lhs} is distinct from ${infer Rhs}`
238
+ ? DistinctParam<Lhs, Rhs, Alias, Table, S>
239
+ : Trim<Cond> extends `${infer Lhs} in (${infer Inner})`
240
+ ? ParamName<Inner> extends infer P
241
+ ? [P] extends [never] ? LooseParams<Inner>
242
+ : P extends string
243
+ ? IsBareColumnRef<Lhs> extends true
244
+ ? ScopedBindArray<Lhs, P, Alias, Table, S>
245
+ : LooseParams<Inner>
246
+ : LooseParams<Inner>
247
+ : LooseParams<Inner>
248
+ : Trim<Cond> extends `${infer Lhs}:${infer Tail}`
249
+ ? CleanParamIdent<Tail> extends infer P
250
+ ? [P] extends [never] ? LooseParams<Cond>
251
+ : P extends "" ? LooseParams<Cond>
252
+ : P extends string
253
+ // Recognized `col <op> :p` ONLY when, after removing the trailing
254
+ // comparison operator, the left side is a bare (optionally
255
+ // alias-qualified) identifier — no arithmetic, function call, or
256
+ // second placeholder. Anything else widens to loose (spec §6.4).
257
+ ? StripTrailingCmpOp<Lhs> extends infer Col extends string
258
+ ? IsBareColumnRef<Col> extends true
259
+ ? ScopedBind<Col, P, Alias, Table, S>
260
+ : LooseParams<Cond>
261
+ : LooseParams<Cond>
262
+ : LooseParams<Cond>
263
+ : LooseParams<Cond>
264
+ : LooseParams<Cond>;
265
+
266
+ // Honor a qualified ref only when its qualifier is the target's own alias or
267
+ // the target base-table name (spec §6.1); a foreign qualifier (e.g. a FROM-clause
268
+ // alias) widens to DriverParamValue. An unqualified ref binds to the target.
269
+ type ScopedBind<Col extends string, P extends string, Alias extends string,
270
+ Table extends string, S extends DatabaseSchema> =
271
+ Trim<Col> extends `${infer Qual}.${infer _C}`
272
+ ? Qual extends Alias ? { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S> }
273
+ : LowerEq<Qual, BaseName<Table>> extends true ? { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S> }
274
+ : { [K in P]: DriverParamValue }
275
+ : { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S> };
276
+
277
+ // Same scoping, but the bound type is the column type as an array (IN-list).
278
+ type ScopedBindArray<Col extends string, P extends string, Alias extends string,
279
+ Table extends string, S extends DatabaseSchema> =
280
+ Trim<Col> extends `${infer Qual}.${infer _C}`
281
+ ? Qual extends Alias ? { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S>[] }
282
+ : LowerEq<Qual, BaseName<Table>> extends true ? { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S>[] }
283
+ : { [K in P]: DriverParamValue }
284
+ : { [K in P]: ColumnTypeFromTableKey<Table, ColOf<Col>, S>[] };
285
+
286
+ type BaseName<TableKey extends string> =
287
+ TableKey extends `${string}.${infer T}` ? T : TableKey;
288
+ type LowerEq<A extends string, B extends string> =
289
+ Lowercase<A> extends Lowercase<B> ? true : false;
290
+
291
+ // Target's own alias: the token after the target table in `update <t> <alias>`,
292
+ // `delete from <t> <alias>`, or `insert into <t> as <alias>` (spec §6.1).
293
+ type TargetAlias<N extends string> =
294
+ N extends `update ${infer Rest}` ? AliasAfterTable<Rest>
295
+ : N extends `delete from ${infer Rest}` ? AliasAfterTable<Rest>
296
+ : N extends `insert into ${infer Rest}`
297
+ ? Rest extends `${infer _T} as ${infer A} ${string}` ? FirstToken<Trim<A>>
298
+ : Rest extends `${infer _T} as ${infer A}` ? FirstToken<Trim<A>> : ""
299
+ : "";
300
+ // `<table> <alias> set|from|where|using|...` — alias is the 2nd token unless it
301
+ // is itself a clause keyword or a parenthesised list.
302
+ type AliasAfterTable<Rest extends string> =
303
+ Rest extends `${infer _Table} ${infer After}`
304
+ ? FirstToken<Trim<After>> extends infer A extends string
305
+ ? A extends "set" | "where" | "from" | "using" | "(" | "as" ? ""
306
+ : A extends `(${string}` ? "" : A
307
+ : ""
308
+ : "";
309
+
310
+ // Strip a trailing recognized comparison operator (and surrounding spaces) from
311
+ // the left side of a `col <op> :p` split. COMPOUND/symbol ops first (so `!=`,
312
+ // `<=`, `>=`, `<>` are not mis-split by the `=`/`<`/`>` arms), then the word ops
313
+ // `like`/`ilike` (case-insensitive). If nothing recognized trails, returns the
314
+ // input unchanged so the bare-ref check below fails → loose.
315
+ type StripTrailingCmpOp<S extends string> =
316
+ Trim<S> extends `${infer P}!=` ? Trim<P>
317
+ : Trim<S> extends `${infer P}<>` ? Trim<P>
318
+ : Trim<S> extends `${infer P}<=` ? Trim<P>
319
+ : Trim<S> extends `${infer P}>=` ? Trim<P>
320
+ : Trim<S> extends `${infer P}=` ? Trim<P>
321
+ : Trim<S> extends `${infer P}<` ? Trim<P>
322
+ : Trim<S> extends `${infer P}>` ? Trim<P>
323
+ : Trim<S> extends `${infer P} ${infer Op}`
324
+ ? Lowercase<Op> extends "like" | "ilike" ? Trim<P> : Trim<S>
325
+ : Trim<S>;
326
+
327
+ // True iff `S` (trimmed) is a single column ref: an identifier, optionally
328
+ // alias/schema-qualified with dots, and NOTHING else — no space, arithmetic,
329
+ // parenthesis, pipe, percent, or extra colon. This is what makes `amount + `,
330
+ // `lower(x)`, and an empty Lhs (reversed `:p = col`) fail recognition → loose.
331
+ type IsBareColumnRef<S extends string> =
332
+ Trim<S> extends "" ? false
333
+ : Trim<S> extends `${string}${" " | "+" | "-" | "*" | "/" | "(" | ")" | ":" | "|" | "%"}${string}` ? false
334
+ : true;
335
+
336
+ type BetweenParams<Lhs extends string, Lo extends string, Hi extends string,
337
+ Alias extends string, Table extends string, S extends DatabaseSchema> =
338
+ ScopedColType<Lhs, Alias, Table, S> extends infer CT
339
+ ? IsBareColumnRef<Lhs> extends true
340
+ ? MergeName<ParamName<Lo>, CT> & MergeName<ParamName<Hi>, CT>
341
+ & LooseLeftover<Lo, Hi>
342
+ : LooseParams<`${Lo} ${Hi}`>
343
+ : {};
344
+
345
+ type DistinctParam<Lhs extends string, Rhs extends string,
346
+ Alias extends string, Table extends string, S extends DatabaseSchema> =
347
+ IsBareColumnRef<Lhs> extends true
348
+ ? MergeName<ParamName<Rhs>, ScopedColType<Lhs, Alias, Table, S>>
349
+ : LooseParams<Rhs>;
350
+
351
+ // Column type for a (possibly qualified) bare ref under target-alias scoping:
352
+ // the column type when the qualifier is the target alias / base name or absent,
353
+ // else DriverParamValue (foreign qualifier).
354
+ type ScopedColType<Col extends string, Alias extends string,
355
+ Table extends string, S extends DatabaseSchema> =
356
+ Trim<Col> extends `${infer Qual}.${infer _C}`
357
+ ? Qual extends Alias ? ColumnTypeFromTableKey<Table, ColOf<Col>, S>
358
+ : LowerEq<Qual, BaseName<Table>> extends true ? ColumnTypeFromTableKey<Table, ColOf<Col>, S>
359
+ : DriverParamValue
360
+ : ColumnTypeFromTableKey<Table, ColOf<Col>, S>;
361
+
362
+ // { name: T } when name is a real param, else {} (a literal operand contributes none).
363
+ type MergeName<P, T> = [P] extends [never] ? {} : P extends string ? { [K in P]: T } : {};
364
+ // If an operand is not a placeholder it contributes nothing; this no-op keeps
365
+ // the between arm total.
366
+ type LooseLeftover<_Lo extends string, _Hi extends string> = {};
367
+
368
+ type WhereParams<
369
+ Conds extends readonly string[], Alias extends string, Table extends string,
370
+ S extends DatabaseSchema, Acc = {},
371
+ > = Conds extends readonly [infer C extends string, ...infer R extends string[]]
372
+ ? WhereParams<R, Alias, Table, S, Acc & WhereParam<C, Alias, Table, S>> : Acc;
373
+
374
+ // Colon pre-gate: a WHERE block with no `:` at all binds no params (every WhereParam
375
+ // arm on a colon-free cond falls through to the loose path → {}), so skip the
376
+ // SplitConds + WhereParams fold (and TargetAlias) entirely. Behavior-preserving — the
377
+ // gated-out case produced {} anyway. Helps the hot param-free outer WHEREs (e.g.
378
+ // `("groups" like '%…%' …)` on the big SELECTs). `::casts`/literal colons keep the
379
+ // full path (gate passes), exactly as before.
380
+ type WhereParamsFor<N extends string, Table extends string, S extends DatabaseSchema> =
381
+ WhereBlock<N> extends infer W extends string
382
+ ? W extends `${string}:${string}`
383
+ ? WhereParams<SplitConds<W>, TargetAlias<N>, Table, S>
384
+ : {}
385
+ : {};
386
+
387
+ // ---- leading-WITH (CTE) split ----------------------------------------------
388
+ // A write builder may prepend `with <name> as [materialized ](<body>) update …`
389
+ // (Task 1.2). The normalized string then starts with `with `, so the dispatch
390
+ // below would route it down the loose select/with arm and bind the UPDATE's own
391
+ // WHERE/SET params imprecisely (`oid: never`, since no real update-target table
392
+ // is resolved). To keep precise UPDATE/INSERT/DELETE typing while still capturing
393
+ // the CTE body's `:params`, peel a leading `with`-clause off: split at the FIRST
394
+ // paren-depth-0 main-statement keyword (`update `/`insert into `/`delete from `).
395
+ // The peeled head (CTE bodies) contributes loose `:params`; the tail dispatches
396
+ // as the real write statement. SELECT/with-SELECT (no DML keyword at depth 0) is
397
+ // left untouched and flows through the existing select arm unchanged.
398
+ //
399
+ // `SplitLeadingWith<N>` walks char-by-char tracking paren depth; when at depth 0
400
+ // it matches a DML keyword boundary, returning `{ head; tail }`. Step-capped; on
401
+ // overrun or no match it yields `never` (caller falls back to the plain dispatch).
402
+ type WithSplit = { head: string; tail: string };
403
+ // Step cap ≈ char cap: this walk consumes one step per char of the FULL
404
+ // `with … <dml>` prefix — the entire CTE head PLUS the depth-0 keyword boundary,
405
+ // not just a clause tail. So the 600-step cap means leading-CTE text up to ~600
406
+ // chars is split precisely; beyond that (or when no depth-0 DML keyword exists,
407
+ // e.g. a leading-`with` SELECT) this yields `never` and the caller DEGRADES TO
408
+ // LOOSE param typing — a safe fallback, NOT a hard error.
409
+ //
410
+ // The cap is intentionally NOT raised: large raw `with …` statements (e.g. the
411
+ // multi-CTE / `with recursive` fixtures fed to the raw sql tag) push tsc past its
412
+ // instantiation-depth limit (TS2589) at 1200. 600 keeps every real builder CTE
413
+ // prefix precise while staying within budget for the raw-string passthrough.
414
+ type SplitLeadingWith<S extends string, Acc extends string = "", Depth extends any[] = [], Steps extends any[] = []> =
415
+ Steps["length"] extends 600 ? never
416
+ : Depth extends []
417
+ // At top level, a DML keyword starts the real statement — split here.
418
+ ? S extends `update ${infer _R}` ? { head: Acc; tail: S }
419
+ : S extends `insert into ${infer _R}` ? { head: Acc; tail: S }
420
+ : S extends `delete from ${infer _R}` ? { head: Acc; tail: S }
421
+ : S extends `(${infer R}` ? SplitLeadingWith<R, `${Acc}(`, [any, ...Depth], [any, ...Steps]>
422
+ : S extends `${infer C}${infer R}` ? SplitLeadingWith<R, `${Acc}${C}`, Depth, [any, ...Steps]>
423
+ : never
424
+ // Inside parens — only track depth; never split.
425
+ : S extends `(${infer R}` ? SplitLeadingWith<R, `${Acc}(`, [any, ...Depth], [any, ...Steps]>
426
+ : S extends `)${infer R}`
427
+ ? Depth extends [any, ...infer D extends any[]] ? SplitLeadingWith<R, `${Acc})`, D, [any, ...Steps]> : never
428
+ : S extends `${infer C}${infer R}` ? SplitLeadingWith<R, `${Acc}${C}`, Depth, [any, ...Steps]>
429
+ : never;
430
+
431
+ // ---- dispatch ----
432
+ type ParamsForKind<N extends string, S extends DatabaseSchema> =
433
+ N extends `insert into ${string}` ? InsertParams<N, S>
434
+ : N extends `update ${string}`
435
+ ? UpdateTargetTable<N, S> extends infer T extends string
436
+ ? SetParams<SplitTopLevel<ExtractSetBlock<N>>, T, S> & WhereParamsFor<N, T, S> : {}
437
+ : N extends `delete from ${string}`
438
+ ? DeleteTargetTable<N, S> extends infer T extends string ? WhereParamsFor<N, T, S> : {}
439
+ // Leading `with`-clause wrapping a DML statement (builder CTE prefix): peel
440
+ // the CTE head (loose params) and dispatch the inner write statement, so its
441
+ // own params stay precisely typed.
442
+ : N extends `with ${string}`
443
+ // `[never] extends [never]` guards the no-DML-keyword case (with-SELECT):
444
+ // SplitLeadingWith yields `never`, and we keep the loose select/with arm.
445
+ ? [SplitLeadingWith<N>] extends [never]
446
+ ? DeleteTargetTable<N, S> extends infer T extends string
447
+ ? LooseParamsSkipLit<N> & WhereParamsFor<N, T, S>
448
+ : LooseParamsSkipLit<N>
449
+ : SplitLeadingWith<N> extends infer W extends WithSplit
450
+ ? LooseParamsSkipLit<W["head"]> & ParamsForKind<W["tail"], S>
451
+ : LooseParamsSkipLit<N>
452
+ : N extends `select ${string}`
453
+ ? DeleteTargetTable<N, S> extends infer T extends string
454
+ ? LooseParamsSkipLit<N> & WhereParamsFor<N, T, S>
455
+ : LooseParamsSkipLit<N>
456
+ : {};
457
+
458
+ export type ExtractParams<Query extends string, S extends DatabaseSchema> =
459
+ NormalizeQueryKeepParams<Query> extends infer N extends string ? Simplify<ParamsForKind<N, S>> : {};
460
+
461
+ // ---- RETURNING ----
462
+ type TargetForReturning<N extends string, S extends DatabaseSchema> =
463
+ N extends `insert into ${string}` ? InsertTargetTable<N, S>
464
+ : N extends `update ${string}` ? UpdateTargetTable<N, S>
465
+ : N extends `delete from ${string}` ? DeleteTargetTable<N, S>
466
+ : never;
467
+
468
+ // Reuse the existing GetReturnType inferrer (aliases, `as`, casts, functions,
469
+ // expressions, `*`) by synthesizing `select <returning-list> from <target>` and
470
+ // running the full machinery over it (spec §6/§7 — "reuse GetReturnType for
471
+ // aliases/expressions where applicable"). A bare `*` short-circuits to the full
472
+ // row. `T` is the normalized target key (e.g. "public.orders"), which the
473
+ // validator resolves as a schema-qualified FROM source.
474
+ //
475
+ // Cheap pre-filter: a query with no `returning` keyword at all can have no
476
+ // RETURNING row, so skip the (expensive) `NormalizeQuery` char-walk entirely and
477
+ // return `{}` after a single `Lowercase` intrinsic. This matters when many
478
+ // `BoundSql<Q, S>` types are unioned (e.g. a big `createSql` smoke-test array):
479
+ // each element's phantom `__returning` member is resolved during the structural
480
+ // union comparison, and running a full normalize per element exhausts TS's
481
+ // cumulative instantiation budget (TS2589). The word-only test is safe — a false
482
+ // positive (the literal `returning` inside a string/identifier) just falls through
483
+ // to the accurate path below, so correctness is unchanged.
484
+ export type ExtractReturning<Query extends string, S extends DatabaseSchema> =
485
+ Lowercase<Query> extends `${string}returning${string}`
486
+ // A very long RETURNING query (e.g. a writable-CTE INSERT … RETURNING) runs
487
+ // the full inferrer below, which is a deep chain; when many `BoundSql<Q,S>`
488
+ // are unioned (a big `createSql` smoke-test array) the union comparison
489
+ // resolves each element's phantom `__returning`, and that one deep inner
490
+ // chain tips TS past its instantiation-depth limit (TS2589). Degrade the
491
+ // RETURNING row to `{}` on over-budget queries — the documented "precision
492
+ // traded away on very wide/long queries" path. Ordinary-size RETURNING
493
+ // queries keep precise row inference.
494
+ ? ExceedsLengthBudget<Query> extends true ? {} : ExtractReturningInner<Query, S>
495
+ : {};
496
+
497
+ type ExtractReturningInner<Query extends string, S extends DatabaseSchema> =
498
+ NormalizeQuery<Query> extends infer N extends string
499
+ ? ExtractReturningList<N> extends infer L extends string
500
+ ? L extends "" ? {}
501
+ : TargetForReturning<N, S> extends infer T extends string
502
+ ? Trim<L> extends "*"
503
+ ? RowTypeForTable<T, S>
504
+ : Simplify<GetReturnType<`select ${L} from ${T}`, S>>
505
+ : {}
506
+ : {}
507
+ : {};
@@ -0,0 +1,58 @@
1
+ // src/builder/index.ts
2
+
3
+ // Values
4
+ export { createSelectQuery, type SelectQueryBuilder } from "./select.js";
5
+ export { createSelectFn } from "./db.js";
6
+ export { createConditionTree, ConditionTreeBuilder } from "./condition-tree.js";
7
+ export {
8
+ createConditionalQuery,
9
+ withConditions,
10
+ conditionalSQL,
11
+ processConditionalSQL,
12
+ processParams,
13
+ normalizeWhitespace,
14
+ } from "./conditional-sql.js";
15
+ export { assembleSelectSQL } from "./assemble.js";
16
+ export { createInsertQuery, type InsertQueryBuilder } from "./insert.js";
17
+ export { createUpdateQuery, type UpdateQueryBuilder } from "./update.js";
18
+ export { createDeleteQuery, type DeleteQueryBuilder } from "./delete.js";
19
+ export { createSql } from "./sql.js";
20
+ export { createMutateFn, type MutationHandler, type MutationReturnType } from "./mutate.js";
21
+ export {
22
+ scanPlaceholders, expandScanned, collectScanned, assertAllProvided, prepareScanned,
23
+ } from "./scanner.js";
24
+
25
+ // Types — only those needed to use the runtime API (spec scope).
26
+ export type { QueryParamValue, QueryParamInput } from "./params.js";
27
+ export type { RuntimeSelectState } from "./state.js";
28
+ export type { AnySqlTag, SqlTag } from "./sql-tag.js";
29
+ export type {
30
+ BuilderSQL,
31
+ BuilderReturnType,
32
+ BuilderResultBrand,
33
+ } from "./return-type.js";
34
+ export type {
35
+ ValidQuery,
36
+ ValidQueryBuilder,
37
+ FragmentErrors,
38
+ SelectResult,
39
+ SelectResultArray,
40
+ SelectBuilderResult,
41
+ SelectBuilderResultArray,
42
+ MergeOverrides,
43
+ IsValidSelect,
44
+ QueryHandler,
45
+ } from "./db.js";
46
+ export type {
47
+ ConditionalQueryResult,
48
+ ProcessedSQL,
49
+ ValidateConditionalSQL,
50
+ ConditionalSQLOutput,
51
+ ConditionalSQLOptions,
52
+ TypedConditionalSQLOutput,
53
+ } from "./conditional-sql.js";
54
+ export type { DriverParamValue, PlaceholderOccurrence } from "./scanner.js";
55
+ export type { ExtractParams, ExtractReturning } from "./extract-params.js";
56
+ export type { TypedSql, BoundSql } from "./sql.js";
57
+ export type { BoundWrite } from "./insert.js";
58
+ export type { WriteParamsFor, WriteReturnFor } from "./write-tag.js";
@@ -0,0 +1,75 @@
1
+ // src/builder/insert.ts
2
+ import type { DatabaseSchema } from "../schema.js";
3
+ import { assembleInsertSQL } from "./write-assemble.js";
4
+ import { EMPTY_INSERT_STATE, type RuntimeInsertState } from "./write-state.js";
5
+ import {
6
+ assertAllProvided, collectScanned, expandScanned, type DriverParamValue,
7
+ } from "./scanner.js";
8
+ import type { InsertTag, WriteParamsFor, WriteReturnFor } from "./write-tag.js";
9
+
10
+ type PushVal<T extends InsertTag, Col extends string, Text extends string, Cond extends boolean> =
11
+ Omit<T, "values"> & { readonly values: readonly [...T["values"], { col: Col; text: Text; cond: Cond }] };
12
+
13
+ export interface InsertQueryBuilder<S extends DatabaseSchema, T extends InsertTag> {
14
+ into<Tbl extends string>(table: Tbl): InsertQueryBuilder<S, Omit<T, "table"> & { table: Tbl }>;
15
+ // INSERT...SELECT: explicit column list, then a free-text SELECT body. When
16
+ // `fromSelect` is set the VALUES path is bypassed and the SELECT form is built.
17
+ columns<C extends string>(cols: C): InsertQueryBuilder<S, Omit<T, "columns"> & { columns: C }>;
18
+ fromSelect<Q extends string>(q: Q): InsertQueryBuilder<S, Omit<T, "fromSelect"> & { fromSelect: Q }>;
19
+ value<Col extends string, Text extends string>(col: Col, text: Text):
20
+ InsertQueryBuilder<S, PushVal<T, Col, Text, false>>;
21
+ valueIf<Col extends string, Text extends string>(cond: boolean, col: Col, text: Text):
22
+ InsertQueryBuilder<S, PushVal<T, Col, Text, true>>;
23
+ onConflict<C extends string>(clause: C):
24
+ InsertQueryBuilder<S, Omit<T, "conflict"> & { conflict: C }>;
25
+ returning<R extends string>(cols: R):
26
+ InsertQueryBuilder<S, Omit<T, "returning"> & { returning: R }>;
27
+ withParams(params: WriteParamsFor<T, S>): BoundWrite<S, T>;
28
+ toString(): string;
29
+ }
30
+
31
+ export interface BoundWrite<S extends DatabaseSchema, T> {
32
+ toString(): string;
33
+ getParams(): ReadonlyArray<DriverParamValue>;
34
+ readonly __returning?: WriteReturnFor<T, S>;
35
+ }
36
+
37
+ class InsertImpl<S extends DatabaseSchema, T extends InsertTag> {
38
+ constructor(private readonly st: RuntimeInsertState) {}
39
+ private next(st: RuntimeInsertState): any { return new InsertImpl<S, any>(st); }
40
+ into(table: string): any { return this.next({ ...this.st, table }); }
41
+ // Store the explicit column list / SELECT body for the INSERT...SELECT form.
42
+ columns(cols: string): any { return this.next({ ...this.st, columns: cols }); }
43
+ fromSelect(q: string): any { return this.next({ ...this.st, fromSelect: q }); }
44
+ value(col: string, text: string): any {
45
+ return this.next({ ...this.st, values: [...this.st.values, { col, text }] });
46
+ }
47
+ valueIf(cond: boolean, col: string, text: string): any {
48
+ return cond ? this.value(col, text) : this.next(this.st);
49
+ }
50
+ onConflict(clause: string): any { return this.next({ ...this.st, conflict: clause }); }
51
+ returning(cols: string): any { return this.next({ ...this.st, returning: cols }); }
52
+ withParams(params: Record<string, DriverParamValue>): any {
53
+ return this.next({ ...this.st, namedParams: { ...this.st.namedParams, ...params } });
54
+ }
55
+ toString(): string {
56
+ const sql = assembleInsertSQL(this.st);
57
+ assertAllProvided(sql, this.st.namedParams);
58
+ return expandScanned(sql, this.st.namedParams);
59
+ }
60
+ getParams(): ReadonlyArray<DriverParamValue> {
61
+ const sql = assembleInsertSQL(this.st);
62
+ assertAllProvided(sql, this.st.namedParams);
63
+ return collectScanned(sql, this.st.namedParams);
64
+ }
65
+ }
66
+
67
+ export type EmptyInsertTag = {
68
+ kind: "insert"; table: ""; values: readonly []; conflict: null;
69
+ columns: ""; fromSelect: "";
70
+ wheres: readonly []; using: readonly []; from: readonly []; returning: null;
71
+ };
72
+
73
+ export function createInsertQuery<S extends DatabaseSchema>(): InsertQueryBuilder<S, EmptyInsertTag> {
74
+ return new InsertImpl<S, EmptyInsertTag>(EMPTY_INSERT_STATE) as unknown as InsertQueryBuilder<S, EmptyInsertTag>;
75
+ }