arkormx 2.11.11 → 2.11.13

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.
@@ -212,6 +212,10 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
212
212
  case "morphMany":
213
213
  await this.loadMorphMany(name, metadata, constraint);
214
214
  return;
215
+ case "morphToMany":
216
+ case "morphedByMany":
217
+ await this.loadPolymorphicManyToMany(name, metadata, constraint);
218
+ return;
215
219
  default: await this.loadIndividually(name, resolver, constraint);
216
220
  }
217
221
  }
@@ -295,7 +299,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
295
299
  }
296
300
  let query = metadata.relatedModel.query().whereIn(metadata.ownerKey, keys);
297
301
  query = this.applyConstraint(query, constraint);
298
- const relatedModels = (await query.get()).all();
302
+ const relatedModels = await this.getPartitionedModels(query, metadata.ownerKey, keys);
299
303
  const relatedByOwnerKey = /* @__PURE__ */ new Map();
300
304
  relatedModels.forEach((related) => {
301
305
  const value = this.readModelAttribute(related, metadata.ownerKey);
@@ -327,7 +331,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
327
331
  }
328
332
  let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
329
333
  query = this.applyConstraint(query, constraint);
330
- const relatedModels = (await query.get()).all();
334
+ const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
331
335
  const relatedByForeignKey = /* @__PURE__ */ new Map();
332
336
  relatedModels.forEach((related) => {
333
337
  const value = this.readModelAttribute(related, metadata.foreignKey);
@@ -373,6 +377,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
373
377
  }
374
378
  let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
375
379
  query = this.applyConstraint(query, constraint);
380
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1 && await this.loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows)) return;
376
381
  const relatedModels = (await query.get()).all();
377
382
  const relatedByKey = /* @__PURE__ */ new Map();
378
383
  relatedModels.forEach((related) => {
@@ -401,6 +406,65 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
401
406
  model.setLoadedRelation(name, new ArkormCollection(related));
402
407
  });
403
408
  }
409
+ async loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows) {
410
+ if (metadata.pivotWhere) {
411
+ await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
412
+ return true;
413
+ }
414
+ const pivotColumns = this.getBelongsToManyPivotColumns(metadata);
415
+ const pivotAliases = new Map(pivotColumns.map((column, index) => [column, `__arkorm_pivot_${index}`]));
416
+ const parentAlias = pivotAliases.get(metadata.foreignPivotKey);
417
+ const throughTable = metadata.throughTable;
418
+ const relatedTable = metadata.relatedModel.getTable();
419
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${metadata.foreignPivotKey}`, parentKeys).addSelect({ ...Object.fromEntries([...pivotAliases].map(([column, alias]) => [`${throughTable}.${column}`, alias])) }).getRowsForEagerLoad([`${throughTable}.${metadata.foreignPivotKey}`]);
420
+ if (!rows) {
421
+ await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
422
+ return true;
423
+ }
424
+ const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
425
+ const relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
426
+ const relatedByKey = /* @__PURE__ */ new Map();
427
+ relatedModels.forEach((related) => {
428
+ const key = this.readModelAttribute(related, metadata.relatedKey);
429
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
430
+ });
431
+ const relatedByParent = /* @__PURE__ */ new Map();
432
+ rows.forEach((row) => {
433
+ const parentKey = row[parentAlias];
434
+ const relatedKey = row[metadata.relatedKey];
435
+ if (parentKey == null || relatedKey == null) return;
436
+ const related = relatedByKey.get(this.toLookupKey(relatedKey));
437
+ if (!related) return;
438
+ const pivot = Object.fromEntries([...pivotAliases].map(([column, alias]) => [column, row[alias]]));
439
+ const bucket = relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
440
+ bucket.push(this.attachBelongsToManyPivot(metadata, related, pivot));
441
+ relatedByParent.set(this.toLookupKey(parentKey), bucket);
442
+ });
443
+ this.models.forEach((model) => {
444
+ const parentKey = model.getAttribute(metadata.parentKey);
445
+ const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
446
+ model.setLoadedRelation(name, new ArkormCollection(related));
447
+ });
448
+ return true;
449
+ }
450
+ async loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows) {
451
+ const rowsByParent = /* @__PURE__ */ new Map();
452
+ pivotRows.forEach((row) => {
453
+ const parentKey = row[metadata.foreignPivotKey];
454
+ if (parentKey == null) return;
455
+ const bucket = rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
456
+ bucket.push(row);
457
+ rowsByParent.set(this.toLookupKey(parentKey), bucket);
458
+ });
459
+ await Promise.all(this.models.map(async (model) => {
460
+ const parentKey = model.getAttribute(metadata.parentKey);
461
+ const rows = parentKey == null ? [] : rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
462
+ const relatedIds = this.collectUniqueRowValues(rows, metadata.relatedPivotKey);
463
+ const related = relatedIds.length ? (await query.clone().whereIn(metadata.relatedKey, relatedIds).get()).all() : [];
464
+ const pivotByRelated = new Map(rows.map((row) => [this.toLookupKey(row[metadata.relatedPivotKey]), row]));
465
+ model.setLoadedRelation(name, new ArkormCollection(related.map((entry) => this.attachBelongsToManyPivot(metadata, entry, pivotByRelated.get(this.toLookupKey(this.readModelAttribute(entry, metadata.relatedKey)))))));
466
+ }));
467
+ }
404
468
  buildBelongsToManyPivotWhere(metadata, parentKeys) {
405
469
  const baseCondition = {
406
470
  type: "comparison",
@@ -461,7 +525,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
461
525
  }
462
526
  let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
463
527
  query = this.applyConstraint(query, constraint);
464
- const relatedModels = (await query.get()).all();
528
+ const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
465
529
  const relatedByForeignKey = /* @__PURE__ */ new Map();
466
530
  relatedModels.forEach((related) => {
467
531
  const value = this.readModelAttribute(related, metadata.foreignKey);
@@ -509,6 +573,15 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
509
573
  }
510
574
  let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
511
575
  query = this.applyConstraint(query, constraint);
576
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
577
+ const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
578
+ this.models.forEach((model) => {
579
+ const parentKey = model.getAttribute(metadata.localKey);
580
+ const related = parentKey == null ? [] : limited.get(this.toLookupKey(parentKey)) ?? [];
581
+ model.setLoadedRelation(name, new ArkormCollection(related));
582
+ });
583
+ return;
584
+ }
512
585
  const relatedModels = (await query.get()).all();
513
586
  const relatedByIntermediate = /* @__PURE__ */ new Map();
514
587
  relatedModels.forEach((related) => {
@@ -568,6 +641,15 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
568
641
  }
569
642
  let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
570
643
  query = this.applyConstraint(query, constraint);
644
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
645
+ const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
646
+ this.models.forEach((model) => {
647
+ const parentKey = model.getAttribute(metadata.localKey);
648
+ const related = parentKey == null ? void 0 : limited.get(this.toLookupKey(parentKey))?.[0];
649
+ model.setLoadedRelation(name, related ?? this.resolveSingleDefault(resolver, model));
650
+ });
651
+ return;
652
+ }
571
653
  const relatedModels = (await query.get()).all();
572
654
  const relatedByIntermediate = /* @__PURE__ */ new Map();
573
655
  relatedModels.forEach((related) => {
@@ -591,6 +673,49 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
591
673
  model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
592
674
  });
593
675
  }
676
+ async loadLimitedThrough(metadata, query, parentKeys, throughRows) {
677
+ const parentAlias = "__arkorm_parent_key";
678
+ const throughTable = metadata.throughTable;
679
+ const relatedTable = metadata.relatedModel.getTable();
680
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.secondKey)}`, "=", `${throughTable}.${metadata.secondLocalKey}`).whereIn(`${throughTable}.${metadata.firstKey}`, parentKeys).addSelect({ [`${throughTable}.${metadata.firstKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${metadata.firstKey}`]);
681
+ if (!rows) return await this.loadLimitedThroughIndividually(metadata, query, throughRows);
682
+ const relatedKey = metadata.relatedModel.getPrimaryKey();
683
+ const selectedIds = this.collectUniqueRowValues(rows, relatedKey);
684
+ const relatedModels = (await query.clone().withoutPagination().whereIn(relatedKey, selectedIds).get()).all();
685
+ const relatedByKey = /* @__PURE__ */ new Map();
686
+ relatedModels.forEach((related) => {
687
+ const key = this.readModelAttribute(related, relatedKey);
688
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
689
+ });
690
+ return rows.reduce((all, row) => {
691
+ const parentKey = row[parentAlias];
692
+ const key = row[relatedKey];
693
+ if (parentKey == null || key == null) return all;
694
+ const related = relatedByKey.get(this.toLookupKey(key));
695
+ if (!related) return all;
696
+ const lookup = this.toLookupKey(parentKey);
697
+ const bucket = all.get(lookup) ?? [];
698
+ bucket.push(related);
699
+ all.set(lookup, bucket);
700
+ return all;
701
+ }, /* @__PURE__ */ new Map());
702
+ }
703
+ async loadLimitedThroughIndividually(metadata, query, throughRows) {
704
+ const intermediateByParent = /* @__PURE__ */ new Map();
705
+ throughRows.forEach((row) => {
706
+ const parentKey = row[metadata.firstKey];
707
+ const intermediateKey = row[metadata.secondLocalKey];
708
+ if (parentKey == null || intermediateKey == null) return;
709
+ const lookup = this.toLookupKey(parentKey);
710
+ const bucket = intermediateByParent.get(lookup) ?? [];
711
+ bucket.push(intermediateKey);
712
+ intermediateByParent.set(lookup, bucket);
713
+ });
714
+ const entries = await Promise.all([...intermediateByParent].map(async ([parentKey, intermediateKeys]) => {
715
+ return [parentKey, (await query.clone().whereIn(metadata.secondKey, intermediateKeys).get()).all()];
716
+ }));
717
+ return new Map(entries);
718
+ }
594
719
  /**
595
720
  * Loads an inverse polymorphic ("morph to") relationship for the set of
596
721
  * models. Parents are grouped by their morph type, so each distinct type
@@ -624,7 +749,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
624
749
  const ownerKey = metadata.ownerKey ?? relatedModel.getPrimaryKey();
625
750
  let query = relatedModel.query().whereIn(ownerKey, ids);
626
751
  query = this.applyConstraint(query, constraint);
627
- (await query.get()).all().forEach((related) => {
752
+ (await this.getPartitionedModels(query, ownerKey, ids)).forEach((related) => {
628
753
  const value = this.readModelAttribute(related, ownerKey);
629
754
  if (value == null) return;
630
755
  relatedByTypeAndKey.set(`${morphType}:${this.toLookupKey(value)}`, related);
@@ -668,6 +793,91 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
668
793
  model.setLoadedRelation(name, new ArkormCollection(bucket));
669
794
  });
670
795
  }
796
+ async loadPolymorphicManyToMany(name, metadata, constraint) {
797
+ const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
798
+ if (parentKeys.length === 0) {
799
+ this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
800
+ return;
801
+ }
802
+ const parentPivotKey = metadata.type === "morphToMany" ? metadata.morphIdColumn : metadata.foreignPivotKey;
803
+ const morphType = metadata.type === "morphToMany" ? this.morphTypeOf(this.models[0]) : metadata.relatedModel.name;
804
+ const pivotRows = await this.createRelationTableLoader().selectRows({
805
+ table: metadata.throughTable,
806
+ where: {
807
+ type: "group",
808
+ operator: "and",
809
+ conditions: [{
810
+ type: "comparison",
811
+ column: parentPivotKey,
812
+ operator: "in",
813
+ value: parentKeys
814
+ }, {
815
+ type: "comparison",
816
+ column: metadata.morphTypeColumn,
817
+ operator: "=",
818
+ value: morphType
819
+ }]
820
+ },
821
+ columns: [parentPivotKey, metadata.relatedPivotKey].map((column) => ({ column }))
822
+ });
823
+ const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
824
+ if (relatedIds.length === 0) {
825
+ this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
826
+ return;
827
+ }
828
+ let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
829
+ query = this.applyConstraint(query, constraint);
830
+ let pairs = pivotRows.map((row) => ({
831
+ parent: row[parentPivotKey],
832
+ related: row[metadata.relatedPivotKey]
833
+ }));
834
+ let relatedModels;
835
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
836
+ const parentAlias = "__arkorm_parent_key";
837
+ const throughTable = metadata.throughTable;
838
+ const relatedTable = metadata.relatedModel.getTable();
839
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${parentPivotKey}`, parentKeys).where({ [`${throughTable}.${metadata.morphTypeColumn}`]: morphType }).addSelect({ [`${throughTable}.${parentPivotKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${parentPivotKey}`]);
840
+ if (rows) {
841
+ pairs = rows.map((row) => ({
842
+ parent: row[parentAlias],
843
+ related: row[metadata.relatedKey]
844
+ }));
845
+ const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
846
+ relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
847
+ } else {
848
+ await this.loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows);
849
+ return;
850
+ }
851
+ } else relatedModels = (await query.get()).all();
852
+ const relatedByKey = /* @__PURE__ */ new Map();
853
+ relatedModels.forEach((related) => {
854
+ const key = this.readModelAttribute(related, metadata.relatedKey);
855
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
856
+ });
857
+ const relatedByParent = /* @__PURE__ */ new Map();
858
+ pairs.forEach(({ parent, related }) => {
859
+ if (parent == null || related == null) return;
860
+ const model = relatedByKey.get(this.toLookupKey(related));
861
+ if (!model) return;
862
+ const lookup = this.toLookupKey(parent);
863
+ const bucket = relatedByParent.get(lookup) ?? [];
864
+ bucket.push(model);
865
+ relatedByParent.set(lookup, bucket);
866
+ });
867
+ this.models.forEach((model) => {
868
+ const parentKey = model.getAttribute(metadata.parentKey);
869
+ const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
870
+ model.setLoadedRelation(name, new ArkormCollection(related));
871
+ });
872
+ }
873
+ async loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows) {
874
+ await Promise.all(this.models.map(async (model) => {
875
+ const parentKey = model.getAttribute(metadata.parentKey);
876
+ const ids = pivotRows.filter((row) => this.toLookupKey(row[parentPivotKey]) === this.toLookupKey(parentKey)).map((row) => row[metadata.relatedPivotKey]);
877
+ const related = ids.length ? (await query.clone().whereIn(metadata.relatedKey, ids).get()).all() : [];
878
+ model.setLoadedRelation(name, new ArkormCollection(related));
879
+ }));
880
+ }
671
881
  /**
672
882
  * Batch-loads the children for a morphOne/morphMany relationship. Parents are
673
883
  * grouped by their own morph type (constructor name), so each distinct type
@@ -700,7 +910,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
700
910
  await Promise.all(Array.from(keysByType.entries()).map(async ([morphType, keys]) => {
701
911
  let query = metadata.relatedModel.query().whereIn(metadata.morphIdColumn, keys).where({ [metadata.morphTypeColumn]: morphType });
702
912
  query = this.applyConstraint(query, constraint);
703
- (await query.get()).all().forEach((related) => {
913
+ (await this.getPartitionedModels(query, metadata.morphIdColumn, keys)).forEach((related) => {
704
914
  const value = this.readModelAttribute(related, metadata.morphIdColumn);
705
915
  if (value == null) return;
706
916
  const key = `${morphType}:${this.toLookupKey(value)}`;
@@ -743,6 +953,14 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
743
953
  if (!constraint) return query;
744
954
  return constraint(query) ?? query;
745
955
  }
956
+ async getPartitionedModels(query, partitionColumn, partitionValues) {
957
+ if (partitionValues.length <= 1) return (await query.get()).all();
958
+ const partitioned = await query.getForEagerLoad([partitionColumn]);
959
+ if (partitioned) return partitioned.all();
960
+ return (await Promise.all(partitionValues.map(async (value) => {
961
+ return (await query.clone().where({ [partitionColumn]: value }).get()).all();
962
+ }))).flat();
963
+ }
746
964
  /**
747
965
  * Collects unique values from the set of models based on a resolver function, which
748
966
  * is used to extract the value from each model.
@@ -184,6 +184,10 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
184
184
  case "morphMany":
185
185
  await this.loadMorphMany(name, metadata, constraint);
186
186
  return;
187
+ case "morphToMany":
188
+ case "morphedByMany":
189
+ await this.loadPolymorphicManyToMany(name, metadata, constraint);
190
+ return;
187
191
  default: await this.loadIndividually(name, resolver, constraint);
188
192
  }
189
193
  }
@@ -267,7 +271,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
267
271
  }
268
272
  let query = metadata.relatedModel.query().whereIn(metadata.ownerKey, keys);
269
273
  query = this.applyConstraint(query, constraint);
270
- const relatedModels = (await query.get()).all();
274
+ const relatedModels = await this.getPartitionedModels(query, metadata.ownerKey, keys);
271
275
  const relatedByOwnerKey = /* @__PURE__ */ new Map();
272
276
  relatedModels.forEach((related) => {
273
277
  const value = this.readModelAttribute(related, metadata.ownerKey);
@@ -299,7 +303,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
299
303
  }
300
304
  let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
301
305
  query = this.applyConstraint(query, constraint);
302
- const relatedModels = (await query.get()).all();
306
+ const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
303
307
  const relatedByForeignKey = /* @__PURE__ */ new Map();
304
308
  relatedModels.forEach((related) => {
305
309
  const value = this.readModelAttribute(related, metadata.foreignKey);
@@ -345,6 +349,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
345
349
  }
346
350
  let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
347
351
  query = this.applyConstraint(query, constraint);
352
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1 && await this.loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows)) return;
348
353
  const relatedModels = (await query.get()).all();
349
354
  const relatedByKey = /* @__PURE__ */ new Map();
350
355
  relatedModels.forEach((related) => {
@@ -373,6 +378,65 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
373
378
  model.setLoadedRelation(name, new ArkormCollection(related));
374
379
  });
375
380
  }
381
+ async loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows) {
382
+ if (metadata.pivotWhere) {
383
+ await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
384
+ return true;
385
+ }
386
+ const pivotColumns = this.getBelongsToManyPivotColumns(metadata);
387
+ const pivotAliases = new Map(pivotColumns.map((column, index) => [column, `__arkorm_pivot_${index}`]));
388
+ const parentAlias = pivotAliases.get(metadata.foreignPivotKey);
389
+ const throughTable = metadata.throughTable;
390
+ const relatedTable = metadata.relatedModel.getTable();
391
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${metadata.foreignPivotKey}`, parentKeys).addSelect({ ...Object.fromEntries([...pivotAliases].map(([column, alias]) => [`${throughTable}.${column}`, alias])) }).getRowsForEagerLoad([`${throughTable}.${metadata.foreignPivotKey}`]);
392
+ if (!rows) {
393
+ await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
394
+ return true;
395
+ }
396
+ const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
397
+ const relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
398
+ const relatedByKey = /* @__PURE__ */ new Map();
399
+ relatedModels.forEach((related) => {
400
+ const key = this.readModelAttribute(related, metadata.relatedKey);
401
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
402
+ });
403
+ const relatedByParent = /* @__PURE__ */ new Map();
404
+ rows.forEach((row) => {
405
+ const parentKey = row[parentAlias];
406
+ const relatedKey = row[metadata.relatedKey];
407
+ if (parentKey == null || relatedKey == null) return;
408
+ const related = relatedByKey.get(this.toLookupKey(relatedKey));
409
+ if (!related) return;
410
+ const pivot = Object.fromEntries([...pivotAliases].map(([column, alias]) => [column, row[alias]]));
411
+ const bucket = relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
412
+ bucket.push(this.attachBelongsToManyPivot(metadata, related, pivot));
413
+ relatedByParent.set(this.toLookupKey(parentKey), bucket);
414
+ });
415
+ this.models.forEach((model) => {
416
+ const parentKey = model.getAttribute(metadata.parentKey);
417
+ const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
418
+ model.setLoadedRelation(name, new ArkormCollection(related));
419
+ });
420
+ return true;
421
+ }
422
+ async loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows) {
423
+ const rowsByParent = /* @__PURE__ */ new Map();
424
+ pivotRows.forEach((row) => {
425
+ const parentKey = row[metadata.foreignPivotKey];
426
+ if (parentKey == null) return;
427
+ const bucket = rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
428
+ bucket.push(row);
429
+ rowsByParent.set(this.toLookupKey(parentKey), bucket);
430
+ });
431
+ await Promise.all(this.models.map(async (model) => {
432
+ const parentKey = model.getAttribute(metadata.parentKey);
433
+ const rows = parentKey == null ? [] : rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
434
+ const relatedIds = this.collectUniqueRowValues(rows, metadata.relatedPivotKey);
435
+ const related = relatedIds.length ? (await query.clone().whereIn(metadata.relatedKey, relatedIds).get()).all() : [];
436
+ const pivotByRelated = new Map(rows.map((row) => [this.toLookupKey(row[metadata.relatedPivotKey]), row]));
437
+ model.setLoadedRelation(name, new ArkormCollection(related.map((entry) => this.attachBelongsToManyPivot(metadata, entry, pivotByRelated.get(this.toLookupKey(this.readModelAttribute(entry, metadata.relatedKey)))))));
438
+ }));
439
+ }
376
440
  buildBelongsToManyPivotWhere(metadata, parentKeys) {
377
441
  const baseCondition = {
378
442
  type: "comparison",
@@ -433,7 +497,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
433
497
  }
434
498
  let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
435
499
  query = this.applyConstraint(query, constraint);
436
- const relatedModels = (await query.get()).all();
500
+ const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
437
501
  const relatedByForeignKey = /* @__PURE__ */ new Map();
438
502
  relatedModels.forEach((related) => {
439
503
  const value = this.readModelAttribute(related, metadata.foreignKey);
@@ -481,6 +545,15 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
481
545
  }
482
546
  let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
483
547
  query = this.applyConstraint(query, constraint);
548
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
549
+ const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
550
+ this.models.forEach((model) => {
551
+ const parentKey = model.getAttribute(metadata.localKey);
552
+ const related = parentKey == null ? [] : limited.get(this.toLookupKey(parentKey)) ?? [];
553
+ model.setLoadedRelation(name, new ArkormCollection(related));
554
+ });
555
+ return;
556
+ }
484
557
  const relatedModels = (await query.get()).all();
485
558
  const relatedByIntermediate = /* @__PURE__ */ new Map();
486
559
  relatedModels.forEach((related) => {
@@ -540,6 +613,15 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
540
613
  }
541
614
  let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
542
615
  query = this.applyConstraint(query, constraint);
616
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
617
+ const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
618
+ this.models.forEach((model) => {
619
+ const parentKey = model.getAttribute(metadata.localKey);
620
+ const related = parentKey == null ? void 0 : limited.get(this.toLookupKey(parentKey))?.[0];
621
+ model.setLoadedRelation(name, related ?? this.resolveSingleDefault(resolver, model));
622
+ });
623
+ return;
624
+ }
543
625
  const relatedModels = (await query.get()).all();
544
626
  const relatedByIntermediate = /* @__PURE__ */ new Map();
545
627
  relatedModels.forEach((related) => {
@@ -563,6 +645,49 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
563
645
  model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
564
646
  });
565
647
  }
648
+ async loadLimitedThrough(metadata, query, parentKeys, throughRows) {
649
+ const parentAlias = "__arkorm_parent_key";
650
+ const throughTable = metadata.throughTable;
651
+ const relatedTable = metadata.relatedModel.getTable();
652
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.secondKey)}`, "=", `${throughTable}.${metadata.secondLocalKey}`).whereIn(`${throughTable}.${metadata.firstKey}`, parentKeys).addSelect({ [`${throughTable}.${metadata.firstKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${metadata.firstKey}`]);
653
+ if (!rows) return await this.loadLimitedThroughIndividually(metadata, query, throughRows);
654
+ const relatedKey = metadata.relatedModel.getPrimaryKey();
655
+ const selectedIds = this.collectUniqueRowValues(rows, relatedKey);
656
+ const relatedModels = (await query.clone().withoutPagination().whereIn(relatedKey, selectedIds).get()).all();
657
+ const relatedByKey = /* @__PURE__ */ new Map();
658
+ relatedModels.forEach((related) => {
659
+ const key = this.readModelAttribute(related, relatedKey);
660
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
661
+ });
662
+ return rows.reduce((all, row) => {
663
+ const parentKey = row[parentAlias];
664
+ const key = row[relatedKey];
665
+ if (parentKey == null || key == null) return all;
666
+ const related = relatedByKey.get(this.toLookupKey(key));
667
+ if (!related) return all;
668
+ const lookup = this.toLookupKey(parentKey);
669
+ const bucket = all.get(lookup) ?? [];
670
+ bucket.push(related);
671
+ all.set(lookup, bucket);
672
+ return all;
673
+ }, /* @__PURE__ */ new Map());
674
+ }
675
+ async loadLimitedThroughIndividually(metadata, query, throughRows) {
676
+ const intermediateByParent = /* @__PURE__ */ new Map();
677
+ throughRows.forEach((row) => {
678
+ const parentKey = row[metadata.firstKey];
679
+ const intermediateKey = row[metadata.secondLocalKey];
680
+ if (parentKey == null || intermediateKey == null) return;
681
+ const lookup = this.toLookupKey(parentKey);
682
+ const bucket = intermediateByParent.get(lookup) ?? [];
683
+ bucket.push(intermediateKey);
684
+ intermediateByParent.set(lookup, bucket);
685
+ });
686
+ const entries = await Promise.all([...intermediateByParent].map(async ([parentKey, intermediateKeys]) => {
687
+ return [parentKey, (await query.clone().whereIn(metadata.secondKey, intermediateKeys).get()).all()];
688
+ }));
689
+ return new Map(entries);
690
+ }
566
691
  /**
567
692
  * Loads an inverse polymorphic ("morph to") relationship for the set of
568
693
  * models. Parents are grouped by their morph type, so each distinct type
@@ -596,7 +721,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
596
721
  const ownerKey = metadata.ownerKey ?? relatedModel.getPrimaryKey();
597
722
  let query = relatedModel.query().whereIn(ownerKey, ids);
598
723
  query = this.applyConstraint(query, constraint);
599
- (await query.get()).all().forEach((related) => {
724
+ (await this.getPartitionedModels(query, ownerKey, ids)).forEach((related) => {
600
725
  const value = this.readModelAttribute(related, ownerKey);
601
726
  if (value == null) return;
602
727
  relatedByTypeAndKey.set(`${morphType}:${this.toLookupKey(value)}`, related);
@@ -640,6 +765,91 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
640
765
  model.setLoadedRelation(name, new ArkormCollection(bucket));
641
766
  });
642
767
  }
768
+ async loadPolymorphicManyToMany(name, metadata, constraint) {
769
+ const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
770
+ if (parentKeys.length === 0) {
771
+ this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
772
+ return;
773
+ }
774
+ const parentPivotKey = metadata.type === "morphToMany" ? metadata.morphIdColumn : metadata.foreignPivotKey;
775
+ const morphType = metadata.type === "morphToMany" ? this.morphTypeOf(this.models[0]) : metadata.relatedModel.name;
776
+ const pivotRows = await this.createRelationTableLoader().selectRows({
777
+ table: metadata.throughTable,
778
+ where: {
779
+ type: "group",
780
+ operator: "and",
781
+ conditions: [{
782
+ type: "comparison",
783
+ column: parentPivotKey,
784
+ operator: "in",
785
+ value: parentKeys
786
+ }, {
787
+ type: "comparison",
788
+ column: metadata.morphTypeColumn,
789
+ operator: "=",
790
+ value: morphType
791
+ }]
792
+ },
793
+ columns: [parentPivotKey, metadata.relatedPivotKey].map((column) => ({ column }))
794
+ });
795
+ const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
796
+ if (relatedIds.length === 0) {
797
+ this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
798
+ return;
799
+ }
800
+ let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
801
+ query = this.applyConstraint(query, constraint);
802
+ let pairs = pivotRows.map((row) => ({
803
+ parent: row[parentPivotKey],
804
+ related: row[metadata.relatedPivotKey]
805
+ }));
806
+ let relatedModels;
807
+ if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
808
+ const parentAlias = "__arkorm_parent_key";
809
+ const throughTable = metadata.throughTable;
810
+ const relatedTable = metadata.relatedModel.getTable();
811
+ const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${parentPivotKey}`, parentKeys).where({ [`${throughTable}.${metadata.morphTypeColumn}`]: morphType }).addSelect({ [`${throughTable}.${parentPivotKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${parentPivotKey}`]);
812
+ if (rows) {
813
+ pairs = rows.map((row) => ({
814
+ parent: row[parentAlias],
815
+ related: row[metadata.relatedKey]
816
+ }));
817
+ const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
818
+ relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
819
+ } else {
820
+ await this.loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows);
821
+ return;
822
+ }
823
+ } else relatedModels = (await query.get()).all();
824
+ const relatedByKey = /* @__PURE__ */ new Map();
825
+ relatedModels.forEach((related) => {
826
+ const key = this.readModelAttribute(related, metadata.relatedKey);
827
+ if (key != null) relatedByKey.set(this.toLookupKey(key), related);
828
+ });
829
+ const relatedByParent = /* @__PURE__ */ new Map();
830
+ pairs.forEach(({ parent, related }) => {
831
+ if (parent == null || related == null) return;
832
+ const model = relatedByKey.get(this.toLookupKey(related));
833
+ if (!model) return;
834
+ const lookup = this.toLookupKey(parent);
835
+ const bucket = relatedByParent.get(lookup) ?? [];
836
+ bucket.push(model);
837
+ relatedByParent.set(lookup, bucket);
838
+ });
839
+ this.models.forEach((model) => {
840
+ const parentKey = model.getAttribute(metadata.parentKey);
841
+ const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
842
+ model.setLoadedRelation(name, new ArkormCollection(related));
843
+ });
844
+ }
845
+ async loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows) {
846
+ await Promise.all(this.models.map(async (model) => {
847
+ const parentKey = model.getAttribute(metadata.parentKey);
848
+ const ids = pivotRows.filter((row) => this.toLookupKey(row[parentPivotKey]) === this.toLookupKey(parentKey)).map((row) => row[metadata.relatedPivotKey]);
849
+ const related = ids.length ? (await query.clone().whereIn(metadata.relatedKey, ids).get()).all() : [];
850
+ model.setLoadedRelation(name, new ArkormCollection(related));
851
+ }));
852
+ }
643
853
  /**
644
854
  * Batch-loads the children for a morphOne/morphMany relationship. Parents are
645
855
  * grouped by their own morph type (constructor name), so each distinct type
@@ -672,7 +882,7 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
672
882
  await Promise.all(Array.from(keysByType.entries()).map(async ([morphType, keys]) => {
673
883
  let query = metadata.relatedModel.query().whereIn(metadata.morphIdColumn, keys).where({ [metadata.morphTypeColumn]: morphType });
674
884
  query = this.applyConstraint(query, constraint);
675
- (await query.get()).all().forEach((related) => {
885
+ (await this.getPartitionedModels(query, metadata.morphIdColumn, keys)).forEach((related) => {
676
886
  const value = this.readModelAttribute(related, metadata.morphIdColumn);
677
887
  if (value == null) return;
678
888
  const key = `${morphType}:${this.toLookupKey(value)}`;
@@ -715,6 +925,14 @@ var SetBasedEagerLoader = class SetBasedEagerLoader {
715
925
  if (!constraint) return query;
716
926
  return constraint(query) ?? query;
717
927
  }
928
+ async getPartitionedModels(query, partitionColumn, partitionValues) {
929
+ if (partitionValues.length <= 1) return (await query.get()).all();
930
+ const partitioned = await query.getForEagerLoad([partitionColumn]);
931
+ if (partitioned) return partitioned.all();
932
+ return (await Promise.all(partitionValues.map(async (value) => {
933
+ return (await query.clone().where({ [partitionColumn]: value }).get()).all();
934
+ }))).flat();
935
+ }
718
936
  /**
719
937
  * Collects unique values from the set of models based on a resolver function, which
720
938
  * is used to extract the value from each model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkormx",
3
- "version": "2.11.11",
3
+ "version": "2.11.13",
4
4
  "description": "Modern TypeScript-first ORM for Node.js.",
5
5
  "keywords": [
6
6
  "orm",