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.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { str } from "@h3ravel/support";
|
|
|
5
5
|
import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
|
|
6
6
|
import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
8
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
8
9
|
import { createRequire } from "module";
|
|
9
10
|
import { Logger } from "@h3ravel/shared";
|
|
10
11
|
import { Command } from "@h3ravel/musket";
|
|
@@ -93,17 +94,43 @@ function resolveCast(definition) {
|
|
|
93
94
|
|
|
94
95
|
//#endregion
|
|
95
96
|
//#region src/Exceptions/ArkormException.ts
|
|
96
|
-
/**
|
|
97
|
-
* The ArkormException class is a custom error type for handling
|
|
98
|
-
* exceptions specific to the Arkormˣ.
|
|
99
|
-
*
|
|
100
|
-
* @author Legacy (3m1n3nc3)
|
|
101
|
-
* @since 0.1.0
|
|
102
|
-
*/
|
|
103
97
|
var ArkormException = class extends Error {
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
code;
|
|
99
|
+
operation;
|
|
100
|
+
model;
|
|
101
|
+
delegate;
|
|
102
|
+
relation;
|
|
103
|
+
scope;
|
|
104
|
+
meta;
|
|
105
|
+
constructor(message, context = {}) {
|
|
106
|
+
super(message, context.cause === void 0 ? void 0 : { cause: context.cause });
|
|
106
107
|
this.name = "ArkormException";
|
|
108
|
+
this.code = context.code;
|
|
109
|
+
this.operation = context.operation;
|
|
110
|
+
this.model = context.model;
|
|
111
|
+
this.delegate = context.delegate;
|
|
112
|
+
this.relation = context.relation;
|
|
113
|
+
this.scope = context.scope;
|
|
114
|
+
this.meta = context.meta;
|
|
115
|
+
}
|
|
116
|
+
getContext() {
|
|
117
|
+
return {
|
|
118
|
+
code: this.code,
|
|
119
|
+
operation: this.operation,
|
|
120
|
+
model: this.model,
|
|
121
|
+
delegate: this.delegate,
|
|
122
|
+
relation: this.relation,
|
|
123
|
+
scope: this.scope,
|
|
124
|
+
meta: this.meta,
|
|
125
|
+
cause: this.cause
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
toJSON() {
|
|
129
|
+
return {
|
|
130
|
+
name: this.name,
|
|
131
|
+
message: this.message,
|
|
132
|
+
...this.getContext()
|
|
133
|
+
};
|
|
107
134
|
}
|
|
108
135
|
};
|
|
109
136
|
|
|
@@ -1213,6 +1240,18 @@ const runMigrationWithPrisma = async (migration, options = {}) => {
|
|
|
1213
1240
|
};
|
|
1214
1241
|
};
|
|
1215
1242
|
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/Exceptions/UnsupportedAdapterFeatureException.ts
|
|
1245
|
+
var UnsupportedAdapterFeatureException = class extends ArkormException {
|
|
1246
|
+
constructor(message, context = {}) {
|
|
1247
|
+
super(message, {
|
|
1248
|
+
code: "UNSUPPORTED_ADAPTER_FEATURE",
|
|
1249
|
+
...context
|
|
1250
|
+
});
|
|
1251
|
+
this.name = "UnsupportedAdapterFeatureException";
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1216
1255
|
//#endregion
|
|
1217
1256
|
//#region src/helpers/runtime-config.ts
|
|
1218
1257
|
const resolveDefaultStubsPath = () => {
|
|
@@ -1247,6 +1286,7 @@ let runtimeConfigLoadingPromise;
|
|
|
1247
1286
|
let runtimeClientResolver;
|
|
1248
1287
|
let runtimePaginationURLDriverFactory;
|
|
1249
1288
|
let runtimePaginationCurrentPageResolver;
|
|
1289
|
+
const transactionClientStorage = new AsyncLocalStorage();
|
|
1250
1290
|
const mergePathConfig = (paths) => {
|
|
1251
1291
|
const defaults = baseConfig.paths ?? {};
|
|
1252
1292
|
const current = userConfig.paths ?? {};
|
|
@@ -1414,9 +1454,36 @@ const getDefaultStubsPath = () => {
|
|
|
1414
1454
|
* @returns
|
|
1415
1455
|
*/
|
|
1416
1456
|
const getRuntimePrismaClient = () => {
|
|
1457
|
+
const activeTransactionClient = transactionClientStorage.getStore();
|
|
1458
|
+
if (activeTransactionClient) return activeTransactionClient;
|
|
1417
1459
|
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
1418
1460
|
return resolveClient(runtimeClientResolver);
|
|
1419
1461
|
};
|
|
1462
|
+
const getActiveTransactionClient = () => {
|
|
1463
|
+
return transactionClientStorage.getStore();
|
|
1464
|
+
};
|
|
1465
|
+
const isTransactionCapableClient = (value) => {
|
|
1466
|
+
if (!value || typeof value !== "object") return false;
|
|
1467
|
+
return typeof value.$transaction === "function";
|
|
1468
|
+
};
|
|
1469
|
+
const runArkormTransaction = async (callback, options = {}) => {
|
|
1470
|
+
const activeTransactionClient = transactionClientStorage.getStore();
|
|
1471
|
+
if (activeTransactionClient) return await callback(activeTransactionClient);
|
|
1472
|
+
const client = getRuntimePrismaClient();
|
|
1473
|
+
if (!client) throw new ArkormException("Cannot start a transaction without a configured Prisma client.", {
|
|
1474
|
+
code: "CLIENT_NOT_CONFIGURED",
|
|
1475
|
+
operation: "transaction"
|
|
1476
|
+
});
|
|
1477
|
+
if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
|
|
1478
|
+
code: "TRANSACTION_NOT_SUPPORTED",
|
|
1479
|
+
operation: "transaction"
|
|
1480
|
+
});
|
|
1481
|
+
return await client.$transaction(async (transactionClient) => {
|
|
1482
|
+
return await transactionClientStorage.run(transactionClient, async () => {
|
|
1483
|
+
return await callback(transactionClient);
|
|
1484
|
+
});
|
|
1485
|
+
}, options);
|
|
1486
|
+
};
|
|
1420
1487
|
/**
|
|
1421
1488
|
* Get the configured pagination URL driver factory from runtime config.
|
|
1422
1489
|
*
|
|
@@ -2772,6 +2839,18 @@ const defineFactory = (model, definition) => {
|
|
|
2772
2839
|
return new InlineFactory(model, definition);
|
|
2773
2840
|
};
|
|
2774
2841
|
|
|
2842
|
+
//#endregion
|
|
2843
|
+
//#region src/Exceptions/MissingDelegateException.ts
|
|
2844
|
+
var MissingDelegateException = class extends ArkormException {
|
|
2845
|
+
constructor(message, context = {}) {
|
|
2846
|
+
super(message, {
|
|
2847
|
+
code: "MISSING_DELEGATE",
|
|
2848
|
+
...context
|
|
2849
|
+
});
|
|
2850
|
+
this.name = "MissingDelegateException";
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
|
|
2775
2854
|
//#endregion
|
|
2776
2855
|
//#region src/Exceptions/ModelNotFoundException.ts
|
|
2777
2856
|
/**
|
|
@@ -2783,8 +2862,12 @@ const defineFactory = (model, definition) => {
|
|
|
2783
2862
|
*/
|
|
2784
2863
|
var ModelNotFoundException = class extends ArkormException {
|
|
2785
2864
|
modelName;
|
|
2786
|
-
constructor(modelName, message = "No query results for the given model.") {
|
|
2787
|
-
super(message
|
|
2865
|
+
constructor(modelName, message = "No query results for the given model.", context = {}) {
|
|
2866
|
+
super(message, {
|
|
2867
|
+
code: "MODEL_NOT_FOUND",
|
|
2868
|
+
model: modelName,
|
|
2869
|
+
...context
|
|
2870
|
+
});
|
|
2788
2871
|
this.name = "ModelNotFoundException";
|
|
2789
2872
|
this.modelName = modelName;
|
|
2790
2873
|
}
|
|
@@ -2793,6 +2876,54 @@ var ModelNotFoundException = class extends ArkormException {
|
|
|
2793
2876
|
}
|
|
2794
2877
|
};
|
|
2795
2878
|
|
|
2879
|
+
//#endregion
|
|
2880
|
+
//#region src/Exceptions/QueryConstraintException.ts
|
|
2881
|
+
var QueryConstraintException = class extends ArkormException {
|
|
2882
|
+
constructor(message, context = {}) {
|
|
2883
|
+
super(message, {
|
|
2884
|
+
code: "QUERY_CONSTRAINT",
|
|
2885
|
+
...context
|
|
2886
|
+
});
|
|
2887
|
+
this.name = "QueryConstraintException";
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2891
|
+
//#endregion
|
|
2892
|
+
//#region src/Exceptions/RelationResolutionException.ts
|
|
2893
|
+
var RelationResolutionException = class extends ArkormException {
|
|
2894
|
+
constructor(message, context = {}) {
|
|
2895
|
+
super(message, {
|
|
2896
|
+
code: "RELATION_RESOLUTION_FAILED",
|
|
2897
|
+
...context
|
|
2898
|
+
});
|
|
2899
|
+
this.name = "RelationResolutionException";
|
|
2900
|
+
}
|
|
2901
|
+
};
|
|
2902
|
+
|
|
2903
|
+
//#endregion
|
|
2904
|
+
//#region src/Exceptions/ScopeNotDefinedException.ts
|
|
2905
|
+
var ScopeNotDefinedException = class extends ArkormException {
|
|
2906
|
+
constructor(message, context = {}) {
|
|
2907
|
+
super(message, {
|
|
2908
|
+
code: "SCOPE_NOT_DEFINED",
|
|
2909
|
+
...context
|
|
2910
|
+
});
|
|
2911
|
+
this.name = "ScopeNotDefinedException";
|
|
2912
|
+
}
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
//#endregion
|
|
2916
|
+
//#region src/Exceptions/UniqueConstraintResolutionException.ts
|
|
2917
|
+
var UniqueConstraintResolutionException = class extends ArkormException {
|
|
2918
|
+
constructor(message, context = {}) {
|
|
2919
|
+
super(message, {
|
|
2920
|
+
code: "UNIQUE_CONSTRAINT_RESOLUTION_FAILED",
|
|
2921
|
+
...context
|
|
2922
|
+
});
|
|
2923
|
+
this.name = "UniqueConstraintResolutionException";
|
|
2924
|
+
}
|
|
2925
|
+
};
|
|
2926
|
+
|
|
2796
2927
|
//#endregion
|
|
2797
2928
|
//#region src/helpers/prisma.ts
|
|
2798
2929
|
/**
|
|
@@ -3033,6 +3164,7 @@ var BelongsToManyRelation = class extends Relation {
|
|
|
3033
3164
|
async getResults() {
|
|
3034
3165
|
const parentValue = this.parent.getAttribute(this.parentKey);
|
|
3035
3166
|
const ids = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.foreignPivotKey]: parentValue } })).map((row) => row[this.relatedPivotKey]);
|
|
3167
|
+
if (ids.length === 0) return new ArkormCollection([]);
|
|
3036
3168
|
return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
|
|
3037
3169
|
}
|
|
3038
3170
|
};
|
|
@@ -3119,6 +3251,7 @@ var HasManyThroughRelation = class extends Relation {
|
|
|
3119
3251
|
async getResults() {
|
|
3120
3252
|
const localValue = this.parent.getAttribute(this.localKey);
|
|
3121
3253
|
const keys = (await this.related.getDelegate(this.throughDelegate).findMany({ where: { [this.firstKey]: localValue } })).map((row) => row[this.secondLocalKey]);
|
|
3254
|
+
if (keys.length === 0) return new ArkormCollection([]);
|
|
3122
3255
|
return this.applyConstraint(this.related.query().where({ [this.secondKey]: { in: keys } })).get();
|
|
3123
3256
|
}
|
|
3124
3257
|
};
|
|
@@ -3276,6 +3409,7 @@ var MorphToManyRelation = class extends Relation {
|
|
|
3276
3409
|
[`${this.morphName}Id`]: parentValue,
|
|
3277
3410
|
[`${this.morphName}Type`]: morphType
|
|
3278
3411
|
} })).map((row) => row[this.relatedPivotKey]);
|
|
3412
|
+
if (ids.length === 0) return new ArkormCollection([]);
|
|
3279
3413
|
return this.applyConstraint(this.related.query().where({ [this.relatedKey]: { in: ids } })).get();
|
|
3280
3414
|
}
|
|
3281
3415
|
};
|
|
@@ -4027,7 +4161,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4027
4161
|
scope(name, ...args) {
|
|
4028
4162
|
const methodName = `scope${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
4029
4163
|
const scope = this.model.prototype?.[methodName];
|
|
4030
|
-
if (typeof scope !== "function") throw new
|
|
4164
|
+
if (typeof scope !== "function") throw new ScopeNotDefinedException(`Scope [${name}] is not defined.`, {
|
|
4165
|
+
operation: "scope",
|
|
4166
|
+
model: this.model.name,
|
|
4167
|
+
scope: name
|
|
4168
|
+
});
|
|
4031
4169
|
const scoped = scope.call(void 0, this, ...args);
|
|
4032
4170
|
if (scoped && scoped !== this) return scoped;
|
|
4033
4171
|
return this;
|
|
@@ -4149,7 +4287,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4149
4287
|
const relationCache = /* @__PURE__ */ new WeakMap();
|
|
4150
4288
|
const rows = await this.delegate.findMany(this.buildFindArgs());
|
|
4151
4289
|
const normalizedRows = this.randomOrderEnabled ? this.shuffleRows(rows) : rows;
|
|
4152
|
-
const models = this.model.
|
|
4290
|
+
const models = await this.model.hydrateManyRetrieved(normalizedRows);
|
|
4153
4291
|
let filteredModels = models;
|
|
4154
4292
|
if (this.hasRelationFilters()) if (this.hasOrRelationFilters() && this.args.where) {
|
|
4155
4293
|
const baseIds = new Set(models.map((model) => this.getModelId(model)).filter((id) => id != null));
|
|
@@ -4179,13 +4317,13 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4179
4317
|
if (rows.length === 0) return null;
|
|
4180
4318
|
const row = this.shuffleRows(rows)[0];
|
|
4181
4319
|
if (!row) return null;
|
|
4182
|
-
const model = this.model.
|
|
4320
|
+
const model = await this.model.hydrateRetrieved(row);
|
|
4183
4321
|
await model.load(this.eagerLoads);
|
|
4184
4322
|
return model;
|
|
4185
4323
|
}
|
|
4186
4324
|
const row = await this.delegate.findFirst(this.buildFindArgs());
|
|
4187
4325
|
if (!row) return null;
|
|
4188
|
-
const model = this.model.
|
|
4326
|
+
const model = await this.model.hydrateRetrieved(row);
|
|
4189
4327
|
await model.load(this.eagerLoads);
|
|
4190
4328
|
return model;
|
|
4191
4329
|
}
|
|
@@ -4205,7 +4343,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4205
4343
|
async findOr(value, keyOrCallback, maybeCallback) {
|
|
4206
4344
|
const key = typeof keyOrCallback === "string" ? keyOrCallback : "id";
|
|
4207
4345
|
const callback = typeof keyOrCallback === "function" ? keyOrCallback : maybeCallback;
|
|
4208
|
-
if (!callback) throw new
|
|
4346
|
+
if (!callback) throw new QueryConstraintException("findOr requires a fallback callback.", {
|
|
4347
|
+
operation: "findOr",
|
|
4348
|
+
model: this.model.name
|
|
4349
|
+
});
|
|
4209
4350
|
const found = await this.find(value, key);
|
|
4210
4351
|
if (found) return found;
|
|
4211
4352
|
return callback();
|
|
@@ -4319,7 +4460,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4319
4460
|
async insertGetId(values, sequence) {
|
|
4320
4461
|
const created = await this.delegate.create({ data: values });
|
|
4321
4462
|
const key = sequence ?? "id";
|
|
4322
|
-
if (!(key in created)) throw new
|
|
4463
|
+
if (!(key in created)) throw new UniqueConstraintResolutionException(`Inserted record does not contain key [${key}].`, {
|
|
4464
|
+
operation: "insertGetId",
|
|
4465
|
+
model: this.model.name,
|
|
4466
|
+
meta: { key }
|
|
4467
|
+
});
|
|
4323
4468
|
return created[key];
|
|
4324
4469
|
}
|
|
4325
4470
|
/**
|
|
@@ -4356,7 +4501,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4356
4501
|
*/
|
|
4357
4502
|
async update(data) {
|
|
4358
4503
|
const where = this.buildWhere();
|
|
4359
|
-
if (!where) throw new
|
|
4504
|
+
if (!where) throw new QueryConstraintException("Update requires a where clause.", {
|
|
4505
|
+
operation: "update",
|
|
4506
|
+
model: this.model.name
|
|
4507
|
+
});
|
|
4360
4508
|
const uniqueWhere = await this.resolveUniqueWhere(where);
|
|
4361
4509
|
const updated = await this.delegate.update({
|
|
4362
4510
|
where: uniqueWhere,
|
|
@@ -4372,7 +4520,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4372
4520
|
*/
|
|
4373
4521
|
async updateFrom(data) {
|
|
4374
4522
|
const where = this.buildWhere();
|
|
4375
|
-
if (!where) throw new
|
|
4523
|
+
if (!where) throw new QueryConstraintException("Update requires a where clause.", {
|
|
4524
|
+
operation: "updateFrom",
|
|
4525
|
+
model: this.model.name
|
|
4526
|
+
});
|
|
4376
4527
|
const delegate = this.delegate;
|
|
4377
4528
|
if (typeof delegate.updateMany === "function") {
|
|
4378
4529
|
const result = await delegate.updateMany({
|
|
@@ -4437,7 +4588,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4437
4588
|
*/
|
|
4438
4589
|
async delete() {
|
|
4439
4590
|
const where = this.buildWhere();
|
|
4440
|
-
if (!where) throw new
|
|
4591
|
+
if (!where) throw new QueryConstraintException("Delete requires a where clause.", {
|
|
4592
|
+
operation: "delete",
|
|
4593
|
+
model: this.model.name
|
|
4594
|
+
});
|
|
4441
4595
|
const uniqueWhere = await this.resolveUniqueWhere(where);
|
|
4442
4596
|
const deleted = await this.delegate.delete({ where: uniqueWhere });
|
|
4443
4597
|
return this.model.hydrate(deleted);
|
|
@@ -4503,7 +4657,10 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4503
4657
|
if (Array.isArray(source)) return source;
|
|
4504
4658
|
}
|
|
4505
4659
|
if (Array.isArray(source)) return source;
|
|
4506
|
-
throw new
|
|
4660
|
+
throw new QueryConstraintException("insertUsing expects a query builder, array of records, or async resolver.", {
|
|
4661
|
+
operation: "insertUsing",
|
|
4662
|
+
model: this.model.name
|
|
4663
|
+
});
|
|
4507
4664
|
}
|
|
4508
4665
|
/**
|
|
4509
4666
|
* Execute callback when no records exist.
|
|
@@ -4587,7 +4744,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4587
4744
|
*/
|
|
4588
4745
|
whereRaw(sql, bindings = []) {
|
|
4589
4746
|
const delegate = this.delegate;
|
|
4590
|
-
if (typeof delegate.applyRawWhere !== "function") throw new
|
|
4747
|
+
if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
|
|
4748
|
+
operation: "whereRaw",
|
|
4749
|
+
model: this.model.name,
|
|
4750
|
+
meta: { feature: "rawWhere" }
|
|
4751
|
+
});
|
|
4591
4752
|
this.args.where = delegate.applyRawWhere(this.buildWhere(), sql, bindings);
|
|
4592
4753
|
return this;
|
|
4593
4754
|
}
|
|
@@ -4600,7 +4761,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4600
4761
|
*/
|
|
4601
4762
|
orWhereRaw(sql, bindings = []) {
|
|
4602
4763
|
const delegate = this.delegate;
|
|
4603
|
-
if (typeof delegate.applyRawWhere !== "function") throw new
|
|
4764
|
+
if (typeof delegate.applyRawWhere !== "function") throw new UnsupportedAdapterFeatureException("Raw where clauses are not supported by the current adapter.", {
|
|
4765
|
+
operation: "orWhereRaw",
|
|
4766
|
+
model: this.model.name,
|
|
4767
|
+
meta: { feature: "rawWhere" }
|
|
4768
|
+
});
|
|
4604
4769
|
const rawWhere = delegate.applyRawWhere(void 0, sql, bindings);
|
|
4605
4770
|
return this.orWhere(rawWhere);
|
|
4606
4771
|
}
|
|
@@ -4722,9 +4887,16 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4722
4887
|
async resolveUniqueWhere(where) {
|
|
4723
4888
|
if (this.isUniqueWhere(where)) return where;
|
|
4724
4889
|
const row = await this.delegate.findFirst({ where });
|
|
4725
|
-
if (!row) throw new
|
|
4890
|
+
if (!row) throw new ModelNotFoundException(this.model.name, "Record not found for update/delete operation.", {
|
|
4891
|
+
operation: "resolveUniqueWhere",
|
|
4892
|
+
meta: { where }
|
|
4893
|
+
});
|
|
4726
4894
|
const record = row;
|
|
4727
|
-
if (!Object.prototype.hasOwnProperty.call(record, "id")) throw new
|
|
4895
|
+
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.", {
|
|
4896
|
+
operation: "resolveUniqueWhere",
|
|
4897
|
+
model: this.model.name,
|
|
4898
|
+
meta: { where }
|
|
4899
|
+
});
|
|
4728
4900
|
return { id: record.id };
|
|
4729
4901
|
}
|
|
4730
4902
|
/**
|
|
@@ -4847,7 +5019,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4847
5019
|
if (cached) return await cached;
|
|
4848
5020
|
const resolver = (async () => {
|
|
4849
5021
|
const relationMethod = model[relation];
|
|
4850
|
-
if (typeof relationMethod !== "function") throw new
|
|
5022
|
+
if (typeof relationMethod !== "function") throw new RelationResolutionException(`Relation [${relation}] is not defined on the model.`, {
|
|
5023
|
+
operation: "resolveRelatedResults",
|
|
5024
|
+
model: this.model.name,
|
|
5025
|
+
relation
|
|
5026
|
+
});
|
|
4851
5027
|
const relationInstance = relationMethod.call(model);
|
|
4852
5028
|
if (callback && typeof relationInstance.constrain === "function") relationInstance.constrain((query) => {
|
|
4853
5029
|
return callback(query) ?? query;
|
|
@@ -4862,7 +5038,11 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4862
5038
|
if (results instanceof ArkormCollection) return results.all();
|
|
4863
5039
|
return results;
|
|
4864
5040
|
}
|
|
4865
|
-
throw new
|
|
5041
|
+
throw new RelationResolutionException(`Relation [${relation}] does not support result resolution.`, {
|
|
5042
|
+
operation: "resolveRelatedResults",
|
|
5043
|
+
model: this.model.name,
|
|
5044
|
+
relation
|
|
5045
|
+
});
|
|
4866
5046
|
})();
|
|
4867
5047
|
callbackMap.set(callbackCacheKey, resolver);
|
|
4868
5048
|
return await resolver;
|
|
@@ -4903,6 +5083,8 @@ var QueryBuilder = class QueryBuilder {
|
|
|
4903
5083
|
* @since 0.1.0
|
|
4904
5084
|
*/
|
|
4905
5085
|
var Model = class Model {
|
|
5086
|
+
static lifecycleStates = /* @__PURE__ */ new WeakMap();
|
|
5087
|
+
static eventsSuppressed = 0;
|
|
4906
5088
|
static factoryClass;
|
|
4907
5089
|
static client;
|
|
4908
5090
|
static delegate;
|
|
@@ -4910,13 +5092,20 @@ var Model = class Model {
|
|
|
4910
5092
|
static deletedAtColumn = "deletedAt";
|
|
4911
5093
|
static globalScopes = {};
|
|
4912
5094
|
static eventListeners = {};
|
|
5095
|
+
static dispatchesEvents = {};
|
|
4913
5096
|
casts = {};
|
|
4914
5097
|
hidden = [];
|
|
4915
5098
|
visible = [];
|
|
4916
5099
|
appends = [];
|
|
4917
5100
|
attributes;
|
|
5101
|
+
original;
|
|
5102
|
+
changes;
|
|
5103
|
+
touchedAttributes;
|
|
4918
5104
|
constructor(attributes = {}) {
|
|
4919
5105
|
this.attributes = {};
|
|
5106
|
+
this.original = {};
|
|
5107
|
+
this.changes = {};
|
|
5108
|
+
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
4920
5109
|
this.fill(attributes);
|
|
4921
5110
|
return new Proxy(this, {
|
|
4922
5111
|
get: (target, key, receiver) => {
|
|
@@ -4947,7 +5136,11 @@ var Model = class Model {
|
|
|
4947
5136
|
}
|
|
4948
5137
|
static factory(count) {
|
|
4949
5138
|
const factoryClass = this.factoryClass;
|
|
4950
|
-
if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}]
|
|
5139
|
+
if (!factoryClass) throw new ArkormException(`Factory is not configured for model [${this.name}].`, {
|
|
5140
|
+
code: "FACTORY_NOT_CONFIGURED",
|
|
5141
|
+
operation: "factory",
|
|
5142
|
+
model: this.name
|
|
5143
|
+
});
|
|
4951
5144
|
const factory = new factoryClass();
|
|
4952
5145
|
if (typeof count === "number") factory.count(count);
|
|
4953
5146
|
return factory;
|
|
@@ -4963,6 +5156,21 @@ var Model = class Model {
|
|
|
4963
5156
|
this.globalScopes[name] = scope;
|
|
4964
5157
|
}
|
|
4965
5158
|
/**
|
|
5159
|
+
* Execute a callback without applying global scopes for the current model class.
|
|
5160
|
+
*
|
|
5161
|
+
* @param callback
|
|
5162
|
+
* @returns
|
|
5163
|
+
*/
|
|
5164
|
+
static async withoutGlobalScopes(callback) {
|
|
5165
|
+
const state = Model.getLifecycleState(this);
|
|
5166
|
+
state.globalScopesSuppressed += 1;
|
|
5167
|
+
try {
|
|
5168
|
+
return await callback();
|
|
5169
|
+
} finally {
|
|
5170
|
+
state.globalScopesSuppressed = Math.max(0, state.globalScopesSuppressed - 1);
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
/**
|
|
4966
5174
|
* Remove a global scope by name.
|
|
4967
5175
|
*
|
|
4968
5176
|
* @param name
|
|
@@ -4984,11 +5192,60 @@ var Model = class Model {
|
|
|
4984
5192
|
* @param listener
|
|
4985
5193
|
*/
|
|
4986
5194
|
static on(event, listener) {
|
|
5195
|
+
Model.ensureModelBooted(this);
|
|
4987
5196
|
this.ensureOwnEventListeners();
|
|
4988
5197
|
if (!this.eventListeners[event]) this.eventListeners[event] = [];
|
|
4989
5198
|
this.eventListeners[event]?.push(listener);
|
|
4990
5199
|
}
|
|
4991
5200
|
/**
|
|
5201
|
+
* Register a model lifecycle callback listener.
|
|
5202
|
+
*
|
|
5203
|
+
* @param event
|
|
5204
|
+
* @param listener
|
|
5205
|
+
*/
|
|
5206
|
+
static event(event, listener) {
|
|
5207
|
+
this.on(event, listener);
|
|
5208
|
+
}
|
|
5209
|
+
static retrieved(listener) {
|
|
5210
|
+
this.event("retrieved", listener);
|
|
5211
|
+
}
|
|
5212
|
+
static saving(listener) {
|
|
5213
|
+
this.event("saving", listener);
|
|
5214
|
+
}
|
|
5215
|
+
static saved(listener) {
|
|
5216
|
+
this.event("saved", listener);
|
|
5217
|
+
}
|
|
5218
|
+
static creating(listener) {
|
|
5219
|
+
this.event("creating", listener);
|
|
5220
|
+
}
|
|
5221
|
+
static created(listener) {
|
|
5222
|
+
this.event("created", listener);
|
|
5223
|
+
}
|
|
5224
|
+
static updating(listener) {
|
|
5225
|
+
this.event("updating", listener);
|
|
5226
|
+
}
|
|
5227
|
+
static updated(listener) {
|
|
5228
|
+
this.event("updated", listener);
|
|
5229
|
+
}
|
|
5230
|
+
static deleting(listener) {
|
|
5231
|
+
this.event("deleting", listener);
|
|
5232
|
+
}
|
|
5233
|
+
static deleted(listener) {
|
|
5234
|
+
this.event("deleted", listener);
|
|
5235
|
+
}
|
|
5236
|
+
static restoring(listener) {
|
|
5237
|
+
this.event("restoring", listener);
|
|
5238
|
+
}
|
|
5239
|
+
static restored(listener) {
|
|
5240
|
+
this.event("restored", listener);
|
|
5241
|
+
}
|
|
5242
|
+
static forceDeleting(listener) {
|
|
5243
|
+
this.event("forceDeleting", listener);
|
|
5244
|
+
}
|
|
5245
|
+
static forceDeleted(listener) {
|
|
5246
|
+
this.event("forceDeleted", listener);
|
|
5247
|
+
}
|
|
5248
|
+
/**
|
|
4992
5249
|
* Remove listeners for an event. If listener is omitted, all listeners for that event are removed.
|
|
4993
5250
|
*
|
|
4994
5251
|
* @param event
|
|
@@ -5009,6 +5266,34 @@ var Model = class Model {
|
|
|
5009
5266
|
this.eventListeners = {};
|
|
5010
5267
|
}
|
|
5011
5268
|
/**
|
|
5269
|
+
* Execute a callback while suppressing lifecycle events for all models.
|
|
5270
|
+
*
|
|
5271
|
+
* @param callback
|
|
5272
|
+
* @returns
|
|
5273
|
+
*/
|
|
5274
|
+
static async withoutEvents(callback) {
|
|
5275
|
+
Model.eventsSuppressed += 1;
|
|
5276
|
+
try {
|
|
5277
|
+
return await callback();
|
|
5278
|
+
} finally {
|
|
5279
|
+
Model.eventsSuppressed = Math.max(0, Model.eventsSuppressed - 1);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
/**
|
|
5283
|
+
* Execute a callback within a transaction scope.
|
|
5284
|
+
* Nested calls reuse the active transaction client.
|
|
5285
|
+
*
|
|
5286
|
+
* @param callback
|
|
5287
|
+
* @param options
|
|
5288
|
+
* @returns
|
|
5289
|
+
*/
|
|
5290
|
+
static async transaction(callback, options = {}) {
|
|
5291
|
+
ensureArkormConfigLoading();
|
|
5292
|
+
return await runArkormTransaction(async (client) => {
|
|
5293
|
+
return await callback(client);
|
|
5294
|
+
}, options);
|
|
5295
|
+
}
|
|
5296
|
+
/**
|
|
5012
5297
|
* Get the Prisma delegate for the model.
|
|
5013
5298
|
* If a delegate name is provided, it will attempt to resolve that delegate.
|
|
5014
5299
|
* Otherwise, it will attempt to resolve a delegate based on the model's name or
|
|
@@ -5026,9 +5311,20 @@ var Model = class Model {
|
|
|
5026
5311
|
`${str(key).singular()}`,
|
|
5027
5312
|
`${str(key).camel().singular()}`
|
|
5028
5313
|
];
|
|
5314
|
+
const activeTransactionClient = getActiveTransactionClient();
|
|
5029
5315
|
const runtimeClient = getRuntimePrismaClient();
|
|
5030
|
-
const
|
|
5031
|
-
|
|
5316
|
+
const sources = activeTransactionClient ? [
|
|
5317
|
+
activeTransactionClient,
|
|
5318
|
+
this.client,
|
|
5319
|
+
runtimeClient
|
|
5320
|
+
] : [this.client, runtimeClient];
|
|
5321
|
+
const resolved = candidates.flatMap((name) => sources.map((source) => source?.[name])).find((candidate) => isDelegateLike(candidate));
|
|
5322
|
+
if (!resolved) throw new MissingDelegateException(`Database delegate [${key}] is not configured.`, {
|
|
5323
|
+
operation: "getDelegate",
|
|
5324
|
+
model: this.name,
|
|
5325
|
+
delegate: key,
|
|
5326
|
+
meta: { candidates }
|
|
5327
|
+
});
|
|
5032
5328
|
return resolved;
|
|
5033
5329
|
}
|
|
5034
5330
|
/**
|
|
@@ -5038,16 +5334,27 @@ var Model = class Model {
|
|
|
5038
5334
|
* @returns
|
|
5039
5335
|
*/
|
|
5040
5336
|
static query() {
|
|
5337
|
+
Model.ensureModelBooted(this);
|
|
5041
5338
|
let builder = new QueryBuilder(this.getDelegate(), this);
|
|
5042
5339
|
const modelClass = this;
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5340
|
+
if (!Model.areGlobalScopesSuppressed(modelClass)) {
|
|
5341
|
+
modelClass.ensureOwnGlobalScopes();
|
|
5342
|
+
Object.values(modelClass.globalScopes).forEach((scope) => {
|
|
5343
|
+
const scoped = scope(builder);
|
|
5344
|
+
if (scoped && scoped !== builder) builder = scoped;
|
|
5345
|
+
});
|
|
5346
|
+
}
|
|
5048
5347
|
return builder;
|
|
5049
5348
|
}
|
|
5050
5349
|
/**
|
|
5350
|
+
* Boot hook for subclasses to register scopes or perform one-time setup.
|
|
5351
|
+
*/
|
|
5352
|
+
static boot() {}
|
|
5353
|
+
/**
|
|
5354
|
+
* Booted hook for subclasses to register callbacks after boot logic runs.
|
|
5355
|
+
*/
|
|
5356
|
+
static booted() {}
|
|
5357
|
+
/**
|
|
5051
5358
|
* Get a query builder instance that includes soft-deleted records.
|
|
5052
5359
|
*
|
|
5053
5360
|
* @param this
|
|
@@ -5098,7 +5405,10 @@ var Model = class Model {
|
|
|
5098
5405
|
* @returns
|
|
5099
5406
|
*/
|
|
5100
5407
|
static hydrate(attributes) {
|
|
5101
|
-
|
|
5408
|
+
const model = new this(attributes);
|
|
5409
|
+
model.syncOriginal();
|
|
5410
|
+
model.syncChanges({});
|
|
5411
|
+
return model;
|
|
5102
5412
|
}
|
|
5103
5413
|
/**
|
|
5104
5414
|
* Hydrate multiple model instances from an array of plain objects of attributes.
|
|
@@ -5110,6 +5420,36 @@ var Model = class Model {
|
|
|
5110
5420
|
static hydrateMany(attributes) {
|
|
5111
5421
|
return attributes.map((attribute) => new this(attribute));
|
|
5112
5422
|
}
|
|
5423
|
+
/**
|
|
5424
|
+
* Hydrate a model instance and dispatch the retrieved lifecycle event.
|
|
5425
|
+
*
|
|
5426
|
+
* @param this
|
|
5427
|
+
* @param attributes
|
|
5428
|
+
* @returns
|
|
5429
|
+
*/
|
|
5430
|
+
static async hydrateRetrieved(attributes) {
|
|
5431
|
+
Model.ensureModelBooted(this);
|
|
5432
|
+
if (!Model.hasEventListeners(this, "retrieved")) return this.hydrate(attributes);
|
|
5433
|
+
const model = this.hydrate(attributes);
|
|
5434
|
+
await Model.dispatchEvent(this, "retrieved", model);
|
|
5435
|
+
return model;
|
|
5436
|
+
}
|
|
5437
|
+
/**
|
|
5438
|
+
* Hydrate multiple model instances and dispatch the retrieved lifecycle event for each.
|
|
5439
|
+
*
|
|
5440
|
+
* @param this
|
|
5441
|
+
* @param attributes
|
|
5442
|
+
* @returns
|
|
5443
|
+
*/
|
|
5444
|
+
static async hydrateManyRetrieved(attributes) {
|
|
5445
|
+
Model.ensureModelBooted(this);
|
|
5446
|
+
if (!Model.hasEventListeners(this, "retrieved")) return this.hydrateMany(attributes);
|
|
5447
|
+
const models = this.hydrateMany(attributes);
|
|
5448
|
+
await Promise.all(models.map(async (model) => {
|
|
5449
|
+
await Model.dispatchEvent(this, "retrieved", model);
|
|
5450
|
+
}));
|
|
5451
|
+
return models;
|
|
5452
|
+
}
|
|
5113
5453
|
fill(attributes) {
|
|
5114
5454
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
5115
5455
|
this.setAttribute(key, value);
|
|
@@ -5135,6 +5475,7 @@ var Model = class Model {
|
|
|
5135
5475
|
else if (mutator) resolved = mutator.call(this, resolved);
|
|
5136
5476
|
if (cast) resolved = resolveCast(cast).set(resolved);
|
|
5137
5477
|
this.attributes[key] = resolved;
|
|
5478
|
+
this.touchedAttributes.add(key);
|
|
5138
5479
|
return this;
|
|
5139
5480
|
}
|
|
5140
5481
|
/**
|
|
@@ -5147,12 +5488,15 @@ var Model = class Model {
|
|
|
5147
5488
|
async save() {
|
|
5148
5489
|
const identifier = this.getAttribute("id");
|
|
5149
5490
|
const payload = this.getRawAttributes();
|
|
5491
|
+
const previousOriginal = this.getOriginal();
|
|
5150
5492
|
const constructor = this.constructor;
|
|
5151
5493
|
if (identifier == null) {
|
|
5152
5494
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
5153
5495
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
5154
5496
|
const model = await constructor.query().create(payload);
|
|
5155
5497
|
this.fill(model.getRawAttributes());
|
|
5498
|
+
this.syncChanges(previousOriginal);
|
|
5499
|
+
this.syncOriginal();
|
|
5156
5500
|
await Model.dispatchEvent(constructor, "created", this);
|
|
5157
5501
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5158
5502
|
return this;
|
|
@@ -5161,11 +5505,21 @@ var Model = class Model {
|
|
|
5161
5505
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
5162
5506
|
const model = await constructor.query().where({ id: identifier }).update(payload);
|
|
5163
5507
|
this.fill(model.getRawAttributes());
|
|
5508
|
+
this.syncChanges(previousOriginal);
|
|
5509
|
+
this.syncOriginal();
|
|
5164
5510
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
5165
5511
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5166
5512
|
return this;
|
|
5167
5513
|
}
|
|
5168
5514
|
/**
|
|
5515
|
+
* Save the model without dispatching lifecycle events.
|
|
5516
|
+
*
|
|
5517
|
+
* @returns
|
|
5518
|
+
*/
|
|
5519
|
+
async saveQuietly() {
|
|
5520
|
+
return await Model.withoutEvents(() => this.save());
|
|
5521
|
+
}
|
|
5522
|
+
/**
|
|
5169
5523
|
* Delete the model from the database.
|
|
5170
5524
|
* If soft deletes are enabled, it will perform a soft delete by
|
|
5171
5525
|
* setting the deleted at column to the current date.
|
|
@@ -5176,21 +5530,34 @@ var Model = class Model {
|
|
|
5176
5530
|
async delete() {
|
|
5177
5531
|
const identifier = this.getAttribute("id");
|
|
5178
5532
|
if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
|
|
5533
|
+
const previousOriginal = this.getOriginal();
|
|
5179
5534
|
const constructor = this.constructor;
|
|
5180
5535
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5181
5536
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5182
5537
|
if (softDeleteConfig.enabled) {
|
|
5183
5538
|
const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
|
|
5184
5539
|
this.fill(model.getRawAttributes());
|
|
5540
|
+
this.syncChanges(previousOriginal);
|
|
5541
|
+
this.syncOriginal();
|
|
5185
5542
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5186
5543
|
return this;
|
|
5187
5544
|
}
|
|
5188
5545
|
const deleted = await constructor.query().where({ id: identifier }).delete();
|
|
5189
5546
|
this.fill(deleted.getRawAttributes());
|
|
5547
|
+
this.syncChanges(previousOriginal);
|
|
5548
|
+
this.syncOriginal();
|
|
5190
5549
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5191
5550
|
return this;
|
|
5192
5551
|
}
|
|
5193
5552
|
/**
|
|
5553
|
+
* Delete the model without dispatching lifecycle events.
|
|
5554
|
+
*
|
|
5555
|
+
* @returns
|
|
5556
|
+
*/
|
|
5557
|
+
async deleteQuietly() {
|
|
5558
|
+
return await Model.withoutEvents(() => this.delete());
|
|
5559
|
+
}
|
|
5560
|
+
/**
|
|
5194
5561
|
* Permanently delete the model from the database, regardless of whether soft
|
|
5195
5562
|
* deletes are enabled.
|
|
5196
5563
|
*
|
|
@@ -5199,16 +5566,27 @@ var Model = class Model {
|
|
|
5199
5566
|
async forceDelete() {
|
|
5200
5567
|
const identifier = this.getAttribute("id");
|
|
5201
5568
|
if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
|
|
5569
|
+
const previousOriginal = this.getOriginal();
|
|
5202
5570
|
const constructor = this.constructor;
|
|
5203
5571
|
await Model.dispatchEvent(constructor, "forceDeleting", this);
|
|
5204
5572
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5205
5573
|
const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
|
|
5206
5574
|
this.fill(deleted.getRawAttributes());
|
|
5575
|
+
this.syncChanges(previousOriginal);
|
|
5576
|
+
this.syncOriginal();
|
|
5207
5577
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5208
5578
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
5209
5579
|
return this;
|
|
5210
5580
|
}
|
|
5211
5581
|
/**
|
|
5582
|
+
* Force delete the model without dispatching lifecycle events.
|
|
5583
|
+
*
|
|
5584
|
+
* @returns
|
|
5585
|
+
*/
|
|
5586
|
+
async forceDeleteQuietly() {
|
|
5587
|
+
return await Model.withoutEvents(() => this.forceDelete());
|
|
5588
|
+
}
|
|
5589
|
+
/**
|
|
5212
5590
|
* Restore a soft-deleted model by setting the deleted at column to null.
|
|
5213
5591
|
*
|
|
5214
5592
|
* @returns
|
|
@@ -5219,13 +5597,24 @@ var Model = class Model {
|
|
|
5219
5597
|
const constructor = this.constructor;
|
|
5220
5598
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5221
5599
|
if (!softDeleteConfig.enabled) return this;
|
|
5600
|
+
const previousOriginal = this.getOriginal();
|
|
5222
5601
|
await Model.dispatchEvent(constructor, "restoring", this);
|
|
5223
5602
|
const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
|
|
5224
5603
|
this.fill(model.getRawAttributes());
|
|
5604
|
+
this.syncChanges(previousOriginal);
|
|
5605
|
+
this.syncOriginal();
|
|
5225
5606
|
await Model.dispatchEvent(constructor, "restored", this);
|
|
5226
5607
|
return this;
|
|
5227
5608
|
}
|
|
5228
5609
|
/**
|
|
5610
|
+
* Restore the model without dispatching lifecycle events.
|
|
5611
|
+
*
|
|
5612
|
+
* @returns
|
|
5613
|
+
*/
|
|
5614
|
+
async restoreQuietly() {
|
|
5615
|
+
return await Model.withoutEvents(() => this.restore());
|
|
5616
|
+
}
|
|
5617
|
+
/**
|
|
5229
5618
|
* Load related models onto the current model instance.
|
|
5230
5619
|
*
|
|
5231
5620
|
* @param relations
|
|
@@ -5251,6 +5640,42 @@ var Model = class Model {
|
|
|
5251
5640
|
getRawAttributes() {
|
|
5252
5641
|
return { ...this.attributes };
|
|
5253
5642
|
}
|
|
5643
|
+
getOriginal(key) {
|
|
5644
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.original[key]);
|
|
5645
|
+
return Object.entries(this.original).reduce((all, [originalKey, value]) => {
|
|
5646
|
+
all[originalKey] = Model.cloneAttributeValue(value);
|
|
5647
|
+
return all;
|
|
5648
|
+
}, {});
|
|
5649
|
+
}
|
|
5650
|
+
/**
|
|
5651
|
+
* Determine whether the model has unsaved attribute changes.
|
|
5652
|
+
*
|
|
5653
|
+
* @param keys
|
|
5654
|
+
* @returns
|
|
5655
|
+
*/
|
|
5656
|
+
isDirty(keys) {
|
|
5657
|
+
return Object.keys(this.getDirtyAttributes(keys)).length > 0;
|
|
5658
|
+
}
|
|
5659
|
+
/**
|
|
5660
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
5661
|
+
*
|
|
5662
|
+
* @param keys
|
|
5663
|
+
* @returns
|
|
5664
|
+
*/
|
|
5665
|
+
isClean(keys) {
|
|
5666
|
+
return !this.isDirty(keys);
|
|
5667
|
+
}
|
|
5668
|
+
/**
|
|
5669
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
5670
|
+
*
|
|
5671
|
+
* @param keys
|
|
5672
|
+
* @returns
|
|
5673
|
+
*/
|
|
5674
|
+
wasChanged(keys) {
|
|
5675
|
+
const keyList = this.normalizeAttributeKeys(keys);
|
|
5676
|
+
if (keyList.length === 0) return Object.keys(this.changes).length > 0;
|
|
5677
|
+
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
5678
|
+
}
|
|
5254
5679
|
/**
|
|
5255
5680
|
* Convert the model instance to a plain object, applying visibility
|
|
5256
5681
|
* rules, appends, and mutators.
|
|
@@ -5278,6 +5703,47 @@ var Model = class Model {
|
|
|
5278
5703
|
return this.toObject();
|
|
5279
5704
|
}
|
|
5280
5705
|
/**
|
|
5706
|
+
* Determine if another model represents the same persisted record.
|
|
5707
|
+
*
|
|
5708
|
+
* @param model
|
|
5709
|
+
* @returns
|
|
5710
|
+
*/
|
|
5711
|
+
is(model) {
|
|
5712
|
+
if (!(model instanceof Model)) return false;
|
|
5713
|
+
if (this.constructor !== model.constructor) return false;
|
|
5714
|
+
const identifier = this.getAttribute("id");
|
|
5715
|
+
const otherIdentifier = model.getAttribute("id");
|
|
5716
|
+
if (identifier == null || otherIdentifier == null) return false;
|
|
5717
|
+
return identifier === otherIdentifier;
|
|
5718
|
+
}
|
|
5719
|
+
/**
|
|
5720
|
+
* Determine if another model does not represent the same persisted record.
|
|
5721
|
+
*
|
|
5722
|
+
* @param model
|
|
5723
|
+
* @returns
|
|
5724
|
+
*/
|
|
5725
|
+
isNot(model) {
|
|
5726
|
+
return !this.is(model);
|
|
5727
|
+
}
|
|
5728
|
+
/**
|
|
5729
|
+
* Determine if another model is the same in-memory instance.
|
|
5730
|
+
*
|
|
5731
|
+
* @param model
|
|
5732
|
+
* @returns
|
|
5733
|
+
*/
|
|
5734
|
+
isSame(model) {
|
|
5735
|
+
return this === model;
|
|
5736
|
+
}
|
|
5737
|
+
/**
|
|
5738
|
+
* Determine if another model is not the same in-memory instance.
|
|
5739
|
+
*
|
|
5740
|
+
* @param model
|
|
5741
|
+
* @returns
|
|
5742
|
+
*/
|
|
5743
|
+
isNotSame(model) {
|
|
5744
|
+
return !this.isSame(model);
|
|
5745
|
+
}
|
|
5746
|
+
/**
|
|
5281
5747
|
* Define a has one relationship.
|
|
5282
5748
|
*
|
|
5283
5749
|
* @param related
|
|
@@ -5400,6 +5866,34 @@ var Model = class Model {
|
|
|
5400
5866
|
return typeof method === "function" ? method : null;
|
|
5401
5867
|
}
|
|
5402
5868
|
/**
|
|
5869
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
5870
|
+
*
|
|
5871
|
+
* @param keys
|
|
5872
|
+
* @returns
|
|
5873
|
+
*/
|
|
5874
|
+
getDirtyAttributes(keys) {
|
|
5875
|
+
const requestedKeys = this.normalizeAttributeKeys(keys);
|
|
5876
|
+
return (requestedKeys.length > 0 ? requestedKeys : Array.from(new Set([...Object.keys(this.original), ...this.touchedAttributes]))).reduce((dirty, key) => {
|
|
5877
|
+
const currentValue = this.attributes[key];
|
|
5878
|
+
const originalValue = this.original[key];
|
|
5879
|
+
const hasCurrent = Object.prototype.hasOwnProperty.call(this.attributes, key);
|
|
5880
|
+
const hasOriginal = Object.prototype.hasOwnProperty.call(this.original, key);
|
|
5881
|
+
if (!hasCurrent && !hasOriginal) return dirty;
|
|
5882
|
+
if (hasCurrent !== hasOriginal || !Model.areAttributeValuesEqual(currentValue, originalValue)) dirty[key] = Model.cloneAttributeValue(currentValue);
|
|
5883
|
+
return dirty;
|
|
5884
|
+
}, {});
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
5888
|
+
*
|
|
5889
|
+
* @param keys
|
|
5890
|
+
* @returns
|
|
5891
|
+
*/
|
|
5892
|
+
normalizeAttributeKeys(keys) {
|
|
5893
|
+
if (typeof keys === "undefined") return [];
|
|
5894
|
+
return Array.isArray(keys) ? keys : [keys];
|
|
5895
|
+
}
|
|
5896
|
+
/**
|
|
5403
5897
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
5404
5898
|
*
|
|
5405
5899
|
* @param key
|
|
@@ -5441,6 +5935,144 @@ var Model = class Model {
|
|
|
5441
5935
|
if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
|
|
5442
5936
|
}
|
|
5443
5937
|
/**
|
|
5938
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
5939
|
+
*
|
|
5940
|
+
* @param value
|
|
5941
|
+
* @returns
|
|
5942
|
+
*/
|
|
5943
|
+
static cloneAttributeValue(value) {
|
|
5944
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
5945
|
+
if (Array.isArray(value)) return value.map((item) => Model.cloneAttributeValue(item));
|
|
5946
|
+
if (value && typeof value === "object") return Object.entries(value).reduce((all, [key, nestedValue]) => {
|
|
5947
|
+
all[key] = Model.cloneAttributeValue(nestedValue);
|
|
5948
|
+
return all;
|
|
5949
|
+
}, {});
|
|
5950
|
+
return value;
|
|
5951
|
+
}
|
|
5952
|
+
/**
|
|
5953
|
+
* Compare attribute values for dirty/change detection.
|
|
5954
|
+
*
|
|
5955
|
+
* @param left
|
|
5956
|
+
* @param right
|
|
5957
|
+
* @returns
|
|
5958
|
+
*/
|
|
5959
|
+
static areAttributeValuesEqual(left, right) {
|
|
5960
|
+
if (left === right) return true;
|
|
5961
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
5962
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
5963
|
+
if (left.length !== right.length) return false;
|
|
5964
|
+
return left.every((value, index) => Model.areAttributeValuesEqual(value, right[index]));
|
|
5965
|
+
}
|
|
5966
|
+
if (left && right && typeof left === "object" && typeof right === "object") {
|
|
5967
|
+
const leftEntries = Object.entries(left);
|
|
5968
|
+
const rightEntries = Object.entries(right);
|
|
5969
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
5970
|
+
return leftEntries.every(([key, value]) => {
|
|
5971
|
+
return Object.prototype.hasOwnProperty.call(right, key) && Model.areAttributeValuesEqual(value, right[key]);
|
|
5972
|
+
});
|
|
5973
|
+
}
|
|
5974
|
+
return false;
|
|
5975
|
+
}
|
|
5976
|
+
/**
|
|
5977
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
5978
|
+
*/
|
|
5979
|
+
syncOriginal() {
|
|
5980
|
+
this.original = Object.entries(this.attributes).reduce((all, [key, value]) => {
|
|
5981
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
5982
|
+
return all;
|
|
5983
|
+
}, {});
|
|
5984
|
+
this.touchedAttributes.clear();
|
|
5985
|
+
}
|
|
5986
|
+
/**
|
|
5987
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
5988
|
+
*
|
|
5989
|
+
* @param previousOriginal
|
|
5990
|
+
*/
|
|
5991
|
+
syncChanges(previousOriginal) {
|
|
5992
|
+
this.changes = Object.entries(this.getDirtyAttributes()).reduce((all, [key, value]) => {
|
|
5993
|
+
if (!Object.prototype.hasOwnProperty.call(previousOriginal, key) || !Model.areAttributeValuesEqual(value, previousOriginal[key])) all[key] = Model.cloneAttributeValue(value);
|
|
5994
|
+
return all;
|
|
5995
|
+
}, {});
|
|
5996
|
+
}
|
|
5997
|
+
/**
|
|
5998
|
+
* Resolve lifecycle state for the provided model class.
|
|
5999
|
+
*
|
|
6000
|
+
* @param modelClass
|
|
6001
|
+
* @returns
|
|
6002
|
+
*/
|
|
6003
|
+
static getLifecycleState(modelClass) {
|
|
6004
|
+
const existing = Model.lifecycleStates.get(modelClass);
|
|
6005
|
+
if (existing) return existing;
|
|
6006
|
+
const state = {
|
|
6007
|
+
booted: false,
|
|
6008
|
+
booting: false,
|
|
6009
|
+
globalScopesSuppressed: 0
|
|
6010
|
+
};
|
|
6011
|
+
Model.lifecycleStates.set(modelClass, state);
|
|
6012
|
+
return state;
|
|
6013
|
+
}
|
|
6014
|
+
/**
|
|
6015
|
+
* Ensure the target model class has executed its boot lifecycle.
|
|
6016
|
+
*
|
|
6017
|
+
* @param modelClass
|
|
6018
|
+
*/
|
|
6019
|
+
static ensureModelBooted(modelClass) {
|
|
6020
|
+
const state = Model.getLifecycleState(modelClass);
|
|
6021
|
+
if (state.booted || state.booting) return;
|
|
6022
|
+
state.booting = true;
|
|
6023
|
+
try {
|
|
6024
|
+
const boot = modelClass.boot;
|
|
6025
|
+
if (boot !== Model.boot) boot.call(modelClass);
|
|
6026
|
+
const booted = modelClass.booted;
|
|
6027
|
+
if (booted !== Model.booted) booted.call(modelClass);
|
|
6028
|
+
state.booted = true;
|
|
6029
|
+
} finally {
|
|
6030
|
+
state.booting = false;
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
/**
|
|
6034
|
+
* Determine if global scopes are currently suppressed for the model class.
|
|
6035
|
+
*
|
|
6036
|
+
* @param modelClass
|
|
6037
|
+
* @returns
|
|
6038
|
+
*/
|
|
6039
|
+
static areGlobalScopesSuppressed(modelClass) {
|
|
6040
|
+
return Model.getLifecycleState(modelClass).globalScopesSuppressed > 0;
|
|
6041
|
+
}
|
|
6042
|
+
/**
|
|
6043
|
+
* Resolve configured class-based event handlers for a lifecycle event.
|
|
6044
|
+
*
|
|
6045
|
+
* @param modelClass
|
|
6046
|
+
* @param event
|
|
6047
|
+
* @returns
|
|
6048
|
+
*/
|
|
6049
|
+
static resolveDispatchedEventListeners(modelClass, event) {
|
|
6050
|
+
const configured = modelClass.dispatchesEvents[event];
|
|
6051
|
+
if (!configured) return [];
|
|
6052
|
+
return (Array.isArray(configured) ? configured : [configured]).map((entry) => {
|
|
6053
|
+
const handler = typeof entry === "function" ? new entry() : entry;
|
|
6054
|
+
if (!handler || typeof handler.handle !== "function") throw new ArkormException(`Invalid event handler configured for [${modelClass.name}.${event}].`);
|
|
6055
|
+
return async (model) => {
|
|
6056
|
+
await handler.handle(model);
|
|
6057
|
+
};
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
/**
|
|
6061
|
+
* Determine whether a lifecycle event has any registered listeners.
|
|
6062
|
+
*
|
|
6063
|
+
* @param modelClass
|
|
6064
|
+
* @param event
|
|
6065
|
+
* @returns
|
|
6066
|
+
*/
|
|
6067
|
+
static hasEventListeners(modelClass, event) {
|
|
6068
|
+
if (Model.eventsSuppressed > 0) return false;
|
|
6069
|
+
modelClass.ensureOwnEventListeners();
|
|
6070
|
+
if ((modelClass.eventListeners[event] || []).length > 0) return true;
|
|
6071
|
+
const configuredDispatchers = modelClass.dispatchesEvents[event];
|
|
6072
|
+
if (!configuredDispatchers) return false;
|
|
6073
|
+
return Array.isArray(configuredDispatchers) ? configuredDispatchers.length > 0 : true;
|
|
6074
|
+
}
|
|
6075
|
+
/**
|
|
5444
6076
|
* Dispatches lifecycle events to registered listeners.
|
|
5445
6077
|
*
|
|
5446
6078
|
* @param modelClass
|
|
@@ -5448,8 +6080,9 @@ var Model = class Model {
|
|
|
5448
6080
|
* @param model
|
|
5449
6081
|
*/
|
|
5450
6082
|
static async dispatchEvent(modelClass, event, model) {
|
|
5451
|
-
|
|
5452
|
-
|
|
6083
|
+
Model.ensureModelBooted(modelClass);
|
|
6084
|
+
if (!Model.hasEventListeners(modelClass, event)) return;
|
|
6085
|
+
const listeners = [...Model.resolveDispatchedEventListeners(modelClass, event), ...modelClass.eventListeners[event] || []];
|
|
5453
6086
|
for (const listener of listeners) await listener(model);
|
|
5454
6087
|
}
|
|
5455
6088
|
/**
|
|
@@ -5469,4 +6102,4 @@ var Model = class Model {
|
|
|
5469
6102
|
};
|
|
5470
6103
|
|
|
5471
6104
|
//#endregion
|
|
5472
|
-
export { ArkormCollection, ArkormException, Attribute, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
|
|
6105
|
+
export { ArkormCollection, ArkormException, Attribute, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, QueryConstraintException, RelationResolutionException, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
|