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