@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 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
- function set(instruction, label) {
324
- const name = label || instruction.name || "pending";
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
- Pending.set(placeholder, function set(key) {
327
- let output = instruction.call(this, key, this);
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
- const attach = (from, name) => set(function attach(key) {
461
- const proxyCache = new WeakMap();
462
- const getCurrent = from.call(this, key);
463
- function subscribe(sub) {
464
- let child;
465
- function start() {
466
- const instance = getCurrent();
467
- if (instance) {
468
- child = new Subscriber(instance, sub.onUpdate);
469
- if (sub.active)
470
- child.commit();
471
- sub.dependant.add(child);
472
- proxyCache.set(sub, child.proxy);
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
- function refresh() {
476
- if (child) {
477
- child.release();
478
- sub.dependant.delete(child);
479
- proxyCache.set(sub, undefined);
480
- child = undefined;
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.onUpdate();
483
+ sub.follow(key, refresh);
484
484
  }
485
- start();
486
- sub.follow(key, refresh);
487
- }
488
- return (local) => {
489
- if (!local)
490
- return getCurrent();
491
- if (!proxyCache.has(local))
492
- subscribe(local);
493
- return proxyCache.get(local);
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$6 = issues({
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$6.NothingInContext(Type.name);
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$6.BadConsumerProps();
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$6.NoProviderType();
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
- class State {
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 isTypeof(maybe) {
834
- return (typeof maybe == "function" &&
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 tag(id) {
907
- return this.find(true).tag(id);
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
- const use = (input, argument) => attach(function use(key) {
943
- let instance;
944
- const update = (next) => {
945
- instance =
946
- next instanceof Model ? next :
947
- next && bootstrap(next);
948
- if (instance) {
949
- Parent.set(instance, this.subject);
950
- manage(instance);
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
- if (typeof argument == "function")
953
- argument(instance);
954
- else if (!instance && argument !== false)
955
- throw Oops$3.UndefinedNotAllowed(key);
956
- };
957
- if (input) {
958
- instance =
959
- Model.isTypeof(input) ? new input() :
960
- input instanceof Model ? input :
961
- typeof input === "function" ? input() :
962
- typeof input === "object" ? bootstrap(input) :
963
- Oops$3.BadArgument(typeof input).throw();
964
- if (instance)
965
- update(instance);
966
- }
967
- else
968
- argument = false;
969
- this.manage(key, instance, update);
970
- return () => instance;
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 ref(cb) {
991
- return set(function ref(key) {
992
- const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
993
- defineProperty(refObjectFunction, "current", {
994
- set: refObjectFunction,
995
- get: () => this.state[key]
996
- });
997
- return {
998
- value: refObjectFunction
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 set(function on(key) {
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 set(function memo(key) {
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 set(function lazy(key) {
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 set(function act(key) {
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 set(function from(key) {
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 set(function suspense(key) {
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.attach = attach;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expressive/mvc",
3
- "version": "1.0.0-beta.20",
3
+ "version": "1.0.0-beta.21",
4
4
  "description": "Use classes to define state in React!",
5
5
  "author": {
6
6
  "name": "Gabe Klein",
package/types/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export {
2
- State,
3
2
  Model,
4
3
  Model as default,
5
4
  Singleton
@@ -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 set <T = any> (instruction: Model.Instruction<T>, name?: string): T;
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 a ref-compatible property for use with components.
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> (this: any, callback?: InterceptCallback<T>): Model.Ref<T>;
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 fn - Factory function to generate a getter to subscribe dependancies.
154
+ * @param compute - Factory function to generate a getter to subscribe dependancies.
145
155
  */
146
- export function from <R> (fn: (property: string) => (this: any, state: any) => R): 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 fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
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, fn: (this: InstanceOf<T>, on: InstanceOf<T>) => R): R;
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 fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
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, fn: (this: T, on: T) => R): R;
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; output will be generated by provided function.
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
- * @param source - Source model from which computed value will be a subscriber.
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
- * @param fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
200
- * Will update automatically as input values change.
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, fn: (this: T, on: T) => R): Exclude<R, undefined>;
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 abstract class State {
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 {