@expressive/mvc 0.9.2 → 0.9.4

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.d.ts CHANGED
@@ -29,6 +29,32 @@ declare class Subscriber<T extends {} = any> {
29
29
  private event;
30
30
  }
31
31
 
32
+ declare const LOCAL = "$debug local";
33
+ declare const STATE = "$debug state";
34
+ declare const UPDATE = "$debug update";
35
+ declare const CONTROL = "$debug controller";
36
+ declare const Debug: {
37
+ readonly CONTROL: "$debug controller";
38
+ readonly LOCAL: "$debug local";
39
+ readonly STATE: "$debug state";
40
+ readonly UPDATE: "$debug update";
41
+ };
42
+ declare type Debug<T extends {}> = T & {
43
+ /** Controller for this instance. */
44
+ [CONTROL]?: Control<T>;
45
+ /** Current subscriber (if present) while used in a live context (e.g. hook or effect). */
46
+ [LOCAL]?: Subscriber<T>;
47
+ /** Current state of this instance. */
48
+ [STATE]?: Model.Values<T>;
49
+ /**
50
+ * Last update causing a refresh to subscribers.
51
+ *
52
+ * If accessed directly, will contain all keys from last push.
53
+ * If within a subscribed function, will contain only keys which explicitly caused a refresh.
54
+ */
55
+ [UPDATE]?: readonly Model.Event<T>[];
56
+ };
57
+
32
58
  declare namespace Model {
33
59
  export { Control };
34
60
  export { Subscriber };
@@ -52,7 +78,7 @@ declare namespace Model {
52
78
  *
53
79
  * TODO: Should exclude methods
54
80
  **/
55
- export type Field<T, U extends Model = Model> = Exclude<keyof T & string, keyof U>;
81
+ export type Field<T, U extends Model = Model> = Exclude<keyof T, keyof U | keyof Debug<{}>> & string;
56
82
  /**
57
83
  * Including but not limited to `keyof T` which are not methods or defined by base Model.
58
84
  **/
@@ -415,12 +441,13 @@ declare class MVC extends Model {
415
441
  * **React Hook** - Fetch and subscribe to instance of this controller within ambient component.
416
442
  */
417
443
  static tap<T extends MVC>(this: Model.Type<T>): T;
418
- static tap<I extends MVC, K extends Model.Field<I>>(this: Model.Type<I>, key: K, expect: true): Exclude<I[K], undefined>;
419
- static tap<I extends MVC, K extends Model.Field<I>>(this: Model.Type<I>, key: K, expect?: boolean): I[K];
420
- static tap<T, I extends MVC>(this: Model.Type<I>, from: (this: I, state: I) => Promise<T>, expect: true): Exclude<T, undefined>;
421
- static tap<T, I extends MVC>(this: Model.Type<I>, from: (this: I, state: I) => Promise<T>, expect?: boolean): T;
422
- static tap<T, I extends MVC>(this: Model.Type<I>, from: (this: I, state: I) => T, expect: true): Exclude<T, undefined>;
423
- static tap<T, I extends MVC>(this: Model.Type<I>, from: (this: I, state: I) => T, expect?: boolean): T;
444
+ static tap<T extends MVC, K extends Model.Field<T>>(this: Model.Type<T>, key: K, expect: true): Exclude<T[K], undefined>;
445
+ static tap<T extends MVC, K extends Model.Field<T>>(this: Model.Type<T>, key: K, expect?: boolean): T[K];
446
+ static tap<T extends MVC, R>(this: Model.Type<T>, connect: (this: T, model: T) => () => R): R;
447
+ static tap<T extends MVC, R>(this: Model.Type<T>, connect: (this: T, model: T) => (() => R) | null): R | null;
448
+ static tap<T extends MVC, R>(this: Model.Type<T>, compute: (this: T, model: T) => Promise<R> | R, expect: true): Exclude<R, undefined>;
449
+ static tap<T extends MVC, R>(this: Model.Type<T>, compute: (this: T, model: T) => Promise<R>, expect?: boolean): R | undefined;
450
+ static tap<T extends MVC, R>(this: Model.Type<T>, compute: (this: T, model: T) => R, expect?: boolean): R;
424
451
  static use<I extends MVC>(this: Model.Type<I>, watch: Model.Field<I>[], callback?: (instance: I) => void): I;
425
452
  static use<I extends MVC>(this: Model.Type<I>, callback?: (instance: I) => void): I;
426
453
  static use<I extends MVC>(this: Model.Type<I>, apply: Model.Compat<I>, keys?: Model.Event<I>[]): I;
@@ -433,32 +460,6 @@ declare class MVC extends Model {
433
460
  static meta<T, M extends Class>(this: M, from: (this: M, state: M) => T, expect?: boolean): T;
434
461
  }
435
462
 
436
- declare const LOCAL = "$debug local";
437
- declare const STATE = "$debug state";
438
- declare const UPDATE = "$debug update";
439
- declare const CONTROL = "$debug controller";
440
- declare const Debug: {
441
- readonly CONTROL: "$debug controller";
442
- readonly LOCAL: "$debug local";
443
- readonly STATE: "$debug state";
444
- readonly UPDATE: "$debug update";
445
- };
446
- declare type Debug<T extends {}> = T & {
447
- /** Controller for this instance. */
448
- [CONTROL]?: Control<T>;
449
- /** Current subscriber (if present) while used in a live context (e.g. hook or effect). */
450
- [LOCAL]?: Subscriber<T>;
451
- /** Current state of this instance. */
452
- [STATE]?: Model.Values<T>;
453
- /**
454
- * Last update causing a refresh to subscribers.
455
- *
456
- * If accessed directly, will contain all keys from last push.
457
- * If within a subscribed function, will contain only keys which explicitly caused a refresh.
458
- */
459
- [UPDATE]?: readonly Model.Event<T>[];
460
- };
461
-
462
463
  declare namespace Consumer {
463
464
  type HasProps<E extends Class> = {
464
465
  /** @deprecated - use for instead */
@@ -538,7 +539,7 @@ declare class Global extends MVC {
538
539
  * Returns a thenable; resolves after successful update.
539
540
  * If instance does not already exist, one will be created.
540
541
  */
541
- static set<T extends typeof Global>(this: T, values: Model.Compat<InstanceOf<T>, Global>): PromiseLike<Exclude<keyof InstanceOf<T> & string, keyof Global>[]>;
542
+ static set<T extends typeof Global>(this: T, values: Model.Compat<InstanceOf<T>, Global>): PromiseLike<Model.Field<InstanceOf<T>, Global>[]>;
542
543
  /**
543
544
  * **React Hook** - Fetch most instance of this controller from context, if it exists.
544
545
  *
@@ -597,7 +598,10 @@ declare namespace useTap {
597
598
  declare function useTap<T extends Model>(source: useTap.Source<T>): T;
598
599
  declare function useTap<T extends Model, K extends Model.Field<T>>(source: useTap.Source<T>, path: K, expect: true): Exclude<T[K], undefined>;
599
600
  declare function useTap<T extends Model, K extends Model.Field<T>>(source: useTap.Source<T>, path: K, expect?: boolean): T[K];
600
- declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
601
- declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: (this: T, from: T) => R, expect?: boolean): R;
601
+ declare function useTap<T extends Model, R>(source: useTap.Source<T>, connect: (this: T, model: T) => () => R): R;
602
+ declare function useTap<T extends Model, R>(source: useTap.Source<T>, connect: (this: T, model: T) => (() => R) | null): R | null;
603
+ declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: (this: T, model: T) => Promise<R> | R, expect: true): Exclude<R, undefined>;
604
+ declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: (this: T, model: T) => Promise<R>, expect?: boolean): R | undefined;
605
+ declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: (this: T, model: T) => R, expect?: boolean): R;
602
606
 
603
607
  export { Consumer, Debug, Global, MVC, MVC as Model, Provider, add, MVC as default, get, has, ref, run, set, tap, use, useContext, useModel, useTap, useValue };
package/lib/index.esm.js CHANGED
@@ -19,11 +19,11 @@ function issues(register) {
19
19
 
20
20
  const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
21
21
 
22
- const Oops$d = issues({
22
+ const Oops$e = issues({
23
23
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
24
24
  });
25
25
  function suspend(source, key) {
26
- const error = Oops$d.NotReady(source.subject, key);
26
+ const error = Oops$e.NotReady(source.subject, key);
27
27
  const promise = new Promise(resolve => {
28
28
  const release = source.addListener(forKey => {
29
29
  if (forKey == key)
@@ -63,7 +63,7 @@ function mayRetry(fn) {
63
63
  return compute();
64
64
  }
65
65
 
66
- const Oops$c = issues({
66
+ const Oops$d = issues({
67
67
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
68
68
  });
69
69
  const INFO = new WeakMap();
@@ -83,7 +83,7 @@ function computeMode(self, source, setter, key, required) {
83
83
  return setter.call(sub.proxy, sub.proxy);
84
84
  }
85
85
  catch (err) {
86
- Oops$c.Failed(subject, key, initial).warn();
86
+ Oops$d.Failed(subject, key, initial).warn();
87
87
  throw err;
88
88
  }
89
89
  };
@@ -110,6 +110,7 @@ function computeMode(self, source, setter, key, required) {
110
110
  else
111
111
  pending.add(refresh);
112
112
  });
113
+ sub.watch.set(key, false);
113
114
  try {
114
115
  const value = compute(true);
115
116
  state.set(key, value);
@@ -457,7 +458,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
457
458
  });
458
459
  }
459
460
 
460
- const Oops$b = issues({
461
+ const Oops$c = issues({
461
462
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
462
463
  });
463
464
  function run(task) {
@@ -465,7 +466,7 @@ function run(task) {
465
466
  let pending = false;
466
467
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
467
468
  if (pending)
468
- return Promise.reject(Oops$b.DuplicatePending(key));
469
+ return Promise.reject(Oops$c.DuplicatePending(key));
469
470
  pending = true;
470
471
  this.update(key);
471
472
  try {
@@ -487,7 +488,7 @@ function run(task) {
487
488
  });
488
489
  }
489
490
 
490
- const Oops$a = issues({
491
+ const Oops$b = issues({
491
492
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
492
493
  });
493
494
  function factoryMode(self, output, key, required) {
@@ -511,7 +512,7 @@ function factoryMode(self, output, key, required) {
511
512
  const suspend = () => {
512
513
  if (required === false)
513
514
  return undefined;
514
- const issue = Oops$a.NotReady(subject, key);
515
+ const issue = Oops$b.NotReady(subject, key);
515
516
  assign(pending, {
516
517
  message: issue.message,
517
518
  stack: issue.stack
@@ -528,7 +529,7 @@ function factoryMode(self, output, key, required) {
528
529
  };
529
530
  }
530
531
 
531
- const Oops$9 = issues({
532
+ const Oops$a = issues({
532
533
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
533
534
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
534
535
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
@@ -541,7 +542,7 @@ function get(arg0, arg1, arg2) {
541
542
  let getter;
542
543
  // Easy mistake, using a peer, will always be unresolved.
543
544
  if (typeof arg0 == "symbol")
544
- throw Oops$9.PeerNotAllowed(subject, key);
545
+ throw Oops$a.PeerNotAllowed(subject, key);
545
546
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
546
547
  // throw Oops.BadSource(subject, key, arg0);
547
548
  this.state.set(key, undefined);
@@ -575,7 +576,7 @@ function get(arg0, arg1, arg2) {
575
576
  init();
576
577
  }
577
578
  catch (err) {
578
- Oops$9.ComputeFailed(subject, key).warn();
579
+ Oops$a.ComputeFailed(subject, key).warn();
579
580
  throw err;
580
581
  }
581
582
  return () => {
@@ -586,7 +587,7 @@ function get(arg0, arg1, arg2) {
586
587
  });
587
588
  }
588
589
 
589
- const Oops$8 = issues({
590
+ const Oops$9 = issues({
590
591
  BadCallback: () => `Callback for effect-callback may only return a function.`
591
592
  });
592
593
  function createEffect(model, callback, keys) {
@@ -600,7 +601,7 @@ function createEffect(model, callback, keys) {
600
601
  if (unSet instanceof Promise)
601
602
  unSet = undefined;
602
603
  if (unSet && typeof unSet !== "function")
603
- throw Oops$8.BadCallback();
604
+ throw Oops$9.BadCallback();
604
605
  }
605
606
  function invoke() {
606
607
  if (busy)
@@ -643,7 +644,7 @@ function createValueEffect(callback) {
643
644
  if (typeof out == "function")
644
645
  unSet = out;
645
646
  else
646
- throw Oops$8.BadCallback();
647
+ throw Oops$9.BadCallback();
647
648
  };
648
649
  }
649
650
 
@@ -841,7 +842,7 @@ function createProxy(emit, watch) {
841
842
  }
842
843
 
843
844
  const Parent = new WeakMap();
844
- const Oops$7 = issues({
845
+ const Oops$8 = issues({
845
846
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
846
847
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
847
848
  });
@@ -858,7 +859,7 @@ function use$1(input, argument) {
858
859
  input = new input();
859
860
  }
860
861
  else if (input && typeof input !== "object")
861
- throw Oops$7.BadArgument(typeof input);
862
+ throw Oops$8.BadArgument(typeof input);
862
863
  if (input instanceof Map || input instanceof Set)
863
864
  return keyed(this, key, input);
864
865
  const onUpdate = (next) => {
@@ -875,7 +876,7 @@ function use$1(input, argument) {
875
876
  const suspend = () => {
876
877
  if (required === false)
877
878
  return;
878
- const issue = Oops$7.NotReady(subject, key);
879
+ const issue = Oops$8.NotReady(subject, key);
879
880
  assign(pending, {
880
881
  message: issue.message,
881
882
  stack: issue.stack
@@ -920,7 +921,7 @@ function use$1(input, argument) {
920
921
  });
921
922
  }
922
923
 
923
- const Oops$6 = issues({
924
+ const Oops$7 = issues({
924
925
  Required: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
925
926
  Unexpected: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
926
927
  });
@@ -931,19 +932,100 @@ function has(Type, required) {
931
932
  const expected = Type.name;
932
933
  if (!value) {
933
934
  if (required !== false)
934
- throw Oops$6.Required(expected, child);
935
+ throw Oops$7.Required(expected, child);
935
936
  }
936
937
  else if (!(value instanceof Type))
937
- throw Oops$6.Unexpected(expected, child, value);
938
+ throw Oops$7.Unexpected(expected, child, value);
938
939
  return { value };
939
940
  });
940
941
  }
941
942
 
942
- const Oops$5 = issues({
943
+ const Oops$6 = issues({
943
944
  Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
944
945
  KeysExpected: (key) => `Key "${key}" was expected in current update.`,
945
946
  StrictUpdate: () => `Expected update but none in progress.`,
946
- StrictNoUpdate: () => `Update is not expected but one is in progress!`,
947
+ StrictNoUpdate: () => `Update is not expected but one is in progress!`
948
+ });
949
+ function addEventListener(source, arg1, arg2, arg3) {
950
+ const single = typeof arg1 == "string";
951
+ const keys = typeof arg1 == "string" ? [arg1] :
952
+ typeof arg1 == "object" ? arg1 :
953
+ undefined;
954
+ if (typeof arg2 == "function" && keys)
955
+ return Control.for(source, control => {
956
+ for (const key of keys)
957
+ try {
958
+ void source[key];
959
+ }
960
+ catch (e) { }
961
+ const invoke = single
962
+ ? () => arg2.call(source, control.state.get(arg1), arg1)
963
+ : (frame) => arg2.call(source, frame);
964
+ const removeListener = control.addListener(key => {
965
+ if (!key && !keys.length)
966
+ invoke([]);
967
+ if (keys.includes(key)) {
968
+ if (arg3)
969
+ removeListener();
970
+ return invoke;
971
+ }
972
+ });
973
+ return removeListener;
974
+ });
975
+ return new Promise((resolve, reject) => {
976
+ const timeout = (message) => {
977
+ const k = keys ? keys.join(", ") : "any";
978
+ removeListener();
979
+ reject(Oops$6.Timeout(k, message));
980
+ };
981
+ const removeListener = Control.for(source, control => {
982
+ const pending = control.frame;
983
+ if (keys === null) {
984
+ if (pending.size)
985
+ reject(Oops$6.StrictNoUpdate());
986
+ else
987
+ resolve(null);
988
+ return;
989
+ }
990
+ if (keys) {
991
+ if (keys.length) {
992
+ if (arg2 === 0 && single && !pending.has(keys[0])) {
993
+ reject(Oops$6.KeysExpected(keys[0]));
994
+ return;
995
+ }
996
+ else
997
+ for (const key of keys)
998
+ try {
999
+ void source[key];
1000
+ }
1001
+ catch (e) { }
1002
+ }
1003
+ }
1004
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1005
+ reject(Oops$6.StrictUpdate());
1006
+ return;
1007
+ }
1008
+ else
1009
+ control.waiting.add(resolve);
1010
+ return control.addListener(key => {
1011
+ if (!key) {
1012
+ if (keys && !keys.length)
1013
+ resolve([]);
1014
+ else
1015
+ timeout(`lifetime of ${source}`);
1016
+ }
1017
+ else if (!keys || keys.includes(key)) {
1018
+ removeListener();
1019
+ return keys => resolve(single ? control.state.get(key) : keys);
1020
+ }
1021
+ });
1022
+ });
1023
+ if (arg2 && typeof arg2 == "number")
1024
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1025
+ });
1026
+ }
1027
+
1028
+ const Oops$5 = issues({
947
1029
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
948
1030
  });
949
1031
  class Model {
@@ -954,82 +1036,7 @@ class Model {
954
1036
  on(arg1, arg2, arg3) {
955
1037
  if (typeof arg1 == "function")
956
1038
  return createEffect(this.is, arg1, arg2);
957
- const single = typeof arg1 == "string";
958
- const keys = typeof arg1 == "string" ? [arg1] :
959
- typeof arg1 == "object" ? arg1 :
960
- undefined;
961
- if (typeof arg2 == "function" && keys)
962
- return Control.for(this, control => {
963
- for (const key of keys)
964
- try {
965
- void this[key];
966
- }
967
- catch (e) { }
968
- const invoke = single
969
- ? () => arg2.call(this, control.state.get(arg1), arg1)
970
- : (frame) => arg2.call(this, frame);
971
- const removeListener = control.addListener(key => {
972
- if (!key && !keys.length)
973
- invoke([]);
974
- if (keys.includes(key)) {
975
- if (arg3)
976
- removeListener();
977
- return invoke;
978
- }
979
- });
980
- return removeListener;
981
- });
982
- return new Promise((resolve, reject) => {
983
- const timeout = (message) => {
984
- const k = keys ? keys.join(", ") : "any";
985
- removeListener();
986
- reject(Oops$5.Timeout(k, message));
987
- };
988
- const removeListener = Control.for(this, control => {
989
- const pending = control.frame;
990
- if (keys === null) {
991
- if (pending.size)
992
- reject(Oops$5.StrictNoUpdate());
993
- else
994
- resolve(null);
995
- return;
996
- }
997
- if (keys) {
998
- if (keys.length) {
999
- if (arg2 === 0 && single && !pending.has(keys[0])) {
1000
- reject(Oops$5.KeysExpected(keys[0]));
1001
- return;
1002
- }
1003
- else
1004
- for (const key of keys)
1005
- try {
1006
- void this[key];
1007
- }
1008
- catch (e) { }
1009
- }
1010
- }
1011
- else if (!pending.size && arg1 === true || arg2 === 0) {
1012
- reject(Oops$5.StrictUpdate());
1013
- return;
1014
- }
1015
- else
1016
- control.waiting.add(resolve);
1017
- return control.addListener(key => {
1018
- if (!key) {
1019
- if (keys && !keys.length)
1020
- resolve([]);
1021
- else
1022
- timeout(`lifetime of ${this}`);
1023
- }
1024
- else if (!keys || keys.includes(key)) {
1025
- removeListener();
1026
- return keys => resolve(single ? control.state.get(key) : keys);
1027
- }
1028
- });
1029
- });
1030
- if (arg2 && typeof arg2 == "number")
1031
- setTimeout(() => timeout(`${arg2}ms`), arg2);
1032
- });
1039
+ return addEventListener(this.is, arg1, arg2, arg3);
1033
1040
  }
1034
1041
  export(subset) {
1035
1042
  const { state } = Control.for(this);
@@ -1282,6 +1289,19 @@ function useValue(source, compute, suspend) {
1282
1289
  else
1283
1290
  refresh();
1284
1291
  };
1292
+ if (value === null) {
1293
+ sub.watch.clear();
1294
+ return {
1295
+ proxy: null,
1296
+ release: () => { }
1297
+ };
1298
+ }
1299
+ if (typeof value == "function") {
1300
+ const get = value;
1301
+ sub.watch.clear();
1302
+ compute = () => get();
1303
+ value = get();
1304
+ }
1285
1305
  if (value instanceof Promise) {
1286
1306
  value.then(set);
1287
1307
  value = undefined;