@zeix/cause-effect 0.15.1 → 0.15.2
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/README.md +39 -1
- package/index.dev.js +517 -392
- package/index.js +1 -1
- package/index.ts +15 -4
- package/package.json +1 -1
- package/src/computed.ts +2 -2
- package/src/diff.ts +57 -44
- package/src/effect.ts +2 -6
- package/src/errors.ts +56 -0
- package/src/match.ts +2 -2
- package/src/resolve.ts +2 -2
- package/src/signal.ts +21 -43
- package/src/state.ts +3 -2
- package/src/store.ts +402 -179
- package/src/util.ts +50 -6
- package/test/computed.test.ts +1 -1
- package/test/diff.test.ts +321 -4
- package/test/effect.test.ts +1 -1
- package/test/match.test.ts +13 -3
- package/test/signal.test.ts +12 -140
- package/test/store.test.ts +963 -20
- package/types/index.d.ts +5 -4
- package/types/src/diff.d.ts +5 -3
- package/types/src/errors.d.ts +19 -0
- package/types/src/match.d.ts +1 -1
- package/types/src/resolve.d.ts +1 -1
- package/types/src/signal.d.ts +12 -19
- package/types/src/store.d.ts +48 -30
- package/types/src/util.d.ts +8 -5
- package/index.d.ts +0 -36
- package/types/test-new-effect.d.ts +0 -1
package/index.dev.js
CHANGED
|
@@ -1,380 +1,76 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var requestId;
|
|
7
|
-
var updateDOM = () => {
|
|
8
|
-
requestId = undefined;
|
|
9
|
-
const updates = Array.from(updateMap.values());
|
|
10
|
-
updateMap.clear();
|
|
11
|
-
for (const update of updates) {
|
|
12
|
-
update();
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
class CircularDependencyError extends Error {
|
|
3
|
+
constructor(where) {
|
|
4
|
+
super(`Circular dependency detected in ${where}`);
|
|
5
|
+
this.name = "CircularDependencyError";
|
|
13
6
|
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
queueMicrotask(updateDOM);
|
|
21
|
-
var watch = (notice) => {
|
|
22
|
-
const cleanups = new Set;
|
|
23
|
-
const w = notice;
|
|
24
|
-
w.off = (on) => {
|
|
25
|
-
cleanups.add(on);
|
|
26
|
-
};
|
|
27
|
-
w.cleanup = () => {
|
|
28
|
-
for (const cleanup of cleanups) {
|
|
29
|
-
cleanup();
|
|
30
|
-
}
|
|
31
|
-
cleanups.clear();
|
|
32
|
-
};
|
|
33
|
-
return w;
|
|
34
|
-
};
|
|
35
|
-
var subscribe = (watchers) => {
|
|
36
|
-
if (active && !watchers.has(active)) {
|
|
37
|
-
const watcher = active;
|
|
38
|
-
watchers.add(watcher);
|
|
39
|
-
active.off(() => {
|
|
40
|
-
watchers.delete(watcher);
|
|
41
|
-
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class InvalidSignalValueError extends TypeError {
|
|
10
|
+
constructor(where, value) {
|
|
11
|
+
super(`Invalid signal value ${value} in ${where}`);
|
|
12
|
+
this.name = "InvalidSignalValueError";
|
|
42
13
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
watcher();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class NullishSignalValueError extends TypeError {
|
|
17
|
+
constructor(where) {
|
|
18
|
+
super(`Nullish signal values are not allowed in ${where}`);
|
|
19
|
+
this.name = "NullishSignalValueError";
|
|
50
20
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
watcher();
|
|
58
|
-
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class StoreKeyExistsError extends Error {
|
|
24
|
+
constructor(key, value) {
|
|
25
|
+
super(`Could not add store key "${key}" with value ${value} because it already exists`);
|
|
26
|
+
this.name = "StoreKeyExistsError";
|
|
59
27
|
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
flush();
|
|
67
|
-
batchDepth--;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class StoreKeyRangeError extends RangeError {
|
|
31
|
+
constructor(index) {
|
|
32
|
+
super(`Could not remove store index ${String(index)} because it is out of range`);
|
|
33
|
+
this.name = "StoreKeyRangeError";
|
|
68
34
|
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} finally {
|
|
76
|
-
active = prev;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class StoreKeyReadonlyError extends Error {
|
|
38
|
+
constructor(key, value) {
|
|
39
|
+
super(`Could not set store key "${key}" to ${value} because it is readonly`);
|
|
40
|
+
this.name = "StoreKeyReadonlyError";
|
|
77
41
|
}
|
|
78
|
-
}
|
|
79
|
-
var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
|
|
80
|
-
updateMap.set(dedupe || Symbol(), () => {
|
|
81
|
-
try {
|
|
82
|
-
resolve(fn());
|
|
83
|
-
} catch (error) {
|
|
84
|
-
reject(error);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
requestTick();
|
|
88
|
-
});
|
|
42
|
+
}
|
|
89
43
|
|
|
90
44
|
// src/util.ts
|
|
45
|
+
var UNSET = Symbol();
|
|
91
46
|
var isString = (value) => typeof value === "string";
|
|
92
47
|
var isNumber = (value) => typeof value === "number";
|
|
48
|
+
var isSymbol = (value) => typeof value === "symbol";
|
|
93
49
|
var isFunction = (fn) => typeof fn === "function";
|
|
94
50
|
var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
|
|
51
|
+
var isDefinedObject = (value) => !!value && typeof value === "object";
|
|
95
52
|
var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
|
|
96
53
|
var isRecord = (value) => isObjectOfType(value, "Object");
|
|
54
|
+
var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
|
|
97
55
|
var validArrayIndexes = (keys) => {
|
|
98
56
|
if (!keys.length)
|
|
99
57
|
return null;
|
|
100
58
|
const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
|
|
101
59
|
return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
|
|
102
60
|
};
|
|
103
|
-
var hasMethod = (obj, methodName) => (methodName in obj) && isFunction(obj[methodName]);
|
|
104
61
|
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
105
62
|
var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
super(`Circular dependency in ${where} detected`);
|
|
110
|
-
this.name = "CircularDependencyError";
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/state.ts
|
|
115
|
-
var TYPE_STATE = "State";
|
|
116
|
-
var state = (initialValue) => {
|
|
117
|
-
const watchers = new Set;
|
|
118
|
-
let value = initialValue;
|
|
119
|
-
const s = {
|
|
120
|
-
[Symbol.toStringTag]: TYPE_STATE,
|
|
121
|
-
get: () => {
|
|
122
|
-
subscribe(watchers);
|
|
123
|
-
return value;
|
|
124
|
-
},
|
|
125
|
-
set: (v) => {
|
|
126
|
-
if (isEqual(value, v))
|
|
127
|
-
return;
|
|
128
|
-
value = v;
|
|
129
|
-
notify(watchers);
|
|
130
|
-
if (UNSET === value)
|
|
131
|
-
watchers.clear();
|
|
132
|
-
},
|
|
133
|
-
update: (fn) => {
|
|
134
|
-
s.set(fn(value));
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
return s;
|
|
138
|
-
};
|
|
139
|
-
var isState = (value) => isObjectOfType(value, TYPE_STATE);
|
|
140
|
-
|
|
141
|
-
// src/effect.ts
|
|
142
|
-
var effect = (callback) => {
|
|
143
|
-
const isAsync = isAsyncFunction(callback);
|
|
144
|
-
let running = false;
|
|
145
|
-
let controller;
|
|
146
|
-
const run = watch(() => observe(() => {
|
|
147
|
-
if (running)
|
|
148
|
-
throw new CircularDependencyError("effect");
|
|
149
|
-
running = true;
|
|
150
|
-
controller?.abort();
|
|
151
|
-
controller = undefined;
|
|
152
|
-
let cleanup;
|
|
153
|
-
try {
|
|
154
|
-
if (isAsync) {
|
|
155
|
-
controller = new AbortController;
|
|
156
|
-
const currentController = controller;
|
|
157
|
-
callback(controller.signal).then((cleanup2) => {
|
|
158
|
-
if (isFunction(cleanup2) && controller === currentController)
|
|
159
|
-
run.off(cleanup2);
|
|
160
|
-
}).catch((error) => {
|
|
161
|
-
if (!isAbortError(error))
|
|
162
|
-
console.error("Async effect error:", error);
|
|
163
|
-
});
|
|
164
|
-
} else {
|
|
165
|
-
cleanup = callback();
|
|
166
|
-
if (isFunction(cleanup))
|
|
167
|
-
run.off(cleanup);
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (!isAbortError(error))
|
|
171
|
-
console.error("Effect callback error:", error);
|
|
172
|
-
}
|
|
173
|
-
running = false;
|
|
174
|
-
}, run));
|
|
175
|
-
run();
|
|
176
|
-
return () => {
|
|
177
|
-
controller?.abort();
|
|
178
|
-
run.cleanup();
|
|
179
|
-
};
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// src/store.ts
|
|
183
|
-
var TYPE_STORE = "Store";
|
|
184
|
-
var store = (initialValue) => {
|
|
185
|
-
const watchers = new Set;
|
|
186
|
-
const eventTarget = new EventTarget;
|
|
187
|
-
const signals = new Map;
|
|
188
|
-
const cleanups = new Map;
|
|
189
|
-
const size = state(0);
|
|
190
|
-
const current = () => {
|
|
191
|
-
const keys = Array.from(signals.keys());
|
|
192
|
-
const arrayIndexes = validArrayIndexes(keys);
|
|
193
|
-
if (arrayIndexes)
|
|
194
|
-
return arrayIndexes.map((index) => signals.get(String(index))?.get());
|
|
195
|
-
const record = {};
|
|
196
|
-
for (const [key, signal] of signals) {
|
|
197
|
-
record[key] = signal.get();
|
|
198
|
-
}
|
|
63
|
+
var recordToArray = (record) => {
|
|
64
|
+
const indexes = validArrayIndexes(Object.keys(record));
|
|
65
|
+
if (indexes === null)
|
|
199
66
|
return record;
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
signals.set(stringKey, signal);
|
|
206
|
-
const cleanup = effect(() => {
|
|
207
|
-
const currentValue = signal.get();
|
|
208
|
-
if (currentValue != null)
|
|
209
|
-
emit("store-change", { [key]: currentValue });
|
|
210
|
-
});
|
|
211
|
-
cleanups.set(stringKey, cleanup);
|
|
212
|
-
};
|
|
213
|
-
const removeProperty = (key) => {
|
|
214
|
-
const stringKey = String(key);
|
|
215
|
-
signals.delete(stringKey);
|
|
216
|
-
const cleanup = cleanups.get(stringKey);
|
|
217
|
-
if (cleanup)
|
|
218
|
-
cleanup();
|
|
219
|
-
cleanups.delete(stringKey);
|
|
220
|
-
};
|
|
221
|
-
const reconcile = (oldValue, newValue) => {
|
|
222
|
-
const changes = diff(oldValue, newValue);
|
|
223
|
-
batch(() => {
|
|
224
|
-
if (Object.keys(changes.add).length) {
|
|
225
|
-
for (const key in changes.add) {
|
|
226
|
-
const value = changes.add[key];
|
|
227
|
-
if (value != null)
|
|
228
|
-
addProperty(key, value);
|
|
229
|
-
}
|
|
230
|
-
emit("store-add", changes.add);
|
|
231
|
-
}
|
|
232
|
-
if (Object.keys(changes.change).length) {
|
|
233
|
-
for (const key in changes.change) {
|
|
234
|
-
const signal = signals.get(key);
|
|
235
|
-
const value = changes.change[key];
|
|
236
|
-
if (signal && value != null && hasMethod(signal, "set"))
|
|
237
|
-
signal.set(value);
|
|
238
|
-
}
|
|
239
|
-
emit("store-change", changes.change);
|
|
240
|
-
}
|
|
241
|
-
if (Object.keys(changes.remove).length) {
|
|
242
|
-
for (const key in changes.remove) {
|
|
243
|
-
removeProperty(key);
|
|
244
|
-
}
|
|
245
|
-
emit("store-remove", changes.remove);
|
|
246
|
-
}
|
|
247
|
-
size.set(signals.size);
|
|
248
|
-
});
|
|
249
|
-
return changes.changed;
|
|
250
|
-
};
|
|
251
|
-
reconcile({}, initialValue);
|
|
252
|
-
setTimeout(() => {
|
|
253
|
-
const initialAdditionsEvent = new CustomEvent("store-add", {
|
|
254
|
-
detail: initialValue
|
|
255
|
-
});
|
|
256
|
-
eventTarget.dispatchEvent(initialAdditionsEvent);
|
|
257
|
-
}, 0);
|
|
258
|
-
const storeProps = [
|
|
259
|
-
"add",
|
|
260
|
-
"get",
|
|
261
|
-
"remove",
|
|
262
|
-
"set",
|
|
263
|
-
"update",
|
|
264
|
-
"addEventListener",
|
|
265
|
-
"removeEventListener",
|
|
266
|
-
"dispatchEvent",
|
|
267
|
-
"size"
|
|
268
|
-
];
|
|
269
|
-
return new Proxy({}, {
|
|
270
|
-
get(_target, prop) {
|
|
271
|
-
switch (prop) {
|
|
272
|
-
case "add":
|
|
273
|
-
return (k, v) => {
|
|
274
|
-
if (!signals.has(k)) {
|
|
275
|
-
addProperty(k, v);
|
|
276
|
-
notify(watchers);
|
|
277
|
-
emit("store-add", {
|
|
278
|
-
[k]: v
|
|
279
|
-
});
|
|
280
|
-
size.set(signals.size);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
case "get":
|
|
284
|
-
return () => {
|
|
285
|
-
subscribe(watchers);
|
|
286
|
-
return current();
|
|
287
|
-
};
|
|
288
|
-
case "remove":
|
|
289
|
-
return (k) => {
|
|
290
|
-
if (signals.has(k)) {
|
|
291
|
-
removeProperty(k);
|
|
292
|
-
notify(watchers);
|
|
293
|
-
emit("store-remove", { [k]: UNSET });
|
|
294
|
-
size.set(signals.size);
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
case "set":
|
|
298
|
-
return (v) => {
|
|
299
|
-
if (reconcile(current(), v)) {
|
|
300
|
-
notify(watchers);
|
|
301
|
-
if (UNSET === v)
|
|
302
|
-
watchers.clear();
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
case "update":
|
|
306
|
-
return (fn) => {
|
|
307
|
-
const oldValue = current();
|
|
308
|
-
const newValue = fn(oldValue);
|
|
309
|
-
if (reconcile(oldValue, newValue)) {
|
|
310
|
-
notify(watchers);
|
|
311
|
-
if (UNSET === newValue)
|
|
312
|
-
watchers.clear();
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
case "addEventListener":
|
|
316
|
-
return eventTarget.addEventListener.bind(eventTarget);
|
|
317
|
-
case "removeEventListener":
|
|
318
|
-
return eventTarget.removeEventListener.bind(eventTarget);
|
|
319
|
-
case "dispatchEvent":
|
|
320
|
-
return eventTarget.dispatchEvent.bind(eventTarget);
|
|
321
|
-
case "size":
|
|
322
|
-
return size;
|
|
323
|
-
}
|
|
324
|
-
if (prop === Symbol.toStringTag)
|
|
325
|
-
return TYPE_STORE;
|
|
326
|
-
if (prop === Symbol.iterator) {
|
|
327
|
-
return function* () {
|
|
328
|
-
for (const [key, signal] of signals) {
|
|
329
|
-
yield [key, signal];
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
return signals.get(String(prop));
|
|
334
|
-
},
|
|
335
|
-
has(_target, prop) {
|
|
336
|
-
const key = String(prop);
|
|
337
|
-
return signals.has(key) || storeProps.includes(key) || prop === Symbol.toStringTag || prop === Symbol.iterator;
|
|
338
|
-
},
|
|
339
|
-
ownKeys() {
|
|
340
|
-
return Array.from(signals.keys()).map((key) => String(key));
|
|
341
|
-
},
|
|
342
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
343
|
-
const signal = signals.get(String(prop));
|
|
344
|
-
return signal ? {
|
|
345
|
-
enumerable: true,
|
|
346
|
-
configurable: true,
|
|
347
|
-
writable: true,
|
|
348
|
-
value: signal
|
|
349
|
-
} : undefined;
|
|
350
|
-
}
|
|
351
|
-
});
|
|
67
|
+
const array = [];
|
|
68
|
+
for (const index of indexes) {
|
|
69
|
+
array.push(record[String(index)]);
|
|
70
|
+
}
|
|
71
|
+
return array;
|
|
352
72
|
};
|
|
353
|
-
var
|
|
354
|
-
|
|
355
|
-
// src/signal.ts
|
|
356
|
-
var UNSET = Symbol();
|
|
357
|
-
var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
|
|
358
|
-
function toSignal(value) {
|
|
359
|
-
if (isSignal(value))
|
|
360
|
-
return value;
|
|
361
|
-
if (isComputedCallback(value))
|
|
362
|
-
return computed(value);
|
|
363
|
-
if (Array.isArray(value))
|
|
364
|
-
return store(value);
|
|
365
|
-
if (Array.isArray(value) || isRecord(value))
|
|
366
|
-
return store(value);
|
|
367
|
-
return state(value);
|
|
368
|
-
}
|
|
369
|
-
function toMutableSignal(value) {
|
|
370
|
-
if (isState(value) || isStore(value))
|
|
371
|
-
return value;
|
|
372
|
-
if (Array.isArray(value))
|
|
373
|
-
return store(value);
|
|
374
|
-
if (isRecord(value))
|
|
375
|
-
return store(value);
|
|
376
|
-
return state(value);
|
|
377
|
-
}
|
|
73
|
+
var valueString = (value) => isString(value) ? `"${value}"` : isDefinedObject(value) ? JSON.stringify(value) : String(value);
|
|
378
74
|
|
|
379
75
|
// src/diff.ts
|
|
380
76
|
var isEqual = (a, b, visited) => {
|
|
@@ -415,46 +111,143 @@ var isEqual = (a, b, visited) => {
|
|
|
415
111
|
}
|
|
416
112
|
return true;
|
|
417
113
|
}
|
|
418
|
-
return false;
|
|
419
|
-
} finally {
|
|
420
|
-
visited.delete(a);
|
|
421
|
-
visited.delete(b);
|
|
114
|
+
return false;
|
|
115
|
+
} finally {
|
|
116
|
+
visited.delete(a);
|
|
117
|
+
visited.delete(b);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var diff = (oldObj, newObj) => {
|
|
121
|
+
const oldValid = isRecordOrArray(oldObj);
|
|
122
|
+
const newValid = isRecordOrArray(newObj);
|
|
123
|
+
if (!oldValid || !newValid) {
|
|
124
|
+
const changed2 = !Object.is(oldObj, newObj);
|
|
125
|
+
return {
|
|
126
|
+
changed: changed2,
|
|
127
|
+
add: changed2 && newValid ? newObj : {},
|
|
128
|
+
change: {},
|
|
129
|
+
remove: changed2 && oldValid ? oldObj : {}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const visited = new WeakSet;
|
|
133
|
+
const add = {};
|
|
134
|
+
const change = {};
|
|
135
|
+
const remove = {};
|
|
136
|
+
const oldKeys = Object.keys(oldObj);
|
|
137
|
+
const newKeys = Object.keys(newObj);
|
|
138
|
+
const allKeys = new Set([...oldKeys, ...newKeys]);
|
|
139
|
+
for (const key of allKeys) {
|
|
140
|
+
const oldHas = key in oldObj;
|
|
141
|
+
const newHas = key in newObj;
|
|
142
|
+
if (!oldHas && newHas) {
|
|
143
|
+
add[key] = newObj[key];
|
|
144
|
+
continue;
|
|
145
|
+
} else if (oldHas && !newHas) {
|
|
146
|
+
remove[key] = UNSET;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const oldValue = oldObj[key];
|
|
150
|
+
const newValue = newObj[key];
|
|
151
|
+
if (!isEqual(oldValue, newValue, visited))
|
|
152
|
+
change[key] = newValue;
|
|
153
|
+
}
|
|
154
|
+
const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
|
|
155
|
+
return {
|
|
156
|
+
changed,
|
|
157
|
+
add,
|
|
158
|
+
change,
|
|
159
|
+
remove
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/scheduler.ts
|
|
164
|
+
var active;
|
|
165
|
+
var pending = new Set;
|
|
166
|
+
var batchDepth = 0;
|
|
167
|
+
var updateMap = new Map;
|
|
168
|
+
var requestId;
|
|
169
|
+
var updateDOM = () => {
|
|
170
|
+
requestId = undefined;
|
|
171
|
+
const updates = Array.from(updateMap.values());
|
|
172
|
+
updateMap.clear();
|
|
173
|
+
for (const update of updates) {
|
|
174
|
+
update();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var requestTick = () => {
|
|
178
|
+
if (requestId)
|
|
179
|
+
cancelAnimationFrame(requestId);
|
|
180
|
+
requestId = requestAnimationFrame(updateDOM);
|
|
181
|
+
};
|
|
182
|
+
queueMicrotask(updateDOM);
|
|
183
|
+
var watch = (notice) => {
|
|
184
|
+
const cleanups = new Set;
|
|
185
|
+
const w = notice;
|
|
186
|
+
w.off = (on) => {
|
|
187
|
+
cleanups.add(on);
|
|
188
|
+
};
|
|
189
|
+
w.cleanup = () => {
|
|
190
|
+
for (const cleanup of cleanups) {
|
|
191
|
+
cleanup();
|
|
192
|
+
}
|
|
193
|
+
cleanups.clear();
|
|
194
|
+
};
|
|
195
|
+
return w;
|
|
196
|
+
};
|
|
197
|
+
var subscribe = (watchers) => {
|
|
198
|
+
if (active && !watchers.has(active)) {
|
|
199
|
+
const watcher = active;
|
|
200
|
+
watchers.add(watcher);
|
|
201
|
+
active.off(() => {
|
|
202
|
+
watchers.delete(watcher);
|
|
203
|
+
});
|
|
422
204
|
}
|
|
423
205
|
};
|
|
424
|
-
var
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
continue;
|
|
439
|
-
} else if (oldHas && !newHas) {
|
|
440
|
-
remove[key] = UNSET;
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
const oldValue = oldRecord[key];
|
|
444
|
-
const newValue = newRecord[key];
|
|
445
|
-
if (!isEqual(oldValue, newValue, visited))
|
|
446
|
-
change[key] = newValue;
|
|
206
|
+
var notify = (watchers) => {
|
|
207
|
+
for (const watcher of watchers) {
|
|
208
|
+
if (batchDepth)
|
|
209
|
+
pending.add(watcher);
|
|
210
|
+
else
|
|
211
|
+
watcher();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var flush = () => {
|
|
215
|
+
while (pending.size) {
|
|
216
|
+
const watchers = Array.from(pending);
|
|
217
|
+
pending.clear();
|
|
218
|
+
for (const watcher of watchers) {
|
|
219
|
+
watcher();
|
|
447
220
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var batch = (fn) => {
|
|
224
|
+
batchDepth++;
|
|
225
|
+
try {
|
|
226
|
+
fn();
|
|
227
|
+
} finally {
|
|
228
|
+
flush();
|
|
229
|
+
batchDepth--;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var observe = (run, watcher) => {
|
|
233
|
+
const prev = active;
|
|
234
|
+
active = watcher;
|
|
235
|
+
try {
|
|
236
|
+
run();
|
|
237
|
+
} finally {
|
|
238
|
+
active = prev;
|
|
239
|
+
}
|
|
457
240
|
};
|
|
241
|
+
var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
|
|
242
|
+
updateMap.set(dedupe || Symbol(), () => {
|
|
243
|
+
try {
|
|
244
|
+
resolve(fn());
|
|
245
|
+
} catch (error) {
|
|
246
|
+
reject(error);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
requestTick();
|
|
250
|
+
});
|
|
458
251
|
|
|
459
252
|
// src/computed.ts
|
|
460
253
|
var TYPE_COMPUTED = "Computed";
|
|
@@ -555,6 +348,46 @@ var computed = (fn) => {
|
|
|
555
348
|
};
|
|
556
349
|
var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
|
|
557
350
|
var isComputedCallback = (value) => isFunction(value) && value.length < 2;
|
|
351
|
+
// src/effect.ts
|
|
352
|
+
var effect = (callback) => {
|
|
353
|
+
const isAsync = isAsyncFunction(callback);
|
|
354
|
+
let running = false;
|
|
355
|
+
let controller;
|
|
356
|
+
const run = watch(() => observe(() => {
|
|
357
|
+
if (running)
|
|
358
|
+
throw new CircularDependencyError("effect");
|
|
359
|
+
running = true;
|
|
360
|
+
controller?.abort();
|
|
361
|
+
controller = undefined;
|
|
362
|
+
let cleanup;
|
|
363
|
+
try {
|
|
364
|
+
if (isAsync) {
|
|
365
|
+
controller = new AbortController;
|
|
366
|
+
const currentController = controller;
|
|
367
|
+
callback(controller.signal).then((cleanup2) => {
|
|
368
|
+
if (isFunction(cleanup2) && controller === currentController)
|
|
369
|
+
run.off(cleanup2);
|
|
370
|
+
}).catch((error) => {
|
|
371
|
+
if (!isAbortError(error))
|
|
372
|
+
console.error("Async effect error:", error);
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
cleanup = callback();
|
|
376
|
+
if (isFunction(cleanup))
|
|
377
|
+
run.off(cleanup);
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (!isAbortError(error))
|
|
381
|
+
console.error("Effect callback error:", error);
|
|
382
|
+
}
|
|
383
|
+
running = false;
|
|
384
|
+
}, run));
|
|
385
|
+
run();
|
|
386
|
+
return () => {
|
|
387
|
+
controller?.abort();
|
|
388
|
+
run.cleanup();
|
|
389
|
+
};
|
|
390
|
+
};
|
|
558
391
|
// src/match.ts
|
|
559
392
|
function match(result, handlers) {
|
|
560
393
|
try {
|
|
@@ -562,8 +395,8 @@ function match(result, handlers) {
|
|
|
562
395
|
handlers.nil?.();
|
|
563
396
|
else if (result.errors)
|
|
564
397
|
handlers.err?.(result.errors);
|
|
565
|
-
else
|
|
566
|
-
handlers.ok
|
|
398
|
+
else if (result.ok)
|
|
399
|
+
handlers.ok(result.values);
|
|
567
400
|
} catch (error) {
|
|
568
401
|
if (handlers.err && (!result.errors || !result.errors.includes(toError(error))))
|
|
569
402
|
handlers.err(result.errors ? [...result.errors, toError(error)] : [toError(error)]);
|
|
@@ -593,10 +426,293 @@ function resolve(signals) {
|
|
|
593
426
|
return { ok: false, errors };
|
|
594
427
|
return { ok: true, values };
|
|
595
428
|
}
|
|
429
|
+
// src/state.ts
|
|
430
|
+
var TYPE_STATE = "State";
|
|
431
|
+
var state = (initialValue) => {
|
|
432
|
+
const watchers = new Set;
|
|
433
|
+
let value = initialValue;
|
|
434
|
+
const s = {
|
|
435
|
+
[Symbol.toStringTag]: TYPE_STATE,
|
|
436
|
+
get: () => {
|
|
437
|
+
subscribe(watchers);
|
|
438
|
+
return value;
|
|
439
|
+
},
|
|
440
|
+
set: (v) => {
|
|
441
|
+
if (v == null)
|
|
442
|
+
throw new NullishSignalValueError("state");
|
|
443
|
+
if (isEqual(value, v))
|
|
444
|
+
return;
|
|
445
|
+
value = v;
|
|
446
|
+
notify(watchers);
|
|
447
|
+
if (UNSET === value)
|
|
448
|
+
watchers.clear();
|
|
449
|
+
},
|
|
450
|
+
update: (fn) => {
|
|
451
|
+
s.set(fn(value));
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
return s;
|
|
455
|
+
};
|
|
456
|
+
var isState = (value) => isObjectOfType(value, TYPE_STATE);
|
|
457
|
+
|
|
458
|
+
// src/store.ts
|
|
459
|
+
var TYPE_STORE = "Store";
|
|
460
|
+
var STORE_EVENT_ADD = "store-add";
|
|
461
|
+
var STORE_EVENT_CHANGE = "store-change";
|
|
462
|
+
var STORE_EVENT_REMOVE = "store-remove";
|
|
463
|
+
var STORE_EVENT_SORT = "store-sort";
|
|
464
|
+
var store = (initialValue) => {
|
|
465
|
+
const watchers = new Set;
|
|
466
|
+
const eventTarget = new EventTarget;
|
|
467
|
+
const signals = new Map;
|
|
468
|
+
const cleanups = new Map;
|
|
469
|
+
const isArrayLike = Array.isArray(initialValue);
|
|
470
|
+
const size = state(0);
|
|
471
|
+
const current = () => {
|
|
472
|
+
const record = {};
|
|
473
|
+
for (const [key, signal] of signals) {
|
|
474
|
+
record[key] = signal.get();
|
|
475
|
+
}
|
|
476
|
+
return record;
|
|
477
|
+
};
|
|
478
|
+
const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
479
|
+
const getSortedIndexes = () => Array.from(signals.keys()).map((k) => Number(k)).filter((n) => Number.isInteger(n)).sort((a, b) => a - b);
|
|
480
|
+
const isValidValue = (key, value) => {
|
|
481
|
+
if (value == null)
|
|
482
|
+
throw new NullishSignalValueError(`store for key "${key}"`);
|
|
483
|
+
if (value === UNSET)
|
|
484
|
+
return true;
|
|
485
|
+
if (isSymbol(value) || isFunction(value) || isComputed(value))
|
|
486
|
+
throw new InvalidSignalValueError(`store for key "${key}"`, valueString(value));
|
|
487
|
+
return true;
|
|
488
|
+
};
|
|
489
|
+
const addProperty = (key, value, single = false) => {
|
|
490
|
+
if (!isValidValue(key, value))
|
|
491
|
+
return false;
|
|
492
|
+
const signal = isState(value) || isStore(value) ? value : isRecord(value) ? store(value) : Array.isArray(value) ? store(value) : state(value);
|
|
493
|
+
signals.set(key, signal);
|
|
494
|
+
const cleanup = effect(() => {
|
|
495
|
+
const currentValue = signal.get();
|
|
496
|
+
if (currentValue != null)
|
|
497
|
+
emit(STORE_EVENT_CHANGE, {
|
|
498
|
+
[key]: currentValue
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
cleanups.set(key, cleanup);
|
|
502
|
+
if (single) {
|
|
503
|
+
size.set(signals.size);
|
|
504
|
+
notify(watchers);
|
|
505
|
+
emit(STORE_EVENT_ADD, {
|
|
506
|
+
[key]: value
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
};
|
|
511
|
+
const removeProperty = (key, single = false) => {
|
|
512
|
+
const ok = signals.delete(key);
|
|
513
|
+
if (ok) {
|
|
514
|
+
const cleanup = cleanups.get(key);
|
|
515
|
+
if (cleanup)
|
|
516
|
+
cleanup();
|
|
517
|
+
cleanups.delete(key);
|
|
518
|
+
}
|
|
519
|
+
if (single) {
|
|
520
|
+
size.set(signals.size);
|
|
521
|
+
notify(watchers);
|
|
522
|
+
emit(STORE_EVENT_REMOVE, {
|
|
523
|
+
[key]: UNSET
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return ok;
|
|
527
|
+
};
|
|
528
|
+
const reconcile = (oldValue, newValue, initialRun) => {
|
|
529
|
+
const changes = diff(oldValue, newValue);
|
|
530
|
+
batch(() => {
|
|
531
|
+
if (Object.keys(changes.add).length) {
|
|
532
|
+
for (const key in changes.add) {
|
|
533
|
+
const value = changes.add[key] ?? UNSET;
|
|
534
|
+
addProperty(key, value);
|
|
535
|
+
}
|
|
536
|
+
if (initialRun) {
|
|
537
|
+
setTimeout(() => {
|
|
538
|
+
emit(STORE_EVENT_ADD, changes.add);
|
|
539
|
+
}, 0);
|
|
540
|
+
} else {
|
|
541
|
+
emit(STORE_EVENT_ADD, changes.add);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (Object.keys(changes.change).length) {
|
|
545
|
+
for (const key in changes.change) {
|
|
546
|
+
const value = changes.change[key];
|
|
547
|
+
if (!isValidValue(key, value))
|
|
548
|
+
continue;
|
|
549
|
+
const signal = signals.get(key);
|
|
550
|
+
if (isMutableSignal(signal))
|
|
551
|
+
signal.set(value);
|
|
552
|
+
else
|
|
553
|
+
throw new StoreKeyReadonlyError(key, valueString(value));
|
|
554
|
+
}
|
|
555
|
+
emit(STORE_EVENT_CHANGE, changes.change);
|
|
556
|
+
}
|
|
557
|
+
if (Object.keys(changes.remove).length) {
|
|
558
|
+
for (const key in changes.remove)
|
|
559
|
+
removeProperty(key);
|
|
560
|
+
emit(STORE_EVENT_REMOVE, changes.remove);
|
|
561
|
+
}
|
|
562
|
+
size.set(signals.size);
|
|
563
|
+
});
|
|
564
|
+
return changes.changed;
|
|
565
|
+
};
|
|
566
|
+
reconcile({}, initialValue, true);
|
|
567
|
+
const s = {
|
|
568
|
+
add: isArrayLike ? (v) => {
|
|
569
|
+
const nextIndex = signals.size;
|
|
570
|
+
const key = String(nextIndex);
|
|
571
|
+
addProperty(key, v, true);
|
|
572
|
+
} : (k, v) => {
|
|
573
|
+
if (!signals.has(k))
|
|
574
|
+
addProperty(k, v, true);
|
|
575
|
+
else
|
|
576
|
+
throw new StoreKeyExistsError(k, valueString(v));
|
|
577
|
+
},
|
|
578
|
+
get: () => {
|
|
579
|
+
subscribe(watchers);
|
|
580
|
+
return recordToArray(current());
|
|
581
|
+
},
|
|
582
|
+
remove: isArrayLike ? (index) => {
|
|
583
|
+
const currentArray = recordToArray(current());
|
|
584
|
+
const currentLength = signals.size;
|
|
585
|
+
if (!Array.isArray(currentArray) || index <= -currentLength || index >= currentLength)
|
|
586
|
+
throw new StoreKeyRangeError(index);
|
|
587
|
+
const newArray = [...currentArray];
|
|
588
|
+
newArray.splice(index, 1);
|
|
589
|
+
if (reconcile(currentArray, newArray))
|
|
590
|
+
notify(watchers);
|
|
591
|
+
} : (k) => {
|
|
592
|
+
if (signals.has(k))
|
|
593
|
+
removeProperty(k, true);
|
|
594
|
+
},
|
|
595
|
+
set: (v) => {
|
|
596
|
+
if (reconcile(current(), v)) {
|
|
597
|
+
notify(watchers);
|
|
598
|
+
if (UNSET === v)
|
|
599
|
+
watchers.clear();
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
update: (fn) => {
|
|
603
|
+
const oldValue = current();
|
|
604
|
+
const newValue = fn(recordToArray(oldValue));
|
|
605
|
+
if (reconcile(oldValue, newValue)) {
|
|
606
|
+
notify(watchers);
|
|
607
|
+
if (UNSET === newValue)
|
|
608
|
+
watchers.clear();
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
sort: (compareFn) => {
|
|
612
|
+
const entries = Array.from(signals.entries()).map(([key, signal]) => [key, signal.get()]).sort(compareFn ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
|
|
613
|
+
const newOrder = entries.map(([key]) => String(key));
|
|
614
|
+
const newSignals = new Map;
|
|
615
|
+
entries.forEach(([key], newIndex) => {
|
|
616
|
+
const oldKey = String(key);
|
|
617
|
+
const newKey = isArrayLike ? String(newIndex) : String(key);
|
|
618
|
+
const signal = signals.get(oldKey);
|
|
619
|
+
if (signal)
|
|
620
|
+
newSignals.set(newKey, signal);
|
|
621
|
+
});
|
|
622
|
+
signals.clear();
|
|
623
|
+
newSignals.forEach((signal, key) => signals.set(key, signal));
|
|
624
|
+
notify(watchers);
|
|
625
|
+
emit(STORE_EVENT_SORT, newOrder);
|
|
626
|
+
},
|
|
627
|
+
addEventListener: eventTarget.addEventListener.bind(eventTarget),
|
|
628
|
+
removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
|
|
629
|
+
dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
|
|
630
|
+
size
|
|
631
|
+
};
|
|
632
|
+
return new Proxy({}, {
|
|
633
|
+
get(_target, prop) {
|
|
634
|
+
if (prop === Symbol.toStringTag)
|
|
635
|
+
return TYPE_STORE;
|
|
636
|
+
if (prop === Symbol.isConcatSpreadable)
|
|
637
|
+
return isArrayLike;
|
|
638
|
+
if (prop === Symbol.iterator)
|
|
639
|
+
return isArrayLike ? function* () {
|
|
640
|
+
const indexes = getSortedIndexes();
|
|
641
|
+
for (const index of indexes) {
|
|
642
|
+
const signal = signals.get(String(index));
|
|
643
|
+
if (signal)
|
|
644
|
+
yield signal;
|
|
645
|
+
}
|
|
646
|
+
} : function* () {
|
|
647
|
+
for (const [key, signal] of signals)
|
|
648
|
+
yield [key, signal];
|
|
649
|
+
};
|
|
650
|
+
if (isSymbol(prop))
|
|
651
|
+
return;
|
|
652
|
+
if (prop in s)
|
|
653
|
+
return s[prop];
|
|
654
|
+
if (prop === "length" && isArrayLike) {
|
|
655
|
+
subscribe(watchers);
|
|
656
|
+
return size.get();
|
|
657
|
+
}
|
|
658
|
+
return signals.get(prop);
|
|
659
|
+
},
|
|
660
|
+
has(_target, prop) {
|
|
661
|
+
const stringProp = String(prop);
|
|
662
|
+
return stringProp && signals.has(stringProp) || Object.keys(s).includes(stringProp) || prop === Symbol.toStringTag || prop === Symbol.iterator || prop === Symbol.isConcatSpreadable || prop === "length" && isArrayLike;
|
|
663
|
+
},
|
|
664
|
+
ownKeys() {
|
|
665
|
+
return isArrayLike ? getSortedIndexes().map((key) => String(key)).concat(["length"]) : Array.from(signals.keys()).map((key) => String(key));
|
|
666
|
+
},
|
|
667
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
668
|
+
const nonEnumerable = (value) => ({
|
|
669
|
+
enumerable: false,
|
|
670
|
+
configurable: true,
|
|
671
|
+
writable: false,
|
|
672
|
+
value
|
|
673
|
+
});
|
|
674
|
+
if (prop === "length" && isArrayLike)
|
|
675
|
+
return {
|
|
676
|
+
enumerable: true,
|
|
677
|
+
configurable: true,
|
|
678
|
+
writable: false,
|
|
679
|
+
value: size.get()
|
|
680
|
+
};
|
|
681
|
+
if (prop === Symbol.isConcatSpreadable)
|
|
682
|
+
return nonEnumerable(isArrayLike);
|
|
683
|
+
if (prop === Symbol.toStringTag)
|
|
684
|
+
return nonEnumerable(TYPE_STORE);
|
|
685
|
+
if (isSymbol(prop))
|
|
686
|
+
return;
|
|
687
|
+
if (Object.keys(s).includes(prop))
|
|
688
|
+
return nonEnumerable(s[prop]);
|
|
689
|
+
const signal = signals.get(prop);
|
|
690
|
+
return signal ? {
|
|
691
|
+
enumerable: true,
|
|
692
|
+
configurable: true,
|
|
693
|
+
writable: true,
|
|
694
|
+
value: signal
|
|
695
|
+
} : undefined;
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
};
|
|
699
|
+
var isStore = (value) => isObjectOfType(value, TYPE_STORE);
|
|
700
|
+
|
|
701
|
+
// src/signal.ts
|
|
702
|
+
var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
|
|
703
|
+
var isMutableSignal = (value) => isState(value) || isStore(value);
|
|
704
|
+
function toSignal(value) {
|
|
705
|
+
if (isSignal(value))
|
|
706
|
+
return value;
|
|
707
|
+
if (isComputedCallback(value))
|
|
708
|
+
return computed(value);
|
|
709
|
+
if (Array.isArray(value) || isRecord(value))
|
|
710
|
+
return store(value);
|
|
711
|
+
return state(value);
|
|
712
|
+
}
|
|
596
713
|
export {
|
|
597
714
|
watch,
|
|
598
715
|
toSignal,
|
|
599
|
-
toMutableSignal,
|
|
600
716
|
toError,
|
|
601
717
|
subscribe,
|
|
602
718
|
store,
|
|
@@ -605,11 +721,15 @@ export {
|
|
|
605
721
|
observe,
|
|
606
722
|
notify,
|
|
607
723
|
match,
|
|
724
|
+
isSymbol,
|
|
608
725
|
isString,
|
|
609
726
|
isStore,
|
|
610
727
|
isState,
|
|
611
728
|
isSignal,
|
|
729
|
+
isRecordOrArray,
|
|
730
|
+
isRecord,
|
|
612
731
|
isNumber,
|
|
732
|
+
isMutableSignal,
|
|
613
733
|
isFunction,
|
|
614
734
|
isEqual,
|
|
615
735
|
isComputedCallback,
|
|
@@ -626,5 +746,10 @@ export {
|
|
|
626
746
|
TYPE_STORE,
|
|
627
747
|
TYPE_STATE,
|
|
628
748
|
TYPE_COMPUTED,
|
|
749
|
+
StoreKeyReadonlyError,
|
|
750
|
+
StoreKeyRangeError,
|
|
751
|
+
StoreKeyExistsError,
|
|
752
|
+
NullishSignalValueError,
|
|
753
|
+
InvalidSignalValueError,
|
|
629
754
|
CircularDependencyError
|
|
630
755
|
};
|