@zeix/cause-effect 0.14.2 → 0.15.1
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 +256 -35
- package/index.d.ts +31 -6
- package/index.dev.js +383 -47
- package/index.js +1 -1
- package/index.ts +33 -4
- package/package.json +2 -2
- package/src/computed.ts +15 -6
- package/src/diff.ts +148 -0
- package/src/effect.ts +57 -50
- package/src/match.ts +52 -0
- package/src/resolve.ts +48 -0
- package/src/signal.ts +60 -14
- package/src/state.ts +4 -3
- package/src/store.ts +324 -0
- package/src/util.ts +54 -4
- package/test/batch.test.ts +23 -19
- package/test/benchmark.test.ts +8 -8
- package/test/computed.test.ts +15 -11
- package/test/diff.test.ts +638 -0
- package/test/effect.test.ts +656 -48
- package/test/match.test.ts +378 -0
- package/test/resolve.test.ts +156 -0
- package/test/signal.test.ts +451 -0
- package/test/store.test.ts +746 -0
- package/tsconfig.json +9 -10
- package/types/index.d.ts +15 -0
- package/types/src/diff.d.ts +30 -0
- package/types/src/effect.d.ts +16 -0
- package/types/src/match.d.ts +21 -0
- package/types/src/resolve.d.ts +29 -0
- package/types/src/signal.d.ts +44 -0
- package/{src → types/src}/state.d.ts +1 -1
- package/types/src/store.d.ts +62 -0
- package/types/src/util.d.ts +14 -0
- package/types/test-new-effect.d.ts +1 -0
- package/src/effect.d.ts +0 -17
- package/src/signal.d.ts +0 -26
- package/src/util.d.ts +0 -7
- /package/{src → types/src}/computed.d.ts +0 -0
- /package/{src → types/src}/scheduler.d.ts +0 -0
package/index.dev.js
CHANGED
|
@@ -88,8 +88,20 @@ var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// src/util.ts
|
|
91
|
-
var
|
|
91
|
+
var isString = (value) => typeof value === "string";
|
|
92
|
+
var isNumber = (value) => typeof value === "number";
|
|
93
|
+
var isFunction = (fn) => typeof fn === "function";
|
|
94
|
+
var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
|
|
92
95
|
var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
|
|
96
|
+
var isRecord = (value) => isObjectOfType(value, "Object");
|
|
97
|
+
var validArrayIndexes = (keys) => {
|
|
98
|
+
if (!keys.length)
|
|
99
|
+
return null;
|
|
100
|
+
const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
|
|
101
|
+
return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
|
|
102
|
+
};
|
|
103
|
+
var hasMethod = (obj, methodName) => (methodName in obj) && isFunction(obj[methodName]);
|
|
104
|
+
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
93
105
|
var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
|
|
94
106
|
|
|
95
107
|
class CircularDependencyError extends Error {
|
|
@@ -111,7 +123,7 @@ var state = (initialValue) => {
|
|
|
111
123
|
return value;
|
|
112
124
|
},
|
|
113
125
|
set: (v) => {
|
|
114
|
-
if (
|
|
126
|
+
if (isEqual(value, v))
|
|
115
127
|
return;
|
|
116
128
|
value = v;
|
|
117
129
|
notify(watchers);
|
|
@@ -126,10 +138,323 @@ var state = (initialValue) => {
|
|
|
126
138
|
};
|
|
127
139
|
var isState = (value) => isObjectOfType(value, TYPE_STATE);
|
|
128
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
|
+
}
|
|
199
|
+
return record;
|
|
200
|
+
};
|
|
201
|
+
const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
202
|
+
const addProperty = (key, value) => {
|
|
203
|
+
const stringKey = String(key);
|
|
204
|
+
const signal = toMutableSignal(value);
|
|
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
|
+
});
|
|
352
|
+
};
|
|
353
|
+
var isStore = (value) => isObjectOfType(value, TYPE_STORE);
|
|
354
|
+
|
|
129
355
|
// src/signal.ts
|
|
130
356
|
var UNSET = Symbol();
|
|
131
|
-
var isSignal = (value) => isState(value) || isComputed(value);
|
|
132
|
-
|
|
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
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/diff.ts
|
|
380
|
+
var isEqual = (a, b, visited) => {
|
|
381
|
+
if (Object.is(a, b))
|
|
382
|
+
return true;
|
|
383
|
+
if (typeof a !== typeof b)
|
|
384
|
+
return false;
|
|
385
|
+
if (typeof a !== "object" || a === null || b === null)
|
|
386
|
+
return false;
|
|
387
|
+
if (!visited)
|
|
388
|
+
visited = new WeakSet;
|
|
389
|
+
if (visited.has(a) || visited.has(b))
|
|
390
|
+
throw new CircularDependencyError("isEqual");
|
|
391
|
+
visited.add(a);
|
|
392
|
+
visited.add(b);
|
|
393
|
+
try {
|
|
394
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
395
|
+
if (a.length !== b.length)
|
|
396
|
+
return false;
|
|
397
|
+
for (let i = 0;i < a.length; i++) {
|
|
398
|
+
if (!isEqual(a[i], b[i], visited))
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
404
|
+
return false;
|
|
405
|
+
if (isRecord(a) && isRecord(b)) {
|
|
406
|
+
const aKeys = Object.keys(a);
|
|
407
|
+
const bKeys = Object.keys(b);
|
|
408
|
+
if (aKeys.length !== bKeys.length)
|
|
409
|
+
return false;
|
|
410
|
+
for (const key of aKeys) {
|
|
411
|
+
if (!(key in b))
|
|
412
|
+
return false;
|
|
413
|
+
if (!isEqual(a[key], b[key], visited))
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
} finally {
|
|
420
|
+
visited.delete(a);
|
|
421
|
+
visited.delete(b);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var diff = (oldObj, newObj) => {
|
|
425
|
+
const visited = new WeakSet;
|
|
426
|
+
const diffRecords = (oldRecord, newRecord) => {
|
|
427
|
+
const add = {};
|
|
428
|
+
const change = {};
|
|
429
|
+
const remove = {};
|
|
430
|
+
const oldKeys = Object.keys(oldRecord);
|
|
431
|
+
const newKeys = Object.keys(newRecord);
|
|
432
|
+
const allKeys = new Set([...oldKeys, ...newKeys]);
|
|
433
|
+
for (const key of allKeys) {
|
|
434
|
+
const oldHas = key in oldRecord;
|
|
435
|
+
const newHas = key in newRecord;
|
|
436
|
+
if (!oldHas && newHas) {
|
|
437
|
+
add[key] = newRecord[key];
|
|
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;
|
|
447
|
+
}
|
|
448
|
+
const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
|
|
449
|
+
return {
|
|
450
|
+
changed,
|
|
451
|
+
add,
|
|
452
|
+
change,
|
|
453
|
+
remove
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
return diffRecords(oldObj, newObj);
|
|
457
|
+
};
|
|
133
458
|
|
|
134
459
|
// src/computed.ts
|
|
135
460
|
var TYPE_COMPUTED = "Computed";
|
|
@@ -142,7 +467,7 @@ var computed = (fn) => {
|
|
|
142
467
|
let changed = false;
|
|
143
468
|
let computing = false;
|
|
144
469
|
const ok = (v) => {
|
|
145
|
-
if (!
|
|
470
|
+
if (!isEqual(v, value)) {
|
|
146
471
|
value = v;
|
|
147
472
|
changed = true;
|
|
148
473
|
}
|
|
@@ -169,17 +494,20 @@ var computed = (fn) => {
|
|
|
169
494
|
};
|
|
170
495
|
const mark = watch(() => {
|
|
171
496
|
dirty = true;
|
|
172
|
-
controller?.abort(
|
|
497
|
+
controller?.abort();
|
|
173
498
|
if (watchers.size)
|
|
174
499
|
notify(watchers);
|
|
175
500
|
else
|
|
176
501
|
mark.cleanup();
|
|
177
502
|
});
|
|
503
|
+
mark.off(() => {
|
|
504
|
+
controller?.abort();
|
|
505
|
+
});
|
|
178
506
|
const compute = () => observe(() => {
|
|
179
507
|
if (computing)
|
|
180
508
|
throw new CircularDependencyError("computed");
|
|
181
509
|
changed = false;
|
|
182
|
-
if (
|
|
510
|
+
if (isAsyncFunction(fn)) {
|
|
183
511
|
if (controller)
|
|
184
512
|
return value;
|
|
185
513
|
controller = new AbortController;
|
|
@@ -196,7 +524,7 @@ var computed = (fn) => {
|
|
|
196
524
|
try {
|
|
197
525
|
result = controller ? fn(controller.signal) : fn();
|
|
198
526
|
} catch (e) {
|
|
199
|
-
if (e
|
|
527
|
+
if (isAbortError(e))
|
|
200
528
|
nil();
|
|
201
529
|
else
|
|
202
530
|
err(e);
|
|
@@ -227,67 +555,75 @@ var computed = (fn) => {
|
|
|
227
555
|
};
|
|
228
556
|
var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
|
|
229
557
|
var isComputedCallback = (value) => isFunction(value) && value.length < 2;
|
|
230
|
-
// src/
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
pending2 = true;
|
|
253
|
-
return value;
|
|
254
|
-
} catch (e) {
|
|
255
|
-
errors.push(toError(e));
|
|
256
|
-
return UNSET;
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
let cleanup;
|
|
558
|
+
// src/match.ts
|
|
559
|
+
function match(result, handlers) {
|
|
560
|
+
try {
|
|
561
|
+
if (result.pending)
|
|
562
|
+
handlers.nil?.();
|
|
563
|
+
else if (result.errors)
|
|
564
|
+
handlers.err?.(result.errors);
|
|
565
|
+
else
|
|
566
|
+
handlers.ok?.(result.values);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
if (handlers.err && (!result.errors || !result.errors.includes(toError(error))))
|
|
569
|
+
handlers.err(result.errors ? [...result.errors, toError(error)] : [toError(error)]);
|
|
570
|
+
else
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// src/resolve.ts
|
|
575
|
+
function resolve(signals) {
|
|
576
|
+
const errors = [];
|
|
577
|
+
let pending2 = false;
|
|
578
|
+
const values = {};
|
|
579
|
+
for (const [key, signal] of Object.entries(signals)) {
|
|
260
580
|
try {
|
|
261
|
-
|
|
581
|
+
const value = signal.get();
|
|
582
|
+
if (value === UNSET)
|
|
583
|
+
pending2 = true;
|
|
584
|
+
else
|
|
585
|
+
values[key] = value;
|
|
262
586
|
} catch (e) {
|
|
263
|
-
|
|
264
|
-
} finally {
|
|
265
|
-
if (isFunction(cleanup))
|
|
266
|
-
run.off(cleanup);
|
|
587
|
+
errors.push(toError(e));
|
|
267
588
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
589
|
+
}
|
|
590
|
+
if (pending2)
|
|
591
|
+
return { ok: false, pending: true };
|
|
592
|
+
if (errors.length > 0)
|
|
593
|
+
return { ok: false, errors };
|
|
594
|
+
return { ok: true, values };
|
|
272
595
|
}
|
|
273
596
|
export {
|
|
274
597
|
watch,
|
|
275
598
|
toSignal,
|
|
599
|
+
toMutableSignal,
|
|
600
|
+
toError,
|
|
276
601
|
subscribe,
|
|
602
|
+
store,
|
|
277
603
|
state,
|
|
604
|
+
resolve,
|
|
278
605
|
observe,
|
|
279
606
|
notify,
|
|
607
|
+
match,
|
|
608
|
+
isString,
|
|
609
|
+
isStore,
|
|
280
610
|
isState,
|
|
281
611
|
isSignal,
|
|
612
|
+
isNumber,
|
|
282
613
|
isFunction,
|
|
614
|
+
isEqual,
|
|
283
615
|
isComputedCallback,
|
|
284
616
|
isComputed,
|
|
617
|
+
isAsyncFunction,
|
|
618
|
+
isAbortError,
|
|
285
619
|
flush,
|
|
286
620
|
enqueue,
|
|
287
621
|
effect,
|
|
622
|
+
diff,
|
|
288
623
|
computed,
|
|
289
624
|
batch,
|
|
290
625
|
UNSET,
|
|
626
|
+
TYPE_STORE,
|
|
291
627
|
TYPE_STATE,
|
|
292
628
|
TYPE_COMPUTED,
|
|
293
629
|
CircularDependencyError
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var q,f=new Set,p=0,h=new Map,k,r=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},F$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(r)};queueMicrotask(r);var E=($)=>{let B=new Set,W=$;return W.off=(F)=>{B.add(F)},W.cleanup=()=>{for(let F of B)F();B.clear()},W},O=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},C=($)=>{for(let B of $)if(p)f.add(B);else B()},w=()=>{while(f.size){let $=Array.from(f);f.clear();for(let B of $)B()}},v=($)=>{p++;try{$()}finally{w(),p--}},T=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},G$=($,B)=>new Promise((W,F)=>{h.set(B||Symbol(),()=>{try{W($())}catch(Q){F(Q)}}),F$()});var a=($)=>typeof $==="string",e=($)=>typeof $==="number",D=($)=>typeof $==="function",y=($)=>D($)&&$.constructor.name==="AsyncFunction",P=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,R=($)=>P($,"Object"),$$=($)=>{if(!$.length)return null;let B=$.map((W)=>a(W)?parseInt(W,10):e(W)?W:NaN);return B.every((W)=>Number.isFinite(W)&&W>=0)?B.sort((W,F)=>W-F):null},B$=($,B)=>(B in $)&&D($[B]),V=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class I extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var d="State",S=($)=>{let B=new Set,W=$,F={[Symbol.toStringTag]:d,get:()=>{return O(B),W},set:(Q)=>{if(K(W,Q))return;if(W=Q,C(B),z===W)B.clear()},update:(Q)=>{F.set(Q(W))}};return F},b=($)=>P($,d);var o=($)=>{let B=y($),W=!1,F,Q=E(()=>T(()=>{if(W)throw new I("effect");W=!0,F?.abort(),F=void 0;let Z;try{if(B){F=new AbortController;let A=F;$(F.signal).then((H)=>{if(D(H)&&F===A)Q.off(H)}).catch((H)=>{if(!V(H))console.error("Async effect error:",H)})}else if(Z=$(),D(Z))Q.off(Z)}catch(A){if(!V(A))console.error("Effect callback error:",A)}W=!1},Q));return Q(),()=>{F?.abort(),Q.cleanup()}};var c="Store",m=($)=>{let B=new Set,W=new EventTarget,F=new Map,Q=new Map,Z=S(0),A=()=>{let M=Array.from(F.keys()),L=$$(M);if(L)return L.map((J)=>F.get(String(J))?.get());let G={};for(let[J,X]of F)G[J]=X.get();return G},H=(M,L)=>W.dispatchEvent(new CustomEvent(M,{detail:L})),x=(M,L)=>{let G=String(M),J=n(L);F.set(G,J);let X=o(()=>{let j=J.get();if(j!=null)H("store-change",{[M]:j})});Q.set(G,X)},U=(M)=>{let L=String(M);F.delete(L);let G=Q.get(L);if(G)G();Q.delete(L)},N=(M,L)=>{let G=u(M,L);return v(()=>{if(Object.keys(G.add).length){for(let J in G.add){let X=G.add[J];if(X!=null)x(J,X)}H("store-add",G.add)}if(Object.keys(G.change).length){for(let J in G.change){let X=F.get(J),j=G.change[J];if(X&&j!=null&&B$(X,"set"))X.set(j)}H("store-change",G.change)}if(Object.keys(G.remove).length){for(let J in G.remove)U(J);H("store-remove",G.remove)}Z.set(F.size)}),G.changed};N({},$),setTimeout(()=>{let M=new CustomEvent("store-add",{detail:$});W.dispatchEvent(M)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(M,L){switch(L){case"add":return(G,J)=>{if(!F.has(G))x(G,J),C(B),H("store-add",{[G]:J}),Z.set(F.size)};case"get":return()=>{return O(B),A()};case"remove":return(G)=>{if(F.has(G))U(G),C(B),H("store-remove",{[G]:z}),Z.set(F.size)};case"set":return(G)=>{if(N(A(),G)){if(C(B),z===G)B.clear()}};case"update":return(G)=>{let J=A(),X=G(J);if(N(J,X)){if(C(B),z===X)B.clear()}};case"addEventListener":return W.addEventListener.bind(W);case"removeEventListener":return W.removeEventListener.bind(W);case"dispatchEvent":return W.dispatchEvent.bind(W);case"size":return Z}if(L===Symbol.toStringTag)return c;if(L===Symbol.iterator)return function*(){for(let[G,J]of F)yield[G,J]};return F.get(String(L))},has(M,L){let G=String(L);return F.has(G)||_.includes(G)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(F.keys()).map((M)=>String(M))},getOwnPropertyDescriptor(M,L){let G=F.get(String(L));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})},g=($)=>P($,c);var z=Symbol(),W$=($)=>b($)||t($)||g($);function J$($){if(W$($))return $;if(s($))return i($);if(Array.isArray($))return m($);if(Array.isArray($)||R($))return m($);return S($)}function n($){if(b($)||g($))return $;if(Array.isArray($))return m($);if(R($))return m($);return S($)}var K=($,B,W)=>{if(Object.is($,B))return!0;if(typeof $!==typeof B)return!1;if(typeof $!=="object"||$===null||B===null)return!1;if(!W)W=new WeakSet;if(W.has($)||W.has(B))throw new I("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let F=0;F<$.length;F++)if(!K($[F],B[F],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(R($)&&R(B)){let F=Object.keys($),Q=Object.keys(B);if(F.length!==Q.length)return!1;for(let Z of F){if(!(Z in B))return!1;if(!K($[Z],B[Z],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((Q,Z)=>{let A={},H={},x={},U=Object.keys(Q),N=Object.keys(Z),_=new Set([...U,...N]);for(let L of _){let G=L in Q,J=L in Z;if(!G&&J){A[L]=Z[L];continue}else if(G&&!J){x[L]=z;continue}let X=Q[L],j=Z[L];if(!K(X,j,W))H[L]=j}return{changed:Object.keys(A).length>0||Object.keys(H).length>0||Object.keys(x).length>0,add:A,change:H,remove:x}})($,B)};var l="Computed",i=($)=>{let B=new Set,W=z,F,Q,Z=!0,A=!1,H=!1,x=(J)=>{if(!K(J,W))W=J,A=!0;F=void 0,Z=!1},U=()=>{A=z!==W,W=z,F=void 0},N=(J)=>{let X=Y(J);A=!F||X.name!==F.name||X.message!==F.message,W=z,F=X},_=(J)=>(X)=>{if(H=!1,Q=void 0,J(X),A)C(B)},M=E(()=>{if(Z=!0,Q?.abort(),B.size)C(B);else M.cleanup()});M.off(()=>{Q?.abort()});let L=()=>T(()=>{if(H)throw new I("computed");if(A=!1,y($)){if(Q)return W;Q=new AbortController,Q.signal.addEventListener("abort",()=>{H=!1,Q=void 0,L()},{once:!0})}let J;H=!0;try{J=Q?$(Q.signal):$()}catch(X){if(V(X))U();else N(X);H=!1;return}if(J instanceof Promise)J.then(_(x),_(N));else if(J==null||z===J)U();else x(J);H=!1},M);return{[Symbol.toStringTag]:l,get:()=>{if(O(B),w(),Z)L();if(F)throw F;return W}}},t=($)=>P($,l),s=($)=>D($)&&$.length<2;function L$($,B){try{if($.pending)B.nil?.();else if($.errors)B.err?.($.errors);else B.ok?.($.values)}catch(W){if(B.err&&(!$.errors||!$.errors.includes(Y(W))))B.err($.errors?[...$.errors,Y(W)]:[Y(W)]);else throw W}}function Q$($){let B=[],W=!1,F={};for(let[Q,Z]of Object.entries($))try{let A=Z.get();if(A===z)W=!0;else F[Q]=A}catch(A){B.push(Y(A))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:F}}export{E as watch,J$ as toSignal,n as toMutableSignal,Y as toError,O as subscribe,m as store,S as state,Q$ as resolve,T as observe,C as notify,L$ as match,a as isString,g as isStore,b as isState,W$ as isSignal,e as isNumber,D as isFunction,K as isEqual,s as isComputedCallback,t as isComputed,y as isAsyncFunction,V as isAbortError,w as flush,G$ as enqueue,o as effect,u as diff,i as computed,v as batch,z as UNSET,c as TYPE_STORE,d as TYPE_STATE,l as TYPE_COMPUTED,I as CircularDependencyError};
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.15.1
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,9 +9,19 @@ export {
|
|
|
9
9
|
type ComputedCallback,
|
|
10
10
|
computed,
|
|
11
11
|
isComputed,
|
|
12
|
+
isComputedCallback,
|
|
12
13
|
TYPE_COMPUTED,
|
|
13
14
|
} from './src/computed'
|
|
14
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
type DiffResult,
|
|
17
|
+
diff,
|
|
18
|
+
isEqual,
|
|
19
|
+
type UnknownRecord,
|
|
20
|
+
type UnknownRecordOrArray,
|
|
21
|
+
} from './src/diff'
|
|
22
|
+
export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
|
|
23
|
+
export { type MatchHandlers, match } from './src/match'
|
|
24
|
+
export { type ResolveResult, resolve } from './src/resolve'
|
|
15
25
|
export {
|
|
16
26
|
batch,
|
|
17
27
|
type Cleanup,
|
|
@@ -25,13 +35,32 @@ export {
|
|
|
25
35
|
watch,
|
|
26
36
|
} from './src/scheduler'
|
|
27
37
|
export {
|
|
28
|
-
isComputedCallback,
|
|
29
38
|
isSignal,
|
|
30
39
|
type MaybeSignal,
|
|
31
40
|
type Signal,
|
|
32
41
|
type SignalValues,
|
|
42
|
+
toMutableSignal,
|
|
33
43
|
toSignal,
|
|
34
44
|
UNSET,
|
|
45
|
+
type UnknownSignalRecord,
|
|
35
46
|
} from './src/signal'
|
|
36
47
|
export { isState, type State, state, TYPE_STATE } from './src/state'
|
|
37
|
-
export {
|
|
48
|
+
export {
|
|
49
|
+
isStore,
|
|
50
|
+
type Store,
|
|
51
|
+
type StoreAddEvent,
|
|
52
|
+
type StoreChangeEvent,
|
|
53
|
+
type StoreEventMap,
|
|
54
|
+
type StoreRemoveEvent,
|
|
55
|
+
store,
|
|
56
|
+
TYPE_STORE,
|
|
57
|
+
} from './src/store'
|
|
58
|
+
export {
|
|
59
|
+
CircularDependencyError,
|
|
60
|
+
isAbortError,
|
|
61
|
+
isAsyncFunction,
|
|
62
|
+
isFunction,
|
|
63
|
+
isNumber,
|
|
64
|
+
isString,
|
|
65
|
+
toError,
|
|
66
|
+
} from './src/util'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeix/cause-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"author": "Esther Brunner",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.ts",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"lint": "bunx biome lint --write"
|
|
30
30
|
},
|
|
31
31
|
"type": "module",
|
|
32
|
-
"types": "index.d.ts"
|
|
32
|
+
"types": "types/index.d.ts"
|
|
33
33
|
}
|
package/src/computed.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isEqual } from './diff'
|
|
1
2
|
import {
|
|
2
3
|
flush,
|
|
3
4
|
notify,
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
import { UNSET } from './signal'
|
|
10
11
|
import {
|
|
11
12
|
CircularDependencyError,
|
|
13
|
+
isAbortError,
|
|
14
|
+
isAsyncFunction,
|
|
12
15
|
isFunction,
|
|
13
16
|
isObjectOfType,
|
|
14
17
|
toError,
|
|
@@ -50,7 +53,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
50
53
|
|
|
51
54
|
// Functions to update internal state
|
|
52
55
|
const ok = (v: T): undefined => {
|
|
53
|
-
if (!
|
|
56
|
+
if (!isEqual(v, value)) {
|
|
54
57
|
value = v
|
|
55
58
|
changed = true
|
|
56
59
|
}
|
|
@@ -83,25 +86,31 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
83
86
|
// Own watcher: called when notified from sources (push)
|
|
84
87
|
const mark = watch(() => {
|
|
85
88
|
dirty = true
|
|
86
|
-
controller?.abort(
|
|
89
|
+
controller?.abort()
|
|
87
90
|
if (watchers.size) notify(watchers)
|
|
88
91
|
else mark.cleanup()
|
|
89
92
|
})
|
|
93
|
+
mark.off(() => {
|
|
94
|
+
controller?.abort()
|
|
95
|
+
})
|
|
90
96
|
|
|
91
97
|
// Called when requested by dependencies (pull)
|
|
92
98
|
const compute = () =>
|
|
93
99
|
observe(() => {
|
|
94
100
|
if (computing) throw new CircularDependencyError('computed')
|
|
95
101
|
changed = false
|
|
96
|
-
if (
|
|
97
|
-
|
|
102
|
+
if (isAsyncFunction(fn)) {
|
|
103
|
+
// Return current value until promise resolves
|
|
104
|
+
if (controller) return value
|
|
98
105
|
controller = new AbortController()
|
|
99
106
|
controller.signal.addEventListener(
|
|
100
107
|
'abort',
|
|
101
108
|
() => {
|
|
102
109
|
computing = false
|
|
103
110
|
controller = undefined
|
|
104
|
-
|
|
111
|
+
|
|
112
|
+
// Retry computation with updated state
|
|
113
|
+
compute()
|
|
105
114
|
},
|
|
106
115
|
{
|
|
107
116
|
once: true,
|
|
@@ -113,7 +122,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
|
|
|
113
122
|
try {
|
|
114
123
|
result = controller ? fn(controller.signal) : (fn as () => T)()
|
|
115
124
|
} catch (e) {
|
|
116
|
-
if (e
|
|
125
|
+
if (isAbortError(e)) nil()
|
|
117
126
|
else err(e)
|
|
118
127
|
computing = false
|
|
119
128
|
return
|