@stonyx/orm 0.3.2-beta.76 → 0.3.2-beta.78

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.
@@ -104,6 +104,9 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
104
104
  // Auto-persist to SQL — skip for DB loads (isDbRecord) and relationship resolution (_relationshipKey)
105
105
  const shouldPersist = orm?.sqlDb && !options.isDbRecord && !userOptions._relationshipKey && !options._skipAutoPersist;
106
106
  if (shouldPersist) {
107
+ // Capture ID before persist — SQL adapters re-key pending IDs to real DB IDs,
108
+ // but relationship registries were keyed with this original ID
109
+ const registryId = record.id;
107
110
  const response = { data: { id: record.id } };
108
111
  orm.sqlDb.persist('create', modelName, { rawData }, response)
109
112
  .catch((err) => {
@@ -117,9 +120,7 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
117
120
  .finally(() => {
118
121
  // Evict non-memory records after persist to prevent unbounded heap growth (stonyx#81)
119
122
  if (store._memoryResolver && !store._memoryResolver(modelName)) {
120
- const ms = store.get(modelName);
121
- if (ms)
122
- ms.delete(record.id);
123
+ store.evictRecord(modelName, record.id, registryId);
123
124
  }
124
125
  });
125
126
  }
package/dist/store.d.ts CHANGED
@@ -46,6 +46,16 @@ export default class Store {
46
46
  private _isMemoryModel;
47
47
  set(key: string, value: Map<number | string, unknown>): void;
48
48
  remove(key: string, id?: number | string): void;
49
+ /**
50
+ * Evict a record from the store with full relationship registry cleanup,
51
+ * WITHOUT calling record.clean(). This preserves the caller's reference
52
+ * to the returned record (used by memory:false post-persist eviction).
53
+ *
54
+ * @param registryId - The ID used when the record's relationships were
55
+ * registered. For SQL models with pending IDs, this is the original
56
+ * negative pending ID (before the adapter re-keyed to the real DB ID).
57
+ */
58
+ evictRecord(modelName: string, id: unknown, registryId?: unknown): void;
49
59
  unloadRecord(model: string, id: unknown, options?: UnloadOptions): void;
50
60
  unloadAllRecords(model: string, options?: UnloadOptions): void;
51
61
  private _removeFromHasManyArrays;
package/dist/store.js CHANGED
@@ -127,6 +127,40 @@ export default class Store {
127
127
  return this.unloadRecord(key, id);
128
128
  this.unloadAllRecords(key);
129
129
  }
130
+ /**
131
+ * Evict a record from the store with full relationship registry cleanup,
132
+ * WITHOUT calling record.clean(). This preserves the caller's reference
133
+ * to the returned record (used by memory:false post-persist eviction).
134
+ *
135
+ * @param registryId - The ID used when the record's relationships were
136
+ * registered. For SQL models with pending IDs, this is the original
137
+ * negative pending ID (before the adapter re-keyed to the real DB ID).
138
+ */
139
+ evictRecord(modelName, id, registryId) {
140
+ const modelStore = this.data.get(modelName);
141
+ if (!modelStore)
142
+ return;
143
+ if (typeof id !== 'string' && typeof id !== 'number')
144
+ return;
145
+ const raw = modelStore.get(id);
146
+ if (!raw || !isStoreRecord(raw))
147
+ return;
148
+ const visited = new Set([`${modelName}:${id}`]);
149
+ // Remove from hasMany arrays and nullify belongsTo references using current ID
150
+ // (the adapter updates record.id, so value-based matches need the current ID)
151
+ this._removeFromHasManyArrays(modelName, id, visited);
152
+ this._nullifyBelongsToReferences(modelName, id, visited);
153
+ // Clean up relationship registry entries using the registry key
154
+ // (belongsTo/hasMany registries were keyed by the ID at registration time,
155
+ // which may differ from the current ID if SQL persist re-keyed the record)
156
+ const cleanupId = registryId ?? id;
157
+ this._cleanupRelationshipRegistries(modelName, cleanupId);
158
+ // If registryId differs from id, also clean with current id as safety net
159
+ if (registryId !== undefined && registryId !== id) {
160
+ this._cleanupRelationshipRegistries(modelName, id);
161
+ }
162
+ modelStore.delete(id);
163
+ }
130
164
  unloadRecord(model, id, options = {}) {
131
165
  const modelStore = this.data.get(model);
132
166
  if (!modelStore) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-beta.76",
7
+ "version": "0.3.2-beta.78",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",
@@ -139,6 +139,9 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
139
139
  // Auto-persist to SQL — skip for DB loads (isDbRecord) and relationship resolution (_relationshipKey)
140
140
  const shouldPersist = orm?.sqlDb && !options.isDbRecord && !userOptions._relationshipKey && !options._skipAutoPersist;
141
141
  if (shouldPersist) {
142
+ // Capture ID before persist — SQL adapters re-key pending IDs to real DB IDs,
143
+ // but relationship registries were keyed with this original ID
144
+ const registryId = record.id;
142
145
  const response = { data: { id: record.id } };
143
146
  orm!.sqlDb!.persist('create', modelName, { rawData }, response)
144
147
  .catch((err: unknown) => {
@@ -152,8 +155,7 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
152
155
  .finally(() => {
153
156
  // Evict non-memory records after persist to prevent unbounded heap growth (stonyx#81)
154
157
  if (store._memoryResolver && !store._memoryResolver(modelName)) {
155
- const ms = store.get(modelName);
156
- if (ms) ms.delete(record.id as number | string);
158
+ store.evictRecord(modelName, record.id, registryId);
157
159
  }
158
160
  });
159
161
  }
package/src/store.ts CHANGED
@@ -193,6 +193,44 @@ export default class Store {
193
193
  this.unloadAllRecords(key);
194
194
  }
195
195
 
196
+ /**
197
+ * Evict a record from the store with full relationship registry cleanup,
198
+ * WITHOUT calling record.clean(). This preserves the caller's reference
199
+ * to the returned record (used by memory:false post-persist eviction).
200
+ *
201
+ * @param registryId - The ID used when the record's relationships were
202
+ * registered. For SQL models with pending IDs, this is the original
203
+ * negative pending ID (before the adapter re-keyed to the real DB ID).
204
+ */
205
+ evictRecord(modelName: string, id: unknown, registryId?: unknown): void {
206
+ const modelStore = this.data.get(modelName);
207
+ if (!modelStore) return;
208
+
209
+ if (typeof id !== 'string' && typeof id !== 'number') return;
210
+ const raw = modelStore.get(id);
211
+ if (!raw || !isStoreRecord(raw)) return;
212
+
213
+ const visited = new Set([`${modelName}:${id}`]);
214
+
215
+ // Remove from hasMany arrays and nullify belongsTo references using current ID
216
+ // (the adapter updates record.id, so value-based matches need the current ID)
217
+ this._removeFromHasManyArrays(modelName, id, visited);
218
+ this._nullifyBelongsToReferences(modelName, id, visited);
219
+
220
+ // Clean up relationship registry entries using the registry key
221
+ // (belongsTo/hasMany registries were keyed by the ID at registration time,
222
+ // which may differ from the current ID if SQL persist re-keyed the record)
223
+ const cleanupId = registryId ?? id;
224
+ this._cleanupRelationshipRegistries(modelName, cleanupId);
225
+
226
+ // If registryId differs from id, also clean with current id as safety net
227
+ if (registryId !== undefined && registryId !== id) {
228
+ this._cleanupRelationshipRegistries(modelName, id);
229
+ }
230
+
231
+ modelStore.delete(id);
232
+ }
233
+
196
234
  unloadRecord(model: string, id: unknown, options: UnloadOptions = {}): void {
197
235
  const modelStore = this.data.get(model);
198
236