arkormx 1.3.4 → 2.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,99 +1,20 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { dirname, extname, join, resolve } from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- import { str } from "@h3ravel/support";
5
- import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
6
- import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
1
+ import { sql } from "kysely";
7
2
  import { AsyncLocalStorage } from "async_hooks";
8
3
  import { createJiti } from "@rexxars/jiti";
9
4
  import { pathToFileURL } from "node:url";
5
+ import { dirname, extname, join, resolve } from "node:path";
10
6
  import { createRequire } from "module";
7
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
11
8
  import { fileURLToPath } from "url";
9
+ import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
10
+ import { str } from "@h3ravel/support";
11
+ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
12
13
  import { Logger } from "@h3ravel/shared";
13
14
  import { Command } from "@h3ravel/musket";
14
15
  import { createHash } from "node:crypto";
15
16
  import { Collection } from "@h3ravel/collect.js";
16
17
 
17
- //#region src/Attribute.ts
18
- var Attribute = class Attribute {
19
- get;
20
- set;
21
- constructor(options = {}) {
22
- this.get = options.get;
23
- this.set = options.set;
24
- }
25
- static make(options) {
26
- return new Attribute(options);
27
- }
28
- static isAttribute(value) {
29
- if (!value || typeof value !== "object") return false;
30
- return value instanceof Attribute;
31
- }
32
- };
33
-
34
- //#endregion
35
- //#region src/casts.ts
36
- const builtinCasts = {
37
- string: {
38
- get: (value) => value == null ? value : String(value),
39
- set: (value) => value == null ? value : String(value)
40
- },
41
- number: {
42
- get: (value) => value == null ? value : Number(value),
43
- set: (value) => value == null ? value : Number(value)
44
- },
45
- boolean: {
46
- get: (value) => value == null ? value : Boolean(value),
47
- set: (value) => value == null ? value : Boolean(value)
48
- },
49
- date: {
50
- get: (value) => {
51
- if (value == null || value instanceof Date) return value;
52
- return new Date(String(value));
53
- },
54
- set: (value) => {
55
- if (value == null || value instanceof Date) return value;
56
- return new Date(String(value));
57
- }
58
- },
59
- json: {
60
- get: (value) => {
61
- if (value == null || typeof value !== "string") return value;
62
- try {
63
- return JSON.parse(value);
64
- } catch {
65
- return value;
66
- }
67
- },
68
- set: (value) => {
69
- if (value == null || typeof value === "string") return value;
70
- return JSON.stringify(value);
71
- }
72
- },
73
- array: {
74
- get: (value) => {
75
- if (Array.isArray(value)) return value;
76
- if (typeof value === "string") try {
77
- const parsed = JSON.parse(value);
78
- return Array.isArray(parsed) ? parsed : [parsed];
79
- } catch {
80
- return [value];
81
- }
82
- if (value == null) return value;
83
- return [value];
84
- },
85
- set: (value) => {
86
- if (value == null) return value;
87
- return Array.isArray(value) ? value : [value];
88
- }
89
- }
90
- };
91
- function resolveCast(definition) {
92
- if (typeof definition === "string") return builtinCasts[definition];
93
- return definition;
94
- }
95
-
96
- //#endregion
97
18
  //#region src/Exceptions/ArkormException.ts
98
19
  var ArkormException = class extends Error {
99
20
  code;
@@ -136,119 +57,1066 @@ var ArkormException = class extends Error {
136
57
  };
137
58
 
138
59
  //#endregion
139
- //#region src/database/ForeignKeyBuilder.ts
140
- /**
141
- * The ForeignKeyBuilder class provides a fluent interface for defining
142
- * foreign key constraints in a migration. It allows you to specify
143
- * the referenced table and column, as well as actions to take on
144
- * delete and aliases for the relation.
145
- *
146
- * @author Legacy (3m1n3nc3)
147
- * @since 0.2.2
148
- */
149
- var ForeignKeyBuilder = class {
150
- foreignKey;
151
- constructor(foreignKey) {
152
- this.foreignKey = foreignKey;
60
+ //#region src/Exceptions/UnsupportedAdapterFeatureException.ts
61
+ var UnsupportedAdapterFeatureException = class extends ArkormException {
62
+ constructor(message, context = {}) {
63
+ super(message, {
64
+ code: "UNSUPPORTED_ADAPTER_FEATURE",
65
+ ...context
66
+ });
67
+ this.name = "UnsupportedAdapterFeatureException";
153
68
  }
154
- /**
155
- * Defines the referenced table and column for this foreign key constraint.
156
- *
157
- * @param table
158
- * @param column
159
- * @returns
160
- */
161
- references(table, column) {
162
- this.foreignKey.referencesTable = table;
163
- this.foreignKey.referencesColumn = column;
164
- return this;
69
+ };
70
+
71
+ //#endregion
72
+ //#region src/adapters/KyselyDatabaseAdapter.ts
73
+ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
74
+ capabilities = {
75
+ transactions: true,
76
+ returning: true,
77
+ insertMany: true,
78
+ updateMany: true,
79
+ deleteMany: true,
80
+ exists: true,
81
+ relationLoads: false,
82
+ relationAggregates: false,
83
+ relationFilters: false,
84
+ rawWhere: false
85
+ };
86
+ constructor(db) {
87
+ this.db = db;
88
+ }
89
+ resolveTable(target) {
90
+ if (target.table && target.table.trim().length > 0) return target.table;
91
+ throw new ArkormException("Kysely adapter requires a concrete target table.", {
92
+ operation: "adapter.table",
93
+ model: target.modelName,
94
+ meta: { target }
95
+ });
165
96
  }
166
- /**
167
- * Defines the action to take when a referenced record is deleted, such
168
- * as "CASCADE", "SET NULL", or "RESTRICT".
169
- *
170
- * @param action
171
- * @returns
172
- */
173
- onDelete(action) {
174
- this.foreignKey.onDelete = action;
175
- return this;
97
+ resolvePrimaryKey(target) {
98
+ return this.mapColumn(target, target.primaryKey || "id");
176
99
  }
177
- /**
178
- * Defines an alias for the relation represented by this foreign key, which
179
- * can be used in the ORM for more intuitive access to related models.
180
- *
181
- * @param name
182
- * @returns
183
- */
184
- alias(name) {
185
- this.foreignKey.relationAlias = name;
186
- return this;
100
+ mapColumn(target, column) {
101
+ return target.columns?.[column] ?? column;
187
102
  }
188
- /**
189
- * Defines an alias for the inverse relation represented by this foreign key.
190
- *
191
- * @param name
192
- * @returns
193
- */
194
- inverseAlias(name) {
195
- this.foreignKey.inverseRelationAlias = name;
196
- return this;
103
+ reverseColumnMap(target) {
104
+ return Object.entries(target.columns ?? {}).reduce((all, [attribute, column]) => {
105
+ all[column] = attribute;
106
+ return all;
107
+ }, {});
197
108
  }
198
- /**
199
- * Defines an alias for the foreign key field itself, which can be
200
- * used in the ORM for more intuitive access to the foreign key value.
201
- *
202
- * @param fieldName
203
- * @returns
204
- */
205
- as(fieldName) {
206
- this.foreignKey.fieldAlias = fieldName;
207
- return this;
109
+ mapRow(target, row) {
110
+ if (!row) return null;
111
+ const reverseMap = this.reverseColumnMap(target);
112
+ return Object.entries(row).reduce((mapped, [key, value]) => {
113
+ mapped[reverseMap[key] ?? key] = value;
114
+ return mapped;
115
+ }, {});
116
+ }
117
+ mapRows(target, rows) {
118
+ return rows.map((row) => this.mapRow(target, row));
119
+ }
120
+ mapValues(target, values) {
121
+ return Object.entries(values).reduce((mapped, [key, value]) => {
122
+ mapped[this.mapColumn(target, key)] = value;
123
+ return mapped;
124
+ }, {});
125
+ }
126
+ buildSelectList(target, columns) {
127
+ if (!columns || columns.length === 0) return sql.raw("*");
128
+ return sql.join(columns.map(({ column, alias }) => {
129
+ const mappedColumn = this.mapColumn(target, column);
130
+ const resultAlias = alias ?? column;
131
+ if (mappedColumn === resultAlias) return sql.ref(mappedColumn);
132
+ return sql`${sql.ref(mappedColumn)} as ${sql.id(resultAlias)}`;
133
+ }));
134
+ }
135
+ buildOrderBy(target, orderBy) {
136
+ if (!orderBy || orderBy.length === 0) return sql``;
137
+ return sql` order by ${sql.join(orderBy.map(({ column, direction }) => {
138
+ return sql`${sql.ref(this.mapColumn(target, column))} ${sql.raw(direction === "desc" ? "desc" : "asc")}`;
139
+ }), sql`, `)}`;
140
+ }
141
+ buildConditionValueList(value) {
142
+ if (Array.isArray(value)) return value;
143
+ return typeof value === "undefined" ? [] : [value];
144
+ }
145
+ buildComparisonCondition(target, condition) {
146
+ const column = sql.ref(this.mapColumn(target, condition.column));
147
+ if (condition.operator === "is-null") return sql`${column} is null`;
148
+ if (condition.operator === "is-not-null") return sql`${column} is not null`;
149
+ if (condition.operator === "in") {
150
+ const values = this.buildConditionValueList(condition.value);
151
+ if (values.length === 0) return sql`1 = 0`;
152
+ return sql`${column} in (${sql.join(values)})`;
153
+ }
154
+ if (condition.operator === "not-in") {
155
+ const values = this.buildConditionValueList(condition.value);
156
+ if (values.length === 0) return sql`1 = 1`;
157
+ return sql`${column} not in (${sql.join(values)})`;
158
+ }
159
+ if (condition.operator === "contains") return sql`${column} like ${`%${String(condition.value ?? "")}%`}`;
160
+ if (condition.operator === "starts-with") return sql`${column} like ${`${String(condition.value ?? "")}%`}`;
161
+ if (condition.operator === "ends-with") return sql`${column} like ${`%${String(condition.value ?? "")}`}`;
162
+ return sql`${column} ${condition.operator === "!=" ? sql.raw("!=") : sql.raw(condition.operator)} ${condition.value}`;
163
+ }
164
+ buildWhereCondition(target, condition) {
165
+ if (!condition) return sql`1 = 1`;
166
+ if (condition.type === "comparison") return this.buildComparisonCondition(target, condition);
167
+ if (condition.type === "group") {
168
+ const group = condition;
169
+ const conditions = group.conditions.map((entry) => {
170
+ return this.buildWhereCondition(target, entry);
171
+ });
172
+ if (conditions.length === 0) return sql`1 = 1`;
173
+ const separator = group.operator === "or" ? sql` or ` : sql` and `;
174
+ return sql`(${sql.join(conditions, separator)})`;
175
+ }
176
+ if (condition.type === "not") {
177
+ const notCondition = condition;
178
+ return sql`not (${this.buildWhereCondition(target, notCondition.condition)})`;
179
+ }
180
+ throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the Kysely adapter.", {
181
+ operation: "adapter.where",
182
+ meta: {
183
+ feature: "rawWhere",
184
+ sql: condition.sql
185
+ }
186
+ });
187
+ }
188
+ buildWhereClause(target, condition) {
189
+ if (!condition) return sql``;
190
+ return sql` where ${this.buildWhereCondition(target, condition)}`;
191
+ }
192
+ buildPaginationClause(spec) {
193
+ const clauses = [];
194
+ if (typeof spec.limit === "number") clauses.push(sql` limit ${spec.limit}`);
195
+ if (typeof spec.offset === "number") clauses.push(sql` offset ${spec.offset}`);
196
+ if (clauses.length === 0) return sql``;
197
+ return sql.join(clauses, sql``);
198
+ }
199
+ assertNoRelationLoads(spec) {
200
+ if ("relationLoads" in spec && spec.relationLoads && spec.relationLoads.length > 0) throw new UnsupportedAdapterFeatureException("Kysely adapter relation-load execution is planned for a later phase.", {
201
+ operation: "adapter.relationLoads",
202
+ meta: { feature: "relationLoads" }
203
+ });
208
204
  }
205
+ async select(spec) {
206
+ this.assertNoRelationLoads(spec);
207
+ const result = await sql`
208
+ select ${this.buildSelectList(spec.target, spec.columns)}
209
+ from ${sql.table(this.resolveTable(spec.target))}
210
+ ${this.buildWhereClause(spec.target, spec.where)}
211
+ ${this.buildOrderBy(spec.target, spec.orderBy)}
212
+ ${this.buildPaginationClause(spec)}
213
+ `.execute(this.db);
214
+ return this.mapRows(spec.target, result.rows);
215
+ }
216
+ async selectOne(spec) {
217
+ return (await this.select({
218
+ ...spec,
219
+ limit: spec.limit ?? 1
220
+ }))[0] ?? null;
221
+ }
222
+ async insert(spec) {
223
+ const values = this.mapValues(spec.target, spec.values);
224
+ const columns = Object.keys(values);
225
+ const result = columns.length === 0 ? await sql`
226
+ insert into ${sql.table(this.resolveTable(spec.target))}
227
+ default values
228
+ returning *
229
+ `.execute(this.db) : await sql`
230
+ insert into ${sql.table(this.resolveTable(spec.target))} (${sql.join(columns.map((column) => sql.id(column)), sql`, `)})
231
+ values (${sql.join(columns.map((column) => values[column]), sql`, `)})
232
+ returning *
233
+ `.execute(this.db);
234
+ return this.mapRow(spec.target, result.rows[0]);
235
+ }
236
+ async insertMany(spec) {
237
+ if (spec.values.length === 0) return 0;
238
+ const rows = spec.values.map((row) => this.mapValues(spec.target, row));
239
+ const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
240
+ if (columns.length === 0) return (await sql`
241
+ insert into ${sql.table(this.resolveTable(spec.target))}
242
+ default values
243
+ ${spec.ignoreDuplicates ? sql` on conflict do nothing` : sql``}
244
+ returning ${sql.id(this.resolvePrimaryKey(spec.target))}
245
+ `.execute(this.db)).rows.length;
246
+ const values = sql.join(rows.map((row) => {
247
+ return sql`(${sql.join(columns.map((column) => row[column] ?? null), sql`, `)})`;
248
+ }), sql`, `);
249
+ return (await sql`
250
+ insert into ${sql.table(this.resolveTable(spec.target))} (${sql.join(columns.map((column) => sql.id(column)), sql`, `)})
251
+ values ${values}
252
+ ${spec.ignoreDuplicates ? sql` on conflict do nothing` : sql``}
253
+ returning ${sql.id(this.resolvePrimaryKey(spec.target))}
254
+ `.execute(this.db)).rows.length;
255
+ }
256
+ async update(spec) {
257
+ const values = this.mapValues(spec.target, spec.values);
258
+ const assignments = Object.entries(values).map(([column, value]) => {
259
+ return sql`${sql.id(column)} = ${value}`;
260
+ });
261
+ if (assignments.length === 0) return await this.selectOne({
262
+ target: spec.target,
263
+ where: spec.where,
264
+ limit: 1
265
+ });
266
+ const result = await sql`
267
+ update ${sql.table(this.resolveTable(spec.target))}
268
+ set ${sql.join(assignments, sql`, `)}
269
+ ${this.buildWhereClause(spec.target, spec.where)}
270
+ returning *
271
+ `.execute(this.db);
272
+ return this.mapRow(spec.target, result.rows[0]);
273
+ }
274
+ async updateMany(spec) {
275
+ const values = this.mapValues(spec.target, spec.values);
276
+ const assignments = Object.entries(values).map(([column, value]) => {
277
+ return sql`${sql.id(column)} = ${value}`;
278
+ });
279
+ if (assignments.length === 0) return 0;
280
+ return (await sql`
281
+ update ${sql.table(this.resolveTable(spec.target))}
282
+ set ${sql.join(assignments, sql`, `)}
283
+ ${this.buildWhereClause(spec.target, spec.where)}
284
+ returning ${sql.id(this.resolvePrimaryKey(spec.target))}
285
+ `.execute(this.db)).rows.length;
286
+ }
287
+ async delete(spec) {
288
+ const result = await sql`
289
+ delete from ${sql.table(this.resolveTable(spec.target))}
290
+ ${this.buildWhereClause(spec.target, spec.where)}
291
+ returning *
292
+ `.execute(this.db);
293
+ return this.mapRow(spec.target, result.rows[0]);
294
+ }
295
+ async deleteMany(spec) {
296
+ return (await sql`
297
+ delete from ${sql.table(this.resolveTable(spec.target))}
298
+ ${this.buildWhereClause(spec.target, spec.where)}
299
+ returning ${sql.id(this.resolvePrimaryKey(spec.target))}
300
+ `.execute(this.db)).rows.length;
301
+ }
302
+ async count(spec) {
303
+ const result = await sql`
304
+ select count(*)::int as count
305
+ from ${sql.table(this.resolveTable(spec.target))}
306
+ ${this.buildWhereClause(spec.target, spec.where)}
307
+ `.execute(this.db);
308
+ return Number(result.rows[0]?.count ?? 0);
309
+ }
310
+ async exists(spec) {
311
+ const result = await sql`
312
+ select exists(
313
+ select 1
314
+ from ${sql.table(this.resolveTable(spec.target))}
315
+ ${this.buildWhereClause(spec.target, spec.where)}
316
+ limit 1
317
+ ) as exists
318
+ `.execute(this.db);
319
+ return Boolean(result.rows[0]?.exists);
320
+ }
321
+ async transaction(callback, context = {}) {
322
+ let transactionBuilder = this.db.transaction();
323
+ if (context.readOnly !== void 0) transactionBuilder = transactionBuilder.setAccessMode(context.readOnly ? "read only" : "read write");
324
+ if (context.isolationLevel) transactionBuilder = transactionBuilder.setIsolationLevel(context.isolationLevel);
325
+ return await transactionBuilder.execute(async (transaction) => {
326
+ return await callback(new KyselyDatabaseAdapter(transaction));
327
+ });
328
+ }
329
+ };
330
+ const createKyselyAdapter = (db) => {
331
+ return new KyselyDatabaseAdapter(db);
209
332
  };
210
333
 
211
334
  //#endregion
212
- //#region src/database/TableBuilder.ts
213
- const PRISMA_ENUM_MEMBER_REGEX$1 = /^[A-Za-z][A-Za-z0-9_]*$/;
214
- const normalizeEnumMember = (columnName, value) => {
215
- const normalized = value.trim();
216
- if (!normalized) throw new Error(`Enum column [${columnName}] must define only non-empty values.`);
217
- if (!PRISMA_ENUM_MEMBER_REGEX$1.test(normalized)) throw new Error(`Enum column [${columnName}] contains invalid Prisma enum value [${normalized}].`);
218
- return normalized;
335
+ //#region src/Exceptions/MissingDelegateException.ts
336
+ var MissingDelegateException = class extends ArkormException {
337
+ constructor(message, context = {}) {
338
+ super(message, {
339
+ code: "MISSING_DELEGATE",
340
+ ...context
341
+ });
342
+ this.name = "MissingDelegateException";
343
+ }
219
344
  };
220
- const normalizeEnumMembers = (columnName, values) => {
221
- const normalizedValues = values.map((value) => normalizeEnumMember(columnName, value));
222
- const seen = /* @__PURE__ */ new Set();
223
- for (const value of normalizedValues) {
224
- if (seen.has(value)) throw new Error(`Enum column [${columnName}] contains duplicate enum value [${value}].`);
225
- seen.add(value);
345
+
346
+ //#endregion
347
+ //#region src/helpers/runtime-module-loader.ts
348
+ var RuntimeModuleLoader = class {
349
+ static async load(filePath) {
350
+ const resolvedPath = resolve(filePath);
351
+ return await createJiti(pathToFileURL(resolvedPath).href, {
352
+ interopDefault: false,
353
+ tsconfigPaths: true
354
+ }).import(resolvedPath);
226
355
  }
227
- return normalizedValues;
228
356
  };
229
- /**
230
- * The EnumBuilder class provides a fluent interface for configuring enum columns
231
- * after they are defined on a table.
232
- *
233
- * @author Legacy (3m1n3nc3)
234
- * @since 0.2.3
235
- */
236
- var EnumBuilder = class {
237
- tableBuilder;
238
- columnName;
239
- constructor(tableBuilder, columnName) {
240
- this.tableBuilder = tableBuilder;
241
- this.columnName = columnName;
357
+
358
+ //#endregion
359
+ //#region src/helpers/runtime-config.ts
360
+ const resolveDefaultStubsPath = () => {
361
+ let current = path.dirname(fileURLToPath(import.meta.url));
362
+ while (true) {
363
+ const packageJsonPath = path.join(current, "package.json");
364
+ const stubsPath = path.join(current, "stubs");
365
+ if (existsSync(packageJsonPath) && existsSync(stubsPath)) return stubsPath;
366
+ const parent = path.dirname(current);
367
+ if (parent === current) break;
368
+ current = parent;
242
369
  }
243
- /**
244
- * Defines the Prisma enum name for this column.
245
- *
246
- * @param name
247
- * @returns
248
- */
249
- enumName(name) {
250
- this.tableBuilder.enumName(name, this.columnName);
251
- return this;
370
+ return path.join(process.cwd(), "stubs");
371
+ };
372
+ const baseConfig = {
373
+ paths: {
374
+ stubs: resolveDefaultStubsPath(),
375
+ seeders: path.join(process.cwd(), "database", "seeders"),
376
+ models: path.join(process.cwd(), "src", "models"),
377
+ migrations: path.join(process.cwd(), "database", "migrations"),
378
+ factories: path.join(process.cwd(), "database", "factories"),
379
+ buildOutput: path.join(process.cwd(), "dist")
380
+ },
381
+ outputExt: "ts"
382
+ };
383
+ const userConfig = {
384
+ ...baseConfig,
385
+ paths: { ...baseConfig.paths ?? {} }
386
+ };
387
+ let runtimeConfigLoaded = false;
388
+ let runtimeConfigLoadingPromise;
389
+ let runtimeClientResolver;
390
+ let runtimePaginationURLDriverFactory;
391
+ let runtimePaginationCurrentPageResolver;
392
+ const transactionClientStorage = new AsyncLocalStorage();
393
+ const mergePathConfig = (paths) => {
394
+ const defaults = baseConfig.paths ?? {};
395
+ const current = userConfig.paths ?? {};
396
+ const incoming = Object.entries(paths ?? {}).reduce((all, [key, value]) => {
397
+ if (typeof value === "string" && value.trim().length > 0) all[key] = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
398
+ return all;
399
+ }, {});
400
+ return {
401
+ ...defaults,
402
+ ...current,
403
+ ...incoming
404
+ };
405
+ };
406
+ /**
407
+ * Define the ArkORM runtime configuration. This function can be used to provide.
408
+ *
409
+ * @param config The ArkORM configuration object.
410
+ * @returns The same configuration object.
411
+ */
412
+ const defineConfig = (config) => {
413
+ return config;
414
+ };
415
+ /**
416
+ * Get the user-provided ArkORM configuration.
417
+ *
418
+ * @returns The user-provided ArkORM configuration object.
419
+ */
420
+ const getUserConfig = (key) => {
421
+ if (key) return userConfig[key];
422
+ return userConfig;
423
+ };
424
+ /**
425
+ * Configure the ArkORM runtime with the provided Prisma client resolver and
426
+ * delegate mapping resolver.
427
+ *
428
+ * @param prisma
429
+ * @param mapping
430
+ */
431
+ const configureArkormRuntime = (prisma, options = {}) => {
432
+ const nextConfig = {
433
+ ...userConfig,
434
+ prisma,
435
+ paths: mergePathConfig(options.paths)
436
+ };
437
+ if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
438
+ if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
439
+ Object.assign(userConfig, { ...nextConfig });
440
+ runtimeClientResolver = prisma;
441
+ runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
442
+ runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
443
+ };
444
+ /**
445
+ * Reset the ArkORM runtime configuration.
446
+ * This is primarily intended for testing purposes.
447
+ */
448
+ const resetArkormRuntimeForTests = () => {
449
+ Object.assign(userConfig, {
450
+ ...baseConfig,
451
+ paths: { ...baseConfig.paths ?? {} }
452
+ });
453
+ runtimeConfigLoaded = false;
454
+ runtimeConfigLoadingPromise = void 0;
455
+ runtimeClientResolver = void 0;
456
+ runtimePaginationURLDriverFactory = void 0;
457
+ runtimePaginationCurrentPageResolver = void 0;
458
+ };
459
+ /**
460
+ * Resolve a Prisma client instance from the provided resolver, which can be either
461
+ * a direct client instance or a function that returns a client instance.
462
+ *
463
+ * @param resolver
464
+ * @returns
465
+ */
466
+ const resolveClient = (resolver) => {
467
+ if (!resolver) return void 0;
468
+ const client = typeof resolver === "function" ? resolver() : resolver;
469
+ if (!client || typeof client !== "object") return void 0;
470
+ return client;
471
+ };
472
+ /**
473
+ * Resolve and apply the ArkORM configuration from an imported module.
474
+ * This function checks for a default export and falls back to the module itself, then validates
475
+ * the configuration object and applies it to the runtime if valid.
476
+ *
477
+ * @param imported
478
+ * @returns
479
+ */
480
+ const resolveAndApplyConfig = (imported) => {
481
+ const config = imported?.default ?? imported;
482
+ if (!config || typeof config !== "object" || !config.prisma) return;
483
+ configureArkormRuntime(config.prisma, {
484
+ pagination: config.pagination,
485
+ paths: config.paths,
486
+ outputExt: config.outputExt
487
+ });
488
+ runtimeConfigLoaded = true;
489
+ };
490
+ /**
491
+ * Dynamically import a configuration file.
492
+ * A cache-busting query parameter is appended to ensure the latest version is loaded.
493
+ *
494
+ * @param configPath
495
+ * @returns A promise that resolves to the imported configuration module.
496
+ */
497
+ const importConfigFile = (configPath) => {
498
+ return RuntimeModuleLoader.load(configPath);
499
+ };
500
+ const loadRuntimeConfigSync = () => {
501
+ const require = createRequire(import.meta.url);
502
+ const syncConfigPaths = [path.join(process.cwd(), "arkormx.config.cjs")];
503
+ for (const configPath of syncConfigPaths) {
504
+ if (!existsSync(configPath)) continue;
505
+ try {
506
+ resolveAndApplyConfig(require(configPath));
507
+ return true;
508
+ } catch {
509
+ continue;
510
+ }
511
+ }
512
+ return false;
513
+ };
514
+ /**
515
+ * Load the ArkORM configuration by searching for configuration files in the
516
+ * current working directory.
517
+ * @returns
518
+ */
519
+ const loadArkormConfig = async () => {
520
+ if (runtimeConfigLoaded) return;
521
+ if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
522
+ if (loadRuntimeConfigSync()) return;
523
+ runtimeConfigLoadingPromise = (async () => {
524
+ const configPaths = [path.join(process.cwd(), "arkormx.config.js"), path.join(process.cwd(), "arkormx.config.ts")];
525
+ for (const configPath of configPaths) {
526
+ if (!existsSync(configPath)) continue;
527
+ try {
528
+ resolveAndApplyConfig(await importConfigFile(configPath));
529
+ return;
530
+ } catch {
531
+ continue;
532
+ }
533
+ }
534
+ runtimeConfigLoaded = true;
535
+ })();
536
+ await runtimeConfigLoadingPromise;
537
+ };
538
+ /**
539
+ * Ensure that the ArkORM configuration is loaded.
540
+ * This function can be called to trigger the loading process if it hasn't already been initiated.
541
+ * If the configuration is already loaded, it will return immediately.
542
+ *
543
+ * @returns
544
+ */
545
+ const ensureArkormConfigLoading = () => {
546
+ if (runtimeConfigLoaded) return;
547
+ if (!runtimeConfigLoadingPromise) loadArkormConfig();
548
+ };
549
+ const getDefaultStubsPath = () => {
550
+ return resolveDefaultStubsPath();
551
+ };
552
+ /**
553
+ * Get the runtime Prisma client.
554
+ * This function will trigger the loading of the ArkORM configuration if
555
+ * it hasn't already been loaded.
556
+ *
557
+ * @returns
558
+ */
559
+ const getRuntimePrismaClient = () => {
560
+ const activeTransactionClient = transactionClientStorage.getStore();
561
+ if (activeTransactionClient) return activeTransactionClient;
562
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
563
+ return resolveClient(runtimeClientResolver);
564
+ };
565
+ const getActiveTransactionClient = () => {
566
+ return transactionClientStorage.getStore();
567
+ };
568
+ const isTransactionCapableClient = (value) => {
569
+ if (!value || typeof value !== "object") return false;
570
+ return typeof value.$transaction === "function";
571
+ };
572
+ const runArkormTransaction = async (callback, options = {}) => {
573
+ const activeTransactionClient = transactionClientStorage.getStore();
574
+ if (activeTransactionClient) return await callback(activeTransactionClient);
575
+ const client = getRuntimePrismaClient();
576
+ if (!client) throw new ArkormException("Cannot start a transaction without a configured Prisma client.", {
577
+ code: "CLIENT_NOT_CONFIGURED",
578
+ operation: "transaction"
579
+ });
580
+ if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
581
+ code: "TRANSACTION_NOT_SUPPORTED",
582
+ operation: "transaction"
583
+ });
584
+ return await client.$transaction(async (transactionClient) => {
585
+ return await transactionClientStorage.run(transactionClient, async () => {
586
+ return await callback(transactionClient);
587
+ });
588
+ }, options);
589
+ };
590
+ /**
591
+ * Get the configured pagination URL driver factory from runtime config.
592
+ *
593
+ * @returns
594
+ */
595
+ const getRuntimePaginationURLDriverFactory = () => {
596
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
597
+ return runtimePaginationURLDriverFactory;
598
+ };
599
+ /**
600
+ * Get the configured current-page resolver from runtime config.
601
+ *
602
+ * @returns
603
+ */
604
+ const getRuntimePaginationCurrentPageResolver = () => {
605
+ if (!runtimeConfigLoaded) loadRuntimeConfigSync();
606
+ return runtimePaginationCurrentPageResolver;
607
+ };
608
+ /**
609
+ * Check if a given value is a Prisma delegate-like object
610
+ * by verifying the presence of common delegate methods.
611
+ *
612
+ * @param value The value to check.
613
+ * @returns True if the value is a Prisma delegate-like object, false otherwise.
614
+ */
615
+ const isDelegateLike = (value) => {
616
+ if (!value || typeof value !== "object") return false;
617
+ const candidate = value;
618
+ return [
619
+ "findMany",
620
+ "findFirst",
621
+ "create",
622
+ "update",
623
+ "delete",
624
+ "count"
625
+ ].every((method) => typeof candidate[method] === "function");
626
+ };
627
+ loadArkormConfig();
628
+
629
+ //#endregion
630
+ //#region src/helpers/prisma.ts
631
+ /**
632
+ * Create an adapter to convert a Prisma client instance into a format
633
+ * compatible with ArkORM's expectations.
634
+ *
635
+ * @param prisma The Prisma client instance to adapt.
636
+ * @param mapping An optional mapping of Prisma delegate names to ArkORM delegate names.
637
+ * @returns A record of adapted Prisma delegates compatible with ArkORM.
638
+ */
639
+ function createPrismaAdapter(prisma) {
640
+ return Object.entries(prisma).reduce((accumulator, [key, value]) => {
641
+ if (!isDelegateLike(value)) return accumulator;
642
+ accumulator[key] = value;
643
+ return accumulator;
644
+ }, {});
645
+ }
646
+ /**
647
+ * Create a delegate mapping record for Model.setClient() from a Prisma client.
648
+ *
649
+ * @param prisma The Prisma client instance.
650
+ * @param mapping Optional mapping of Arkormˣ delegate names to Prisma delegate names.
651
+ * @returns A delegate map keyed by Arkormˣ delegate names.
652
+ */
653
+ function createPrismaDelegateMap(prisma, mapping = {}) {
654
+ const delegates = createPrismaAdapter(prisma);
655
+ if (Object.keys(mapping).length === 0) return delegates;
656
+ return Object.entries(mapping).reduce((accumulator, [arkormDelegate, prismaDelegate]) => {
657
+ const resolved = delegates[prismaDelegate];
658
+ if (!resolved) return accumulator;
659
+ accumulator[arkormDelegate] = resolved;
660
+ return accumulator;
661
+ }, {});
662
+ }
663
+ /**
664
+ * Infer the Prisma delegate name for a given model name using a simple convention.
665
+ *
666
+ * @param modelName The name of the model to infer the delegate name for.
667
+ * @returns The inferred Prisma delegate name.
668
+ */
669
+ function inferDelegateName(modelName) {
670
+ return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}s`;
671
+ }
672
+
673
+ //#endregion
674
+ //#region src/adapters/PrismaDatabaseAdapter.ts
675
+ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
676
+ capabilities;
677
+ delegates;
678
+ constructor(prisma, mapping = {}) {
679
+ this.prisma = prisma;
680
+ this.mapping = mapping;
681
+ this.delegates = Object.entries(prisma).reduce((accumulator, [key, value]) => {
682
+ if (!isDelegateLike(value)) return accumulator;
683
+ accumulator[key] = value;
684
+ return accumulator;
685
+ }, {});
686
+ this.capabilities = {
687
+ transactions: this.hasTransactionSupport(prisma),
688
+ insertMany: Object.values(this.delegates).some((delegate) => typeof delegate.createMany === "function"),
689
+ updateMany: Object.values(this.delegates).some((delegate) => typeof delegate.updateMany === "function"),
690
+ deleteMany: false,
691
+ exists: true,
692
+ relationLoads: false,
693
+ relationAggregates: false,
694
+ relationFilters: false,
695
+ rawWhere: false,
696
+ returning: false
697
+ };
698
+ }
699
+ hasTransactionSupport(client) {
700
+ return Boolean(client) && typeof client.$transaction === "function";
701
+ }
702
+ normalizeCandidate(value) {
703
+ return value.trim();
704
+ }
705
+ unique(values) {
706
+ return [...new Set(values.filter(Boolean))];
707
+ }
708
+ toQuerySelect(columns) {
709
+ if (!columns || columns.length === 0) return void 0;
710
+ return columns.reduce((select, column) => {
711
+ select[column.column] = true;
712
+ return select;
713
+ }, {});
714
+ }
715
+ toQueryOrderBy(orderBy) {
716
+ if (!orderBy || orderBy.length === 0) return void 0;
717
+ return orderBy.map((entry) => ({ [entry.column]: entry.direction }));
718
+ }
719
+ toComparisonWhere(condition) {
720
+ if (condition.operator === "is-null") return { [condition.column]: null };
721
+ if (condition.operator === "is-not-null") return { [condition.column]: { not: null } };
722
+ if (condition.operator === "=") return { [condition.column]: condition.value };
723
+ if (condition.operator === "!=") return { [condition.column]: { not: condition.value } };
724
+ if (condition.operator === ">") return { [condition.column]: { gt: condition.value } };
725
+ if (condition.operator === ">=") return { [condition.column]: { gte: condition.value } };
726
+ if (condition.operator === "<") return { [condition.column]: { lt: condition.value } };
727
+ if (condition.operator === "<=") return { [condition.column]: { lte: condition.value } };
728
+ if (condition.operator === "in") return { [condition.column]: { in: Array.isArray(condition.value) ? condition.value : [condition.value] } };
729
+ if (condition.operator === "not-in") return { [condition.column]: { notIn: Array.isArray(condition.value) ? condition.value : [condition.value] } };
730
+ if (condition.operator === "contains") return { [condition.column]: { contains: condition.value } };
731
+ if (condition.operator === "starts-with") return { [condition.column]: { startsWith: condition.value } };
732
+ return { [condition.column]: { endsWith: condition.value } };
733
+ }
734
+ toQueryWhere(condition) {
735
+ if (!condition) return void 0;
736
+ if (condition.type === "comparison") return this.toComparisonWhere(condition);
737
+ if (condition.type === "group") {
738
+ const group = condition;
739
+ const grouped = group.conditions.map((entry) => this.toQueryWhere(entry)).filter((entry) => Boolean(entry));
740
+ if (grouped.length === 0) return void 0;
741
+ return group.operator === "and" ? { AND: grouped } : { OR: grouped };
742
+ }
743
+ if (condition.type === "not") {
744
+ const notCondition = condition;
745
+ const nested = this.toQueryWhere(notCondition.condition);
746
+ if (!nested) return void 0;
747
+ return { NOT: nested };
748
+ }
749
+ throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the Prisma compatibility adapter.", {
750
+ operation: "adapter.where",
751
+ meta: {
752
+ feature: "rawWhere",
753
+ sql: condition.sql
754
+ }
755
+ });
756
+ }
757
+ buildFindArgs(spec) {
758
+ return {
759
+ include: this.toQueryInclude(spec.relationLoads),
760
+ where: this.toQueryWhere(spec.where),
761
+ orderBy: this.toQueryOrderBy(spec.orderBy),
762
+ select: this.toQuerySelect(spec.columns),
763
+ skip: spec.offset,
764
+ take: spec.limit
765
+ };
766
+ }
767
+ toQueryInclude(relationLoads) {
768
+ if (!relationLoads || relationLoads.length === 0) return void 0;
769
+ return relationLoads.reduce((include, plan) => {
770
+ const nestedInclude = this.toQueryInclude(plan.relationLoads);
771
+ const nestedSelect = this.toQuerySelect(plan.columns);
772
+ const nestedWhere = this.toQueryWhere(plan.constraint);
773
+ const nestedOrderBy = this.toQueryOrderBy(plan.orderBy);
774
+ if (!nestedInclude && !nestedSelect && !nestedWhere && !nestedOrderBy && plan.offset === void 0 && plan.limit === void 0) {
775
+ include[plan.relation] = true;
776
+ return include;
777
+ }
778
+ include[plan.relation] = {
779
+ where: nestedWhere,
780
+ orderBy: nestedOrderBy,
781
+ select: nestedSelect,
782
+ include: nestedInclude,
783
+ skip: plan.offset,
784
+ take: plan.limit
785
+ };
786
+ return include;
787
+ }, {});
788
+ }
789
+ resolveDelegate(target) {
790
+ const tableName = target.table ? this.normalizeCandidate(target.table) : "";
791
+ const singularTableName = tableName ? `${str(tableName).singular()}` : "";
792
+ const camelTableName = tableName ? `${str(tableName).camel()}` : "";
793
+ const camelSingularTableName = tableName ? `${str(tableName).camel().singular()}` : "";
794
+ const candidates = this.unique([
795
+ target.table ? this.mapping[target.table] : "",
796
+ tableName,
797
+ singularTableName ? this.mapping[singularTableName] : "",
798
+ singularTableName,
799
+ camelTableName ? this.mapping[camelTableName] : "",
800
+ camelTableName,
801
+ camelSingularTableName ? this.mapping[camelSingularTableName] : "",
802
+ camelSingularTableName,
803
+ target.modelName ? this.mapping[target.modelName] : "",
804
+ target.modelName ? this.normalizeCandidate(target.modelName) : "",
805
+ target.modelName ? inferDelegateName(target.modelName) : "",
806
+ target.modelName ? this.mapping[inferDelegateName(target.modelName)] : ""
807
+ ]);
808
+ const resolved = candidates.map((candidate) => this.delegates[candidate]).find(Boolean);
809
+ if (resolved) return resolved;
810
+ throw new MissingDelegateException("Prisma delegate could not be resolved for adapter target.", {
811
+ operation: "getDelegate",
812
+ model: target.modelName,
813
+ delegate: target.table,
814
+ meta: {
815
+ target,
816
+ candidates
817
+ }
818
+ });
819
+ }
820
+ async select(spec) {
821
+ return await this.resolveDelegate(spec.target).findMany(this.buildFindArgs(spec));
822
+ }
823
+ async selectOne(spec) {
824
+ return await this.resolveDelegate(spec.target).findFirst(this.buildFindArgs(spec));
825
+ }
826
+ async insert(spec) {
827
+ return await this.resolveDelegate(spec.target).create({ data: spec.values });
828
+ }
829
+ async insertMany(spec) {
830
+ const delegate = this.resolveDelegate(spec.target);
831
+ if (typeof delegate.createMany === "function") {
832
+ const result = await delegate.createMany({
833
+ data: spec.values,
834
+ skipDuplicates: spec.ignoreDuplicates
835
+ });
836
+ if (typeof result === "number") return result;
837
+ return typeof result?.count === "number" ? result.count : spec.values.length;
838
+ }
839
+ let inserted = 0;
840
+ for (const values of spec.values) try {
841
+ await delegate.create({ data: values });
842
+ inserted += 1;
843
+ } catch (error) {
844
+ if (!spec.ignoreDuplicates) throw error;
845
+ }
846
+ return spec.ignoreDuplicates ? inserted : spec.values.length;
847
+ }
848
+ async update(spec) {
849
+ const delegate = this.resolveDelegate(spec.target);
850
+ const where = this.toQueryWhere(spec.where);
851
+ if (!where) return null;
852
+ return await delegate.update({
853
+ where,
854
+ data: spec.values
855
+ });
856
+ }
857
+ async updateMany(spec) {
858
+ const delegate = this.resolveDelegate(spec.target);
859
+ const where = this.toQueryWhere(spec.where);
860
+ if (typeof delegate.updateMany === "function") {
861
+ const result = await delegate.updateMany({
862
+ where,
863
+ data: spec.values
864
+ });
865
+ if (typeof result === "number") return result;
866
+ return typeof result?.count === "number" ? result.count : 0;
867
+ }
868
+ const rows = await delegate.findMany({ where });
869
+ await Promise.all(rows.map(async (row) => {
870
+ await delegate.update({
871
+ where: row,
872
+ data: spec.values
873
+ });
874
+ }));
875
+ return rows.length;
876
+ }
877
+ async delete(spec) {
878
+ const delegate = this.resolveDelegate(spec.target);
879
+ const where = this.toQueryWhere(spec.where);
880
+ if (!where) return null;
881
+ return await delegate.delete({ where });
882
+ }
883
+ async deleteMany(spec) {
884
+ const delegate = this.resolveDelegate(spec.target);
885
+ const where = this.toQueryWhere(spec.where);
886
+ const rows = await delegate.findMany({ where });
887
+ await Promise.all(rows.map(async (row) => {
888
+ await delegate.delete({ where: row });
889
+ }));
890
+ return rows.length;
891
+ }
892
+ async count(spec) {
893
+ return await this.resolveDelegate(spec.target).count({ where: this.toQueryWhere(spec.where) });
894
+ }
895
+ async exists(spec) {
896
+ return await this.selectOne({
897
+ ...spec,
898
+ limit: 1
899
+ }) != null;
900
+ }
901
+ async loadRelations(_spec) {
902
+ throw new UnsupportedAdapterFeatureException("Relation batch loading is not supported by the Prisma compatibility adapter yet.", {
903
+ operation: "adapter.loadRelations",
904
+ meta: { feature: "relationLoads" }
905
+ });
906
+ }
907
+ async transaction(callback, context = {}) {
908
+ if (!this.hasTransactionSupport(this.prisma)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the Prisma compatibility adapter.", {
909
+ operation: "adapter.transaction",
910
+ meta: { feature: "transactions" }
911
+ });
912
+ return await this.prisma.$transaction(async (transactionClient) => {
913
+ return await callback(new PrismaDatabaseAdapter(transactionClient, this.mapping));
914
+ }, {
915
+ isolationLevel: context.isolationLevel,
916
+ maxWait: context.maxWait,
917
+ timeout: context.timeout
918
+ });
919
+ }
920
+ };
921
+ const createPrismaDatabaseAdapter = (prisma, mapping = {}) => {
922
+ return new PrismaDatabaseAdapter(prisma, mapping);
923
+ };
924
+ const createPrismaCompatibilityAdapter = createPrismaDatabaseAdapter;
925
+
926
+ //#endregion
927
+ //#region src/Attribute.ts
928
+ var Attribute = class Attribute {
929
+ get;
930
+ set;
931
+ constructor(options = {}) {
932
+ this.get = options.get;
933
+ this.set = options.set;
934
+ }
935
+ static make(options) {
936
+ return new Attribute(options);
937
+ }
938
+ static isAttribute(value) {
939
+ if (!value || typeof value !== "object") return false;
940
+ return value instanceof Attribute;
941
+ }
942
+ };
943
+
944
+ //#endregion
945
+ //#region src/casts.ts
946
+ const builtinCasts = {
947
+ string: {
948
+ get: (value) => value == null ? value : String(value),
949
+ set: (value) => value == null ? value : String(value)
950
+ },
951
+ number: {
952
+ get: (value) => value == null ? value : Number(value),
953
+ set: (value) => value == null ? value : Number(value)
954
+ },
955
+ boolean: {
956
+ get: (value) => value == null ? value : Boolean(value),
957
+ set: (value) => value == null ? value : Boolean(value)
958
+ },
959
+ date: {
960
+ get: (value) => {
961
+ if (value == null || value instanceof Date) return value;
962
+ return new Date(String(value));
963
+ },
964
+ set: (value) => {
965
+ if (value == null || value instanceof Date) return value;
966
+ return new Date(String(value));
967
+ }
968
+ },
969
+ json: {
970
+ get: (value) => {
971
+ if (value == null || typeof value !== "string") return value;
972
+ try {
973
+ return JSON.parse(value);
974
+ } catch {
975
+ return value;
976
+ }
977
+ },
978
+ set: (value) => {
979
+ if (value == null || typeof value === "string") return value;
980
+ return JSON.stringify(value);
981
+ }
982
+ },
983
+ array: {
984
+ get: (value) => {
985
+ if (Array.isArray(value)) return value;
986
+ if (typeof value === "string") try {
987
+ const parsed = JSON.parse(value);
988
+ return Array.isArray(parsed) ? parsed : [parsed];
989
+ } catch {
990
+ return [value];
991
+ }
992
+ if (value == null) return value;
993
+ return [value];
994
+ },
995
+ set: (value) => {
996
+ if (value == null) return value;
997
+ return Array.isArray(value) ? value : [value];
998
+ }
999
+ }
1000
+ };
1001
+ function resolveCast(definition) {
1002
+ if (typeof definition === "string") return builtinCasts[definition];
1003
+ return definition;
1004
+ }
1005
+
1006
+ //#endregion
1007
+ //#region src/database/ForeignKeyBuilder.ts
1008
+ /**
1009
+ * The ForeignKeyBuilder class provides a fluent interface for defining
1010
+ * foreign key constraints in a migration. It allows you to specify
1011
+ * the referenced table and column, as well as actions to take on
1012
+ * delete and aliases for the relation.
1013
+ *
1014
+ * @author Legacy (3m1n3nc3)
1015
+ * @since 0.2.2
1016
+ */
1017
+ var ForeignKeyBuilder = class {
1018
+ foreignKey;
1019
+ constructor(foreignKey) {
1020
+ this.foreignKey = foreignKey;
1021
+ }
1022
+ /**
1023
+ * Defines the referenced table and column for this foreign key constraint.
1024
+ *
1025
+ * @param table
1026
+ * @param column
1027
+ * @returns
1028
+ */
1029
+ references(table, column) {
1030
+ this.foreignKey.referencesTable = table;
1031
+ this.foreignKey.referencesColumn = column;
1032
+ return this;
1033
+ }
1034
+ /**
1035
+ * Defines the action to take when a referenced record is deleted, such
1036
+ * as "CASCADE", "SET NULL", or "RESTRICT".
1037
+ *
1038
+ * @param action
1039
+ * @returns
1040
+ */
1041
+ onDelete(action) {
1042
+ this.foreignKey.onDelete = action;
1043
+ return this;
1044
+ }
1045
+ /**
1046
+ * Defines an alias for the relation represented by this foreign key, which
1047
+ * can be used in the ORM for more intuitive access to related models.
1048
+ *
1049
+ * @param name
1050
+ * @returns
1051
+ */
1052
+ alias(name) {
1053
+ this.foreignKey.relationAlias = name;
1054
+ return this;
1055
+ }
1056
+ /**
1057
+ * Defines an alias for the inverse relation represented by this foreign key.
1058
+ *
1059
+ * @param name
1060
+ * @returns
1061
+ */
1062
+ inverseAlias(name) {
1063
+ this.foreignKey.inverseRelationAlias = name;
1064
+ return this;
1065
+ }
1066
+ /**
1067
+ * Defines an alias for the foreign key field itself, which can be
1068
+ * used in the ORM for more intuitive access to the foreign key value.
1069
+ *
1070
+ * @param fieldName
1071
+ * @returns
1072
+ */
1073
+ as(fieldName) {
1074
+ this.foreignKey.fieldAlias = fieldName;
1075
+ return this;
1076
+ }
1077
+ };
1078
+
1079
+ //#endregion
1080
+ //#region src/database/TableBuilder.ts
1081
+ const PRISMA_ENUM_MEMBER_REGEX$1 = /^[A-Za-z][A-Za-z0-9_]*$/;
1082
+ const normalizeEnumMember = (columnName, value) => {
1083
+ const normalized = value.trim();
1084
+ if (!normalized) throw new Error(`Enum column [${columnName}] must define only non-empty values.`);
1085
+ if (!PRISMA_ENUM_MEMBER_REGEX$1.test(normalized)) throw new Error(`Enum column [${columnName}] contains invalid Prisma enum value [${normalized}].`);
1086
+ return normalized;
1087
+ };
1088
+ const normalizeEnumMembers = (columnName, values) => {
1089
+ const normalizedValues = values.map((value) => normalizeEnumMember(columnName, value));
1090
+ const seen = /* @__PURE__ */ new Set();
1091
+ for (const value of normalizedValues) {
1092
+ if (seen.has(value)) throw new Error(`Enum column [${columnName}] contains duplicate enum value [${value}].`);
1093
+ seen.add(value);
1094
+ }
1095
+ return normalizedValues;
1096
+ };
1097
+ /**
1098
+ * The EnumBuilder class provides a fluent interface for configuring enum columns
1099
+ * after they are defined on a table.
1100
+ *
1101
+ * @author Legacy (3m1n3nc3)
1102
+ * @since 0.2.3
1103
+ */
1104
+ var EnumBuilder = class {
1105
+ tableBuilder;
1106
+ columnName;
1107
+ constructor(tableBuilder, columnName) {
1108
+ this.tableBuilder = tableBuilder;
1109
+ this.columnName = columnName;
1110
+ }
1111
+ /**
1112
+ * Defines the Prisma enum name for this column.
1113
+ *
1114
+ * @param name
1115
+ * @returns
1116
+ */
1117
+ enumName(name) {
1118
+ this.tableBuilder.enumName(name, this.columnName);
1119
+ return this;
252
1120
  }
253
1121
  /**
254
1122
  * Marks the enum column as nullable.
@@ -952,272 +1820,34 @@ const buildFieldLine = (column) => {
952
1820
  return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${updatedAt}${mapped}`;
953
1821
  };
954
1822
  /**
955
- * Build a Prisma enum block string based on an enum name and its values, validating that
956
- * at least one value is provided.
957
- *
958
- * @param enumName The name of the enum to create.
959
- * @param values The array of values for the enum.
960
- * @returns The Prisma enum block string.
961
- */
962
- const buildEnumBlock = (enumName, values) => {
963
- if (values.length === 0) throw new ArkormException(`Enum [${enumName}] must define at least one value.`);
964
- return `enum ${enumName} {\n${values.map((value) => ` ${value}`).join("\n")}\n}`;
965
- };
966
- /**
967
- * Find the Prisma enum block in a schema string that corresponds to a given enum
968
- * name, returning its details if found.
969
- *
970
- * @param schema The Prisma schema string to search for the enum block.
971
- * @param enumName The name of the enum to find in the schema.
972
- * @returns
973
- */
974
- const findEnumBlock = (schema, enumName) => {
975
- const candidates = [...schema.matchAll(PRISMA_ENUM_REGEX)];
976
- for (const match of candidates) {
977
- const block = match[0];
978
- const matchedEnumName = match[1];
979
- const start = match.index ?? 0;
980
- const end = start + block.length;
981
- if (matchedEnumName === enumName) return {
982
- enumName: matchedEnumName,
983
- block,
984
- start,
985
- end
986
- };
987
- }
988
- return null;
989
- };
990
- /**
991
- * Ensure that Prisma enum blocks exist in the schema for any enum columns defined in a
992
- * create or alter table operation, adding them if necessary and validating against
993
- * existing blocks.
994
- *
995
- * @param schema The current Prisma schema string to check and modify.
996
- * @param columns The array of schema column definitions to check for enum types and ensure corresponding blocks exist for.
997
- * @returns
998
- */
999
- const ensureEnumBlocks = (schema, columns) => {
1000
- let nextSchema = schema;
1001
- for (const column of columns) {
1002
- if (column.type !== "enum") continue;
1003
- const enumName = resolveEnumName(column);
1004
- const enumValues = column.enumValues ?? [];
1005
- const existing = findEnumBlock(nextSchema, enumName);
1006
- if (existing) {
1007
- const existingValues = validateEnumValues(column, enumName, extractEnumBlockValues(existing.block));
1008
- if (enumValues.length === 0) {
1009
- validateEnumDefaultValue(column, enumName, existingValues);
1010
- continue;
1011
- }
1012
- const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1013
- if (existingValues.join("|") !== normalizedEnumValues.join("|")) throw new ArkormException(`Prisma enum [${enumName}] already exists with different values.`);
1014
- validateEnumDefaultValue(column, enumName, existingValues);
1015
- continue;
1016
- }
1017
- if (enumValues.length === 0) throw new ArkormException(`Prisma enum [${enumName}] was not found for column [${column.name}].`);
1018
- const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1019
- validateEnumDefaultValue(column, enumName, normalizedEnumValues);
1020
- const block = buildEnumBlock(enumName, normalizedEnumValues);
1021
- nextSchema = `${nextSchema.trimEnd()}\n\n${block}\n`;
1022
- }
1023
- return nextSchema;
1024
- };
1025
- /**
1026
- * Build a Prisma model-level @@index definition line.
1027
- *
1028
- * @param index The schema index definition to convert to a Prisma \@\@index line.
1029
- * @returns
1030
- */
1031
- const buildIndexLine = (index) => {
1032
- return ` @@index([${index.columns.join(", ")}]${typeof index.name === "string" && index.name.trim().length > 0 ? `, name: "${index.name.replace(/"/g, "\\\"")}"` : ""})`;
1033
- };
1034
- /**
1035
- * Derive a relation field name from a foreign key column name by applying
1036
- * common conventions, such as removing "Id" suffixes and converting to camelCase.
1037
- *
1038
- * @param columnName The name of the foreign key column.
1039
- * @returns The derived relation field name.
1040
- */
1041
- const deriveRelationFieldName = (columnName) => {
1042
- const trimmed = columnName.trim();
1043
- if (!trimmed) return "relation";
1044
- if (trimmed.endsWith("Id") && trimmed.length > 2) {
1045
- const root = trimmed.slice(0, -2);
1046
- return `${root.charAt(0).toLowerCase()}${root.slice(1)}`;
1047
- }
1048
- if (trimmed.endsWith("_id") && trimmed.length > 3) return trimmed.slice(0, -3).replace(/_([a-zA-Z0-9])/g, (_, letter) => letter.toUpperCase());
1049
- return `${trimmed.charAt(0).toLowerCase()}${trimmed.slice(1)}`;
1050
- };
1051
- /**
1052
- * Derive a relation name for both sides of a relation based on the
1053
- * source and target model names, using an explicit alias if provided or a
1054
- * convention of combining the full source model name with the target model name.
1055
- *
1056
- * @param sourceModelName The name of the source model in the relation.
1057
- * @param targetModelName The name of the target model in the relation.
1058
- * @param explicitAlias An optional explicit alias for the relation.
1059
- * @returns The derived or explicit relation alias.
1060
- */
1061
- const deriveRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
1062
- if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
1063
- return [sourceModelName, targetModelName].sort((left, right) => left.localeCompare(right)).join("");
1064
- };
1065
- const deriveInverseRelationAlias = deriveRelationAlias;
1066
- const deriveSingularFieldName = (modelName) => {
1067
- if (!modelName) return "item";
1068
- return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
1069
- };
1070
- const deriveCollectionFieldName = (modelName) => {
1071
- if (!modelName) return "items";
1072
- const camel = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
1073
- if (camel.endsWith("s")) return `${camel}es`;
1074
- return `${camel}s`;
1075
- };
1076
- const resolveForeignKeyColumn = (columns, foreignKey) => {
1077
- return columns.find((column) => column.name === foreignKey.column);
1078
- };
1079
- const isOneToOneForeignKey = (column) => {
1080
- return Boolean(column?.unique || column?.primary);
1081
- };
1082
- /**
1083
- * Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
1084
- *
1085
- * @param action The foreign key action to format.
1086
- * @returns The corresponding Prisma onDelete action string.
1087
- */
1088
- const formatRelationAction = (action) => {
1089
- if (action === "cascade") return "Cascade";
1090
- if (action === "restrict") return "Restrict";
1091
- if (action === "setNull") return "SetNull";
1092
- if (action === "setDefault") return "SetDefault";
1093
- return "NoAction";
1094
- };
1095
- /**
1096
- * Build a Prisma relation field line based on a SchemaForeignKey
1097
- * definition, including relation name and onDelete action.
1098
- *
1099
- * @param foreignKey The foreign key definition to convert to a relation line.
1100
- * @returns The corresponding Prisma schema line for the relation field.
1101
- */
1102
- const buildRelationLine = (sourceModelName, foreignKey, columns = []) => {
1103
- if (!foreignKey.referencesTable.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced table.`);
1104
- if (!foreignKey.referencesColumn.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced column.`);
1105
- const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1106
- const fieldName = foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column);
1107
- const targetModel = toModelName(foreignKey.referencesTable);
1108
- const relationName = deriveRelationAlias(sourceModelName, targetModel, foreignKey.relationAlias?.trim());
1109
- const optional = sourceColumn?.nullable ? "?" : "";
1110
- const onDelete = foreignKey.onDelete ? `, onDelete: ${formatRelationAction(foreignKey.onDelete)}` : "";
1111
- return ` ${fieldName} ${targetModel}${optional} @relation("${relationName.replace(/"/g, "\\\"")}", fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
1112
- };
1113
- /**
1114
- * Build a Prisma relation field line for the inverse side of a relation, based
1115
- * on the source and target model names and the foreign key definition, using
1116
- * naming conventions and any explicit inverse alias provided.
1117
- *
1118
- * @param sourceModelName The name of the source model in the relation.
1119
- * @param targetModelName The name of the target model in the relation.
1120
- * @param foreignKey The foreign key definition for the relation.
1121
- * @returns The Prisma schema line for the inverse relation field.
1122
- */
1123
- const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey, columns = []) => {
1124
- const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1125
- const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
1126
- const relationName = deriveRelationAlias(sourceModelName, targetModelName, foreignKey.relationAlias?.trim());
1127
- return ` ${fieldName} ${isOneToOneForeignKey(sourceColumn) ? `${sourceModelName}?` : `${sourceModelName}[]`} @relation("${relationName.replace(/"/g, "\\\"")}")`;
1128
- };
1129
- /**
1130
- * Inject a line into the body of a Prisma model block if it does not already
1131
- * exist, using a provided existence check function to determine if the line
1132
- * is already present.
1133
- *
1134
- * @param bodyLines The lines of the model block body to modify.
1135
- * @param line The line to inject if it does not already exist.
1136
- * @param exists A function that checks if a given line already exists in the body.
1137
- * @returns
1138
- */
1139
- const injectLineIntoModelBody = (bodyLines, line, exists) => {
1140
- if (bodyLines.some(exists)) return bodyLines;
1141
- const insertIndex = Math.max(1, bodyLines.length - 1);
1142
- bodyLines.splice(insertIndex, 0, line);
1143
- return bodyLines;
1144
- };
1145
- /**
1146
- * Apply inverse relation definitions to a Prisma schema string based on the
1147
- * foreign keys defined in a create or alter table operation, ensuring that
1148
- * related models have corresponding relation fields for bi-directional navigation.
1149
- *
1150
- * @param schema The Prisma schema string to modify.
1151
- * @param sourceModelName The name of the source model in the relation.
1152
- * @param foreignKeys An array of foreign key definitions to process.
1153
- * @returns The updated Prisma schema string with inverse relations applied.
1154
- */
1155
- const applyInverseRelations = (schema, sourceModelName, foreignKeys, columns = []) => {
1156
- let nextSchema = schema;
1157
- for (const foreignKey of foreignKeys) {
1158
- const targetModel = findModelBlock(nextSchema, foreignKey.referencesTable);
1159
- if (!targetModel) continue;
1160
- const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1161
- const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey, columns);
1162
- const targetBodyLines = targetModel.block.split("\n");
1163
- const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
1164
- const fieldRegex = new RegExp(`^\\s*${escapeRegex(fieldName)}\\s+`);
1165
- injectLineIntoModelBody(targetBodyLines, inverseLine, (line) => fieldRegex.test(line));
1166
- const updatedTarget = targetBodyLines.join("\n");
1167
- nextSchema = `${nextSchema.slice(0, targetModel.start)}${updatedTarget}${nextSchema.slice(targetModel.end)}`;
1168
- }
1169
- return nextSchema;
1170
- };
1171
- /**
1172
- * Build a Prisma model block string based on a SchemaTableCreateOperation, including
1173
- * all fields and any necessary mapping.
1823
+ * Build a Prisma enum block string based on an enum name and its values, validating that
1824
+ * at least one value is provided.
1174
1825
  *
1175
- * @param operation The schema table create operation to convert.
1176
- * @returns The corresponding Prisma model block string.
1826
+ * @param enumName The name of the enum to create.
1827
+ * @param values The array of values for the enum.
1828
+ * @returns The Prisma enum block string.
1177
1829
  */
1178
- const buildModelBlock = (operation) => {
1179
- const modelName = toModelName(operation.table);
1180
- const mapped = operation.table !== modelName.toLowerCase();
1181
- const fields = operation.columns.map(buildFieldLine);
1182
- const relations = (operation.foreignKeys ?? []).map((foreignKey) => buildRelationLine(modelName, foreignKey, operation.columns));
1183
- const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
1184
- return `model ${modelName} {\n${(metadata.length > 0 ? [
1185
- ...fields,
1186
- ...relations,
1187
- "",
1188
- ...metadata
1189
- ] : [...fields, ...relations]).join("\n")}\n}`;
1830
+ const buildEnumBlock = (enumName, values) => {
1831
+ if (values.length === 0) throw new ArkormException(`Enum [${enumName}] must define at least one value.`);
1832
+ return `enum ${enumName} {\n${values.map((value) => ` ${value}`).join("\n")}\n}`;
1190
1833
  };
1191
1834
  /**
1192
- * Find the Prisma model block in a schema string that corresponds to a given
1193
- * table name, using both explicit mapping and naming conventions.
1835
+ * Find the Prisma enum block in a schema string that corresponds to a given enum
1836
+ * name, returning its details if found.
1194
1837
  *
1195
- * @param schema
1196
- * @param table
1838
+ * @param schema The Prisma schema string to search for the enum block.
1839
+ * @param enumName The name of the enum to find in the schema.
1197
1840
  * @returns
1198
1841
  */
1199
- const findModelBlock = (schema, table) => {
1200
- const candidates = [...schema.matchAll(PRISMA_MODEL_REGEX)];
1201
- const explicitMapRegex = new RegExp(`@@map\\("${escapeRegex(table)}"\\)`);
1842
+ const findEnumBlock = (schema, enumName) => {
1843
+ const candidates = [...schema.matchAll(PRISMA_ENUM_REGEX)];
1202
1844
  for (const match of candidates) {
1203
1845
  const block = match[0];
1204
- const modelName = match[1];
1846
+ const matchedEnumName = match[1];
1205
1847
  const start = match.index ?? 0;
1206
1848
  const end = start + block.length;
1207
- if (explicitMapRegex.test(block)) return {
1208
- modelName,
1209
- block,
1210
- start,
1211
- end
1212
- };
1213
- if (modelName.toLowerCase() === table.toLowerCase()) return {
1214
- modelName,
1215
- block,
1216
- start,
1217
- end
1218
- };
1219
- if (modelName.toLowerCase() === toModelName(table).toLowerCase()) return {
1220
- modelName,
1849
+ if (matchedEnumName === enumName) return {
1850
+ enumName: matchedEnumName,
1221
1851
  block,
1222
1852
  start,
1223
1853
  end
@@ -1226,600 +1856,543 @@ const findModelBlock = (schema, table) => {
1226
1856
  return null;
1227
1857
  };
1228
1858
  /**
1229
- * Apply a create table operation to a Prisma schema string, adding a new model
1230
- * block for the specified table and fields.
1231
- *
1232
- * @param schema The current Prisma schema string.
1233
- * @param operation The schema table create operation to apply.
1234
- * @returns The updated Prisma schema string with the new model block.
1235
- */
1236
- const applyCreateTableOperation = (schema, operation) => {
1237
- if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
1238
- const schemaWithEnums = ensureEnumBlocks(schema, operation.columns);
1239
- const block = buildModelBlock(operation);
1240
- return applyInverseRelations(`${schemaWithEnums.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? [], operation.columns);
1241
- };
1242
- /**
1243
- * Apply an alter table operation to a Prisma schema string, modifying the model
1244
- * block for the specified table by adding and removing fields as needed.
1859
+ * Ensure that Prisma enum blocks exist in the schema for any enum columns defined in a
1860
+ * create or alter table operation, adding them if necessary and validating against
1861
+ * existing blocks.
1245
1862
  *
1246
- * @param schema The current Prisma schema string.
1247
- * @param operation The schema table alter operation to apply.
1248
- * @returns The updated Prisma schema string with the modified model block.
1863
+ * @param schema The current Prisma schema string to check and modify.
1864
+ * @param columns The array of schema column definitions to check for enum types and ensure corresponding blocks exist for.
1865
+ * @returns
1249
1866
  */
1250
- const applyAlterTableOperation = (schema, operation) => {
1251
- const model = findModelBlock(schema, operation.table);
1252
- if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
1253
- const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
1254
- const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
1255
- if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
1256
- let block = refreshedModel.block;
1257
- const bodyLines = block.split("\n");
1258
- operation.dropColumns.forEach((column) => {
1259
- const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
1260
- for (let index = 0; index < bodyLines.length; index += 1) if (columnRegex.test(bodyLines[index])) {
1261
- bodyLines.splice(index, 1);
1262
- return;
1867
+ const ensureEnumBlocks = (schema, columns) => {
1868
+ let nextSchema = schema;
1869
+ for (const column of columns) {
1870
+ if (column.type !== "enum") continue;
1871
+ const enumName = resolveEnumName(column);
1872
+ const enumValues = column.enumValues ?? [];
1873
+ const existing = findEnumBlock(nextSchema, enumName);
1874
+ if (existing) {
1875
+ const existingValues = validateEnumValues(column, enumName, extractEnumBlockValues(existing.block));
1876
+ if (enumValues.length === 0) {
1877
+ validateEnumDefaultValue(column, enumName, existingValues);
1878
+ continue;
1879
+ }
1880
+ const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1881
+ if (existingValues.join("|") !== normalizedEnumValues.join("|")) throw new ArkormException(`Prisma enum [${enumName}] already exists with different values.`);
1882
+ validateEnumDefaultValue(column, enumName, existingValues);
1883
+ continue;
1263
1884
  }
1264
- });
1265
- operation.addColumns.forEach((column) => {
1266
- const fieldLine = buildFieldLine(column);
1267
- const columnRegex = new RegExp(`^\\s*${escapeRegex(column.name)}\\s+`);
1268
- if (bodyLines.some((line) => columnRegex.test(line))) return;
1269
- const defaultInsertIndex = Math.max(1, bodyLines.length - 1);
1270
- const afterInsertIndex = typeof column.after === "string" && column.after.length > 0 ? bodyLines.findIndex((line) => new RegExp(`^\\s*${escapeRegex(column.after)}\\s+`).test(line)) : -1;
1271
- const insertIndex = afterInsertIndex > 0 ? Math.min(afterInsertIndex + 1, defaultInsertIndex) : defaultInsertIndex;
1272
- bodyLines.splice(insertIndex, 0, fieldLine);
1273
- });
1274
- (operation.addIndexes ?? []).forEach((index) => {
1275
- const indexLine = buildIndexLine(index);
1276
- if (bodyLines.some((line) => line.trim() === indexLine.trim())) return;
1277
- const insertIndex = Math.max(1, bodyLines.length - 1);
1278
- bodyLines.splice(insertIndex, 0, indexLine);
1279
- });
1280
- for (const foreignKey of operation.addForeignKeys ?? []) {
1281
- const relationLine = buildRelationLine(model.modelName, foreignKey, operation.addColumns);
1282
- const relationRegex = new RegExp(`^\\s*${escapeRegex(foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column))}\\s+`);
1283
- injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
1885
+ if (enumValues.length === 0) throw new ArkormException(`Prisma enum [${enumName}] was not found for column [${column.name}].`);
1886
+ const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1887
+ validateEnumDefaultValue(column, enumName, normalizedEnumValues);
1888
+ const block = buildEnumBlock(enumName, normalizedEnumValues);
1889
+ nextSchema = `${nextSchema.trimEnd()}\n\n${block}\n`;
1284
1890
  }
1285
- block = bodyLines.join("\n");
1286
- return applyInverseRelations(`${schemaWithEnums.slice(0, refreshedModel.start)}${block}${schemaWithEnums.slice(refreshedModel.end)}`, model.modelName, operation.addForeignKeys ?? [], operation.addColumns);
1287
- };
1288
- /**
1289
- * Apply a drop table operation to a Prisma schema string, removing the model block
1290
- * for the specified table.
1291
- */
1292
- const applyDropTableOperation = (schema, operation) => {
1293
- const model = findModelBlock(schema, operation.table);
1294
- if (!model) return schema;
1295
- const before = schema.slice(0, model.start).trimEnd();
1296
- const after = schema.slice(model.end).trimStart();
1297
- return `${before}${before && after ? "\n\n" : ""}${after}`;
1891
+ return nextSchema;
1298
1892
  };
1299
1893
  /**
1300
- * The SchemaBuilder class provides a fluent interface for defining
1301
- * database schema operations in a migration, such as creating, altering, and
1302
- * dropping tables.
1894
+ * Build a Prisma model-level @@index definition line.
1303
1895
  *
1304
- * @param schema The current Prisma schema string.
1305
- * @param operations The list of schema operations to apply.
1306
- * @returns The updated Prisma schema string after applying all operations.
1896
+ * @param index The schema index definition to convert to a Prisma \@\@index line.
1897
+ * @returns
1307
1898
  */
1308
- const applyOperationsToPrismaSchema = (schema, operations) => {
1309
- return operations.reduce((current, operation) => {
1310
- if (operation.type === "createTable") return applyCreateTableOperation(current, operation);
1311
- if (operation.type === "alterTable") return applyAlterTableOperation(current, operation);
1312
- return applyDropTableOperation(current, operation);
1313
- }, schema);
1899
+ const buildIndexLine = (index) => {
1900
+ return ` @@index([${index.columns.join(", ")}]${typeof index.name === "string" && index.name.trim().length > 0 ? `, name: "${index.name.replace(/"/g, "\\\"")}"` : ""})`;
1314
1901
  };
1315
1902
  /**
1316
- * Run a Prisma CLI command using npx, capturing and throwing any errors that occur.
1903
+ * Derive a relation field name from a foreign key column name by applying
1904
+ * common conventions, such as removing "Id" suffixes and converting to camelCase.
1317
1905
  *
1318
- * @param args The arguments to pass to the Prisma CLI command.
1319
- * @param cwd The current working directory to run the command in.
1320
- * @returns void
1906
+ * @param columnName The name of the foreign key column.
1907
+ * @returns The derived relation field name.
1321
1908
  */
1322
- const runPrismaCommand = (args, cwd) => {
1323
- const command = spawnSync("npx", ["prisma", ...args], {
1324
- cwd,
1325
- encoding: "utf-8"
1326
- });
1327
- if (command.status === 0) return;
1328
- const errorOutput = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
1329
- throw new ArkormException(errorOutput ? `Prisma command failed: prisma ${args.join(" ")}\n${errorOutput}` : `Prisma command failed: prisma ${args.join(" ")}`);
1909
+ const deriveRelationFieldName = (columnName) => {
1910
+ const trimmed = columnName.trim();
1911
+ if (!trimmed) return "relation";
1912
+ if (trimmed.endsWith("Id") && trimmed.length > 2) {
1913
+ const root = trimmed.slice(0, -2);
1914
+ return `${root.charAt(0).toLowerCase()}${root.slice(1)}`;
1915
+ }
1916
+ if (trimmed.endsWith("_id") && trimmed.length > 3) return trimmed.slice(0, -3).replace(/_([a-zA-Z0-9])/g, (_, letter) => letter.toUpperCase());
1917
+ return `${trimmed.charAt(0).toLowerCase()}${trimmed.slice(1)}`;
1330
1918
  };
1331
1919
  /**
1332
- * Generate a new migration file with a given name and options, including
1333
- * writing the file to disk if specified.
1920
+ * Derive a relation name for both sides of a relation based on the
1921
+ * source and target model names, using an explicit alias if provided or a
1922
+ * convention of combining the full source model name with the target model name.
1334
1923
  *
1335
- * @param name
1336
- * @returns
1924
+ * @param sourceModelName The name of the source model in the relation.
1925
+ * @param targetModelName The name of the target model in the relation.
1926
+ * @param explicitAlias An optional explicit alias for the relation.
1927
+ * @returns The derived or explicit relation alias.
1337
1928
  */
1338
- const resolveMigrationClassName = (name) => {
1339
- const cleaned = name.replace(/[^a-zA-Z0-9]+/g, " ").trim();
1340
- if (!cleaned) return "GeneratedMigration";
1341
- return `${cleaned.split(/\s+/g).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}Migration`;
1929
+ const deriveRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
1930
+ if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
1931
+ return [sourceModelName, targetModelName].sort((left, right) => left.localeCompare(right)).join("");
1342
1932
  };
1343
- /**
1344
- * Pad a number with leading zeros to ensure it is at least two digits, for
1345
- * use in migration timestamps.
1346
- *
1347
- * @param value
1348
- * @returns
1349
- */
1350
- const pad = (value) => String(value).padStart(2, "0");
1351
- /**
1352
- * Create a timestamp string in the format YYYYMMDDHHMMSS for use in migration
1353
- * file names, based on the current date and time or a provided date.
1354
- *
1355
- * @param date
1356
- * @returns
1357
- */
1358
- const createMigrationTimestamp = (date = /* @__PURE__ */ new Date()) => {
1359
- return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
1933
+ const deriveInverseRelationAlias = deriveRelationAlias;
1934
+ const deriveSingularFieldName = (modelName) => {
1935
+ if (!modelName) return "item";
1936
+ return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
1360
1937
  };
1361
- /**
1362
- * Convert a migration name to a slug suitable for use in a file name, by
1363
- * lowercasing and replacing non-alphanumeric characters with underscores.
1938
+ const deriveCollectionFieldName = (modelName) => {
1939
+ if (!modelName) return "items";
1940
+ const camel = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
1941
+ if (camel.endsWith("s")) return `${camel}es`;
1942
+ return `${camel}s`;
1943
+ };
1944
+ const resolveForeignKeyColumn = (columns, foreignKey) => {
1945
+ return columns.find((column) => column.name === foreignKey.column);
1946
+ };
1947
+ const isOneToOneForeignKey = (column) => {
1948
+ return Boolean(column?.unique || column?.primary);
1949
+ };
1950
+ /**
1951
+ * Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
1364
1952
  *
1365
- * @param name
1366
- * @returns
1953
+ * @param action The foreign key action to format.
1954
+ * @returns The corresponding Prisma onDelete action string.
1367
1955
  */
1368
- const toMigrationFileSlug = (name) => {
1369
- return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration";
1956
+ const formatRelationAction = (action) => {
1957
+ if (action === "cascade") return "Cascade";
1958
+ if (action === "restrict") return "Restrict";
1959
+ if (action === "setNull") return "SetNull";
1960
+ if (action === "setDefault") return "SetDefault";
1961
+ return "NoAction";
1370
1962
  };
1371
1963
  /**
1372
- * Build the source code for a new migration file based on a given class
1373
- * name, using a template with empty up and down methods.
1964
+ * Build a Prisma relation field line based on a SchemaForeignKey
1965
+ * definition, including relation name and onDelete action.
1374
1966
  *
1375
- * @param className
1376
- * @returns
1967
+ * @param foreignKey The foreign key definition to convert to a relation line.
1968
+ * @returns The corresponding Prisma schema line for the relation field.
1377
1969
  */
1378
- const buildMigrationSource = (className, extension = "ts") => {
1379
- if (extension === "js") return [
1380
- "import { Migration } from 'arkormx'",
1381
- "",
1382
- `export default class ${className} extends Migration {`,
1383
- " /**",
1384
- " * @param {import('arkormx').SchemaBuilder} schema",
1385
- " * @returns {Promise<void>}",
1386
- " */",
1387
- " async up (schema) {",
1388
- " }",
1389
- "",
1390
- " /**",
1391
- " * @param {import('arkormx').SchemaBuilder} schema",
1392
- " * @returns {Promise<void>}",
1393
- " */",
1394
- " async down (schema) {",
1395
- " }",
1396
- "}",
1397
- ""
1398
- ].join("\n");
1399
- return [
1400
- "import { Migration, SchemaBuilder } from 'arkormx'",
1401
- "",
1402
- `export default class ${className} extends Migration {`,
1403
- " public async up (schema: SchemaBuilder): Promise<void> {",
1404
- " }",
1405
- "",
1406
- " public async down (schema: SchemaBuilder): Promise<void> {",
1407
- " }",
1408
- "}",
1409
- ""
1410
- ].join("\n");
1970
+ const buildRelationLine = (sourceModelName, foreignKey, columns = []) => {
1971
+ if (!foreignKey.referencesTable.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced table.`);
1972
+ if (!foreignKey.referencesColumn.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced column.`);
1973
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1974
+ const fieldName = foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column);
1975
+ const targetModel = toModelName(foreignKey.referencesTable);
1976
+ const relationName = deriveRelationAlias(sourceModelName, targetModel, foreignKey.relationAlias?.trim());
1977
+ const optional = sourceColumn?.nullable ? "?" : "";
1978
+ const onDelete = foreignKey.onDelete ? `, onDelete: ${formatRelationAction(foreignKey.onDelete)}` : "";
1979
+ return ` ${fieldName} ${targetModel}${optional} @relation("${relationName.replace(/"/g, "\\\"")}", fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
1411
1980
  };
1412
1981
  /**
1413
- * Generate a new migration file with a given name and options, including
1414
- * writing the file to disk if specified, and return the details of the generated file.
1982
+ * Build a Prisma relation field line for the inverse side of a relation, based
1983
+ * on the source and target model names and the foreign key definition, using
1984
+ * naming conventions and any explicit inverse alias provided.
1415
1985
  *
1416
- * @param name
1417
- * @param options
1418
- * @returns
1986
+ * @param sourceModelName The name of the source model in the relation.
1987
+ * @param targetModelName The name of the target model in the relation.
1988
+ * @param foreignKey The foreign key definition for the relation.
1989
+ * @returns The Prisma schema line for the inverse relation field.
1419
1990
  */
1420
- const generateMigrationFile = (name, options = {}) => {
1421
- const timestamp = createMigrationTimestamp(/* @__PURE__ */ new Date());
1422
- const fileSlug = toMigrationFileSlug(name);
1423
- const className = resolveMigrationClassName(name);
1424
- const extension = options.extension ?? "ts";
1425
- const directory = options.directory ?? join(process.cwd(), "database", "migrations");
1426
- const fileName = `${timestamp}_${fileSlug}.${extension}`;
1427
- const filePath = join(directory, fileName);
1428
- const content = buildMigrationSource(className, extension);
1429
- if (options.write ?? true) {
1430
- if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1431
- if (existsSync(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
1432
- writeFileSync(filePath, content);
1433
- }
1434
- return {
1435
- fileName,
1436
- filePath,
1437
- className,
1438
- content
1439
- };
1991
+ const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey, columns = []) => {
1992
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1993
+ const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
1994
+ const relationName = deriveRelationAlias(sourceModelName, targetModelName, foreignKey.relationAlias?.trim());
1995
+ return ` ${fieldName} ${isOneToOneForeignKey(sourceColumn) ? `${sourceModelName}?` : `${sourceModelName}[]`} @relation("${relationName.replace(/"/g, "\\\"")}")`;
1440
1996
  };
1441
1997
  /**
1442
- * Get the list of schema operations that would be performed by a given migration class when run in a specified direction (up or down), without actually applying them.
1998
+ * Inject a line into the body of a Prisma model block if it does not already
1999
+ * exist, using a provided existence check function to determine if the line
2000
+ * is already present.
1443
2001
  *
1444
- * @param migration The migration class or instance to analyze.
1445
- * @param direction The direction of the migration to plan for ('up' or 'down').
1446
- * @returns A promise that resolves to an array of schema operations that would be performed.
2002
+ * @param bodyLines The lines of the model block body to modify.
2003
+ * @param line The line to inject if it does not already exist.
2004
+ * @param exists A function that checks if a given line already exists in the body.
2005
+ * @returns
1447
2006
  */
1448
- const getMigrationPlan = async (migration, direction = "up") => {
1449
- const instance = typeof migration === "function" ? new migration() : migration;
1450
- const schema = new SchemaBuilder();
1451
- if (direction === "up") await instance.up(schema);
1452
- else await instance.down(schema);
1453
- return schema.getOperations();
2007
+ const injectLineIntoModelBody = (bodyLines, line, exists) => {
2008
+ if (bodyLines.some(exists)) return bodyLines;
2009
+ const insertIndex = Math.max(1, bodyLines.length - 1);
2010
+ bodyLines.splice(insertIndex, 0, line);
2011
+ return bodyLines;
1454
2012
  };
1455
2013
  /**
1456
- * Apply the schema operations defined in a migration to a Prisma schema
1457
- * file, updating the file on disk if specified, and return the updated
1458
- * schema and list of operations applied.
2014
+ * Apply inverse relation definitions to a Prisma schema string based on the
2015
+ * foreign keys defined in a create or alter table operation, ensuring that
2016
+ * related models have corresponding relation fields for bi-directional navigation.
1459
2017
  *
1460
- * @param migration The migration class or instance to apply.
1461
- * @param options Options for applying the migration, including schema path and write flag.
1462
- * @returns A promise that resolves to an object containing the updated schema, schema path, and list of operations applied.
2018
+ * @param schema The Prisma schema string to modify.
2019
+ * @param sourceModelName The name of the source model in the relation.
2020
+ * @param foreignKeys An array of foreign key definitions to process.
2021
+ * @returns The updated Prisma schema string with inverse relations applied.
1463
2022
  */
1464
- const applyMigrationToPrismaSchema = async (migration, options = {}) => {
1465
- const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1466
- if (!existsSync(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
1467
- const source = readFileSync(schemaPath, "utf-8");
1468
- const operations = await getMigrationPlan(migration, "up");
1469
- const schema = applyOperationsToPrismaSchema(source, operations);
1470
- if (options.write ?? true) writeFileSync(schemaPath, schema);
1471
- return {
1472
- schema,
1473
- schemaPath,
1474
- operations
1475
- };
2023
+ const applyInverseRelations = (schema, sourceModelName, foreignKeys, columns = []) => {
2024
+ let nextSchema = schema;
2025
+ for (const foreignKey of foreignKeys) {
2026
+ const targetModel = findModelBlock(nextSchema, foreignKey.referencesTable);
2027
+ if (!targetModel) continue;
2028
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
2029
+ const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey, columns);
2030
+ const targetBodyLines = targetModel.block.split("\n");
2031
+ const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
2032
+ const fieldRegex = new RegExp(`^\\s*${escapeRegex(fieldName)}\\s+`);
2033
+ injectLineIntoModelBody(targetBodyLines, inverseLine, (line) => fieldRegex.test(line));
2034
+ const updatedTarget = targetBodyLines.join("\n");
2035
+ nextSchema = `${nextSchema.slice(0, targetModel.start)}${updatedTarget}${nextSchema.slice(targetModel.end)}`;
2036
+ }
2037
+ return nextSchema;
1476
2038
  };
1477
2039
  /**
1478
- * Apply the rollback (down) operations defined in a migration to a Prisma schema file.
1479
- *
1480
- * @param migration The migration class or instance to rollback.
1481
- * @param options Options for applying the rollback, including schema path and write flag.
1482
- * @returns A promise that resolves to an object containing the updated schema, schema path, and rollback operations applied.
2040
+ * Build a Prisma model block string based on a SchemaTableCreateOperation, including
2041
+ * all fields and any necessary mapping.
2042
+ *
2043
+ * @param operation The schema table create operation to convert.
2044
+ * @returns The corresponding Prisma model block string.
1483
2045
  */
1484
- const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) => {
1485
- const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1486
- if (!existsSync(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
1487
- const source = readFileSync(schemaPath, "utf-8");
1488
- const operations = await getMigrationPlan(migration, "down");
1489
- const schema = applyOperationsToPrismaSchema(source, operations);
1490
- if (options.write ?? true) writeFileSync(schemaPath, schema);
1491
- return {
1492
- schema,
1493
- schemaPath,
1494
- operations
1495
- };
2046
+ const buildModelBlock = (operation) => {
2047
+ const modelName = toModelName(operation.table);
2048
+ const mapped = operation.table !== modelName.toLowerCase();
2049
+ const fields = operation.columns.map(buildFieldLine);
2050
+ const relations = (operation.foreignKeys ?? []).map((foreignKey) => buildRelationLine(modelName, foreignKey, operation.columns));
2051
+ const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
2052
+ return `model ${modelName} {\n${(metadata.length > 0 ? [
2053
+ ...fields,
2054
+ ...relations,
2055
+ "",
2056
+ ...metadata
2057
+ ] : [...fields, ...relations]).join("\n")}\n}`;
1496
2058
  };
1497
2059
  /**
1498
- * Run a migration by applying its schema operations to a Prisma schema
1499
- * file, optionally generating Prisma client code and running migrations after
1500
- * applying the schema changes.
2060
+ * Find the Prisma model block in a schema string that corresponds to a given
2061
+ * table name, using both explicit mapping and naming conventions.
1501
2062
  *
1502
- * @param migration The migration class or instance to run.
1503
- * @param options Options for running the migration, including schema path, write flag, and Prisma commands.
1504
- * @returns A promise that resolves to an object containing the schema path and list of operations applied.
2063
+ * @param schema
2064
+ * @param table
2065
+ * @returns
1505
2066
  */
1506
- const runMigrationWithPrisma = async (migration, options = {}) => {
1507
- const cwd = options.cwd ?? process.cwd();
1508
- const applied = await applyMigrationToPrismaSchema(migration, {
1509
- schemaPath: options.schemaPath ?? join(cwd, "prisma", "schema.prisma"),
1510
- write: options.write
1511
- });
1512
- const shouldGenerate = options.runGenerate ?? true;
1513
- const shouldMigrate = options.runMigrate ?? true;
1514
- const mode = options.migrateMode ?? "dev";
1515
- if (shouldGenerate) runPrismaCommand(["generate"], cwd);
1516
- if (shouldMigrate) if (mode === "deploy") runPrismaCommand(["migrate", "deploy"], cwd);
1517
- else runPrismaCommand([
1518
- "migrate",
1519
- "dev",
1520
- "--name",
1521
- options.migrationName ?? `arkorm_${createMigrationTimestamp()}`
1522
- ], cwd);
1523
- return {
1524
- schemaPath: applied.schemaPath,
1525
- operations: applied.operations
1526
- };
1527
- };
1528
-
1529
- //#endregion
1530
- //#region src/helpers/runtime-module-loader.ts
1531
- var RuntimeModuleLoader = class {
1532
- static async load(filePath) {
1533
- const resolvedPath = resolve(filePath);
1534
- return await createJiti(pathToFileURL(resolvedPath).href, {
1535
- interopDefault: false,
1536
- tsconfigPaths: true
1537
- }).import(resolvedPath);
1538
- }
1539
- };
1540
-
1541
- //#endregion
1542
- //#region src/Exceptions/UnsupportedAdapterFeatureException.ts
1543
- var UnsupportedAdapterFeatureException = class extends ArkormException {
1544
- constructor(message, context = {}) {
1545
- super(message, {
1546
- code: "UNSUPPORTED_ADAPTER_FEATURE",
1547
- ...context
1548
- });
1549
- this.name = "UnsupportedAdapterFeatureException";
1550
- }
1551
- };
1552
-
1553
- //#endregion
1554
- //#region src/helpers/runtime-config.ts
1555
- const resolveDefaultStubsPath = () => {
1556
- let current = path.dirname(fileURLToPath(import.meta.url));
1557
- while (true) {
1558
- const packageJsonPath = path.join(current, "package.json");
1559
- const stubsPath = path.join(current, "stubs");
1560
- if (existsSync$1(packageJsonPath) && existsSync$1(stubsPath)) return stubsPath;
1561
- const parent = path.dirname(current);
1562
- if (parent === current) break;
1563
- current = parent;
2067
+ const findModelBlock = (schema, table) => {
2068
+ const candidates = [...schema.matchAll(PRISMA_MODEL_REGEX)];
2069
+ const explicitMapRegex = new RegExp(`@@map\\("${escapeRegex(table)}"\\)`);
2070
+ for (const match of candidates) {
2071
+ const block = match[0];
2072
+ const modelName = match[1];
2073
+ const start = match.index ?? 0;
2074
+ const end = start + block.length;
2075
+ if (explicitMapRegex.test(block)) return {
2076
+ modelName,
2077
+ block,
2078
+ start,
2079
+ end
2080
+ };
2081
+ if (modelName.toLowerCase() === table.toLowerCase()) return {
2082
+ modelName,
2083
+ block,
2084
+ start,
2085
+ end
2086
+ };
2087
+ if (modelName.toLowerCase() === toModelName(table).toLowerCase()) return {
2088
+ modelName,
2089
+ block,
2090
+ start,
2091
+ end
2092
+ };
1564
2093
  }
1565
- return path.join(process.cwd(), "stubs");
1566
- };
1567
- const baseConfig = {
1568
- paths: {
1569
- stubs: resolveDefaultStubsPath(),
1570
- seeders: path.join(process.cwd(), "database", "seeders"),
1571
- models: path.join(process.cwd(), "src", "models"),
1572
- migrations: path.join(process.cwd(), "database", "migrations"),
1573
- factories: path.join(process.cwd(), "database", "factories"),
1574
- buildOutput: path.join(process.cwd(), "dist")
1575
- },
1576
- outputExt: "ts"
1577
- };
1578
- const userConfig = {
1579
- ...baseConfig,
1580
- paths: { ...baseConfig.paths ?? {} }
1581
- };
1582
- let runtimeConfigLoaded = false;
1583
- let runtimeConfigLoadingPromise;
1584
- let runtimeClientResolver;
1585
- let runtimePaginationURLDriverFactory;
1586
- let runtimePaginationCurrentPageResolver;
1587
- const transactionClientStorage = new AsyncLocalStorage();
1588
- const mergePathConfig = (paths) => {
1589
- const defaults = baseConfig.paths ?? {};
1590
- const current = userConfig.paths ?? {};
1591
- const incoming = Object.entries(paths ?? {}).reduce((all, [key, value]) => {
1592
- if (typeof value === "string" && value.trim().length > 0) all[key] = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
1593
- return all;
1594
- }, {});
1595
- return {
1596
- ...defaults,
1597
- ...current,
1598
- ...incoming
1599
- };
2094
+ return null;
1600
2095
  };
1601
2096
  /**
1602
- * Define the ArkORM runtime configuration. This function can be used to provide.
2097
+ * Apply a create table operation to a Prisma schema string, adding a new model
2098
+ * block for the specified table and fields.
1603
2099
  *
1604
- * @param config The ArkORM configuration object.
1605
- * @returns The same configuration object.
2100
+ * @param schema The current Prisma schema string.
2101
+ * @param operation The schema table create operation to apply.
2102
+ * @returns The updated Prisma schema string with the new model block.
1606
2103
  */
1607
- const defineConfig = (config) => {
1608
- return config;
2104
+ const applyCreateTableOperation = (schema, operation) => {
2105
+ if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
2106
+ const schemaWithEnums = ensureEnumBlocks(schema, operation.columns);
2107
+ const block = buildModelBlock(operation);
2108
+ return applyInverseRelations(`${schemaWithEnums.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? [], operation.columns);
1609
2109
  };
1610
2110
  /**
1611
- * Get the user-provided ArkORM configuration.
2111
+ * Apply an alter table operation to a Prisma schema string, modifying the model
2112
+ * block for the specified table by adding and removing fields as needed.
1612
2113
  *
1613
- * @returns The user-provided ArkORM configuration object.
2114
+ * @param schema The current Prisma schema string.
2115
+ * @param operation The schema table alter operation to apply.
2116
+ * @returns The updated Prisma schema string with the modified model block.
1614
2117
  */
1615
- const getUserConfig = (key) => {
1616
- if (key) return userConfig[key];
1617
- return userConfig;
2118
+ const applyAlterTableOperation = (schema, operation) => {
2119
+ const model = findModelBlock(schema, operation.table);
2120
+ if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
2121
+ const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
2122
+ const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
2123
+ if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
2124
+ let block = refreshedModel.block;
2125
+ const bodyLines = block.split("\n");
2126
+ operation.dropColumns.forEach((column) => {
2127
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
2128
+ for (let index = 0; index < bodyLines.length; index += 1) if (columnRegex.test(bodyLines[index])) {
2129
+ bodyLines.splice(index, 1);
2130
+ return;
2131
+ }
2132
+ });
2133
+ operation.addColumns.forEach((column) => {
2134
+ const fieldLine = buildFieldLine(column);
2135
+ const columnRegex = new RegExp(`^\\s*${escapeRegex(column.name)}\\s+`);
2136
+ if (bodyLines.some((line) => columnRegex.test(line))) return;
2137
+ const defaultInsertIndex = Math.max(1, bodyLines.length - 1);
2138
+ const afterInsertIndex = typeof column.after === "string" && column.after.length > 0 ? bodyLines.findIndex((line) => new RegExp(`^\\s*${escapeRegex(column.after)}\\s+`).test(line)) : -1;
2139
+ const insertIndex = afterInsertIndex > 0 ? Math.min(afterInsertIndex + 1, defaultInsertIndex) : defaultInsertIndex;
2140
+ bodyLines.splice(insertIndex, 0, fieldLine);
2141
+ });
2142
+ (operation.addIndexes ?? []).forEach((index) => {
2143
+ const indexLine = buildIndexLine(index);
2144
+ if (bodyLines.some((line) => line.trim() === indexLine.trim())) return;
2145
+ const insertIndex = Math.max(1, bodyLines.length - 1);
2146
+ bodyLines.splice(insertIndex, 0, indexLine);
2147
+ });
2148
+ for (const foreignKey of operation.addForeignKeys ?? []) {
2149
+ const relationLine = buildRelationLine(model.modelName, foreignKey, operation.addColumns);
2150
+ const relationRegex = new RegExp(`^\\s*${escapeRegex(foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column))}\\s+`);
2151
+ injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
2152
+ }
2153
+ block = bodyLines.join("\n");
2154
+ return applyInverseRelations(`${schemaWithEnums.slice(0, refreshedModel.start)}${block}${schemaWithEnums.slice(refreshedModel.end)}`, model.modelName, operation.addForeignKeys ?? [], operation.addColumns);
1618
2155
  };
1619
2156
  /**
1620
- * Configure the ArkORM runtime with the provided Prisma client resolver and
1621
- * delegate mapping resolver.
2157
+ * Apply a drop table operation to a Prisma schema string, removing the model block
2158
+ * for the specified table.
2159
+ */
2160
+ const applyDropTableOperation = (schema, operation) => {
2161
+ const model = findModelBlock(schema, operation.table);
2162
+ if (!model) return schema;
2163
+ const before = schema.slice(0, model.start).trimEnd();
2164
+ const after = schema.slice(model.end).trimStart();
2165
+ return `${before}${before && after ? "\n\n" : ""}${after}`;
2166
+ };
2167
+ /**
2168
+ * The SchemaBuilder class provides a fluent interface for defining
2169
+ * database schema operations in a migration, such as creating, altering, and
2170
+ * dropping tables.
1622
2171
  *
1623
- * @param prisma
1624
- * @param mapping
2172
+ * @param schema The current Prisma schema string.
2173
+ * @param operations The list of schema operations to apply.
2174
+ * @returns The updated Prisma schema string after applying all operations.
1625
2175
  */
1626
- const configureArkormRuntime = (prisma, options = {}) => {
1627
- const nextConfig = {
1628
- ...userConfig,
1629
- prisma,
1630
- paths: mergePathConfig(options.paths)
1631
- };
1632
- if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
1633
- if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
1634
- Object.assign(userConfig, { ...nextConfig });
1635
- runtimeClientResolver = prisma;
1636
- runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
1637
- runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
2176
+ const applyOperationsToPrismaSchema = (schema, operations) => {
2177
+ return operations.reduce((current, operation) => {
2178
+ if (operation.type === "createTable") return applyCreateTableOperation(current, operation);
2179
+ if (operation.type === "alterTable") return applyAlterTableOperation(current, operation);
2180
+ return applyDropTableOperation(current, operation);
2181
+ }, schema);
1638
2182
  };
1639
2183
  /**
1640
- * Reset the ArkORM runtime configuration.
1641
- * This is primarily intended for testing purposes.
2184
+ * Run a Prisma CLI command using npx, capturing and throwing any errors that occur.
2185
+ *
2186
+ * @param args The arguments to pass to the Prisma CLI command.
2187
+ * @param cwd The current working directory to run the command in.
2188
+ * @returns void
1642
2189
  */
1643
- const resetArkormRuntimeForTests = () => {
1644
- Object.assign(userConfig, {
1645
- ...baseConfig,
1646
- paths: { ...baseConfig.paths ?? {} }
2190
+ const runPrismaCommand = (args, cwd) => {
2191
+ const command = spawnSync("npx", ["prisma", ...args], {
2192
+ cwd,
2193
+ encoding: "utf-8"
1647
2194
  });
1648
- runtimeConfigLoaded = false;
1649
- runtimeConfigLoadingPromise = void 0;
1650
- runtimeClientResolver = void 0;
1651
- runtimePaginationURLDriverFactory = void 0;
1652
- runtimePaginationCurrentPageResolver = void 0;
2195
+ if (command.status === 0) return;
2196
+ const errorOutput = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
2197
+ throw new ArkormException(errorOutput ? `Prisma command failed: prisma ${args.join(" ")}\n${errorOutput}` : `Prisma command failed: prisma ${args.join(" ")}`);
1653
2198
  };
1654
2199
  /**
1655
- * Resolve a Prisma client instance from the provided resolver, which can be either
1656
- * a direct client instance or a function that returns a client instance.
2200
+ * Generate a new migration file with a given name and options, including
2201
+ * writing the file to disk if specified.
1657
2202
  *
1658
- * @param resolver
2203
+ * @param name
1659
2204
  * @returns
1660
2205
  */
1661
- const resolveClient = (resolver) => {
1662
- if (!resolver) return void 0;
1663
- const client = typeof resolver === "function" ? resolver() : resolver;
1664
- if (!client || typeof client !== "object") return void 0;
1665
- return client;
2206
+ const resolveMigrationClassName = (name) => {
2207
+ const cleaned = name.replace(/[^a-zA-Z0-9]+/g, " ").trim();
2208
+ if (!cleaned) return "GeneratedMigration";
2209
+ return `${cleaned.split(/\s+/g).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}Migration`;
1666
2210
  };
1667
2211
  /**
1668
- * Resolve and apply the ArkORM configuration from an imported module.
1669
- * This function checks for a default export and falls back to the module itself, then validates
1670
- * the configuration object and applies it to the runtime if valid.
2212
+ * Pad a number with leading zeros to ensure it is at least two digits, for
2213
+ * use in migration timestamps.
1671
2214
  *
1672
- * @param imported
2215
+ * @param value
1673
2216
  * @returns
1674
2217
  */
1675
- const resolveAndApplyConfig = (imported) => {
1676
- const config = imported?.default ?? imported;
1677
- if (!config || typeof config !== "object" || !config.prisma) return;
1678
- configureArkormRuntime(config.prisma, {
1679
- pagination: config.pagination,
1680
- paths: config.paths,
1681
- outputExt: config.outputExt
1682
- });
1683
- runtimeConfigLoaded = true;
1684
- };
2218
+ const pad = (value) => String(value).padStart(2, "0");
1685
2219
  /**
1686
- * Dynamically import a configuration file.
1687
- * A cache-busting query parameter is appended to ensure the latest version is loaded.
2220
+ * Create a timestamp string in the format YYYYMMDDHHMMSS for use in migration
2221
+ * file names, based on the current date and time or a provided date.
1688
2222
  *
1689
- * @param configPath
1690
- * @returns A promise that resolves to the imported configuration module.
2223
+ * @param date
2224
+ * @returns
1691
2225
  */
1692
- const importConfigFile = (configPath) => {
1693
- return RuntimeModuleLoader.load(configPath);
1694
- };
1695
- const loadRuntimeConfigSync = () => {
1696
- const require = createRequire(import.meta.url);
1697
- const syncConfigPaths = [path.join(process.cwd(), "arkormx.config.cjs")];
1698
- for (const configPath of syncConfigPaths) {
1699
- if (!existsSync$1(configPath)) continue;
1700
- try {
1701
- resolveAndApplyConfig(require(configPath));
1702
- return true;
1703
- } catch {
1704
- continue;
1705
- }
1706
- }
1707
- return false;
2226
+ const createMigrationTimestamp = (date = /* @__PURE__ */ new Date()) => {
2227
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
1708
2228
  };
1709
2229
  /**
1710
- * Load the ArkORM configuration by searching for configuration files in the
1711
- * current working directory.
2230
+ * Convert a migration name to a slug suitable for use in a file name, by
2231
+ * lowercasing and replacing non-alphanumeric characters with underscores.
2232
+ *
2233
+ * @param name
1712
2234
  * @returns
1713
- */
1714
- const loadArkormConfig = async () => {
1715
- if (runtimeConfigLoaded) return;
1716
- if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
1717
- if (loadRuntimeConfigSync()) return;
1718
- runtimeConfigLoadingPromise = (async () => {
1719
- const configPaths = [path.join(process.cwd(), "arkormx.config.js"), path.join(process.cwd(), "arkormx.config.ts")];
1720
- for (const configPath of configPaths) {
1721
- if (!existsSync$1(configPath)) continue;
1722
- try {
1723
- resolveAndApplyConfig(await importConfigFile(configPath));
1724
- return;
1725
- } catch {
1726
- continue;
1727
- }
1728
- }
1729
- runtimeConfigLoaded = true;
1730
- })();
1731
- await runtimeConfigLoadingPromise;
2235
+ */
2236
+ const toMigrationFileSlug = (name) => {
2237
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration";
1732
2238
  };
1733
2239
  /**
1734
- * Ensure that the ArkORM configuration is loaded.
1735
- * This function can be called to trigger the loading process if it hasn't already been initiated.
1736
- * If the configuration is already loaded, it will return immediately.
2240
+ * Build the source code for a new migration file based on a given class
2241
+ * name, using a template with empty up and down methods.
1737
2242
  *
2243
+ * @param className
1738
2244
  * @returns
1739
2245
  */
1740
- const ensureArkormConfigLoading = () => {
1741
- if (runtimeConfigLoaded) return;
1742
- if (!runtimeConfigLoadingPromise) loadArkormConfig();
1743
- };
1744
- const getDefaultStubsPath = () => {
1745
- return resolveDefaultStubsPath();
2246
+ const buildMigrationSource = (className, extension = "ts") => {
2247
+ if (extension === "js") return [
2248
+ "import { Migration } from 'arkormx'",
2249
+ "",
2250
+ `export default class ${className} extends Migration {`,
2251
+ " /**",
2252
+ " * @param {import('arkormx').SchemaBuilder} schema",
2253
+ " * @returns {Promise<void>}",
2254
+ " */",
2255
+ " async up (schema) {",
2256
+ " }",
2257
+ "",
2258
+ " /**",
2259
+ " * @param {import('arkormx').SchemaBuilder} schema",
2260
+ " * @returns {Promise<void>}",
2261
+ " */",
2262
+ " async down (schema) {",
2263
+ " }",
2264
+ "}",
2265
+ ""
2266
+ ].join("\n");
2267
+ return [
2268
+ "import { Migration, SchemaBuilder } from 'arkormx'",
2269
+ "",
2270
+ `export default class ${className} extends Migration {`,
2271
+ " public async up (schema: SchemaBuilder): Promise<void> {",
2272
+ " }",
2273
+ "",
2274
+ " public async down (schema: SchemaBuilder): Promise<void> {",
2275
+ " }",
2276
+ "}",
2277
+ ""
2278
+ ].join("\n");
1746
2279
  };
1747
2280
  /**
1748
- * Get the runtime Prisma client.
1749
- * This function will trigger the loading of the ArkORM configuration if
1750
- * it hasn't already been loaded.
2281
+ * Generate a new migration file with a given name and options, including
2282
+ * writing the file to disk if specified, and return the details of the generated file.
1751
2283
  *
2284
+ * @param name
2285
+ * @param options
1752
2286
  * @returns
1753
2287
  */
1754
- const getRuntimePrismaClient = () => {
1755
- const activeTransactionClient = transactionClientStorage.getStore();
1756
- if (activeTransactionClient) return activeTransactionClient;
1757
- if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1758
- return resolveClient(runtimeClientResolver);
1759
- };
1760
- const getActiveTransactionClient = () => {
1761
- return transactionClientStorage.getStore();
1762
- };
1763
- const isTransactionCapableClient = (value) => {
1764
- if (!value || typeof value !== "object") return false;
1765
- return typeof value.$transaction === "function";
2288
+ const generateMigrationFile = (name, options = {}) => {
2289
+ const timestamp = createMigrationTimestamp(/* @__PURE__ */ new Date());
2290
+ const fileSlug = toMigrationFileSlug(name);
2291
+ const className = resolveMigrationClassName(name);
2292
+ const extension = options.extension ?? "ts";
2293
+ const directory = options.directory ?? join(process.cwd(), "database", "migrations");
2294
+ const fileName = `${timestamp}_${fileSlug}.${extension}`;
2295
+ const filePath = join(directory, fileName);
2296
+ const content = buildMigrationSource(className, extension);
2297
+ if (options.write ?? true) {
2298
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
2299
+ if (existsSync$1(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
2300
+ writeFileSync$1(filePath, content);
2301
+ }
2302
+ return {
2303
+ fileName,
2304
+ filePath,
2305
+ className,
2306
+ content
2307
+ };
1766
2308
  };
1767
- const runArkormTransaction = async (callback, options = {}) => {
1768
- const activeTransactionClient = transactionClientStorage.getStore();
1769
- if (activeTransactionClient) return await callback(activeTransactionClient);
1770
- const client = getRuntimePrismaClient();
1771
- if (!client) throw new ArkormException("Cannot start a transaction without a configured Prisma client.", {
1772
- code: "CLIENT_NOT_CONFIGURED",
1773
- operation: "transaction"
1774
- });
1775
- if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
1776
- code: "TRANSACTION_NOT_SUPPORTED",
1777
- operation: "transaction"
1778
- });
1779
- return await client.$transaction(async (transactionClient) => {
1780
- return await transactionClientStorage.run(transactionClient, async () => {
1781
- return await callback(transactionClient);
1782
- });
1783
- }, options);
2309
+ /**
2310
+ * Get the list of schema operations that would be performed by a given migration class when run in a specified direction (up or down), without actually applying them.
2311
+ *
2312
+ * @param migration The migration class or instance to analyze.
2313
+ * @param direction The direction of the migration to plan for ('up' or 'down').
2314
+ * @returns A promise that resolves to an array of schema operations that would be performed.
2315
+ */
2316
+ const getMigrationPlan = async (migration, direction = "up") => {
2317
+ const instance = typeof migration === "function" ? new migration() : migration;
2318
+ const schema = new SchemaBuilder();
2319
+ if (direction === "up") await instance.up(schema);
2320
+ else await instance.down(schema);
2321
+ return schema.getOperations();
1784
2322
  };
1785
2323
  /**
1786
- * Get the configured pagination URL driver factory from runtime config.
1787
- *
1788
- * @returns
2324
+ * Apply the schema operations defined in a migration to a Prisma schema
2325
+ * file, updating the file on disk if specified, and return the updated
2326
+ * schema and list of operations applied.
2327
+ *
2328
+ * @param migration The migration class or instance to apply.
2329
+ * @param options Options for applying the migration, including schema path and write flag.
2330
+ * @returns A promise that resolves to an object containing the updated schema, schema path, and list of operations applied.
1789
2331
  */
1790
- const getRuntimePaginationURLDriverFactory = () => {
1791
- if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1792
- return runtimePaginationURLDriverFactory;
2332
+ const applyMigrationToPrismaSchema = async (migration, options = {}) => {
2333
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
2334
+ if (!existsSync$1(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
2335
+ const source = readFileSync$1(schemaPath, "utf-8");
2336
+ const operations = await getMigrationPlan(migration, "up");
2337
+ const schema = applyOperationsToPrismaSchema(source, operations);
2338
+ if (options.write ?? true) writeFileSync$1(schemaPath, schema);
2339
+ return {
2340
+ schema,
2341
+ schemaPath,
2342
+ operations
2343
+ };
1793
2344
  };
1794
2345
  /**
1795
- * Get the configured current-page resolver from runtime config.
2346
+ * Apply the rollback (down) operations defined in a migration to a Prisma schema file.
1796
2347
  *
1797
- * @returns
2348
+ * @param migration The migration class or instance to rollback.
2349
+ * @param options Options for applying the rollback, including schema path and write flag.
2350
+ * @returns A promise that resolves to an object containing the updated schema, schema path, and rollback operations applied.
1798
2351
  */
1799
- const getRuntimePaginationCurrentPageResolver = () => {
1800
- if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1801
- return runtimePaginationCurrentPageResolver;
2352
+ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) => {
2353
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
2354
+ if (!existsSync$1(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
2355
+ const source = readFileSync$1(schemaPath, "utf-8");
2356
+ const operations = await getMigrationPlan(migration, "down");
2357
+ const schema = applyOperationsToPrismaSchema(source, operations);
2358
+ if (options.write ?? true) writeFileSync$1(schemaPath, schema);
2359
+ return {
2360
+ schema,
2361
+ schemaPath,
2362
+ operations
2363
+ };
1802
2364
  };
1803
2365
  /**
1804
- * Check if a given value is a Prisma delegate-like object
1805
- * by verifying the presence of common delegate methods.
2366
+ * Run a migration by applying its schema operations to a Prisma schema
2367
+ * file, optionally generating Prisma client code and running migrations after
2368
+ * applying the schema changes.
1806
2369
  *
1807
- * @param value The value to check.
1808
- * @returns True if the value is a Prisma delegate-like object, false otherwise.
2370
+ * @param migration The migration class or instance to run.
2371
+ * @param options Options for running the migration, including schema path, write flag, and Prisma commands.
2372
+ * @returns A promise that resolves to an object containing the schema path and list of operations applied.
1809
2373
  */
1810
- const isDelegateLike = (value) => {
1811
- if (!value || typeof value !== "object") return false;
1812
- const candidate = value;
1813
- return [
1814
- "findMany",
1815
- "findFirst",
1816
- "create",
1817
- "update",
1818
- "delete",
1819
- "count"
1820
- ].every((method) => typeof candidate[method] === "function");
2374
+ const runMigrationWithPrisma = async (migration, options = {}) => {
2375
+ const cwd = options.cwd ?? process.cwd();
2376
+ const applied = await applyMigrationToPrismaSchema(migration, {
2377
+ schemaPath: options.schemaPath ?? join(cwd, "prisma", "schema.prisma"),
2378
+ write: options.write
2379
+ });
2380
+ const shouldGenerate = options.runGenerate ?? true;
2381
+ const shouldMigrate = options.runMigrate ?? true;
2382
+ const mode = options.migrateMode ?? "dev";
2383
+ if (shouldGenerate) runPrismaCommand(["generate"], cwd);
2384
+ if (shouldMigrate) if (mode === "deploy") runPrismaCommand(["migrate", "deploy"], cwd);
2385
+ else runPrismaCommand([
2386
+ "migrate",
2387
+ "dev",
2388
+ "--name",
2389
+ options.migrationName ?? `arkorm_${createMigrationTimestamp()}`
2390
+ ], cwd);
2391
+ return {
2392
+ schemaPath: applied.schemaPath,
2393
+ operations: applied.operations
2394
+ };
1821
2395
  };
1822
- loadArkormConfig();
1823
2396
 
1824
2397
  //#endregion
1825
2398
  //#region src/cli/CliApp.ts
@@ -1849,7 +2422,7 @@ var CliApp = class {
1849
2422
  */
1850
2423
  ensureDirectory(filePath) {
1851
2424
  const dir = dirname$1(filePath);
1852
- if (!existsSync$1(dir)) mkdirSync$1(dir, { recursive: true });
2425
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1853
2426
  }
1854
2427
  /**
1855
2428
  * Convert absolute paths under current working directory into relative display paths.
@@ -1898,13 +2471,13 @@ var CliApp = class {
1898
2471
  * @returns
1899
2472
  */
1900
2473
  resolveRuntimeDirectoryPath(directoryPath) {
1901
- if (existsSync$1(directoryPath)) return directoryPath;
2474
+ if (existsSync(directoryPath)) return directoryPath;
1902
2475
  const { buildOutput } = this.getConfig("paths") || {};
1903
2476
  if (typeof buildOutput !== "string" || buildOutput.trim().length === 0) return directoryPath;
1904
2477
  const relativeSource = relative(process.cwd(), directoryPath);
1905
2478
  if (!relativeSource || relativeSource.startsWith("..")) return directoryPath;
1906
2479
  const mappedDirectory = join$1(buildOutput, relativeSource);
1907
- return existsSync$1(mappedDirectory) ? mappedDirectory : directoryPath;
2480
+ return existsSync(mappedDirectory) ? mappedDirectory : directoryPath;
1908
2481
  }
1909
2482
  /**
1910
2483
  * Resolve a script file path for runtime execution.
@@ -1934,7 +2507,7 @@ var CliApp = class {
1934
2507
  } else candidates.push(mappedFile);
1935
2508
  }
1936
2509
  }
1937
- const runtimeMatch = candidates.find((path) => existsSync$1(path));
2510
+ const runtimeMatch = candidates.find((path) => existsSync(path));
1938
2511
  if (runtimeMatch) return runtimeMatch;
1939
2512
  return filePath;
1940
2513
  }
@@ -1946,14 +2519,14 @@ var CliApp = class {
1946
2519
  * @param replacements
1947
2520
  */
1948
2521
  generateFile(stubPath, outputPath, replacements, options) {
1949
- if (existsSync$1(outputPath) && !options?.force) {
2522
+ if (existsSync(outputPath) && !options?.force) {
1950
2523
  this.command.error(`Error: ${this.formatPathForLog(outputPath)} already exists.`);
1951
2524
  process.exit(1);
1952
- } else if (existsSync$1(outputPath) && options?.force) rmSync$1(outputPath);
1953
- let content = readFileSync$1(stubPath, "utf-8");
2525
+ } else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
2526
+ let content = readFileSync(stubPath, "utf-8");
1954
2527
  for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
1955
2528
  this.ensureDirectory(outputPath);
1956
- writeFileSync$1(outputPath, content);
2529
+ writeFileSync(outputPath, content);
1957
2530
  return outputPath;
1958
2531
  }
1959
2532
  /**
@@ -2091,18 +2664,18 @@ var CliApp = class {
2091
2664
  */
2092
2665
  ensurePrismaModelEntry(modelName, delegateName) {
2093
2666
  const schemaPath = join$1(process.cwd(), "prisma", "schema.prisma");
2094
- if (!existsSync$1(schemaPath)) return {
2667
+ if (!existsSync(schemaPath)) return {
2095
2668
  path: schemaPath,
2096
2669
  updated: false
2097
2670
  };
2098
- const source = readFileSync$1(schemaPath, "utf-8");
2671
+ const source = readFileSync(schemaPath, "utf-8");
2099
2672
  const existingByTable = findModelBlock(source, delegateName);
2100
2673
  const existingByName = new RegExp(`model\\s+${modelName}\\s*\\{`, "m").test(source);
2101
2674
  if (existingByTable || existingByName) return {
2102
2675
  path: schemaPath,
2103
2676
  updated: false
2104
2677
  };
2105
- writeFileSync$1(schemaPath, applyCreateTableOperation(source, {
2678
+ writeFileSync(schemaPath, applyCreateTableOperation(source, {
2106
2679
  type: "createTable",
2107
2680
  table: delegateName,
2108
2681
  columns: [{
@@ -2479,17 +3052,17 @@ var CliApp = class {
2479
3052
  syncModelsFromPrisma(options = {}) {
2480
3053
  const schemaPath = options.schemaPath ?? join$1(process.cwd(), "prisma", "schema.prisma");
2481
3054
  const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join$1(process.cwd(), "src", "models"));
2482
- if (!existsSync$1(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
2483
- if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
2484
- const schema = readFileSync$1(schemaPath, "utf-8");
3055
+ if (!existsSync(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
3056
+ if (!existsSync(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
3057
+ const schema = readFileSync(schemaPath, "utf-8");
2485
3058
  const prismaEnums = this.parsePrismaEnums(schema);
2486
3059
  const prismaModels = this.parsePrismaModels(schema);
2487
- const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
3060
+ const modelFiles = readdirSync(modelsDir).filter((file) => file.endsWith(".ts"));
2488
3061
  const updated = [];
2489
3062
  const skipped = [];
2490
3063
  modelFiles.forEach((file) => {
2491
3064
  const filePath = join$1(modelsDir, file);
2492
- const source = readFileSync$1(filePath, "utf-8");
3065
+ const source = readFileSync(filePath, "utf-8");
2493
3066
  const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
2494
3067
  if (!classMatch) {
2495
3068
  skipped.push(filePath);
@@ -2507,7 +3080,7 @@ var CliApp = class {
2507
3080
  skipped.push(filePath);
2508
3081
  return;
2509
3082
  }
2510
- writeFileSync$1(filePath, synced.content);
3083
+ writeFileSync(filePath, synced.content);
2511
3084
  updated.push(filePath);
2512
3085
  });
2513
3086
  return {
@@ -2544,18 +3117,18 @@ var InitCommand = class extends Command {
2544
3117
  const stubsDir = typeof stubs === "string" && stubs.trim().length > 0 ? stubs : getDefaultStubsPath();
2545
3118
  const preferredStubPath = join(stubsDir, "arkormx.config.stub");
2546
3119
  const legacyStubPath = join(stubsDir, "arkorm.config.stub");
2547
- const stubPath = existsSync$1(preferredStubPath) ? preferredStubPath : legacyStubPath;
2548
- if (existsSync$1(outputDir) && !this.option("force")) {
3120
+ const stubPath = existsSync(preferredStubPath) ? preferredStubPath : legacyStubPath;
3121
+ if (existsSync(outputDir) && !this.option("force")) {
2549
3122
  this.error("Error: Arkormˣ has already been initialized. Use --force to reinitialize.");
2550
3123
  process.exit(1);
2551
3124
  }
2552
3125
  this.app.ensureDirectory(outputDir);
2553
- if (existsSync$1(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
2554
- if (!existsSync$1(stubPath)) {
3126
+ if (existsSync(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
3127
+ if (!existsSync(stubPath)) {
2555
3128
  this.error(`Error: Missing config stub at ${preferredStubPath} (or ${legacyStubPath})`);
2556
3129
  process.exit(1);
2557
3130
  }
2558
- writeFileSync$1(outputDir, readFileSync$1(stubPath, "utf-8"));
3131
+ writeFileSync(outputDir, readFileSync(stubPath, "utf-8"));
2559
3132
  this.success("Arkormˣ initialized successfully!");
2560
3133
  }
2561
3134
  };
@@ -2697,13 +3270,13 @@ const buildMigrationIdentity = (filePath, className) => {
2697
3270
  return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
2698
3271
  };
2699
3272
  const computeMigrationChecksum = (filePath) => {
2700
- const source = readFileSync(filePath, "utf-8");
3273
+ const source = readFileSync$1(filePath, "utf-8");
2701
3274
  return createHash("sha256").update(source).digest("hex");
2702
3275
  };
2703
3276
  const readAppliedMigrationsState = (stateFilePath) => {
2704
- if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
3277
+ if (!existsSync$1(stateFilePath)) return { ...DEFAULT_STATE };
2705
3278
  try {
2706
- const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
3279
+ const parsed = JSON.parse(readFileSync$1(stateFilePath, "utf-8"));
2707
3280
  if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2708
3281
  return {
2709
3282
  version: 1,
@@ -2720,8 +3293,8 @@ const readAppliedMigrationsState = (stateFilePath) => {
2720
3293
  };
2721
3294
  const writeAppliedMigrationsState = (stateFilePath, state) => {
2722
3295
  const directory = dirname(stateFilePath);
2723
- if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
2724
- writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
3296
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
3297
+ writeFileSync$1(stateFilePath, JSON.stringify(state, null, 2));
2725
3298
  };
2726
3299
  const isMigrationApplied = (state, identity, checksum) => {
2727
3300
  const matched = state.migrations.find((migration) => migration.id === identity);
@@ -2767,10 +3340,24 @@ const markMigrationRun = (state, run) => {
2767
3340
  const getLastMigrationRun = (state) => {
2768
3341
  const runs = state.runs ?? [];
2769
3342
  if (runs.length === 0) return void 0;
2770
- return [...runs].sort((left, right) => right.appliedAt.localeCompare(left.appliedAt))[0];
3343
+ return runs.map((run, index) => ({
3344
+ run,
3345
+ index
3346
+ })).sort((left, right) => {
3347
+ const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
3348
+ if (appliedAtOrder !== 0) return appliedAtOrder;
3349
+ return right.index - left.index;
3350
+ })[0]?.run;
2771
3351
  };
2772
3352
  const getLatestAppliedMigrations = (state, steps) => {
2773
- return [...state.migrations].sort((left, right) => right.appliedAt.localeCompare(left.appliedAt)).slice(0, Math.max(0, steps));
3353
+ return state.migrations.map((migration, index) => ({
3354
+ migration,
3355
+ index
3356
+ })).sort((left, right) => {
3357
+ const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
3358
+ if (appliedAtOrder !== 0) return appliedAtOrder;
3359
+ return right.index - left.index;
3360
+ }).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
2774
3361
  };
2775
3362
 
2776
3363
  //#endregion
@@ -2822,7 +3409,7 @@ var MigrateCommand = class extends Command {
2822
3409
  this.app.command = this;
2823
3410
  const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
2824
3411
  const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
2825
- if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
3412
+ if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2826
3413
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2827
3414
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2828
3415
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
@@ -2890,7 +3477,7 @@ var MigrateCommand = class extends Command {
2890
3477
  * @param migrationsDir The directory to load migration classes from.
2891
3478
  */
2892
3479
  async loadAllMigrations(migrationsDir) {
2893
- const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
3480
+ const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
2894
3481
  return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
2895
3482
  }
2896
3483
  /**
@@ -2912,7 +3499,7 @@ var MigrateCommand = class extends Command {
2912
3499
  `${base}Migration.js`,
2913
3500
  `${base}Migration.mjs`,
2914
3501
  `${base}Migration.cjs`
2915
- ].map((file) => join(migrationsDir, file)).find((file) => existsSync(file));
3502
+ ].map((file) => join(migrationsDir, file)).find((file) => existsSync$1(file));
2916
3503
  if (!target) return [[void 0, name]];
2917
3504
  const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
2918
3505
  return (await this.loadMigrationClassesFromFile(runtimeTarget)).map((cls) => [cls, runtimeTarget]);
@@ -2959,7 +3546,7 @@ var MigrateRollbackCommand = class extends Command {
2959
3546
  this.app.command = this;
2960
3547
  const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
2961
3548
  const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
2962
- if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
3549
+ if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2963
3550
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2964
3551
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2965
3552
  let appliedState = readAppliedMigrationsState(stateFilePath);
@@ -3005,7 +3592,7 @@ var MigrateRollbackCommand = class extends Command {
3005
3592
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("RolledBack", file)));
3006
3593
  }
3007
3594
  async loadAllMigrations(migrationsDir) {
3008
- const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
3595
+ const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
3009
3596
  return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
3010
3597
  }
3011
3598
  async loadMigrationClassesFromFile(filePath) {
@@ -3039,11 +3626,11 @@ var MigrationHistoryCommand = class extends Command {
3039
3626
  this.app.command = this;
3040
3627
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
3041
3628
  if (this.option("delete")) {
3042
- if (!existsSync(stateFilePath)) {
3629
+ if (!existsSync$1(stateFilePath)) {
3043
3630
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
3044
3631
  return;
3045
3632
  }
3046
- rmSync(stateFilePath);
3633
+ rmSync$1(stateFilePath);
3047
3634
  this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
3048
3635
  return;
3049
3636
  }
@@ -3172,7 +3759,7 @@ var SeedCommand = class extends Command {
3172
3759
  this.app.command = this;
3173
3760
  const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join(process.cwd(), "database", "seeders");
3174
3761
  const seedersDir = this.app.resolveRuntimeDirectoryPath(configuredSeedersDir);
3175
- if (!existsSync(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
3762
+ if (!existsSync$1(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
3176
3763
  const classes = this.option("all") ? await this.loadAllSeeders(seedersDir) : await this.loadNamedSeeder(seedersDir, this.argument("name") ?? "DatabaseSeeder");
3177
3764
  if (classes.length === 0) return void this.error("ERROR: No seeder classes found to run.");
3178
3765
  for (const SeederClassItem of classes) await new SeederClassItem().run();
@@ -3186,7 +3773,7 @@ var SeedCommand = class extends Command {
3186
3773
  * @returns
3187
3774
  */
3188
3775
  async loadAllSeeders(seedersDir) {
3189
- const files = readdirSync(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join(seedersDir, file)));
3776
+ const files = readdirSync$1(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join(seedersDir, file)));
3190
3777
  return (await Promise.all(files.map(async (file) => await this.loadSeederClassesFromFile(file)))).flat();
3191
3778
  }
3192
3779
  /**
@@ -3207,7 +3794,7 @@ var SeedCommand = class extends Command {
3207
3794
  `${base}Seeder.js`,
3208
3795
  `${base}Seeder.mjs`,
3209
3796
  `${base}Seeder.cjs`
3210
- ].map((file) => join(seedersDir, file)).find((file) => existsSync(file));
3797
+ ].map((file) => join(seedersDir, file)).find((file) => existsSync$1(file));
3211
3798
  if (!target) return [];
3212
3799
  const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
3213
3800
  return await this.loadSeederClassesFromFile(runtimeTarget);
@@ -3377,18 +3964,6 @@ const defineFactory = (model, definition) => {
3377
3964
  return new InlineFactory(model, definition);
3378
3965
  };
3379
3966
 
3380
- //#endregion
3381
- //#region src/Exceptions/MissingDelegateException.ts
3382
- var MissingDelegateException = class extends ArkormException {
3383
- constructor(message, context = {}) {
3384
- super(message, {
3385
- code: "MISSING_DELEGATE",
3386
- ...context
3387
- });
3388
- this.name = "MissingDelegateException";
3389
- }
3390
- };
3391
-
3392
3967
  //#endregion
3393
3968
  //#region src/Exceptions/ModelNotFoundException.ts
3394
3969
  /**
@@ -3451,55 +4026,59 @@ var ScopeNotDefinedException = class extends ArkormException {
3451
4026
  };
3452
4027
 
3453
4028
  //#endregion
3454
- //#region src/Exceptions/UniqueConstraintResolutionException.ts
3455
- var UniqueConstraintResolutionException = class extends ArkormException {
3456
- constructor(message, context = {}) {
3457
- super(message, {
3458
- code: "UNIQUE_CONSTRAINT_RESOLUTION_FAILED",
3459
- ...context
3460
- });
3461
- this.name = "UniqueConstraintResolutionException";
3462
- }
3463
- };
3464
-
3465
- //#endregion
3466
- //#region src/helpers/prisma.ts
3467
- /**
3468
- * Create an adapter to convert a Prisma client instance into a format
3469
- * compatible with ArkORM's expectations.
3470
- *
3471
- * @param prisma The Prisma client instance to adapt.
3472
- * @param mapping An optional mapping of Prisma delegate names to ArkORM delegate names.
3473
- * @returns A record of adapted Prisma delegates compatible with ArkORM.
3474
- */
3475
- function createPrismaAdapter(prisma) {
3476
- return Object.entries(prisma).reduce((accumulator, [key, value]) => {
3477
- if (!isDelegateLike(value)) return accumulator;
3478
- accumulator[key] = value;
3479
- return accumulator;
3480
- }, {});
3481
- }
3482
- /**
3483
- * Create a delegate mapping record for Model.setClient() from a Prisma client.
3484
- *
3485
- * @param prisma The Prisma client instance.
3486
- * @param mapping Optional mapping of Arkormˣ delegate names to Prisma delegate names.
3487
- * @returns A delegate map keyed by Arkormˣ delegate names.
3488
- */
3489
- function createPrismaDelegateMap(prisma) {
3490
- return createPrismaAdapter(prisma);
3491
- }
3492
- /**
3493
- * Infer the Prisma delegate name for a given model name using a simple convention.
3494
- *
3495
- * @param modelName The name of the model to infer the delegate name for.
3496
- * @returns The inferred Prisma delegate name.
3497
- */
3498
- function inferDelegateName(modelName) {
3499
- return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}s`;
3500
- }
3501
-
3502
- //#endregion
4029
+ //#region src/Exceptions/UniqueConstraintResolutionException.ts
4030
+ var UniqueConstraintResolutionException = class extends ArkormException {
4031
+ constructor(message, context = {}) {
4032
+ super(message, {
4033
+ code: "UNIQUE_CONSTRAINT_RESOLUTION_FAILED",
4034
+ ...context
4035
+ });
4036
+ this.name = "UniqueConstraintResolutionException";
4037
+ }
4038
+ };
4039
+
4040
+ //#endregion
4041
+ //#region src/relationship/RelationTableLoader.ts
4042
+ var RelationTableLoader = class {
4043
+ constructor(adapter) {
4044
+ this.adapter = adapter;
4045
+ }
4046
+ async selectRows(spec) {
4047
+ return await this.adapter.select({
4048
+ target: { table: spec.table },
4049
+ where: spec.where,
4050
+ columns: spec.columns,
4051
+ orderBy: spec.orderBy,
4052
+ limit: spec.limit,
4053
+ offset: spec.offset
4054
+ });
4055
+ }
4056
+ async selectRow(spec) {
4057
+ return await this.adapter.selectOne({
4058
+ target: { table: spec.table },
4059
+ where: spec.where,
4060
+ columns: spec.columns,
4061
+ orderBy: spec.orderBy,
4062
+ limit: spec.limit ?? 1,
4063
+ offset: spec.offset
4064
+ });
4065
+ }
4066
+ async selectColumnValues(spec) {
4067
+ return (await this.selectRows({
4068
+ ...spec.lookup,
4069
+ columns: [{ column: spec.column }]
4070
+ })).map((row) => row[spec.column]);
4071
+ }
4072
+ async selectColumnValue(spec) {
4073
+ return (await this.selectRow({
4074
+ ...spec.lookup,
4075
+ columns: [{ column: spec.column }],
4076
+ limit: 1
4077
+ }))?.[spec.column] ?? null;
4078
+ }
4079
+ };
4080
+
4081
+ //#endregion
3503
4082
  //#region src/relationship/Relation.ts
3504
4083
  /**
3505
4084
  * Base class for all relationship types. Not meant to be used directly.
@@ -3509,6 +4088,17 @@ function inferDelegateName(modelName) {
3509
4088
  */
3510
4089
  var Relation = class {
3511
4090
  constraint = null;
4091
+ getRelationAdapter() {
4092
+ const adapter = this.getRelatedModel().getAdapter();
4093
+ if (!adapter) throw new UnsupportedAdapterFeatureException("Relationship resolution requires a configured adapter.", { operation: "relation.adapter" });
4094
+ return adapter;
4095
+ }
4096
+ getRelatedModel() {
4097
+ return this.related;
4098
+ }
4099
+ createRelationTableLoader() {
4100
+ return new RelationTableLoader(this.getRelationAdapter());
4101
+ }
3512
4102
  /**
3513
4103
  * Apply a constraint to the relationship query.
3514
4104
  *
@@ -3725,9 +4315,31 @@ var BelongsToManyRelation = class extends Relation {
3725
4315
  */
3726
4316
  async getQuery() {
3727
4317
  const parentValue = this.parent.getAttribute(this.parentKey);
3728
- const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.foreignPivotKey]: parentValue } })).map((row) => row[this.relatedPivotKey]);
4318
+ const ids = await this.createRelationTableLoader().selectColumnValues({
4319
+ lookup: {
4320
+ table: this.throughDelegate,
4321
+ where: {
4322
+ type: "comparison",
4323
+ column: this.foreignPivotKey,
4324
+ operator: "=",
4325
+ value: parentValue
4326
+ }
4327
+ },
4328
+ column: this.relatedPivotKey
4329
+ });
3729
4330
  return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } }));
3730
4331
  }
4332
+ getMetadata() {
4333
+ return {
4334
+ type: "belongsToMany",
4335
+ relatedModel: this.related,
4336
+ throughTable: this.throughDelegate,
4337
+ foreignPivotKey: this.foreignPivotKey,
4338
+ relatedPivotKey: this.relatedPivotKey,
4339
+ parentKey: this.parentKey,
4340
+ relatedKey: this.relatedKey
4341
+ };
4342
+ }
3731
4343
  /**
3732
4344
  * Fetches the related models for this relationship.
3733
4345
  *
@@ -3794,6 +4406,14 @@ var BelongsToRelation = class extends SingleResultRelation {
3794
4406
  const foreignValue = this.parent.getAttribute(this.foreignKey);
3795
4407
  return this.applyConstraint(this.related.query().where({ [this.ownerKey]: foreignValue }));
3796
4408
  }
4409
+ getMetadata() {
4410
+ return {
4411
+ type: "belongsTo",
4412
+ relatedModel: this.related,
4413
+ foreignKey: this.foreignKey,
4414
+ ownerKey: this.ownerKey
4415
+ };
4416
+ }
3797
4417
  /**
3798
4418
  * Fetches the related models for this relationship.
3799
4419
  *
@@ -3829,6 +4449,14 @@ var HasManyRelation = class extends Relation {
3829
4449
  const localValue = this.parent.getAttribute(this.localKey);
3830
4450
  return this.applyConstraint(this.related.query().where({ [this.foreignKey]: localValue }));
3831
4451
  }
4452
+ getMetadata() {
4453
+ return {
4454
+ type: "hasMany",
4455
+ relatedModel: this.related,
4456
+ foreignKey: this.foreignKey,
4457
+ localKey: this.localKey
4458
+ };
4459
+ }
3832
4460
  /**
3833
4461
  * Fetches the related models for this relationship.
3834
4462
  *
@@ -3866,9 +4494,31 @@ var HasManyThroughRelation = class extends Relation {
3866
4494
  */
3867
4495
  async getQuery() {
3868
4496
  const localValue = this.parent.getAttribute(this.localKey);
3869
- const keys = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.firstKey]: localValue } })).map((row) => row[this.secondLocalKey]);
4497
+ const keys = await this.createRelationTableLoader().selectColumnValues({
4498
+ lookup: {
4499
+ table: this.throughDelegate,
4500
+ where: {
4501
+ type: "comparison",
4502
+ column: this.firstKey,
4503
+ operator: "=",
4504
+ value: localValue
4505
+ }
4506
+ },
4507
+ column: this.secondLocalKey
4508
+ });
3870
4509
  return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: keys } }));
3871
4510
  }
4511
+ getMetadata() {
4512
+ return {
4513
+ type: "hasManyThrough",
4514
+ relatedModel: this.related,
4515
+ throughTable: this.throughDelegate,
4516
+ firstKey: this.firstKey,
4517
+ secondKey: this.secondKey,
4518
+ localKey: this.localKey,
4519
+ secondLocalKey: this.secondLocalKey
4520
+ };
4521
+ }
3872
4522
  /**
3873
4523
  * Fetches the related models for this relationship.
3874
4524
  *
@@ -3902,6 +4552,14 @@ var HasOneRelation = class extends SingleResultRelation {
3902
4552
  const localValue = this.parent.getAttribute(this.localKey);
3903
4553
  return this.applyConstraint(this.related.query().where({ [this.foreignKey]: localValue }));
3904
4554
  }
4555
+ getMetadata() {
4556
+ return {
4557
+ type: "hasOne",
4558
+ relatedModel: this.related,
4559
+ foreignKey: this.foreignKey,
4560
+ localKey: this.localKey
4561
+ };
4562
+ }
3905
4563
  /**
3906
4564
  * Fetches the related models for this relationship.
3907
4565
  *
@@ -3937,9 +4595,31 @@ var HasOneThroughRelation = class extends SingleResultRelation {
3937
4595
  */
3938
4596
  async getQuery() {
3939
4597
  const localValue = this.parent.getAttribute(this.localKey);
3940
- const intermediate = await this.related.getDelegate(this.throughDelegate).findFirst({ where: { [this.firstKey]: localValue } });
3941
- if (!intermediate) return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: [] } }));
3942
- return this.applyConstraint(this.related.query().where({ [this.secondKey]: intermediate[this.secondLocalKey] }));
4598
+ const intermediateKey = await this.createRelationTableLoader().selectColumnValue({
4599
+ lookup: {
4600
+ table: this.throughDelegate,
4601
+ where: {
4602
+ type: "comparison",
4603
+ column: this.firstKey,
4604
+ operator: "=",
4605
+ value: localValue
4606
+ }
4607
+ },
4608
+ column: this.secondLocalKey
4609
+ });
4610
+ if (intermediateKey == null) return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: [] } }));
4611
+ return this.applyConstraint(this.related.query().where({ [this.secondKey]: intermediateKey }));
4612
+ }
4613
+ getMetadata() {
4614
+ return {
4615
+ type: "hasOneThrough",
4616
+ relatedModel: this.related,
4617
+ throughTable: this.throughDelegate,
4618
+ firstKey: this.firstKey,
4619
+ secondKey: this.secondKey,
4620
+ localKey: this.localKey,
4621
+ secondLocalKey: this.secondLocalKey
4622
+ };
3943
4623
  }
3944
4624
  /**
3945
4625
  * Fetches the related models for this relationship.
@@ -3980,6 +4660,16 @@ var MorphManyRelation = class extends Relation {
3980
4660
  [`${this.morphName}Type`]: type
3981
4661
  }));
3982
4662
  }
4663
+ getMetadata() {
4664
+ return {
4665
+ type: "morphMany",
4666
+ relatedModel: this.related,
4667
+ morphName: this.morphName,
4668
+ morphIdColumn: `${this.morphName}Id`,
4669
+ morphTypeColumn: `${this.morphName}Type`,
4670
+ localKey: this.localKey
4671
+ };
4672
+ }
3983
4673
  /**
3984
4674
  * Fetches the related models for this relationship.
3985
4675
  *
@@ -4017,6 +4707,16 @@ var MorphOneRelation = class extends SingleResultRelation {
4017
4707
  [`${this.morphName}Type`]: type
4018
4708
  }));
4019
4709
  }
4710
+ getMetadata() {
4711
+ return {
4712
+ type: "morphOne",
4713
+ relatedModel: this.related,
4714
+ morphName: this.morphName,
4715
+ morphIdColumn: `${this.morphName}Id`,
4716
+ morphTypeColumn: `${this.morphName}Type`,
4717
+ localKey: this.localKey
4718
+ };
4719
+ }
4020
4720
  /**
4021
4721
  * Fetches the related models for this relationship.
4022
4722
  *
@@ -4054,12 +4754,42 @@ var MorphToManyRelation = class extends Relation {
4054
4754
  async getQuery() {
4055
4755
  const parentValue = this.parent.getAttribute(this.parentKey);
4056
4756
  const morphType = this.parent.constructor.name;
4057
- const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: {
4058
- [`${this.morphName}Id`]: parentValue,
4059
- [`${this.morphName}Type`]: morphType
4060
- } })).map((row) => row[this.relatedPivotKey]);
4757
+ const ids = await this.createRelationTableLoader().selectColumnValues({
4758
+ lookup: {
4759
+ table: this.throughDelegate,
4760
+ where: {
4761
+ type: "group",
4762
+ operator: "and",
4763
+ conditions: [{
4764
+ type: "comparison",
4765
+ column: `${this.morphName}Id`,
4766
+ operator: "=",
4767
+ value: parentValue
4768
+ }, {
4769
+ type: "comparison",
4770
+ column: `${this.morphName}Type`,
4771
+ operator: "=",
4772
+ value: morphType
4773
+ }]
4774
+ }
4775
+ },
4776
+ column: this.relatedPivotKey
4777
+ });
4061
4778
  return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } }));
4062
4779
  }
4780
+ getMetadata() {
4781
+ return {
4782
+ type: "morphToMany",
4783
+ relatedModel: this.related,
4784
+ throughTable: this.throughDelegate,
4785
+ morphName: this.morphName,
4786
+ morphIdColumn: `${this.morphName}Id`,
4787
+ morphTypeColumn: `${this.morphName}Type`,
4788
+ relatedPivotKey: this.relatedPivotKey,
4789
+ parentKey: this.parentKey,
4790
+ relatedKey: this.relatedKey
4791
+ };
4792
+ }
4063
4793
  /**
4064
4794
  * Fetches the related models for this relationship.
4065
4795
  *
@@ -4261,7 +4991,13 @@ var Paginator = class {
4261
4991
  * @since 0.1.0
4262
4992
  */
4263
4993
  var QueryBuilder = class QueryBuilder {
4264
- args = {};
4994
+ queryWhere;
4995
+ legacyWhere;
4996
+ queryRelationLoads;
4997
+ queryOrderBy;
4998
+ querySelect;
4999
+ offsetValue;
5000
+ limitValue;
4265
5001
  eagerLoads = {};
4266
5002
  includeTrashed = false;
4267
5003
  onlyTrashedRecords = false;
@@ -4271,12 +5007,11 @@ var QueryBuilder = class QueryBuilder {
4271
5007
  /**
4272
5008
  * Creates a new QueryBuilder instance.
4273
5009
  *
4274
- * @param delegate
4275
5010
  * @param model
4276
5011
  */
4277
- constructor(delegate, model) {
4278
- this.delegate = delegate;
5012
+ constructor(model, adapter) {
4279
5013
  this.model = model;
5014
+ this.adapter = adapter;
4280
5015
  }
4281
5016
  resolvePaginationPage(page, options) {
4282
5017
  if (typeof page !== "undefined") return Number.isFinite(page) ? Math.max(1, page) : 1;
@@ -4450,11 +5185,26 @@ var QueryBuilder = class QueryBuilder {
4450
5185
  return this.clone().where(this.buildComparisonWhere(key, operator, value)).first();
4451
5186
  }
4452
5187
  addLogicalWhere(operator, where) {
4453
- if (!this.args.where) {
4454
- this.args.where = where;
5188
+ const condition = this.tryBuildQueryCondition(where);
5189
+ if (!this.legacyWhere && condition) {
5190
+ if (!this.queryWhere) {
5191
+ this.queryWhere = condition;
5192
+ return this;
5193
+ }
5194
+ this.queryWhere = {
5195
+ type: "group",
5196
+ operator: operator === "AND" ? "and" : "or",
5197
+ conditions: [this.queryWhere, condition]
5198
+ };
5199
+ return this;
5200
+ }
5201
+ const existingWhere = this.legacyWhere ?? this.toDelegateWhere(this.queryWhere);
5202
+ this.queryWhere = void 0;
5203
+ if (!existingWhere) {
5204
+ this.legacyWhere = where;
4455
5205
  return this;
4456
5206
  }
4457
- this.args.where = { [operator]: [this.args.where, where] };
5207
+ this.legacyWhere = { [operator]: [existingWhere, where] };
4458
5208
  return this;
4459
5209
  }
4460
5210
  buildComparisonWhere(key, operator, value) {
@@ -4498,7 +5248,12 @@ var QueryBuilder = class QueryBuilder {
4498
5248
  */
4499
5249
  orderBy(orderBy) {
4500
5250
  this.randomOrderEnabled = false;
4501
- this.args.orderBy = orderBy;
5251
+ const normalized = this.normalizeQueryOrderBy(orderBy);
5252
+ if (!normalized) throw new UnsupportedAdapterFeatureException("Order clauses must use Arkorm-normalizable column directions.", {
5253
+ operation: "orderBy",
5254
+ model: this.model.name
5255
+ });
5256
+ this.queryOrderBy = normalized;
4502
5257
  return this;
4503
5258
  }
4504
5259
  /**
@@ -4518,7 +5273,7 @@ var QueryBuilder = class QueryBuilder {
4518
5273
  * @returns
4519
5274
  */
4520
5275
  reorder(column, direction = "asc") {
4521
- this.args.orderBy = void 0;
5276
+ this.queryOrderBy = void 0;
4522
5277
  this.randomOrderEnabled = false;
4523
5278
  if (!column) return this;
4524
5279
  return this.orderBy({ [column]: direction });
@@ -4548,7 +5303,13 @@ var QueryBuilder = class QueryBuilder {
4548
5303
  * @returns
4549
5304
  */
4550
5305
  include(include) {
4551
- this.args.include = include;
5306
+ const normalized = this.normalizeRelationLoads(include);
5307
+ if (normalized === null) throw new UnsupportedAdapterFeatureException("Include clauses could not be normalized into Arkorm relation load plans.", {
5308
+ operation: "include",
5309
+ model: this.model.name,
5310
+ meta: { feature: "relationLoads" }
5311
+ });
5312
+ this.queryRelationLoads = normalized;
4552
5313
  return this;
4553
5314
  }
4554
5315
  /**
@@ -4560,14 +5321,6 @@ var QueryBuilder = class QueryBuilder {
4560
5321
  */
4561
5322
  with(relations) {
4562
5323
  const relationMap = this.normalizeWith(relations);
4563
- const names = Object.keys(relationMap);
4564
- this.args.include = {
4565
- ...this.args.include || {},
4566
- ...names.reduce((accumulator, name) => {
4567
- accumulator[name] = true;
4568
- return accumulator;
4569
- }, {})
4570
- };
4571
5324
  Object.entries(relationMap).forEach(([name, constraint]) => {
4572
5325
  this.eagerLoads[name] = constraint;
4573
5326
  });
@@ -4880,7 +5633,12 @@ var QueryBuilder = class QueryBuilder {
4880
5633
  * @returns
4881
5634
  */
4882
5635
  select(select) {
4883
- this.args.select = select;
5636
+ const normalized = this.normalizeQuerySelect(select);
5637
+ if (normalized === null) throw new UnsupportedAdapterFeatureException("Select clauses must use Arkorm-normalizable column projections.", {
5638
+ operation: "select",
5639
+ model: this.model.name
5640
+ });
5641
+ this.querySelect = normalized;
4884
5642
  return this;
4885
5643
  }
4886
5644
  /**
@@ -4891,7 +5649,7 @@ var QueryBuilder = class QueryBuilder {
4891
5649
  * @returns
4892
5650
  */
4893
5651
  skip(skip) {
4894
- this.args.skip = skip;
5652
+ this.offsetValue = skip;
4895
5653
  return this;
4896
5654
  }
4897
5655
  /**
@@ -4910,7 +5668,7 @@ var QueryBuilder = class QueryBuilder {
4910
5668
  * @returns
4911
5669
  */
4912
5670
  take(take) {
4913
- this.args.take = take;
5671
+ this.limitValue = take;
4914
5672
  return this;
4915
5673
  }
4916
5674
  /**
@@ -4941,16 +5699,13 @@ var QueryBuilder = class QueryBuilder {
4941
5699
  */
4942
5700
  async get() {
4943
5701
  const relationCache = /* @__PURE__ */ new WeakMap();
4944
- const rows = await this.delegate.findMany(this.buildFindArgs());
5702
+ const rows = await this.executeReadRows();
4945
5703
  const normalizedRows = this.randomOrderEnabled ? this.shuffleRows(rows) : rows;
4946
5704
  const models = await this.model.hydrateManyRetrieved(normalizedRows);
4947
5705
  let filteredModels = models;
4948
- if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.args.where) {
5706
+ if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.hasBaseWhereConstraints()) {
4949
5707
  const baseIds = new Set(models.map((model) => this.getModelId(model)).filter((id) => id != null));
4950
- const allRows = await this.delegate.findMany({
4951
- ...this.args,
4952
- where: this.buildSoftDeleteOnlyWhere()
4953
- });
5708
+ const allRows = await this.executeReadRows(this.buildSoftDeleteOnlyWhere(), true);
4954
5709
  const allModels = this.model.hydrateMany(allRows);
4955
5710
  filteredModels = await this.filterModelsByRelationConstraints(allModels, relationCache, baseIds);
4956
5711
  } else filteredModels = await this.filterModelsByRelationConstraints(models, relationCache);
@@ -4969,7 +5724,7 @@ var QueryBuilder = class QueryBuilder {
4969
5724
  async first() {
4970
5725
  if (this.hasRelationFilters() || this.hasRelationAggregates()) return (await this.get()).all()[0] ?? null;
4971
5726
  if (this.randomOrderEnabled) {
4972
- const rows = await this.delegate.findMany(this.buildFindArgs());
5727
+ const rows = await this.executeReadRows();
4973
5728
  if (rows.length === 0) return null;
4974
5729
  const row = this.shuffleRows(rows)[0];
4975
5730
  if (!row) return null;
@@ -4977,7 +5732,7 @@ var QueryBuilder = class QueryBuilder {
4977
5732
  await model.load(this.eagerLoads);
4978
5733
  return model;
4979
5734
  }
4980
- const row = await this.delegate.findFirst(this.buildFindArgs());
5735
+ const row = await this.executeReadRow();
4981
5736
  if (!row) return null;
4982
5737
  const model = await this.model.hydrateRetrieved(row);
4983
5738
  await model.load(this.eagerLoads);
@@ -4993,11 +5748,12 @@ var QueryBuilder = class QueryBuilder {
4993
5748
  if (!model) throw new ModelNotFoundException(this.model.name, "Record not found.");
4994
5749
  return model;
4995
5750
  }
4996
- async find(value, key = "id") {
4997
- return this.where({ [key]: value }).first();
5751
+ async find(value, key) {
5752
+ const resolvedKey = key ?? this.model.getPrimaryKey();
5753
+ return this.where({ [resolvedKey]: value }).first();
4998
5754
  }
4999
5755
  async findOr(value, keyOrCallback, maybeCallback) {
5000
- const key = typeof keyOrCallback === "string" ? keyOrCallback : "id";
5756
+ const key = typeof keyOrCallback === "string" ? keyOrCallback : this.model.getPrimaryKey();
5001
5757
  const callback = typeof keyOrCallback === "function" ? keyOrCallback : maybeCallback;
5002
5758
  if (!callback) throw new QueryConstraintException("findOr requires a fallback callback.", {
5003
5759
  operation: "findOr",
@@ -5014,7 +5770,7 @@ var QueryBuilder = class QueryBuilder {
5014
5770
  * @returns
5015
5771
  */
5016
5772
  async value(column) {
5017
- const row = await this.delegate.findFirst(this.buildFindArgs());
5773
+ const row = await this.executeReadRow();
5018
5774
  if (!row) return null;
5019
5775
  return row[column] ?? null;
5020
5776
  }
@@ -5037,7 +5793,7 @@ var QueryBuilder = class QueryBuilder {
5037
5793
  * @returns
5038
5794
  */
5039
5795
  async pluck(column, key) {
5040
- const rows = await this.delegate.findMany(this.buildFindArgs());
5796
+ const rows = await this.executeReadRows();
5041
5797
  if (!key) return new ArkormCollection(rows.map((row) => row[column]));
5042
5798
  return new ArkormCollection(rows.sort((leftRow, rightRow) => String(leftRow[key]).localeCompare(String(rightRow[key]))).map((row) => row[column]));
5043
5799
  }
@@ -5048,7 +5804,7 @@ var QueryBuilder = class QueryBuilder {
5048
5804
  * @returns
5049
5805
  */
5050
5806
  async create(data) {
5051
- const created = await this.delegate.create({ data });
5807
+ const created = await this.executeInsertRow(data);
5052
5808
  return this.model.hydrate(created);
5053
5809
  }
5054
5810
  /**
@@ -5070,14 +5826,11 @@ var QueryBuilder = class QueryBuilder {
5070
5826
  async insert(values) {
5071
5827
  const payloads = this.normalizeInsertPayloads(values);
5072
5828
  if (payloads.length === 0) return true;
5073
- const delegate = this.delegate;
5074
- if (typeof delegate.createMany === "function") {
5075
- await delegate.createMany({ data: payloads });
5829
+ if (payloads.length === 1) {
5830
+ await this.executeInsertRow(payloads[0]);
5076
5831
  return true;
5077
5832
  }
5078
- await Promise.all(payloads.map(async (payload) => {
5079
- await this.delegate.create({ data: payload });
5080
- }));
5833
+ await this.executeInsertManyRows(payloads);
5081
5834
  return true;
5082
5835
  }
5083
5836
  /**
@@ -5089,22 +5842,7 @@ var QueryBuilder = class QueryBuilder {
5089
5842
  async insertOrIgnore(values) {
5090
5843
  const payloads = this.normalizeInsertPayloads(values);
5091
5844
  if (payloads.length === 0) return 0;
5092
- const delegate = this.delegate;
5093
- if (typeof delegate.createMany === "function") {
5094
- const result = await delegate.createMany({
5095
- data: payloads,
5096
- skipDuplicates: true
5097
- });
5098
- return this.resolveAffectedCount(result, payloads.length);
5099
- }
5100
- let inserted = 0;
5101
- for (const payload of payloads) try {
5102
- await this.delegate.create({ data: payload });
5103
- inserted += 1;
5104
- } catch {
5105
- continue;
5106
- }
5107
- return inserted;
5845
+ return await this.executeInsertManyRows(payloads, true);
5108
5846
  }
5109
5847
  /**
5110
5848
  * Insert a record and return its primary key value.
@@ -5114,8 +5852,8 @@ var QueryBuilder = class QueryBuilder {
5114
5852
  * @returns
5115
5853
  */
5116
5854
  async insertGetId(values, sequence) {
5117
- const created = await this.delegate.create({ data: values });
5118
- const key = sequence ?? "id";
5855
+ const created = await this.executeInsertRow(values);
5856
+ const key = sequence ?? this.model.getPrimaryKey();
5119
5857
  if (!(key in created)) throw new UniqueConstraintResolutionException(`Inserted record does not contain key [${key}].`, {
5120
5858
  operation: "insertGetId",
5121
5859
  model: this.model.name,
@@ -5162,10 +5900,7 @@ var QueryBuilder = class QueryBuilder {
5162
5900
  model: this.model.name
5163
5901
  });
5164
5902
  const uniqueWhere = await this.resolveUniqueWhere(where);
5165
- const updated = await this.delegate.update({
5166
- where: uniqueWhere,
5167
- data
5168
- });
5903
+ const updated = await this.executeUpdateRow(uniqueWhere, data);
5169
5904
  return this.model.hydrate(updated);
5170
5905
  }
5171
5906
  /**
@@ -5180,16 +5915,7 @@ var QueryBuilder = class QueryBuilder {
5180
5915
  operation: "updateFrom",
5181
5916
  model: this.model.name
5182
5917
  });
5183
- const delegate = this.delegate;
5184
- if (typeof delegate.updateMany === "function") {
5185
- const result = await delegate.updateMany({
5186
- where,
5187
- data
5188
- });
5189
- return this.resolveAffectedCount(result, 0);
5190
- }
5191
- await this.update(data);
5192
- return 1;
5918
+ return await this.executeUpdateManyRows(where, data);
5193
5919
  }
5194
5920
  /**
5195
5921
  * Insert a record when no match exists, otherwise update the matching record.
@@ -5199,13 +5925,13 @@ var QueryBuilder = class QueryBuilder {
5199
5925
  * @returns
5200
5926
  */
5201
5927
  async updateOrInsert(attributes, values = {}) {
5202
- const exists = await this.delegate.findFirst({ where: attributes }) != null;
5928
+ const exists = await this.clone().where(attributes).first() != null;
5203
5929
  const resolvedValues = typeof values === "function" ? await values(exists) : values;
5204
5930
  if (!exists) {
5205
- await this.delegate.create({ data: {
5931
+ await this.executeInsertRow({
5206
5932
  ...attributes,
5207
5933
  ...resolvedValues
5208
- } });
5934
+ });
5209
5935
  return true;
5210
5936
  }
5211
5937
  return await this.clone().where(attributes).update(resolvedValues) != null;
@@ -5249,9 +5975,53 @@ var QueryBuilder = class QueryBuilder {
5249
5975
  model: this.model.name
5250
5976
  });
5251
5977
  const uniqueWhere = await this.resolveUniqueWhere(where);
5252
- const deleted = await this.delegate.delete({ where: uniqueWhere });
5978
+ const deleted = await this.executeDeleteRow(uniqueWhere);
5253
5979
  return this.model.hydrate(deleted);
5254
5980
  }
5981
+ tryBuildInsertSpec(values) {
5982
+ return {
5983
+ target: this.buildQueryTarget(),
5984
+ values
5985
+ };
5986
+ }
5987
+ tryBuildInsertManySpec(values) {
5988
+ return {
5989
+ target: this.buildQueryTarget(),
5990
+ values
5991
+ };
5992
+ }
5993
+ tryBuildInsertOrIgnoreManySpec(values) {
5994
+ return {
5995
+ ...this.tryBuildInsertManySpec(values),
5996
+ ignoreDuplicates: true
5997
+ };
5998
+ }
5999
+ tryBuildUpdateSpec(where, values) {
6000
+ const condition = this.tryBuildQueryCondition(where);
6001
+ if (!condition) return null;
6002
+ return {
6003
+ target: this.buildQueryTarget(),
6004
+ where: condition,
6005
+ values
6006
+ };
6007
+ }
6008
+ tryBuildUpdateManySpec(where, values) {
6009
+ const condition = this.tryBuildQueryCondition(where);
6010
+ if (condition === null) return null;
6011
+ return {
6012
+ target: this.buildQueryTarget(),
6013
+ where: condition,
6014
+ values
6015
+ };
6016
+ }
6017
+ tryBuildDeleteSpec(where) {
6018
+ const condition = this.tryBuildQueryCondition(where);
6019
+ if (!condition) return null;
6020
+ return {
6021
+ target: this.buildQueryTarget(),
6022
+ where: condition
6023
+ };
6024
+ }
5255
6025
  /**
5256
6026
  * Counts the number of records matching the current query constraints.
5257
6027
  *
@@ -5259,7 +6029,7 @@ var QueryBuilder = class QueryBuilder {
5259
6029
  */
5260
6030
  async count() {
5261
6031
  if (this.hasRelationFilters()) return (await this.get()).all().length;
5262
- return this.delegate.count({ where: this.buildWhere() });
6032
+ return this.executeReadCount();
5263
6033
  }
5264
6034
  /**
5265
6035
  * Determines if any records exist for the current query constraints.
@@ -5268,7 +6038,7 @@ var QueryBuilder = class QueryBuilder {
5268
6038
  */
5269
6039
  async exists() {
5270
6040
  if (this.hasRelationFilters()) return await this.count() > 0;
5271
- return await this.delegate.findFirst(this.buildFindArgs()) != null;
6041
+ return await this.executeReadExists();
5272
6042
  }
5273
6043
  /**
5274
6044
  * Determines if no records exist for the current query constraints.
@@ -5345,7 +6115,7 @@ var QueryBuilder = class QueryBuilder {
5345
6115
  * @returns
5346
6116
  */
5347
6117
  async min(column) {
5348
- const rows = await this.delegate.findMany(this.buildFindArgs());
6118
+ const rows = await this.executeReadRows();
5349
6119
  if (rows.length === 0) return null;
5350
6120
  const values = rows.map((row) => row[column]).filter((value) => value != null);
5351
6121
  if (values.length === 0) return null;
@@ -5358,7 +6128,7 @@ var QueryBuilder = class QueryBuilder {
5358
6128
  * @returns
5359
6129
  */
5360
6130
  async max(column) {
5361
- const rows = await this.delegate.findMany(this.buildFindArgs());
6131
+ const rows = await this.executeReadRows();
5362
6132
  if (rows.length === 0) return null;
5363
6133
  const values = rows.map((row) => row[column]).filter((value) => value != null);
5364
6134
  if (values.length === 0) return null;
@@ -5371,7 +6141,7 @@ var QueryBuilder = class QueryBuilder {
5371
6141
  * @returns
5372
6142
  */
5373
6143
  async sum(column) {
5374
- return (await this.delegate.findMany(this.buildFindArgs())).reduce((total, row) => {
6144
+ return (await this.executeReadRows()).reduce((total, row) => {
5375
6145
  const value = row[column];
5376
6146
  const numeric = typeof value === "number" ? value : Number(value);
5377
6147
  return Number.isFinite(numeric) ? total + numeric : total;
@@ -5384,7 +6154,7 @@ var QueryBuilder = class QueryBuilder {
5384
6154
  * @returns
5385
6155
  */
5386
6156
  async avg(column) {
5387
- const values = (await this.delegate.findMany(this.buildFindArgs())).map((row) => {
6157
+ const values = (await this.executeReadRows()).map((row) => {
5388
6158
  const value = row[column];
5389
6159
  return typeof value === "number" ? value : Number(value);
5390
6160
  }).filter((value) => Number.isFinite(value));
@@ -5399,13 +6169,16 @@ var QueryBuilder = class QueryBuilder {
5399
6169
  * @returns
5400
6170
  */
5401
6171
  whereRaw(sql, bindings = []) {
5402
- const delegate = this.delegate;
5403
- if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
6172
+ if (!this.adapter?.capabilities?.rawWhere) throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
5404
6173
  operation: "whereRaw",
5405
6174
  model: this.model.name,
5406
6175
  meta: { feature: "rawWhere" }
5407
6176
  });
5408
- this.args.where = delegate.applyRawWhere(this.buildWhere(), sql, bindings);
6177
+ this.appendQueryCondition("AND", {
6178
+ type: "raw",
6179
+ sql,
6180
+ bindings
6181
+ });
5409
6182
  return this;
5410
6183
  }
5411
6184
  /**
@@ -5416,14 +6189,17 @@ var QueryBuilder = class QueryBuilder {
5416
6189
  * @returns
5417
6190
  */
5418
6191
  orWhereRaw(sql, bindings = []) {
5419
- const delegate = this.delegate;
5420
- if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
6192
+ if (!this.adapter?.capabilities?.rawWhere) throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
5421
6193
  operation: "orWhereRaw",
5422
6194
  model: this.model.name,
5423
6195
  meta: { feature: "rawWhere" }
5424
6196
  });
5425
- const rawWhere = delegate.applyRawWhere(void 0, sql, bindings);
5426
- return this.orWhere(rawWhere);
6197
+ this.appendQueryCondition("OR", {
6198
+ type: "raw",
6199
+ sql,
6200
+ bindings
6201
+ });
6202
+ return this;
5427
6203
  }
5428
6204
  /**
5429
6205
  * Paginates the query results and returns a LengthAwarePaginator instance
@@ -5474,13 +6250,14 @@ var QueryBuilder = class QueryBuilder {
5474
6250
  * @returns
5475
6251
  */
5476
6252
  clone() {
5477
- const builder = new QueryBuilder(this.delegate, this.model);
5478
- builder.args.where = this.args.where;
5479
- builder.args.include = this.args.include;
5480
- builder.args.orderBy = this.args.orderBy;
5481
- builder.args.select = this.args.select;
5482
- builder.args.skip = this.args.skip;
5483
- builder.args.take = this.args.take;
6253
+ const builder = new QueryBuilder(this.model, this.adapter);
6254
+ builder.queryWhere = this.queryWhere;
6255
+ builder.legacyWhere = this.legacyWhere;
6256
+ builder.queryRelationLoads = this.queryRelationLoads ? this.cloneRelationLoads(this.queryRelationLoads) : void 0;
6257
+ builder.queryOrderBy = this.queryOrderBy ? [...this.queryOrderBy] : void 0;
6258
+ builder.querySelect = this.querySelect ? [...this.querySelect] : void 0;
6259
+ builder.offsetValue = this.offsetValue;
6260
+ builder.limitValue = this.limitValue;
5484
6261
  builder.includeTrashed = this.includeTrashed;
5485
6262
  builder.onlyTrashedRecords = this.onlyTrashedRecords;
5486
6263
  builder.randomOrderEnabled = this.randomOrderEnabled;
@@ -5509,6 +6286,424 @@ var QueryBuilder = class QueryBuilder {
5509
6286
  }, {});
5510
6287
  return relations;
5511
6288
  }
6289
+ buildQueryTarget() {
6290
+ const metadata = this.model.getModelMetadata();
6291
+ return {
6292
+ model: this.model,
6293
+ modelName: this.model.name,
6294
+ table: metadata.table,
6295
+ primaryKey: metadata.primaryKey,
6296
+ columns: metadata.columns,
6297
+ softDelete: metadata.softDelete
6298
+ };
6299
+ }
6300
+ hasBaseWhereConstraints() {
6301
+ return this.queryWhere != null || this.legacyWhere != null;
6302
+ }
6303
+ normalizeQuerySelect(select) {
6304
+ if (Array.isArray(select) || typeof select !== "object" || !select) return null;
6305
+ const entries = Object.entries(select);
6306
+ if (entries.some(([, value]) => value !== true && value !== false && value !== void 0)) return null;
6307
+ const columns = entries.filter(([, value]) => value === true).map(([column]) => ({ column }));
6308
+ return columns.length > 0 ? columns : [];
6309
+ }
6310
+ normalizeQueryOrderBy(orderBy) {
6311
+ return (Array.isArray(orderBy) ? orderBy : [orderBy]).reduce((accumulator, clause) => {
6312
+ if (!accumulator) return null;
6313
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return null;
6314
+ const entries = Object.entries(clause);
6315
+ for (const [column, direction] of entries) {
6316
+ if (direction !== "asc" && direction !== "desc") return null;
6317
+ accumulator.push({
6318
+ column,
6319
+ direction
6320
+ });
6321
+ }
6322
+ return accumulator;
6323
+ }, []);
6324
+ }
6325
+ cloneRelationLoads(plans) {
6326
+ return plans.map((plan) => {
6327
+ return {
6328
+ relation: plan.relation,
6329
+ constraint: plan.constraint,
6330
+ orderBy: plan.orderBy ? [...plan.orderBy] : void 0,
6331
+ limit: plan.limit,
6332
+ offset: plan.offset,
6333
+ columns: plan.columns ? [...plan.columns] : void 0,
6334
+ relationLoads: plan.relationLoads ? this.cloneRelationLoads(plan.relationLoads) : void 0
6335
+ };
6336
+ });
6337
+ }
6338
+ normalizeRelationLoadSelect(select) {
6339
+ if (Array.isArray(select) || typeof select !== "object" || !select) return null;
6340
+ const entries = Object.entries(select);
6341
+ if (entries.some(([, value]) => value !== true && value !== false && value !== void 0)) return null;
6342
+ return entries.filter(([, value]) => value === true).map(([column]) => ({ column }));
6343
+ }
6344
+ normalizeRelationLoadOrderBy(orderBy) {
6345
+ const clauses = Array.isArray(orderBy) ? orderBy : [orderBy];
6346
+ const normalized = [];
6347
+ for (const clause of clauses) {
6348
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return null;
6349
+ for (const [column, direction] of Object.entries(clause)) {
6350
+ if (direction !== "asc" && direction !== "desc") return null;
6351
+ normalized.push({
6352
+ column,
6353
+ direction
6354
+ });
6355
+ }
6356
+ }
6357
+ return normalized;
6358
+ }
6359
+ normalizeRelationLoads(include) {
6360
+ if (Array.isArray(include) || typeof include !== "object" || !include) return null;
6361
+ const plans = [];
6362
+ for (const [relation, value] of Object.entries(include)) {
6363
+ if (value === false || value === void 0) continue;
6364
+ if (value === true) {
6365
+ plans.push({ relation });
6366
+ continue;
6367
+ }
6368
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6369
+ const options = value;
6370
+ const constraint = options.where === void 0 ? void 0 : this.tryBuildQueryCondition(options.where);
6371
+ const orderBy = options.orderBy === void 0 ? void 0 : this.normalizeRelationLoadOrderBy(options.orderBy);
6372
+ const columns = options.select === void 0 ? void 0 : this.normalizeRelationLoadSelect(options.select);
6373
+ const relationLoads = options.include === void 0 ? void 0 : this.normalizeRelationLoads(options.include);
6374
+ if (constraint === null || orderBy === null || columns === null || relationLoads === null) return null;
6375
+ if (options.skip !== void 0 && typeof options.skip !== "number" || options.take !== void 0 && typeof options.take !== "number") return null;
6376
+ plans.push({
6377
+ relation,
6378
+ constraint,
6379
+ orderBy,
6380
+ limit: options.take,
6381
+ offset: options.skip,
6382
+ columns,
6383
+ relationLoads
6384
+ });
6385
+ }
6386
+ return plans;
6387
+ }
6388
+ appendQueryCondition(operator, condition) {
6389
+ if (!this.queryWhere) {
6390
+ this.queryWhere = condition;
6391
+ return;
6392
+ }
6393
+ this.queryWhere = {
6394
+ type: "group",
6395
+ operator: operator === "AND" ? "and" : "or",
6396
+ conditions: [this.queryWhere, condition]
6397
+ };
6398
+ }
6399
+ toDelegateWhere(condition) {
6400
+ if (!condition) return void 0;
6401
+ if (condition.type === "comparison") {
6402
+ if (condition.operator === "is-null") return { [condition.column]: null };
6403
+ if (condition.operator === "is-not-null") return { [condition.column]: { not: null } };
6404
+ if (condition.operator === "=") return { [condition.column]: condition.value };
6405
+ if (condition.operator === "!=") return { [condition.column]: { not: condition.value } };
6406
+ if (condition.operator === ">") return { [condition.column]: { gt: condition.value } };
6407
+ if (condition.operator === ">=") return { [condition.column]: { gte: condition.value } };
6408
+ if (condition.operator === "<") return { [condition.column]: { lt: condition.value } };
6409
+ if (condition.operator === "<=") return { [condition.column]: { lte: condition.value } };
6410
+ if (condition.operator === "in") return { [condition.column]: { in: Array.isArray(condition.value) ? condition.value : [condition.value] } };
6411
+ if (condition.operator === "not-in") return { [condition.column]: { notIn: Array.isArray(condition.value) ? condition.value : [condition.value] } };
6412
+ if (condition.operator === "contains") return { [condition.column]: { contains: condition.value } };
6413
+ if (condition.operator === "starts-with") return { [condition.column]: { startsWith: condition.value } };
6414
+ return { [condition.column]: { endsWith: condition.value } };
6415
+ }
6416
+ if (condition.type === "group") {
6417
+ const conditions = condition.conditions.map((entry) => this.toDelegateWhere(entry)).filter((entry) => Boolean(entry));
6418
+ if (conditions.length === 0) return void 0;
6419
+ return { [condition.operator === "and" ? "AND" : "OR"]: conditions };
6420
+ }
6421
+ if (condition.type === "not") {
6422
+ const nested = this.toDelegateWhere(condition.condition);
6423
+ if (!nested) return void 0;
6424
+ return { NOT: nested };
6425
+ }
6426
+ }
6427
+ buildSoftDeleteQueryCondition() {
6428
+ const softDeleteConfig = this.model.getSoftDeleteConfig();
6429
+ if (!softDeleteConfig.enabled || this.includeTrashed) return void 0;
6430
+ return {
6431
+ type: "comparison",
6432
+ column: softDeleteConfig.column,
6433
+ operator: this.onlyTrashedRecords ? "is-not-null" : "is-null"
6434
+ };
6435
+ }
6436
+ buildQueryWhereCondition(softDeleteOnly = false) {
6437
+ if (this.legacyWhere) {
6438
+ const fallbackWhere = softDeleteOnly ? this.buildSoftDeleteOnlyWhere() : this.buildWhere();
6439
+ return this.tryBuildQueryCondition(fallbackWhere);
6440
+ }
6441
+ const softDeleteCondition = this.buildSoftDeleteQueryCondition();
6442
+ if (softDeleteOnly) return softDeleteCondition;
6443
+ if (!this.queryWhere) return softDeleteCondition;
6444
+ if (!softDeleteCondition) return this.queryWhere;
6445
+ return {
6446
+ type: "group",
6447
+ operator: "and",
6448
+ conditions: [this.queryWhere, softDeleteCondition]
6449
+ };
6450
+ }
6451
+ tryBuildQuerySelectColumns() {
6452
+ return this.querySelect;
6453
+ }
6454
+ tryBuildQueryOrderBy() {
6455
+ return this.queryOrderBy;
6456
+ }
6457
+ tryBuildFieldCondition(column, value) {
6458
+ if (value === null) return {
6459
+ type: "comparison",
6460
+ column,
6461
+ operator: "is-null"
6462
+ };
6463
+ if (value instanceof Date || typeof value !== "object") return {
6464
+ type: "comparison",
6465
+ column,
6466
+ operator: "=",
6467
+ value
6468
+ };
6469
+ if (Array.isArray(value)) return null;
6470
+ const clause = value;
6471
+ const conditions = [];
6472
+ for (const [operator, operand] of Object.entries(clause)) {
6473
+ if (operator === "equals") {
6474
+ conditions.push({
6475
+ type: "comparison",
6476
+ column,
6477
+ operator: operand === null ? "is-null" : "=",
6478
+ value: operand
6479
+ });
6480
+ continue;
6481
+ }
6482
+ if (operator === "not") {
6483
+ if (operand && typeof operand === "object" && !Array.isArray(operand)) return null;
6484
+ conditions.push({
6485
+ type: "comparison",
6486
+ column,
6487
+ operator: operand === null ? "is-not-null" : "!=",
6488
+ value: operand
6489
+ });
6490
+ continue;
6491
+ }
6492
+ if (operator === "in" || operator === "notIn") {
6493
+ if (!Array.isArray(operand)) return null;
6494
+ conditions.push({
6495
+ type: "comparison",
6496
+ column,
6497
+ operator: operator === "in" ? "in" : "not-in",
6498
+ value: operand
6499
+ });
6500
+ continue;
6501
+ }
6502
+ if (operator === "gt" || operator === "gte" || operator === "lt" || operator === "lte") {
6503
+ const comparison = {
6504
+ type: "comparison",
6505
+ column,
6506
+ operator: operator === "gt" ? ">" : operator === "gte" ? ">=" : operator === "lt" ? "<" : "<=",
6507
+ value: operand
6508
+ };
6509
+ conditions.push(comparison);
6510
+ continue;
6511
+ }
6512
+ if (operator === "contains" || operator === "startsWith" || operator === "endsWith") {
6513
+ conditions.push({
6514
+ type: "comparison",
6515
+ column,
6516
+ operator: operator === "startsWith" ? "starts-with" : operator === "endsWith" ? "ends-with" : "contains",
6517
+ value: operand
6518
+ });
6519
+ continue;
6520
+ }
6521
+ return null;
6522
+ }
6523
+ if (conditions.length === 0) return null;
6524
+ return conditions.length === 1 ? conditions[0] ?? null : {
6525
+ type: "group",
6526
+ operator: "and",
6527
+ conditions
6528
+ };
6529
+ }
6530
+ tryBuildQueryCondition(where) {
6531
+ if (!where) return void 0;
6532
+ if (Array.isArray(where) || typeof where !== "object") return null;
6533
+ const conditions = [];
6534
+ for (const [key, value] of Object.entries(where)) {
6535
+ if (key === "AND" || key === "OR") {
6536
+ if (!Array.isArray(value)) return null;
6537
+ const nested = value.map((entry) => this.tryBuildQueryCondition(entry)).filter((entry) => entry !== void 0);
6538
+ if (nested.some((entry) => entry == null)) return null;
6539
+ if (nested.length > 0) conditions.push({
6540
+ type: "group",
6541
+ operator: key === "AND" ? "and" : "or",
6542
+ conditions: nested
6543
+ });
6544
+ continue;
6545
+ }
6546
+ if (key === "NOT") {
6547
+ const nested = Array.isArray(value) ? value.map((entry) => this.tryBuildQueryCondition(entry)).filter((entry) => entry !== void 0) : [this.tryBuildQueryCondition(value)].filter((entry) => entry !== void 0);
6548
+ if (nested.some((entry) => entry == null)) return null;
6549
+ if (nested.length === 0) continue;
6550
+ conditions.push({
6551
+ type: "not",
6552
+ condition: nested.length === 1 ? nested[0] : {
6553
+ type: "group",
6554
+ operator: "and",
6555
+ conditions: nested
6556
+ }
6557
+ });
6558
+ continue;
6559
+ }
6560
+ const condition = this.tryBuildFieldCondition(key, value);
6561
+ if (!condition) return null;
6562
+ conditions.push(condition);
6563
+ }
6564
+ if (conditions.length === 0) return void 0;
6565
+ return conditions.length === 1 ? conditions[0] ?? void 0 : {
6566
+ type: "group",
6567
+ operator: "and",
6568
+ conditions
6569
+ };
6570
+ }
6571
+ tryBuildSelectSpec(where, softDeleteOnly = false) {
6572
+ const columns = this.tryBuildQuerySelectColumns();
6573
+ const orderBy = this.tryBuildQueryOrderBy();
6574
+ const condition = this.buildQueryWhereCondition(softDeleteOnly);
6575
+ if (columns === null || orderBy === null || condition === null) return null;
6576
+ return {
6577
+ target: this.buildQueryTarget(),
6578
+ columns,
6579
+ where: condition,
6580
+ orderBy,
6581
+ limit: this.limitValue,
6582
+ offset: this.offsetValue,
6583
+ relationLoads: this.queryRelationLoads
6584
+ };
6585
+ }
6586
+ tryBuildAggregateSpec() {
6587
+ const condition = this.buildQueryWhereCondition(false);
6588
+ if (condition === null) return null;
6589
+ return {
6590
+ target: this.buildQueryTarget(),
6591
+ where: condition,
6592
+ aggregate: { type: "count" }
6593
+ };
6594
+ }
6595
+ requireAdapter() {
6596
+ if (!this.adapter) throw new UnsupportedAdapterFeatureException("Query execution requires a configured database adapter.", {
6597
+ operation: "query.execute",
6598
+ model: this.model.name,
6599
+ meta: { feature: "adapter" }
6600
+ });
6601
+ return this.adapter;
6602
+ }
6603
+ async executeReadRows(whereOverride, useWhereOverride = false) {
6604
+ const adapter = this.requireAdapter();
6605
+ const spec = this.tryBuildSelectSpec(useWhereOverride ? whereOverride : this.buildWhere(), useWhereOverride);
6606
+ if (!spec) throw new UnsupportedAdapterFeatureException("Query shape could not be compiled into an Arkorm select specification.", {
6607
+ operation: "query.select",
6608
+ model: this.model.name
6609
+ });
6610
+ return await adapter.select(spec);
6611
+ }
6612
+ async executeReadRow() {
6613
+ const adapter = this.requireAdapter();
6614
+ const spec = this.tryBuildSelectSpec(this.buildWhere());
6615
+ if (!spec) throw new UnsupportedAdapterFeatureException("Query shape could not be compiled into an Arkorm select specification.", {
6616
+ operation: "query.selectOne",
6617
+ model: this.model.name
6618
+ });
6619
+ return await adapter.selectOne(spec);
6620
+ }
6621
+ async executeReadCount() {
6622
+ const adapter = this.requireAdapter();
6623
+ const spec = this.tryBuildAggregateSpec();
6624
+ if (!spec) throw new UnsupportedAdapterFeatureException("Query shape could not be compiled into an Arkorm aggregate specification.", {
6625
+ operation: "query.count",
6626
+ model: this.model.name
6627
+ });
6628
+ return await adapter.count(spec);
6629
+ }
6630
+ async executeReadExists() {
6631
+ const adapter = this.requireAdapter();
6632
+ const spec = this.tryBuildSelectSpec(this.buildWhere());
6633
+ if (!spec) throw new UnsupportedAdapterFeatureException("Query shape could not be compiled into an Arkorm select specification.", {
6634
+ operation: "query.exists",
6635
+ model: this.model.name
6636
+ });
6637
+ if (typeof adapter.exists === "function") return await adapter.exists({
6638
+ ...spec,
6639
+ limit: 1
6640
+ });
6641
+ return await adapter.selectOne({
6642
+ ...spec,
6643
+ limit: 1
6644
+ }) != null;
6645
+ }
6646
+ async executeInsertRow(values) {
6647
+ return await this.requireAdapter().insert(this.tryBuildInsertSpec(values));
6648
+ }
6649
+ async executeInsertManyRows(values, ignoreDuplicates = false) {
6650
+ const adapter = this.requireAdapter();
6651
+ if (typeof adapter.insertMany === "function") return await adapter.insertMany(ignoreDuplicates ? this.tryBuildInsertOrIgnoreManySpec(values) : this.tryBuildInsertManySpec(values));
6652
+ let inserted = 0;
6653
+ for (const value of values) try {
6654
+ await adapter.insert(this.tryBuildInsertSpec(value));
6655
+ inserted += 1;
6656
+ } catch (error) {
6657
+ if (!ignoreDuplicates) throw error;
6658
+ }
6659
+ return inserted;
6660
+ }
6661
+ async executeUpdateRow(where, values) {
6662
+ const adapter = this.requireAdapter();
6663
+ const spec = this.tryBuildUpdateSpec(where, values);
6664
+ if (!spec) throw new UnsupportedAdapterFeatureException("Update could not be compiled into an Arkorm update specification.", {
6665
+ operation: "query.update",
6666
+ model: this.model.name
6667
+ });
6668
+ const updated = await adapter.update(spec);
6669
+ if (!updated) throw new ModelNotFoundException(this.model.name, "Record not found for update operation.", { operation: "update" });
6670
+ return updated;
6671
+ }
6672
+ async executeUpdateManyRows(where, values) {
6673
+ const adapter = this.requireAdapter();
6674
+ const spec = this.tryBuildUpdateManySpec(where, values);
6675
+ if (!spec) throw new UnsupportedAdapterFeatureException("Update-many could not be compiled into an Arkorm update specification.", {
6676
+ operation: "query.updateMany",
6677
+ model: this.model.name
6678
+ });
6679
+ if (typeof adapter.updateMany === "function") return await adapter.updateMany(spec);
6680
+ const rows = await adapter.select({
6681
+ target: spec.target,
6682
+ where: spec.where
6683
+ });
6684
+ let updated = 0;
6685
+ for (const row of rows) {
6686
+ const rowWhere = this.tryBuildQueryCondition(row);
6687
+ if (!rowWhere) continue;
6688
+ if (await adapter.update({
6689
+ target: spec.target,
6690
+ where: rowWhere,
6691
+ values: spec.values
6692
+ })) updated += 1;
6693
+ }
6694
+ return updated;
6695
+ }
6696
+ async executeDeleteRow(where) {
6697
+ const adapter = this.requireAdapter();
6698
+ const spec = this.tryBuildDeleteSpec(where);
6699
+ if (!spec) throw new UnsupportedAdapterFeatureException("Delete could not be compiled into an Arkorm delete specification.", {
6700
+ operation: "query.delete",
6701
+ model: this.model.name
6702
+ });
6703
+ const deleted = await adapter.delete(spec);
6704
+ if (!deleted) throw new ModelNotFoundException(this.model.name, "Record not found for delete operation.", { operation: "delete" });
6705
+ return deleted;
6706
+ }
5512
6707
  /**
5513
6708
  * Builds the where clause for the query, taking into account soft delete
5514
6709
  * settings if applicable.
@@ -5516,24 +6711,19 @@ var QueryBuilder = class QueryBuilder {
5516
6711
  * @returns
5517
6712
  */
5518
6713
  buildWhere() {
6714
+ const baseWhere = this.legacyWhere ?? this.toDelegateWhere(this.queryWhere);
5519
6715
  const softDeleteConfig = this.model.getSoftDeleteConfig();
5520
- if (!softDeleteConfig.enabled) return this.args.where;
5521
- if (this.includeTrashed) return this.args.where;
6716
+ if (!softDeleteConfig.enabled) return baseWhere;
6717
+ if (this.includeTrashed) return baseWhere;
5522
6718
  const softDeleteClause = this.onlyTrashedRecords ? { [softDeleteConfig.column]: { not: null } } : { [softDeleteConfig.column]: null };
5523
- if (!this.args.where) return softDeleteClause;
5524
- return { AND: [this.args.where, softDeleteClause] };
6719
+ if (!baseWhere) return softDeleteClause;
6720
+ return { AND: [baseWhere, softDeleteClause] };
5525
6721
  }
5526
6722
  /**
5527
6723
  * Builds the arguments for the findMany delegate method, including the where clause.
5528
6724
  *
5529
6725
  * @returns
5530
6726
  */
5531
- buildFindArgs() {
5532
- return {
5533
- ...this.args,
5534
- where: this.buildWhere()
5535
- };
5536
- }
5537
6727
  /**
5538
6728
  * Resolves a unique where clause for update and delete operations.
5539
6729
  *
@@ -5542,18 +6732,29 @@ var QueryBuilder = class QueryBuilder {
5542
6732
  */
5543
6733
  async resolveUniqueWhere(where) {
5544
6734
  if (this.isUniqueWhere(where)) return where;
5545
- const row = await this.delegate.findFirst({ where });
6735
+ const condition = this.tryBuildQueryCondition(where);
6736
+ if (!condition) throw new UniqueConstraintResolutionException("Unable to resolve a unique identifier for update/delete operation from the current query shape.", {
6737
+ operation: "resolveUniqueWhere",
6738
+ model: this.model.name,
6739
+ meta: { where }
6740
+ });
6741
+ const row = await this.requireAdapter().selectOne({
6742
+ target: this.buildQueryTarget(),
6743
+ columns: [{ column: this.model.getPrimaryKey() }],
6744
+ where: condition,
6745
+ limit: 1
6746
+ });
5546
6747
  if (!row) throw new ModelNotFoundException(this.model.name, "Record not found for update/delete operation.", {
5547
6748
  operation: "resolveUniqueWhere",
5548
6749
  meta: { where }
5549
6750
  });
5550
- const record = row;
5551
- if (!Object.prototype.hasOwnProperty.call(record, "id")) throw new UniqueConstraintResolutionException("Unable to resolve a unique identifier for update/delete operation. Include an id in the query constraints.", {
6751
+ const primaryKey = this.model.getPrimaryKey();
6752
+ if (!Object.prototype.hasOwnProperty.call(row, primaryKey)) throw new UniqueConstraintResolutionException(`Unable to resolve a unique identifier for update/delete operation. Include [${primaryKey}] in the query constraints.`, {
5552
6753
  operation: "resolveUniqueWhere",
5553
6754
  model: this.model.name,
5554
6755
  meta: { where }
5555
6756
  });
5556
- return { id: record.id };
6757
+ return { [primaryKey]: row[primaryKey] };
5557
6758
  }
5558
6759
  /**
5559
6760
  * Checks if the provided where clause is already a unique
@@ -5563,7 +6764,8 @@ var QueryBuilder = class QueryBuilder {
5563
6764
  * @returns
5564
6765
  */
5565
6766
  isUniqueWhere(where) {
5566
- return Object.keys(where).length === 1 && Object.prototype.hasOwnProperty.call(where, "id");
6767
+ const primaryKey = this.model.getPrimaryKey();
6768
+ return Object.keys(where).length === 1 && Object.prototype.hasOwnProperty.call(where, primaryKey);
5567
6769
  }
5568
6770
  shuffleRows(rows) {
5569
6771
  const shuffled = [...rows];
@@ -5603,7 +6805,7 @@ var QueryBuilder = class QueryBuilder {
5603
6805
  getModelId(model) {
5604
6806
  const readable = model;
5605
6807
  if (typeof readable.getAttribute !== "function") return null;
5606
- const id = readable.getAttribute("id");
6808
+ const id = readable.getAttribute(this.model.getPrimaryKey());
5607
6809
  if (typeof id === "number" || typeof id === "string") return id;
5608
6810
  return null;
5609
6811
  }
@@ -5742,8 +6944,12 @@ var Model = class Model {
5742
6944
  static lifecycleStates = /* @__PURE__ */ new WeakMap();
5743
6945
  static eventsSuppressed = 0;
5744
6946
  static factoryClass;
6947
+ static adapter;
5745
6948
  static client;
5746
6949
  static delegate;
6950
+ static table;
6951
+ static primaryKey = "id";
6952
+ static columns = {};
5747
6953
  static softDeletes = false;
5748
6954
  static deletedAtColumn = "deletedAt";
5749
6955
  static globalScopes = {};
@@ -5787,6 +6993,37 @@ var Model = class Model {
5787
6993
  static setClient(client) {
5788
6994
  this.client = client;
5789
6995
  }
6996
+ static setAdapter(adapter) {
6997
+ this.adapter = adapter;
6998
+ }
6999
+ static getTable() {
7000
+ return this.table || this.delegate || `${str(this.name).camel().plural()}`;
7001
+ }
7002
+ static getPrimaryKey() {
7003
+ return this.primaryKey || "id";
7004
+ }
7005
+ static getColumnMap() {
7006
+ return { ...this.columns };
7007
+ }
7008
+ static getColumnName(attribute) {
7009
+ return this.getColumnMap()[attribute] ?? attribute;
7010
+ }
7011
+ static getModelMetadata() {
7012
+ return {
7013
+ table: this.getTable(),
7014
+ primaryKey: this.getPrimaryKey(),
7015
+ columns: this.getColumnMap(),
7016
+ softDelete: this.getSoftDeleteConfig()
7017
+ };
7018
+ }
7019
+ static getRelationMetadata(name) {
7020
+ const resolver = this.prototype[name];
7021
+ if (typeof resolver !== "function") return null;
7022
+ const instance = new this({});
7023
+ const relation = resolver.call(instance);
7024
+ if (!(relation instanceof Relation)) return null;
7025
+ return relation.getMetadata();
7026
+ }
5790
7027
  static setFactory(factoryClass) {
5791
7028
  this.factoryClass = factoryClass;
5792
7029
  }
@@ -5960,7 +7197,7 @@ var Model = class Model {
5960
7197
  */
5961
7198
  static getDelegate(delegate) {
5962
7199
  ensureArkormConfigLoading();
5963
- const key = delegate || this.delegate || `${str(this.name).camel().plural()}`;
7200
+ const key = delegate || this.delegate || this.getTable();
5964
7201
  const candidates = [
5965
7202
  key,
5966
7203
  `${str(key).camel()}`,
@@ -5983,6 +7220,13 @@ var Model = class Model {
5983
7220
  });
5984
7221
  return resolved;
5985
7222
  }
7223
+ static getAdapter() {
7224
+ ensureArkormConfigLoading();
7225
+ if (this.adapter) return this.adapter;
7226
+ const client = getActiveTransactionClient() ?? this.client ?? getRuntimePrismaClient();
7227
+ if (!client || typeof client !== "object") return void 0;
7228
+ return createPrismaCompatibilityAdapter(client);
7229
+ }
5986
7230
  /**
5987
7231
  * Get a new query builder instance for the model.
5988
7232
  *
@@ -5991,7 +7235,8 @@ var Model = class Model {
5991
7235
  */
5992
7236
  static query() {
5993
7237
  Model.ensureModelBooted(this);
5994
- let builder = new QueryBuilder(this.getDelegate(), this);
7238
+ const modelStatic = this;
7239
+ let builder = new QueryBuilder(modelStatic, modelStatic.getAdapter());
5995
7240
  const modelClass = this;
5996
7241
  if (!Model.areGlobalScopesSuppressed(modelClass)) {
5997
7242
  modelClass.ensureOwnGlobalScopes();
@@ -6142,10 +7387,11 @@ var Model = class Model {
6142
7387
  * @returns
6143
7388
  */
6144
7389
  async save() {
6145
- const identifier = this.getAttribute("id");
7390
+ const constructor = this.constructor;
7391
+ const primaryKey = constructor.getPrimaryKey();
7392
+ const identifier = this.getAttribute(primaryKey);
6146
7393
  const payload = this.getRawAttributes();
6147
7394
  const previousOriginal = this.getOriginal();
6148
- const constructor = this.constructor;
6149
7395
  if (identifier == null) {
6150
7396
  await Model.dispatchEvent(constructor, "saving", this);
6151
7397
  await Model.dispatchEvent(constructor, "creating", this);
@@ -6159,7 +7405,7 @@ var Model = class Model {
6159
7405
  }
6160
7406
  await Model.dispatchEvent(constructor, "saving", this);
6161
7407
  await Model.dispatchEvent(constructor, "updating", this);
6162
- const model = await constructor.query().where({ id: identifier }).update(payload);
7408
+ const model = await constructor.query().where({ [primaryKey]: identifier }).update(payload);
6163
7409
  this.fill(model.getRawAttributes());
6164
7410
  this.syncChanges(previousOriginal);
6165
7411
  this.syncOriginal();
@@ -6184,21 +7430,22 @@ var Model = class Model {
6184
7430
  * @returns
6185
7431
  */
6186
7432
  async delete() {
6187
- const identifier = this.getAttribute("id");
6188
- if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
6189
- const previousOriginal = this.getOriginal();
6190
7433
  const constructor = this.constructor;
7434
+ const primaryKey = constructor.getPrimaryKey();
7435
+ const identifier = this.getAttribute(primaryKey);
7436
+ if (identifier == null) throw new ArkormException(primaryKey === "id" ? "Cannot delete a model without an id." : `Cannot delete a model without a [${primaryKey}] value.`);
7437
+ const previousOriginal = this.getOriginal();
6191
7438
  await Model.dispatchEvent(constructor, "deleting", this);
6192
7439
  const softDeleteConfig = constructor.getSoftDeleteConfig();
6193
7440
  if (softDeleteConfig.enabled) {
6194
- const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
7441
+ const model = await constructor.query().where({ [primaryKey]: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
6195
7442
  this.fill(model.getRawAttributes());
6196
7443
  this.syncChanges(previousOriginal);
6197
7444
  this.syncOriginal();
6198
7445
  await Model.dispatchEvent(constructor, "deleted", this);
6199
7446
  return this;
6200
7447
  }
6201
- const deleted = await constructor.query().where({ id: identifier }).delete();
7448
+ const deleted = await constructor.query().where({ [primaryKey]: identifier }).delete();
6202
7449
  this.fill(deleted.getRawAttributes());
6203
7450
  this.syncChanges(previousOriginal);
6204
7451
  this.syncOriginal();
@@ -6220,13 +7467,14 @@ var Model = class Model {
6220
7467
  * @returns
6221
7468
  */
6222
7469
  async forceDelete() {
6223
- const identifier = this.getAttribute("id");
6224
- if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
6225
- const previousOriginal = this.getOriginal();
6226
7470
  const constructor = this.constructor;
7471
+ const primaryKey = constructor.getPrimaryKey();
7472
+ const identifier = this.getAttribute(primaryKey);
7473
+ if (identifier == null) throw new ArkormException(primaryKey === "id" ? "Cannot force delete a model without an id." : `Cannot force delete a model without a [${primaryKey}] value.`);
7474
+ const previousOriginal = this.getOriginal();
6227
7475
  await Model.dispatchEvent(constructor, "forceDeleting", this);
6228
7476
  await Model.dispatchEvent(constructor, "deleting", this);
6229
- const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
7477
+ const deleted = await constructor.query().withTrashed().where({ [primaryKey]: identifier }).delete();
6230
7478
  this.fill(deleted.getRawAttributes());
6231
7479
  this.syncChanges(previousOriginal);
6232
7480
  this.syncOriginal();
@@ -6248,14 +7496,15 @@ var Model = class Model {
6248
7496
  * @returns
6249
7497
  */
6250
7498
  async restore() {
6251
- const identifier = this.getAttribute("id");
6252
- if (identifier == null) throw new ArkormException("Cannot restore a model without an id.");
6253
7499
  const constructor = this.constructor;
7500
+ const primaryKey = constructor.getPrimaryKey();
7501
+ const identifier = this.getAttribute(primaryKey);
7502
+ if (identifier == null) throw new ArkormException(primaryKey === "id" ? "Cannot restore a model without an id." : `Cannot restore a model without a [${primaryKey}] value.`);
6254
7503
  const softDeleteConfig = constructor.getSoftDeleteConfig();
6255
7504
  if (!softDeleteConfig.enabled) return this;
6256
7505
  const previousOriginal = this.getOriginal();
6257
7506
  await Model.dispatchEvent(constructor, "restoring", this);
6258
- const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
7507
+ const model = await constructor.query().withTrashed().where({ [primaryKey]: identifier }).update({ [softDeleteConfig.column]: null });
6259
7508
  this.fill(model.getRawAttributes());
6260
7509
  this.syncChanges(previousOriginal);
6261
7510
  this.syncOriginal();
@@ -6367,8 +7616,9 @@ var Model = class Model {
6367
7616
  is(model) {
6368
7617
  if (!(model instanceof Model)) return false;
6369
7618
  if (this.constructor !== model.constructor) return false;
6370
- const identifier = this.getAttribute("id");
6371
- const otherIdentifier = model.getAttribute("id");
7619
+ const primaryKey = this.constructor.getPrimaryKey();
7620
+ const identifier = this.getAttribute(primaryKey);
7621
+ const otherIdentifier = model.getAttribute(primaryKey);
6372
7622
  if (identifier == null || otherIdentifier == null) return false;
6373
7623
  return identifier === otherIdentifier;
6374
7624
  }
@@ -6407,8 +7657,9 @@ var Model = class Model {
6407
7657
  * @param localKey
6408
7658
  * @returns
6409
7659
  */
6410
- hasOne(related, foreignKey, localKey = "id") {
6411
- return new HasOneRelation(this, related, foreignKey, localKey);
7660
+ hasOne(related, foreignKey, localKey) {
7661
+ const constructor = this.constructor;
7662
+ return new HasOneRelation(this, related, foreignKey, localKey ?? constructor.getPrimaryKey());
6412
7663
  }
6413
7664
  /**
6414
7665
  * Define a has many relationship.
@@ -6418,8 +7669,9 @@ var Model = class Model {
6418
7669
  * @param localKey
6419
7670
  * @returns
6420
7671
  */
6421
- hasMany(related, foreignKey, localKey = "id") {
6422
- return new HasManyRelation(this, related, foreignKey, localKey);
7672
+ hasMany(related, foreignKey, localKey) {
7673
+ const constructor = this.constructor;
7674
+ return new HasManyRelation(this, related, foreignKey, localKey ?? constructor.getPrimaryKey());
6423
7675
  }
6424
7676
  /**
6425
7677
  * Define a belongs to relationship.
@@ -6429,8 +7681,8 @@ var Model = class Model {
6429
7681
  * @param ownerKey
6430
7682
  * @returns
6431
7683
  */
6432
- belongsTo(related, foreignKey, ownerKey = "id") {
6433
- return new BelongsToRelation(this, related, foreignKey, ownerKey);
7684
+ belongsTo(related, foreignKey, ownerKey) {
7685
+ return new BelongsToRelation(this, related, foreignKey, ownerKey ?? related.getPrimaryKey());
6434
7686
  }
6435
7687
  /**
6436
7688
  * Define a belongs to many relationship.
@@ -6443,8 +7695,9 @@ var Model = class Model {
6443
7695
  * @param relatedKey
6444
7696
  * @returns
6445
7697
  */
6446
- belongsToMany(related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey = "id", relatedKey = "id") {
6447
- return new BelongsToManyRelation(this, related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey, relatedKey);
7698
+ belongsToMany(related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
7699
+ const constructor = this.constructor;
7700
+ return new BelongsToManyRelation(this, related, throughDelegate, foreignPivotKey, relatedPivotKey, parentKey ?? constructor.getPrimaryKey(), relatedKey ?? related.getPrimaryKey());
6448
7701
  }
6449
7702
  /**
6450
7703
  * Define a has one through relationship.
@@ -6457,8 +7710,9 @@ var Model = class Model {
6457
7710
  * @param secondLocalKey
6458
7711
  * @returns
6459
7712
  */
6460
- hasOneThrough(related, throughDelegate, firstKey, secondKey, localKey = "id", secondLocalKey = "id") {
6461
- return new HasOneThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey);
7713
+ hasOneThrough(related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey = "id") {
7714
+ const constructor = this.constructor;
7715
+ return new HasOneThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey ?? constructor.getPrimaryKey(), secondLocalKey);
6462
7716
  }
6463
7717
  /**
6464
7718
  * Define a has many through relationship.
@@ -6471,8 +7725,9 @@ var Model = class Model {
6471
7725
  * @param secondLocalKey
6472
7726
  * @returns
6473
7727
  */
6474
- hasManyThrough(related, throughDelegate, firstKey, secondKey, localKey = "id", secondLocalKey = "id") {
6475
- return new HasManyThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey);
7728
+ hasManyThrough(related, throughDelegate, firstKey, secondKey, localKey, secondLocalKey = "id") {
7729
+ const constructor = this.constructor;
7730
+ return new HasManyThroughRelation(this, related, throughDelegate, firstKey, secondKey, localKey ?? constructor.getPrimaryKey(), secondLocalKey);
6476
7731
  }
6477
7732
  /**
6478
7733
  * Define a polymorphic one to one relationship.
@@ -6482,8 +7737,9 @@ var Model = class Model {
6482
7737
  * @param localKey
6483
7738
  * @returns
6484
7739
  */
6485
- morphOne(related, morphName, localKey = "id") {
6486
- return new MorphOneRelation(this, related, morphName, localKey);
7740
+ morphOne(related, morphName, localKey) {
7741
+ const constructor = this.constructor;
7742
+ return new MorphOneRelation(this, related, morphName, localKey ?? constructor.getPrimaryKey());
6487
7743
  }
6488
7744
  /**
6489
7745
  * Define a polymorphic one to many relationship.
@@ -6493,8 +7749,9 @@ var Model = class Model {
6493
7749
  * @param localKey
6494
7750
  * @returns
6495
7751
  */
6496
- morphMany(related, morphName, localKey = "id") {
6497
- return new MorphManyRelation(this, related, morphName, localKey);
7752
+ morphMany(related, morphName, localKey) {
7753
+ const constructor = this.constructor;
7754
+ return new MorphManyRelation(this, related, morphName, localKey ?? constructor.getPrimaryKey());
6498
7755
  }
6499
7756
  /**
6500
7757
  * Define a polymorphic many to many relationship.
@@ -6507,8 +7764,9 @@ var Model = class Model {
6507
7764
  * @param relatedKey
6508
7765
  * @returns
6509
7766
  */
6510
- morphToMany(related, throughDelegate, morphName, relatedPivotKey, parentKey = "id", relatedKey = "id") {
6511
- return new MorphToManyRelation(this, related, throughDelegate, morphName, relatedPivotKey, parentKey, relatedKey);
7767
+ morphToMany(related, throughDelegate, morphName, relatedPivotKey, parentKey, relatedKey) {
7768
+ const constructor = this.constructor;
7769
+ return new MorphToManyRelation(this, related, throughDelegate, morphName, relatedPivotKey, parentKey ?? constructor.getPrimaryKey(), relatedKey ?? related.getPrimaryKey());
6512
7770
  }
6513
7771
  /**
6514
7772
  * Resolve a get mutator method for a given attribute key, if it exists.
@@ -6758,4 +8016,4 @@ var Model = class Model {
6758
8016
  };
6759
8017
 
6760
8018
  //#endregion
6761
- export { ArkormCollection, ArkormException, Attribute, CliApp, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, QueryConstraintException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
8019
+ export { ArkormCollection, ArkormException, Attribute, CliApp, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, KyselyDatabaseAdapter, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, PrismaDatabaseAdapter, QueryBuilder, QueryConstraintException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createKyselyAdapter, createMigrationTimestamp, createPrismaAdapter, createPrismaCompatibilityAdapter, createPrismaDatabaseAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };