@signaltree/core 7.1.5 → 7.1.6

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.
Files changed (99) hide show
  1. package/dist/constants.js +6 -0
  2. package/dist/deep-equal.js +41 -0
  3. package/dist/enhancers/batching/batching.js +230 -0
  4. package/dist/enhancers/devtools/devtools.js +318 -0
  5. package/dist/enhancers/effects/effects.js +66 -0
  6. package/dist/enhancers/entities/entities.js +7 -0
  7. package/dist/enhancers/index.js +72 -0
  8. package/dist/enhancers/memoization/memoization.js +420 -0
  9. package/dist/enhancers/presets/lib/presets.js +27 -0
  10. package/dist/enhancers/serialization/constants.js +15 -0
  11. package/dist/enhancers/serialization/serialization.js +656 -0
  12. package/dist/enhancers/time-travel/time-travel.js +283 -0
  13. package/dist/enhancers/time-travel/utils.js +11 -0
  14. package/dist/enhancers/utils/copy-tree-properties.js +20 -0
  15. package/dist/index.js +26 -0
  16. package/dist/is-built-in-object.js +23 -0
  17. package/dist/lib/async-helpers.js +77 -0
  18. package/dist/lib/constants.js +56 -0
  19. package/dist/lib/edit-session.js +84 -0
  20. package/dist/lib/entity-signal.js +544 -0
  21. package/dist/lib/internals/batch-scope.js +8 -0
  22. package/dist/lib/internals/derived-types.js +6 -0
  23. package/dist/lib/internals/materialize-markers.js +72 -0
  24. package/dist/lib/internals/merge-derived.js +59 -0
  25. package/dist/lib/markers/derived.js +6 -0
  26. package/dist/lib/markers/entity-map.js +20 -0
  27. package/dist/lib/markers/status.js +71 -0
  28. package/dist/lib/markers/stored.js +111 -0
  29. package/dist/lib/memory/memory-manager.js +164 -0
  30. package/dist/lib/path-notifier.js +178 -0
  31. package/dist/lib/presets.js +20 -0
  32. package/dist/lib/security/security-validator.js +121 -0
  33. package/dist/lib/signal-tree.js +415 -0
  34. package/dist/lib/types.js +3 -0
  35. package/dist/lib/utils.js +264 -0
  36. package/dist/lru-cache.js +64 -0
  37. package/dist/parse-path.js +13 -0
  38. package/package.json +1 -1
  39. package/src/enhancers/batching/batching.d.ts +10 -0
  40. package/src/enhancers/batching/batching.types.d.ts +1 -0
  41. package/src/enhancers/batching/index.d.ts +1 -0
  42. package/src/enhancers/batching/test-setup.d.ts +3 -0
  43. package/src/enhancers/devtools/devtools.d.ts +68 -0
  44. package/src/enhancers/devtools/devtools.types.d.ts +1 -0
  45. package/src/enhancers/devtools/index.d.ts +1 -0
  46. package/src/enhancers/devtools/test-setup.d.ts +3 -0
  47. package/src/enhancers/effects/effects.d.ts +9 -0
  48. package/src/enhancers/effects/effects.types.d.ts +1 -0
  49. package/src/enhancers/effects/index.d.ts +1 -0
  50. package/src/enhancers/entities/entities.d.ts +7 -0
  51. package/src/enhancers/entities/entities.types.d.ts +1 -0
  52. package/src/enhancers/entities/index.d.ts +1 -0
  53. package/src/enhancers/entities/test-setup.d.ts +3 -0
  54. package/src/enhancers/index.d.ts +3 -0
  55. package/src/enhancers/memoization/index.d.ts +1 -0
  56. package/src/enhancers/memoization/memoization.d.ts +54 -0
  57. package/src/enhancers/memoization/memoization.types.d.ts +1 -0
  58. package/src/enhancers/memoization/test-setup.d.ts +3 -0
  59. package/src/enhancers/presets/index.d.ts +1 -0
  60. package/src/enhancers/presets/lib/presets.d.ts +8 -0
  61. package/src/enhancers/serialization/constants.d.ts +14 -0
  62. package/src/enhancers/serialization/index.d.ts +2 -0
  63. package/src/enhancers/serialization/serialization.d.ts +68 -0
  64. package/src/enhancers/serialization/test-setup.d.ts +3 -0
  65. package/src/enhancers/test-helpers/types-equals.d.ts +2 -0
  66. package/src/enhancers/time-travel/index.d.ts +1 -0
  67. package/src/enhancers/time-travel/test-setup.d.ts +3 -0
  68. package/src/enhancers/time-travel/time-travel.d.ts +10 -0
  69. package/src/enhancers/time-travel/time-travel.types.d.ts +1 -0
  70. package/src/enhancers/time-travel/utils.d.ts +2 -0
  71. package/src/enhancers/types.d.ts +1 -0
  72. package/src/enhancers/typing/helpers-types.d.ts +2 -0
  73. package/src/enhancers/utils/copy-tree-properties.d.ts +1 -0
  74. package/src/index.d.ts +26 -0
  75. package/src/lib/async-helpers.d.ts +8 -0
  76. package/src/lib/constants.d.ts +41 -0
  77. package/src/lib/dev-proxy.d.ts +3 -0
  78. package/src/lib/edit-session.d.ts +21 -0
  79. package/src/lib/entity-signal.d.ts +1 -0
  80. package/src/lib/internals/batch-scope.d.ts +3 -0
  81. package/src/lib/internals/builder-types.d.ts +13 -0
  82. package/src/lib/internals/derived-types.d.ts +19 -0
  83. package/src/lib/internals/materialize-markers.d.ts +5 -0
  84. package/src/lib/internals/merge-derived.d.ts +4 -0
  85. package/src/lib/markers/derived.d.ts +9 -0
  86. package/src/lib/markers/entity-map.d.ts +4 -0
  87. package/src/lib/markers/index.d.ts +3 -0
  88. package/src/lib/markers/status.d.ts +32 -0
  89. package/src/lib/markers/stored.d.ts +23 -0
  90. package/src/lib/memory/memory-manager.d.ts +30 -0
  91. package/src/lib/path-notifier.d.ts +34 -0
  92. package/src/lib/performance/diff-engine.d.ts +33 -0
  93. package/src/lib/performance/path-index.d.ts +25 -0
  94. package/src/lib/performance/update-engine.d.ts +32 -0
  95. package/src/lib/presets.d.ts +34 -0
  96. package/src/lib/security/security-validator.d.ts +33 -0
  97. package/src/lib/signal-tree.d.ts +6 -0
  98. package/src/lib/types.d.ts +300 -0
  99. package/src/lib/utils.d.ts +25 -0
@@ -0,0 +1,544 @@
1
+ import { signal, computed } from '@angular/core';
2
+
3
+ function createEntitySignal(config, pathNotifier, basePath) {
4
+ const storage = new Map();
5
+ const allSignal = signal([]);
6
+ const countSignal = signal(0);
7
+ const idsSignal = signal([]);
8
+ const mapSignal = signal(new Map());
9
+ const nodeCache = new Map();
10
+ const selectId = config.selectId ?? (entity => entity['id']);
11
+ const tapHandlers = [];
12
+ const interceptHandlers = [];
13
+ function updateSignals() {
14
+ const entities = Array.from(storage.values());
15
+ const ids = Array.from(storage.keys());
16
+ const map = new Map(storage);
17
+ allSignal.set(entities);
18
+ countSignal.set(entities.length);
19
+ idsSignal.set(ids);
20
+ mapSignal.set(map);
21
+ }
22
+ function createEntityNode(id, entity) {
23
+ const node = () => storage.get(id);
24
+ for (const key of Object.keys(entity)) {
25
+ Object.defineProperty(node, key, {
26
+ get: () => {
27
+ const current = storage.get(id);
28
+ const value = current?.[key];
29
+ return () => value;
30
+ },
31
+ enumerable: true,
32
+ configurable: true
33
+ });
34
+ }
35
+ return node;
36
+ }
37
+ function getOrCreateNode(id, entity) {
38
+ let node = nodeCache.get(id);
39
+ if (!node) {
40
+ node = createEntityNode(id, entity);
41
+ nodeCache.set(id, node);
42
+ }
43
+ return node;
44
+ }
45
+ const whereCache = new WeakMap();
46
+ const findCache = new WeakMap();
47
+ const api = {
48
+ byId(id) {
49
+ const entity = storage.get(id);
50
+ if (!entity) return undefined;
51
+ return getOrCreateNode(id, entity);
52
+ },
53
+ byIdOrFail(id) {
54
+ const node = api.byId(id);
55
+ if (!node) {
56
+ throw new Error(`Entity with id ${String(id)} not found`);
57
+ }
58
+ return node;
59
+ },
60
+ get all() {
61
+ return allSignal;
62
+ },
63
+ get count() {
64
+ return countSignal;
65
+ },
66
+ get ids() {
67
+ return idsSignal;
68
+ },
69
+ get map() {
70
+ return mapSignal;
71
+ },
72
+ has(id) {
73
+ return computed(() => mapSignal().has(id));
74
+ },
75
+ get isEmpty() {
76
+ return computed(() => countSignal() === 0);
77
+ },
78
+ where(predicate) {
79
+ const cached = whereCache.get(predicate);
80
+ if (cached) return cached;
81
+ const s = computed(() => allSignal().filter(predicate));
82
+ whereCache.set(predicate, s);
83
+ return s;
84
+ },
85
+ find(predicate) {
86
+ const cached = findCache.get(predicate);
87
+ if (cached) return cached;
88
+ const s = computed(() => allSignal().find(predicate));
89
+ findCache.set(predicate, s);
90
+ return s;
91
+ },
92
+ addOne(entity, opts) {
93
+ const id = opts?.selectId?.(entity) ?? selectId(entity);
94
+ if (storage.has(id)) {
95
+ throw new Error(`Entity with id ${String(id)} already exists`);
96
+ }
97
+ let transformedEntity = entity;
98
+ for (const handler of interceptHandlers) {
99
+ const ctx = {
100
+ block: reason => {
101
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
102
+ },
103
+ transform: value => {
104
+ transformedEntity = value;
105
+ },
106
+ blocked: false,
107
+ blockReason: undefined
108
+ };
109
+ handler.onAdd?.(entity, ctx);
110
+ }
111
+ storage.set(id, transformedEntity);
112
+ nodeCache.delete(id);
113
+ updateSignals();
114
+ pathNotifier.notify(`${basePath}.${String(id)}`, transformedEntity, undefined);
115
+ for (const handler of tapHandlers) {
116
+ handler.onAdd?.(transformedEntity, id);
117
+ }
118
+ return id;
119
+ },
120
+ addMany(entities, opts) {
121
+ const idsToAdd = [];
122
+ for (const entity of entities) {
123
+ const id = opts?.selectId?.(entity) ?? selectId(entity);
124
+ if (storage.has(id)) {
125
+ throw new Error(`Entity with id ${String(id)} already exists`);
126
+ }
127
+ idsToAdd.push(id);
128
+ }
129
+ const addedEntities = [];
130
+ for (let i = 0; i < entities.length; i++) {
131
+ const entity = entities[i];
132
+ const id = idsToAdd[i];
133
+ let transformedEntity = entity;
134
+ for (const handler of interceptHandlers) {
135
+ const ctx = {
136
+ block: reason => {
137
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
138
+ },
139
+ transform: value => {
140
+ transformedEntity = value;
141
+ },
142
+ blocked: false,
143
+ blockReason: undefined
144
+ };
145
+ handler.onAdd?.(entity, ctx);
146
+ }
147
+ storage.set(id, transformedEntity);
148
+ nodeCache.delete(id);
149
+ addedEntities.push({
150
+ id,
151
+ entity: transformedEntity
152
+ });
153
+ }
154
+ updateSignals();
155
+ for (const {
156
+ id,
157
+ entity
158
+ } of addedEntities) {
159
+ pathNotifier.notify(`${basePath}.${String(id)}`, entity, undefined);
160
+ }
161
+ for (const {
162
+ id,
163
+ entity
164
+ } of addedEntities) {
165
+ for (const handler of tapHandlers) {
166
+ handler.onAdd?.(entity, id);
167
+ }
168
+ }
169
+ return idsToAdd;
170
+ },
171
+ updateOne(id, changes) {
172
+ const entity = storage.get(id);
173
+ if (!entity) {
174
+ throw new Error(`Entity with id ${String(id)} not found`);
175
+ }
176
+ const prev = entity;
177
+ let transformedChanges = changes;
178
+ for (const handler of interceptHandlers) {
179
+ const ctx = {
180
+ block: reason => {
181
+ throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
182
+ },
183
+ transform: value => {
184
+ transformedChanges = value;
185
+ },
186
+ blocked: false,
187
+ blockReason: undefined
188
+ };
189
+ handler.onUpdate?.(id, changes, ctx);
190
+ }
191
+ const finalUpdated = {
192
+ ...entity,
193
+ ...transformedChanges
194
+ };
195
+ storage.set(id, finalUpdated);
196
+ nodeCache.delete(id);
197
+ updateSignals();
198
+ pathNotifier.notify(`${basePath}.${String(id)}`, finalUpdated, prev);
199
+ for (const handler of tapHandlers) {
200
+ handler.onUpdate?.(id, transformedChanges, finalUpdated);
201
+ }
202
+ },
203
+ updateMany(ids, changes) {
204
+ if (ids.length === 0) return;
205
+ const updatedEntities = [];
206
+ for (const id of ids) {
207
+ const entity = storage.get(id);
208
+ if (!entity) {
209
+ throw new Error(`Entity with id ${String(id)} not found`);
210
+ }
211
+ const prev = entity;
212
+ let transformedChanges = changes;
213
+ for (const handler of interceptHandlers) {
214
+ const ctx = {
215
+ block: reason => {
216
+ throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
217
+ },
218
+ transform: value => {
219
+ transformedChanges = value;
220
+ },
221
+ blocked: false,
222
+ blockReason: undefined
223
+ };
224
+ handler.onUpdate?.(id, changes, ctx);
225
+ }
226
+ const finalUpdated = {
227
+ ...entity,
228
+ ...transformedChanges
229
+ };
230
+ storage.set(id, finalUpdated);
231
+ nodeCache.delete(id);
232
+ updatedEntities.push({
233
+ id,
234
+ prev,
235
+ finalUpdated,
236
+ transformedChanges
237
+ });
238
+ }
239
+ updateSignals();
240
+ for (const {
241
+ id,
242
+ prev,
243
+ finalUpdated
244
+ } of updatedEntities) {
245
+ pathNotifier.notify(`${basePath}.${String(id)}`, finalUpdated, prev);
246
+ }
247
+ for (const {
248
+ id,
249
+ transformedChanges,
250
+ finalUpdated
251
+ } of updatedEntities) {
252
+ for (const handler of tapHandlers) {
253
+ handler.onUpdate?.(id, transformedChanges, finalUpdated);
254
+ }
255
+ }
256
+ },
257
+ updateWhere(predicate, changes) {
258
+ const idsToUpdate = [];
259
+ for (const [id, entity] of storage) {
260
+ if (predicate(entity)) {
261
+ idsToUpdate.push(id);
262
+ }
263
+ }
264
+ if (idsToUpdate.length > 0) {
265
+ api.updateMany(idsToUpdate, changes);
266
+ }
267
+ return idsToUpdate.length;
268
+ },
269
+ removeOne(id) {
270
+ const entity = storage.get(id);
271
+ if (!entity) {
272
+ throw new Error(`Entity with id ${String(id)} not found`);
273
+ }
274
+ for (const handler of interceptHandlers) {
275
+ const ctx = {
276
+ block: reason => {
277
+ throw new Error(`Cannot remove entity: ${reason || 'blocked by interceptor'}`);
278
+ },
279
+ transform: () => {},
280
+ blocked: false,
281
+ blockReason: undefined
282
+ };
283
+ handler.onRemove?.(id, entity, ctx);
284
+ }
285
+ storage.delete(id);
286
+ nodeCache.delete(id);
287
+ updateSignals();
288
+ pathNotifier.notify(`${basePath}.${String(id)}`, undefined, entity);
289
+ for (const handler of tapHandlers) {
290
+ handler.onRemove?.(id, entity);
291
+ }
292
+ },
293
+ removeMany(ids) {
294
+ if (ids.length === 0) return;
295
+ const entitiesToRemove = [];
296
+ for (const id of ids) {
297
+ const entity = storage.get(id);
298
+ if (!entity) {
299
+ throw new Error(`Entity with id ${String(id)} not found`);
300
+ }
301
+ for (const handler of interceptHandlers) {
302
+ const ctx = {
303
+ block: reason => {
304
+ throw new Error(`Cannot remove entity: ${reason || 'blocked by interceptor'}`);
305
+ },
306
+ transform: () => {},
307
+ blocked: false,
308
+ blockReason: undefined
309
+ };
310
+ handler.onRemove?.(id, entity, ctx);
311
+ }
312
+ entitiesToRemove.push({
313
+ id,
314
+ entity
315
+ });
316
+ }
317
+ for (const {
318
+ id
319
+ } of entitiesToRemove) {
320
+ storage.delete(id);
321
+ nodeCache.delete(id);
322
+ }
323
+ updateSignals();
324
+ for (const {
325
+ id,
326
+ entity
327
+ } of entitiesToRemove) {
328
+ pathNotifier.notify(`${basePath}.${String(id)}`, undefined, entity);
329
+ }
330
+ for (const {
331
+ id,
332
+ entity
333
+ } of entitiesToRemove) {
334
+ for (const handler of tapHandlers) {
335
+ handler.onRemove?.(id, entity);
336
+ }
337
+ }
338
+ },
339
+ removeWhere(predicate) {
340
+ const idsToRemove = [];
341
+ for (const [id, entity] of storage) {
342
+ if (predicate(entity)) {
343
+ idsToRemove.push(id);
344
+ }
345
+ }
346
+ if (idsToRemove.length > 0) {
347
+ api.removeMany(idsToRemove);
348
+ }
349
+ return idsToRemove.length;
350
+ },
351
+ upsertOne(entity, opts) {
352
+ const id = opts?.selectId?.(entity) ?? selectId(entity);
353
+ if (storage.has(id)) {
354
+ api.updateOne(id, entity);
355
+ } else {
356
+ api.addOne(entity, opts);
357
+ }
358
+ return id;
359
+ },
360
+ upsertMany(entities, opts) {
361
+ if (entities.length === 0) return [];
362
+ const toAdd = [];
363
+ const toUpdate = [];
364
+ for (const entity of entities) {
365
+ const id = opts?.selectId?.(entity) ?? selectId(entity);
366
+ const existing = storage.get(id);
367
+ if (existing !== undefined) {
368
+ toUpdate.push({
369
+ entity,
370
+ id,
371
+ prev: existing
372
+ });
373
+ } else {
374
+ toAdd.push({
375
+ entity,
376
+ id
377
+ });
378
+ }
379
+ }
380
+ const addedEntities = [];
381
+ for (const {
382
+ entity,
383
+ id
384
+ } of toAdd) {
385
+ let transformedEntity = entity;
386
+ for (const handler of interceptHandlers) {
387
+ const ctx = {
388
+ block: reason => {
389
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
390
+ },
391
+ transform: value => {
392
+ transformedEntity = value;
393
+ },
394
+ blocked: false,
395
+ blockReason: undefined
396
+ };
397
+ handler.onAdd?.(entity, ctx);
398
+ }
399
+ storage.set(id, transformedEntity);
400
+ nodeCache.delete(id);
401
+ addedEntities.push({
402
+ id,
403
+ entity: transformedEntity
404
+ });
405
+ }
406
+ const updatedEntities = [];
407
+ for (const {
408
+ entity,
409
+ id,
410
+ prev
411
+ } of toUpdate) {
412
+ let transformedChanges = entity;
413
+ for (const handler of interceptHandlers) {
414
+ const ctx = {
415
+ block: reason => {
416
+ throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
417
+ },
418
+ transform: value => {
419
+ transformedChanges = value;
420
+ },
421
+ blocked: false,
422
+ blockReason: undefined
423
+ };
424
+ handler.onUpdate?.(id, entity, ctx);
425
+ }
426
+ const finalUpdated = {
427
+ ...prev,
428
+ ...transformedChanges
429
+ };
430
+ storage.set(id, finalUpdated);
431
+ nodeCache.delete(id);
432
+ updatedEntities.push({
433
+ id,
434
+ prev,
435
+ finalUpdated,
436
+ transformedChanges
437
+ });
438
+ }
439
+ updateSignals();
440
+ for (const {
441
+ id,
442
+ entity
443
+ } of addedEntities) {
444
+ pathNotifier.notify(`${basePath}.${String(id)}`, entity, undefined);
445
+ }
446
+ for (const {
447
+ id,
448
+ prev,
449
+ finalUpdated
450
+ } of updatedEntities) {
451
+ pathNotifier.notify(`${basePath}.${String(id)}`, finalUpdated, prev);
452
+ }
453
+ for (const {
454
+ id,
455
+ entity
456
+ } of addedEntities) {
457
+ for (const handler of tapHandlers) {
458
+ handler.onAdd?.(entity, id);
459
+ }
460
+ }
461
+ for (const {
462
+ id,
463
+ transformedChanges,
464
+ finalUpdated
465
+ } of updatedEntities) {
466
+ for (const handler of tapHandlers) {
467
+ handler.onUpdate?.(id, transformedChanges, finalUpdated);
468
+ }
469
+ }
470
+ return [...toAdd.map(a => a.id), ...toUpdate.map(u => u.id)];
471
+ },
472
+ clear() {
473
+ storage.clear();
474
+ nodeCache.clear();
475
+ updateSignals();
476
+ },
477
+ removeAll() {
478
+ api.clear();
479
+ },
480
+ setAll(entities, opts) {
481
+ storage.clear();
482
+ nodeCache.clear();
483
+ const addedIds = [];
484
+ for (const entity of entities) {
485
+ const id = opts?.selectId?.(entity) ?? selectId(entity);
486
+ let transformedEntity = entity;
487
+ for (const handler of interceptHandlers) {
488
+ const ctx = {
489
+ block: reason => {
490
+ throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
491
+ },
492
+ transform: value => {
493
+ transformedEntity = value;
494
+ },
495
+ blocked: false,
496
+ blockReason: undefined
497
+ };
498
+ handler.onAdd?.(entity, ctx);
499
+ }
500
+ storage.set(id, transformedEntity);
501
+ addedIds.push(id);
502
+ }
503
+ updateSignals();
504
+ for (let i = 0; i < addedIds.length; i++) {
505
+ const id = addedIds[i];
506
+ const entity = storage.get(id);
507
+ pathNotifier.notify(`${basePath}.${String(id)}`, entity, undefined);
508
+ }
509
+ for (let i = 0; i < addedIds.length; i++) {
510
+ const id = addedIds[i];
511
+ const entity = storage.get(id);
512
+ if (entity) {
513
+ for (const handler of tapHandlers) {
514
+ handler.onAdd?.(entity, id);
515
+ }
516
+ }
517
+ }
518
+ },
519
+ tap(handlers) {
520
+ tapHandlers.push(handlers);
521
+ return () => {
522
+ const idx = tapHandlers.indexOf(handlers);
523
+ if (idx > -1) tapHandlers.splice(idx, 1);
524
+ };
525
+ },
526
+ intercept(handlers) {
527
+ interceptHandlers.push(handlers);
528
+ return () => {
529
+ const idx = interceptHandlers.indexOf(handlers);
530
+ if (idx > -1) interceptHandlers.splice(idx, 1);
531
+ };
532
+ }
533
+ };
534
+ return new Proxy(api, {
535
+ get: (target, prop) => {
536
+ if (typeof prop === 'string' && !isNaN(Number(prop))) {
537
+ return api.byId(Number(prop));
538
+ }
539
+ return target[prop];
540
+ }
541
+ });
542
+ }
543
+
544
+ export { createEntitySignal };
@@ -0,0 +1,8 @@
1
+ function batchScope(fn) {
2
+ try {
3
+ fn();
4
+ } finally {
5
+ }
6
+ }
7
+
8
+ export { batchScope };
@@ -0,0 +1,6 @@
1
+ function derivedFrom() {
2
+ return fn => fn;
3
+ }
4
+ const externalDerived = derivedFrom;
5
+
6
+ export { derivedFrom, externalDerived };
@@ -0,0 +1,72 @@
1
+ import { isSignal } from '@angular/core';
2
+ import { getPathNotifier } from '../path-notifier.js';
3
+ import { isNodeAccessor } from '../utils.js';
4
+
5
+ const MARKER_PROCESSORS = [];
6
+ function isRegisteredMarker(value) {
7
+ if (value === null || typeof value !== 'object') {
8
+ return false;
9
+ }
10
+ if (Object.getOwnPropertySymbols(value).length === 0) {
11
+ return false;
12
+ }
13
+ for (const processor of MARKER_PROCESSORS) {
14
+ if (processor.check(value)) {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ function registerMarkerProcessor(check, create) {
21
+ const alreadyRegistered = MARKER_PROCESSORS.some(p => p.check === check);
22
+ if (alreadyRegistered) {
23
+ return;
24
+ }
25
+ MARKER_PROCESSORS.push({
26
+ check,
27
+ create: create
28
+ });
29
+ }
30
+ function materializeMarkers(node, notifier, path = []) {
31
+ if (node == null) return;
32
+ if (typeof node !== 'object' && typeof node !== 'function') return;
33
+ if (isSignal(node)) return;
34
+ const isAccessor = typeof node === 'function' && isNodeAccessor(node);
35
+ if (typeof node === 'function' && !isAccessor) return;
36
+ const getNotifier = () => {
37
+ if (!notifier) {
38
+ notifier = getPathNotifier();
39
+ }
40
+ return notifier;
41
+ };
42
+ const keys = Object.keys(node);
43
+ for (const key of keys) {
44
+ const value = node[key];
45
+ const currentPath = [...path, key];
46
+ const pathString = currentPath.join('.');
47
+ let processed = false;
48
+ for (const processor of MARKER_PROCESSORS) {
49
+ if (processor.check(value)) {
50
+ try {
51
+ const materialized = processor.create(value, getNotifier(), pathString);
52
+ node[key] = materialized;
53
+ processed = true;
54
+ } catch (err) {
55
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
56
+ console.error(`SignalTree: Failed to materialize marker at "${pathString}"`, err);
57
+ }
58
+ }
59
+ break;
60
+ }
61
+ }
62
+ if (!processed && value != null) {
63
+ if (isNodeAccessor(value)) {
64
+ materializeMarkers(value, notifier, currentPath);
65
+ } else if (typeof value === 'object' && !Array.isArray(value) && !isSignal(value)) {
66
+ materializeMarkers(value, notifier, currentPath);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export { isRegisteredMarker, materializeMarkers, registerMarkerProcessor };
@@ -0,0 +1,59 @@
1
+ import { computed, isSignal } from '@angular/core';
2
+ import { isDerivedMarker } from '../markers/derived.js';
3
+
4
+ function isSignalLike(value) {
5
+ return isSignal(value);
6
+ }
7
+ function ensurePathAndGetTarget($, path) {
8
+ if (!path) return $;
9
+ const parts = path.split('.');
10
+ let current = $;
11
+ for (const part of parts) {
12
+ if (!(part in current)) {
13
+ current[part] = {};
14
+ }
15
+ current = current[part];
16
+ }
17
+ return current;
18
+ }
19
+ function mergeDerivedState($, derivedDef, path = '') {
20
+ if (!derivedDef || typeof derivedDef !== 'object') {
21
+ return;
22
+ }
23
+ for (const [key, value] of Object.entries(derivedDef)) {
24
+ const currentPath = path ? `${path}.${key}` : key;
25
+ if (isDerivedMarker(value)) {
26
+ const target = ensurePathAndGetTarget($, path);
27
+ if (key in target && isSignalLike(target[key])) {
28
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
29
+ console.warn(`SignalTree: Derived "${currentPath}" overwrites source signal. ` + `Consider using a different key to avoid confusion.`);
30
+ }
31
+ }
32
+ target[key] = computed(value.factory);
33
+ } else if (isSignalLike(value)) {
34
+ const target = ensurePathAndGetTarget($, path);
35
+ if (key in target && isSignalLike(target[key])) {
36
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
37
+ console.warn(`SignalTree: Derived signal "${currentPath}" overwrites source signal. ` + `Consider using a different key to avoid confusion.`);
38
+ }
39
+ }
40
+ target[key] = value;
41
+ } else if (typeof value === 'object' && value !== null) {
42
+ const target = ensurePathAndGetTarget($, path);
43
+ if (!(key in target)) {
44
+ target[key] = {};
45
+ } else if (isSignalLike(target[key])) {
46
+ throw new Error(`SignalTree: Cannot merge derived object into "${currentPath}" ` + `because source is a signal. Either make source an object or use a different key.`);
47
+ }
48
+ mergeDerivedState($, value, currentPath);
49
+ }
50
+ }
51
+ }
52
+ function applyDerivedFactories($, factories) {
53
+ for (const factory of factories) {
54
+ const derivedDef = factory($);
55
+ mergeDerivedState($, derivedDef);
56
+ }
57
+ }
58
+
59
+ export { applyDerivedFactories, mergeDerivedState };
@@ -0,0 +1,6 @@
1
+ const DERIVED_MARKER = Symbol.for('signaltree:derived');
2
+ function isDerivedMarker(value) {
3
+ return value !== null && typeof value === 'object' && DERIVED_MARKER in value && value[DERIVED_MARKER] === true;
4
+ }
5
+
6
+ export { isDerivedMarker };