arkormx 0.2.11 → 1.1.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/README.md +17 -0
- package/dist/cli.mjs +37 -9
- package/dist/index.cjs +680 -38
- package/dist/index.d.cts +308 -7
- package/dist/index.d.mts +308 -7
- package/dist/index.mjs +672 -39
- package/package.json +1 -1
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
|
-
|
|
134
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,13 +5121,20 @@ 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 = [];
|
|
4945
5128
|
appends = [];
|
|
4946
5129
|
attributes;
|
|
5130
|
+
original;
|
|
5131
|
+
changes;
|
|
5132
|
+
touchedAttributes;
|
|
4947
5133
|
constructor(attributes = {}) {
|
|
4948
5134
|
this.attributes = {};
|
|
5135
|
+
this.original = {};
|
|
5136
|
+
this.changes = {};
|
|
5137
|
+
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
4949
5138
|
this.fill(attributes);
|
|
4950
5139
|
return new Proxy(this, {
|
|
4951
5140
|
get: (target, key, receiver) => {
|
|
@@ -4976,7 +5165,11 @@ var Model = class Model {
|
|
|
4976
5165
|
}
|
|
4977
5166
|
static factory(count) {
|
|
4978
5167
|
const factoryClass = this.factoryClass;
|
|
4979
|
-
if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}]
|
|
5168
|
+
if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}].`, {
|
|
5169
|
+
code: "FACTORY_NOT_CONFIGURED",
|
|
5170
|
+
operation: "factory",
|
|
5171
|
+
model: this.name
|
|
5172
|
+
});
|
|
4980
5173
|
const factory = new factoryClass();
|
|
4981
5174
|
if (typeof count === "number") factory.count(count);
|
|
4982
5175
|
return factory;
|
|
@@ -4992,6 +5185,21 @@ var Model = class Model {
|
|
|
4992
5185
|
this.globalScopes[name] = scope;
|
|
4993
5186
|
}
|
|
4994
5187
|
/**
|
|
5188
|
+
* Execute a callback without applying global scopes for the current model class.
|
|
5189
|
+
*
|
|
5190
|
+
* @param callback
|
|
5191
|
+
* @returns
|
|
5192
|
+
*/
|
|
5193
|
+
static async withoutGlobalScopes(callback) {
|
|
5194
|
+
const state = Model.getLifecycleState(this);
|
|
5195
|
+
state.globalScopesSuppressed += 1;
|
|
5196
|
+
try {
|
|
5197
|
+
return await callback();
|
|
5198
|
+
} finally {
|
|
5199
|
+
state.globalScopesSuppressed = Math.max(0, state.globalScopesSuppressed - 1);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
/**
|
|
4995
5203
|
* Remove a global scope by name.
|
|
4996
5204
|
*
|
|
4997
5205
|
* @param name
|
|
@@ -5013,11 +5221,60 @@ var Model = class Model {
|
|
|
5013
5221
|
* @param listener
|
|
5014
5222
|
*/
|
|
5015
5223
|
static on(event, listener) {
|
|
5224
|
+
Model.ensureModelBooted(this);
|
|
5016
5225
|
this.ensureOwnEventListeners();
|
|
5017
5226
|
if (!this.eventListeners[event]) this.eventListeners[event] = [];
|
|
5018
5227
|
this.eventListeners[event]?.push(listener);
|
|
5019
5228
|
}
|
|
5020
5229
|
/**
|
|
5230
|
+
* Register a model lifecycle callback listener.
|
|
5231
|
+
*
|
|
5232
|
+
* @param event
|
|
5233
|
+
* @param listener
|
|
5234
|
+
*/
|
|
5235
|
+
static event(event, listener) {
|
|
5236
|
+
this.on(event, listener);
|
|
5237
|
+
}
|
|
5238
|
+
static retrieved(listener) {
|
|
5239
|
+
this.event("retrieved", listener);
|
|
5240
|
+
}
|
|
5241
|
+
static saving(listener) {
|
|
5242
|
+
this.event("saving", listener);
|
|
5243
|
+
}
|
|
5244
|
+
static saved(listener) {
|
|
5245
|
+
this.event("saved", listener);
|
|
5246
|
+
}
|
|
5247
|
+
static creating(listener) {
|
|
5248
|
+
this.event("creating", listener);
|
|
5249
|
+
}
|
|
5250
|
+
static created(listener) {
|
|
5251
|
+
this.event("created", listener);
|
|
5252
|
+
}
|
|
5253
|
+
static updating(listener) {
|
|
5254
|
+
this.event("updating", listener);
|
|
5255
|
+
}
|
|
5256
|
+
static updated(listener) {
|
|
5257
|
+
this.event("updated", listener);
|
|
5258
|
+
}
|
|
5259
|
+
static deleting(listener) {
|
|
5260
|
+
this.event("deleting", listener);
|
|
5261
|
+
}
|
|
5262
|
+
static deleted(listener) {
|
|
5263
|
+
this.event("deleted", listener);
|
|
5264
|
+
}
|
|
5265
|
+
static restoring(listener) {
|
|
5266
|
+
this.event("restoring", listener);
|
|
5267
|
+
}
|
|
5268
|
+
static restored(listener) {
|
|
5269
|
+
this.event("restored", listener);
|
|
5270
|
+
}
|
|
5271
|
+
static forceDeleting(listener) {
|
|
5272
|
+
this.event("forceDeleting", listener);
|
|
5273
|
+
}
|
|
5274
|
+
static forceDeleted(listener) {
|
|
5275
|
+
this.event("forceDeleted", listener);
|
|
5276
|
+
}
|
|
5277
|
+
/**
|
|
5021
5278
|
* Remove listeners for an event. If listener is omitted, all listeners for that event are removed.
|
|
5022
5279
|
*
|
|
5023
5280
|
* @param event
|
|
@@ -5038,6 +5295,34 @@ var Model = class Model {
|
|
|
5038
5295
|
this.eventListeners = {};
|
|
5039
5296
|
}
|
|
5040
5297
|
/**
|
|
5298
|
+
* Execute a callback while suppressing lifecycle events for all models.
|
|
5299
|
+
*
|
|
5300
|
+
* @param callback
|
|
5301
|
+
* @returns
|
|
5302
|
+
*/
|
|
5303
|
+
static async withoutEvents(callback) {
|
|
5304
|
+
Model.eventsSuppressed += 1;
|
|
5305
|
+
try {
|
|
5306
|
+
return await callback();
|
|
5307
|
+
} finally {
|
|
5308
|
+
Model.eventsSuppressed = Math.max(0, Model.eventsSuppressed - 1);
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
/**
|
|
5312
|
+
* Execute a callback within a transaction scope.
|
|
5313
|
+
* Nested calls reuse the active transaction client.
|
|
5314
|
+
*
|
|
5315
|
+
* @param callback
|
|
5316
|
+
* @param options
|
|
5317
|
+
* @returns
|
|
5318
|
+
*/
|
|
5319
|
+
static async transaction(callback, options = {}) {
|
|
5320
|
+
ensureArkormConfigLoading();
|
|
5321
|
+
return await runArkormTransaction(async (client) => {
|
|
5322
|
+
return await callback(client);
|
|
5323
|
+
}, options);
|
|
5324
|
+
}
|
|
5325
|
+
/**
|
|
5041
5326
|
* Get the Prisma delegate for the model.
|
|
5042
5327
|
* If a delegate name is provided, it will attempt to resolve that delegate.
|
|
5043
5328
|
* Otherwise, it will attempt to resolve a delegate based on the model's name or
|
|
@@ -5055,9 +5340,20 @@ var Model = class Model {
|
|
|
5055
5340
|
`${(0, _h3ravel_support.str)(key).singular()}`,
|
|
5056
5341
|
`${(0, _h3ravel_support.str)(key).camel().singular()}`
|
|
5057
5342
|
];
|
|
5343
|
+
const activeTransactionClient = getActiveTransactionClient();
|
|
5058
5344
|
const runtimeClient = getRuntimePrismaClient();
|
|
5059
|
-
const
|
|
5060
|
-
|
|
5345
|
+
const sources = activeTransactionClient ? [
|
|
5346
|
+
activeTransactionClient,
|
|
5347
|
+
this.client,
|
|
5348
|
+
runtimeClient
|
|
5349
|
+
] : [this.client, runtimeClient];
|
|
5350
|
+
const resolved = candidates.flatMap((name) => sources.map((source) => source?.[name])).find((candidate) => isDelegateLike(candidate));
|
|
5351
|
+
if (!resolved) throw new MissingDelegateException(`Database delegate [${key}] is not configured.`, {
|
|
5352
|
+
operation: "getDelegate",
|
|
5353
|
+
model: this.name,
|
|
5354
|
+
delegate: key,
|
|
5355
|
+
meta: { candidates }
|
|
5356
|
+
});
|
|
5061
5357
|
return resolved;
|
|
5062
5358
|
}
|
|
5063
5359
|
/**
|
|
@@ -5067,16 +5363,27 @@ var Model = class Model {
|
|
|
5067
5363
|
* @returns
|
|
5068
5364
|
*/
|
|
5069
5365
|
static query() {
|
|
5366
|
+
Model.ensureModelBooted(this);
|
|
5070
5367
|
let builder = new QueryBuilder(this.getDelegate(), this);
|
|
5071
5368
|
const modelClass = this;
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5369
|
+
if (!Model.areGlobalScopesSuppressed(modelClass)) {
|
|
5370
|
+
modelClass.ensureOwnGlobalScopes();
|
|
5371
|
+
Object.values(modelClass.globalScopes).forEach((scope) => {
|
|
5372
|
+
const scoped = scope(builder);
|
|
5373
|
+
if (scoped && scoped !== builder) builder = scoped;
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5077
5376
|
return builder;
|
|
5078
5377
|
}
|
|
5079
5378
|
/**
|
|
5379
|
+
* Boot hook for subclasses to register scopes or perform one-time setup.
|
|
5380
|
+
*/
|
|
5381
|
+
static boot() {}
|
|
5382
|
+
/**
|
|
5383
|
+
* Booted hook for subclasses to register callbacks after boot logic runs.
|
|
5384
|
+
*/
|
|
5385
|
+
static booted() {}
|
|
5386
|
+
/**
|
|
5080
5387
|
* Get a query builder instance that includes soft-deleted records.
|
|
5081
5388
|
*
|
|
5082
5389
|
* @param this
|
|
@@ -5127,7 +5434,10 @@ var Model = class Model {
|
|
|
5127
5434
|
* @returns
|
|
5128
5435
|
*/
|
|
5129
5436
|
static hydrate(attributes) {
|
|
5130
|
-
|
|
5437
|
+
const model = new this(attributes);
|
|
5438
|
+
model.syncOriginal();
|
|
5439
|
+
model.syncChanges({});
|
|
5440
|
+
return model;
|
|
5131
5441
|
}
|
|
5132
5442
|
/**
|
|
5133
5443
|
* Hydrate multiple model instances from an array of plain objects of attributes.
|
|
@@ -5139,6 +5449,36 @@ var Model = class Model {
|
|
|
5139
5449
|
static hydrateMany(attributes) {
|
|
5140
5450
|
return attributes.map((attribute) => new this(attribute));
|
|
5141
5451
|
}
|
|
5452
|
+
/**
|
|
5453
|
+
* Hydrate a model instance and dispatch the retrieved lifecycle event.
|
|
5454
|
+
*
|
|
5455
|
+
* @param this
|
|
5456
|
+
* @param attributes
|
|
5457
|
+
* @returns
|
|
5458
|
+
*/
|
|
5459
|
+
static async hydrateRetrieved(attributes) {
|
|
5460
|
+
Model.ensureModelBooted(this);
|
|
5461
|
+
if (!Model.hasEventListeners(this, "retrieved")) return this.hydrate(attributes);
|
|
5462
|
+
const model = this.hydrate(attributes);
|
|
5463
|
+
await Model.dispatchEvent(this, "retrieved", model);
|
|
5464
|
+
return model;
|
|
5465
|
+
}
|
|
5466
|
+
/**
|
|
5467
|
+
* Hydrate multiple model instances and dispatch the retrieved lifecycle event for each.
|
|
5468
|
+
*
|
|
5469
|
+
* @param this
|
|
5470
|
+
* @param attributes
|
|
5471
|
+
* @returns
|
|
5472
|
+
*/
|
|
5473
|
+
static async hydrateManyRetrieved(attributes) {
|
|
5474
|
+
Model.ensureModelBooted(this);
|
|
5475
|
+
if (!Model.hasEventListeners(this, "retrieved")) return this.hydrateMany(attributes);
|
|
5476
|
+
const models = this.hydrateMany(attributes);
|
|
5477
|
+
await Promise.all(models.map(async (model) => {
|
|
5478
|
+
await Model.dispatchEvent(this, "retrieved", model);
|
|
5479
|
+
}));
|
|
5480
|
+
return models;
|
|
5481
|
+
}
|
|
5142
5482
|
fill(attributes) {
|
|
5143
5483
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
5144
5484
|
this.setAttribute(key, value);
|
|
@@ -5164,6 +5504,7 @@ var Model = class Model {
|
|
|
5164
5504
|
else if (mutator) resolved = mutator.call(this, resolved);
|
|
5165
5505
|
if (cast) resolved = resolveCast(cast).set(resolved);
|
|
5166
5506
|
this.attributes[key] = resolved;
|
|
5507
|
+
this.touchedAttributes.add(key);
|
|
5167
5508
|
return this;
|
|
5168
5509
|
}
|
|
5169
5510
|
/**
|
|
@@ -5176,12 +5517,15 @@ var Model = class Model {
|
|
|
5176
5517
|
async save() {
|
|
5177
5518
|
const identifier = this.getAttribute("id");
|
|
5178
5519
|
const payload = this.getRawAttributes();
|
|
5520
|
+
const previousOriginal = this.getOriginal();
|
|
5179
5521
|
const constructor = this.constructor;
|
|
5180
5522
|
if (identifier == null) {
|
|
5181
5523
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
5182
5524
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
5183
5525
|
const model = await constructor.query().create(payload);
|
|
5184
5526
|
this.fill(model.getRawAttributes());
|
|
5527
|
+
this.syncChanges(previousOriginal);
|
|
5528
|
+
this.syncOriginal();
|
|
5185
5529
|
await Model.dispatchEvent(constructor, "created", this);
|
|
5186
5530
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5187
5531
|
return this;
|
|
@@ -5190,11 +5534,21 @@ var Model = class Model {
|
|
|
5190
5534
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
5191
5535
|
const model = await constructor.query().where({ id: identifier }).update(payload);
|
|
5192
5536
|
this.fill(model.getRawAttributes());
|
|
5537
|
+
this.syncChanges(previousOriginal);
|
|
5538
|
+
this.syncOriginal();
|
|
5193
5539
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
5194
5540
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5195
5541
|
return this;
|
|
5196
5542
|
}
|
|
5197
5543
|
/**
|
|
5544
|
+
* Save the model without dispatching lifecycle events.
|
|
5545
|
+
*
|
|
5546
|
+
* @returns
|
|
5547
|
+
*/
|
|
5548
|
+
async saveQuietly() {
|
|
5549
|
+
return await Model.withoutEvents(() => this.save());
|
|
5550
|
+
}
|
|
5551
|
+
/**
|
|
5198
5552
|
* Delete the model from the database.
|
|
5199
5553
|
* If soft deletes are enabled, it will perform a soft delete by
|
|
5200
5554
|
* setting the deleted at column to the current date.
|
|
@@ -5205,21 +5559,34 @@ var Model = class Model {
|
|
|
5205
5559
|
async delete() {
|
|
5206
5560
|
const identifier = this.getAttribute("id");
|
|
5207
5561
|
if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
|
|
5562
|
+
const previousOriginal = this.getOriginal();
|
|
5208
5563
|
const constructor = this.constructor;
|
|
5209
5564
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5210
5565
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5211
5566
|
if (softDeleteConfig.enabled) {
|
|
5212
5567
|
const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
|
|
5213
5568
|
this.fill(model.getRawAttributes());
|
|
5569
|
+
this.syncChanges(previousOriginal);
|
|
5570
|
+
this.syncOriginal();
|
|
5214
5571
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5215
5572
|
return this;
|
|
5216
5573
|
}
|
|
5217
5574
|
const deleted = await constructor.query().where({ id: identifier }).delete();
|
|
5218
5575
|
this.fill(deleted.getRawAttributes());
|
|
5576
|
+
this.syncChanges(previousOriginal);
|
|
5577
|
+
this.syncOriginal();
|
|
5219
5578
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5220
5579
|
return this;
|
|
5221
5580
|
}
|
|
5222
5581
|
/**
|
|
5582
|
+
* Delete the model without dispatching lifecycle events.
|
|
5583
|
+
*
|
|
5584
|
+
* @returns
|
|
5585
|
+
*/
|
|
5586
|
+
async deleteQuietly() {
|
|
5587
|
+
return await Model.withoutEvents(() => this.delete());
|
|
5588
|
+
}
|
|
5589
|
+
/**
|
|
5223
5590
|
* Permanently delete the model from the database, regardless of whether soft
|
|
5224
5591
|
* deletes are enabled.
|
|
5225
5592
|
*
|
|
@@ -5228,16 +5595,27 @@ var Model = class Model {
|
|
|
5228
5595
|
async forceDelete() {
|
|
5229
5596
|
const identifier = this.getAttribute("id");
|
|
5230
5597
|
if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
|
|
5598
|
+
const previousOriginal = this.getOriginal();
|
|
5231
5599
|
const constructor = this.constructor;
|
|
5232
5600
|
await Model.dispatchEvent(constructor, "forceDeleting", this);
|
|
5233
5601
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5234
5602
|
const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
|
|
5235
5603
|
this.fill(deleted.getRawAttributes());
|
|
5604
|
+
this.syncChanges(previousOriginal);
|
|
5605
|
+
this.syncOriginal();
|
|
5236
5606
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5237
5607
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
5238
5608
|
return this;
|
|
5239
5609
|
}
|
|
5240
5610
|
/**
|
|
5611
|
+
* Force delete the model without dispatching lifecycle events.
|
|
5612
|
+
*
|
|
5613
|
+
* @returns
|
|
5614
|
+
*/
|
|
5615
|
+
async forceDeleteQuietly() {
|
|
5616
|
+
return await Model.withoutEvents(() => this.forceDelete());
|
|
5617
|
+
}
|
|
5618
|
+
/**
|
|
5241
5619
|
* Restore a soft-deleted model by setting the deleted at column to null.
|
|
5242
5620
|
*
|
|
5243
5621
|
* @returns
|
|
@@ -5248,13 +5626,24 @@ var Model = class Model {
|
|
|
5248
5626
|
const constructor = this.constructor;
|
|
5249
5627
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5250
5628
|
if (!softDeleteConfig.enabled) return this;
|
|
5629
|
+
const previousOriginal = this.getOriginal();
|
|
5251
5630
|
await Model.dispatchEvent(constructor, "restoring", this);
|
|
5252
5631
|
const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
|
|
5253
5632
|
this.fill(model.getRawAttributes());
|
|
5633
|
+
this.syncChanges(previousOriginal);
|
|
5634
|
+
this.syncOriginal();
|
|
5254
5635
|
await Model.dispatchEvent(constructor, "restored", this);
|
|
5255
5636
|
return this;
|
|
5256
5637
|
}
|
|
5257
5638
|
/**
|
|
5639
|
+
* Restore the model without dispatching lifecycle events.
|
|
5640
|
+
*
|
|
5641
|
+
* @returns
|
|
5642
|
+
*/
|
|
5643
|
+
async restoreQuietly() {
|
|
5644
|
+
return await Model.withoutEvents(() => this.restore());
|
|
5645
|
+
}
|
|
5646
|
+
/**
|
|
5258
5647
|
* Load related models onto the current model instance.
|
|
5259
5648
|
*
|
|
5260
5649
|
* @param relations
|
|
@@ -5280,6 +5669,42 @@ var Model = class Model {
|
|
|
5280
5669
|
getRawAttributes() {
|
|
5281
5670
|
return { ...this.attributes };
|
|
5282
5671
|
}
|
|
5672
|
+
getOriginal(key) {
|
|
5673
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.original[key]);
|
|
5674
|
+
return Object.entries(this.original).reduce((all, [originalKey, value]) => {
|
|
5675
|
+
all[originalKey] = Model.cloneAttributeValue(value);
|
|
5676
|
+
return all;
|
|
5677
|
+
}, {});
|
|
5678
|
+
}
|
|
5679
|
+
/**
|
|
5680
|
+
* Determine whether the model has unsaved attribute changes.
|
|
5681
|
+
*
|
|
5682
|
+
* @param keys
|
|
5683
|
+
* @returns
|
|
5684
|
+
*/
|
|
5685
|
+
isDirty(keys) {
|
|
5686
|
+
return Object.keys(this.getDirtyAttributes(keys)).length > 0;
|
|
5687
|
+
}
|
|
5688
|
+
/**
|
|
5689
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
5690
|
+
*
|
|
5691
|
+
* @param keys
|
|
5692
|
+
* @returns
|
|
5693
|
+
*/
|
|
5694
|
+
isClean(keys) {
|
|
5695
|
+
return !this.isDirty(keys);
|
|
5696
|
+
}
|
|
5697
|
+
/**
|
|
5698
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
5699
|
+
*
|
|
5700
|
+
* @param keys
|
|
5701
|
+
* @returns
|
|
5702
|
+
*/
|
|
5703
|
+
wasChanged(keys) {
|
|
5704
|
+
const keyList = this.normalizeAttributeKeys(keys);
|
|
5705
|
+
if (keyList.length === 0) return Object.keys(this.changes).length > 0;
|
|
5706
|
+
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
5707
|
+
}
|
|
5283
5708
|
/**
|
|
5284
5709
|
* Convert the model instance to a plain object, applying visibility
|
|
5285
5710
|
* rules, appends, and mutators.
|
|
@@ -5307,6 +5732,47 @@ var Model = class Model {
|
|
|
5307
5732
|
return this.toObject();
|
|
5308
5733
|
}
|
|
5309
5734
|
/**
|
|
5735
|
+
* Determine if another model represents the same persisted record.
|
|
5736
|
+
*
|
|
5737
|
+
* @param model
|
|
5738
|
+
* @returns
|
|
5739
|
+
*/
|
|
5740
|
+
is(model) {
|
|
5741
|
+
if (!(model instanceof Model)) return false;
|
|
5742
|
+
if (this.constructor !== model.constructor) return false;
|
|
5743
|
+
const identifier = this.getAttribute("id");
|
|
5744
|
+
const otherIdentifier = model.getAttribute("id");
|
|
5745
|
+
if (identifier == null || otherIdentifier == null) return false;
|
|
5746
|
+
return identifier === otherIdentifier;
|
|
5747
|
+
}
|
|
5748
|
+
/**
|
|
5749
|
+
* Determine if another model does not represent the same persisted record.
|
|
5750
|
+
*
|
|
5751
|
+
* @param model
|
|
5752
|
+
* @returns
|
|
5753
|
+
*/
|
|
5754
|
+
isNot(model) {
|
|
5755
|
+
return !this.is(model);
|
|
5756
|
+
}
|
|
5757
|
+
/**
|
|
5758
|
+
* Determine if another model is the same in-memory instance.
|
|
5759
|
+
*
|
|
5760
|
+
* @param model
|
|
5761
|
+
* @returns
|
|
5762
|
+
*/
|
|
5763
|
+
isSame(model) {
|
|
5764
|
+
return this === model;
|
|
5765
|
+
}
|
|
5766
|
+
/**
|
|
5767
|
+
* Determine if another model is not the same in-memory instance.
|
|
5768
|
+
*
|
|
5769
|
+
* @param model
|
|
5770
|
+
* @returns
|
|
5771
|
+
*/
|
|
5772
|
+
isNotSame(model) {
|
|
5773
|
+
return !this.isSame(model);
|
|
5774
|
+
}
|
|
5775
|
+
/**
|
|
5310
5776
|
* Define a has one relationship.
|
|
5311
5777
|
*
|
|
5312
5778
|
* @param related
|
|
@@ -5429,6 +5895,34 @@ var Model = class Model {
|
|
|
5429
5895
|
return typeof method === "function" ? method : null;
|
|
5430
5896
|
}
|
|
5431
5897
|
/**
|
|
5898
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
5899
|
+
*
|
|
5900
|
+
* @param keys
|
|
5901
|
+
* @returns
|
|
5902
|
+
*/
|
|
5903
|
+
getDirtyAttributes(keys) {
|
|
5904
|
+
const requestedKeys = this.normalizeAttributeKeys(keys);
|
|
5905
|
+
return (requestedKeys.length > 0 ? requestedKeys : Array.from(new Set([...Object.keys(this.original), ...this.touchedAttributes]))).reduce((dirty, key) => {
|
|
5906
|
+
const currentValue = this.attributes[key];
|
|
5907
|
+
const originalValue = this.original[key];
|
|
5908
|
+
const hasCurrent = Object.prototype.hasOwnProperty.call(this.attributes, key);
|
|
5909
|
+
const hasOriginal = Object.prototype.hasOwnProperty.call(this.original, key);
|
|
5910
|
+
if (!hasCurrent && !hasOriginal) return dirty;
|
|
5911
|
+
if (hasCurrent !== hasOriginal || !Model.areAttributeValuesEqual(currentValue, originalValue)) dirty[key] = Model.cloneAttributeValue(currentValue);
|
|
5912
|
+
return dirty;
|
|
5913
|
+
}, {});
|
|
5914
|
+
}
|
|
5915
|
+
/**
|
|
5916
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
5917
|
+
*
|
|
5918
|
+
* @param keys
|
|
5919
|
+
* @returns
|
|
5920
|
+
*/
|
|
5921
|
+
normalizeAttributeKeys(keys) {
|
|
5922
|
+
if (typeof keys === "undefined") return [];
|
|
5923
|
+
return Array.isArray(keys) ? keys : [keys];
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5432
5926
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
5433
5927
|
*
|
|
5434
5928
|
* @param key
|
|
@@ -5470,6 +5964,144 @@ var Model = class Model {
|
|
|
5470
5964
|
if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
|
|
5471
5965
|
}
|
|
5472
5966
|
/**
|
|
5967
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
5968
|
+
*
|
|
5969
|
+
* @param value
|
|
5970
|
+
* @returns
|
|
5971
|
+
*/
|
|
5972
|
+
static cloneAttributeValue(value) {
|
|
5973
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
5974
|
+
if (Array.isArray(value)) return value.map((item) => Model.cloneAttributeValue(item));
|
|
5975
|
+
if (value && typeof value === "object") return Object.entries(value).reduce((all, [key, nestedValue]) => {
|
|
5976
|
+
all[key] = Model.cloneAttributeValue(nestedValue);
|
|
5977
|
+
return all;
|
|
5978
|
+
}, {});
|
|
5979
|
+
return value;
|
|
5980
|
+
}
|
|
5981
|
+
/**
|
|
5982
|
+
* Compare attribute values for dirty/change detection.
|
|
5983
|
+
*
|
|
5984
|
+
* @param left
|
|
5985
|
+
* @param right
|
|
5986
|
+
* @returns
|
|
5987
|
+
*/
|
|
5988
|
+
static areAttributeValuesEqual(left, right) {
|
|
5989
|
+
if (left === right) return true;
|
|
5990
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
5991
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
5992
|
+
if (left.length !== right.length) return false;
|
|
5993
|
+
return left.every((value, index) => Model.areAttributeValuesEqual(value, right[index]));
|
|
5994
|
+
}
|
|
5995
|
+
if (left && right && typeof left === "object" && typeof right === "object") {
|
|
5996
|
+
const leftEntries = Object.entries(left);
|
|
5997
|
+
const rightEntries = Object.entries(right);
|
|
5998
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
5999
|
+
return leftEntries.every(([key, value]) => {
|
|
6000
|
+
return Object.prototype.hasOwnProperty.call(right, key) && Model.areAttributeValuesEqual(value, right[key]);
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
return false;
|
|
6004
|
+
}
|
|
6005
|
+
/**
|
|
6006
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
6007
|
+
*/
|
|
6008
|
+
syncOriginal() {
|
|
6009
|
+
this.original = Object.entries(this.attributes).reduce((all, [key, value]) => {
|
|
6010
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
6011
|
+
return all;
|
|
6012
|
+
}, {});
|
|
6013
|
+
this.touchedAttributes.clear();
|
|
6014
|
+
}
|
|
6015
|
+
/**
|
|
6016
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
6017
|
+
*
|
|
6018
|
+
* @param previousOriginal
|
|
6019
|
+
*/
|
|
6020
|
+
syncChanges(previousOriginal) {
|
|
6021
|
+
this.changes = Object.entries(this.getDirtyAttributes()).reduce((all, [key, value]) => {
|
|
6022
|
+
if (!Object.prototype.hasOwnProperty.call(previousOriginal, key) || !Model.areAttributeValuesEqual(value, previousOriginal[key])) all[key] = Model.cloneAttributeValue(value);
|
|
6023
|
+
return all;
|
|
6024
|
+
}, {});
|
|
6025
|
+
}
|
|
6026
|
+
/**
|
|
6027
|
+
* Resolve lifecycle state for the provided model class.
|
|
6028
|
+
*
|
|
6029
|
+
* @param modelClass
|
|
6030
|
+
* @returns
|
|
6031
|
+
*/
|
|
6032
|
+
static getLifecycleState(modelClass) {
|
|
6033
|
+
const existing = Model.lifecycleStates.get(modelClass);
|
|
6034
|
+
if (existing) return existing;
|
|
6035
|
+
const state = {
|
|
6036
|
+
booted: false,
|
|
6037
|
+
booting: false,
|
|
6038
|
+
globalScopesSuppressed: 0
|
|
6039
|
+
};
|
|
6040
|
+
Model.lifecycleStates.set(modelClass, state);
|
|
6041
|
+
return state;
|
|
6042
|
+
}
|
|
6043
|
+
/**
|
|
6044
|
+
* Ensure the target model class has executed its boot lifecycle.
|
|
6045
|
+
*
|
|
6046
|
+
* @param modelClass
|
|
6047
|
+
*/
|
|
6048
|
+
static ensureModelBooted(modelClass) {
|
|
6049
|
+
const state = Model.getLifecycleState(modelClass);
|
|
6050
|
+
if (state.booted || state.booting) return;
|
|
6051
|
+
state.booting = true;
|
|
6052
|
+
try {
|
|
6053
|
+
const boot = modelClass.boot;
|
|
6054
|
+
if (boot !== Model.boot) boot.call(modelClass);
|
|
6055
|
+
const booted = modelClass.booted;
|
|
6056
|
+
if (booted !== Model.booted) booted.call(modelClass);
|
|
6057
|
+
state.booted = true;
|
|
6058
|
+
} finally {
|
|
6059
|
+
state.booting = false;
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
/**
|
|
6063
|
+
* Determine if global scopes are currently suppressed for the model class.
|
|
6064
|
+
*
|
|
6065
|
+
* @param modelClass
|
|
6066
|
+
* @returns
|
|
6067
|
+
*/
|
|
6068
|
+
static areGlobalScopesSuppressed(modelClass) {
|
|
6069
|
+
return Model.getLifecycleState(modelClass).globalScopesSuppressed > 0;
|
|
6070
|
+
}
|
|
6071
|
+
/**
|
|
6072
|
+
* Resolve configured class-based event handlers for a lifecycle event.
|
|
6073
|
+
*
|
|
6074
|
+
* @param modelClass
|
|
6075
|
+
* @param event
|
|
6076
|
+
* @returns
|
|
6077
|
+
*/
|
|
6078
|
+
static resolveDispatchedEventListeners(modelClass, event) {
|
|
6079
|
+
const configured = modelClass.dispatchesEvents[event];
|
|
6080
|
+
if (!configured) return [];
|
|
6081
|
+
return (Array.isArray(configured) ? configured : [configured]).map((entry) => {
|
|
6082
|
+
const handler = typeof entry === "function" ? new entry() : entry;
|
|
6083
|
+
if (!handler || typeof handler.handle !== "function") throw new ArkormException(`Invalid event handler configured for [${modelClass.name}.${event}].`);
|
|
6084
|
+
return async (model) => {
|
|
6085
|
+
await handler.handle(model);
|
|
6086
|
+
};
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
/**
|
|
6090
|
+
* Determine whether a lifecycle event has any registered listeners.
|
|
6091
|
+
*
|
|
6092
|
+
* @param modelClass
|
|
6093
|
+
* @param event
|
|
6094
|
+
* @returns
|
|
6095
|
+
*/
|
|
6096
|
+
static hasEventListeners(modelClass, event) {
|
|
6097
|
+
if (Model.eventsSuppressed > 0) return false;
|
|
6098
|
+
modelClass.ensureOwnEventListeners();
|
|
6099
|
+
if ((modelClass.eventListeners[event] || []).length > 0) return true;
|
|
6100
|
+
const configuredDispatchers = modelClass.dispatchesEvents[event];
|
|
6101
|
+
if (!configuredDispatchers) return false;
|
|
6102
|
+
return Array.isArray(configuredDispatchers) ? configuredDispatchers.length > 0 : true;
|
|
6103
|
+
}
|
|
6104
|
+
/**
|
|
5473
6105
|
* Dispatches lifecycle events to registered listeners.
|
|
5474
6106
|
*
|
|
5475
6107
|
* @param modelClass
|
|
@@ -5477,8 +6109,9 @@ var Model = class Model {
|
|
|
5477
6109
|
* @param model
|
|
5478
6110
|
*/
|
|
5479
6111
|
static async dispatchEvent(modelClass, event, model) {
|
|
5480
|
-
|
|
5481
|
-
|
|
6112
|
+
Model.ensureModelBooted(modelClass);
|
|
6113
|
+
if (!Model.hasEventListeners(modelClass, event)) return;
|
|
6114
|
+
const listeners = [...Model.resolveDispatchedEventListeners(modelClass, event), ...modelClass.eventListeners[event] || []];
|
|
5482
6115
|
for (const listener of listeners) await listener(model);
|
|
5483
6116
|
}
|
|
5484
6117
|
/**
|
|
@@ -5515,6 +6148,7 @@ exports.MigrateCommand = MigrateCommand;
|
|
|
5515
6148
|
exports.MigrateRollbackCommand = MigrateRollbackCommand;
|
|
5516
6149
|
exports.Migration = Migration;
|
|
5517
6150
|
exports.MigrationHistoryCommand = MigrationHistoryCommand;
|
|
6151
|
+
exports.MissingDelegateException = MissingDelegateException;
|
|
5518
6152
|
exports.Model = Model;
|
|
5519
6153
|
exports.ModelFactory = ModelFactory;
|
|
5520
6154
|
exports.ModelNotFoundException = ModelNotFoundException;
|
|
@@ -5522,12 +6156,17 @@ exports.ModelsSyncCommand = ModelsSyncCommand;
|
|
|
5522
6156
|
exports.PRISMA_MODEL_REGEX = PRISMA_MODEL_REGEX;
|
|
5523
6157
|
exports.Paginator = Paginator;
|
|
5524
6158
|
exports.QueryBuilder = QueryBuilder;
|
|
6159
|
+
exports.QueryConstraintException = QueryConstraintException;
|
|
6160
|
+
exports.RelationResolutionException = RelationResolutionException;
|
|
5525
6161
|
exports.SEEDER_BRAND = SEEDER_BRAND;
|
|
5526
6162
|
exports.SchemaBuilder = SchemaBuilder;
|
|
6163
|
+
exports.ScopeNotDefinedException = ScopeNotDefinedException;
|
|
5527
6164
|
exports.SeedCommand = SeedCommand;
|
|
5528
6165
|
exports.Seeder = Seeder;
|
|
5529
6166
|
exports.TableBuilder = TableBuilder;
|
|
5530
6167
|
exports.URLDriver = URLDriver;
|
|
6168
|
+
exports.UniqueConstraintResolutionException = UniqueConstraintResolutionException;
|
|
6169
|
+
exports.UnsupportedAdapterFeatureException = UnsupportedAdapterFeatureException;
|
|
5531
6170
|
exports.applyAlterTableOperation = applyAlterTableOperation;
|
|
5532
6171
|
exports.applyCreateTableOperation = applyCreateTableOperation;
|
|
5533
6172
|
exports.applyDropTableOperation = applyDropTableOperation;
|
|
@@ -5559,6 +6198,7 @@ exports.findModelBlock = findModelBlock;
|
|
|
5559
6198
|
exports.formatDefaultValue = formatDefaultValue;
|
|
5560
6199
|
exports.formatRelationAction = formatRelationAction;
|
|
5561
6200
|
exports.generateMigrationFile = generateMigrationFile;
|
|
6201
|
+
exports.getActiveTransactionClient = getActiveTransactionClient;
|
|
5562
6202
|
exports.getDefaultStubsPath = getDefaultStubsPath;
|
|
5563
6203
|
exports.getLastMigrationRun = getLastMigrationRun;
|
|
5564
6204
|
exports.getLatestAppliedMigrations = getLatestAppliedMigrations;
|
|
@@ -5570,6 +6210,7 @@ exports.getUserConfig = getUserConfig;
|
|
|
5570
6210
|
exports.inferDelegateName = inferDelegateName;
|
|
5571
6211
|
exports.isDelegateLike = isDelegateLike;
|
|
5572
6212
|
exports.isMigrationApplied = isMigrationApplied;
|
|
6213
|
+
exports.isTransactionCapableClient = isTransactionCapableClient;
|
|
5573
6214
|
exports.loadArkormConfig = loadArkormConfig;
|
|
5574
6215
|
exports.markMigrationApplied = markMigrationApplied;
|
|
5575
6216
|
exports.markMigrationRun = markMigrationRun;
|
|
@@ -5581,6 +6222,7 @@ exports.resolveCast = resolveCast;
|
|
|
5581
6222
|
exports.resolveMigrationClassName = resolveMigrationClassName;
|
|
5582
6223
|
exports.resolveMigrationStateFilePath = resolveMigrationStateFilePath;
|
|
5583
6224
|
exports.resolvePrismaType = resolvePrismaType;
|
|
6225
|
+
exports.runArkormTransaction = runArkormTransaction;
|
|
5584
6226
|
exports.runMigrationWithPrisma = runMigrationWithPrisma;
|
|
5585
6227
|
exports.runPrismaCommand = runPrismaCommand;
|
|
5586
6228
|
exports.toMigrationFileSlug = toMigrationFileSlug;
|