@expressive/mvc 1.0.0-beta.20 → 1.0.0-beta.21
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/lib/index.js +246 -243
- package/package.json +1 -1
- package/types/index.d.ts +0 -1
- package/types/instructions.d.ts +33 -16
- package/types/model.d.ts +53 -56
package/lib/index.js
CHANGED
|
@@ -313,18 +313,17 @@ function eventsFor(prefix) {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
const CONTROL = Symbol("control");
|
|
316
|
-
const CREATE = Symbol("start");
|
|
317
316
|
const LOCAL = Symbol("local");
|
|
318
317
|
const STATE = Symbol("state");
|
|
319
|
-
const Pending = new Map();
|
|
320
318
|
function manage(src) {
|
|
321
319
|
return src[CONTROL];
|
|
322
320
|
}
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
const Pending = new Map();
|
|
322
|
+
function apply(fn, label) {
|
|
323
|
+
const name = label || fn.name || "pending";
|
|
325
324
|
const placeholder = Symbol(`${name} instruction`);
|
|
326
|
-
|
|
327
|
-
let output =
|
|
325
|
+
function setup(key) {
|
|
326
|
+
let output = fn.call(this, key, this);
|
|
328
327
|
if (typeof output == "function") {
|
|
329
328
|
const getter = output;
|
|
330
329
|
output = Object.assign(Object.assign({}, getOwnPropertyDescriptor(this.subject, key)), { get() {
|
|
@@ -333,7 +332,8 @@ function set(instruction, label) {
|
|
|
333
332
|
}
|
|
334
333
|
if (output)
|
|
335
334
|
defineProperty(this.subject, key, output);
|
|
336
|
-
}
|
|
335
|
+
}
|
|
336
|
+
Pending.set(placeholder, setup);
|
|
337
337
|
return placeholder;
|
|
338
338
|
}
|
|
339
339
|
class Controller {
|
|
@@ -345,14 +345,10 @@ class Controller {
|
|
|
345
345
|
this.waiting = [];
|
|
346
346
|
}
|
|
347
347
|
static setup(onto) {
|
|
348
|
-
const create = onto[CREATE];
|
|
349
348
|
const control = new this(onto);
|
|
350
|
-
const after = create && create.call(onto, control);
|
|
351
349
|
defineLazy(onto, CONTROL, () => {
|
|
352
350
|
onto[STATE] = control.state;
|
|
353
351
|
control.start();
|
|
354
|
-
if (after)
|
|
355
|
-
after();
|
|
356
352
|
if (onto.didCreate)
|
|
357
353
|
onto.didCreate();
|
|
358
354
|
return control;
|
|
@@ -457,42 +453,44 @@ function keys(from, using) {
|
|
|
457
453
|
return from.select(using);
|
|
458
454
|
}
|
|
459
455
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
456
|
+
function child(from, name) {
|
|
457
|
+
return apply(function child(key) {
|
|
458
|
+
const proxyCache = new WeakMap();
|
|
459
|
+
const getCurrent = from.call(this, key);
|
|
460
|
+
function subscribe(sub) {
|
|
461
|
+
let child;
|
|
462
|
+
function start() {
|
|
463
|
+
const instance = getCurrent();
|
|
464
|
+
if (instance) {
|
|
465
|
+
child = new Subscriber(instance, sub.onUpdate);
|
|
466
|
+
if (sub.active)
|
|
467
|
+
child.commit();
|
|
468
|
+
sub.dependant.add(child);
|
|
469
|
+
proxyCache.set(sub, child.proxy);
|
|
470
|
+
}
|
|
473
471
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
472
|
+
function refresh() {
|
|
473
|
+
if (child) {
|
|
474
|
+
child.release();
|
|
475
|
+
sub.dependant.delete(child);
|
|
476
|
+
proxyCache.set(sub, undefined);
|
|
477
|
+
child = undefined;
|
|
478
|
+
}
|
|
479
|
+
start();
|
|
480
|
+
sub.onUpdate();
|
|
481
481
|
}
|
|
482
482
|
start();
|
|
483
|
-
sub.
|
|
483
|
+
sub.follow(key, refresh);
|
|
484
484
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
};
|
|
495
|
-
}, name || from.name);
|
|
485
|
+
return (local) => {
|
|
486
|
+
if (!local)
|
|
487
|
+
return getCurrent();
|
|
488
|
+
if (!proxyCache.has(local))
|
|
489
|
+
subscribe(local);
|
|
490
|
+
return proxyCache.get(local);
|
|
491
|
+
};
|
|
492
|
+
}, name || from.name);
|
|
493
|
+
}
|
|
496
494
|
|
|
497
495
|
const Oops$7 = issues({
|
|
498
496
|
HasPropertyUndefined: (control, property) => `${control}.${property} is marked as required for this render.`
|
|
@@ -556,6 +554,56 @@ function useModel(Type, args, callback) {
|
|
|
556
554
|
return hook.proxy;
|
|
557
555
|
}
|
|
558
556
|
|
|
557
|
+
const Oops$6 = issues({
|
|
558
|
+
CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
|
|
559
|
+
AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
|
|
560
|
+
});
|
|
561
|
+
const PendingContext = new WeakMap();
|
|
562
|
+
const ContextWasUsed = new WeakMap();
|
|
563
|
+
function tap(type, argument) {
|
|
564
|
+
return child(function tap(key) {
|
|
565
|
+
return pendingAccess(this.subject, type, key, argument);
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function pendingAccess(from, type, key, argument) {
|
|
569
|
+
if ("current" in type)
|
|
570
|
+
return () => type.current;
|
|
571
|
+
if ("current" in from.constructor)
|
|
572
|
+
throw Oops$6.CantAttachGlobal(from, type.name);
|
|
573
|
+
let instance;
|
|
574
|
+
getPending(from).push(context => {
|
|
575
|
+
instance = context.get(type);
|
|
576
|
+
if (typeof argument == "function") {
|
|
577
|
+
if (argument(instance) === false)
|
|
578
|
+
instance = undefined;
|
|
579
|
+
}
|
|
580
|
+
else if (!instance && argument)
|
|
581
|
+
throw Oops$6.AmbientRequired(type.name, from, key);
|
|
582
|
+
});
|
|
583
|
+
return () => instance;
|
|
584
|
+
}
|
|
585
|
+
function usePeerContext(subject) {
|
|
586
|
+
if (ContextWasUsed.has(subject)) {
|
|
587
|
+
if (ContextWasUsed.get(subject))
|
|
588
|
+
useLookup();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const pending = PendingContext.get(subject);
|
|
592
|
+
if (pending) {
|
|
593
|
+
const local = useLookup();
|
|
594
|
+
for (const init of pending)
|
|
595
|
+
init(local);
|
|
596
|
+
PendingContext.delete(subject);
|
|
597
|
+
}
|
|
598
|
+
ContextWasUsed.set(subject, !!pending);
|
|
599
|
+
}
|
|
600
|
+
function getPending(subject) {
|
|
601
|
+
let pending = PendingContext.get(subject);
|
|
602
|
+
if (!pending)
|
|
603
|
+
PendingContext.set(subject, pending = []);
|
|
604
|
+
return pending;
|
|
605
|
+
}
|
|
606
|
+
|
|
559
607
|
class Lookup {
|
|
560
608
|
constructor() {
|
|
561
609
|
this.table = new Map();
|
|
@@ -611,7 +659,7 @@ class Lookup {
|
|
|
611
659
|
}
|
|
612
660
|
}
|
|
613
661
|
|
|
614
|
-
const Oops$
|
|
662
|
+
const Oops$5 = issues({
|
|
615
663
|
NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`,
|
|
616
664
|
NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`,
|
|
617
665
|
BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
|
|
@@ -621,7 +669,7 @@ const useLookup = () => react.useContext(LookupContext);
|
|
|
621
669
|
function useFromContext(Type, strict) {
|
|
622
670
|
const instance = useLookup().get(Type);
|
|
623
671
|
if (!instance && strict)
|
|
624
|
-
throw Oops$
|
|
672
|
+
throw Oops$5.NothingInContext(Type.name);
|
|
625
673
|
return instance;
|
|
626
674
|
}
|
|
627
675
|
function Consumer(props) {
|
|
@@ -632,13 +680,13 @@ function Consumer(props) {
|
|
|
632
680
|
if (typeof callback == "function")
|
|
633
681
|
callback(Control.get(!!has));
|
|
634
682
|
else
|
|
635
|
-
throw Oops$
|
|
683
|
+
throw Oops$5.BadConsumerProps();
|
|
636
684
|
return null;
|
|
637
685
|
}
|
|
638
686
|
function useNewContext(from, inject) {
|
|
639
687
|
return react.useMemo(() => {
|
|
640
688
|
if (!inject)
|
|
641
|
-
throw Oops$
|
|
689
|
+
throw Oops$5.NoProviderType();
|
|
642
690
|
const context = from.push(inject);
|
|
643
691
|
for (const instance of context.local)
|
|
644
692
|
for (const apply of getPending(instance))
|
|
@@ -681,63 +729,74 @@ function Provider(props) {
|
|
|
681
729
|
: render);
|
|
682
730
|
}
|
|
683
731
|
|
|
684
|
-
const Oops$5 = issues({
|
|
685
|
-
CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
|
|
686
|
-
AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
|
|
687
|
-
});
|
|
688
|
-
const PendingContext = new WeakMap();
|
|
689
|
-
const ContextWasUsed = new WeakMap();
|
|
690
|
-
const tap = (type, argument) => attach(function tap(key) {
|
|
691
|
-
return pendingAccess(this.subject, type, key, argument);
|
|
692
|
-
});
|
|
693
|
-
function pendingAccess(from, type, key, argument) {
|
|
694
|
-
if ("current" in type)
|
|
695
|
-
return () => type.current;
|
|
696
|
-
if ("current" in from.constructor)
|
|
697
|
-
throw Oops$5.CantAttachGlobal(from, type.name);
|
|
698
|
-
let instance;
|
|
699
|
-
getPending(from).push(context => {
|
|
700
|
-
instance = context.get(type);
|
|
701
|
-
if (typeof argument == "function") {
|
|
702
|
-
if (argument(instance) === false)
|
|
703
|
-
instance = undefined;
|
|
704
|
-
}
|
|
705
|
-
else if (!instance && argument)
|
|
706
|
-
throw Oops$5.AmbientRequired(type.name, from, key);
|
|
707
|
-
});
|
|
708
|
-
return () => instance;
|
|
709
|
-
}
|
|
710
|
-
function usePeerContext(subject) {
|
|
711
|
-
if (ContextWasUsed.has(subject)) {
|
|
712
|
-
if (ContextWasUsed.get(subject))
|
|
713
|
-
useLookup();
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
const pending = PendingContext.get(subject);
|
|
717
|
-
if (pending) {
|
|
718
|
-
const local = useLookup();
|
|
719
|
-
for (const init of pending)
|
|
720
|
-
init(local);
|
|
721
|
-
PendingContext.delete(subject);
|
|
722
|
-
}
|
|
723
|
-
ContextWasUsed.set(subject, !!pending);
|
|
724
|
-
}
|
|
725
|
-
function getPending(subject) {
|
|
726
|
-
let pending = PendingContext.get(subject);
|
|
727
|
-
if (!pending)
|
|
728
|
-
PendingContext.set(subject, pending = []);
|
|
729
|
-
return pending;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
732
|
const Oops$4 = issues({
|
|
733
733
|
StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
|
|
734
734
|
NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
|
|
735
735
|
});
|
|
736
|
-
|
|
736
|
+
const useComponentLifecycle = lifecycle("component");
|
|
737
|
+
class Model {
|
|
737
738
|
constructor() {
|
|
738
|
-
Controller.setup(this);
|
|
739
|
+
const control = Controller.setup(this);
|
|
739
740
|
define(this, "get", this);
|
|
740
741
|
define(this, "set", this);
|
|
742
|
+
defer(control, "on");
|
|
743
|
+
defer(control, "effect");
|
|
744
|
+
control.include(() => {
|
|
745
|
+
delete this.on;
|
|
746
|
+
delete this.effect;
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
tap(path, expect) {
|
|
750
|
+
const proxy = useWatcher(this, path, expect);
|
|
751
|
+
this.update("willRender", true);
|
|
752
|
+
return proxy;
|
|
753
|
+
}
|
|
754
|
+
tag(id) {
|
|
755
|
+
return useSubscriber(this, id);
|
|
756
|
+
}
|
|
757
|
+
on(subset, handler, squash, once) {
|
|
758
|
+
const control = manage(this);
|
|
759
|
+
const set = keys(control, subset);
|
|
760
|
+
const batch = {};
|
|
761
|
+
const remove = control.addListener(batch);
|
|
762
|
+
const callback = squash
|
|
763
|
+
? handler.bind(this)
|
|
764
|
+
: (frame) => {
|
|
765
|
+
for (const key of frame)
|
|
766
|
+
if (set.includes(key))
|
|
767
|
+
handler.call(this, control.state[key], key);
|
|
768
|
+
};
|
|
769
|
+
const handle = once
|
|
770
|
+
? (k) => { remove(); callback(k); }
|
|
771
|
+
: callback;
|
|
772
|
+
for (const key of set)
|
|
773
|
+
batch[key] = handle;
|
|
774
|
+
ensure(control, set);
|
|
775
|
+
return remove;
|
|
776
|
+
}
|
|
777
|
+
once(select, callback, squash) {
|
|
778
|
+
if (callback)
|
|
779
|
+
return this.on(select, callback, squash, true);
|
|
780
|
+
else
|
|
781
|
+
return new Promise(resolve => {
|
|
782
|
+
this.on(select, resolve, true, true);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
effect(callback, select) {
|
|
786
|
+
const control = manage(this);
|
|
787
|
+
let target = this;
|
|
788
|
+
const effect = createEffect(callback);
|
|
789
|
+
const invoke = () => effect.call(target, target);
|
|
790
|
+
if (select) {
|
|
791
|
+
invoke();
|
|
792
|
+
return this.on(select, invoke, true);
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
const sub = new Subscriber(control, invoke);
|
|
796
|
+
target = sub.proxy;
|
|
797
|
+
invoke();
|
|
798
|
+
return sub.commit();
|
|
799
|
+
}
|
|
741
800
|
}
|
|
742
801
|
import(from, subset) {
|
|
743
802
|
for (const key of keys(manage(this), subset))
|
|
@@ -788,11 +847,6 @@ class State {
|
|
|
788
847
|
toString() {
|
|
789
848
|
return this.constructor.name;
|
|
790
849
|
}
|
|
791
|
-
tap(path, expect) {
|
|
792
|
-
const proxy = useWatcher(this, path, expect);
|
|
793
|
-
this.update("willRender", true);
|
|
794
|
-
return proxy;
|
|
795
|
-
}
|
|
796
850
|
static create(...args) {
|
|
797
851
|
const instance = new this(...args);
|
|
798
852
|
manage(instance);
|
|
@@ -803,19 +857,6 @@ class State {
|
|
|
803
857
|
instance.update("willRender", true);
|
|
804
858
|
return instance;
|
|
805
859
|
}
|
|
806
|
-
static use(args, callback) {
|
|
807
|
-
return useModel(this, args, callback);
|
|
808
|
-
}
|
|
809
|
-
static uses(props, only) {
|
|
810
|
-
return this.use([], instance => {
|
|
811
|
-
instance.import(props, only);
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
static using(props, only) {
|
|
815
|
-
const instance = this.use([]);
|
|
816
|
-
instance.import(props, only);
|
|
817
|
-
return instance;
|
|
818
|
-
}
|
|
819
860
|
static find(strict) {
|
|
820
861
|
return useFromContext(this, strict);
|
|
821
862
|
}
|
|
@@ -830,72 +871,8 @@ class State {
|
|
|
830
871
|
static tap(key, expect) {
|
|
831
872
|
return this.find(true).tap(key, expect);
|
|
832
873
|
}
|
|
833
|
-
static
|
|
834
|
-
return (
|
|
835
|
-
maybe.prototype instanceof this);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
State.CONTROL = CONTROL;
|
|
839
|
-
State.STATE = STATE;
|
|
840
|
-
State.INIT = CREATE;
|
|
841
|
-
State.LOCAL = LOCAL;
|
|
842
|
-
|
|
843
|
-
const useComponentLifecycle = lifecycle("component");
|
|
844
|
-
class Model extends State {
|
|
845
|
-
[CREATE](using) {
|
|
846
|
-
defer(using, "on");
|
|
847
|
-
defer(using, "effect");
|
|
848
|
-
return () => {
|
|
849
|
-
delete this.on;
|
|
850
|
-
delete this.effect;
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
on(subset, handler, squash, once) {
|
|
854
|
-
const control = manage(this);
|
|
855
|
-
const set = keys(control, subset);
|
|
856
|
-
const batch = {};
|
|
857
|
-
const remove = control.addListener(batch);
|
|
858
|
-
const callback = squash
|
|
859
|
-
? handler.bind(this)
|
|
860
|
-
: (frame) => {
|
|
861
|
-
for (const key of frame)
|
|
862
|
-
if (set.includes(key))
|
|
863
|
-
handler.call(this, control.state[key], key);
|
|
864
|
-
};
|
|
865
|
-
const handle = once
|
|
866
|
-
? (k) => { remove(); callback(k); }
|
|
867
|
-
: callback;
|
|
868
|
-
for (const key of set)
|
|
869
|
-
batch[key] = handle;
|
|
870
|
-
ensure(control, set);
|
|
871
|
-
return remove;
|
|
872
|
-
}
|
|
873
|
-
once(select, callback, squash) {
|
|
874
|
-
if (callback)
|
|
875
|
-
return this.on(select, callback, squash, true);
|
|
876
|
-
else
|
|
877
|
-
return new Promise(resolve => {
|
|
878
|
-
this.on(select, resolve, true, true);
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
effect(callback, select) {
|
|
882
|
-
const control = manage(this);
|
|
883
|
-
let target = this;
|
|
884
|
-
const effect = createEffect(callback);
|
|
885
|
-
const invoke = () => effect.call(target, target);
|
|
886
|
-
if (select) {
|
|
887
|
-
invoke();
|
|
888
|
-
return this.on(select, invoke, true);
|
|
889
|
-
}
|
|
890
|
-
else {
|
|
891
|
-
const sub = new Subscriber(control, invoke);
|
|
892
|
-
target = sub.proxy;
|
|
893
|
-
invoke();
|
|
894
|
-
return sub.commit();
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
tag(id) {
|
|
898
|
-
return useSubscriber(this, id);
|
|
874
|
+
static tag(id) {
|
|
875
|
+
return this.find(true).tag(id);
|
|
899
876
|
}
|
|
900
877
|
static use(args, callback) {
|
|
901
878
|
const instance = useModel(this, args, callback);
|
|
@@ -903,13 +880,27 @@ class Model extends State {
|
|
|
903
880
|
usePeerContext(instance.get);
|
|
904
881
|
return instance;
|
|
905
882
|
}
|
|
906
|
-
static
|
|
907
|
-
return this.
|
|
883
|
+
static uses(props, only) {
|
|
884
|
+
return this.use([], instance => {
|
|
885
|
+
instance.import(props, only);
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
static using(props, only) {
|
|
889
|
+
const instance = this.use([]);
|
|
890
|
+
instance.import(props, only);
|
|
891
|
+
return instance;
|
|
908
892
|
}
|
|
909
893
|
static meta(path) {
|
|
910
894
|
return useWatcher(this, path);
|
|
911
895
|
}
|
|
896
|
+
static isTypeof(maybe) {
|
|
897
|
+
return (typeof maybe == "function" &&
|
|
898
|
+
maybe.prototype instanceof this);
|
|
899
|
+
}
|
|
912
900
|
}
|
|
901
|
+
Model.CONTROL = CONTROL;
|
|
902
|
+
Model.STATE = STATE;
|
|
903
|
+
Model.LOCAL = LOCAL;
|
|
913
904
|
defineLazy(Model, CONTROL, function () {
|
|
914
905
|
return new Controller(this).start();
|
|
915
906
|
});
|
|
@@ -939,73 +930,86 @@ function bootstrap(object) {
|
|
|
939
930
|
defineProperty(control, key, breakdown[key]);
|
|
940
931
|
return control;
|
|
941
932
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
next
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
933
|
+
function use(input, argument) {
|
|
934
|
+
return child(function use(key) {
|
|
935
|
+
let instance;
|
|
936
|
+
const update = (next) => {
|
|
937
|
+
instance =
|
|
938
|
+
next instanceof Model ? next :
|
|
939
|
+
next && bootstrap(next);
|
|
940
|
+
if (instance) {
|
|
941
|
+
Parent.set(instance, this.subject);
|
|
942
|
+
manage(instance);
|
|
943
|
+
}
|
|
944
|
+
if (typeof argument == "function")
|
|
945
|
+
argument(instance);
|
|
946
|
+
else if (!instance && argument !== false)
|
|
947
|
+
throw Oops$3.UndefinedNotAllowed(key);
|
|
948
|
+
};
|
|
949
|
+
if (input) {
|
|
950
|
+
instance =
|
|
951
|
+
Model.isTypeof(input) ? new input() :
|
|
952
|
+
input instanceof Model ? input :
|
|
953
|
+
typeof input === "function" ? input() :
|
|
954
|
+
typeof input === "object" ? bootstrap(input) :
|
|
955
|
+
Oops$3.BadArgument(typeof input).throw();
|
|
956
|
+
if (instance)
|
|
957
|
+
update(instance);
|
|
951
958
|
}
|
|
952
|
-
|
|
953
|
-
argument
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
const parent = (Expects, required) => set(function parent() {
|
|
973
|
-
const child = this.subject;
|
|
974
|
-
const expected = Expects.name;
|
|
975
|
-
const value = Parent.get(this.subject);
|
|
976
|
-
if (!value) {
|
|
977
|
-
if (required)
|
|
978
|
-
throw Oops$3.ParentRequired(expected, child);
|
|
979
|
-
}
|
|
980
|
-
else if (!(value instanceof Expects))
|
|
981
|
-
throw Oops$3.UnexpectedParent(expected, child, value);
|
|
982
|
-
return { value };
|
|
983
|
-
});
|
|
959
|
+
else
|
|
960
|
+
argument = false;
|
|
961
|
+
this.manage(key, instance, update);
|
|
962
|
+
return () => instance;
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
function parent(Expects, required) {
|
|
966
|
+
return apply(function parent() {
|
|
967
|
+
const child = this.subject;
|
|
968
|
+
const expected = Expects.name;
|
|
969
|
+
const value = Parent.get(this.subject);
|
|
970
|
+
if (!value) {
|
|
971
|
+
if (required)
|
|
972
|
+
throw Oops$3.ParentRequired(expected, child);
|
|
973
|
+
}
|
|
974
|
+
else if (!(value instanceof Expects))
|
|
975
|
+
throw Oops$3.UnexpectedParent(expected, child, value);
|
|
976
|
+
return { value };
|
|
977
|
+
});
|
|
978
|
+
}
|
|
984
979
|
|
|
985
980
|
const Oops$2 = issues({
|
|
986
981
|
DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
|
|
987
982
|
BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
|
|
988
983
|
PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
|
|
989
984
|
});
|
|
990
|
-
function
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
985
|
+
function createRef(key, cb) {
|
|
986
|
+
const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
|
|
987
|
+
defineProperty(refObjectFunction, "current", {
|
|
988
|
+
set: refObjectFunction,
|
|
989
|
+
get: () => this.state[key]
|
|
990
|
+
});
|
|
991
|
+
return refObjectFunction;
|
|
992
|
+
}
|
|
993
|
+
function ref(arg) {
|
|
994
|
+
return apply(function ref(key) {
|
|
995
|
+
let value = {};
|
|
996
|
+
if (typeof arg == "object") {
|
|
997
|
+
const source = manage(arg);
|
|
998
|
+
for (const key in source.state)
|
|
999
|
+
defineLazy(value, key, createRef.bind(source, key));
|
|
1000
|
+
}
|
|
1001
|
+
else
|
|
1002
|
+
value = createRef.call(this, key, arg);
|
|
1003
|
+
return { value };
|
|
1000
1004
|
});
|
|
1001
1005
|
}
|
|
1002
1006
|
function on(initial, cb) {
|
|
1003
|
-
return
|
|
1007
|
+
return apply(function on(key) {
|
|
1004
1008
|
this.manage(key, initial, cb && createValueEffect(cb));
|
|
1005
1009
|
});
|
|
1006
1010
|
}
|
|
1007
1011
|
function memo(factory, defer) {
|
|
1008
|
-
return
|
|
1012
|
+
return apply(function memo(key) {
|
|
1009
1013
|
const source = this.subject;
|
|
1010
1014
|
const get = () => factory.call(source);
|
|
1011
1015
|
if (defer)
|
|
@@ -1015,7 +1019,7 @@ function memo(factory, defer) {
|
|
|
1015
1019
|
});
|
|
1016
1020
|
}
|
|
1017
1021
|
function lazy(value) {
|
|
1018
|
-
return
|
|
1022
|
+
return apply(function lazy(key) {
|
|
1019
1023
|
const source = this.subject;
|
|
1020
1024
|
source[key] = value;
|
|
1021
1025
|
defineProperty(this.state, key, {
|
|
@@ -1024,7 +1028,7 @@ function lazy(value) {
|
|
|
1024
1028
|
});
|
|
1025
1029
|
}
|
|
1026
1030
|
function act(task) {
|
|
1027
|
-
return
|
|
1031
|
+
return apply(function act(key) {
|
|
1028
1032
|
let pending = false;
|
|
1029
1033
|
const invoke = (...args) => {
|
|
1030
1034
|
if (pending)
|
|
@@ -1049,7 +1053,7 @@ function act(task) {
|
|
|
1049
1053
|
});
|
|
1050
1054
|
}
|
|
1051
1055
|
function from(source, setter, getter) {
|
|
1052
|
-
return
|
|
1056
|
+
return apply(function from(key) {
|
|
1053
1057
|
const { subject } = this;
|
|
1054
1058
|
let getSource;
|
|
1055
1059
|
// Easy mistake, using a peer, will always be unresolved.
|
|
@@ -1101,7 +1105,7 @@ function pending(source, compute) {
|
|
|
1101
1105
|
});
|
|
1102
1106
|
}
|
|
1103
1107
|
function suspendForAsync(source) {
|
|
1104
|
-
return
|
|
1108
|
+
return apply(function suspense(key) {
|
|
1105
1109
|
let waiting = true;
|
|
1106
1110
|
let output;
|
|
1107
1111
|
let error;
|
|
@@ -1170,9 +1174,9 @@ exports.Consumer = Consumer;
|
|
|
1170
1174
|
exports.Model = Model;
|
|
1171
1175
|
exports.Provider = Provider;
|
|
1172
1176
|
exports.Singleton = Singleton;
|
|
1173
|
-
exports.State = State;
|
|
1174
1177
|
exports.act = act;
|
|
1175
|
-
exports.
|
|
1178
|
+
exports.apply = apply;
|
|
1179
|
+
exports.child = child;
|
|
1176
1180
|
exports["default"] = Model;
|
|
1177
1181
|
exports.from = from;
|
|
1178
1182
|
exports.lazy = lazy;
|
|
@@ -1181,6 +1185,5 @@ exports.on = on;
|
|
|
1181
1185
|
exports.parent = parent;
|
|
1182
1186
|
exports.pending = pending;
|
|
1183
1187
|
exports.ref = ref;
|
|
1184
|
-
exports.set = set;
|
|
1185
1188
|
exports.tap = tap;
|
|
1186
1189
|
exports.use = use;
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
package/types/instructions.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Async, Class, InstanceOf, InterceptCallback } from './types';
|
|
|
5
5
|
* Run instruction as controller sets itself up.
|
|
6
6
|
* This will specialize the behavior of a given property.
|
|
7
7
|
**/
|
|
8
|
-
export function
|
|
8
|
+
export function apply <T = any> (instruction: Model.Instruction<T>, name?: string): T;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Creates a new child-instance of specified controller.
|
|
@@ -117,14 +117,24 @@ export function on <T = any> (initialValue: undefined, onUpdate: InterceptCallba
|
|
|
117
117
|
export function on <T = any> (initialValue: T, onUpdate: InterceptCallback<T>): T;
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Creates
|
|
120
|
+
* Creates an object with references to all managed values.
|
|
121
|
+
* Each property will set value in state when invoked.
|
|
122
|
+
*
|
|
123
|
+
* *Properties are simultaneously a ref-function and ref-object, use as needed.*
|
|
124
|
+
*
|
|
125
|
+
* @param target - Source model from which to reference values.
|
|
126
|
+
*/
|
|
127
|
+
export function ref <T = Model> (target: T): { [P in Model.Fields<T>]: Model.Ref<T[P]> };
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a ref-compatible property.
|
|
121
131
|
* Will persist value, and updates to this are made part of controller event-stream.
|
|
122
132
|
*
|
|
123
133
|
* *Output is simultaneously a ref-function and ref-object, use as needed.*
|
|
124
134
|
*
|
|
125
135
|
* @param callback - Optional callback to synchronously fire when reference is first set or does update.
|
|
126
136
|
*/
|
|
127
|
-
export function ref <T = HTMLElement> (
|
|
137
|
+
export function ref <T = HTMLElement> (callback?: InterceptCallback<T>): Model.Ref<T>;
|
|
128
138
|
|
|
129
139
|
/**
|
|
130
140
|
* Sets an exotic method with managed ready-state. Property accepts an async function.
|
|
@@ -141,29 +151,29 @@ export function act <T extends Async> (action: T): T & { active: boolean };
|
|
|
141
151
|
/**
|
|
142
152
|
* Implement a computed value; output is returned by function from provided factory.
|
|
143
153
|
*
|
|
144
|
-
* @param
|
|
154
|
+
* @param compute - Factory function to generate a getter to subscribe dependancies.
|
|
145
155
|
*/
|
|
146
|
-
export function from <R> (
|
|
156
|
+
export function from <R> (compute: (property: string) => (this: any, state: any) => R): R;
|
|
147
157
|
|
|
148
158
|
/**
|
|
149
159
|
* Implement a computed value; output will be generated by provided function.
|
|
150
160
|
*
|
|
151
161
|
* @param source - Source Model to find and subscribe to, for input.
|
|
152
162
|
*
|
|
153
|
-
* @param
|
|
163
|
+
* @param compute - Compute function. Bound to a subscriber-proxy of source, returns output value.
|
|
154
164
|
* Will update automatically as input values change.
|
|
155
165
|
*/
|
|
156
|
-
export function from <T extends Class, R> (source: T,
|
|
166
|
+
export function from <T extends Class, R> (source: T, compute: (this: InstanceOf<T>, on: InstanceOf<T>) => R): R;
|
|
157
167
|
|
|
158
168
|
/**
|
|
159
169
|
* Implement a computed value; output will be generated by provided function.
|
|
160
170
|
*
|
|
161
171
|
* @param source - Source model from which computed value will be a subscriber.
|
|
162
172
|
*
|
|
163
|
-
* @param
|
|
173
|
+
* @param compute - Compute function. Bound to a subscriber-proxy of source, returns output value.
|
|
164
174
|
* Will update automatically as input values change.
|
|
165
175
|
*/
|
|
166
|
-
export function from <T, R> (source: T,
|
|
176
|
+
export function from <T, R> (source: T, compute: (this: T, on: T) => R): R;
|
|
167
177
|
|
|
168
178
|
/**
|
|
169
179
|
* Memoized value. Computes and stores a value returned by provided factory.
|
|
@@ -190,13 +200,20 @@ export function pending (waitFor: () => Promise<void>): undefined;
|
|
|
190
200
|
export function pending <T> (waitFor: () => Promise<T>): T;
|
|
191
201
|
|
|
192
202
|
/**
|
|
193
|
-
* Suspend for a computed value
|
|
194
|
-
* Will throw a promise if value is accessed while undefined, promise will
|
|
195
|
-
* resolve once value gains any value.
|
|
203
|
+
* Suspend for a computed value, returned by syncronous function.
|
|
196
204
|
*
|
|
197
|
-
*
|
|
205
|
+
* If accessed while computing to undefined, will throw a promise. Function is subscribed
|
|
206
|
+
* and will re-evaluate anytime used properties change. Once function returns any value,
|
|
207
|
+
* outstanding promises resolve and are not thrown on subsequent access of this property.
|
|
198
208
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
209
|
+
* Similar to `from`, compute function will only run when inputs change and not upon access.
|
|
210
|
+
*
|
|
211
|
+
* This behavior is recursive. If return value _becomes_ undefined, property
|
|
212
|
+
* access will throw Promises again, awaiting its next value.
|
|
213
|
+
*
|
|
214
|
+
* @param source - Source object, from which computed value will be a subscriber.
|
|
215
|
+
*
|
|
216
|
+
* @param compute - Compute function returns output value. Bound to a subscriber of source.
|
|
217
|
+
* Will re-run automatically as input values change.
|
|
201
218
|
*/
|
|
202
|
-
export function pending <T, R> (source: T,
|
|
219
|
+
export function pending <T, R> (source: T, compute: (this: T, on: T) => R): Exclude<R, undefined>;
|
package/types/model.d.ts
CHANGED
|
@@ -153,7 +153,8 @@ declare const CONTROL: unique symbol;
|
|
|
153
153
|
declare const LOCAL: unique symbol;
|
|
154
154
|
declare const STATE: unique symbol;
|
|
155
155
|
|
|
156
|
-
export
|
|
156
|
+
export interface Model extends Lifecycle {}
|
|
157
|
+
export abstract class Model {
|
|
157
158
|
/** Controller for this instance. */
|
|
158
159
|
[CONTROL]: Model.Controller;
|
|
159
160
|
|
|
@@ -280,6 +281,50 @@ export abstract class State {
|
|
|
280
281
|
|
|
281
282
|
tap <K extends Model.SelectField<this>> (key: K, expect?: boolean): ReturnType<K>;
|
|
282
283
|
tap <K extends Model.SelectField<this>> (key: K, expect: true): Exclude<ReturnType<K>, undefined>;
|
|
284
|
+
|
|
285
|
+
// Keyed
|
|
286
|
+
on <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false, once?: boolean): Callback;
|
|
287
|
+
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false, once?: boolean): Callback;
|
|
288
|
+
// Squash
|
|
289
|
+
on <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true, once?: boolean): Callback;
|
|
290
|
+
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true, once?: boolean): Callback;
|
|
291
|
+
// Unknown
|
|
292
|
+
on <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean, once?: boolean): Callback;
|
|
293
|
+
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean, once?: boolean): Callback;
|
|
294
|
+
|
|
295
|
+
// Keyed
|
|
296
|
+
once <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false): Callback;
|
|
297
|
+
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false): Callback;
|
|
298
|
+
// Squash
|
|
299
|
+
once <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true): Callback;
|
|
300
|
+
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true): Callback;
|
|
301
|
+
// Promise
|
|
302
|
+
once <S extends Model.SelectEvents<this>> (via: S): Promise<Selector.From<S>[]>;
|
|
303
|
+
once <P extends Model.EventsCompat<this>> (key: P | P[]): Promise<P[]>;
|
|
304
|
+
// Unknown
|
|
305
|
+
once <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean): Callback;
|
|
306
|
+
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean): Callback;
|
|
307
|
+
|
|
308
|
+
effect(callback: (this: this, state: this) => void, select?: Model.SelectFields<this>): Callback;
|
|
309
|
+
effect(callback: (this: this, state: this) => void, select?: (keyof this)[]): Callback;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* **React Hook** - Attach to instance of this controller within ambient component.
|
|
313
|
+
*
|
|
314
|
+
* This method will fire lifecycle events on given controller.
|
|
315
|
+
*
|
|
316
|
+
* @param id - Argument passed to controller-lifecycle methods. Use to identify the consumer.
|
|
317
|
+
*/
|
|
318
|
+
tag(id?: any): this;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* **React Hook** - Attach to instance of this controller within ambient component.
|
|
322
|
+
*
|
|
323
|
+
* This method will fire lifecycle events on given controller.
|
|
324
|
+
*
|
|
325
|
+
* @param idFactory - Will be invoked with fetched instance. Use this to register a tag as-needed.
|
|
326
|
+
*/
|
|
327
|
+
tag(idFactory: (idFactory: this) => any): this;
|
|
283
328
|
|
|
284
329
|
/** Use symbol to access controller of a model. */
|
|
285
330
|
static CONTROL: typeof CONTROL;
|
|
@@ -387,61 +432,6 @@ export abstract class State {
|
|
|
387
432
|
static tap <T extends Class, I extends InstanceOf<T>, K extends Model.Fields<I>> (this: T, key: K, expect: true): Exclude<I[K], undefined>;
|
|
388
433
|
static tap <T extends Class, I extends InstanceOf<T>, K extends Model.SelectField<I>> (this: T, key: K, expect: true): Exclude<ReturnType<K>, undefined>;
|
|
389
434
|
|
|
390
|
-
/**
|
|
391
|
-
* Static equivalent of `x instanceof this`.
|
|
392
|
-
*
|
|
393
|
-
* Will determine if provided class is a subtype of this one.
|
|
394
|
-
*/
|
|
395
|
-
static isTypeof <T extends Class>(this: T, subject: any): subject is T;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export interface Model extends Lifecycle {}
|
|
399
|
-
export abstract class Model extends State {
|
|
400
|
-
// Keyed
|
|
401
|
-
on <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false, once?: boolean): Callback;
|
|
402
|
-
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false, once?: boolean): Callback;
|
|
403
|
-
// Squash
|
|
404
|
-
on <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true, once?: boolean): Callback;
|
|
405
|
-
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true, once?: boolean): Callback;
|
|
406
|
-
// Unknown
|
|
407
|
-
on <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean, once?: boolean): Callback;
|
|
408
|
-
on <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean, once?: boolean): Callback;
|
|
409
|
-
|
|
410
|
-
// Keyed
|
|
411
|
-
once <S extends Model.SelectEvents<this>> (via: S, cb: Selector.Callback<S, this>, squash?: false): Callback;
|
|
412
|
-
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: UpdateCallback<this, P>, squash?: false): Callback;
|
|
413
|
-
// Squash
|
|
414
|
-
once <S extends Model.SelectEvents<this>> (via: S, cb: (keys: Selector.From<S>[]) => void, squash: true): Callback;
|
|
415
|
-
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: (keys: P[]) => void, squash: true): Callback;
|
|
416
|
-
// Promise
|
|
417
|
-
once <S extends Model.SelectEvents<this>> (via: S): Promise<Selector.From<S>[]>;
|
|
418
|
-
once <P extends Model.EventsCompat<this>> (key: P | P[]): Promise<P[]>;
|
|
419
|
-
// Unknown
|
|
420
|
-
once <S extends Model.SelectEvents<this>> (via: S, cb: unknown, squash: boolean): Callback;
|
|
421
|
-
once <P extends Model.EventsCompat<this>> (key: P | P[], listener: unknown, squash: boolean): Callback;
|
|
422
|
-
|
|
423
|
-
effect(callback: (this: this, state: this) => void, select?: Model.SelectFields<this>): Callback;
|
|
424
|
-
effect(callback: (this: this, state: this) => void, select?: (keyof this)[]): Callback;
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* **React Hook** - Attach to instance of this controller within ambient component.
|
|
428
|
-
*
|
|
429
|
-
* This method will fire lifecycle events on given controller.
|
|
430
|
-
*
|
|
431
|
-
* @param id - Argument passed to controller-lifecycle methods. Use to identify the consumer.
|
|
432
|
-
*/
|
|
433
|
-
tag(id?: any): this;
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* **React Hook** - Attach to instance of this controller within ambient component.
|
|
437
|
-
*
|
|
438
|
-
* This method will fire lifecycle events on given controller.
|
|
439
|
-
*
|
|
440
|
-
* @param idFactory - Will be invoked with fetched instance. Use this to register a tag as-needed.
|
|
441
|
-
*/
|
|
442
|
-
tag(idFactory: (idFactory: this) => any): this;
|
|
443
|
-
|
|
444
|
-
|
|
445
435
|
/**
|
|
446
436
|
* **React Hook** - Attach to instance of this controller within ambient component.
|
|
447
437
|
*
|
|
@@ -475,6 +465,13 @@ export abstract class Model extends State {
|
|
|
475
465
|
* Documentation TBD.
|
|
476
466
|
*/
|
|
477
467
|
static meta <T extends Class, K extends Model.SelectField<T>> (this: T, key?: K): ReturnType<K>;
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Static equivalent of `x instanceof this`.
|
|
471
|
+
*
|
|
472
|
+
* Will determine if provided class is a subtype of this one.
|
|
473
|
+
*/
|
|
474
|
+
static isTypeof <T extends Class>(this: T, subject: any): subject is T;
|
|
478
475
|
}
|
|
479
476
|
|
|
480
477
|
export class Singleton extends Model {
|