@getstrata/core 0.5.98 → 0.5.100
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/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 +56 -19
- package/dist/core/database/relationQuery.d.ts +24 -1
- package/dist/core/database/relationships.d.ts +4 -2
- 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 +497 -145
- package/dist/entries/database/query.js +1 -1
- package/dist/entries/database/relationships.js +85 -21
- package/dist/entries/database/repositoryQuery.js +242 -8
- 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 +606 -178
- 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)})`);
|
|
@@ -2084,26 +2084,67 @@ function belongsToMany(definition) {
|
|
|
2084
2084
|
...definition
|
|
2085
2085
|
};
|
|
2086
2086
|
}
|
|
2087
|
+
function relationMatchKey(value) {
|
|
2088
|
+
if (value === null || value === undefined) {
|
|
2089
|
+
return "";
|
|
2090
|
+
}
|
|
2091
|
+
if (typeof value === "bigint") {
|
|
2092
|
+
return value.toString();
|
|
2093
|
+
}
|
|
2094
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2095
|
+
return String(value);
|
|
2096
|
+
}
|
|
2097
|
+
if (typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
2098
|
+
return BigInt(value).toString();
|
|
2099
|
+
}
|
|
2100
|
+
return String(value);
|
|
2101
|
+
}
|
|
2102
|
+
function getByRelationKey(map, key) {
|
|
2103
|
+
if (map.has(key)) {
|
|
2104
|
+
return map.get(key);
|
|
2105
|
+
}
|
|
2106
|
+
const want = relationMatchKey(key);
|
|
2107
|
+
if (want === "") {
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
for (const [existing, value] of map) {
|
|
2111
|
+
if (relationMatchKey(existing) === want) {
|
|
2112
|
+
return value;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2087
2117
|
function indexHasManyRelation(parents, children, relation) {
|
|
2088
2118
|
const groups = new Map;
|
|
2119
|
+
const originalKeys = new Map;
|
|
2089
2120
|
for (const parent of parents) {
|
|
2090
|
-
|
|
2121
|
+
const key = relationMatchKey(parent[relation.localKey]);
|
|
2122
|
+
if (!groups.has(key)) {
|
|
2123
|
+
groups.set(key, []);
|
|
2124
|
+
originalKeys.set(key, parent[relation.localKey]);
|
|
2125
|
+
}
|
|
2091
2126
|
}
|
|
2092
2127
|
for (const child of children) {
|
|
2093
|
-
const
|
|
2094
|
-
const group = groups.get(key);
|
|
2128
|
+
const group = groups.get(relationMatchKey(child[relation.foreignKey]));
|
|
2095
2129
|
if (!group) {
|
|
2096
2130
|
continue;
|
|
2097
2131
|
}
|
|
2098
2132
|
group.push(child);
|
|
2099
2133
|
}
|
|
2100
|
-
|
|
2134
|
+
const result = new Map;
|
|
2135
|
+
for (const [key, group] of groups) {
|
|
2136
|
+
const original = originalKeys.get(key);
|
|
2137
|
+
if (original !== undefined) {
|
|
2138
|
+
result.set(original, group);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return result;
|
|
2101
2142
|
}
|
|
2102
2143
|
function indexHasOneRelation(parents, children, relation) {
|
|
2103
2144
|
const grouped = indexHasManyRelation(parents, children, relation);
|
|
2104
2145
|
const result = new Map;
|
|
2105
2146
|
for (const parent of parents) {
|
|
2106
|
-
const matches = grouped
|
|
2147
|
+
const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
|
|
2107
2148
|
result.set(parent[relation.localKey], matches[0]);
|
|
2108
2149
|
}
|
|
2109
2150
|
return result;
|
|
@@ -2111,12 +2152,12 @@ function indexHasOneRelation(parents, children, relation) {
|
|
|
2111
2152
|
function indexBelongsToRelation(children, parents, relation) {
|
|
2112
2153
|
const parentsById = new Map;
|
|
2113
2154
|
for (const parent of parents) {
|
|
2114
|
-
parentsById.set(parent[relation.ownerKey], parent);
|
|
2155
|
+
parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
|
|
2115
2156
|
}
|
|
2116
2157
|
const result = new Map;
|
|
2117
2158
|
for (const child of children) {
|
|
2118
2159
|
const foreignKey = child[relation.foreignKey];
|
|
2119
|
-
const parent = parentsById.get(foreignKey);
|
|
2160
|
+
const parent = parentsById.get(relationMatchKey(foreignKey));
|
|
2120
2161
|
if (parent) {
|
|
2121
2162
|
result.set(foreignKey, parent);
|
|
2122
2163
|
}
|
|
@@ -2126,23 +2167,33 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
2126
2167
|
function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
2127
2168
|
const relatedById = new Map;
|
|
2128
2169
|
for (const related of relatedRows) {
|
|
2129
|
-
relatedById.set(related[relation.relatedKey], related);
|
|
2170
|
+
relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
|
|
2130
2171
|
}
|
|
2131
2172
|
const groups = new Map;
|
|
2173
|
+
const originalKeys = new Map;
|
|
2132
2174
|
for (const parent of parents) {
|
|
2133
|
-
|
|
2175
|
+
const key = relationMatchKey(parent[relation.parentKey]);
|
|
2176
|
+
if (!groups.has(key)) {
|
|
2177
|
+
groups.set(key, []);
|
|
2178
|
+
originalKeys.set(key, parent[relation.parentKey]);
|
|
2179
|
+
}
|
|
2134
2180
|
}
|
|
2135
2181
|
for (const pivot of pivotRows) {
|
|
2136
|
-
const
|
|
2137
|
-
const
|
|
2138
|
-
const group = groups.get(parentId);
|
|
2139
|
-
const related = relatedById.get(relatedId);
|
|
2182
|
+
const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
|
|
2183
|
+
const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
|
|
2140
2184
|
if (!group || !related) {
|
|
2141
2185
|
continue;
|
|
2142
2186
|
}
|
|
2143
2187
|
group.push(related);
|
|
2144
2188
|
}
|
|
2145
|
-
|
|
2189
|
+
const result = new Map;
|
|
2190
|
+
for (const [key, group] of groups) {
|
|
2191
|
+
const original = originalKeys.get(key);
|
|
2192
|
+
if (original !== undefined) {
|
|
2193
|
+
result.set(original, group);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
return result;
|
|
2146
2197
|
}
|
|
2147
2198
|
function morphMany(definition) {
|
|
2148
2199
|
return {
|
|
@@ -2164,27 +2215,38 @@ function morphTo(definition) {
|
|
|
2164
2215
|
}
|
|
2165
2216
|
function indexMorphManyRelation(parents, children, relation) {
|
|
2166
2217
|
const groups = new Map;
|
|
2218
|
+
const originalKeys = new Map;
|
|
2167
2219
|
for (const parent of parents) {
|
|
2168
|
-
|
|
2220
|
+
const key = relationMatchKey(parent[relation.localKey]);
|
|
2221
|
+
if (!groups.has(key)) {
|
|
2222
|
+
groups.set(key, []);
|
|
2223
|
+
originalKeys.set(key, parent[relation.localKey]);
|
|
2224
|
+
}
|
|
2169
2225
|
}
|
|
2170
2226
|
for (const child of children) {
|
|
2171
2227
|
if (child[relation.morphTypeKey] !== relation.morphType) {
|
|
2172
2228
|
continue;
|
|
2173
2229
|
}
|
|
2174
|
-
const
|
|
2175
|
-
const group = groups.get(key);
|
|
2230
|
+
const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
|
|
2176
2231
|
if (!group) {
|
|
2177
2232
|
continue;
|
|
2178
2233
|
}
|
|
2179
2234
|
group.push(child);
|
|
2180
2235
|
}
|
|
2181
|
-
|
|
2236
|
+
const result = new Map;
|
|
2237
|
+
for (const [key, group] of groups) {
|
|
2238
|
+
const original = originalKeys.get(key);
|
|
2239
|
+
if (original !== undefined) {
|
|
2240
|
+
result.set(original, group);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return result;
|
|
2182
2244
|
}
|
|
2183
2245
|
function indexMorphOneRelation(parents, children, relation) {
|
|
2184
2246
|
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
2185
2247
|
const result = new Map;
|
|
2186
2248
|
for (const parent of parents) {
|
|
2187
|
-
const matches = grouped
|
|
2249
|
+
const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
|
|
2188
2250
|
result.set(parent[relation.localKey], matches[0]);
|
|
2189
2251
|
}
|
|
2190
2252
|
return result;
|
|
@@ -2197,7 +2259,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
2197
2259
|
if (!parents) {
|
|
2198
2260
|
continue;
|
|
2199
2261
|
}
|
|
2200
|
-
const parent = parents
|
|
2262
|
+
const parent = getByRelationKey(parents, child[relation.morphIdKey]);
|
|
2201
2263
|
if (parent) {
|
|
2202
2264
|
result.set(child[relation.morphIdKey], parent);
|
|
2203
2265
|
}
|
|
@@ -2362,13 +2424,30 @@ class RepositoryQuery {
|
|
|
2362
2424
|
});
|
|
2363
2425
|
return this;
|
|
2364
2426
|
}
|
|
2427
|
+
withBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
2428
|
+
this.eagerLoads.push({
|
|
2429
|
+
kind: "belongsToMany",
|
|
2430
|
+
as,
|
|
2431
|
+
relation,
|
|
2432
|
+
repository: relatedRepository,
|
|
2433
|
+
options
|
|
2434
|
+
});
|
|
2435
|
+
return this;
|
|
2436
|
+
}
|
|
2365
2437
|
async get() {
|
|
2366
2438
|
const rows = await this.repository.findAll(this.buildOptions());
|
|
2367
2439
|
return await this.attach(rows);
|
|
2368
2440
|
}
|
|
2369
2441
|
async first() {
|
|
2370
|
-
const rows = await this.
|
|
2371
|
-
|
|
2442
|
+
const rows = await this.repository.findAll({ ...this.buildOptions(), limit: 1 });
|
|
2443
|
+
const attached = await this.attach(rows);
|
|
2444
|
+
return attached[0] ?? null;
|
|
2445
|
+
}
|
|
2446
|
+
async count() {
|
|
2447
|
+
return await this.repository.count(this.buildOptions());
|
|
2448
|
+
}
|
|
2449
|
+
async attachToRows(rows) {
|
|
2450
|
+
return await this.attach(rows);
|
|
2372
2451
|
}
|
|
2373
2452
|
async paginate(options) {
|
|
2374
2453
|
return await this.repository.paginate({
|
|
@@ -2418,7 +2497,7 @@ class RepositoryQuery {
|
|
|
2418
2497
|
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
|
|
2419
2498
|
result = result.map((row) => ({
|
|
2420
2499
|
...row,
|
|
2421
|
-
[load.as]: grouped2
|
|
2500
|
+
[load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
|
|
2422
2501
|
}));
|
|
2423
2502
|
continue;
|
|
2424
2503
|
}
|
|
@@ -2427,7 +2506,7 @@ class RepositoryQuery {
|
|
|
2427
2506
|
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
|
|
2428
2507
|
result = result.map((row) => ({
|
|
2429
2508
|
...row,
|
|
2430
|
-
[load.as]: grouped2
|
|
2509
|
+
[load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
|
|
2431
2510
|
}));
|
|
2432
2511
|
continue;
|
|
2433
2512
|
}
|
|
@@ -2436,7 +2515,16 @@ class RepositoryQuery {
|
|
|
2436
2515
|
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
|
|
2437
2516
|
result = result.map((row) => ({
|
|
2438
2517
|
...row,
|
|
2439
|
-
[load.as]: grouped2
|
|
2518
|
+
[load.as]: getByRelationKey(grouped2, row[relation2.localKey])
|
|
2519
|
+
}));
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
if (load.kind === "belongsToMany") {
|
|
2523
|
+
const relation2 = load.relation;
|
|
2524
|
+
const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
|
|
2525
|
+
result = result.map((row) => ({
|
|
2526
|
+
...row,
|
|
2527
|
+
[load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
|
|
2440
2528
|
}));
|
|
2441
2529
|
continue;
|
|
2442
2530
|
}
|
|
@@ -2445,7 +2533,7 @@ class RepositoryQuery {
|
|
|
2445
2533
|
const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
|
|
2446
2534
|
result = result.map((row) => ({
|
|
2447
2535
|
...row,
|
|
2448
|
-
[load.as]: grouped2
|
|
2536
|
+
[load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
|
|
2449
2537
|
}));
|
|
2450
2538
|
continue;
|
|
2451
2539
|
}
|
|
@@ -2453,7 +2541,7 @@ class RepositoryQuery {
|
|
|
2453
2541
|
const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
|
|
2454
2542
|
result = result.map((row) => ({
|
|
2455
2543
|
...row,
|
|
2456
|
-
[load.as]: grouped
|
|
2544
|
+
[load.as]: getByRelationKey(grouped, row[relation.foreignKey])
|
|
2457
2545
|
}));
|
|
2458
2546
|
}
|
|
2459
2547
|
return result;
|
|
@@ -2468,6 +2556,10 @@ class BaseRepository {
|
|
|
2468
2556
|
this.table = table;
|
|
2469
2557
|
this.connection = connection;
|
|
2470
2558
|
}
|
|
2559
|
+
async count(options = {}) {
|
|
2560
|
+
const { whereNodes, where, ...rest } = options;
|
|
2561
|
+
return await this.countWhere(where ?? {}, rest, whereNodes ?? []);
|
|
2562
|
+
}
|
|
2471
2563
|
async findAll(options = {}) {
|
|
2472
2564
|
return await withDatabaseErrorHandling(async () => {
|
|
2473
2565
|
const { whereNodes, ...queryOptions } = options;
|
|
@@ -2740,7 +2832,7 @@ class BaseRepository {
|
|
|
2740
2832
|
const grouped = await this.loadMorphManyForParents(parents, relation, options);
|
|
2741
2833
|
const result = new Map;
|
|
2742
2834
|
for (const parent of parents) {
|
|
2743
|
-
const matches = grouped
|
|
2835
|
+
const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
|
|
2744
2836
|
result.set(parent[relation.localKey], matches[0]);
|
|
2745
2837
|
}
|
|
2746
2838
|
return result;
|
|
@@ -2769,12 +2861,29 @@ class BaseRepository {
|
|
|
2769
2861
|
}, options);
|
|
2770
2862
|
const indexed = new Map;
|
|
2771
2863
|
for (const parent of parents) {
|
|
2772
|
-
indexed.set(parent[ownerKey], parent);
|
|
2864
|
+
indexed.set(relationMatchKey(parent[ownerKey]), parent);
|
|
2773
2865
|
}
|
|
2774
2866
|
parentsByType.set(morphType, indexed);
|
|
2775
2867
|
}
|
|
2776
2868
|
return indexMorphToRelation(children, parentsByType, relation);
|
|
2777
2869
|
}
|
|
2870
|
+
async loadBelongsToManyForParents(parents, relation, relatedRepository, options = {}) {
|
|
2871
|
+
if (parents.length === 0) {
|
|
2872
|
+
return indexBelongsToManyRelation(parents, [], [], relation);
|
|
2873
|
+
}
|
|
2874
|
+
const parentIds = [...new Set(parents.map((parent) => parent[relation.parentKey]))];
|
|
2875
|
+
const pivotRows = await this.connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = ANY($1)`, [parentIds]);
|
|
2876
|
+
if (pivotRows.length === 0) {
|
|
2877
|
+
return indexBelongsToManyRelation(parents, [], [], relation);
|
|
2878
|
+
}
|
|
2879
|
+
const relatedIds = [
|
|
2880
|
+
...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
|
|
2881
|
+
];
|
|
2882
|
+
const relatedRows = await relatedRepository.withConnection(this.connection).findWhere({
|
|
2883
|
+
[relation.relatedKey]: relatedIds
|
|
2884
|
+
}, options);
|
|
2885
|
+
return indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation);
|
|
2886
|
+
}
|
|
2778
2887
|
}
|
|
2779
2888
|
var baseRepository_default = BaseRepository;
|
|
2780
2889
|
// ../../src/core/database/bunSql.ts
|
|
@@ -2804,7 +2913,40 @@ function createDatabaseConnection(source) {
|
|
|
2804
2913
|
}
|
|
2805
2914
|
};
|
|
2806
2915
|
}
|
|
2916
|
+
// ../../src/core/database/inflection.ts
|
|
2917
|
+
function singularize(word) {
|
|
2918
|
+
if (word.endsWith("ies") && word.length > 3) {
|
|
2919
|
+
return `${word.slice(0, -3)}y`;
|
|
2920
|
+
}
|
|
2921
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) {
|
|
2922
|
+
return word.slice(0, -2);
|
|
2923
|
+
}
|
|
2924
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
2925
|
+
return word.slice(0, -1);
|
|
2926
|
+
}
|
|
2927
|
+
return word;
|
|
2928
|
+
}
|
|
2929
|
+
function foreignKeyFromTable(tableName) {
|
|
2930
|
+
return `${singularize(tableName)}_id`;
|
|
2931
|
+
}
|
|
2932
|
+
function pivotTableName(leftTable, rightTable) {
|
|
2933
|
+
return [singularize(leftTable), singularize(rightTable)].sort().join("_");
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2807
2936
|
// ../../src/core/database/factory.ts
|
|
2937
|
+
function inferFactoryForeignKey(parent, explicit) {
|
|
2938
|
+
if (explicit) {
|
|
2939
|
+
if (explicit.endsWith("_id")) {
|
|
2940
|
+
return explicit;
|
|
2941
|
+
}
|
|
2942
|
+
return `${explicit}_id`;
|
|
2943
|
+
}
|
|
2944
|
+
if (typeof parent.getRepository === "function") {
|
|
2945
|
+
return foreignKeyFromTable(parent.getRepository().getTable().name);
|
|
2946
|
+
}
|
|
2947
|
+
throw new Error("Factory.for() requires a foreign key or a parent Model.");
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2808
2950
|
class Factory {
|
|
2809
2951
|
quantity = 1;
|
|
2810
2952
|
counted = false;
|
|
@@ -2815,6 +2957,7 @@ class Factory {
|
|
|
2815
2957
|
children = [];
|
|
2816
2958
|
afterMakingCallbacks = [];
|
|
2817
2959
|
afterCreatingCallbacks = [];
|
|
2960
|
+
model;
|
|
2818
2961
|
definition() {
|
|
2819
2962
|
throw new Error("Factory definition must be implemented by subclass.");
|
|
2820
2963
|
}
|
|
@@ -2855,8 +2998,9 @@ class Factory {
|
|
|
2855
2998
|
if (parent.id === undefined || parent.id === null) {
|
|
2856
2999
|
throw new Error("Factory.for() requires a parent with an id.");
|
|
2857
3000
|
}
|
|
3001
|
+
const key = inferFactoryForeignKey(parent, foreignKey);
|
|
2858
3002
|
const next = this.clone();
|
|
2859
|
-
next.parentAssociations = [...this.parentAssociations, { foreignKey, value: parent.id }];
|
|
3003
|
+
next.parentAssociations = [...this.parentAssociations, { foreignKey: key, value: parent.id }];
|
|
2860
3004
|
return next;
|
|
2861
3005
|
}
|
|
2862
3006
|
recycle(parent, foreignKey) {
|
|
@@ -2937,29 +3081,17 @@ class Factory {
|
|
|
2937
3081
|
}
|
|
2938
3082
|
return values;
|
|
2939
3083
|
}
|
|
2940
|
-
persist(
|
|
3084
|
+
async persist(values) {
|
|
3085
|
+
if (this.model) {
|
|
3086
|
+
const created = await this.model.create(values);
|
|
3087
|
+
if (created && typeof created.toObject === "function") {
|
|
3088
|
+
return created.toObject();
|
|
3089
|
+
}
|
|
3090
|
+
return created;
|
|
3091
|
+
}
|
|
2941
3092
|
throw new Error("Factory.persist() must be implemented to use create().");
|
|
2942
3093
|
}
|
|
2943
3094
|
}
|
|
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
3095
|
// ../../src/core/database/migrations/advisoryLock.ts
|
|
2964
3096
|
var MIGRATION_LOCK_KEY = 42424242;
|
|
2965
3097
|
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
@@ -3085,6 +3217,9 @@ function ownerId(owner, ownerKey) {
|
|
|
3085
3217
|
}
|
|
3086
3218
|
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
3087
3219
|
}
|
|
3220
|
+
function thenGet(get, onfulfilled, onrejected) {
|
|
3221
|
+
return get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
3222
|
+
}
|
|
3088
3223
|
|
|
3089
3224
|
class HasManyRelationQuery {
|
|
3090
3225
|
parent;
|
|
@@ -3148,13 +3283,28 @@ class HasManyRelationQuery {
|
|
|
3148
3283
|
return rows[0] ?? null;
|
|
3149
3284
|
}
|
|
3150
3285
|
async count() {
|
|
3151
|
-
return
|
|
3286
|
+
return this.scopedQuery().count();
|
|
3287
|
+
}
|
|
3288
|
+
then(onfulfilled, onrejected) {
|
|
3289
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3152
3290
|
}
|
|
3153
3291
|
async create(attributes = {}) {
|
|
3154
3292
|
return this.related.create(attributes, {
|
|
3155
3293
|
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3156
3294
|
});
|
|
3157
3295
|
}
|
|
3296
|
+
async save(related) {
|
|
3297
|
+
const forced = {
|
|
3298
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3299
|
+
};
|
|
3300
|
+
const savable = related;
|
|
3301
|
+
if (typeof savable.save === "function") {
|
|
3302
|
+
savable.mergeAttributes?.(forced);
|
|
3303
|
+
await savable.save();
|
|
3304
|
+
return related;
|
|
3305
|
+
}
|
|
3306
|
+
return this.create(related);
|
|
3307
|
+
}
|
|
3158
3308
|
async createMany(records) {
|
|
3159
3309
|
const created = [];
|
|
3160
3310
|
for (const attributes of records) {
|
|
@@ -3202,11 +3352,17 @@ class HasOneRelationQuery {
|
|
|
3202
3352
|
return this.get();
|
|
3203
3353
|
}
|
|
3204
3354
|
async count() {
|
|
3205
|
-
return
|
|
3355
|
+
return this.inner.count();
|
|
3356
|
+
}
|
|
3357
|
+
then(onfulfilled, onrejected) {
|
|
3358
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3206
3359
|
}
|
|
3207
3360
|
async create(attributes = {}) {
|
|
3208
3361
|
return this.inner.create(attributes);
|
|
3209
3362
|
}
|
|
3363
|
+
async save(related) {
|
|
3364
|
+
return this.inner.save(related);
|
|
3365
|
+
}
|
|
3210
3366
|
}
|
|
3211
3367
|
|
|
3212
3368
|
class BelongsToRelationQuery {
|
|
@@ -3214,12 +3370,17 @@ class BelongsToRelationQuery {
|
|
|
3214
3370
|
related;
|
|
3215
3371
|
relation;
|
|
3216
3372
|
kind = "belongsTo";
|
|
3373
|
+
extraWhere = {};
|
|
3217
3374
|
extraOptions = {};
|
|
3218
3375
|
constructor(parent, related, relation) {
|
|
3219
3376
|
this.parent = parent;
|
|
3220
3377
|
this.related = related;
|
|
3221
3378
|
this.relation = relation;
|
|
3222
3379
|
}
|
|
3380
|
+
where(where) {
|
|
3381
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3382
|
+
return this;
|
|
3383
|
+
}
|
|
3223
3384
|
orderBy(orderBy) {
|
|
3224
3385
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3225
3386
|
return this;
|
|
@@ -3233,10 +3394,10 @@ class BelongsToRelationQuery {
|
|
|
3233
3394
|
}
|
|
3234
3395
|
toExistsClause(parentTable) {
|
|
3235
3396
|
const relatedTable = this.related.repository().getTable().name;
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
};
|
|
3397
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
3398
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3399
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(relatedTable)} WHERE ${qualifyColumn(relatedTable, this.relation.ownerKey)} = ${qualifyColumn(parentTable, this.relation.foreignKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3400
|
+
return { sql, params: extra.params };
|
|
3240
3401
|
}
|
|
3241
3402
|
async get() {
|
|
3242
3403
|
const foreign = this.parent.get(this.relation.foreignKey);
|
|
@@ -3244,7 +3405,7 @@ class BelongsToRelationQuery {
|
|
|
3244
3405
|
return null;
|
|
3245
3406
|
}
|
|
3246
3407
|
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3247
|
-
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign }));
|
|
3408
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign, ...this.extraWhere }));
|
|
3248
3409
|
if (this.extraOptions.orderBy) {
|
|
3249
3410
|
query = query.orderBy(this.extraOptions.orderBy);
|
|
3250
3411
|
}
|
|
@@ -3254,6 +3415,9 @@ class BelongsToRelationQuery {
|
|
|
3254
3415
|
async first() {
|
|
3255
3416
|
return this.get();
|
|
3256
3417
|
}
|
|
3418
|
+
then(onfulfilled, onrejected) {
|
|
3419
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3420
|
+
}
|
|
3257
3421
|
async associate(owner) {
|
|
3258
3422
|
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3259
3423
|
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
@@ -3273,6 +3437,7 @@ class BelongsToManyRelationQuery {
|
|
|
3273
3437
|
kind = "belongsToMany";
|
|
3274
3438
|
extraWhere = {};
|
|
3275
3439
|
extraOptions = {};
|
|
3440
|
+
pivotValues = {};
|
|
3276
3441
|
constructor(parent, related, relation) {
|
|
3277
3442
|
this.parent = parent;
|
|
3278
3443
|
this.related = related;
|
|
@@ -3286,7 +3451,13 @@ class BelongsToManyRelationQuery {
|
|
|
3286
3451
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3287
3452
|
return this;
|
|
3288
3453
|
}
|
|
3289
|
-
applyEagerLoad(
|
|
3454
|
+
applyEagerLoad(query, alias) {
|
|
3455
|
+
query.withBelongsToMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3456
|
+
}
|
|
3457
|
+
withPivotValues(values) {
|
|
3458
|
+
this.pivotValues = { ...this.pivotValues, ...values };
|
|
3459
|
+
return this;
|
|
3460
|
+
}
|
|
3290
3461
|
hydrateEager(row, alias) {
|
|
3291
3462
|
const value = row[alias] ?? [];
|
|
3292
3463
|
const rows = Array.isArray(value) ? value : [];
|
|
@@ -3326,13 +3497,34 @@ class BelongsToManyRelationQuery {
|
|
|
3326
3497
|
return rows[0] ?? null;
|
|
3327
3498
|
}
|
|
3328
3499
|
async count() {
|
|
3329
|
-
|
|
3500
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3501
|
+
const rows = await this.connection().unsafe(`SELECT COUNT(*) AS count FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3502
|
+
return Number(rows[0]?.count ?? 0);
|
|
3503
|
+
}
|
|
3504
|
+
then(onfulfilled, onrejected) {
|
|
3505
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3330
3506
|
}
|
|
3331
3507
|
async attach(ids) {
|
|
3508
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3509
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3510
|
+
const extraKeys = Object.keys(this.pivotValues);
|
|
3511
|
+
const extraColumns = extraKeys.length > 0 ? `, ${extraKeys.join(", ")}` : "";
|
|
3512
|
+
const extraPlaceholders = extraKeys.map((_, index) => `$${index + 3}`).join(", ");
|
|
3513
|
+
const extraValues = extraKeys.map((key) => this.pivotValues[key]);
|
|
3514
|
+
for (const id of list) {
|
|
3515
|
+
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]);
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
async toggle(ids) {
|
|
3332
3519
|
const list = Array.isArray(ids) ? ids : [ids];
|
|
3333
3520
|
const parentId = this.parent.get(this.relation.parentKey);
|
|
3334
3521
|
for (const id of list) {
|
|
3335
|
-
await this.connection().unsafe(`
|
|
3522
|
+
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]);
|
|
3523
|
+
if (existing.length > 0) {
|
|
3524
|
+
await this.detach(id);
|
|
3525
|
+
} else {
|
|
3526
|
+
await this.attach(id);
|
|
3527
|
+
}
|
|
3336
3528
|
}
|
|
3337
3529
|
}
|
|
3338
3530
|
async detach(ids) {
|
|
@@ -3404,6 +3596,17 @@ class MorphManyRelationQuery {
|
|
|
3404
3596
|
const rows = await this.get();
|
|
3405
3597
|
return rows[0] ?? null;
|
|
3406
3598
|
}
|
|
3599
|
+
async count() {
|
|
3600
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3601
|
+
return repository.query(asWhere({
|
|
3602
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3603
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
3604
|
+
...this.extraWhere
|
|
3605
|
+
})).count();
|
|
3606
|
+
}
|
|
3607
|
+
then(onfulfilled, onrejected) {
|
|
3608
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3609
|
+
}
|
|
3407
3610
|
async create(attributes = {}) {
|
|
3408
3611
|
return this.related.create(attributes, {
|
|
3409
3612
|
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
@@ -3444,6 +3647,15 @@ class MorphOneRelationQuery {
|
|
|
3444
3647
|
async get() {
|
|
3445
3648
|
return this.inner.first();
|
|
3446
3649
|
}
|
|
3650
|
+
async first() {
|
|
3651
|
+
return this.get();
|
|
3652
|
+
}
|
|
3653
|
+
async count() {
|
|
3654
|
+
return this.inner.count();
|
|
3655
|
+
}
|
|
3656
|
+
then(onfulfilled, onrejected) {
|
|
3657
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3658
|
+
}
|
|
3447
3659
|
async create(attributes = {}) {
|
|
3448
3660
|
return this.inner.create(attributes);
|
|
3449
3661
|
}
|
|
@@ -3454,11 +3666,16 @@ class MorphToRelationQuery {
|
|
|
3454
3666
|
relatedByType;
|
|
3455
3667
|
relation;
|
|
3456
3668
|
kind = "morphTo";
|
|
3669
|
+
extraWhere = {};
|
|
3457
3670
|
constructor(parent, relatedByType, relation) {
|
|
3458
3671
|
this.parent = parent;
|
|
3459
3672
|
this.relatedByType = relatedByType;
|
|
3460
3673
|
this.relation = relation;
|
|
3461
3674
|
}
|
|
3675
|
+
where(where) {
|
|
3676
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3677
|
+
return this;
|
|
3678
|
+
}
|
|
3462
3679
|
applyEagerLoad(query, alias) {
|
|
3463
3680
|
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
3464
3681
|
type,
|
|
@@ -3476,9 +3693,11 @@ class MorphToRelationQuery {
|
|
|
3476
3693
|
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
3477
3694
|
}
|
|
3478
3695
|
const relatedTable = related.repository().getTable();
|
|
3696
|
+
const extra = buildAdvancedWhereClause(relatedTable.name, this.extraWhere, [], []);
|
|
3697
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3479
3698
|
return {
|
|
3480
|
-
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}`,
|
|
3481
|
-
params:
|
|
3699
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}${extraSql ? ` AND ${extraSql}` : ""}`,
|
|
3700
|
+
params: extra.params
|
|
3482
3701
|
};
|
|
3483
3702
|
}
|
|
3484
3703
|
async get() {
|
|
@@ -3489,13 +3708,17 @@ class MorphToRelationQuery {
|
|
|
3489
3708
|
return null;
|
|
3490
3709
|
}
|
|
3491
3710
|
const table = related.repository().getTable();
|
|
3492
|
-
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id })).first();
|
|
3711
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id, ...this.extraWhere })).first();
|
|
3493
3712
|
return row ? related.newFromRecord(row) : null;
|
|
3494
3713
|
}
|
|
3714
|
+
then(onfulfilled, onrejected) {
|
|
3715
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
3716
|
+
}
|
|
3495
3717
|
}
|
|
3496
3718
|
|
|
3497
3719
|
// ../../src/core/database/model.ts
|
|
3498
3720
|
var modelRepositories = new WeakMap;
|
|
3721
|
+
var namedModels = new Map;
|
|
3499
3722
|
var modelGlobalScopes = new WeakMap;
|
|
3500
3723
|
var modelObservers = new WeakMap;
|
|
3501
3724
|
var modelBooted = new WeakSet;
|
|
@@ -3513,38 +3736,59 @@ function accessorName(key) {
|
|
|
3513
3736
|
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
3514
3737
|
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
3515
3738
|
}
|
|
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);
|
|
3739
|
+
function isLoadableModel(value) {
|
|
3740
|
+
return Boolean(value && typeof value === "object" && typeof value.load === "function" && typeof value.loaded === "function" && typeof value.setLoaded === "function");
|
|
3530
3741
|
}
|
|
3531
|
-
async function
|
|
3532
|
-
|
|
3533
|
-
if (!head) {
|
|
3742
|
+
async function eagerLoadOnModels(models, paths) {
|
|
3743
|
+
if (models.length === 0 || paths.length === 0) {
|
|
3534
3744
|
return;
|
|
3535
3745
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3746
|
+
const grouped = new Map;
|
|
3747
|
+
for (const path of paths) {
|
|
3748
|
+
const [head, ...rest] = path.split(".");
|
|
3749
|
+
if (!head) {
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
3752
|
+
const nested = rest.join(".");
|
|
3753
|
+
const existing = grouped.get(head) ?? [];
|
|
3754
|
+
if (nested) {
|
|
3755
|
+
existing.push(nested);
|
|
3756
|
+
}
|
|
3757
|
+
grouped.set(head, existing);
|
|
3539
3758
|
}
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3759
|
+
for (const [head, nested] of grouped) {
|
|
3760
|
+
const unloaded = models.filter((model) => model.loaded(head) === undefined);
|
|
3761
|
+
if (unloaded.length > 0) {
|
|
3762
|
+
const first = unloaded[0];
|
|
3763
|
+
if (!first) {
|
|
3764
|
+
continue;
|
|
3765
|
+
}
|
|
3766
|
+
const method = first[head];
|
|
3767
|
+
if (typeof method !== "function") {
|
|
3768
|
+
throw new Error(`${first.constructor.name} has no relation method ${head}().`);
|
|
3769
|
+
}
|
|
3770
|
+
const relationQuery = method.call(first);
|
|
3771
|
+
const query = first.getRepository().query();
|
|
3772
|
+
relationQuery.applyEagerLoad(query, head);
|
|
3773
|
+
const attached = await query.attachToRows(unloaded.map((model) => model.toObject()));
|
|
3774
|
+
for (const [index, model] of unloaded.entries()) {
|
|
3775
|
+
const row = attached[index] ?? model.toObject();
|
|
3776
|
+
model.setLoaded(head, relationQuery.hydrateEager(row, head));
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
if (nested.length === 0) {
|
|
3780
|
+
continue;
|
|
3545
3781
|
}
|
|
3782
|
+
const children = models.flatMap((model) => {
|
|
3783
|
+
const loaded = model.loaded(head);
|
|
3784
|
+
return Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
|
|
3785
|
+
});
|
|
3786
|
+
await eagerLoadOnModels(children.filter(isLoadableModel), nested);
|
|
3546
3787
|
}
|
|
3547
3788
|
}
|
|
3789
|
+
async function loadNested(model, path) {
|
|
3790
|
+
await eagerLoadOnModels([model], [path]);
|
|
3791
|
+
}
|
|
3548
3792
|
function resolveModelRepository(model) {
|
|
3549
3793
|
const repository = modelRepositories.get(model);
|
|
3550
3794
|
if (!repository) {
|
|
@@ -3552,6 +3796,48 @@ function resolveModelRepository(model) {
|
|
|
3552
3796
|
}
|
|
3553
3797
|
return repository;
|
|
3554
3798
|
}
|
|
3799
|
+
function registerModelClass(name, model) {
|
|
3800
|
+
namedModels.set(name, model);
|
|
3801
|
+
}
|
|
3802
|
+
function resolveRelated(related) {
|
|
3803
|
+
if (typeof related === "string") {
|
|
3804
|
+
const found = namedModels.get(related);
|
|
3805
|
+
if (!found) {
|
|
3806
|
+
throw new Error(`Model [${related}] is not registered. Call registerModelClass() first.`);
|
|
3807
|
+
}
|
|
3808
|
+
return found;
|
|
3809
|
+
}
|
|
3810
|
+
if (typeof related === "function" && typeof related.repository !== "function") {
|
|
3811
|
+
return related();
|
|
3812
|
+
}
|
|
3813
|
+
return related;
|
|
3814
|
+
}
|
|
3815
|
+
function inferRelationMethodName(callee) {
|
|
3816
|
+
const stack = new Error().stack ?? "";
|
|
3817
|
+
let seenCallee = false;
|
|
3818
|
+
for (const line of stack.split(`
|
|
3819
|
+
`)) {
|
|
3820
|
+
const match = /at (?:async )?(?:[^.\s]+\.)?(\w+)/.exec(line);
|
|
3821
|
+
const name = match?.[1];
|
|
3822
|
+
if (!name || name === "Error" || name === "inferRelationMethodName") {
|
|
3823
|
+
continue;
|
|
3824
|
+
}
|
|
3825
|
+
if (!seenCallee) {
|
|
3826
|
+
if (name === callee) {
|
|
3827
|
+
seenCallee = true;
|
|
3828
|
+
}
|
|
3829
|
+
continue;
|
|
3830
|
+
}
|
|
3831
|
+
if (name !== callee) {
|
|
3832
|
+
return name;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
function morphClassOf(model) {
|
|
3838
|
+
const statics = modelStatics(model.constructor === Function ? model : model.constructor);
|
|
3839
|
+
return statics.$morphClass ?? (model.constructor === Function ? model.name : model.constructor.name);
|
|
3840
|
+
}
|
|
3555
3841
|
function modelStatics(model) {
|
|
3556
3842
|
return model;
|
|
3557
3843
|
}
|
|
@@ -3581,6 +3867,11 @@ function hydrateValue(value, cast) {
|
|
|
3581
3867
|
case "bool":
|
|
3582
3868
|
case "boolean":
|
|
3583
3869
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
3870
|
+
case "integer":
|
|
3871
|
+
case "int":
|
|
3872
|
+
return value === "" ? null : Number(value);
|
|
3873
|
+
case "hashed":
|
|
3874
|
+
return value;
|
|
3584
3875
|
default:
|
|
3585
3876
|
return value;
|
|
3586
3877
|
}
|
|
@@ -3598,6 +3889,11 @@ function dehydrateValue(value, cast) {
|
|
|
3598
3889
|
case "bool":
|
|
3599
3890
|
case "boolean":
|
|
3600
3891
|
return Boolean(value);
|
|
3892
|
+
case "integer":
|
|
3893
|
+
case "int":
|
|
3894
|
+
return value === "" ? null : Number(value);
|
|
3895
|
+
case "hashed":
|
|
3896
|
+
return value;
|
|
3601
3897
|
default:
|
|
3602
3898
|
return value;
|
|
3603
3899
|
}
|
|
@@ -3652,6 +3948,153 @@ function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
|
3652
3948
|
return result;
|
|
3653
3949
|
}
|
|
3654
3950
|
|
|
3951
|
+
class ModelQuery {
|
|
3952
|
+
modelClass;
|
|
3953
|
+
query;
|
|
3954
|
+
eager = [];
|
|
3955
|
+
constructor(modelClass, query) {
|
|
3956
|
+
this.modelClass = modelClass;
|
|
3957
|
+
this.query = query;
|
|
3958
|
+
}
|
|
3959
|
+
with(...relations) {
|
|
3960
|
+
const statics = modelStatics(this.modelClass);
|
|
3961
|
+
ensureBooted(this.modelClass);
|
|
3962
|
+
const dummy = statics.newFromRecord({}, false);
|
|
3963
|
+
for (const path of relations) {
|
|
3964
|
+
const name = path.split(".")[0] ?? path;
|
|
3965
|
+
const method = dummy[name];
|
|
3966
|
+
if (typeof method !== "function") {
|
|
3967
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
3968
|
+
}
|
|
3969
|
+
const relationQuery = method.call(dummy);
|
|
3970
|
+
this.eager.push({ name, path, relationQuery });
|
|
3971
|
+
relationQuery.applyEagerLoad(this.query, name);
|
|
3972
|
+
}
|
|
3973
|
+
return this;
|
|
3974
|
+
}
|
|
3975
|
+
where(input) {
|
|
3976
|
+
this.query.where(input);
|
|
3977
|
+
return this;
|
|
3978
|
+
}
|
|
3979
|
+
orWhere(input) {
|
|
3980
|
+
this.query.orWhere(input);
|
|
3981
|
+
return this;
|
|
3982
|
+
}
|
|
3983
|
+
orderBy(orderBy) {
|
|
3984
|
+
this.query.orderBy(orderBy);
|
|
3985
|
+
return this;
|
|
3986
|
+
}
|
|
3987
|
+
limit(limit) {
|
|
3988
|
+
this.query.limit(limit);
|
|
3989
|
+
return this;
|
|
3990
|
+
}
|
|
3991
|
+
offset(offset) {
|
|
3992
|
+
this.query.offset(offset);
|
|
3993
|
+
return this;
|
|
3994
|
+
}
|
|
3995
|
+
whereNull(column) {
|
|
3996
|
+
this.query.whereNull(column);
|
|
3997
|
+
return this;
|
|
3998
|
+
}
|
|
3999
|
+
whereIn(column, values) {
|
|
4000
|
+
this.query.whereIn(column, values);
|
|
4001
|
+
return this;
|
|
4002
|
+
}
|
|
4003
|
+
whereExists(sql, params = []) {
|
|
4004
|
+
this.query.whereExists(sql, params);
|
|
4005
|
+
return this;
|
|
4006
|
+
}
|
|
4007
|
+
whereNotExists(sql, params = []) {
|
|
4008
|
+
this.query.whereNotExists(sql, params);
|
|
4009
|
+
return this;
|
|
4010
|
+
}
|
|
4011
|
+
whereHas(name, constrain) {
|
|
4012
|
+
return this.constrainExists(name, constrain, false);
|
|
4013
|
+
}
|
|
4014
|
+
has(name) {
|
|
4015
|
+
return this.constrainExists(name, undefined, false);
|
|
4016
|
+
}
|
|
4017
|
+
doesntHave(name) {
|
|
4018
|
+
return this.constrainExists(name, undefined, true);
|
|
4019
|
+
}
|
|
4020
|
+
whereDoesntHave(name, constrain) {
|
|
4021
|
+
return this.constrainExists(name, constrain, true);
|
|
4022
|
+
}
|
|
4023
|
+
withHasMany(...args) {
|
|
4024
|
+
this.query.withHasMany(...args);
|
|
4025
|
+
return this;
|
|
4026
|
+
}
|
|
4027
|
+
withBelongsTo(...args) {
|
|
4028
|
+
this.query.withBelongsTo(...args);
|
|
4029
|
+
return this;
|
|
4030
|
+
}
|
|
4031
|
+
withBelongsToMany(...args) {
|
|
4032
|
+
this.query.withBelongsToMany(...args);
|
|
4033
|
+
return this;
|
|
4034
|
+
}
|
|
4035
|
+
withMorphMany(...args) {
|
|
4036
|
+
this.query.withMorphMany(...args);
|
|
4037
|
+
return this;
|
|
4038
|
+
}
|
|
4039
|
+
withMorphOne(...args) {
|
|
4040
|
+
this.query.withMorphOne(...args);
|
|
4041
|
+
return this;
|
|
4042
|
+
}
|
|
4043
|
+
withMorphTo(...args) {
|
|
4044
|
+
this.query.withMorphTo(...args);
|
|
4045
|
+
return this;
|
|
4046
|
+
}
|
|
4047
|
+
async get() {
|
|
4048
|
+
const statics = modelStatics(this.modelClass);
|
|
4049
|
+
const rows = await this.query.get();
|
|
4050
|
+
const models = [];
|
|
4051
|
+
for (const row of rows) {
|
|
4052
|
+
const model = statics.newFromRecord(row, true);
|
|
4053
|
+
await runObservers(model, "retrieved");
|
|
4054
|
+
for (const { name, relationQuery } of this.eager) {
|
|
4055
|
+
model.setLoaded(name, relationQuery.hydrateEager(row, name));
|
|
4056
|
+
}
|
|
4057
|
+
models.push(model);
|
|
4058
|
+
}
|
|
4059
|
+
const nested = this.eager.filter((item) => item.path.includes(".")).map((item) => item.path);
|
|
4060
|
+
await eagerLoadOnModels(models.filter(isLoadableModel), nested);
|
|
4061
|
+
return models;
|
|
4062
|
+
}
|
|
4063
|
+
async first() {
|
|
4064
|
+
this.query.limit(1);
|
|
4065
|
+
const models = await this.get();
|
|
4066
|
+
return models[0] ?? null;
|
|
4067
|
+
}
|
|
4068
|
+
async find(id) {
|
|
4069
|
+
const primaryKey = resolveModelRepository(this.modelClass).getTable().primaryKey;
|
|
4070
|
+
return this.where({ [primaryKey]: id }).first();
|
|
4071
|
+
}
|
|
4072
|
+
async findOrFail(id, errorFactory) {
|
|
4073
|
+
const model = await this.find(id);
|
|
4074
|
+
if (model) {
|
|
4075
|
+
return model;
|
|
4076
|
+
}
|
|
4077
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.modelClass.name} ${String(id)} not found.`);
|
|
4078
|
+
}
|
|
4079
|
+
then(onfulfilled, onrejected) {
|
|
4080
|
+
return this.get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
4081
|
+
}
|
|
4082
|
+
constrainExists(name, constrain, not) {
|
|
4083
|
+
const statics = modelStatics(this.modelClass);
|
|
4084
|
+
ensureBooted(this.modelClass);
|
|
4085
|
+
const repository = resolveModelRepository(this.modelClass);
|
|
4086
|
+
const dummy = statics.newFromRecord({});
|
|
4087
|
+
const method = dummy[name];
|
|
4088
|
+
if (typeof method !== "function") {
|
|
4089
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
4090
|
+
}
|
|
4091
|
+
const relationQuery = method.call(dummy);
|
|
4092
|
+
constrain?.(relationQuery);
|
|
4093
|
+
const exists = relationQuery.toExistsClause(repository.getTable().name);
|
|
4094
|
+
return not ? this.whereNotExists(exists.sql, exists.params) : this.whereExists(exists.sql, exists.params);
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
|
|
3655
4098
|
class Model {
|
|
3656
4099
|
attributes;
|
|
3657
4100
|
repository;
|
|
@@ -3662,6 +4105,7 @@ class Model {
|
|
|
3662
4105
|
static $hidden;
|
|
3663
4106
|
static $visible;
|
|
3664
4107
|
static $appends;
|
|
4108
|
+
static $morphClass;
|
|
3665
4109
|
_exists;
|
|
3666
4110
|
loadedRelations = {};
|
|
3667
4111
|
hiddenOverrides = [];
|
|
@@ -3671,6 +4115,7 @@ class Model {
|
|
|
3671
4115
|
this.attributes = attributes;
|
|
3672
4116
|
this.repository = repository;
|
|
3673
4117
|
this._exists = exists;
|
|
4118
|
+
this.attributes = modelStatics(this.constructor).hydrateAttributes(attributes);
|
|
3674
4119
|
}
|
|
3675
4120
|
getRepository() {
|
|
3676
4121
|
return this.repository;
|
|
@@ -3731,7 +4176,7 @@ class Model {
|
|
|
3731
4176
|
return this;
|
|
3732
4177
|
}
|
|
3733
4178
|
primaryKey() {
|
|
3734
|
-
|
|
4179
|
+
return this.repository.getTable().primaryKey;
|
|
3735
4180
|
}
|
|
3736
4181
|
static primaryKeyField() {
|
|
3737
4182
|
return resolveModelRepository(this).getTable().primaryKey;
|
|
@@ -3772,7 +4217,7 @@ class Model {
|
|
|
3772
4217
|
for (const scope of getGlobalScopes(this)) {
|
|
3773
4218
|
query = scope(query);
|
|
3774
4219
|
}
|
|
3775
|
-
return query;
|
|
4220
|
+
return new ModelQuery(this, query);
|
|
3776
4221
|
}
|
|
3777
4222
|
static newFromRecord(record, exists = true) {
|
|
3778
4223
|
const repository = resolveModelRepository(this);
|
|
@@ -3791,79 +4236,36 @@ class Model {
|
|
|
3791
4236
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
3792
4237
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
3793
4238
|
const pending = statics.newFromRecord({ ...payload }, false);
|
|
4239
|
+
if (await runObservers(pending, "saving") === false) {
|
|
4240
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
4241
|
+
}
|
|
3794
4242
|
if (await runObservers(pending, "creating") === false) {
|
|
3795
4243
|
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
3796
4244
|
}
|
|
3797
4245
|
const record = await repository.create(payload);
|
|
3798
4246
|
const created = statics.fromRecord(record, repository, true);
|
|
3799
4247
|
await runObservers(created, "created");
|
|
4248
|
+
await runObservers(created, "saved");
|
|
3800
4249
|
return created;
|
|
3801
4250
|
}
|
|
3802
4251
|
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
|
-
};
|
|
4252
|
+
return Model.query.call(this).with(...relations);
|
|
3848
4253
|
}
|
|
3849
4254
|
static whereHas(name, constrain) {
|
|
3850
|
-
return
|
|
4255
|
+
return Model.query.call(this).whereHas(name, constrain);
|
|
3851
4256
|
}
|
|
3852
4257
|
static has(name) {
|
|
3853
|
-
return
|
|
4258
|
+
return Model.query.call(this).has(name);
|
|
3854
4259
|
}
|
|
3855
4260
|
static doesntHave(name) {
|
|
3856
|
-
return
|
|
4261
|
+
return Model.query.call(this).doesntHave(name);
|
|
3857
4262
|
}
|
|
3858
4263
|
static whereDoesntHave(name, constrain) {
|
|
3859
|
-
return
|
|
4264
|
+
return Model.query.call(this).whereDoesntHave(name, constrain);
|
|
3860
4265
|
}
|
|
3861
4266
|
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;
|
|
4267
|
+
const primaryKey = resolveModelRepository(this).getTable().primaryKey;
|
|
4268
|
+
return Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
3867
4269
|
}
|
|
3868
4270
|
static async findOrFail(id, errorFactory) {
|
|
3869
4271
|
const model = await Model.find.call(this, id);
|
|
@@ -3873,8 +4275,6 @@ class Model {
|
|
|
3873
4275
|
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
3874
4276
|
}
|
|
3875
4277
|
static async all(options = {}) {
|
|
3876
|
-
const statics = modelStatics(this);
|
|
3877
|
-
const repository = resolveModelRepository(this);
|
|
3878
4278
|
let query = Model.query.call(this);
|
|
3879
4279
|
if (options.orderBy) {
|
|
3880
4280
|
query = query.orderBy(options.orderBy);
|
|
@@ -3882,21 +4282,17 @@ class Model {
|
|
|
3882
4282
|
if (options.limit !== undefined) {
|
|
3883
4283
|
query = query.limit(options.limit);
|
|
3884
4284
|
}
|
|
3885
|
-
|
|
3886
|
-
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
4285
|
+
return query.get();
|
|
3887
4286
|
}
|
|
3888
4287
|
static where(where) {
|
|
3889
4288
|
return Model.query.call(this).where(where);
|
|
3890
4289
|
}
|
|
3891
4290
|
static async firstWhere(where, options = {}) {
|
|
3892
|
-
const statics = modelStatics(this);
|
|
3893
|
-
const repository = resolveModelRepository(this);
|
|
3894
4291
|
let query = Model.query.call(this).where(where);
|
|
3895
4292
|
if (options.orderBy) {
|
|
3896
4293
|
query = query.orderBy(options.orderBy);
|
|
3897
4294
|
}
|
|
3898
|
-
|
|
3899
|
-
return record ? statics.fromRecord(record, repository, true) : null;
|
|
4295
|
+
return query.first();
|
|
3900
4296
|
}
|
|
3901
4297
|
static async firstOrNew(where, values = {}) {
|
|
3902
4298
|
const existing = await Model.firstWhere.call(this, where);
|
|
@@ -3925,6 +4321,9 @@ class Model {
|
|
|
3925
4321
|
const casts = ModelClass.$casts ?? {};
|
|
3926
4322
|
const table = this.repository.getTable();
|
|
3927
4323
|
const updating = this.$exists;
|
|
4324
|
+
if (await runObservers(this, "saving") === false) {
|
|
4325
|
+
return this;
|
|
4326
|
+
}
|
|
3928
4327
|
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
3929
4328
|
return this;
|
|
3930
4329
|
}
|
|
@@ -3933,6 +4332,7 @@ class Model {
|
|
|
3933
4332
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
3934
4333
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
3935
4334
|
await runObservers(this, "updated");
|
|
4335
|
+
await runObservers(this, "saved");
|
|
3936
4336
|
return this;
|
|
3937
4337
|
}
|
|
3938
4338
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -3942,6 +4342,7 @@ class Model {
|
|
|
3942
4342
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
3943
4343
|
this._exists = true;
|
|
3944
4344
|
await runObservers(this, "created");
|
|
4345
|
+
await runObservers(this, "saved");
|
|
3945
4346
|
return this;
|
|
3946
4347
|
}
|
|
3947
4348
|
async update(changes) {
|
|
@@ -3974,7 +4375,7 @@ class Model {
|
|
|
3974
4375
|
}
|
|
3975
4376
|
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
3976
4377
|
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
3977
|
-
const loaded = grouped
|
|
4378
|
+
const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
|
|
3978
4379
|
return Object.assign(this, { [as]: loaded });
|
|
3979
4380
|
}
|
|
3980
4381
|
async loadHasOne(as, relation, childRepository, options = {}) {
|
|
@@ -3984,7 +4385,7 @@ class Model {
|
|
|
3984
4385
|
}
|
|
3985
4386
|
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
3986
4387
|
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
3987
|
-
const loaded = grouped
|
|
4388
|
+
const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
|
|
3988
4389
|
return Object.assign(this, { [as]: loaded });
|
|
3989
4390
|
}
|
|
3990
4391
|
async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
@@ -4004,28 +4405,31 @@ class Model {
|
|
|
4004
4405
|
}
|
|
4005
4406
|
});
|
|
4006
4407
|
const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
|
|
4007
|
-
const loaded = grouped
|
|
4408
|
+
const loaded = getByRelationKey(grouped, parentId) ?? [];
|
|
4008
4409
|
return Object.assign(this, { [as]: loaded });
|
|
4009
4410
|
}
|
|
4010
4411
|
hasMany(related, foreignKey, localKey) {
|
|
4011
4412
|
const table = this.repository.getTable();
|
|
4012
|
-
|
|
4013
|
-
|
|
4413
|
+
const relatedClass = resolveRelated(related);
|
|
4414
|
+
return new HasManyRelationQuery(this, relatedClass, hasMany({
|
|
4415
|
+
name: relatedClass.repository().getTable().name,
|
|
4014
4416
|
localKey: localKey ?? table.primaryKey,
|
|
4015
4417
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4016
4418
|
}));
|
|
4017
4419
|
}
|
|
4018
4420
|
hasOne(related, foreignKey, localKey) {
|
|
4019
4421
|
const table = this.repository.getTable();
|
|
4020
|
-
|
|
4021
|
-
|
|
4422
|
+
const relatedClass = resolveRelated(related);
|
|
4423
|
+
return new HasOneRelationQuery(this, relatedClass, hasOne({
|
|
4424
|
+
name: relatedClass.repository().getTable().name,
|
|
4022
4425
|
localKey: localKey ?? table.primaryKey,
|
|
4023
4426
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4024
4427
|
}));
|
|
4025
4428
|
}
|
|
4026
4429
|
belongsTo(related, foreignKey, ownerKey) {
|
|
4027
|
-
const
|
|
4028
|
-
|
|
4430
|
+
const relatedClass = resolveRelated(related);
|
|
4431
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4432
|
+
return new BelongsToRelationQuery(this, relatedClass, belongsTo({
|
|
4029
4433
|
name: relatedTable.name,
|
|
4030
4434
|
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
4031
4435
|
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
@@ -4033,8 +4437,9 @@ class Model {
|
|
|
4033
4437
|
}
|
|
4034
4438
|
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
4035
4439
|
const table = this.repository.getTable();
|
|
4036
|
-
const
|
|
4037
|
-
|
|
4440
|
+
const relatedClass = resolveRelated(related);
|
|
4441
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
4442
|
+
return new BelongsToManyRelationQuery(this, relatedClass, belongsToMany({
|
|
4038
4443
|
name: relatedTable.name,
|
|
4039
4444
|
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
4040
4445
|
parentKey: table.primaryKey,
|
|
@@ -4043,31 +4448,38 @@ class Model {
|
|
|
4043
4448
|
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
4044
4449
|
}));
|
|
4045
4450
|
}
|
|
4046
|
-
morphMany(related, morphName, typeKey, idKey) {
|
|
4451
|
+
morphMany(related, morphName, typeKey, idKey, morphType) {
|
|
4047
4452
|
const table = this.repository.getTable();
|
|
4048
|
-
|
|
4453
|
+
const relatedClass = resolveRelated(related);
|
|
4454
|
+
return new MorphManyRelationQuery(this, relatedClass, morphMany({
|
|
4049
4455
|
name: morphName,
|
|
4050
4456
|
localKey: table.primaryKey,
|
|
4051
4457
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4052
4458
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4053
|
-
morphType:
|
|
4459
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4054
4460
|
}));
|
|
4055
4461
|
}
|
|
4056
|
-
morphOne(related, morphName, typeKey, idKey) {
|
|
4462
|
+
morphOne(related, morphName, typeKey, idKey, morphType) {
|
|
4057
4463
|
const table = this.repository.getTable();
|
|
4058
|
-
|
|
4464
|
+
const relatedClass = resolveRelated(related);
|
|
4465
|
+
return new MorphOneRelationQuery(this, relatedClass, morphOne({
|
|
4059
4466
|
name: morphName,
|
|
4060
4467
|
localKey: table.primaryKey,
|
|
4061
4468
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4062
4469
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4063
|
-
morphType:
|
|
4470
|
+
morphType: morphType ?? morphClassOf(this)
|
|
4064
4471
|
}));
|
|
4065
4472
|
}
|
|
4066
|
-
morphTo(relatedByType, morphName
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4473
|
+
morphTo(relatedByType, morphName, typeKey, idKey) {
|
|
4474
|
+
const resolvedName = morphName ?? inferRelationMethodName("morphTo");
|
|
4475
|
+
if (!resolvedName) {
|
|
4476
|
+
throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (Laravel infers it from the relation method).`);
|
|
4477
|
+
}
|
|
4478
|
+
const resolvedMap = Object.fromEntries(Object.entries(relatedByType).map(([type, related]) => [type, resolveRelated(related)]));
|
|
4479
|
+
return new MorphToRelationQuery(this, resolvedMap, morphTo({
|
|
4480
|
+
name: resolvedName,
|
|
4481
|
+
morphTypeKey: typeKey ?? `${resolvedName}_type`,
|
|
4482
|
+
morphIdKey: idKey ?? `${resolvedName}_id`
|
|
4071
4483
|
}));
|
|
4072
4484
|
}
|
|
4073
4485
|
async load(...names) {
|
|
@@ -4099,6 +4511,14 @@ class Model {
|
|
|
4099
4511
|
}
|
|
4100
4512
|
function registerModelRepository(model, repository) {
|
|
4101
4513
|
modelRepositories.set(model, repository);
|
|
4514
|
+
const name = model.name;
|
|
4515
|
+
if (name) {
|
|
4516
|
+
namedModels.set(name, model);
|
|
4517
|
+
}
|
|
4518
|
+
const morphClass = model.$morphClass;
|
|
4519
|
+
if (morphClass) {
|
|
4520
|
+
namedModels.set(morphClass, model);
|
|
4521
|
+
}
|
|
4102
4522
|
ensureBooted(model);
|
|
4103
4523
|
return model;
|
|
4104
4524
|
}
|
|
@@ -6043,9 +6463,15 @@ class JsonResource {
|
|
|
6043
6463
|
|
|
6044
6464
|
class ResourceCollection extends JsonResource {
|
|
6045
6465
|
toArray() {
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6466
|
+
const items = this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item });
|
|
6467
|
+
const wrap = this.constructor.wrap;
|
|
6468
|
+
if (wrap === null) {
|
|
6469
|
+
return { data: items };
|
|
6470
|
+
}
|
|
6471
|
+
return { [wrap]: items };
|
|
6472
|
+
}
|
|
6473
|
+
toResponse() {
|
|
6474
|
+
return { ...this.toArray(), ...this.extra };
|
|
6049
6475
|
}
|
|
6050
6476
|
}
|
|
6051
6477
|
function toResourceCollection(items, transformer) {
|
|
@@ -8161,6 +8587,7 @@ export {
|
|
|
8161
8587
|
Mailer,
|
|
8162
8588
|
membershipService_default as MembershipService,
|
|
8163
8589
|
Model,
|
|
8590
|
+
ModelQuery,
|
|
8164
8591
|
MorphManyRelationQuery,
|
|
8165
8592
|
MorphOneRelationQuery,
|
|
8166
8593
|
MorphToRelationQuery,
|
|
@@ -8348,6 +8775,7 @@ export {
|
|
|
8348
8775
|
readTenancyDriver,
|
|
8349
8776
|
redirectResponse,
|
|
8350
8777
|
registerDefaultDatabasePool,
|
|
8778
|
+
registerModelClass,
|
|
8351
8779
|
registerModelRepository,
|
|
8352
8780
|
registerShutdownHandler,
|
|
8353
8781
|
renderKernelErrorChrome,
|