@c9up/atlas 0.2.0 → 0.2.1

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 (234) hide show
  1. package/README.md +8 -0
  2. package/db.darwin-arm64.node +0 -0
  3. package/db.darwin-x64.node +0 -0
  4. package/db.linux-arm64-gnu.node +0 -0
  5. package/db.linux-x64-gnu.node +0 -0
  6. package/db.win32-x64-msvc.node +0 -0
  7. package/dist/AtlasProvider.d.ts +60 -7
  8. package/dist/AtlasProvider.d.ts.map +1 -1
  9. package/dist/AtlasProvider.js +204 -38
  10. package/dist/AtlasProvider.js.map +1 -1
  11. package/dist/BaseEntity.d.ts +17 -0
  12. package/dist/BaseEntity.d.ts.map +1 -1
  13. package/dist/BaseEntity.js.map +1 -1
  14. package/dist/BaseModel.d.ts +63 -27
  15. package/dist/BaseModel.d.ts.map +1 -1
  16. package/dist/BaseModel.js +108 -51
  17. package/dist/BaseModel.js.map +1 -1
  18. package/dist/BaseRepository.d.ts +11 -1
  19. package/dist/BaseRepository.d.ts.map +1 -1
  20. package/dist/BaseRepository.js +95 -20
  21. package/dist/BaseRepository.js.map +1 -1
  22. package/dist/ConnectionManager.d.ts +106 -0
  23. package/dist/ConnectionManager.d.ts.map +1 -0
  24. package/dist/ConnectionManager.js +228 -0
  25. package/dist/ConnectionManager.js.map +1 -0
  26. package/dist/ModelQuery.d.ts +256 -43
  27. package/dist/ModelQuery.d.ts.map +1 -1
  28. package/dist/ModelQuery.js +899 -231
  29. package/dist/ModelQuery.js.map +1 -1
  30. package/dist/Transaction.d.ts +54 -1
  31. package/dist/Transaction.d.ts.map +1 -1
  32. package/dist/Transaction.js +121 -59
  33. package/dist/Transaction.js.map +1 -1
  34. package/dist/adapters/NapiDbAdapter.d.ts +11 -0
  35. package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
  36. package/dist/adapters/NapiDbAdapter.js +35 -7
  37. package/dist/adapters/NapiDbAdapter.js.map +1 -1
  38. package/dist/console/contract.d.ts +62 -0
  39. package/dist/console/contract.d.ts.map +1 -0
  40. package/dist/console/contract.js +38 -0
  41. package/dist/console/contract.js.map +1 -0
  42. package/dist/console/factoryCommands.d.ts +23 -0
  43. package/dist/console/factoryCommands.d.ts.map +1 -0
  44. package/dist/console/factoryCommands.js +62 -0
  45. package/dist/console/factoryCommands.js.map +1 -0
  46. package/dist/console/migrationCommands.d.ts +38 -8
  47. package/dist/console/migrationCommands.d.ts.map +1 -1
  48. package/dist/console/migrationCommands.js +157 -50
  49. package/dist/console/migrationCommands.js.map +1 -1
  50. package/dist/console/schemaCheckCommand.d.ts +11 -17
  51. package/dist/console/schemaCheckCommand.d.ts.map +1 -1
  52. package/dist/console/schemaCheckCommand.js +21 -15
  53. package/dist/console/schemaCheckCommand.js.map +1 -1
  54. package/dist/console/schemaDumpCommand.d.ts +30 -0
  55. package/dist/console/schemaDumpCommand.d.ts.map +1 -0
  56. package/dist/console/schemaDumpCommand.js +69 -0
  57. package/dist/console/schemaDumpCommand.js.map +1 -0
  58. package/dist/console/schemaGenerateCommand.d.ts +100 -0
  59. package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
  60. package/dist/console/schemaGenerateCommand.js +246 -0
  61. package/dist/console/schemaGenerateCommand.js.map +1 -0
  62. package/dist/console/seederCommands.d.ts +46 -0
  63. package/dist/console/seederCommands.d.ts.map +1 -0
  64. package/dist/console/seederCommands.js +136 -0
  65. package/dist/console/seederCommands.js.map +1 -0
  66. package/dist/decorators/entity.d.ts +13 -2
  67. package/dist/decorators/entity.d.ts.map +1 -1
  68. package/dist/decorators/entity.js +10 -0
  69. package/dist/decorators/entity.js.map +1 -1
  70. package/dist/decorators/hooks.d.ts +3 -3
  71. package/dist/decorators/hooks.d.ts.map +1 -1
  72. package/dist/decorators/hooks.js.map +1 -1
  73. package/dist/events.d.ts +5 -0
  74. package/dist/events.d.ts.map +1 -1
  75. package/dist/events.js.map +1 -1
  76. package/dist/factories.d.ts +8 -0
  77. package/dist/factories.d.ts.map +1 -0
  78. package/dist/factories.js +8 -0
  79. package/dist/factories.js.map +1 -0
  80. package/dist/index.d.ts +11 -5
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +12 -4
  83. package/dist/index.js.map +1 -1
  84. package/dist/lucid-schema.d.ts +15 -0
  85. package/dist/lucid-schema.d.ts.map +1 -0
  86. package/dist/lucid-schema.js +16 -0
  87. package/dist/lucid-schema.js.map +1 -0
  88. package/dist/orm.d.ts +14 -0
  89. package/dist/orm.d.ts.map +1 -0
  90. package/dist/orm.js +14 -0
  91. package/dist/orm.js.map +1 -0
  92. package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
  93. package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
  94. package/dist/query/DatabaseQueryBuilder.js +1798 -0
  95. package/dist/query/DatabaseQueryBuilder.js.map +1 -0
  96. package/dist/query/DmlBuilder.d.ts +62 -0
  97. package/dist/query/DmlBuilder.d.ts.map +1 -0
  98. package/dist/query/DmlBuilder.js +84 -0
  99. package/dist/query/DmlBuilder.js.map +1 -0
  100. package/dist/query/QueryBuilder.d.ts +5 -0
  101. package/dist/query/QueryBuilder.d.ts.map +1 -1
  102. package/dist/query/QueryBuilder.js +7 -0
  103. package/dist/query/QueryBuilder.js.map +1 -1
  104. package/dist/query/RawQueryBuilder.d.ts +47 -0
  105. package/dist/query/RawQueryBuilder.d.ts.map +1 -0
  106. package/dist/query/RawQueryBuilder.js +138 -0
  107. package/dist/query/RawQueryBuilder.js.map +1 -0
  108. package/dist/query/interpolate.d.ts +45 -0
  109. package/dist/query/interpolate.d.ts.map +1 -0
  110. package/dist/query/interpolate.js +51 -0
  111. package/dist/query/interpolate.js.map +1 -0
  112. package/dist/query/native.d.ts +8 -0
  113. package/dist/query/native.d.ts.map +1 -1
  114. package/dist/query/native.js +14 -0
  115. package/dist/query/native.js.map +1 -1
  116. package/dist/query/operators.d.ts +4 -0
  117. package/dist/query/operators.d.ts.map +1 -0
  118. package/dist/query/operators.js +27 -0
  119. package/dist/query/operators.js.map +1 -0
  120. package/dist/schema/Migration.d.ts +31 -7
  121. package/dist/schema/Migration.d.ts.map +1 -1
  122. package/dist/schema/Migration.js +34 -7
  123. package/dist/schema/Migration.js.map +1 -1
  124. package/dist/schema/MigrationRunner.d.ts +56 -6
  125. package/dist/schema/MigrationRunner.d.ts.map +1 -1
  126. package/dist/schema/MigrationRunner.js +382 -112
  127. package/dist/schema/MigrationRunner.js.map +1 -1
  128. package/dist/schema/Schema.d.ts +59 -2
  129. package/dist/schema/Schema.d.ts.map +1 -1
  130. package/dist/schema/Schema.js +143 -9
  131. package/dist/schema/Schema.js.map +1 -1
  132. package/dist/schema/SchemaDumper.d.ts +121 -0
  133. package/dist/schema/SchemaDumper.d.ts.map +1 -0
  134. package/dist/schema/SchemaDumper.js +365 -0
  135. package/dist/schema/SchemaDumper.js.map +1 -0
  136. package/dist/schema/Seeder.d.ts +13 -0
  137. package/dist/schema/Seeder.d.ts.map +1 -1
  138. package/dist/schema/Seeder.js +28 -5
  139. package/dist/schema/Seeder.js.map +1 -1
  140. package/dist/schema/TableBuilder.d.ts +27 -8
  141. package/dist/schema/TableBuilder.d.ts.map +1 -1
  142. package/dist/schema/TableBuilder.js +50 -12
  143. package/dist/schema/TableBuilder.js.map +1 -1
  144. package/dist/schema/catalog.d.ts +25 -1
  145. package/dist/schema/catalog.d.ts.map +1 -1
  146. package/dist/schema/catalog.js +69 -22
  147. package/dist/schema/catalog.js.map +1 -1
  148. package/dist/schema/introspect.d.ts +1 -1
  149. package/dist/schema/introspect.d.ts.map +1 -1
  150. package/dist/schema/introspect.js +26 -11
  151. package/dist/schema/introspect.js.map +1 -1
  152. package/dist/seeders.d.ts +7 -0
  153. package/dist/seeders.d.ts.map +1 -0
  154. package/dist/seeders.js +7 -0
  155. package/dist/seeders.js.map +1 -0
  156. package/dist/services/db.d.ts +120 -9
  157. package/dist/services/db.d.ts.map +1 -1
  158. package/dist/services/db.js +182 -26
  159. package/dist/services/db.js.map +1 -1
  160. package/dist/testing/DatabaseCleanup.d.ts +29 -6
  161. package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
  162. package/dist/testing/DatabaseCleanup.js +44 -17
  163. package/dist/testing/DatabaseCleanup.js.map +1 -1
  164. package/dist/testing/DbAssertions.d.ts +48 -0
  165. package/dist/testing/DbAssertions.d.ts.map +1 -0
  166. package/dist/testing/DbAssertions.js +80 -0
  167. package/dist/testing/DbAssertions.js.map +1 -0
  168. package/dist/testing/Factory.d.ts +111 -25
  169. package/dist/testing/Factory.d.ts.map +1 -1
  170. package/dist/testing/Factory.js +331 -56
  171. package/dist/testing/Factory.js.map +1 -1
  172. package/dist/testing/TestUtils.d.ts +49 -0
  173. package/dist/testing/TestUtils.d.ts.map +1 -0
  174. package/dist/testing/TestUtils.js +79 -0
  175. package/dist/testing/TestUtils.js.map +1 -0
  176. package/dist/testing/index.d.ts +3 -1
  177. package/dist/testing/index.d.ts.map +1 -1
  178. package/dist/testing/index.js +3 -1
  179. package/dist/testing/index.js.map +1 -1
  180. package/dist/types/relations.d.ts +31 -0
  181. package/dist/types/relations.d.ts.map +1 -0
  182. package/dist/types/relations.js +20 -0
  183. package/dist/types/relations.js.map +1 -0
  184. package/index.darwin-arm64.node +0 -0
  185. package/index.darwin-x64.node +0 -0
  186. package/index.linux-arm64-gnu.node +0 -0
  187. package/index.linux-x64-gnu.node +0 -0
  188. package/index.win32-x64-msvc.node +0 -0
  189. package/package.json +30 -2
  190. package/src/AtlasProvider.ts +297 -51
  191. package/src/BaseEntity.ts +29 -0
  192. package/src/BaseModel.ts +174 -37
  193. package/src/BaseRepository.ts +136 -17
  194. package/src/ConnectionManager.ts +298 -0
  195. package/src/ModelQuery.ts +1309 -260
  196. package/src/Transaction.ts +194 -60
  197. package/src/adapters/NapiDbAdapter.ts +72 -14
  198. package/src/console/contract.ts +96 -0
  199. package/src/console/factoryCommands.ts +73 -0
  200. package/src/console/migrationCommands.ts +253 -63
  201. package/src/console/schemaCheckCommand.ts +26 -22
  202. package/src/console/schemaDumpCommand.ts +96 -0
  203. package/src/console/schemaGenerateCommand.ts +380 -0
  204. package/src/console/seederCommands.ts +174 -0
  205. package/src/decorators/entity.ts +18 -2
  206. package/src/decorators/hooks.ts +5 -3
  207. package/src/events.ts +5 -0
  208. package/src/factories.ts +10 -0
  209. package/src/index.ts +50 -3
  210. package/src/lucid-schema.ts +22 -0
  211. package/src/orm.ts +13 -0
  212. package/src/query/DatabaseQueryBuilder.ts +2724 -0
  213. package/src/query/DmlBuilder.ts +131 -0
  214. package/src/query/QueryBuilder.ts +8 -0
  215. package/src/query/RawQueryBuilder.ts +173 -0
  216. package/src/query/interpolate.ts +73 -0
  217. package/src/query/native.ts +29 -0
  218. package/src/query/operators.ts +27 -0
  219. package/src/schema/Migration.ts +36 -7
  220. package/src/schema/MigrationRunner.ts +483 -119
  221. package/src/schema/Schema.ts +168 -19
  222. package/src/schema/SchemaDumper.ts +518 -0
  223. package/src/schema/Seeder.ts +47 -10
  224. package/src/schema/TableBuilder.ts +48 -11
  225. package/src/schema/catalog.ts +94 -24
  226. package/src/schema/introspect.ts +26 -9
  227. package/src/seeders.ts +16 -0
  228. package/src/services/db.ts +343 -31
  229. package/src/testing/DatabaseCleanup.ts +73 -21
  230. package/src/testing/DbAssertions.ts +127 -0
  231. package/src/testing/Factory.ts +559 -79
  232. package/src/testing/TestUtils.ts +123 -0
  233. package/src/testing/index.ts +12 -1
  234. package/src/types/relations.ts +43 -0
@@ -0,0 +1,1798 @@
1
+ /**
2
+ * Connection-level query builder — Adonis Lucid's `db.query()` / `db.from()` /
3
+ * `db.table()` / `db.insertQuery()`. Unlike {@link ModelQuery} it is NOT bound to
4
+ * a model: it reads and writes plain rows against a table, executing through a
5
+ * connection (or a transaction client passed as `{ client }`).
6
+ *
7
+ * Reads AND writes go through the native compiler directly (`compileStatementNative`),
8
+ * the same path the repository uses — so quoting, casts, the full WHERE grammar
9
+ * (between/like/raw/exists), joins, locks and parameter binding are identical.
10
+ *
11
+ * const rows = await db.from('users').where('is_active', true).orderBy('id')
12
+ * const user = await db.query().from('users').where('id', 1).first()
13
+ * await db.table('audit_logs').insert({ user_id: 1, action: 'login' })
14
+ * await db.from('users').where('id', 1).update({ is_active: false })
15
+ */
16
+ var _a;
17
+ import { Paginator } from "../ModelQuery.js";
18
+ import { DmlBuilder } from "./DmlBuilder.js";
19
+ import { compiledStatement, interpolateQuery, } from "./interpolate.js";
20
+ import { compileStatementNative, normalizeDialect, } from "./native.js";
21
+ import { negateOperator } from "./operators.js";
22
+ import { RawSql } from "./QueryBuilder.js";
23
+ import { RawQueryBuilder } from "./RawQueryBuilder.js";
24
+ /** Build the query-builder entry points a transaction client exposes. */
25
+ export function makeTransactionQueryBuilders(exec, dialect) {
26
+ // Capture the ORIGINAL low-level executor now — trx assembly does
27
+ // `Object.assign(conn, makeTransactionQueryBuilders(conn, …))`, which
28
+ // overwrites `conn.query` with the dispatcher below. Without this bind, the
29
+ // with-SQL branch would call itself and recurse forever.
30
+ const rawExecQuery = exec.query.bind(exec);
31
+ function query(sql, params, meta) {
32
+ return sql === undefined
33
+ ? new DatabaseQueryBuilder(exec, dialect)
34
+ : rawExecQuery(sql, params, meta);
35
+ }
36
+ return {
37
+ from: (table) => new DatabaseQueryBuilder(exec, dialect, table),
38
+ table: (table) => new DatabaseQueryBuilder(exec, dialect, table),
39
+ insertQuery: () => new DatabaseQueryBuilder(exec, dialect),
40
+ query,
41
+ rawQuery: (sql, bindings = []) => new RawQueryBuilder(exec, dialect, sql, bindings),
42
+ raw: (sql, params = []) => new RawSql(sql, params),
43
+ };
44
+ }
45
+ export class DatabaseQueryBuilder {
46
+ #exec;
47
+ #dialect;
48
+ #table;
49
+ #selects = [];
50
+ #wheres = [];
51
+ #orderBys = [];
52
+ #groupBys = [];
53
+ #havings = [];
54
+ #joins = [];
55
+ #unions = [];
56
+ #ctes = [];
57
+ #schema;
58
+ #lockMode;
59
+ #lockModifier;
60
+ #distinctOn = [];
61
+ #returningCols = [];
62
+ #onConflictCols;
63
+ #mergeMode;
64
+ #mergeCols = [];
65
+ /** Custom merge assignments from `merge({ col: value | db.raw(...) })`. */
66
+ #mergeSet;
67
+ #distinctFlag = false;
68
+ #limit;
69
+ #offset;
70
+ #debug = false;
71
+ #comments = [];
72
+ #reporterData;
73
+ #fromSubquery;
74
+ /**
75
+ * Raw / subquery SELECT fragments that carry their own bound params — Lucid
76
+ * `select(db.raw(sql, bindings))` and `select(subquery.as('x'))`. Rendered into
77
+ * the SELECT list by the native compiler with their placeholders remapped.
78
+ */
79
+ #selectRaw = [];
80
+ /**
81
+ * This builder's own alias, set by `.as(alias)`. Consumed when the builder is
82
+ * used as a derived `FROM (…) AS <alias>` or as a `SELECT (…) AS <alias>`
83
+ * subquery — the Lucid/Knex `.as()` convention.
84
+ */
85
+ #alias;
86
+ /** Caller-facing statement timeout in ms (Lucid `timeout(ms)`), applied via a race in the read paths. */
87
+ #timeoutMs;
88
+ /** `timeout(ms, { cancel: true })` — also apply a SERVER-side statement timeout. */
89
+ #cancelTimeout = false;
90
+ /**
91
+ * When true this builder came from a `db.connection(name, { mode: 'read' })`
92
+ * scope — its write methods throw (Lucid read/write replica guard).
93
+ */
94
+ #readOnly;
95
+ constructor(exec, dialect, table = "", options) {
96
+ this.#exec = exec;
97
+ this.#dialect = dialect;
98
+ this.#table = table;
99
+ this.#readOnly = options?.readOnly ?? false;
100
+ }
101
+ /** Guard the write methods when the builder is scoped to a read connection. */
102
+ #assertWritable() {
103
+ if (this.#readOnly) {
104
+ throw new Error("[atlas] write blocked: this query builder is scoped to a connection opened with { mode: 'read' }. Use { mode: 'write' } for mutations.");
105
+ }
106
+ }
107
+ from(source, alias) {
108
+ if (typeof source === "string") {
109
+ this.#table = source;
110
+ this.#fromSubquery = undefined;
111
+ return this;
112
+ }
113
+ // A callback builds the subquery on a fresh sub-builder. The alias may come
114
+ // from the explicit 2nd arg (atlas DX) OR from `.as()` inside the callback
115
+ // (`from((sub) => sub.from('x').as('totals'))` — the Lucid convention).
116
+ const sub = typeof source === "function" ? this.#buildSub(source) : source;
117
+ const { sql, params } = sub.#compiledNative();
118
+ this.#fromSubquery = {
119
+ sql,
120
+ params,
121
+ alias: alias ?? sub.#alias ?? "derived",
122
+ };
123
+ return this;
124
+ }
125
+ /**
126
+ * Name this builder as a derived table / SELECT subquery — Lucid/Knex `.as()`.
127
+ * `db.from((s) => s.from('exams').sum('marks as total').as('totals'))` or
128
+ * `parent.select(db.from('logins').select('ip').limit(1).as('last_ip'))`.
129
+ */
130
+ as(alias) {
131
+ this.#alias = alias;
132
+ return this;
133
+ }
134
+ /** Run `cb` against a fresh sub-builder (same executor/dialect) and return it. */
135
+ #buildSub(cb) {
136
+ const sub = new _a(this.#exec, this.#dialect);
137
+ cb(sub);
138
+ return sub;
139
+ }
140
+ /** Select the table for a write (Lucid `db.table`). Alias of {@link from}. */
141
+ table(table) {
142
+ this.#table = table;
143
+ return this;
144
+ }
145
+ /**
146
+ * Add columns to the SELECT list (Lucid/Knex `select`). Accepts bare names,
147
+ * arrays, and `{ alias: 'column' }` objects for aliasing —
148
+ * `select('id', ['name', 'email'], { total: 'COUNT(*)' })`.
149
+ */
150
+ select(...columns) {
151
+ for (const col of columns) {
152
+ if (typeof col === "string") {
153
+ this.#selects.push(col);
154
+ }
155
+ else if (Array.isArray(col)) {
156
+ this.#selects.push(...col);
157
+ }
158
+ else if (col instanceof RawSql) {
159
+ // Lucid `select(db.raw(sql, bindings))` — verbatim fragment + params.
160
+ this.#selectRaw.push({ sql: col.sql, params: [...col.params] });
161
+ }
162
+ else if (col instanceof _a) {
163
+ // Lucid `select(subquery.as('alias'))` — a correlated subquery column.
164
+ const alias = col.#alias;
165
+ if (!alias) {
166
+ throw new Error("select(subquery) requires the subquery to be named with .as('alias')");
167
+ }
168
+ const { sql, params } = col.#compiledNative();
169
+ this.#selectRaw.push({
170
+ sql: `(${sql}) AS ${this.#quoteAlias(alias)}`,
171
+ params,
172
+ });
173
+ }
174
+ else {
175
+ for (const [alias, expr] of Object.entries(col)) {
176
+ this.#selects.push(`${expr} AS ${alias}`);
177
+ }
178
+ }
179
+ }
180
+ return this;
181
+ }
182
+ /** Validate + dialect-quote a bare alias identifier. */
183
+ #quoteAlias(alias) {
184
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
185
+ throw new Error(`Invalid alias '${alias}' — expected a bare identifier.`);
186
+ }
187
+ const q = this.#dialect === "mysql" ? "`" : '"';
188
+ return `${q}${alias}${q}`;
189
+ }
190
+ where(columnOrCbOrObj, operatorOrValue, value) {
191
+ return this.#where("and", columnOrCbOrObj, operatorOrValue, value);
192
+ }
193
+ orWhere(columnOrCbOrObj, operatorOrValue, value) {
194
+ return this.#where("or", columnOrCbOrObj, operatorOrValue, value);
195
+ }
196
+ #where(boolean, columnOrCbOrObj, operatorOrValue, value) {
197
+ if (typeof columnOrCbOrObj === "function") {
198
+ // Parenthesised group: collect the callback's wheres on a sub-builder.
199
+ const sub = new _a(this.#exec, this.#dialect);
200
+ columnOrCbOrObj(sub);
201
+ this.#wheres.push({
202
+ kind: "group",
203
+ conditions: sub.#compiledWheres(),
204
+ boolean,
205
+ });
206
+ return this;
207
+ }
208
+ if (typeof columnOrCbOrObj === "object") {
209
+ for (const [col, val] of Object.entries(columnOrCbOrObj)) {
210
+ this.#cmp(boolean, col, "=", val);
211
+ }
212
+ return this;
213
+ }
214
+ return this.#pushBasic(boolean, columnOrCbOrObj, operatorOrValue, value);
215
+ }
216
+ #pushBasic(boolean, column, operatorOrValue, value) {
217
+ if (value === undefined) {
218
+ this.#cmp(boolean, column, "=", operatorOrValue);
219
+ }
220
+ else {
221
+ this.#cmp(boolean, column, operatorOrValue, value);
222
+ }
223
+ return this;
224
+ }
225
+ /** Push a `col <op> value` comparison. */
226
+ #cmp(boolean, column, operator, value) {
227
+ this.#wheres.push({ kind: "cmp", column, operator, value, boolean });
228
+ }
229
+ whereNotIn(column, arg) {
230
+ return this.#applyIn("and", true, column, arg);
231
+ }
232
+ /** Shared IN/NOT IN dispatch — values, tuple (`[cols],[rows]`) or subquery. */
233
+ #applyIn(boolean, negated, column, arg) {
234
+ if (Array.isArray(column)) {
235
+ const rows = (Array.isArray(arg) ? arg : []).map((r) => Array.isArray(r) ? r : [r]);
236
+ return this.#pushInTuple(boolean, negated, column, rows);
237
+ }
238
+ if (arg instanceof _a || typeof arg === "function") {
239
+ return this.#pushInSub(boolean, negated, column, arg);
240
+ }
241
+ this.#cmp(boolean, column, negated ? "NOT IN" : "IN", arg);
242
+ return this;
243
+ }
244
+ /** WHERE col BETWEEN ? AND ? — inclusive (Lucid/Knex `whereBetween`). */
245
+ whereBetween(column, range) {
246
+ this.#cmp("and", column, "BETWEEN", [...range]);
247
+ return this;
248
+ }
249
+ /** WHERE col NOT BETWEEN ? AND ? (Lucid/Knex `whereNotBetween`). */
250
+ whereNotBetween(column, range) {
251
+ this.#cmp("and", column, "NOT BETWEEN", [...range]);
252
+ return this;
253
+ }
254
+ /** WHERE col LIKE ? — case-sensitive (Lucid/Knex `whereLike`). */
255
+ whereLike(column, pattern) {
256
+ this.#cmp("and", column, "LIKE", pattern);
257
+ return this;
258
+ }
259
+ /** WHERE col ILIKE ? — case-insensitive; compiled to LOWER(..) LIKE on sqlite/mysql. */
260
+ whereILike(column, pattern) {
261
+ this.#cmp("and", column, "ILIKE", pattern);
262
+ return this;
263
+ }
264
+ /** A raw WHERE fragment with `?` bindings (Lucid/Knex `whereRaw`). */
265
+ whereRaw(sql, bindings = []) {
266
+ return this.#pushRaw("and", false, sql, bindings);
267
+ }
268
+ /** Alias of {@link whereRaw} — AND is the default (Lucid `andWhereRaw`). */
269
+ andWhereRaw(sql, bindings = []) {
270
+ return this.#pushRaw("and", false, sql, bindings);
271
+ }
272
+ /** OR-combined raw WHERE fragment (Lucid `orWhereRaw`). */
273
+ orWhereRaw(sql, bindings = []) {
274
+ return this.#pushRaw("or", false, sql, bindings);
275
+ }
276
+ /** WHERE NOT (raw fragment) (Lucid `whereNotRaw`). */
277
+ whereNotRaw(sql, bindings = []) {
278
+ return this.#pushRaw("and", true, sql, bindings);
279
+ }
280
+ /** Alias of {@link whereNotRaw} (Lucid `andWhereNotRaw`). */
281
+ andWhereNotRaw(sql, bindings = []) {
282
+ return this.#pushRaw("and", true, sql, bindings);
283
+ }
284
+ /** OR NOT (raw fragment) (Lucid `orWhereNotRaw`). */
285
+ orWhereNotRaw(sql, bindings = []) {
286
+ return this.#pushRaw("or", true, sql, bindings);
287
+ }
288
+ #pushRaw(boolean, negated, sql, bindings) {
289
+ this.#wheres.push({
290
+ kind: "raw",
291
+ sql: negated ? `NOT (${sql})` : sql,
292
+ bindings,
293
+ boolean,
294
+ });
295
+ return this;
296
+ }
297
+ /** WHERE left <op> right — both COLUMNS (Lucid/Knex `whereColumn`). */
298
+ whereColumn(left, operator, right) {
299
+ return this.#pushColumn("and", false, left, operator, right);
300
+ }
301
+ /** Alias of {@link whereColumn} — AND is the default (Lucid `andWhereColumn`). */
302
+ andWhereColumn(left, operator, right) {
303
+ return this.#pushColumn("and", false, left, operator, right);
304
+ }
305
+ /** OR left <op> right — both COLUMNS (Lucid `orWhereColumn`). */
306
+ orWhereColumn(left, operator, right) {
307
+ return this.#pushColumn("or", false, left, operator, right);
308
+ }
309
+ /** WHERE NOT (left <op> right) — both COLUMNS (Lucid `whereNotColumn`). */
310
+ whereNotColumn(left, operator, right) {
311
+ return this.#pushColumn("and", true, left, operator, right);
312
+ }
313
+ /** Alias of {@link whereNotColumn} (Lucid `andWhereNotColumn`). */
314
+ andWhereNotColumn(left, operator, right) {
315
+ return this.#pushColumn("and", true, left, operator, right);
316
+ }
317
+ /** OR NOT (left <op> right) — both COLUMNS (Lucid `orWhereNotColumn`). */
318
+ orWhereNotColumn(left, operator, right) {
319
+ return this.#pushColumn("or", true, left, operator, right);
320
+ }
321
+ #pushColumn(boolean, negated, left, operator, right) {
322
+ const ops = new Set(["=", "!=", "<>", "<", ">", "<=", ">="]);
323
+ if (!ops.has(operator)) {
324
+ throw new Error(`whereColumn: unsupported operator '${operator}'`);
325
+ }
326
+ const base = `${this.#quoteIdent(left)} ${operator} ${this.#quoteIdent(right)}`;
327
+ const sql = negated ? `NOT (${base})` : base;
328
+ this.#wheres.push({ kind: "raw", sql, bindings: [], boolean });
329
+ return this;
330
+ }
331
+ /** WHERE EXISTS (subquery) — a builder or a callback (Lucid/Knex `whereExists`). */
332
+ whereExists(sub) {
333
+ return this.#pushExists("and", false, sub);
334
+ }
335
+ /** Alias of {@link whereExists} — AND is the default (Lucid `andWhereExists`). */
336
+ andWhereExists(sub) {
337
+ return this.#pushExists("and", false, sub);
338
+ }
339
+ /** WHERE NOT EXISTS (subquery). */
340
+ whereNotExists(sub) {
341
+ return this.#pushExists("and", true, sub);
342
+ }
343
+ /** Alias of {@link whereNotExists} (Lucid `andWhereNotExists`). */
344
+ andWhereNotExists(sub) {
345
+ return this.#pushExists("and", true, sub);
346
+ }
347
+ /** OR EXISTS (subquery). */
348
+ orWhereExists(sub) {
349
+ return this.#pushExists("or", false, sub);
350
+ }
351
+ /** OR NOT EXISTS (subquery) (Lucid `orWhereNotExists`). */
352
+ orWhereNotExists(sub) {
353
+ return this.#pushExists("or", true, sub);
354
+ }
355
+ #pushExists(boolean, negated, sub) {
356
+ this.#wheres.push({
357
+ kind: "exists",
358
+ negated,
359
+ subquery: this.#resolveSub(sub).#selectSpec(),
360
+ boolean,
361
+ });
362
+ return this;
363
+ }
364
+ whereJsonPath(column, path, operatorOrValue, value) {
365
+ return this.#pushJsonPath("and", column, path, operatorOrValue, value);
366
+ }
367
+ andWhereJsonPath(column, path, operatorOrValue, value) {
368
+ return this.#pushJsonPath("and", column, path, operatorOrValue, value);
369
+ }
370
+ orWhereJsonPath(column, path, operatorOrValue, value) {
371
+ return this.#pushJsonPath("or", column, path, operatorOrValue, value);
372
+ }
373
+ #pushJsonPath(boolean, column, path, operatorOrValue, value) {
374
+ // 3-arg form defaults the operator to `=`; 4-arg passes it explicitly.
375
+ const [operator, val] = value === undefined
376
+ ? ["=", operatorOrValue]
377
+ : [String(operatorOrValue), value];
378
+ this.#wheres.push({
379
+ kind: "json",
380
+ jsonOp: "path",
381
+ column,
382
+ negated: false,
383
+ path,
384
+ operator: String(operator),
385
+ value: val,
386
+ boolean,
387
+ });
388
+ return this;
389
+ }
390
+ /** WHERE json column `@>` value — contains (Postgres/MySQL; `whereJsonSupersetOf`). */
391
+ whereJsonSupersetOf(column, value) {
392
+ return this.#pushJsonContainment("and", false, "superset", column, value);
393
+ }
394
+ /** Lucid alias of {@link whereJsonSupersetOf} (`whereJsonSuperset`). */
395
+ whereJsonSuperset(column, value) {
396
+ return this.#pushJsonContainment("and", false, "superset", column, value);
397
+ }
398
+ /** OR json `@>` (Lucid `orWhereJsonSuperset`). */
399
+ orWhereJsonSupersetOf(column, value) {
400
+ return this.#pushJsonContainment("or", false, "superset", column, value);
401
+ }
402
+ orWhereJsonSuperset(column, value) {
403
+ return this.#pushJsonContainment("or", false, "superset", column, value);
404
+ }
405
+ /** WHERE NOT json `@>` (Lucid `whereNotJsonSuperset`). */
406
+ whereNotJsonSupersetOf(column, value) {
407
+ return this.#pushJsonContainment("and", true, "superset", column, value);
408
+ }
409
+ whereNotJsonSuperset(column, value) {
410
+ return this.#pushJsonContainment("and", true, "superset", column, value);
411
+ }
412
+ /** OR NOT json `@>` (Lucid `orWhereNotJsonSuperset`). */
413
+ orWhereNotJsonSupersetOf(column, value) {
414
+ return this.#pushJsonContainment("or", true, "superset", column, value);
415
+ }
416
+ orWhereNotJsonSuperset(column, value) {
417
+ return this.#pushJsonContainment("or", true, "superset", column, value);
418
+ }
419
+ /** WHERE json column `<@` value — contained by (`whereJsonSubsetOf`). */
420
+ whereJsonSubsetOf(column, value) {
421
+ return this.#pushJsonContainment("and", false, "subset", column, value);
422
+ }
423
+ /** Lucid alias of {@link whereJsonSubsetOf} (`whereJsonSubset`). */
424
+ whereJsonSubset(column, value) {
425
+ return this.#pushJsonContainment("and", false, "subset", column, value);
426
+ }
427
+ /** OR json `<@` (Lucid `orWhereJsonSubset`). */
428
+ orWhereJsonSubsetOf(column, value) {
429
+ return this.#pushJsonContainment("or", false, "subset", column, value);
430
+ }
431
+ orWhereJsonSubset(column, value) {
432
+ return this.#pushJsonContainment("or", false, "subset", column, value);
433
+ }
434
+ /** WHERE NOT json `<@` (Lucid `whereNotJsonSubset`). */
435
+ whereNotJsonSubsetOf(column, value) {
436
+ return this.#pushJsonContainment("and", true, "subset", column, value);
437
+ }
438
+ whereNotJsonSubset(column, value) {
439
+ return this.#pushJsonContainment("and", true, "subset", column, value);
440
+ }
441
+ /** OR NOT json `<@` (Lucid `orWhereNotJsonSubset`). */
442
+ orWhereNotJsonSubsetOf(column, value) {
443
+ return this.#pushJsonContainment("or", true, "subset", column, value);
444
+ }
445
+ orWhereNotJsonSubset(column, value) {
446
+ return this.#pushJsonContainment("or", true, "subset", column, value);
447
+ }
448
+ /**
449
+ * Structural JSON match (Lucid `whereJson`) — the column's JSON must equal
450
+ * `value` (canonical comparison on Postgres/MySQL). AND is the default.
451
+ */
452
+ whereJson(column, value) {
453
+ return this.#pushJsonContainment("and", false, "equals", column, value);
454
+ }
455
+ /** Alias of {@link whereJson} (Lucid `andWhereJson`). */
456
+ andWhereJson(column, value) {
457
+ return this.#pushJsonContainment("and", false, "equals", column, value);
458
+ }
459
+ /** OR form of {@link whereJson} (Lucid `orWhereJson`). */
460
+ orWhereJson(column, value) {
461
+ return this.#pushJsonContainment("or", false, "equals", column, value);
462
+ }
463
+ /** WHERE NOT structural JSON match (Lucid `whereNotJson`). */
464
+ whereNotJson(column, value) {
465
+ return this.#pushJsonContainment("and", true, "equals", column, value);
466
+ }
467
+ /** Alias of {@link whereNotJson} (Lucid `andWhereNotJson`). */
468
+ andWhereNotJson(column, value) {
469
+ return this.#pushJsonContainment("and", true, "equals", column, value);
470
+ }
471
+ /** OR NOT structural JSON match (Lucid `orWhereNotJson`). */
472
+ orWhereNotJson(column, value) {
473
+ return this.#pushJsonContainment("or", true, "equals", column, value);
474
+ }
475
+ #pushJsonContainment(boolean, negated, jsonOp, column, value) {
476
+ this.#wheres.push({
477
+ kind: "json",
478
+ jsonOp,
479
+ column,
480
+ negated,
481
+ value: typeof value === "string" ? value : JSON.stringify(value),
482
+ boolean,
483
+ });
484
+ return this;
485
+ }
486
+ /** ORDER BY <raw> — keeps its position among orderBy terms (Lucid `orderByRaw`). */
487
+ orderByRaw(sql) {
488
+ this.#orderBys.push({ raw: sql });
489
+ return this;
490
+ }
491
+ /** GROUP BY <raw expression> (Lucid/Knex `groupByRaw`). */
492
+ groupByRaw(sql) {
493
+ this.#groupBys.push(sql);
494
+ return this;
495
+ }
496
+ /** HAVING <raw> with `?` bindings (Lucid/Knex `havingRaw`). */
497
+ havingRaw(sql, bindings = []) {
498
+ this.#havings.push({ kind: "raw", sql, bindings, type: "and" });
499
+ return this;
500
+ }
501
+ join(table, leftOrBuild, operatorOrRight, right) {
502
+ return this.#pushJoin("INNER", table, leftOrBuild, operatorOrRight, right);
503
+ }
504
+ innerJoin(table, leftOrBuild, operatorOrRight, right) {
505
+ return this.#pushJoin("INNER", table, leftOrBuild, operatorOrRight, right);
506
+ }
507
+ leftJoin(table, leftOrBuild, operatorOrRight, right) {
508
+ return this.#pushJoin("LEFT", table, leftOrBuild, operatorOrRight, right);
509
+ }
510
+ leftOuterJoin(table, leftOrBuild, operatorOrRight, right) {
511
+ return this.#pushJoin("LEFT", table, leftOrBuild, operatorOrRight, right);
512
+ }
513
+ rightJoin(table, leftOrBuild, operatorOrRight, right) {
514
+ return this.#pushJoin("RIGHT", table, leftOrBuild, operatorOrRight, right);
515
+ }
516
+ rightOuterJoin(table, leftOrBuild, operatorOrRight, right) {
517
+ return this.#pushJoin("RIGHT", table, leftOrBuild, operatorOrRight, right);
518
+ }
519
+ fullOuterJoin(table, leftOrBuild, operatorOrRight, right) {
520
+ return this.#pushJoin("FULL OUTER", table, leftOrBuild, operatorOrRight, right);
521
+ }
522
+ /** `INNER JOIN table ON <left> = <right>` — quoted equi-join sugar (Lucid `joinOn`). */
523
+ joinOn(table, left, right) {
524
+ return this.#pushJoin("INNER", table, left, right);
525
+ }
526
+ /** `CROSS JOIN table` (Lucid/Knex `crossJoin`). */
527
+ crossJoin(table) {
528
+ this.#joins.push({
529
+ sql: `CROSS JOIN ${this.#quoteJoinRef(table)}`,
530
+ params: [],
531
+ });
532
+ return this;
533
+ }
534
+ /** Validate + quote a `[[schema.]table.]column` join reference (up to 3 segments). */
535
+ #quoteJoinRef(ref) {
536
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*){0,2}$/.test(ref)) {
537
+ throw new Error(`Invalid join/column identifier '${ref}' — expected [[schema.]table.]column. Use joinRaw() for anything else.`);
538
+ }
539
+ const q = this.#dialect === "mysql" ? "`" : '"';
540
+ return ref
541
+ .split(".")
542
+ .map((seg) => `${q}${seg}${q}`)
543
+ .join(".");
544
+ }
545
+ /**
546
+ * Build a JOIN from the string forms — 3-arg `(left, right)` or 4-arg
547
+ * `(left, operator, right)` — or the callback `ON` builder (Lucid/Knex).
548
+ */
549
+ #pushJoin(kind, table, leftOrBuild, operatorOrRight, right) {
550
+ const tq = this.#quoteJoinRef(table);
551
+ if (typeof leftOrBuild === "function") {
552
+ const parts = [];
553
+ const jb = {
554
+ on(l, opOrR, r) {
555
+ parts.push(r === undefined
556
+ ? { kind: "and", left: l, right: opOrR }
557
+ : { kind: "and", left: l, operator: opOrR, right: r });
558
+ return jb;
559
+ },
560
+ andOn(l, opOrR, r) {
561
+ parts.push(r === undefined
562
+ ? { kind: "and", left: l, right: opOrR }
563
+ : { kind: "and", left: l, operator: opOrR, right: r });
564
+ return jb;
565
+ },
566
+ orOn(l, opOrR, r) {
567
+ parts.push(r === undefined
568
+ ? { kind: "or", left: l, right: opOrR }
569
+ : { kind: "or", left: l, operator: opOrR, right: r });
570
+ return jb;
571
+ },
572
+ onVal(l, v) {
573
+ parts.push({ kind: "and", left: l, value: { v } });
574
+ return jb;
575
+ },
576
+ andOnVal(l, v) {
577
+ parts.push({ kind: "and", left: l, value: { v } });
578
+ return jb;
579
+ },
580
+ orOnVal(l, v) {
581
+ parts.push({ kind: "or", left: l, value: { v } });
582
+ return jb;
583
+ },
584
+ onIn(l, values) {
585
+ parts.push({ kind: "and", left: l, values: [...values] });
586
+ return jb;
587
+ },
588
+ onNotIn(l, values) {
589
+ parts.push({
590
+ kind: "and",
591
+ left: l,
592
+ values: [...values],
593
+ notIn: true,
594
+ });
595
+ return jb;
596
+ },
597
+ onNull(l) {
598
+ parts.push({ kind: "and", left: l, nullOp: "IS NULL" });
599
+ return jb;
600
+ },
601
+ onNotNull(l) {
602
+ parts.push({ kind: "and", left: l, nullOp: "IS NOT NULL" });
603
+ return jb;
604
+ },
605
+ onBetween(l, range) {
606
+ parts.push({ kind: "and", left: l, between: [range[0], range[1]] });
607
+ return jb;
608
+ },
609
+ onNotBetween(l, range) {
610
+ parts.push({
611
+ kind: "and",
612
+ left: l,
613
+ between: [range[0], range[1]],
614
+ notBetween: true,
615
+ });
616
+ return jb;
617
+ },
618
+ onExists: (sub) => {
619
+ const { sql, params } = this.#resolveSub(sub).#compiledNative();
620
+ parts.push({ kind: "and", exists: { sql, params, not: false } });
621
+ return jb;
622
+ },
623
+ onNotExists: (sub) => {
624
+ const { sql, params } = this.#resolveSub(sub).#compiledNative();
625
+ parts.push({ kind: "and", exists: { sql, params, not: true } });
626
+ return jb;
627
+ },
628
+ };
629
+ leftOrBuild(jb);
630
+ const { sql: on, params } = this.#compileJoinParts(parts);
631
+ this.#joins.push({ sql: `${kind} JOIN ${tq} ${on}`, params });
632
+ return this;
633
+ }
634
+ // String form: 3-arg `(left, right)` or 4-arg `(left, operator, right)`.
635
+ const left = leftOrBuild;
636
+ const operator = right === undefined ? "=" : (operatorOrRight ?? "=");
637
+ const rightCol = right === undefined ? operatorOrRight : right;
638
+ if (rightCol === undefined) {
639
+ throw new Error("join() string form requires both left and right operands");
640
+ }
641
+ this.#joins.push({
642
+ sql: `${kind} JOIN ${tq} ON ${this.#quoteJoinRef(left)} ${this.#validateJoinOp(operator)} ${this.#quoteJoinRef(rightCol)}`,
643
+ params: [],
644
+ });
645
+ return this;
646
+ }
647
+ /** Render accumulated `ON` parts to SQL + ordered bound params. */
648
+ #compileJoinParts(parts) {
649
+ const params = [];
650
+ const sql = parts
651
+ .map((p, i) => {
652
+ const prefix = i === 0 ? "ON" : p.kind === "or" ? "OR" : "AND";
653
+ if (p.exists) {
654
+ params.push(...p.exists.params);
655
+ return `${prefix} ${p.exists.not ? "NOT EXISTS" : "EXISTS"} (${p.exists.sql})`;
656
+ }
657
+ const col = this.#quoteJoinRef(p.left ?? "");
658
+ if (p.nullOp) {
659
+ return `${prefix} ${col} ${p.nullOp}`;
660
+ }
661
+ if (p.between) {
662
+ params.push(p.between[0], p.between[1]);
663
+ return `${prefix} ${col} ${p.notBetween ? "NOT BETWEEN" : "BETWEEN"} ? AND ?`;
664
+ }
665
+ if (p.values) {
666
+ const placeholders = p.values.map(() => "?").join(", ");
667
+ params.push(...p.values);
668
+ return `${prefix} ${col} ${p.notIn ? "NOT IN" : "IN"} (${placeholders})`;
669
+ }
670
+ if (p.value) {
671
+ params.push(p.value.v);
672
+ return `${prefix} ${col} ${this.#validateJoinOp(p.operator ?? "=")} ?`;
673
+ }
674
+ return `${prefix} ${col} ${this.#validateJoinOp(p.operator ?? "=")} ${this.#quoteJoinRef(p.right ?? "")}`;
675
+ })
676
+ .join(" ");
677
+ return { sql, params };
678
+ }
679
+ /** Allowlist the comparison operator embedded verbatim into a JOIN's ON SQL. */
680
+ #validateJoinOp(op) {
681
+ const t = op.trim();
682
+ const up = t.toUpperCase();
683
+ const allowed = new Set([
684
+ "=",
685
+ "<>",
686
+ "!=",
687
+ "<",
688
+ "<=",
689
+ ">",
690
+ ">=",
691
+ "LIKE",
692
+ "NOT LIKE",
693
+ "ILIKE",
694
+ ]);
695
+ if (allowed.has(t))
696
+ return t;
697
+ if (allowed.has(up))
698
+ return up;
699
+ throw new Error(`Unsupported join operator '${op}'.`);
700
+ }
701
+ /** A sub-query passed as a builder, or built in a callback (Lucid accepts both). */
702
+ #resolveSub(sub) {
703
+ return typeof sub === "function" ? this.#buildSub(sub) : sub;
704
+ }
705
+ /** `UNION` with another query — a builder or a callback (Lucid/Knex `union`). */
706
+ union(sub) {
707
+ return this.#pushUnion(this.#resolveSub(sub), false);
708
+ }
709
+ /** `UNION ALL` (Lucid/Knex `unionAll`). */
710
+ unionAll(sub) {
711
+ return this.#pushUnion(this.#resolveSub(sub), true, "union");
712
+ }
713
+ /** `INTERSECT` — rows present in both queries (Lucid/Knex `intersect`). */
714
+ intersect(sub) {
715
+ return this.#pushUnion(this.#resolveSub(sub), false, "intersect");
716
+ }
717
+ /** `INTERSECT ALL` — duplicate-preserving {@link intersect} (Postgres/MySQL). */
718
+ intersectAll(sub) {
719
+ return this.#pushUnion(this.#resolveSub(sub), true, "intersect");
720
+ }
721
+ /** `EXCEPT` — rows in this query but not the other (Lucid/Knex `except`). */
722
+ except(sub) {
723
+ return this.#pushUnion(this.#resolveSub(sub), false, "except");
724
+ }
725
+ /** `EXCEPT ALL` — duplicate-preserving {@link except} (Postgres/MySQL). */
726
+ exceptAll(sub) {
727
+ return this.#pushUnion(this.#resolveSub(sub), true, "except");
728
+ }
729
+ #pushUnion(sub, all, op = "union") {
730
+ const { sql, params } = sub.#compiledNative();
731
+ this.#unions.push({ sql, params, all, op });
732
+ return this;
733
+ }
734
+ /**
735
+ * `WITH name AS (subquery)` common table expression (Lucid/Knex `with`). The
736
+ * body is a pre-built builder OR a callback that builds one.
737
+ */
738
+ with(name, sub, options = {}) {
739
+ const { sql, params } = this.#resolveSub(sub).#compiledNative();
740
+ this.#ctes.push({
741
+ name,
742
+ sql,
743
+ params,
744
+ recursive: options.recursive ?? false,
745
+ materialized: options.materialized ?? null,
746
+ columns: options.columns,
747
+ });
748
+ return this;
749
+ }
750
+ /**
751
+ * `WITH RECURSIVE name[(cols)] AS (subquery)` (Lucid/Knex `withRecursive`).
752
+ * The optional `columns` list restricts/names the CTE's output columns.
753
+ */
754
+ withRecursive(name, sub, columns) {
755
+ return this.with(name, sub, { recursive: true, columns });
756
+ }
757
+ /** `WITH name AS MATERIALIZED (subquery)` — Postgres (Lucid/Knex `withMaterialized`). */
758
+ withMaterialized(name, sub) {
759
+ return this.with(name, sub, { materialized: true });
760
+ }
761
+ /** `WITH name AS NOT MATERIALIZED (subquery)` — Postgres (Lucid/Knex `withNotMaterialized`). */
762
+ withNotMaterialized(name, sub) {
763
+ return this.with(name, sub, { materialized: false });
764
+ }
765
+ /**
766
+ * A deep copy of this builder — every accumulated clause is duplicated so
767
+ * mutating the clone never touches the original (Lucid/Knex `clone`). Shares
768
+ * only the executor + dialect.
769
+ */
770
+ clone() {
771
+ const c = new _a(this.#exec, this.#dialect, this.#table, {
772
+ readOnly: this.#readOnly,
773
+ });
774
+ c.#selects = [...this.#selects];
775
+ c.#selectRaw = this.#selectRaw.map((s) => ({
776
+ ...s,
777
+ params: [...s.params],
778
+ }));
779
+ c.#alias = this.#alias;
780
+ c.#wheres = this.#wheres.map((w) => ({ ...w }));
781
+ c.#orderBys = this.#orderBys.map((o) => ({ ...o }));
782
+ c.#groupBys = [...this.#groupBys];
783
+ c.#havings = this.#havings.map((h) => ({ ...h }));
784
+ c.#joins = this.#joins.map((j) => ({ sql: j.sql, params: [...j.params] }));
785
+ c.#unions = this.#unions.map((u) => ({ ...u, params: [...u.params] }));
786
+ c.#ctes = this.#ctes.map((cte) => ({ ...cte, params: [...cte.params] }));
787
+ c.#schema = this.#schema;
788
+ c.#lockMode = this.#lockMode;
789
+ c.#lockModifier = this.#lockModifier;
790
+ c.#distinctOn = [...this.#distinctOn];
791
+ c.#returningCols = [...this.#returningCols];
792
+ c.#onConflictCols = this.#onConflictCols
793
+ ? [...this.#onConflictCols]
794
+ : undefined;
795
+ c.#mergeMode = this.#mergeMode;
796
+ c.#mergeCols = [...this.#mergeCols];
797
+ c.#mergeSet = this.#mergeSet
798
+ ? this.#mergeSet.map((s) => ({ ...s }))
799
+ : undefined;
800
+ c.#distinctFlag = this.#distinctFlag;
801
+ c.#limit = this.#limit;
802
+ c.#offset = this.#offset;
803
+ c.#debug = this.#debug;
804
+ c.#comments = [...this.#comments];
805
+ c.#reporterData = this.#reporterData
806
+ ? { ...this.#reporterData }
807
+ : undefined;
808
+ c.#fromSubquery = this.#fromSubquery
809
+ ? { ...this.#fromSubquery, params: [...this.#fromSubquery.params] }
810
+ : undefined;
811
+ return c;
812
+ }
813
+ /** Qualify the table with a schema (Lucid/Knex `withSchema`). */
814
+ withSchema(schema) {
815
+ this.#schema = schema;
816
+ return this;
817
+ }
818
+ /**
819
+ * Wrap every WHERE clause added so far into its own parenthesised group, so
820
+ * subsequent clauses combine with the group rather than its inner conditions
821
+ * (Lucid `wrapExisting`): `q.where(a).orWhere(b).wrapExisting().where(c)` →
822
+ * `WHERE (a OR b) AND c`.
823
+ */
824
+ wrapExisting() {
825
+ if (this.#wheres.length > 0) {
826
+ this.#wheres = [
827
+ {
828
+ kind: "group",
829
+ conditions: this.#compiledWheres(),
830
+ boolean: "and",
831
+ },
832
+ ];
833
+ }
834
+ return this;
835
+ }
836
+ /** Log the compiled SQL + bindings to the console on the next run (Lucid/Knex `debug`). */
837
+ debug(enabled = true) {
838
+ this.#debug = enabled;
839
+ return this;
840
+ }
841
+ /**
842
+ * Set a caller-facing statement timeout in ms (Lucid `timeout(ms)`). The
843
+ * awaiting promise rejects after `ms` on the read paths (exec/first/pluck/
844
+ * aggregate). With `{ cancel: true }` a SERVER-side statement timeout is also
845
+ * applied — Postgres `statement_timeout`, MySQL `MAX_EXECUTION_TIME` (SELECT) —
846
+ * so the server aborts the query, not just the client. (SQLite has no server
847
+ * timeout; the client race applies.) No argument clears the timeout.
848
+ */
849
+ timeout(ms, options) {
850
+ this.#timeoutMs = ms;
851
+ this.#cancelTimeout = options?.cancel === true;
852
+ return this;
853
+ }
854
+ /**
855
+ * Race `work` against the configured `.timeout(ms)`. Rejects the awaiter after
856
+ * `ms`; the losing DB promise is swallowed so a post-timeout driver error never
857
+ * surfaces as an unhandled rejection. No timeout set → returns `work` as-is.
858
+ * Matches Lucid's DEFAULT (non-cancelling) timeout — the driver still completes
859
+ * the query server-side.
860
+ */
861
+ #raceTimeout(work) {
862
+ const ms = this.#timeoutMs;
863
+ if (!ms || ms <= 0)
864
+ return work;
865
+ let timer;
866
+ const guard = new Promise((_, reject) => {
867
+ timer = setTimeout(() => reject(new Error(`Query timed out after ${ms}ms`)), ms);
868
+ });
869
+ work.catch(() => { });
870
+ return Promise.race([work, guard]).finally(() => clearTimeout(timer));
871
+ }
872
+ /** Prepend a `/* … *​/` SQL comment to the compiled query (Lucid/Knex `comment`). */
873
+ comment(text) {
874
+ // Reject the comment terminator so a comment can never break out of `/* */`.
875
+ if (text.includes("*/")) {
876
+ throw new Error("comment() text may not contain '*/'");
877
+ }
878
+ this.#comments.push(text);
879
+ return this;
880
+ }
881
+ /**
882
+ * Attach arbitrary metadata to the `db:query` event this query emits (Adonis
883
+ * Lucid `reporterData`) — a listener reads it off `event.reporterData`.
884
+ * Repeated calls merge; setting it forces emission so the data reaches a
885
+ * listener even when the connection has `debug: false`.
886
+ */
887
+ reporterData(data) {
888
+ this.#reporterData = { ...this.#reporterData, ...data };
889
+ this.#debug = true;
890
+ return this;
891
+ }
892
+ /** QueryMeta carrying the debug/reporterData channel to the connection. */
893
+ #queryMeta(method) {
894
+ return {
895
+ method,
896
+ debug: this.#debug,
897
+ reporterData: this.#reporterData,
898
+ serverTimeoutMs: this.#cancelTimeout && this.#timeoutMs != null
899
+ ? this.#timeoutMs
900
+ : undefined,
901
+ };
902
+ }
903
+ /** Validate + quote an identifier (optionally `table.column`) for the dialect. */
904
+ #quoteIdent(name) {
905
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(name)) {
906
+ throw new Error(`unsafe identifier ${JSON.stringify(name)}`);
907
+ }
908
+ const q = this.#dialect === "mysql" ? "`" : '"';
909
+ return name
910
+ .split(".")
911
+ .map((p) => `${q}${p}${q}`)
912
+ .join(".");
913
+ }
914
+ /**
915
+ * SELECT DISTINCT (Lucid/Knex `distinct`). With columns, those are added to
916
+ * the SELECT list too — `distinct('a', 'b')` ≈ `SELECT DISTINCT a, b`.
917
+ */
918
+ distinct(...columns) {
919
+ this.#distinctFlag = true;
920
+ if (columns.length > 0)
921
+ this.#selects.push(...columns);
922
+ return this;
923
+ }
924
+ /** Postgres `SELECT DISTINCT ON (cols)` (Lucid/Knex `distinctOn`). */
925
+ distinctOn(...columns) {
926
+ this.#distinctOn.push(...columns);
927
+ return this;
928
+ }
929
+ /** GROUP BY columns (Lucid/Knex `groupBy`). */
930
+ groupBy(...columns) {
931
+ this.#groupBys.push(...columns);
932
+ return this;
933
+ }
934
+ /** HAVING condition after GROUP BY (Lucid/Knex `having`). */
935
+ having(column, operatorOrValue, value) {
936
+ this.#havings.push(value === undefined
937
+ ? { column, operator: "=", value: operatorOrValue, type: "and" }
938
+ : {
939
+ column,
940
+ operator: operatorOrValue,
941
+ value,
942
+ type: "and",
943
+ });
944
+ return this;
945
+ }
946
+ /** OR-combined HAVING condition (Lucid/Knex `orHaving`). */
947
+ orHaving(column, operatorOrValue, value) {
948
+ this.#havings.push(value === undefined
949
+ ? { column, operator: "=", value: operatorOrValue, type: "or" }
950
+ : {
951
+ column,
952
+ operator: operatorOrValue,
953
+ value,
954
+ type: "or",
955
+ });
956
+ return this;
957
+ }
958
+ /** HAVING col IS NULL (Lucid/Knex `havingNull`). */
959
+ havingNull(column) {
960
+ this.#havings.push({
961
+ column,
962
+ operator: "IS NULL",
963
+ value: null,
964
+ type: "and",
965
+ });
966
+ return this;
967
+ }
968
+ /** HAVING col IS NOT NULL (Lucid/Knex `havingNotNull`). */
969
+ havingNotNull(column) {
970
+ this.#havings.push({
971
+ column,
972
+ operator: "IS NOT NULL",
973
+ value: null,
974
+ type: "and",
975
+ });
976
+ return this;
977
+ }
978
+ /** HAVING col IN (...) (Lucid/Knex `havingIn`). */
979
+ havingIn(column, values) {
980
+ this.#havings.push({ column, operator: "IN", value: values, type: "and" });
981
+ return this;
982
+ }
983
+ /** HAVING col NOT IN (...) (Lucid/Knex `havingNotIn`). */
984
+ havingNotIn(column, values) {
985
+ this.#havings.push({
986
+ column,
987
+ operator: "NOT IN",
988
+ value: values,
989
+ type: "and",
990
+ });
991
+ return this;
992
+ }
993
+ /** HAVING col BETWEEN ? AND ? (Lucid/Knex `havingBetween`). */
994
+ havingBetween(column, range) {
995
+ this.#havings.push({
996
+ column,
997
+ operator: "BETWEEN",
998
+ value: [...range],
999
+ type: "and",
1000
+ });
1001
+ return this;
1002
+ }
1003
+ /** HAVING col NOT BETWEEN ? AND ? (Lucid/Knex `havingNotBetween`). */
1004
+ havingNotBetween(column, range) {
1005
+ this.#havings.push({
1006
+ column,
1007
+ operator: "NOT BETWEEN",
1008
+ value: [...range],
1009
+ type: "and",
1010
+ });
1011
+ return this;
1012
+ }
1013
+ whereIn(column, arg) {
1014
+ return this.#applyIn("and", false, column, arg);
1015
+ }
1016
+ #pushInSub(boolean, negated, column, sub) {
1017
+ this.#wheres.push({
1018
+ kind: "inSub",
1019
+ column,
1020
+ negated,
1021
+ subquery: this.#resolveSub(sub).#selectSpec(),
1022
+ boolean,
1023
+ });
1024
+ return this;
1025
+ }
1026
+ #pushInTuple(boolean, negated, columns, rows) {
1027
+ this.#wheres.push({ kind: "inTuple", columns, rows, negated, boolean });
1028
+ return this;
1029
+ }
1030
+ whereNull(column) {
1031
+ this.#cmp("and", column, "IS NULL", null);
1032
+ return this;
1033
+ }
1034
+ whereNotNull(column) {
1035
+ this.#cmp("and", column, "IS NOT NULL", null);
1036
+ return this;
1037
+ }
1038
+ andWhereIn(column, arg) {
1039
+ return this.#applyIn("and", false, column, arg);
1040
+ }
1041
+ andWhereNotIn(column, arg) {
1042
+ return this.#applyIn("and", true, column, arg);
1043
+ }
1044
+ /** Alias of {@link whereNull} (Lucid `andWhereNull`). */
1045
+ andWhereNull(column) {
1046
+ return this.whereNull(column);
1047
+ }
1048
+ /** Alias of {@link whereNotNull} (Lucid `andWhereNotNull`). */
1049
+ andWhereNotNull(column) {
1050
+ return this.whereNotNull(column);
1051
+ }
1052
+ /** Alias of {@link whereBetween} (Lucid `andWhereBetween`). */
1053
+ andWhereBetween(column, range) {
1054
+ return this.whereBetween(column, range);
1055
+ }
1056
+ /** Alias of {@link whereNotBetween} (Lucid `andWhereNotBetween`). */
1057
+ andWhereNotBetween(column, range) {
1058
+ return this.whereNotBetween(column, range);
1059
+ }
1060
+ whereNot(columnOrCbOrObj, operatorOrValue, value) {
1061
+ return this.#applyWhereNot("and", columnOrCbOrObj, operatorOrValue, value);
1062
+ }
1063
+ andWhereNot(columnOrCbOrObj, operatorOrValue, value) {
1064
+ return this.#applyWhereNot("and", columnOrCbOrObj, operatorOrValue, value);
1065
+ }
1066
+ orWhereNot(columnOrCbOrObj, operatorOrValue, value) {
1067
+ return this.#applyWhereNot("or", columnOrCbOrObj, operatorOrValue, value);
1068
+ }
1069
+ #applyWhereNot(boolean, columnOrCbOrObj, operatorOrValue, value) {
1070
+ if (typeof columnOrCbOrObj === "function") {
1071
+ const sub = new _a(this.#exec, this.#dialect);
1072
+ columnOrCbOrObj(sub);
1073
+ this.#wheres.push({
1074
+ kind: "group",
1075
+ conditions: sub.#compiledWheres(),
1076
+ boolean,
1077
+ negated: true,
1078
+ });
1079
+ return this;
1080
+ }
1081
+ if (typeof columnOrCbOrObj === "object") {
1082
+ for (const [col, val] of Object.entries(columnOrCbOrObj)) {
1083
+ this.#cmp(boolean, col, "<>", val);
1084
+ }
1085
+ return this;
1086
+ }
1087
+ if (value === undefined) {
1088
+ this.#cmp(boolean, columnOrCbOrObj, negateOperator("="), operatorOrValue);
1089
+ }
1090
+ else {
1091
+ this.#cmp(boolean, columnOrCbOrObj, negateOperator(String(operatorOrValue)), value);
1092
+ }
1093
+ return this;
1094
+ }
1095
+ orWhereIn(column, valuesOrSub) {
1096
+ if (valuesOrSub instanceof _a ||
1097
+ typeof valuesOrSub === "function") {
1098
+ return this.#pushInSub("or", false, column, valuesOrSub);
1099
+ }
1100
+ this.#cmp("or", column, "IN", valuesOrSub);
1101
+ return this;
1102
+ }
1103
+ /** OR col NOT IN (...) (Lucid/Knex `orWhereNotIn`). */
1104
+ orWhereNotIn(column, values) {
1105
+ this.#cmp("or", column, "NOT IN", values);
1106
+ return this;
1107
+ }
1108
+ /** OR col IS NULL (Lucid/Knex `orWhereNull`). */
1109
+ orWhereNull(column) {
1110
+ this.#cmp("or", column, "IS NULL", null);
1111
+ return this;
1112
+ }
1113
+ /** OR col IS NOT NULL (Lucid/Knex `orWhereNotNull`). */
1114
+ orWhereNotNull(column) {
1115
+ this.#cmp("or", column, "IS NOT NULL", null);
1116
+ return this;
1117
+ }
1118
+ /** OR col BETWEEN ? AND ? (Lucid/Knex `orWhereBetween`). */
1119
+ orWhereBetween(column, range) {
1120
+ this.#cmp("or", column, "BETWEEN", [...range]);
1121
+ return this;
1122
+ }
1123
+ /** OR col NOT BETWEEN ? AND ? (Lucid/Knex `orWhereNotBetween`). */
1124
+ orWhereNotBetween(column, range) {
1125
+ this.#cmp("or", column, "NOT BETWEEN", [...range]);
1126
+ return this;
1127
+ }
1128
+ /** OR col LIKE ? (Lucid/Knex `orWhereLike`). */
1129
+ orWhereLike(column, pattern) {
1130
+ this.#cmp("or", column, "LIKE", pattern);
1131
+ return this;
1132
+ }
1133
+ /** OR col ILIKE ? (Lucid/Knex `orWhereILike`). */
1134
+ orWhereILike(column, pattern) {
1135
+ this.#cmp("or", column, "ILIKE", pattern);
1136
+ return this;
1137
+ }
1138
+ /** A raw JOIN fragment with `?` bindings (Lucid/Knex `joinRaw`). */
1139
+ joinRaw(sql, bindings = []) {
1140
+ this.#joins.push({ sql, params: bindings });
1141
+ return this;
1142
+ }
1143
+ /** `FOR UPDATE` row lock (Lucid/Knex `forUpdate`). Dropped on SQLite. */
1144
+ forUpdate() {
1145
+ if (this.#dialect === "sqlite") {
1146
+ console.warn("[atlas] forUpdate ignored on sqlite (no row-level lock support)");
1147
+ }
1148
+ else {
1149
+ this.#lockMode = "FOR UPDATE";
1150
+ }
1151
+ return this;
1152
+ }
1153
+ /** `FOR SHARE` row lock (Lucid/Knex `forShare`). Dropped on SQLite. */
1154
+ forShare() {
1155
+ if (this.#dialect === "sqlite") {
1156
+ console.warn("[atlas] forShare ignored on sqlite (no row-level lock support)");
1157
+ }
1158
+ else {
1159
+ this.#lockMode = "FOR SHARE";
1160
+ }
1161
+ return this;
1162
+ }
1163
+ /** Postgres `FOR NO KEY UPDATE` — weaker lock that doesn't block FK checks (Lucid/Knex). */
1164
+ forNoKeyUpdate() {
1165
+ if (this.#dialect === "postgres") {
1166
+ this.#lockMode = "FOR NO KEY UPDATE";
1167
+ }
1168
+ else {
1169
+ console.warn(`[atlas] forNoKeyUpdate ignored on ${this.#dialect} (Postgres-only lock)`);
1170
+ }
1171
+ return this;
1172
+ }
1173
+ /** Postgres `FOR KEY SHARE` — the weakest share lock (Lucid/Knex). */
1174
+ forKeyShare() {
1175
+ if (this.#dialect === "postgres") {
1176
+ this.#lockMode = "FOR KEY SHARE";
1177
+ }
1178
+ else {
1179
+ console.warn(`[atlas] forKeyShare ignored on ${this.#dialect} (Postgres-only lock)`);
1180
+ }
1181
+ return this;
1182
+ }
1183
+ /** Append `SKIP LOCKED` — skip locked rows instead of waiting (Lucid/Knex). */
1184
+ skipLocked() {
1185
+ if (this.#dialect === "sqlite") {
1186
+ console.warn("[atlas] skipLocked ignored on sqlite (no row-level lock)");
1187
+ }
1188
+ else {
1189
+ this.#lockModifier = "SKIP LOCKED";
1190
+ }
1191
+ return this;
1192
+ }
1193
+ /** Append `NOWAIT` — error immediately on a locked row (Lucid/Knex). */
1194
+ noWait() {
1195
+ if (this.#dialect === "sqlite") {
1196
+ console.warn("[atlas] noWait ignored on sqlite (no row-level lock)");
1197
+ }
1198
+ else {
1199
+ this.#lockModifier = "NOWAIT";
1200
+ }
1201
+ return this;
1202
+ }
1203
+ /** Apply `cb` only when `condition` is truthy, else `elseCb` (Lucid `if`). */
1204
+ if(condition, cb, elseCb) {
1205
+ if (condition)
1206
+ cb(this);
1207
+ else
1208
+ elseCb?.(this);
1209
+ return this;
1210
+ }
1211
+ /** Inverse of {@link if} — apply `cb` only when `condition` is falsy (Lucid `unless`). */
1212
+ unless(condition, cb, elseCb) {
1213
+ if (!condition)
1214
+ cb(this);
1215
+ else
1216
+ elseCb?.(this);
1217
+ return this;
1218
+ }
1219
+ /** Apply the first `[guard, cb]` whose guard is truthy; a trailing bare cb is
1220
+ * the default (Adonis Lucid `match`). */
1221
+ match(...blocks) {
1222
+ for (const block of blocks) {
1223
+ if (typeof block === "function") {
1224
+ block(this);
1225
+ return this;
1226
+ }
1227
+ const [guard, cb] = block;
1228
+ if (guard) {
1229
+ cb(this);
1230
+ return this;
1231
+ }
1232
+ }
1233
+ return this;
1234
+ }
1235
+ orderBy(columnOrTerms, direction = "asc") {
1236
+ // A raw fragment (e.g. `db.ref('posts.created_at')`) orders verbatim.
1237
+ if (columnOrTerms instanceof RawSql) {
1238
+ const dir = direction === "desc" ? "DESC" : "ASC";
1239
+ this.#orderBys.push({ raw: `${columnOrTerms.sql} ${dir}` });
1240
+ return this;
1241
+ }
1242
+ if (Array.isArray(columnOrTerms)) {
1243
+ for (const t of columnOrTerms) {
1244
+ if (typeof t === "string") {
1245
+ this.#orderBys.push({ column: t, direction: "asc" });
1246
+ }
1247
+ else {
1248
+ this.#orderBys.push({
1249
+ column: t.column,
1250
+ direction: t.order ?? "asc",
1251
+ });
1252
+ }
1253
+ }
1254
+ return this;
1255
+ }
1256
+ this.#orderBys.push({ column: columnOrTerms, direction });
1257
+ return this;
1258
+ }
1259
+ limit(n) {
1260
+ this.#limit = n;
1261
+ return this;
1262
+ }
1263
+ offset(n) {
1264
+ this.#offset = n;
1265
+ return this;
1266
+ }
1267
+ /** The table, qualified with a schema when {@link withSchema} was used. */
1268
+ #qualifiedTable() {
1269
+ return this.#schema ? `${this.#schema}.${this.#table}` : this.#table;
1270
+ }
1271
+ /** `FOR UPDATE`(+`SKIP LOCKED`/`NOWAIT`) or null — the modifier needs a base lock. */
1272
+ #composedLockMode() {
1273
+ if (!this.#lockMode)
1274
+ return null;
1275
+ return this.#lockModifier
1276
+ ? `${this.#lockMode} ${this.#lockModifier}`
1277
+ : this.#lockMode;
1278
+ }
1279
+ /** LIMIT/OFFSET for a 1-based page (Lucid/Knex `forPage`). */
1280
+ forPage(page, perPage = 20) {
1281
+ this.#limit = perPage;
1282
+ this.#offset = (Math.max(1, page) - 1) * perPage;
1283
+ return this;
1284
+ }
1285
+ /** Build the SELECT spec JSON directly (full grammar: joins, locks, raw, etc.). */
1286
+ #selectSpec(select) {
1287
+ return {
1288
+ kind: "select",
1289
+ table: this.#qualifiedTable(),
1290
+ fromSubquery: this.#fromSubquery ?? null,
1291
+ select: select ?? (this.#selects.length > 0 ? this.#selects : ["*"]),
1292
+ wheres: this.#compiledWheres(),
1293
+ orderBy: this.#orderBys,
1294
+ groupBy: this.#groupBys,
1295
+ having: this.#havings,
1296
+ limit: this.#limit ?? null,
1297
+ offset: this.#offset ?? null,
1298
+ distinct: this.#distinctFlag,
1299
+ distinctOn: this.#distinctOn,
1300
+ ctes: this.#ctes,
1301
+ unions: this.#unions,
1302
+ selectSubqueries: [],
1303
+ selectRaw: this.#selectRaw,
1304
+ joins: this.#joins,
1305
+ lockMode: this.#composedLockMode(),
1306
+ };
1307
+ }
1308
+ /**
1309
+ * Native compiled SELECT `{ sql, params }` (Postgres `$N` placeholders) — the
1310
+ * form atlas executes against the driver and embeds inside parent queries.
1311
+ * Public `toSQL()` normalizes this to Knex `?` for Lucid parity.
1312
+ */
1313
+ #compiledNative() {
1314
+ const compiled = compileStatementNative(this.#selectSpec(), this.#dialect);
1315
+ const sql = this.#commentPrefix() + compiled.statements[0];
1316
+ if (this.#debug) {
1317
+ console.debug("[atlas:sql]", sql, compiled.params);
1318
+ }
1319
+ return { sql, params: compiled.params };
1320
+ }
1321
+ /**
1322
+ * The compiled SELECT WITHOUT executing (Lucid `toSQL`). `.sql` uses `?`
1323
+ * placeholders (Knex-normalized, like Lucid); `.toNative()` yields the native
1324
+ * form. Returns both `bindings` (Lucid's name) and `params` (atlas's) — same
1325
+ * array, so either name ports.
1326
+ */
1327
+ toSQL() {
1328
+ const { sql, params } = this.#compiledNative();
1329
+ return compiledStatement(sql, params);
1330
+ }
1331
+ /** Apply `cb` only on the given dialect(s) (Lucid `ifDialect`; Lucid names accepted). */
1332
+ ifDialect(dialect, cb) {
1333
+ const set = (Array.isArray(dialect) ? dialect : [dialect]).map(normalizeDialect);
1334
+ if (set.includes(this.#dialect))
1335
+ cb(this);
1336
+ return this;
1337
+ }
1338
+ /** Apply `cb` on every dialect EXCEPT the given one(s) (Lucid `unlessDialect`). */
1339
+ unlessDialect(dialect, cb) {
1340
+ const set = (Array.isArray(dialect) ? dialect : [dialect]).map(normalizeDialect);
1341
+ if (!set.includes(this.#dialect))
1342
+ cb(this);
1343
+ return this;
1344
+ }
1345
+ /** Run the SELECT and return every row. */
1346
+ async exec() {
1347
+ const { sql, params } = this.#compiledNative();
1348
+ return this.#raceTimeout(this.#exec.query(sql, params, this.#queryMeta("exec")));
1349
+ }
1350
+ /** Run the SELECT and return the first row (Lucid `first`), or `null`. */
1351
+ async first() {
1352
+ this.#limit = 1;
1353
+ const rows = await this.exec();
1354
+ return rows[0] ?? null;
1355
+ }
1356
+ /** {@link first} but throws when no row matches (Lucid `firstOrFail`). */
1357
+ async firstOrFail() {
1358
+ const row = await this.first();
1359
+ if (row === null)
1360
+ throw new Error("firstOrFail: no matching row");
1361
+ return row;
1362
+ }
1363
+ /** Return a single column's values across the result set (Lucid/Knex `pluck`). */
1364
+ async pluck(column) {
1365
+ const { sql, params } = this.#compiledNative();
1366
+ const rows = await this.#raceTimeout(this.#exec.query(sql, params, this.#queryMeta("pluck")));
1367
+ return rows.map((r) => r[column]);
1368
+ }
1369
+ /** SQL with bindings substituted as literals, for inspection (Lucid `toQuery`). */
1370
+ toQuery() {
1371
+ const { sql, params } = this.toSQL();
1372
+ return interpolateQuery(sql, params);
1373
+ }
1374
+ /** `{ sql, bindings }` — the compiled native query (Lucid `toNative`). */
1375
+ toNative() {
1376
+ const { sql, params } = this.#compiledNative();
1377
+ return { sql, bindings: params };
1378
+ }
1379
+ /** Run a scalar aggregate (COUNT/SUM/AVG/MIN/MAX) over the current WHERE. */
1380
+ async #aggregate(expr) {
1381
+ const compiled = compileStatementNative(this.#selectSpec([`${expr} AS aggregate`]), this.#dialect);
1382
+ const rows = await this.#raceTimeout(this.#exec.query(compiled.statements[0], compiled.params, this.#queryMeta("aggregate")));
1383
+ return Number(rows[0]?.aggregate ?? 0);
1384
+ }
1385
+ /**
1386
+ * Build a `FN(expr) AS alias` projection string from an `expr [as alias]` form.
1387
+ * `'* as total'` → `COUNT(*) AS total`; `'amount'` → `SUM(amount)`.
1388
+ */
1389
+ #aggProjection(fn, expr) {
1390
+ const m = expr.match(/^(.*?)\s+as\s+(.+)$/i);
1391
+ return m ? `${fn}(${m[1].trim()}) AS ${m[2].trim()}` : `${fn}(${expr})`;
1392
+ }
1393
+ count(aliasExpr) {
1394
+ if (aliasExpr === undefined)
1395
+ return this.#aggregate("COUNT(*)");
1396
+ this.#selects.push(this.#aggProjection("COUNT", aliasExpr));
1397
+ return this;
1398
+ }
1399
+ sum(expr) {
1400
+ return this.#aggMethod("SUM", expr);
1401
+ }
1402
+ avg(expr) {
1403
+ return this.#aggMethod("AVG", expr);
1404
+ }
1405
+ min(expr) {
1406
+ return this.#aggMethod("MIN", expr);
1407
+ }
1408
+ max(expr) {
1409
+ return this.#aggMethod("MAX", expr);
1410
+ }
1411
+ /** An aliased `expr` (`col as alias`) is a chainable projection; a bare column is a terminal scalar. */
1412
+ #aggMethod(fn, expr) {
1413
+ if (/\s+as\s+/i.test(expr)) {
1414
+ this.#selects.push(this.#aggProjection(fn, expr));
1415
+ return this;
1416
+ }
1417
+ return this.#aggregate(`${fn}(${expr})`);
1418
+ }
1419
+ /** COUNT(DISTINCT column) (Lucid/Knex `countDistinct`). */
1420
+ countDistinct(column) {
1421
+ return this.#aggregate(`COUNT(DISTINCT ${column})`);
1422
+ }
1423
+ /** SUM(DISTINCT column) (Lucid/Knex `sumDistinct`). */
1424
+ sumDistinct(column) {
1425
+ return this.#aggregate(`SUM(DISTINCT ${column})`);
1426
+ }
1427
+ /** AVG(DISTINCT column) (Lucid/Knex `avgDistinct`). */
1428
+ avgDistinct(column) {
1429
+ return this.#aggregate(`AVG(DISTINCT ${column})`);
1430
+ }
1431
+ /** WHERE clauses translated to the native compiler's JSON shapes. */
1432
+ #compiledWheres() {
1433
+ return this.#wheres.map((w) => {
1434
+ switch (w.kind) {
1435
+ case "raw":
1436
+ return {
1437
+ kind: "raw",
1438
+ sql: w.sql,
1439
+ bindings: w.bindings,
1440
+ type: w.boolean,
1441
+ };
1442
+ case "exists":
1443
+ return {
1444
+ kind: "exists",
1445
+ negated: w.negated,
1446
+ subquery: w.subquery,
1447
+ type: w.boolean,
1448
+ };
1449
+ case "inSub":
1450
+ return {
1451
+ kind: "inSub",
1452
+ column: w.column,
1453
+ negated: w.negated,
1454
+ subquery: w.subquery,
1455
+ type: w.boolean,
1456
+ };
1457
+ case "inTuple":
1458
+ return {
1459
+ kind: "inTuple",
1460
+ columns: w.columns,
1461
+ rows: w.rows,
1462
+ negated: w.negated,
1463
+ type: w.boolean,
1464
+ };
1465
+ case "json":
1466
+ return {
1467
+ kind: "json",
1468
+ jsonOp: w.jsonOp,
1469
+ column: w.column,
1470
+ negated: w.negated,
1471
+ path: w.path,
1472
+ operator: w.operator,
1473
+ value: w.value,
1474
+ type: w.boolean,
1475
+ };
1476
+ case "group":
1477
+ return {
1478
+ kind: "group",
1479
+ conditions: w.conditions,
1480
+ type: w.boolean,
1481
+ negated: w.negated ?? false,
1482
+ };
1483
+ default:
1484
+ return {
1485
+ column: w.column,
1486
+ operator: w.operator,
1487
+ value: w.value,
1488
+ type: w.boolean,
1489
+ };
1490
+ }
1491
+ });
1492
+ }
1493
+ /**
1494
+ * Columns to return from a subsequent insert/update/delete (Lucid `returning`).
1495
+ * Accepts spread names, an array, or `'*'` — `returning('id')`,
1496
+ * `returning(['id', 'created_at'])`, `returning('*')`.
1497
+ */
1498
+ returning(...columns) {
1499
+ for (const c of columns) {
1500
+ if (Array.isArray(c))
1501
+ this.#returningCols.push(...c);
1502
+ else
1503
+ this.#returningCols.push(c);
1504
+ }
1505
+ return this;
1506
+ }
1507
+ /** The `/* … *​/` prefix for the compiled SQL (Lucid `comment`), or empty. */
1508
+ #commentPrefix() {
1509
+ return this.#comments.length > 0
1510
+ ? `${this.#comments.map((c) => `/* ${c} */`).join(" ")} `
1511
+ : "";
1512
+ }
1513
+ /**
1514
+ * Compile a DML spec (adding RETURNING when set) with the comment prefix —
1515
+ * shared by the lazy builders' `.toSQL()` and their execution.
1516
+ */
1517
+ #compileDmlSpec(spec) {
1518
+ const withReturning = this.#returningCols.length > 0
1519
+ ? { ...spec, returning: this.#returningCols }
1520
+ : spec;
1521
+ const compiled = compileStatementNative(withReturning, this.#dialect);
1522
+ const sql = this.#commentPrefix() + compiled.statements[0];
1523
+ return { sql, bindings: compiled.params, params: compiled.params };
1524
+ }
1525
+ /** Run a DML spec: RETURNING rows when set, else execute; `interpret` shapes the result. */
1526
+ async #runDml(spec, interpret) {
1527
+ const { sql, params } = this.#compileDmlSpec(spec);
1528
+ const method = String(spec.kind ?? "dml");
1529
+ if (this.#returningCols.length > 0) {
1530
+ const rows = await this.#raceTimeout(this.#exec.query(sql, params, this.#queryMeta(method)));
1531
+ return interpret(null, rows);
1532
+ }
1533
+ const result = await this.#raceTimeout(this.#exec.execute(sql, params, this.#queryMeta(method)));
1534
+ return interpret(result, null);
1535
+ }
1536
+ /** An insert/upsert result: RETURNING rows, else `[insertId]` (MySQL/SQLite) or `[]`. */
1537
+ #interpretInsert = (result, rows) => {
1538
+ if (rows)
1539
+ return rows;
1540
+ if (this.#dialect === "mysql" || this.#dialect === "sqlite") {
1541
+ const id = lastInsertIdOf(result);
1542
+ if (id !== undefined)
1543
+ return [id];
1544
+ }
1545
+ return [];
1546
+ };
1547
+ /** An update/delete result: RETURNING rows, else the affected-row count. */
1548
+ #interpretWrite = (result, rows) => {
1549
+ return rows ?? rowsAffected(result);
1550
+ };
1551
+ /** The chainable-clause hooks the lazy {@link DmlBuilder} delegates back to. */
1552
+ #dmlHooks() {
1553
+ return {
1554
+ onConflict: (...c) => {
1555
+ this.onConflict(...c);
1556
+ },
1557
+ merge: (...a) => {
1558
+ this.merge(...a);
1559
+ },
1560
+ ignore: () => {
1561
+ this.ignore();
1562
+ },
1563
+ returning: (...c) => {
1564
+ this.returning(...c);
1565
+ },
1566
+ timeout: (ms, options) => {
1567
+ this.timeout(ms, options);
1568
+ },
1569
+ comment: (t) => {
1570
+ this.comment(t);
1571
+ },
1572
+ debug: (enabled) => {
1573
+ this.debug(enabled);
1574
+ },
1575
+ reporterData: (data) => {
1576
+ this.reporterData(data);
1577
+ },
1578
+ };
1579
+ }
1580
+ /**
1581
+ * Conflict target for an upsert (Lucid/Knex `onConflict`). Accepts spread
1582
+ * names, an array, or no argument (any unique constraint) —
1583
+ * `onConflict('email')`, `onConflict(['email', 'tenant_id'])`, `onConflict()`.
1584
+ */
1585
+ onConflict(...columns) {
1586
+ this.#onConflictCols = columns.flat();
1587
+ return this;
1588
+ }
1589
+ /**
1590
+ * On conflict, UPDATE columns (Lucid/Knex `merge`). No argument updates every
1591
+ * insert column; spread names or an array update only those; an object sets
1592
+ * custom values (scalars or `db.raw(...)` expressions) —
1593
+ * `merge()`, `merge(['a', 'b'])`, `merge({ login_count: db.raw('users.login_count + 1') })`.
1594
+ */
1595
+ merge(...args) {
1596
+ this.#mergeMode = "merge";
1597
+ const cols = [];
1598
+ const set = [];
1599
+ for (const a of args) {
1600
+ if (typeof a === "string") {
1601
+ cols.push(a);
1602
+ }
1603
+ else if (Array.isArray(a)) {
1604
+ cols.push(...a);
1605
+ }
1606
+ else {
1607
+ for (const [col, v] of Object.entries(a)) {
1608
+ if (v instanceof RawSql) {
1609
+ set.push({ column: col, raw: v.sql, rawParams: [...v.params] });
1610
+ }
1611
+ else {
1612
+ set.push({ column: col, value: v });
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ this.#mergeCols = cols;
1618
+ this.#mergeSet = set.length > 0 ? set : undefined;
1619
+ return this;
1620
+ }
1621
+ /** On conflict, do nothing (Lucid/Knex `onConflict(...).ignore()`). */
1622
+ ignore() {
1623
+ this.#mergeMode = "ignore";
1624
+ return this;
1625
+ }
1626
+ /** Build the insert or upsert spec from the current onConflict/merge/returning state. */
1627
+ #buildInsertOrUpsertSpec(rows) {
1628
+ if (this.#onConflictCols) {
1629
+ const conflictColumns = this.#onConflictCols;
1630
+ const allCols = rows[0]?.map(([c]) => c) ?? [];
1631
+ const updateColumns = this.#mergeMode === "ignore"
1632
+ ? []
1633
+ : this.#mergeCols.length > 0
1634
+ ? this.#mergeCols
1635
+ : allCols.filter((c) => !conflictColumns.includes(c));
1636
+ return {
1637
+ kind: "upsert",
1638
+ table: this.#qualifiedTable(),
1639
+ rows,
1640
+ conflictColumns,
1641
+ updateColumns,
1642
+ updateSet: this.#mergeSet ?? [],
1643
+ ctes: this.#ctes,
1644
+ };
1645
+ }
1646
+ return {
1647
+ kind: "insert",
1648
+ table: this.#qualifiedTable(),
1649
+ rows,
1650
+ ctes: this.#ctes,
1651
+ };
1652
+ }
1653
+ #buildUpdateSpec(set) {
1654
+ return {
1655
+ kind: "update",
1656
+ table: this.#qualifiedTable(),
1657
+ set,
1658
+ wheres: this.#compiledWheres(),
1659
+ ctes: this.#ctes,
1660
+ };
1661
+ }
1662
+ #buildDeleteSpec() {
1663
+ return {
1664
+ kind: "delete",
1665
+ table: this.#qualifiedTable(),
1666
+ wheres: this.#compiledWheres(),
1667
+ ctes: this.#ctes,
1668
+ };
1669
+ }
1670
+ /**
1671
+ * Insert one row (Lucid `db.table(t).insert(data)`). Lazy + chainable: the
1672
+ * statement runs on `await`/`.exec()`, so `insert(data).onConflict(...).merge()`,
1673
+ * `insert(data).returning(...)` and `insert(data).toSQL()` all work. Resolves to
1674
+ * the RETURNING rows, or `[insertId]` (MySQL/SQLite) / `[]` otherwise.
1675
+ */
1676
+ insert(data) {
1677
+ this.#assertWritable();
1678
+ const rows = [Object.entries(data)];
1679
+ return new DmlBuilder(() => rows[0].length === 0
1680
+ ? Promise.resolve([])
1681
+ : this.#runDml(this.#buildInsertOrUpsertSpec(rows), this.#interpretInsert), () => this.#compileDmlSpec(this.#buildInsertOrUpsertSpec(rows)), this.#dmlHooks());
1682
+ }
1683
+ /** Insert many rows in one statement (Lucid/Knex `multiInsert`). Lazy + chainable. */
1684
+ multiInsert(rows) {
1685
+ this.#assertWritable();
1686
+ // Lucid fills missing keys with NULL — take the union of every row's
1687
+ // columns, then project each row onto it so all rows share one column set.
1688
+ const cols = Array.from(new Set(rows.flatMap((r) => Object.keys(r))));
1689
+ const rowEntries = rows.map((r) => cols.map((c) => [c, c in r ? r[c] : null]));
1690
+ return new DmlBuilder(() => rows.length === 0
1691
+ ? Promise.resolve([])
1692
+ : this.#runDml(this.#buildInsertOrUpsertSpec(rowEntries), this.#interpretInsert), () => this.#compileDmlSpec(this.#buildInsertOrUpsertSpec(rowEntries)), this.#dmlHooks());
1693
+ }
1694
+ update(dataOrColumn, value) {
1695
+ this.#assertWritable();
1696
+ const data = typeof dataOrColumn === "string"
1697
+ ? { [dataOrColumn]: value }
1698
+ : dataOrColumn;
1699
+ const set = Object.entries(data).map(([col, v]) => v instanceof RawSql
1700
+ ? [col, { raw: v.sql, rawParams: [...v.params] }]
1701
+ : [col, v]);
1702
+ return new DmlBuilder(() => set.length === 0
1703
+ ? Promise.resolve(0)
1704
+ : this.#runDml(this.#buildUpdateSpec(set), this.#interpretWrite), () => this.#compileDmlSpec(this.#buildUpdateSpec(set)), this.#dmlHooks());
1705
+ }
1706
+ /** Delete rows matching the current WHERE (Lucid `delete`). Lazy + chainable. */
1707
+ delete() {
1708
+ this.#assertWritable();
1709
+ return new DmlBuilder(() => this.#runDml(this.#buildDeleteSpec(), this.#interpretWrite), () => this.#compileDmlSpec(this.#buildDeleteSpec()), this.#dmlHooks());
1710
+ }
1711
+ /** Alias of {@link delete} (Lucid/Knex `del`). */
1712
+ del() {
1713
+ return this.delete();
1714
+ }
1715
+ increment(colOrPatch, amount = 1) {
1716
+ return this.#runIncDec("increment", colOrPatch, amount);
1717
+ }
1718
+ decrement(colOrPatch, amount = 1) {
1719
+ return this.#runIncDec("decrement", colOrPatch, amount);
1720
+ }
1721
+ async #runIncDec(op, colOrPatch, amount) {
1722
+ this.#assertWritable();
1723
+ const patch = typeof colOrPatch === "string" ? { [colOrPatch]: amount } : colOrPatch;
1724
+ const set = Object.entries(patch).map(([col, value]) => [col, { op, value }]);
1725
+ if (set.length === 0)
1726
+ return 0;
1727
+ const compiled = compileStatementNative({
1728
+ kind: "update",
1729
+ table: this.#qualifiedTable(),
1730
+ set,
1731
+ wheres: this.#compiledWheres(),
1732
+ ctes: this.#ctes,
1733
+ }, this.#dialect);
1734
+ const result = await this.#raceTimeout(this.#exec.execute(this.#commentPrefix() + compiled.statements[0], compiled.params, this.#queryMeta(op)));
1735
+ return rowsAffected(result);
1736
+ }
1737
+ /**
1738
+ * Offset paginate the current query (Lucid/Knex `paginate`). Runs a COUNT over
1739
+ * the WHERE, then the page slice, and returns a {@link Paginator}.
1740
+ */
1741
+ async paginate(page, perPage = 20) {
1742
+ const p = Math.max(1, Math.floor(page));
1743
+ const pp = Math.max(1, Math.floor(perPage));
1744
+ // COUNT over the WHERE only — ignore limit/offset/orderBy (they don't apply
1745
+ // to a total). Built directly so paginate() doesn't disturb builder state.
1746
+ const countCompiled = compileStatementNative({
1747
+ kind: "select",
1748
+ table: this.#qualifiedTable(),
1749
+ select: ["COUNT(*) AS aggregate"],
1750
+ wheres: this.#compiledWheres(),
1751
+ orderBy: [],
1752
+ groupBy: this.#groupBys,
1753
+ having: this.#havings,
1754
+ limit: null,
1755
+ offset: null,
1756
+ distinct: this.#distinctFlag,
1757
+ distinctOn: this.#distinctOn,
1758
+ ctes: this.#ctes,
1759
+ unions: this.#unions,
1760
+ selectSubqueries: [],
1761
+ joins: this.#joins,
1762
+ lockMode: null,
1763
+ }, this.#dialect);
1764
+ const countRows = await this.#exec.query(countCompiled.statements[0], countCompiled.params, this.#queryMeta("paginate"));
1765
+ const total = Number(countRows[0]?.aggregate ?? 0);
1766
+ this.#limit = pp;
1767
+ this.#offset = (p - 1) * pp;
1768
+ const items = await this.exec();
1769
+ return new Paginator(items, { total, perPage: pp, currentPage: p });
1770
+ }
1771
+ /** Thenable, so `await db.from('users').where(...)` resolves to the rows. */
1772
+ // biome-ignore lint/suspicious/noThenProperty: Adonis Lucid query builders are awaitable by design — `await db.from(t).where(...)` must resolve to the rows; the thenable is the intended public API.
1773
+ then(onfulfilled, onrejected) {
1774
+ return this.exec().then(onfulfilled, onrejected);
1775
+ }
1776
+ }
1777
+ _a = DatabaseQueryBuilder;
1778
+ /** Read an affected-row count from an execute() result, whatever its shape. */
1779
+ function rowsAffected(result) {
1780
+ if (result !== null &&
1781
+ typeof result === "object" &&
1782
+ "rowsAffected" in result &&
1783
+ typeof result.rowsAffected === "number") {
1784
+ return result.rowsAffected;
1785
+ }
1786
+ return 0;
1787
+ }
1788
+ /** Read the auto-increment id off an execute outcome (MySQL/SQLite). */
1789
+ function lastInsertIdOf(result) {
1790
+ if (result !== null &&
1791
+ typeof result === "object" &&
1792
+ "lastInsertId" in result &&
1793
+ typeof result.lastInsertId === "number") {
1794
+ return result.lastInsertId;
1795
+ }
1796
+ return undefined;
1797
+ }
1798
+ //# sourceMappingURL=DatabaseQueryBuilder.js.map