@getstrata/core 0.5.99 → 0.5.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.100
4
+
5
+ HiroApp dogfood of 0.5.99: eager `with()` / nested `load("a.b")` missed related rows when a Postgres int4 PK arrived as a JS `number` and an int8 FK as a `bigint`. Map matching used `===`.
6
+
7
+ - Relation indexers and eager attach compare keys with `relationMatchKey` (`1`, `1n`, `"1"` match).
8
+ - Sequential `load("position")` / `application.position()` already used SQL `get()` and were unaffected.
9
+ - `registerModelRepository(User, users)` already names `User` and `$morphClass`. Do not also call `registerModelClass("User", User)`. `registerModelClass` is only for an alias that is neither `constructor.name` nor `$morphClass`.
10
+
11
+ Still non-conforming (honest): Bun cannot infer `morphTo()` method names. `hashed` cast is a no-op. `Factory.has()` without a bound model still needs an explicit FK.
12
+
3
13
  ## 0.5.99
4
14
 
5
15
  HiroApp dogfood of 0.5.98 found lookalike APIs. This release matches Laravel call shape and SQL, not just export names.
@@ -25,6 +25,7 @@ type ModelObserver = {
25
25
  deleting?: (model: AnyModel) => unknown;
26
26
  deleted?: (model: AnyModel) => unknown;
27
27
  };
28
+ /** Alias for `hasMany("Name")` when `Name` is not `constructor.name` or `$morphClass`. */
28
29
  declare function registerModelClass(name: string, model: object): void;
29
30
  declare function hydrateValue(value: unknown, cast: CastType): unknown;
30
31
  declare function dehydrateValue(value: unknown, cast: CastType): unknown;
@@ -135,6 +136,7 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
135
136
  setLoaded(name: string, value: unknown): this;
136
137
  mergeAttributes(patch: Partial<TEntity>): this;
137
138
  }
139
+ /** Binds a Model class to its table/connection and names it for `hasMany("User")`. */
138
140
  declare function registerModelRepository<TModelClass>(model: TModelClass, repository: object): TModelClass;
139
141
  export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, } from "./relationQuery.ts";
140
142
  export type { CastType, GlobalScopeFn, ModelClassType, ModelConstructor };
@@ -48,6 +48,8 @@ declare function belongsToMany<TParent, TRelated, Pivot extends object, ParentKe
48
48
  foreignPivotKey: ForeignPivotKey;
49
49
  relatedPivotKey: RelatedPivotKey;
50
50
  }): BelongsToManyRelation<TParent, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>;
51
+ declare function relationMatchKey(value: unknown): string;
52
+ declare function getByRelationKey<K, V>(map: ReadonlyMap<K, V>, key: unknown): V | undefined;
51
53
  declare function indexHasManyRelation<TParent, TChild, LocalKey extends keyof TParent & string, ForeignKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: HasManyRelation<TParent, TChild, LocalKey, ForeignKey>): Map<TParent[LocalKey], TChild[]>;
52
54
  declare function indexHasOneRelation<TParent, TChild, LocalKey extends keyof TParent & string, ForeignKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: HasOneRelation<TParent, TChild, LocalKey, ForeignKey>): Map<TParent[LocalKey], TChild | undefined>;
53
55
  declare function indexBelongsToRelation<TChild, TParent, ForeignKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], parents: readonly TParent[], relation: BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>): Map<TChild[ForeignKey], TParent>;
@@ -95,6 +97,6 @@ declare function morphTo<TChild, MorphTypeKey extends keyof TChild & string = ke
95
97
  }): MorphToRelation<TChild, MorphTypeKey, MorphIdKey>;
96
98
  declare function indexMorphManyRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphManyRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild[]>;
97
99
  declare function indexMorphOneRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphOneRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild | undefined>;
98
- declare function indexMorphToRelation<TChild, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], parentsByType: ReadonlyMap<string, ReadonlyMap<TParent[OwnerKey], TParent>>, relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>): Map<TChild[MorphIdKey], TParent>;
100
+ declare function indexMorphToRelation<TChild, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, _OwnerKey extends keyof TParent & string = keyof TParent & string>(children: readonly TChild[], parentsByType: ReadonlyMap<string, ReadonlyMap<unknown, TParent>>, relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>): Map<TChild[MorphIdKey], TParent>;
99
101
  export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, };
100
- export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, };
102
+ export { belongsTo, belongsToMany, getByRelationKey, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, relationMatchKey, };
@@ -982,26 +982,67 @@ function belongsToMany(definition) {
982
982
  ...definition
983
983
  };
984
984
  }
985
+ function relationMatchKey(value) {
986
+ if (value === null || value === undefined) {
987
+ return "";
988
+ }
989
+ if (typeof value === "bigint") {
990
+ return value.toString();
991
+ }
992
+ if (typeof value === "number" && Number.isFinite(value)) {
993
+ return String(value);
994
+ }
995
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
996
+ return BigInt(value).toString();
997
+ }
998
+ return String(value);
999
+ }
1000
+ function getByRelationKey(map, key) {
1001
+ if (map.has(key)) {
1002
+ return map.get(key);
1003
+ }
1004
+ const want = relationMatchKey(key);
1005
+ if (want === "") {
1006
+ return;
1007
+ }
1008
+ for (const [existing, value] of map) {
1009
+ if (relationMatchKey(existing) === want) {
1010
+ return value;
1011
+ }
1012
+ }
1013
+ return;
1014
+ }
985
1015
  function indexHasManyRelation(parents, children, relation) {
986
1016
  const groups = new Map;
1017
+ const originalKeys = new Map;
987
1018
  for (const parent of parents) {
988
- groups.set(parent[relation.localKey], []);
1019
+ const key = relationMatchKey(parent[relation.localKey]);
1020
+ if (!groups.has(key)) {
1021
+ groups.set(key, []);
1022
+ originalKeys.set(key, parent[relation.localKey]);
1023
+ }
989
1024
  }
990
1025
  for (const child of children) {
991
- const key = child[relation.foreignKey];
992
- const group = groups.get(key);
1026
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
993
1027
  if (!group) {
994
1028
  continue;
995
1029
  }
996
1030
  group.push(child);
997
1031
  }
998
- return groups;
1032
+ const result = new Map;
1033
+ for (const [key, group] of groups) {
1034
+ const original = originalKeys.get(key);
1035
+ if (original !== undefined) {
1036
+ result.set(original, group);
1037
+ }
1038
+ }
1039
+ return result;
999
1040
  }
1000
1041
  function indexHasOneRelation(parents, children, relation) {
1001
1042
  const grouped = indexHasManyRelation(parents, children, relation);
1002
1043
  const result = new Map;
1003
1044
  for (const parent of parents) {
1004
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1045
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
1005
1046
  result.set(parent[relation.localKey], matches[0]);
1006
1047
  }
1007
1048
  return result;
@@ -1009,12 +1050,12 @@ function indexHasOneRelation(parents, children, relation) {
1009
1050
  function indexBelongsToRelation(children, parents, relation) {
1010
1051
  const parentsById = new Map;
1011
1052
  for (const parent of parents) {
1012
- parentsById.set(parent[relation.ownerKey], parent);
1053
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
1013
1054
  }
1014
1055
  const result = new Map;
1015
1056
  for (const child of children) {
1016
1057
  const foreignKey = child[relation.foreignKey];
1017
- const parent = parentsById.get(foreignKey);
1058
+ const parent = parentsById.get(relationMatchKey(foreignKey));
1018
1059
  if (parent) {
1019
1060
  result.set(foreignKey, parent);
1020
1061
  }
@@ -1024,23 +1065,33 @@ function indexBelongsToRelation(children, parents, relation) {
1024
1065
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
1025
1066
  const relatedById = new Map;
1026
1067
  for (const related of relatedRows) {
1027
- relatedById.set(related[relation.relatedKey], related);
1068
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
1028
1069
  }
1029
1070
  const groups = new Map;
1071
+ const originalKeys = new Map;
1030
1072
  for (const parent of parents) {
1031
- groups.set(parent[relation.parentKey], []);
1073
+ const key = relationMatchKey(parent[relation.parentKey]);
1074
+ if (!groups.has(key)) {
1075
+ groups.set(key, []);
1076
+ originalKeys.set(key, parent[relation.parentKey]);
1077
+ }
1032
1078
  }
1033
1079
  for (const pivot of pivotRows) {
1034
- const parentId = pivot[relation.foreignPivotKey];
1035
- const relatedId = pivot[relation.relatedPivotKey];
1036
- const group = groups.get(parentId);
1037
- const related = relatedById.get(relatedId);
1080
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
1081
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
1038
1082
  if (!group || !related) {
1039
1083
  continue;
1040
1084
  }
1041
1085
  group.push(related);
1042
1086
  }
1043
- return groups;
1087
+ const result = new Map;
1088
+ for (const [key, group] of groups) {
1089
+ const original = originalKeys.get(key);
1090
+ if (original !== undefined) {
1091
+ result.set(original, group);
1092
+ }
1093
+ }
1094
+ return result;
1044
1095
  }
1045
1096
  function morphMany(definition) {
1046
1097
  return {
@@ -1062,27 +1113,38 @@ function morphTo(definition) {
1062
1113
  }
1063
1114
  function indexMorphManyRelation(parents, children, relation) {
1064
1115
  const groups = new Map;
1116
+ const originalKeys = new Map;
1065
1117
  for (const parent of parents) {
1066
- groups.set(parent[relation.localKey], []);
1118
+ const key = relationMatchKey(parent[relation.localKey]);
1119
+ if (!groups.has(key)) {
1120
+ groups.set(key, []);
1121
+ originalKeys.set(key, parent[relation.localKey]);
1122
+ }
1067
1123
  }
1068
1124
  for (const child of children) {
1069
1125
  if (child[relation.morphTypeKey] !== relation.morphType) {
1070
1126
  continue;
1071
1127
  }
1072
- const key = child[relation.morphIdKey];
1073
- const group = groups.get(key);
1128
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
1074
1129
  if (!group) {
1075
1130
  continue;
1076
1131
  }
1077
1132
  group.push(child);
1078
1133
  }
1079
- return groups;
1134
+ const result = new Map;
1135
+ for (const [key, group] of groups) {
1136
+ const original = originalKeys.get(key);
1137
+ if (original !== undefined) {
1138
+ result.set(original, group);
1139
+ }
1140
+ }
1141
+ return result;
1080
1142
  }
1081
1143
  function indexMorphOneRelation(parents, children, relation) {
1082
1144
  const grouped = indexMorphManyRelation(parents, children, relation);
1083
1145
  const result = new Map;
1084
1146
  for (const parent of parents) {
1085
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1147
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
1086
1148
  result.set(parent[relation.localKey], matches[0]);
1087
1149
  }
1088
1150
  return result;
@@ -1095,7 +1157,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
1095
1157
  if (!parents) {
1096
1158
  continue;
1097
1159
  }
1098
- const parent = parents.get(child[relation.morphIdKey]);
1160
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
1099
1161
  if (parent) {
1100
1162
  result.set(child[relation.morphIdKey], parent);
1101
1163
  }
@@ -1762,7 +1824,7 @@ class Model {
1762
1824
  }
1763
1825
  async loadHasMany(as, relation, childRepository, options = {}) {
1764
1826
  const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
1765
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
1827
+ const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
1766
1828
  return Object.assign(this, { [as]: loaded });
1767
1829
  }
1768
1830
  async loadHasOne(as, relation, childRepository, options = {}) {
@@ -1772,7 +1834,7 @@ class Model {
1772
1834
  }
1773
1835
  async loadBelongsTo(as, relation, parentRepository, options = {}) {
1774
1836
  const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
1775
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
1837
+ const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
1776
1838
  return Object.assign(this, { [as]: loaded });
1777
1839
  }
1778
1840
  async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
@@ -1792,7 +1854,7 @@ class Model {
1792
1854
  }
1793
1855
  });
1794
1856
  const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
1795
- const loaded = grouped.get(parentId) ?? [];
1857
+ const loaded = getByRelationKey(grouped, parentId) ?? [];
1796
1858
  return Object.assign(this, { [as]: loaded });
1797
1859
  }
1798
1860
  hasMany(related, foreignKey, localKey) {
@@ -24,26 +24,67 @@ function belongsToMany(definition) {
24
24
  ...definition
25
25
  };
26
26
  }
27
+ function relationMatchKey(value) {
28
+ if (value === null || value === undefined) {
29
+ return "";
30
+ }
31
+ if (typeof value === "bigint") {
32
+ return value.toString();
33
+ }
34
+ if (typeof value === "number" && Number.isFinite(value)) {
35
+ return String(value);
36
+ }
37
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
38
+ return BigInt(value).toString();
39
+ }
40
+ return String(value);
41
+ }
42
+ function getByRelationKey(map, key) {
43
+ if (map.has(key)) {
44
+ return map.get(key);
45
+ }
46
+ const want = relationMatchKey(key);
47
+ if (want === "") {
48
+ return;
49
+ }
50
+ for (const [existing, value] of map) {
51
+ if (relationMatchKey(existing) === want) {
52
+ return value;
53
+ }
54
+ }
55
+ return;
56
+ }
27
57
  function indexHasManyRelation(parents, children, relation) {
28
58
  const groups = new Map;
59
+ const originalKeys = new Map;
29
60
  for (const parent of parents) {
30
- groups.set(parent[relation.localKey], []);
61
+ const key = relationMatchKey(parent[relation.localKey]);
62
+ if (!groups.has(key)) {
63
+ groups.set(key, []);
64
+ originalKeys.set(key, parent[relation.localKey]);
65
+ }
31
66
  }
32
67
  for (const child of children) {
33
- const key = child[relation.foreignKey];
34
- const group = groups.get(key);
68
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
35
69
  if (!group) {
36
70
  continue;
37
71
  }
38
72
  group.push(child);
39
73
  }
40
- return groups;
74
+ const result = new Map;
75
+ for (const [key, group] of groups) {
76
+ const original = originalKeys.get(key);
77
+ if (original !== undefined) {
78
+ result.set(original, group);
79
+ }
80
+ }
81
+ return result;
41
82
  }
42
83
  function indexHasOneRelation(parents, children, relation) {
43
84
  const grouped = indexHasManyRelation(parents, children, relation);
44
85
  const result = new Map;
45
86
  for (const parent of parents) {
46
- const matches = grouped.get(parent[relation.localKey]) ?? [];
87
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
47
88
  result.set(parent[relation.localKey], matches[0]);
48
89
  }
49
90
  return result;
@@ -51,12 +92,12 @@ function indexHasOneRelation(parents, children, relation) {
51
92
  function indexBelongsToRelation(children, parents, relation) {
52
93
  const parentsById = new Map;
53
94
  for (const parent of parents) {
54
- parentsById.set(parent[relation.ownerKey], parent);
95
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
55
96
  }
56
97
  const result = new Map;
57
98
  for (const child of children) {
58
99
  const foreignKey = child[relation.foreignKey];
59
- const parent = parentsById.get(foreignKey);
100
+ const parent = parentsById.get(relationMatchKey(foreignKey));
60
101
  if (parent) {
61
102
  result.set(foreignKey, parent);
62
103
  }
@@ -66,23 +107,33 @@ function indexBelongsToRelation(children, parents, relation) {
66
107
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
67
108
  const relatedById = new Map;
68
109
  for (const related of relatedRows) {
69
- relatedById.set(related[relation.relatedKey], related);
110
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
70
111
  }
71
112
  const groups = new Map;
113
+ const originalKeys = new Map;
72
114
  for (const parent of parents) {
73
- groups.set(parent[relation.parentKey], []);
115
+ const key = relationMatchKey(parent[relation.parentKey]);
116
+ if (!groups.has(key)) {
117
+ groups.set(key, []);
118
+ originalKeys.set(key, parent[relation.parentKey]);
119
+ }
74
120
  }
75
121
  for (const pivot of pivotRows) {
76
- const parentId = pivot[relation.foreignPivotKey];
77
- const relatedId = pivot[relation.relatedPivotKey];
78
- const group = groups.get(parentId);
79
- const related = relatedById.get(relatedId);
122
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
123
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
80
124
  if (!group || !related) {
81
125
  continue;
82
126
  }
83
127
  group.push(related);
84
128
  }
85
- return groups;
129
+ const result = new Map;
130
+ for (const [key, group] of groups) {
131
+ const original = originalKeys.get(key);
132
+ if (original !== undefined) {
133
+ result.set(original, group);
134
+ }
135
+ }
136
+ return result;
86
137
  }
87
138
  function morphMany(definition) {
88
139
  return {
@@ -104,27 +155,38 @@ function morphTo(definition) {
104
155
  }
105
156
  function indexMorphManyRelation(parents, children, relation) {
106
157
  const groups = new Map;
158
+ const originalKeys = new Map;
107
159
  for (const parent of parents) {
108
- groups.set(parent[relation.localKey], []);
160
+ const key = relationMatchKey(parent[relation.localKey]);
161
+ if (!groups.has(key)) {
162
+ groups.set(key, []);
163
+ originalKeys.set(key, parent[relation.localKey]);
164
+ }
109
165
  }
110
166
  for (const child of children) {
111
167
  if (child[relation.morphTypeKey] !== relation.morphType) {
112
168
  continue;
113
169
  }
114
- const key = child[relation.morphIdKey];
115
- const group = groups.get(key);
170
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
116
171
  if (!group) {
117
172
  continue;
118
173
  }
119
174
  group.push(child);
120
175
  }
121
- return groups;
176
+ const result = new Map;
177
+ for (const [key, group] of groups) {
178
+ const original = originalKeys.get(key);
179
+ if (original !== undefined) {
180
+ result.set(original, group);
181
+ }
182
+ }
183
+ return result;
122
184
  }
123
185
  function indexMorphOneRelation(parents, children, relation) {
124
186
  const grouped = indexMorphManyRelation(parents, children, relation);
125
187
  const result = new Map;
126
188
  for (const parent of parents) {
127
- const matches = grouped.get(parent[relation.localKey]) ?? [];
189
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
128
190
  result.set(parent[relation.localKey], matches[0]);
129
191
  }
130
192
  return result;
@@ -137,7 +199,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
137
199
  if (!parents) {
138
200
  continue;
139
201
  }
140
- const parent = parents.get(child[relation.morphIdKey]);
202
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
141
203
  if (parent) {
142
204
  result.set(child[relation.morphIdKey], parent);
143
205
  }
@@ -147,6 +209,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
147
209
  export {
148
210
  belongsTo,
149
211
  belongsToMany,
212
+ getByRelationKey,
150
213
  hasMany,
151
214
  hasOne,
152
215
  indexBelongsToManyRelation,
@@ -158,5 +221,6 @@ export {
158
221
  indexMorphToRelation,
159
222
  morphMany,
160
223
  morphOne,
161
- morphTo
224
+ morphTo,
225
+ relationMatchKey
162
226
  };
@@ -422,6 +422,214 @@ function buildDeleteByIdQuery(table, id) {
422
422
  };
423
423
  }
424
424
 
425
+ // ../../src/core/database/relationships.ts
426
+ function hasMany(definition) {
427
+ return {
428
+ type: "hasMany",
429
+ ...definition
430
+ };
431
+ }
432
+ function hasOne(definition) {
433
+ return {
434
+ type: "hasOne",
435
+ ...definition
436
+ };
437
+ }
438
+ function belongsTo(definition) {
439
+ return {
440
+ type: "belongsTo",
441
+ ...definition
442
+ };
443
+ }
444
+ function belongsToMany(definition) {
445
+ return {
446
+ type: "belongsToMany",
447
+ ...definition
448
+ };
449
+ }
450
+ function relationMatchKey(value) {
451
+ if (value === null || value === undefined) {
452
+ return "";
453
+ }
454
+ if (typeof value === "bigint") {
455
+ return value.toString();
456
+ }
457
+ if (typeof value === "number" && Number.isFinite(value)) {
458
+ return String(value);
459
+ }
460
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
461
+ return BigInt(value).toString();
462
+ }
463
+ return String(value);
464
+ }
465
+ function getByRelationKey(map, key) {
466
+ if (map.has(key)) {
467
+ return map.get(key);
468
+ }
469
+ const want = relationMatchKey(key);
470
+ if (want === "") {
471
+ return;
472
+ }
473
+ for (const [existing, value] of map) {
474
+ if (relationMatchKey(existing) === want) {
475
+ return value;
476
+ }
477
+ }
478
+ return;
479
+ }
480
+ function indexHasManyRelation(parents, children, relation) {
481
+ const groups = new Map;
482
+ const originalKeys = new Map;
483
+ for (const parent of parents) {
484
+ const key = relationMatchKey(parent[relation.localKey]);
485
+ if (!groups.has(key)) {
486
+ groups.set(key, []);
487
+ originalKeys.set(key, parent[relation.localKey]);
488
+ }
489
+ }
490
+ for (const child of children) {
491
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
492
+ if (!group) {
493
+ continue;
494
+ }
495
+ group.push(child);
496
+ }
497
+ const result = new Map;
498
+ for (const [key, group] of groups) {
499
+ const original = originalKeys.get(key);
500
+ if (original !== undefined) {
501
+ result.set(original, group);
502
+ }
503
+ }
504
+ return result;
505
+ }
506
+ function indexHasOneRelation(parents, children, relation) {
507
+ const grouped = indexHasManyRelation(parents, children, relation);
508
+ const result = new Map;
509
+ for (const parent of parents) {
510
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
511
+ result.set(parent[relation.localKey], matches[0]);
512
+ }
513
+ return result;
514
+ }
515
+ function indexBelongsToRelation(children, parents, relation) {
516
+ const parentsById = new Map;
517
+ for (const parent of parents) {
518
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
519
+ }
520
+ const result = new Map;
521
+ for (const child of children) {
522
+ const foreignKey = child[relation.foreignKey];
523
+ const parent = parentsById.get(relationMatchKey(foreignKey));
524
+ if (parent) {
525
+ result.set(foreignKey, parent);
526
+ }
527
+ }
528
+ return result;
529
+ }
530
+ function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
531
+ const relatedById = new Map;
532
+ for (const related of relatedRows) {
533
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
534
+ }
535
+ const groups = new Map;
536
+ const originalKeys = new Map;
537
+ for (const parent of parents) {
538
+ const key = relationMatchKey(parent[relation.parentKey]);
539
+ if (!groups.has(key)) {
540
+ groups.set(key, []);
541
+ originalKeys.set(key, parent[relation.parentKey]);
542
+ }
543
+ }
544
+ for (const pivot of pivotRows) {
545
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
546
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
547
+ if (!group || !related) {
548
+ continue;
549
+ }
550
+ group.push(related);
551
+ }
552
+ const result = new Map;
553
+ for (const [key, group] of groups) {
554
+ const original = originalKeys.get(key);
555
+ if (original !== undefined) {
556
+ result.set(original, group);
557
+ }
558
+ }
559
+ return result;
560
+ }
561
+ function morphMany(definition) {
562
+ return {
563
+ type: "morphMany",
564
+ ...definition
565
+ };
566
+ }
567
+ function morphOne(definition) {
568
+ return {
569
+ type: "morphOne",
570
+ ...definition
571
+ };
572
+ }
573
+ function morphTo(definition) {
574
+ return {
575
+ type: "morphTo",
576
+ ...definition
577
+ };
578
+ }
579
+ function indexMorphManyRelation(parents, children, relation) {
580
+ const groups = new Map;
581
+ const originalKeys = new Map;
582
+ for (const parent of parents) {
583
+ const key = relationMatchKey(parent[relation.localKey]);
584
+ if (!groups.has(key)) {
585
+ groups.set(key, []);
586
+ originalKeys.set(key, parent[relation.localKey]);
587
+ }
588
+ }
589
+ for (const child of children) {
590
+ if (child[relation.morphTypeKey] !== relation.morphType) {
591
+ continue;
592
+ }
593
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
594
+ if (!group) {
595
+ continue;
596
+ }
597
+ group.push(child);
598
+ }
599
+ const result = new Map;
600
+ for (const [key, group] of groups) {
601
+ const original = originalKeys.get(key);
602
+ if (original !== undefined) {
603
+ result.set(original, group);
604
+ }
605
+ }
606
+ return result;
607
+ }
608
+ function indexMorphOneRelation(parents, children, relation) {
609
+ const grouped = indexMorphManyRelation(parents, children, relation);
610
+ const result = new Map;
611
+ for (const parent of parents) {
612
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
613
+ result.set(parent[relation.localKey], matches[0]);
614
+ }
615
+ return result;
616
+ }
617
+ function indexMorphToRelation(children, parentsByType, relation) {
618
+ const result = new Map;
619
+ for (const child of children) {
620
+ const morphType = String(child[relation.morphTypeKey]);
621
+ const parents = parentsByType.get(morphType);
622
+ if (!parents) {
623
+ continue;
624
+ }
625
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
626
+ if (parent) {
627
+ result.set(child[relation.morphIdKey], parent);
628
+ }
629
+ }
630
+ return result;
631
+ }
632
+
425
633
  // ../../src/core/database/whereBuilder.ts
426
634
  class WhereBuilder {
427
635
  nodes = [];
@@ -652,7 +860,7 @@ class RepositoryQuery {
652
860
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
653
861
  result = result.map((row) => ({
654
862
  ...row,
655
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
863
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
656
864
  }));
657
865
  continue;
658
866
  }
@@ -661,7 +869,7 @@ class RepositoryQuery {
661
869
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
662
870
  result = result.map((row) => ({
663
871
  ...row,
664
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
872
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
665
873
  }));
666
874
  continue;
667
875
  }
@@ -670,7 +878,7 @@ class RepositoryQuery {
670
878
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
671
879
  result = result.map((row) => ({
672
880
  ...row,
673
- [load.as]: grouped2.get(row[relation2.localKey])
881
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey])
674
882
  }));
675
883
  continue;
676
884
  }
@@ -679,7 +887,7 @@ class RepositoryQuery {
679
887
  const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
680
888
  result = result.map((row) => ({
681
889
  ...row,
682
- [load.as]: grouped2.get(row[relation2.parentKey]) ?? []
890
+ [load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
683
891
  }));
684
892
  continue;
685
893
  }
@@ -688,7 +896,7 @@ class RepositoryQuery {
688
896
  const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
689
897
  result = result.map((row) => ({
690
898
  ...row,
691
- [load.as]: grouped2.get(row[relation2.morphIdKey])
899
+ [load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
692
900
  }));
693
901
  continue;
694
902
  }
@@ -696,7 +904,7 @@ class RepositoryQuery {
696
904
  const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
697
905
  result = result.map((row) => ({
698
906
  ...row,
699
- [load.as]: grouped.get(row[relation.foreignKey])
907
+ [load.as]: getByRelationKey(grouped, row[relation.foreignKey])
700
908
  }));
701
909
  }
702
910
  return result;
package/dist/index.js CHANGED
@@ -2084,26 +2084,67 @@ function belongsToMany(definition) {
2084
2084
  ...definition
2085
2085
  };
2086
2086
  }
2087
+ function relationMatchKey(value) {
2088
+ if (value === null || value === undefined) {
2089
+ return "";
2090
+ }
2091
+ if (typeof value === "bigint") {
2092
+ return value.toString();
2093
+ }
2094
+ if (typeof value === "number" && Number.isFinite(value)) {
2095
+ return String(value);
2096
+ }
2097
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
2098
+ return BigInt(value).toString();
2099
+ }
2100
+ return String(value);
2101
+ }
2102
+ function getByRelationKey(map, key) {
2103
+ if (map.has(key)) {
2104
+ return map.get(key);
2105
+ }
2106
+ const want = relationMatchKey(key);
2107
+ if (want === "") {
2108
+ return;
2109
+ }
2110
+ for (const [existing, value] of map) {
2111
+ if (relationMatchKey(existing) === want) {
2112
+ return value;
2113
+ }
2114
+ }
2115
+ return;
2116
+ }
2087
2117
  function indexHasManyRelation(parents, children, relation) {
2088
2118
  const groups = new Map;
2119
+ const originalKeys = new Map;
2089
2120
  for (const parent of parents) {
2090
- groups.set(parent[relation.localKey], []);
2121
+ const key = relationMatchKey(parent[relation.localKey]);
2122
+ if (!groups.has(key)) {
2123
+ groups.set(key, []);
2124
+ originalKeys.set(key, parent[relation.localKey]);
2125
+ }
2091
2126
  }
2092
2127
  for (const child of children) {
2093
- const key = child[relation.foreignKey];
2094
- const group = groups.get(key);
2128
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
2095
2129
  if (!group) {
2096
2130
  continue;
2097
2131
  }
2098
2132
  group.push(child);
2099
2133
  }
2100
- return groups;
2134
+ const result = new Map;
2135
+ for (const [key, group] of groups) {
2136
+ const original = originalKeys.get(key);
2137
+ if (original !== undefined) {
2138
+ result.set(original, group);
2139
+ }
2140
+ }
2141
+ return result;
2101
2142
  }
2102
2143
  function indexHasOneRelation(parents, children, relation) {
2103
2144
  const grouped = indexHasManyRelation(parents, children, relation);
2104
2145
  const result = new Map;
2105
2146
  for (const parent of parents) {
2106
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2147
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2107
2148
  result.set(parent[relation.localKey], matches[0]);
2108
2149
  }
2109
2150
  return result;
@@ -2111,12 +2152,12 @@ function indexHasOneRelation(parents, children, relation) {
2111
2152
  function indexBelongsToRelation(children, parents, relation) {
2112
2153
  const parentsById = new Map;
2113
2154
  for (const parent of parents) {
2114
- parentsById.set(parent[relation.ownerKey], parent);
2155
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
2115
2156
  }
2116
2157
  const result = new Map;
2117
2158
  for (const child of children) {
2118
2159
  const foreignKey = child[relation.foreignKey];
2119
- const parent = parentsById.get(foreignKey);
2160
+ const parent = parentsById.get(relationMatchKey(foreignKey));
2120
2161
  if (parent) {
2121
2162
  result.set(foreignKey, parent);
2122
2163
  }
@@ -2126,23 +2167,33 @@ function indexBelongsToRelation(children, parents, relation) {
2126
2167
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
2127
2168
  const relatedById = new Map;
2128
2169
  for (const related of relatedRows) {
2129
- relatedById.set(related[relation.relatedKey], related);
2170
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
2130
2171
  }
2131
2172
  const groups = new Map;
2173
+ const originalKeys = new Map;
2132
2174
  for (const parent of parents) {
2133
- groups.set(parent[relation.parentKey], []);
2175
+ const key = relationMatchKey(parent[relation.parentKey]);
2176
+ if (!groups.has(key)) {
2177
+ groups.set(key, []);
2178
+ originalKeys.set(key, parent[relation.parentKey]);
2179
+ }
2134
2180
  }
2135
2181
  for (const pivot of pivotRows) {
2136
- const parentId = pivot[relation.foreignPivotKey];
2137
- const relatedId = pivot[relation.relatedPivotKey];
2138
- const group = groups.get(parentId);
2139
- const related = relatedById.get(relatedId);
2182
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
2183
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
2140
2184
  if (!group || !related) {
2141
2185
  continue;
2142
2186
  }
2143
2187
  group.push(related);
2144
2188
  }
2145
- return groups;
2189
+ const result = new Map;
2190
+ for (const [key, group] of groups) {
2191
+ const original = originalKeys.get(key);
2192
+ if (original !== undefined) {
2193
+ result.set(original, group);
2194
+ }
2195
+ }
2196
+ return result;
2146
2197
  }
2147
2198
  function morphMany(definition) {
2148
2199
  return {
@@ -2164,27 +2215,38 @@ function morphTo(definition) {
2164
2215
  }
2165
2216
  function indexMorphManyRelation(parents, children, relation) {
2166
2217
  const groups = new Map;
2218
+ const originalKeys = new Map;
2167
2219
  for (const parent of parents) {
2168
- groups.set(parent[relation.localKey], []);
2220
+ const key = relationMatchKey(parent[relation.localKey]);
2221
+ if (!groups.has(key)) {
2222
+ groups.set(key, []);
2223
+ originalKeys.set(key, parent[relation.localKey]);
2224
+ }
2169
2225
  }
2170
2226
  for (const child of children) {
2171
2227
  if (child[relation.morphTypeKey] !== relation.morphType) {
2172
2228
  continue;
2173
2229
  }
2174
- const key = child[relation.morphIdKey];
2175
- const group = groups.get(key);
2230
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
2176
2231
  if (!group) {
2177
2232
  continue;
2178
2233
  }
2179
2234
  group.push(child);
2180
2235
  }
2181
- return groups;
2236
+ const result = new Map;
2237
+ for (const [key, group] of groups) {
2238
+ const original = originalKeys.get(key);
2239
+ if (original !== undefined) {
2240
+ result.set(original, group);
2241
+ }
2242
+ }
2243
+ return result;
2182
2244
  }
2183
2245
  function indexMorphOneRelation(parents, children, relation) {
2184
2246
  const grouped = indexMorphManyRelation(parents, children, relation);
2185
2247
  const result = new Map;
2186
2248
  for (const parent of parents) {
2187
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2249
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2188
2250
  result.set(parent[relation.localKey], matches[0]);
2189
2251
  }
2190
2252
  return result;
@@ -2197,7 +2259,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
2197
2259
  if (!parents) {
2198
2260
  continue;
2199
2261
  }
2200
- const parent = parents.get(child[relation.morphIdKey]);
2262
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
2201
2263
  if (parent) {
2202
2264
  result.set(child[relation.morphIdKey], parent);
2203
2265
  }
@@ -2435,7 +2497,7 @@ class RepositoryQuery {
2435
2497
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2436
2498
  result = result.map((row) => ({
2437
2499
  ...row,
2438
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
2500
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2439
2501
  }));
2440
2502
  continue;
2441
2503
  }
@@ -2444,7 +2506,7 @@ class RepositoryQuery {
2444
2506
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2445
2507
  result = result.map((row) => ({
2446
2508
  ...row,
2447
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
2509
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2448
2510
  }));
2449
2511
  continue;
2450
2512
  }
@@ -2453,7 +2515,7 @@ class RepositoryQuery {
2453
2515
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2454
2516
  result = result.map((row) => ({
2455
2517
  ...row,
2456
- [load.as]: grouped2.get(row[relation2.localKey])
2518
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey])
2457
2519
  }));
2458
2520
  continue;
2459
2521
  }
@@ -2462,7 +2524,7 @@ class RepositoryQuery {
2462
2524
  const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
2463
2525
  result = result.map((row) => ({
2464
2526
  ...row,
2465
- [load.as]: grouped2.get(row[relation2.parentKey]) ?? []
2527
+ [load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
2466
2528
  }));
2467
2529
  continue;
2468
2530
  }
@@ -2471,7 +2533,7 @@ class RepositoryQuery {
2471
2533
  const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
2472
2534
  result = result.map((row) => ({
2473
2535
  ...row,
2474
- [load.as]: grouped2.get(row[relation2.morphIdKey])
2536
+ [load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
2475
2537
  }));
2476
2538
  continue;
2477
2539
  }
@@ -2479,7 +2541,7 @@ class RepositoryQuery {
2479
2541
  const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
2480
2542
  result = result.map((row) => ({
2481
2543
  ...row,
2482
- [load.as]: grouped.get(row[relation.foreignKey])
2544
+ [load.as]: getByRelationKey(grouped, row[relation.foreignKey])
2483
2545
  }));
2484
2546
  }
2485
2547
  return result;
@@ -2770,7 +2832,7 @@ class BaseRepository {
2770
2832
  const grouped = await this.loadMorphManyForParents(parents, relation, options);
2771
2833
  const result = new Map;
2772
2834
  for (const parent of parents) {
2773
- const matches = grouped.get(parent[relation.localKey]) ?? [];
2835
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
2774
2836
  result.set(parent[relation.localKey], matches[0]);
2775
2837
  }
2776
2838
  return result;
@@ -2799,7 +2861,7 @@ class BaseRepository {
2799
2861
  }, options);
2800
2862
  const indexed = new Map;
2801
2863
  for (const parent of parents) {
2802
- indexed.set(parent[ownerKey], parent);
2864
+ indexed.set(relationMatchKey(parent[ownerKey]), parent);
2803
2865
  }
2804
2866
  parentsByType.set(morphType, indexed);
2805
2867
  }
@@ -4313,7 +4375,7 @@ class Model {
4313
4375
  }
4314
4376
  async loadHasMany(as, relation, childRepository, options = {}) {
4315
4377
  const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
4316
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
4378
+ const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
4317
4379
  return Object.assign(this, { [as]: loaded });
4318
4380
  }
4319
4381
  async loadHasOne(as, relation, childRepository, options = {}) {
@@ -4323,7 +4385,7 @@ class Model {
4323
4385
  }
4324
4386
  async loadBelongsTo(as, relation, parentRepository, options = {}) {
4325
4387
  const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
4326
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
4388
+ const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
4327
4389
  return Object.assign(this, { [as]: loaded });
4328
4390
  }
4329
4391
  async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
@@ -4343,7 +4405,7 @@ class Model {
4343
4405
  }
4344
4406
  });
4345
4407
  const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
4346
- const loaded = grouped.get(parentId) ?? [];
4408
+ const loaded = getByRelationKey(grouped, parentId) ?? [];
4347
4409
  return Object.assign(this, { [as]: loaded });
4348
4410
  }
4349
4411
  hasMany(related, foreignKey, localKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.99",
3
+ "version": "0.5.100",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",