arkormx 2.4.4 → 2.4.6

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