arkormx 2.6.1 → 2.8.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/cli.mjs +53 -7
- package/dist/{index-BTBfGF_o.d.mts → index-4mi5sLRA.d.cts} +556 -7
- package/dist/{index-CBh5Z2sS.d.cts → index-De8zXqTD.d.mts} +556 -7
- package/dist/index.cjs +948 -15
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +948 -16
- package/dist/relationship/index.cjs +1 -1
- package/dist/relationship/index.d.cts +1 -1
- package/dist/relationship/index.d.mts +1 -1
- package/dist/relationship/index.mjs +1 -1
- package/dist/{relationship-DT4myOWX.mjs → relationship--l8RA_yy.mjs} +52 -8
- package/dist/{relationship-CxCQj_Pg.cjs → relationship-WiXlopzY.cjs} +52 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_relationship = require('./relationship-
|
|
2
|
+
const require_relationship = require('./relationship-WiXlopzY.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
|
|
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
|
|
@@ -4914,6 +5635,56 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4914
5635
|
if (!model) throw new ModelNotFoundException(this.model.name, "Record not found.");
|
|
4915
5636
|
return model;
|
|
4916
5637
|
}
|
|
5638
|
+
/**
|
|
5639
|
+
* Returns the first record matching the given attributes or instantiates a
|
|
5640
|
+
* new, unpersisted model populated with the merged attributes and values.
|
|
5641
|
+
*
|
|
5642
|
+
* @param attributes
|
|
5643
|
+
* @param values
|
|
5644
|
+
* @returns
|
|
5645
|
+
*/
|
|
5646
|
+
async firstOrNew(attributes, values = {}) {
|
|
5647
|
+
const existing = await this.clone().where(attributes).first();
|
|
5648
|
+
if (existing) return existing;
|
|
5649
|
+
const ModelConstructor = this.model;
|
|
5650
|
+
return new ModelConstructor({
|
|
5651
|
+
...attributes,
|
|
5652
|
+
...values
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
/**
|
|
5656
|
+
* Returns the first record matching the given attributes or creates and
|
|
5657
|
+
* persists a new record populated with the merged attributes and values.
|
|
5658
|
+
*
|
|
5659
|
+
* @param attributes
|
|
5660
|
+
* @param values
|
|
5661
|
+
* @returns
|
|
5662
|
+
*/
|
|
5663
|
+
async firstOrCreate(attributes, values = {}) {
|
|
5664
|
+
const existing = await this.clone().where(attributes).first();
|
|
5665
|
+
if (existing) return existing;
|
|
5666
|
+
return await this.create({
|
|
5667
|
+
...attributes,
|
|
5668
|
+
...values
|
|
5669
|
+
});
|
|
5670
|
+
}
|
|
5671
|
+
async firstOr(columnsOrCallback, maybeCallback) {
|
|
5672
|
+
const callback = typeof columnsOrCallback === "function" ? columnsOrCallback : maybeCallback;
|
|
5673
|
+
if (!callback) throw new QueryConstraintException("firstOr requires a fallback callback.", {
|
|
5674
|
+
operation: "firstOr",
|
|
5675
|
+
model: this.model.name
|
|
5676
|
+
});
|
|
5677
|
+
if (Array.isArray(columnsOrCallback) && columnsOrCallback.length > 0) {
|
|
5678
|
+
const select = columnsOrCallback.reduce((all, column) => {
|
|
5679
|
+
all[column] = true;
|
|
5680
|
+
return all;
|
|
5681
|
+
}, {});
|
|
5682
|
+
this.select(select);
|
|
5683
|
+
}
|
|
5684
|
+
const found = await this.first();
|
|
5685
|
+
if (found) return found;
|
|
5686
|
+
return callback();
|
|
5687
|
+
}
|
|
4917
5688
|
async find(value, key) {
|
|
4918
5689
|
const resolvedKey = key ?? this.model.getPrimaryKey();
|
|
4919
5690
|
return this.where({ [resolvedKey]: value }).first();
|
|
@@ -5123,6 +5894,23 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5123
5894
|
}
|
|
5124
5895
|
return await this.clone().where(attributes).update(resolvedValues) != null;
|
|
5125
5896
|
}
|
|
5897
|
+
/**
|
|
5898
|
+
* Update the first record matching the given attributes, or create a new
|
|
5899
|
+
* record populated with the merged attributes and values when none exists.
|
|
5900
|
+
*
|
|
5901
|
+
* @param attributes
|
|
5902
|
+
* @param values
|
|
5903
|
+
* @returns
|
|
5904
|
+
*/
|
|
5905
|
+
async updateOrCreate(attributes, values = {}) {
|
|
5906
|
+
const existing = await this.clone().where(attributes).first();
|
|
5907
|
+
if (!existing) return await this.create({
|
|
5908
|
+
...attributes,
|
|
5909
|
+
...values
|
|
5910
|
+
});
|
|
5911
|
+
if (Object.keys(values).length === 0) return existing;
|
|
5912
|
+
return await this.clone().where(attributes).update(values);
|
|
5913
|
+
}
|
|
5126
5914
|
shouldFallbackUpdateOrInsertUpsert(error) {
|
|
5127
5915
|
if (!(error instanceof QueryExecutionException)) return false;
|
|
5128
5916
|
const cause = error.cause;
|
|
@@ -5174,13 +5962,25 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5174
5962
|
if (!this.isUniqueWhere(where) && directSpec && typeof adapter.deleteFirst === "function") {
|
|
5175
5963
|
const deleted = await adapter.deleteFirst(directSpec);
|
|
5176
5964
|
if (!deleted) return null;
|
|
5177
|
-
return this.
|
|
5965
|
+
return this.hydrateDeleted(deleted);
|
|
5178
5966
|
}
|
|
5179
5967
|
const uniqueWhere = await this.resolveUniqueWhere(where, false);
|
|
5180
5968
|
if (!uniqueWhere) return null;
|
|
5181
5969
|
const deleted = await this.executeDeleteRow(uniqueWhere, false);
|
|
5182
5970
|
if (!deleted) return null;
|
|
5183
|
-
return this.
|
|
5971
|
+
return this.hydrateDeleted(deleted);
|
|
5972
|
+
}
|
|
5973
|
+
/**
|
|
5974
|
+
* Hydrate a row that was just deleted, marking the resulting model as no
|
|
5975
|
+
* longer existing in the database.
|
|
5976
|
+
*
|
|
5977
|
+
* @param attributes
|
|
5978
|
+
* @returns
|
|
5979
|
+
*/
|
|
5980
|
+
hydrateDeleted(attributes) {
|
|
5981
|
+
const model = this.model.hydrate(attributes);
|
|
5982
|
+
model.exists = false;
|
|
5983
|
+
return model;
|
|
5184
5984
|
}
|
|
5185
5985
|
/**
|
|
5186
5986
|
* Deletes the first record matching the current query constraints and throws
|
|
@@ -6026,6 +6826,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6026
6826
|
columns,
|
|
6027
6827
|
distinct: this.queryDistinct || void 0,
|
|
6028
6828
|
groupBy: this.queryGroupBy ? [...this.queryGroupBy] : void 0,
|
|
6829
|
+
joins: this.queryJoins ? [...this.queryJoins] : void 0,
|
|
6029
6830
|
where: condition,
|
|
6030
6831
|
orderBy,
|
|
6031
6832
|
limit: this.limitValue,
|
|
@@ -6042,6 +6843,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6042
6843
|
if (this.hasRelationFilters() && this.canExecuteRelationFiltersInAdapter() && relationFilters === null) return null;
|
|
6043
6844
|
return {
|
|
6044
6845
|
target: this.buildQueryTarget(),
|
|
6846
|
+
joins: this.queryJoins ? [...this.queryJoins] : void 0,
|
|
6045
6847
|
where: condition,
|
|
6046
6848
|
relationFilters: this.canExecuteRelationFiltersInAdapter() ? relationFilters ?? void 0 : void 0,
|
|
6047
6849
|
aggregate: { type: "count" }
|
|
@@ -6547,9 +7349,12 @@ var Model = class Model {
|
|
|
6547
7349
|
this.hidden = [];
|
|
6548
7350
|
this.visible = [];
|
|
6549
7351
|
this.appends = [];
|
|
7352
|
+
this.exists = false;
|
|
7353
|
+
this.wasRecentlyCreated = false;
|
|
6550
7354
|
this.attributes = {};
|
|
6551
7355
|
this.original = {};
|
|
6552
7356
|
this.changes = {};
|
|
7357
|
+
this.previous = {};
|
|
6553
7358
|
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
6554
7359
|
this.fill(attributes);
|
|
6555
7360
|
return new Proxy(this, {
|
|
@@ -6909,6 +7714,68 @@ var Model = class Model {
|
|
|
6909
7714
|
return this.query().scope(name, ...args);
|
|
6910
7715
|
}
|
|
6911
7716
|
/**
|
|
7717
|
+
* Start a query constrained by the given where clause.
|
|
7718
|
+
*
|
|
7719
|
+
* @param this
|
|
7720
|
+
* @param where
|
|
7721
|
+
* @returns
|
|
7722
|
+
*/
|
|
7723
|
+
static where(where) {
|
|
7724
|
+
return this.query().where(where);
|
|
7725
|
+
}
|
|
7726
|
+
/**
|
|
7727
|
+
* Retrieve all records for the model.
|
|
7728
|
+
*
|
|
7729
|
+
* @param this
|
|
7730
|
+
* @returns
|
|
7731
|
+
*/
|
|
7732
|
+
static async all() {
|
|
7733
|
+
return await this.query().get();
|
|
7734
|
+
}
|
|
7735
|
+
/**
|
|
7736
|
+
* Create and persist a new record, returning the hydrated model instance.
|
|
7737
|
+
*
|
|
7738
|
+
* @param this
|
|
7739
|
+
* @param data
|
|
7740
|
+
* @returns
|
|
7741
|
+
*/
|
|
7742
|
+
static async create(data) {
|
|
7743
|
+
return await this.query().create(data);
|
|
7744
|
+
}
|
|
7745
|
+
/**
|
|
7746
|
+
* Insert new records or update existing records by one or more unique keys.
|
|
7747
|
+
*
|
|
7748
|
+
* @param this
|
|
7749
|
+
* @param values
|
|
7750
|
+
* @param uniqueBy
|
|
7751
|
+
* @param update
|
|
7752
|
+
* @returns
|
|
7753
|
+
*/
|
|
7754
|
+
static async upsert(values, uniqueBy, update = null) {
|
|
7755
|
+
return await this.query().upsert(values, uniqueBy, update);
|
|
7756
|
+
}
|
|
7757
|
+
/**
|
|
7758
|
+
* Delete records by their primary key(s), dispatching model events for each
|
|
7759
|
+
* matched record. Returns the number of records deleted.
|
|
7760
|
+
*
|
|
7761
|
+
* @param this
|
|
7762
|
+
* @param ids
|
|
7763
|
+
* @returns
|
|
7764
|
+
*/
|
|
7765
|
+
static async destroy(ids) {
|
|
7766
|
+
const constructor = this;
|
|
7767
|
+
const identifiers = (Array.isArray(ids) ? ids : [ids]).filter((identifier, index, all) => all.indexOf(identifier) === index);
|
|
7768
|
+
const primaryKey = constructor.getPrimaryKey();
|
|
7769
|
+
let deleted = 0;
|
|
7770
|
+
for (const identifier of identifiers) {
|
|
7771
|
+
const model = await constructor.query().where({ [primaryKey]: identifier }).first();
|
|
7772
|
+
if (!model) continue;
|
|
7773
|
+
await model.delete();
|
|
7774
|
+
deleted += 1;
|
|
7775
|
+
}
|
|
7776
|
+
return deleted;
|
|
7777
|
+
}
|
|
7778
|
+
/**
|
|
6912
7779
|
* Get the soft delete configuration for the model, including whether
|
|
6913
7780
|
* soft deletes are enabled and the name of the deleted at column.
|
|
6914
7781
|
*
|
|
@@ -6931,6 +7798,7 @@ var Model = class Model {
|
|
|
6931
7798
|
const model = new this(attributes);
|
|
6932
7799
|
model.syncOriginal();
|
|
6933
7800
|
model.syncChanges({});
|
|
7801
|
+
model.exists = true;
|
|
6934
7802
|
return model;
|
|
6935
7803
|
}
|
|
6936
7804
|
/**
|
|
@@ -6941,7 +7809,8 @@ var Model = class Model {
|
|
|
6941
7809
|
* @returns
|
|
6942
7810
|
*/
|
|
6943
7811
|
static hydrateMany(attributes) {
|
|
6944
|
-
|
|
7812
|
+
const constructor = this;
|
|
7813
|
+
return attributes.map((attribute) => constructor.hydrate(attribute));
|
|
6945
7814
|
}
|
|
6946
7815
|
/**
|
|
6947
7816
|
* Hydrate a model instance and dispatch the retrieved lifecycle event.
|
|
@@ -6989,6 +7858,11 @@ var Model = class Model {
|
|
|
6989
7858
|
return false;
|
|
6990
7859
|
}
|
|
6991
7860
|
}
|
|
7861
|
+
async updateOrFail(attributes) {
|
|
7862
|
+
const primaryKey = this.constructor.getPrimaryKey();
|
|
7863
|
+
if (this.getAttribute(primaryKey) == null) throw new require_relationship.ArkormException(primaryKey === "id" ? "Cannot update a model without an id." : `Cannot update a model without a [${primaryKey}] value.`);
|
|
7864
|
+
return await this.fill(attributes).saveOrFail();
|
|
7865
|
+
}
|
|
6992
7866
|
getAttribute(key) {
|
|
6993
7867
|
const attributeMutator = this.resolveAttributeMutator(key);
|
|
6994
7868
|
const mutator = this.resolveGetMutator(key);
|
|
@@ -7012,29 +7886,35 @@ var Model = class Model {
|
|
|
7012
7886
|
return this;
|
|
7013
7887
|
}
|
|
7014
7888
|
/**
|
|
7015
|
-
* Save the model to the database.
|
|
7016
|
-
* If the model
|
|
7017
|
-
*
|
|
7018
|
-
*
|
|
7019
|
-
*
|
|
7889
|
+
* Save the model to the database.
|
|
7890
|
+
* If the model already exists in the database it performs an update;
|
|
7891
|
+
* otherwise it performs an insert. Existence is tracked through the
|
|
7892
|
+
* `exists` flag rather than the presence of a primary-key value, so a model
|
|
7893
|
+
* built with an explicit primary key still inserts on its first save.
|
|
7894
|
+
*
|
|
7895
|
+
* @returns
|
|
7020
7896
|
*/
|
|
7021
7897
|
async save() {
|
|
7022
7898
|
const constructor = this.constructor;
|
|
7023
7899
|
const primaryKey = constructor.getPrimaryKey();
|
|
7024
|
-
const identifier = this.getAttribute(primaryKey);
|
|
7025
7900
|
const previousOriginal = this.getOriginal();
|
|
7026
|
-
if (
|
|
7901
|
+
if (!this.exists) {
|
|
7027
7902
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
7028
7903
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
7029
7904
|
const payload = this.normalizePersistenceAttributes(this.getRawAttributes());
|
|
7030
7905
|
const model = await constructor.query().create(payload);
|
|
7031
7906
|
this.fill(model.getRawAttributes());
|
|
7032
7907
|
this.syncChanges(previousOriginal);
|
|
7908
|
+
this.syncPrevious(previousOriginal);
|
|
7033
7909
|
this.syncOriginal();
|
|
7910
|
+
this.exists = true;
|
|
7911
|
+
this.wasRecentlyCreated = true;
|
|
7034
7912
|
await Model.dispatchEvent(constructor, "created", this);
|
|
7035
7913
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
7036
7914
|
return this;
|
|
7037
7915
|
}
|
|
7916
|
+
const identifier = this.getAttribute(primaryKey);
|
|
7917
|
+
if (identifier == null) throw new require_relationship.ArkormException(primaryKey === "id" ? "Cannot update an existing model without an id." : `Cannot update an existing model without a [${primaryKey}] value.`);
|
|
7038
7918
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
7039
7919
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
7040
7920
|
const payload = this.normalizePersistenceAttributes(this.getDirtyAttributes());
|
|
@@ -7042,6 +7922,7 @@ var Model = class Model {
|
|
|
7042
7922
|
const model = await constructor.query().where({ [primaryKey]: identifier }).update(payload);
|
|
7043
7923
|
this.fill(model.getRawAttributes());
|
|
7044
7924
|
this.syncChanges(previousOriginal);
|
|
7925
|
+
this.syncPrevious(previousOriginal);
|
|
7045
7926
|
this.syncOriginal();
|
|
7046
7927
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
7047
7928
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
@@ -7056,6 +7937,15 @@ var Model = class Model {
|
|
|
7056
7937
|
return await Model.withoutEvents(() => this.save());
|
|
7057
7938
|
}
|
|
7058
7939
|
/**
|
|
7940
|
+
* Save the model within a transaction, rolling back and rethrowing if the
|
|
7941
|
+
* operation fails. Unlike update(), this never swallows errors.
|
|
7942
|
+
*
|
|
7943
|
+
* @returns
|
|
7944
|
+
*/
|
|
7945
|
+
async saveOrFail() {
|
|
7946
|
+
return await this.constructor.transaction(async () => await this.save());
|
|
7947
|
+
}
|
|
7948
|
+
/**
|
|
7059
7949
|
* Delete the model from the database.
|
|
7060
7950
|
* If soft deletes are enabled, it will perform a soft delete by
|
|
7061
7951
|
* setting the deleted at column to the current date.
|
|
@@ -7083,6 +7973,7 @@ var Model = class Model {
|
|
|
7083
7973
|
this.fill(deleted.getRawAttributes());
|
|
7084
7974
|
this.syncChanges(previousOriginal);
|
|
7085
7975
|
this.syncOriginal();
|
|
7976
|
+
this.exists = false;
|
|
7086
7977
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
7087
7978
|
return this;
|
|
7088
7979
|
}
|
|
@@ -7095,6 +7986,15 @@ var Model = class Model {
|
|
|
7095
7986
|
return await Model.withoutEvents(() => this.delete());
|
|
7096
7987
|
}
|
|
7097
7988
|
/**
|
|
7989
|
+
* Delete the model within a transaction, rolling back and rethrowing if the
|
|
7990
|
+
* operation fails.
|
|
7991
|
+
*
|
|
7992
|
+
* @returns
|
|
7993
|
+
*/
|
|
7994
|
+
async deleteOrFail() {
|
|
7995
|
+
return await this.constructor.transaction(async () => await this.delete());
|
|
7996
|
+
}
|
|
7997
|
+
/**
|
|
7098
7998
|
* Permanently delete the model from the database, regardless of whether soft
|
|
7099
7999
|
* deletes are enabled.
|
|
7100
8000
|
*
|
|
@@ -7112,6 +8012,7 @@ var Model = class Model {
|
|
|
7112
8012
|
this.fill(deleted.getRawAttributes());
|
|
7113
8013
|
this.syncChanges(previousOriginal);
|
|
7114
8014
|
this.syncOriginal();
|
|
8015
|
+
this.exists = false;
|
|
7115
8016
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
7116
8017
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
7117
8018
|
return this;
|
|
@@ -7266,6 +8167,25 @@ var Model = class Model {
|
|
|
7266
8167
|
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
7267
8168
|
}
|
|
7268
8169
|
/**
|
|
8170
|
+
* Get the attributes that were changed during the last successful
|
|
8171
|
+
* persistence operation.
|
|
8172
|
+
*
|
|
8173
|
+
* @returns
|
|
8174
|
+
*/
|
|
8175
|
+
getChanges() {
|
|
8176
|
+
return Object.entries(this.changes).reduce((all, [key, value]) => {
|
|
8177
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
8178
|
+
return all;
|
|
8179
|
+
}, {});
|
|
8180
|
+
}
|
|
8181
|
+
getPrevious(key) {
|
|
8182
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.previous[key]);
|
|
8183
|
+
return Object.entries(this.previous).reduce((all, [previousKey, value]) => {
|
|
8184
|
+
all[previousKey] = Model.cloneAttributeValue(value);
|
|
8185
|
+
return all;
|
|
8186
|
+
}, {});
|
|
8187
|
+
}
|
|
8188
|
+
/**
|
|
7269
8189
|
* Convert the model instance to a plain object, applying visibility
|
|
7270
8190
|
* rules, appends, and mutators.
|
|
7271
8191
|
*
|
|
@@ -7682,6 +8602,18 @@ var Model = class Model {
|
|
|
7682
8602
|
}, {});
|
|
7683
8603
|
}
|
|
7684
8604
|
/**
|
|
8605
|
+
* Capture the attribute snapshot that was persisted before the most recent
|
|
8606
|
+
* save so it can be read back via getPrevious().
|
|
8607
|
+
*
|
|
8608
|
+
* @param previousOriginal
|
|
8609
|
+
*/
|
|
8610
|
+
syncPrevious(previousOriginal) {
|
|
8611
|
+
this.previous = Object.entries(previousOriginal).reduce((all, [key, value]) => {
|
|
8612
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
8613
|
+
return all;
|
|
8614
|
+
}, {});
|
|
8615
|
+
}
|
|
8616
|
+
/**
|
|
7685
8617
|
* Resolve lifecycle state for the provided model class.
|
|
7686
8618
|
*
|
|
7687
8619
|
* @param modelClass
|
|
@@ -8366,6 +9298,7 @@ exports.EnumBuilder = require_relationship.EnumBuilder;
|
|
|
8366
9298
|
exports.ForeignKeyBuilder = require_relationship.ForeignKeyBuilder;
|
|
8367
9299
|
exports.InitCommand = InitCommand;
|
|
8368
9300
|
exports.InlineFactory = InlineFactory;
|
|
9301
|
+
exports.JoinClause = JoinClause;
|
|
8369
9302
|
exports.KyselyDatabaseAdapter = KyselyDatabaseAdapter;
|
|
8370
9303
|
exports.LengthAwarePaginator = require_relationship.LengthAwarePaginator;
|
|
8371
9304
|
exports.MIGRATION_BRAND = MIGRATION_BRAND;
|