@signaltree/core 6.2.3 → 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 +444 -468
- package/package.json +1 -1
|
@@ -1,120 +1,91 @@
|
|
|
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
|
-
return this.mapSignal;
|
|
47
|
-
}
|
|
48
|
-
has(id) {
|
|
49
|
-
return computed(() => this.storage.has(id));
|
|
50
|
-
}
|
|
51
|
-
get isEmpty() {
|
|
52
|
-
return computed(() => this.storage.size === 0);
|
|
53
|
-
}
|
|
54
|
-
where(predicate) {
|
|
55
|
-
return computed(() => {
|
|
56
|
-
const result = [];
|
|
57
|
-
for (const entity of this.storage.values()) {
|
|
58
|
-
if (predicate(entity)) {
|
|
59
|
-
result.push(entity);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
find(predicate) {
|
|
66
|
-
return computed(() => {
|
|
67
|
-
for (const entity of this.storage.values()) {
|
|
68
|
-
if (predicate(entity)) {
|
|
69
|
-
return entity;
|
|
70
|
-
}
|
|
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`);
|
|
71
55
|
}
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return id;
|
|
102
|
-
}
|
|
103
|
-
addMany(entities, opts) {
|
|
104
|
-
const idsToAdd = [];
|
|
105
|
-
for (const entity of entities) {
|
|
106
|
-
const id = opts?.selectId?.(entity) ?? this.selectId(entity);
|
|
107
|
-
if (this.storage.has(id)) {
|
|
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)) {
|
|
108
85
|
throw new Error(`Entity with id ${String(id)} already exists`);
|
|
109
86
|
}
|
|
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
87
|
let transformedEntity = entity;
|
|
117
|
-
for (const handler of
|
|
88
|
+
for (const handler of interceptHandlers) {
|
|
118
89
|
const ctx = {
|
|
119
90
|
block: reason => {
|
|
120
91
|
throw new Error(`Cannot add entity: ${reason || 'blocked by interceptor'}`);
|
|
@@ -127,73 +98,74 @@ class EntitySignalImpl {
|
|
|
127
98
|
};
|
|
128
99
|
handler.onAdd?.(entity, ctx);
|
|
129
100
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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 {
|
|
133
146
|
id,
|
|
134
|
-
entity
|
|
135
|
-
})
|
|
136
|
-
|
|
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);
|
|
147
|
+
entity
|
|
148
|
+
} of addedEntities) {
|
|
149
|
+
pathNotifier.notify(`${basePath}.${String(id)}`, entity, undefined);
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
block: reason => {
|
|
164
|
-
throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
|
|
165
|
-
},
|
|
166
|
-
transform: value => {
|
|
167
|
-
transformedChanges = value;
|
|
168
|
-
},
|
|
169
|
-
blocked: false,
|
|
170
|
-
blockReason: undefined
|
|
171
|
-
};
|
|
172
|
-
handler.onUpdate?.(id, changes, ctx);
|
|
173
|
-
}
|
|
174
|
-
const finalUpdated = {
|
|
175
|
-
...entity,
|
|
176
|
-
...transformedChanges
|
|
177
|
-
};
|
|
178
|
-
this.storage.set(id, finalUpdated);
|
|
179
|
-
this.nodeCache.delete(id);
|
|
180
|
-
this.updateSignals();
|
|
181
|
-
this.pathNotifier.notify(`${this.basePath}.${String(id)}`, finalUpdated, prev);
|
|
182
|
-
for (const handler of this.tapHandlers) {
|
|
183
|
-
handler.onUpdate?.(id, transformedChanges, finalUpdated);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
updateMany(ids, changes) {
|
|
187
|
-
if (ids.length === 0) return;
|
|
188
|
-
const updatedEntities = [];
|
|
189
|
-
for (const id of ids) {
|
|
190
|
-
const entity = this.storage.get(id);
|
|
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);
|
|
191
163
|
if (!entity) {
|
|
192
164
|
throw new Error(`Entity with id ${String(id)} not found`);
|
|
193
165
|
}
|
|
194
166
|
const prev = entity;
|
|
195
167
|
let transformedChanges = changes;
|
|
196
|
-
for (const handler of
|
|
168
|
+
for (const handler of interceptHandlers) {
|
|
197
169
|
const ctx = {
|
|
198
170
|
block: reason => {
|
|
199
171
|
throw new Error(`Cannot update entity: ${reason || 'blocked by interceptor'}`);
|
|
@@ -210,78 +182,86 @@ class EntitySignalImpl {
|
|
|
210
182
|
...entity,
|
|
211
183
|
...transformedChanges
|
|
212
184
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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 {
|
|
216
231
|
id,
|
|
217
232
|
prev,
|
|
218
|
-
finalUpdated
|
|
219
|
-
|
|
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);
|
|
233
|
+
finalUpdated
|
|
234
|
+
} of updatedEntities) {
|
|
235
|
+
pathNotifier.notify(`${basePath}.${String(id)}`, finalUpdated, prev);
|
|
237
236
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
245
|
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
},
|
|
262
|
-
transform: () => {},
|
|
263
|
-
blocked: false,
|
|
264
|
-
blockReason: undefined
|
|
265
|
-
};
|
|
266
|
-
handler.onRemove?.(id, entity, ctx);
|
|
267
|
-
}
|
|
268
|
-
this.storage.delete(id);
|
|
269
|
-
this.nodeCache.delete(id);
|
|
270
|
-
this.updateSignals();
|
|
271
|
-
this.pathNotifier.notify(`${this.basePath}.${String(id)}`, undefined, entity);
|
|
272
|
-
for (const handler of this.tapHandlers) {
|
|
273
|
-
handler.onRemove?.(id, entity);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
removeMany(ids) {
|
|
277
|
-
if (ids.length === 0) return;
|
|
278
|
-
const entitiesToRemove = [];
|
|
279
|
-
for (const id of ids) {
|
|
280
|
-
const entity = this.storage.get(id);
|
|
246
|
+
},
|
|
247
|
+
updateWhere(predicate, changes) {
|
|
248
|
+
const idsToUpdate = [];
|
|
249
|
+
for (const [id, entity] of storage) {
|
|
250
|
+
if (predicate(entity)) {
|
|
251
|
+
idsToUpdate.push(id);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
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);
|
|
281
261
|
if (!entity) {
|
|
282
262
|
throw new Error(`Entity with id ${String(id)} not found`);
|
|
283
263
|
}
|
|
284
|
-
for (const handler of
|
|
264
|
+
for (const handler of interceptHandlers) {
|
|
285
265
|
const ctx = {
|
|
286
266
|
block: reason => {
|
|
287
267
|
throw new Error(`Cannot remove entity: ${reason || 'blocked by interceptor'}`);
|
|
@@ -292,267 +272,263 @@ class EntitySignalImpl {
|
|
|
292
272
|
};
|
|
293
273
|
handler.onRemove?.(id, entity, ctx);
|
|
294
274
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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) {
|
|
275
|
+
storage.delete(id);
|
|
276
|
+
nodeCache.delete(id);
|
|
277
|
+
updateSignals();
|
|
278
|
+
pathNotifier.notify(`${basePath}.${String(id)}`, undefined, entity);
|
|
279
|
+
for (const handler of tapHandlers) {
|
|
318
280
|
handler.onRemove?.(id, entity);
|
|
319
281
|
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return id;
|
|
342
|
-
}
|
|
343
|
-
upsertMany(entities, 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,
|
|
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({
|
|
352
303
|
id,
|
|
353
|
-
|
|
304
|
+
entity
|
|
354
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) {
|
|
332
|
+
if (predicate(entity)) {
|
|
333
|
+
idsToRemove.push(id);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (idsToRemove.length > 0) {
|
|
337
|
+
api.removeMany(idsToRemove);
|
|
338
|
+
}
|
|
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);
|
|
355
345
|
} else {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
346
|
+
api.addOne(entity, opts);
|
|
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
|
|
359
394
|
});
|
|
360
395
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
|
378
419
|
};
|
|
379
|
-
|
|
420
|
+
storage.set(id, finalUpdated);
|
|
421
|
+
nodeCache.delete(id);
|
|
422
|
+
updatedEntities.push({
|
|
423
|
+
id,
|
|
424
|
+
prev,
|
|
425
|
+
finalUpdated,
|
|
426
|
+
transformedChanges
|
|
427
|
+
});
|
|
380
428
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
addedEntities.push({
|
|
429
|
+
updateSignals();
|
|
430
|
+
for (const {
|
|
384
431
|
id,
|
|
385
|
-
entity
|
|
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);
|
|
432
|
+
entity
|
|
433
|
+
} of addedEntities) {
|
|
434
|
+
pathNotifier.notify(`${basePath}.${String(id)}`, entity, undefined);
|
|
407
435
|
}
|
|
408
|
-
const
|
|
409
|
-
...prev,
|
|
410
|
-
...transformedChanges
|
|
411
|
-
};
|
|
412
|
-
this.storage.set(id, finalUpdated);
|
|
413
|
-
this.nodeCache.delete(id);
|
|
414
|
-
updatedEntities.push({
|
|
436
|
+
for (const {
|
|
415
437
|
id,
|
|
416
438
|
prev,
|
|
417
|
-
finalUpdated
|
|
418
|
-
|
|
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);
|
|
439
|
+
finalUpdated
|
|
440
|
+
} of updatedEntities) {
|
|
441
|
+
pathNotifier.notify(`${basePath}.${String(id)}`, finalUpdated, prev);
|
|
441
442
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
handler.onUpdate?.(id, transformedChanges, finalUpdated);
|
|
443
|
+
for (const {
|
|
444
|
+
id,
|
|
445
|
+
entity
|
|
446
|
+
} of addedEntities) {
|
|
447
|
+
for (const handler of tapHandlers) {
|
|
448
|
+
handler.onAdd?.(entity, id);
|
|
449
|
+
}
|
|
450
450
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
removeAll() {
|
|
460
|
-
this.clear();
|
|
461
|
-
}
|
|
462
|
-
setAll(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);
|
|
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
|
+
}
|
|
481
459
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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);
|
|
496
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
|
+
};
|
|
497
522
|
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
this.tapHandlers.push(handlers);
|
|
501
|
-
return () => {
|
|
502
|
-
const idx = this.tapHandlers.indexOf(handlers);
|
|
503
|
-
if (idx > -1) this.tapHandlers.splice(idx, 1);
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
intercept(handlers) {
|
|
507
|
-
this.interceptHandlers.push(handlers);
|
|
508
|
-
return () => {
|
|
509
|
-
const idx = this.interceptHandlers.indexOf(handlers);
|
|
510
|
-
if (idx > -1) this.interceptHandlers.splice(idx, 1);
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
updateSignals() {
|
|
514
|
-
const entities = Array.from(this.storage.values());
|
|
515
|
-
const ids = Array.from(this.storage.keys());
|
|
516
|
-
const map = new Map(this.storage);
|
|
517
|
-
this.allSignal.set(entities);
|
|
518
|
-
this.countSignal.set(entities.length);
|
|
519
|
-
this.idsSignal.set(ids);
|
|
520
|
-
this.mapSignal.set(map);
|
|
521
|
-
}
|
|
522
|
-
getOrCreateNode(id, entity) {
|
|
523
|
-
let node = this.nodeCache.get(id);
|
|
524
|
-
if (!node) {
|
|
525
|
-
node = this.createEntityNode(id, entity);
|
|
526
|
-
this.nodeCache.set(id, node);
|
|
527
|
-
}
|
|
528
|
-
return node;
|
|
529
|
-
}
|
|
530
|
-
createEntityNode(id, entity) {
|
|
531
|
-
const node = () => this.storage.get(id);
|
|
532
|
-
for (const key of Object.keys(entity)) {
|
|
533
|
-
Object.defineProperty(node, key, {
|
|
534
|
-
get: () => {
|
|
535
|
-
const current = this.storage.get(id);
|
|
536
|
-
const value = current?.[key];
|
|
537
|
-
return () => value;
|
|
538
|
-
},
|
|
539
|
-
enumerable: true,
|
|
540
|
-
configurable: true
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
return node;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function createEntitySignal(config, pathNotifier, basePath) {
|
|
547
|
-
const impl = new EntitySignalImpl(config, pathNotifier, basePath);
|
|
548
|
-
return new Proxy(impl, {
|
|
523
|
+
};
|
|
524
|
+
return new Proxy(api, {
|
|
549
525
|
get: (target, prop) => {
|
|
550
526
|
if (typeof prop === 'string' && !isNaN(Number(prop))) {
|
|
551
|
-
return
|
|
527
|
+
return api.byId(Number(prop));
|
|
552
528
|
}
|
|
553
529
|
return target[prop];
|
|
554
530
|
}
|
|
555
531
|
});
|
|
556
532
|
}
|
|
557
533
|
|
|
558
|
-
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,
|