@getstrata/core 0.5.97 → 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/dist/index.js CHANGED
@@ -1497,6 +1497,12 @@ class ServiceContainer {
1497
1497
  resolve(key) {
1498
1498
  return this.get(key);
1499
1499
  }
1500
+ make(key) {
1501
+ return this.resolve(key);
1502
+ }
1503
+ instance(key, value) {
1504
+ return this.set(key, value);
1505
+ }
1500
1506
  has(key) {
1501
1507
  return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
1502
1508
  }
@@ -1525,6 +1531,9 @@ class ConfigStore {
1525
1531
  class EventBus {
1526
1532
  constructor() {}
1527
1533
  listeners = new Map;
1534
+ on(event, listener) {
1535
+ return this.listen(event, listener);
1536
+ }
1528
1537
  listen(event, listener) {
1529
1538
  const handlers = this.listeners.get(event) ?? new Set;
1530
1539
  handlers.add(listener);
@@ -1536,6 +1545,9 @@ class EventBus {
1536
1545
  }
1537
1546
  };
1538
1547
  }
1548
+ async emit(event, payload) {
1549
+ await this.dispatch(event, payload);
1550
+ }
1539
1551
  async dispatch(event, payload) {
1540
1552
  const handlers = this.listeners.get(event);
1541
1553
  if (!handlers || handlers.size === 0) {
@@ -1737,7 +1749,16 @@ function appendWhereParts(tableName, where, params) {
1737
1749
  }
1738
1750
  return clauses.join(" AND ");
1739
1751
  }
1752
+ function remapExistsSql(sql, existsParams, params) {
1753
+ const offset = params.length;
1754
+ params.push(...existsParams);
1755
+ return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
1756
+ }
1740
1757
  function buildWhereNodeClause(tableName, node, params) {
1758
+ if ("exists" in node) {
1759
+ const body = remapExistsSql(node.exists.sql, node.exists.params, params);
1760
+ return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
1761
+ }
1741
1762
  if ("where" in node) {
1742
1763
  return appendWhereParts(tableName, node.where, params);
1743
1764
  }
@@ -2255,6 +2276,23 @@ class RepositoryQuery {
2255
2276
  this.queryOptions = { ...this.queryOptions, limit };
2256
2277
  return this;
2257
2278
  }
2279
+ whereNull(column) {
2280
+ return this.where({ [column]: null });
2281
+ }
2282
+ whereNotNull(column) {
2283
+ return this.where({ [column]: { isNull: false } });
2284
+ }
2285
+ whereIn(column, values) {
2286
+ return this.where({ [column]: values });
2287
+ }
2288
+ whereExists(sql, params = []) {
2289
+ this.whereNodes.push({ kind: "and", exists: { sql, params } });
2290
+ return this;
2291
+ }
2292
+ whereNotExists(sql, params = []) {
2293
+ this.whereNodes.push({ kind: "and", exists: { sql, params, not: true } });
2294
+ return this;
2295
+ }
2258
2296
  offset(offset) {
2259
2297
  this.queryOptions = { ...this.queryOptions, offset };
2260
2298
  return this;
@@ -2766,6 +2804,162 @@ function createDatabaseConnection(source) {
2766
2804
  }
2767
2805
  };
2768
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
+ }
2769
2963
  // ../../src/core/database/migrations/advisoryLock.ts
2770
2964
  var MIGRATION_LOCK_KEY = 42424242;
2771
2965
  async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
@@ -2878,10 +3072,479 @@ async function freshDatabase(db, migrations, options = {}) {
2878
3072
  }
2879
3073
  await runFresh();
2880
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
+
2881
3497
  // ../../src/core/database/model.ts
2882
3498
  var modelRepositories = new WeakMap;
2883
3499
  var modelGlobalScopes = new WeakMap;
3500
+ var modelObservers = new WeakMap;
2884
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
+ }
2885
3548
  function resolveModelRepository(model) {
2886
3549
  const repository = modelRepositories.get(model);
2887
3550
  if (!repository) {
@@ -2996,12 +3659,22 @@ class Model {
2996
3659
  static $guarded;
2997
3660
  static $casts = {};
2998
3661
  static $timestamps = true;
3662
+ static $hidden;
3663
+ static $visible;
3664
+ static $appends;
2999
3665
  _exists;
3666
+ loadedRelations = {};
3667
+ hiddenOverrides = [];
3668
+ visibleOverrides = [];
3669
+ appended = [];
3000
3670
  constructor(attributes, repository, exists = true) {
3001
3671
  this.attributes = attributes;
3002
3672
  this.repository = repository;
3003
3673
  this._exists = exists;
3004
3674
  }
3675
+ getRepository() {
3676
+ return this.repository;
3677
+ }
3005
3678
  get $exists() {
3006
3679
  return this._exists;
3007
3680
  }
@@ -3014,6 +3687,49 @@ class Model {
3014
3687
  toObject() {
3015
3688
  return { ...this.attributes };
3016
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
+ }
3017
3733
  primaryKey() {
3018
3734
  throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
3019
3735
  }
@@ -3034,6 +3750,10 @@ class Model {
3034
3750
  return new statics(hydrated, repository, exists);
3035
3751
  }
3036
3752
  static boot() {}
3753
+ static observe(observer) {
3754
+ const existing = modelObservers.get(this) ?? [];
3755
+ modelObservers.set(this, [...existing, observer]);
3756
+ }
3037
3757
  static addGlobalScope(_name, scope) {
3038
3758
  ensureBooted(this);
3039
3759
  const existing = modelGlobalScopes.get(this) ?? [];
@@ -3054,17 +3774,89 @@ class Model {
3054
3774
  }
3055
3775
  return query;
3056
3776
  }
3057
- static async create(attributes) {
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 = {}) {
3058
3782
  const statics = modelStatics(this);
3059
3783
  ensureBooted(this);
3060
3784
  const repository = resolveModelRepository(this);
3061
3785
  const table = repository.getTable();
3062
3786
  const timestamps = statics.$timestamps ?? true;
3063
- const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
3787
+ const assignable = {
3788
+ ...filterMassAssignable(statics.$fillable, statics.$guarded, attributes),
3789
+ ...forced
3790
+ };
3064
3791
  const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
3065
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
+ }
3066
3797
  const record = await repository.create(payload);
3067
- return statics.fromRecord(record, repository, true);
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);
3068
3860
  }
3069
3861
  static async find(id) {
3070
3862
  const statics = modelStatics(this);
@@ -3093,6 +3885,9 @@ class Model {
3093
3885
  const rows = await query.get();
3094
3886
  return rows.map((row) => statics.fromRecord(row, repository, true));
3095
3887
  }
3888
+ static where(where) {
3889
+ return Model.query.call(this).where(where);
3890
+ }
3096
3891
  static async firstWhere(where, options = {}) {
3097
3892
  const statics = modelStatics(this);
3098
3893
  const repository = resolveModelRepository(this);
@@ -3103,15 +3898,41 @@ class Model {
3103
3898
  const record = await query.first();
3104
3899
  return record ? statics.fromRecord(record, repository, true) : null;
3105
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
+ }
3106
3922
  async save() {
3107
3923
  const ModelClass = modelStatics(this.constructor);
3108
3924
  const timestamps = ModelClass.$timestamps ?? true;
3109
3925
  const casts = ModelClass.$casts ?? {};
3110
3926
  const table = this.repository.getTable();
3111
- if (this.$exists) {
3927
+ const updating = this.$exists;
3928
+ if (await runObservers(this, updating ? "updating" : "creating") === false) {
3929
+ return this;
3930
+ }
3931
+ if (updating) {
3112
3932
  const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
3113
3933
  const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
3114
3934
  this.attributes = ModelClass.hydrateAttributes(record2);
3935
+ await runObservers(this, "updated");
3115
3936
  return this;
3116
3937
  }
3117
3938
  const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
@@ -3120,6 +3941,7 @@ class Model {
3120
3941
  const record = await this.repository.create(payload);
3121
3942
  this.attributes = ModelClass.hydrateAttributes(record);
3122
3943
  this._exists = true;
3944
+ await runObservers(this, "created");
3123
3945
  return this;
3124
3946
  }
3125
3947
  async update(changes) {
@@ -3129,10 +3951,14 @@ class Model {
3129
3951
  return await this.save();
3130
3952
  }
3131
3953
  async delete() {
3132
- if (resolveSoftDeleteColumn(this.repository.getTable())) {
3133
- return await this.repository.deleteById(this.id);
3954
+ if (await runObservers(this, "deleting") === false) {
3955
+ return false;
3134
3956
  }
3135
- return await this.repository.forceDeleteById(this.id);
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;
3136
3962
  }
3137
3963
  async forceDelete() {
3138
3964
  return await this.repository.forceDeleteById(this.id);
@@ -3181,6 +4007,91 @@ class Model {
3181
4007
  const loaded = grouped.get(parentId) ?? [];
3182
4008
  return Object.assign(this, { [as]: loaded });
3183
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
+ }
3184
4095
  mergeAttributes(patch) {
3185
4096
  Object.assign(this.attributes, patch);
3186
4097
  return this;
@@ -5079,6 +5990,64 @@ function createRequireAuthMiddleware(auth2) {
5079
5990
  function serializeDate(value) {
5080
5991
  return value instanceof Date ? value.toISOString() : value;
5081
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
+ }
5082
6051
  function toResourceCollection(items, transformer) {
5083
6052
  return items.map(transformer);
5084
6053
  }
@@ -7161,6 +8130,8 @@ export {
7161
8130
  AuthManager,
7162
8131
  BadRequestError,
7163
8132
  baseRepository_default as BaseRepository,
8133
+ BelongsToManyRelationQuery,
8134
+ BelongsToRelationQuery,
7164
8135
  Blueprint,
7165
8136
  CACHE_TAGS,
7166
8137
  repository_default as CacheRepository,
@@ -7173,19 +8144,26 @@ export {
7173
8144
  DatabaseTokenGuard,
7174
8145
  EtaViewEngine,
7175
8146
  EventBus,
8147
+ Factory,
7176
8148
  failedJobRepository_default as FailedJobRepository,
7177
8149
  failedJobService_default as FailedJobService,
7178
8150
  ForbiddenError,
7179
8151
  ForeignIdColumnDefinition,
7180
8152
  FormRequest,
7181
8153
  GuestGuard,
8154
+ HasManyRelationQuery,
8155
+ HasOneRelationQuery,
7182
8156
  HttpError,
7183
8157
  Job,
8158
+ JsonResource,
7184
8159
  LocalStorageDriver,
7185
8160
  LogMailDriver,
7186
8161
  Mailer,
7187
8162
  membershipService_default as MembershipService,
7188
8163
  Model,
8164
+ MorphManyRelationQuery,
8165
+ MorphOneRelationQuery,
8166
+ MorphToRelationQuery,
7189
8167
  MySqlGrammar,
7190
8168
  NotFoundError,
7191
8169
  Notification,
@@ -7200,6 +8178,7 @@ export {
7200
8178
  RedisQueue,
7201
8179
  RepositoryQuery,
7202
8180
  ResilientQueue,
8181
+ ResourceCollection,
7203
8182
  Schedule,
7204
8183
  Schema,
7205
8184
  ServiceContainer,
@@ -7297,6 +8276,7 @@ export {
7297
8276
  eventBus,
7298
8277
  events,
7299
8278
  filterMassAssignable,
8279
+ foreignKeyFromTable,
7300
8280
  formatAdminValue,
7301
8281
  freshDatabase,
7302
8282
  generateCspNonce,
@@ -7355,6 +8335,7 @@ export {
7355
8335
  parseMultipartUpload,
7356
8336
  parsePaginationQuery,
7357
8337
  parsePositiveIntParam,
8338
+ pivotTableName,
7358
8339
  policyGate,
7359
8340
  prometheusRegistry,
7360
8341
  queue,
@@ -7423,6 +8404,7 @@ export {
7423
8404
  serverHtmxContentSecurityPolicy,
7424
8405
  setActiveApplicationContext,
7425
8406
  signedUrl,
8407
+ singularize,
7426
8408
  spaContentSecurityPolicy,
7427
8409
  storageFacade as storage,
7428
8410
  strictApiContentSecurityPolicy,
@@ -7436,6 +8418,7 @@ export {
7436
8418
  trustForwardedFor,
7437
8419
  validateObject,
7438
8420
  verifyCsrfToken,
8421
+ whenLoaded,
7439
8422
  withErrorHandling,
7440
8423
  withMiddleware,
7441
8424
  withMigrationLock,