arkormx 2.6.0 → 2.7.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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_relationship = require('./relationship-D9u7BlWh.cjs');
2
+ const require_relationship = require('./relationship-B8FaJYIx.cjs');
3
3
  let pg = require("pg");
4
4
  let node_path = require("node:path");
5
5
  let module$1 = require("module");
@@ -64,7 +64,8 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
64
64
  rawSelect: true,
65
65
  rawWhere: true,
66
66
  distinct: true,
67
- groupBy: true
67
+ groupBy: true,
68
+ joins: true
68
69
  };
69
70
  }
70
71
  resolveConfiguredDatabaseName(connectionString) {
@@ -89,6 +90,85 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
89
90
  quoteIdentifier(value) {
90
91
  return `"${value.replace(/"/g, "\"\"")}"`;
91
92
  }
93
+ /**
94
+ * Wraps bare camelCase identifiers in a fragment of raw SQL with double quotes
95
+ * so PostgreSQL preserves their casing instead of folding them to lower case.
96
+ *
97
+ * Identifiers that are already quoted, string literals, dollar-quoted bodies,
98
+ * comments and function names (a camelCase token immediately followed by `(`)
99
+ * are left untouched. Lower-case identifiers are also left alone since
100
+ * PostgreSQL folds them to the same value with or without quotes.
101
+ *
102
+ * @param sql The raw SQL fragment to normalize.
103
+ * @returns
104
+ */
105
+ quoteCamelCaseIdentifiers(sql) {
106
+ let result = "";
107
+ let index = 0;
108
+ const isIdentifierStart = (char) => /[A-Za-z_]/.test(char);
109
+ const isIdentifierPart = (char) => /[A-Za-z0-9_$]/.test(char);
110
+ const isMixedCase = (token) => /[A-Z]/.test(token) && /[a-z]/.test(token);
111
+ while (index < sql.length) {
112
+ const char = sql[index];
113
+ if (char === "'") {
114
+ const start = index;
115
+ index += 1;
116
+ while (index < sql.length) {
117
+ if (sql[index] === "'" && sql[index + 1] === "'") {
118
+ index += 2;
119
+ continue;
120
+ }
121
+ if (sql[index] === "'") {
122
+ index += 1;
123
+ break;
124
+ }
125
+ index += 1;
126
+ }
127
+ result += sql.slice(start, index);
128
+ continue;
129
+ }
130
+ if (char === "\"") {
131
+ const start = index;
132
+ index += 1;
133
+ while (index < sql.length) {
134
+ if (sql[index] === "\"" && sql[index + 1] === "\"") {
135
+ index += 2;
136
+ continue;
137
+ }
138
+ if (sql[index] === "\"") {
139
+ index += 1;
140
+ break;
141
+ }
142
+ index += 1;
143
+ }
144
+ result += sql.slice(start, index);
145
+ continue;
146
+ }
147
+ if (char === "$") {
148
+ const tagMatch = /^\$[A-Za-z0-9_]*\$/.exec(sql.slice(index));
149
+ if (tagMatch) {
150
+ const tag = tagMatch[0];
151
+ const closeIndex = sql.indexOf(tag, index + tag.length);
152
+ const end = closeIndex === -1 ? sql.length : closeIndex + tag.length;
153
+ result += sql.slice(index, end);
154
+ index = end;
155
+ continue;
156
+ }
157
+ }
158
+ if (isIdentifierStart(char)) {
159
+ const start = index;
160
+ while (index < sql.length && isIdentifierPart(sql[index])) index += 1;
161
+ const token = sql.slice(start, index);
162
+ const isFunctionCall = sql[index] === "(";
163
+ if (isMixedCase(token) && !isFunctionCall) result += this.quoteIdentifier(token);
164
+ else result += token;
165
+ continue;
166
+ }
167
+ result += char;
168
+ index += 1;
169
+ }
170
+ return result;
171
+ }
92
172
  quoteLiteral(value) {
93
173
  if (value == null) return "null";
94
174
  if (typeof value === "number" || typeof value === "bigint") return String(value);
@@ -108,10 +188,102 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
108
188
  async executeRawStatement(statement, executor = this.db) {
109
189
  await kysely.sql.raw(statement).execute(executor);
110
190
  }
191
+ /**
192
+ * Splits a SQL script into individual top-level statements.
193
+ *
194
+ * The PostgreSQL wire protocol used by Kysely rejects scripts that contain
195
+ * more than one command, so multi-statement raw SQL (for example a migration
196
+ * that mixes `do $$ ... $$` blocks with `alter table` statements) must be
197
+ * executed one statement at a time. Semicolons inside single-quoted strings,
198
+ * double-quoted identifiers, dollar-quoted bodies and comments are ignored.
199
+ *
200
+ * @param sql The raw SQL script to split.
201
+ * @returns
202
+ */
203
+ splitSqlStatements(sql) {
204
+ const statements = [];
205
+ let current = "";
206
+ let index = 0;
207
+ while (index < sql.length) {
208
+ const char = sql[index];
209
+ const next = sql[index + 1];
210
+ if (char === "-" && next === "-") {
211
+ const end = sql.indexOf("\n", index);
212
+ const stop = end === -1 ? sql.length : end;
213
+ current += sql.slice(index, stop);
214
+ index = stop;
215
+ continue;
216
+ }
217
+ if (char === "/" && next === "*") {
218
+ const end = sql.indexOf("*/", index + 2);
219
+ const stop = end === -1 ? sql.length : end + 2;
220
+ current += sql.slice(index, stop);
221
+ index = stop;
222
+ continue;
223
+ }
224
+ if (char === "'") {
225
+ const start = index;
226
+ index += 1;
227
+ while (index < sql.length) {
228
+ if (sql[index] === "'" && sql[index + 1] === "'") {
229
+ index += 2;
230
+ continue;
231
+ }
232
+ if (sql[index] === "'") {
233
+ index += 1;
234
+ break;
235
+ }
236
+ index += 1;
237
+ }
238
+ current += sql.slice(start, index);
239
+ continue;
240
+ }
241
+ if (char === "\"") {
242
+ const start = index;
243
+ index += 1;
244
+ while (index < sql.length) {
245
+ if (sql[index] === "\"" && sql[index + 1] === "\"") {
246
+ index += 2;
247
+ continue;
248
+ }
249
+ if (sql[index] === "\"") {
250
+ index += 1;
251
+ break;
252
+ }
253
+ index += 1;
254
+ }
255
+ current += sql.slice(start, index);
256
+ continue;
257
+ }
258
+ if (char === "$") {
259
+ const tagMatch = /^\$[A-Za-z0-9_]*\$/.exec(sql.slice(index));
260
+ if (tagMatch) {
261
+ const tag = tagMatch[0];
262
+ const closeIndex = sql.indexOf(tag, index + tag.length);
263
+ const end = closeIndex === -1 ? sql.length : closeIndex + tag.length;
264
+ current += sql.slice(index, end);
265
+ index = end;
266
+ continue;
267
+ }
268
+ }
269
+ if (char === ";") {
270
+ if (current.trim().length > 0) statements.push(current.trim());
271
+ current = "";
272
+ index += 1;
273
+ continue;
274
+ }
275
+ current += char;
276
+ index += 1;
277
+ }
278
+ if (current.trim().length > 0) statements.push(current.trim());
279
+ return statements;
280
+ }
111
281
  async rawQuery(spec) {
112
282
  const statement = this.interpolateRawSql(spec.sql, spec.bindings);
283
+ const statements = this.splitSqlStatements(statement);
284
+ if (statements.length > 1) return await this.runMultiStatementRawQuery(statements, statement);
113
285
  try {
114
- return (await kysely.sql.raw(statement).execute(this.db)).rows ?? [];
286
+ return (await kysely.sql.raw(statements[0] ?? statement).execute(this.db)).rows ?? [];
115
287
  } catch (error) {
116
288
  throw new QueryExecutionException("Raw query execution failed for the Kysely adapter.", {
117
289
  code: "QUERY_EXECUTION_FAILED",
@@ -123,6 +295,45 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
123
295
  });
124
296
  }
125
297
  }
298
+ /**
299
+ * Executes a multi-statement raw SQL script one statement at a time.
300
+ *
301
+ * Each statement is sent individually so the PostgreSQL extended protocol
302
+ * accepts it, and the rows of the last statement that returns any are used as
303
+ * the result. The script is wrapped in a transaction when the adapter is not
304
+ * already operating inside one so partial failures do not leave the database
305
+ * in a half-applied state.
306
+ *
307
+ * @param statements The individual statements to execute in order.
308
+ * @param fullScript The original (joined) script, used for error context.
309
+ * @returns
310
+ */
311
+ async runMultiStatementRawQuery(statements, fullScript) {
312
+ const execute = async (executor) => {
313
+ let rows = [];
314
+ for (const statement of statements) {
315
+ const result = await kysely.sql.raw(statement).execute(executor);
316
+ if (result.rows && result.rows.length > 0) rows = result.rows;
317
+ }
318
+ return rows;
319
+ };
320
+ try {
321
+ if (!this.db.isTransaction) return await this.db.transaction().execute((transaction) => execute(transaction));
322
+ return await execute(this.db);
323
+ } catch (error) {
324
+ throw new QueryExecutionException("Raw query execution failed for the Kysely adapter.", {
325
+ code: "QUERY_EXECUTION_FAILED",
326
+ operation: "adapter.rawQuery",
327
+ delegate: "raw",
328
+ inspection: this.tryInspectRawQuery(fullScript),
329
+ meta: {
330
+ sql: fullScript,
331
+ statements
332
+ },
333
+ cause: error
334
+ });
335
+ }
336
+ }
126
337
  tryInspectRawQuery(statement) {
127
338
  return {
128
339
  adapter: "kysely",
@@ -404,6 +615,70 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
404
615
  if (!groupBy || groupBy.length === 0) return kysely.sql``;
405
616
  return kysely.sql` group by ${kysely.sql.join(groupBy.map((column) => kysely.sql.ref(this.mapColumn(target, column))), kysely.sql`, `)}`;
406
617
  }
618
+ buildJoinClause(target, joins) {
619
+ if (!joins || joins.length === 0) return kysely.sql``;
620
+ return kysely.sql` ${kysely.sql.join(joins.map((join) => this.buildSingleJoin(target, join)), kysely.sql` `)}`;
621
+ }
622
+ joinKeyword(join) {
623
+ const base = {
624
+ inner: "inner join",
625
+ left: "left join",
626
+ right: "right join",
627
+ full: "full join",
628
+ cross: "cross join"
629
+ }[join.type];
630
+ return join.lateral ? `${base} lateral` : base;
631
+ }
632
+ buildJoinSource(join) {
633
+ if (join.subquery) {
634
+ const subquery = this.buildSelectStatement(join.subquery);
635
+ return join.alias ? kysely.sql`(${subquery}) as ${kysely.sql.id(join.alias)}` : kysely.sql`(${subquery})`;
636
+ }
637
+ if (join.subquerySql) return join.alias ? kysely.sql`(${kysely.sql.raw(join.subquerySql)}) as ${kysely.sql.id(join.alias)}` : kysely.sql.raw(`(${join.subquerySql})`);
638
+ const table = this.resolveMappedTable(join.table ?? "");
639
+ return join.alias ? kysely.sql`${kysely.sql.table(table)} as ${kysely.sql.id(join.alias)}` : kysely.sql.table(table);
640
+ }
641
+ buildSingleJoin(target, join) {
642
+ const keyword = kysely.sql.raw(this.joinKeyword(join));
643
+ const source = this.buildJoinSource(join);
644
+ if (join.type === "cross") return kysely.sql`${keyword} ${source}`;
645
+ if (join.constraints.length === 0) return join.lateral ? kysely.sql`${keyword} ${source} on true` : kysely.sql`${keyword} ${source}`;
646
+ return kysely.sql`${keyword} ${source} on ${this.buildJoinConstraints(target, join.constraints)}`;
647
+ }
648
+ buildJoinConstraints(target, constraints) {
649
+ const parts = [];
650
+ constraints.forEach((constraint, index) => {
651
+ if (index > 0) parts.push(kysely.sql.raw(` ${constraint.boolean} `));
652
+ parts.push(this.buildJoinConstraint(target, constraint));
653
+ });
654
+ return kysely.sql`${kysely.sql.join(parts, kysely.sql``)}`;
655
+ }
656
+ buildJoinConstraint(target, constraint) {
657
+ if (constraint.type === "column") return kysely.sql`${kysely.sql.ref(constraint.first)} ${kysely.sql.raw(constraint.operator)} ${kysely.sql.ref(constraint.second)}`;
658
+ if (constraint.type === "null") return constraint.not ? kysely.sql`${kysely.sql.ref(constraint.column)} is not null` : kysely.sql`${kysely.sql.ref(constraint.column)} is null`;
659
+ if (constraint.type === "raw") return this.buildRawWhereCondition({
660
+ type: "raw",
661
+ sql: constraint.sql,
662
+ bindings: constraint.bindings
663
+ });
664
+ if (constraint.type === "nested") return kysely.sql`(${this.buildJoinConstraints(target, constraint.constraints)})`;
665
+ const column = kysely.sql.ref(constraint.column);
666
+ const operator = constraint.operator;
667
+ if (operator === "is-null") return kysely.sql`${column} is null`;
668
+ if (operator === "is-not-null") return kysely.sql`${column} is not null`;
669
+ if (operator === "in") {
670
+ const values = this.buildConditionValueList(constraint.value);
671
+ return values.length === 0 ? kysely.sql`1 = 0` : kysely.sql`${column} in (${kysely.sql.join(values)})`;
672
+ }
673
+ if (operator === "not-in") {
674
+ const values = this.buildConditionValueList(constraint.value);
675
+ return values.length === 0 ? kysely.sql`1 = 1` : kysely.sql`${column} not in (${kysely.sql.join(values)})`;
676
+ }
677
+ if (operator === "contains") return kysely.sql`${column} like ${`%${String(constraint.value ?? "")}%`}`;
678
+ if (operator === "starts-with") return kysely.sql`${column} like ${`${String(constraint.value ?? "")}%`}`;
679
+ if (operator === "ends-with") return kysely.sql`${column} like ${`%${String(constraint.value ?? "")}`}`;
680
+ return kysely.sql`${column} ${kysely.sql.raw(operator)} ${constraint.value}`;
681
+ }
407
682
  buildConditionValueList(value) {
408
683
  if (Array.isArray(value)) return value;
409
684
  return typeof value === "undefined" ? [] : [value];
@@ -433,7 +708,7 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
433
708
  if (segments.length !== bindings.length + 1) throw new require_relationship.ArkormException("Raw where bindings do not match the number of placeholders.");
434
709
  const parts = [];
435
710
  segments.forEach((segment, index) => {
436
- if (segment.length > 0) parts.push(kysely.sql.raw(segment));
711
+ if (segment.length > 0) parts.push(kysely.sql.raw(this.quoteCamelCaseIdentifiers(segment)));
437
712
  if (index < bindings.length) parts.push(kysely.sql`${bindings[index]}`);
438
713
  });
439
714
  if (parts.length === 0) return kysely.sql`1 = 1`;
@@ -697,6 +972,7 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
697
972
  select ${spec.distinct ? kysely.sql`distinct ` : kysely.sql``}${this.buildSelectList(spec.target, spec.columns)}
698
973
  ${this.buildRelationAggregateSelectList(spec.target, spec.relationAggregates)}
699
974
  from ${kysely.sql.table(this.resolveTable(spec.target))}
975
+ ${this.buildJoinClause(spec.target, spec.joins)}
700
976
  ${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
701
977
  ${this.buildGroupBy(spec.target, spec.groupBy)}
702
978
  ${this.buildOrderBy(spec.target, spec.orderBy)}
@@ -707,6 +983,7 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
707
983
  return kysely.sql`
708
984
  select count(*)::int as count
709
985
  from ${kysely.sql.table(this.resolveTable(spec.target))}
986
+ ${this.buildJoinClause(spec.target, spec.joins)}
710
987
  ${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
711
988
  `;
712
989
  }
@@ -715,6 +992,7 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
715
992
  select exists(
716
993
  select 1
717
994
  from ${kysely.sql.table(this.resolveTable(spec.target))}
995
+ ${this.buildJoinClause(spec.target, spec.joins)}
718
996
  ${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
719
997
  ${this.buildGroupBy(spec.target, spec.groupBy)}
720
998
  limit 1
@@ -1446,6 +1724,10 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
1446
1724
  operation: "adapter.select",
1447
1725
  meta: { feature: "groupBy" }
1448
1726
  });
1727
+ if (spec.joins?.length) throw new require_relationship.UnsupportedAdapterFeatureException("Join clauses are not supported by the Prisma compatibility adapter; use a SQL-backed adapter or DB.raw().", {
1728
+ operation: "adapter.select",
1729
+ meta: { feature: "joins" }
1730
+ });
1449
1731
  return {
1450
1732
  include: this.toQueryInclude(spec.relationLoads),
1451
1733
  where: this.toQueryWhere(spec.where),
@@ -3800,6 +4082,204 @@ const resolveRuntimeCompatibilityQuerySchemaOrThrow = (key, candidates, modelNam
3800
4082
  return resolved;
3801
4083
  };
3802
4084
 
4085
+ //#endregion
4086
+ //#region src/JoinClause.ts
4087
+ /**
4088
+ * A fluent builder for the `on`/`where` constraints of a join clause.
4089
+ *
4090
+ * Instances are handed to the closure form of the query builder join helpers
4091
+ * (for example `query.join('posts', join => join.on(...).where(...))`) and
4092
+ * mirror Laravel's `JoinClause` surface. Column identifiers are treated as raw
4093
+ * database identifiers (qualify them as `table.column` when needed).
4094
+ *
4095
+ * @author Legacy (3m1n3nc3)
4096
+ */
4097
+ var JoinClause = class JoinClause {
4098
+ constructor() {
4099
+ this.constraints = [];
4100
+ }
4101
+ /**
4102
+ * Adds a column-to-column `on` constraint, joined with `and`.
4103
+ *
4104
+ * Accepts either a closure (for a nested group) or a column comparison in
4105
+ * the `(first, second)` or `(first, operator, second)` form.
4106
+ *
4107
+ * @param first The left-hand column or a nested closure.
4108
+ * @param operator The comparison operator (defaults to `=`).
4109
+ * @param second The right-hand column.
4110
+ * @returns
4111
+ */
4112
+ on(first, operator, second) {
4113
+ return this.addOn("and", first, operator, second);
4114
+ }
4115
+ /**
4116
+ * Adds a column-to-column `on` constraint, joined with `or`.
4117
+ *
4118
+ * @param first The left-hand column or a nested closure.
4119
+ * @param operator The comparison operator (defaults to `=`).
4120
+ * @param second The right-hand column.
4121
+ * @returns
4122
+ */
4123
+ orOn(first, operator, second) {
4124
+ return this.addOn("or", first, operator, second);
4125
+ }
4126
+ /**
4127
+ * Adds a column-to-value constraint, joined with `and`.
4128
+ *
4129
+ * @param column The column being compared.
4130
+ * @param operator The comparison operator or the value when omitted.
4131
+ * @param value The value to compare against.
4132
+ * @returns
4133
+ */
4134
+ where(column, operator, value) {
4135
+ return this.addWhere("and", column, operator, value);
4136
+ }
4137
+ /**
4138
+ * Adds a column-to-value constraint, joined with `or`.
4139
+ *
4140
+ * @param column The column being compared.
4141
+ * @param operator The comparison operator or the value when omitted.
4142
+ * @param value The value to compare against.
4143
+ * @returns
4144
+ */
4145
+ orWhere(column, operator, value) {
4146
+ return this.addWhere("or", column, operator, value);
4147
+ }
4148
+ /**
4149
+ * Adds an `is null` constraint joined with `and`.
4150
+ *
4151
+ * @param column The column to test for null.
4152
+ * @returns
4153
+ */
4154
+ whereNull(column) {
4155
+ this.constraints.push({
4156
+ type: "null",
4157
+ boolean: "and",
4158
+ column,
4159
+ not: false
4160
+ });
4161
+ return this;
4162
+ }
4163
+ /**
4164
+ * Adds an `is null` constraint joined with `or`.
4165
+ *
4166
+ * @param column The column to test for null.
4167
+ * @returns
4168
+ */
4169
+ orWhereNull(column) {
4170
+ this.constraints.push({
4171
+ type: "null",
4172
+ boolean: "or",
4173
+ column,
4174
+ not: false
4175
+ });
4176
+ return this;
4177
+ }
4178
+ /**
4179
+ * Adds an `is not null` constraint joined with `and`.
4180
+ *
4181
+ * @param column The column to test for non-null.
4182
+ * @returns
4183
+ */
4184
+ whereNotNull(column) {
4185
+ this.constraints.push({
4186
+ type: "null",
4187
+ boolean: "and",
4188
+ column,
4189
+ not: true
4190
+ });
4191
+ return this;
4192
+ }
4193
+ /**
4194
+ * Adds an `is not null` constraint joined with `or`.
4195
+ *
4196
+ * @param column The column to test for non-null.
4197
+ * @returns
4198
+ */
4199
+ orWhereNotNull(column) {
4200
+ this.constraints.push({
4201
+ type: "null",
4202
+ boolean: "or",
4203
+ column,
4204
+ not: true
4205
+ });
4206
+ return this;
4207
+ }
4208
+ /**
4209
+ * Adds a raw constraint joined with `and`.
4210
+ *
4211
+ * @param sql The raw SQL fragment (with `?` placeholders for bindings).
4212
+ * @param bindings The values bound to the placeholders.
4213
+ * @returns
4214
+ */
4215
+ onRaw(sql, bindings = []) {
4216
+ this.constraints.push({
4217
+ type: "raw",
4218
+ boolean: "and",
4219
+ sql,
4220
+ bindings
4221
+ });
4222
+ return this;
4223
+ }
4224
+ /**
4225
+ * Adds a raw constraint joined with `or`.
4226
+ *
4227
+ * @param sql The raw SQL fragment (with `?` placeholders for bindings).
4228
+ * @param bindings The values bound to the placeholders.
4229
+ * @returns
4230
+ */
4231
+ orOnRaw(sql, bindings = []) {
4232
+ this.constraints.push({
4233
+ type: "raw",
4234
+ boolean: "or",
4235
+ sql,
4236
+ bindings
4237
+ });
4238
+ return this;
4239
+ }
4240
+ /**
4241
+ * Returns the accumulated constraints for this join clause.
4242
+ *
4243
+ * @returns
4244
+ */
4245
+ getConstraints() {
4246
+ return this.constraints;
4247
+ }
4248
+ addOn(boolean, first, operator, second) {
4249
+ if (typeof first === "function") {
4250
+ const nested = new JoinClause();
4251
+ first(nested);
4252
+ this.constraints.push({
4253
+ type: "nested",
4254
+ boolean,
4255
+ constraints: nested.getConstraints()
4256
+ });
4257
+ return this;
4258
+ }
4259
+ const [resolvedOperator, resolvedSecond] = second === void 0 ? ["=", operator] : [operator, second];
4260
+ if (typeof resolvedSecond !== "string") throw new Error("A join \"on\" constraint requires a second column.");
4261
+ this.constraints.push({
4262
+ type: "column",
4263
+ boolean,
4264
+ first,
4265
+ operator: resolvedOperator ?? "=",
4266
+ second: resolvedSecond
4267
+ });
4268
+ return this;
4269
+ }
4270
+ addWhere(boolean, column, operator, value) {
4271
+ const [resolvedOperator, resolvedValue] = value === void 0 ? ["=", operator] : [operator, value];
4272
+ this.constraints.push({
4273
+ type: "value",
4274
+ boolean,
4275
+ column,
4276
+ operator: resolvedOperator ?? "=",
4277
+ value: resolvedValue
4278
+ });
4279
+ return this;
4280
+ }
4281
+ };
4282
+
3803
4283
  //#endregion
3804
4284
  //#region src/Exceptions/ModelNotFoundException.ts
3805
4285
  /**
@@ -4771,7 +5251,248 @@ var QueryBuilder = class QueryBuilder {
4771
5251
  return this;
4772
5252
  }
4773
5253
  /**
4774
- * Adds a skip clause to the query for pagination.
5254
+ * Adds a join clause to the query.
5255
+ *
5256
+ * The `first`/`second` arguments are treated as raw database identifiers, so
5257
+ * qualify them as `table.column` when needed. Pass a closure as `first` to
5258
+ * build a compound `on` condition through a {@link JoinClause}.
5259
+ *
5260
+ * @param table The table (or aliased table) to join.
5261
+ * @param first The left-hand column or a closure receiving a JoinClause.
5262
+ * @param operator The comparison operator (defaults to `=`).
5263
+ * @param second The right-hand column.
5264
+ * @param type The join type (defaults to `inner`).
5265
+ * @returns
5266
+ */
5267
+ join(table, first, operator, second, type = "inner") {
5268
+ return this.addJoin(type, table, first, operator, second);
5269
+ }
5270
+ /**
5271
+ * Adds an inner join clause to the query.
5272
+ *
5273
+ * @param table The table (or aliased table) to join.
5274
+ * @param first The left-hand column or a closure receiving a JoinClause.
5275
+ * @param operator The comparison operator (defaults to `=`).
5276
+ * @param second The right-hand column.
5277
+ * @returns
5278
+ */
5279
+ innerJoin(table, first, operator, second) {
5280
+ return this.addJoin("inner", table, first, operator, second);
5281
+ }
5282
+ /**
5283
+ * Adds a left join clause to the query.
5284
+ *
5285
+ * @param table The table (or aliased table) to join.
5286
+ * @param first The left-hand column or a closure receiving a JoinClause.
5287
+ * @param operator The comparison operator (defaults to `=`).
5288
+ * @param second The right-hand column.
5289
+ * @returns
5290
+ */
5291
+ leftJoin(table, first, operator, second) {
5292
+ return this.addJoin("left", table, first, operator, second);
5293
+ }
5294
+ /**
5295
+ * Adds a right join clause to the query.
5296
+ *
5297
+ * @param table The table (or aliased table) to join.
5298
+ * @param first The left-hand column or a closure receiving a JoinClause.
5299
+ * @param operator The comparison operator (defaults to `=`).
5300
+ * @param second The right-hand column.
5301
+ * @returns
5302
+ */
5303
+ rightJoin(table, first, operator, second) {
5304
+ return this.addJoin("right", table, first, operator, second);
5305
+ }
5306
+ /**
5307
+ * Adds a cross join clause to the query.
5308
+ *
5309
+ * When a `first` column (or closure) is supplied the cross join is promoted
5310
+ * to an inner join with the given constraints, mirroring Laravel's behaviour.
5311
+ *
5312
+ * @param table The table (or aliased table) to join.
5313
+ * @param first Optional column or closure to constrain the join.
5314
+ * @returns
5315
+ */
5316
+ crossJoin(table, first) {
5317
+ if (first === void 0) return this.addJoin("cross", table);
5318
+ return this.addJoin("inner", table, first);
5319
+ }
5320
+ /**
5321
+ * Adds a join clause that compares a column to a value.
5322
+ *
5323
+ * @param table The table (or aliased table) to join.
5324
+ * @param first The column being compared.
5325
+ * @param operator The comparison operator.
5326
+ * @param value The value to compare against.
5327
+ * @param type The join type (defaults to `inner`).
5328
+ * @returns
5329
+ */
5330
+ joinWhere(table, first, operator, value, type = "inner") {
5331
+ return this.addJoinWhere(type, table, first, operator, value);
5332
+ }
5333
+ /**
5334
+ * Adds a left join clause that compares a column to a value.
5335
+ *
5336
+ * @param table The table (or aliased table) to join.
5337
+ * @param first The column being compared.
5338
+ * @param operator The comparison operator.
5339
+ * @param value The value to compare against.
5340
+ * @returns
5341
+ */
5342
+ leftJoinWhere(table, first, operator, value) {
5343
+ return this.addJoinWhere("left", table, first, operator, value);
5344
+ }
5345
+ /**
5346
+ * Adds a right join clause that compares a column to a value.
5347
+ *
5348
+ * @param table The table (or aliased table) to join.
5349
+ * @param first The column being compared.
5350
+ * @param operator The comparison operator.
5351
+ * @param value The value to compare against.
5352
+ * @returns
5353
+ */
5354
+ rightJoinWhere(table, first, operator, value) {
5355
+ return this.addJoinWhere("right", table, first, operator, value);
5356
+ }
5357
+ /**
5358
+ * Adds a subquery join clause to the query.
5359
+ *
5360
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5361
+ * @param alias The alias assigned to the subquery.
5362
+ * @param first The left-hand column or a closure receiving a JoinClause.
5363
+ * @param operator The comparison operator (defaults to `=`).
5364
+ * @param second The right-hand column.
5365
+ * @param type The join type (defaults to `inner`).
5366
+ * @returns
5367
+ */
5368
+ joinSub(query, alias, first, operator, second, type = "inner") {
5369
+ return this.addJoinSub(type, query, alias, first, operator, second);
5370
+ }
5371
+ /**
5372
+ * Adds a subquery left join clause to the query.
5373
+ *
5374
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5375
+ * @param alias The alias assigned to the subquery.
5376
+ * @param first The left-hand column or a closure receiving a JoinClause.
5377
+ * @param operator The comparison operator (defaults to `=`).
5378
+ * @param second The right-hand column.
5379
+ * @returns
5380
+ */
5381
+ leftJoinSub(query, alias, first, operator, second) {
5382
+ return this.addJoinSub("left", query, alias, first, operator, second);
5383
+ }
5384
+ /**
5385
+ * Adds a subquery right join clause to the query.
5386
+ *
5387
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5388
+ * @param alias The alias assigned to the subquery.
5389
+ * @param first The left-hand column or a closure receiving a JoinClause.
5390
+ * @param operator The comparison operator (defaults to `=`).
5391
+ * @param second The right-hand column.
5392
+ * @returns
5393
+ */
5394
+ rightJoinSub(query, alias, first, operator, second) {
5395
+ return this.addJoinSub("right", query, alias, first, operator, second);
5396
+ }
5397
+ /**
5398
+ * Adds a cross subquery join clause to the query.
5399
+ *
5400
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5401
+ * @param alias The alias assigned to the subquery.
5402
+ * @returns
5403
+ */
5404
+ crossJoinSub(query, alias) {
5405
+ return this.addJoinSub("cross", query, alias);
5406
+ }
5407
+ /**
5408
+ * Adds a lateral join clause to the query.
5409
+ *
5410
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5411
+ * @param alias The alias assigned to the subquery.
5412
+ * @param type The join type (defaults to `inner`).
5413
+ * @returns
5414
+ */
5415
+ joinLateral(query, alias, type = "inner") {
5416
+ return this.addJoinSub(type, query, alias, void 0, void 0, void 0, true);
5417
+ }
5418
+ /**
5419
+ * Adds a lateral left join clause to the query.
5420
+ *
5421
+ * @param query The subquery (a QueryBuilder instance or raw SQL string).
5422
+ * @param alias The alias assigned to the subquery.
5423
+ * @returns
5424
+ */
5425
+ leftJoinLateral(query, alias) {
5426
+ return this.addJoinSub("left", query, alias, void 0, void 0, void 0, true);
5427
+ }
5428
+ /**
5429
+ * Builds a self-contained select specification used when this query is joined
5430
+ * as a subquery by another query builder.
5431
+ *
5432
+ * @returns
5433
+ */
5434
+ buildJoinSubquerySpec() {
5435
+ const spec = this.tryBuildSelectSpec(this.buildWhere());
5436
+ if (!spec) throw new require_relationship.UnsupportedAdapterFeatureException("Subquery join could not be compiled into an Arkorm select specification.", {
5437
+ operation: "query.joinSub",
5438
+ model: this.model.name
5439
+ });
5440
+ return spec;
5441
+ }
5442
+ guardJoinSupport() {
5443
+ if (!this.adapter?.capabilities?.joins) throw new require_relationship.UnsupportedAdapterFeatureException("Join clauses are not supported by the current adapter.", {
5444
+ operation: "join",
5445
+ model: this.model.name,
5446
+ meta: { feature: "joins" }
5447
+ });
5448
+ }
5449
+ pushJoin(join) {
5450
+ (this.queryJoins ??= []).push(join);
5451
+ }
5452
+ resolveJoinConstraints(first, operator, second) {
5453
+ if (first === void 0) return [];
5454
+ const clause = new JoinClause();
5455
+ if (typeof first === "function") first(clause);
5456
+ else clause.on(first, operator, second);
5457
+ return clause.getConstraints();
5458
+ }
5459
+ resolveJoinSource(query) {
5460
+ if (typeof query === "string") return { subquerySql: query };
5461
+ return { subquery: query.buildJoinSubquerySpec() };
5462
+ }
5463
+ addJoin(type, table, first, operator, second) {
5464
+ this.guardJoinSupport();
5465
+ this.pushJoin({
5466
+ type,
5467
+ table,
5468
+ constraints: this.resolveJoinConstraints(first, operator, second)
5469
+ });
5470
+ return this;
5471
+ }
5472
+ addJoinWhere(type, table, column, operator, value) {
5473
+ this.guardJoinSupport();
5474
+ const clause = new JoinClause();
5475
+ clause.where(column, operator, value);
5476
+ this.pushJoin({
5477
+ type,
5478
+ table,
5479
+ constraints: clause.getConstraints()
5480
+ });
5481
+ return this;
5482
+ }
5483
+ addJoinSub(type, query, alias, first, operator, second, lateral = false) {
5484
+ this.guardJoinSupport();
5485
+ this.pushJoin({
5486
+ type,
5487
+ alias,
5488
+ ...this.resolveJoinSource(query),
5489
+ ...lateral ? { lateral: true } : {},
5490
+ constraints: this.resolveJoinConstraints(first, operator, second)
5491
+ });
5492
+ return this;
5493
+ }
5494
+ /**
5495
+ * Adds a skip clause to the query for pagination.
4775
5496
  * This will overwrite any existing skip clause.
4776
5497
  *
4777
5498
  * @param skip
@@ -6026,6 +6747,7 @@ var QueryBuilder = class QueryBuilder {
6026
6747
  columns,
6027
6748
  distinct: this.queryDistinct || void 0,
6028
6749
  groupBy: this.queryGroupBy ? [...this.queryGroupBy] : void 0,
6750
+ joins: this.queryJoins ? [...this.queryJoins] : void 0,
6029
6751
  where: condition,
6030
6752
  orderBy,
6031
6753
  limit: this.limitValue,
@@ -6042,6 +6764,7 @@ var QueryBuilder = class QueryBuilder {
6042
6764
  if (this.hasRelationFilters() && this.canExecuteRelationFiltersInAdapter() && relationFilters === null) return null;
6043
6765
  return {
6044
6766
  target: this.buildQueryTarget(),
6767
+ joins: this.queryJoins ? [...this.queryJoins] : void 0,
6045
6768
  where: condition,
6046
6769
  relationFilters: this.canExecuteRelationFiltersInAdapter() ? relationFilters ?? void 0 : void 0,
6047
6770
  aggregate: { type: "count" }
@@ -8366,6 +9089,7 @@ exports.EnumBuilder = require_relationship.EnumBuilder;
8366
9089
  exports.ForeignKeyBuilder = require_relationship.ForeignKeyBuilder;
8367
9090
  exports.InitCommand = InitCommand;
8368
9091
  exports.InlineFactory = InlineFactory;
9092
+ exports.JoinClause = JoinClause;
8369
9093
  exports.KyselyDatabaseAdapter = KyselyDatabaseAdapter;
8370
9094
  exports.LengthAwarePaginator = require_relationship.LengthAwarePaginator;
8371
9095
  exports.MIGRATION_BRAND = MIGRATION_BRAND;