arkormx 0.2.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ let path = require("path");
34
34
  path = __toESM(path);
35
35
  let fs = require("fs");
36
36
  let url = require("url");
37
+ let async_hooks = require("async_hooks");
37
38
  let module$1 = require("module");
38
39
  let _h3ravel_shared = require("@h3ravel/shared");
39
40
  let _h3ravel_musket = require("@h3ravel/musket");
@@ -122,17 +123,43 @@ function resolveCast(definition) {
122
123
 
123
124
  //#endregion
124
125
  //#region src/Exceptions/ArkormException.ts
125
- /**
126
- * The ArkormException class is a custom error type for handling
127
- * exceptions specific to the Arkormˣ.
128
- *
129
- * @author Legacy (3m1n3nc3)
130
- * @since 0.1.0
131
- */
132
126
  var ArkormException = class extends Error {
133
- constructor(message) {
134
- super(message);
127
+ code;
128
+ operation;
129
+ model;
130
+ delegate;
131
+ relation;
132
+ scope;
133
+ meta;
134
+ constructor(message, context = {}) {
135
+ super(message, context.cause === void 0 ? void 0 : { cause: context.cause });
135
136
  this.name = "ArkormException";
137
+ this.code = context.code;
138
+ this.operation = context.operation;
139
+ this.model = context.model;
140
+ this.delegate = context.delegate;
141
+ this.relation = context.relation;
142
+ this.scope = context.scope;
143
+ this.meta = context.meta;
144
+ }
145
+ getContext() {
146
+ return {
147
+ code: this.code,
148
+ operation: this.operation,
149
+ model: this.model,
150
+ delegate: this.delegate,
151
+ relation: this.relation,
152
+ scope: this.scope,
153
+ meta: this.meta,
154
+ cause: this.cause
155
+ };
156
+ }
157
+ toJSON() {
158
+ return {
159
+ name: this.name,
160
+ message: this.message,
161
+ ...this.getContext()
162
+ };
136
163
  }
137
164
  };
138
165
 
@@ -1242,6 +1269,18 @@ const runMigrationWithPrisma = async (migration, options = {}) => {
1242
1269
  };
1243
1270
  };
1244
1271
 
1272
+ //#endregion
1273
+ //#region src/Exceptions/UnsupportedAdapterFeatureException.ts
1274
+ var UnsupportedAdapterFeatureException = class extends ArkormException {
1275
+ constructor(message, context = {}) {
1276
+ super(message, {
1277
+ code: "UNSUPPORTED_ADAPTER_FEATURE",
1278
+ ...context
1279
+ });
1280
+ this.name = "UnsupportedAdapterFeatureException";
1281
+ }
1282
+ };
1283
+
1245
1284
  //#endregion
1246
1285
  //#region src/helpers/runtime-config.ts
1247
1286
  const resolveDefaultStubsPath = () => {
@@ -1276,6 +1315,7 @@ let runtimeConfigLoadingPromise;
1276
1315
  let runtimeClientResolver;
1277
1316
  let runtimePaginationURLDriverFactory;
1278
1317
  let runtimePaginationCurrentPageResolver;
1318
+ const transactionClientStorage = new async_hooks.AsyncLocalStorage();
1279
1319
  const mergePathConfig = (paths) => {
1280
1320
  const defaults = baseConfig.paths ?? {};
1281
1321
  const current = userConfig.paths ?? {};
@@ -1443,9 +1483,36 @@ const getDefaultStubsPath = () => {
1443
1483
  * @returns
1444
1484
  */
1445
1485
  const getRuntimePrismaClient = () => {
1486
+ const activeTransactionClient = transactionClientStorage.getStore();
1487
+ if (activeTransactionClient) return activeTransactionClient;
1446
1488
  if (!runtimeConfigLoaded) loadRuntimeConfigSync();
1447
1489
  return resolveClient(runtimeClientResolver);
1448
1490
  };
1491
+ const getActiveTransactionClient = () => {
1492
+ return transactionClientStorage.getStore();
1493
+ };
1494
+ const isTransactionCapableClient = (value) => {
1495
+ if (!value || typeof value !== "object") return false;
1496
+ return typeof value.$transaction === "function";
1497
+ };
1498
+ const runArkormTransaction = async (callback, options = {}) => {
1499
+ const activeTransactionClient = transactionClientStorage.getStore();
1500
+ if (activeTransactionClient) return await callback(activeTransactionClient);
1501
+ const client = getRuntimePrismaClient();
1502
+ if (!client) throw new ArkormException("Cannot start a transaction without a configured Prisma client.", {
1503
+ code: "CLIENT_NOT_CONFIGURED",
1504
+ operation: "transaction"
1505
+ });
1506
+ if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
1507
+ code: "TRANSACTION_NOT_SUPPORTED",
1508
+ operation: "transaction"
1509
+ });
1510
+ return await client.$transaction(async (transactionClient) => {
1511
+ return await transactionClientStorage.run(transactionClient, async () => {
1512
+ return await callback(transactionClient);
1513
+ });
1514
+ }, options);
1515
+ };
1449
1516
  /**
1450
1517
  * Get the configured pagination URL driver factory from runtime config.
1451
1518
  *
@@ -2801,6 +2868,18 @@ const defineFactory = (model, definition) => {
2801
2868
  return new InlineFactory(model, definition);
2802
2869
  };
2803
2870
 
2871
+ //#endregion
2872
+ //#region src/Exceptions/MissingDelegateException.ts
2873
+ var MissingDelegateException = class extends ArkormException {
2874
+ constructor(message, context = {}) {
2875
+ super(message, {
2876
+ code: "MISSING_DELEGATE",
2877
+ ...context
2878
+ });
2879
+ this.name = "MissingDelegateException";
2880
+ }
2881
+ };
2882
+
2804
2883
  //#endregion
2805
2884
  //#region src/Exceptions/ModelNotFoundException.ts
2806
2885
  /**
@@ -2812,8 +2891,12 @@ const defineFactory = (model, definition) => {
2812
2891
  */
2813
2892
  var ModelNotFoundException = class extends ArkormException {
2814
2893
  modelName;
2815
- constructor(modelName, message = "No query results for the given model.") {
2816
- super(message);
2894
+ constructor(modelName, message = "No query results for the given model.", context = {}) {
2895
+ super(message, {
2896
+ code: "MODEL_NOT_FOUND",
2897
+ model: modelName,
2898
+ ...context
2899
+ });
2817
2900
  this.name = "ModelNotFoundException";
2818
2901
  this.modelName = modelName;
2819
2902
  }
@@ -2822,6 +2905,54 @@ var ModelNotFoundException = class extends ArkormException {
2822
2905
  }
2823
2906
  };
2824
2907
 
2908
+ //#endregion
2909
+ //#region src/Exceptions/QueryConstraintException.ts
2910
+ var QueryConstraintException = class extends ArkormException {
2911
+ constructor(message, context = {}) {
2912
+ super(message, {
2913
+ code: "QUERY_CONSTRAINT",
2914
+ ...context
2915
+ });
2916
+ this.name = "QueryConstraintException";
2917
+ }
2918
+ };
2919
+
2920
+ //#endregion
2921
+ //#region src/Exceptions/RelationResolutionException.ts
2922
+ var RelationResolutionException = class extends ArkormException {
2923
+ constructor(message, context = {}) {
2924
+ super(message, {
2925
+ code: "RELATION_RESOLUTION_FAILED",
2926
+ ...context
2927
+ });
2928
+ this.name = "RelationResolutionException";
2929
+ }
2930
+ };
2931
+
2932
+ //#endregion
2933
+ //#region src/Exceptions/ScopeNotDefinedException.ts
2934
+ var ScopeNotDefinedException = class extends ArkormException {
2935
+ constructor(message, context = {}) {
2936
+ super(message, {
2937
+ code: "SCOPE_NOT_DEFINED",
2938
+ ...context
2939
+ });
2940
+ this.name = "ScopeNotDefinedException";
2941
+ }
2942
+ };
2943
+
2944
+ //#endregion
2945
+ //#region src/Exceptions/UniqueConstraintResolutionException.ts
2946
+ var UniqueConstraintResolutionException = class extends ArkormException {
2947
+ constructor(message, context = {}) {
2948
+ super(message, {
2949
+ code: "UNIQUE_CONSTRAINT_RESOLUTION_FAILED",
2950
+ ...context
2951
+ });
2952
+ this.name = "UniqueConstraintResolutionException";
2953
+ }
2954
+ };
2955
+
2825
2956
  //#endregion
2826
2957
  //#region src/helpers/prisma.ts
2827
2958
  /**
@@ -3062,6 +3193,7 @@ var BelongsToManyRelation = class extends Relation {
3062
3193
  async getResults() {
3063
3194
  const parentValue = this.parent.getAttribute(this.parentKey);
3064
3195
  const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.foreignPivotKey]: parentValue } })).map((row) => row[this.relatedPivotKey]);
3196
+ if (ids.length === 0) return new ArkormCollection([]);
3065
3197
  return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
3066
3198
  }
3067
3199
  };
@@ -3148,6 +3280,7 @@ var HasManyThroughRelation = class extends Relation {
3148
3280
  async getResults() {
3149
3281
  const localValue = this.parent.getAttribute(this.localKey);
3150
3282
  const keys = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.firstKey]: localValue } })).map((row) => row[this.secondLocalKey]);
3283
+ if (keys.length === 0) return new ArkormCollection([]);
3151
3284
  return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: keys } })).get();
3152
3285
  }
3153
3286
  };
@@ -3305,6 +3438,7 @@ var MorphToManyRelation = class extends Relation {
3305
3438
  [`${this.morphName}Id`]: parentValue,
3306
3439
  [`${this.morphName}Type`]: morphType
3307
3440
  } })).map((row) => row[this.relatedPivotKey]);
3441
+ if (ids.length === 0) return new ArkormCollection([]);
3308
3442
  return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
3309
3443
  }
3310
3444
  };
@@ -4056,7 +4190,11 @@ var QueryBuilder = class QueryBuilder {
4056
4190
  scope(name, ...args) {
4057
4191
  const methodName = `scope${name.charAt(0).toUpperCase()}${name.slice(1)}`;
4058
4192
  const scope = this.model.prototype?.[methodName];
4059
- if (typeof scope !== "function") throw new ArkormException(`Scope [${name}] is not defined.`);
4193
+ if (typeof scope !== "function") throw new ScopeNotDefinedException(`Scope [${name}] is not defined.`, {
4194
+ operation: "scope",
4195
+ model: this.model.name,
4196
+ scope: name
4197
+ });
4060
4198
  const scoped = scope.call(void 0, this, ...args);
4061
4199
  if (scoped && scoped !== this) return scoped;
4062
4200
  return this;
@@ -4178,7 +4316,7 @@ var QueryBuilder = class QueryBuilder {
4178
4316
  const relationCache = /* @__PURE__ */ new WeakMap();
4179
4317
  const rows = await this.delegate.findMany(this.buildFindArgs());
4180
4318
  const normalizedRows = this.randomOrderEnabled ? this.shuffleRows(rows) : rows;
4181
- const models = this.model.hydrateMany(normalizedRows);
4319
+ const models = await this.model.hydrateManyRetrieved(normalizedRows);
4182
4320
  let filteredModels = models;
4183
4321
  if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.args.where) {
4184
4322
  const baseIds = new Set(models.map((model) => this.getModelId(model)).filter((id) => id != null));
@@ -4208,13 +4346,13 @@ var QueryBuilder = class QueryBuilder {
4208
4346
  if (rows.length === 0) return null;
4209
4347
  const row = this.shuffleRows(rows)[0];
4210
4348
  if (!row) return null;
4211
- const model = this.model.hydrate(row);
4349
+ const model = await this.model.hydrateRetrieved(row);
4212
4350
  await model.load(this.eagerLoads);
4213
4351
  return model;
4214
4352
  }
4215
4353
  const row = await this.delegate.findFirst(this.buildFindArgs());
4216
4354
  if (!row) return null;
4217
- const model = this.model.hydrate(row);
4355
+ const model = await this.model.hydrateRetrieved(row);
4218
4356
  await model.load(this.eagerLoads);
4219
4357
  return model;
4220
4358
  }
@@ -4234,7 +4372,10 @@ var QueryBuilder = class QueryBuilder {
4234
4372
  async findOr(value, keyOrCallback, maybeCallback) {
4235
4373
  const key = typeof keyOrCallback === "string" ? keyOrCallback : "id";
4236
4374
  const callback = typeof keyOrCallback === "function" ? keyOrCallback : maybeCallback;
4237
- if (!callback) throw new ArkormException("findOr requires a fallback callback.");
4375
+ if (!callback) throw new QueryConstraintException("findOr requires a fallback callback.", {
4376
+ operation: "findOr",
4377
+ model: this.model.name
4378
+ });
4238
4379
  const found = await this.find(value, key);
4239
4380
  if (found) return found;
4240
4381
  return callback();
@@ -4348,7 +4489,11 @@ var QueryBuilder = class QueryBuilder {
4348
4489
  async insertGetId(values, sequence) {
4349
4490
  const created = await this.delegate.create({ data: values });
4350
4491
  const key = sequence ?? "id";
4351
- if (!(key in created)) throw new ArkormException(`Inserted record does not contain key [${key}].`);
4492
+ if (!(key in created)) throw new UniqueConstraintResolutionException(`Inserted record does not contain key [${key}].`, {
4493
+ operation: "insertGetId",
4494
+ model: this.model.name,
4495
+ meta: { key }
4496
+ });
4352
4497
  return created[key];
4353
4498
  }
4354
4499
  /**
@@ -4385,7 +4530,10 @@ var QueryBuilder = class QueryBuilder {
4385
4530
  */
4386
4531
  async update(data) {
4387
4532
  const where = this.buildWhere();
4388
- if (!where) throw new ArkormException("Update requires a where clause.");
4533
+ if (!where) throw new QueryConstraintException("Update requires a where clause.", {
4534
+ operation: "update",
4535
+ model: this.model.name
4536
+ });
4389
4537
  const uniqueWhere = await this.resolveUniqueWhere(where);
4390
4538
  const updated = await this.delegate.update({
4391
4539
  where: uniqueWhere,
@@ -4401,7 +4549,10 @@ var QueryBuilder = class QueryBuilder {
4401
4549
  */
4402
4550
  async updateFrom(data) {
4403
4551
  const where = this.buildWhere();
4404
- if (!where) throw new ArkormException("Update requires a where clause.");
4552
+ if (!where) throw new QueryConstraintException("Update requires a where clause.", {
4553
+ operation: "updateFrom",
4554
+ model: this.model.name
4555
+ });
4405
4556
  const delegate = this.delegate;
4406
4557
  if (typeof delegate.updateMany === "function") {
4407
4558
  const result = await delegate.updateMany({
@@ -4466,7 +4617,10 @@ var QueryBuilder = class QueryBuilder {
4466
4617
  */
4467
4618
  async delete() {
4468
4619
  const where = this.buildWhere();
4469
- if (!where) throw new ArkormException("Delete requires a where clause.");
4620
+ if (!where) throw new QueryConstraintException("Delete requires a where clause.", {
4621
+ operation: "delete",
4622
+ model: this.model.name
4623
+ });
4470
4624
  const uniqueWhere = await this.resolveUniqueWhere(where);
4471
4625
  const deleted = await this.delegate.delete({ where: uniqueWhere });
4472
4626
  return this.model.hydrate(deleted);
@@ -4532,7 +4686,10 @@ var QueryBuilder = class QueryBuilder {
4532
4686
  if (Array.isArray(source)) return source;
4533
4687
  }
4534
4688
  if (Array.isArray(source)) return source;
4535
- throw new ArkormException("insertUsing expects a query builder, array of records, or async resolver.");
4689
+ throw new QueryConstraintException("insertUsing expects a query builder, array of records, or async resolver.", {
4690
+ operation: "insertUsing",
4691
+ model: this.model.name
4692
+ });
4536
4693
  }
4537
4694
  /**
4538
4695
  * Execute callback when no records exist.
@@ -4616,7 +4773,11 @@ var QueryBuilder = class QueryBuilder {
4616
4773
  */
4617
4774
  whereRaw(sql, bindings = []) {
4618
4775
  const delegate = this.delegate;
4619
- if (typeof delegate.applyRawWhere !== "function") throw new ArkormException("Raw where clauses are not supported by the current adapter.");
4776
+ if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
4777
+ operation: "whereRaw",
4778
+ model: this.model.name,
4779
+ meta: { feature: "rawWhere" }
4780
+ });
4620
4781
  this.args.where = delegate.applyRawWhere(this.buildWhere(), sql, bindings);
4621
4782
  return this;
4622
4783
  }
@@ -4629,7 +4790,11 @@ var QueryBuilder = class QueryBuilder {
4629
4790
  */
4630
4791
  orWhereRaw(sql, bindings = []) {
4631
4792
  const delegate = this.delegate;
4632
- if (typeof delegate.applyRawWhere !== "function") throw new ArkormException("Raw where clauses are not supported by the current adapter.");
4793
+ if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
4794
+ operation: "orWhereRaw",
4795
+ model: this.model.name,
4796
+ meta: { feature: "rawWhere" }
4797
+ });
4633
4798
  const rawWhere = delegate.applyRawWhere(void 0, sql, bindings);
4634
4799
  return this.orWhere(rawWhere);
4635
4800
  }
@@ -4751,9 +4916,16 @@ var QueryBuilder = class QueryBuilder {
4751
4916
  async resolveUniqueWhere(where) {
4752
4917
  if (this.isUniqueWhere(where)) return where;
4753
4918
  const row = await this.delegate.findFirst({ where });
4754
- if (!row) throw new ArkormException("Record not found for update/delete operation.");
4919
+ if (!row) throw new ModelNotFoundException(this.model.name, "Record not found for update/delete operation.", {
4920
+ operation: "resolveUniqueWhere",
4921
+ meta: { where }
4922
+ });
4755
4923
  const record = row;
4756
- if (!Object.prototype.hasOwnProperty.call(record, "id")) throw new ArkormException("Unable to resolve a unique identifier for update/delete operation. Include an id in the query constraints.");
4924
+ 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.", {
4925
+ operation: "resolveUniqueWhere",
4926
+ model: this.model.name,
4927
+ meta: { where }
4928
+ });
4757
4929
  return { id: record.id };
4758
4930
  }
4759
4931
  /**
@@ -4876,7 +5048,11 @@ var QueryBuilder = class QueryBuilder {
4876
5048
  if (cached) return await cached;
4877
5049
  const resolver = (async () => {
4878
5050
  const relationMethod = model[relation];
4879
- if (typeof relationMethod !== "function") throw new ArkormException(`Relation [${relation}] is not defined on the model.`);
5051
+ if (typeof relationMethod !== "function") throw new RelationResolutionException(`Relation [${relation}] is not defined on the model.`, {
5052
+ operation: "resolveRelatedResults",
5053
+ model: this.model.name,
5054
+ relation
5055
+ });
4880
5056
  const relationInstance = relationMethod.call(model);
4881
5057
  if (callback && typeof relationInstance.constrain === "function") relationInstance.constrain((query) => {
4882
5058
  return callback(query) ?? query;
@@ -4891,7 +5067,11 @@ var QueryBuilder = class QueryBuilder {
4891
5067
  if (results instanceof ArkormCollection) return results.all();
4892
5068
  return results;
4893
5069
  }
4894
- throw new ArkormException(`Relation [${relation}] does not support result resolution.`);
5070
+ throw new RelationResolutionException(`Relation [${relation}] does not support result resolution.`, {
5071
+ operation: "resolveRelatedResults",
5072
+ model: this.model.name,
5073
+ relation
5074
+ });
4895
5075
  })();
4896
5076
  callbackMap.set(callbackCacheKey, resolver);
4897
5077
  return await resolver;
@@ -4932,6 +5112,8 @@ var QueryBuilder = class QueryBuilder {
4932
5112
  * @since 0.1.0
4933
5113
  */
4934
5114
  var Model = class Model {
5115
+ static lifecycleStates = /* @__PURE__ */ new WeakMap();
5116
+ static eventsSuppressed = 0;
4935
5117
  static factoryClass;
4936
5118
  static client;
4937
5119
  static delegate;
@@ -4939,6 +5121,7 @@ var Model = class Model {
4939
5121
  static deletedAtColumn = "deletedAt";
4940
5122
  static globalScopes = {};
4941
5123
  static eventListeners = {};
5124
+ static dispatchesEvents = {};
4942
5125
  casts = {};
4943
5126
  hidden = [];
4944
5127
  visible = [];
@@ -4976,7 +5159,11 @@ var Model = class Model {
4976
5159
  }
4977
5160
  static factory(count) {
4978
5161
  const factoryClass = this.factoryClass;
4979
- if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}].`);
5162
+ if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}].`, {
5163
+ code: "FACTORY_NOT_CONFIGURED",
5164
+ operation: "factory",
5165
+ model: this.name
5166
+ });
4980
5167
  const factory = new factoryClass();
4981
5168
  if (typeof count === "number") factory.count(count);
4982
5169
  return factory;
@@ -4992,6 +5179,21 @@ var Model = class Model {
4992
5179
  this.globalScopes[name] = scope;
4993
5180
  }
4994
5181
  /**
5182
+ * Execute a callback without applying global scopes for the current model class.
5183
+ *
5184
+ * @param callback
5185
+ * @returns
5186
+ */
5187
+ static async withoutGlobalScopes(callback) {
5188
+ const state = Model.getLifecycleState(this);
5189
+ state.globalScopesSuppressed += 1;
5190
+ try {
5191
+ return await callback();
5192
+ } finally {
5193
+ state.globalScopesSuppressed = Math.max(0, state.globalScopesSuppressed - 1);
5194
+ }
5195
+ }
5196
+ /**
4995
5197
  * Remove a global scope by name.
4996
5198
  *
4997
5199
  * @param name
@@ -5013,11 +5215,60 @@ var Model = class Model {
5013
5215
  * @param listener
5014
5216
  */
5015
5217
  static on(event, listener) {
5218
+ Model.ensureModelBooted(this);
5016
5219
  this.ensureOwnEventListeners();
5017
5220
  if (!this.eventListeners[event]) this.eventListeners[event] = [];
5018
5221
  this.eventListeners[event]?.push(listener);
5019
5222
  }
5020
5223
  /**
5224
+ * Register a model lifecycle callback listener.
5225
+ *
5226
+ * @param event
5227
+ * @param listener
5228
+ */
5229
+ static event(event, listener) {
5230
+ this.on(event, listener);
5231
+ }
5232
+ static retrieved(listener) {
5233
+ this.event("retrieved", listener);
5234
+ }
5235
+ static saving(listener) {
5236
+ this.event("saving", listener);
5237
+ }
5238
+ static saved(listener) {
5239
+ this.event("saved", listener);
5240
+ }
5241
+ static creating(listener) {
5242
+ this.event("creating", listener);
5243
+ }
5244
+ static created(listener) {
5245
+ this.event("created", listener);
5246
+ }
5247
+ static updating(listener) {
5248
+ this.event("updating", listener);
5249
+ }
5250
+ static updated(listener) {
5251
+ this.event("updated", listener);
5252
+ }
5253
+ static deleting(listener) {
5254
+ this.event("deleting", listener);
5255
+ }
5256
+ static deleted(listener) {
5257
+ this.event("deleted", listener);
5258
+ }
5259
+ static restoring(listener) {
5260
+ this.event("restoring", listener);
5261
+ }
5262
+ static restored(listener) {
5263
+ this.event("restored", listener);
5264
+ }
5265
+ static forceDeleting(listener) {
5266
+ this.event("forceDeleting", listener);
5267
+ }
5268
+ static forceDeleted(listener) {
5269
+ this.event("forceDeleted", listener);
5270
+ }
5271
+ /**
5021
5272
  * Remove listeners for an event. If listener is omitted, all listeners for that event are removed.
5022
5273
  *
5023
5274
  * @param event
@@ -5038,6 +5289,34 @@ var Model = class Model {
5038
5289
  this.eventListeners = {};
5039
5290
  }
5040
5291
  /**
5292
+ * Execute a callback while suppressing lifecycle events for all models.
5293
+ *
5294
+ * @param callback
5295
+ * @returns
5296
+ */
5297
+ static async withoutEvents(callback) {
5298
+ Model.eventsSuppressed += 1;
5299
+ try {
5300
+ return await callback();
5301
+ } finally {
5302
+ Model.eventsSuppressed = Math.max(0, Model.eventsSuppressed - 1);
5303
+ }
5304
+ }
5305
+ /**
5306
+ * Execute a callback within a transaction scope.
5307
+ * Nested calls reuse the active transaction client.
5308
+ *
5309
+ * @param callback
5310
+ * @param options
5311
+ * @returns
5312
+ */
5313
+ static async transaction(callback, options = {}) {
5314
+ ensureArkormConfigLoading();
5315
+ return await runArkormTransaction(async (client) => {
5316
+ return await callback(client);
5317
+ }, options);
5318
+ }
5319
+ /**
5041
5320
  * Get the Prisma delegate for the model.
5042
5321
  * If a delegate name is provided, it will attempt to resolve that delegate.
5043
5322
  * Otherwise, it will attempt to resolve a delegate based on the model's name or
@@ -5055,9 +5334,20 @@ var Model = class Model {
5055
5334
  `${(0, _h3ravel_support.str)(key).singular()}`,
5056
5335
  `${(0, _h3ravel_support.str)(key).camel().singular()}`
5057
5336
  ];
5337
+ const activeTransactionClient = getActiveTransactionClient();
5058
5338
  const runtimeClient = getRuntimePrismaClient();
5059
- const resolved = candidates.map((name) => this.client?.[name] ?? runtimeClient?.[name]).find((candidate) => isDelegateLike(candidate));
5060
- if (!resolved) throw new ArkormException(`Database delegate [${key}] is not configured.`);
5339
+ const sources = activeTransactionClient ? [
5340
+ activeTransactionClient,
5341
+ this.client,
5342
+ runtimeClient
5343
+ ] : [this.client, runtimeClient];
5344
+ const resolved = candidates.flatMap((name) => sources.map((source) => source?.[name])).find((candidate) => isDelegateLike(candidate));
5345
+ if (!resolved) throw new MissingDelegateException(`Database delegate [${key}] is not configured.`, {
5346
+ operation: "getDelegate",
5347
+ model: this.name,
5348
+ delegate: key,
5349
+ meta: { candidates }
5350
+ });
5061
5351
  return resolved;
5062
5352
  }
5063
5353
  /**
@@ -5067,16 +5357,27 @@ var Model = class Model {
5067
5357
  * @returns
5068
5358
  */
5069
5359
  static query() {
5360
+ Model.ensureModelBooted(this);
5070
5361
  let builder = new QueryBuilder(this.getDelegate(), this);
5071
5362
  const modelClass = this;
5072
- modelClass.ensureOwnGlobalScopes();
5073
- Object.values(modelClass.globalScopes).forEach((scope) => {
5074
- const scoped = scope(builder);
5075
- if (scoped && scoped !== builder) builder = scoped;
5076
- });
5363
+ if (!Model.areGlobalScopesSuppressed(modelClass)) {
5364
+ modelClass.ensureOwnGlobalScopes();
5365
+ Object.values(modelClass.globalScopes).forEach((scope) => {
5366
+ const scoped = scope(builder);
5367
+ if (scoped && scoped !== builder) builder = scoped;
5368
+ });
5369
+ }
5077
5370
  return builder;
5078
5371
  }
5079
5372
  /**
5373
+ * Boot hook for subclasses to register scopes or perform one-time setup.
5374
+ */
5375
+ static boot() {}
5376
+ /**
5377
+ * Booted hook for subclasses to register callbacks after boot logic runs.
5378
+ */
5379
+ static booted() {}
5380
+ /**
5080
5381
  * Get a query builder instance that includes soft-deleted records.
5081
5382
  *
5082
5383
  * @param this
@@ -5139,6 +5440,36 @@ var Model = class Model {
5139
5440
  static hydrateMany(attributes) {
5140
5441
  return attributes.map((attribute) => new this(attribute));
5141
5442
  }
5443
+ /**
5444
+ * Hydrate a model instance and dispatch the retrieved lifecycle event.
5445
+ *
5446
+ * @param this
5447
+ * @param attributes
5448
+ * @returns
5449
+ */
5450
+ static async hydrateRetrieved(attributes) {
5451
+ Model.ensureModelBooted(this);
5452
+ if (!Model.hasEventListeners(this, "retrieved")) return this.hydrate(attributes);
5453
+ const model = this.hydrate(attributes);
5454
+ await Model.dispatchEvent(this, "retrieved", model);
5455
+ return model;
5456
+ }
5457
+ /**
5458
+ * Hydrate multiple model instances and dispatch the retrieved lifecycle event for each.
5459
+ *
5460
+ * @param this
5461
+ * @param attributes
5462
+ * @returns
5463
+ */
5464
+ static async hydrateManyRetrieved(attributes) {
5465
+ Model.ensureModelBooted(this);
5466
+ if (!Model.hasEventListeners(this, "retrieved")) return this.hydrateMany(attributes);
5467
+ const models = this.hydrateMany(attributes);
5468
+ await Promise.all(models.map(async (model) => {
5469
+ await Model.dispatchEvent(this, "retrieved", model);
5470
+ }));
5471
+ return models;
5472
+ }
5142
5473
  fill(attributes) {
5143
5474
  Object.entries(attributes).forEach(([key, value]) => {
5144
5475
  this.setAttribute(key, value);
@@ -5195,6 +5526,14 @@ var Model = class Model {
5195
5526
  return this;
5196
5527
  }
5197
5528
  /**
5529
+ * Save the model without dispatching lifecycle events.
5530
+ *
5531
+ * @returns
5532
+ */
5533
+ async saveQuietly() {
5534
+ return await Model.withoutEvents(() => this.save());
5535
+ }
5536
+ /**
5198
5537
  * Delete the model from the database.
5199
5538
  * If soft deletes are enabled, it will perform a soft delete by
5200
5539
  * setting the deleted at column to the current date.
@@ -5220,6 +5559,14 @@ var Model = class Model {
5220
5559
  return this;
5221
5560
  }
5222
5561
  /**
5562
+ * Delete the model without dispatching lifecycle events.
5563
+ *
5564
+ * @returns
5565
+ */
5566
+ async deleteQuietly() {
5567
+ return await Model.withoutEvents(() => this.delete());
5568
+ }
5569
+ /**
5223
5570
  * Permanently delete the model from the database, regardless of whether soft
5224
5571
  * deletes are enabled.
5225
5572
  *
@@ -5238,6 +5585,14 @@ var Model = class Model {
5238
5585
  return this;
5239
5586
  }
5240
5587
  /**
5588
+ * Force delete the model without dispatching lifecycle events.
5589
+ *
5590
+ * @returns
5591
+ */
5592
+ async forceDeleteQuietly() {
5593
+ return await Model.withoutEvents(() => this.forceDelete());
5594
+ }
5595
+ /**
5241
5596
  * Restore a soft-deleted model by setting the deleted at column to null.
5242
5597
  *
5243
5598
  * @returns
@@ -5255,6 +5610,14 @@ var Model = class Model {
5255
5610
  return this;
5256
5611
  }
5257
5612
  /**
5613
+ * Restore the model without dispatching lifecycle events.
5614
+ *
5615
+ * @returns
5616
+ */
5617
+ async restoreQuietly() {
5618
+ return await Model.withoutEvents(() => this.restore());
5619
+ }
5620
+ /**
5258
5621
  * Load related models onto the current model instance.
5259
5622
  *
5260
5623
  * @param relations
@@ -5307,6 +5670,47 @@ var Model = class Model {
5307
5670
  return this.toObject();
5308
5671
  }
5309
5672
  /**
5673
+ * Determine if another model represents the same persisted record.
5674
+ *
5675
+ * @param model
5676
+ * @returns
5677
+ */
5678
+ is(model) {
5679
+ if (!(model instanceof Model)) return false;
5680
+ if (this.constructor !== model.constructor) return false;
5681
+ const identifier = this.getAttribute("id");
5682
+ const otherIdentifier = model.getAttribute("id");
5683
+ if (identifier == null || otherIdentifier == null) return false;
5684
+ return identifier === otherIdentifier;
5685
+ }
5686
+ /**
5687
+ * Determine if another model does not represent the same persisted record.
5688
+ *
5689
+ * @param model
5690
+ * @returns
5691
+ */
5692
+ isNot(model) {
5693
+ return !this.is(model);
5694
+ }
5695
+ /**
5696
+ * Determine if another model is the same in-memory instance.
5697
+ *
5698
+ * @param model
5699
+ * @returns
5700
+ */
5701
+ isSame(model) {
5702
+ return this === model;
5703
+ }
5704
+ /**
5705
+ * Determine if another model is not the same in-memory instance.
5706
+ *
5707
+ * @param model
5708
+ * @returns
5709
+ */
5710
+ isNotSame(model) {
5711
+ return !this.isSame(model);
5712
+ }
5713
+ /**
5310
5714
  * Define a has one relationship.
5311
5715
  *
5312
5716
  * @param related
@@ -5470,6 +5874,84 @@ var Model = class Model {
5470
5874
  if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
5471
5875
  }
5472
5876
  /**
5877
+ * Resolve lifecycle state for the provided model class.
5878
+ *
5879
+ * @param modelClass
5880
+ * @returns
5881
+ */
5882
+ static getLifecycleState(modelClass) {
5883
+ const existing = Model.lifecycleStates.get(modelClass);
5884
+ if (existing) return existing;
5885
+ const state = {
5886
+ booted: false,
5887
+ booting: false,
5888
+ globalScopesSuppressed: 0
5889
+ };
5890
+ Model.lifecycleStates.set(modelClass, state);
5891
+ return state;
5892
+ }
5893
+ /**
5894
+ * Ensure the target model class has executed its boot lifecycle.
5895
+ *
5896
+ * @param modelClass
5897
+ */
5898
+ static ensureModelBooted(modelClass) {
5899
+ const state = Model.getLifecycleState(modelClass);
5900
+ if (state.booted || state.booting) return;
5901
+ state.booting = true;
5902
+ try {
5903
+ const boot = modelClass.boot;
5904
+ if (boot !== Model.boot) boot.call(modelClass);
5905
+ const booted = modelClass.booted;
5906
+ if (booted !== Model.booted) booted.call(modelClass);
5907
+ state.booted = true;
5908
+ } finally {
5909
+ state.booting = false;
5910
+ }
5911
+ }
5912
+ /**
5913
+ * Determine if global scopes are currently suppressed for the model class.
5914
+ *
5915
+ * @param modelClass
5916
+ * @returns
5917
+ */
5918
+ static areGlobalScopesSuppressed(modelClass) {
5919
+ return Model.getLifecycleState(modelClass).globalScopesSuppressed > 0;
5920
+ }
5921
+ /**
5922
+ * Resolve configured class-based event handlers for a lifecycle event.
5923
+ *
5924
+ * @param modelClass
5925
+ * @param event
5926
+ * @returns
5927
+ */
5928
+ static resolveDispatchedEventListeners(modelClass, event) {
5929
+ const configured = modelClass.dispatchesEvents[event];
5930
+ if (!configured) return [];
5931
+ return (Array.isArray(configured) ? configured : [configured]).map((entry) => {
5932
+ const handler = typeof entry === "function" ? new entry() : entry;
5933
+ if (!handler || typeof handler.handle !== "function") throw new ArkormException(`Invalid event handler configured for [${modelClass.name}.${event}].`);
5934
+ return async (model) => {
5935
+ await handler.handle(model);
5936
+ };
5937
+ });
5938
+ }
5939
+ /**
5940
+ * Determine whether a lifecycle event has any registered listeners.
5941
+ *
5942
+ * @param modelClass
5943
+ * @param event
5944
+ * @returns
5945
+ */
5946
+ static hasEventListeners(modelClass, event) {
5947
+ if (Model.eventsSuppressed > 0) return false;
5948
+ modelClass.ensureOwnEventListeners();
5949
+ if ((modelClass.eventListeners[event] || []).length > 0) return true;
5950
+ const configuredDispatchers = modelClass.dispatchesEvents[event];
5951
+ if (!configuredDispatchers) return false;
5952
+ return Array.isArray(configuredDispatchers) ? configuredDispatchers.length > 0 : true;
5953
+ }
5954
+ /**
5473
5955
  * Dispatches lifecycle events to registered listeners.
5474
5956
  *
5475
5957
  * @param modelClass
@@ -5477,8 +5959,9 @@ var Model = class Model {
5477
5959
  * @param model
5478
5960
  */
5479
5961
  static async dispatchEvent(modelClass, event, model) {
5480
- modelClass.ensureOwnEventListeners();
5481
- const listeners = modelClass.eventListeners[event] || [];
5962
+ Model.ensureModelBooted(modelClass);
5963
+ if (!Model.hasEventListeners(modelClass, event)) return;
5964
+ const listeners = [...Model.resolveDispatchedEventListeners(modelClass, event), ...modelClass.eventListeners[event] || []];
5482
5965
  for (const listener of listeners) await listener(model);
5483
5966
  }
5484
5967
  /**
@@ -5515,6 +5998,7 @@ exports.MigrateCommand = MigrateCommand;
5515
5998
  exports.MigrateRollbackCommand = MigrateRollbackCommand;
5516
5999
  exports.Migration = Migration;
5517
6000
  exports.MigrationHistoryCommand = MigrationHistoryCommand;
6001
+ exports.MissingDelegateException = MissingDelegateException;
5518
6002
  exports.Model = Model;
5519
6003
  exports.ModelFactory = ModelFactory;
5520
6004
  exports.ModelNotFoundException = ModelNotFoundException;
@@ -5522,12 +6006,17 @@ exports.ModelsSyncCommand = ModelsSyncCommand;
5522
6006
  exports.PRISMA_MODEL_REGEX = PRISMA_MODEL_REGEX;
5523
6007
  exports.Paginator = Paginator;
5524
6008
  exports.QueryBuilder = QueryBuilder;
6009
+ exports.QueryConstraintException = QueryConstraintException;
6010
+ exports.RelationResolutionException = RelationResolutionException;
5525
6011
  exports.SEEDER_BRAND = SEEDER_BRAND;
5526
6012
  exports.SchemaBuilder = SchemaBuilder;
6013
+ exports.ScopeNotDefinedException = ScopeNotDefinedException;
5527
6014
  exports.SeedCommand = SeedCommand;
5528
6015
  exports.Seeder = Seeder;
5529
6016
  exports.TableBuilder = TableBuilder;
5530
6017
  exports.URLDriver = URLDriver;
6018
+ exports.UniqueConstraintResolutionException = UniqueConstraintResolutionException;
6019
+ exports.UnsupportedAdapterFeatureException = UnsupportedAdapterFeatureException;
5531
6020
  exports.applyAlterTableOperation = applyAlterTableOperation;
5532
6021
  exports.applyCreateTableOperation = applyCreateTableOperation;
5533
6022
  exports.applyDropTableOperation = applyDropTableOperation;
@@ -5559,6 +6048,7 @@ exports.findModelBlock = findModelBlock;
5559
6048
  exports.formatDefaultValue = formatDefaultValue;
5560
6049
  exports.formatRelationAction = formatRelationAction;
5561
6050
  exports.generateMigrationFile = generateMigrationFile;
6051
+ exports.getActiveTransactionClient = getActiveTransactionClient;
5562
6052
  exports.getDefaultStubsPath = getDefaultStubsPath;
5563
6053
  exports.getLastMigrationRun = getLastMigrationRun;
5564
6054
  exports.getLatestAppliedMigrations = getLatestAppliedMigrations;
@@ -5570,6 +6060,7 @@ exports.getUserConfig = getUserConfig;
5570
6060
  exports.inferDelegateName = inferDelegateName;
5571
6061
  exports.isDelegateLike = isDelegateLike;
5572
6062
  exports.isMigrationApplied = isMigrationApplied;
6063
+ exports.isTransactionCapableClient = isTransactionCapableClient;
5573
6064
  exports.loadArkormConfig = loadArkormConfig;
5574
6065
  exports.markMigrationApplied = markMigrationApplied;
5575
6066
  exports.markMigrationRun = markMigrationRun;
@@ -5581,6 +6072,7 @@ exports.resolveCast = resolveCast;
5581
6072
  exports.resolveMigrationClassName = resolveMigrationClassName;
5582
6073
  exports.resolveMigrationStateFilePath = resolveMigrationStateFilePath;
5583
6074
  exports.resolvePrismaType = resolvePrismaType;
6075
+ exports.runArkormTransaction = runArkormTransaction;
5584
6076
  exports.runMigrationWithPrisma = runMigrationWithPrisma;
5585
6077
  exports.runPrismaCommand = runPrismaCommand;
5586
6078
  exports.toMigrationFileSlug = toMigrationFileSlug;