@stonyx/orm 0.3.2-beta.69 → 0.3.2-beta.70
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/manage-record.js +23 -2
- package/dist/serializer.js +27 -2
- package/package.json +1 -1
- package/src/manage-record.ts +24 -2
- package/src/serializer.ts +27 -2
package/dist/manage-record.js
CHANGED
|
@@ -52,13 +52,34 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
|
|
|
52
52
|
relationship.push(record);
|
|
53
53
|
pendingHasMany.splice(0);
|
|
54
54
|
}
|
|
55
|
+
// FK-based inverse hasMany wiring — when a child record is created with a
|
|
56
|
+
// foreign-key field (e.g. `owner: 'owner-1'` on an animal), find any parent
|
|
57
|
+
// whose hasMany registry targets this model and push the child into the
|
|
58
|
+
// parent's shared array. This covers edge cases where the child is created
|
|
59
|
+
// in a separate async frame without a belongsTo handler firing.
|
|
60
|
+
const hasManyReg = getHasManyRegistry();
|
|
61
|
+
if (hasManyReg) {
|
|
62
|
+
for (const [parentModelName, targetMap] of hasManyReg) {
|
|
63
|
+
const childArrayMap = targetMap.get(modelName);
|
|
64
|
+
if (!childArrayMap)
|
|
65
|
+
continue;
|
|
66
|
+
// Check if rawData contains a FK field matching the parent model name
|
|
67
|
+
const fkValue = rawData[parentModelName];
|
|
68
|
+
if (fkValue === undefined || fkValue === null)
|
|
69
|
+
continue;
|
|
70
|
+
const parentArray = childArrayMap.get(fkValue);
|
|
71
|
+
if (parentArray && !parentArray.includes(record)) {
|
|
72
|
+
parentArray.push(record);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
55
76
|
// Fulfill pending belongsTo relationships
|
|
56
77
|
const pendingBelongsToQueue = getPendingBelongsToRegistry();
|
|
57
78
|
const pendingBelongsToRaw = pendingBelongsToQueue.get(modelName)?.get(record.id);
|
|
58
79
|
const pendingBelongsTo = Array.isArray(pendingBelongsToRaw) ? pendingBelongsToRaw : undefined;
|
|
59
80
|
if (pendingBelongsTo) {
|
|
60
81
|
const belongsToReg = getBelongsToRegistry();
|
|
61
|
-
const
|
|
82
|
+
const pendingHasManyReg = getHasManyRegistry();
|
|
62
83
|
for (const { sourceRecord, sourceModelName, relationshipKey, relationshipId } of pendingBelongsTo) {
|
|
63
84
|
// Update the belongsTo relationship on the source record
|
|
64
85
|
sourceRecord.__relationships[relationshipKey] = record;
|
|
@@ -72,7 +93,7 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
|
|
|
72
93
|
}
|
|
73
94
|
}
|
|
74
95
|
// Wire inverse hasMany if it exists
|
|
75
|
-
const inverseHasMany =
|
|
96
|
+
const inverseHasMany = pendingHasManyReg.get(modelName)?.get(sourceModelName)?.get(record.id);
|
|
76
97
|
if (inverseHasMany && !inverseHasMany.includes(sourceRecord)) {
|
|
77
98
|
inverseHasMany.push(sourceRecord);
|
|
78
99
|
}
|
package/dist/serializer.js
CHANGED
|
@@ -79,8 +79,33 @@ export default class Serializer {
|
|
|
79
79
|
// Pass relationship key name to handler for pending fulfillment
|
|
80
80
|
const handlerOptions = { ...options, _relationshipKey: key };
|
|
81
81
|
const childRecord = handler(record, data, handlerOptions);
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// hasMany relationships use a getter so format()/toJSON() always read
|
|
83
|
+
// the live registry array instead of a stale snapshot captured at
|
|
84
|
+
// serialization time. This is critical when child records are created
|
|
85
|
+
// in a later async frame — the belongsTo inverse wiring pushes into
|
|
86
|
+
// the shared registry array, and the getter ensures the parent sees it.
|
|
87
|
+
const isHasMany = handler.__relationshipType === 'hasMany';
|
|
88
|
+
if (isHasMany) {
|
|
89
|
+
// `childRecord` IS the shared registry array — define a getter that
|
|
90
|
+
// always dereferences through the same array reference.
|
|
91
|
+
const registryArray = childRecord;
|
|
92
|
+
Object.defineProperty(rec, key, {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
configurable: true,
|
|
95
|
+
get: () => registryArray,
|
|
96
|
+
set(v) { relatedRecords[key] = v; }
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(relatedRecords, key, {
|
|
99
|
+
enumerable: true,
|
|
100
|
+
configurable: true,
|
|
101
|
+
get: () => registryArray,
|
|
102
|
+
set(v) { Object.defineProperty(relatedRecords, key, { value: v, writable: true, enumerable: true, configurable: true }); }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
rec[key] = childRecord;
|
|
107
|
+
relatedRecords[key] = childRecord;
|
|
108
|
+
}
|
|
84
109
|
continue;
|
|
85
110
|
}
|
|
86
111
|
// Aggregate property handling — use the rawData value, not the aggregate descriptor
|
package/package.json
CHANGED
package/src/manage-record.ts
CHANGED
|
@@ -79,6 +79,28 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
|
|
|
79
79
|
pendingHasMany.splice(0);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// FK-based inverse hasMany wiring — when a child record is created with a
|
|
83
|
+
// foreign-key field (e.g. `owner: 'owner-1'` on an animal), find any parent
|
|
84
|
+
// whose hasMany registry targets this model and push the child into the
|
|
85
|
+
// parent's shared array. This covers edge cases where the child is created
|
|
86
|
+
// in a separate async frame without a belongsTo handler firing.
|
|
87
|
+
const hasManyReg = getHasManyRegistry();
|
|
88
|
+
if (hasManyReg) {
|
|
89
|
+
for (const [parentModelName, targetMap] of hasManyReg) {
|
|
90
|
+
const childArrayMap = targetMap.get(modelName);
|
|
91
|
+
if (!childArrayMap) continue;
|
|
92
|
+
|
|
93
|
+
// Check if rawData contains a FK field matching the parent model name
|
|
94
|
+
const fkValue = rawData[parentModelName];
|
|
95
|
+
if (fkValue === undefined || fkValue === null) continue;
|
|
96
|
+
|
|
97
|
+
const parentArray = childArrayMap.get(fkValue);
|
|
98
|
+
if (parentArray && !parentArray.includes(record)) {
|
|
99
|
+
parentArray.push(record);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
// Fulfill pending belongsTo relationships
|
|
83
105
|
const pendingBelongsToQueue = getPendingBelongsToRegistry();
|
|
84
106
|
const pendingBelongsToRaw = pendingBelongsToQueue.get(modelName)?.get(record.id);
|
|
@@ -86,7 +108,7 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
|
|
|
86
108
|
|
|
87
109
|
if (pendingBelongsTo) {
|
|
88
110
|
const belongsToReg = getBelongsToRegistry();
|
|
89
|
-
const
|
|
111
|
+
const pendingHasManyReg = getHasManyRegistry();
|
|
90
112
|
|
|
91
113
|
for (const { sourceRecord, sourceModelName, relationshipKey, relationshipId } of pendingBelongsTo) {
|
|
92
114
|
// Update the belongsTo relationship on the source record
|
|
@@ -103,7 +125,7 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
|
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
// Wire inverse hasMany if it exists
|
|
106
|
-
const inverseHasMany =
|
|
128
|
+
const inverseHasMany = pendingHasManyReg.get(modelName)?.get(sourceModelName)?.get(record.id);
|
|
107
129
|
|
|
108
130
|
if (inverseHasMany && !inverseHasMany.includes(sourceRecord)) {
|
|
109
131
|
inverseHasMany.push(sourceRecord);
|
package/src/serializer.ts
CHANGED
|
@@ -94,8 +94,33 @@ export default class Serializer {
|
|
|
94
94
|
const handlerOptions = { ...options, _relationshipKey: key };
|
|
95
95
|
const childRecord = handler(record, data, handlerOptions);
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// hasMany relationships use a getter so format()/toJSON() always read
|
|
98
|
+
// the live registry array instead of a stale snapshot captured at
|
|
99
|
+
// serialization time. This is critical when child records are created
|
|
100
|
+
// in a later async frame — the belongsTo inverse wiring pushes into
|
|
101
|
+
// the shared registry array, and the getter ensures the parent sees it.
|
|
102
|
+
const isHasMany = (handler as { __relationshipType?: string }).__relationshipType === 'hasMany';
|
|
103
|
+
|
|
104
|
+
if (isHasMany) {
|
|
105
|
+
// `childRecord` IS the shared registry array — define a getter that
|
|
106
|
+
// always dereferences through the same array reference.
|
|
107
|
+
const registryArray = childRecord as unknown[];
|
|
108
|
+
Object.defineProperty(rec, key, {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
get: () => registryArray,
|
|
112
|
+
set(v: unknown) { relatedRecords[key] = v; }
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(relatedRecords, key, {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
configurable: true,
|
|
117
|
+
get: () => registryArray,
|
|
118
|
+
set(v: unknown) { Object.defineProperty(relatedRecords, key, { value: v, writable: true, enumerable: true, configurable: true }); }
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
rec[key] = childRecord;
|
|
122
|
+
relatedRecords[key] = childRecord;
|
|
123
|
+
}
|
|
99
124
|
|
|
100
125
|
continue;
|
|
101
126
|
}
|