@zeix/cause-effect 0.14.2 → 0.15.0
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 -27
- package/index.d.ts +31 -6
- package/index.dev.js +381 -46
- package/index.js +1 -1
- package/index.ts +23 -4
- package/package.json +2 -2
- package/src/computed.ts +15 -6
- package/src/diff.ts +136 -0
- package/src/effect.ts +58 -50
- package/src/match.ts +57 -0
- package/src/resolve.ts +58 -0
- package/src/signal.ts +46 -14
- package/src/state.ts +4 -3
- package/src/store.ts +325 -0
- package/src/util.ts +56 -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/store.test.ts +719 -0
- package/tsconfig.json +9 -10
- package/types/index.d.ts +15 -0
- package/types/src/diff.d.ts +27 -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 +40 -0
- package/{src → types/src}/state.d.ts +1 -1
- package/types/src/store.d.ts +57 -0
- package/types/src/util.d.ts +15 -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 isFunction = (
|
|
91
|
+
var isFunction = (fn) => typeof fn === "function";
|
|
92
|
+
var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
|
|
92
93
|
var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
|
|
94
|
+
var isRecord = (value) => isObjectOfType(value, "Object");
|
|
95
|
+
var arrayToRecord = (array) => {
|
|
96
|
+
const record = {};
|
|
97
|
+
for (let i = 0;i < array.length; i++) {
|
|
98
|
+
if (i in array)
|
|
99
|
+
record[String(i)] = array[i];
|
|
100
|
+
}
|
|
101
|
+
return record;
|
|
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,319 @@ 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
|
+
}
|
|
161
|
+
}).catch((error) => {
|
|
162
|
+
if (!isAbortError(error))
|
|
163
|
+
console.error("Async effect error:", error);
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
cleanup = callback();
|
|
167
|
+
if (isFunction(cleanup))
|
|
168
|
+
run.off(cleanup);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (!isAbortError(error))
|
|
172
|
+
console.error("Effect callback error:", error);
|
|
173
|
+
}
|
|
174
|
+
running = false;
|
|
175
|
+
}, run));
|
|
176
|
+
run();
|
|
177
|
+
return () => {
|
|
178
|
+
controller?.abort();
|
|
179
|
+
run.cleanup();
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/store.ts
|
|
184
|
+
var TYPE_STORE = "Store";
|
|
185
|
+
var store = (initialValue) => {
|
|
186
|
+
const watchers = new Set;
|
|
187
|
+
const eventTarget = new EventTarget;
|
|
188
|
+
const signals = new Map;
|
|
189
|
+
const cleanups = new Map;
|
|
190
|
+
const size = state(0);
|
|
191
|
+
const current = () => {
|
|
192
|
+
const record = {};
|
|
193
|
+
for (const [key, value] of signals) {
|
|
194
|
+
record[key] = value.get();
|
|
195
|
+
}
|
|
196
|
+
return record;
|
|
197
|
+
};
|
|
198
|
+
const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
199
|
+
const addSignalAndEffect = (key, value) => {
|
|
200
|
+
const signal = toMutableSignal(value);
|
|
201
|
+
signals.set(key, signal);
|
|
202
|
+
const cleanup = effect(() => {
|
|
203
|
+
const value2 = signal.get();
|
|
204
|
+
if (value2 != null)
|
|
205
|
+
emit("store-change", { [key]: value2 });
|
|
206
|
+
});
|
|
207
|
+
cleanups.set(key, cleanup);
|
|
208
|
+
};
|
|
209
|
+
const removeSignalAndEffect = (key) => {
|
|
210
|
+
signals.delete(key);
|
|
211
|
+
const cleanup = cleanups.get(key);
|
|
212
|
+
if (cleanup)
|
|
213
|
+
cleanup();
|
|
214
|
+
cleanups.delete(key);
|
|
215
|
+
};
|
|
216
|
+
const reconcile = (oldValue, newValue) => {
|
|
217
|
+
const changes = diff(oldValue, newValue);
|
|
218
|
+
batch(() => {
|
|
219
|
+
if (Object.keys(changes.add).length) {
|
|
220
|
+
for (const key in changes.add) {
|
|
221
|
+
const value = changes.add[key];
|
|
222
|
+
if (value != null)
|
|
223
|
+
addSignalAndEffect(key, value);
|
|
224
|
+
}
|
|
225
|
+
emit("store-add", changes.add);
|
|
226
|
+
}
|
|
227
|
+
if (Object.keys(changes.change).length) {
|
|
228
|
+
for (const key in changes.change) {
|
|
229
|
+
const signal = signals.get(key);
|
|
230
|
+
const value = changes.change[key];
|
|
231
|
+
if (signal && value != null && hasMethod(signal, "set"))
|
|
232
|
+
signal.set(value);
|
|
233
|
+
}
|
|
234
|
+
emit("store-change", changes.change);
|
|
235
|
+
}
|
|
236
|
+
if (Object.keys(changes.remove).length) {
|
|
237
|
+
for (const key in changes.remove) {
|
|
238
|
+
removeSignalAndEffect(key);
|
|
239
|
+
}
|
|
240
|
+
emit("store-remove", changes.remove);
|
|
241
|
+
}
|
|
242
|
+
size.set(signals.size);
|
|
243
|
+
});
|
|
244
|
+
return changes.changed;
|
|
245
|
+
};
|
|
246
|
+
reconcile({}, initialValue);
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
const initialAdditionsEvent = new CustomEvent("store-add", {
|
|
249
|
+
detail: initialValue
|
|
250
|
+
});
|
|
251
|
+
eventTarget.dispatchEvent(initialAdditionsEvent);
|
|
252
|
+
}, 0);
|
|
253
|
+
const storeProps = [
|
|
254
|
+
"add",
|
|
255
|
+
"get",
|
|
256
|
+
"remove",
|
|
257
|
+
"set",
|
|
258
|
+
"update",
|
|
259
|
+
"addEventListener",
|
|
260
|
+
"removeEventListener",
|
|
261
|
+
"dispatchEvent",
|
|
262
|
+
"size"
|
|
263
|
+
];
|
|
264
|
+
return new Proxy({}, {
|
|
265
|
+
get(_target, prop) {
|
|
266
|
+
const key = String(prop);
|
|
267
|
+
switch (prop) {
|
|
268
|
+
case "add":
|
|
269
|
+
return (k, v) => {
|
|
270
|
+
if (!signals.has(k)) {
|
|
271
|
+
addSignalAndEffect(k, v);
|
|
272
|
+
notify(watchers);
|
|
273
|
+
emit("store-add", {
|
|
274
|
+
[k]: v
|
|
275
|
+
});
|
|
276
|
+
size.set(signals.size);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
case "get":
|
|
280
|
+
return () => {
|
|
281
|
+
subscribe(watchers);
|
|
282
|
+
return current();
|
|
283
|
+
};
|
|
284
|
+
case "remove":
|
|
285
|
+
return (k) => {
|
|
286
|
+
if (signals.has(k)) {
|
|
287
|
+
removeSignalAndEffect(k);
|
|
288
|
+
notify(watchers);
|
|
289
|
+
emit("store-remove", { [k]: UNSET });
|
|
290
|
+
size.set(signals.size);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
case "set":
|
|
294
|
+
return (v) => {
|
|
295
|
+
if (reconcile(current(), v)) {
|
|
296
|
+
notify(watchers);
|
|
297
|
+
if (UNSET === v)
|
|
298
|
+
watchers.clear();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
case "update":
|
|
302
|
+
return (fn) => {
|
|
303
|
+
const oldValue = current();
|
|
304
|
+
const newValue = fn(oldValue);
|
|
305
|
+
if (reconcile(oldValue, newValue)) {
|
|
306
|
+
notify(watchers);
|
|
307
|
+
if (UNSET === newValue)
|
|
308
|
+
watchers.clear();
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
case "addEventListener":
|
|
312
|
+
return eventTarget.addEventListener.bind(eventTarget);
|
|
313
|
+
case "removeEventListener":
|
|
314
|
+
return eventTarget.removeEventListener.bind(eventTarget);
|
|
315
|
+
case "dispatchEvent":
|
|
316
|
+
return eventTarget.dispatchEvent.bind(eventTarget);
|
|
317
|
+
case "size":
|
|
318
|
+
return size;
|
|
319
|
+
}
|
|
320
|
+
if (prop === Symbol.toStringTag)
|
|
321
|
+
return TYPE_STORE;
|
|
322
|
+
if (prop === Symbol.iterator) {
|
|
323
|
+
return function* () {
|
|
324
|
+
for (const [key2, signal] of signals) {
|
|
325
|
+
yield [key2, signal];
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return signals.get(key);
|
|
330
|
+
},
|
|
331
|
+
has(_target, prop) {
|
|
332
|
+
const key = String(prop);
|
|
333
|
+
return signals.has(key) || storeProps.includes(key) || prop === Symbol.toStringTag || prop === Symbol.iterator;
|
|
334
|
+
},
|
|
335
|
+
ownKeys() {
|
|
336
|
+
return Array.from(signals.keys());
|
|
337
|
+
},
|
|
338
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
339
|
+
const signal = signals.get(String(prop));
|
|
340
|
+
return signal ? {
|
|
341
|
+
enumerable: true,
|
|
342
|
+
configurable: true,
|
|
343
|
+
writable: true,
|
|
344
|
+
value: signal
|
|
345
|
+
} : undefined;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
var isStore = (value) => isObjectOfType(value, TYPE_STORE);
|
|
350
|
+
|
|
129
351
|
// src/signal.ts
|
|
130
352
|
var UNSET = Symbol();
|
|
131
|
-
var isSignal = (value) => isState(value) || isComputed(value);
|
|
132
|
-
|
|
353
|
+
var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
|
|
354
|
+
function toSignal(value) {
|
|
355
|
+
if (isSignal(value))
|
|
356
|
+
return value;
|
|
357
|
+
if (isComputedCallback(value))
|
|
358
|
+
return computed(value);
|
|
359
|
+
if (Array.isArray(value))
|
|
360
|
+
return store(arrayToRecord(value));
|
|
361
|
+
if (isRecord(value))
|
|
362
|
+
return store(value);
|
|
363
|
+
return state(value);
|
|
364
|
+
}
|
|
365
|
+
function toMutableSignal(value) {
|
|
366
|
+
if (isState(value) || isStore(value))
|
|
367
|
+
return value;
|
|
368
|
+
if (Array.isArray(value))
|
|
369
|
+
return store(arrayToRecord(value));
|
|
370
|
+
if (isRecord(value))
|
|
371
|
+
return store(value);
|
|
372
|
+
return state(value);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/diff.ts
|
|
376
|
+
var isEqual = (a, b, visited) => {
|
|
377
|
+
if (Object.is(a, b))
|
|
378
|
+
return true;
|
|
379
|
+
if (typeof a !== typeof b)
|
|
380
|
+
return false;
|
|
381
|
+
if (typeof a !== "object" || a === null || b === null)
|
|
382
|
+
return false;
|
|
383
|
+
if (!visited)
|
|
384
|
+
visited = new WeakSet;
|
|
385
|
+
if (visited.has(a) || visited.has(b))
|
|
386
|
+
throw new CircularDependencyError("isEqual");
|
|
387
|
+
visited.add(a);
|
|
388
|
+
visited.add(b);
|
|
389
|
+
try {
|
|
390
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
391
|
+
if (a.length !== b.length)
|
|
392
|
+
return false;
|
|
393
|
+
for (let i = 0;i < a.length; i++) {
|
|
394
|
+
if (!isEqual(a[i], b[i], visited))
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
400
|
+
return false;
|
|
401
|
+
if (isRecord(a) && isRecord(b)) {
|
|
402
|
+
const aKeys = Object.keys(a);
|
|
403
|
+
const bKeys = Object.keys(b);
|
|
404
|
+
if (aKeys.length !== bKeys.length)
|
|
405
|
+
return false;
|
|
406
|
+
for (const key of aKeys) {
|
|
407
|
+
if (!(key in b))
|
|
408
|
+
return false;
|
|
409
|
+
if (!isEqual(a[key], b[key], visited))
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
} finally {
|
|
416
|
+
visited.delete(a);
|
|
417
|
+
visited.delete(b);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
var diff = (oldObj, newObj) => {
|
|
421
|
+
const visited = new WeakSet;
|
|
422
|
+
const diffRecords = (oldRecord, newRecord) => {
|
|
423
|
+
const add = {};
|
|
424
|
+
const change = {};
|
|
425
|
+
const remove = {};
|
|
426
|
+
const oldKeys = Object.keys(oldRecord);
|
|
427
|
+
const newKeys = Object.keys(newRecord);
|
|
428
|
+
const allKeys = new Set([...oldKeys, ...newKeys]);
|
|
429
|
+
for (const key of allKeys) {
|
|
430
|
+
const oldHas = key in oldRecord;
|
|
431
|
+
const newHas = key in newRecord;
|
|
432
|
+
if (!oldHas && newHas) {
|
|
433
|
+
add[key] = newRecord[key];
|
|
434
|
+
continue;
|
|
435
|
+
} else if (oldHas && !newHas) {
|
|
436
|
+
remove[key] = UNSET;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const oldValue = oldRecord[key];
|
|
440
|
+
const newValue = newRecord[key];
|
|
441
|
+
if (!isEqual(oldValue, newValue, visited))
|
|
442
|
+
change[key] = newValue;
|
|
443
|
+
}
|
|
444
|
+
const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
|
|
445
|
+
return {
|
|
446
|
+
changed,
|
|
447
|
+
add,
|
|
448
|
+
change,
|
|
449
|
+
remove
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
return diffRecords(oldObj, newObj);
|
|
453
|
+
};
|
|
133
454
|
|
|
134
455
|
// src/computed.ts
|
|
135
456
|
var TYPE_COMPUTED = "Computed";
|
|
@@ -142,7 +463,7 @@ var computed = (fn) => {
|
|
|
142
463
|
let changed = false;
|
|
143
464
|
let computing = false;
|
|
144
465
|
const ok = (v) => {
|
|
145
|
-
if (!
|
|
466
|
+
if (!isEqual(v, value)) {
|
|
146
467
|
value = v;
|
|
147
468
|
changed = true;
|
|
148
469
|
}
|
|
@@ -169,17 +490,20 @@ var computed = (fn) => {
|
|
|
169
490
|
};
|
|
170
491
|
const mark = watch(() => {
|
|
171
492
|
dirty = true;
|
|
172
|
-
controller?.abort(
|
|
493
|
+
controller?.abort();
|
|
173
494
|
if (watchers.size)
|
|
174
495
|
notify(watchers);
|
|
175
496
|
else
|
|
176
497
|
mark.cleanup();
|
|
177
498
|
});
|
|
499
|
+
mark.off(() => {
|
|
500
|
+
controller?.abort();
|
|
501
|
+
});
|
|
178
502
|
const compute = () => observe(() => {
|
|
179
503
|
if (computing)
|
|
180
504
|
throw new CircularDependencyError("computed");
|
|
181
505
|
changed = false;
|
|
182
|
-
if (
|
|
506
|
+
if (isAsyncFunction(fn)) {
|
|
183
507
|
if (controller)
|
|
184
508
|
return value;
|
|
185
509
|
controller = new AbortController;
|
|
@@ -196,7 +520,7 @@ var computed = (fn) => {
|
|
|
196
520
|
try {
|
|
197
521
|
result = controller ? fn(controller.signal) : fn();
|
|
198
522
|
} catch (e) {
|
|
199
|
-
if (e
|
|
523
|
+
if (isAbortError(e))
|
|
200
524
|
nil();
|
|
201
525
|
else
|
|
202
526
|
err(e);
|
|
@@ -227,67 +551,78 @@ var computed = (fn) => {
|
|
|
227
551
|
};
|
|
228
552
|
var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
|
|
229
553
|
var isComputedCallback = (value) => isFunction(value) && value.length < 2;
|
|
230
|
-
// src/
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
554
|
+
// src/match.ts
|
|
555
|
+
function match(result, handlers) {
|
|
556
|
+
try {
|
|
557
|
+
if (result.pending) {
|
|
558
|
+
handlers.nil?.();
|
|
559
|
+
} else if (result.errors) {
|
|
560
|
+
handlers.err?.(result.errors);
|
|
561
|
+
} else {
|
|
562
|
+
handlers.ok?.(result.values);
|
|
239
563
|
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
errors.push(toError(e));
|
|
256
|
-
return UNSET;
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
let cleanup;
|
|
564
|
+
} catch (error) {
|
|
565
|
+
if (handlers.err && (!result.errors || !result.errors.includes(toError(error)))) {
|
|
566
|
+
const allErrors = result.errors ? [...result.errors, toError(error)] : [toError(error)];
|
|
567
|
+
handlers.err(allErrors);
|
|
568
|
+
} else {
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// src/resolve.ts
|
|
574
|
+
function resolve(signals) {
|
|
575
|
+
const errors = [];
|
|
576
|
+
let pending2 = false;
|
|
577
|
+
const values = {};
|
|
578
|
+
for (const [key, signal] of Object.entries(signals)) {
|
|
260
579
|
try {
|
|
261
|
-
|
|
580
|
+
const value = signal.get();
|
|
581
|
+
if (value === UNSET) {
|
|
582
|
+
pending2 = true;
|
|
583
|
+
} else {
|
|
584
|
+
values[key] = value;
|
|
585
|
+
}
|
|
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
|
+
}
|
|
593
|
+
if (errors.length > 0) {
|
|
594
|
+
return { ok: false, errors };
|
|
595
|
+
}
|
|
596
|
+
return { ok: true, values };
|
|
272
597
|
}
|
|
273
598
|
export {
|
|
274
599
|
watch,
|
|
275
600
|
toSignal,
|
|
601
|
+
toError,
|
|
276
602
|
subscribe,
|
|
603
|
+
store,
|
|
277
604
|
state,
|
|
605
|
+
resolve,
|
|
278
606
|
observe,
|
|
279
607
|
notify,
|
|
608
|
+
match,
|
|
609
|
+
isStore,
|
|
280
610
|
isState,
|
|
281
611
|
isSignal,
|
|
282
612
|
isFunction,
|
|
613
|
+
isEqual,
|
|
283
614
|
isComputedCallback,
|
|
284
615
|
isComputed,
|
|
616
|
+
isAsyncFunction,
|
|
617
|
+
isAbortError,
|
|
285
618
|
flush,
|
|
286
619
|
enqueue,
|
|
287
620
|
effect,
|
|
621
|
+
diff,
|
|
288
622
|
computed,
|
|
289
623
|
batch,
|
|
290
624
|
UNSET,
|
|
625
|
+
TYPE_STORE,
|
|
291
626
|
TYPE_STATE,
|
|
292
627
|
TYPE_COMPUTED,
|
|
293
628
|
CircularDependencyError
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var q,f=new Set,p=0,h=new Map,k,l=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},B$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(l)};queueMicrotask(l);var m=($)=>{let B=new Set,W=$;return W.off=(x)=>{B.add(x)},W.cleanup=()=>{for(let x of B)x();B.clear()},W},U=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},M=($)=>{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--}},y=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},W$=($,B)=>new Promise((W,x)=>{h.set(B||Symbol(),()=>{try{W($())}catch(J){x(J)}}),B$()});var A=($)=>typeof $==="function",E=($)=>A($)&&$.constructor.name==="AsyncFunction",O=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,V=($)=>O($,"Object");var d=($)=>{let B={};for(let W=0;W<$.length;W++)if(W in $)B[String(W)]=$[W];return B},a=($,B)=>(B in $)&&A($[B]),R=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class D extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var o="State",S=($)=>{let B=new Set,W=$,x={[Symbol.toStringTag]:o,get:()=>{return U(B),W},set:(J)=>{if(P(W,J))return;if(W=J,M(B),C===W)B.clear()},update:(J)=>{x.set(J(W))}};return x},b=($)=>O($,o);var c=($)=>{let B=E($),W=!1,x,J=m(()=>y(()=>{if(W)throw new D("effect");W=!0,x?.abort(),x=void 0;let F;try{if(B){x=new AbortController;let H=x;$(x.signal).then((z)=>{if(A(z)&&x===H)J.off(z)}).catch((z)=>{if(!R(z))console.error("Async effect error:",z)})}else if(F=$(),A(F))J.off(F)}catch(H){if(!R(H))console.error("Effect callback error:",H)}W=!1},J));return J(),()=>{x?.abort(),J.cleanup()}};var n="Store",T=($)=>{let B=new Set,W=new EventTarget,x=new Map,J=new Map,F=S(0),H=()=>{let Z={};for(let[L,Q]of x)Z[L]=Q.get();return Z},z=(Z,L)=>W.dispatchEvent(new CustomEvent(Z,{detail:L})),K=(Z,L)=>{let Q=e(L);x.set(Z,Q);let G=c(()=>{let X=Q.get();if(X!=null)z("store-change",{[Z]:X})});J.set(Z,G)},N=(Z)=>{x.delete(Z);let L=J.get(Z);if(L)L();J.delete(Z)},j=(Z,L)=>{let Q=u(Z,L);return v(()=>{if(Object.keys(Q.add).length){for(let G in Q.add){let X=Q.add[G];if(X!=null)K(G,X)}z("store-add",Q.add)}if(Object.keys(Q.change).length){for(let G in Q.change){let X=x.get(G),I=Q.change[G];if(X&&I!=null&&a(X,"set"))X.set(I)}z("store-change",Q.change)}if(Object.keys(Q.remove).length){for(let G in Q.remove)N(G);z("store-remove",Q.remove)}F.set(x.size)}),Q.changed};j({},$),setTimeout(()=>{let Z=new CustomEvent("store-add",{detail:$});W.dispatchEvent(Z)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(Z,L){let Q=String(L);switch(L){case"add":return(G,X)=>{if(!x.has(G))K(G,X),M(B),z("store-add",{[G]:X}),F.set(x.size)};case"get":return()=>{return U(B),H()};case"remove":return(G)=>{if(x.has(G))N(G),M(B),z("store-remove",{[G]:C}),F.set(x.size)};case"set":return(G)=>{if(j(H(),G)){if(M(B),C===G)B.clear()}};case"update":return(G)=>{let X=H(),I=G(X);if(j(X,I)){if(M(B),C===I)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 F}if(L===Symbol.toStringTag)return n;if(L===Symbol.iterator)return function*(){for(let[G,X]of x)yield[G,X]};return x.get(Q)},has(Z,L){let Q=String(L);return x.has(Q)||_.includes(Q)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(x.keys())},getOwnPropertyDescriptor(Z,L){let Q=x.get(String(L));return Q?{enumerable:!0,configurable:!0,writable:!0,value:Q}:void 0}})},g=($)=>O($,n);var C=Symbol(),$$=($)=>b($)||t($)||g($);function x$($){if($$($))return $;if(s($))return i($);if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}function e($){if(b($)||g($))return $;if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}var P=($,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 D("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let x=0;x<$.length;x++)if(!P($[x],B[x],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(V($)&&V(B)){let x=Object.keys($),J=Object.keys(B);if(x.length!==J.length)return!1;for(let F of x){if(!(F in B))return!1;if(!P($[F],B[F],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((J,F)=>{let H={},z={},K={},N=Object.keys(J),j=Object.keys(F),_=new Set([...N,...j]);for(let L of _){let Q=L in J,G=L in F;if(!Q&&G){H[L]=F[L];continue}else if(Q&&!G){K[L]=C;continue}let X=J[L],I=F[L];if(!P(X,I,W))z[L]=I}return{changed:Object.keys(H).length>0||Object.keys(z).length>0||Object.keys(K).length>0,add:H,change:z,remove:K}})($,B)};var r="Computed",i=($)=>{let B=new Set,W=C,x,J,F=!0,H=!1,z=!1,K=(G)=>{if(!P(G,W))W=G,H=!0;x=void 0,F=!1},N=()=>{H=C!==W,W=C,x=void 0},j=(G)=>{let X=Y(G);H=!x||X.name!==x.name||X.message!==x.message,W=C,x=X},_=(G)=>(X)=>{if(z=!1,J=void 0,G(X),H)M(B)},Z=m(()=>{if(F=!0,J?.abort(),B.size)M(B);else Z.cleanup()});Z.off(()=>{J?.abort()});let L=()=>y(()=>{if(z)throw new D("computed");if(H=!1,E($)){if(J)return W;J=new AbortController,J.signal.addEventListener("abort",()=>{z=!1,J=void 0,L()},{once:!0})}let G;z=!0;try{G=J?$(J.signal):$()}catch(X){if(R(X))N();else j(X);z=!1;return}if(G instanceof Promise)G.then(_(K),_(j));else if(G==null||C===G)N();else K(G);z=!1},Z);return{[Symbol.toStringTag]:r,get:()=>{if(U(B),w(),F)L();if(x)throw x;return W}}},t=($)=>O($,r),s=($)=>A($)&&$.length<2;function G$($,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)))){let x=$.errors?[...$.errors,Y(W)]:[Y(W)];B.err(x)}else throw W}}function J$($){let B=[],W=!1,x={};for(let[J,F]of Object.entries($))try{let H=F.get();if(H===C)W=!0;else x[J]=H}catch(H){B.push(Y(H))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:x}}export{m as watch,x$ as toSignal,Y as toError,U as subscribe,T as store,S as state,J$ as resolve,y as observe,M as notify,G$ as match,g as isStore,b as isState,$$ as isSignal,A as isFunction,P as isEqual,s as isComputedCallback,t as isComputed,E as isAsyncFunction,R as isAbortError,w as flush,W$ as enqueue,c as effect,u as diff,i as computed,v as batch,C as UNSET,n as TYPE_STORE,o as TYPE_STATE,r as TYPE_COMPUTED,D as CircularDependencyError};
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.15.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,9 +9,13 @@ export {
|
|
|
9
9
|
type ComputedCallback,
|
|
10
10
|
computed,
|
|
11
11
|
isComputed,
|
|
12
|
+
isComputedCallback,
|
|
12
13
|
TYPE_COMPUTED,
|
|
13
14
|
} from './src/computed'
|
|
14
|
-
export { type
|
|
15
|
+
export { type DiffResult, diff, isEqual, type UnknownRecord } from './src/diff'
|
|
16
|
+
export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
|
|
17
|
+
export { type MatchHandlers, match } from './src/match'
|
|
18
|
+
export { type ResolveResult, resolve } from './src/resolve'
|
|
15
19
|
export {
|
|
16
20
|
batch,
|
|
17
21
|
type Cleanup,
|
|
@@ -25,7 +29,6 @@ export {
|
|
|
25
29
|
watch,
|
|
26
30
|
} from './src/scheduler'
|
|
27
31
|
export {
|
|
28
|
-
isComputedCallback,
|
|
29
32
|
isSignal,
|
|
30
33
|
type MaybeSignal,
|
|
31
34
|
type Signal,
|
|
@@ -34,4 +37,20 @@ export {
|
|
|
34
37
|
UNSET,
|
|
35
38
|
} from './src/signal'
|
|
36
39
|
export { isState, type State, state, TYPE_STATE } from './src/state'
|
|
37
|
-
export {
|
|
40
|
+
export {
|
|
41
|
+
isStore,
|
|
42
|
+
type Store,
|
|
43
|
+
type StoreAddEvent,
|
|
44
|
+
type StoreChangeEvent,
|
|
45
|
+
type StoreEventMap,
|
|
46
|
+
type StoreRemoveEvent,
|
|
47
|
+
store,
|
|
48
|
+
TYPE_STORE,
|
|
49
|
+
} from './src/store'
|
|
50
|
+
export {
|
|
51
|
+
CircularDependencyError,
|
|
52
|
+
isAbortError,
|
|
53
|
+
isAsyncFunction,
|
|
54
|
+
isFunction,
|
|
55
|
+
toError,
|
|
56
|
+
} 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.0",
|
|
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
|