@zeix/cause-effect 0.17.1 → 0.17.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.
- package/.ai-context.md +13 -0
- package/.github/copilot-instructions.md +4 -0
- package/.zed/settings.json +3 -0
- package/CLAUDE.md +41 -7
- package/README.md +48 -25
- package/archive/benchmark.ts +0 -5
- package/archive/collection.ts +6 -65
- package/archive/composite.ts +85 -0
- package/archive/computed.ts +18 -20
- package/archive/list.ts +7 -75
- package/archive/memo.ts +15 -15
- package/archive/state.ts +2 -1
- package/archive/store.ts +8 -78
- package/archive/task.ts +20 -25
- package/index.dev.js +508 -526
- package/index.js +1 -1
- package/index.ts +9 -11
- package/package.json +6 -6
- package/src/classes/collection.ts +70 -107
- package/src/classes/computed.ts +165 -149
- package/src/classes/list.ts +145 -107
- package/src/classes/ref.ts +19 -17
- package/src/classes/state.ts +21 -17
- package/src/classes/store.ts +125 -73
- package/src/diff.ts +2 -1
- package/src/effect.ts +17 -10
- package/src/errors.ts +14 -1
- package/src/resolve.ts +1 -1
- package/src/signal.ts +3 -2
- package/src/system.ts +159 -61
- package/src/util.ts +0 -6
- package/test/batch.test.ts +4 -11
- package/test/benchmark.test.ts +4 -2
- package/test/collection.test.ts +106 -107
- package/test/computed.test.ts +351 -112
- package/test/effect.test.ts +2 -2
- package/test/list.test.ts +62 -102
- package/test/ref.test.ts +128 -2
- package/test/state.test.ts +16 -22
- package/test/store.test.ts +101 -108
- package/test/util/dependency-graph.ts +2 -2
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +5 -7
- package/types/index.d.ts +3 -3
- package/types/src/classes/collection.d.ts +9 -10
- package/types/src/classes/computed.d.ts +17 -20
- package/types/src/classes/list.d.ts +8 -6
- package/types/src/classes/ref.d.ts +8 -12
- package/types/src/classes/state.d.ts +5 -8
- package/types/src/classes/store.d.ts +14 -13
- package/types/src/effect.d.ts +1 -2
- package/types/src/errors.d.ts +2 -1
- package/types/src/signal.d.ts +3 -2
- package/types/src/system.d.ts +47 -34
- package/types/src/util.d.ts +1 -2
- package/src/classes/composite.ts +0 -176
- package/types/src/classes/composite.d.ts +0 -15
package/index.dev.js
CHANGED
|
@@ -1,5 +1,122 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/system.ts
|
|
2
|
+
var activeWatcher;
|
|
3
|
+
var watchersMap = new WeakMap;
|
|
4
|
+
var watchedCallbackMap = new WeakMap;
|
|
5
|
+
var unwatchedCallbackMap = new WeakMap;
|
|
6
|
+
var pendingReactions = new Set;
|
|
7
|
+
var batchDepth = 0;
|
|
2
8
|
var UNSET = Symbol();
|
|
9
|
+
var createWatcher = (push, pull) => {
|
|
10
|
+
const cleanups = new Set;
|
|
11
|
+
const watcher = push;
|
|
12
|
+
watcher.run = () => {
|
|
13
|
+
const prev = activeWatcher;
|
|
14
|
+
activeWatcher = watcher;
|
|
15
|
+
try {
|
|
16
|
+
pull();
|
|
17
|
+
} finally {
|
|
18
|
+
activeWatcher = prev;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
watcher.onCleanup = (cleanup) => {
|
|
22
|
+
cleanups.add(cleanup);
|
|
23
|
+
};
|
|
24
|
+
watcher.stop = () => {
|
|
25
|
+
try {
|
|
26
|
+
for (const cleanup of cleanups)
|
|
27
|
+
cleanup();
|
|
28
|
+
} finally {
|
|
29
|
+
cleanups.clear();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return watcher;
|
|
33
|
+
};
|
|
34
|
+
var untrack = (callback) => {
|
|
35
|
+
const prev = activeWatcher;
|
|
36
|
+
activeWatcher = undefined;
|
|
37
|
+
try {
|
|
38
|
+
callback();
|
|
39
|
+
} finally {
|
|
40
|
+
activeWatcher = prev;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var registerWatchCallbacks = (signal, watched, unwatched) => {
|
|
44
|
+
watchedCallbackMap.set(signal, watched);
|
|
45
|
+
if (unwatched)
|
|
46
|
+
unwatchedCallbackMap.set(signal, unwatched);
|
|
47
|
+
};
|
|
48
|
+
var subscribeTo = (signal) => {
|
|
49
|
+
if (!activeWatcher || watchersMap.get(signal)?.has(activeWatcher))
|
|
50
|
+
return false;
|
|
51
|
+
const watcher = activeWatcher;
|
|
52
|
+
if (!watchersMap.has(signal))
|
|
53
|
+
watchersMap.set(signal, new Set);
|
|
54
|
+
const watchers = watchersMap.get(signal);
|
|
55
|
+
assert(watchers);
|
|
56
|
+
if (!watchers.size) {
|
|
57
|
+
const watchedCallback = watchedCallbackMap.get(signal);
|
|
58
|
+
if (watchedCallback)
|
|
59
|
+
untrack(watchedCallback);
|
|
60
|
+
}
|
|
61
|
+
watchers.add(watcher);
|
|
62
|
+
watcher.onCleanup(() => {
|
|
63
|
+
watchers.delete(watcher);
|
|
64
|
+
if (!watchers.size) {
|
|
65
|
+
const unwatchedCallback = unwatchedCallbackMap.get(signal);
|
|
66
|
+
if (unwatchedCallback)
|
|
67
|
+
untrack(unwatchedCallback);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
var unsubscribeAllFrom = (signal) => {
|
|
73
|
+
const watchers = watchersMap.get(signal);
|
|
74
|
+
if (!watchers)
|
|
75
|
+
return;
|
|
76
|
+
for (const watcher of watchers)
|
|
77
|
+
watcher.stop();
|
|
78
|
+
watchers.clear();
|
|
79
|
+
};
|
|
80
|
+
var notifyOf = (signal) => {
|
|
81
|
+
const watchers = watchersMap.get(signal);
|
|
82
|
+
if (!watchers?.size)
|
|
83
|
+
return false;
|
|
84
|
+
for (const react of watchers) {
|
|
85
|
+
if (batchDepth)
|
|
86
|
+
pendingReactions.add(react);
|
|
87
|
+
else
|
|
88
|
+
react();
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
};
|
|
92
|
+
var flush = () => {
|
|
93
|
+
while (pendingReactions.size) {
|
|
94
|
+
const watchers = Array.from(pendingReactions);
|
|
95
|
+
pendingReactions.clear();
|
|
96
|
+
for (const react of watchers)
|
|
97
|
+
react();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var batch = (callback) => {
|
|
101
|
+
batchDepth++;
|
|
102
|
+
try {
|
|
103
|
+
callback();
|
|
104
|
+
} finally {
|
|
105
|
+
flush();
|
|
106
|
+
batchDepth--;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var track = (watcher, run) => {
|
|
110
|
+
const prev = activeWatcher;
|
|
111
|
+
activeWatcher = watcher || undefined;
|
|
112
|
+
try {
|
|
113
|
+
run();
|
|
114
|
+
} finally {
|
|
115
|
+
activeWatcher = prev;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/util.ts
|
|
3
120
|
var isString = (value) => typeof value === "string";
|
|
4
121
|
var isNumber = (value) => typeof value === "number";
|
|
5
122
|
var isSymbol = (value) => typeof value === "symbol";
|
|
@@ -101,128 +218,66 @@ var diff = (oldObj, newObj) => {
|
|
|
101
218
|
};
|
|
102
219
|
};
|
|
103
220
|
|
|
104
|
-
// src/system.ts
|
|
105
|
-
var activeWatcher;
|
|
106
|
-
var pendingReactions = new Set;
|
|
107
|
-
var batchDepth = 0;
|
|
108
|
-
var createWatcher = (react) => {
|
|
109
|
-
const cleanups = new Set;
|
|
110
|
-
const watcher = react;
|
|
111
|
-
watcher.onCleanup = (cleanup) => {
|
|
112
|
-
cleanups.add(cleanup);
|
|
113
|
-
};
|
|
114
|
-
watcher.stop = () => {
|
|
115
|
-
for (const cleanup of cleanups)
|
|
116
|
-
cleanup();
|
|
117
|
-
cleanups.clear();
|
|
118
|
-
};
|
|
119
|
-
return watcher;
|
|
120
|
-
};
|
|
121
|
-
var subscribeActiveWatcher = (watchers) => {
|
|
122
|
-
if (activeWatcher && !watchers.has(activeWatcher)) {
|
|
123
|
-
const watcher = activeWatcher;
|
|
124
|
-
watcher.onCleanup(() => watchers.delete(watcher));
|
|
125
|
-
watchers.add(watcher);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
var notifyWatchers = (watchers) => {
|
|
129
|
-
for (const react of watchers) {
|
|
130
|
-
if (batchDepth)
|
|
131
|
-
pendingReactions.add(react);
|
|
132
|
-
else
|
|
133
|
-
react();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
var flushPendingReactions = () => {
|
|
137
|
-
while (pendingReactions.size) {
|
|
138
|
-
const watchers = Array.from(pendingReactions);
|
|
139
|
-
pendingReactions.clear();
|
|
140
|
-
for (const watcher of watchers)
|
|
141
|
-
watcher();
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
var batchSignalWrites = (callback) => {
|
|
145
|
-
batchDepth++;
|
|
146
|
-
try {
|
|
147
|
-
callback();
|
|
148
|
-
} finally {
|
|
149
|
-
flushPendingReactions();
|
|
150
|
-
batchDepth--;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
var trackSignalReads = (watcher, run) => {
|
|
154
|
-
const prev = activeWatcher;
|
|
155
|
-
activeWatcher = watcher || undefined;
|
|
156
|
-
try {
|
|
157
|
-
run();
|
|
158
|
-
} finally {
|
|
159
|
-
activeWatcher = prev;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
var emitNotification = (listeners, payload) => {
|
|
163
|
-
for (const listener of listeners) {
|
|
164
|
-
if (batchDepth)
|
|
165
|
-
pendingReactions.add(() => listener(payload));
|
|
166
|
-
else
|
|
167
|
-
listener(payload);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
221
|
// src/classes/computed.ts
|
|
172
222
|
var TYPE_COMPUTED = "Computed";
|
|
173
223
|
|
|
174
224
|
class Memo {
|
|
175
|
-
#watchers = new Set;
|
|
176
225
|
#callback;
|
|
177
226
|
#value;
|
|
178
227
|
#error;
|
|
179
228
|
#dirty = true;
|
|
180
229
|
#computing = false;
|
|
181
230
|
#watcher;
|
|
182
|
-
constructor(callback,
|
|
183
|
-
validateCallback(
|
|
184
|
-
|
|
231
|
+
constructor(callback, options) {
|
|
232
|
+
validateCallback(this.constructor.name, callback, isMemoCallback);
|
|
233
|
+
const initialValue = options?.initialValue ?? UNSET;
|
|
234
|
+
validateSignalValue(this.constructor.name, initialValue, options?.guard);
|
|
185
235
|
this.#callback = callback;
|
|
186
236
|
this.#value = initialValue;
|
|
187
|
-
|
|
237
|
+
if (options?.watched)
|
|
238
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
239
|
+
}
|
|
240
|
+
#getWatcher() {
|
|
241
|
+
this.#watcher ||= createWatcher(() => {
|
|
188
242
|
this.#dirty = true;
|
|
189
|
-
if (this
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
243
|
+
if (!notifyOf(this))
|
|
244
|
+
this.#watcher?.stop();
|
|
245
|
+
}, () => {
|
|
246
|
+
if (this.#computing)
|
|
247
|
+
throw new CircularDependencyError("memo");
|
|
248
|
+
let result;
|
|
249
|
+
this.#computing = true;
|
|
250
|
+
try {
|
|
251
|
+
result = this.#callback(this.#value);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
this.#value = UNSET;
|
|
254
|
+
this.#error = createError(e);
|
|
255
|
+
this.#computing = false;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (result == null || UNSET === result) {
|
|
259
|
+
this.#value = UNSET;
|
|
260
|
+
this.#error = undefined;
|
|
261
|
+
} else {
|
|
262
|
+
this.#value = result;
|
|
263
|
+
this.#error = undefined;
|
|
264
|
+
this.#dirty = false;
|
|
265
|
+
}
|
|
266
|
+
this.#computing = false;
|
|
267
|
+
});
|
|
268
|
+
this.#watcher.onCleanup(() => {
|
|
269
|
+
this.#watcher = undefined;
|
|
193
270
|
});
|
|
271
|
+
return this.#watcher;
|
|
194
272
|
}
|
|
195
273
|
get [Symbol.toStringTag]() {
|
|
196
274
|
return TYPE_COMPUTED;
|
|
197
275
|
}
|
|
198
276
|
get() {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (this.#dirty)
|
|
202
|
-
|
|
203
|
-
if (this.#computing)
|
|
204
|
-
throw new CircularDependencyError("memo");
|
|
205
|
-
let result;
|
|
206
|
-
this.#computing = true;
|
|
207
|
-
try {
|
|
208
|
-
result = this.#callback(this.#value);
|
|
209
|
-
} catch (e) {
|
|
210
|
-
this.#value = UNSET;
|
|
211
|
-
this.#error = createError(e);
|
|
212
|
-
this.#computing = false;
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (result == null || UNSET === result) {
|
|
216
|
-
this.#value = UNSET;
|
|
217
|
-
this.#error = undefined;
|
|
218
|
-
} else {
|
|
219
|
-
this.#value = result;
|
|
220
|
-
this.#error = undefined;
|
|
221
|
-
this.#dirty = false;
|
|
222
|
-
}
|
|
223
|
-
this.#computing = false;
|
|
224
|
-
});
|
|
225
|
-
}
|
|
277
|
+
subscribeTo(this);
|
|
278
|
+
flush();
|
|
279
|
+
if (this.#dirty)
|
|
280
|
+
this.#getWatcher().run();
|
|
226
281
|
if (this.#error)
|
|
227
282
|
throw this.#error;
|
|
228
283
|
return this.#value;
|
|
@@ -230,7 +285,6 @@ class Memo {
|
|
|
230
285
|
}
|
|
231
286
|
|
|
232
287
|
class Task {
|
|
233
|
-
#watchers = new Set;
|
|
234
288
|
#callback;
|
|
235
289
|
#value;
|
|
236
290
|
#error;
|
|
@@ -239,245 +293,137 @@ class Task {
|
|
|
239
293
|
#changed = false;
|
|
240
294
|
#watcher;
|
|
241
295
|
#controller;
|
|
242
|
-
constructor(callback,
|
|
243
|
-
validateCallback(
|
|
244
|
-
|
|
296
|
+
constructor(callback, options) {
|
|
297
|
+
validateCallback(this.constructor.name, callback, isTaskCallback);
|
|
298
|
+
const initialValue = options?.initialValue ?? UNSET;
|
|
299
|
+
validateSignalValue(this.constructor.name, initialValue, options?.guard);
|
|
245
300
|
this.#callback = callback;
|
|
246
301
|
this.#value = initialValue;
|
|
247
|
-
|
|
248
|
-
this
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.#
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.#
|
|
268
|
-
this.#
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
const nil = () => {
|
|
274
|
-
this.#changed = UNSET !== this.#value;
|
|
275
|
-
this.#value = UNSET;
|
|
276
|
-
this.#error = undefined;
|
|
277
|
-
};
|
|
278
|
-
const err = (e) => {
|
|
279
|
-
const newError = createError(e);
|
|
280
|
-
this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
|
|
281
|
-
this.#value = UNSET;
|
|
282
|
-
this.#error = newError;
|
|
283
|
-
};
|
|
284
|
-
const settle = (fn) => (arg) => {
|
|
285
|
-
this.#computing = false;
|
|
286
|
-
this.#controller = undefined;
|
|
287
|
-
fn(arg);
|
|
288
|
-
if (this.#changed)
|
|
289
|
-
notifyWatchers(this.#watchers);
|
|
290
|
-
};
|
|
291
|
-
const compute = () => trackSignalReads(this.#watcher, () => {
|
|
292
|
-
if (this.#computing)
|
|
293
|
-
throw new CircularDependencyError("task");
|
|
294
|
-
this.#changed = false;
|
|
295
|
-
if (this.#controller)
|
|
296
|
-
return this.#value;
|
|
297
|
-
this.#controller = new AbortController;
|
|
298
|
-
this.#controller.signal.addEventListener("abort", () => {
|
|
302
|
+
if (options?.watched)
|
|
303
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
304
|
+
}
|
|
305
|
+
#getWatcher() {
|
|
306
|
+
if (!this.#watcher) {
|
|
307
|
+
const ok = (v) => {
|
|
308
|
+
if (!isEqual(v, this.#value)) {
|
|
309
|
+
this.#value = v;
|
|
310
|
+
this.#changed = true;
|
|
311
|
+
}
|
|
312
|
+
this.#error = undefined;
|
|
313
|
+
this.#dirty = false;
|
|
314
|
+
};
|
|
315
|
+
const nil = () => {
|
|
316
|
+
this.#changed = UNSET !== this.#value;
|
|
317
|
+
this.#value = UNSET;
|
|
318
|
+
this.#error = undefined;
|
|
319
|
+
};
|
|
320
|
+
const err = (e) => {
|
|
321
|
+
const newError = createError(e);
|
|
322
|
+
this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
|
|
323
|
+
this.#value = UNSET;
|
|
324
|
+
this.#error = newError;
|
|
325
|
+
};
|
|
326
|
+
const settle = (fn) => (arg) => {
|
|
299
327
|
this.#computing = false;
|
|
300
328
|
this.#controller = undefined;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
329
|
+
fn(arg);
|
|
330
|
+
if (this.#changed && !notifyOf(this))
|
|
331
|
+
this.#watcher?.stop();
|
|
332
|
+
};
|
|
333
|
+
this.#watcher = createWatcher(() => {
|
|
334
|
+
this.#dirty = true;
|
|
335
|
+
this.#controller?.abort();
|
|
336
|
+
if (!notifyOf(this))
|
|
337
|
+
this.#watcher?.stop();
|
|
338
|
+
}, () => {
|
|
339
|
+
if (this.#computing)
|
|
340
|
+
throw new CircularDependencyError("task");
|
|
341
|
+
this.#changed = false;
|
|
342
|
+
if (this.#controller)
|
|
343
|
+
return this.#value;
|
|
344
|
+
this.#controller = new AbortController;
|
|
345
|
+
this.#controller.signal.addEventListener("abort", () => {
|
|
346
|
+
this.#computing = false;
|
|
347
|
+
this.#controller = undefined;
|
|
348
|
+
this.#getWatcher().run();
|
|
349
|
+
}, {
|
|
350
|
+
once: true
|
|
351
|
+
});
|
|
352
|
+
let result;
|
|
353
|
+
this.#computing = true;
|
|
354
|
+
try {
|
|
355
|
+
result = this.#callback(this.#value, this.#controller.signal);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
if (isAbortError(e))
|
|
358
|
+
nil();
|
|
359
|
+
else
|
|
360
|
+
err(e);
|
|
361
|
+
this.#computing = false;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (result instanceof Promise)
|
|
365
|
+
result.then(settle(ok), settle(err));
|
|
366
|
+
else if (result == null || UNSET === result)
|
|
311
367
|
nil();
|
|
312
368
|
else
|
|
313
|
-
|
|
369
|
+
ok(result);
|
|
314
370
|
this.#computing = false;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
371
|
+
});
|
|
372
|
+
this.#watcher.onCleanup(() => {
|
|
373
|
+
this.#controller?.abort();
|
|
374
|
+
this.#controller = undefined;
|
|
375
|
+
this.#watcher = undefined;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return this.#watcher;
|
|
379
|
+
}
|
|
380
|
+
get [Symbol.toStringTag]() {
|
|
381
|
+
return TYPE_COMPUTED;
|
|
382
|
+
}
|
|
383
|
+
get() {
|
|
384
|
+
subscribeTo(this);
|
|
385
|
+
flush();
|
|
325
386
|
if (this.#dirty)
|
|
326
|
-
|
|
387
|
+
this.#getWatcher().run();
|
|
327
388
|
if (this.#error)
|
|
328
389
|
throw this.#error;
|
|
329
390
|
return this.#value;
|
|
330
391
|
}
|
|
331
392
|
}
|
|
332
|
-
var createComputed = (callback,
|
|
393
|
+
var createComputed = (callback, options) => isAsyncFunction(callback) ? new Task(callback, options) : new Memo(callback, options);
|
|
333
394
|
var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
|
|
334
395
|
var isMemoCallback = (value) => isSyncFunction(value) && value.length < 2;
|
|
335
396
|
var isTaskCallback = (value) => isAsyncFunction(value) && value.length < 3;
|
|
336
397
|
|
|
337
|
-
// src/classes/composite.ts
|
|
338
|
-
class Composite {
|
|
339
|
-
signals = new Map;
|
|
340
|
-
#validate;
|
|
341
|
-
#create;
|
|
342
|
-
#watchers = new Map;
|
|
343
|
-
#listeners = {
|
|
344
|
-
add: new Set,
|
|
345
|
-
change: new Set,
|
|
346
|
-
remove: new Set
|
|
347
|
-
};
|
|
348
|
-
#batching = false;
|
|
349
|
-
constructor(values, validate, create) {
|
|
350
|
-
this.#validate = validate;
|
|
351
|
-
this.#create = create;
|
|
352
|
-
this.change({
|
|
353
|
-
add: values,
|
|
354
|
-
change: {},
|
|
355
|
-
remove: {},
|
|
356
|
-
changed: true
|
|
357
|
-
}, true);
|
|
358
|
-
}
|
|
359
|
-
#addWatcher(key) {
|
|
360
|
-
const watcher = createWatcher(() => {
|
|
361
|
-
trackSignalReads(watcher, () => {
|
|
362
|
-
this.signals.get(key)?.get();
|
|
363
|
-
if (!this.#batching)
|
|
364
|
-
emitNotification(this.#listeners.change, [key]);
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
this.#watchers.set(key, watcher);
|
|
368
|
-
watcher();
|
|
369
|
-
}
|
|
370
|
-
add(key, value) {
|
|
371
|
-
if (!this.#validate(key, value))
|
|
372
|
-
return false;
|
|
373
|
-
this.signals.set(key, this.#create(value));
|
|
374
|
-
if (this.#listeners.change.size)
|
|
375
|
-
this.#addWatcher(key);
|
|
376
|
-
if (!this.#batching)
|
|
377
|
-
emitNotification(this.#listeners.add, [key]);
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
remove(key) {
|
|
381
|
-
const ok = this.signals.delete(key);
|
|
382
|
-
if (!ok)
|
|
383
|
-
return false;
|
|
384
|
-
const watcher = this.#watchers.get(key);
|
|
385
|
-
if (watcher) {
|
|
386
|
-
watcher.stop();
|
|
387
|
-
this.#watchers.delete(key);
|
|
388
|
-
}
|
|
389
|
-
if (!this.#batching)
|
|
390
|
-
emitNotification(this.#listeners.remove, [key]);
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
change(changes, initialRun) {
|
|
394
|
-
this.#batching = true;
|
|
395
|
-
if (Object.keys(changes.add).length) {
|
|
396
|
-
for (const key in changes.add)
|
|
397
|
-
this.add(key, changes.add[key]);
|
|
398
|
-
const notify = () => emitNotification(this.#listeners.add, Object.keys(changes.add));
|
|
399
|
-
if (initialRun)
|
|
400
|
-
setTimeout(notify, 0);
|
|
401
|
-
else
|
|
402
|
-
notify();
|
|
403
|
-
}
|
|
404
|
-
if (Object.keys(changes.change).length) {
|
|
405
|
-
batchSignalWrites(() => {
|
|
406
|
-
for (const key in changes.change) {
|
|
407
|
-
const value = changes.change[key];
|
|
408
|
-
if (!this.#validate(key, value))
|
|
409
|
-
continue;
|
|
410
|
-
const signal = this.signals.get(key);
|
|
411
|
-
if (guardMutableSignal(`list item "${key}"`, value, signal))
|
|
412
|
-
signal.set(value);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
emitNotification(this.#listeners.change, Object.keys(changes.change));
|
|
416
|
-
}
|
|
417
|
-
if (Object.keys(changes.remove).length) {
|
|
418
|
-
for (const key in changes.remove)
|
|
419
|
-
this.remove(key);
|
|
420
|
-
emitNotification(this.#listeners.remove, Object.keys(changes.remove));
|
|
421
|
-
}
|
|
422
|
-
this.#batching = false;
|
|
423
|
-
return changes.changed;
|
|
424
|
-
}
|
|
425
|
-
clear() {
|
|
426
|
-
const keys = Array.from(this.signals.keys());
|
|
427
|
-
this.signals.clear();
|
|
428
|
-
this.#watchers.clear();
|
|
429
|
-
emitNotification(this.#listeners.remove, keys);
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
on(type, listener) {
|
|
433
|
-
this.#listeners[type].add(listener);
|
|
434
|
-
if (type === "change" && !this.#watchers.size) {
|
|
435
|
-
this.#batching = true;
|
|
436
|
-
for (const key of this.signals.keys())
|
|
437
|
-
this.#addWatcher(key);
|
|
438
|
-
this.#batching = false;
|
|
439
|
-
}
|
|
440
|
-
return () => {
|
|
441
|
-
this.#listeners[type].delete(listener);
|
|
442
|
-
if (type === "change" && !this.#listeners.change.size) {
|
|
443
|
-
if (this.#watchers.size) {
|
|
444
|
-
for (const watcher of this.#watchers.values())
|
|
445
|
-
watcher.stop();
|
|
446
|
-
this.#watchers.clear();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
398
|
// src/classes/state.ts
|
|
454
399
|
var TYPE_STATE = "State";
|
|
455
400
|
|
|
456
401
|
class State {
|
|
457
|
-
#watchers = new Set;
|
|
458
402
|
#value;
|
|
459
|
-
constructor(initialValue) {
|
|
460
|
-
validateSignalValue(
|
|
403
|
+
constructor(initialValue, options) {
|
|
404
|
+
validateSignalValue(TYPE_STATE, initialValue, options?.guard);
|
|
461
405
|
this.#value = initialValue;
|
|
406
|
+
if (options?.watched)
|
|
407
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
462
408
|
}
|
|
463
409
|
get [Symbol.toStringTag]() {
|
|
464
410
|
return TYPE_STATE;
|
|
465
411
|
}
|
|
466
412
|
get() {
|
|
467
|
-
|
|
413
|
+
subscribeTo(this);
|
|
468
414
|
return this.#value;
|
|
469
415
|
}
|
|
470
416
|
set(newValue) {
|
|
471
|
-
validateSignalValue(
|
|
417
|
+
validateSignalValue(TYPE_STATE, newValue);
|
|
472
418
|
if (isEqual(this.#value, newValue))
|
|
473
419
|
return;
|
|
474
420
|
this.#value = newValue;
|
|
475
|
-
|
|
421
|
+
notifyOf(this);
|
|
476
422
|
if (UNSET === this.#value)
|
|
477
|
-
this
|
|
423
|
+
unsubscribeAllFrom(this);
|
|
478
424
|
}
|
|
479
425
|
update(updater) {
|
|
480
|
-
validateCallback(
|
|
426
|
+
validateCallback(`${TYPE_STATE} update`, updater);
|
|
481
427
|
this.set(updater(this.#value));
|
|
482
428
|
}
|
|
483
429
|
}
|
|
@@ -487,21 +433,27 @@ var isState = (value) => isObjectOfType(value, TYPE_STATE);
|
|
|
487
433
|
var TYPE_LIST = "List";
|
|
488
434
|
|
|
489
435
|
class List {
|
|
490
|
-
#
|
|
491
|
-
#
|
|
492
|
-
#listeners = {
|
|
493
|
-
sort: new Set
|
|
494
|
-
};
|
|
495
|
-
#order = [];
|
|
436
|
+
#signals = new Map;
|
|
437
|
+
#keys = [];
|
|
496
438
|
#generateKey;
|
|
497
|
-
|
|
498
|
-
|
|
439
|
+
#validate;
|
|
440
|
+
constructor(initialValue, options) {
|
|
441
|
+
validateSignalValue(TYPE_LIST, initialValue, Array.isArray);
|
|
499
442
|
let keyCounter = 0;
|
|
443
|
+
const keyConfig = options?.keyConfig;
|
|
500
444
|
this.#generateKey = isString(keyConfig) ? () => `${keyConfig}${keyCounter++}` : isFunction(keyConfig) ? (item) => keyConfig(item) : () => String(keyCounter++);
|
|
501
|
-
this.#
|
|
502
|
-
validateSignalValue(
|
|
445
|
+
this.#validate = (key, value) => {
|
|
446
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value, options?.guard);
|
|
503
447
|
return true;
|
|
504
|
-
}
|
|
448
|
+
};
|
|
449
|
+
this.#change({
|
|
450
|
+
add: this.#toRecord(initialValue),
|
|
451
|
+
change: {},
|
|
452
|
+
remove: {},
|
|
453
|
+
changed: true
|
|
454
|
+
});
|
|
455
|
+
if (options?.watched)
|
|
456
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
505
457
|
}
|
|
506
458
|
#toRecord(array) {
|
|
507
459
|
const record = {};
|
|
@@ -509,17 +461,51 @@ class List {
|
|
|
509
461
|
const value = array[i];
|
|
510
462
|
if (value === undefined)
|
|
511
463
|
continue;
|
|
512
|
-
let key = this.#
|
|
464
|
+
let key = this.#keys[i];
|
|
513
465
|
if (!key) {
|
|
514
466
|
key = this.#generateKey(value);
|
|
515
|
-
this.#
|
|
467
|
+
this.#keys[i] = key;
|
|
516
468
|
}
|
|
517
469
|
record[key] = value;
|
|
518
470
|
}
|
|
519
471
|
return record;
|
|
520
472
|
}
|
|
473
|
+
#add(key, value) {
|
|
474
|
+
if (!this.#validate(key, value))
|
|
475
|
+
return false;
|
|
476
|
+
this.#signals.set(key, new State(value));
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
#change(changes) {
|
|
480
|
+
if (Object.keys(changes.add).length) {
|
|
481
|
+
for (const key in changes.add)
|
|
482
|
+
this.#add(key, changes.add[key]);
|
|
483
|
+
}
|
|
484
|
+
if (Object.keys(changes.change).length) {
|
|
485
|
+
batch(() => {
|
|
486
|
+
for (const key in changes.change) {
|
|
487
|
+
const value = changes.change[key];
|
|
488
|
+
if (!this.#validate(key, value))
|
|
489
|
+
continue;
|
|
490
|
+
const signal = this.#signals.get(key);
|
|
491
|
+
if (guardMutableSignal(`${TYPE_LIST} item "${key}"`, value, signal))
|
|
492
|
+
signal.set(value);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (Object.keys(changes.remove).length) {
|
|
497
|
+
for (const key in changes.remove) {
|
|
498
|
+
this.#signals.delete(key);
|
|
499
|
+
const index = this.#keys.indexOf(key);
|
|
500
|
+
if (index !== -1)
|
|
501
|
+
this.#keys.splice(index, 1);
|
|
502
|
+
}
|
|
503
|
+
this.#keys = this.#keys.filter(() => true);
|
|
504
|
+
}
|
|
505
|
+
return changes.changed;
|
|
506
|
+
}
|
|
521
507
|
get #value() {
|
|
522
|
-
return this.#
|
|
508
|
+
return this.#keys.map((key) => this.#signals.get(key)?.get()).filter((v) => v !== undefined);
|
|
523
509
|
}
|
|
524
510
|
get [Symbol.toStringTag]() {
|
|
525
511
|
return TYPE_LIST;
|
|
@@ -528,136 +514,117 @@ class List {
|
|
|
528
514
|
return true;
|
|
529
515
|
}
|
|
530
516
|
*[Symbol.iterator]() {
|
|
531
|
-
for (const key of this.#
|
|
532
|
-
const signal = this.#
|
|
517
|
+
for (const key of this.#keys) {
|
|
518
|
+
const signal = this.#signals.get(key);
|
|
533
519
|
if (signal)
|
|
534
520
|
yield signal;
|
|
535
521
|
}
|
|
536
522
|
}
|
|
537
523
|
get length() {
|
|
538
|
-
|
|
539
|
-
return this.#
|
|
524
|
+
subscribeTo(this);
|
|
525
|
+
return this.#keys.length;
|
|
540
526
|
}
|
|
541
527
|
get() {
|
|
542
|
-
|
|
528
|
+
subscribeTo(this);
|
|
543
529
|
return this.#value;
|
|
544
530
|
}
|
|
545
531
|
set(newValue) {
|
|
546
532
|
if (UNSET === newValue) {
|
|
547
|
-
this.#
|
|
548
|
-
|
|
549
|
-
this
|
|
533
|
+
this.#signals.clear();
|
|
534
|
+
notifyOf(this);
|
|
535
|
+
unsubscribeAllFrom(this);
|
|
550
536
|
return;
|
|
551
537
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const changed = this.#composite.change(changes);
|
|
556
|
-
if (changed) {
|
|
557
|
-
for (const key of removedKeys) {
|
|
558
|
-
const index = this.#order.indexOf(key);
|
|
559
|
-
if (index !== -1)
|
|
560
|
-
this.#order.splice(index, 1);
|
|
561
|
-
}
|
|
562
|
-
this.#order = this.#order.filter(() => true);
|
|
563
|
-
notifyWatchers(this.#watchers);
|
|
564
|
-
}
|
|
538
|
+
const changes = diff(this.#toRecord(this.#value), this.#toRecord(newValue));
|
|
539
|
+
if (this.#change(changes))
|
|
540
|
+
notifyOf(this);
|
|
565
541
|
}
|
|
566
542
|
update(fn) {
|
|
567
543
|
this.set(fn(this.get()));
|
|
568
544
|
}
|
|
569
545
|
at(index) {
|
|
570
|
-
return this.#
|
|
546
|
+
return this.#signals.get(this.#keys[index]);
|
|
571
547
|
}
|
|
572
548
|
keys() {
|
|
573
|
-
|
|
549
|
+
subscribeTo(this);
|
|
550
|
+
return this.#keys.values();
|
|
574
551
|
}
|
|
575
552
|
byKey(key) {
|
|
576
|
-
return this.#
|
|
553
|
+
return this.#signals.get(key);
|
|
577
554
|
}
|
|
578
555
|
keyAt(index) {
|
|
579
|
-
return this.#
|
|
556
|
+
return this.#keys[index];
|
|
580
557
|
}
|
|
581
558
|
indexOfKey(key) {
|
|
582
|
-
return this.#
|
|
559
|
+
return this.#keys.indexOf(key);
|
|
583
560
|
}
|
|
584
561
|
add(value) {
|
|
585
562
|
const key = this.#generateKey(value);
|
|
586
|
-
if (this.#
|
|
563
|
+
if (this.#signals.has(key))
|
|
587
564
|
throw new DuplicateKeyError("store", key, value);
|
|
588
|
-
if (!this.#
|
|
589
|
-
this.#
|
|
590
|
-
const ok = this.#
|
|
565
|
+
if (!this.#keys.includes(key))
|
|
566
|
+
this.#keys.push(key);
|
|
567
|
+
const ok = this.#add(key, value);
|
|
591
568
|
if (ok)
|
|
592
|
-
|
|
569
|
+
notifyOf(this);
|
|
593
570
|
return key;
|
|
594
571
|
}
|
|
595
572
|
remove(keyOrIndex) {
|
|
596
|
-
const key = isNumber(keyOrIndex) ? this.#
|
|
597
|
-
const ok = this.#
|
|
573
|
+
const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex;
|
|
574
|
+
const ok = this.#signals.delete(key);
|
|
598
575
|
if (ok) {
|
|
599
|
-
const index = isNumber(keyOrIndex) ? keyOrIndex : this.#
|
|
576
|
+
const index = isNumber(keyOrIndex) ? keyOrIndex : this.#keys.indexOf(key);
|
|
600
577
|
if (index >= 0)
|
|
601
|
-
this.#
|
|
602
|
-
this.#
|
|
603
|
-
|
|
578
|
+
this.#keys.splice(index, 1);
|
|
579
|
+
this.#keys = this.#keys.filter(() => true);
|
|
580
|
+
notifyOf(this);
|
|
604
581
|
}
|
|
605
582
|
}
|
|
606
583
|
sort(compareFn) {
|
|
607
|
-
const entries = this.#
|
|
584
|
+
const entries = this.#keys.map((key) => [key, this.#signals.get(key)?.get()]).sort(isFunction(compareFn) ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
|
|
608
585
|
const newOrder = entries.map(([key]) => key);
|
|
609
|
-
if (!isEqual(this.#
|
|
610
|
-
this.#
|
|
611
|
-
|
|
612
|
-
emitNotification(this.#listeners.sort, this.#order);
|
|
586
|
+
if (!isEqual(this.#keys, newOrder)) {
|
|
587
|
+
this.#keys = newOrder;
|
|
588
|
+
notifyOf(this);
|
|
613
589
|
}
|
|
614
590
|
}
|
|
615
591
|
splice(start, deleteCount, ...items) {
|
|
616
|
-
const length = this.#
|
|
592
|
+
const length = this.#keys.length;
|
|
617
593
|
const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
|
|
618
594
|
const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
|
|
619
595
|
const add = {};
|
|
620
596
|
const remove = {};
|
|
621
597
|
for (let i = 0;i < actualDeleteCount; i++) {
|
|
622
598
|
const index = actualStart + i;
|
|
623
|
-
const key = this.#
|
|
599
|
+
const key = this.#keys[index];
|
|
624
600
|
if (key) {
|
|
625
|
-
const signal = this.#
|
|
601
|
+
const signal = this.#signals.get(key);
|
|
626
602
|
if (signal)
|
|
627
603
|
remove[key] = signal.get();
|
|
628
604
|
}
|
|
629
605
|
}
|
|
630
|
-
const newOrder = this.#
|
|
606
|
+
const newOrder = this.#keys.slice(0, actualStart);
|
|
631
607
|
for (const item of items) {
|
|
632
608
|
const key = this.#generateKey(item);
|
|
633
609
|
newOrder.push(key);
|
|
634
610
|
add[key] = item;
|
|
635
611
|
}
|
|
636
|
-
newOrder.push(...this.#
|
|
612
|
+
newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount));
|
|
637
613
|
const changed = !!(Object.keys(add).length || Object.keys(remove).length);
|
|
638
614
|
if (changed) {
|
|
639
|
-
this.#
|
|
615
|
+
this.#change({
|
|
640
616
|
add,
|
|
641
617
|
change: {},
|
|
642
618
|
remove,
|
|
643
619
|
changed
|
|
644
620
|
});
|
|
645
|
-
this.#
|
|
646
|
-
|
|
621
|
+
this.#keys = newOrder.filter(() => true);
|
|
622
|
+
notifyOf(this);
|
|
647
623
|
}
|
|
648
624
|
return Object.values(remove);
|
|
649
625
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
this.#listeners.sort.add(listener);
|
|
653
|
-
return () => {
|
|
654
|
-
this.#listeners.sort.delete(listener);
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
return this.#composite.on(type, listener);
|
|
658
|
-
}
|
|
659
|
-
deriveCollection(callback) {
|
|
660
|
-
return new DerivedCollection(this, callback);
|
|
626
|
+
deriveCollection(callback, options) {
|
|
627
|
+
return new DerivedCollection(this, callback, options);
|
|
661
628
|
}
|
|
662
629
|
}
|
|
663
630
|
var isList = (value) => isObjectOfType(value, TYPE_LIST);
|
|
@@ -666,21 +633,57 @@ var isList = (value) => isObjectOfType(value, TYPE_LIST);
|
|
|
666
633
|
var TYPE_STORE = "Store";
|
|
667
634
|
|
|
668
635
|
class BaseStore {
|
|
669
|
-
#
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
636
|
+
#signals = new Map;
|
|
637
|
+
constructor(initialValue, options) {
|
|
638
|
+
validateSignalValue(TYPE_STORE, initialValue, options?.guard ?? isRecord);
|
|
639
|
+
this.#change({
|
|
640
|
+
add: initialValue,
|
|
641
|
+
change: {},
|
|
642
|
+
remove: {},
|
|
643
|
+
changed: true
|
|
644
|
+
});
|
|
645
|
+
if (options?.watched)
|
|
646
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
677
647
|
}
|
|
678
648
|
get #value() {
|
|
679
649
|
const record = {};
|
|
680
|
-
for (const [key, signal] of this.#
|
|
650
|
+
for (const [key, signal] of this.#signals.entries())
|
|
681
651
|
record[key] = signal.get();
|
|
682
652
|
return record;
|
|
683
653
|
}
|
|
654
|
+
#validate(key, value) {
|
|
655
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
#add(key, value) {
|
|
659
|
+
if (!this.#validate(key, value))
|
|
660
|
+
return false;
|
|
661
|
+
this.#signals.set(key, createMutableSignal(value));
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
#change(changes) {
|
|
665
|
+
if (Object.keys(changes.add).length) {
|
|
666
|
+
for (const key in changes.add)
|
|
667
|
+
this.#add(key, changes.add[key]);
|
|
668
|
+
}
|
|
669
|
+
if (Object.keys(changes.change).length) {
|
|
670
|
+
batch(() => {
|
|
671
|
+
for (const key in changes.change) {
|
|
672
|
+
const value = changes.change[key];
|
|
673
|
+
if (!this.#validate(key, value))
|
|
674
|
+
continue;
|
|
675
|
+
const signal = this.#signals.get(key);
|
|
676
|
+
if (guardMutableSignal(`list item "${key}"`, value, signal))
|
|
677
|
+
signal.set(value);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
if (Object.keys(changes.remove).length) {
|
|
682
|
+
for (const key in changes.remove)
|
|
683
|
+
this.remove(key);
|
|
684
|
+
}
|
|
685
|
+
return changes.changed;
|
|
686
|
+
}
|
|
684
687
|
get [Symbol.toStringTag]() {
|
|
685
688
|
return TYPE_STORE;
|
|
686
689
|
}
|
|
@@ -688,53 +691,50 @@ class BaseStore {
|
|
|
688
691
|
return false;
|
|
689
692
|
}
|
|
690
693
|
*[Symbol.iterator]() {
|
|
691
|
-
for (const [key, signal] of this.#
|
|
694
|
+
for (const [key, signal] of this.#signals.entries())
|
|
692
695
|
yield [key, signal];
|
|
693
696
|
}
|
|
697
|
+
keys() {
|
|
698
|
+
subscribeTo(this);
|
|
699
|
+
return this.#signals.keys();
|
|
700
|
+
}
|
|
701
|
+
byKey(key) {
|
|
702
|
+
return this.#signals.get(key);
|
|
703
|
+
}
|
|
694
704
|
get() {
|
|
695
|
-
|
|
705
|
+
subscribeTo(this);
|
|
696
706
|
return this.#value;
|
|
697
707
|
}
|
|
698
708
|
set(newValue) {
|
|
699
709
|
if (UNSET === newValue) {
|
|
700
|
-
this.#
|
|
701
|
-
|
|
702
|
-
this
|
|
710
|
+
this.#signals.clear();
|
|
711
|
+
notifyOf(this);
|
|
712
|
+
unsubscribeAllFrom(this);
|
|
703
713
|
return;
|
|
704
714
|
}
|
|
705
|
-
const
|
|
706
|
-
const changed = this.#composite.change(diff(oldValue, newValue));
|
|
715
|
+
const changed = this.#change(diff(this.#value, newValue));
|
|
707
716
|
if (changed)
|
|
708
|
-
|
|
709
|
-
}
|
|
710
|
-
keys() {
|
|
711
|
-
return this.#composite.signals.keys();
|
|
712
|
-
}
|
|
713
|
-
byKey(key) {
|
|
714
|
-
return this.#composite.signals.get(key);
|
|
717
|
+
notifyOf(this);
|
|
715
718
|
}
|
|
716
719
|
update(fn) {
|
|
717
720
|
this.set(fn(this.get()));
|
|
718
721
|
}
|
|
719
722
|
add(key, value) {
|
|
720
|
-
if (this.#
|
|
721
|
-
throw new DuplicateKeyError(
|
|
722
|
-
const ok = this.#
|
|
723
|
+
if (this.#signals.has(key))
|
|
724
|
+
throw new DuplicateKeyError(TYPE_STORE, key, value);
|
|
725
|
+
const ok = this.#add(key, value);
|
|
723
726
|
if (ok)
|
|
724
|
-
|
|
727
|
+
notifyOf(this);
|
|
725
728
|
return key;
|
|
726
729
|
}
|
|
727
730
|
remove(key) {
|
|
728
|
-
const ok = this.#
|
|
731
|
+
const ok = this.#signals.delete(key);
|
|
729
732
|
if (ok)
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
on(type, listener) {
|
|
733
|
-
return this.#composite.on(type, listener);
|
|
733
|
+
notifyOf(this);
|
|
734
734
|
}
|
|
735
735
|
}
|
|
736
|
-
var createStore = (initialValue) => {
|
|
737
|
-
const instance = new BaseStore(initialValue);
|
|
736
|
+
var createStore = (initialValue, options) => {
|
|
737
|
+
const instance = new BaseStore(initialValue, options);
|
|
738
738
|
return new Proxy(instance, {
|
|
739
739
|
get(target, prop) {
|
|
740
740
|
if (prop in target) {
|
|
@@ -806,6 +806,13 @@ class DuplicateKeyError extends Error {
|
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
+
class FailedAssertionError extends Error {
|
|
810
|
+
constructor(message = "unexpected condition") {
|
|
811
|
+
super(`Assertion failed: ${message}`);
|
|
812
|
+
this.name = "FailedAssertionError";
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
809
816
|
class InvalidCallbackError extends TypeError {
|
|
810
817
|
constructor(where, value) {
|
|
811
818
|
super(`Invalid ${where} callback ${valueString(value)}`);
|
|
@@ -840,6 +847,10 @@ class ReadonlySignalError extends Error {
|
|
|
840
847
|
this.name = "ReadonlySignalError";
|
|
841
848
|
}
|
|
842
849
|
}
|
|
850
|
+
function assert(condition, msg) {
|
|
851
|
+
if (!condition)
|
|
852
|
+
throw new FailedAssertionError(msg);
|
|
853
|
+
}
|
|
843
854
|
var createError = (reason) => reason instanceof Error ? reason : Error(String(reason));
|
|
844
855
|
var validateCallback = (where, value, guard = isFunction) => {
|
|
845
856
|
if (!guard(value))
|
|
@@ -861,24 +872,18 @@ var guardMutableSignal = (what, value, signal) => {
|
|
|
861
872
|
var TYPE_COLLECTION = "Collection";
|
|
862
873
|
|
|
863
874
|
class DerivedCollection {
|
|
864
|
-
#watchers = new Set;
|
|
865
875
|
#source;
|
|
866
876
|
#callback;
|
|
867
877
|
#signals = new Map;
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
sort: new Set
|
|
874
|
-
};
|
|
875
|
-
#order = [];
|
|
876
|
-
constructor(source, callback) {
|
|
877
|
-
validateCallback("collection", callback);
|
|
878
|
+
#keys = [];
|
|
879
|
+
#dirty = true;
|
|
880
|
+
#watcher;
|
|
881
|
+
constructor(source, callback, options) {
|
|
882
|
+
validateCallback(TYPE_COLLECTION, callback);
|
|
878
883
|
if (isFunction(source))
|
|
879
884
|
source = source();
|
|
880
885
|
if (!isCollectionSource(source))
|
|
881
|
-
throw new InvalidCollectionSourceError(
|
|
886
|
+
throw new InvalidCollectionSourceError(TYPE_COLLECTION, source);
|
|
882
887
|
this.#source = source;
|
|
883
888
|
this.#callback = callback;
|
|
884
889
|
for (let i = 0;i < this.#source.length; i++) {
|
|
@@ -887,77 +892,57 @@ class DerivedCollection {
|
|
|
887
892
|
continue;
|
|
888
893
|
this.#add(key);
|
|
889
894
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
895
|
+
if (options?.watched)
|
|
896
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
897
|
+
}
|
|
898
|
+
#getWatcher() {
|
|
899
|
+
this.#watcher ||= createWatcher(() => {
|
|
900
|
+
this.#dirty = true;
|
|
901
|
+
if (!notifyOf(this))
|
|
902
|
+
this.#watcher?.stop();
|
|
903
|
+
}, () => {
|
|
904
|
+
const newKeys = Array.from(this.#source.keys());
|
|
905
|
+
const allKeys = new Set([...this.#keys, ...newKeys]);
|
|
906
|
+
const addedKeys = [];
|
|
907
|
+
const removedKeys = [];
|
|
908
|
+
for (const key of allKeys) {
|
|
909
|
+
const oldHas = this.#keys.includes(key);
|
|
910
|
+
const newHas = newKeys.includes(key);
|
|
911
|
+
if (!oldHas && newHas)
|
|
912
|
+
addedKeys.push(key);
|
|
913
|
+
else if (oldHas && !newHas)
|
|
914
|
+
removedKeys.push(key);
|
|
898
915
|
}
|
|
899
|
-
|
|
900
|
-
emitNotification(this.#listeners.add, additions);
|
|
901
|
-
});
|
|
902
|
-
this.#source.on("remove", (removals) => {
|
|
903
|
-
for (const key of removals) {
|
|
904
|
-
if (!this.#signals.has(key))
|
|
905
|
-
continue;
|
|
916
|
+
for (const key of removedKeys)
|
|
906
917
|
this.#signals.delete(key);
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (watcher) {
|
|
912
|
-
watcher.stop();
|
|
913
|
-
this.#ownWatchers.delete(key);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
this.#order = this.#order.filter(() => true);
|
|
917
|
-
notifyWatchers(this.#watchers);
|
|
918
|
-
emitNotification(this.#listeners.remove, removals);
|
|
918
|
+
for (const key of addedKeys)
|
|
919
|
+
this.#add(key);
|
|
920
|
+
this.#keys = newKeys;
|
|
921
|
+
this.#dirty = false;
|
|
919
922
|
});
|
|
920
|
-
this.#
|
|
921
|
-
this.#
|
|
922
|
-
notifyWatchers(this.#watchers);
|
|
923
|
-
emitNotification(this.#listeners.sort, newOrder);
|
|
923
|
+
this.#watcher.onCleanup(() => {
|
|
924
|
+
this.#watcher = undefined;
|
|
924
925
|
});
|
|
926
|
+
return this.#watcher;
|
|
925
927
|
}
|
|
926
928
|
#add(key) {
|
|
927
929
|
const computedCallback = isAsyncCollectionCallback(this.#callback) ? async (_, abort) => {
|
|
928
|
-
const
|
|
929
|
-
if (!sourceSignal)
|
|
930
|
-
return UNSET;
|
|
931
|
-
const sourceValue = sourceSignal.get();
|
|
930
|
+
const sourceValue = this.#source.byKey(key)?.get();
|
|
932
931
|
if (sourceValue === UNSET)
|
|
933
932
|
return UNSET;
|
|
934
933
|
return this.#callback(sourceValue, abort);
|
|
935
934
|
} : () => {
|
|
936
|
-
const
|
|
937
|
-
if (!sourceSignal)
|
|
938
|
-
return UNSET;
|
|
939
|
-
const sourceValue = sourceSignal.get();
|
|
935
|
+
const sourceValue = this.#source.byKey(key)?.get();
|
|
940
936
|
if (sourceValue === UNSET)
|
|
941
937
|
return UNSET;
|
|
942
938
|
return this.#callback(sourceValue);
|
|
943
939
|
};
|
|
944
940
|
const signal = createComputed(computedCallback);
|
|
945
941
|
this.#signals.set(key, signal);
|
|
946
|
-
if (!this.#
|
|
947
|
-
this.#
|
|
948
|
-
if (this.#listeners.change.size)
|
|
949
|
-
this.#addWatcher(key);
|
|
942
|
+
if (!this.#keys.includes(key))
|
|
943
|
+
this.#keys.push(key);
|
|
950
944
|
return true;
|
|
951
945
|
}
|
|
952
|
-
#addWatcher(key) {
|
|
953
|
-
const watcher = createWatcher(() => {
|
|
954
|
-
trackSignalReads(watcher, () => {
|
|
955
|
-
this.#signals.get(key)?.get();
|
|
956
|
-
});
|
|
957
|
-
});
|
|
958
|
-
this.#ownWatchers.set(key, watcher);
|
|
959
|
-
watcher();
|
|
960
|
-
}
|
|
961
946
|
get [Symbol.toStringTag]() {
|
|
962
947
|
return TYPE_COLLECTION;
|
|
963
948
|
}
|
|
@@ -965,54 +950,44 @@ class DerivedCollection {
|
|
|
965
950
|
return true;
|
|
966
951
|
}
|
|
967
952
|
*[Symbol.iterator]() {
|
|
968
|
-
for (const key of this.#
|
|
953
|
+
for (const key of this.#keys) {
|
|
969
954
|
const signal = this.#signals.get(key);
|
|
970
955
|
if (signal)
|
|
971
956
|
yield signal;
|
|
972
957
|
}
|
|
973
958
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
959
|
+
keys() {
|
|
960
|
+
subscribeTo(this);
|
|
961
|
+
if (this.#dirty)
|
|
962
|
+
this.#getWatcher().run();
|
|
963
|
+
return this.#keys.values();
|
|
977
964
|
}
|
|
978
965
|
get() {
|
|
979
|
-
|
|
980
|
-
|
|
966
|
+
subscribeTo(this);
|
|
967
|
+
if (this.#dirty)
|
|
968
|
+
this.#getWatcher().run();
|
|
969
|
+
return this.#keys.map((key) => this.#signals.get(key)?.get()).filter((v) => v != null && v !== UNSET);
|
|
981
970
|
}
|
|
982
971
|
at(index) {
|
|
983
|
-
return this.#signals.get(this.#
|
|
984
|
-
}
|
|
985
|
-
keys() {
|
|
986
|
-
return this.#order.values();
|
|
972
|
+
return this.#signals.get(this.#keys[index]);
|
|
987
973
|
}
|
|
988
974
|
byKey(key) {
|
|
989
975
|
return this.#signals.get(key);
|
|
990
976
|
}
|
|
991
977
|
keyAt(index) {
|
|
992
|
-
return this.#
|
|
978
|
+
return this.#keys[index];
|
|
993
979
|
}
|
|
994
980
|
indexOfKey(key) {
|
|
995
|
-
return this.#
|
|
981
|
+
return this.#keys.indexOf(key);
|
|
996
982
|
}
|
|
997
|
-
|
|
998
|
-
this
|
|
999
|
-
if (type === "change" && !this.#ownWatchers.size) {
|
|
1000
|
-
for (const key of this.#signals.keys())
|
|
1001
|
-
this.#addWatcher(key);
|
|
1002
|
-
}
|
|
1003
|
-
return () => {
|
|
1004
|
-
this.#listeners[type].delete(listener);
|
|
1005
|
-
if (type === "change" && !this.#listeners.change.size) {
|
|
1006
|
-
if (this.#ownWatchers.size) {
|
|
1007
|
-
for (const watcher of this.#ownWatchers.values())
|
|
1008
|
-
watcher.stop();
|
|
1009
|
-
this.#ownWatchers.clear();
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
983
|
+
deriveCollection(callback, options) {
|
|
984
|
+
return new DerivedCollection(this, callback, options);
|
|
1013
985
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
986
|
+
get length() {
|
|
987
|
+
subscribeTo(this);
|
|
988
|
+
if (this.#dirty)
|
|
989
|
+
this.#getWatcher().run();
|
|
990
|
+
return this.#keys.length;
|
|
1016
991
|
}
|
|
1017
992
|
}
|
|
1018
993
|
var isCollection = (value) => isObjectOfType(value, TYPE_COLLECTION);
|
|
@@ -1022,21 +997,22 @@ var isAsyncCollectionCallback = (callback) => isAsyncFunction(callback);
|
|
|
1022
997
|
var TYPE_REF = "Ref";
|
|
1023
998
|
|
|
1024
999
|
class Ref {
|
|
1025
|
-
#watchers = new Set;
|
|
1026
1000
|
#value;
|
|
1027
|
-
constructor(value,
|
|
1028
|
-
validateSignalValue(
|
|
1001
|
+
constructor(value, options) {
|
|
1002
|
+
validateSignalValue(TYPE_REF, value, options?.guard);
|
|
1029
1003
|
this.#value = value;
|
|
1004
|
+
if (options?.watched)
|
|
1005
|
+
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
1030
1006
|
}
|
|
1031
1007
|
get [Symbol.toStringTag]() {
|
|
1032
1008
|
return TYPE_REF;
|
|
1033
1009
|
}
|
|
1034
1010
|
get() {
|
|
1035
|
-
|
|
1011
|
+
subscribeTo(this);
|
|
1036
1012
|
return this.#value;
|
|
1037
1013
|
}
|
|
1038
1014
|
notify() {
|
|
1039
|
-
|
|
1015
|
+
notifyOf(this);
|
|
1040
1016
|
}
|
|
1041
1017
|
}
|
|
1042
1018
|
var isRef = (value) => isObjectOfType(value, TYPE_REF);
|
|
@@ -1047,7 +1023,9 @@ var createEffect = (callback) => {
|
|
|
1047
1023
|
const isAsync = isAsyncFunction(callback);
|
|
1048
1024
|
let running = false;
|
|
1049
1025
|
let controller;
|
|
1050
|
-
const watcher = createWatcher(() =>
|
|
1026
|
+
const watcher = createWatcher(() => {
|
|
1027
|
+
watcher.run();
|
|
1028
|
+
}, () => {
|
|
1051
1029
|
if (running)
|
|
1052
1030
|
throw new CircularDependencyError("effect");
|
|
1053
1031
|
running = true;
|
|
@@ -1063,7 +1041,7 @@ var createEffect = (callback) => {
|
|
|
1063
1041
|
watcher.onCleanup(cleanup2);
|
|
1064
1042
|
}).catch((error) => {
|
|
1065
1043
|
if (!isAbortError(error))
|
|
1066
|
-
console.error("
|
|
1044
|
+
console.error("Error in async effect callback:", error);
|
|
1067
1045
|
});
|
|
1068
1046
|
} else {
|
|
1069
1047
|
cleanup = callback();
|
|
@@ -1072,14 +1050,18 @@ var createEffect = (callback) => {
|
|
|
1072
1050
|
}
|
|
1073
1051
|
} catch (error) {
|
|
1074
1052
|
if (!isAbortError(error))
|
|
1075
|
-
console.error("
|
|
1053
|
+
console.error("Error in effect callback:", error);
|
|
1076
1054
|
}
|
|
1077
1055
|
running = false;
|
|
1078
|
-
})
|
|
1056
|
+
});
|
|
1079
1057
|
watcher();
|
|
1080
1058
|
return () => {
|
|
1081
1059
|
controller?.abort();
|
|
1082
|
-
|
|
1060
|
+
try {
|
|
1061
|
+
watcher.stop();
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
console.error("Error in effect cleanup:", error);
|
|
1064
|
+
}
|
|
1083
1065
|
};
|
|
1084
1066
|
};
|
|
1085
1067
|
// src/match.ts
|
|
@@ -1125,10 +1107,11 @@ export {
|
|
|
1125
1107
|
valueString,
|
|
1126
1108
|
validateSignalValue,
|
|
1127
1109
|
validateCallback,
|
|
1128
|
-
|
|
1129
|
-
|
|
1110
|
+
untrack,
|
|
1111
|
+
track,
|
|
1112
|
+
subscribeTo,
|
|
1130
1113
|
resolve,
|
|
1131
|
-
|
|
1114
|
+
notifyOf,
|
|
1132
1115
|
match,
|
|
1133
1116
|
isTaskCallback,
|
|
1134
1117
|
isSymbol,
|
|
@@ -1151,8 +1134,7 @@ export {
|
|
|
1151
1134
|
isAsyncFunction,
|
|
1152
1135
|
isAbortError,
|
|
1153
1136
|
guardMutableSignal,
|
|
1154
|
-
|
|
1155
|
-
emitNotification,
|
|
1137
|
+
flush,
|
|
1156
1138
|
diff,
|
|
1157
1139
|
createWatcher,
|
|
1158
1140
|
createStore,
|
|
@@ -1160,7 +1142,7 @@ export {
|
|
|
1160
1142
|
createError,
|
|
1161
1143
|
createEffect,
|
|
1162
1144
|
createComputed,
|
|
1163
|
-
|
|
1145
|
+
batch,
|
|
1164
1146
|
UNSET,
|
|
1165
1147
|
Task,
|
|
1166
1148
|
TYPE_STORE,
|