@expressive/mvc 0.9.3 → 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
  };
@@ -458,7 +458,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
458
458
  });
459
459
  }
460
460
 
461
- const Oops$b = issues({
461
+ const Oops$c = issues({
462
462
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
463
463
  });
464
464
  function run(task) {
@@ -466,7 +466,7 @@ function run(task) {
466
466
  let pending = false;
467
467
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
468
468
  if (pending)
469
- return Promise.reject(Oops$b.DuplicatePending(key));
469
+ return Promise.reject(Oops$c.DuplicatePending(key));
470
470
  pending = true;
471
471
  this.update(key);
472
472
  try {
@@ -488,7 +488,7 @@ function run(task) {
488
488
  });
489
489
  }
490
490
 
491
- const Oops$a = issues({
491
+ const Oops$b = issues({
492
492
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
493
493
  });
494
494
  function factoryMode(self, output, key, required) {
@@ -512,7 +512,7 @@ function factoryMode(self, output, key, required) {
512
512
  const suspend = () => {
513
513
  if (required === false)
514
514
  return undefined;
515
- const issue = Oops$a.NotReady(subject, key);
515
+ const issue = Oops$b.NotReady(subject, key);
516
516
  assign(pending, {
517
517
  message: issue.message,
518
518
  stack: issue.stack
@@ -529,7 +529,7 @@ function factoryMode(self, output, key, required) {
529
529
  };
530
530
  }
531
531
 
532
- const Oops$9 = issues({
532
+ const Oops$a = issues({
533
533
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
534
534
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
535
535
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
@@ -542,7 +542,7 @@ function get(arg0, arg1, arg2) {
542
542
  let getter;
543
543
  // Easy mistake, using a peer, will always be unresolved.
544
544
  if (typeof arg0 == "symbol")
545
- throw Oops$9.PeerNotAllowed(subject, key);
545
+ throw Oops$a.PeerNotAllowed(subject, key);
546
546
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
547
547
  // throw Oops.BadSource(subject, key, arg0);
548
548
  this.state.set(key, undefined);
@@ -576,7 +576,7 @@ function get(arg0, arg1, arg2) {
576
576
  init();
577
577
  }
578
578
  catch (err) {
579
- Oops$9.ComputeFailed(subject, key).warn();
579
+ Oops$a.ComputeFailed(subject, key).warn();
580
580
  throw err;
581
581
  }
582
582
  return () => {
@@ -587,7 +587,7 @@ function get(arg0, arg1, arg2) {
587
587
  });
588
588
  }
589
589
 
590
- const Oops$8 = issues({
590
+ const Oops$9 = issues({
591
591
  BadCallback: () => `Callback for effect-callback may only return a function.`
592
592
  });
593
593
  function createEffect(model, callback, keys) {
@@ -601,7 +601,7 @@ function createEffect(model, callback, keys) {
601
601
  if (unSet instanceof Promise)
602
602
  unSet = undefined;
603
603
  if (unSet && typeof unSet !== "function")
604
- throw Oops$8.BadCallback();
604
+ throw Oops$9.BadCallback();
605
605
  }
606
606
  function invoke() {
607
607
  if (busy)
@@ -644,7 +644,7 @@ function createValueEffect(callback) {
644
644
  if (typeof out == "function")
645
645
  unSet = out;
646
646
  else
647
- throw Oops$8.BadCallback();
647
+ throw Oops$9.BadCallback();
648
648
  };
649
649
  }
650
650
 
@@ -842,7 +842,7 @@ function createProxy(emit, watch) {
842
842
  }
843
843
 
844
844
  const Parent = new WeakMap();
845
- const Oops$7 = issues({
845
+ const Oops$8 = issues({
846
846
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
847
847
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
848
848
  });
@@ -859,7 +859,7 @@ function use$1(input, argument) {
859
859
  input = new input();
860
860
  }
861
861
  else if (input && typeof input !== "object")
862
- throw Oops$7.BadArgument(typeof input);
862
+ throw Oops$8.BadArgument(typeof input);
863
863
  if (input instanceof Map || input instanceof Set)
864
864
  return keyed(this, key, input);
865
865
  const onUpdate = (next) => {
@@ -876,7 +876,7 @@ function use$1(input, argument) {
876
876
  const suspend = () => {
877
877
  if (required === false)
878
878
  return;
879
- const issue = Oops$7.NotReady(subject, key);
879
+ const issue = Oops$8.NotReady(subject, key);
880
880
  assign(pending, {
881
881
  message: issue.message,
882
882
  stack: issue.stack
@@ -921,7 +921,7 @@ function use$1(input, argument) {
921
921
  });
922
922
  }
923
923
 
924
- const Oops$6 = issues({
924
+ const Oops$7 = issues({
925
925
  Required: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
926
926
  Unexpected: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
927
927
  });
@@ -932,19 +932,100 @@ function has(Type, required) {
932
932
  const expected = Type.name;
933
933
  if (!value) {
934
934
  if (required !== false)
935
- throw Oops$6.Required(expected, child);
935
+ throw Oops$7.Required(expected, child);
936
936
  }
937
937
  else if (!(value instanceof Type))
938
- throw Oops$6.Unexpected(expected, child, value);
938
+ throw Oops$7.Unexpected(expected, child, value);
939
939
  return { value };
940
940
  });
941
941
  }
942
942
 
943
- const Oops$5 = issues({
943
+ const Oops$6 = issues({
944
944
  Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
945
945
  KeysExpected: (key) => `Key "${key}" was expected in current update.`,
946
946
  StrictUpdate: () => `Expected update but none in progress.`,
947
- 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({
948
1029
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
949
1030
  });
950
1031
  class Model {
@@ -955,82 +1036,7 @@ class Model {
955
1036
  on(arg1, arg2, arg3) {
956
1037
  if (typeof arg1 == "function")
957
1038
  return createEffect(this.is, arg1, arg2);
958
- const single = typeof arg1 == "string";
959
- const keys = typeof arg1 == "string" ? [arg1] :
960
- typeof arg1 == "object" ? arg1 :
961
- undefined;
962
- if (typeof arg2 == "function" && keys)
963
- return Control.for(this, control => {
964
- for (const key of keys)
965
- try {
966
- void this[key];
967
- }
968
- catch (e) { }
969
- const invoke = single
970
- ? () => arg2.call(this, control.state.get(arg1), arg1)
971
- : (frame) => arg2.call(this, frame);
972
- const removeListener = control.addListener(key => {
973
- if (!key && !keys.length)
974
- invoke([]);
975
- if (keys.includes(key)) {
976
- if (arg3)
977
- removeListener();
978
- return invoke;
979
- }
980
- });
981
- return removeListener;
982
- });
983
- return new Promise((resolve, reject) => {
984
- const timeout = (message) => {
985
- const k = keys ? keys.join(", ") : "any";
986
- removeListener();
987
- reject(Oops$5.Timeout(k, message));
988
- };
989
- const removeListener = Control.for(this, control => {
990
- const pending = control.frame;
991
- if (keys === null) {
992
- if (pending.size)
993
- reject(Oops$5.StrictNoUpdate());
994
- else
995
- resolve(null);
996
- return;
997
- }
998
- if (keys) {
999
- if (keys.length) {
1000
- if (arg2 === 0 && single && !pending.has(keys[0])) {
1001
- reject(Oops$5.KeysExpected(keys[0]));
1002
- return;
1003
- }
1004
- else
1005
- for (const key of keys)
1006
- try {
1007
- void this[key];
1008
- }
1009
- catch (e) { }
1010
- }
1011
- }
1012
- else if (!pending.size && arg1 === true || arg2 === 0) {
1013
- reject(Oops$5.StrictUpdate());
1014
- return;
1015
- }
1016
- else
1017
- control.waiting.add(resolve);
1018
- return control.addListener(key => {
1019
- if (!key) {
1020
- if (keys && !keys.length)
1021
- resolve([]);
1022
- else
1023
- timeout(`lifetime of ${this}`);
1024
- }
1025
- else if (!keys || keys.includes(key)) {
1026
- removeListener();
1027
- return keys => resolve(single ? control.state.get(key) : keys);
1028
- }
1029
- });
1030
- });
1031
- if (arg2 && typeof arg2 == "number")
1032
- setTimeout(() => timeout(`${arg2}ms`), arg2);
1033
- });
1039
+ return addEventListener(this.is, arg1, arg2, arg3);
1034
1040
  }
1035
1041
  export(subset) {
1036
1042
  const { state } = Control.for(this);
@@ -1283,6 +1289,19 @@ function useValue(source, compute, suspend) {
1283
1289
  else
1284
1290
  refresh();
1285
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
+ }
1286
1305
  if (value instanceof Promise) {
1287
1306
  value.then(set);
1288
1307
  value = undefined;