arkormx 2.4.5 → 2.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_relationship = require('./relationship-Cku0y1Mt.cjs');
2
+ const require_relationship = require('./relationship-CuPNAoHm.cjs');
3
3
  let pg = require("pg");
4
4
  let node_path = require("node:path");
5
5
  let module$1 = require("module");
@@ -10,6 +10,7 @@ let _h3ravel_support = require("@h3ravel/support");
10
10
  let kysely = require("kysely");
11
11
  let _h3ravel_shared = require("@h3ravel/shared");
12
12
  let _h3ravel_musket = require("@h3ravel/musket");
13
+ let node_async_hooks = require("node:async_hooks");
13
14
 
14
15
  //#region src/Exceptions/QueryExecutionException.ts
15
16
  var QueryExecutionException = class extends require_relationship.ArkormException {
@@ -188,6 +189,17 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
188
189
  const mappedColumns = index.columns.map((column) => this.quoteIdentifier(this.resolveSchemaColumnName(column, columns))).join(", ");
189
190
  return `create index if not exists ${this.quoteIdentifier(this.resolveSchemaIndexName(table, index))} on ${this.quoteIdentifier(table)} (${mappedColumns})`;
190
191
  }
192
+ buildSchemaPrimaryKeyConstraint(table, primaryKey, columns = []) {
193
+ const name = primaryKey.name?.trim() || `${table}_pkey`;
194
+ const mappedColumns = primaryKey.columns.map((column) => this.quoteIdentifier(this.resolveSchemaColumnName(column, columns))).join(", ");
195
+ return `constraint ${this.quoteIdentifier(name)} primary key (${mappedColumns})`;
196
+ }
197
+ buildSchemaUniqueConstraint(table, constraint, columns = []) {
198
+ const mappedColumnNames = constraint.columns.map((column) => this.resolveSchemaColumnName(column, columns));
199
+ const name = constraint.name?.trim() || `${table}_${mappedColumnNames.join("_")}_key`;
200
+ const mappedColumns = mappedColumnNames.map((column) => this.quoteIdentifier(column)).join(", ");
201
+ return `constraint ${this.quoteIdentifier(name)} unique (${mappedColumns})`;
202
+ }
191
203
  async ensureEnumTypes(table, columns, executor = this.db) {
192
204
  for (const column of columns) {
193
205
  if (column.type !== "enum") continue;
@@ -208,8 +220,15 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
208
220
  const table = this.resolveMappedTable(operation.table);
209
221
  await this.ensureEnumTypes(table, operation.columns, executor);
210
222
  const columnDefinitions = operation.columns.map((column) => this.buildSchemaColumnDefinition(table, column));
223
+ const primaryKeys = operation.primaryKey ? [this.buildSchemaPrimaryKeyConstraint(table, operation.primaryKey, operation.columns)] : [];
224
+ const uniqueConstraints = (operation.uniqueConstraints ?? []).map((constraint) => this.buildSchemaUniqueConstraint(table, constraint, operation.columns));
211
225
  const foreignKeys = (operation.foreignKeys ?? []).map((foreignKey) => this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.columns));
212
- const definitions = [...columnDefinitions, ...foreignKeys].join(", ");
226
+ const definitions = [
227
+ ...columnDefinitions,
228
+ ...primaryKeys,
229
+ ...uniqueConstraints,
230
+ ...foreignKeys
231
+ ].join(", ");
213
232
  await this.executeRawStatement(`create table if not exists ${this.quoteIdentifier(table)} (${definitions})`, executor);
214
233
  for (const index of operation.indexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.columns), executor);
215
234
  }
@@ -219,6 +238,8 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
219
238
  for (const column of operation.addColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add column if not exists ${this.buildSchemaColumnDefinition(table, column)}`, executor);
220
239
  for (const column of operation.dropColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} drop column if exists ${this.quoteIdentifier(column)}`, executor);
221
240
  for (const foreignKey of operation.addForeignKeys ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.addColumns)}`, executor);
241
+ if (operation.addPrimaryKey) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaPrimaryKeyConstraint(table, operation.addPrimaryKey, operation.addColumns)}`, executor);
242
+ for (const constraint of operation.addUniqueConstraints ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaUniqueConstraint(table, constraint, operation.addColumns)}`, executor);
222
243
  for (const index of operation.addIndexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.addColumns), executor);
223
244
  }
224
245
  async executeDropTableOperation(operation, executor) {
@@ -3529,6 +3550,9 @@ var Seeder = class Seeder {
3529
3550
  static {
3530
3551
  this[SEEDER_BRAND] = true;
3531
3552
  }
3553
+ static {
3554
+ this.executionReport = new node_async_hooks.AsyncLocalStorage();
3555
+ }
3532
3556
  /**
3533
3557
  * Runs one or more seeders.
3534
3558
  *
@@ -3538,6 +3562,19 @@ var Seeder = class Seeder {
3538
3562
  await Seeder.runSeeders(...seeders);
3539
3563
  }
3540
3564
  /**
3565
+ * Run seeders and return every seeder class executed, including nested calls.
3566
+ *
3567
+ * @param seeders
3568
+ * @returns
3569
+ */
3570
+ static async runWithReport(...seeders) {
3571
+ const report = [];
3572
+ await this.executionReport.run(report, async () => {
3573
+ await this.runSeeders(...seeders);
3574
+ });
3575
+ return report;
3576
+ }
3577
+ /**
3541
3578
  * Converts a SeederInput into a Seeder instance.
3542
3579
  *
3543
3580
  * @param input The SeederInput to convert.
@@ -3561,7 +3598,11 @@ var Seeder = class Seeder {
3561
3598
  all.push(current);
3562
3599
  return all;
3563
3600
  }, []);
3564
- for (const seeder of queue) await this.toSeederInstance(seeder).run();
3601
+ for (const seeder of queue) {
3602
+ const instance = this.toSeederInstance(seeder);
3603
+ this.executionReport.getStore()?.push(instance.constructor.name);
3604
+ await instance.run();
3605
+ }
3565
3606
  }
3566
3607
  };
3567
3608
 
@@ -3594,9 +3635,10 @@ var SeedCommand = class extends _h3ravel_musket.Command {
3594
3635
  if (seederDirs.length === 0 && require_relationship.getRegisteredSeeders().length === 0) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
3595
3636
  const classes = this.option("all") ? await this.loadAllSeeders(seederDirs) : await this.loadNamedSeeder(seederDirs, this.argument("name") ?? "DatabaseSeeder");
3596
3637
  if (classes.length === 0) return void this.error("ERROR: No seeder classes found to run.");
3597
- for (const SeederClassItem of classes) await new SeederClassItem().run();
3638
+ const executedSeeders = [];
3639
+ for (const SeederClassItem of classes) executedSeeders.push(...await Seeder.runWithReport(new SeederClassItem()));
3598
3640
  this.success("Database seeding completed");
3599
- classes.forEach((cls) => this.success(this.app.splitLogger("Seeded", cls.name)));
3641
+ executedSeeders.forEach((name) => this.success(this.app.splitLogger("Seeded", name)));
3600
3642
  }
3601
3643
  /**
3602
3644
  * Load all seeder classes from the specified directory.
@@ -3665,191 +3707,42 @@ var logo_default = String.raw`
3665
3707
  `;
3666
3708
 
3667
3709
  //#endregion
3668
- //#region src/database/factories.ts
3669
- /**
3670
- * Base class for defining model factories.
3671
- * Not meant to be used directly.
3672
- *
3673
- * @template TModel The type of model the factory creates.
3674
- * @template TAttributes The type of attributes used to create the model.
3675
- * @author Legacy (3m1n3nc3)
3676
- * @since 0.1.0
3677
- */
3678
- var ModelFactory = class ModelFactory {
3679
- constructor() {
3680
- this.amount = 1;
3681
- this.sequence = 0;
3682
- this.states = [];
3683
- }
3684
- /**
3685
- * Set the number of models to create.
3686
- *
3687
- * @param amount
3688
- * @returns
3689
- */
3690
- count(amount) {
3691
- this.amount = Math.max(1, Math.floor(amount));
3692
- return this;
3693
- }
3694
- /**
3695
- * Define a state transformation for the factory.
3696
- * States are applied in the order they were defined.
3697
- *
3698
- * @param resolver A function that takes the current attributes and sequence number, and returns the transformed attributes.
3699
- * @returns The factory instance for chaining.
3700
- */
3701
- state(resolver) {
3702
- this.states.push(resolver);
3703
- return this;
3704
- }
3705
- /**
3706
- * Create a new model instance without saving it to the database.
3707
- *
3708
- * @param overrides
3709
- * @returns
3710
- */
3711
- make(overrides = {}) {
3712
- const attributes = this.buildAttributes(overrides);
3713
- return new this.model(attributes);
3714
- }
3715
- /**
3716
- * Create a new model instance from an async factory definition without
3717
- * saving it to the database.
3718
- *
3719
- * @param overrides
3720
- * @returns
3721
- */
3722
- async makeAsync(overrides = {}) {
3723
- const attributes = await this.buildAttributesAsync(overrides);
3724
- return new this.model(attributes);
3725
- }
3726
- /**
3727
- * Create multiple model instances without saving them to the database.
3728
- *
3729
- * @param amount
3730
- * @param overrides
3731
- * @returns
3732
- */
3733
- makeMany(amount = this.amount, overrides = {}) {
3734
- const total = Math.max(1, Math.floor(amount));
3735
- return Array.from({ length: total }, () => this.make(overrides));
3736
- }
3737
- /**
3738
- * Create multiple model instances from async factory definitions without
3739
- * saving them to the database.
3740
- *
3741
- * @param amount
3742
- * @param overrides
3743
- * @returns
3744
- */
3745
- async makeManyAsync(amount = this.amount, overrides = {}) {
3746
- const total = Math.max(1, Math.floor(amount));
3747
- const models = [];
3748
- for (let index = 0; index < total; index++) models.push(await this.makeAsync(overrides));
3749
- return models;
3750
- }
3751
- /**
3752
- * Create a new model instance and save it to the database.
3753
- *
3754
- * @param overrides
3755
- * @returns
3756
- */
3757
- async create(overrides = {}) {
3758
- const model = await this.makeAsync(overrides);
3759
- if (typeof model.save !== "function") throw new Error("Factory model does not support save().");
3760
- return await model.save();
3761
- }
3762
- /**
3763
- * Create multiple model instances and save them to the database.
3764
- *
3765
- * @param amount
3766
- * @param overrides
3767
- * @returns
3768
- */
3769
- async createMany(amount = this.amount, overrides = {}) {
3770
- const models = await this.makeManyAsync(amount, overrides);
3771
- return await Promise.all(models.map(async (model) => {
3772
- if (typeof model.save !== "function") throw new Error("Factory model does not support save().");
3773
- return await model.save();
3774
- }));
3775
- }
3776
- /**
3777
- * Build the attributes for a model instance, applying the factory
3778
- * definition and any defined states, and merging in any overrides.
3779
- *
3780
- * @param overrides
3781
- * @returns
3782
- */
3783
- buildAttributes(overrides) {
3784
- const sequence = this.sequence;
3785
- this.sequence += 1;
3786
- let resolved = this.definition(sequence);
3787
- if (ModelFactory.isPromiseLike(resolved)) {
3788
- this.sequence = sequence;
3789
- throw new Error("This factory has an async definition. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
3790
- }
3791
- for (const state of this.states) {
3792
- resolved = state(resolved, sequence);
3793
- if (ModelFactory.isPromiseLike(resolved)) {
3794
- this.sequence = sequence;
3795
- throw new Error("This factory has an async state. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
3796
- }
3797
- }
3798
- return {
3799
- ...resolved,
3800
- ...overrides
3801
- };
3802
- }
3803
- /**
3804
- * Build attributes for async and sync factory definitions.
3805
- *
3806
- * @param overrides
3807
- * @returns
3808
- */
3809
- async buildAttributesAsync(overrides) {
3810
- const sequence = this.sequence;
3811
- this.sequence += 1;
3812
- let resolved = await this.definition(sequence);
3813
- for (const state of this.states) resolved = await state(resolved, sequence);
3814
- return {
3815
- ...resolved,
3816
- ...overrides
3817
- };
3818
- }
3819
- static isPromiseLike(value) {
3820
- return typeof value?.then === "function";
3821
- }
3710
+ //#region src/helpers/runtime-compatibility.ts
3711
+ const isObjectLike = (value) => {
3712
+ return Boolean(value) && typeof value === "object";
3822
3713
  };
3823
- /**
3824
- * A helper class for defining factories using an inline definition
3825
- * function, without needing to create a separate factory class.
3826
- *
3827
- * @template TModel
3828
- * @template TAttributes
3829
- * @author Legacy (3m1n3nc3)
3830
- * @since 0.1.0
3831
- */
3832
- var InlineFactory = class extends ModelFactory {
3833
- constructor(model, resolver) {
3834
- super();
3835
- this.resolver = resolver;
3836
- this.model = model;
3837
- }
3838
- definition(sequence) {
3839
- return this.resolver(sequence);
3840
- }
3714
+ const isCompatibilityClient = (value) => {
3715
+ return Boolean(value) && typeof value === "object";
3841
3716
  };
3842
- /**
3843
- * Define a factory for a given model using an inline definition function.
3844
- *
3845
- * @template TModel The type of model the factory creates.
3846
- * @template TAttributes The type of attributes used to create the model.
3847
- * @param model The model constructor.
3848
- * @param definition The factory definition function.
3849
- * @returns A new instance of the model factory.
3850
- */
3851
- const defineFactory = (model, definition) => {
3852
- return new InlineFactory(model, definition);
3717
+ const getCompatibilitySources = (preferredClient) => {
3718
+ const activeTransactionClient = require_relationship.getActiveTransactionClient();
3719
+ const runtimeClient = require_relationship.getRuntimeClient();
3720
+ return activeTransactionClient ? [
3721
+ activeTransactionClient,
3722
+ preferredClient,
3723
+ runtimeClient
3724
+ ] : [preferredClient, runtimeClient];
3725
+ };
3726
+ const getRuntimeCompatibilityAdapter = (preferredClient) => {
3727
+ const client = getCompatibilitySources(preferredClient).find((source) => isCompatibilityClient(source));
3728
+ if (!client) return void 0;
3729
+ return createPrismaCompatibilityAdapter(client);
3730
+ };
3731
+ const resolveRuntimeCompatibilityQuerySchema = (candidates, preferredClient) => {
3732
+ return getCompatibilitySources(preferredClient).flatMap((source) => {
3733
+ if (!isObjectLike(source)) return [];
3734
+ return candidates.map((candidate) => source[candidate]);
3735
+ }).find((candidate) => require_relationship.isQuerySchemaLike(candidate));
3736
+ };
3737
+ const resolveRuntimeCompatibilityQuerySchemaOrThrow = (key, candidates, modelName, preferredClient) => {
3738
+ const resolved = resolveRuntimeCompatibilityQuerySchema(candidates, preferredClient);
3739
+ if (!resolved) throw new MissingDelegateException(`Database delegate [${key}] is not configured.`, {
3740
+ operation: "getDelegate",
3741
+ model: modelName,
3742
+ delegate: key,
3743
+ meta: { candidates }
3744
+ });
3745
+ return resolved;
3853
3746
  };
3854
3747
 
3855
3748
  //#endregion
@@ -6325,164 +6218,6 @@ var QueryBuilder = class QueryBuilder {
6325
6218
  }
6326
6219
  };
6327
6220
 
6328
- //#endregion
6329
- //#region src/helpers/runtime-compatibility.ts
6330
- const isObjectLike = (value) => {
6331
- return Boolean(value) && typeof value === "object";
6332
- };
6333
- const isCompatibilityClient = (value) => {
6334
- return Boolean(value) && typeof value === "object";
6335
- };
6336
- const getCompatibilitySources = (preferredClient) => {
6337
- const activeTransactionClient = require_relationship.getActiveTransactionClient();
6338
- const runtimeClient = require_relationship.getRuntimeClient();
6339
- return activeTransactionClient ? [
6340
- activeTransactionClient,
6341
- preferredClient,
6342
- runtimeClient
6343
- ] : [preferredClient, runtimeClient];
6344
- };
6345
- const getRuntimeCompatibilityAdapter = (preferredClient) => {
6346
- const client = getCompatibilitySources(preferredClient).find((source) => isCompatibilityClient(source));
6347
- if (!client) return void 0;
6348
- return createPrismaCompatibilityAdapter(client);
6349
- };
6350
- const resolveRuntimeCompatibilityQuerySchema = (candidates, preferredClient) => {
6351
- return getCompatibilitySources(preferredClient).flatMap((source) => {
6352
- if (!isObjectLike(source)) return [];
6353
- return candidates.map((candidate) => source[candidate]);
6354
- }).find((candidate) => require_relationship.isQuerySchemaLike(candidate));
6355
- };
6356
- const resolveRuntimeCompatibilityQuerySchemaOrThrow = (key, candidates, modelName, preferredClient) => {
6357
- const resolved = resolveRuntimeCompatibilityQuerySchema(candidates, preferredClient);
6358
- if (!resolved) throw new MissingDelegateException(`Database delegate [${key}] is not configured.`, {
6359
- operation: "getDelegate",
6360
- model: modelName,
6361
- delegate: key,
6362
- meta: { candidates }
6363
- });
6364
- return resolved;
6365
- };
6366
-
6367
- //#endregion
6368
- //#region src/DB.ts
6369
- const defaultSoftDeleteConfig = {
6370
- enabled: false,
6371
- column: "deletedAt"
6372
- };
6373
- var DB = class DB {
6374
- constructor(adapter) {
6375
- this.scopedAdapter = adapter;
6376
- }
6377
- static setAdapter(adapter) {
6378
- this.adapter = adapter;
6379
- }
6380
- static getAdapter() {
6381
- const runtimeAdapter = require_relationship.getRuntimeAdapter();
6382
- if (runtimeAdapter) return runtimeAdapter;
6383
- if (this.adapter) return this.adapter;
6384
- return getRuntimeCompatibilityAdapter();
6385
- }
6386
- getAdapter() {
6387
- return this.scopedAdapter ?? DB.getAdapter();
6388
- }
6389
- static table(table, options = {}) {
6390
- return new DB().table(table, options);
6391
- }
6392
- table(table, options = {}) {
6393
- return DB.createTableModel(table, options, this.getAdapter()).query();
6394
- }
6395
- static async raw(sql, bindings = []) {
6396
- return await new DB().raw(sql, bindings);
6397
- }
6398
- async raw(sql, bindings = []) {
6399
- const adapter = this.getAdapter();
6400
- if (!adapter) throw new require_relationship.ArkormException("Raw queries require a configured database adapter.", {
6401
- code: "ADAPTER_NOT_CONFIGURED",
6402
- operation: "db.raw"
6403
- });
6404
- if (!adapter.rawQuery) throw new require_relationship.UnsupportedAdapterFeatureException("Raw queries are not supported by the current adapter.", {
6405
- operation: "db.raw",
6406
- meta: { feature: "rawQuery" }
6407
- });
6408
- const rows = await adapter.rawQuery({
6409
- sql,
6410
- bindings
6411
- });
6412
- return require_relationship.ArkormCollection.make(rows);
6413
- }
6414
- static async transaction(callback, context) {
6415
- return await new DB().transaction(callback, context);
6416
- }
6417
- async transaction(callback, context) {
6418
- const adapter = this.getAdapter();
6419
- if (!adapter) throw new require_relationship.ArkormException("DB transactions require a configured database adapter.", {
6420
- code: "ADAPTER_NOT_CONFIGURED",
6421
- operation: "db.transaction"
6422
- });
6423
- return await adapter.transaction(async (transactionAdapter) => {
6424
- return await callback(new DB(transactionAdapter));
6425
- }, context);
6426
- }
6427
- static createTableModel(table, options, adapter) {
6428
- const primaryKey = options.primaryKey ?? "id";
6429
- const resolvedAdapter = options.adapter ?? adapter ?? DB.getAdapter();
6430
- const persistedMetadata = DB.resolvePersistedTableMetadata(table, options, resolvedAdapter);
6431
- const columns = {
6432
- ...persistedMetadata.columns,
6433
- ...options.columns ?? {}
6434
- };
6435
- const softDelete = options.softDelete ?? defaultSoftDeleteConfig;
6436
- const primaryKeyGeneration = options.primaryKeyGeneration ? { ...options.primaryKeyGeneration } : persistedMetadata.primaryKeyGeneration?.column === primaryKey ? {
6437
- strategy: persistedMetadata.primaryKeyGeneration.strategy,
6438
- prismaDefault: persistedMetadata.primaryKeyGeneration.prismaDefault,
6439
- databaseDefault: persistedMetadata.primaryKeyGeneration.databaseDefault,
6440
- runtimeFactory: persistedMetadata.primaryKeyGeneration.runtimeFactory
6441
- } : void 0;
6442
- const timestampColumns = options.timestampColumns?.map((column) => ({ ...column })) ?? persistedMetadata.timestampColumns?.map((column) => ({ ...column }));
6443
- const buildMetadata = () => {
6444
- return {
6445
- table,
6446
- primaryKey,
6447
- columns: { ...columns },
6448
- softDelete: { ...softDelete },
6449
- primaryKeyGeneration,
6450
- timestampColumns
6451
- };
6452
- };
6453
- const modelStatic = {
6454
- query: () => new QueryBuilder(modelStatic, modelStatic.getAdapter()),
6455
- hydrate: (attributes) => ({ ...attributes }),
6456
- hydrateMany: (attributes) => attributes.map((attribute) => ({ ...attribute })),
6457
- hydrateRetrieved: async (attributes) => ({ ...attributes }),
6458
- hydrateManyRetrieved: async (attributes) => attributes.map((attribute) => ({ ...attribute })),
6459
- getAdapter: () => resolvedAdapter,
6460
- getColumnMap: () => ({ ...columns }),
6461
- getColumnName: (attribute) => columns[attribute] ?? attribute,
6462
- getModelMetadata: () => buildMetadata(),
6463
- getPrimaryKey: () => primaryKey,
6464
- getRelationMetadata: () => null,
6465
- setAdapter: () => {},
6466
- getSoftDeleteConfig: () => ({ ...softDelete }),
6467
- getTable: () => table
6468
- };
6469
- return modelStatic;
6470
- }
6471
- static resolvePersistedTableMetadata(table, options, adapter) {
6472
- if (options.persistedMetadata === false) return {
6473
- columns: {},
6474
- enums: {}
6475
- };
6476
- const persistedMetadataOptions = typeof options.persistedMetadata === "object" ? options.persistedMetadata : {};
6477
- return require_relationship.getPersistedTableMetadata(table, {
6478
- cwd: persistedMetadataOptions.cwd,
6479
- configuredPath: persistedMetadataOptions.configuredPath,
6480
- features: require_relationship.resolvePersistedMetadataFeatures(require_relationship.getUserConfig("features")),
6481
- strict: persistedMetadataOptions.strict ?? (Boolean(adapter) && !(adapter instanceof PrismaDatabaseAdapter))
6482
- });
6483
- }
6484
- };
6485
-
6486
6221
  //#endregion
6487
6222
  //#region src/Model.ts
6488
6223
  /**
@@ -7725,6 +7460,552 @@ var Model = class Model {
7725
7460
  }
7726
7461
  };
7727
7462
 
7463
+ //#endregion
7464
+ //#region src/database/factories.ts
7465
+ /**
7466
+ * Base class for defining model factories.
7467
+ * Not meant to be used directly.
7468
+ *
7469
+ * @template TModel The type of model the factory creates.
7470
+ * @template TAttributes The type of attributes used to create the model.
7471
+ * @author Legacy (3m1n3nc3)
7472
+ * @since 0.1.0
7473
+ */
7474
+ var ModelFactory = class ModelFactory {
7475
+ constructor() {
7476
+ this.amount = 1;
7477
+ this.sequence = 0;
7478
+ this.states = [];
7479
+ this.configured = false;
7480
+ this.afterMakingCallbacks = [];
7481
+ this.afterCreatingCallbacks = [];
7482
+ this.hasRelations = [];
7483
+ this.forRelations = [];
7484
+ this.attachedRelations = [];
7485
+ this.recyclePool = /* @__PURE__ */ new Map();
7486
+ this.recycleOffsets = /* @__PURE__ */ new Map();
7487
+ }
7488
+ /**
7489
+ * Configure states and lifecycle callbacks for each new factory instance.
7490
+ */
7491
+ configure() {}
7492
+ /**
7493
+ * Set the number of models to create.
7494
+ *
7495
+ * @param amount
7496
+ * @returns
7497
+ */
7498
+ count(amount) {
7499
+ this.ensureConfigured();
7500
+ this.amount = Math.max(1, Math.floor(amount));
7501
+ return this;
7502
+ }
7503
+ /**
7504
+ * Define a state transformation for the factory.
7505
+ * States are applied in the order they were defined.
7506
+ *
7507
+ * @param resolver A function that takes the current attributes and sequence number, and returns the transformed attributes.
7508
+ * @returns The factory instance for chaining.
7509
+ */
7510
+ state(resolver) {
7511
+ this.ensureConfigured();
7512
+ this.states.push(resolver);
7513
+ return this;
7514
+ }
7515
+ /**
7516
+ * Register a callback that runs after a model is made.
7517
+ *
7518
+ * @param callback
7519
+ * @returns
7520
+ */
7521
+ afterMaking(callback) {
7522
+ this.ensureConfigured();
7523
+ this.afterMakingCallbacks.push(callback);
7524
+ return this;
7525
+ }
7526
+ /**
7527
+ * Register a callback that runs after a model is persisted.
7528
+ *
7529
+ * @param callback
7530
+ * @returns
7531
+ */
7532
+ afterCreating(callback) {
7533
+ this.ensureConfigured();
7534
+ this.afterCreatingCallbacks.push(callback);
7535
+ return this;
7536
+ }
7537
+ /**
7538
+ * Create a new model instance without saving it to the database.
7539
+ *
7540
+ * @param overrides
7541
+ * @returns
7542
+ */
7543
+ make(overrides = {}) {
7544
+ this.ensureConfigured();
7545
+ const attributes = this.buildAttributes(overrides);
7546
+ const model = new this.model(attributes);
7547
+ this.runCallbacksSync(this.afterMakingCallbacks, model, "afterMaking");
7548
+ return model;
7549
+ }
7550
+ /**
7551
+ * Create a new model instance from an async factory definition without
7552
+ * saving it to the database.
7553
+ *
7554
+ * @param overrides
7555
+ * @returns
7556
+ */
7557
+ async makeAsync(overrides = {}) {
7558
+ this.ensureConfigured();
7559
+ const attributes = await this.buildAttributesAsync(overrides);
7560
+ const model = new this.model(attributes);
7561
+ await this.runCallbacks(this.afterMakingCallbacks, model);
7562
+ return model;
7563
+ }
7564
+ /**
7565
+ * Create multiple model instances without saving them to the database.
7566
+ *
7567
+ * @param amount
7568
+ * @param overrides
7569
+ * @returns
7570
+ */
7571
+ makeMany(amount = this.amount, overrides = {}) {
7572
+ const total = Math.max(1, Math.floor(amount));
7573
+ return Array.from({ length: total }, () => this.make(overrides));
7574
+ }
7575
+ /**
7576
+ * Create multiple model instances from async factory definitions without
7577
+ * saving them to the database.
7578
+ *
7579
+ * @param amount
7580
+ * @param overrides
7581
+ * @returns
7582
+ */
7583
+ async makeManyAsync(amount = this.amount, overrides = {}) {
7584
+ const total = Math.max(1, Math.floor(amount));
7585
+ const models = [];
7586
+ for (let index = 0; index < total; index++) models.push(await this.makeAsync(overrides));
7587
+ return models;
7588
+ }
7589
+ /**
7590
+ * Create a new model instance and save it to the database.
7591
+ *
7592
+ * @param overrides
7593
+ * @returns
7594
+ */
7595
+ async create(overrides = {}) {
7596
+ return await this.createPersisted(overrides);
7597
+ }
7598
+ /**
7599
+ * Create multiple model instances and save them to the database.
7600
+ *
7601
+ * @param amount
7602
+ * @param overrides
7603
+ * @returns
7604
+ */
7605
+ async createMany(amount = this.amount, overrides = {}) {
7606
+ this.ensureConfigured();
7607
+ const total = Math.max(1, Math.floor(amount));
7608
+ const models = [];
7609
+ for (let index = 0; index < total; index++) models.push(await this.create(overrides));
7610
+ return models;
7611
+ }
7612
+ /**
7613
+ * Create related models through a has-one or has-many relationship.
7614
+ *
7615
+ * @param factory
7616
+ * @param relationship
7617
+ * @returns
7618
+ */
7619
+ has(factory, relationship) {
7620
+ this.ensureConfigured();
7621
+ this.hasRelations.push({
7622
+ factory,
7623
+ relationship
7624
+ });
7625
+ return this;
7626
+ }
7627
+ /**
7628
+ * Associate the created model with a parent model or factory.
7629
+ *
7630
+ * @param related
7631
+ * @param relationship
7632
+ * @returns
7633
+ */
7634
+ for(related, relationship) {
7635
+ this.ensureConfigured();
7636
+ this.forRelations.push({
7637
+ related,
7638
+ relationship
7639
+ });
7640
+ return this;
7641
+ }
7642
+ /**
7643
+ * Create or reuse related models and attach them through a many-to-many relationship.
7644
+ *
7645
+ * @param related
7646
+ * @param pivot
7647
+ * @param relationship
7648
+ * @returns
7649
+ */
7650
+ hasAttached(related, pivot = {}, relationship) {
7651
+ this.ensureConfigured();
7652
+ this.attachedRelations.push({
7653
+ related,
7654
+ pivot,
7655
+ relationship
7656
+ });
7657
+ return this;
7658
+ }
7659
+ /**
7660
+ * Reuse existing models when resolving factory-backed relationships.
7661
+ *
7662
+ * @param models
7663
+ * @returns
7664
+ */
7665
+ recycle(models) {
7666
+ this.ensureConfigured();
7667
+ (Array.isArray(models) ? models : "all" in models ? models.all() : [models]).forEach((model) => {
7668
+ const constructor = model.constructor;
7669
+ const existing = this.recyclePool.get(constructor) ?? [];
7670
+ if (!existing.includes(model)) existing.push(model);
7671
+ this.recyclePool.set(constructor, existing);
7672
+ });
7673
+ return this;
7674
+ }
7675
+ /**
7676
+ * Get the model contgructor
7677
+ *
7678
+ * @returns
7679
+ */
7680
+ getModelConstructor() {
7681
+ return this.model;
7682
+ }
7683
+ /**
7684
+ * Build the attributes for a model instance, applying the factory
7685
+ * definition and any defined states, and merging in any overrides.
7686
+ *
7687
+ * @param overrides
7688
+ * @returns
7689
+ */
7690
+ buildAttributes(overrides) {
7691
+ const sequence = this.sequence;
7692
+ this.sequence += 1;
7693
+ let resolved = this.definition(sequence);
7694
+ if (ModelFactory.isPromiseLike(resolved)) {
7695
+ this.sequence = sequence;
7696
+ throw new Error("This factory has an async definition. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
7697
+ }
7698
+ for (const state of this.states) {
7699
+ resolved = state(resolved, sequence);
7700
+ if (ModelFactory.isPromiseLike(resolved)) {
7701
+ this.sequence = sequence;
7702
+ throw new Error("This factory has an async state. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
7703
+ }
7704
+ }
7705
+ const attributes = {
7706
+ ...resolved,
7707
+ ...overrides
7708
+ };
7709
+ return this.resolveAttributesSync(this.applyBelongsToRelationshipsSync(attributes));
7710
+ }
7711
+ /**
7712
+ * Build attributes for async and sync factory definitions.
7713
+ *
7714
+ * @param overrides
7715
+ * @returns
7716
+ */
7717
+ async buildAttributesAsync(overrides) {
7718
+ const sequence = this.sequence;
7719
+ this.sequence += 1;
7720
+ let resolved = await this.definition(sequence);
7721
+ for (const state of this.states) resolved = await state(resolved, sequence);
7722
+ const attributes = {
7723
+ ...resolved,
7724
+ ...overrides
7725
+ };
7726
+ return await this.resolveAttributesAsync(await this.applyBelongsToRelationships(attributes));
7727
+ }
7728
+ ensureConfigured() {
7729
+ if (this.configured) return;
7730
+ this.configured = true;
7731
+ this.configure();
7732
+ }
7733
+ resolveAttributesSync(attributes) {
7734
+ const resolved = {};
7735
+ for (const [key, value] of Object.entries(attributes)) {
7736
+ if (ModelFactory.isFactory(value)) throw new Error("This factory definition creates a related model. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
7737
+ const next = typeof value === "function" ? value(resolved) : value;
7738
+ if (ModelFactory.isPromiseLike(next)) throw new Error("This factory has an async attribute resolver. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
7739
+ resolved[key] = next;
7740
+ }
7741
+ return resolved;
7742
+ }
7743
+ async resolveAttributesAsync(attributes) {
7744
+ const resolved = {};
7745
+ for (const [key, value] of Object.entries(attributes)) {
7746
+ if (ModelFactory.isFactory(value)) {
7747
+ resolved[key] = await this.resolveFactoryKey(value);
7748
+ continue;
7749
+ }
7750
+ resolved[key] = typeof value === "function" ? await value(resolved) : value;
7751
+ }
7752
+ return resolved;
7753
+ }
7754
+ applyBelongsToRelationshipsSync(attributes) {
7755
+ return this.forRelations.reduce((resolved, relation) => {
7756
+ if (relation.related instanceof Model) return this.mergeBelongsToAttribute(resolved, relation.related, relation.relationship);
7757
+ throw new Error("This factory creates a parent model. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.");
7758
+ }, attributes);
7759
+ }
7760
+ async applyBelongsToRelationships(attributes) {
7761
+ let resolved = attributes;
7762
+ for (const relation of this.forRelations) {
7763
+ const related = relation.related instanceof Model ? relation.related : await this.resolveFactoryModel(relation.related);
7764
+ resolved = this.mergeBelongsToAttribute(resolved, related, relation.relationship);
7765
+ }
7766
+ return resolved;
7767
+ }
7768
+ mergeBelongsToAttribute(attributes, related, relationship) {
7769
+ const relationName = relationship ?? `${(0, _h3ravel_support.str)(related.constructor.name).camel().singular()}`;
7770
+ const metadata = this.model.getRelationMetadata?.(relationName);
7771
+ if (metadata?.type !== "belongsTo" || !metadata.foreignKey || !metadata.ownerKey) throw new Error(`Factory relationship [${relationName}] is not a belongsTo relation on [${this.model.name}].`);
7772
+ return {
7773
+ ...attributes,
7774
+ [metadata.foreignKey]: related.getAttribute(metadata.ownerKey)
7775
+ };
7776
+ }
7777
+ async createPersisted(overrides, persist) {
7778
+ const model = await this.makeAsync(overrides);
7779
+ const persisted = persist ? await persist(model) : await this.saveModel(model);
7780
+ await this.createHasRelations(persisted);
7781
+ await this.createAttachedRelations(persisted);
7782
+ await this.runCallbacks(this.afterCreatingCallbacks, persisted);
7783
+ return persisted;
7784
+ }
7785
+ async saveModel(model) {
7786
+ const saveable = model;
7787
+ const constructor = model?.constructor;
7788
+ const primaryKey = constructor.getPrimaryKey?.() ?? "id";
7789
+ if (saveable.getAttribute?.(primaryKey) != null && constructor.query && saveable.getRawAttributes) return await constructor.query().create(saveable.getRawAttributes());
7790
+ if (typeof saveable.save !== "function") throw new Error("Factory model does not support save().");
7791
+ return await saveable.save();
7792
+ }
7793
+ async createHasRelations(model) {
7794
+ for (const definition of this.hasRelations) {
7795
+ const relationship = definition.relationship ?? `${(0, _h3ravel_support.str)(definition.factory.getModelConstructor().name).camel().plural()}`;
7796
+ const relation = this.resolveRelation(model, relationship);
7797
+ definition.factory.inheritRecyclePool(this.recyclePool);
7798
+ for (let index = 0; index < definition.factory.amount; index++) await definition.factory.createPersisted({}, async (related) => await relation.save(related));
7799
+ }
7800
+ }
7801
+ async createAttachedRelations(model) {
7802
+ for (const definition of this.attachedRelations) {
7803
+ const factory = definition.related instanceof ModelFactory ? definition.related : null;
7804
+ const relatedModels = factory ? await factory.inheritRecyclePool(this.recyclePool).createMany() : Array.isArray(definition.related) ? definition.related : [definition.related];
7805
+ const relatedName = factory?.getModelConstructor().name ?? relatedModels[0]?.constructor.name;
7806
+ const relationship = definition.relationship ?? `${(0, _h3ravel_support.str)(relatedName ?? "").camel().plural()}`;
7807
+ const relation = this.resolveRelation(model, relationship);
7808
+ if (typeof relation.attach !== "function") throw new Error(`Factory relationship [${relationship}] does not support attach().`);
7809
+ await relation.attach(relatedModels, definition.pivot);
7810
+ }
7811
+ }
7812
+ resolveRelation(model, relationship) {
7813
+ const resolver = model[relationship];
7814
+ if (typeof resolver !== "function") throw new Error(`Factory relationship [${relationship}] is not defined on [${this.model.name}].`);
7815
+ return resolver.call(model);
7816
+ }
7817
+ async resolveFactoryKey(factory) {
7818
+ const model = await this.resolveFactoryModel(factory);
7819
+ const primaryKey = model.constructor.getPrimaryKey?.() ?? "id";
7820
+ return model.getAttribute(primaryKey);
7821
+ }
7822
+ async resolveFactoryModel(factory) {
7823
+ factory.inheritRecyclePool(this.recyclePool);
7824
+ return factory.takeRecycledModel() ?? await factory.create();
7825
+ }
7826
+ inheritRecyclePool(pool) {
7827
+ pool.forEach((models, constructor) => {
7828
+ const existing = this.recyclePool.get(constructor) ?? [];
7829
+ const inherited = models.filter((model) => !existing.includes(model));
7830
+ this.recyclePool.set(constructor, [...existing, ...inherited]);
7831
+ });
7832
+ return this;
7833
+ }
7834
+ takeRecycledModel() {
7835
+ const constructor = this.model;
7836
+ const models = this.recyclePool.get(constructor);
7837
+ if (!models || models.length === 0) return null;
7838
+ const offset = this.recycleOffsets.get(constructor) ?? 0;
7839
+ this.recycleOffsets.set(constructor, offset + 1);
7840
+ return models[offset % models.length] ?? null;
7841
+ }
7842
+ runCallbacksSync(callbacks, model, callbackName) {
7843
+ callbacks.forEach((callback) => {
7844
+ const result = callback(model);
7845
+ if (ModelFactory.isPromiseLike(result)) throw new Error(`Factory ${callbackName} callback is async. Use makeAsync(), makeManyAsync(), create(), or createMany() instead.`);
7846
+ });
7847
+ }
7848
+ async runCallbacks(callbacks, model) {
7849
+ for (const callback of callbacks) await callback(model);
7850
+ }
7851
+ static isPromiseLike(value) {
7852
+ return typeof value?.then === "function";
7853
+ }
7854
+ static isFactory(value) {
7855
+ return value instanceof ModelFactory;
7856
+ }
7857
+ };
7858
+ /**
7859
+ * A helper class for defining factories using an inline definition
7860
+ * function, without needing to create a separate factory class.
7861
+ *
7862
+ * @template TModel
7863
+ * @template TAttributes
7864
+ * @author Legacy (3m1n3nc3)
7865
+ * @since 0.1.0
7866
+ */
7867
+ var InlineFactory = class extends ModelFactory {
7868
+ constructor(model, resolver) {
7869
+ super();
7870
+ this.resolver = resolver;
7871
+ this.model = model;
7872
+ }
7873
+ definition(sequence) {
7874
+ return this.resolver(sequence);
7875
+ }
7876
+ };
7877
+ /**
7878
+ * Define a factory for a given model using an inline definition function.
7879
+ *
7880
+ * @template TModel The type of model the factory creates.
7881
+ * @template TAttributes The type of attributes used to create the model.
7882
+ * @param model The model constructor.
7883
+ * @param definition The factory definition function.
7884
+ * @returns A new instance of the model factory.
7885
+ */
7886
+ const defineFactory = (model, definition) => {
7887
+ return new InlineFactory(model, definition);
7888
+ };
7889
+
7890
+ //#endregion
7891
+ //#region src/DB.ts
7892
+ const defaultSoftDeleteConfig = {
7893
+ enabled: false,
7894
+ column: "deletedAt"
7895
+ };
7896
+ var DB = class DB {
7897
+ constructor(adapter) {
7898
+ this.scopedAdapter = adapter;
7899
+ }
7900
+ static setAdapter(adapter) {
7901
+ this.adapter = adapter;
7902
+ }
7903
+ static getAdapter() {
7904
+ const runtimeAdapter = require_relationship.getRuntimeAdapter();
7905
+ if (runtimeAdapter) return runtimeAdapter;
7906
+ if (this.adapter) return this.adapter;
7907
+ return getRuntimeCompatibilityAdapter();
7908
+ }
7909
+ getAdapter() {
7910
+ return this.scopedAdapter ?? DB.getAdapter();
7911
+ }
7912
+ static table(table, options = {}) {
7913
+ return new DB().table(table, options);
7914
+ }
7915
+ table(table, options = {}) {
7916
+ return DB.createTableModel(table, options, this.getAdapter()).query();
7917
+ }
7918
+ static async raw(sql, bindings = []) {
7919
+ return await new DB().raw(sql, bindings);
7920
+ }
7921
+ async raw(sql, bindings = []) {
7922
+ const adapter = this.getAdapter();
7923
+ if (!adapter) throw new require_relationship.ArkormException("Raw queries require a configured database adapter.", {
7924
+ code: "ADAPTER_NOT_CONFIGURED",
7925
+ operation: "db.raw"
7926
+ });
7927
+ if (!adapter.rawQuery) throw new require_relationship.UnsupportedAdapterFeatureException("Raw queries are not supported by the current adapter.", {
7928
+ operation: "db.raw",
7929
+ meta: { feature: "rawQuery" }
7930
+ });
7931
+ const rows = await adapter.rawQuery({
7932
+ sql,
7933
+ bindings
7934
+ });
7935
+ return require_relationship.ArkormCollection.make(rows);
7936
+ }
7937
+ static async transaction(callback, context) {
7938
+ return await new DB().transaction(callback, context);
7939
+ }
7940
+ async transaction(callback, context) {
7941
+ const adapter = this.getAdapter();
7942
+ if (!adapter) throw new require_relationship.ArkormException("DB transactions require a configured database adapter.", {
7943
+ code: "ADAPTER_NOT_CONFIGURED",
7944
+ operation: "db.transaction"
7945
+ });
7946
+ return await adapter.transaction(async (transactionAdapter) => {
7947
+ return await callback(new DB(transactionAdapter));
7948
+ }, context);
7949
+ }
7950
+ static createTableModel(table, options, adapter) {
7951
+ const primaryKey = options.primaryKey ?? "id";
7952
+ const resolvedAdapter = options.adapter ?? adapter ?? DB.getAdapter();
7953
+ const persistedMetadata = DB.resolvePersistedTableMetadata(table, options, resolvedAdapter);
7954
+ const columns = {
7955
+ ...persistedMetadata.columns,
7956
+ ...options.columns ?? {}
7957
+ };
7958
+ const softDelete = options.softDelete ?? defaultSoftDeleteConfig;
7959
+ const primaryKeyGeneration = options.primaryKeyGeneration ? { ...options.primaryKeyGeneration } : persistedMetadata.primaryKeyGeneration?.column === primaryKey ? {
7960
+ strategy: persistedMetadata.primaryKeyGeneration.strategy,
7961
+ prismaDefault: persistedMetadata.primaryKeyGeneration.prismaDefault,
7962
+ databaseDefault: persistedMetadata.primaryKeyGeneration.databaseDefault,
7963
+ runtimeFactory: persistedMetadata.primaryKeyGeneration.runtimeFactory
7964
+ } : void 0;
7965
+ const timestampColumns = options.timestampColumns?.map((column) => ({ ...column })) ?? persistedMetadata.timestampColumns?.map((column) => ({ ...column }));
7966
+ const buildMetadata = () => {
7967
+ return {
7968
+ table,
7969
+ primaryKey,
7970
+ columns: { ...columns },
7971
+ softDelete: { ...softDelete },
7972
+ primaryKeyGeneration,
7973
+ timestampColumns
7974
+ };
7975
+ };
7976
+ const modelStatic = {
7977
+ query: () => new QueryBuilder(modelStatic, modelStatic.getAdapter()),
7978
+ hydrate: (attributes) => ({ ...attributes }),
7979
+ hydrateMany: (attributes) => attributes.map((attribute) => ({ ...attribute })),
7980
+ hydrateRetrieved: async (attributes) => ({ ...attributes }),
7981
+ hydrateManyRetrieved: async (attributes) => attributes.map((attribute) => ({ ...attribute })),
7982
+ getAdapter: () => resolvedAdapter,
7983
+ getColumnMap: () => ({ ...columns }),
7984
+ getColumnName: (attribute) => columns[attribute] ?? attribute,
7985
+ getModelMetadata: () => buildMetadata(),
7986
+ getPrimaryKey: () => primaryKey,
7987
+ getRelationMetadata: () => null,
7988
+ setAdapter: () => {},
7989
+ getSoftDeleteConfig: () => ({ ...softDelete }),
7990
+ getTable: () => table
7991
+ };
7992
+ return modelStatic;
7993
+ }
7994
+ static resolvePersistedTableMetadata(table, options, adapter) {
7995
+ if (options.persistedMetadata === false) return {
7996
+ columns: {},
7997
+ enums: {}
7998
+ };
7999
+ const persistedMetadataOptions = typeof options.persistedMetadata === "object" ? options.persistedMetadata : {};
8000
+ return require_relationship.getPersistedTableMetadata(table, {
8001
+ cwd: persistedMetadataOptions.cwd,
8002
+ configuredPath: persistedMetadataOptions.configuredPath,
8003
+ features: require_relationship.resolvePersistedMetadataFeatures(require_relationship.getUserConfig("features")),
8004
+ strict: persistedMetadataOptions.strict ?? (Boolean(adapter) && !(adapter instanceof PrismaDatabaseAdapter))
8005
+ });
8006
+ }
8007
+ };
8008
+
7728
8009
  //#endregion
7729
8010
  //#region src/PivotModel.ts
7730
8011
  /**
@@ -7810,7 +8091,9 @@ exports.buildMigrationIdentity = require_relationship.buildMigrationIdentity;
7810
8091
  exports.buildMigrationRunId = require_relationship.buildMigrationRunId;
7811
8092
  exports.buildMigrationSource = require_relationship.buildMigrationSource;
7812
8093
  exports.buildModelBlock = require_relationship.buildModelBlock;
8094
+ exports.buildPrimaryKeyLine = require_relationship.buildPrimaryKeyLine;
7813
8095
  exports.buildRelationLine = require_relationship.buildRelationLine;
8096
+ exports.buildUniqueConstraintLine = require_relationship.buildUniqueConstraintLine;
7814
8097
  exports.computeMigrationChecksum = require_relationship.computeMigrationChecksum;
7815
8098
  exports.configureArkormRuntime = require_relationship.configureArkormRuntime;
7816
8099
  exports.createEmptyAppliedMigrationsState = require_relationship.createEmptyAppliedMigrationsState;