@getstrata/core 0.5.99 → 0.5.101

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
@@ -2078,32 +2078,80 @@ function belongsTo(definition) {
2078
2078
  ...definition
2079
2079
  };
2080
2080
  }
2081
+ function hasManyThrough(definition) {
2082
+ return {
2083
+ type: "hasManyThrough",
2084
+ throughParentKey: "__through_parent_id",
2085
+ ...definition
2086
+ };
2087
+ }
2081
2088
  function belongsToMany(definition) {
2082
2089
  return {
2083
2090
  type: "belongsToMany",
2084
2091
  ...definition
2085
2092
  };
2086
2093
  }
2094
+ function relationMatchKey(value) {
2095
+ if (value === null || value === undefined) {
2096
+ return "";
2097
+ }
2098
+ if (typeof value === "bigint") {
2099
+ return value.toString();
2100
+ }
2101
+ if (typeof value === "number" && Number.isFinite(value)) {
2102
+ return String(value);
2103
+ }
2104
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
2105
+ return BigInt(value).toString();
2106
+ }
2107
+ return String(value);
2108
+ }
2109
+ function getByRelationKey(map, key) {
2110
+ if (map.has(key)) {
2111
+ return map.get(key);
2112
+ }
2113
+ const want = relationMatchKey(key);
2114
+ if (want === "") {
2115
+ return;
2116
+ }
2117
+ for (const [existing, value] of map) {
2118
+ if (relationMatchKey(existing) === want) {
2119
+ return value;
2120
+ }
2121
+ }
2122
+ return;
2123
+ }
2087
2124
  function indexHasManyRelation(parents, children, relation) {
2088
2125
  const groups = new Map;
2126
+ const originalKeys = new Map;
2089
2127
  for (const parent of parents) {
2090
- groups.set(parent[relation.localKey], []);
2128
+ const key = relationMatchKey(parent[relation.localKey]);
2129
+ if (!groups.has(key)) {
2130
+ groups.set(key, []);
2131
+ originalKeys.set(key, parent[relation.localKey]);
2132
+ }
2091
2133
  }
2092
2134
  for (const child of children) {
2093
- const key = child[relation.foreignKey];
2094
- const group = groups.get(key);
2135
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
2095
2136
  if (!group) {
2096
2137
  continue;
2097
2138
  }
2098
2139
  group.push(child);
2099
2140
  }
2100
- return groups;
2141
+ const result = new Map;
2142
+ for (const [key, group] of groups) {
2143
+ const original = originalKeys.get(key);
2144
+ if (original !== undefined) {
2145
+ result.set(original, group);
2146
+ }
2147
+ }
2148
+ return result;
2101
2149
  }
2102
2150
  function indexHasOneRelation(parents, children, relation) {
2103
2151
  const grouped = indexHasManyRelation(parents, children, relation);
2104
2152
  const result = new Map;
2105
2153
  for (const parent of parents) {
2106
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2154
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2107
2155
  result.set(parent[relation.localKey], matches[0]);
2108
2156
  }
2109
2157
  return result;
@@ -2111,12 +2159,12 @@ function indexHasOneRelation(parents, children, relation) {
2111
2159
  function indexBelongsToRelation(children, parents, relation) {
2112
2160
  const parentsById = new Map;
2113
2161
  for (const parent of parents) {
2114
- parentsById.set(parent[relation.ownerKey], parent);
2162
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
2115
2163
  }
2116
2164
  const result = new Map;
2117
2165
  for (const child of children) {
2118
2166
  const foreignKey = child[relation.foreignKey];
2119
- const parent = parentsById.get(foreignKey);
2167
+ const parent = parentsById.get(relationMatchKey(foreignKey));
2120
2168
  if (parent) {
2121
2169
  result.set(foreignKey, parent);
2122
2170
  }
@@ -2126,23 +2174,33 @@ function indexBelongsToRelation(children, parents, relation) {
2126
2174
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
2127
2175
  const relatedById = new Map;
2128
2176
  for (const related of relatedRows) {
2129
- relatedById.set(related[relation.relatedKey], related);
2177
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
2130
2178
  }
2131
2179
  const groups = new Map;
2180
+ const originalKeys = new Map;
2132
2181
  for (const parent of parents) {
2133
- groups.set(parent[relation.parentKey], []);
2182
+ const key = relationMatchKey(parent[relation.parentKey]);
2183
+ if (!groups.has(key)) {
2184
+ groups.set(key, []);
2185
+ originalKeys.set(key, parent[relation.parentKey]);
2186
+ }
2134
2187
  }
2135
2188
  for (const pivot of pivotRows) {
2136
- const parentId = pivot[relation.foreignPivotKey];
2137
- const relatedId = pivot[relation.relatedPivotKey];
2138
- const group = groups.get(parentId);
2139
- const related = relatedById.get(relatedId);
2189
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
2190
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
2140
2191
  if (!group || !related) {
2141
2192
  continue;
2142
2193
  }
2143
2194
  group.push(related);
2144
2195
  }
2145
- return groups;
2196
+ const result = new Map;
2197
+ for (const [key, group] of groups) {
2198
+ const original = originalKeys.get(key);
2199
+ if (original !== undefined) {
2200
+ result.set(original, group);
2201
+ }
2202
+ }
2203
+ return result;
2146
2204
  }
2147
2205
  function morphMany(definition) {
2148
2206
  return {
@@ -2164,31 +2222,62 @@ function morphTo(definition) {
2164
2222
  }
2165
2223
  function indexMorphManyRelation(parents, children, relation) {
2166
2224
  const groups = new Map;
2225
+ const originalKeys = new Map;
2167
2226
  for (const parent of parents) {
2168
- groups.set(parent[relation.localKey], []);
2227
+ const key = relationMatchKey(parent[relation.localKey]);
2228
+ if (!groups.has(key)) {
2229
+ groups.set(key, []);
2230
+ originalKeys.set(key, parent[relation.localKey]);
2231
+ }
2169
2232
  }
2170
2233
  for (const child of children) {
2171
2234
  if (child[relation.morphTypeKey] !== relation.morphType) {
2172
2235
  continue;
2173
2236
  }
2174
- const key = child[relation.morphIdKey];
2175
- const group = groups.get(key);
2237
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
2176
2238
  if (!group) {
2177
2239
  continue;
2178
2240
  }
2179
2241
  group.push(child);
2180
2242
  }
2181
- return groups;
2243
+ const result = new Map;
2244
+ for (const [key, group] of groups) {
2245
+ const original = originalKeys.get(key);
2246
+ if (original !== undefined) {
2247
+ result.set(original, group);
2248
+ }
2249
+ }
2250
+ return result;
2182
2251
  }
2183
2252
  function indexMorphOneRelation(parents, children, relation) {
2184
2253
  const grouped = indexMorphManyRelation(parents, children, relation);
2185
2254
  const result = new Map;
2186
2255
  for (const parent of parents) {
2187
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2256
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2188
2257
  result.set(parent[relation.localKey], matches[0]);
2189
2258
  }
2190
2259
  return result;
2191
2260
  }
2261
+ function indexHasManyThroughRelation(parents, children, relation) {
2262
+ const throughKey = relation.throughParentKey ?? "__through_parent_id";
2263
+ const grouped = new Map;
2264
+ for (const child of children) {
2265
+ const key = relationMatchKey(child[throughKey]);
2266
+ if (key === "") {
2267
+ continue;
2268
+ }
2269
+ const existing = grouped.get(key) ?? [];
2270
+ const { [throughKey]: _through, ...far } = child;
2271
+ existing.push(far);
2272
+ grouped.set(key, existing);
2273
+ }
2274
+ const result = new Map;
2275
+ for (const parent of parents) {
2276
+ const key = relationMatchKey(parent[relation.localKey]);
2277
+ result.set(parent[relation.localKey], grouped.get(key) ?? []);
2278
+ }
2279
+ return result;
2280
+ }
2192
2281
  function indexMorphToRelation(children, parentsByType, relation) {
2193
2282
  const result = new Map;
2194
2283
  for (const child of children) {
@@ -2197,7 +2286,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
2197
2286
  if (!parents) {
2198
2287
  continue;
2199
2288
  }
2200
- const parent = parents.get(child[relation.morphIdKey]);
2289
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
2201
2290
  if (parent) {
2202
2291
  result.set(child[relation.morphIdKey], parent);
2203
2292
  }
@@ -2372,6 +2461,24 @@ class RepositoryQuery {
2372
2461
  });
2373
2462
  return this;
2374
2463
  }
2464
+ withHasManyThrough(as, relation, farRepository, options = {}) {
2465
+ this.eagerLoads.push({
2466
+ kind: "hasManyThrough",
2467
+ as,
2468
+ relation,
2469
+ repository: farRepository,
2470
+ options
2471
+ });
2472
+ return this;
2473
+ }
2474
+ withTrashed() {
2475
+ this.queryOptions = { ...this.queryOptions, withTrashed: true };
2476
+ return this;
2477
+ }
2478
+ onlyTrashed() {
2479
+ this.queryOptions = { ...this.queryOptions, onlyTrashed: true };
2480
+ return this;
2481
+ }
2375
2482
  async get() {
2376
2483
  const rows = await this.repository.findAll(this.buildOptions());
2377
2484
  return await this.attach(rows);
@@ -2435,7 +2542,7 @@ class RepositoryQuery {
2435
2542
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2436
2543
  result = result.map((row) => ({
2437
2544
  ...row,
2438
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
2545
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2439
2546
  }));
2440
2547
  continue;
2441
2548
  }
@@ -2444,7 +2551,7 @@ class RepositoryQuery {
2444
2551
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2445
2552
  result = result.map((row) => ({
2446
2553
  ...row,
2447
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
2554
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2448
2555
  }));
2449
2556
  continue;
2450
2557
  }
@@ -2453,7 +2560,16 @@ class RepositoryQuery {
2453
2560
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2454
2561
  result = result.map((row) => ({
2455
2562
  ...row,
2456
- [load.as]: grouped2.get(row[relation2.localKey])
2563
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey])
2564
+ }));
2565
+ continue;
2566
+ }
2567
+ if (load.kind === "hasManyThrough") {
2568
+ const relation2 = load.relation;
2569
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
2570
+ result = result.map((row) => ({
2571
+ ...row,
2572
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2457
2573
  }));
2458
2574
  continue;
2459
2575
  }
@@ -2462,7 +2578,7 @@ class RepositoryQuery {
2462
2578
  const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
2463
2579
  result = result.map((row) => ({
2464
2580
  ...row,
2465
- [load.as]: grouped2.get(row[relation2.parentKey]) ?? []
2581
+ [load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
2466
2582
  }));
2467
2583
  continue;
2468
2584
  }
@@ -2471,7 +2587,7 @@ class RepositoryQuery {
2471
2587
  const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
2472
2588
  result = result.map((row) => ({
2473
2589
  ...row,
2474
- [load.as]: grouped2.get(row[relation2.morphIdKey])
2590
+ [load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
2475
2591
  }));
2476
2592
  continue;
2477
2593
  }
@@ -2479,7 +2595,7 @@ class RepositoryQuery {
2479
2595
  const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
2480
2596
  result = result.map((row) => ({
2481
2597
  ...row,
2482
- [load.as]: grouped.get(row[relation.foreignKey])
2598
+ [load.as]: getByRelationKey(grouped, row[relation.foreignKey])
2483
2599
  }));
2484
2600
  }
2485
2601
  return result;
@@ -2745,6 +2861,55 @@ class BaseRepository {
2745
2861
  }, options);
2746
2862
  return indexHasManyRelation(parents, children, relation);
2747
2863
  }
2864
+ async findHasManyThrough(parentId, relation, options = {}) {
2865
+ const grouped = await this.loadHasManyThroughForParents([{ [relation.localKey]: parentId }], relation, options);
2866
+ return getByRelationKey(grouped, parentId) ?? [];
2867
+ }
2868
+ async loadHasManyThroughForParents(parents, relation, options = {}) {
2869
+ if (parents.length === 0) {
2870
+ return indexHasManyThroughRelation(parents, [], relation);
2871
+ }
2872
+ const parentIds = [...new Set(parents.map((parent) => parent[relation.localKey]))];
2873
+ const throughParentKey = relation.throughParentKey ?? "__through_parent_id";
2874
+ const farTable = this.table.name;
2875
+ const columns = this.table.columns.map((column) => `${qualifyColumn(farTable, column)}`).join(", ");
2876
+ const placeholders = parentIds.map((_, index) => `$${index + 1}`).join(", ");
2877
+ const { text: extraWhere, params: extraParams } = this.buildThroughWhere(options, parentIds.length);
2878
+ const softDelete = this.throughSoftDeleteClause(options);
2879
+ const sql = `SELECT ${columns}, ${qualifyColumn(relation.throughTable, relation.firstKey)} AS ${throughParentKey} FROM ${quoteIdentifier(farTable)} INNER JOIN ${quoteIdentifier(relation.throughTable)} ON ${qualifyColumn(relation.throughTable, relation.secondLocalKey)} = ${qualifyColumn(farTable, relation.secondKey)} WHERE ${qualifyColumn(relation.throughTable, relation.firstKey)} IN (${placeholders})${softDelete}${extraWhere}`;
2880
+ const children = await this.connection.unsafe(sql, [
2881
+ ...parentIds,
2882
+ ...extraParams
2883
+ ]);
2884
+ return indexHasManyThroughRelation(parents, children, relation);
2885
+ }
2886
+ throughSoftDeleteClause(options) {
2887
+ const column = resolveSoftDeleteColumn(this.table);
2888
+ if (!column) {
2889
+ return "";
2890
+ }
2891
+ const qualified = qualifyColumn(this.table.name, column);
2892
+ if (options.onlyTrashed) {
2893
+ return ` AND ${qualified} IS NOT NULL`;
2894
+ }
2895
+ if (options.withTrashed) {
2896
+ return "";
2897
+ }
2898
+ return ` AND ${qualified} IS NULL`;
2899
+ }
2900
+ buildThroughWhere(options, paramOffset = 1) {
2901
+ const where = options.where ?? {};
2902
+ const entries = Object.entries(where);
2903
+ if (entries.length === 0) {
2904
+ return { text: "", params: [] };
2905
+ }
2906
+ const params = [];
2907
+ const clauses = entries.map(([column, value], index) => {
2908
+ params.push(value);
2909
+ return `${qualifyColumn(this.table.name, column)} = $${paramOffset + index + 1}`;
2910
+ });
2911
+ return { text: ` AND ${clauses.join(" AND ")}`, params };
2912
+ }
2748
2913
  async loadBelongsToForParents(children, relation, parentRepository, options = {}) {
2749
2914
  if (children.length === 0) {
2750
2915
  return new Map;
@@ -2770,7 +2935,7 @@ class BaseRepository {
2770
2935
  const grouped = await this.loadMorphManyForParents(parents, relation, options);
2771
2936
  const result = new Map;
2772
2937
  for (const parent of parents) {
2773
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2938
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2774
2939
  result.set(parent[relation.localKey], matches[0]);
2775
2940
  }
2776
2941
  return result;
@@ -2799,7 +2964,7 @@ class BaseRepository {
2799
2964
  }, options);
2800
2965
  const indexed = new Map;
2801
2966
  for (const parent of parents) {
2802
- indexed.set(parent[ownerKey], parent);
2967
+ indexed.set(relationMatchKey(parent[ownerKey]), parent);
2803
2968
  }
2804
2969
  parentsByType.set(morphType, indexed);
2805
2970
  }
@@ -2810,7 +2975,8 @@ class BaseRepository {
2810
2975
  return indexBelongsToManyRelation(parents, [], [], relation);
2811
2976
  }
2812
2977
  const parentIds = [...new Set(parents.map((parent) => parent[relation.parentKey]))];
2813
- const pivotRows = await this.connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = ANY($1)`, [parentIds]);
2978
+ const placeholders = parentIds.map((_, index) => `$${index + 1}`).join(", ");
2979
+ const pivotRows = await this.connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} IN (${placeholders})`, parentIds);
2814
2980
  if (pivotRows.length === 0) {
2815
2981
  return indexBelongsToManyRelation(parents, [], [], relation);
2816
2982
  }
@@ -3472,7 +3638,8 @@ class BelongsToManyRelationQuery {
3472
3638
  return;
3473
3639
  }
3474
3640
  const list = Array.isArray(ids) ? ids : [ids];
3475
- await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = ANY($2)`, [parentId, list]);
3641
+ const placeholders = list.map((_, index) => `$${index + 2}`).join(", ");
3642
+ await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} IN (${placeholders})`, [parentId, ...list]);
3476
3643
  }
3477
3644
  async sync(ids) {
3478
3645
  await this.detach();
@@ -3654,6 +3821,65 @@ class MorphToRelationQuery {
3654
3821
  }
3655
3822
  }
3656
3823
 
3824
+ class HasManyThroughRelationQuery {
3825
+ parent;
3826
+ related;
3827
+ relation;
3828
+ kind = "hasManyThrough";
3829
+ extraWhere = {};
3830
+ extraOptions = {};
3831
+ constructor(parent, related, relation) {
3832
+ this.parent = parent;
3833
+ this.related = related;
3834
+ this.relation = relation;
3835
+ }
3836
+ where(where) {
3837
+ this.extraWhere = { ...this.extraWhere, ...where };
3838
+ return this;
3839
+ }
3840
+ orderBy(orderBy) {
3841
+ this.extraOptions = { ...this.extraOptions, orderBy };
3842
+ return this;
3843
+ }
3844
+ limit(limit) {
3845
+ this.extraOptions = { ...this.extraOptions, limit };
3846
+ return this;
3847
+ }
3848
+ applyEagerLoad(query, alias) {
3849
+ query.withHasManyThrough(alias, this.relation, this.related.repository(), this.extraOptions);
3850
+ }
3851
+ hydrateEager(row, alias) {
3852
+ const value = row[alias] ?? [];
3853
+ const rows = Array.isArray(value) ? value : [];
3854
+ return rows.map((item) => this.related.newFromRecord(item));
3855
+ }
3856
+ toExistsClause(parentTable) {
3857
+ const farTable = this.related.repository().getTable().name;
3858
+ const extra = buildAdvancedWhereClause(farTable, this.extraWhere, [], []);
3859
+ const extraSql = extra.clause.replace(/^ WHERE /, "");
3860
+ const sql = `SELECT 1 FROM ${quoteIdentifier(farTable)} INNER JOIN ${quoteIdentifier(this.relation.throughTable)} ON ${qualifyColumn(this.relation.throughTable, this.relation.secondLocalKey)} = ${qualifyColumn(farTable, this.relation.secondKey)} WHERE ${qualifyColumn(this.relation.throughTable, this.relation.firstKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
3861
+ return { sql, params: extra.params };
3862
+ }
3863
+ async get() {
3864
+ const rows = await this.related.repository().withConnection(this.parent.getRepository().getConnection()).findHasManyThrough(this.parent.get(this.relation.localKey), this.relation, {
3865
+ ...this.extraOptions,
3866
+ where: this.extraWhere
3867
+ });
3868
+ return rows.map((row) => this.related.newFromRecord(row));
3869
+ }
3870
+ async first() {
3871
+ const rows = await this.limit(1).get();
3872
+ return rows[0] ?? null;
3873
+ }
3874
+ async count() {
3875
+ const rows = await this.get();
3876
+ return rows.length;
3877
+ }
3878
+ then(onfulfilled, onrejected) {
3879
+ return thenGet(() => this.get(), onfulfilled, onrejected);
3880
+ }
3881
+ }
3882
+
3657
3883
  // ../../src/core/database/model.ts
3658
3884
  var modelRepositories = new WeakMap;
3659
3885
  var namedModels = new Map;
@@ -3982,6 +4208,18 @@ class ModelQuery {
3982
4208
  this.query.withMorphTo(...args);
3983
4209
  return this;
3984
4210
  }
4211
+ withHasManyThrough(...args) {
4212
+ this.query.withHasManyThrough(...args);
4213
+ return this;
4214
+ }
4215
+ withTrashed() {
4216
+ this.query.withTrashed();
4217
+ return this;
4218
+ }
4219
+ onlyTrashed() {
4220
+ this.query.onlyTrashed();
4221
+ return this;
4222
+ }
3985
4223
  async get() {
3986
4224
  const statics = modelStatics(this.modelClass);
3987
4225
  const rows = await this.query.get();
@@ -4189,6 +4427,31 @@ class Model {
4189
4427
  static with(...relations) {
4190
4428
  return Model.query.call(this).with(...relations);
4191
4429
  }
4430
+ static withTrashed() {
4431
+ return Model.query.call(this).withTrashed();
4432
+ }
4433
+ static onlyTrashed() {
4434
+ return Model.query.call(this).onlyTrashed();
4435
+ }
4436
+ static async chunk(count, callback) {
4437
+ const statics = modelStatics(this);
4438
+ ensureBooted(this);
4439
+ await resolveModelRepository(this).chunk(count, async (rows) => {
4440
+ return await callback(rows.map((row) => statics.newFromRecord(row, true)));
4441
+ });
4442
+ }
4443
+ static async cursorPaginate(options) {
4444
+ const statics = modelStatics(this);
4445
+ ensureBooted(this);
4446
+ const page = await resolveModelRepository(this).cursorPaginate({
4447
+ perPage: options.perPage,
4448
+ cursor: options.cursor
4449
+ });
4450
+ return {
4451
+ data: page.data.map((row) => statics.newFromRecord(row, true)),
4452
+ meta: page.meta
4453
+ };
4454
+ }
4192
4455
  static whereHas(name, constrain) {
4193
4456
  return Model.query.call(this).whereHas(name, constrain);
4194
4457
  }
@@ -4313,7 +4576,7 @@ class Model {
4313
4576
  }
4314
4577
  async loadHasMany(as, relation, childRepository, options = {}) {
4315
4578
  const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
4316
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
4579
+ const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
4317
4580
  return Object.assign(this, { [as]: loaded });
4318
4581
  }
4319
4582
  async loadHasOne(as, relation, childRepository, options = {}) {
@@ -4323,7 +4586,7 @@ class Model {
4323
4586
  }
4324
4587
  async loadBelongsTo(as, relation, parentRepository, options = {}) {
4325
4588
  const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
4326
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
4589
+ const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
4327
4590
  return Object.assign(this, { [as]: loaded });
4328
4591
  }
4329
4592
  async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
@@ -4343,7 +4606,7 @@ class Model {
4343
4606
  }
4344
4607
  });
4345
4608
  const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
4346
- const loaded = grouped.get(parentId) ?? [];
4609
+ const loaded = getByRelationKey(grouped, parentId) ?? [];
4347
4610
  return Object.assign(this, { [as]: loaded });
4348
4611
  }
4349
4612
  hasMany(related, foreignKey, localKey) {
@@ -4373,6 +4636,20 @@ class Model {
4373
4636
  ownerKey: ownerKey ?? relatedTable.primaryKey
4374
4637
  }));
4375
4638
  }
4639
+ hasManyThrough(related, through, firstKey, secondKey, localKey, secondLocalKey) {
4640
+ const table = this.repository.getTable();
4641
+ const relatedClass = resolveRelated(related);
4642
+ const throughClass = resolveRelated(through);
4643
+ const throughTable = throughClass.repository().getTable();
4644
+ return new HasManyThroughRelationQuery(this, relatedClass, hasManyThrough({
4645
+ name: relatedClass.repository().getTable().name,
4646
+ throughTable: throughTable.name,
4647
+ localKey: localKey ?? table.primaryKey,
4648
+ firstKey: firstKey ?? foreignKeyFromTable(table.name),
4649
+ secondLocalKey: secondLocalKey ?? throughTable.primaryKey,
4650
+ secondKey: secondKey ?? foreignKeyFromTable(throughTable.name)
4651
+ }));
4652
+ }
4376
4653
  belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
4377
4654
  const table = this.repository.getTable();
4378
4655
  const relatedClass = resolveRelated(related);
@@ -8014,17 +8291,33 @@ async function resolveTenantForRequest(request) {
8014
8291
  if (Number.isInteger(parsedHeader) && parsedHeader > 0 && parsedHeader !== userTenantId) {
8015
8292
  throw new ForbiddenError("Tenant header does not match your account.");
8016
8293
  }
8017
- return await resolveTenant(userTenantId) ?? DEFAULT_TENANT;
8294
+ const memberTenant = await resolveTenant(userTenantId);
8295
+ if (memberTenant) {
8296
+ return memberTenant;
8297
+ }
8298
+ return DEFAULT_TENANT;
8018
8299
  }
8019
8300
  if (Number.isInteger(parsedHeader) && parsedHeader > 0) {
8020
- return await resolveTenant(parsedHeader) ?? DEFAULT_TENANT;
8301
+ const headerTenant = await resolveTenant(parsedHeader);
8302
+ if (headerTenant) {
8303
+ return headerTenant;
8304
+ }
8305
+ return DEFAULT_TENANT;
8021
8306
  }
8022
- return await resolveTenant(userTenantId) ?? DEFAULT_TENANT;
8307
+ const adminTenant = await resolveTenant(userTenantId);
8308
+ if (adminTenant) {
8309
+ return adminTenant;
8310
+ }
8311
+ return DEFAULT_TENANT;
8023
8312
  }
8024
8313
  }
8025
8314
  const headerTenantId = Number.isInteger(parsedHeader) && parsedHeader > 0 ? parsedHeader : null;
8026
8315
  const tenantId = isPublicReadsEnabled() && headerTenantId !== null ? headerTenantId : DEFAULT_TENANT.id;
8027
- return await resolveTenant(tenantId) ?? DEFAULT_TENANT;
8316
+ const guestTenant = await resolveTenant(tenantId);
8317
+ if (guestTenant) {
8318
+ return guestTenant;
8319
+ }
8320
+ return DEFAULT_TENANT;
8028
8321
  }
8029
8322
  function createTenantMiddleware() {
8030
8323
  return async (request, next) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.99",
3
+ "version": "0.5.101",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",