arkormx 2.0.0-next.1 → 2.0.0-next.3
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/README.md +42 -13
- package/dist/cli.mjs +273 -63
- package/dist/index.cjs +1707 -102
- package/dist/index.d.cts +539 -115
- package/dist/index.d.mts +539 -115
- package/dist/index.mjs +1694 -102
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
}) : target, mod));
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
|
+
let _h3ravel_support = require("@h3ravel/support");
|
|
29
30
|
let kysely = require("kysely");
|
|
30
31
|
let async_hooks = require("async_hooks");
|
|
31
32
|
let _rexxars_jiti = require("@rexxars/jiti");
|
|
@@ -36,7 +37,6 @@ let fs = require("fs");
|
|
|
36
37
|
let url = require("url");
|
|
37
38
|
let path = require("path");
|
|
38
39
|
path = __toESM(path);
|
|
39
|
-
let _h3ravel_support = require("@h3ravel/support");
|
|
40
40
|
let node_fs = require("node:fs");
|
|
41
41
|
let node_child_process = require("node:child_process");
|
|
42
42
|
let _h3ravel_shared = require("@h3ravel/shared");
|
|
@@ -99,24 +99,223 @@ var UnsupportedAdapterFeatureException = class extends ArkormException {
|
|
|
99
99
|
|
|
100
100
|
//#endregion
|
|
101
101
|
//#region src/adapters/KyselyDatabaseAdapter.ts
|
|
102
|
+
/**
|
|
103
|
+
* Database adapter implementation for Kysely, allowing Arkorm to execute queries using Kysely
|
|
104
|
+
* as the underlying query builder and executor.
|
|
105
|
+
*
|
|
106
|
+
* @author Legacy (3m1n3nc3)
|
|
107
|
+
* @since 2.0.0-next.0
|
|
108
|
+
*/
|
|
102
109
|
var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
110
|
+
static migrationStateTable = "arkormx_migrations";
|
|
111
|
+
static migrationRunTable = "arkormx_migration_runs";
|
|
103
112
|
capabilities = {
|
|
104
113
|
transactions: true,
|
|
105
114
|
returning: true,
|
|
106
115
|
insertMany: true,
|
|
116
|
+
upsert: true,
|
|
107
117
|
updateMany: true,
|
|
108
118
|
deleteMany: true,
|
|
109
119
|
exists: true,
|
|
110
120
|
relationLoads: false,
|
|
111
|
-
relationAggregates:
|
|
112
|
-
relationFilters:
|
|
121
|
+
relationAggregates: true,
|
|
122
|
+
relationFilters: true,
|
|
113
123
|
rawWhere: false
|
|
114
124
|
};
|
|
115
|
-
constructor(db) {
|
|
125
|
+
constructor(db, mapping = {}) {
|
|
116
126
|
this.db = db;
|
|
127
|
+
this.mapping = mapping;
|
|
128
|
+
}
|
|
129
|
+
quoteIdentifier(value) {
|
|
130
|
+
return `"${value.replace(/"/g, "\"\"")}"`;
|
|
131
|
+
}
|
|
132
|
+
quoteLiteral(value) {
|
|
133
|
+
if (value == null) return "null";
|
|
134
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
135
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
136
|
+
if (value instanceof Date) return `'${value.toISOString().replace(/'/g, "''")}'`;
|
|
137
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
138
|
+
}
|
|
139
|
+
async executeRawStatement(statement, executor = this.db) {
|
|
140
|
+
await kysely.sql.raw(statement).execute(executor);
|
|
141
|
+
}
|
|
142
|
+
resolveSchemaColumnName(columnName, columns = []) {
|
|
143
|
+
return columns.find((column) => column.name === columnName)?.map ?? columnName;
|
|
144
|
+
}
|
|
145
|
+
resolveSchemaIndexName(table, index) {
|
|
146
|
+
if (typeof index.name === "string" && index.name.trim().length > 0) return index.name;
|
|
147
|
+
return `${table}_${index.columns.join("_")}_idx`;
|
|
148
|
+
}
|
|
149
|
+
resolveSchemaForeignKeyName(table, foreignKey) {
|
|
150
|
+
return `${table}_${foreignKey.column}_fkey`;
|
|
151
|
+
}
|
|
152
|
+
resolveSchemaColumnType(column) {
|
|
153
|
+
if (column.type === "id") return "integer";
|
|
154
|
+
if (column.type === "uuid") return "uuid";
|
|
155
|
+
if (column.type === "enum") return this.quoteIdentifier(column.enumName ?? `${column.name}_enum`);
|
|
156
|
+
if (column.type === "string") return "varchar(255)";
|
|
157
|
+
if (column.type === "text") return "text";
|
|
158
|
+
if (column.type === "integer") return "integer";
|
|
159
|
+
if (column.type === "bigInteger") return "bigint";
|
|
160
|
+
if (column.type === "float") return "double precision";
|
|
161
|
+
if (column.type === "boolean") return "boolean";
|
|
162
|
+
if (column.type === "json") return "jsonb";
|
|
163
|
+
if (column.type === "date") return "date";
|
|
164
|
+
return "timestamptz";
|
|
165
|
+
}
|
|
166
|
+
resolveSchemaColumnDefault(column) {
|
|
167
|
+
const value = column.default ?? (column.updatedAt ? "now()" : void 0);
|
|
168
|
+
if (value === void 0) return null;
|
|
169
|
+
if (value === "now()") return "now()";
|
|
170
|
+
return this.quoteLiteral(value);
|
|
171
|
+
}
|
|
172
|
+
shouldUseIdentity(column) {
|
|
173
|
+
if (column.autoIncrement === false) return false;
|
|
174
|
+
if (column.type === "id") return column.default === void 0;
|
|
175
|
+
return column.autoIncrement === true;
|
|
176
|
+
}
|
|
177
|
+
buildSchemaColumnDefinition(column) {
|
|
178
|
+
const parts = [this.quoteIdentifier(column.map ?? column.name), this.resolveSchemaColumnType(column)];
|
|
179
|
+
if (this.shouldUseIdentity(column)) parts.push("generated by default as identity");
|
|
180
|
+
const defaultValue = this.resolveSchemaColumnDefault(column);
|
|
181
|
+
if (defaultValue && !this.shouldUseIdentity(column)) parts.push(`default ${defaultValue}`);
|
|
182
|
+
if (column.primary) parts.push("primary key");
|
|
183
|
+
else if (column.unique) parts.push("unique");
|
|
184
|
+
if (!column.nullable && !column.primary) parts.push("not null");
|
|
185
|
+
return parts.join(" ");
|
|
186
|
+
}
|
|
187
|
+
buildSchemaForeignKeyConstraint(table, foreignKey, columns = []) {
|
|
188
|
+
const localColumn = this.resolveSchemaColumnName(foreignKey.column, columns);
|
|
189
|
+
const referencedTable = this.resolveMappedTable(foreignKey.referencesTable);
|
|
190
|
+
const action = foreignKey.onDelete ? ` on delete ${foreignKey.onDelete === "setNull" ? "set null" : foreignKey.onDelete === "setDefault" ? "set default" : foreignKey.onDelete}` : "";
|
|
191
|
+
return `constraint ${this.quoteIdentifier(this.resolveSchemaForeignKeyName(table, foreignKey))} foreign key (${this.quoteIdentifier(localColumn)}) references ${this.quoteIdentifier(referencedTable)} (${this.quoteIdentifier(foreignKey.referencesColumn)})${action}`;
|
|
192
|
+
}
|
|
193
|
+
buildSchemaIndexStatement(table, index, columns = []) {
|
|
194
|
+
const mappedColumns = index.columns.map((column) => this.quoteIdentifier(this.resolveSchemaColumnName(column, columns))).join(", ");
|
|
195
|
+
return `create index if not exists ${this.quoteIdentifier(this.resolveSchemaIndexName(table, index))} on ${this.quoteIdentifier(table)} (${mappedColumns})`;
|
|
196
|
+
}
|
|
197
|
+
async ensureEnumTypes(columns, executor = this.db) {
|
|
198
|
+
for (const column of columns) {
|
|
199
|
+
if (column.type !== "enum") continue;
|
|
200
|
+
const enumName = column.enumName ?? `${column.name}_enum`;
|
|
201
|
+
if ((await kysely.sql`
|
|
202
|
+
select exists(
|
|
203
|
+
select 1
|
|
204
|
+
from pg_type
|
|
205
|
+
where typname = ${enumName}
|
|
206
|
+
) as exists
|
|
207
|
+
`.execute(executor)).rows[0]?.exists) continue;
|
|
208
|
+
const values = column.enumValues ?? [];
|
|
209
|
+
if (values.length === 0) throw new ArkormException(`Enum column [${column.name}] requires enum values for database-backed migrations.`);
|
|
210
|
+
await this.executeRawStatement(`create type ${this.quoteIdentifier(enumName)} as enum (${values.map((value) => this.quoteLiteral(value)).join(", ")})`, executor);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async executeCreateTableOperation(operation, executor) {
|
|
214
|
+
const table = this.resolveMappedTable(operation.table);
|
|
215
|
+
await this.ensureEnumTypes(operation.columns, executor);
|
|
216
|
+
const columnDefinitions = operation.columns.map((column) => this.buildSchemaColumnDefinition(column));
|
|
217
|
+
const foreignKeys = (operation.foreignKeys ?? []).map((foreignKey) => this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.columns));
|
|
218
|
+
const definitions = [...columnDefinitions, ...foreignKeys].join(", ");
|
|
219
|
+
await this.executeRawStatement(`create table if not exists ${this.quoteIdentifier(table)} (${definitions})`, executor);
|
|
220
|
+
for (const index of operation.indexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.columns), executor);
|
|
221
|
+
}
|
|
222
|
+
async executeAlterTableOperation(operation, executor) {
|
|
223
|
+
const table = this.resolveMappedTable(operation.table);
|
|
224
|
+
await this.ensureEnumTypes(operation.addColumns, executor);
|
|
225
|
+
for (const column of operation.addColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add column if not exists ${this.buildSchemaColumnDefinition(column)}`, executor);
|
|
226
|
+
for (const column of operation.dropColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} drop column if exists ${this.quoteIdentifier(column)}`, executor);
|
|
227
|
+
for (const foreignKey of operation.addForeignKeys ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.addColumns)}`, executor);
|
|
228
|
+
for (const index of operation.addIndexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.addColumns), executor);
|
|
229
|
+
}
|
|
230
|
+
async executeDropTableOperation(operation, executor) {
|
|
231
|
+
const table = this.resolveMappedTable(operation.table);
|
|
232
|
+
await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(table)} cascade`, executor);
|
|
233
|
+
}
|
|
234
|
+
async ensureMigrationStateTables(executor = this.db) {
|
|
235
|
+
await this.executeRawStatement(`
|
|
236
|
+
create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)} (
|
|
237
|
+
id text primary key,
|
|
238
|
+
file text not null,
|
|
239
|
+
class_name text not null,
|
|
240
|
+
applied_at timestamptz not null,
|
|
241
|
+
checksum text null
|
|
242
|
+
)
|
|
243
|
+
`, executor);
|
|
244
|
+
await this.executeRawStatement(`
|
|
245
|
+
create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)} (
|
|
246
|
+
id text primary key,
|
|
247
|
+
applied_at timestamptz not null,
|
|
248
|
+
migration_ids jsonb not null
|
|
249
|
+
)
|
|
250
|
+
`, executor);
|
|
251
|
+
}
|
|
252
|
+
async writeAppliedMigrationsStateInternal(state, executor) {
|
|
253
|
+
await this.ensureMigrationStateTables(executor);
|
|
254
|
+
await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)}`, executor);
|
|
255
|
+
await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)}`, executor);
|
|
256
|
+
for (const migration of state.migrations) await kysely.sql`
|
|
257
|
+
insert into ${kysely.sql.table(KyselyDatabaseAdapter.migrationStateTable)} (id, file, class_name, applied_at, checksum)
|
|
258
|
+
values (${migration.id}, ${migration.file}, ${migration.className}, ${migration.appliedAt}, ${migration.checksum ?? null})
|
|
259
|
+
`.execute(executor);
|
|
260
|
+
for (const run of state.runs ?? []) await kysely.sql`
|
|
261
|
+
insert into ${kysely.sql.table(KyselyDatabaseAdapter.migrationRunTable)} (id, applied_at, migration_ids)
|
|
262
|
+
values (${run.id}, ${run.appliedAt}, cast(${JSON.stringify(run.migrationIds)} as jsonb))
|
|
263
|
+
`.execute(executor);
|
|
264
|
+
}
|
|
265
|
+
async resetDatabaseInternal(executor) {
|
|
266
|
+
const tablesResult = await kysely.sql`
|
|
267
|
+
select table_name, table_schema
|
|
268
|
+
from information_schema.tables
|
|
269
|
+
where table_schema = current_schema()
|
|
270
|
+
and table_type = 'BASE TABLE'
|
|
271
|
+
order by table_name asc
|
|
272
|
+
`.execute(executor);
|
|
273
|
+
for (const row of tablesResult.rows) await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(row.table_schema)}.${this.quoteIdentifier(row.table_name)} cascade`, executor);
|
|
274
|
+
const enumTypesResult = await kysely.sql`
|
|
275
|
+
select t.typname as enum_name, n.nspname as enum_schema
|
|
276
|
+
from pg_type t
|
|
277
|
+
inner join pg_namespace n on n.oid = t.typnamespace
|
|
278
|
+
where t.typtype = 'e'
|
|
279
|
+
and n.nspname = current_schema()
|
|
280
|
+
order by t.typname asc
|
|
281
|
+
`.execute(executor);
|
|
282
|
+
for (const row of enumTypesResult.rows) await this.executeRawStatement(`drop type if exists ${this.quoteIdentifier(row.enum_schema)}.${this.quoteIdentifier(row.enum_name)} cascade`, executor);
|
|
283
|
+
}
|
|
284
|
+
introspectionTypeToTs(typeName, enumValues) {
|
|
285
|
+
if (enumValues && enumValues.length > 0) return enumValues.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(" | ");
|
|
286
|
+
switch (typeName) {
|
|
287
|
+
case "bool": return "boolean";
|
|
288
|
+
case "int2":
|
|
289
|
+
case "int4":
|
|
290
|
+
case "int8":
|
|
291
|
+
case "float4":
|
|
292
|
+
case "float8":
|
|
293
|
+
case "numeric":
|
|
294
|
+
case "money": return "number";
|
|
295
|
+
case "json":
|
|
296
|
+
case "jsonb": return "Record<string, unknown> | unknown[]";
|
|
297
|
+
case "date":
|
|
298
|
+
case "timestamp":
|
|
299
|
+
case "timestamptz": return "Date";
|
|
300
|
+
case "bytea": return "Uint8Array";
|
|
301
|
+
case "uuid":
|
|
302
|
+
case "varchar":
|
|
303
|
+
case "bpchar":
|
|
304
|
+
case "char":
|
|
305
|
+
case "text":
|
|
306
|
+
case "citext":
|
|
307
|
+
case "time":
|
|
308
|
+
case "timetz":
|
|
309
|
+
case "interval":
|
|
310
|
+
case "inet":
|
|
311
|
+
case "cidr":
|
|
312
|
+
case "macaddr":
|
|
313
|
+
case "macaddr8": return "string";
|
|
314
|
+
default: return "unknown";
|
|
315
|
+
}
|
|
117
316
|
}
|
|
118
317
|
resolveTable(target) {
|
|
119
|
-
if (target.table && target.table.trim().length > 0) return target.table;
|
|
318
|
+
if (target.table && target.table.trim().length > 0) return this.mapping[target.table] ?? target.table;
|
|
120
319
|
throw new ArkormException("Kysely adapter requires a concrete target table.", {
|
|
121
320
|
operation: "adapter.table",
|
|
122
321
|
model: target.modelName,
|
|
@@ -225,29 +424,223 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
225
424
|
if (clauses.length === 0) return kysely.sql``;
|
|
226
425
|
return kysely.sql.join(clauses, kysely.sql``);
|
|
227
426
|
}
|
|
427
|
+
buildColumnReference(table, column) {
|
|
428
|
+
return kysely.sql`${kysely.sql.table(table)}.${kysely.sql.id(column)}`;
|
|
429
|
+
}
|
|
430
|
+
buildRelatedTargetFromRelation(target, relation) {
|
|
431
|
+
const metadata = target.model?.getRelationMetadata(relation);
|
|
432
|
+
if (!metadata) throw new UnsupportedAdapterFeatureException(`Relation [${relation}] could not be resolved for SQL-backed relation execution.`, {
|
|
433
|
+
operation: "adapter.relation.metadata",
|
|
434
|
+
model: target.modelName,
|
|
435
|
+
relation
|
|
436
|
+
});
|
|
437
|
+
if (metadata.type !== "hasMany" && metadata.type !== "hasOne" && metadata.type !== "belongsTo" && metadata.type !== "belongsToMany" && metadata.type !== "hasOneThrough" && metadata.type !== "hasManyThrough") throw new UnsupportedAdapterFeatureException(`Relation [${relation}] is not supported for SQL-backed relation execution by the Kysely adapter yet.`, {
|
|
438
|
+
operation: "adapter.relation.metadata",
|
|
439
|
+
model: target.modelName,
|
|
440
|
+
relation,
|
|
441
|
+
meta: {
|
|
442
|
+
feature: "relationFilters",
|
|
443
|
+
relationType: metadata.type
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const relatedMetadata = metadata.relatedModel.getModelMetadata();
|
|
447
|
+
return {
|
|
448
|
+
metadata,
|
|
449
|
+
relatedTarget: {
|
|
450
|
+
model: metadata.relatedModel,
|
|
451
|
+
modelName: metadata.relatedModel.name,
|
|
452
|
+
table: relatedMetadata.table,
|
|
453
|
+
primaryKey: relatedMetadata.primaryKey,
|
|
454
|
+
columns: relatedMetadata.columns,
|
|
455
|
+
softDelete: relatedMetadata.softDelete
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
resolveMappedTable(table) {
|
|
460
|
+
return this.mapping[table] ?? table;
|
|
461
|
+
}
|
|
462
|
+
buildBelongsToManyJoinSource(outerTarget, relatedTarget, metadata) {
|
|
463
|
+
const outerTable = this.resolveTable(outerTarget);
|
|
464
|
+
const relatedTable = this.resolveTable(relatedTarget);
|
|
465
|
+
const pivotTable = this.resolveMappedTable(metadata.throughTable);
|
|
466
|
+
return {
|
|
467
|
+
from: kysely.sql`${kysely.sql.table(relatedTable)} inner join ${kysely.sql.table(pivotTable)} on ${this.buildColumnReference(relatedTable, this.mapColumn(relatedTarget, metadata.relatedKey))} = ${this.buildColumnReference(pivotTable, metadata.relatedPivotKey)}`,
|
|
468
|
+
condition: kysely.sql`
|
|
469
|
+
${this.buildColumnReference(pivotTable, metadata.foreignPivotKey)}
|
|
470
|
+
=
|
|
471
|
+
${this.buildColumnReference(outerTable, this.mapColumn(outerTarget, metadata.parentKey))}
|
|
472
|
+
`
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
buildThroughJoinSource(outerTarget, relatedTarget, metadata) {
|
|
476
|
+
const outerTable = this.resolveTable(outerTarget);
|
|
477
|
+
const relatedTable = this.resolveTable(relatedTarget);
|
|
478
|
+
const throughTable = this.resolveMappedTable(metadata.throughTable);
|
|
479
|
+
return {
|
|
480
|
+
from: kysely.sql`${kysely.sql.table(relatedTable)} inner join ${kysely.sql.table(throughTable)} on ${this.buildColumnReference(relatedTable, this.mapColumn(relatedTarget, metadata.secondKey))} = ${this.buildColumnReference(throughTable, metadata.secondLocalKey)}`,
|
|
481
|
+
condition: kysely.sql`
|
|
482
|
+
${this.buildColumnReference(throughTable, metadata.firstKey)}
|
|
483
|
+
=
|
|
484
|
+
${this.buildColumnReference(outerTable, this.mapColumn(outerTarget, metadata.localKey))}
|
|
485
|
+
`
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
buildRelatedJoinCondition(outerTarget, relation) {
|
|
489
|
+
const { metadata, relatedTarget } = this.buildRelatedTargetFromRelation(outerTarget, relation);
|
|
490
|
+
const outerTable = this.resolveTable(outerTarget);
|
|
491
|
+
const relatedTable = this.resolveTable(relatedTarget);
|
|
492
|
+
if (metadata.type === "belongsToMany") {
|
|
493
|
+
const joinSource = this.buildBelongsToManyJoinSource(outerTarget, relatedTarget, metadata);
|
|
494
|
+
return {
|
|
495
|
+
relatedTarget,
|
|
496
|
+
from: joinSource.from,
|
|
497
|
+
condition: joinSource.condition
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (metadata.type === "hasOneThrough" || metadata.type === "hasManyThrough") {
|
|
501
|
+
const joinSource = this.buildThroughJoinSource(outerTarget, relatedTarget, metadata);
|
|
502
|
+
return {
|
|
503
|
+
relatedTarget,
|
|
504
|
+
from: joinSource.from,
|
|
505
|
+
condition: joinSource.condition
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (metadata.type === "hasMany" || metadata.type === "hasOne") return {
|
|
509
|
+
relatedTarget,
|
|
510
|
+
from: kysely.sql`${kysely.sql.table(relatedTable)}`,
|
|
511
|
+
condition: kysely.sql`
|
|
512
|
+
${this.buildColumnReference(relatedTable, this.mapColumn(relatedTarget, metadata.foreignKey))}
|
|
513
|
+
=
|
|
514
|
+
${this.buildColumnReference(outerTable, this.mapColumn(outerTarget, metadata.localKey))}
|
|
515
|
+
`
|
|
516
|
+
};
|
|
517
|
+
return {
|
|
518
|
+
relatedTarget,
|
|
519
|
+
from: kysely.sql`${kysely.sql.table(relatedTable)}`,
|
|
520
|
+
condition: kysely.sql`
|
|
521
|
+
${this.buildColumnReference(relatedTable, this.mapColumn(relatedTarget, metadata.ownerKey))}
|
|
522
|
+
=
|
|
523
|
+
${this.buildColumnReference(outerTable, this.mapColumn(outerTarget, metadata.foreignKey))}
|
|
524
|
+
`
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
combineConditions(conditions) {
|
|
528
|
+
const filtered = conditions.filter((condition) => Boolean(condition));
|
|
529
|
+
if (filtered.length === 0) return kysely.sql`1 = 1`;
|
|
530
|
+
if (filtered.length === 1) return filtered[0];
|
|
531
|
+
return kysely.sql`(${kysely.sql.join(filtered, kysely.sql` and `)})`;
|
|
532
|
+
}
|
|
533
|
+
buildRelationFilterExpression(target, filter) {
|
|
534
|
+
const { relatedTarget, from, condition } = this.buildRelatedJoinCondition(target, filter.relation);
|
|
535
|
+
return kysely.sql`(
|
|
536
|
+
select count(*)::int
|
|
537
|
+
from ${from}
|
|
538
|
+
where ${this.combineConditions([condition, filter.where ? this.buildWhereCondition(relatedTarget, filter.where) : void 0])}
|
|
539
|
+
) ${filter.operator === "!=" ? kysely.sql.raw("!=") : kysely.sql.raw(filter.operator)} ${filter.count}`;
|
|
540
|
+
}
|
|
541
|
+
buildRelationFilterCondition(target, relationFilters) {
|
|
542
|
+
if (!relationFilters || relationFilters.length === 0) return kysely.sql`1 = 1`;
|
|
543
|
+
let expression = null;
|
|
544
|
+
relationFilters.forEach((filter) => {
|
|
545
|
+
const next = this.buildRelationFilterExpression(target, filter);
|
|
546
|
+
if (!expression) {
|
|
547
|
+
expression = next;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
expression = filter.boolean === "OR" ? kysely.sql`(${expression} or ${next})` : kysely.sql`(${expression} and ${next})`;
|
|
551
|
+
});
|
|
552
|
+
return expression ?? kysely.sql`1 = 1`;
|
|
553
|
+
}
|
|
554
|
+
buildQueryFilterCondition(target, condition, relationFilters) {
|
|
555
|
+
let expression = condition ? this.buildWhereCondition(target, condition) : null;
|
|
556
|
+
relationFilters?.forEach((filter) => {
|
|
557
|
+
const next = this.buildRelationFilterExpression(target, filter);
|
|
558
|
+
if (!expression) {
|
|
559
|
+
expression = next;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
expression = filter.boolean === "OR" ? kysely.sql`(${expression} or ${next})` : kysely.sql`(${expression} and ${next})`;
|
|
563
|
+
});
|
|
564
|
+
return expression ?? kysely.sql`1 = 1`;
|
|
565
|
+
}
|
|
566
|
+
buildRelationAggregateSelectList(target, relationAggregates) {
|
|
567
|
+
if (!relationAggregates || relationAggregates.length === 0) return kysely.sql``;
|
|
568
|
+
return kysely.sql.join(relationAggregates.map((aggregate) => {
|
|
569
|
+
const { relatedTarget, from, condition } = this.buildRelatedJoinCondition(target, aggregate.relation);
|
|
570
|
+
const relatedTable = this.resolveTable(relatedTarget);
|
|
571
|
+
const whereCondition = this.combineConditions([condition, aggregate.where ? this.buildWhereCondition(relatedTarget, aggregate.where) : void 0]);
|
|
572
|
+
if (aggregate.type === "exists") return kysely.sql`, exists(
|
|
573
|
+
select 1
|
|
574
|
+
from ${from}
|
|
575
|
+
where ${whereCondition}
|
|
576
|
+
) as ${kysely.sql.id(aggregate.alias ?? `${aggregate.relation}Exists`)}`;
|
|
577
|
+
const selectedColumn = aggregate.column ? this.buildColumnReference(relatedTable, this.mapColumn(relatedTarget, aggregate.column)) : kysely.sql.raw("*");
|
|
578
|
+
return kysely.sql`, (
|
|
579
|
+
select ${aggregate.type === "count" ? kysely.sql`count(*)::int` : aggregate.type === "sum" ? kysely.sql`sum(${selectedColumn})::double precision` : aggregate.type === "avg" ? kysely.sql`avg(${selectedColumn})::double precision` : aggregate.type === "min" ? kysely.sql`min(${selectedColumn})` : kysely.sql`max(${selectedColumn})`}
|
|
580
|
+
from ${from}
|
|
581
|
+
where ${whereCondition}
|
|
582
|
+
) as ${kysely.sql.id(aggregate.alias ?? `${aggregate.relation}${aggregate.type}`)}`;
|
|
583
|
+
}), kysely.sql``);
|
|
584
|
+
}
|
|
585
|
+
buildCombinedWhereClause(target, condition, relationFilters) {
|
|
586
|
+
if (!condition && (!relationFilters || relationFilters.length === 0)) return kysely.sql``;
|
|
587
|
+
return kysely.sql` where ${this.buildQueryFilterCondition(target, condition, relationFilters)}`;
|
|
588
|
+
}
|
|
589
|
+
buildSingleRowTargetCte(target, where) {
|
|
590
|
+
const primaryKey = this.resolvePrimaryKey(target);
|
|
591
|
+
return kysely.sql`target_row as (
|
|
592
|
+
select ${kysely.sql.id(primaryKey)}
|
|
593
|
+
from ${kysely.sql.table(this.resolveTable(target))}
|
|
594
|
+
where ${this.buildWhereCondition(target, where)}
|
|
595
|
+
limit 1
|
|
596
|
+
)`;
|
|
597
|
+
}
|
|
228
598
|
assertNoRelationLoads(spec) {
|
|
229
599
|
if ("relationLoads" in spec && spec.relationLoads && spec.relationLoads.length > 0) throw new UnsupportedAdapterFeatureException("Kysely adapter relation-load execution is planned for a later phase.", {
|
|
230
600
|
operation: "adapter.relationLoads",
|
|
231
601
|
meta: { feature: "relationLoads" }
|
|
232
602
|
});
|
|
233
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Selects records from the database matching the specified criteria and returns
|
|
606
|
+
* them as an array of database rows.
|
|
607
|
+
*
|
|
608
|
+
* @param spec The specification defining the selection criteria.
|
|
609
|
+
* @returns A promise that resolves to an array of database rows.
|
|
610
|
+
*/
|
|
234
611
|
async select(spec) {
|
|
235
612
|
this.assertNoRelationLoads(spec);
|
|
236
613
|
const result = await kysely.sql`
|
|
237
614
|
select ${this.buildSelectList(spec.target, spec.columns)}
|
|
615
|
+
${this.buildRelationAggregateSelectList(spec.target, spec.relationAggregates)}
|
|
238
616
|
from ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
239
|
-
${this.
|
|
617
|
+
${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
|
|
240
618
|
${this.buildOrderBy(spec.target, spec.orderBy)}
|
|
241
619
|
${this.buildPaginationClause(spec)}
|
|
242
620
|
`.execute(this.db);
|
|
243
621
|
return this.mapRows(spec.target, result.rows);
|
|
244
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Selects a single record from the database matching the specified criteria and returns it as
|
|
625
|
+
* a database row. If multiple records match the criteria, only the first one is returned.
|
|
626
|
+
* If no records match, null is returned.
|
|
627
|
+
*
|
|
628
|
+
* @param spec The specification defining the selection criteria.
|
|
629
|
+
* @returns A promise that resolves to a database row or null if no records match.
|
|
630
|
+
*/
|
|
245
631
|
async selectOne(spec) {
|
|
246
632
|
return (await this.select({
|
|
247
633
|
...spec,
|
|
248
634
|
limit: spec.limit ?? 1
|
|
249
635
|
}))[0] ?? null;
|
|
250
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* Inserts a new record into the database with the specified values and returns the
|
|
639
|
+
* inserted record as a database row.
|
|
640
|
+
*
|
|
641
|
+
* @param spec
|
|
642
|
+
* @returns
|
|
643
|
+
*/
|
|
251
644
|
async insert(spec) {
|
|
252
645
|
const values = this.mapValues(spec.target, spec.values);
|
|
253
646
|
const columns = Object.keys(values);
|
|
@@ -262,6 +655,13 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
262
655
|
`.execute(this.db);
|
|
263
656
|
return this.mapRow(spec.target, result.rows[0]);
|
|
264
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Inserts multiple records into the database with the specified values and returns the number
|
|
660
|
+
* of records successfully inserted.
|
|
661
|
+
*
|
|
662
|
+
* @param spec The specification defining the values to be inserted.
|
|
663
|
+
* @returns A promise that resolves to the number of records successfully inserted.
|
|
664
|
+
*/
|
|
265
665
|
async insertMany(spec) {
|
|
266
666
|
if (spec.values.length === 0) return 0;
|
|
267
667
|
const rows = spec.values.map((row) => this.mapValues(spec.target, row));
|
|
@@ -282,6 +682,39 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
282
682
|
returning ${kysely.sql.id(this.resolvePrimaryKey(spec.target))}
|
|
283
683
|
`.execute(this.db)).rows.length;
|
|
284
684
|
}
|
|
685
|
+
async upsert(spec) {
|
|
686
|
+
if (spec.values.length === 0) return 0;
|
|
687
|
+
const rows = spec.values.map((row) => this.mapValues(spec.target, row));
|
|
688
|
+
const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
|
|
689
|
+
const uniqueColumns = spec.uniqueBy.map((column) => this.mapColumn(spec.target, column));
|
|
690
|
+
const updateColumns = (spec.updateColumns ?? []).map((column) => this.mapColumn(spec.target, column)).filter((column) => !uniqueColumns.includes(column));
|
|
691
|
+
const conflictTarget = kysely.sql.join(uniqueColumns.map((column) => kysely.sql.id(column)), kysely.sql`, `);
|
|
692
|
+
if (columns.length === 0) {
|
|
693
|
+
await kysely.sql`
|
|
694
|
+
insert into ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
695
|
+
default values
|
|
696
|
+
on conflict (${conflictTarget}) do nothing
|
|
697
|
+
`.execute(this.db);
|
|
698
|
+
return spec.values.length;
|
|
699
|
+
}
|
|
700
|
+
const values = kysely.sql.join(rows.map((row) => {
|
|
701
|
+
return kysely.sql`(${kysely.sql.join(columns.map((column) => row[column] ?? null), kysely.sql`, `)})`;
|
|
702
|
+
}), kysely.sql`, `);
|
|
703
|
+
const conflictAction = updateColumns.length === 0 ? kysely.sql`do nothing` : kysely.sql`do update set ${kysely.sql.join(updateColumns.map((column) => kysely.sql`${kysely.sql.id(column)} = excluded.${kysely.sql.id(column)}`), kysely.sql`, `)}`;
|
|
704
|
+
await kysely.sql`
|
|
705
|
+
insert into ${kysely.sql.table(this.resolveTable(spec.target))} (${kysely.sql.join(columns.map((column) => kysely.sql.id(column)), kysely.sql`, `)})
|
|
706
|
+
values ${values}
|
|
707
|
+
on conflict (${conflictTarget}) ${conflictAction}
|
|
708
|
+
`.execute(this.db);
|
|
709
|
+
return spec.values.length;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Updates records in the database matching the specified criteria with the given values
|
|
713
|
+
* and returns the updated record as a database row.
|
|
714
|
+
*
|
|
715
|
+
* @param spec The specification defining the update criteria and values.
|
|
716
|
+
* @returns A promise that resolves to the updated record as a database row, or null if no records match the criteria.
|
|
717
|
+
*/
|
|
285
718
|
async update(spec) {
|
|
286
719
|
const values = this.mapValues(spec.target, spec.values);
|
|
287
720
|
const assignments = Object.entries(values).map(([column, value]) => {
|
|
@@ -300,6 +733,41 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
300
733
|
`.execute(this.db);
|
|
301
734
|
return this.mapRow(spec.target, result.rows[0]);
|
|
302
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* Updates a single record in the database matching the specified criteria with the given values.
|
|
738
|
+
*
|
|
739
|
+
* @param spec
|
|
740
|
+
* @returns
|
|
741
|
+
*/
|
|
742
|
+
async updateFirst(spec) {
|
|
743
|
+
const values = this.mapValues(spec.target, spec.values);
|
|
744
|
+
const assignments = Object.entries(values).map(([column, value]) => {
|
|
745
|
+
return kysely.sql`${kysely.sql.id(column)} = ${value}`;
|
|
746
|
+
});
|
|
747
|
+
if (assignments.length === 0) return await this.selectOne({
|
|
748
|
+
target: spec.target,
|
|
749
|
+
where: spec.where,
|
|
750
|
+
limit: 1
|
|
751
|
+
});
|
|
752
|
+
const primaryKey = this.resolvePrimaryKey(spec.target);
|
|
753
|
+
const table = this.resolveTable(spec.target);
|
|
754
|
+
const result = await kysely.sql`
|
|
755
|
+
with ${this.buildSingleRowTargetCte(spec.target, spec.where)}
|
|
756
|
+
update ${kysely.sql.table(table)}
|
|
757
|
+
set ${kysely.sql.join(assignments, kysely.sql`, `)}
|
|
758
|
+
from target_row
|
|
759
|
+
where ${this.buildColumnReference(table, primaryKey)} = ${kysely.sql`target_row.${kysely.sql.id(primaryKey)}`}
|
|
760
|
+
returning ${kysely.sql.table(table)}.*
|
|
761
|
+
`.execute(this.db);
|
|
762
|
+
return this.mapRow(spec.target, result.rows[0]);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Updates multiple records in the database matching the specified criteria with the
|
|
766
|
+
* given values and returns the number of records successfully updated.
|
|
767
|
+
*
|
|
768
|
+
* @param spec The specification defining the update criteria and values.
|
|
769
|
+
* @returns A promise that resolves to the number of records successfully updated.
|
|
770
|
+
*/
|
|
303
771
|
async updateMany(spec) {
|
|
304
772
|
const values = this.mapValues(spec.target, spec.values);
|
|
305
773
|
const assignments = Object.entries(values).map(([column, value]) => {
|
|
@@ -313,6 +781,13 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
313
781
|
returning ${kysely.sql.id(this.resolvePrimaryKey(spec.target))}
|
|
314
782
|
`.execute(this.db)).rows.length;
|
|
315
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Deletes records from the database matching the specified criteria and returns the
|
|
786
|
+
* deleted record as a database row.
|
|
787
|
+
*
|
|
788
|
+
* @param spec The specification defining the delete criteria.
|
|
789
|
+
* @returns A promise that resolves to the deleted record as a database row, or null if no records match the criteria.
|
|
790
|
+
*/
|
|
316
791
|
async delete(spec) {
|
|
317
792
|
const result = await kysely.sql`
|
|
318
793
|
delete from ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
@@ -321,6 +796,31 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
321
796
|
`.execute(this.db);
|
|
322
797
|
return this.mapRow(spec.target, result.rows[0]);
|
|
323
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* Deletes a single record from the database matching the specified criteria and returns it as a database row.
|
|
801
|
+
*
|
|
802
|
+
* @param spec
|
|
803
|
+
* @returns
|
|
804
|
+
*/
|
|
805
|
+
async deleteFirst(spec) {
|
|
806
|
+
const primaryKey = this.resolvePrimaryKey(spec.target);
|
|
807
|
+
const table = this.resolveTable(spec.target);
|
|
808
|
+
const result = await kysely.sql`
|
|
809
|
+
with ${this.buildSingleRowTargetCte(spec.target, spec.where)}
|
|
810
|
+
delete from ${kysely.sql.table(table)}
|
|
811
|
+
using target_row
|
|
812
|
+
where ${this.buildColumnReference(table, primaryKey)} = ${kysely.sql`target_row.${kysely.sql.id(primaryKey)}`}
|
|
813
|
+
returning ${kysely.sql.table(table)}.*
|
|
814
|
+
`.execute(this.db);
|
|
815
|
+
return this.mapRow(spec.target, result.rows[0]);
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Deletes multiple records from the database matching the specified criteria and
|
|
819
|
+
* returns the number of records successfully deleted.
|
|
820
|
+
*
|
|
821
|
+
* @param spec The specification defining the delete criteria.
|
|
822
|
+
* @returns A promise that resolves to the number of records successfully deleted.
|
|
823
|
+
*/
|
|
324
824
|
async deleteMany(spec) {
|
|
325
825
|
return (await kysely.sql`
|
|
326
826
|
delete from ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
@@ -328,36 +828,170 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
|
|
|
328
828
|
returning ${kysely.sql.id(this.resolvePrimaryKey(spec.target))}
|
|
329
829
|
`.execute(this.db)).rows.length;
|
|
330
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Counts the number of records in the database matching the specified criteria and returns
|
|
833
|
+
* the count as a number.
|
|
834
|
+
*
|
|
835
|
+
* @param spec The specification defining the count criteria.
|
|
836
|
+
* @returns A promise that resolves to the number of records matching the criteria.
|
|
837
|
+
*/
|
|
331
838
|
async count(spec) {
|
|
332
839
|
const result = await kysely.sql`
|
|
333
840
|
select count(*)::int as count
|
|
334
841
|
from ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
335
|
-
${this.
|
|
842
|
+
${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
|
|
336
843
|
`.execute(this.db);
|
|
337
844
|
return Number(result.rows[0]?.count ?? 0);
|
|
338
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Checks for the existence of records matching the specified criteria.
|
|
848
|
+
*
|
|
849
|
+
* @param spec The specification defining the existence criteria.
|
|
850
|
+
* @returns A promise that resolves to a boolean indicating whether any records match the criteria.
|
|
851
|
+
*/
|
|
339
852
|
async exists(spec) {
|
|
340
853
|
const result = await kysely.sql`
|
|
341
854
|
select exists(
|
|
342
855
|
select 1
|
|
343
856
|
from ${kysely.sql.table(this.resolveTable(spec.target))}
|
|
344
|
-
${this.
|
|
857
|
+
${this.buildCombinedWhereClause(spec.target, spec.where, spec.relationFilters)}
|
|
345
858
|
limit 1
|
|
346
859
|
) as exists
|
|
347
860
|
`.execute(this.db);
|
|
348
861
|
return Boolean(result.rows[0]?.exists);
|
|
349
862
|
}
|
|
863
|
+
async introspectModels(options = {}) {
|
|
864
|
+
const tables = options.tables?.filter(Boolean) ?? [];
|
|
865
|
+
const result = await kysely.sql`
|
|
866
|
+
select
|
|
867
|
+
cls.relname as table_name,
|
|
868
|
+
att.attname as column_name,
|
|
869
|
+
not att.attnotnull as is_nullable,
|
|
870
|
+
typ.typname as type_name,
|
|
871
|
+
case when typ.typcategory = 'A' then elem.typname else null end as element_type_name,
|
|
872
|
+
case when typ.typtype = 'e'
|
|
873
|
+
then array(select enumlabel from pg_enum where enumtypid = typ.oid order by enumsortorder)
|
|
874
|
+
else null
|
|
875
|
+
end as enum_values,
|
|
876
|
+
case when elem.typtype = 'e'
|
|
877
|
+
then array(select enumlabel from pg_enum where enumtypid = elem.oid order by enumsortorder)
|
|
878
|
+
else null
|
|
879
|
+
end as element_enum_values
|
|
880
|
+
from pg_attribute att
|
|
881
|
+
inner join pg_class cls on cls.oid = att.attrelid
|
|
882
|
+
inner join pg_namespace ns on ns.oid = cls.relnamespace
|
|
883
|
+
inner join pg_type typ on typ.oid = att.atttypid
|
|
884
|
+
left join pg_type elem on elem.oid = typ.typelem and typ.typcategory = 'A'
|
|
885
|
+
where cls.relkind in ('r', 'p')
|
|
886
|
+
and att.attnum > 0
|
|
887
|
+
and not att.attisdropped
|
|
888
|
+
and ns.nspname not in ('pg_catalog', 'information_schema')
|
|
889
|
+
${tables.length > 0 ? kysely.sql` and cls.relname in (${kysely.sql.join(tables)})` : kysely.sql``}
|
|
890
|
+
order by cls.relname asc, att.attnum asc
|
|
891
|
+
`.execute(this.db);
|
|
892
|
+
const models = /* @__PURE__ */ new Map();
|
|
893
|
+
result.rows.forEach((row) => {
|
|
894
|
+
const existing = models.get(row.table_name) ?? {
|
|
895
|
+
name: (0, _h3ravel_support.str)(row.table_name).studly().singular().toString(),
|
|
896
|
+
table: row.table_name,
|
|
897
|
+
fields: []
|
|
898
|
+
};
|
|
899
|
+
const isArray = row.element_type_name !== null;
|
|
900
|
+
const baseType = isArray ? this.introspectionTypeToTs(row.element_type_name ?? "unknown", row.element_enum_values) : this.introspectionTypeToTs(row.type_name, row.enum_values);
|
|
901
|
+
existing.fields.push({
|
|
902
|
+
name: row.column_name,
|
|
903
|
+
type: isArray ? `Array<${baseType}>` : baseType,
|
|
904
|
+
nullable: row.is_nullable
|
|
905
|
+
});
|
|
906
|
+
models.set(row.table_name, existing);
|
|
907
|
+
});
|
|
908
|
+
return [...models.values()];
|
|
909
|
+
}
|
|
910
|
+
async executeSchemaOperations(operations) {
|
|
911
|
+
if (operations.length === 0) return;
|
|
912
|
+
await this.transaction(async (adapter) => {
|
|
913
|
+
const transactionAdapter = adapter;
|
|
914
|
+
for (const operation of operations) {
|
|
915
|
+
if (operation.type === "createTable") {
|
|
916
|
+
await transactionAdapter.executeCreateTableOperation(operation, transactionAdapter.db);
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (operation.type === "alterTable") {
|
|
920
|
+
await transactionAdapter.executeAlterTableOperation(operation, transactionAdapter.db);
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
await transactionAdapter.executeDropTableOperation(operation, transactionAdapter.db);
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
async resetDatabase() {
|
|
928
|
+
await this.transaction(async (adapter) => {
|
|
929
|
+
const transactionAdapter = adapter;
|
|
930
|
+
await transactionAdapter.resetDatabaseInternal(transactionAdapter.db);
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
async readAppliedMigrationsState() {
|
|
934
|
+
await this.ensureMigrationStateTables();
|
|
935
|
+
const migrationsResult = await kysely.sql`
|
|
936
|
+
select id, file, class_name, applied_at, checksum
|
|
937
|
+
from ${kysely.sql.table(KyselyDatabaseAdapter.migrationStateTable)}
|
|
938
|
+
order by applied_at asc, id asc
|
|
939
|
+
`.execute(this.db);
|
|
940
|
+
const runsResult = await kysely.sql`
|
|
941
|
+
select id, applied_at, migration_ids
|
|
942
|
+
from ${kysely.sql.table(KyselyDatabaseAdapter.migrationRunTable)}
|
|
943
|
+
order by applied_at asc, id asc
|
|
944
|
+
`.execute(this.db);
|
|
945
|
+
return {
|
|
946
|
+
version: 1,
|
|
947
|
+
migrations: migrationsResult.rows.map((row) => ({
|
|
948
|
+
id: row.id,
|
|
949
|
+
file: row.file,
|
|
950
|
+
className: row.class_name,
|
|
951
|
+
appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
|
|
952
|
+
checksum: row.checksum ?? void 0
|
|
953
|
+
})),
|
|
954
|
+
runs: runsResult.rows.map((row) => ({
|
|
955
|
+
id: row.id,
|
|
956
|
+
appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
|
|
957
|
+
migrationIds: Array.isArray(row.migration_ids) ? row.migration_ids.filter((value) => typeof value === "string") : typeof row.migration_ids === "string" ? JSON.parse(row.migration_ids) : []
|
|
958
|
+
}))
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async writeAppliedMigrationsState(state) {
|
|
962
|
+
await this.transaction(async (adapter) => {
|
|
963
|
+
const transactionAdapter = adapter;
|
|
964
|
+
await transactionAdapter.writeAppliedMigrationsStateInternal(state, transactionAdapter.db);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Executes a series of database operations within a transaction.
|
|
969
|
+
* The provided callback function is called with a new instance of the
|
|
970
|
+
* KyselyDatabaseAdapter that is bound to the transaction context.
|
|
971
|
+
*
|
|
972
|
+
* @param callback The callback function containing the database operations to be executed within the transaction.
|
|
973
|
+
* @param context The transaction context specifying options such as read-only mode and isolation level.
|
|
974
|
+
* @returns A promise that resolves to the result of the callback function.
|
|
975
|
+
*/
|
|
350
976
|
async transaction(callback, context = {}) {
|
|
351
977
|
let transactionBuilder = this.db.transaction();
|
|
352
978
|
if (context.readOnly !== void 0) transactionBuilder = transactionBuilder.setAccessMode(context.readOnly ? "read only" : "read write");
|
|
353
979
|
if (context.isolationLevel) transactionBuilder = transactionBuilder.setIsolationLevel(context.isolationLevel);
|
|
354
980
|
return await transactionBuilder.execute(async (transaction) => {
|
|
355
|
-
return await callback(new KyselyDatabaseAdapter(transaction));
|
|
981
|
+
return await callback(new KyselyDatabaseAdapter(transaction, this.mapping));
|
|
356
982
|
});
|
|
357
983
|
}
|
|
358
984
|
};
|
|
359
|
-
|
|
360
|
-
|
|
985
|
+
/**
|
|
986
|
+
* Factory function to create a KyselyDatabaseAdapter instance with the given Kysely executor
|
|
987
|
+
* and optional table name mapping.
|
|
988
|
+
*
|
|
989
|
+
* @param db The Kysely executor to be used by the adapter.
|
|
990
|
+
* @param mapping Optional table name mapping for the adapter.
|
|
991
|
+
* @returns A new instance of KyselyDatabaseAdapter.
|
|
992
|
+
*/
|
|
993
|
+
const createKyselyAdapter = (db, mapping = {}) => {
|
|
994
|
+
return new KyselyDatabaseAdapter(db, mapping);
|
|
361
995
|
};
|
|
362
996
|
|
|
363
997
|
//#endregion
|
|
@@ -416,6 +1050,7 @@ const userConfig = {
|
|
|
416
1050
|
let runtimeConfigLoaded = false;
|
|
417
1051
|
let runtimeConfigLoadingPromise;
|
|
418
1052
|
let runtimeClientResolver;
|
|
1053
|
+
let runtimeAdapter;
|
|
419
1054
|
let runtimePaginationURLDriverFactory;
|
|
420
1055
|
let runtimePaginationCurrentPageResolver;
|
|
421
1056
|
const transactionClientStorage = new async_hooks.AsyncLocalStorage();
|
|
@@ -441,6 +1076,12 @@ const mergePathConfig = (paths) => {
|
|
|
441
1076
|
const defineConfig = (config) => {
|
|
442
1077
|
return config;
|
|
443
1078
|
};
|
|
1079
|
+
const bindAdapterToModels = (adapter, models) => {
|
|
1080
|
+
models.forEach((model) => {
|
|
1081
|
+
model.setAdapter(adapter);
|
|
1082
|
+
});
|
|
1083
|
+
return adapter;
|
|
1084
|
+
};
|
|
444
1085
|
/**
|
|
445
1086
|
* Get the user-provided ArkORM configuration.
|
|
446
1087
|
*
|
|
@@ -460,15 +1101,22 @@ const getUserConfig = (key) => {
|
|
|
460
1101
|
const configureArkormRuntime = (prisma, options = {}) => {
|
|
461
1102
|
const nextConfig = {
|
|
462
1103
|
...userConfig,
|
|
463
|
-
prisma,
|
|
464
1104
|
paths: mergePathConfig(options.paths)
|
|
465
1105
|
};
|
|
1106
|
+
nextConfig.prisma = prisma;
|
|
466
1107
|
if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
|
|
1108
|
+
if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
|
|
1109
|
+
if (options.boot !== void 0) nextConfig.boot = options.boot;
|
|
467
1110
|
if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
|
|
468
1111
|
Object.assign(userConfig, { ...nextConfig });
|
|
469
1112
|
runtimeClientResolver = prisma;
|
|
1113
|
+
runtimeAdapter = options.adapter;
|
|
470
1114
|
runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
|
|
471
1115
|
runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
|
|
1116
|
+
options.boot?.({
|
|
1117
|
+
prisma: resolveClient(prisma),
|
|
1118
|
+
bindAdapter: bindAdapterToModels
|
|
1119
|
+
});
|
|
472
1120
|
};
|
|
473
1121
|
/**
|
|
474
1122
|
* Reset the ArkORM runtime configuration.
|
|
@@ -482,6 +1130,7 @@ const resetArkormRuntimeForTests = () => {
|
|
|
482
1130
|
runtimeConfigLoaded = false;
|
|
483
1131
|
runtimeConfigLoadingPromise = void 0;
|
|
484
1132
|
runtimeClientResolver = void 0;
|
|
1133
|
+
runtimeAdapter = void 0;
|
|
485
1134
|
runtimePaginationURLDriverFactory = void 0;
|
|
486
1135
|
runtimePaginationCurrentPageResolver = void 0;
|
|
487
1136
|
};
|
|
@@ -508,8 +1157,10 @@ const resolveClient = (resolver) => {
|
|
|
508
1157
|
*/
|
|
509
1158
|
const resolveAndApplyConfig = (imported) => {
|
|
510
1159
|
const config = imported?.default ?? imported;
|
|
511
|
-
if (!config || typeof config !== "object"
|
|
1160
|
+
if (!config || typeof config !== "object") return;
|
|
512
1161
|
configureArkormRuntime(config.prisma, {
|
|
1162
|
+
adapter: config.adapter,
|
|
1163
|
+
boot: config.boot,
|
|
513
1164
|
pagination: config.pagination,
|
|
514
1165
|
paths: config.paths,
|
|
515
1166
|
outputExt: config.outputExt
|
|
@@ -591,6 +1242,10 @@ const getRuntimePrismaClient = () => {
|
|
|
591
1242
|
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
592
1243
|
return resolveClient(runtimeClientResolver);
|
|
593
1244
|
};
|
|
1245
|
+
const getRuntimeAdapter = () => {
|
|
1246
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
1247
|
+
return runtimeAdapter;
|
|
1248
|
+
};
|
|
594
1249
|
const getActiveTransactionClient = () => {
|
|
595
1250
|
return transactionClientStorage.getStore();
|
|
596
1251
|
};
|
|
@@ -658,9 +1313,11 @@ loadArkormConfig();
|
|
|
658
1313
|
//#endregion
|
|
659
1314
|
//#region src/helpers/prisma.ts
|
|
660
1315
|
/**
|
|
661
|
-
* Create an adapter to convert a Prisma client instance into a format
|
|
1316
|
+
* Create an adapter to convert a Prisma client instance into a format
|
|
662
1317
|
* compatible with ArkORM's expectations.
|
|
663
|
-
*
|
|
1318
|
+
*
|
|
1319
|
+
* @deprecated Prefer createPrismaDatabaseAdapter(prisma) for runtime usage.
|
|
1320
|
+
*
|
|
664
1321
|
* @param prisma The Prisma client instance to adapt.
|
|
665
1322
|
* @param mapping An optional mapping of Prisma delegate names to ArkORM delegate names.
|
|
666
1323
|
* @returns A record of adapted Prisma delegates compatible with ArkORM.
|
|
@@ -675,6 +1332,9 @@ function createPrismaAdapter(prisma) {
|
|
|
675
1332
|
/**
|
|
676
1333
|
* Create a delegate mapping record for Model.setClient() from a Prisma client.
|
|
677
1334
|
*
|
|
1335
|
+
* @deprecated Prefer createPrismaDatabaseAdapter(prisma, mapping) and bind the
|
|
1336
|
+
* resulting adapter with Model.setAdapter(...).
|
|
1337
|
+
*
|
|
678
1338
|
* @param prisma The Prisma client instance.
|
|
679
1339
|
* @param mapping Optional mapping of Arkormˣ delegate names to Prisma delegate names.
|
|
680
1340
|
* @returns A delegate map keyed by Arkormˣ delegate names.
|
|
@@ -701,6 +1361,13 @@ function inferDelegateName(modelName) {
|
|
|
701
1361
|
|
|
702
1362
|
//#endregion
|
|
703
1363
|
//#region src/adapters/PrismaDatabaseAdapter.ts
|
|
1364
|
+
/**
|
|
1365
|
+
* Database adapter implementation for Prisma, allowing Arkorm to execute queries using Prisma
|
|
1366
|
+
* as the underlying query builder and executor.
|
|
1367
|
+
*
|
|
1368
|
+
* @author Legacy (3m1n3nc3)
|
|
1369
|
+
* @since 2.0.0-next.0
|
|
1370
|
+
*/
|
|
704
1371
|
var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
705
1372
|
capabilities;
|
|
706
1373
|
delegates;
|
|
@@ -715,6 +1382,7 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
715
1382
|
this.capabilities = {
|
|
716
1383
|
transactions: this.hasTransactionSupport(prisma),
|
|
717
1384
|
insertMany: Object.values(this.delegates).some((delegate) => typeof delegate.createMany === "function"),
|
|
1385
|
+
upsert: false,
|
|
718
1386
|
updateMany: Object.values(this.delegates).some((delegate) => typeof delegate.updateMany === "function"),
|
|
719
1387
|
deleteMany: false,
|
|
720
1388
|
exists: true,
|
|
@@ -734,6 +1402,27 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
734
1402
|
unique(values) {
|
|
735
1403
|
return [...new Set(values.filter(Boolean))];
|
|
736
1404
|
}
|
|
1405
|
+
runtimeModelTypeToTs(typeName, kind, enumValues) {
|
|
1406
|
+
if (kind === "enum" && enumValues && enumValues.length > 0) return enumValues.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(" | ");
|
|
1407
|
+
switch (typeName) {
|
|
1408
|
+
case "Int":
|
|
1409
|
+
case "Float":
|
|
1410
|
+
case "Decimal":
|
|
1411
|
+
case "BigInt": return "number";
|
|
1412
|
+
case "Boolean": return "boolean";
|
|
1413
|
+
case "DateTime": return "Date";
|
|
1414
|
+
case "Json": return "Record<string, unknown> | unknown[]";
|
|
1415
|
+
case "Bytes": return "Uint8Array";
|
|
1416
|
+
case "String":
|
|
1417
|
+
case "UUID": return "string";
|
|
1418
|
+
default: return "string";
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
getRuntimeDataModel() {
|
|
1422
|
+
const runtimeDataModel = this.prisma._runtimeDataModel;
|
|
1423
|
+
if (runtimeDataModel && typeof runtimeDataModel === "object") return runtimeDataModel;
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
737
1426
|
toQuerySelect(columns) {
|
|
738
1427
|
if (!columns || columns.length === 0) return void 0;
|
|
739
1428
|
return columns.reduce((select, column) => {
|
|
@@ -815,6 +1504,29 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
815
1504
|
return include;
|
|
816
1505
|
}, {});
|
|
817
1506
|
}
|
|
1507
|
+
async introspectModels(options = {}) {
|
|
1508
|
+
const runtimeDataModel = this.getRuntimeDataModel();
|
|
1509
|
+
if (!runtimeDataModel?.models) return [];
|
|
1510
|
+
const requestedTables = new Set(options.tables?.filter(Boolean) ?? []);
|
|
1511
|
+
const enums = runtimeDataModel.enums ?? {};
|
|
1512
|
+
return Object.entries(runtimeDataModel.models).flatMap(([name, model]) => {
|
|
1513
|
+
const table = model.dbName ?? `${(0, _h3ravel_support.str)(name).camel().plural()}`;
|
|
1514
|
+
if (requestedTables.size > 0 && !requestedTables.has(table)) return [];
|
|
1515
|
+
return [{
|
|
1516
|
+
name,
|
|
1517
|
+
table,
|
|
1518
|
+
fields: (model.fields ?? []).filter((field) => field.kind !== "object").map((field) => {
|
|
1519
|
+
const enumValues = field.kind === "enum" ? (enums[field.type]?.values ?? []).map((value) => typeof value === "string" ? value : value.name ?? "").filter(Boolean) : null;
|
|
1520
|
+
const baseType = this.runtimeModelTypeToTs(field.type, field.kind, enumValues);
|
|
1521
|
+
return {
|
|
1522
|
+
name: field.name,
|
|
1523
|
+
type: field.isList ? `Array<${baseType}>` : baseType,
|
|
1524
|
+
nullable: field.isRequired === false
|
|
1525
|
+
};
|
|
1526
|
+
})
|
|
1527
|
+
}];
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
818
1530
|
resolveDelegate(target) {
|
|
819
1531
|
const tableName = target.table ? this.normalizeCandidate(target.table) : "";
|
|
820
1532
|
const singularTableName = tableName ? `${(0, _h3ravel_support.str)(tableName).singular()}` : "";
|
|
@@ -846,15 +1558,41 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
846
1558
|
}
|
|
847
1559
|
});
|
|
848
1560
|
}
|
|
1561
|
+
/**
|
|
1562
|
+
* @todo Implement relationLoads by performing separate queries and merging results
|
|
1563
|
+
* in-memory, since Prisma does not support nested reads with constraints, ordering, or
|
|
1564
|
+
* pagination on related models as of now.
|
|
1565
|
+
*
|
|
1566
|
+
* @param spec
|
|
1567
|
+
* @returns
|
|
1568
|
+
*/
|
|
849
1569
|
async select(spec) {
|
|
850
1570
|
return await this.resolveDelegate(spec.target).findMany(this.buildFindArgs(spec));
|
|
851
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Selects a single record matching the specified criteria.
|
|
1574
|
+
*
|
|
1575
|
+
* @param spec
|
|
1576
|
+
* @returns
|
|
1577
|
+
*/
|
|
852
1578
|
async selectOne(spec) {
|
|
853
1579
|
return await this.resolveDelegate(spec.target).findFirst(this.buildFindArgs(spec));
|
|
854
1580
|
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Inserts a single record into the database and returns the created record.
|
|
1583
|
+
*
|
|
1584
|
+
* @param spec
|
|
1585
|
+
* @returns
|
|
1586
|
+
*/
|
|
855
1587
|
async insert(spec) {
|
|
856
1588
|
return await this.resolveDelegate(spec.target).create({ data: spec.values });
|
|
857
1589
|
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Inserts multiple records into the database.
|
|
1592
|
+
*
|
|
1593
|
+
* @param spec
|
|
1594
|
+
* @returns
|
|
1595
|
+
*/
|
|
858
1596
|
async insertMany(spec) {
|
|
859
1597
|
const delegate = this.resolveDelegate(spec.target);
|
|
860
1598
|
if (typeof delegate.createMany === "function") {
|
|
@@ -874,6 +1612,12 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
874
1612
|
}
|
|
875
1613
|
return spec.ignoreDuplicates ? inserted : spec.values.length;
|
|
876
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Updates a single record matching the specified criteria and returns the updated record.
|
|
1617
|
+
*
|
|
1618
|
+
* @param spec
|
|
1619
|
+
* @returns
|
|
1620
|
+
*/
|
|
877
1621
|
async update(spec) {
|
|
878
1622
|
const delegate = this.resolveDelegate(spec.target);
|
|
879
1623
|
const where = this.toQueryWhere(spec.where);
|
|
@@ -883,6 +1627,12 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
883
1627
|
data: spec.values
|
|
884
1628
|
});
|
|
885
1629
|
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Updates multiple records matching the specified criteria.
|
|
1632
|
+
*
|
|
1633
|
+
* @param spec
|
|
1634
|
+
* @returns
|
|
1635
|
+
*/
|
|
886
1636
|
async updateMany(spec) {
|
|
887
1637
|
const delegate = this.resolveDelegate(spec.target);
|
|
888
1638
|
const where = this.toQueryWhere(spec.where);
|
|
@@ -903,12 +1653,24 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
903
1653
|
}));
|
|
904
1654
|
return rows.length;
|
|
905
1655
|
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Deletes a single record matching the specified criteria and returns the deleted record.
|
|
1658
|
+
*
|
|
1659
|
+
* @param spec
|
|
1660
|
+
* @returns
|
|
1661
|
+
*/
|
|
906
1662
|
async delete(spec) {
|
|
907
1663
|
const delegate = this.resolveDelegate(spec.target);
|
|
908
1664
|
const where = this.toQueryWhere(spec.where);
|
|
909
1665
|
if (!where) return null;
|
|
910
1666
|
return await delegate.delete({ where });
|
|
911
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Deletes multiple records matching the specified criteria.
|
|
1670
|
+
*
|
|
1671
|
+
* @param spec
|
|
1672
|
+
* @returns
|
|
1673
|
+
*/
|
|
912
1674
|
async deleteMany(spec) {
|
|
913
1675
|
const delegate = this.resolveDelegate(spec.target);
|
|
914
1676
|
const where = this.toQueryWhere(spec.where);
|
|
@@ -918,21 +1680,46 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
918
1680
|
}));
|
|
919
1681
|
return rows.length;
|
|
920
1682
|
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Counts the number of records matching the specified criteria.
|
|
1685
|
+
*
|
|
1686
|
+
* @param spec
|
|
1687
|
+
* @returns
|
|
1688
|
+
*/
|
|
921
1689
|
async count(spec) {
|
|
922
1690
|
return await this.resolveDelegate(spec.target).count({ where: this.toQueryWhere(spec.where) });
|
|
923
1691
|
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Checks for the existence of records matching the specified criteria.
|
|
1694
|
+
*
|
|
1695
|
+
* @param spec
|
|
1696
|
+
* @returns
|
|
1697
|
+
*/
|
|
924
1698
|
async exists(spec) {
|
|
925
1699
|
return await this.selectOne({
|
|
926
1700
|
...spec,
|
|
927
1701
|
limit: 1
|
|
928
1702
|
}) != null;
|
|
929
1703
|
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Loads related models for a batch of parent records based on the specified relation load plans.
|
|
1706
|
+
*
|
|
1707
|
+
* @param _spec
|
|
1708
|
+
*/
|
|
930
1709
|
async loadRelations(_spec) {
|
|
931
1710
|
throw new UnsupportedAdapterFeatureException("Relation batch loading is not supported by the Prisma compatibility adapter yet.", {
|
|
932
1711
|
operation: "adapter.loadRelations",
|
|
933
1712
|
meta: { feature: "relationLoads" }
|
|
934
1713
|
});
|
|
935
1714
|
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Executes a series of database operations within a transaction.
|
|
1717
|
+
* If the underlying Prisma client does not support transactions, an exception is thrown.
|
|
1718
|
+
*
|
|
1719
|
+
* @param callback
|
|
1720
|
+
* @param context
|
|
1721
|
+
* @returns
|
|
1722
|
+
*/
|
|
936
1723
|
async transaction(callback, context = {}) {
|
|
937
1724
|
if (!this.hasTransactionSupport(this.prisma)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the Prisma compatibility adapter.", {
|
|
938
1725
|
operation: "adapter.transaction",
|
|
@@ -947,9 +1734,25 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
947
1734
|
});
|
|
948
1735
|
}
|
|
949
1736
|
};
|
|
1737
|
+
/**
|
|
1738
|
+
* Factory function to create a PrismaDatabaseAdapter instance with the given
|
|
1739
|
+
* Prisma client and optional delegate name mapping.
|
|
1740
|
+
*
|
|
1741
|
+
* @param prisma The Prisma client instance to be used by the adapter.
|
|
1742
|
+
* @param mapping Optional mapping of delegate names.
|
|
1743
|
+
* @returns A new instance of PrismaDatabaseAdapter.
|
|
1744
|
+
*/
|
|
950
1745
|
const createPrismaDatabaseAdapter = (prisma, mapping = {}) => {
|
|
951
1746
|
return new PrismaDatabaseAdapter(prisma, mapping);
|
|
952
1747
|
};
|
|
1748
|
+
/**
|
|
1749
|
+
* Alias for createPrismaDatabaseAdapter to maintain backward compatibility with
|
|
1750
|
+
* previous versions of Arkorm that exported the adapter factory under a different name.
|
|
1751
|
+
*
|
|
1752
|
+
* @param prisma The Prisma client instance to be used by the adapter.
|
|
1753
|
+
* @param mapping Optional mapping of delegate names.
|
|
1754
|
+
* @returns A new instance of PrismaDatabaseAdapter.
|
|
1755
|
+
*/
|
|
953
1756
|
const createPrismaCompatibilityAdapter = createPrismaDatabaseAdapter;
|
|
954
1757
|
|
|
955
1758
|
//#endregion
|
|
@@ -2349,6 +3152,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
|
|
|
2349
3152
|
else await instance.down(schema);
|
|
2350
3153
|
return schema.getOperations();
|
|
2351
3154
|
};
|
|
3155
|
+
const supportsDatabaseMigrationExecution = (adapter) => {
|
|
3156
|
+
return typeof adapter?.executeSchemaOperations === "function";
|
|
3157
|
+
};
|
|
3158
|
+
const supportsDatabaseReset = (adapter) => {
|
|
3159
|
+
return typeof adapter?.resetDatabase === "function";
|
|
3160
|
+
};
|
|
3161
|
+
const stripPrismaSchemaModelsAndEnums = (schema) => {
|
|
3162
|
+
const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
3163
|
+
return stripped.length > 0 ? `${stripped}\n` : "";
|
|
3164
|
+
};
|
|
3165
|
+
const applyMigrationToDatabase = async (adapter, migration) => {
|
|
3166
|
+
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
3167
|
+
const operations = await getMigrationPlan(migration, "up");
|
|
3168
|
+
await adapter.executeSchemaOperations(operations);
|
|
3169
|
+
return { operations };
|
|
3170
|
+
};
|
|
3171
|
+
const applyMigrationRollbackToDatabase = async (adapter, migration) => {
|
|
3172
|
+
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
3173
|
+
const operations = await getMigrationPlan(migration, "down");
|
|
3174
|
+
await adapter.executeSchemaOperations(operations);
|
|
3175
|
+
return { operations };
|
|
3176
|
+
};
|
|
2352
3177
|
/**
|
|
2353
3178
|
* Apply the schema operations defined in a migration to a Prisma schema
|
|
2354
3179
|
* file, updating the file on disk if specified, and return the updated
|
|
@@ -2921,6 +3746,40 @@ var CliApp = class {
|
|
|
2921
3746
|
lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
|
|
2922
3747
|
return lines.join("\n");
|
|
2923
3748
|
}
|
|
3749
|
+
parseModelSyncSource(modelSource) {
|
|
3750
|
+
const classMatch = modelSource.match(/export\s+class\s+(\w+)\s+extends\s+Model(?:<[^\n]+>)?\s*\{/);
|
|
3751
|
+
if (!classMatch) return null;
|
|
3752
|
+
const className = classMatch[1];
|
|
3753
|
+
const tableMatch = modelSource.match(/protected\s+static\s+override\s+table\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+table\s*=\s*['"]([^'"]+)['"]/);
|
|
3754
|
+
const delegateMatch = modelSource.match(/protected\s+static\s+override\s+delegate\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+delegate\s*=\s*['"]([^'"]+)['"]/);
|
|
3755
|
+
return {
|
|
3756
|
+
className,
|
|
3757
|
+
table: tableMatch?.[1] ?? delegateMatch?.[1] ?? (0, _h3ravel_support.str)(className).camel().plural().toString()
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
3760
|
+
syncModelFiles(modelFiles, resolveStructure, enums) {
|
|
3761
|
+
const updated = [];
|
|
3762
|
+
const skipped = [];
|
|
3763
|
+
modelFiles.forEach((filePath) => {
|
|
3764
|
+
const source = (0, fs.readFileSync)(filePath, "utf-8");
|
|
3765
|
+
const structure = resolveStructure(filePath, source);
|
|
3766
|
+
if (!structure || structure.fields.length === 0) {
|
|
3767
|
+
skipped.push(filePath);
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
const synced = this.syncModelDeclarations(source, structure.fields, enums);
|
|
3771
|
+
if (!synced.updated) {
|
|
3772
|
+
skipped.push(filePath);
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
(0, fs.writeFileSync)(filePath, synced.content);
|
|
3776
|
+
updated.push(filePath);
|
|
3777
|
+
});
|
|
3778
|
+
return {
|
|
3779
|
+
updated,
|
|
3780
|
+
skipped
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
2924
3783
|
/**
|
|
2925
3784
|
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2926
3785
|
*
|
|
@@ -3013,7 +3872,7 @@ var CliApp = class {
|
|
|
3013
3872
|
*/
|
|
3014
3873
|
syncModelDeclarations(modelSource, declarations, enums) {
|
|
3015
3874
|
const lines = modelSource.split("\n");
|
|
3016
|
-
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model
|
|
3875
|
+
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model(?:<[^\n]+>)?\s*\{/.test(line));
|
|
3017
3876
|
if (classIndex < 0) return {
|
|
3018
3877
|
content: modelSource,
|
|
3019
3878
|
updated: false
|
|
@@ -3067,6 +3926,36 @@ var CliApp = class {
|
|
|
3067
3926
|
updated: contentWithImports !== modelSource
|
|
3068
3927
|
};
|
|
3069
3928
|
}
|
|
3929
|
+
async syncModels(options = {}) {
|
|
3930
|
+
const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", (0, path.join)(process.cwd(), "src", "models"));
|
|
3931
|
+
if (!(0, fs.existsSync)(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
3932
|
+
const modelFiles = (0, fs.readdirSync)(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => (0, path.join)(modelsDir, file));
|
|
3933
|
+
const adapter = this.getConfig("adapter");
|
|
3934
|
+
if (adapter && typeof adapter.introspectModels === "function") {
|
|
3935
|
+
const sources = modelFiles.reduce((all, filePath) => {
|
|
3936
|
+
const parsed = this.parseModelSyncSource((0, fs.readFileSync)(filePath, "utf-8"));
|
|
3937
|
+
if (parsed) all.set(filePath, parsed);
|
|
3938
|
+
return all;
|
|
3939
|
+
}, /* @__PURE__ */ new Map());
|
|
3940
|
+
const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
|
|
3941
|
+
const structuresByTable = new Map(discovered.map((model) => [model.table, model]));
|
|
3942
|
+
const result = this.syncModelFiles(modelFiles, (filePath) => {
|
|
3943
|
+
const parsed = sources.get(filePath);
|
|
3944
|
+
return parsed ? structuresByTable.get(parsed.table) : void 0;
|
|
3945
|
+
}, /* @__PURE__ */ new Map());
|
|
3946
|
+
return {
|
|
3947
|
+
source: "adapter",
|
|
3948
|
+
modelsDir,
|
|
3949
|
+
total: modelFiles.length,
|
|
3950
|
+
updated: result.updated,
|
|
3951
|
+
skipped: result.skipped
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
return {
|
|
3955
|
+
source: "prisma",
|
|
3956
|
+
...this.syncModelsFromPrisma(options)
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3070
3959
|
/**
|
|
3071
3960
|
* Sync model attribute declarations in model files based on the Prisma schema.
|
|
3072
3961
|
* This method reads the Prisma schema to extract model definitions and their
|
|
@@ -3085,39 +3974,19 @@ var CliApp = class {
|
|
|
3085
3974
|
if (!(0, fs.existsSync)(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
3086
3975
|
const schema = (0, fs.readFileSync)(schemaPath, "utf-8");
|
|
3087
3976
|
const prismaEnums = this.parsePrismaEnums(schema);
|
|
3088
|
-
const prismaModels = this.parsePrismaModels(schema);
|
|
3089
|
-
const modelFiles = (0, fs.readdirSync)(modelsDir).filter((file) => file.endsWith(".ts"));
|
|
3090
|
-
const
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
|
|
3096
|
-
if (!classMatch) {
|
|
3097
|
-
skipped.push(filePath);
|
|
3098
|
-
return;
|
|
3099
|
-
}
|
|
3100
|
-
const className = classMatch[1];
|
|
3101
|
-
const delegate = classMatch[2];
|
|
3102
|
-
const prismaModel = prismaModels.find((model) => model.table === delegate) ?? prismaModels.find((model) => model.name === className);
|
|
3103
|
-
if (!prismaModel || prismaModel.fields.length === 0) {
|
|
3104
|
-
skipped.push(filePath);
|
|
3105
|
-
return;
|
|
3106
|
-
}
|
|
3107
|
-
const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
|
|
3108
|
-
if (!synced.updated) {
|
|
3109
|
-
skipped.push(filePath);
|
|
3110
|
-
return;
|
|
3111
|
-
}
|
|
3112
|
-
(0, fs.writeFileSync)(filePath, synced.content);
|
|
3113
|
-
updated.push(filePath);
|
|
3114
|
-
});
|
|
3977
|
+
const prismaModels = this.parsePrismaModels(schema);
|
|
3978
|
+
const modelFiles = (0, fs.readdirSync)(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => (0, path.join)(modelsDir, file));
|
|
3979
|
+
const result = this.syncModelFiles(modelFiles, (filePath, source) => {
|
|
3980
|
+
const parsed = this.parseModelSyncSource(source);
|
|
3981
|
+
if (!parsed) return void 0;
|
|
3982
|
+
return prismaModels.find((model) => model.table === parsed.table) ?? prismaModels.find((model) => model.name === parsed.className);
|
|
3983
|
+
}, prismaEnums);
|
|
3115
3984
|
return {
|
|
3116
3985
|
schemaPath,
|
|
3117
3986
|
modelsDir,
|
|
3118
3987
|
total: modelFiles.length,
|
|
3119
|
-
updated,
|
|
3120
|
-
skipped
|
|
3988
|
+
updated: result.updated,
|
|
3989
|
+
skipped: result.skipped
|
|
3121
3990
|
};
|
|
3122
3991
|
}
|
|
3123
3992
|
};
|
|
@@ -3285,10 +4154,13 @@ var MakeSeederCommand = class extends _h3ravel_musket.Command {
|
|
|
3285
4154
|
|
|
3286
4155
|
//#endregion
|
|
3287
4156
|
//#region src/helpers/migration-history.ts
|
|
3288
|
-
const
|
|
4157
|
+
const createEmptyAppliedMigrationsState = () => ({
|
|
3289
4158
|
version: 1,
|
|
3290
4159
|
migrations: [],
|
|
3291
4160
|
runs: []
|
|
4161
|
+
});
|
|
4162
|
+
const supportsDatabaseMigrationState = (adapter) => {
|
|
4163
|
+
return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
|
|
3292
4164
|
};
|
|
3293
4165
|
const resolveMigrationStateFilePath = (cwd, configuredPath) => {
|
|
3294
4166
|
if (configuredPath && configuredPath.trim().length > 0) return (0, node_path.resolve)(configuredPath);
|
|
@@ -3303,10 +4175,10 @@ const computeMigrationChecksum = (filePath) => {
|
|
|
3303
4175
|
return (0, node_crypto.createHash)("sha256").update(source).digest("hex");
|
|
3304
4176
|
};
|
|
3305
4177
|
const readAppliedMigrationsState = (stateFilePath) => {
|
|
3306
|
-
if (!(0, node_fs.existsSync)(stateFilePath)) return
|
|
4178
|
+
if (!(0, node_fs.existsSync)(stateFilePath)) return createEmptyAppliedMigrationsState();
|
|
3307
4179
|
try {
|
|
3308
4180
|
const parsed = JSON.parse((0, node_fs.readFileSync)(stateFilePath, "utf-8"));
|
|
3309
|
-
if (!Array.isArray(parsed.migrations)) return
|
|
4181
|
+
if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
|
|
3310
4182
|
return {
|
|
3311
4183
|
version: 1,
|
|
3312
4184
|
migrations: parsed.migrations.filter((migration) => {
|
|
@@ -3317,14 +4189,34 @@ const readAppliedMigrationsState = (stateFilePath) => {
|
|
|
3317
4189
|
}) : []
|
|
3318
4190
|
};
|
|
3319
4191
|
} catch {
|
|
3320
|
-
return
|
|
4192
|
+
return createEmptyAppliedMigrationsState();
|
|
3321
4193
|
}
|
|
3322
4194
|
};
|
|
4195
|
+
const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
|
|
4196
|
+
if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
|
|
4197
|
+
return readAppliedMigrationsState(stateFilePath);
|
|
4198
|
+
};
|
|
3323
4199
|
const writeAppliedMigrationsState = (stateFilePath, state) => {
|
|
3324
4200
|
const directory = (0, node_path.dirname)(stateFilePath);
|
|
3325
4201
|
if (!(0, node_fs.existsSync)(directory)) (0, node_fs.mkdirSync)(directory, { recursive: true });
|
|
3326
4202
|
(0, node_fs.writeFileSync)(stateFilePath, JSON.stringify(state, null, 2));
|
|
3327
4203
|
};
|
|
4204
|
+
const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
|
|
4205
|
+
if (supportsDatabaseMigrationState(adapter)) {
|
|
4206
|
+
await adapter.writeAppliedMigrationsState(state);
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
4209
|
+
writeAppliedMigrationsState(stateFilePath, state);
|
|
4210
|
+
};
|
|
4211
|
+
const deleteAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
|
|
4212
|
+
if (supportsDatabaseMigrationState(adapter)) {
|
|
4213
|
+
await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
|
|
4214
|
+
return "database";
|
|
4215
|
+
}
|
|
4216
|
+
if (!(0, node_fs.existsSync)(stateFilePath)) return "missing-file";
|
|
4217
|
+
writeAppliedMigrationsState(stateFilePath, createEmptyAppliedMigrationsState());
|
|
4218
|
+
return "file";
|
|
4219
|
+
};
|
|
3328
4220
|
const isMigrationApplied = (state, identity, checksum) => {
|
|
3329
4221
|
const matched = state.migrations.find((migration) => migration.id === identity);
|
|
3330
4222
|
if (!matched) return false;
|
|
@@ -3443,7 +4335,9 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
|
|
|
3443
4335
|
const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
|
|
3444
4336
|
if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
|
|
3445
4337
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
3446
|
-
let appliedState =
|
|
4338
|
+
let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
|
|
4339
|
+
const adapter = this.app.getConfig("adapter");
|
|
4340
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
3447
4341
|
const skipped = [];
|
|
3448
4342
|
const changed = [];
|
|
3449
4343
|
const pending = classes.filter(([migrationClass, file]) => {
|
|
@@ -3465,10 +4359,16 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
|
|
|
3465
4359
|
this.success("No pending migration classes to apply.");
|
|
3466
4360
|
return;
|
|
3467
4361
|
}
|
|
3468
|
-
for (const [MigrationClassItem] of pending)
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
4362
|
+
for (const [MigrationClassItem] of pending) {
|
|
4363
|
+
if (useDatabaseMigrations) {
|
|
4364
|
+
await applyMigrationToDatabase(adapter, MigrationClassItem);
|
|
4365
|
+
continue;
|
|
4366
|
+
}
|
|
4367
|
+
await applyMigrationToPrismaSchema(MigrationClassItem, {
|
|
4368
|
+
schemaPath,
|
|
4369
|
+
write: true
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
3472
4372
|
if (appliedState) {
|
|
3473
4373
|
const runAppliedIds = [];
|
|
3474
4374
|
for (const [migrationClass, file] of pending) {
|
|
@@ -3487,10 +4387,10 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
|
|
|
3487
4387
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3488
4388
|
migrationIds: runAppliedIds
|
|
3489
4389
|
});
|
|
3490
|
-
|
|
4390
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
3491
4391
|
}
|
|
3492
|
-
if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
3493
|
-
if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
4392
|
+
if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
4393
|
+
if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
3494
4394
|
else runPrismaCommand([
|
|
3495
4395
|
"migrate",
|
|
3496
4396
|
"dev",
|
|
@@ -3550,6 +4450,85 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
|
|
|
3550
4450
|
}
|
|
3551
4451
|
};
|
|
3552
4452
|
|
|
4453
|
+
//#endregion
|
|
4454
|
+
//#region src/cli/commands/MigrateFreshCommand.ts
|
|
4455
|
+
var MigrateFreshCommand = class extends _h3ravel_musket.Command {
|
|
4456
|
+
signature = `migrate:fresh
|
|
4457
|
+
{--skip-generate : Skip prisma generate}
|
|
4458
|
+
{--skip-migrate : Skip prisma database sync}
|
|
4459
|
+
{--state-file= : Path to applied migration state file}
|
|
4460
|
+
{--schema= : Explicit prisma schema path}
|
|
4461
|
+
`;
|
|
4462
|
+
description = "Reset the database and rerun all migration classes";
|
|
4463
|
+
async handle() {
|
|
4464
|
+
this.app.command = this;
|
|
4465
|
+
const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? (0, node_path.join)(process.cwd(), "database", "migrations");
|
|
4466
|
+
const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
|
|
4467
|
+
if (!(0, node_fs.existsSync)(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
|
|
4468
|
+
const adapter = this.app.getConfig("adapter");
|
|
4469
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
4470
|
+
const schemaPath = this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : (0, node_path.join)(process.cwd(), "prisma", "schema.prisma");
|
|
4471
|
+
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
4472
|
+
const migrations = await this.loadAllMigrations(migrationsDir);
|
|
4473
|
+
if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
|
|
4474
|
+
if (supportsDatabaseReset(adapter)) await adapter.resetDatabase();
|
|
4475
|
+
else {
|
|
4476
|
+
if (!(0, node_fs.existsSync)(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
|
|
4477
|
+
(0, node_fs.writeFileSync)(schemaPath, stripPrismaSchemaModelsAndEnums((0, node_fs.readFileSync)(schemaPath, "utf-8")));
|
|
4478
|
+
}
|
|
4479
|
+
let appliedState = createEmptyAppliedMigrationsState();
|
|
4480
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
4481
|
+
for (const [MigrationClassItem] of migrations) {
|
|
4482
|
+
if (useDatabaseMigrations) {
|
|
4483
|
+
await applyMigrationToDatabase(adapter, MigrationClassItem);
|
|
4484
|
+
continue;
|
|
4485
|
+
}
|
|
4486
|
+
await applyMigrationToPrismaSchema(MigrationClassItem, {
|
|
4487
|
+
schemaPath,
|
|
4488
|
+
write: true
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
|
|
4492
|
+
id: buildMigrationIdentity(file, migrationClass.name),
|
|
4493
|
+
file,
|
|
4494
|
+
className: migrationClass.name,
|
|
4495
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4496
|
+
checksum: computeMigrationChecksum(file)
|
|
4497
|
+
});
|
|
4498
|
+
appliedState = markMigrationRun(appliedState, {
|
|
4499
|
+
id: buildMigrationRunId(),
|
|
4500
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4501
|
+
migrationIds: appliedState.migrations.map((migration) => migration.id)
|
|
4502
|
+
});
|
|
4503
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
4504
|
+
if (!useDatabaseMigrations) {
|
|
4505
|
+
const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
|
|
4506
|
+
if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
|
|
4507
|
+
if (!this.option("skip-migrate")) runPrismaCommand([
|
|
4508
|
+
"db",
|
|
4509
|
+
"push",
|
|
4510
|
+
"--force-reset",
|
|
4511
|
+
...schemaArgs
|
|
4512
|
+
], process.cwd());
|
|
4513
|
+
}
|
|
4514
|
+
this.success(`Refreshed database with ${migrations.length} migration(s).`);
|
|
4515
|
+
migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
|
|
4516
|
+
}
|
|
4517
|
+
async loadAllMigrations(migrationsDir) {
|
|
4518
|
+
const files = (0, node_fs.readdirSync)(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath((0, node_path.join)(migrationsDir, file)));
|
|
4519
|
+
return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
|
|
4520
|
+
}
|
|
4521
|
+
async loadMigrationClassesFromFile(filePath) {
|
|
4522
|
+
const imported = await RuntimeModuleLoader.load(filePath);
|
|
4523
|
+
return Object.values(imported).filter((value) => {
|
|
4524
|
+
if (typeof value !== "function") return false;
|
|
4525
|
+
const candidate = value;
|
|
4526
|
+
const prototype = candidate.prototype;
|
|
4527
|
+
return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
|
|
3553
4532
|
//#endregion
|
|
3554
4533
|
//#region src/cli/commands/MigrateRollbackCommand.ts
|
|
3555
4534
|
/**
|
|
@@ -3578,7 +4557,9 @@ var MigrateRollbackCommand = class extends _h3ravel_musket.Command {
|
|
|
3578
4557
|
if (!(0, node_fs.existsSync)(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
|
|
3579
4558
|
const schemaPath = this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : (0, node_path.join)(process.cwd(), "prisma", "schema.prisma");
|
|
3580
4559
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
3581
|
-
|
|
4560
|
+
const adapter = this.app.getConfig("adapter");
|
|
4561
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
4562
|
+
let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
|
|
3582
4563
|
const stepOption = this.option("step");
|
|
3583
4564
|
const stepCount = stepOption == null ? void 0 : Number(stepOption);
|
|
3584
4565
|
if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
|
|
@@ -3600,17 +4581,23 @@ var MigrateRollbackCommand = class extends _h3ravel_musket.Command {
|
|
|
3600
4581
|
rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
|
|
3601
4582
|
return;
|
|
3602
4583
|
}
|
|
3603
|
-
for (const [MigrationClassItem] of rollbackClasses)
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
4584
|
+
for (const [MigrationClassItem] of rollbackClasses) {
|
|
4585
|
+
if (useDatabaseMigrations) {
|
|
4586
|
+
await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
|
|
4590
|
+
schemaPath,
|
|
4591
|
+
write: true
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
3607
4594
|
for (const [migrationClass, file] of rollbackClasses) {
|
|
3608
4595
|
const identity = buildMigrationIdentity(file, migrationClass.name);
|
|
3609
4596
|
appliedState = removeAppliedMigration(appliedState, identity);
|
|
3610
4597
|
}
|
|
3611
|
-
|
|
3612
|
-
if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
3613
|
-
if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
4598
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
4599
|
+
if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
4600
|
+
if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
3614
4601
|
else runPrismaCommand([
|
|
3615
4602
|
"migrate",
|
|
3616
4603
|
"dev",
|
|
@@ -3654,7 +4641,14 @@ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
|
|
|
3654
4641
|
async handle() {
|
|
3655
4642
|
this.app.command = this;
|
|
3656
4643
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
4644
|
+
const adapter = this.app.getConfig("adapter");
|
|
4645
|
+
const usesDatabaseState = supportsDatabaseMigrationState(adapter);
|
|
3657
4646
|
if (this.option("delete")) {
|
|
4647
|
+
if (usesDatabaseState) {
|
|
4648
|
+
await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
|
|
4649
|
+
this.success("Deleted tracked migration state from database.");
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
3658
4652
|
if (!(0, node_fs.existsSync)(stateFilePath)) {
|
|
3659
4653
|
this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
|
|
3660
4654
|
return;
|
|
@@ -3664,22 +4658,19 @@ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
|
|
|
3664
4658
|
return;
|
|
3665
4659
|
}
|
|
3666
4660
|
if (this.option("reset")) {
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
migrations: []
|
|
3670
|
-
});
|
|
3671
|
-
this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
|
|
4661
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
|
|
4662
|
+
this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
|
|
3672
4663
|
return;
|
|
3673
4664
|
}
|
|
3674
|
-
const state =
|
|
4665
|
+
const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
|
|
3675
4666
|
if (this.option("json")) {
|
|
3676
4667
|
this.success(JSON.stringify({
|
|
3677
|
-
path: stateFilePath,
|
|
4668
|
+
path: usesDatabaseState ? "database" : stateFilePath,
|
|
3678
4669
|
...state
|
|
3679
4670
|
}, null, 2));
|
|
3680
4671
|
return;
|
|
3681
4672
|
}
|
|
3682
|
-
this.success(this.app.splitLogger("State", stateFilePath));
|
|
4673
|
+
this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
|
|
3683
4674
|
this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
|
|
3684
4675
|
if (state.migrations.length === 0) {
|
|
3685
4676
|
this.success("No tracked migrations found.");
|
|
@@ -3695,20 +4686,21 @@ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
|
|
|
3695
4686
|
//#region src/cli/commands/ModelsSyncCommand.ts
|
|
3696
4687
|
var ModelsSyncCommand = class extends _h3ravel_musket.Command {
|
|
3697
4688
|
signature = `models:sync
|
|
3698
|
-
{--schema= : Path to prisma schema file}
|
|
4689
|
+
{--schema= : Path to prisma schema file used when adapter introspection is unavailable}
|
|
3699
4690
|
{--models= : Path to models directory}
|
|
3700
4691
|
`;
|
|
3701
|
-
description = "Sync model declare attributes from
|
|
4692
|
+
description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
|
|
3702
4693
|
async handle() {
|
|
3703
4694
|
this.app.command = this;
|
|
3704
|
-
const result = this.app.
|
|
4695
|
+
const result = await this.app.syncModels({
|
|
3705
4696
|
schemaPath: this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : void 0,
|
|
3706
4697
|
modelsDir: this.option("models") ? (0, node_path.resolve)(String(this.option("models"))) : void 0
|
|
3707
4698
|
});
|
|
3708
4699
|
const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
|
|
3709
4700
|
this.success("SUCCESS: Model sync completed with the following results:");
|
|
3710
4701
|
[
|
|
3711
|
-
this.app.splitLogger("
|
|
4702
|
+
this.app.splitLogger("Source", result.source === "adapter" ? "adapter introspection" : "prisma schema"),
|
|
4703
|
+
...result.schemaPath ? [this.app.splitLogger("Schema", result.schemaPath)] : [],
|
|
3712
4704
|
this.app.splitLogger("Models", result.modelsDir),
|
|
3713
4705
|
this.app.splitLogger("Processed", String(result.total)),
|
|
3714
4706
|
...updatedLines,
|
|
@@ -4068,6 +5060,13 @@ var UniqueConstraintResolutionException = class extends ArkormException {
|
|
|
4068
5060
|
|
|
4069
5061
|
//#endregion
|
|
4070
5062
|
//#region src/relationship/RelationTableLoader.ts
|
|
5063
|
+
/**
|
|
5064
|
+
* Utility class responsible for loading data from relation tables, which are used to
|
|
5065
|
+
* manage relationships between models in Arkorm.
|
|
5066
|
+
*
|
|
5067
|
+
* @author Legacy (3m1n3nc3)
|
|
5068
|
+
* @since 2.0.0-next.0
|
|
5069
|
+
*/
|
|
4071
5070
|
var RelationTableLoader = class {
|
|
4072
5071
|
constructor(adapter) {
|
|
4073
5072
|
this.adapter = adapter;
|
|
@@ -4829,6 +5828,472 @@ var MorphToManyRelation = class extends Relation {
|
|
|
4829
5828
|
}
|
|
4830
5829
|
};
|
|
4831
5830
|
|
|
5831
|
+
//#endregion
|
|
5832
|
+
//#region src/relationship/SetBasedEagerLoader.ts
|
|
5833
|
+
/**
|
|
5834
|
+
* Utility class responsible for performing set-based eager loading of relationships for
|
|
5835
|
+
* a collection of models.
|
|
5836
|
+
*
|
|
5837
|
+
* @author Legacy (3m1n3nc3)
|
|
5838
|
+
* @since 2.0.0-next.2
|
|
5839
|
+
*/
|
|
5840
|
+
var SetBasedEagerLoader = class {
|
|
5841
|
+
constructor(models, relations) {
|
|
5842
|
+
this.models = models;
|
|
5843
|
+
this.relations = relations;
|
|
5844
|
+
}
|
|
5845
|
+
/**
|
|
5846
|
+
* Performs eager loading of all specified relationships for the set of models.
|
|
5847
|
+
*
|
|
5848
|
+
* @returns
|
|
5849
|
+
*/
|
|
5850
|
+
async load() {
|
|
5851
|
+
if (this.models.length === 0) return;
|
|
5852
|
+
await Promise.all(Object.entries(this.relations).map(async ([name, constraint]) => {
|
|
5853
|
+
await this.loadRelation(name, constraint);
|
|
5854
|
+
}));
|
|
5855
|
+
}
|
|
5856
|
+
/**
|
|
5857
|
+
* Loads a specific relationship for the set of models based on the relationship name
|
|
5858
|
+
* and an optional constraint.
|
|
5859
|
+
*
|
|
5860
|
+
* @param name The name of the relationship to load.
|
|
5861
|
+
* @param constraint An optional constraint to apply to the query.
|
|
5862
|
+
* @returns A promise that resolves when the relationship is loaded.
|
|
5863
|
+
*/
|
|
5864
|
+
async loadRelation(name, constraint) {
|
|
5865
|
+
const resolver = this.resolveRelationResolver(name);
|
|
5866
|
+
if (!resolver) return;
|
|
5867
|
+
const metadata = resolver.call(this.models[0]).getMetadata();
|
|
5868
|
+
switch (metadata.type) {
|
|
5869
|
+
case "belongsTo":
|
|
5870
|
+
await this.loadBelongsTo(name, resolver, metadata, constraint);
|
|
5871
|
+
return;
|
|
5872
|
+
case "belongsToMany":
|
|
5873
|
+
await this.loadBelongsToMany(name, metadata, constraint);
|
|
5874
|
+
return;
|
|
5875
|
+
case "hasMany":
|
|
5876
|
+
await this.loadHasMany(name, metadata, constraint);
|
|
5877
|
+
return;
|
|
5878
|
+
case "hasOne":
|
|
5879
|
+
await this.loadHasOne(name, resolver, metadata, constraint);
|
|
5880
|
+
return;
|
|
5881
|
+
case "hasManyThrough":
|
|
5882
|
+
await this.loadHasManyThrough(name, metadata, constraint);
|
|
5883
|
+
return;
|
|
5884
|
+
case "hasOneThrough":
|
|
5885
|
+
await this.loadHasOneThrough(name, resolver, metadata, constraint);
|
|
5886
|
+
return;
|
|
5887
|
+
default: await this.loadIndividually(name, resolver, constraint);
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
/**
|
|
5891
|
+
* Resolves the relation resolver function for a given relationship name by inspecting
|
|
5892
|
+
* the first model in the set.
|
|
5893
|
+
*
|
|
5894
|
+
* @param name The name of the relationship to resolve.
|
|
5895
|
+
* @returns The relation resolver function or null if not found.
|
|
5896
|
+
*/
|
|
5897
|
+
resolveRelationResolver(name) {
|
|
5898
|
+
const resolver = this.models[0][name];
|
|
5899
|
+
if (typeof resolver !== "function") return null;
|
|
5900
|
+
return resolver;
|
|
5901
|
+
}
|
|
5902
|
+
/**
|
|
5903
|
+
* Loads a "belongs to" relationship for the set of models.
|
|
5904
|
+
*
|
|
5905
|
+
* @param name The name of the relationship to load.
|
|
5906
|
+
* @param resolver The relation resolver function.
|
|
5907
|
+
* @param metadata The metadata for the relationship.
|
|
5908
|
+
* @param constraint An optional constraint to apply to the query.
|
|
5909
|
+
* @returns A promise that resolves when the relationship is loaded.
|
|
5910
|
+
*/
|
|
5911
|
+
async loadBelongsTo(name, resolver, metadata, constraint) {
|
|
5912
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.foreignKey));
|
|
5913
|
+
if (keys.length === 0) {
|
|
5914
|
+
this.models.forEach((model) => {
|
|
5915
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
5916
|
+
});
|
|
5917
|
+
return;
|
|
5918
|
+
}
|
|
5919
|
+
let query = metadata.relatedModel.query().whereIn(metadata.ownerKey, keys);
|
|
5920
|
+
query = this.applyConstraint(query, constraint);
|
|
5921
|
+
const relatedModels = (await query.get()).all();
|
|
5922
|
+
const relatedByOwnerKey = /* @__PURE__ */ new Map();
|
|
5923
|
+
relatedModels.forEach((related) => {
|
|
5924
|
+
const value = this.readModelAttribute(related, metadata.ownerKey);
|
|
5925
|
+
if (value == null) return;
|
|
5926
|
+
const lookupKey = this.toLookupKey(value);
|
|
5927
|
+
if (!relatedByOwnerKey.has(lookupKey)) relatedByOwnerKey.set(lookupKey, related);
|
|
5928
|
+
});
|
|
5929
|
+
this.models.forEach((model) => {
|
|
5930
|
+
const foreignValue = model.getAttribute(metadata.foreignKey);
|
|
5931
|
+
const relationValue = foreignValue == null ? void 0 : relatedByOwnerKey.get(this.toLookupKey(foreignValue));
|
|
5932
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
5933
|
+
});
|
|
5934
|
+
}
|
|
5935
|
+
/**
|
|
5936
|
+
* Loads a "has many" relationship for the set of models.
|
|
5937
|
+
*
|
|
5938
|
+
* @param name
|
|
5939
|
+
* @param metadata
|
|
5940
|
+
* @param constraint
|
|
5941
|
+
* @returns
|
|
5942
|
+
*/
|
|
5943
|
+
async loadHasMany(name, metadata, constraint) {
|
|
5944
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
5945
|
+
if (keys.length === 0) {
|
|
5946
|
+
this.models.forEach((model) => {
|
|
5947
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
5948
|
+
});
|
|
5949
|
+
return;
|
|
5950
|
+
}
|
|
5951
|
+
let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
|
|
5952
|
+
query = this.applyConstraint(query, constraint);
|
|
5953
|
+
const relatedModels = (await query.get()).all();
|
|
5954
|
+
const relatedByForeignKey = /* @__PURE__ */ new Map();
|
|
5955
|
+
relatedModels.forEach((related) => {
|
|
5956
|
+
const value = this.readModelAttribute(related, metadata.foreignKey);
|
|
5957
|
+
if (value == null) return;
|
|
5958
|
+
const lookupKey = this.toLookupKey(value);
|
|
5959
|
+
const bucket = relatedByForeignKey.get(lookupKey) ?? [];
|
|
5960
|
+
bucket.push(related);
|
|
5961
|
+
relatedByForeignKey.set(lookupKey, bucket);
|
|
5962
|
+
});
|
|
5963
|
+
this.models.forEach((model) => {
|
|
5964
|
+
const localValue = model.getAttribute(metadata.localKey);
|
|
5965
|
+
const related = localValue == null ? [] : relatedByForeignKey.get(this.toLookupKey(localValue)) ?? [];
|
|
5966
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
/**
|
|
5970
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
5971
|
+
*
|
|
5972
|
+
* @param name
|
|
5973
|
+
* @param metadata
|
|
5974
|
+
* @param constraint
|
|
5975
|
+
* @returns
|
|
5976
|
+
*/
|
|
5977
|
+
async loadBelongsToMany(name, metadata, constraint) {
|
|
5978
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
|
|
5979
|
+
if (parentKeys.length === 0) {
|
|
5980
|
+
this.models.forEach((model) => {
|
|
5981
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
5982
|
+
});
|
|
5983
|
+
return;
|
|
5984
|
+
}
|
|
5985
|
+
const pivotRows = await this.createRelationTableLoader().selectRows({
|
|
5986
|
+
table: metadata.throughTable,
|
|
5987
|
+
where: {
|
|
5988
|
+
type: "comparison",
|
|
5989
|
+
column: metadata.foreignPivotKey,
|
|
5990
|
+
operator: "in",
|
|
5991
|
+
value: parentKeys
|
|
5992
|
+
}
|
|
5993
|
+
});
|
|
5994
|
+
const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
|
|
5995
|
+
if (relatedIds.length === 0) {
|
|
5996
|
+
this.models.forEach((model) => {
|
|
5997
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
5998
|
+
});
|
|
5999
|
+
return;
|
|
6000
|
+
}
|
|
6001
|
+
let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
|
|
6002
|
+
query = this.applyConstraint(query, constraint);
|
|
6003
|
+
const relatedModels = (await query.get()).all();
|
|
6004
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
6005
|
+
relatedModels.forEach((related) => {
|
|
6006
|
+
const relatedValue = this.readModelAttribute(related, metadata.relatedKey);
|
|
6007
|
+
if (relatedValue == null) return;
|
|
6008
|
+
relatedByKey.set(this.toLookupKey(relatedValue), related);
|
|
6009
|
+
});
|
|
6010
|
+
const relatedKeysByParent = /* @__PURE__ */ new Map();
|
|
6011
|
+
pivotRows.forEach((row) => {
|
|
6012
|
+
const parentValue = row[metadata.foreignPivotKey];
|
|
6013
|
+
const relatedValue = row[metadata.relatedPivotKey];
|
|
6014
|
+
if (parentValue == null || relatedValue == null) return;
|
|
6015
|
+
const bucket = relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
6016
|
+
bucket.push(relatedValue);
|
|
6017
|
+
relatedKeysByParent.set(this.toLookupKey(parentValue), bucket);
|
|
6018
|
+
});
|
|
6019
|
+
this.models.forEach((model) => {
|
|
6020
|
+
const parentValue = model.getAttribute(metadata.parentKey);
|
|
6021
|
+
const related = (parentValue == null ? [] : relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? []).reduce((all, relatedValue) => {
|
|
6022
|
+
const candidate = relatedByKey.get(this.toLookupKey(relatedValue));
|
|
6023
|
+
if (candidate) all.push(candidate);
|
|
6024
|
+
return all;
|
|
6025
|
+
}, []);
|
|
6026
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
/**
|
|
6030
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
6031
|
+
*
|
|
6032
|
+
* @param name
|
|
6033
|
+
* @param resolver
|
|
6034
|
+
* @param metadata
|
|
6035
|
+
* @param constraint
|
|
6036
|
+
* @returns
|
|
6037
|
+
*/
|
|
6038
|
+
async loadHasOne(name, resolver, metadata, constraint) {
|
|
6039
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
6040
|
+
if (keys.length === 0) {
|
|
6041
|
+
this.models.forEach((model) => {
|
|
6042
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
6043
|
+
});
|
|
6044
|
+
return;
|
|
6045
|
+
}
|
|
6046
|
+
let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
|
|
6047
|
+
query = this.applyConstraint(query, constraint);
|
|
6048
|
+
const relatedModels = (await query.get()).all();
|
|
6049
|
+
const relatedByForeignKey = /* @__PURE__ */ new Map();
|
|
6050
|
+
relatedModels.forEach((related) => {
|
|
6051
|
+
const value = this.readModelAttribute(related, metadata.foreignKey);
|
|
6052
|
+
if (value == null) return;
|
|
6053
|
+
const lookupKey = this.toLookupKey(value);
|
|
6054
|
+
if (!relatedByForeignKey.has(lookupKey)) relatedByForeignKey.set(lookupKey, related);
|
|
6055
|
+
});
|
|
6056
|
+
this.models.forEach((model) => {
|
|
6057
|
+
const localValue = model.getAttribute(metadata.localKey);
|
|
6058
|
+
const relationValue = localValue == null ? void 0 : relatedByForeignKey.get(this.toLookupKey(localValue));
|
|
6059
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
/**
|
|
6063
|
+
* Loads a "has many through" relationship for the set of models.
|
|
6064
|
+
*
|
|
6065
|
+
* @param name
|
|
6066
|
+
* @param metadata
|
|
6067
|
+
* @param constraint
|
|
6068
|
+
* @returns
|
|
6069
|
+
*/
|
|
6070
|
+
async loadHasManyThrough(name, metadata, constraint) {
|
|
6071
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
6072
|
+
if (parentKeys.length === 0) {
|
|
6073
|
+
this.models.forEach((model) => {
|
|
6074
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
6075
|
+
});
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
6079
|
+
table: metadata.throughTable,
|
|
6080
|
+
where: {
|
|
6081
|
+
type: "comparison",
|
|
6082
|
+
column: metadata.firstKey,
|
|
6083
|
+
operator: "in",
|
|
6084
|
+
value: parentKeys
|
|
6085
|
+
}
|
|
6086
|
+
});
|
|
6087
|
+
const intermediateKeys = this.collectUniqueRowValues(throughRows, metadata.secondLocalKey);
|
|
6088
|
+
if (intermediateKeys.length === 0) {
|
|
6089
|
+
this.models.forEach((model) => {
|
|
6090
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
6091
|
+
});
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
6095
|
+
query = this.applyConstraint(query, constraint);
|
|
6096
|
+
const relatedModels = (await query.get()).all();
|
|
6097
|
+
const relatedByIntermediate = /* @__PURE__ */ new Map();
|
|
6098
|
+
relatedModels.forEach((related) => {
|
|
6099
|
+
const relatedValue = this.readModelAttribute(related, metadata.secondKey);
|
|
6100
|
+
if (relatedValue == null) return;
|
|
6101
|
+
const bucket = relatedByIntermediate.get(this.toLookupKey(relatedValue)) ?? [];
|
|
6102
|
+
bucket.push(related);
|
|
6103
|
+
relatedByIntermediate.set(this.toLookupKey(relatedValue), bucket);
|
|
6104
|
+
});
|
|
6105
|
+
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
6106
|
+
throughRows.forEach((row) => {
|
|
6107
|
+
const parentValue = row[metadata.firstKey];
|
|
6108
|
+
const intermediateValue = row[metadata.secondLocalKey];
|
|
6109
|
+
if (parentValue == null || intermediateValue == null) return;
|
|
6110
|
+
const bucket = intermediateByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
6111
|
+
bucket.push(intermediateValue);
|
|
6112
|
+
intermediateByParent.set(this.toLookupKey(parentValue), bucket);
|
|
6113
|
+
});
|
|
6114
|
+
this.models.forEach((model) => {
|
|
6115
|
+
const parentValue = model.getAttribute(metadata.localKey);
|
|
6116
|
+
const related = (parentValue == null ? [] : intermediateByParent.get(this.toLookupKey(parentValue)) ?? []).flatMap((intermediateValue) => relatedByIntermediate.get(this.toLookupKey(intermediateValue)) ?? []);
|
|
6117
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
6118
|
+
});
|
|
6119
|
+
}
|
|
6120
|
+
/**
|
|
6121
|
+
* Loads a "has one through" relationship for the set of models.
|
|
6122
|
+
*
|
|
6123
|
+
* @param name
|
|
6124
|
+
* @param resolver
|
|
6125
|
+
* @param metadata
|
|
6126
|
+
* @param constraint
|
|
6127
|
+
* @returns
|
|
6128
|
+
*/
|
|
6129
|
+
async loadHasOneThrough(name, resolver, metadata, constraint) {
|
|
6130
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
6131
|
+
if (parentKeys.length === 0) {
|
|
6132
|
+
this.models.forEach((model) => {
|
|
6133
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
6134
|
+
});
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
6138
|
+
table: metadata.throughTable,
|
|
6139
|
+
where: {
|
|
6140
|
+
type: "comparison",
|
|
6141
|
+
column: metadata.firstKey,
|
|
6142
|
+
operator: "in",
|
|
6143
|
+
value: parentKeys
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
6146
|
+
const intermediateKeys = this.collectUniqueRowValues(throughRows, metadata.secondLocalKey);
|
|
6147
|
+
if (intermediateKeys.length === 0) {
|
|
6148
|
+
this.models.forEach((model) => {
|
|
6149
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
6150
|
+
});
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
6154
|
+
query = this.applyConstraint(query, constraint);
|
|
6155
|
+
const relatedModels = (await query.get()).all();
|
|
6156
|
+
const relatedByIntermediate = /* @__PURE__ */ new Map();
|
|
6157
|
+
relatedModels.forEach((related) => {
|
|
6158
|
+
const relatedValue = this.readModelAttribute(related, metadata.secondKey);
|
|
6159
|
+
if (relatedValue == null) return;
|
|
6160
|
+
const lookupKey = this.toLookupKey(relatedValue);
|
|
6161
|
+
if (!relatedByIntermediate.has(lookupKey)) relatedByIntermediate.set(lookupKey, related);
|
|
6162
|
+
});
|
|
6163
|
+
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
6164
|
+
throughRows.forEach((row) => {
|
|
6165
|
+
const parentValue = row[metadata.firstKey];
|
|
6166
|
+
const intermediateValue = row[metadata.secondLocalKey];
|
|
6167
|
+
if (parentValue == null || intermediateValue == null) return;
|
|
6168
|
+
const lookupKey = this.toLookupKey(parentValue);
|
|
6169
|
+
if (!intermediateByParent.has(lookupKey)) intermediateByParent.set(lookupKey, intermediateValue);
|
|
6170
|
+
});
|
|
6171
|
+
this.models.forEach((model) => {
|
|
6172
|
+
const parentValue = model.getAttribute(metadata.localKey);
|
|
6173
|
+
const intermediateValue = parentValue == null ? void 0 : intermediateByParent.get(this.toLookupKey(parentValue));
|
|
6174
|
+
const relationValue = intermediateValue == null ? void 0 : relatedByIntermediate.get(this.toLookupKey(intermediateValue));
|
|
6175
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
6176
|
+
});
|
|
6177
|
+
}
|
|
6178
|
+
/**
|
|
6179
|
+
* Fallback method to load relationships individually for each model when the
|
|
6180
|
+
* relationship type is not supported for set-based loading.
|
|
6181
|
+
*
|
|
6182
|
+
* @param name
|
|
6183
|
+
* @param resolver
|
|
6184
|
+
* @param constraint
|
|
6185
|
+
*/
|
|
6186
|
+
async loadIndividually(name, resolver, constraint) {
|
|
6187
|
+
await Promise.all(this.models.map(async (model) => {
|
|
6188
|
+
const relation = resolver.call(model);
|
|
6189
|
+
if (constraint) relation.constrain(constraint);
|
|
6190
|
+
model.setLoadedRelation(name, await relation.getResults());
|
|
6191
|
+
}));
|
|
6192
|
+
}
|
|
6193
|
+
/**
|
|
6194
|
+
* Applies an eager load constraint to a query if provided.
|
|
6195
|
+
*
|
|
6196
|
+
* @param query
|
|
6197
|
+
* @param constraint
|
|
6198
|
+
* @returns
|
|
6199
|
+
*/
|
|
6200
|
+
applyConstraint(query, constraint) {
|
|
6201
|
+
if (!constraint) return query;
|
|
6202
|
+
return constraint(query) ?? query;
|
|
6203
|
+
}
|
|
6204
|
+
/**
|
|
6205
|
+
* Collects unique values from the set of models based on a resolver function, which
|
|
6206
|
+
* is used to extract the value from each model.
|
|
6207
|
+
*
|
|
6208
|
+
* @param resolve A function that takes a model and returns the value to be collected.
|
|
6209
|
+
* @returns An array of unique values.
|
|
6210
|
+
*/
|
|
6211
|
+
collectUniqueKeys(resolve) {
|
|
6212
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6213
|
+
const values = [];
|
|
6214
|
+
this.models.forEach((model) => {
|
|
6215
|
+
const value = resolve(model);
|
|
6216
|
+
if (value == null) return;
|
|
6217
|
+
const lookupKey = this.toLookupKey(value);
|
|
6218
|
+
if (seen.has(lookupKey)) return;
|
|
6219
|
+
seen.add(lookupKey);
|
|
6220
|
+
values.push(value);
|
|
6221
|
+
});
|
|
6222
|
+
return values;
|
|
6223
|
+
}
|
|
6224
|
+
/**
|
|
6225
|
+
* Collects unique values from an array of database rows based on a specified key, which
|
|
6226
|
+
* is used to extract the value from each row.
|
|
6227
|
+
*
|
|
6228
|
+
* @param rows An array of database rows.
|
|
6229
|
+
* @param key The key to extract values from each row.
|
|
6230
|
+
* @returns An array of unique values.
|
|
6231
|
+
*/
|
|
6232
|
+
collectUniqueRowValues(rows, key) {
|
|
6233
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6234
|
+
const values = [];
|
|
6235
|
+
rows.forEach((row) => {
|
|
6236
|
+
const value = row[key];
|
|
6237
|
+
if (value == null) return;
|
|
6238
|
+
const lookupKey = this.toLookupKey(value);
|
|
6239
|
+
if (seen.has(lookupKey)) return;
|
|
6240
|
+
seen.add(lookupKey);
|
|
6241
|
+
values.push(value);
|
|
6242
|
+
});
|
|
6243
|
+
return values;
|
|
6244
|
+
}
|
|
6245
|
+
/**
|
|
6246
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
6247
|
+
*
|
|
6248
|
+
* @returns
|
|
6249
|
+
*/
|
|
6250
|
+
createRelationTableLoader() {
|
|
6251
|
+
return new RelationTableLoader(this.resolveAdapter());
|
|
6252
|
+
}
|
|
6253
|
+
/**
|
|
6254
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
6255
|
+
*
|
|
6256
|
+
* @returns
|
|
6257
|
+
*/
|
|
6258
|
+
resolveAdapter() {
|
|
6259
|
+
const adapter = this.models[0].constructor.getAdapter?.();
|
|
6260
|
+
if (!adapter) throw new Error("Set-based eager loading requires a configured adapter.");
|
|
6261
|
+
return adapter;
|
|
6262
|
+
}
|
|
6263
|
+
/**
|
|
6264
|
+
* Reads an attribute value from a model using the getAttribute method, which is used
|
|
6265
|
+
* to access model attributes in a way that is compatible with Arkorm's internal model structure.
|
|
6266
|
+
*
|
|
6267
|
+
* @param model The model to read the attribute from.
|
|
6268
|
+
* @param key The name of the attribute to read.
|
|
6269
|
+
* @returns
|
|
6270
|
+
*/
|
|
6271
|
+
readModelAttribute(model, key) {
|
|
6272
|
+
return model.getAttribute?.(key);
|
|
6273
|
+
}
|
|
6274
|
+
/**
|
|
6275
|
+
* Resolves the default result for a relationship when no related models are found.
|
|
6276
|
+
*
|
|
6277
|
+
* @param resolver
|
|
6278
|
+
* @param model
|
|
6279
|
+
* @returns
|
|
6280
|
+
*/
|
|
6281
|
+
resolveSingleDefault(resolver, model) {
|
|
6282
|
+
return resolver.call(model).resolveDefaultResult?.() ?? null;
|
|
6283
|
+
}
|
|
6284
|
+
/**
|
|
6285
|
+
* Generates a unique lookup key for a given value, which is used to store and retrieve
|
|
6286
|
+
* values in maps during the eager loading process.
|
|
6287
|
+
*
|
|
6288
|
+
* @param value The value to generate a lookup key for.
|
|
6289
|
+
* @returns A unique string representing the value.
|
|
6290
|
+
*/
|
|
6291
|
+
toLookupKey(value) {
|
|
6292
|
+
if (value instanceof Date) return `date:${value.toISOString()}`;
|
|
6293
|
+
return `${typeof value}:${String(value)}`;
|
|
6294
|
+
}
|
|
6295
|
+
};
|
|
6296
|
+
|
|
4832
6297
|
//#endregion
|
|
4833
6298
|
//#region src/URLDriver.ts
|
|
4834
6299
|
/**
|
|
@@ -5727,21 +7192,20 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5727
7192
|
* @returns
|
|
5728
7193
|
*/
|
|
5729
7194
|
async get() {
|
|
7195
|
+
const useAdapterRelationFeatures = this.canExecuteRelationFeaturesInAdapter();
|
|
5730
7196
|
const relationCache = /* @__PURE__ */ new WeakMap();
|
|
5731
7197
|
const rows = await this.executeReadRows();
|
|
5732
7198
|
const normalizedRows = this.randomOrderEnabled ? this.shuffleRows(rows) : rows;
|
|
5733
7199
|
const models = await this.model.hydrateManyRetrieved(normalizedRows);
|
|
5734
7200
|
let filteredModels = models;
|
|
5735
|
-
if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.hasBaseWhereConstraints()) {
|
|
7201
|
+
if (this.hasRelationFilters() && !useAdapterRelationFeatures) if (this.hasOrRelationFilters() && this.hasBaseWhereConstraints()) {
|
|
5736
7202
|
const baseIds = new Set(models.map((model) => this.getModelId(model)).filter((id) => id != null));
|
|
5737
7203
|
const allRows = await this.executeReadRows(this.buildSoftDeleteOnlyWhere(), true);
|
|
5738
7204
|
const allModels = this.model.hydrateMany(allRows);
|
|
5739
7205
|
filteredModels = await this.filterModelsByRelationConstraints(allModels, relationCache, baseIds);
|
|
5740
7206
|
} else filteredModels = await this.filterModelsByRelationConstraints(models, relationCache);
|
|
5741
|
-
if (this.hasRelationAggregates()) await this.applyRelationAggregates(filteredModels, relationCache);
|
|
5742
|
-
await
|
|
5743
|
-
await model.load(this.eagerLoads);
|
|
5744
|
-
}));
|
|
7207
|
+
if (this.hasRelationAggregates() && !useAdapterRelationFeatures) await this.applyRelationAggregates(filteredModels, relationCache);
|
|
7208
|
+
await this.eagerLoadModels(filteredModels);
|
|
5745
7209
|
return new ArkormCollection(filteredModels);
|
|
5746
7210
|
}
|
|
5747
7211
|
/**
|
|
@@ -5751,20 +7215,20 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5751
7215
|
* @returns
|
|
5752
7216
|
*/
|
|
5753
7217
|
async first() {
|
|
5754
|
-
if (this.hasRelationFilters() || this.hasRelationAggregates()) return (await this.get()).all()[0] ?? null;
|
|
7218
|
+
if ((this.hasRelationFilters() || this.hasRelationAggregates()) && !this.canExecuteRelationFeaturesInAdapter()) return (await this.get()).all()[0] ?? null;
|
|
5755
7219
|
if (this.randomOrderEnabled) {
|
|
5756
7220
|
const rows = await this.executeReadRows();
|
|
5757
7221
|
if (rows.length === 0) return null;
|
|
5758
7222
|
const row = this.shuffleRows(rows)[0];
|
|
5759
7223
|
if (!row) return null;
|
|
5760
7224
|
const model = await this.model.hydrateRetrieved(row);
|
|
5761
|
-
await
|
|
7225
|
+
await this.eagerLoadModels([model]);
|
|
5762
7226
|
return model;
|
|
5763
7227
|
}
|
|
5764
7228
|
const row = await this.executeReadRow();
|
|
5765
7229
|
if (!row) return null;
|
|
5766
7230
|
const model = await this.model.hydrateRetrieved(row);
|
|
5767
|
-
await
|
|
7231
|
+
await this.eagerLoadModels([model]);
|
|
5768
7232
|
return model;
|
|
5769
7233
|
}
|
|
5770
7234
|
/**
|
|
@@ -5928,6 +7392,13 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5928
7392
|
operation: "update",
|
|
5929
7393
|
model: this.model.name
|
|
5930
7394
|
});
|
|
7395
|
+
const directSpec = this.tryBuildUpdateSpec(where, data);
|
|
7396
|
+
const adapter = this.requireAdapter();
|
|
7397
|
+
if (!this.isUniqueWhere(where) && directSpec && typeof adapter.updateFirst === "function") {
|
|
7398
|
+
const updated = await adapter.updateFirst(directSpec);
|
|
7399
|
+
if (!updated) throw new ModelNotFoundException(this.model.name, "Record not found for update operation.", { operation: "update" });
|
|
7400
|
+
return this.model.hydrate(updated);
|
|
7401
|
+
}
|
|
5931
7402
|
const uniqueWhere = await this.resolveUniqueWhere(where);
|
|
5932
7403
|
const updated = await this.executeUpdateRow(uniqueWhere, data);
|
|
5933
7404
|
return this.model.hydrate(updated);
|
|
@@ -5954,6 +7425,13 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5954
7425
|
* @returns
|
|
5955
7426
|
*/
|
|
5956
7427
|
async updateOrInsert(attributes, values = {}) {
|
|
7428
|
+
if (typeof values !== "function" && this.adapter?.capabilities?.upsert && typeof this.requireAdapter().upsert === "function") {
|
|
7429
|
+
await this.executeUpsertRows([{
|
|
7430
|
+
...attributes,
|
|
7431
|
+
...values
|
|
7432
|
+
}], Object.keys(attributes), Object.keys(values));
|
|
7433
|
+
return true;
|
|
7434
|
+
}
|
|
5957
7435
|
const exists = await this.clone().where(attributes).first() != null;
|
|
5958
7436
|
const resolvedValues = typeof values === "function" ? await values(exists) : values;
|
|
5959
7437
|
if (!exists) {
|
|
@@ -5976,6 +7454,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
5976
7454
|
async upsert(values, uniqueBy, update = null) {
|
|
5977
7455
|
if (values.length === 0) return 0;
|
|
5978
7456
|
const uniqueKeys = Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy];
|
|
7457
|
+
if (this.adapter?.capabilities?.upsert && typeof this.requireAdapter().upsert === "function") return await this.executeUpsertRows(values, uniqueKeys, update ?? void 0);
|
|
5979
7458
|
let affected = 0;
|
|
5980
7459
|
for (const row of values) {
|
|
5981
7460
|
const attributes = uniqueKeys.reduce((all, key) => {
|
|
@@ -6003,6 +7482,13 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6003
7482
|
operation: "delete",
|
|
6004
7483
|
model: this.model.name
|
|
6005
7484
|
});
|
|
7485
|
+
const directSpec = this.tryBuildDeleteSpec(where);
|
|
7486
|
+
const adapter = this.requireAdapter();
|
|
7487
|
+
if (!this.isUniqueWhere(where) && directSpec && typeof adapter.deleteFirst === "function") {
|
|
7488
|
+
const deleted = await adapter.deleteFirst(directSpec);
|
|
7489
|
+
if (!deleted) throw new ModelNotFoundException(this.model.name, "Record not found for delete operation.", { operation: "delete" });
|
|
7490
|
+
return this.model.hydrate(deleted);
|
|
7491
|
+
}
|
|
6006
7492
|
const uniqueWhere = await this.resolveUniqueWhere(where);
|
|
6007
7493
|
const deleted = await this.executeDeleteRow(uniqueWhere);
|
|
6008
7494
|
return this.model.hydrate(deleted);
|
|
@@ -6019,6 +7505,14 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6019
7505
|
values
|
|
6020
7506
|
};
|
|
6021
7507
|
}
|
|
7508
|
+
tryBuildUpsertSpec(values, uniqueBy, updateColumns) {
|
|
7509
|
+
return {
|
|
7510
|
+
target: this.buildQueryTarget(),
|
|
7511
|
+
values,
|
|
7512
|
+
uniqueBy,
|
|
7513
|
+
updateColumns
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
6022
7516
|
tryBuildInsertOrIgnoreManySpec(values) {
|
|
6023
7517
|
return {
|
|
6024
7518
|
...this.tryBuildInsertManySpec(values),
|
|
@@ -6057,7 +7551,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6057
7551
|
* @returns
|
|
6058
7552
|
*/
|
|
6059
7553
|
async count() {
|
|
6060
|
-
if (this.hasRelationFilters()) return (await this.get()).all().length;
|
|
7554
|
+
if (this.hasRelationFilters() && !this.canExecuteRelationFeaturesInAdapter()) return (await this.get()).all().length;
|
|
6061
7555
|
return this.executeReadCount();
|
|
6062
7556
|
}
|
|
6063
7557
|
/**
|
|
@@ -6066,7 +7560,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6066
7560
|
* @returns
|
|
6067
7561
|
*/
|
|
6068
7562
|
async exists() {
|
|
6069
|
-
if (this.hasRelationFilters()) return await this.count() > 0;
|
|
7563
|
+
if (this.hasRelationFilters() && !this.canExecuteRelationFeaturesInAdapter()) return await this.count() > 0;
|
|
6070
7564
|
return await this.executeReadExists();
|
|
6071
7565
|
}
|
|
6072
7566
|
/**
|
|
@@ -6241,7 +7735,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6241
7735
|
*/
|
|
6242
7736
|
async paginate(perPage = 15, page = void 0, options = {}) {
|
|
6243
7737
|
const currentPage = this.resolvePaginationPage(page, options);
|
|
6244
|
-
if (this.hasRelationFilters() || this.hasRelationAggregates()) {
|
|
7738
|
+
if ((this.hasRelationFilters() || this.hasRelationAggregates()) && !this.canExecuteRelationFeaturesInAdapter()) {
|
|
6245
7739
|
const pageSize = Math.max(1, perPage);
|
|
6246
7740
|
const rows = (await this.get()).all();
|
|
6247
7741
|
const start = (currentPage - 1) * pageSize;
|
|
@@ -6260,7 +7754,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6260
7754
|
*/
|
|
6261
7755
|
async simplePaginate(perPage = 15, page = void 0, options = {}) {
|
|
6262
7756
|
const currentPage = this.resolvePaginationPage(page, options);
|
|
6263
|
-
if (this.hasRelationFilters() || this.hasRelationAggregates()) {
|
|
7757
|
+
if ((this.hasRelationFilters() || this.hasRelationAggregates()) && !this.canExecuteRelationFeaturesInAdapter()) {
|
|
6264
7758
|
const pageSize = Math.max(1, perPage);
|
|
6265
7759
|
const rows = (await this.get()).all();
|
|
6266
7760
|
const start = (currentPage - 1) * pageSize;
|
|
@@ -6364,6 +7858,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6364
7858
|
};
|
|
6365
7859
|
});
|
|
6366
7860
|
}
|
|
7861
|
+
async eagerLoadModels(models) {
|
|
7862
|
+
if (models.length === 0 || Object.keys(this.eagerLoads).length === 0) return;
|
|
7863
|
+
await new SetBasedEagerLoader(models, this.eagerLoads).load();
|
|
7864
|
+
}
|
|
6367
7865
|
normalizeRelationLoadSelect(select) {
|
|
6368
7866
|
if (Array.isArray(select) || typeof select !== "object" || !select) return null;
|
|
6369
7867
|
const entries = Object.entries(select);
|
|
@@ -6601,7 +8099,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6601
8099
|
const columns = this.tryBuildQuerySelectColumns();
|
|
6602
8100
|
const orderBy = this.tryBuildQueryOrderBy();
|
|
6603
8101
|
const condition = this.buildQueryWhereCondition(softDeleteOnly);
|
|
8102
|
+
const relationFilters = this.tryBuildRelationFilterSpecs();
|
|
8103
|
+
const relationAggregates = this.tryBuildRelationAggregateSpecs();
|
|
6604
8104
|
if (columns === null || orderBy === null || condition === null) return null;
|
|
8105
|
+
if (this.hasRelationFilters() && this.canExecuteRelationFiltersInAdapter() && relationFilters === null) return null;
|
|
8106
|
+
if (this.hasRelationAggregates() && this.canExecuteRelationAggregatesInAdapter() && relationAggregates === null) return null;
|
|
6605
8107
|
return {
|
|
6606
8108
|
target: this.buildQueryTarget(),
|
|
6607
8109
|
columns,
|
|
@@ -6609,15 +8111,20 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6609
8111
|
orderBy,
|
|
6610
8112
|
limit: this.limitValue,
|
|
6611
8113
|
offset: this.offsetValue,
|
|
6612
|
-
relationLoads: this.queryRelationLoads
|
|
8114
|
+
relationLoads: this.queryRelationLoads,
|
|
8115
|
+
relationFilters: this.canExecuteRelationFiltersInAdapter() ? relationFilters ?? void 0 : void 0,
|
|
8116
|
+
relationAggregates: this.canExecuteRelationAggregatesInAdapter() ? relationAggregates ?? void 0 : void 0
|
|
6613
8117
|
};
|
|
6614
8118
|
}
|
|
6615
8119
|
tryBuildAggregateSpec() {
|
|
6616
8120
|
const condition = this.buildQueryWhereCondition(false);
|
|
8121
|
+
const relationFilters = this.tryBuildRelationFilterSpecs();
|
|
6617
8122
|
if (condition === null) return null;
|
|
8123
|
+
if (this.hasRelationFilters() && this.canExecuteRelationFiltersInAdapter() && relationFilters === null) return null;
|
|
6618
8124
|
return {
|
|
6619
8125
|
target: this.buildQueryTarget(),
|
|
6620
8126
|
where: condition,
|
|
8127
|
+
relationFilters: this.canExecuteRelationFiltersInAdapter() ? relationFilters ?? void 0 : void 0,
|
|
6621
8128
|
aggregate: { type: "count" }
|
|
6622
8129
|
};
|
|
6623
8130
|
}
|
|
@@ -6687,6 +8194,14 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6687
8194
|
}
|
|
6688
8195
|
return inserted;
|
|
6689
8196
|
}
|
|
8197
|
+
async executeUpsertRows(values, uniqueBy, updateColumns) {
|
|
8198
|
+
const adapter = this.requireAdapter();
|
|
8199
|
+
if (typeof adapter.upsert !== "function") throw new UnsupportedAdapterFeatureException("Upsert is not supported by the current adapter.", {
|
|
8200
|
+
operation: "query.upsert",
|
|
8201
|
+
model: this.model.name
|
|
8202
|
+
});
|
|
8203
|
+
return await adapter.upsert(this.tryBuildUpsertSpec(values, uniqueBy, updateColumns));
|
|
8204
|
+
}
|
|
6690
8205
|
async executeUpdateRow(where, values) {
|
|
6691
8206
|
const adapter = this.requireAdapter();
|
|
6692
8207
|
const spec = this.tryBuildUpdateSpec(where, values);
|
|
@@ -6815,6 +8330,71 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6815
8330
|
hasRelationAggregates() {
|
|
6816
8331
|
return this.relationAggregates.length > 0;
|
|
6817
8332
|
}
|
|
8333
|
+
canExecuteRelationFiltersInAdapter() {
|
|
8334
|
+
const adapter = this.adapter;
|
|
8335
|
+
if (!this.hasRelationFilters()) return false;
|
|
8336
|
+
return adapter?.capabilities?.relationFilters === true && this.tryBuildRelationFilterSpecs() !== null;
|
|
8337
|
+
}
|
|
8338
|
+
canExecuteRelationAggregatesInAdapter() {
|
|
8339
|
+
const adapter = this.adapter;
|
|
8340
|
+
if (!this.hasRelationAggregates()) return false;
|
|
8341
|
+
return adapter?.capabilities?.relationAggregates === true && this.tryBuildRelationAggregateSpecs() !== null;
|
|
8342
|
+
}
|
|
8343
|
+
canExecuteRelationFeaturesInAdapter() {
|
|
8344
|
+
const filtersSupported = !this.hasRelationFilters() || this.canExecuteRelationFiltersInAdapter();
|
|
8345
|
+
const aggregatesSupported = !this.hasRelationAggregates() || this.canExecuteRelationAggregatesInAdapter();
|
|
8346
|
+
return filtersSupported && aggregatesSupported;
|
|
8347
|
+
}
|
|
8348
|
+
tryBuildRelationFilterSpecs() {
|
|
8349
|
+
return this.relationFilters.reduce((specs, filter) => {
|
|
8350
|
+
if (!specs) return null;
|
|
8351
|
+
const metadata = this.model.getRelationMetadata(filter.relation);
|
|
8352
|
+
if (!this.isSqlRelationFeatureMetadata(metadata)) return null;
|
|
8353
|
+
const where = this.tryBuildRelationConstraintWhere(filter.relation, filter.callback);
|
|
8354
|
+
if (where === null) return null;
|
|
8355
|
+
specs.push({
|
|
8356
|
+
relation: filter.relation,
|
|
8357
|
+
operator: filter.operator,
|
|
8358
|
+
count: filter.count,
|
|
8359
|
+
boolean: filter.boolean,
|
|
8360
|
+
where
|
|
8361
|
+
});
|
|
8362
|
+
return specs;
|
|
8363
|
+
}, []);
|
|
8364
|
+
}
|
|
8365
|
+
tryBuildRelationAggregateSpecs() {
|
|
8366
|
+
return this.relationAggregates.reduce((specs, aggregate) => {
|
|
8367
|
+
if (!specs) return null;
|
|
8368
|
+
const metadata = this.model.getRelationMetadata(aggregate.relation);
|
|
8369
|
+
if (!this.isSqlRelationFeatureMetadata(metadata)) return null;
|
|
8370
|
+
const where = this.tryBuildRelationConstraintWhere(aggregate.relation);
|
|
8371
|
+
if (where === null) return null;
|
|
8372
|
+
specs.push({
|
|
8373
|
+
relation: aggregate.relation,
|
|
8374
|
+
type: aggregate.type,
|
|
8375
|
+
column: aggregate.column,
|
|
8376
|
+
alias: this.buildAggregateAttributeKey(aggregate),
|
|
8377
|
+
where
|
|
8378
|
+
});
|
|
8379
|
+
return specs;
|
|
8380
|
+
}, []);
|
|
8381
|
+
}
|
|
8382
|
+
tryBuildRelationConstraintWhere(relation, callback) {
|
|
8383
|
+
const metadata = this.model.getRelationMetadata(relation);
|
|
8384
|
+
if (!this.isSqlRelationFeatureMetadata(metadata)) return null;
|
|
8385
|
+
const relatedQuery = metadata?.relatedModel.query();
|
|
8386
|
+
if (!relatedQuery) return null;
|
|
8387
|
+
if (callback) {
|
|
8388
|
+
const constrained = callback(relatedQuery);
|
|
8389
|
+
if (constrained && constrained !== relatedQuery) return null;
|
|
8390
|
+
}
|
|
8391
|
+
if (relatedQuery.hasRelationFilters() || relatedQuery.hasRelationAggregates() || relatedQuery.queryRelationLoads || relatedQuery.querySelect || relatedQuery.queryOrderBy || relatedQuery.offsetValue !== void 0 || relatedQuery.limitValue !== void 0 || relatedQuery.randomOrderEnabled) return null;
|
|
8392
|
+
return relatedQuery.buildQueryWhereCondition(false);
|
|
8393
|
+
}
|
|
8394
|
+
isSqlRelationFeatureMetadata(metadata) {
|
|
8395
|
+
if (!metadata) return false;
|
|
8396
|
+
return metadata.type === "hasMany" || metadata.type === "hasOne" || metadata.type === "belongsTo" || metadata.type === "belongsToMany" || metadata.type === "hasOneThrough" || metadata.type === "hasManyThrough";
|
|
8397
|
+
}
|
|
6818
8398
|
async filterModelsByRelationConstraints(models, relationCache, baseIds) {
|
|
6819
8399
|
return (await Promise.all(models.map(async (model) => {
|
|
6820
8400
|
let result = null;
|
|
@@ -6971,6 +8551,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
6971
8551
|
*/
|
|
6972
8552
|
var Model = class Model {
|
|
6973
8553
|
static lifecycleStates = /* @__PURE__ */ new WeakMap();
|
|
8554
|
+
static emittedDeprecationWarnings = /* @__PURE__ */ new Set();
|
|
6974
8555
|
static eventsSuppressed = 0;
|
|
6975
8556
|
static factoryClass;
|
|
6976
8557
|
static adapter;
|
|
@@ -7014,12 +8595,24 @@ var Model = class Model {
|
|
|
7014
8595
|
}
|
|
7015
8596
|
});
|
|
7016
8597
|
}
|
|
8598
|
+
static emitDeprecationWarning(code, message) {
|
|
8599
|
+
if (Model.emittedDeprecationWarnings.has(code)) return;
|
|
8600
|
+
Model.emittedDeprecationWarnings.add(code);
|
|
8601
|
+
process.emitWarning(message, {
|
|
8602
|
+
type: "DeprecationWarning",
|
|
8603
|
+
code
|
|
8604
|
+
});
|
|
8605
|
+
}
|
|
7017
8606
|
/**
|
|
7018
|
-
* Set the Prisma client delegates for all models.
|
|
7019
|
-
*
|
|
7020
|
-
* @
|
|
8607
|
+
* Set the Prisma client delegates for all models.
|
|
8608
|
+
*
|
|
8609
|
+
* @deprecated Use Model.setAdapter(createPrismaDatabaseAdapter(...)) or another
|
|
8610
|
+
* adapter-first bootstrap path instead.
|
|
8611
|
+
*
|
|
8612
|
+
* @param client
|
|
7021
8613
|
*/
|
|
7022
8614
|
static setClient(client) {
|
|
8615
|
+
Model.emitDeprecationWarning("ARKORM_SET_CLIENT_DEPRECATED", "Model.setClient() is deprecated and will be removed in Arkorm 3.0. Use Model.setAdapter(createPrismaDatabaseAdapter(...)) or another adapter-first setup path instead.");
|
|
7023
8616
|
this.client = client;
|
|
7024
8617
|
}
|
|
7025
8618
|
static setAdapter(adapter) {
|
|
@@ -7252,6 +8845,8 @@ var Model = class Model {
|
|
|
7252
8845
|
static getAdapter() {
|
|
7253
8846
|
ensureArkormConfigLoading();
|
|
7254
8847
|
if (this.adapter) return this.adapter;
|
|
8848
|
+
const runtimeAdapter = getRuntimeAdapter();
|
|
8849
|
+
if (runtimeAdapter) return runtimeAdapter;
|
|
7255
8850
|
const client = getActiveTransactionClient() ?? this.client ?? getRuntimePrismaClient();
|
|
7256
8851
|
if (!client || typeof client !== "object") return void 0;
|
|
7257
8852
|
return createPrismaCompatibilityAdapter(client);
|
|
@@ -7556,14 +9151,11 @@ var Model = class Model {
|
|
|
7556
9151
|
*/
|
|
7557
9152
|
async load(relations) {
|
|
7558
9153
|
const relationMap = this.normalizeRelationMap(relations);
|
|
7559
|
-
await
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
const results = await relation.getResults();
|
|
7565
|
-
this.attributes[name] = results;
|
|
7566
|
-
}));
|
|
9154
|
+
await new SetBasedEagerLoader([this], relationMap).load();
|
|
9155
|
+
return this;
|
|
9156
|
+
}
|
|
9157
|
+
setLoadedRelation(name, value) {
|
|
9158
|
+
this.attributes[name] = value;
|
|
7567
9159
|
return this;
|
|
7568
9160
|
}
|
|
7569
9161
|
/**
|
|
@@ -8061,6 +9653,7 @@ exports.MakeMigrationCommand = MakeMigrationCommand;
|
|
|
8061
9653
|
exports.MakeModelCommand = MakeModelCommand;
|
|
8062
9654
|
exports.MakeSeederCommand = MakeSeederCommand;
|
|
8063
9655
|
exports.MigrateCommand = MigrateCommand;
|
|
9656
|
+
exports.MigrateFreshCommand = MigrateFreshCommand;
|
|
8064
9657
|
exports.MigrateRollbackCommand = MigrateRollbackCommand;
|
|
8065
9658
|
exports.Migration = Migration;
|
|
8066
9659
|
exports.MigrationHistoryCommand = MigrationHistoryCommand;
|
|
@@ -8090,9 +9683,12 @@ exports.UnsupportedAdapterFeatureException = UnsupportedAdapterFeatureException;
|
|
|
8090
9683
|
exports.applyAlterTableOperation = applyAlterTableOperation;
|
|
8091
9684
|
exports.applyCreateTableOperation = applyCreateTableOperation;
|
|
8092
9685
|
exports.applyDropTableOperation = applyDropTableOperation;
|
|
9686
|
+
exports.applyMigrationRollbackToDatabase = applyMigrationRollbackToDatabase;
|
|
8093
9687
|
exports.applyMigrationRollbackToPrismaSchema = applyMigrationRollbackToPrismaSchema;
|
|
9688
|
+
exports.applyMigrationToDatabase = applyMigrationToDatabase;
|
|
8094
9689
|
exports.applyMigrationToPrismaSchema = applyMigrationToPrismaSchema;
|
|
8095
9690
|
exports.applyOperationsToPrismaSchema = applyOperationsToPrismaSchema;
|
|
9691
|
+
exports.bindAdapterToModels = bindAdapterToModels;
|
|
8096
9692
|
exports.buildEnumBlock = buildEnumBlock;
|
|
8097
9693
|
exports.buildFieldLine = buildFieldLine;
|
|
8098
9694
|
exports.buildIndexLine = buildIndexLine;
|
|
@@ -8104,6 +9700,7 @@ exports.buildModelBlock = buildModelBlock;
|
|
|
8104
9700
|
exports.buildRelationLine = buildRelationLine;
|
|
8105
9701
|
exports.computeMigrationChecksum = computeMigrationChecksum;
|
|
8106
9702
|
exports.configureArkormRuntime = configureArkormRuntime;
|
|
9703
|
+
exports.createEmptyAppliedMigrationsState = createEmptyAppliedMigrationsState;
|
|
8107
9704
|
exports.createKyselyAdapter = createKyselyAdapter;
|
|
8108
9705
|
exports.createMigrationTimestamp = createMigrationTimestamp;
|
|
8109
9706
|
exports.createPrismaAdapter = createPrismaAdapter;
|
|
@@ -8112,6 +9709,7 @@ exports.createPrismaDatabaseAdapter = createPrismaDatabaseAdapter;
|
|
|
8112
9709
|
exports.createPrismaDelegateMap = createPrismaDelegateMap;
|
|
8113
9710
|
exports.defineConfig = defineConfig;
|
|
8114
9711
|
exports.defineFactory = defineFactory;
|
|
9712
|
+
exports.deleteAppliedMigrationsStateFromStore = deleteAppliedMigrationsStateFromStore;
|
|
8115
9713
|
exports.deriveCollectionFieldName = deriveCollectionFieldName;
|
|
8116
9714
|
exports.deriveInverseRelationAlias = deriveInverseRelationAlias;
|
|
8117
9715
|
exports.deriveRelationAlias = deriveRelationAlias;
|
|
@@ -8131,6 +9729,7 @@ exports.getDefaultStubsPath = getDefaultStubsPath;
|
|
|
8131
9729
|
exports.getLastMigrationRun = getLastMigrationRun;
|
|
8132
9730
|
exports.getLatestAppliedMigrations = getLatestAppliedMigrations;
|
|
8133
9731
|
exports.getMigrationPlan = getMigrationPlan;
|
|
9732
|
+
exports.getRuntimeAdapter = getRuntimeAdapter;
|
|
8134
9733
|
exports.getRuntimePaginationCurrentPageResolver = getRuntimePaginationCurrentPageResolver;
|
|
8135
9734
|
exports.getRuntimePaginationURLDriverFactory = getRuntimePaginationURLDriverFactory;
|
|
8136
9735
|
exports.getRuntimePrismaClient = getRuntimePrismaClient;
|
|
@@ -8144,6 +9743,7 @@ exports.markMigrationApplied = markMigrationApplied;
|
|
|
8144
9743
|
exports.markMigrationRun = markMigrationRun;
|
|
8145
9744
|
exports.pad = pad;
|
|
8146
9745
|
exports.readAppliedMigrationsState = readAppliedMigrationsState;
|
|
9746
|
+
exports.readAppliedMigrationsStateFromStore = readAppliedMigrationsStateFromStore;
|
|
8147
9747
|
exports.removeAppliedMigration = removeAppliedMigration;
|
|
8148
9748
|
exports.resetArkormRuntimeForTests = resetArkormRuntimeForTests;
|
|
8149
9749
|
exports.resolveCast = resolveCast;
|
|
@@ -8154,6 +9754,11 @@ exports.resolvePrismaType = resolvePrismaType;
|
|
|
8154
9754
|
exports.runArkormTransaction = runArkormTransaction;
|
|
8155
9755
|
exports.runMigrationWithPrisma = runMigrationWithPrisma;
|
|
8156
9756
|
exports.runPrismaCommand = runPrismaCommand;
|
|
9757
|
+
exports.stripPrismaSchemaModelsAndEnums = stripPrismaSchemaModelsAndEnums;
|
|
9758
|
+
exports.supportsDatabaseMigrationExecution = supportsDatabaseMigrationExecution;
|
|
9759
|
+
exports.supportsDatabaseMigrationState = supportsDatabaseMigrationState;
|
|
9760
|
+
exports.supportsDatabaseReset = supportsDatabaseReset;
|
|
8157
9761
|
exports.toMigrationFileSlug = toMigrationFileSlug;
|
|
8158
9762
|
exports.toModelName = toModelName;
|
|
8159
|
-
exports.writeAppliedMigrationsState = writeAppliedMigrationsState;
|
|
9763
|
+
exports.writeAppliedMigrationsState = writeAppliedMigrationsState;
|
|
9764
|
+
exports.writeAppliedMigrationsStateToStore = writeAppliedMigrationsStateToStore;
|