@getstrata/core 0.5.91 → 0.5.98
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 +18 -0
- package/dist/core/contracts/container.d.ts +2 -0
- package/dist/core/database/errors.d.ts +1 -1
- package/dist/core/database/factory.d.ts +31 -4
- 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 +50 -1
- package/dist/core/database/relationQuery.d.ts +149 -0
- package/dist/core/database/repositoryQuery.d.ts +5 -0
- package/dist/core/database/whereBuilder.d.ts +9 -1
- package/dist/core/errors/http.d.ts +8 -1
- package/dist/core/events/eventBus.d.ts +2 -0
- package/dist/core/http/resources.d.ts +20 -1
- package/dist/entries/contracts/container.js +6 -0
- package/dist/entries/database/errors.js +4 -3
- package/dist/entries/database/factory.js +116 -5
- package/dist/entries/database/model.js +760 -7
- package/dist/entries/database/query.js +9 -0
- package/dist/entries/database/repositoryQuery.js +26 -0
- package/dist/entries/database/schema.js +9 -0
- package/dist/entries/http/resources.js +62 -1
- package/dist/entries/http/response.js +10 -9
- package/dist/entries/http/webErrorResponse.js +8 -7
- package/dist/framework/public-api.d.ts +6 -4
- package/dist/index.js +1020 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -120,6 +120,22 @@ class PreconditionFailedError extends HttpError {
|
|
|
120
120
|
super(412, message, details);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
function isHttpErrorLike(error) {
|
|
124
|
+
if (typeof error !== "object" || error === null) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const candidate = error;
|
|
128
|
+
return typeof candidate.status === "number" && Number.isInteger(candidate.status) && candidate.status >= 400 && candidate.status < 600 && typeof candidate.message === "string";
|
|
129
|
+
}
|
|
130
|
+
function toHttpError(error) {
|
|
131
|
+
if (error instanceof HttpError) {
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
if (!isHttpErrorLike(error)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return new HttpError(error.status, error.message, error.details);
|
|
138
|
+
}
|
|
123
139
|
|
|
124
140
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
125
141
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1481,6 +1497,12 @@ class ServiceContainer {
|
|
|
1481
1497
|
resolve(key) {
|
|
1482
1498
|
return this.get(key);
|
|
1483
1499
|
}
|
|
1500
|
+
make(key) {
|
|
1501
|
+
return this.resolve(key);
|
|
1502
|
+
}
|
|
1503
|
+
instance(key, value) {
|
|
1504
|
+
return this.set(key, value);
|
|
1505
|
+
}
|
|
1484
1506
|
has(key) {
|
|
1485
1507
|
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
1486
1508
|
}
|
|
@@ -1509,6 +1531,9 @@ class ConfigStore {
|
|
|
1509
1531
|
class EventBus {
|
|
1510
1532
|
constructor() {}
|
|
1511
1533
|
listeners = new Map;
|
|
1534
|
+
on(event, listener) {
|
|
1535
|
+
return this.listen(event, listener);
|
|
1536
|
+
}
|
|
1512
1537
|
listen(event, listener) {
|
|
1513
1538
|
const handlers = this.listeners.get(event) ?? new Set;
|
|
1514
1539
|
handlers.add(listener);
|
|
@@ -1520,6 +1545,9 @@ class EventBus {
|
|
|
1520
1545
|
}
|
|
1521
1546
|
};
|
|
1522
1547
|
}
|
|
1548
|
+
async emit(event, payload) {
|
|
1549
|
+
await this.dispatch(event, payload);
|
|
1550
|
+
}
|
|
1523
1551
|
async dispatch(event, payload) {
|
|
1524
1552
|
const handlers = this.listeners.get(event);
|
|
1525
1553
|
if (!handlers || handlers.size === 0) {
|
|
@@ -1575,8 +1603,9 @@ function getPostgresSqlState(error) {
|
|
|
1575
1603
|
return;
|
|
1576
1604
|
}
|
|
1577
1605
|
function mapDatabaseError(error) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1606
|
+
const httpError = toHttpError(error);
|
|
1607
|
+
if (httpError) {
|
|
1608
|
+
return httpError;
|
|
1580
1609
|
}
|
|
1581
1610
|
if (!isPostgresError(error)) {
|
|
1582
1611
|
const message = error instanceof Error ? error.message : "Database operation failed.";
|
|
@@ -1720,7 +1749,16 @@ function appendWhereParts(tableName, where, params) {
|
|
|
1720
1749
|
}
|
|
1721
1750
|
return clauses.join(" AND ");
|
|
1722
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
|
+
}
|
|
1723
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
|
+
}
|
|
1724
1762
|
if ("where" in node) {
|
|
1725
1763
|
return appendWhereParts(tableName, node.where, params);
|
|
1726
1764
|
}
|
|
@@ -2238,6 +2276,23 @@ class RepositoryQuery {
|
|
|
2238
2276
|
this.queryOptions = { ...this.queryOptions, limit };
|
|
2239
2277
|
return this;
|
|
2240
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
|
+
}
|
|
2241
2296
|
offset(offset) {
|
|
2242
2297
|
this.queryOptions = { ...this.queryOptions, offset };
|
|
2243
2298
|
return this;
|
|
@@ -2749,6 +2804,162 @@ function createDatabaseConnection(source) {
|
|
|
2749
2804
|
}
|
|
2750
2805
|
};
|
|
2751
2806
|
}
|
|
2807
|
+
// ../../src/core/database/factory.ts
|
|
2808
|
+
class Factory {
|
|
2809
|
+
quantity = 1;
|
|
2810
|
+
counted = false;
|
|
2811
|
+
sequenceIndex = 0;
|
|
2812
|
+
stateTransforms = [];
|
|
2813
|
+
sequenceItems = [];
|
|
2814
|
+
parentAssociations = [];
|
|
2815
|
+
children = [];
|
|
2816
|
+
afterMakingCallbacks = [];
|
|
2817
|
+
afterCreatingCallbacks = [];
|
|
2818
|
+
definition() {
|
|
2819
|
+
throw new Error("Factory definition must be implemented by subclass.");
|
|
2820
|
+
}
|
|
2821
|
+
clone() {
|
|
2822
|
+
const next = Object.create(Object.getPrototypeOf(this));
|
|
2823
|
+
Object.assign(next, this);
|
|
2824
|
+
next.stateTransforms = [...this.stateTransforms];
|
|
2825
|
+
next.sequenceItems = [...this.sequenceItems];
|
|
2826
|
+
next.parentAssociations = [...this.parentAssociations];
|
|
2827
|
+
next.children = [...this.children];
|
|
2828
|
+
next.afterMakingCallbacks = [...this.afterMakingCallbacks];
|
|
2829
|
+
next.afterCreatingCallbacks = [...this.afterCreatingCallbacks];
|
|
2830
|
+
return next;
|
|
2831
|
+
}
|
|
2832
|
+
count(quantity) {
|
|
2833
|
+
if (!Number.isInteger(quantity) || quantity < 1) {
|
|
2834
|
+
throw new Error("Factory.count() requires a positive integer.");
|
|
2835
|
+
}
|
|
2836
|
+
const next = this.clone();
|
|
2837
|
+
next.quantity = quantity;
|
|
2838
|
+
next.counted = true;
|
|
2839
|
+
return next;
|
|
2840
|
+
}
|
|
2841
|
+
state(state) {
|
|
2842
|
+
const next = this.clone();
|
|
2843
|
+
next.stateTransforms = [...this.stateTransforms, state];
|
|
2844
|
+
return next;
|
|
2845
|
+
}
|
|
2846
|
+
sequence(...items) {
|
|
2847
|
+
if (items.length === 0) {
|
|
2848
|
+
throw new Error("Factory.sequence() requires at least one attribute set.");
|
|
2849
|
+
}
|
|
2850
|
+
const next = this.clone();
|
|
2851
|
+
next.sequenceItems = [...this.sequenceItems, ...items];
|
|
2852
|
+
return next;
|
|
2853
|
+
}
|
|
2854
|
+
for(parent, foreignKey) {
|
|
2855
|
+
if (parent.id === undefined || parent.id === null) {
|
|
2856
|
+
throw new Error("Factory.for() requires a parent with an id.");
|
|
2857
|
+
}
|
|
2858
|
+
const next = this.clone();
|
|
2859
|
+
next.parentAssociations = [...this.parentAssociations, { foreignKey, value: parent.id }];
|
|
2860
|
+
return next;
|
|
2861
|
+
}
|
|
2862
|
+
recycle(parent, foreignKey) {
|
|
2863
|
+
return this.for(parent, foreignKey);
|
|
2864
|
+
}
|
|
2865
|
+
afterMaking(callback) {
|
|
2866
|
+
const next = this.clone();
|
|
2867
|
+
next.afterMakingCallbacks = [...this.afterMakingCallbacks, callback];
|
|
2868
|
+
return next;
|
|
2869
|
+
}
|
|
2870
|
+
afterCreating(callback) {
|
|
2871
|
+
const next = this.clone();
|
|
2872
|
+
next.afterCreatingCallbacks = [...this.afterCreatingCallbacks, callback];
|
|
2873
|
+
return next;
|
|
2874
|
+
}
|
|
2875
|
+
has(factory, foreignKey) {
|
|
2876
|
+
const next = this.clone();
|
|
2877
|
+
next.children = [
|
|
2878
|
+
...this.children,
|
|
2879
|
+
{ factory, foreignKey }
|
|
2880
|
+
];
|
|
2881
|
+
return next;
|
|
2882
|
+
}
|
|
2883
|
+
make(overrides = {}) {
|
|
2884
|
+
if (!this.counted) {
|
|
2885
|
+
return this.makeOne(overrides);
|
|
2886
|
+
}
|
|
2887
|
+
return Array.from({ length: this.quantity }, () => this.makeOne(overrides));
|
|
2888
|
+
}
|
|
2889
|
+
async create(overrides = {}) {
|
|
2890
|
+
if (!this.counted) {
|
|
2891
|
+
return await this.createOne(overrides);
|
|
2892
|
+
}
|
|
2893
|
+
const records = [];
|
|
2894
|
+
for (let index = 0;index < this.quantity; index += 1) {
|
|
2895
|
+
records.push(await this.createOne(overrides));
|
|
2896
|
+
}
|
|
2897
|
+
return records;
|
|
2898
|
+
}
|
|
2899
|
+
makeOne(overrides = {}) {
|
|
2900
|
+
let record = { ...this.definition() };
|
|
2901
|
+
for (const state of this.stateTransforms) {
|
|
2902
|
+
const patch = typeof state === "function" ? state(record) : state;
|
|
2903
|
+
record = { ...record, ...patch };
|
|
2904
|
+
}
|
|
2905
|
+
if (this.sequenceItems.length > 0) {
|
|
2906
|
+
const item = this.sequenceItems[this.sequenceIndex % this.sequenceItems.length];
|
|
2907
|
+
const patch = typeof item === "function" ? item(this.sequenceIndex) : item;
|
|
2908
|
+
record = { ...record, ...patch };
|
|
2909
|
+
this.sequenceIndex += 1;
|
|
2910
|
+
}
|
|
2911
|
+
for (const association of this.parentAssociations) {
|
|
2912
|
+
record[association.foreignKey] = association.value;
|
|
2913
|
+
}
|
|
2914
|
+
const made = {
|
|
2915
|
+
...record,
|
|
2916
|
+
...overrides
|
|
2917
|
+
};
|
|
2918
|
+
for (const callback of this.afterMakingCallbacks) {
|
|
2919
|
+
callback(made);
|
|
2920
|
+
}
|
|
2921
|
+
return made;
|
|
2922
|
+
}
|
|
2923
|
+
async createOne(overrides = {}) {
|
|
2924
|
+
const created = await this.persist(this.insertable(this.makeOne(overrides)));
|
|
2925
|
+
for (const child of this.children) {
|
|
2926
|
+
await child.factory.for(created, child.foreignKey).create();
|
|
2927
|
+
}
|
|
2928
|
+
for (const callback of this.afterCreatingCallbacks) {
|
|
2929
|
+
await callback(created);
|
|
2930
|
+
}
|
|
2931
|
+
return created;
|
|
2932
|
+
}
|
|
2933
|
+
insertable(record) {
|
|
2934
|
+
const values = { ...record };
|
|
2935
|
+
if (values.id === 0 || values.id === undefined || values.id === null) {
|
|
2936
|
+
delete values.id;
|
|
2937
|
+
}
|
|
2938
|
+
return values;
|
|
2939
|
+
}
|
|
2940
|
+
persist(_values) {
|
|
2941
|
+
throw new Error("Factory.persist() must be implemented to use create().");
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
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
|
+
}
|
|
2752
2963
|
// ../../src/core/database/migrations/advisoryLock.ts
|
|
2753
2964
|
var MIGRATION_LOCK_KEY = 42424242;
|
|
2754
2965
|
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
@@ -2861,10 +3072,479 @@ async function freshDatabase(db, migrations, options = {}) {
|
|
|
2861
3072
|
}
|
|
2862
3073
|
await runFresh();
|
|
2863
3074
|
}
|
|
3075
|
+
// ../../src/core/database/relationQuery.ts
|
|
3076
|
+
function asWhere(where) {
|
|
3077
|
+
return where;
|
|
3078
|
+
}
|
|
3079
|
+
function ownerId(owner, ownerKey) {
|
|
3080
|
+
if (typeof owner.get === "function") {
|
|
3081
|
+
return owner.get(ownerKey);
|
|
3082
|
+
}
|
|
3083
|
+
if (owner && typeof owner === "object" && "id" in owner && owner.id !== undefined) {
|
|
3084
|
+
return owner.id;
|
|
3085
|
+
}
|
|
3086
|
+
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
class HasManyRelationQuery {
|
|
3090
|
+
parent;
|
|
3091
|
+
related;
|
|
3092
|
+
relation;
|
|
3093
|
+
kind = "hasMany";
|
|
3094
|
+
extraWhere = {};
|
|
3095
|
+
extraOptions = {};
|
|
3096
|
+
constructor(parent, related, relation) {
|
|
3097
|
+
this.parent = parent;
|
|
3098
|
+
this.related = related;
|
|
3099
|
+
this.relation = relation;
|
|
3100
|
+
}
|
|
3101
|
+
where(where) {
|
|
3102
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3103
|
+
return this;
|
|
3104
|
+
}
|
|
3105
|
+
orderBy(orderBy) {
|
|
3106
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3107
|
+
return this;
|
|
3108
|
+
}
|
|
3109
|
+
limit(limit) {
|
|
3110
|
+
this.extraOptions = { ...this.extraOptions, limit };
|
|
3111
|
+
return this;
|
|
3112
|
+
}
|
|
3113
|
+
applyEagerLoad(query, alias) {
|
|
3114
|
+
query.withHasMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3115
|
+
}
|
|
3116
|
+
hydrateEager(row, alias) {
|
|
3117
|
+
const value = row[alias] ?? [];
|
|
3118
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3119
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3120
|
+
}
|
|
3121
|
+
toExistsClause(parentTable) {
|
|
3122
|
+
const childTable = this.related.repository().getTable().name;
|
|
3123
|
+
const extra = buildAdvancedWhereClause(childTable, this.extraWhere, [], []);
|
|
3124
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3125
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.foreignKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3126
|
+
return { sql, params: extra.params };
|
|
3127
|
+
}
|
|
3128
|
+
scopedQuery() {
|
|
3129
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3130
|
+
let query = repository.query(asWhere({
|
|
3131
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey),
|
|
3132
|
+
...this.extraWhere
|
|
3133
|
+
}));
|
|
3134
|
+
if (this.extraOptions.orderBy) {
|
|
3135
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
3136
|
+
}
|
|
3137
|
+
if (this.extraOptions.limit !== undefined) {
|
|
3138
|
+
query = query.limit(this.extraOptions.limit);
|
|
3139
|
+
}
|
|
3140
|
+
return query;
|
|
3141
|
+
}
|
|
3142
|
+
async get() {
|
|
3143
|
+
const rows = await this.scopedQuery().get();
|
|
3144
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3145
|
+
}
|
|
3146
|
+
async first() {
|
|
3147
|
+
const rows = await this.limit(1).get();
|
|
3148
|
+
return rows[0] ?? null;
|
|
3149
|
+
}
|
|
3150
|
+
async count() {
|
|
3151
|
+
return (await this.get()).length;
|
|
3152
|
+
}
|
|
3153
|
+
async create(attributes = {}) {
|
|
3154
|
+
return this.related.create(attributes, {
|
|
3155
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
async createMany(records) {
|
|
3159
|
+
const created = [];
|
|
3160
|
+
for (const attributes of records) {
|
|
3161
|
+
created.push(await this.create(attributes));
|
|
3162
|
+
}
|
|
3163
|
+
return created;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
class HasOneRelationQuery {
|
|
3168
|
+
relation;
|
|
3169
|
+
kind = "hasOne";
|
|
3170
|
+
inner;
|
|
3171
|
+
constructor(parent, related, relation) {
|
|
3172
|
+
this.relation = relation;
|
|
3173
|
+
this.inner = new HasManyRelationQuery(parent, related, {
|
|
3174
|
+
type: "hasMany",
|
|
3175
|
+
name: relation.name,
|
|
3176
|
+
localKey: relation.localKey,
|
|
3177
|
+
foreignKey: relation.foreignKey
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
where(where) {
|
|
3181
|
+
this.inner.where(where);
|
|
3182
|
+
return this;
|
|
3183
|
+
}
|
|
3184
|
+
orderBy(orderBy) {
|
|
3185
|
+
this.inner.orderBy(orderBy);
|
|
3186
|
+
return this;
|
|
3187
|
+
}
|
|
3188
|
+
applyEagerLoad(query, alias) {
|
|
3189
|
+
this.inner.limit(1).applyEagerLoad(query, alias);
|
|
3190
|
+
}
|
|
3191
|
+
hydrateEager(row, alias) {
|
|
3192
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
3193
|
+
return hydrated[0];
|
|
3194
|
+
}
|
|
3195
|
+
toExistsClause(parentTable) {
|
|
3196
|
+
return this.inner.toExistsClause(parentTable);
|
|
3197
|
+
}
|
|
3198
|
+
async get() {
|
|
3199
|
+
return this.inner.limit(1).first();
|
|
3200
|
+
}
|
|
3201
|
+
async first() {
|
|
3202
|
+
return this.get();
|
|
3203
|
+
}
|
|
3204
|
+
async count() {
|
|
3205
|
+
return await this.get() ? 1 : 0;
|
|
3206
|
+
}
|
|
3207
|
+
async create(attributes = {}) {
|
|
3208
|
+
return this.inner.create(attributes);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
class BelongsToRelationQuery {
|
|
3213
|
+
parent;
|
|
3214
|
+
related;
|
|
3215
|
+
relation;
|
|
3216
|
+
kind = "belongsTo";
|
|
3217
|
+
extraOptions = {};
|
|
3218
|
+
constructor(parent, related, relation) {
|
|
3219
|
+
this.parent = parent;
|
|
3220
|
+
this.related = related;
|
|
3221
|
+
this.relation = relation;
|
|
3222
|
+
}
|
|
3223
|
+
orderBy(orderBy) {
|
|
3224
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3225
|
+
return this;
|
|
3226
|
+
}
|
|
3227
|
+
applyEagerLoad(query, alias) {
|
|
3228
|
+
query.withBelongsTo(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3229
|
+
}
|
|
3230
|
+
hydrateEager(row, alias) {
|
|
3231
|
+
const value = row[alias];
|
|
3232
|
+
return value ? this.related.newFromRecord(value) : value;
|
|
3233
|
+
}
|
|
3234
|
+
toExistsClause(parentTable) {
|
|
3235
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
3236
|
+
return {
|
|
3237
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable)} WHERE ${qualifyColumn(relatedTable, this.relation.ownerKey)} = ${qualifyColumn(parentTable, this.relation.foreignKey)}`,
|
|
3238
|
+
params: []
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
async get() {
|
|
3242
|
+
const foreign = this.parent.get(this.relation.foreignKey);
|
|
3243
|
+
if (foreign === null || foreign === undefined) {
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3247
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign }));
|
|
3248
|
+
if (this.extraOptions.orderBy) {
|
|
3249
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
3250
|
+
}
|
|
3251
|
+
const row = await query.first();
|
|
3252
|
+
return row ? this.related.newFromRecord(row) : null;
|
|
3253
|
+
}
|
|
3254
|
+
async first() {
|
|
3255
|
+
return this.get();
|
|
3256
|
+
}
|
|
3257
|
+
async associate(owner) {
|
|
3258
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3259
|
+
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
async dissociate() {
|
|
3263
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
3264
|
+
[this.relation.foreignKey]: null
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
class BelongsToManyRelationQuery {
|
|
3270
|
+
parent;
|
|
3271
|
+
related;
|
|
3272
|
+
relation;
|
|
3273
|
+
kind = "belongsToMany";
|
|
3274
|
+
extraWhere = {};
|
|
3275
|
+
extraOptions = {};
|
|
3276
|
+
constructor(parent, related, relation) {
|
|
3277
|
+
this.parent = parent;
|
|
3278
|
+
this.related = related;
|
|
3279
|
+
this.relation = relation;
|
|
3280
|
+
}
|
|
3281
|
+
where(where) {
|
|
3282
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3283
|
+
return this;
|
|
3284
|
+
}
|
|
3285
|
+
orderBy(orderBy) {
|
|
3286
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
3287
|
+
return this;
|
|
3288
|
+
}
|
|
3289
|
+
applyEagerLoad(_query, _alias) {}
|
|
3290
|
+
hydrateEager(row, alias) {
|
|
3291
|
+
const value = row[alias] ?? [];
|
|
3292
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3293
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3294
|
+
}
|
|
3295
|
+
toExistsClause(parentTable) {
|
|
3296
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
3297
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
3298
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3299
|
+
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}` : ""}`;
|
|
3300
|
+
return { sql, params: extra.params };
|
|
3301
|
+
}
|
|
3302
|
+
connection() {
|
|
3303
|
+
return this.parent.getRepository().getConnection();
|
|
3304
|
+
}
|
|
3305
|
+
async get() {
|
|
3306
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3307
|
+
const pivotRows = await this.connection().unsafe(`SELECT * FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3308
|
+
if (pivotRows.length === 0) {
|
|
3309
|
+
return [];
|
|
3310
|
+
}
|
|
3311
|
+
const relatedIds = [
|
|
3312
|
+
...new Set(pivotRows.map((row) => row[this.relation.relatedPivotKey]))
|
|
3313
|
+
];
|
|
3314
|
+
const repository = this.related.repository().withConnection(this.connection());
|
|
3315
|
+
const rows = await repository.findAll({
|
|
3316
|
+
...this.extraOptions,
|
|
3317
|
+
where: asWhere({
|
|
3318
|
+
[this.relation.relatedKey]: relatedIds,
|
|
3319
|
+
...this.extraWhere
|
|
3320
|
+
})
|
|
3321
|
+
});
|
|
3322
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3323
|
+
}
|
|
3324
|
+
async first() {
|
|
3325
|
+
const rows = await this.get();
|
|
3326
|
+
return rows[0] ?? null;
|
|
3327
|
+
}
|
|
3328
|
+
async count() {
|
|
3329
|
+
return (await this.get()).length;
|
|
3330
|
+
}
|
|
3331
|
+
async attach(ids) {
|
|
3332
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3333
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3334
|
+
for (const id of list) {
|
|
3335
|
+
await this.connection().unsafe(`INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}) VALUES ($1, $2)`, [parentId, id]);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
async detach(ids) {
|
|
3339
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
3340
|
+
if (ids === undefined) {
|
|
3341
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
3345
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = ANY($2)`, [parentId, list]);
|
|
3346
|
+
}
|
|
3347
|
+
async sync(ids) {
|
|
3348
|
+
await this.detach();
|
|
3349
|
+
if (ids.length > 0) {
|
|
3350
|
+
await this.attach(ids);
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
async create(attributes = {}) {
|
|
3354
|
+
const related = await this.related.create(attributes);
|
|
3355
|
+
await this.attach(related.id);
|
|
3356
|
+
return related;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
class MorphManyRelationQuery {
|
|
3361
|
+
parent;
|
|
3362
|
+
related;
|
|
3363
|
+
relation;
|
|
3364
|
+
kind = "morphMany";
|
|
3365
|
+
extraWhere = {};
|
|
3366
|
+
extraOptions = {};
|
|
3367
|
+
constructor(parent, related, relation) {
|
|
3368
|
+
this.parent = parent;
|
|
3369
|
+
this.related = related;
|
|
3370
|
+
this.relation = relation;
|
|
3371
|
+
}
|
|
3372
|
+
where(where) {
|
|
3373
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
3374
|
+
return this;
|
|
3375
|
+
}
|
|
3376
|
+
applyEagerLoad(query, alias) {
|
|
3377
|
+
query.withMorphMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
3378
|
+
}
|
|
3379
|
+
hydrateEager(row, alias) {
|
|
3380
|
+
const value = row[alias] ?? [];
|
|
3381
|
+
const rows = Array.isArray(value) ? value : [];
|
|
3382
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
3383
|
+
}
|
|
3384
|
+
toExistsClause(parentTable) {
|
|
3385
|
+
const childTable = this.related.repository().getTable().name;
|
|
3386
|
+
const extra = buildAdvancedWhereClause(childTable, {
|
|
3387
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3388
|
+
...this.extraWhere
|
|
3389
|
+
}, [], []);
|
|
3390
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
3391
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.morphIdKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
3392
|
+
return { sql, params: extra.params };
|
|
3393
|
+
}
|
|
3394
|
+
async get() {
|
|
3395
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
3396
|
+
const rows = await repository.query(asWhere({
|
|
3397
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3398
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
3399
|
+
...this.extraWhere
|
|
3400
|
+
})).get();
|
|
3401
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
3402
|
+
}
|
|
3403
|
+
async first() {
|
|
3404
|
+
const rows = await this.get();
|
|
3405
|
+
return rows[0] ?? null;
|
|
3406
|
+
}
|
|
3407
|
+
async create(attributes = {}) {
|
|
3408
|
+
return this.related.create(attributes, {
|
|
3409
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
3410
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey)
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
class MorphOneRelationQuery {
|
|
3416
|
+
relation;
|
|
3417
|
+
kind = "morphOne";
|
|
3418
|
+
inner;
|
|
3419
|
+
constructor(parent, related, relation) {
|
|
3420
|
+
this.relation = relation;
|
|
3421
|
+
this.inner = new MorphManyRelationQuery(parent, related, {
|
|
3422
|
+
type: "morphMany",
|
|
3423
|
+
name: relation.name,
|
|
3424
|
+
localKey: relation.localKey,
|
|
3425
|
+
morphTypeKey: relation.morphTypeKey,
|
|
3426
|
+
morphIdKey: relation.morphIdKey,
|
|
3427
|
+
morphType: relation.morphType
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
where(where) {
|
|
3431
|
+
this.inner.where(where);
|
|
3432
|
+
return this;
|
|
3433
|
+
}
|
|
3434
|
+
applyEagerLoad(query, alias) {
|
|
3435
|
+
this.inner.applyEagerLoad(query, alias);
|
|
3436
|
+
}
|
|
3437
|
+
hydrateEager(row, alias) {
|
|
3438
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
3439
|
+
return hydrated[0];
|
|
3440
|
+
}
|
|
3441
|
+
toExistsClause(parentTable) {
|
|
3442
|
+
return this.inner.toExistsClause(parentTable);
|
|
3443
|
+
}
|
|
3444
|
+
async get() {
|
|
3445
|
+
return this.inner.first();
|
|
3446
|
+
}
|
|
3447
|
+
async create(attributes = {}) {
|
|
3448
|
+
return this.inner.create(attributes);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
class MorphToRelationQuery {
|
|
3453
|
+
parent;
|
|
3454
|
+
relatedByType;
|
|
3455
|
+
relation;
|
|
3456
|
+
kind = "morphTo";
|
|
3457
|
+
constructor(parent, relatedByType, relation) {
|
|
3458
|
+
this.parent = parent;
|
|
3459
|
+
this.relatedByType = relatedByType;
|
|
3460
|
+
this.relation = relation;
|
|
3461
|
+
}
|
|
3462
|
+
applyEagerLoad(query, alias) {
|
|
3463
|
+
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
3464
|
+
type,
|
|
3465
|
+
model.repository()
|
|
3466
|
+
]));
|
|
3467
|
+
query.withMorphTo(alias, this.relation, repositories);
|
|
3468
|
+
}
|
|
3469
|
+
hydrateEager(row, alias) {
|
|
3470
|
+
return row[alias];
|
|
3471
|
+
}
|
|
3472
|
+
toExistsClause(parentTable) {
|
|
3473
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
3474
|
+
const related = this.relatedByType[type];
|
|
3475
|
+
if (!related) {
|
|
3476
|
+
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
3477
|
+
}
|
|
3478
|
+
const relatedTable = related.repository().getTable();
|
|
3479
|
+
return {
|
|
3480
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}`,
|
|
3481
|
+
params: []
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
async get() {
|
|
3485
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
3486
|
+
const id = this.parent.get(this.relation.morphIdKey);
|
|
3487
|
+
const related = this.relatedByType[type];
|
|
3488
|
+
if (!related || id === null || id === undefined) {
|
|
3489
|
+
return null;
|
|
3490
|
+
}
|
|
3491
|
+
const table = related.repository().getTable();
|
|
3492
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id })).first();
|
|
3493
|
+
return row ? related.newFromRecord(row) : null;
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
|
|
2864
3497
|
// ../../src/core/database/model.ts
|
|
2865
3498
|
var modelRepositories = new WeakMap;
|
|
2866
3499
|
var modelGlobalScopes = new WeakMap;
|
|
3500
|
+
var modelObservers = new WeakMap;
|
|
2867
3501
|
var modelBooted = new WeakSet;
|
|
3502
|
+
async function runObservers(model, hook) {
|
|
3503
|
+
const observers = modelObservers.get(model.constructor) ?? [];
|
|
3504
|
+
for (const observer of observers) {
|
|
3505
|
+
const handler = observer[hook];
|
|
3506
|
+
if (handler && await handler(model) === false) {
|
|
3507
|
+
return false;
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
return true;
|
|
3511
|
+
}
|
|
3512
|
+
function accessorName(key) {
|
|
3513
|
+
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
3514
|
+
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
3515
|
+
}
|
|
3516
|
+
function constrainRelationExists(model, name, constrain, not) {
|
|
3517
|
+
const statics = modelStatics(model);
|
|
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);
|
|
3530
|
+
}
|
|
3531
|
+
async function loadNested(model, path) {
|
|
3532
|
+
const [head, ...rest] = path.split(".");
|
|
3533
|
+
if (!head) {
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
await model.load(head);
|
|
3537
|
+
if (rest.length === 0) {
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
const loaded = model.loaded(head);
|
|
3541
|
+
const children = Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
|
|
3542
|
+
for (const child of children) {
|
|
3543
|
+
if (child && typeof child === "object" && typeof child.load === "function") {
|
|
3544
|
+
await loadNested(child, rest.join("."));
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
2868
3548
|
function resolveModelRepository(model) {
|
|
2869
3549
|
const repository = modelRepositories.get(model);
|
|
2870
3550
|
if (!repository) {
|
|
@@ -2979,12 +3659,22 @@ class Model {
|
|
|
2979
3659
|
static $guarded;
|
|
2980
3660
|
static $casts = {};
|
|
2981
3661
|
static $timestamps = true;
|
|
3662
|
+
static $hidden;
|
|
3663
|
+
static $visible;
|
|
3664
|
+
static $appends;
|
|
2982
3665
|
_exists;
|
|
3666
|
+
loadedRelations = {};
|
|
3667
|
+
hiddenOverrides = [];
|
|
3668
|
+
visibleOverrides = [];
|
|
3669
|
+
appended = [];
|
|
2983
3670
|
constructor(attributes, repository, exists = true) {
|
|
2984
3671
|
this.attributes = attributes;
|
|
2985
3672
|
this.repository = repository;
|
|
2986
3673
|
this._exists = exists;
|
|
2987
3674
|
}
|
|
3675
|
+
getRepository() {
|
|
3676
|
+
return this.repository;
|
|
3677
|
+
}
|
|
2988
3678
|
get $exists() {
|
|
2989
3679
|
return this._exists;
|
|
2990
3680
|
}
|
|
@@ -2997,6 +3687,49 @@ class Model {
|
|
|
2997
3687
|
toObject() {
|
|
2998
3688
|
return { ...this.attributes };
|
|
2999
3689
|
}
|
|
3690
|
+
toArray() {
|
|
3691
|
+
const ModelClass = modelStatics(this.constructor);
|
|
3692
|
+
const hidden = new Set([...ModelClass.$hidden ?? [], ...this.hiddenOverrides]);
|
|
3693
|
+
const visible = this.visibleOverrides.length > 0 ? this.visibleOverrides : ModelClass.$visible;
|
|
3694
|
+
const data = { ...this.attributes };
|
|
3695
|
+
if (visible && visible.length > 0) {
|
|
3696
|
+
for (const key of Object.keys(data)) {
|
|
3697
|
+
if (!visible.includes(key)) {
|
|
3698
|
+
delete data[key];
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
for (const key of hidden) {
|
|
3703
|
+
delete data[key];
|
|
3704
|
+
}
|
|
3705
|
+
for (const key of [...ModelClass.$appends ?? [], ...this.appended]) {
|
|
3706
|
+
const accessor = this[accessorName(key)];
|
|
3707
|
+
if (typeof accessor === "function") {
|
|
3708
|
+
data[key] = accessor.call(this);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
for (const [name, value] of Object.entries(this.loadedRelations)) {
|
|
3712
|
+
if (!hidden.has(name) && (!visible || visible.includes(name) || this.appended.includes(name))) {
|
|
3713
|
+
data[name] = value;
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
return data;
|
|
3717
|
+
}
|
|
3718
|
+
toJSON() {
|
|
3719
|
+
return this.toArray();
|
|
3720
|
+
}
|
|
3721
|
+
makeHidden(...keys) {
|
|
3722
|
+
this.hiddenOverrides.push(...keys);
|
|
3723
|
+
return this;
|
|
3724
|
+
}
|
|
3725
|
+
makeVisible(...keys) {
|
|
3726
|
+
this.visibleOverrides.push(...keys);
|
|
3727
|
+
return this;
|
|
3728
|
+
}
|
|
3729
|
+
append(...keys) {
|
|
3730
|
+
this.appended.push(...keys);
|
|
3731
|
+
return this;
|
|
3732
|
+
}
|
|
3000
3733
|
primaryKey() {
|
|
3001
3734
|
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
3002
3735
|
}
|
|
@@ -3017,6 +3750,10 @@ class Model {
|
|
|
3017
3750
|
return new statics(hydrated, repository, exists);
|
|
3018
3751
|
}
|
|
3019
3752
|
static boot() {}
|
|
3753
|
+
static observe(observer) {
|
|
3754
|
+
const existing = modelObservers.get(this) ?? [];
|
|
3755
|
+
modelObservers.set(this, [...existing, observer]);
|
|
3756
|
+
}
|
|
3020
3757
|
static addGlobalScope(_name, scope) {
|
|
3021
3758
|
ensureBooted(this);
|
|
3022
3759
|
const existing = modelGlobalScopes.get(this) ?? [];
|
|
@@ -3037,17 +3774,89 @@ class Model {
|
|
|
3037
3774
|
}
|
|
3038
3775
|
return query;
|
|
3039
3776
|
}
|
|
3040
|
-
static
|
|
3777
|
+
static newFromRecord(record, exists = true) {
|
|
3778
|
+
const repository = resolveModelRepository(this);
|
|
3779
|
+
return modelStatics(this).fromRecord(record, repository, exists);
|
|
3780
|
+
}
|
|
3781
|
+
static async create(attributes, forced = {}) {
|
|
3041
3782
|
const statics = modelStatics(this);
|
|
3042
3783
|
ensureBooted(this);
|
|
3043
3784
|
const repository = resolveModelRepository(this);
|
|
3044
3785
|
const table = repository.getTable();
|
|
3045
3786
|
const timestamps = statics.$timestamps ?? true;
|
|
3046
|
-
const assignable =
|
|
3787
|
+
const assignable = {
|
|
3788
|
+
...filterMassAssignable(statics.$fillable, statics.$guarded, attributes),
|
|
3789
|
+
...forced
|
|
3790
|
+
};
|
|
3047
3791
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
3048
3792
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
3793
|
+
const pending = statics.newFromRecord({ ...payload }, false);
|
|
3794
|
+
if (await runObservers(pending, "creating") === false) {
|
|
3795
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
3796
|
+
}
|
|
3049
3797
|
const record = await repository.create(payload);
|
|
3050
|
-
|
|
3798
|
+
const created = statics.fromRecord(record, repository, true);
|
|
3799
|
+
await runObservers(created, "created");
|
|
3800
|
+
return created;
|
|
3801
|
+
}
|
|
3802
|
+
static with(...relations) {
|
|
3803
|
+
const statics = modelStatics(this);
|
|
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
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
static whereHas(name, constrain) {
|
|
3850
|
+
return constrainRelationExists(this, name, constrain, false);
|
|
3851
|
+
}
|
|
3852
|
+
static has(name) {
|
|
3853
|
+
return constrainRelationExists(this, name, undefined, false);
|
|
3854
|
+
}
|
|
3855
|
+
static doesntHave(name) {
|
|
3856
|
+
return constrainRelationExists(this, name, undefined, true);
|
|
3857
|
+
}
|
|
3858
|
+
static whereDoesntHave(name, constrain) {
|
|
3859
|
+
return constrainRelationExists(this, name, constrain, true);
|
|
3051
3860
|
}
|
|
3052
3861
|
static async find(id) {
|
|
3053
3862
|
const statics = modelStatics(this);
|
|
@@ -3076,6 +3885,9 @@ class Model {
|
|
|
3076
3885
|
const rows = await query.get();
|
|
3077
3886
|
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
3078
3887
|
}
|
|
3888
|
+
static where(where) {
|
|
3889
|
+
return Model.query.call(this).where(where);
|
|
3890
|
+
}
|
|
3079
3891
|
static async firstWhere(where, options = {}) {
|
|
3080
3892
|
const statics = modelStatics(this);
|
|
3081
3893
|
const repository = resolveModelRepository(this);
|
|
@@ -3086,15 +3898,41 @@ class Model {
|
|
|
3086
3898
|
const record = await query.first();
|
|
3087
3899
|
return record ? statics.fromRecord(record, repository, true) : null;
|
|
3088
3900
|
}
|
|
3901
|
+
static async firstOrNew(where, values = {}) {
|
|
3902
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
3903
|
+
if (existing) {
|
|
3904
|
+
return existing;
|
|
3905
|
+
}
|
|
3906
|
+
return modelStatics(this).newFromRecord({ ...where, ...values }, false);
|
|
3907
|
+
}
|
|
3908
|
+
static async firstOrCreate(where, values = {}) {
|
|
3909
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
3910
|
+
if (existing) {
|
|
3911
|
+
return existing;
|
|
3912
|
+
}
|
|
3913
|
+
return Model.create.call(this, { ...where, ...values });
|
|
3914
|
+
}
|
|
3915
|
+
static async updateOrCreate(where, values = {}) {
|
|
3916
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
3917
|
+
if (existing) {
|
|
3918
|
+
return existing.update(values);
|
|
3919
|
+
}
|
|
3920
|
+
return Model.create.call(this, { ...where, ...values });
|
|
3921
|
+
}
|
|
3089
3922
|
async save() {
|
|
3090
3923
|
const ModelClass = modelStatics(this.constructor);
|
|
3091
3924
|
const timestamps = ModelClass.$timestamps ?? true;
|
|
3092
3925
|
const casts = ModelClass.$casts ?? {};
|
|
3093
3926
|
const table = this.repository.getTable();
|
|
3094
|
-
|
|
3927
|
+
const updating = this.$exists;
|
|
3928
|
+
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
3929
|
+
return this;
|
|
3930
|
+
}
|
|
3931
|
+
if (updating) {
|
|
3095
3932
|
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
3096
3933
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
3097
3934
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
3935
|
+
await runObservers(this, "updated");
|
|
3098
3936
|
return this;
|
|
3099
3937
|
}
|
|
3100
3938
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -3103,6 +3941,7 @@ class Model {
|
|
|
3103
3941
|
const record = await this.repository.create(payload);
|
|
3104
3942
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
3105
3943
|
this._exists = true;
|
|
3944
|
+
await runObservers(this, "created");
|
|
3106
3945
|
return this;
|
|
3107
3946
|
}
|
|
3108
3947
|
async update(changes) {
|
|
@@ -3112,10 +3951,14 @@ class Model {
|
|
|
3112
3951
|
return await this.save();
|
|
3113
3952
|
}
|
|
3114
3953
|
async delete() {
|
|
3115
|
-
if (
|
|
3116
|
-
return
|
|
3954
|
+
if (await runObservers(this, "deleting") === false) {
|
|
3955
|
+
return false;
|
|
3117
3956
|
}
|
|
3118
|
-
|
|
3957
|
+
const deleted = resolveSoftDeleteColumn(this.repository.getTable()) ? await this.repository.deleteById(this.id) : await this.repository.forceDeleteById(this.id);
|
|
3958
|
+
if (deleted) {
|
|
3959
|
+
await runObservers(this, "deleted");
|
|
3960
|
+
}
|
|
3961
|
+
return deleted;
|
|
3119
3962
|
}
|
|
3120
3963
|
async forceDelete() {
|
|
3121
3964
|
return await this.repository.forceDeleteById(this.id);
|
|
@@ -3164,6 +4007,91 @@ class Model {
|
|
|
3164
4007
|
const loaded = grouped.get(parentId) ?? [];
|
|
3165
4008
|
return Object.assign(this, { [as]: loaded });
|
|
3166
4009
|
}
|
|
4010
|
+
hasMany(related, foreignKey, localKey) {
|
|
4011
|
+
const table = this.repository.getTable();
|
|
4012
|
+
return new HasManyRelationQuery(this, related, hasMany({
|
|
4013
|
+
name: related.repository().getTable().name,
|
|
4014
|
+
localKey: localKey ?? table.primaryKey,
|
|
4015
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4016
|
+
}));
|
|
4017
|
+
}
|
|
4018
|
+
hasOne(related, foreignKey, localKey) {
|
|
4019
|
+
const table = this.repository.getTable();
|
|
4020
|
+
return new HasOneRelationQuery(this, related, hasOne({
|
|
4021
|
+
name: related.repository().getTable().name,
|
|
4022
|
+
localKey: localKey ?? table.primaryKey,
|
|
4023
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
4024
|
+
}));
|
|
4025
|
+
}
|
|
4026
|
+
belongsTo(related, foreignKey, ownerKey) {
|
|
4027
|
+
const relatedTable = related.repository().getTable();
|
|
4028
|
+
return new BelongsToRelationQuery(this, related, belongsTo({
|
|
4029
|
+
name: relatedTable.name,
|
|
4030
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
4031
|
+
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
4032
|
+
}));
|
|
4033
|
+
}
|
|
4034
|
+
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
4035
|
+
const table = this.repository.getTable();
|
|
4036
|
+
const relatedTable = related.repository().getTable();
|
|
4037
|
+
return new BelongsToManyRelationQuery(this, related, belongsToMany({
|
|
4038
|
+
name: relatedTable.name,
|
|
4039
|
+
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
4040
|
+
parentKey: table.primaryKey,
|
|
4041
|
+
relatedKey: relatedTable.primaryKey,
|
|
4042
|
+
foreignPivotKey: foreignPivotKey ?? foreignKeyFromTable(table.name),
|
|
4043
|
+
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
4044
|
+
}));
|
|
4045
|
+
}
|
|
4046
|
+
morphMany(related, morphName, typeKey, idKey) {
|
|
4047
|
+
const table = this.repository.getTable();
|
|
4048
|
+
return new MorphManyRelationQuery(this, related, morphMany({
|
|
4049
|
+
name: morphName,
|
|
4050
|
+
localKey: table.primaryKey,
|
|
4051
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4052
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4053
|
+
morphType: table.name
|
|
4054
|
+
}));
|
|
4055
|
+
}
|
|
4056
|
+
morphOne(related, morphName, typeKey, idKey) {
|
|
4057
|
+
const table = this.repository.getTable();
|
|
4058
|
+
return new MorphOneRelationQuery(this, related, morphOne({
|
|
4059
|
+
name: morphName,
|
|
4060
|
+
localKey: table.primaryKey,
|
|
4061
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4062
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
4063
|
+
morphType: table.name
|
|
4064
|
+
}));
|
|
4065
|
+
}
|
|
4066
|
+
morphTo(relatedByType, morphName = "imageable", typeKey, idKey) {
|
|
4067
|
+
return new MorphToRelationQuery(this, relatedByType, morphTo({
|
|
4068
|
+
name: morphName,
|
|
4069
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
4070
|
+
morphIdKey: idKey ?? `${morphName}_id`
|
|
4071
|
+
}));
|
|
4072
|
+
}
|
|
4073
|
+
async load(...names) {
|
|
4074
|
+
for (const name of names) {
|
|
4075
|
+
if (name.includes(".")) {
|
|
4076
|
+
await loadNested(this, name);
|
|
4077
|
+
continue;
|
|
4078
|
+
}
|
|
4079
|
+
const method = this[name];
|
|
4080
|
+
if (typeof method !== "function") {
|
|
4081
|
+
throw new Error(`${this.constructor.name} has no relation method ${name}().`);
|
|
4082
|
+
}
|
|
4083
|
+
const relationQuery = method.call(this);
|
|
4084
|
+
this.loadedRelations[name] = await relationQuery.get();
|
|
4085
|
+
}
|
|
4086
|
+
return this;
|
|
4087
|
+
}
|
|
4088
|
+
loaded(name) {
|
|
4089
|
+
return this.loadedRelations[name];
|
|
4090
|
+
}
|
|
4091
|
+
setLoaded(name, value) {
|
|
4092
|
+
this.loadedRelations[name] = value;
|
|
4093
|
+
return this;
|
|
4094
|
+
}
|
|
3167
4095
|
mergeAttributes(patch) {
|
|
3168
4096
|
Object.assign(this.attributes, patch);
|
|
3169
4097
|
return this;
|
|
@@ -5062,6 +5990,64 @@ function createRequireAuthMiddleware(auth2) {
|
|
|
5062
5990
|
function serializeDate(value) {
|
|
5063
5991
|
return value instanceof Date ? value.toISOString() : value;
|
|
5064
5992
|
}
|
|
5993
|
+
function whenLoaded(model, relation, transform) {
|
|
5994
|
+
const value = model.loaded(relation);
|
|
5995
|
+
if (value === undefined) {
|
|
5996
|
+
return;
|
|
5997
|
+
}
|
|
5998
|
+
return transform ? transform(value) : value;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
class JsonResource {
|
|
6002
|
+
resource;
|
|
6003
|
+
static wrap = "data";
|
|
6004
|
+
extra = {};
|
|
6005
|
+
constructor(resource) {
|
|
6006
|
+
this.resource = resource;
|
|
6007
|
+
}
|
|
6008
|
+
static make(resource) {
|
|
6009
|
+
return new JsonResource(resource);
|
|
6010
|
+
}
|
|
6011
|
+
static collection(items) {
|
|
6012
|
+
return new ResourceCollection(items);
|
|
6013
|
+
}
|
|
6014
|
+
additional(data) {
|
|
6015
|
+
this.extra = { ...this.extra, ...data };
|
|
6016
|
+
return this;
|
|
6017
|
+
}
|
|
6018
|
+
when(condition, value) {
|
|
6019
|
+
return condition ? value : undefined;
|
|
6020
|
+
}
|
|
6021
|
+
whenLoaded(relation, transform) {
|
|
6022
|
+
const model = this.resource;
|
|
6023
|
+
if (typeof model.loaded !== "function") {
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
return whenLoaded(model, relation, transform);
|
|
6027
|
+
}
|
|
6028
|
+
toArray() {
|
|
6029
|
+
if (this.resource && typeof this.resource === "object" && "toArray" in this.resource) {
|
|
6030
|
+
return this.resource.toArray();
|
|
6031
|
+
}
|
|
6032
|
+
return { ...this.resource };
|
|
6033
|
+
}
|
|
6034
|
+
toResponse() {
|
|
6035
|
+
const wrap = this.constructor.wrap;
|
|
6036
|
+
const payload = this.toArray();
|
|
6037
|
+
if (wrap === null) {
|
|
6038
|
+
return { ...payload, ...this.extra };
|
|
6039
|
+
}
|
|
6040
|
+
return { [wrap]: payload, ...this.extra };
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
|
|
6044
|
+
class ResourceCollection extends JsonResource {
|
|
6045
|
+
toArray() {
|
|
6046
|
+
return {
|
|
6047
|
+
data: this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item })
|
|
6048
|
+
};
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
5065
6051
|
function toResourceCollection(items, transformer) {
|
|
5066
6052
|
return items.map(transformer);
|
|
5067
6053
|
}
|
|
@@ -5298,11 +6284,11 @@ async function webErrorResponse(error, request) {
|
|
|
5298
6284
|
if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
|
|
5299
6285
|
return null;
|
|
5300
6286
|
}
|
|
5301
|
-
const mappedError = error
|
|
5302
|
-
if (mappedError
|
|
6287
|
+
const mappedError = toHttpError(error) ?? mapDatabaseError(error);
|
|
6288
|
+
if (mappedError.status === 401) {
|
|
5303
6289
|
return Response.redirect(loginRedirectLocation(request), 302);
|
|
5304
6290
|
}
|
|
5305
|
-
const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
6291
|
+
const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
5306
6292
|
return htmlErrorResponse({
|
|
5307
6293
|
status: mappedError.status,
|
|
5308
6294
|
title: errorPageTitle(mappedError.status, mappedError.message),
|
|
@@ -5326,7 +6312,7 @@ function noContentResponse() {
|
|
|
5326
6312
|
return new Response(null, { status: 204 });
|
|
5327
6313
|
}
|
|
5328
6314
|
function errorResponse(error) {
|
|
5329
|
-
const mappedError = error
|
|
6315
|
+
const mappedError = toHttpError(error) ?? mapDatabaseError(error);
|
|
5330
6316
|
return Response.json({
|
|
5331
6317
|
error: mappedError.message,
|
|
5332
6318
|
...mappedError.details === undefined ? {} : { details: mappedError.details }
|
|
@@ -6699,8 +7685,9 @@ function createTenantMiddleware() {
|
|
|
6699
7685
|
});
|
|
6700
7686
|
});
|
|
6701
7687
|
} catch (error) {
|
|
6702
|
-
|
|
6703
|
-
|
|
7688
|
+
const httpError = toHttpError(error);
|
|
7689
|
+
if (httpError) {
|
|
7690
|
+
return Response.json({ error: httpError.message }, { status: httpError.status });
|
|
6704
7691
|
}
|
|
6705
7692
|
throw error;
|
|
6706
7693
|
}
|
|
@@ -7143,6 +8130,8 @@ export {
|
|
|
7143
8130
|
AuthManager,
|
|
7144
8131
|
BadRequestError,
|
|
7145
8132
|
baseRepository_default as BaseRepository,
|
|
8133
|
+
BelongsToManyRelationQuery,
|
|
8134
|
+
BelongsToRelationQuery,
|
|
7146
8135
|
Blueprint,
|
|
7147
8136
|
CACHE_TAGS,
|
|
7148
8137
|
repository_default as CacheRepository,
|
|
@@ -7155,19 +8144,26 @@ export {
|
|
|
7155
8144
|
DatabaseTokenGuard,
|
|
7156
8145
|
EtaViewEngine,
|
|
7157
8146
|
EventBus,
|
|
8147
|
+
Factory,
|
|
7158
8148
|
failedJobRepository_default as FailedJobRepository,
|
|
7159
8149
|
failedJobService_default as FailedJobService,
|
|
7160
8150
|
ForbiddenError,
|
|
7161
8151
|
ForeignIdColumnDefinition,
|
|
7162
8152
|
FormRequest,
|
|
7163
8153
|
GuestGuard,
|
|
8154
|
+
HasManyRelationQuery,
|
|
8155
|
+
HasOneRelationQuery,
|
|
7164
8156
|
HttpError,
|
|
7165
8157
|
Job,
|
|
8158
|
+
JsonResource,
|
|
7166
8159
|
LocalStorageDriver,
|
|
7167
8160
|
LogMailDriver,
|
|
7168
8161
|
Mailer,
|
|
7169
8162
|
membershipService_default as MembershipService,
|
|
7170
8163
|
Model,
|
|
8164
|
+
MorphManyRelationQuery,
|
|
8165
|
+
MorphOneRelationQuery,
|
|
8166
|
+
MorphToRelationQuery,
|
|
7171
8167
|
MySqlGrammar,
|
|
7172
8168
|
NotFoundError,
|
|
7173
8169
|
Notification,
|
|
@@ -7182,6 +8178,7 @@ export {
|
|
|
7182
8178
|
RedisQueue,
|
|
7183
8179
|
RepositoryQuery,
|
|
7184
8180
|
ResilientQueue,
|
|
8181
|
+
ResourceCollection,
|
|
7185
8182
|
Schedule,
|
|
7186
8183
|
Schema,
|
|
7187
8184
|
ServiceContainer,
|
|
@@ -7279,6 +8276,7 @@ export {
|
|
|
7279
8276
|
eventBus,
|
|
7280
8277
|
events,
|
|
7281
8278
|
filterMassAssignable,
|
|
8279
|
+
foreignKeyFromTable,
|
|
7282
8280
|
formatAdminValue,
|
|
7283
8281
|
freshDatabase,
|
|
7284
8282
|
generateCspNonce,
|
|
@@ -7309,6 +8307,7 @@ export {
|
|
|
7309
8307
|
isEtagEnabled,
|
|
7310
8308
|
isGlobalAdmin,
|
|
7311
8309
|
isHtmxRequest,
|
|
8310
|
+
isHttpErrorLike,
|
|
7312
8311
|
isInsideTenantDatabaseScope,
|
|
7313
8312
|
isPublicReadsEnabled,
|
|
7314
8313
|
isTenancyEnabled,
|
|
@@ -7336,6 +8335,7 @@ export {
|
|
|
7336
8335
|
parseMultipartUpload,
|
|
7337
8336
|
parsePaginationQuery,
|
|
7338
8337
|
parsePositiveIntParam,
|
|
8338
|
+
pivotTableName,
|
|
7339
8339
|
policyGate,
|
|
7340
8340
|
prometheusRegistry,
|
|
7341
8341
|
queue,
|
|
@@ -7371,6 +8371,7 @@ export {
|
|
|
7371
8371
|
resolveCsrfTokenForRequest,
|
|
7372
8372
|
resolveDatabaseDriver,
|
|
7373
8373
|
resolveHtmlContentSecurityPolicy,
|
|
8374
|
+
resolveMembershipLookup,
|
|
7374
8375
|
resolveMembershipService,
|
|
7375
8376
|
resolveOrganizationScope,
|
|
7376
8377
|
resolveRepositoryConnection,
|
|
@@ -7387,6 +8388,7 @@ export {
|
|
|
7387
8388
|
runSeedersFromDirectory,
|
|
7388
8389
|
runWithAuthUser,
|
|
7389
8390
|
runWithDatabaseConnection,
|
|
8391
|
+
runWithMembershipContext,
|
|
7390
8392
|
runWithRequestMeta,
|
|
7391
8393
|
runWithTenant,
|
|
7392
8394
|
runWithTenantDatabase,
|
|
@@ -7402,6 +8404,7 @@ export {
|
|
|
7402
8404
|
serverHtmxContentSecurityPolicy,
|
|
7403
8405
|
setActiveApplicationContext,
|
|
7404
8406
|
signedUrl,
|
|
8407
|
+
singularize,
|
|
7405
8408
|
spaContentSecurityPolicy,
|
|
7406
8409
|
storageFacade as storage,
|
|
7407
8410
|
strictApiContentSecurityPolicy,
|
|
@@ -7409,11 +8412,13 @@ export {
|
|
|
7409
8412
|
stripMarkdown,
|
|
7410
8413
|
temporarySignedUrl,
|
|
7411
8414
|
textResponse,
|
|
8415
|
+
toHttpError,
|
|
7412
8416
|
toPaginatedResourceCollection,
|
|
7413
8417
|
toResourceCollection,
|
|
7414
8418
|
trustForwardedFor,
|
|
7415
8419
|
validateObject,
|
|
7416
8420
|
verifyCsrfToken,
|
|
8421
|
+
whenLoaded,
|
|
7417
8422
|
withErrorHandling,
|
|
7418
8423
|
withMiddleware,
|
|
7419
8424
|
withMigrationLock,
|