@zeix/cause-effect 0.18.0 → 0.18.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/.ai-context.md +14 -3
- package/.github/copilot-instructions.md +15 -5
- package/ARCHITECTURE.md +43 -19
- package/CHANGELOG.md +43 -0
- package/CLAUDE.md +33 -7
- package/GUIDE.md +1 -1
- package/README.md +36 -5
- package/context7.json +4 -0
- package/examples/events-sensor.ts +187 -0
- package/examples/selector-sensor.ts +173 -0
- package/index.dev.js +276 -222
- package/index.js +1 -1
- package/index.ts +4 -2
- package/package.json +2 -2
- package/skills/changelog-keeper/SKILL.md +59 -0
- package/skills/changelog-keeper/agents/openai.yaml +4 -0
- package/src/graph.ts +28 -5
- package/src/nodes/collection.ts +185 -133
- package/src/nodes/list.ts +121 -116
- package/src/nodes/memo.ts +31 -3
- package/src/nodes/sensor.ts +27 -17
- package/src/nodes/state.ts +2 -2
- package/src/nodes/store.ts +71 -72
- package/src/nodes/task.ts +31 -3
- package/test/collection.test.ts +40 -51
- package/test/list.test.ts +192 -0
- package/test/memo.test.ts +194 -0
- package/test/task.test.ts +134 -0
- package/types/index.d.ts +5 -5
- package/types/src/graph.d.ts +12 -2
- package/types/src/nodes/collection.d.ts +12 -7
- package/types/src/nodes/list.d.ts +12 -11
- package/types/src/nodes/memo.d.ts +6 -0
- package/types/src/nodes/sensor.d.ts +15 -9
- package/types/src/nodes/store.d.ts +4 -4
- package/types/src/nodes/task.d.ts +6 -0
- package/COLLECTION_REFACTORING.md +0 -161
package/index.dev.js
CHANGED
|
@@ -415,14 +415,6 @@ function isState(value) {
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
// src/nodes/list.ts
|
|
418
|
-
function keysEqual(a, b) {
|
|
419
|
-
if (a.length !== b.length)
|
|
420
|
-
return false;
|
|
421
|
-
for (let i = 0;i < a.length; i++)
|
|
422
|
-
if (a[i] !== b[i])
|
|
423
|
-
return false;
|
|
424
|
-
return true;
|
|
425
|
-
}
|
|
426
418
|
function isEqual(a, b, visited) {
|
|
427
419
|
if (Object.is(a, b))
|
|
428
420
|
return true;
|
|
@@ -445,10 +437,9 @@ function isEqual(a, b, visited) {
|
|
|
445
437
|
const ba = b;
|
|
446
438
|
if (aa.length !== ba.length)
|
|
447
439
|
return false;
|
|
448
|
-
for (let i = 0;i < aa.length; i++)
|
|
440
|
+
for (let i = 0;i < aa.length; i++)
|
|
449
441
|
if (!isEqual(aa[i], ba[i], visited))
|
|
450
442
|
return false;
|
|
451
|
-
}
|
|
452
443
|
return true;
|
|
453
444
|
}
|
|
454
445
|
if (isRecord(a) && isRecord(b)) {
|
|
@@ -470,62 +461,72 @@ function isEqual(a, b, visited) {
|
|
|
470
461
|
visited.delete(b);
|
|
471
462
|
}
|
|
472
463
|
}
|
|
473
|
-
function
|
|
464
|
+
function keysEqual(a, b) {
|
|
465
|
+
if (a.length !== b.length)
|
|
466
|
+
return false;
|
|
467
|
+
for (let i = 0;i < a.length; i++)
|
|
468
|
+
if (a[i] !== b[i])
|
|
469
|
+
return false;
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
function getKeyGenerator(keyConfig) {
|
|
473
|
+
let keyCounter = 0;
|
|
474
|
+
const contentBased = typeof keyConfig === "function";
|
|
475
|
+
return [
|
|
476
|
+
typeof keyConfig === "string" ? () => `${keyConfig}${keyCounter++}` : contentBased ? (item) => keyConfig(item) || String(keyCounter++) : () => String(keyCounter++),
|
|
477
|
+
contentBased
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
function diffArrays(prev, next, prevKeys, generateKey, contentBased) {
|
|
474
481
|
const visited = new WeakSet;
|
|
475
482
|
const add = {};
|
|
476
483
|
const change = {};
|
|
477
484
|
const remove = {};
|
|
478
|
-
const
|
|
485
|
+
const nextKeys = [];
|
|
479
486
|
let changed = false;
|
|
480
|
-
const
|
|
481
|
-
for (let i = 0;i <
|
|
482
|
-
const key =
|
|
483
|
-
if (key &&
|
|
484
|
-
|
|
487
|
+
const prevByKey = new Map;
|
|
488
|
+
for (let i = 0;i < prev.length; i++) {
|
|
489
|
+
const key = prevKeys[i];
|
|
490
|
+
if (key && prev[i])
|
|
491
|
+
prevByKey.set(key, prev[i]);
|
|
485
492
|
}
|
|
486
493
|
const seenKeys = new Set;
|
|
487
|
-
for (let i = 0;i <
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
494
|
+
for (let i = 0;i < next.length; i++) {
|
|
495
|
+
const val = next[i];
|
|
496
|
+
if (val === undefined)
|
|
490
497
|
continue;
|
|
491
|
-
const key = contentBased ? generateKey(
|
|
498
|
+
const key = contentBased ? generateKey(val) : prevKeys[i] ?? generateKey(val);
|
|
492
499
|
if (seenKeys.has(key))
|
|
493
|
-
throw new DuplicateKeyError(TYPE_LIST, key,
|
|
494
|
-
|
|
500
|
+
throw new DuplicateKeyError(TYPE_LIST, key, val);
|
|
501
|
+
nextKeys.push(key);
|
|
495
502
|
seenKeys.add(key);
|
|
496
|
-
if (!
|
|
497
|
-
add[key] =
|
|
503
|
+
if (!prevByKey.has(key)) {
|
|
504
|
+
add[key] = val;
|
|
505
|
+
changed = true;
|
|
506
|
+
} else if (!isEqual(prevByKey.get(key), val, visited)) {
|
|
507
|
+
change[key] = val;
|
|
498
508
|
changed = true;
|
|
499
|
-
} else {
|
|
500
|
-
const oldValue = oldByKey.get(key);
|
|
501
|
-
if (!isEqual(oldValue, newValue, visited)) {
|
|
502
|
-
change[key] = newValue;
|
|
503
|
-
changed = true;
|
|
504
|
-
}
|
|
505
509
|
}
|
|
506
510
|
}
|
|
507
|
-
for (const [key] of
|
|
511
|
+
for (const [key] of prevByKey) {
|
|
508
512
|
if (!seenKeys.has(key)) {
|
|
509
513
|
remove[key] = null;
|
|
510
514
|
changed = true;
|
|
511
515
|
}
|
|
512
516
|
}
|
|
513
|
-
if (!changed && !keysEqual(
|
|
517
|
+
if (!changed && !keysEqual(prevKeys, nextKeys))
|
|
514
518
|
changed = true;
|
|
515
|
-
return { add, change, remove, newKeys, changed };
|
|
519
|
+
return { add, change, remove, newKeys: nextKeys, changed };
|
|
516
520
|
}
|
|
517
|
-
function createList(
|
|
518
|
-
validateSignalValue(TYPE_LIST,
|
|
521
|
+
function createList(value, options) {
|
|
522
|
+
validateSignalValue(TYPE_LIST, value, Array.isArray);
|
|
519
523
|
const signals = new Map;
|
|
520
524
|
let keys = [];
|
|
521
|
-
|
|
522
|
-
const keyConfig = options?.keyConfig;
|
|
523
|
-
const contentBased = isFunction(keyConfig);
|
|
524
|
-
const generateKey = typeof keyConfig === "string" ? () => `${keyConfig}${keyCounter++}` : contentBased ? (item) => keyConfig(item) : () => String(keyCounter++);
|
|
525
|
+
const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig);
|
|
525
526
|
const buildValue = () => keys.map((key) => signals.get(key)?.get()).filter((v) => v !== undefined);
|
|
526
527
|
const node = {
|
|
527
528
|
fn: buildValue,
|
|
528
|
-
value
|
|
529
|
+
value,
|
|
529
530
|
flags: FLAG_DIRTY,
|
|
530
531
|
sources: null,
|
|
531
532
|
sourcesTail: null,
|
|
@@ -537,34 +538,34 @@ function createList(initialValue, options) {
|
|
|
537
538
|
const toRecord = (array) => {
|
|
538
539
|
const record = {};
|
|
539
540
|
for (let i = 0;i < array.length; i++) {
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
541
|
+
const val = array[i];
|
|
542
|
+
if (val === undefined)
|
|
542
543
|
continue;
|
|
543
544
|
let key = keys[i];
|
|
544
545
|
if (!key) {
|
|
545
|
-
key = generateKey(
|
|
546
|
+
key = generateKey(val);
|
|
546
547
|
keys[i] = key;
|
|
547
548
|
}
|
|
548
|
-
record[key] =
|
|
549
|
+
record[key] = val;
|
|
549
550
|
}
|
|
550
551
|
return record;
|
|
551
552
|
};
|
|
552
553
|
const applyChanges = (changes) => {
|
|
553
554
|
let structural = false;
|
|
554
555
|
for (const key in changes.add) {
|
|
555
|
-
const
|
|
556
|
-
validateSignalValue(`${TYPE_LIST} item for key "${key}"`,
|
|
557
|
-
signals.set(key, createState(
|
|
556
|
+
const val = changes.add[key];
|
|
557
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
558
|
+
signals.set(key, createState(val));
|
|
558
559
|
structural = true;
|
|
559
560
|
}
|
|
560
561
|
if (Object.keys(changes.change).length) {
|
|
561
562
|
batch(() => {
|
|
562
563
|
for (const key in changes.change) {
|
|
563
|
-
const
|
|
564
|
-
validateSignalValue(`${TYPE_LIST} item for key "${key}"`,
|
|
564
|
+
const val = changes.change[key];
|
|
565
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
565
566
|
const signal = signals.get(key);
|
|
566
567
|
if (signal)
|
|
567
|
-
signal.set(
|
|
568
|
+
signal.set(val);
|
|
568
569
|
}
|
|
569
570
|
});
|
|
570
571
|
}
|
|
@@ -581,13 +582,24 @@ function createList(initialValue, options) {
|
|
|
581
582
|
}
|
|
582
583
|
return changes.changed;
|
|
583
584
|
};
|
|
584
|
-
const
|
|
585
|
+
const watched = options?.watched;
|
|
586
|
+
const subscribe = watched ? () => {
|
|
587
|
+
if (activeSink) {
|
|
588
|
+
if (!node.sinks)
|
|
589
|
+
node.stop = watched();
|
|
590
|
+
link(node, activeSink);
|
|
591
|
+
}
|
|
592
|
+
} : () => {
|
|
593
|
+
if (activeSink)
|
|
594
|
+
link(node, activeSink);
|
|
595
|
+
};
|
|
596
|
+
const initRecord = toRecord(value);
|
|
585
597
|
for (const key in initRecord) {
|
|
586
|
-
const
|
|
587
|
-
validateSignalValue(`${TYPE_LIST} item for key "${key}"`,
|
|
588
|
-
signals.set(key, createState(
|
|
598
|
+
const val = initRecord[key];
|
|
599
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
600
|
+
signals.set(key, createState(val));
|
|
589
601
|
}
|
|
590
|
-
node.value =
|
|
602
|
+
node.value = value;
|
|
591
603
|
node.flags = 0;
|
|
592
604
|
const list = {
|
|
593
605
|
[Symbol.toStringTag]: TYPE_LIST,
|
|
@@ -600,19 +612,11 @@ function createList(initialValue, options) {
|
|
|
600
612
|
}
|
|
601
613
|
},
|
|
602
614
|
get length() {
|
|
603
|
-
|
|
604
|
-
if (!node.sinks && options?.watched)
|
|
605
|
-
node.stop = options.watched();
|
|
606
|
-
link(node, activeSink);
|
|
607
|
-
}
|
|
615
|
+
subscribe();
|
|
608
616
|
return keys.length;
|
|
609
617
|
},
|
|
610
618
|
get() {
|
|
611
|
-
|
|
612
|
-
if (!node.sinks && options?.watched)
|
|
613
|
-
node.stop = options.watched();
|
|
614
|
-
link(node, activeSink);
|
|
615
|
-
}
|
|
619
|
+
subscribe();
|
|
616
620
|
if (node.sources) {
|
|
617
621
|
if (node.flags) {
|
|
618
622
|
node.value = untrack(buildValue);
|
|
@@ -625,14 +629,15 @@ function createList(initialValue, options) {
|
|
|
625
629
|
}
|
|
626
630
|
return node.value;
|
|
627
631
|
},
|
|
628
|
-
set(
|
|
629
|
-
const
|
|
630
|
-
const changes = diffArrays(
|
|
632
|
+
set(next) {
|
|
633
|
+
const prev = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
634
|
+
const changes = diffArrays(prev, next, keys, generateKey, contentBased);
|
|
631
635
|
if (changes.changed) {
|
|
632
636
|
keys = changes.newKeys;
|
|
633
637
|
applyChanges(changes);
|
|
634
|
-
propagate(node);
|
|
635
638
|
node.flags |= FLAG_DIRTY;
|
|
639
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
640
|
+
propagate(e.sink);
|
|
636
641
|
if (batchDepth === 0)
|
|
637
642
|
flush();
|
|
638
643
|
}
|
|
@@ -644,11 +649,7 @@ function createList(initialValue, options) {
|
|
|
644
649
|
return signals.get(keys[index]);
|
|
645
650
|
},
|
|
646
651
|
keys() {
|
|
647
|
-
|
|
648
|
-
if (!node.sinks && options?.watched)
|
|
649
|
-
node.stop = options.watched();
|
|
650
|
-
link(node, activeSink);
|
|
651
|
-
}
|
|
652
|
+
subscribe();
|
|
652
653
|
return keys.values();
|
|
653
654
|
},
|
|
654
655
|
byKey(key) {
|
|
@@ -660,18 +661,19 @@ function createList(initialValue, options) {
|
|
|
660
661
|
indexOfKey(key) {
|
|
661
662
|
return keys.indexOf(key);
|
|
662
663
|
},
|
|
663
|
-
add(
|
|
664
|
-
const key = generateKey(
|
|
664
|
+
add(value2) {
|
|
665
|
+
const key = generateKey(value2);
|
|
665
666
|
if (signals.has(key))
|
|
666
|
-
throw new DuplicateKeyError(TYPE_LIST, key,
|
|
667
|
+
throw new DuplicateKeyError(TYPE_LIST, key, value2);
|
|
667
668
|
if (!keys.includes(key))
|
|
668
669
|
keys.push(key);
|
|
669
|
-
validateSignalValue(`${TYPE_LIST} item for key "${key}"`,
|
|
670
|
-
signals.set(key, createState(
|
|
670
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value2);
|
|
671
|
+
signals.set(key, createState(value2));
|
|
671
672
|
node.sources = null;
|
|
672
673
|
node.sourcesTail = null;
|
|
673
|
-
propagate(node);
|
|
674
674
|
node.flags |= FLAG_DIRTY;
|
|
675
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
676
|
+
propagate(e.sink);
|
|
675
677
|
if (batchDepth === 0)
|
|
676
678
|
flush();
|
|
677
679
|
return key;
|
|
@@ -685,8 +687,9 @@ function createList(initialValue, options) {
|
|
|
685
687
|
keys.splice(index, 1);
|
|
686
688
|
node.sources = null;
|
|
687
689
|
node.sourcesTail = null;
|
|
688
|
-
propagate(node);
|
|
689
690
|
node.flags |= FLAG_DIRTY;
|
|
691
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
692
|
+
propagate(e.sink);
|
|
690
693
|
if (batchDepth === 0)
|
|
691
694
|
flush();
|
|
692
695
|
}
|
|
@@ -696,8 +699,9 @@ function createList(initialValue, options) {
|
|
|
696
699
|
const newOrder = entries.map(([key]) => key);
|
|
697
700
|
if (!keysEqual(keys, newOrder)) {
|
|
698
701
|
keys = newOrder;
|
|
699
|
-
propagate(node);
|
|
700
702
|
node.flags |= FLAG_DIRTY;
|
|
703
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
704
|
+
propagate(e.sink);
|
|
701
705
|
if (batchDepth === 0)
|
|
702
706
|
flush();
|
|
703
707
|
}
|
|
@@ -735,8 +739,9 @@ function createList(initialValue, options) {
|
|
|
735
739
|
changed
|
|
736
740
|
});
|
|
737
741
|
keys = newOrder;
|
|
738
|
-
propagate(node);
|
|
739
742
|
node.flags |= FLAG_DIRTY;
|
|
743
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
744
|
+
propagate(e.sink);
|
|
740
745
|
if (batchDepth === 0)
|
|
741
746
|
flush();
|
|
742
747
|
}
|
|
@@ -766,13 +771,30 @@ function createMemo(fn, options) {
|
|
|
766
771
|
sinks: null,
|
|
767
772
|
sinksTail: null,
|
|
768
773
|
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
769
|
-
error: undefined
|
|
774
|
+
error: undefined,
|
|
775
|
+
stop: undefined
|
|
776
|
+
};
|
|
777
|
+
const watched = options?.watched;
|
|
778
|
+
const subscribe = watched ? () => {
|
|
779
|
+
if (activeSink) {
|
|
780
|
+
if (!node.sinks)
|
|
781
|
+
node.stop = watched(() => {
|
|
782
|
+
node.flags |= FLAG_DIRTY;
|
|
783
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
784
|
+
propagate(e.sink);
|
|
785
|
+
if (batchDepth === 0)
|
|
786
|
+
flush();
|
|
787
|
+
});
|
|
788
|
+
link(node, activeSink);
|
|
789
|
+
}
|
|
790
|
+
} : () => {
|
|
791
|
+
if (activeSink)
|
|
792
|
+
link(node, activeSink);
|
|
770
793
|
};
|
|
771
794
|
return {
|
|
772
795
|
[Symbol.toStringTag]: TYPE_MEMO,
|
|
773
796
|
get() {
|
|
774
|
-
|
|
775
|
-
link(node, activeSink);
|
|
797
|
+
subscribe();
|
|
776
798
|
refresh(node);
|
|
777
799
|
if (node.error)
|
|
778
800
|
throw node.error;
|
|
@@ -800,13 +822,30 @@ function createTask(fn, options) {
|
|
|
800
822
|
flags: FLAG_DIRTY,
|
|
801
823
|
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
802
824
|
controller: undefined,
|
|
803
|
-
error: undefined
|
|
825
|
+
error: undefined,
|
|
826
|
+
stop: undefined
|
|
827
|
+
};
|
|
828
|
+
const watched = options?.watched;
|
|
829
|
+
const subscribe = watched ? () => {
|
|
830
|
+
if (activeSink) {
|
|
831
|
+
if (!node.sinks)
|
|
832
|
+
node.stop = watched(() => {
|
|
833
|
+
node.flags |= FLAG_DIRTY;
|
|
834
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
835
|
+
propagate(e.sink);
|
|
836
|
+
if (batchDepth === 0)
|
|
837
|
+
flush();
|
|
838
|
+
});
|
|
839
|
+
link(node, activeSink);
|
|
840
|
+
}
|
|
841
|
+
} : () => {
|
|
842
|
+
if (activeSink)
|
|
843
|
+
link(node, activeSink);
|
|
804
844
|
};
|
|
805
845
|
return {
|
|
806
846
|
[Symbol.toStringTag]: TYPE_TASK,
|
|
807
847
|
get() {
|
|
808
|
-
|
|
809
|
-
link(node, activeSink);
|
|
848
|
+
subscribe();
|
|
810
849
|
refresh(node);
|
|
811
850
|
if (node.error)
|
|
812
851
|
throw node.error;
|
|
@@ -848,19 +887,19 @@ function deriveCollection(source, callback) {
|
|
|
848
887
|
signals.set(key, signal);
|
|
849
888
|
};
|
|
850
889
|
function syncKeys() {
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
if (!keysEqual(
|
|
854
|
-
const
|
|
855
|
-
const
|
|
856
|
-
for (const key of
|
|
857
|
-
if (!
|
|
890
|
+
const nextKeys = Array.from(source.keys());
|
|
891
|
+
const prevKeys = node.value;
|
|
892
|
+
if (!keysEqual(prevKeys, nextKeys)) {
|
|
893
|
+
const a = new Set(prevKeys);
|
|
894
|
+
const b = new Set(nextKeys);
|
|
895
|
+
for (const key of prevKeys)
|
|
896
|
+
if (!b.has(key))
|
|
858
897
|
signals.delete(key);
|
|
859
|
-
for (const key of
|
|
860
|
-
if (!
|
|
898
|
+
for (const key of nextKeys)
|
|
899
|
+
if (!a.has(key))
|
|
861
900
|
addSignal(key);
|
|
862
901
|
}
|
|
863
|
-
return
|
|
902
|
+
return nextKeys;
|
|
864
903
|
}
|
|
865
904
|
const node = {
|
|
866
905
|
fn: syncKeys,
|
|
@@ -945,17 +984,17 @@ function deriveCollection(source, callback) {
|
|
|
945
984
|
};
|
|
946
985
|
return collection;
|
|
947
986
|
}
|
|
948
|
-
function createCollection(
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
951
|
-
validateSignalValue(TYPE_COLLECTION,
|
|
952
|
-
validateCallback(TYPE_COLLECTION,
|
|
987
|
+
function createCollection(watched, options) {
|
|
988
|
+
const value = options?.value ?? [];
|
|
989
|
+
if (value.length)
|
|
990
|
+
validateSignalValue(TYPE_COLLECTION, value, Array.isArray);
|
|
991
|
+
validateCallback(TYPE_COLLECTION, watched, isSyncFunction);
|
|
953
992
|
const signals = new Map;
|
|
954
993
|
const keys = [];
|
|
955
|
-
|
|
956
|
-
const
|
|
957
|
-
const
|
|
958
|
-
const itemFactory = options?.createItem ??
|
|
994
|
+
const itemToKey = new Map;
|
|
995
|
+
const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig);
|
|
996
|
+
const resolveKey = (item) => itemToKey.get(item) ?? (contentBased ? generateKey(item) : undefined);
|
|
997
|
+
const itemFactory = options?.createItem ?? createState;
|
|
959
998
|
function buildValue() {
|
|
960
999
|
const result = [];
|
|
961
1000
|
for (const key of keys) {
|
|
@@ -972,59 +1011,79 @@ function createCollection(start, options) {
|
|
|
972
1011
|
}
|
|
973
1012
|
const node = {
|
|
974
1013
|
fn: buildValue,
|
|
975
|
-
value
|
|
1014
|
+
value,
|
|
976
1015
|
flags: FLAG_DIRTY,
|
|
977
1016
|
sources: null,
|
|
978
1017
|
sourcesTail: null,
|
|
979
1018
|
sinks: null,
|
|
980
1019
|
sinksTail: null,
|
|
981
|
-
equals:
|
|
1020
|
+
equals: SKIP_EQUALITY,
|
|
982
1021
|
error: undefined
|
|
983
1022
|
};
|
|
984
|
-
|
|
985
|
-
if (!changes.changed)
|
|
986
|
-
return;
|
|
987
|
-
let structural = false;
|
|
988
|
-
batch(() => {
|
|
989
|
-
for (const key in changes.add) {
|
|
990
|
-
const value = changes.add[key];
|
|
991
|
-
signals.set(key, itemFactory(key, value));
|
|
992
|
-
if (!keys.includes(key))
|
|
993
|
-
keys.push(key);
|
|
994
|
-
structural = true;
|
|
995
|
-
}
|
|
996
|
-
for (const key in changes.change) {
|
|
997
|
-
const signal = signals.get(key);
|
|
998
|
-
if (signal && isState(signal)) {
|
|
999
|
-
signal.set(changes.change[key]);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
for (const key in changes.remove) {
|
|
1003
|
-
signals.delete(key);
|
|
1004
|
-
const index = keys.indexOf(key);
|
|
1005
|
-
if (index !== -1)
|
|
1006
|
-
keys.splice(index, 1);
|
|
1007
|
-
structural = true;
|
|
1008
|
-
}
|
|
1009
|
-
if (structural) {
|
|
1010
|
-
node.sources = null;
|
|
1011
|
-
node.sourcesTail = null;
|
|
1012
|
-
}
|
|
1013
|
-
node.flags = FLAG_CLEAN;
|
|
1014
|
-
propagate(node);
|
|
1015
|
-
node.flags |= FLAG_DIRTY;
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
for (const item of initialValue) {
|
|
1023
|
+
for (const item of value) {
|
|
1019
1024
|
const key = generateKey(item);
|
|
1020
|
-
signals.set(key, itemFactory(
|
|
1025
|
+
signals.set(key, itemFactory(item));
|
|
1026
|
+
itemToKey.set(item, key);
|
|
1021
1027
|
keys.push(key);
|
|
1022
1028
|
}
|
|
1023
|
-
node.value =
|
|
1029
|
+
node.value = value;
|
|
1024
1030
|
node.flags = FLAG_DIRTY;
|
|
1025
|
-
function
|
|
1026
|
-
if (
|
|
1027
|
-
node.
|
|
1031
|
+
function subscribe() {
|
|
1032
|
+
if (activeSink) {
|
|
1033
|
+
if (!node.sinks)
|
|
1034
|
+
node.stop = watched((changes) => {
|
|
1035
|
+
const { add, change, remove } = changes;
|
|
1036
|
+
if (!add?.length && !change?.length && !remove?.length)
|
|
1037
|
+
return;
|
|
1038
|
+
let structural = false;
|
|
1039
|
+
batch(() => {
|
|
1040
|
+
if (add) {
|
|
1041
|
+
for (const item of add) {
|
|
1042
|
+
const key = generateKey(item);
|
|
1043
|
+
signals.set(key, itemFactory(item));
|
|
1044
|
+
itemToKey.set(item, key);
|
|
1045
|
+
if (!keys.includes(key))
|
|
1046
|
+
keys.push(key);
|
|
1047
|
+
structural = true;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (change) {
|
|
1051
|
+
for (const item of change) {
|
|
1052
|
+
const key = resolveKey(item);
|
|
1053
|
+
if (!key)
|
|
1054
|
+
continue;
|
|
1055
|
+
const signal = signals.get(key);
|
|
1056
|
+
if (signal && isState(signal)) {
|
|
1057
|
+
itemToKey.delete(signal.get());
|
|
1058
|
+
signal.set(item);
|
|
1059
|
+
itemToKey.set(item, key);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (remove) {
|
|
1064
|
+
for (const item of remove) {
|
|
1065
|
+
const key = resolveKey(item);
|
|
1066
|
+
if (!key)
|
|
1067
|
+
continue;
|
|
1068
|
+
itemToKey.delete(item);
|
|
1069
|
+
signals.delete(key);
|
|
1070
|
+
const index = keys.indexOf(key);
|
|
1071
|
+
if (index !== -1)
|
|
1072
|
+
keys.splice(index, 1);
|
|
1073
|
+
structural = true;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (structural) {
|
|
1077
|
+
node.sources = null;
|
|
1078
|
+
node.sourcesTail = null;
|
|
1079
|
+
}
|
|
1080
|
+
node.flags = FLAG_DIRTY;
|
|
1081
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1082
|
+
propagate(e.sink);
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
link(node, activeSink);
|
|
1086
|
+
}
|
|
1028
1087
|
}
|
|
1029
1088
|
const collection = {
|
|
1030
1089
|
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
@@ -1037,24 +1096,15 @@ function createCollection(start, options) {
|
|
|
1037
1096
|
}
|
|
1038
1097
|
},
|
|
1039
1098
|
get length() {
|
|
1040
|
-
|
|
1041
|
-
startWatching();
|
|
1042
|
-
link(node, activeSink);
|
|
1043
|
-
}
|
|
1099
|
+
subscribe();
|
|
1044
1100
|
return keys.length;
|
|
1045
1101
|
},
|
|
1046
1102
|
keys() {
|
|
1047
|
-
|
|
1048
|
-
startWatching();
|
|
1049
|
-
link(node, activeSink);
|
|
1050
|
-
}
|
|
1103
|
+
subscribe();
|
|
1051
1104
|
return keys.values();
|
|
1052
1105
|
},
|
|
1053
1106
|
get() {
|
|
1054
|
-
|
|
1055
|
-
startWatching();
|
|
1056
|
-
link(node, activeSink);
|
|
1057
|
-
}
|
|
1107
|
+
subscribe();
|
|
1058
1108
|
if (node.sources) {
|
|
1059
1109
|
if (node.flags) {
|
|
1060
1110
|
node.value = untrack(buildValue);
|
|
@@ -1159,8 +1209,8 @@ function match(signals, handlers) {
|
|
|
1159
1209
|
}
|
|
1160
1210
|
}
|
|
1161
1211
|
// src/nodes/sensor.ts
|
|
1162
|
-
function createSensor(
|
|
1163
|
-
validateCallback(TYPE_SENSOR,
|
|
1212
|
+
function createSensor(watched, options) {
|
|
1213
|
+
validateCallback(TYPE_SENSOR, watched, isSyncFunction);
|
|
1164
1214
|
if (options?.value !== undefined)
|
|
1165
1215
|
validateSignalValue(TYPE_SENSOR, options.value, options?.guard);
|
|
1166
1216
|
const node = {
|
|
@@ -1176,7 +1226,7 @@ function createSensor(start, options) {
|
|
|
1176
1226
|
get() {
|
|
1177
1227
|
if (activeSink) {
|
|
1178
1228
|
if (!node.sinks)
|
|
1179
|
-
node.stop =
|
|
1229
|
+
node.stop = watched((next) => {
|
|
1180
1230
|
validateSignalValue(TYPE_SENSOR, next, node.guard);
|
|
1181
1231
|
setState(node, next);
|
|
1182
1232
|
});
|
|
@@ -1191,16 +1241,16 @@ function isSensor(value) {
|
|
|
1191
1241
|
return isObjectOfType(value, TYPE_SENSOR);
|
|
1192
1242
|
}
|
|
1193
1243
|
// src/nodes/store.ts
|
|
1194
|
-
function diffRecords(
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
if (!
|
|
1198
|
-
const changed2 = !Object.is(
|
|
1244
|
+
function diffRecords(prev, next) {
|
|
1245
|
+
const prevValid = isRecord(prev) || Array.isArray(prev);
|
|
1246
|
+
const nextValid = isRecord(next) || Array.isArray(next);
|
|
1247
|
+
if (!prevValid || !nextValid) {
|
|
1248
|
+
const changed2 = !Object.is(prev, next);
|
|
1199
1249
|
return {
|
|
1200
1250
|
changed: changed2,
|
|
1201
|
-
add: changed2 &&
|
|
1251
|
+
add: changed2 && nextValid ? next : {},
|
|
1202
1252
|
change: {},
|
|
1203
|
-
remove: changed2 &&
|
|
1253
|
+
remove: changed2 && prevValid ? prev : {}
|
|
1204
1254
|
};
|
|
1205
1255
|
}
|
|
1206
1256
|
const visited = new WeakSet;
|
|
@@ -1208,38 +1258,38 @@ function diffRecords(oldObj, newObj) {
|
|
|
1208
1258
|
const change = {};
|
|
1209
1259
|
const remove = {};
|
|
1210
1260
|
let changed = false;
|
|
1211
|
-
const
|
|
1212
|
-
const
|
|
1213
|
-
for (const key of
|
|
1214
|
-
if (key in
|
|
1215
|
-
if (!isEqual(
|
|
1216
|
-
change[key] =
|
|
1261
|
+
const prevKeys = Object.keys(prev);
|
|
1262
|
+
const nextKeys = Object.keys(next);
|
|
1263
|
+
for (const key of nextKeys) {
|
|
1264
|
+
if (key in prev) {
|
|
1265
|
+
if (!isEqual(prev[key], next[key], visited)) {
|
|
1266
|
+
change[key] = next[key];
|
|
1217
1267
|
changed = true;
|
|
1218
1268
|
}
|
|
1219
1269
|
} else {
|
|
1220
|
-
add[key] =
|
|
1270
|
+
add[key] = next[key];
|
|
1221
1271
|
changed = true;
|
|
1222
1272
|
}
|
|
1223
1273
|
}
|
|
1224
|
-
for (const key of
|
|
1225
|
-
if (!(key in
|
|
1274
|
+
for (const key of prevKeys) {
|
|
1275
|
+
if (!(key in next)) {
|
|
1226
1276
|
remove[key] = undefined;
|
|
1227
1277
|
changed = true;
|
|
1228
1278
|
}
|
|
1229
1279
|
}
|
|
1230
1280
|
return { add, change, remove, changed };
|
|
1231
1281
|
}
|
|
1232
|
-
function createStore(
|
|
1233
|
-
validateSignalValue(TYPE_STORE,
|
|
1282
|
+
function createStore(value, options) {
|
|
1283
|
+
validateSignalValue(TYPE_STORE, value, isRecord);
|
|
1234
1284
|
const signals = new Map;
|
|
1235
|
-
const addSignal = (key,
|
|
1236
|
-
validateSignalValue(`${TYPE_STORE} for key "${key}"`,
|
|
1237
|
-
if (Array.isArray(
|
|
1238
|
-
signals.set(key, createList(
|
|
1239
|
-
else if (isRecord(
|
|
1240
|
-
signals.set(key, createStore(
|
|
1285
|
+
const addSignal = (key, val) => {
|
|
1286
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, val);
|
|
1287
|
+
if (Array.isArray(val))
|
|
1288
|
+
signals.set(key, createList(val));
|
|
1289
|
+
else if (isRecord(val))
|
|
1290
|
+
signals.set(key, createStore(val));
|
|
1241
1291
|
else
|
|
1242
|
-
signals.set(key, createState(
|
|
1292
|
+
signals.set(key, createState(val));
|
|
1243
1293
|
};
|
|
1244
1294
|
const buildValue = () => {
|
|
1245
1295
|
const record = {};
|
|
@@ -1250,7 +1300,7 @@ function createStore(initialValue, options) {
|
|
|
1250
1300
|
};
|
|
1251
1301
|
const node = {
|
|
1252
1302
|
fn: buildValue,
|
|
1253
|
-
value
|
|
1303
|
+
value,
|
|
1254
1304
|
flags: FLAG_DIRTY,
|
|
1255
1305
|
sources: null,
|
|
1256
1306
|
sourcesTail: null,
|
|
@@ -1268,15 +1318,15 @@ function createStore(initialValue, options) {
|
|
|
1268
1318
|
if (Object.keys(changes.change).length) {
|
|
1269
1319
|
batch(() => {
|
|
1270
1320
|
for (const key in changes.change) {
|
|
1271
|
-
const
|
|
1272
|
-
validateSignalValue(`${TYPE_STORE} for key "${key}"`,
|
|
1321
|
+
const val = changes.change[key];
|
|
1322
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, val);
|
|
1273
1323
|
const signal = signals.get(key);
|
|
1274
1324
|
if (signal) {
|
|
1275
|
-
if (isRecord(
|
|
1276
|
-
addSignal(key,
|
|
1325
|
+
if (isRecord(val) !== isStore(signal)) {
|
|
1326
|
+
addSignal(key, val);
|
|
1277
1327
|
structural = true;
|
|
1278
1328
|
} else
|
|
1279
|
-
signal.set(
|
|
1329
|
+
signal.set(val);
|
|
1280
1330
|
}
|
|
1281
1331
|
}
|
|
1282
1332
|
});
|
|
@@ -1291,8 +1341,19 @@ function createStore(initialValue, options) {
|
|
|
1291
1341
|
}
|
|
1292
1342
|
return changes.changed;
|
|
1293
1343
|
};
|
|
1294
|
-
|
|
1295
|
-
|
|
1344
|
+
const watched = options?.watched;
|
|
1345
|
+
const subscribe = watched ? () => {
|
|
1346
|
+
if (activeSink) {
|
|
1347
|
+
if (!node.sinks)
|
|
1348
|
+
node.stop = watched();
|
|
1349
|
+
link(node, activeSink);
|
|
1350
|
+
}
|
|
1351
|
+
} : () => {
|
|
1352
|
+
if (activeSink)
|
|
1353
|
+
link(node, activeSink);
|
|
1354
|
+
};
|
|
1355
|
+
for (const key of Object.keys(value))
|
|
1356
|
+
addSignal(key, value[key]);
|
|
1296
1357
|
const store = {
|
|
1297
1358
|
[Symbol.toStringTag]: TYPE_STORE,
|
|
1298
1359
|
[Symbol.isConcatSpreadable]: false,
|
|
@@ -1304,22 +1365,14 @@ function createStore(initialValue, options) {
|
|
|
1304
1365
|
}
|
|
1305
1366
|
},
|
|
1306
1367
|
keys() {
|
|
1307
|
-
|
|
1308
|
-
if (!node.sinks && options?.watched)
|
|
1309
|
-
node.stop = options.watched();
|
|
1310
|
-
link(node, activeSink);
|
|
1311
|
-
}
|
|
1368
|
+
subscribe();
|
|
1312
1369
|
return signals.keys();
|
|
1313
1370
|
},
|
|
1314
1371
|
byKey(key) {
|
|
1315
1372
|
return signals.get(key);
|
|
1316
1373
|
},
|
|
1317
1374
|
get() {
|
|
1318
|
-
|
|
1319
|
-
if (!node.sinks && options?.watched)
|
|
1320
|
-
node.stop = options.watched();
|
|
1321
|
-
link(node, activeSink);
|
|
1322
|
-
}
|
|
1375
|
+
subscribe();
|
|
1323
1376
|
if (node.sources) {
|
|
1324
1377
|
if (node.flags) {
|
|
1325
1378
|
node.value = untrack(buildValue);
|
|
@@ -1332,12 +1385,13 @@ function createStore(initialValue, options) {
|
|
|
1332
1385
|
}
|
|
1333
1386
|
return node.value;
|
|
1334
1387
|
},
|
|
1335
|
-
set(
|
|
1336
|
-
const
|
|
1337
|
-
const changes = diffRecords(
|
|
1388
|
+
set(next) {
|
|
1389
|
+
const prev = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
1390
|
+
const changes = diffRecords(prev, next);
|
|
1338
1391
|
if (applyChanges(changes)) {
|
|
1339
|
-
propagate(node);
|
|
1340
1392
|
node.flags |= FLAG_DIRTY;
|
|
1393
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1394
|
+
propagate(e.sink);
|
|
1341
1395
|
if (batchDepth === 0)
|
|
1342
1396
|
flush();
|
|
1343
1397
|
}
|
|
@@ -1345,14 +1399,15 @@ function createStore(initialValue, options) {
|
|
|
1345
1399
|
update(fn) {
|
|
1346
1400
|
store.set(fn(store.get()));
|
|
1347
1401
|
},
|
|
1348
|
-
add(key,
|
|
1402
|
+
add(key, value2) {
|
|
1349
1403
|
if (signals.has(key))
|
|
1350
|
-
throw new DuplicateKeyError(TYPE_STORE, key,
|
|
1351
|
-
addSignal(key,
|
|
1404
|
+
throw new DuplicateKeyError(TYPE_STORE, key, value2);
|
|
1405
|
+
addSignal(key, value2);
|
|
1352
1406
|
node.sources = null;
|
|
1353
1407
|
node.sourcesTail = null;
|
|
1354
|
-
propagate(node);
|
|
1355
1408
|
node.flags |= FLAG_DIRTY;
|
|
1409
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1410
|
+
propagate(e.sink);
|
|
1356
1411
|
if (batchDepth === 0)
|
|
1357
1412
|
flush();
|
|
1358
1413
|
return key;
|
|
@@ -1362,8 +1417,9 @@ function createStore(initialValue, options) {
|
|
|
1362
1417
|
if (ok) {
|
|
1363
1418
|
node.sources = null;
|
|
1364
1419
|
node.sourcesTail = null;
|
|
1365
|
-
propagate(node);
|
|
1366
1420
|
node.flags |= FLAG_DIRTY;
|
|
1421
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1422
|
+
propagate(e.sink);
|
|
1367
1423
|
if (batchDepth === 0)
|
|
1368
1424
|
flush();
|
|
1369
1425
|
}
|
|
@@ -1371,10 +1427,8 @@ function createStore(initialValue, options) {
|
|
|
1371
1427
|
};
|
|
1372
1428
|
return new Proxy(store, {
|
|
1373
1429
|
get(target, prop) {
|
|
1374
|
-
if (prop in target)
|
|
1375
|
-
|
|
1376
|
-
return isFunction(value) ? value.bind(target) : value;
|
|
1377
|
-
}
|
|
1430
|
+
if (prop in target)
|
|
1431
|
+
return Reflect.get(target, prop);
|
|
1378
1432
|
if (typeof prop !== "symbol")
|
|
1379
1433
|
return target.byKey(prop);
|
|
1380
1434
|
},
|