@getstrata/core 0.5.97 → 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 +30 -0
- package/dist/core/contracts/container.d.ts +2 -0
- package/dist/core/database/baseRepository.d.ts +3 -1
- package/dist/core/database/factory.d.ts +47 -5
- package/dist/core/database/index.d.ts +3 -1
- package/dist/core/database/inflection.d.ts +4 -0
- package/dist/core/database/model.d.ts +88 -4
- package/dist/core/database/relationQuery.d.ts +172 -0
- package/dist/core/database/repositoryQuery.d.ts +9 -1
- package/dist/core/database/whereBuilder.d.ts +9 -1
- package/dist/core/events/eventBus.d.ts +2 -0
- package/dist/core/http/resources.d.ts +21 -1
- package/dist/entries/contracts/container.js +6 -0
- package/dist/entries/database/factory.js +159 -6
- package/dist/entries/database/model.js +1066 -23
- package/dist/entries/database/query.js +10 -1
- package/dist/entries/database/repositoryQuery.js +55 -3
- package/dist/entries/database/schema.js +10 -1
- package/dist/entries/http/resources.js +68 -1
- package/dist/framework/public-api.d.ts +4 -2
- package/dist/index.js +1374 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1497,6 +1497,12 @@ class ServiceContainer {
|
|
|
1497
1497
|
resolve(key) {
|
|
1498
1498
|
return this.get(key);
|
|
1499
1499
|
}
|
|
1500
|
+
make(key) {
|
|
1501
|
+
return this.resolve(key);
|
|
1502
|
+
}
|
|
1503
|
+
instance(key, value) {
|
|
1504
|
+
return this.set(key, value);
|
|
1505
|
+
}
|
|
1500
1506
|
has(key) {
|
|
1501
1507
|
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
1502
1508
|
}
|
|
@@ -1525,6 +1531,9 @@ class ConfigStore {
|
|
|
1525
1531
|
class EventBus {
|
|
1526
1532
|
constructor() {}
|
|
1527
1533
|
listeners = new Map;
|
|
1534
|
+
on(event, listener) {
|
|
1535
|
+
return this.listen(event, listener);
|
|
1536
|
+
}
|
|
1528
1537
|
listen(event, listener) {
|
|
1529
1538
|
const handlers = this.listeners.get(event) ?? new Set;
|
|
1530
1539
|
handlers.add(listener);
|
|
@@ -1536,6 +1545,9 @@ class EventBus {
|
|
|
1536
1545
|
}
|
|
1537
1546
|
};
|
|
1538
1547
|
}
|
|
1548
|
+
async emit(event, payload) {
|
|
1549
|
+
await this.dispatch(event, payload);
|
|
1550
|
+
}
|
|
1539
1551
|
async dispatch(event, payload) {
|
|
1540
1552
|
const handlers = this.listeners.get(event);
|
|
1541
1553
|
if (!handlers || handlers.size === 0) {
|
|
@@ -1707,7 +1719,7 @@ function buildOperatorClauses(column, operator, params) {
|
|
|
1707
1719
|
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
1708
1720
|
}
|
|
1709
1721
|
if (operator.ilike !== undefined) {
|
|
1710
|
-
clauses.push(`${column} ILIKE ${pushParam(params,
|
|
1722
|
+
clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
|
|
1711
1723
|
}
|
|
1712
1724
|
if (operator.tsMatch !== undefined) {
|
|
1713
1725
|
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
@@ -1737,7 +1749,16 @@ function appendWhereParts(tableName, where, params) {
|
|
|
1737
1749
|
}
|
|
1738
1750
|
return clauses.join(" AND ");
|
|
1739
1751
|
}
|
|
1752
|
+
function remapExistsSql(sql, existsParams, params) {
|
|
1753
|
+
const offset = params.length;
|
|
1754
|
+
params.push(...existsParams);
|
|
1755
|
+
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
1756
|
+
}
|
|
1740
1757
|
function buildWhereNodeClause(tableName, node, params) {
|
|
1758
|
+
if ("exists" in node) {
|
|
1759
|
+
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
1760
|
+
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
1761
|
+
}
|
|
1741
1762
|
if ("where" in node) {
|
|
1742
1763
|
return appendWhereParts(tableName, node.where, params);
|
|
1743
1764
|
}
|
|
@@ -2255,6 +2276,23 @@ class RepositoryQuery {
|
|
|
2255
2276
|
this.queryOptions = { ...this.queryOptions, limit };
|
|
2256
2277
|
return this;
|
|
2257
2278
|
}
|
|
2279
|
+
whereNull(column) {
|
|
2280
|
+
return this.where({ [column]: null });
|
|
2281
|
+
}
|
|
2282
|
+
whereNotNull(column) {
|
|
2283
|
+
return this.where({ [column]: { isNull: false } });
|
|
2284
|
+
}
|
|
2285
|
+
whereIn(column, values) {
|
|
2286
|
+
return this.where({ [column]: values });
|
|
2287
|
+
}
|
|
2288
|
+
whereExists(sql, params = []) {
|
|
2289
|
+
this.whereNodes.push({ kind: "and", exists: { sql, params } });
|
|
2290
|
+
return this;
|
|
2291
|
+
}
|
|
2292
|
+
whereNotExists(sql, params = []) {
|
|
2293
|
+
this.whereNodes.push({ kind: "and", exists: { sql, params, not: true } });
|
|
2294
|
+
return this;
|
|
2295
|
+
}
|
|
2258
2296
|
offset(offset) {
|
|
2259
2297
|
this.queryOptions = { ...this.queryOptions, offset };
|
|
2260
2298
|
return this;
|
|
@@ -2324,13 +2362,30 @@ class RepositoryQuery {
|
|
|
2324
2362
|
});
|
|
2325
2363
|
return this;
|
|
2326
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
|
+
}
|
|
2327
2375
|
async get() {
|
|
2328
2376
|
const rows = await this.repository.findAll(this.buildOptions());
|
|
2329
2377
|
return await this.attach(rows);
|
|
2330
2378
|
}
|
|
2331
2379
|
async first() {
|
|
2332
|
-
const rows = await this.
|
|
2333
|
-
|
|
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);
|
|
2334
2389
|
}
|
|
2335
2390
|
async paginate(options) {
|
|
2336
2391
|
return await this.repository.paginate({
|
|
@@ -2402,6 +2457,15 @@ class RepositoryQuery {
|
|
|
2402
2457
|
}));
|
|
2403
2458
|
continue;
|
|
2404
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
|
+
}
|
|
2405
2469
|
if (load.kind === "morphTo") {
|
|
2406
2470
|
const relation2 = load.relation;
|
|
2407
2471
|
const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
|
|
@@ -2430,6 +2494,10 @@ class BaseRepository {
|
|
|
2430
2494
|
this.table = table;
|
|
2431
2495
|
this.connection = connection;
|
|
2432
2496
|
}
|
|
2497
|
+
async count(options = {}) {
|
|
2498
|
+
const { whereNodes, where, ...rest } = options;
|
|
2499
|
+
return await this.countWhere(where ?? {}, rest, whereNodes ?? []);
|
|
2500
|
+
}
|
|
2433
2501
|
async findAll(options = {}) {
|
|
2434
2502
|
return await withDatabaseErrorHandling(async () => {
|
|
2435
2503
|
const { whereNodes, ...queryOptions } = options;
|
|
@@ -2737,6 +2805,23 @@ class BaseRepository {
|
|
|
2737
2805
|
}
|
|
2738
2806
|
return indexMorphToRelation(children, parentsByType, relation);
|
|
2739
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
|
+
}
|
|
2740
2825
|
}
|
|
2741
2826
|
var baseRepository_default = BaseRepository;
|
|
2742
2827
|
// ../../src/core/database/bunSql.ts
|
|
@@ -2766,6 +2851,185 @@ function createDatabaseConnection(source) {
|
|
|
2766
2851
|
}
|
|
2767
2852
|
};
|
|
2768
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
|
+
|
|
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
|
+
|
|
2888
|
+
class Factory {
|
|
2889
|
+
quantity = 1;
|
|
2890
|
+
counted = false;
|
|
2891
|
+
sequenceIndex = 0;
|
|
2892
|
+
stateTransforms = [];
|
|
2893
|
+
sequenceItems = [];
|
|
2894
|
+
parentAssociations = [];
|
|
2895
|
+
children = [];
|
|
2896
|
+
afterMakingCallbacks = [];
|
|
2897
|
+
afterCreatingCallbacks = [];
|
|
2898
|
+
model;
|
|
2899
|
+
definition() {
|
|
2900
|
+
throw new Error("Factory definition must be implemented by subclass.");
|
|
2901
|
+
}
|
|
2902
|
+
clone() {
|
|
2903
|
+
const next = Object.create(Object.getPrototypeOf(this));
|
|
2904
|
+
Object.assign(next, this);
|
|
2905
|
+
next.stateTransforms = [...this.stateTransforms];
|
|
2906
|
+
next.sequenceItems = [...this.sequenceItems];
|
|
2907
|
+
next.parentAssociations = [...this.parentAssociations];
|
|
2908
|
+
next.children = [...this.children];
|
|
2909
|
+
next.afterMakingCallbacks = [...this.afterMakingCallbacks];
|
|
2910
|
+
next.afterCreatingCallbacks = [...this.afterCreatingCallbacks];
|
|
2911
|
+
return next;
|
|
2912
|
+
}
|
|
2913
|
+
count(quantity) {
|
|
2914
|
+
if (!Number.isInteger(quantity) || quantity < 1) {
|
|
2915
|
+
throw new Error("Factory.count() requires a positive integer.");
|
|
2916
|
+
}
|
|
2917
|
+
const next = this.clone();
|
|
2918
|
+
next.quantity = quantity;
|
|
2919
|
+
next.counted = true;
|
|
2920
|
+
return next;
|
|
2921
|
+
}
|
|
2922
|
+
state(state) {
|
|
2923
|
+
const next = this.clone();
|
|
2924
|
+
next.stateTransforms = [...this.stateTransforms, state];
|
|
2925
|
+
return next;
|
|
2926
|
+
}
|
|
2927
|
+
sequence(...items) {
|
|
2928
|
+
if (items.length === 0) {
|
|
2929
|
+
throw new Error("Factory.sequence() requires at least one attribute set.");
|
|
2930
|
+
}
|
|
2931
|
+
const next = this.clone();
|
|
2932
|
+
next.sequenceItems = [...this.sequenceItems, ...items];
|
|
2933
|
+
return next;
|
|
2934
|
+
}
|
|
2935
|
+
for(parent, foreignKey) {
|
|
2936
|
+
if (parent.id === undefined || parent.id === null) {
|
|
2937
|
+
throw new Error("Factory.for() requires a parent with an id.");
|
|
2938
|
+
}
|
|
2939
|
+
const key = inferFactoryForeignKey(parent, foreignKey);
|
|
2940
|
+
const next = this.clone();
|
|
2941
|
+
next.parentAssociations = [...this.parentAssociations, { foreignKey: key, value: parent.id }];
|
|
2942
|
+
return next;
|
|
2943
|
+
}
|
|
2944
|
+
recycle(parent, foreignKey) {
|
|
2945
|
+
return this.for(parent, foreignKey);
|
|
2946
|
+
}
|
|
2947
|
+
afterMaking(callback) {
|
|
2948
|
+
const next = this.clone();
|
|
2949
|
+
next.afterMakingCallbacks = [...this.afterMakingCallbacks, callback];
|
|
2950
|
+
return next;
|
|
2951
|
+
}
|
|
2952
|
+
afterCreating(callback) {
|
|
2953
|
+
const next = this.clone();
|
|
2954
|
+
next.afterCreatingCallbacks = [...this.afterCreatingCallbacks, callback];
|
|
2955
|
+
return next;
|
|
2956
|
+
}
|
|
2957
|
+
has(factory, foreignKey) {
|
|
2958
|
+
const next = this.clone();
|
|
2959
|
+
next.children = [
|
|
2960
|
+
...this.children,
|
|
2961
|
+
{ factory, foreignKey }
|
|
2962
|
+
];
|
|
2963
|
+
return next;
|
|
2964
|
+
}
|
|
2965
|
+
make(overrides = {}) {
|
|
2966
|
+
if (!this.counted) {
|
|
2967
|
+
return this.makeOne(overrides);
|
|
2968
|
+
}
|
|
2969
|
+
return Array.from({ length: this.quantity }, () => this.makeOne(overrides));
|
|
2970
|
+
}
|
|
2971
|
+
async create(overrides = {}) {
|
|
2972
|
+
if (!this.counted) {
|
|
2973
|
+
return await this.createOne(overrides);
|
|
2974
|
+
}
|
|
2975
|
+
const records = [];
|
|
2976
|
+
for (let index = 0;index < this.quantity; index += 1) {
|
|
2977
|
+
records.push(await this.createOne(overrides));
|
|
2978
|
+
}
|
|
2979
|
+
return records;
|
|
2980
|
+
}
|
|
2981
|
+
makeOne(overrides = {}) {
|
|
2982
|
+
let record = { ...this.definition() };
|
|
2983
|
+
for (const state of this.stateTransforms) {
|
|
2984
|
+
const patch = typeof state === "function" ? state(record) : state;
|
|
2985
|
+
record = { ...record, ...patch };
|
|
2986
|
+
}
|
|
2987
|
+
if (this.sequenceItems.length > 0) {
|
|
2988
|
+
const item = this.sequenceItems[this.sequenceIndex % this.sequenceItems.length];
|
|
2989
|
+
const patch = typeof item === "function" ? item(this.sequenceIndex) : item;
|
|
2990
|
+
record = { ...record, ...patch };
|
|
2991
|
+
this.sequenceIndex += 1;
|
|
2992
|
+
}
|
|
2993
|
+
for (const association of this.parentAssociations) {
|
|
2994
|
+
record[association.foreignKey] = association.value;
|
|
2995
|
+
}
|
|
2996
|
+
const made = {
|
|
2997
|
+
...record,
|
|
2998
|
+
...overrides
|
|
2999
|
+
};
|
|
3000
|
+
for (const callback of this.afterMakingCallbacks) {
|
|
3001
|
+
callback(made);
|
|
3002
|
+
}
|
|
3003
|
+
return made;
|
|
3004
|
+
}
|
|
3005
|
+
async createOne(overrides = {}) {
|
|
3006
|
+
const created = await this.persist(this.insertable(this.makeOne(overrides)));
|
|
3007
|
+
for (const child of this.children) {
|
|
3008
|
+
await child.factory.for(created, child.foreignKey).create();
|
|
3009
|
+
}
|
|
3010
|
+
for (const callback of this.afterCreatingCallbacks) {
|
|
3011
|
+
await callback(created);
|
|
3012
|
+
}
|
|
3013
|
+
return created;
|
|
3014
|
+
}
|
|
3015
|
+
insertable(record) {
|
|
3016
|
+
const values = { ...record };
|
|
3017
|
+
if (values.id === 0 || values.id === undefined || values.id === null) {
|
|
3018
|
+
delete values.id;
|
|
3019
|
+
}
|
|
3020
|
+
return values;
|
|
3021
|
+
}
|
|
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
|
+
}
|
|
3030
|
+
throw new Error("Factory.persist() must be implemented to use create().");
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
2769
3033
|
// ../../src/core/database/migrations/advisoryLock.ts
|
|
2770
3034
|
var MIGRATION_LOCK_KEY = 42424242;
|
|
2771
3035
|
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
@@ -2878,10 +3142,591 @@ async function freshDatabase(db, migrations, options = {}) {
|
|
|
2878
3142
|
}
|
|
2879
3143
|
await runFresh();
|
|
2880
3144
|
}
|
|
3145
|
+
// ../../src/core/database/relationQuery.ts
|
|
3146
|
+
function asWhere(where) {
|
|
3147
|
+
return where;
|
|
3148
|
+
}
|
|
3149
|
+
function ownerId(owner, ownerKey) {
|
|
3150
|
+
if (typeof owner.get === "function") {
|
|
3151
|
+
return owner.get(ownerKey);
|
|
3152
|
+
}
|
|
3153
|
+
if (owner && typeof owner === "object" && "id" in owner && owner.id !== undefined) {
|
|
3154
|
+
return owner.id;
|
|
3155
|
+
}
|
|
3156
|
+
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
3157
|
+
}
|
|
3158
|
+
function thenGet(get, onfulfilled, onrejected) {
|
|
3159
|
+
return get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
class HasManyRelationQuery {
|
|
3163
|
+
parent;
|
|
3164
|
+
related;
|
|
3165
|
+
relation;
|
|
3166
|
+
kind = "hasMany";
|
|
3167
|
+
extraWhere = {};
|
|
3168
|
+
extraOptions = {};
|
|
3169
|
+
constructor(parent, related, relation) {
|
|
3170
|
+
this.parent = parent;
|
|
3171
|
+
this.related = related;
|
|
3172
|
+
this.relation = relation;
|
|
3173
|
+
}
|
|
3174
|
+
where(where) {
|
|
3175
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3176
|
+
return this;
|
|
3177
|
+
}
|
|
3178
|
+
orderBy(orderBy) {
|
|
3179
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3180
|
+
return this;
|
|
3181
|
+
}
|
|
3182
|
+
limit(limit) {
|
|
3183
|
+
this.extraOptions = { ...this.extraOptions, limit };
|
|
3184
|
+
return this;
|
|
3185
|
+
}
|
|
3186
|
+
applyEagerLoad(query, alias) {
|
|
3187
|
+
query.withHasMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3188
|
+
}
|
|
3189
|
+
hydrateEager(row, alias) {
|
|
3190
|
+
const value = row[alias] ?? [];
|
|
3191
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3192
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3193
|
+
}
|
|
3194
|
+
toExistsClause(parentTable) {
|
|
3195
|
+
const childTable = this.related.repository().getTable().name;
|
|
3196
|
+
const extra = buildAdvancedWhereClause(childTable, this.extraWhere, [], []);
|
|
3197
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3198
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.foreignKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3199
|
+
return { sql, params: extra.params };
|
|
3200
|
+
}
|
|
3201
|
+
scopedQuery() {
|
|
3202
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3203
|
+
let query = repository.query(asWhere({
|
|
3204
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey),
|
|
3205
|
+
...this.extraWhere
|
|
3206
|
+
}));
|
|
3207
|
+
if (this.extraOptions.orderBy) {
|
|
3208
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
3209
|
+
}
|
|
3210
|
+
if (this.extraOptions.limit !== undefined) {
|
|
3211
|
+
query = query.limit(this.extraOptions.limit);
|
|
3212
|
+
}
|
|
3213
|
+
return query;
|
|
3214
|
+
}
|
|
3215
|
+
async get() {
|
|
3216
|
+
const rows = await this.scopedQuery().get();
|
|
3217
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3218
|
+
}
|
|
3219
|
+
async first() {
|
|
3220
|
+
const rows = await this.limit(1).get();
|
|
3221
|
+
return rows[0] ?? null;
|
|
3222
|
+
}
|
|
3223
|
+
async count() {
|
|
3224
|
+
return this.scopedQuery().count();
|
|
3225
|
+
}
|
|
3226
|
+
then(onfulfilled, onrejected) {
|
|
3227
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3228
|
+
}
|
|
3229
|
+
async create(attributes = {}) {
|
|
3230
|
+
return this.related.create(attributes, {
|
|
3231
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3232
|
+
});
|
|
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
|
+
}
|
|
3246
|
+
async createMany(records) {
|
|
3247
|
+
const created = [];
|
|
3248
|
+
for (const attributes of records) {
|
|
3249
|
+
created.push(await this.create(attributes));
|
|
3250
|
+
}
|
|
3251
|
+
return created;
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
class HasOneRelationQuery {
|
|
3256
|
+
relation;
|
|
3257
|
+
kind = "hasOne";
|
|
3258
|
+
inner;
|
|
3259
|
+
constructor(parent, related, relation) {
|
|
3260
|
+
this.relation = relation;
|
|
3261
|
+
this.inner = new HasManyRelationQuery(parent, related, {
|
|
3262
|
+
type: "hasMany",
|
|
3263
|
+
name: relation.name,
|
|
3264
|
+
localKey: relation.localKey,
|
|
3265
|
+
foreignKey: relation.foreignKey
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
where(where) {
|
|
3269
|
+
this.inner.where(where);
|
|
3270
|
+
return this;
|
|
3271
|
+
}
|
|
3272
|
+
orderBy(orderBy) {
|
|
3273
|
+
this.inner.orderBy(orderBy);
|
|
3274
|
+
return this;
|
|
3275
|
+
}
|
|
3276
|
+
applyEagerLoad(query, alias) {
|
|
3277
|
+
this.inner.limit(1).applyEagerLoad(query, alias);
|
|
3278
|
+
}
|
|
3279
|
+
hydrateEager(row, alias) {
|
|
3280
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
3281
|
+
return hydrated[0];
|
|
3282
|
+
}
|
|
3283
|
+
toExistsClause(parentTable) {
|
|
3284
|
+
return this.inner.toExistsClause(parentTable);
|
|
3285
|
+
}
|
|
3286
|
+
async get() {
|
|
3287
|
+
return this.inner.limit(1).first();
|
|
3288
|
+
}
|
|
3289
|
+
async first() {
|
|
3290
|
+
return this.get();
|
|
3291
|
+
}
|
|
3292
|
+
async count() {
|
|
3293
|
+
return this.inner.count();
|
|
3294
|
+
}
|
|
3295
|
+
then(onfulfilled, onrejected) {
|
|
3296
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3297
|
+
}
|
|
3298
|
+
async create(attributes = {}) {
|
|
3299
|
+
return this.inner.create(attributes);
|
|
3300
|
+
}
|
|
3301
|
+
async save(related) {
|
|
3302
|
+
return this.inner.save(related);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
class BelongsToRelationQuery {
|
|
3307
|
+
parent;
|
|
3308
|
+
related;
|
|
3309
|
+
relation;
|
|
3310
|
+
kind = "belongsTo";
|
|
3311
|
+
extraWhere = {};
|
|
3312
|
+
extraOptions = {};
|
|
3313
|
+
constructor(parent, related, relation) {
|
|
3314
|
+
this.parent = parent;
|
|
3315
|
+
this.related = related;
|
|
3316
|
+
this.relation = relation;
|
|
3317
|
+
}
|
|
3318
|
+
where(where) {
|
|
3319
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3320
|
+
return this;
|
|
3321
|
+
}
|
|
3322
|
+
orderBy(orderBy) {
|
|
3323
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3324
|
+
return this;
|
|
3325
|
+
}
|
|
3326
|
+
applyEagerLoad(query, alias) {
|
|
3327
|
+
query.withBelongsTo(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3328
|
+
}
|
|
3329
|
+
hydrateEager(row, alias) {
|
|
3330
|
+
const value = row[alias];
|
|
3331
|
+
return value ? this.related.newFromRecord(value) : value;
|
|
3332
|
+
}
|
|
3333
|
+
toExistsClause(parentTable) {
|
|
3334
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
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 };
|
|
3339
|
+
}
|
|
3340
|
+
async get() {
|
|
3341
|
+
const foreign = this.parent.get(this.relation.foreignKey);
|
|
3342
|
+
if (foreign === null || foreign === undefined) {
|
|
3343
|
+
return null;
|
|
3344
|
+
}
|
|
3345
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3346
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign, ...this.extraWhere }));
|
|
3347
|
+
if (this.extraOptions.orderBy) {
|
|
3348
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
3349
|
+
}
|
|
3350
|
+
const row = await query.first();
|
|
3351
|
+
return row ? this.related.newFromRecord(row) : null;
|
|
3352
|
+
}
|
|
3353
|
+
async first() {
|
|
3354
|
+
return this.get();
|
|
3355
|
+
}
|
|
3356
|
+
then(onfulfilled, onrejected) {
|
|
3357
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3358
|
+
}
|
|
3359
|
+
async associate(owner) {
|
|
3360
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3361
|
+
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
async dissociate() {
|
|
3365
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3366
|
+
[this.relation.foreignKey]: null
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
class BelongsToManyRelationQuery {
|
|
3372
|
+
parent;
|
|
3373
|
+
related;
|
|
3374
|
+
relation;
|
|
3375
|
+
kind = "belongsToMany";
|
|
3376
|
+
extraWhere = {};
|
|
3377
|
+
extraOptions = {};
|
|
3378
|
+
pivotValues = {};
|
|
3379
|
+
constructor(parent, related, relation) {
|
|
3380
|
+
this.parent = parent;
|
|
3381
|
+
this.related = related;
|
|
3382
|
+
this.relation = relation;
|
|
3383
|
+
}
|
|
3384
|
+
where(where) {
|
|
3385
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3386
|
+
return this;
|
|
3387
|
+
}
|
|
3388
|
+
orderBy(orderBy) {
|
|
3389
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3390
|
+
return this;
|
|
3391
|
+
}
|
|
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
|
+
}
|
|
3399
|
+
hydrateEager(row, alias) {
|
|
3400
|
+
const value = row[alias] ?? [];
|
|
3401
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3402
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3403
|
+
}
|
|
3404
|
+
toExistsClause(parentTable) {
|
|
3405
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
3406
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
3407
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3408
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(relatedTable)} INNER JOIN ${quoteIdentifier(this.relation.pivotTable)} ON ${qualifyColumn(this.relation.pivotTable, this.relation.relatedPivotKey)} = ${qualifyColumn(relatedTable, this.relation.relatedKey)} WHERE ${qualifyColumn(this.relation.pivotTable, this.relation.foreignPivotKey)} = ${qualifyColumn(parentTable, this.relation.parentKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3409
|
+
return { sql, params: extra.params };
|
|
3410
|
+
}
|
|
3411
|
+
connection() {
|
|
3412
|
+
return this.parent.getRepository().getConnection();
|
|
3413
|
+
}
|
|
3414
|
+
async get() {
|
|
3415
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3416
|
+
const pivotRows = await this.connection().unsafe(`SELECT * FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3417
|
+
if (pivotRows.length === 0) {
|
|
3418
|
+
return [];
|
|
3419
|
+
}
|
|
3420
|
+
const relatedIds = [
|
|
3421
|
+
...new Set(pivotRows.map((row) => row[this.relation.relatedPivotKey]))
|
|
3422
|
+
];
|
|
3423
|
+
const repository = this.related.repository().withConnection(this.connection());
|
|
3424
|
+
const rows = await repository.findAll({
|
|
3425
|
+
...this.extraOptions,
|
|
3426
|
+
where: asWhere({
|
|
3427
|
+
[this.relation.relatedKey]: relatedIds,
|
|
3428
|
+
...this.extraWhere
|
|
3429
|
+
})
|
|
3430
|
+
});
|
|
3431
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3432
|
+
}
|
|
3433
|
+
async first() {
|
|
3434
|
+
const rows = await this.get();
|
|
3435
|
+
return rows[0] ?? null;
|
|
3436
|
+
}
|
|
3437
|
+
async count() {
|
|
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);
|
|
3444
|
+
}
|
|
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) {
|
|
3457
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3458
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3459
|
+
for (const id of list) {
|
|
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
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
async detach(ids) {
|
|
3469
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3470
|
+
if (ids === undefined) {
|
|
3471
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3475
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = ANY($2)`, [parentId, list]);
|
|
3476
|
+
}
|
|
3477
|
+
async sync(ids) {
|
|
3478
|
+
await this.detach();
|
|
3479
|
+
if (ids.length > 0) {
|
|
3480
|
+
await this.attach(ids);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
async create(attributes = {}) {
|
|
3484
|
+
const related = await this.related.create(attributes);
|
|
3485
|
+
await this.attach(related.id);
|
|
3486
|
+
return related;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
class MorphManyRelationQuery {
|
|
3491
|
+
parent;
|
|
3492
|
+
related;
|
|
3493
|
+
relation;
|
|
3494
|
+
kind = "morphMany";
|
|
3495
|
+
extraWhere = {};
|
|
3496
|
+
extraOptions = {};
|
|
3497
|
+
constructor(parent, related, relation) {
|
|
3498
|
+
this.parent = parent;
|
|
3499
|
+
this.related = related;
|
|
3500
|
+
this.relation = relation;
|
|
3501
|
+
}
|
|
3502
|
+
where(where) {
|
|
3503
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3504
|
+
return this;
|
|
3505
|
+
}
|
|
3506
|
+
applyEagerLoad(query, alias) {
|
|
3507
|
+
query.withMorphMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3508
|
+
}
|
|
3509
|
+
hydrateEager(row, alias) {
|
|
3510
|
+
const value = row[alias] ?? [];
|
|
3511
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3512
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3513
|
+
}
|
|
3514
|
+
toExistsClause(parentTable) {
|
|
3515
|
+
const childTable = this.related.repository().getTable().name;
|
|
3516
|
+
const extra = buildAdvancedWhereClause(childTable, {
|
|
3517
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3518
|
+
...this.extraWhere
|
|
3519
|
+
}, [], []);
|
|
3520
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3521
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.morphIdKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3522
|
+
return { sql, params: extra.params };
|
|
3523
|
+
}
|
|
3524
|
+
async get() {
|
|
3525
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3526
|
+
const rows = await repository.query(asWhere({
|
|
3527
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3528
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
3529
|
+
...this.extraWhere
|
|
3530
|
+
})).get();
|
|
3531
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3532
|
+
}
|
|
3533
|
+
async first() {
|
|
3534
|
+
const rows = await this.get();
|
|
3535
|
+
return rows[0] ?? null;
|
|
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
|
+
}
|
|
3548
|
+
async create(attributes = {}) {
|
|
3549
|
+
return this.related.create(attributes, {
|
|
3550
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3551
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey)
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
class MorphOneRelationQuery {
|
|
3557
|
+
relation;
|
|
3558
|
+
kind = "morphOne";
|
|
3559
|
+
inner;
|
|
3560
|
+
constructor(parent, related, relation) {
|
|
3561
|
+
this.relation = relation;
|
|
3562
|
+
this.inner = new MorphManyRelationQuery(parent, related, {
|
|
3563
|
+
type: "morphMany",
|
|
3564
|
+
name: relation.name,
|
|
3565
|
+
localKey: relation.localKey,
|
|
3566
|
+
morphTypeKey: relation.morphTypeKey,
|
|
3567
|
+
morphIdKey: relation.morphIdKey,
|
|
3568
|
+
morphType: relation.morphType
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
where(where) {
|
|
3572
|
+
this.inner.where(where);
|
|
3573
|
+
return this;
|
|
3574
|
+
}
|
|
3575
|
+
applyEagerLoad(query, alias) {
|
|
3576
|
+
this.inner.applyEagerLoad(query, alias);
|
|
3577
|
+
}
|
|
3578
|
+
hydrateEager(row, alias) {
|
|
3579
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
3580
|
+
return hydrated[0];
|
|
3581
|
+
}
|
|
3582
|
+
toExistsClause(parentTable) {
|
|
3583
|
+
return this.inner.toExistsClause(parentTable);
|
|
3584
|
+
}
|
|
3585
|
+
async get() {
|
|
3586
|
+
return this.inner.first();
|
|
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
|
+
}
|
|
3597
|
+
async create(attributes = {}) {
|
|
3598
|
+
return this.inner.create(attributes);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
class MorphToRelationQuery {
|
|
3603
|
+
parent;
|
|
3604
|
+
relatedByType;
|
|
3605
|
+
relation;
|
|
3606
|
+
kind = "morphTo";
|
|
3607
|
+
extraWhere = {};
|
|
3608
|
+
constructor(parent, relatedByType, relation) {
|
|
3609
|
+
this.parent = parent;
|
|
3610
|
+
this.relatedByType = relatedByType;
|
|
3611
|
+
this.relation = relation;
|
|
3612
|
+
}
|
|
3613
|
+
where(where) {
|
|
3614
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3615
|
+
return this;
|
|
3616
|
+
}
|
|
3617
|
+
applyEagerLoad(query, alias) {
|
|
3618
|
+
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
3619
|
+
type,
|
|
3620
|
+
model.repository()
|
|
3621
|
+
]));
|
|
3622
|
+
query.withMorphTo(alias, this.relation, repositories);
|
|
3623
|
+
}
|
|
3624
|
+
hydrateEager(row, alias) {
|
|
3625
|
+
return row[alias];
|
|
3626
|
+
}
|
|
3627
|
+
toExistsClause(parentTable) {
|
|
3628
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
3629
|
+
const related = this.relatedByType[type];
|
|
3630
|
+
if (!related) {
|
|
3631
|
+
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
3632
|
+
}
|
|
3633
|
+
const relatedTable = related.repository().getTable();
|
|
3634
|
+
const extra = buildAdvancedWhereClause(relatedTable.name, this.extraWhere, [], []);
|
|
3635
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3636
|
+
return {
|
|
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
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
async get() {
|
|
3642
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
3643
|
+
const id = this.parent.get(this.relation.morphIdKey);
|
|
3644
|
+
const related = this.relatedByType[type];
|
|
3645
|
+
if (!related || id === null || id === undefined) {
|
|
3646
|
+
return null;
|
|
3647
|
+
}
|
|
3648
|
+
const table = related.repository().getTable();
|
|
3649
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id, ...this.extraWhere })).first();
|
|
3650
|
+
return row ? related.newFromRecord(row) : null;
|
|
3651
|
+
}
|
|
3652
|
+
then(onfulfilled, onrejected) {
|
|
3653
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
|
|
2881
3657
|
// ../../src/core/database/model.ts
|
|
2882
3658
|
var modelRepositories = new WeakMap;
|
|
3659
|
+
var namedModels = new Map;
|
|
2883
3660
|
var modelGlobalScopes = new WeakMap;
|
|
3661
|
+
var modelObservers = new WeakMap;
|
|
2884
3662
|
var modelBooted = new WeakSet;
|
|
3663
|
+
async function runObservers(model, hook) {
|
|
3664
|
+
const observers = modelObservers.get(model.constructor) ?? [];
|
|
3665
|
+
for (const observer of observers) {
|
|
3666
|
+
const handler = observer[hook];
|
|
3667
|
+
if (handler && await handler(model) === false) {
|
|
3668
|
+
return false;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
return true;
|
|
3672
|
+
}
|
|
3673
|
+
function accessorName(key) {
|
|
3674
|
+
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
3675
|
+
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
3676
|
+
}
|
|
3677
|
+
function isLoadableModel(value) {
|
|
3678
|
+
return Boolean(value && typeof value === "object" && typeof value.load === "function" && typeof value.loaded === "function" && typeof value.setLoaded === "function");
|
|
3679
|
+
}
|
|
3680
|
+
async function eagerLoadOnModels(models, paths) {
|
|
3681
|
+
if (models.length === 0 || paths.length === 0) {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
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);
|
|
3696
|
+
}
|
|
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
|
+
}
|
|
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);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
async function loadNested(model, path) {
|
|
3728
|
+
await eagerLoadOnModels([model], [path]);
|
|
3729
|
+
}
|
|
2885
3730
|
function resolveModelRepository(model) {
|
|
2886
3731
|
const repository = modelRepositories.get(model);
|
|
2887
3732
|
if (!repository) {
|
|
@@ -2889,6 +3734,48 @@ function resolveModelRepository(model) {
|
|
|
2889
3734
|
}
|
|
2890
3735
|
return repository;
|
|
2891
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
|
+
}
|
|
2892
3779
|
function modelStatics(model) {
|
|
2893
3780
|
return model;
|
|
2894
3781
|
}
|
|
@@ -2918,6 +3805,11 @@ function hydrateValue(value, cast) {
|
|
|
2918
3805
|
case "bool":
|
|
2919
3806
|
case "boolean":
|
|
2920
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;
|
|
2921
3813
|
default:
|
|
2922
3814
|
return value;
|
|
2923
3815
|
}
|
|
@@ -2935,6 +3827,11 @@ function dehydrateValue(value, cast) {
|
|
|
2935
3827
|
case "bool":
|
|
2936
3828
|
case "boolean":
|
|
2937
3829
|
return Boolean(value);
|
|
3830
|
+
case "integer":
|
|
3831
|
+
case "int":
|
|
3832
|
+
return value === "" ? null : Number(value);
|
|
3833
|
+
case "hashed":
|
|
3834
|
+
return value;
|
|
2938
3835
|
default:
|
|
2939
3836
|
return value;
|
|
2940
3837
|
}
|
|
@@ -2989,6 +3886,153 @@ function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
|
2989
3886
|
return result;
|
|
2990
3887
|
}
|
|
2991
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
|
+
|
|
2992
4036
|
class Model {
|
|
2993
4037
|
attributes;
|
|
2994
4038
|
repository;
|
|
@@ -2996,11 +4040,23 @@ class Model {
|
|
|
2996
4040
|
static $guarded;
|
|
2997
4041
|
static $casts = {};
|
|
2998
4042
|
static $timestamps = true;
|
|
4043
|
+
static $hidden;
|
|
4044
|
+
static $visible;
|
|
4045
|
+
static $appends;
|
|
4046
|
+
static $morphClass;
|
|
2999
4047
|
_exists;
|
|
4048
|
+
loadedRelations = {};
|
|
4049
|
+
hiddenOverrides = [];
|
|
4050
|
+
visibleOverrides = [];
|
|
4051
|
+
appended = [];
|
|
3000
4052
|
constructor(attributes, repository, exists = true) {
|
|
3001
4053
|
this.attributes = attributes;
|
|
3002
4054
|
this.repository = repository;
|
|
3003
4055
|
this._exists = exists;
|
|
4056
|
+
this.attributes = modelStatics(this.constructor).hydrateAttributes(attributes);
|
|
4057
|
+
}
|
|
4058
|
+
getRepository() {
|
|
4059
|
+
return this.repository;
|
|
3004
4060
|
}
|
|
3005
4061
|
get $exists() {
|
|
3006
4062
|
return this._exists;
|
|
@@ -3014,8 +4070,51 @@ class Model {
|
|
|
3014
4070
|
toObject() {
|
|
3015
4071
|
return { ...this.attributes };
|
|
3016
4072
|
}
|
|
4073
|
+
toArray() {
|
|
4074
|
+
const ModelClass = modelStatics(this.constructor);
|
|
4075
|
+
const hidden = new Set([...ModelClass.$hidden ?? [], ...this.hiddenOverrides]);
|
|
4076
|
+
const visible = this.visibleOverrides.length > 0 ? this.visibleOverrides : ModelClass.$visible;
|
|
4077
|
+
const data = { ...this.attributes };
|
|
4078
|
+
if (visible && visible.length > 0) {
|
|
4079
|
+
for (const key of Object.keys(data)) {
|
|
4080
|
+
if (!visible.includes(key)) {
|
|
4081
|
+
delete data[key];
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
for (const key of hidden) {
|
|
4086
|
+
delete data[key];
|
|
4087
|
+
}
|
|
4088
|
+
for (const key of [...ModelClass.$appends ?? [], ...this.appended]) {
|
|
4089
|
+
const accessor = this[accessorName(key)];
|
|
4090
|
+
if (typeof accessor === "function") {
|
|
4091
|
+
data[key] = accessor.call(this);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
for (const [name, value] of Object.entries(this.loadedRelations)) {
|
|
4095
|
+
if (!hidden.has(name) && (!visible || visible.includes(name) || this.appended.includes(name))) {
|
|
4096
|
+
data[name] = value;
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
return data;
|
|
4100
|
+
}
|
|
4101
|
+
toJSON() {
|
|
4102
|
+
return this.toArray();
|
|
4103
|
+
}
|
|
4104
|
+
makeHidden(...keys) {
|
|
4105
|
+
this.hiddenOverrides.push(...keys);
|
|
4106
|
+
return this;
|
|
4107
|
+
}
|
|
4108
|
+
makeVisible(...keys) {
|
|
4109
|
+
this.visibleOverrides.push(...keys);
|
|
4110
|
+
return this;
|
|
4111
|
+
}
|
|
4112
|
+
append(...keys) {
|
|
4113
|
+
this.appended.push(...keys);
|
|
4114
|
+
return this;
|
|
4115
|
+
}
|
|
3017
4116
|
primaryKey() {
|
|
3018
|
-
|
|
4117
|
+
return this.repository.getTable().primaryKey;
|
|
3019
4118
|
}
|
|
3020
4119
|
static primaryKeyField() {
|
|
3021
4120
|
return resolveModelRepository(this).getTable().primaryKey;
|
|
@@ -3034,6 +4133,10 @@ class Model {
|
|
|
3034
4133
|
return new statics(hydrated, repository, exists);
|
|
3035
4134
|
}
|
|
3036
4135
|
static boot() {}
|
|
4136
|
+
static observe(observer) {
|
|
4137
|
+
const existing = modelObservers.get(this) ?? [];
|
|
4138
|
+
modelObservers.set(this, [...existing, observer]);
|
|
4139
|
+
}
|
|
3037
4140
|
static addGlobalScope(_name, scope) {
|
|
3038
4141
|
ensureBooted(this);
|
|
3039
4142
|
const existing = modelGlobalScopes.get(this) ?? [];
|
|
@@ -3052,26 +4155,55 @@ class Model {
|
|
|
3052
4155
|
for (const scope of getGlobalScopes(this)) {
|
|
3053
4156
|
query = scope(query);
|
|
3054
4157
|
}
|
|
3055
|
-
return query;
|
|
4158
|
+
return new ModelQuery(this, query);
|
|
3056
4159
|
}
|
|
3057
|
-
static
|
|
4160
|
+
static newFromRecord(record, exists = true) {
|
|
4161
|
+
const repository = resolveModelRepository(this);
|
|
4162
|
+
return modelStatics(this).fromRecord(record, repository, exists);
|
|
4163
|
+
}
|
|
4164
|
+
static async create(attributes, forced = {}) {
|
|
3058
4165
|
const statics = modelStatics(this);
|
|
3059
4166
|
ensureBooted(this);
|
|
3060
4167
|
const repository = resolveModelRepository(this);
|
|
3061
4168
|
const table = repository.getTable();
|
|
3062
4169
|
const timestamps = statics.$timestamps ?? true;
|
|
3063
|
-
const assignable =
|
|
4170
|
+
const assignable = {
|
|
4171
|
+
...filterMassAssignable(statics.$fillable, statics.$guarded, attributes),
|
|
4172
|
+
...forced
|
|
4173
|
+
};
|
|
3064
4174
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
3065
4175
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
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
|
+
}
|
|
4180
|
+
if (await runObservers(pending, "creating") === false) {
|
|
4181
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
4182
|
+
}
|
|
3066
4183
|
const record = await repository.create(payload);
|
|
3067
|
-
|
|
4184
|
+
const created = statics.fromRecord(record, repository, true);
|
|
4185
|
+
await runObservers(created, "created");
|
|
4186
|
+
await runObservers(created, "saved");
|
|
4187
|
+
return created;
|
|
4188
|
+
}
|
|
4189
|
+
static with(...relations) {
|
|
4190
|
+
return Model.query.call(this).with(...relations);
|
|
4191
|
+
}
|
|
4192
|
+
static whereHas(name, constrain) {
|
|
4193
|
+
return Model.query.call(this).whereHas(name, constrain);
|
|
4194
|
+
}
|
|
4195
|
+
static has(name) {
|
|
4196
|
+
return Model.query.call(this).has(name);
|
|
4197
|
+
}
|
|
4198
|
+
static doesntHave(name) {
|
|
4199
|
+
return Model.query.call(this).doesntHave(name);
|
|
4200
|
+
}
|
|
4201
|
+
static whereDoesntHave(name, constrain) {
|
|
4202
|
+
return Model.query.call(this).whereDoesntHave(name, constrain);
|
|
3068
4203
|
}
|
|
3069
4204
|
static async find(id) {
|
|
3070
|
-
const
|
|
3071
|
-
|
|
3072
|
-
const primaryKey = repository.getTable().primaryKey;
|
|
3073
|
-
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
3074
|
-
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();
|
|
3075
4207
|
}
|
|
3076
4208
|
static async findOrFail(id, errorFactory) {
|
|
3077
4209
|
const model = await Model.find.call(this, id);
|
|
@@ -3081,8 +4213,6 @@ class Model {
|
|
|
3081
4213
|
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
3082
4214
|
}
|
|
3083
4215
|
static async all(options = {}) {
|
|
3084
|
-
const statics = modelStatics(this);
|
|
3085
|
-
const repository = resolveModelRepository(this);
|
|
3086
4216
|
let query = Model.query.call(this);
|
|
3087
4217
|
if (options.orderBy) {
|
|
3088
4218
|
query = query.orderBy(options.orderBy);
|
|
@@ -3090,28 +4220,57 @@ class Model {
|
|
|
3090
4220
|
if (options.limit !== undefined) {
|
|
3091
4221
|
query = query.limit(options.limit);
|
|
3092
4222
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
4223
|
+
return query.get();
|
|
4224
|
+
}
|
|
4225
|
+
static where(where) {
|
|
4226
|
+
return Model.query.call(this).where(where);
|
|
3095
4227
|
}
|
|
3096
4228
|
static async firstWhere(where, options = {}) {
|
|
3097
|
-
const statics = modelStatics(this);
|
|
3098
|
-
const repository = resolveModelRepository(this);
|
|
3099
4229
|
let query = Model.query.call(this).where(where);
|
|
3100
4230
|
if (options.orderBy) {
|
|
3101
4231
|
query = query.orderBy(options.orderBy);
|
|
3102
4232
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
4233
|
+
return query.first();
|
|
4234
|
+
}
|
|
4235
|
+
static async firstOrNew(where, values = {}) {
|
|
4236
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
4237
|
+
if (existing) {
|
|
4238
|
+
return existing;
|
|
4239
|
+
}
|
|
4240
|
+
return modelStatics(this).newFromRecord({ ...where, ...values }, false);
|
|
4241
|
+
}
|
|
4242
|
+
static async firstOrCreate(where, values = {}) {
|
|
4243
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
4244
|
+
if (existing) {
|
|
4245
|
+
return existing;
|
|
4246
|
+
}
|
|
4247
|
+
return Model.create.call(this, { ...where, ...values });
|
|
4248
|
+
}
|
|
4249
|
+
static async updateOrCreate(where, values = {}) {
|
|
4250
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
4251
|
+
if (existing) {
|
|
4252
|
+
return existing.update(values);
|
|
4253
|
+
}
|
|
4254
|
+
return Model.create.call(this, { ...where, ...values });
|
|
3105
4255
|
}
|
|
3106
4256
|
async save() {
|
|
3107
4257
|
const ModelClass = modelStatics(this.constructor);
|
|
3108
4258
|
const timestamps = ModelClass.$timestamps ?? true;
|
|
3109
4259
|
const casts = ModelClass.$casts ?? {};
|
|
3110
4260
|
const table = this.repository.getTable();
|
|
3111
|
-
|
|
4261
|
+
const updating = this.$exists;
|
|
4262
|
+
if (await runObservers(this, "saving") === false) {
|
|
4263
|
+
return this;
|
|
4264
|
+
}
|
|
4265
|
+
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
4266
|
+
return this;
|
|
4267
|
+
}
|
|
4268
|
+
if (updating) {
|
|
3112
4269
|
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
3113
4270
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
3114
4271
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
4272
|
+
await runObservers(this, "updated");
|
|
4273
|
+
await runObservers(this, "saved");
|
|
3115
4274
|
return this;
|
|
3116
4275
|
}
|
|
3117
4276
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -3120,6 +4279,8 @@ class Model {
|
|
|
3120
4279
|
const record = await this.repository.create(payload);
|
|
3121
4280
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
3122
4281
|
this._exists = true;
|
|
4282
|
+
await runObservers(this, "created");
|
|
4283
|
+
await runObservers(this, "saved");
|
|
3123
4284
|
return this;
|
|
3124
4285
|
}
|
|
3125
4286
|
async update(changes) {
|
|
@@ -3129,10 +4290,14 @@ class Model {
|
|
|
3129
4290
|
return await this.save();
|
|
3130
4291
|
}
|
|
3131
4292
|
async delete() {
|
|
3132
|
-
if (
|
|
3133
|
-
return
|
|
4293
|
+
if (await runObservers(this, "deleting") === false) {
|
|
4294
|
+
return false;
|
|
3134
4295
|
}
|
|
3135
|
-
|
|
4296
|
+
const deleted = resolveSoftDeleteColumn(this.repository.getTable()) ? await this.repository.deleteById(this.id) : await this.repository.forceDeleteById(this.id);
|
|
4297
|
+
if (deleted) {
|
|
4298
|
+
await runObservers(this, "deleted");
|
|
4299
|
+
}
|
|
4300
|
+
return deleted;
|
|
3136
4301
|
}
|
|
3137
4302
|
async forceDelete() {
|
|
3138
4303
|
return await this.repository.forceDeleteById(this.id);
|
|
@@ -3181,6 +4346,102 @@ class Model {
|
|
|
3181
4346
|
const loaded = grouped.get(parentId) ?? [];
|
|
3182
4347
|
return Object.assign(this, { [as]: loaded });
|
|
3183
4348
|
}
|
|
4349
|
+
hasMany(related, foreignKey, localKey) {
|
|
4350
|
+
const table = this.repository.getTable();
|
|
4351
|
+
const relatedClass = resolveRelated(related);
|
|
4352
|
+
return new HasManyRelationQuery(this, relatedClass, hasMany({
|
|
4353
|
+
name: relatedClass.repository().getTable().name,
|
|
4354
|
+
localKey: localKey ?? table.primaryKey,
|
|
4355
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4356
|
+
}));
|
|
4357
|
+
}
|
|
4358
|
+
hasOne(related, foreignKey, localKey) {
|
|
4359
|
+
const table = this.repository.getTable();
|
|
4360
|
+
const relatedClass = resolveRelated(related);
|
|
4361
|
+
return new HasOneRelationQuery(this, relatedClass, hasOne({
|
|
4362
|
+
name: relatedClass.repository().getTable().name,
|
|
4363
|
+
localKey: localKey ?? table.primaryKey,
|
|
4364
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4365
|
+
}));
|
|
4366
|
+
}
|
|
4367
|
+
belongsTo(related, foreignKey, ownerKey) {
|
|
4368
|
+
const relatedClass = resolveRelated(related);
|
|
4369
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4370
|
+
return new BelongsToRelationQuery(this, relatedClass, belongsTo({
|
|
4371
|
+
name: relatedTable.name,
|
|
4372
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
4373
|
+
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
4374
|
+
}));
|
|
4375
|
+
}
|
|
4376
|
+
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
4377
|
+
const table = this.repository.getTable();
|
|
4378
|
+
const relatedClass = resolveRelated(related);
|
|
4379
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4380
|
+
return new BelongsToManyRelationQuery(this, relatedClass, belongsToMany({
|
|
4381
|
+
name: relatedTable.name,
|
|
4382
|
+
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
4383
|
+
parentKey: table.primaryKey,
|
|
4384
|
+
relatedKey: relatedTable.primaryKey,
|
|
4385
|
+
foreignPivotKey: foreignPivotKey ?? foreignKeyFromTable(table.name),
|
|
4386
|
+
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
4387
|
+
}));
|
|
4388
|
+
}
|
|
4389
|
+
morphMany(related, morphName, typeKey, idKey, morphType) {
|
|
4390
|
+
const table = this.repository.getTable();
|
|
4391
|
+
const relatedClass = resolveRelated(related);
|
|
4392
|
+
return new MorphManyRelationQuery(this, relatedClass, morphMany({
|
|
4393
|
+
name: morphName,
|
|
4394
|
+
localKey: table.primaryKey,
|
|
4395
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4396
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4397
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4398
|
+
}));
|
|
4399
|
+
}
|
|
4400
|
+
morphOne(related, morphName, typeKey, idKey, morphType) {
|
|
4401
|
+
const table = this.repository.getTable();
|
|
4402
|
+
const relatedClass = resolveRelated(related);
|
|
4403
|
+
return new MorphOneRelationQuery(this, relatedClass, morphOne({
|
|
4404
|
+
name: morphName,
|
|
4405
|
+
localKey: table.primaryKey,
|
|
4406
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4407
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4408
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4409
|
+
}));
|
|
4410
|
+
}
|
|
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`
|
|
4421
|
+
}));
|
|
4422
|
+
}
|
|
4423
|
+
async load(...names) {
|
|
4424
|
+
for (const name of names) {
|
|
4425
|
+
if (name.includes(".")) {
|
|
4426
|
+
await loadNested(this, name);
|
|
4427
|
+
continue;
|
|
4428
|
+
}
|
|
4429
|
+
const method = this[name];
|
|
4430
|
+
if (typeof method !== "function") {
|
|
4431
|
+
throw new Error(`${this.constructor.name} has no relation method ${name}().`);
|
|
4432
|
+
}
|
|
4433
|
+
const relationQuery = method.call(this);
|
|
4434
|
+
this.loadedRelations[name] = await relationQuery.get();
|
|
4435
|
+
}
|
|
4436
|
+
return this;
|
|
4437
|
+
}
|
|
4438
|
+
loaded(name) {
|
|
4439
|
+
return this.loadedRelations[name];
|
|
4440
|
+
}
|
|
4441
|
+
setLoaded(name, value) {
|
|
4442
|
+
this.loadedRelations[name] = value;
|
|
4443
|
+
return this;
|
|
4444
|
+
}
|
|
3184
4445
|
mergeAttributes(patch) {
|
|
3185
4446
|
Object.assign(this.attributes, patch);
|
|
3186
4447
|
return this;
|
|
@@ -3188,6 +4449,14 @@ class Model {
|
|
|
3188
4449
|
}
|
|
3189
4450
|
function registerModelRepository(model, repository) {
|
|
3190
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
|
+
}
|
|
3191
4460
|
ensureBooted(model);
|
|
3192
4461
|
return model;
|
|
3193
4462
|
}
|
|
@@ -5079,6 +6348,70 @@ function createRequireAuthMiddleware(auth2) {
|
|
|
5079
6348
|
function serializeDate(value) {
|
|
5080
6349
|
return value instanceof Date ? value.toISOString() : value;
|
|
5081
6350
|
}
|
|
6351
|
+
function whenLoaded(model, relation, transform) {
|
|
6352
|
+
const value = model.loaded(relation);
|
|
6353
|
+
if (value === undefined) {
|
|
6354
|
+
return;
|
|
6355
|
+
}
|
|
6356
|
+
return transform ? transform(value) : value;
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
class JsonResource {
|
|
6360
|
+
resource;
|
|
6361
|
+
static wrap = "data";
|
|
6362
|
+
extra = {};
|
|
6363
|
+
constructor(resource) {
|
|
6364
|
+
this.resource = resource;
|
|
6365
|
+
}
|
|
6366
|
+
static make(resource) {
|
|
6367
|
+
return new JsonResource(resource);
|
|
6368
|
+
}
|
|
6369
|
+
static collection(items) {
|
|
6370
|
+
return new ResourceCollection(items);
|
|
6371
|
+
}
|
|
6372
|
+
additional(data) {
|
|
6373
|
+
this.extra = { ...this.extra, ...data };
|
|
6374
|
+
return this;
|
|
6375
|
+
}
|
|
6376
|
+
when(condition, value) {
|
|
6377
|
+
return condition ? value : undefined;
|
|
6378
|
+
}
|
|
6379
|
+
whenLoaded(relation, transform) {
|
|
6380
|
+
const model = this.resource;
|
|
6381
|
+
if (typeof model.loaded !== "function") {
|
|
6382
|
+
return;
|
|
6383
|
+
}
|
|
6384
|
+
return whenLoaded(model, relation, transform);
|
|
6385
|
+
}
|
|
6386
|
+
toArray() {
|
|
6387
|
+
if (this.resource && typeof this.resource === "object" && "toArray" in this.resource) {
|
|
6388
|
+
return this.resource.toArray();
|
|
6389
|
+
}
|
|
6390
|
+
return { ...this.resource };
|
|
6391
|
+
}
|
|
6392
|
+
toResponse() {
|
|
6393
|
+
const wrap = this.constructor.wrap;
|
|
6394
|
+
const payload = this.toArray();
|
|
6395
|
+
if (wrap === null) {
|
|
6396
|
+
return { ...payload, ...this.extra };
|
|
6397
|
+
}
|
|
6398
|
+
return { [wrap]: payload, ...this.extra };
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
|
|
6402
|
+
class ResourceCollection extends JsonResource {
|
|
6403
|
+
toArray() {
|
|
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 };
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
5082
6415
|
function toResourceCollection(items, transformer) {
|
|
5083
6416
|
return items.map(transformer);
|
|
5084
6417
|
}
|
|
@@ -7161,6 +8494,8 @@ export {
|
|
|
7161
8494
|
AuthManager,
|
|
7162
8495
|
BadRequestError,
|
|
7163
8496
|
baseRepository_default as BaseRepository,
|
|
8497
|
+
BelongsToManyRelationQuery,
|
|
8498
|
+
BelongsToRelationQuery,
|
|
7164
8499
|
Blueprint,
|
|
7165
8500
|
CACHE_TAGS,
|
|
7166
8501
|
repository_default as CacheRepository,
|
|
@@ -7173,19 +8508,27 @@ export {
|
|
|
7173
8508
|
DatabaseTokenGuard,
|
|
7174
8509
|
EtaViewEngine,
|
|
7175
8510
|
EventBus,
|
|
8511
|
+
Factory,
|
|
7176
8512
|
failedJobRepository_default as FailedJobRepository,
|
|
7177
8513
|
failedJobService_default as FailedJobService,
|
|
7178
8514
|
ForbiddenError,
|
|
7179
8515
|
ForeignIdColumnDefinition,
|
|
7180
8516
|
FormRequest,
|
|
7181
8517
|
GuestGuard,
|
|
8518
|
+
HasManyRelationQuery,
|
|
8519
|
+
HasOneRelationQuery,
|
|
7182
8520
|
HttpError,
|
|
7183
8521
|
Job,
|
|
8522
|
+
JsonResource,
|
|
7184
8523
|
LocalStorageDriver,
|
|
7185
8524
|
LogMailDriver,
|
|
7186
8525
|
Mailer,
|
|
7187
8526
|
membershipService_default as MembershipService,
|
|
7188
8527
|
Model,
|
|
8528
|
+
ModelQuery,
|
|
8529
|
+
MorphManyRelationQuery,
|
|
8530
|
+
MorphOneRelationQuery,
|
|
8531
|
+
MorphToRelationQuery,
|
|
7189
8532
|
MySqlGrammar,
|
|
7190
8533
|
NotFoundError,
|
|
7191
8534
|
Notification,
|
|
@@ -7200,6 +8543,7 @@ export {
|
|
|
7200
8543
|
RedisQueue,
|
|
7201
8544
|
RepositoryQuery,
|
|
7202
8545
|
ResilientQueue,
|
|
8546
|
+
ResourceCollection,
|
|
7203
8547
|
Schedule,
|
|
7204
8548
|
Schema,
|
|
7205
8549
|
ServiceContainer,
|
|
@@ -7297,6 +8641,7 @@ export {
|
|
|
7297
8641
|
eventBus,
|
|
7298
8642
|
events,
|
|
7299
8643
|
filterMassAssignable,
|
|
8644
|
+
foreignKeyFromTable,
|
|
7300
8645
|
formatAdminValue,
|
|
7301
8646
|
freshDatabase,
|
|
7302
8647
|
generateCspNonce,
|
|
@@ -7355,6 +8700,7 @@ export {
|
|
|
7355
8700
|
parseMultipartUpload,
|
|
7356
8701
|
parsePaginationQuery,
|
|
7357
8702
|
parsePositiveIntParam,
|
|
8703
|
+
pivotTableName,
|
|
7358
8704
|
policyGate,
|
|
7359
8705
|
prometheusRegistry,
|
|
7360
8706
|
queue,
|
|
@@ -7367,6 +8713,7 @@ export {
|
|
|
7367
8713
|
readTenancyDriver,
|
|
7368
8714
|
redirectResponse,
|
|
7369
8715
|
registerDefaultDatabasePool,
|
|
8716
|
+
registerModelClass,
|
|
7370
8717
|
registerModelRepository,
|
|
7371
8718
|
registerShutdownHandler,
|
|
7372
8719
|
renderKernelErrorChrome,
|
|
@@ -7423,6 +8770,7 @@ export {
|
|
|
7423
8770
|
serverHtmxContentSecurityPolicy,
|
|
7424
8771
|
setActiveApplicationContext,
|
|
7425
8772
|
signedUrl,
|
|
8773
|
+
singularize,
|
|
7426
8774
|
spaContentSecurityPolicy,
|
|
7427
8775
|
storageFacade as storage,
|
|
7428
8776
|
strictApiContentSecurityPolicy,
|
|
@@ -7436,6 +8784,7 @@ export {
|
|
|
7436
8784
|
trustForwardedFor,
|
|
7437
8785
|
validateObject,
|
|
7438
8786
|
verifyCsrfToken,
|
|
8787
|
+
whenLoaded,
|
|
7439
8788
|
withErrorHandling,
|
|
7440
8789
|
withMiddleware,
|
|
7441
8790
|
withMigrationLock,
|