@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,470 @@
1
+ // src/builder/select.ts
2
+ import type { DatabaseSchema } from "../schema.js";
3
+ import { assembleSelectSQL } from "./assemble.js";
4
+ import { collectParamValues, type QueryParamInput, type QueryParamValue } from "./params.js";
5
+ import { EMPTY_RUNTIME_STATE, type RuntimeSelectState } from "./state.js";
6
+ import { ConditionTreeBuilder } from "./condition-tree.js";
7
+ import type {
8
+ EmptySqlTag,
9
+ ResolveId,
10
+ SelFrag,
11
+ SqlTag,
12
+ WithFrom,
13
+ WithGroupBy,
14
+ WithHaving,
15
+ WithJoin,
16
+ WithLimit,
17
+ WithOffset,
18
+ WithOrderBy,
19
+ WithSelect,
20
+ WithWhere,
21
+ WithoutGroupBy,
22
+ WithoutHaving,
23
+ WithoutJoin,
24
+ WithoutOrderBy,
25
+ WithoutSelect,
26
+ WithoutWhere,
27
+ } from "./sql-tag.js";
28
+ import type { BuilderResultBrand } from "./return-type.js";
29
+
30
+ // Text a condition contributes to the tag: a tree's Expr literal, or the string.
31
+ type CondText<C> = C extends ConditionTreeBuilder<any, infer E extends string> ? E
32
+ : C extends string ? C
33
+ : string;
34
+
35
+ // Render a columns argument (string | readonly string[]) to its joined text.
36
+ type ColsText<Cols> = Cols extends readonly string[] ? JoinArr<Cols>
37
+ : Cols extends string ? Cols
38
+ : string;
39
+ type JoinArr<A extends readonly string[], Acc extends string = ""> =
40
+ A extends readonly [infer H extends string, ...infer R extends readonly string[]]
41
+ ? JoinArr<R, Acc extends "" ? H : `${Acc}, ${H}`>
42
+ : Acc;
43
+
44
+ // Re-flag every select fragment the applyIf transform INTRODUCED as conditional.
45
+ // "Introduced" = a brand-new id OR an existing id whose fragment the transform
46
+ // overwrote (different text/flag). Only fragments byte-identical to the input
47
+ // tag's same-id fragment are left untouched (they keep their flag). This is what
48
+ // makes the F-G2 edge correct: a conditional producer overwriting an
49
+ // unconditional slot carries the conditional flag (spec "Fragment-id reuse").
50
+ // Flat 11-field rebuild (NOT `Omit<After,"selects"> & {…}`) — see the DEPTH NOTE
51
+ // in sql-tag.ts: an `Omit`-based override nests on every chained builder call and
52
+ // crosses TS's depth guard.
53
+ type FlagNewConditional<Before extends SqlTag, After extends SqlTag> = {
54
+ readonly ctes: After["ctes"];
55
+ readonly selects: ReflagSelects<Before["selects"], After["selects"]>;
56
+ readonly from: After["from"];
57
+ readonly joins: After["joins"];
58
+ readonly wheres: After["wheres"];
59
+ readonly groupBys: After["groupBys"];
60
+ readonly havings: After["havings"];
61
+ readonly orderBys: After["orderBys"];
62
+ readonly limit: After["limit"];
63
+ readonly offset: After["offset"];
64
+ readonly union: After["union"];
65
+ };
66
+ type ReflagSelects<
67
+ Before extends readonly SelFrag[],
68
+ After extends readonly SelFrag[],
69
+ > = {
70
+ [I in keyof After]: After[I] extends SelFrag
71
+ ? FindFragById<Before, After[I]["id"]> extends infer B
72
+ ? [B] extends [never]
73
+ ? MarkCond<After[I]> // brand-new id → conditional
74
+ : FragEqual<B, After[I]> extends true
75
+ ? After[I] // untouched → keep its flag
76
+ : MarkCond<After[I]> // overwritten by transform → conditional
77
+ : MarkCond<After[I]>
78
+ : After[I];
79
+ };
80
+ type FindFragById<List extends readonly SelFrag[], Id extends string> =
81
+ List extends readonly [infer H extends SelFrag, ...infer R extends readonly SelFrag[]]
82
+ ? H["id"] extends Id ? H : FindFragById<R, Id>
83
+ : never;
84
+ type FragEqual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
85
+ type MarkCond<F extends SelFrag> = { id: F["id"]; text: F["text"]; cond: true };
86
+
87
+ export interface SelectQueryBuilder<Schema extends DatabaseSchema, Sql extends SqlTag> {
88
+ select<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
89
+ columns: Cols,
90
+ id?: Id,
91
+ ): SelectQueryBuilder<Schema, WithSelect<Sql, ColsText<Cols>, ResolveId<Id, "select", Sql["selects"]>, false>>;
92
+
93
+ selectIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
94
+ condition: boolean,
95
+ columns: Cols,
96
+ id?: Id,
97
+ ): SelectQueryBuilder<Schema, WithSelect<Sql, ColsText<Cols>, ResolveId<Id, "select", Sql["selects"]>, true>>;
98
+
99
+ from<Src extends string | SelectQueryBuilder<Schema, any>>(
100
+ source: Src,
101
+ ): SelectQueryBuilder<Schema, WithFrom<Sql, Src extends string ? Src : string>>;
102
+
103
+ where<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
104
+ condition: Cond,
105
+ id?: Id,
106
+ ): SelectQueryBuilder<Schema, WithWhere<Sql, CondText<Cond>, ResolveId<Id, "where", Sql["wheres"]>>>;
107
+
108
+ whereIf<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
109
+ condition: boolean,
110
+ clause: Cond,
111
+ id?: Id,
112
+ ): SelectQueryBuilder<Schema, WithWhere<Sql, CondText<Cond>, ResolveId<Id, "where", Sql["wheres"]>>>;
113
+
114
+ join<J extends string, Id extends string | undefined = undefined>(
115
+ joinSql: J,
116
+ id?: Id,
117
+ ): SelectQueryBuilder<Schema, WithJoin<Sql, J, ResolveId<Id, "join", Sql["joins"]>>>;
118
+
119
+ joinIf<J extends string, Id extends string | undefined = undefined>(
120
+ condition: boolean,
121
+ joinSql: J,
122
+ id?: Id,
123
+ ): SelectQueryBuilder<Schema, WithJoin<Sql, J, ResolveId<Id, "join", Sql["joins"]>>>;
124
+
125
+ groupBy<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
126
+ columns: Cols,
127
+ id?: Id,
128
+ ): SelectQueryBuilder<Schema, WithGroupBy<Sql, ColsText<Cols>, ResolveId<Id, "group", Sql["groupBys"]>>>;
129
+
130
+ groupByIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
131
+ condition: boolean,
132
+ columns: Cols,
133
+ id?: Id,
134
+ ): SelectQueryBuilder<Schema, WithGroupBy<Sql, ColsText<Cols>, ResolveId<Id, "group", Sql["groupBys"]>>>;
135
+
136
+ having<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
137
+ condition: Cond,
138
+ id?: Id,
139
+ ): SelectQueryBuilder<Schema, WithHaving<Sql, CondText<Cond>, ResolveId<Id, "having", Sql["havings"]>>>;
140
+
141
+ havingIf<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
142
+ condition: boolean,
143
+ clause: Cond,
144
+ id?: Id,
145
+ ): SelectQueryBuilder<Schema, WithHaving<Sql, CondText<Cond>, ResolveId<Id, "having", Sql["havings"]>>>;
146
+
147
+ orderBy<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
148
+ columns: Cols,
149
+ id?: Id,
150
+ ): SelectQueryBuilder<Schema, WithOrderBy<Sql, ColsText<Cols>, ResolveId<Id, "order", Sql["orderBys"]>>>;
151
+
152
+ orderByIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
153
+ condition: boolean,
154
+ columns: Cols,
155
+ id?: Id,
156
+ ): SelectQueryBuilder<Schema, WithOrderBy<Sql, ColsText<Cols>, ResolveId<Id, "order", Sql["orderBys"]>>>;
157
+
158
+ limit<const L extends number>(limit: L): SelectQueryBuilder<Schema, WithLimit<Sql, L>>;
159
+ limitIf<const L extends number>(condition: boolean, limit: L): SelectQueryBuilder<Schema, WithLimit<Sql, L>>;
160
+ offset<const O extends number>(offset: O): SelectQueryBuilder<Schema, WithOffset<Sql, O>>;
161
+ offsetIf<const O extends number>(condition: boolean, offset: O): SelectQueryBuilder<Schema, WithOffset<Sql, O>>;
162
+
163
+ removeSelect<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutSelect<Sql, Id>>;
164
+ removeJoin<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutJoin<Sql, Id>>;
165
+ removeWhere<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutWhere<Sql, Id>>;
166
+ removeGroupBy<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutGroupBy<Sql, Id>>;
167
+ removeHaving<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutHaving<Sql, Id>>;
168
+ removeOrderBy<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutOrderBy<Sql, Id>>;
169
+
170
+ // Runtime introspection over keyed clause state. Plain booleans — no
171
+ // phantom-type transform; intended for imperative dedup helpers (e.g.
172
+ // "upgrade LEFT JOIN to INNER only if the alias is already joined").
173
+ hasSelect(id: string): boolean;
174
+ hasJoin(id: string): boolean;
175
+ hasWhere(id: string): boolean;
176
+ hasGroupBy(id: string): boolean;
177
+ hasHaving(id: string): boolean;
178
+ hasOrderBy(id: string): boolean;
179
+ hasFrom(): boolean;
180
+ hasLimit(): boolean;
181
+ hasOffset(): boolean;
182
+
183
+ withParams<P extends Record<string, QueryParamInput>>(
184
+ params: P,
185
+ ): SelectQueryBuilder<Schema, Sql>;
186
+
187
+ apply<Sql2 extends SqlTag>(
188
+ fn: (b: SelectQueryBuilder<Schema, Sql>) => SelectQueryBuilder<Schema, Sql2>,
189
+ ): SelectQueryBuilder<Schema, Sql2>;
190
+
191
+ applyIf<Sql2 extends SqlTag>(
192
+ condition: boolean,
193
+ fn: (b: SelectQueryBuilder<Schema, Sql>) => SelectQueryBuilder<Schema, Sql2>,
194
+ ): SelectQueryBuilder<Schema, FlagNewConditional<Sql, Sql2>>;
195
+
196
+ getParams(): ReadonlyArray<QueryParamValue>;
197
+ toString(): string;
198
+ toBrandedString(): string & { __type: BuilderResultBrand<Schema, Sql> };
199
+ }
200
+
201
+ // (No `DefaultId`: idless calls resolve a type-level auto id via `ResolveId`
202
+ // in each method's return type — see below.)
203
+
204
+ class SelectQueryBuilderImpl<Schema extends DatabaseSchema, Sql extends SqlTag> {
205
+ readonly _state: RuntimeSelectState;
206
+
207
+ constructor(state?: RuntimeSelectState) {
208
+ this._state = state ?? EMPTY_RUNTIME_STATE;
209
+ }
210
+
211
+ private clone(patch: Partial<RuntimeSelectState>): RuntimeSelectState {
212
+ return { ...this._state, ...patch };
213
+ }
214
+
215
+ private next(state: RuntimeSelectState): any {
216
+ return new SelectQueryBuilderImpl<Schema, any>(state);
217
+ }
218
+
219
+ select(columns: string | readonly string[], id?: string): any {
220
+ const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
221
+ const cols = rawCols.length > 0 ? [...rawCols] : [];
222
+ const key = id ?? `select_${Object.keys(this._state.selectSql).length}`;
223
+ return this.next(this.clone({
224
+ selectSql: { ...this._state.selectSql, [key]: cols },
225
+ }));
226
+ }
227
+
228
+ selectIf(condition: boolean, columns: string | readonly string[], id?: string): any {
229
+ return condition ? this.select(columns, id) : this.next(this._state);
230
+ }
231
+
232
+ from(source: string | { toString(): string; getParams(): ReadonlyArray<QueryParamValue> }): any {
233
+ let fromSql: string;
234
+ if (typeof source === "string") {
235
+ fromSql = source;
236
+ }
237
+ else {
238
+ if (source.getParams().length > 0) {
239
+ throw new Error(
240
+ "from() does not support a parameterized subquery builder: the inner " +
241
+ "builder carries params that cannot be merged into the outer query. " +
242
+ "Inline the subquery as a string or remove its params.",
243
+ );
244
+ }
245
+ fromSql = `(${source.toString()})`;
246
+ }
247
+ return this.next(this.clone({ fromSql }));
248
+ }
249
+
250
+ where(condition: string | ConditionTreeBuilder<any, any>, id?: string): any {
251
+ // An empty condition tree contributes nothing — treat it as a no-op
252
+ // (same as a false whereIf) so we never emit an invalid `WHERE ()`.
253
+ // String conditions are always applied verbatim.
254
+ if (condition instanceof ConditionTreeBuilder && condition.isEmpty()) {
255
+ return this.next(this._state);
256
+ }
257
+ const key = id ?? `where_${Object.keys(this._state.whereSql).length}`;
258
+ const sql = typeof condition === "string" ? condition : condition.toString();
259
+ return this.next(this.clone({ whereSql: { ...this._state.whereSql, [key]: sql } }));
260
+ }
261
+
262
+ whereIf(condition: boolean, clause: string | ConditionTreeBuilder<any, any>, id?: string): any {
263
+ return condition ? this.where(clause, id) : this.next(this._state);
264
+ }
265
+
266
+ join(joinSql: string, id?: string): any {
267
+ const key = id ?? `join_${this._state.joins.length}`;
268
+ // Idempotent by id: re-joining an existing id only replaces its SQL in
269
+ // joinSql below, keeping the ordering array (and thus its FROM-chain
270
+ // position) untouched. A brand-new id is appended at the tail.
271
+ const existing = this._state.joins.some(j => j.id === key);
272
+ const nextJoins = existing ? this._state.joins : [...this._state.joins, { id: key }];
273
+ return this.next(this.clone({
274
+ joinSql: { ...this._state.joinSql, [key]: joinSql },
275
+ joins: nextJoins,
276
+ }));
277
+ }
278
+
279
+ joinIf(condition: boolean, joinSql: string, id?: string): any {
280
+ return condition ? this.join(joinSql, id) : this.next(this._state);
281
+ }
282
+
283
+ groupBy(columns: string | readonly string[], id?: string): any {
284
+ const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
285
+ const key = id ?? `group_${Object.keys(this._state.groupBySql).length}`;
286
+ return this.next(this.clone({
287
+ groupBySql: { ...this._state.groupBySql, [key]: rawCols.join(", ") },
288
+ }));
289
+ }
290
+
291
+ groupByIf(condition: boolean, columns: string | readonly string[], id?: string): any {
292
+ return condition ? this.groupBy(columns, id) : this.next(this._state);
293
+ }
294
+
295
+ having(condition: string | ConditionTreeBuilder<any, any>, id?: string): any {
296
+ // An empty condition tree is a no-op (see where()): never emit
297
+ // `HAVING ()`. String conditions are applied verbatim.
298
+ if (condition instanceof ConditionTreeBuilder && condition.isEmpty()) {
299
+ return this.next(this._state);
300
+ }
301
+ const key = id ?? `having_${Object.keys(this._state.havingSql).length}`;
302
+ const sql = typeof condition === "string" ? condition : condition.toString();
303
+ return this.next(this.clone({ havingSql: { ...this._state.havingSql, [key]: sql } }));
304
+ }
305
+
306
+ havingIf(condition: boolean, clause: string | ConditionTreeBuilder<any, any>, id?: string): any {
307
+ return condition ? this.having(clause, id) : this.next(this._state);
308
+ }
309
+
310
+ orderBy(columns: string | readonly string[], id?: string): any {
311
+ const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
312
+ const key = id ?? `order_${Object.keys(this._state.orderBySql).length}`;
313
+ return this.next(this.clone({
314
+ orderBySql: { ...this._state.orderBySql, [key]: rawCols.join(", ") },
315
+ }));
316
+ }
317
+
318
+ orderByIf(condition: boolean, columns: string | readonly string[], id?: string): any {
319
+ return condition ? this.orderBy(columns, id) : this.next(this._state);
320
+ }
321
+
322
+ limit(limit: number): any {
323
+ return this.next(this.clone({ limit }));
324
+ }
325
+ limitIf(condition: boolean, limit: number): any {
326
+ return condition ? this.limit(limit) : this.next(this._state);
327
+ }
328
+ offset(offset: number): any {
329
+ return this.next(this.clone({ offset }));
330
+ }
331
+ offsetIf(condition: boolean, offset: number): any {
332
+ return condition ? this.offset(offset) : this.next(this._state);
333
+ }
334
+
335
+ removeSelect(id: string): any {
336
+ const nextSelectSql = { ...this._state.selectSql };
337
+ if (!(id in nextSelectSql)) {
338
+ return this.next(this._state);
339
+ }
340
+ delete (nextSelectSql as any)[id];
341
+ return this.next(this.clone({ selectSql: nextSelectSql }));
342
+ }
343
+
344
+ removeJoin(id: string): any {
345
+ const nextJoinSql = { ...this._state.joinSql };
346
+ const hadSql = id in nextJoinSql;
347
+ delete (nextJoinSql as any)[id];
348
+ const nextJoins = this._state.joins.filter(j => j.id !== id);
349
+ if (!hadSql && nextJoins.length === this._state.joins.length) {
350
+ return this.next(this._state);
351
+ }
352
+ return this.next(this.clone({ joinSql: nextJoinSql, joins: nextJoins }));
353
+ }
354
+
355
+ removeWhere(id: string): any {
356
+ const nextWhereSql = { ...this._state.whereSql };
357
+ if (!(id in nextWhereSql)) {
358
+ return this.next(this._state);
359
+ }
360
+ delete (nextWhereSql as any)[id];
361
+ return this.next(this.clone({ whereSql: nextWhereSql }));
362
+ }
363
+
364
+ removeGroupBy(id: string): any {
365
+ const nextGroupBySql = { ...this._state.groupBySql };
366
+ if (!(id in nextGroupBySql)) {
367
+ return this.next(this._state);
368
+ }
369
+ delete (nextGroupBySql as any)[id];
370
+ return this.next(this.clone({ groupBySql: nextGroupBySql }));
371
+ }
372
+
373
+ removeHaving(id: string): any {
374
+ const nextHavingSql = { ...this._state.havingSql };
375
+ if (!(id in nextHavingSql)) {
376
+ return this.next(this._state);
377
+ }
378
+ delete (nextHavingSql as any)[id];
379
+ return this.next(this.clone({ havingSql: nextHavingSql }));
380
+ }
381
+
382
+ removeOrderBy(id: string): any {
383
+ const nextOrderBySql = { ...this._state.orderBySql };
384
+ if (!(id in nextOrderBySql)) {
385
+ return this.next(this._state);
386
+ }
387
+ delete (nextOrderBySql as any)[id];
388
+ return this.next(this.clone({ orderBySql: nextOrderBySql }));
389
+ }
390
+
391
+ // has*: read-only checks against the keyed runtime state.
392
+ hasSelect(id: string): boolean {
393
+ return id in this._state.selectSql;
394
+ }
395
+ hasJoin(id: string): boolean {
396
+ return id in this._state.joinSql;
397
+ }
398
+ hasWhere(id: string): boolean {
399
+ return id in this._state.whereSql;
400
+ }
401
+ hasGroupBy(id: string): boolean {
402
+ return id in this._state.groupBySql;
403
+ }
404
+ hasHaving(id: string): boolean {
405
+ return id in this._state.havingSql;
406
+ }
407
+ hasOrderBy(id: string): boolean {
408
+ return id in this._state.orderBySql;
409
+ }
410
+ hasFrom(): boolean {
411
+ return this._state.fromSql !== undefined;
412
+ }
413
+ hasLimit(): boolean {
414
+ return this._state.limit !== undefined;
415
+ }
416
+ hasOffset(): boolean {
417
+ return this._state.offset !== undefined;
418
+ }
419
+
420
+ withParams(params: Record<string, QueryParamInput>): any {
421
+ return this.next(this.clone({
422
+ namedParams: { ...this._state.namedParams, ...params },
423
+ }));
424
+ }
425
+
426
+ apply(fn: (b: any) => any): any {
427
+ return fn(this);
428
+ }
429
+
430
+ applyIf(condition: boolean, fn: (b: any) => any): any {
431
+ return condition ? fn(this) : this;
432
+ }
433
+
434
+ getParams(): ReadonlyArray<QueryParamValue> {
435
+ const namedParams = this._state.namedParams;
436
+ if (namedParams && Object.keys(namedParams).length > 0) {
437
+ const sql = assembleSelectSQLPreSub(this._state);
438
+ return collectParamValues(sql, namedParams);
439
+ }
440
+ return this._state.params;
441
+ }
442
+
443
+ toString(): string {
444
+ return assembleSelectSQL(this._state);
445
+ }
446
+
447
+ toBrandedString(): any {
448
+ return assembleSelectSQL(this._state);
449
+ }
450
+ }
451
+
452
+ // Build the un-substituted fragment string for getParams ordering (matches OLD:
453
+ // getParams scans fragments joined by " " BEFORE $n substitution).
454
+ function assembleSelectSQLPreSub(state: RuntimeSelectState): string {
455
+ return [
456
+ ...Object.values(state.cteSql),
457
+ ...Object.values(state.selectSql).flat(),
458
+ state.fromSql ?? "",
459
+ ...Object.values(state.joinSql),
460
+ ...Object.values(state.whereSql),
461
+ ...Object.values(state.groupBySql),
462
+ ...Object.values(state.havingSql),
463
+ ...Object.values(state.orderBySql),
464
+ state.unionSql ?? "",
465
+ ].join(" ");
466
+ }
467
+
468
+ export function createSelectQuery<Schema extends DatabaseSchema>(): SelectQueryBuilder<Schema, EmptySqlTag> {
469
+ return new SelectQueryBuilderImpl<Schema, EmptySqlTag>(EMPTY_RUNTIME_STATE) as unknown as SelectQueryBuilder<Schema, EmptySqlTag>;
470
+ }