@signaltree/core 6.2.2 → 6.2.3

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.
@@ -101,11 +101,55 @@ class EntitySignalImpl {
101
101
  return id;
102
102
  }
103
103
  addMany(entities, opts) {
104
- const ids = [];
104
+ const idsToAdd = [];
105
105
  for (const entity of entities) {
106
- ids.push(this.addOne(entity, opts));
106
+ const id = opts?.selectId?.(entity) ?? this.selectId(entity);
107
+ if (this.storage.has(id)) {
108
+ throw new Error(`Entity with id ${String(id)} already exists`);
109
+ }
110
+ idsToAdd.push(id);
111
+ }
112
+ const addedEntities = [];
113
+ for (let i = 0; i < entities.length; i++) {
114
+ const entity = entities[i];
115
+ const id = idsToAdd[i];
116
+ let transformedEntity = entity;
117
+ for (const handler of this.interceptHandlers) {
118
+ const ctx = {
119
+ block: reason => {
120
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
121
+ },
122
+ transform: value => {
123
+ transformedEntity = value;
124
+ },
125
+ blocked: false,
126
+ blockReason: undefined
127
+ };
128
+ handler.onAdd?.(entity, ctx);
129
+ }
130
+ this.storage.set(id, transformedEntity);
131
+ this.nodeCache.delete(id);
132
+ addedEntities.push({
133
+ id,
134
+ entity: transformedEntity
135
+ });
107
136
  }
108
- return ids;
137
+ this.updateSignals();
138
+ for (const {
139
+ id,
140
+ entity
141
+ } of addedEntities) {
142
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, entity, undefined);
143
+ }
144
+ for (const {
145
+ id,
146
+ entity
147
+ } of addedEntities) {
148
+ for (const handler of this.tapHandlers) {
149
+ handler.onAdd?.(entity, id);
150
+ }
151
+ }
152
+ return idsToAdd;
109
153
  }
110
154
  updateOne(id, changes) {
111
155
  const entity = this.storage.get(id);
@@ -140,19 +184,70 @@ class EntitySignalImpl {
140
184
  }
141
185
  }
142
186
  updateMany(ids, changes) {
187
+ if (ids.length === 0) return;
188
+ const updatedEntities = [];
143
189
  for (const id of ids) {
144
- this.updateOne(id, changes);
190
+ const entity = this.storage.get(id);
191
+ if (!entity) {
192
+ throw new Error(`Entity with id ${String(id)} not found`);
193
+ }
194
+ const prev = entity;
195
+ let transformedChanges = changes;
196
+ for (const handler of this.interceptHandlers) {
197
+ const ctx = {
198
+ block: reason => {
199
+ throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
200
+ },
201
+ transform: value => {
202
+ transformedChanges = value;
203
+ },
204
+ blocked: false,
205
+ blockReason: undefined
206
+ };
207
+ handler.onUpdate?.(id, changes, ctx);
208
+ }
209
+ const finalUpdated = {
210
+ ...entity,
211
+ ...transformedChanges
212
+ };
213
+ this.storage.set(id, finalUpdated);
214
+ this.nodeCache.delete(id);
215
+ updatedEntities.push({
216
+ id,
217
+ prev,
218
+ finalUpdated,
219
+ transformedChanges
220
+ });
221
+ }
222
+ this.updateSignals();
223
+ for (const {
224
+ id,
225
+ prev,
226
+ finalUpdated
227
+ } of updatedEntities) {
228
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, finalUpdated, prev);
229
+ }
230
+ for (const {
231
+ id,
232
+ transformedChanges,
233
+ finalUpdated
234
+ } of updatedEntities) {
235
+ for (const handler of this.tapHandlers) {
236
+ handler.onUpdate?.(id, transformedChanges, finalUpdated);
237
+ }
145
238
  }
146
239
  }
147
240
  updateWhere(predicate, changes) {
148
- let count = 0;
241
+ const idsToUpdate = [];
149
242
  for (const [id, entity] of this.storage) {
150
243
  if (predicate(entity)) {
151
- this.updateOne(id, changes);
152
- count++;
244
+ idsToUpdate.push(id);
153
245
  }
154
246
  }
155
- return count;
247
+ if (idsToUpdate.length > 0) {
248
+ this.updateMany(idsToUpdate, changes);
249
+ }
250
+ return idsToUpdate.length;
156
251
  }
157
252
  removeOne(id) {
158
253
  const entity = this.storage.get(id);
@@ -179,8 +274,49 @@ class EntitySignalImpl {
179
274
  }
180
275
  }
181
276
  removeMany(ids) {
277
+ if (ids.length === 0) return;
278
+ const entitiesToRemove = [];
182
279
  for (const id of ids) {
183
- this.removeOne(id);
280
+ const entity = this.storage.get(id);
281
+ if (!entity) {
282
+ throw new Error(`Entity with id ${String(id)} not found`);
283
+ }
284
+ for (const handler of this.interceptHandlers) {
285
+ const ctx = {
286
+ block: reason => {
287
+ throw new Error(`Cannot remove entity: ${reason || 'blocked by interceptor'}`);
288
+ },
289
+ transform: () => {},
290
+ blocked: false,
291
+ blockReason: undefined
292
+ };
293
+ handler.onRemove?.(id, entity, ctx);
294
+ }
295
+ entitiesToRemove.push({
296
+ id,
297
+ entity
298
+ });
299
+ }
300
+ for (const {
301
+ id
302
+ } of entitiesToRemove) {
303
+ this.storage.delete(id);
304
+ this.nodeCache.delete(id);
305
+ }
306
+ this.updateSignals();
307
+ for (const {
308
+ id,
309
+ entity
310
+ } of entitiesToRemove) {
311
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, undefined, entity);
312
+ }
313
+ for (const {
314
+ id,
315
+ entity
316
+ } of entitiesToRemove) {
317
+ for (const handler of this.tapHandlers) {
318
+ handler.onRemove?.(id, entity);
319
+ }
184
320
  }
185
321
  }
186
322
  removeWhere(predicate) {
@@ -190,12 +326,10 @@ class EntitySignalImpl {
190
326
  idsToRemove.push(id);
191
327
  }
192
328
  }
193
- let count = 0;
194
- for (const id of idsToRemove) {
195
- this.removeOne(id);
196
- count++;
329
+ if (idsToRemove.length > 0) {
330
+ this.removeMany(idsToRemove);
197
331
  }
198
- return count;
332
+ return idsToRemove.length;
199
333
  }
200
334
  upsertOne(entity, opts) {
201
335
  const id = opts?.selectId?.(entity) ?? this.selectId(entity);
@@ -207,7 +341,115 @@ class EntitySignalImpl {
207
341
  return id;
208
342
  }
209
343
  upsertMany(entities, opts) {
210
- return entities.map(e => this.upsertOne(e, opts));
344
+ if (entities.length === 0) return [];
345
+ const toAdd = [];
346
+ const toUpdate = [];
347
+ for (const entity of entities) {
348
+ const id = opts?.selectId?.(entity) ?? this.selectId(entity);
349
+ if (this.storage.has(id)) {
350
+ toUpdate.push({
351
+ entity,
352
+ id,
353
+ prev: this.storage.get(id)
354
+ });
355
+ } else {
356
+ toAdd.push({
357
+ entity,
358
+ id
359
+ });
360
+ }
361
+ }
362
+ const addedEntities = [];
363
+ for (const {
364
+ entity,
365
+ id
366
+ } of toAdd) {
367
+ let transformedEntity = entity;
368
+ for (const handler of this.interceptHandlers) {
369
+ const ctx = {
370
+ block: reason => {
371
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
372
+ },
373
+ transform: value => {
374
+ transformedEntity = value;
375
+ },
376
+ blocked: false,
377
+ blockReason: undefined
378
+ };
379
+ handler.onAdd?.(entity, ctx);
380
+ }
381
+ this.storage.set(id, transformedEntity);
382
+ this.nodeCache.delete(id);
383
+ addedEntities.push({
384
+ id,
385
+ entity: transformedEntity
386
+ });
387
+ }
388
+ const updatedEntities = [];
389
+ for (const {
390
+ entity,
391
+ id,
392
+ prev
393
+ } of toUpdate) {
394
+ let transformedChanges = entity;
395
+ for (const handler of this.interceptHandlers) {
396
+ const ctx = {
397
+ block: reason => {
398
+ throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
399
+ },
400
+ transform: value => {
401
+ transformedChanges = value;
402
+ },
403
+ blocked: false,
404
+ blockReason: undefined
405
+ };
406
+ handler.onUpdate?.(id, entity, ctx);
407
+ }
408
+ const finalUpdated = {
409
+ ...prev,
410
+ ...transformedChanges
411
+ };
412
+ this.storage.set(id, finalUpdated);
413
+ this.nodeCache.delete(id);
414
+ updatedEntities.push({
415
+ id,
416
+ prev,
417
+ finalUpdated,
418
+ transformedChanges
419
+ });
420
+ }
421
+ this.updateSignals();
422
+ for (const {
423
+ id,
424
+ entity
425
+ } of addedEntities) {
426
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, entity, undefined);
427
+ }
428
+ for (const {
429
+ id,
430
+ prev,
431
+ finalUpdated
432
+ } of updatedEntities) {
433
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, finalUpdated, prev);
434
+ }
435
+ for (const {
436
+ id,
437
+ entity
438
+ } of addedEntities) {
439
+ for (const handler of this.tapHandlers) {
440
+ handler.onAdd?.(entity, id);
441
+ }
442
+ }
443
+ for (const {
444
+ id,
445
+ transformedChanges,
446
+ finalUpdated
447
+ } of updatedEntities) {
448
+ for (const handler of this.tapHandlers) {
449
+ handler.onUpdate?.(id, transformedChanges, finalUpdated);
450
+ }
451
+ }
452
+ return [...toAdd.map(a => a.id), ...toUpdate.map(u => u.id)];
211
453
  }
212
454
  clear() {
213
455
  this.storage.clear();
@@ -218,8 +460,41 @@ class EntitySignalImpl {
218
460
  this.clear();
219
461
  }
220
462
  setAll(entities, opts) {
221
- this.clear();
222
- this.addMany(entities, opts);
463
+ this.storage.clear();
464
+ this.nodeCache.clear();
465
+ const addedIds = [];
466
+ for (const entity of entities) {
467
+ const id = opts?.selectId?.(entity) ?? this.selectId(entity);
468
+ let transformedEntity = entity;
469
+ for (const handler of this.interceptHandlers) {
470
+ const ctx = {
471
+ block: reason => {
472
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
473
+ },
474
+ transform: value => {
475
+ transformedEntity = value;
476
+ },
477
+ blocked: false,
478
+ blockReason: undefined
479
+ };
480
+ handler.onAdd?.(entity, ctx);
481
+ }
482
+ this.storage.set(id, transformedEntity);
483
+ addedIds.push(id);
484
+ }
485
+ this.updateSignals();
486
+ for (let i = 0; i < addedIds.length; i++) {
487
+ const id = addedIds[i];
488
+ const entity = this.storage.get(id);
489
+ this.pathNotifier.notify(`${this.basePath}.${String(id)}`, entity, undefined);
490
+ }
491
+ for (let i = 0; i < addedIds.length; i++) {
492
+ const id = addedIds[i];
493
+ const entity = this.storage.get(id);
494
+ for (const handler of this.tapHandlers) {
495
+ handler.onAdd?.(entity, id);
496
+ }
497
+ }
223
498
  }
224
499
  tap(handlers) {
225
500
  this.tapHandlers.push(handlers);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "6.2.2",
3
+ "version": "6.2.3",
4
4
  "description": "Lightweight, type-safe signal-based state management for Angular. Core package providing hierarchical signal trees, basic entity management, and async actions.",
5
5
  "type": "module",
6
6
  "sideEffects": false,