@getstrata/core 0.5.98 → 0.5.99
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/CHANGELOG.md +20 -0
- package/dist/core/database/baseRepository.d.ts +3 -1
- package/dist/core/database/factory.d.ts +19 -4
- package/dist/core/database/index.d.ts +1 -1
- package/dist/core/database/model.d.ts +54 -19
- package/dist/core/database/relationQuery.d.ts +24 -1
- package/dist/core/database/repositoryQuery.d.ts +4 -1
- package/dist/core/http/resources.d.ts +2 -1
- package/dist/entries/database/factory.js +44 -2
- package/dist/entries/database/model.js +412 -122
- package/dist/entries/database/query.js +1 -1
- package/dist/entries/database/repositoryQuery.js +29 -3
- package/dist/entries/database/schema.js +1 -1
- package/dist/entries/http/resources.js +9 -3
- package/dist/framework/public-api.d.ts +1 -1
- package/dist/index.js +514 -148
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1719,7 +1719,7 @@ function buildOperatorClauses(column, operator, params) {
|
|
|
1719
1719
|
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
1720
1720
|
}
|
|
1721
1721
|
if (operator.ilike !== undefined) {
|
|
1722
|
-
clauses.push(`${column} ILIKE ${pushParam(params,
|
|
1722
|
+
clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
|
|
1723
1723
|
}
|
|
1724
1724
|
if (operator.tsMatch !== undefined) {
|
|
1725
1725
|
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
@@ -2362,13 +2362,30 @@ class RepositoryQuery {
|
|
|
2362
2362
|
});
|
|
2363
2363
|
return this;
|
|
2364
2364
|
}
|
|
2365
|
+
withBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
2366
|
+
this.eagerLoads.push({
|
|
2367
|
+
kind: "belongsToMany",
|
|
2368
|
+
as,
|
|
2369
|
+
relation,
|
|
2370
|
+
repository: relatedRepository,
|
|
2371
|
+
options
|
|
2372
|
+
});
|
|
2373
|
+
return this;
|
|
2374
|
+
}
|
|
2365
2375
|
async get() {
|
|
2366
2376
|
const rows = await this.repository.findAll(this.buildOptions());
|
|
2367
2377
|
return await this.attach(rows);
|
|
2368
2378
|
}
|
|
2369
2379
|
async first() {
|
|
2370
|
-
const rows = await this.
|
|
2371
|
-
|
|
2380
|
+
const rows = await this.repository.findAll({ ...this.buildOptions(), limit: 1 });
|
|
2381
|
+
const attached = await this.attach(rows);
|
|
2382
|
+
return attached[0] ?? null;
|
|
2383
|
+
}
|
|
2384
|
+
async count() {
|
|
2385
|
+
return await this.repository.count(this.buildOptions());
|
|
2386
|
+
}
|
|
2387
|
+
async attachToRows(rows) {
|
|
2388
|
+
return await this.attach(rows);
|
|
2372
2389
|
}
|
|
2373
2390
|
async paginate(options) {
|
|
2374
2391
|
return await this.repository.paginate({
|
|
@@ -2440,6 +2457,15 @@ class RepositoryQuery {
|
|
|
2440
2457
|
}));
|
|
2441
2458
|
continue;
|
|
2442
2459
|
}
|
|
2460
|
+
if (load.kind === "belongsToMany") {
|
|
2461
|
+
const relation2 = load.relation;
|
|
2462
|
+
const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
|
|
2463
|
+
result = result.map((row) => ({
|
|
2464
|
+
...row,
|
|
2465
|
+
[load.as]: grouped2.get(row[relation2.parentKey]) ?? []
|
|
2466
|
+
}));
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2443
2469
|
if (load.kind === "morphTo") {
|
|
2444
2470
|
const relation2 = load.relation;
|
|
2445
2471
|
const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
|
|
@@ -2468,6 +2494,10 @@ class BaseRepository {
|
|
|
2468
2494
|
this.table = table;
|
|
2469
2495
|
this.connection = connection;
|
|
2470
2496
|
}
|
|
2497
|
+
async count(options = {}) {
|
|
2498
|
+
const { whereNodes, where, ...rest } = options;
|
|
2499
|
+
return await this.countWhere(where ?? {}, rest, whereNodes ?? []);
|
|
2500
|
+
}
|
|
2471
2501
|
async findAll(options = {}) {
|
|
2472
2502
|
return await withDatabaseErrorHandling(async () => {
|
|
2473
2503
|
const { whereNodes, ...queryOptions } = options;
|
|
@@ -2775,6 +2805,23 @@ class BaseRepository {
|
|
|
2775
2805
|
}
|
|
2776
2806
|
return indexMorphToRelation(children, parentsByType, relation);
|
|
2777
2807
|
}
|
|
2808
|
+
async loadBelongsToManyForParents(parents, relation, relatedRepository, options = {}) {
|
|
2809
|
+
if (parents.length === 0) {
|
|
2810
|
+
return indexBelongsToManyRelation(parents, [], [], relation);
|
|
2811
|
+
}
|
|
2812
|
+
const parentIds = [...new Set(parents.map((parent) => parent[relation.parentKey]))];
|
|
2813
|
+
const pivotRows = await this.connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = ANY($1)`, [parentIds]);
|
|
2814
|
+
if (pivotRows.length === 0) {
|
|
2815
|
+
return indexBelongsToManyRelation(parents, [], [], relation);
|
|
2816
|
+
}
|
|
2817
|
+
const relatedIds = [
|
|
2818
|
+
...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
|
|
2819
|
+
];
|
|
2820
|
+
const relatedRows = await relatedRepository.withConnection(this.connection).findWhere({
|
|
2821
|
+
[relation.relatedKey]: relatedIds
|
|
2822
|
+
}, options);
|
|
2823
|
+
return indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation);
|
|
2824
|
+
}
|
|
2778
2825
|
}
|
|
2779
2826
|
var baseRepository_default = BaseRepository;
|
|
2780
2827
|
// ../../src/core/database/bunSql.ts
|
|
@@ -2804,7 +2851,40 @@ function createDatabaseConnection(source) {
|
|
|
2804
2851
|
}
|
|
2805
2852
|
};
|
|
2806
2853
|
}
|
|
2854
|
+
// ../../src/core/database/inflection.ts
|
|
2855
|
+
function singularize(word) {
|
|
2856
|
+
if (word.endsWith("ies") && word.length > 3) {
|
|
2857
|
+
return `${word.slice(0, -3)}y`;
|
|
2858
|
+
}
|
|
2859
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) {
|
|
2860
|
+
return word.slice(0, -2);
|
|
2861
|
+
}
|
|
2862
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
2863
|
+
return word.slice(0, -1);
|
|
2864
|
+
}
|
|
2865
|
+
return word;
|
|
2866
|
+
}
|
|
2867
|
+
function foreignKeyFromTable(tableName) {
|
|
2868
|
+
return `${singularize(tableName)}_id`;
|
|
2869
|
+
}
|
|
2870
|
+
function pivotTableName(leftTable, rightTable) {
|
|
2871
|
+
return [singularize(leftTable), singularize(rightTable)].sort().join("_");
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2807
2874
|
// ../../src/core/database/factory.ts
|
|
2875
|
+
function inferFactoryForeignKey(parent, explicit) {
|
|
2876
|
+
if (explicit) {
|
|
2877
|
+
if (explicit.endsWith("_id")) {
|
|
2878
|
+
return explicit;
|
|
2879
|
+
}
|
|
2880
|
+
return `${explicit}_id`;
|
|
2881
|
+
}
|
|
2882
|
+
if (typeof parent.getRepository === "function") {
|
|
2883
|
+
return foreignKeyFromTable(parent.getRepository().getTable().name);
|
|
2884
|
+
}
|
|
2885
|
+
throw new Error("Factory.for() requires a foreign key or a parent Model.");
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2808
2888
|
class Factory {
|
|
2809
2889
|
quantity = 1;
|
|
2810
2890
|
counted = false;
|
|
@@ -2815,6 +2895,7 @@ class Factory {
|
|
|
2815
2895
|
children = [];
|
|
2816
2896
|
afterMakingCallbacks = [];
|
|
2817
2897
|
afterCreatingCallbacks = [];
|
|
2898
|
+
model;
|
|
2818
2899
|
definition() {
|
|
2819
2900
|
throw new Error("Factory definition must be implemented by subclass.");
|
|
2820
2901
|
}
|
|
@@ -2855,8 +2936,9 @@ class Factory {
|
|
|
2855
2936
|
if (parent.id === undefined || parent.id === null) {
|
|
2856
2937
|
throw new Error("Factory.for() requires a parent with an id.");
|
|
2857
2938
|
}
|
|
2939
|
+
const key = inferFactoryForeignKey(parent, foreignKey);
|
|
2858
2940
|
const next = this.clone();
|
|
2859
|
-
next.parentAssociations = [...this.parentAssociations, { foreignKey, value: parent.id }];
|
|
2941
|
+
next.parentAssociations = [...this.parentAssociations, { foreignKey: key, value: parent.id }];
|
|
2860
2942
|
return next;
|
|
2861
2943
|
}
|
|
2862
2944
|
recycle(parent, foreignKey) {
|
|
@@ -2937,29 +3019,17 @@ class Factory {
|
|
|
2937
3019
|
}
|
|
2938
3020
|
return values;
|
|
2939
3021
|
}
|
|
2940
|
-
persist(
|
|
3022
|
+
async persist(values) {
|
|
3023
|
+
if (this.model) {
|
|
3024
|
+
const created = await this.model.create(values);
|
|
3025
|
+
if (created && typeof created.toObject === "function") {
|
|
3026
|
+
return created.toObject();
|
|
3027
|
+
}
|
|
3028
|
+
return created;
|
|
3029
|
+
}
|
|
2941
3030
|
throw new Error("Factory.persist() must be implemented to use create().");
|
|
2942
3031
|
}
|
|
2943
3032
|
}
|
|
2944
|
-
// ../../src/core/database/inflection.ts
|
|
2945
|
-
function singularize(word) {
|
|
2946
|
-
if (word.endsWith("ies") && word.length > 3) {
|
|
2947
|
-
return `${word.slice(0, -3)}y`;
|
|
2948
|
-
}
|
|
2949
|
-
if (/(ses|xes|zes|ches|shes)$/i.test(word)) {
|
|
2950
|
-
return word.slice(0, -2);
|
|
2951
|
-
}
|
|
2952
|
-
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
2953
|
-
return word.slice(0, -1);
|
|
2954
|
-
}
|
|
2955
|
-
return word;
|
|
2956
|
-
}
|
|
2957
|
-
function foreignKeyFromTable(tableName) {
|
|
2958
|
-
return `${singularize(tableName)}_id`;
|
|
2959
|
-
}
|
|
2960
|
-
function pivotTableName(leftTable, rightTable) {
|
|
2961
|
-
return [singularize(leftTable), singularize(rightTable)].sort().join("_");
|
|
2962
|
-
}
|
|
2963
3033
|
// ../../src/core/database/migrations/advisoryLock.ts
|
|
2964
3034
|
var MIGRATION_LOCK_KEY = 42424242;
|
|
2965
3035
|
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
@@ -3085,6 +3155,9 @@ function ownerId(owner, ownerKey) {
|
|
|
3085
3155
|
}
|
|
3086
3156
|
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
3087
3157
|
}
|
|
3158
|
+
function thenGet(get, onfulfilled, onrejected) {
|
|
3159
|
+
return get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
3160
|
+
}
|
|
3088
3161
|
|
|
3089
3162
|
class HasManyRelationQuery {
|
|
3090
3163
|
parent;
|
|
@@ -3148,13 +3221,28 @@ class HasManyRelationQuery {
|
|
|
3148
3221
|
return rows[0] ?? null;
|
|
3149
3222
|
}
|
|
3150
3223
|
async count() {
|
|
3151
|
-
return
|
|
3224
|
+
return this.scopedQuery().count();
|
|
3225
|
+
}
|
|
3226
|
+
then(onfulfilled, onrejected) {
|
|
3227
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3152
3228
|
}
|
|
3153
3229
|
async create(attributes = {}) {
|
|
3154
3230
|
return this.related.create(attributes, {
|
|
3155
3231
|
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3156
3232
|
});
|
|
3157
3233
|
}
|
|
3234
|
+
async save(related) {
|
|
3235
|
+
const forced = {
|
|
3236
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3237
|
+
};
|
|
3238
|
+
const savable = related;
|
|
3239
|
+
if (typeof savable.save === "function") {
|
|
3240
|
+
savable.mergeAttributes?.(forced);
|
|
3241
|
+
await savable.save();
|
|
3242
|
+
return related;
|
|
3243
|
+
}
|
|
3244
|
+
return this.create(related);
|
|
3245
|
+
}
|
|
3158
3246
|
async createMany(records) {
|
|
3159
3247
|
const created = [];
|
|
3160
3248
|
for (const attributes of records) {
|
|
@@ -3202,11 +3290,17 @@ class HasOneRelationQuery {
|
|
|
3202
3290
|
return this.get();
|
|
3203
3291
|
}
|
|
3204
3292
|
async count() {
|
|
3205
|
-
return
|
|
3293
|
+
return this.inner.count();
|
|
3294
|
+
}
|
|
3295
|
+
then(onfulfilled, onrejected) {
|
|
3296
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3206
3297
|
}
|
|
3207
3298
|
async create(attributes = {}) {
|
|
3208
3299
|
return this.inner.create(attributes);
|
|
3209
3300
|
}
|
|
3301
|
+
async save(related) {
|
|
3302
|
+
return this.inner.save(related);
|
|
3303
|
+
}
|
|
3210
3304
|
}
|
|
3211
3305
|
|
|
3212
3306
|
class BelongsToRelationQuery {
|
|
@@ -3214,12 +3308,17 @@ class BelongsToRelationQuery {
|
|
|
3214
3308
|
related;
|
|
3215
3309
|
relation;
|
|
3216
3310
|
kind = "belongsTo";
|
|
3311
|
+
extraWhere = {};
|
|
3217
3312
|
extraOptions = {};
|
|
3218
3313
|
constructor(parent, related, relation) {
|
|
3219
3314
|
this.parent = parent;
|
|
3220
3315
|
this.related = related;
|
|
3221
3316
|
this.relation = relation;
|
|
3222
3317
|
}
|
|
3318
|
+
where(where) {
|
|
3319
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3320
|
+
return this;
|
|
3321
|
+
}
|
|
3223
3322
|
orderBy(orderBy) {
|
|
3224
3323
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3225
3324
|
return this;
|
|
@@ -3233,10 +3332,10 @@ class BelongsToRelationQuery {
|
|
|
3233
3332
|
}
|
|
3234
3333
|
toExistsClause(parentTable) {
|
|
3235
3334
|
const relatedTable = this.related.repository().getTable().name;
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
};
|
|
3335
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
3336
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3337
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(relatedTable)} WHERE ${qualifyColumn(relatedTable, this.relation.ownerKey)} = ${qualifyColumn(parentTable, this.relation.foreignKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3338
|
+
return { sql, params: extra.params };
|
|
3240
3339
|
}
|
|
3241
3340
|
async get() {
|
|
3242
3341
|
const foreign = this.parent.get(this.relation.foreignKey);
|
|
@@ -3244,7 +3343,7 @@ class BelongsToRelationQuery {
|
|
|
3244
3343
|
return null;
|
|
3245
3344
|
}
|
|
3246
3345
|
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3247
|
-
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign }));
|
|
3346
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign, ...this.extraWhere }));
|
|
3248
3347
|
if (this.extraOptions.orderBy) {
|
|
3249
3348
|
query = query.orderBy(this.extraOptions.orderBy);
|
|
3250
3349
|
}
|
|
@@ -3254,6 +3353,9 @@ class BelongsToRelationQuery {
|
|
|
3254
3353
|
async first() {
|
|
3255
3354
|
return this.get();
|
|
3256
3355
|
}
|
|
3356
|
+
then(onfulfilled, onrejected) {
|
|
3357
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3358
|
+
}
|
|
3257
3359
|
async associate(owner) {
|
|
3258
3360
|
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3259
3361
|
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
@@ -3273,6 +3375,7 @@ class BelongsToManyRelationQuery {
|
|
|
3273
3375
|
kind = "belongsToMany";
|
|
3274
3376
|
extraWhere = {};
|
|
3275
3377
|
extraOptions = {};
|
|
3378
|
+
pivotValues = {};
|
|
3276
3379
|
constructor(parent, related, relation) {
|
|
3277
3380
|
this.parent = parent;
|
|
3278
3381
|
this.related = related;
|
|
@@ -3286,7 +3389,13 @@ class BelongsToManyRelationQuery {
|
|
|
3286
3389
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3287
3390
|
return this;
|
|
3288
3391
|
}
|
|
3289
|
-
applyEagerLoad(
|
|
3392
|
+
applyEagerLoad(query, alias) {
|
|
3393
|
+
query.withBelongsToMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3394
|
+
}
|
|
3395
|
+
withPivotValues(values) {
|
|
3396
|
+
this.pivotValues = { ...this.pivotValues, ...values };
|
|
3397
|
+
return this;
|
|
3398
|
+
}
|
|
3290
3399
|
hydrateEager(row, alias) {
|
|
3291
3400
|
const value = row[alias] ?? [];
|
|
3292
3401
|
const rows = Array.isArray(value) ? value : [];
|
|
@@ -3326,13 +3435,34 @@ class BelongsToManyRelationQuery {
|
|
|
3326
3435
|
return rows[0] ?? null;
|
|
3327
3436
|
}
|
|
3328
3437
|
async count() {
|
|
3329
|
-
|
|
3438
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3439
|
+
const rows = await this.connection().unsafe(`SELECT COUNT(*) AS count FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3440
|
+
return Number(rows[0]?.count ?? 0);
|
|
3441
|
+
}
|
|
3442
|
+
then(onfulfilled, onrejected) {
|
|
3443
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3330
3444
|
}
|
|
3331
3445
|
async attach(ids) {
|
|
3446
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3447
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3448
|
+
const extraKeys = Object.keys(this.pivotValues);
|
|
3449
|
+
const extraColumns = extraKeys.length > 0 ? `, ${extraKeys.join(", ")}` : "";
|
|
3450
|
+
const extraPlaceholders = extraKeys.map((_, index) => `$${index + 3}`).join(", ");
|
|
3451
|
+
const extraValues = extraKeys.map((key) => this.pivotValues[key]);
|
|
3452
|
+
for (const id of list) {
|
|
3453
|
+
await this.connection().unsafe(extraKeys.length > 0 ? `INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}${extraColumns}) VALUES ($1, $2, ${extraPlaceholders})` : `INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}) VALUES ($1, $2)`, [parentId, id, ...extraValues]);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
async toggle(ids) {
|
|
3332
3457
|
const list = Array.isArray(ids) ? ids : [ids];
|
|
3333
3458
|
const parentId = this.parent.get(this.relation.parentKey);
|
|
3334
3459
|
for (const id of list) {
|
|
3335
|
-
await this.connection().unsafe(`
|
|
3460
|
+
const existing = await this.connection().unsafe(`SELECT 1 FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = $2 LIMIT 1`, [parentId, id]);
|
|
3461
|
+
if (existing.length > 0) {
|
|
3462
|
+
await this.detach(id);
|
|
3463
|
+
} else {
|
|
3464
|
+
await this.attach(id);
|
|
3465
|
+
}
|
|
3336
3466
|
}
|
|
3337
3467
|
}
|
|
3338
3468
|
async detach(ids) {
|
|
@@ -3404,6 +3534,17 @@ class MorphManyRelationQuery {
|
|
|
3404
3534
|
const rows = await this.get();
|
|
3405
3535
|
return rows[0] ?? null;
|
|
3406
3536
|
}
|
|
3537
|
+
async count() {
|
|
3538
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3539
|
+
return repository.query(asWhere({
|
|
3540
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3541
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
3542
|
+
...this.extraWhere
|
|
3543
|
+
})).count();
|
|
3544
|
+
}
|
|
3545
|
+
then(onfulfilled, onrejected) {
|
|
3546
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3547
|
+
}
|
|
3407
3548
|
async create(attributes = {}) {
|
|
3408
3549
|
return this.related.create(attributes, {
|
|
3409
3550
|
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
@@ -3444,6 +3585,15 @@ class MorphOneRelationQuery {
|
|
|
3444
3585
|
async get() {
|
|
3445
3586
|
return this.inner.first();
|
|
3446
3587
|
}
|
|
3588
|
+
async first() {
|
|
3589
|
+
return this.get();
|
|
3590
|
+
}
|
|
3591
|
+
async count() {
|
|
3592
|
+
return this.inner.count();
|
|
3593
|
+
}
|
|
3594
|
+
then(onfulfilled, onrejected) {
|
|
3595
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3596
|
+
}
|
|
3447
3597
|
async create(attributes = {}) {
|
|
3448
3598
|
return this.inner.create(attributes);
|
|
3449
3599
|
}
|
|
@@ -3454,11 +3604,16 @@ class MorphToRelationQuery {
|
|
|
3454
3604
|
relatedByType;
|
|
3455
3605
|
relation;
|
|
3456
3606
|
kind = "morphTo";
|
|
3607
|
+
extraWhere = {};
|
|
3457
3608
|
constructor(parent, relatedByType, relation) {
|
|
3458
3609
|
this.parent = parent;
|
|
3459
3610
|
this.relatedByType = relatedByType;
|
|
3460
3611
|
this.relation = relation;
|
|
3461
3612
|
}
|
|
3613
|
+
where(where) {
|
|
3614
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3615
|
+
return this;
|
|
3616
|
+
}
|
|
3462
3617
|
applyEagerLoad(query, alias) {
|
|
3463
3618
|
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
3464
3619
|
type,
|
|
@@ -3476,9 +3631,11 @@ class MorphToRelationQuery {
|
|
|
3476
3631
|
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
3477
3632
|
}
|
|
3478
3633
|
const relatedTable = related.repository().getTable();
|
|
3634
|
+
const extra = buildAdvancedWhereClause(relatedTable.name, this.extraWhere, [], []);
|
|
3635
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3479
3636
|
return {
|
|
3480
|
-
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}`,
|
|
3481
|
-
params:
|
|
3637
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}${extraSql ? ` AND ${extraSql}` : ""}`,
|
|
3638
|
+
params: extra.params
|
|
3482
3639
|
};
|
|
3483
3640
|
}
|
|
3484
3641
|
async get() {
|
|
@@ -3489,13 +3646,17 @@ class MorphToRelationQuery {
|
|
|
3489
3646
|
return null;
|
|
3490
3647
|
}
|
|
3491
3648
|
const table = related.repository().getTable();
|
|
3492
|
-
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id })).first();
|
|
3649
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id, ...this.extraWhere })).first();
|
|
3493
3650
|
return row ? related.newFromRecord(row) : null;
|
|
3494
3651
|
}
|
|
3652
|
+
then(onfulfilled, onrejected) {
|
|
3653
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3654
|
+
}
|
|
3495
3655
|
}
|
|
3496
3656
|
|
|
3497
3657
|
// ../../src/core/database/model.ts
|
|
3498
3658
|
var modelRepositories = new WeakMap;
|
|
3659
|
+
var namedModels = new Map;
|
|
3499
3660
|
var modelGlobalScopes = new WeakMap;
|
|
3500
3661
|
var modelObservers = new WeakMap;
|
|
3501
3662
|
var modelBooted = new WeakSet;
|
|
@@ -3513,38 +3674,59 @@ function accessorName(key) {
|
|
|
3513
3674
|
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
3514
3675
|
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
3515
3676
|
}
|
|
3516
|
-
function
|
|
3517
|
-
|
|
3518
|
-
ensureBooted(model);
|
|
3519
|
-
const repository = resolveModelRepository(model);
|
|
3520
|
-
const dummy = statics.newFromRecord({});
|
|
3521
|
-
const method = dummy[name];
|
|
3522
|
-
if (typeof method !== "function") {
|
|
3523
|
-
throw new Error(`${model.name} has no relation method ${name}().`);
|
|
3524
|
-
}
|
|
3525
|
-
const relationQuery = method.call(dummy);
|
|
3526
|
-
constrain?.(relationQuery);
|
|
3527
|
-
const exists = relationQuery.toExistsClause(repository.getTable().name);
|
|
3528
|
-
const query = Model.query.call(model);
|
|
3529
|
-
return not ? query.whereNotExists(exists.sql, exists.params) : query.whereExists(exists.sql, exists.params);
|
|
3677
|
+
function isLoadableModel(value) {
|
|
3678
|
+
return Boolean(value && typeof value === "object" && typeof value.load === "function" && typeof value.loaded === "function" && typeof value.setLoaded === "function");
|
|
3530
3679
|
}
|
|
3531
|
-
async function
|
|
3532
|
-
|
|
3533
|
-
if (!head) {
|
|
3680
|
+
async function eagerLoadOnModels(models, paths) {
|
|
3681
|
+
if (models.length === 0 || paths.length === 0) {
|
|
3534
3682
|
return;
|
|
3535
3683
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3684
|
+
const grouped = new Map;
|
|
3685
|
+
for (const path of paths) {
|
|
3686
|
+
const [head, ...rest] = path.split(".");
|
|
3687
|
+
if (!head) {
|
|
3688
|
+
continue;
|
|
3689
|
+
}
|
|
3690
|
+
const nested = rest.join(".");
|
|
3691
|
+
const existing = grouped.get(head) ?? [];
|
|
3692
|
+
if (nested) {
|
|
3693
|
+
existing.push(nested);
|
|
3694
|
+
}
|
|
3695
|
+
grouped.set(head, existing);
|
|
3539
3696
|
}
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3697
|
+
for (const [head, nested] of grouped) {
|
|
3698
|
+
const unloaded = models.filter((model) => model.loaded(head) === undefined);
|
|
3699
|
+
if (unloaded.length > 0) {
|
|
3700
|
+
const first = unloaded[0];
|
|
3701
|
+
if (!first) {
|
|
3702
|
+
continue;
|
|
3703
|
+
}
|
|
3704
|
+
const method = first[head];
|
|
3705
|
+
if (typeof method !== "function") {
|
|
3706
|
+
throw new Error(`${first.constructor.name} has no relation method ${head}().`);
|
|
3707
|
+
}
|
|
3708
|
+
const relationQuery = method.call(first);
|
|
3709
|
+
const query = first.getRepository().query();
|
|
3710
|
+
relationQuery.applyEagerLoad(query, head);
|
|
3711
|
+
const attached = await query.attachToRows(unloaded.map((model) => model.toObject()));
|
|
3712
|
+
for (const [index, model] of unloaded.entries()) {
|
|
3713
|
+
const row = attached[index] ?? model.toObject();
|
|
3714
|
+
model.setLoaded(head, relationQuery.hydrateEager(row, head));
|
|
3715
|
+
}
|
|
3545
3716
|
}
|
|
3717
|
+
if (nested.length === 0) {
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
const children = models.flatMap((model) => {
|
|
3721
|
+
const loaded = model.loaded(head);
|
|
3722
|
+
return Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
|
|
3723
|
+
});
|
|
3724
|
+
await eagerLoadOnModels(children.filter(isLoadableModel), nested);
|
|
3546
3725
|
}
|
|
3547
3726
|
}
|
|
3727
|
+
async function loadNested(model, path) {
|
|
3728
|
+
await eagerLoadOnModels([model], [path]);
|
|
3729
|
+
}
|
|
3548
3730
|
function resolveModelRepository(model) {
|
|
3549
3731
|
const repository = modelRepositories.get(model);
|
|
3550
3732
|
if (!repository) {
|
|
@@ -3552,6 +3734,48 @@ function resolveModelRepository(model) {
|
|
|
3552
3734
|
}
|
|
3553
3735
|
return repository;
|
|
3554
3736
|
}
|
|
3737
|
+
function registerModelClass(name, model) {
|
|
3738
|
+
namedModels.set(name, model);
|
|
3739
|
+
}
|
|
3740
|
+
function resolveRelated(related) {
|
|
3741
|
+
if (typeof related === "string") {
|
|
3742
|
+
const found = namedModels.get(related);
|
|
3743
|
+
if (!found) {
|
|
3744
|
+
throw new Error(`Model [${related}] is not registered. Call registerModelClass() first.`);
|
|
3745
|
+
}
|
|
3746
|
+
return found;
|
|
3747
|
+
}
|
|
3748
|
+
if (typeof related === "function" && typeof related.repository !== "function") {
|
|
3749
|
+
return related();
|
|
3750
|
+
}
|
|
3751
|
+
return related;
|
|
3752
|
+
}
|
|
3753
|
+
function inferRelationMethodName(callee) {
|
|
3754
|
+
const stack = new Error().stack ?? "";
|
|
3755
|
+
let seenCallee = false;
|
|
3756
|
+
for (const line of stack.split(`
|
|
3757
|
+
`)) {
|
|
3758
|
+
const match = /at (?:async )?(?:[^.\s]+\.)?(\w+)/.exec(line);
|
|
3759
|
+
const name = match?.[1];
|
|
3760
|
+
if (!name || name === "Error" || name === "inferRelationMethodName") {
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3763
|
+
if (!seenCallee) {
|
|
3764
|
+
if (name === callee) {
|
|
3765
|
+
seenCallee = true;
|
|
3766
|
+
}
|
|
3767
|
+
continue;
|
|
3768
|
+
}
|
|
3769
|
+
if (name !== callee) {
|
|
3770
|
+
return name;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
function morphClassOf(model) {
|
|
3776
|
+
const statics = modelStatics(model.constructor === Function ? model : model.constructor);
|
|
3777
|
+
return statics.$morphClass ?? (model.constructor === Function ? model.name : model.constructor.name);
|
|
3778
|
+
}
|
|
3555
3779
|
function modelStatics(model) {
|
|
3556
3780
|
return model;
|
|
3557
3781
|
}
|
|
@@ -3581,6 +3805,11 @@ function hydrateValue(value, cast) {
|
|
|
3581
3805
|
case "bool":
|
|
3582
3806
|
case "boolean":
|
|
3583
3807
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
3808
|
+
case "integer":
|
|
3809
|
+
case "int":
|
|
3810
|
+
return value === "" ? null : Number(value);
|
|
3811
|
+
case "hashed":
|
|
3812
|
+
return value;
|
|
3584
3813
|
default:
|
|
3585
3814
|
return value;
|
|
3586
3815
|
}
|
|
@@ -3598,6 +3827,11 @@ function dehydrateValue(value, cast) {
|
|
|
3598
3827
|
case "bool":
|
|
3599
3828
|
case "boolean":
|
|
3600
3829
|
return Boolean(value);
|
|
3830
|
+
case "integer":
|
|
3831
|
+
case "int":
|
|
3832
|
+
return value === "" ? null : Number(value);
|
|
3833
|
+
case "hashed":
|
|
3834
|
+
return value;
|
|
3601
3835
|
default:
|
|
3602
3836
|
return value;
|
|
3603
3837
|
}
|
|
@@ -3652,6 +3886,153 @@ function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
|
3652
3886
|
return result;
|
|
3653
3887
|
}
|
|
3654
3888
|
|
|
3889
|
+
class ModelQuery {
|
|
3890
|
+
modelClass;
|
|
3891
|
+
query;
|
|
3892
|
+
eager = [];
|
|
3893
|
+
constructor(modelClass, query) {
|
|
3894
|
+
this.modelClass = modelClass;
|
|
3895
|
+
this.query = query;
|
|
3896
|
+
}
|
|
3897
|
+
with(...relations) {
|
|
3898
|
+
const statics = modelStatics(this.modelClass);
|
|
3899
|
+
ensureBooted(this.modelClass);
|
|
3900
|
+
const dummy = statics.newFromRecord({}, false);
|
|
3901
|
+
for (const path of relations) {
|
|
3902
|
+
const name = path.split(".")[0] ?? path;
|
|
3903
|
+
const method = dummy[name];
|
|
3904
|
+
if (typeof method !== "function") {
|
|
3905
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
3906
|
+
}
|
|
3907
|
+
const relationQuery = method.call(dummy);
|
|
3908
|
+
this.eager.push({ name, path, relationQuery });
|
|
3909
|
+
relationQuery.applyEagerLoad(this.query, name);
|
|
3910
|
+
}
|
|
3911
|
+
return this;
|
|
3912
|
+
}
|
|
3913
|
+
where(input) {
|
|
3914
|
+
this.query.where(input);
|
|
3915
|
+
return this;
|
|
3916
|
+
}
|
|
3917
|
+
orWhere(input) {
|
|
3918
|
+
this.query.orWhere(input);
|
|
3919
|
+
return this;
|
|
3920
|
+
}
|
|
3921
|
+
orderBy(orderBy) {
|
|
3922
|
+
this.query.orderBy(orderBy);
|
|
3923
|
+
return this;
|
|
3924
|
+
}
|
|
3925
|
+
limit(limit) {
|
|
3926
|
+
this.query.limit(limit);
|
|
3927
|
+
return this;
|
|
3928
|
+
}
|
|
3929
|
+
offset(offset) {
|
|
3930
|
+
this.query.offset(offset);
|
|
3931
|
+
return this;
|
|
3932
|
+
}
|
|
3933
|
+
whereNull(column) {
|
|
3934
|
+
this.query.whereNull(column);
|
|
3935
|
+
return this;
|
|
3936
|
+
}
|
|
3937
|
+
whereIn(column, values) {
|
|
3938
|
+
this.query.whereIn(column, values);
|
|
3939
|
+
return this;
|
|
3940
|
+
}
|
|
3941
|
+
whereExists(sql, params = []) {
|
|
3942
|
+
this.query.whereExists(sql, params);
|
|
3943
|
+
return this;
|
|
3944
|
+
}
|
|
3945
|
+
whereNotExists(sql, params = []) {
|
|
3946
|
+
this.query.whereNotExists(sql, params);
|
|
3947
|
+
return this;
|
|
3948
|
+
}
|
|
3949
|
+
whereHas(name, constrain) {
|
|
3950
|
+
return this.constrainExists(name, constrain, false);
|
|
3951
|
+
}
|
|
3952
|
+
has(name) {
|
|
3953
|
+
return this.constrainExists(name, undefined, false);
|
|
3954
|
+
}
|
|
3955
|
+
doesntHave(name) {
|
|
3956
|
+
return this.constrainExists(name, undefined, true);
|
|
3957
|
+
}
|
|
3958
|
+
whereDoesntHave(name, constrain) {
|
|
3959
|
+
return this.constrainExists(name, constrain, true);
|
|
3960
|
+
}
|
|
3961
|
+
withHasMany(...args) {
|
|
3962
|
+
this.query.withHasMany(...args);
|
|
3963
|
+
return this;
|
|
3964
|
+
}
|
|
3965
|
+
withBelongsTo(...args) {
|
|
3966
|
+
this.query.withBelongsTo(...args);
|
|
3967
|
+
return this;
|
|
3968
|
+
}
|
|
3969
|
+
withBelongsToMany(...args) {
|
|
3970
|
+
this.query.withBelongsToMany(...args);
|
|
3971
|
+
return this;
|
|
3972
|
+
}
|
|
3973
|
+
withMorphMany(...args) {
|
|
3974
|
+
this.query.withMorphMany(...args);
|
|
3975
|
+
return this;
|
|
3976
|
+
}
|
|
3977
|
+
withMorphOne(...args) {
|
|
3978
|
+
this.query.withMorphOne(...args);
|
|
3979
|
+
return this;
|
|
3980
|
+
}
|
|
3981
|
+
withMorphTo(...args) {
|
|
3982
|
+
this.query.withMorphTo(...args);
|
|
3983
|
+
return this;
|
|
3984
|
+
}
|
|
3985
|
+
async get() {
|
|
3986
|
+
const statics = modelStatics(this.modelClass);
|
|
3987
|
+
const rows = await this.query.get();
|
|
3988
|
+
const models = [];
|
|
3989
|
+
for (const row of rows) {
|
|
3990
|
+
const model = statics.newFromRecord(row, true);
|
|
3991
|
+
await runObservers(model, "retrieved");
|
|
3992
|
+
for (const { name, relationQuery } of this.eager) {
|
|
3993
|
+
model.setLoaded(name, relationQuery.hydrateEager(row, name));
|
|
3994
|
+
}
|
|
3995
|
+
models.push(model);
|
|
3996
|
+
}
|
|
3997
|
+
const nested = this.eager.filter((item) => item.path.includes(".")).map((item) => item.path);
|
|
3998
|
+
await eagerLoadOnModels(models.filter(isLoadableModel), nested);
|
|
3999
|
+
return models;
|
|
4000
|
+
}
|
|
4001
|
+
async first() {
|
|
4002
|
+
this.query.limit(1);
|
|
4003
|
+
const models = await this.get();
|
|
4004
|
+
return models[0] ?? null;
|
|
4005
|
+
}
|
|
4006
|
+
async find(id) {
|
|
4007
|
+
const primaryKey = resolveModelRepository(this.modelClass).getTable().primaryKey;
|
|
4008
|
+
return this.where({ [primaryKey]: id }).first();
|
|
4009
|
+
}
|
|
4010
|
+
async findOrFail(id, errorFactory) {
|
|
4011
|
+
const model = await this.find(id);
|
|
4012
|
+
if (model) {
|
|
4013
|
+
return model;
|
|
4014
|
+
}
|
|
4015
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.modelClass.name} ${String(id)} not found.`);
|
|
4016
|
+
}
|
|
4017
|
+
then(onfulfilled, onrejected) {
|
|
4018
|
+
return this.get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
4019
|
+
}
|
|
4020
|
+
constrainExists(name, constrain, not) {
|
|
4021
|
+
const statics = modelStatics(this.modelClass);
|
|
4022
|
+
ensureBooted(this.modelClass);
|
|
4023
|
+
const repository = resolveModelRepository(this.modelClass);
|
|
4024
|
+
const dummy = statics.newFromRecord({});
|
|
4025
|
+
const method = dummy[name];
|
|
4026
|
+
if (typeof method !== "function") {
|
|
4027
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
4028
|
+
}
|
|
4029
|
+
const relationQuery = method.call(dummy);
|
|
4030
|
+
constrain?.(relationQuery);
|
|
4031
|
+
const exists = relationQuery.toExistsClause(repository.getTable().name);
|
|
4032
|
+
return not ? this.whereNotExists(exists.sql, exists.params) : this.whereExists(exists.sql, exists.params);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
|
|
3655
4036
|
class Model {
|
|
3656
4037
|
attributes;
|
|
3657
4038
|
repository;
|
|
@@ -3662,6 +4043,7 @@ class Model {
|
|
|
3662
4043
|
static $hidden;
|
|
3663
4044
|
static $visible;
|
|
3664
4045
|
static $appends;
|
|
4046
|
+
static $morphClass;
|
|
3665
4047
|
_exists;
|
|
3666
4048
|
loadedRelations = {};
|
|
3667
4049
|
hiddenOverrides = [];
|
|
@@ -3671,6 +4053,7 @@ class Model {
|
|
|
3671
4053
|
this.attributes = attributes;
|
|
3672
4054
|
this.repository = repository;
|
|
3673
4055
|
this._exists = exists;
|
|
4056
|
+
this.attributes = modelStatics(this.constructor).hydrateAttributes(attributes);
|
|
3674
4057
|
}
|
|
3675
4058
|
getRepository() {
|
|
3676
4059
|
return this.repository;
|
|
@@ -3731,7 +4114,7 @@ class Model {
|
|
|
3731
4114
|
return this;
|
|
3732
4115
|
}
|
|
3733
4116
|
primaryKey() {
|
|
3734
|
-
|
|
4117
|
+
return this.repository.getTable().primaryKey;
|
|
3735
4118
|
}
|
|
3736
4119
|
static primaryKeyField() {
|
|
3737
4120
|
return resolveModelRepository(this).getTable().primaryKey;
|
|
@@ -3772,7 +4155,7 @@ class Model {
|
|
|
3772
4155
|
for (const scope of getGlobalScopes(this)) {
|
|
3773
4156
|
query = scope(query);
|
|
3774
4157
|
}
|
|
3775
|
-
return query;
|
|
4158
|
+
return new ModelQuery(this, query);
|
|
3776
4159
|
}
|
|
3777
4160
|
static newFromRecord(record, exists = true) {
|
|
3778
4161
|
const repository = resolveModelRepository(this);
|
|
@@ -3791,79 +4174,36 @@ class Model {
|
|
|
3791
4174
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
3792
4175
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
3793
4176
|
const pending = statics.newFromRecord({ ...payload }, false);
|
|
4177
|
+
if (await runObservers(pending, "saving") === false) {
|
|
4178
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
4179
|
+
}
|
|
3794
4180
|
if (await runObservers(pending, "creating") === false) {
|
|
3795
4181
|
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
3796
4182
|
}
|
|
3797
4183
|
const record = await repository.create(payload);
|
|
3798
4184
|
const created = statics.fromRecord(record, repository, true);
|
|
3799
4185
|
await runObservers(created, "created");
|
|
4186
|
+
await runObservers(created, "saved");
|
|
3800
4187
|
return created;
|
|
3801
4188
|
}
|
|
3802
4189
|
static with(...relations) {
|
|
3803
|
-
|
|
3804
|
-
ensureBooted(this);
|
|
3805
|
-
const repository = resolveModelRepository(this);
|
|
3806
|
-
const dummy = statics.fromRecord({}, repository, false);
|
|
3807
|
-
const resolved = relations.map((path) => {
|
|
3808
|
-
const name = path.split(".")[0] ?? path;
|
|
3809
|
-
const method = dummy[name];
|
|
3810
|
-
if (typeof method !== "function") {
|
|
3811
|
-
throw new Error(`${this.name} has no relation method ${name}().`);
|
|
3812
|
-
}
|
|
3813
|
-
const relationQuery = method.call(dummy);
|
|
3814
|
-
return { name, path, relationQuery };
|
|
3815
|
-
});
|
|
3816
|
-
const query = Model.query.call(this);
|
|
3817
|
-
for (const { name, relationQuery } of resolved) {
|
|
3818
|
-
if (relationQuery.kind !== "belongsToMany") {
|
|
3819
|
-
relationQuery.applyEagerLoad(query, name);
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3822
|
-
return {
|
|
3823
|
-
async get() {
|
|
3824
|
-
const rows = await query.get();
|
|
3825
|
-
const models = [];
|
|
3826
|
-
for (const row of rows) {
|
|
3827
|
-
const model = statics.fromRecord(row, repository, true);
|
|
3828
|
-
for (const { name, path, relationQuery } of resolved) {
|
|
3829
|
-
if (relationQuery.kind === "belongsToMany") {
|
|
3830
|
-
await model.load(path.includes(".") ? path : name);
|
|
3831
|
-
continue;
|
|
3832
|
-
}
|
|
3833
|
-
model.setLoaded(name, relationQuery.hydrateEager(row, name));
|
|
3834
|
-
const nested = path.split(".").slice(1).join(".");
|
|
3835
|
-
if (nested) {
|
|
3836
|
-
await loadNested(model, path);
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
models.push(model);
|
|
3840
|
-
}
|
|
3841
|
-
return models;
|
|
3842
|
-
},
|
|
3843
|
-
async first() {
|
|
3844
|
-
const [model] = await this.get();
|
|
3845
|
-
return model ?? null;
|
|
3846
|
-
}
|
|
3847
|
-
};
|
|
4190
|
+
return Model.query.call(this).with(...relations);
|
|
3848
4191
|
}
|
|
3849
4192
|
static whereHas(name, constrain) {
|
|
3850
|
-
return
|
|
4193
|
+
return Model.query.call(this).whereHas(name, constrain);
|
|
3851
4194
|
}
|
|
3852
4195
|
static has(name) {
|
|
3853
|
-
return
|
|
4196
|
+
return Model.query.call(this).has(name);
|
|
3854
4197
|
}
|
|
3855
4198
|
static doesntHave(name) {
|
|
3856
|
-
return
|
|
4199
|
+
return Model.query.call(this).doesntHave(name);
|
|
3857
4200
|
}
|
|
3858
4201
|
static whereDoesntHave(name, constrain) {
|
|
3859
|
-
return
|
|
4202
|
+
return Model.query.call(this).whereDoesntHave(name, constrain);
|
|
3860
4203
|
}
|
|
3861
4204
|
static async find(id) {
|
|
3862
|
-
const
|
|
3863
|
-
|
|
3864
|
-
const primaryKey = repository.getTable().primaryKey;
|
|
3865
|
-
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
3866
|
-
return record ? statics.fromRecord(record, repository, true) : null;
|
|
4205
|
+
const primaryKey = resolveModelRepository(this).getTable().primaryKey;
|
|
4206
|
+
return Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
3867
4207
|
}
|
|
3868
4208
|
static async findOrFail(id, errorFactory) {
|
|
3869
4209
|
const model = await Model.find.call(this, id);
|
|
@@ -3873,8 +4213,6 @@ class Model {
|
|
|
3873
4213
|
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
3874
4214
|
}
|
|
3875
4215
|
static async all(options = {}) {
|
|
3876
|
-
const statics = modelStatics(this);
|
|
3877
|
-
const repository = resolveModelRepository(this);
|
|
3878
4216
|
let query = Model.query.call(this);
|
|
3879
4217
|
if (options.orderBy) {
|
|
3880
4218
|
query = query.orderBy(options.orderBy);
|
|
@@ -3882,21 +4220,17 @@ class Model {
|
|
|
3882
4220
|
if (options.limit !== undefined) {
|
|
3883
4221
|
query = query.limit(options.limit);
|
|
3884
4222
|
}
|
|
3885
|
-
|
|
3886
|
-
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
4223
|
+
return query.get();
|
|
3887
4224
|
}
|
|
3888
4225
|
static where(where) {
|
|
3889
4226
|
return Model.query.call(this).where(where);
|
|
3890
4227
|
}
|
|
3891
4228
|
static async firstWhere(where, options = {}) {
|
|
3892
|
-
const statics = modelStatics(this);
|
|
3893
|
-
const repository = resolveModelRepository(this);
|
|
3894
4229
|
let query = Model.query.call(this).where(where);
|
|
3895
4230
|
if (options.orderBy) {
|
|
3896
4231
|
query = query.orderBy(options.orderBy);
|
|
3897
4232
|
}
|
|
3898
|
-
|
|
3899
|
-
return record ? statics.fromRecord(record, repository, true) : null;
|
|
4233
|
+
return query.first();
|
|
3900
4234
|
}
|
|
3901
4235
|
static async firstOrNew(where, values = {}) {
|
|
3902
4236
|
const existing = await Model.firstWhere.call(this, where);
|
|
@@ -3925,6 +4259,9 @@ class Model {
|
|
|
3925
4259
|
const casts = ModelClass.$casts ?? {};
|
|
3926
4260
|
const table = this.repository.getTable();
|
|
3927
4261
|
const updating = this.$exists;
|
|
4262
|
+
if (await runObservers(this, "saving") === false) {
|
|
4263
|
+
return this;
|
|
4264
|
+
}
|
|
3928
4265
|
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
3929
4266
|
return this;
|
|
3930
4267
|
}
|
|
@@ -3933,6 +4270,7 @@ class Model {
|
|
|
3933
4270
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
3934
4271
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
3935
4272
|
await runObservers(this, "updated");
|
|
4273
|
+
await runObservers(this, "saved");
|
|
3936
4274
|
return this;
|
|
3937
4275
|
}
|
|
3938
4276
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -3942,6 +4280,7 @@ class Model {
|
|
|
3942
4280
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
3943
4281
|
this._exists = true;
|
|
3944
4282
|
await runObservers(this, "created");
|
|
4283
|
+
await runObservers(this, "saved");
|
|
3945
4284
|
return this;
|
|
3946
4285
|
}
|
|
3947
4286
|
async update(changes) {
|
|
@@ -4009,23 +4348,26 @@ class Model {
|
|
|
4009
4348
|
}
|
|
4010
4349
|
hasMany(related, foreignKey, localKey) {
|
|
4011
4350
|
const table = this.repository.getTable();
|
|
4012
|
-
|
|
4013
|
-
|
|
4351
|
+
const relatedClass = resolveRelated(related);
|
|
4352
|
+
return new HasManyRelationQuery(this, relatedClass, hasMany({
|
|
4353
|
+
name: relatedClass.repository().getTable().name,
|
|
4014
4354
|
localKey: localKey ?? table.primaryKey,
|
|
4015
4355
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4016
4356
|
}));
|
|
4017
4357
|
}
|
|
4018
4358
|
hasOne(related, foreignKey, localKey) {
|
|
4019
4359
|
const table = this.repository.getTable();
|
|
4020
|
-
|
|
4021
|
-
|
|
4360
|
+
const relatedClass = resolveRelated(related);
|
|
4361
|
+
return new HasOneRelationQuery(this, relatedClass, hasOne({
|
|
4362
|
+
name: relatedClass.repository().getTable().name,
|
|
4022
4363
|
localKey: localKey ?? table.primaryKey,
|
|
4023
4364
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4024
4365
|
}));
|
|
4025
4366
|
}
|
|
4026
4367
|
belongsTo(related, foreignKey, ownerKey) {
|
|
4027
|
-
const
|
|
4028
|
-
|
|
4368
|
+
const relatedClass = resolveRelated(related);
|
|
4369
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4370
|
+
return new BelongsToRelationQuery(this, relatedClass, belongsTo({
|
|
4029
4371
|
name: relatedTable.name,
|
|
4030
4372
|
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
4031
4373
|
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
@@ -4033,8 +4375,9 @@ class Model {
|
|
|
4033
4375
|
}
|
|
4034
4376
|
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
4035
4377
|
const table = this.repository.getTable();
|
|
4036
|
-
const
|
|
4037
|
-
|
|
4378
|
+
const relatedClass = resolveRelated(related);
|
|
4379
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4380
|
+
return new BelongsToManyRelationQuery(this, relatedClass, belongsToMany({
|
|
4038
4381
|
name: relatedTable.name,
|
|
4039
4382
|
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
4040
4383
|
parentKey: table.primaryKey,
|
|
@@ -4043,31 +4386,38 @@ class Model {
|
|
|
4043
4386
|
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
4044
4387
|
}));
|
|
4045
4388
|
}
|
|
4046
|
-
morphMany(related, morphName, typeKey, idKey) {
|
|
4389
|
+
morphMany(related, morphName, typeKey, idKey, morphType) {
|
|
4047
4390
|
const table = this.repository.getTable();
|
|
4048
|
-
|
|
4391
|
+
const relatedClass = resolveRelated(related);
|
|
4392
|
+
return new MorphManyRelationQuery(this, relatedClass, morphMany({
|
|
4049
4393
|
name: morphName,
|
|
4050
4394
|
localKey: table.primaryKey,
|
|
4051
4395
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4052
4396
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4053
|
-
morphType:
|
|
4397
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4054
4398
|
}));
|
|
4055
4399
|
}
|
|
4056
|
-
morphOne(related, morphName, typeKey, idKey) {
|
|
4400
|
+
morphOne(related, morphName, typeKey, idKey, morphType) {
|
|
4057
4401
|
const table = this.repository.getTable();
|
|
4058
|
-
|
|
4402
|
+
const relatedClass = resolveRelated(related);
|
|
4403
|
+
return new MorphOneRelationQuery(this, relatedClass, morphOne({
|
|
4059
4404
|
name: morphName,
|
|
4060
4405
|
localKey: table.primaryKey,
|
|
4061
4406
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4062
4407
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4063
|
-
morphType:
|
|
4408
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4064
4409
|
}));
|
|
4065
4410
|
}
|
|
4066
|
-
morphTo(relatedByType, morphName
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4411
|
+
morphTo(relatedByType, morphName, typeKey, idKey) {
|
|
4412
|
+
const resolvedName = morphName ?? inferRelationMethodName("morphTo");
|
|
4413
|
+
if (!resolvedName) {
|
|
4414
|
+
throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (Laravel infers it from the relation method).`);
|
|
4415
|
+
}
|
|
4416
|
+
const resolvedMap = Object.fromEntries(Object.entries(relatedByType).map(([type, related]) => [type, resolveRelated(related)]));
|
|
4417
|
+
return new MorphToRelationQuery(this, resolvedMap, morphTo({
|
|
4418
|
+
name: resolvedName,
|
|
4419
|
+
morphTypeKey: typeKey ?? `${resolvedName}_type`,
|
|
4420
|
+
morphIdKey: idKey ?? `${resolvedName}_id`
|
|
4071
4421
|
}));
|
|
4072
4422
|
}
|
|
4073
4423
|
async load(...names) {
|
|
@@ -4099,6 +4449,14 @@ class Model {
|
|
|
4099
4449
|
}
|
|
4100
4450
|
function registerModelRepository(model, repository) {
|
|
4101
4451
|
modelRepositories.set(model, repository);
|
|
4452
|
+
const name = model.name;
|
|
4453
|
+
if (name) {
|
|
4454
|
+
namedModels.set(name, model);
|
|
4455
|
+
}
|
|
4456
|
+
const morphClass = model.$morphClass;
|
|
4457
|
+
if (morphClass) {
|
|
4458
|
+
namedModels.set(morphClass, model);
|
|
4459
|
+
}
|
|
4102
4460
|
ensureBooted(model);
|
|
4103
4461
|
return model;
|
|
4104
4462
|
}
|
|
@@ -6043,9 +6401,15 @@ class JsonResource {
|
|
|
6043
6401
|
|
|
6044
6402
|
class ResourceCollection extends JsonResource {
|
|
6045
6403
|
toArray() {
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6404
|
+
const items = this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item });
|
|
6405
|
+
const wrap = this.constructor.wrap;
|
|
6406
|
+
if (wrap === null) {
|
|
6407
|
+
return { data: items };
|
|
6408
|
+
}
|
|
6409
|
+
return { [wrap]: items };
|
|
6410
|
+
}
|
|
6411
|
+
toResponse() {
|
|
6412
|
+
return { ...this.toArray(), ...this.extra };
|
|
6049
6413
|
}
|
|
6050
6414
|
}
|
|
6051
6415
|
function toResourceCollection(items, transformer) {
|
|
@@ -8161,6 +8525,7 @@ export {
|
|
|
8161
8525
|
Mailer,
|
|
8162
8526
|
membershipService_default as MembershipService,
|
|
8163
8527
|
Model,
|
|
8528
|
+
ModelQuery,
|
|
8164
8529
|
MorphManyRelationQuery,
|
|
8165
8530
|
MorphOneRelationQuery,
|
|
8166
8531
|
MorphToRelationQuery,
|
|
@@ -8348,6 +8713,7 @@ export {
|
|
|
8348
8713
|
readTenancyDriver,
|
|
8349
8714
|
redirectResponse,
|
|
8350
8715
|
registerDefaultDatabasePool,
|
|
8716
|
+
registerModelClass,
|
|
8351
8717
|
registerModelRepository,
|
|
8352
8718
|
registerShutdownHandler,
|
|
8353
8719
|
renderKernelErrorChrome,
|