@expressive/mvc 0.15.1 → 0.16.0

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
@@ -17,13 +17,14 @@ declare namespace Subscriber {
17
17
  declare class Subscriber<T extends {} = any> {
18
18
  parent: Control<T>;
19
19
  onUpdate: Subscriber.OnEvent;
20
+ suspend?: boolean | undefined;
20
21
  proxy: T;
21
22
  release: Callback;
22
23
  latest?: Model.Event<T>[];
23
24
  active: boolean;
24
25
  dependant: Set<Listener>;
25
26
  watch: Map<any, boolean | (() => true | void)>;
26
- constructor(parent: Control<T>, onUpdate: Subscriber.OnEvent);
27
+ constructor(parent: Control<T>, onUpdate: Subscriber.OnEvent, suspend?: boolean | undefined);
27
28
  get using(): Model.Key<T>[];
28
29
  add(key: any, value?: boolean | Callback): void;
29
30
  commit(): () => void;
@@ -62,7 +63,7 @@ declare namespace Model {
62
63
  /** Including but not limited to T. */
63
64
  type Extends<T> = T | (string & Record<never, never>);
64
65
  /** Any typeof Model, using class constructor as the reference. */
65
- export type Type<T extends Model = Model> = (new (...args: any[]) => T);
66
+ export type Type<T extends Model = Model> = abstract new (...args: any[]) => T;
66
67
  /** A typeof Model, specifically one which may be created without arguments. */
67
68
  export type New<T extends Model = Model> = (new () => T) & typeof Model;
68
69
  export type Effect<T> = (this: T, argument: T) => Callback | Promise<any> | void;
@@ -474,13 +475,16 @@ declare class MVC extends Model {
474
475
  * **React Hook** - Fetch and subscribe to instance of this controller within ambient component.
475
476
  */
476
477
  static tap<T extends MVC>(this: Model.Type<T>): T;
477
- static tap<T extends MVC, K extends Model.Key<T>>(this: Model.Type<T>, key: K, expect: true): Exclude<T[K], undefined>;
478
- static tap<T extends MVC, K extends Model.Key<T>>(this: Model.Type<T>, key: K, expect?: boolean): NoVoid<T[K]>;
479
- static tap<T extends MVC, R>(this: Model.Type<T>, connect: MVC.TapCallback<T, () => R>): NoVoid<R>;
480
- static tap<T extends MVC, R>(this: Model.Type<T>, connect: MVC.TapCallback<T, (() => R) | null>): NoVoid<R> | null;
478
+ static tap<T extends MVC, R extends readonly unknown[] | []>(this: Model.Type<T>, compute: MVC.TapCallback<T, R | (() => R)>, expect?: boolean): R;
479
+ static tap<T extends MVC, R extends readonly unknown[] | []>(this: Model.Type<T>, compute: MVC.TapCallback<T, Promise<R> | (() => R) | null>, expect?: boolean): R | null;
480
+ static tap<T extends MVC, R extends readonly unknown[] | []>(this: Model.Type<T>, compute: MVC.TapCallback<T, Promise<R> | R>, expect: true): Exclude<R, undefined>;
481
481
  static tap<T extends MVC, R>(this: Model.Type<T>, compute: MVC.TapCallback<T, Promise<R> | R>, expect: true): Exclude<R, undefined>;
482
482
  static tap<T extends MVC, R>(this: Model.Type<T>, compute: MVC.TapCallback<T, Promise<R>>, expect?: boolean): NoVoid<R> | null;
483
483
  static tap<T extends MVC, R>(this: Model.Type<T>, compute: MVC.TapCallback<T, R>, expect?: boolean): NoVoid<R>;
484
+ static tap<T extends MVC, R>(this: Model.Type<T>, init: MVC.TapCallback<T, () => R>): NoVoid<R>;
485
+ static tap<T extends MVC, R>(this: Model.Type<T>, init: MVC.TapCallback<T, (() => R) | null>): NoVoid<R> | null;
486
+ static tap<T extends MVC, K extends Model.Key<T>>(this: Model.Type<T>, key: K, expect: true): Exclude<T[K], undefined>;
487
+ static tap<T extends MVC, K extends Model.Key<T>>(this: Model.Type<T>, key: K, expect?: boolean): NoVoid<T[K]>;
484
488
  static use<I extends MVC>(this: Model.Type<I>, watch: Model.Key<I>[], callback?: (instance: I) => void): I;
485
489
  static use<I extends MVC>(this: Model.Type<I>, callback?: (instance: I) => void): I;
486
490
  static use<I extends MVC>(this: Model.Type<I>, apply: Model.Compat<I>, keys?: Model.Event<I>[]): I;
@@ -505,8 +509,12 @@ declare namespace useTap {
505
509
  type Callback<T extends Model, R> = (this: T, model: T, update: MVC.ForceUpdate) => R;
506
510
  }
507
511
  declare function useTap<T extends Model>(source: useTap.Source<T>): T;
508
- declare function useTap<T extends Model, K extends Model.Key<T>>(source: useTap.Source<T>, path: K, expect: true): Exclude<T[K], undefined>;
509
- declare function useTap<T extends Model, K extends Model.Key<T>>(source: useTap.Source<T>, path: K, expect?: boolean): NoVoid<T[K]>;
512
+ declare function useTap<T extends Model>(source: useTap.Source<T>, expect: true): {
513
+ [P in Model.Key<T>]: Exclude<T[P], undefined>;
514
+ };
515
+ declare function useTap<T extends Model>(source: useTap.Source<T>, expect?: boolean): {
516
+ [P in Model.Key<T>]: T[P] | undefined;
517
+ };
510
518
  declare function useTap<T extends Model, R>(source: useTap.Source<T>, connect: useTap.Callback<T, () => R>): NoVoid<R>;
511
519
  declare function useTap<T extends Model, R>(source: useTap.Source<T>, connect: useTap.Callback<T, (() => R) | null>): NoVoid<R> | null;
512
520
  declare function useTap<T extends Model, R>(source: useTap.Source<T>, compute: useTap.Callback<T, Promise<R> | R>, expect: true): Exclude<R, undefined>;
package/lib/index.esm.js CHANGED
@@ -314,9 +314,10 @@ function awaitUpdate(source, arg1, arg2) {
314
314
 
315
315
  const REGISTER$1 = new WeakMap();
316
316
  class Subscriber {
317
- constructor(parent, onUpdate) {
317
+ constructor(parent, onUpdate, suspend) {
318
318
  this.parent = parent;
319
319
  this.onUpdate = onUpdate;
320
+ this.suspend = suspend;
320
321
  this.active = false;
321
322
  this.dependant = new Set();
322
323
  this.watch = new Map();
@@ -506,9 +507,13 @@ class Control {
506
507
  set: this.ref(key),
507
508
  get() {
508
509
  const sub = subscriber(this);
509
- if (sub)
510
+ const value = state.get(key);
511
+ if (sub) {
510
512
  sub.add(key);
511
- return state.get(key);
513
+ if (value === undefined && sub.suspend)
514
+ throw suspend(this, key);
515
+ }
516
+ return value;
512
517
  }
513
518
  });
514
519
  }
@@ -698,11 +703,22 @@ function add(instruction, label) {
698
703
  if (!state.has(key) && shouldSuspend)
699
704
  throw suspend(control, key);
700
705
  const local = subscriber(this);
706
+ const required = local && local.suspend;
701
707
  if (local)
702
708
  local.add(key);
703
- return onGet
704
- ? onGet(local)
705
- : state.get(key);
709
+ try {
710
+ const value = onGet
711
+ ? onGet(local)
712
+ : state.get(key);
713
+ if (value === undefined && required === true)
714
+ throw suspend(control, key);
715
+ return value;
716
+ }
717
+ catch (err) {
718
+ if (err instanceof Promise && required === false)
719
+ return;
720
+ throw err;
721
+ }
706
722
  }
707
723
  });
708
724
  }
@@ -828,7 +844,7 @@ function get(arg0, arg1, arg2) {
828
844
  let source = this;
829
845
  let setter;
830
846
  if (typeof arg0 == "function") {
831
- let result = mayRetry(arg0.bind(subject, key, subject));
847
+ const result = mayRetry(arg0.bind(subject, key, subject));
832
848
  if (typeof result == "function")
833
849
  setter = result;
834
850
  else {
@@ -1396,7 +1412,7 @@ function useCompute(source, compute, suspend) {
1396
1412
  sub.commit();
1397
1413
  update = () => {
1398
1414
  const next = make();
1399
- if (value != next)
1415
+ if (notEqual(value, next))
1400
1416
  reassign(next);
1401
1417
  };
1402
1418
  }
@@ -1415,7 +1431,12 @@ function useCompute(source, compute, suspend) {
1415
1431
  return null;
1416
1432
  React.useLayoutEffect(() => local.release, deps);
1417
1433
  return local.proxy;
1418
- }
1434
+ }
1435
+ /** Values are not equal for purposes of a refresh. */
1436
+ const notEqual = (a, b) => (b !== a && (!Array.isArray(a) ||
1437
+ !Array.isArray(b) ||
1438
+ a.length !== b.length ||
1439
+ a.some((x, i) => x !== b[i])));
1419
1440
 
1420
1441
  function useTap(source, arg1, arg2) {
1421
1442
  const instance = typeof source == "object"
@@ -1425,18 +1446,13 @@ function useTap(source, arg1, arg2) {
1425
1446
  : useContext(source);
1426
1447
  return typeof arg1 == "function"
1427
1448
  ? useCompute(instance, arg1, arg2)
1428
- : useSubscribe(instance, arg1, arg2);
1449
+ : useSubscribe(instance, arg1);
1429
1450
  }
1430
- function useSubscribe(source, path, expect) {
1451
+ function useSubscribe(source, expect) {
1431
1452
  const deps = [uid(source)];
1432
- const local = use(refresh => (control(source).subscribe(() => refresh)), deps);
1453
+ const local = use(refresh => (new Subscriber(control(source), () => refresh, expect)), deps);
1433
1454
  React.useLayoutEffect(() => local.commit(), deps);
1434
- if (path === undefined)
1435
- return local.proxy;
1436
- const value = local.proxy[path];
1437
- if (value === undefined && expect)
1438
- throw suspend(local.parent, path);
1439
- return value;
1455
+ return local.proxy;
1440
1456
  }
1441
1457
 
1442
1458
  const Global$1 = new WeakMap();
@@ -1497,7 +1513,7 @@ class MVC extends Model {
1497
1513
  const instance = this.get();
1498
1514
  return typeof arg1 == "function"
1499
1515
  ? useCompute(instance, arg1, arg2)
1500
- : useSubscribe(instance, arg1, arg2);
1516
+ : useSubscribe(instance, arg1);
1501
1517
  }
1502
1518
  static use(a, b) {
1503
1519
  return useNew(this, a, b);
@@ -1505,7 +1521,7 @@ class MVC extends Model {
1505
1521
  static meta(arg1, arg2) {
1506
1522
  return typeof arg1 == "function"
1507
1523
  ? useCompute(this, arg1, arg2)
1508
- : useSubscribe(this, arg1, arg2);
1524
+ : useSubscribe(this, arg1);
1509
1525
  }
1510
1526
  }
1511
1527
 
package/lib/index.js CHANGED
@@ -322,9 +322,10 @@ function awaitUpdate(source, arg1, arg2) {
322
322
 
323
323
  const REGISTER$1 = new WeakMap();
324
324
  class Subscriber {
325
- constructor(parent, onUpdate) {
325
+ constructor(parent, onUpdate, suspend) {
326
326
  this.parent = parent;
327
327
  this.onUpdate = onUpdate;
328
+ this.suspend = suspend;
328
329
  this.active = false;
329
330
  this.dependant = new Set();
330
331
  this.watch = new Map();
@@ -514,9 +515,13 @@ class Control {
514
515
  set: this.ref(key),
515
516
  get() {
516
517
  const sub = subscriber(this);
517
- if (sub)
518
+ const value = state.get(key);
519
+ if (sub) {
518
520
  sub.add(key);
519
- return state.get(key);
521
+ if (value === undefined && sub.suspend)
522
+ throw suspend(this, key);
523
+ }
524
+ return value;
520
525
  }
521
526
  });
522
527
  }
@@ -706,11 +711,22 @@ function add(instruction, label) {
706
711
  if (!state.has(key) && shouldSuspend)
707
712
  throw suspend(control, key);
708
713
  const local = subscriber(this);
714
+ const required = local && local.suspend;
709
715
  if (local)
710
716
  local.add(key);
711
- return onGet
712
- ? onGet(local)
713
- : state.get(key);
717
+ try {
718
+ const value = onGet
719
+ ? onGet(local)
720
+ : state.get(key);
721
+ if (value === undefined && required === true)
722
+ throw suspend(control, key);
723
+ return value;
724
+ }
725
+ catch (err) {
726
+ if (err instanceof Promise && required === false)
727
+ return;
728
+ throw err;
729
+ }
714
730
  }
715
731
  });
716
732
  }
@@ -836,7 +852,7 @@ function get(arg0, arg1, arg2) {
836
852
  let source = this;
837
853
  let setter;
838
854
  if (typeof arg0 == "function") {
839
- let result = mayRetry(arg0.bind(subject, key, subject));
855
+ const result = mayRetry(arg0.bind(subject, key, subject));
840
856
  if (typeof result == "function")
841
857
  setter = result;
842
858
  else {
@@ -1404,7 +1420,7 @@ function useCompute(source, compute, suspend) {
1404
1420
  sub.commit();
1405
1421
  update = () => {
1406
1422
  const next = make();
1407
- if (value != next)
1423
+ if (notEqual(value, next))
1408
1424
  reassign(next);
1409
1425
  };
1410
1426
  }
@@ -1423,7 +1439,12 @@ function useCompute(source, compute, suspend) {
1423
1439
  return null;
1424
1440
  React__default["default"].useLayoutEffect(() => local.release, deps);
1425
1441
  return local.proxy;
1426
- }
1442
+ }
1443
+ /** Values are not equal for purposes of a refresh. */
1444
+ const notEqual = (a, b) => (b !== a && (!Array.isArray(a) ||
1445
+ !Array.isArray(b) ||
1446
+ a.length !== b.length ||
1447
+ a.some((x, i) => x !== b[i])));
1427
1448
 
1428
1449
  function useTap(source, arg1, arg2) {
1429
1450
  const instance = typeof source == "object"
@@ -1433,18 +1454,13 @@ function useTap(source, arg1, arg2) {
1433
1454
  : useContext(source);
1434
1455
  return typeof arg1 == "function"
1435
1456
  ? useCompute(instance, arg1, arg2)
1436
- : useSubscribe(instance, arg1, arg2);
1457
+ : useSubscribe(instance, arg1);
1437
1458
  }
1438
- function useSubscribe(source, path, expect) {
1459
+ function useSubscribe(source, expect) {
1439
1460
  const deps = [uid(source)];
1440
- const local = use(refresh => (control(source).subscribe(() => refresh)), deps);
1461
+ const local = use(refresh => (new Subscriber(control(source), () => refresh, expect)), deps);
1441
1462
  React__default["default"].useLayoutEffect(() => local.commit(), deps);
1442
- if (path === undefined)
1443
- return local.proxy;
1444
- const value = local.proxy[path];
1445
- if (value === undefined && expect)
1446
- throw suspend(local.parent, path);
1447
- return value;
1463
+ return local.proxy;
1448
1464
  }
1449
1465
 
1450
1466
  const Global$1 = new WeakMap();
@@ -1505,7 +1521,7 @@ class MVC extends Model {
1505
1521
  const instance = this.get();
1506
1522
  return typeof arg1 == "function"
1507
1523
  ? useCompute(instance, arg1, arg2)
1508
- : useSubscribe(instance, arg1, arg2);
1524
+ : useSubscribe(instance, arg1);
1509
1525
  }
1510
1526
  static use(a, b) {
1511
1527
  return useNew(this, a, b);
@@ -1513,7 +1529,7 @@ class MVC extends Model {
1513
1529
  static meta(arg1, arg2) {
1514
1530
  return typeof arg1 == "function"
1515
1531
  ? useCompute(this, arg1, arg2)
1516
- : useSubscribe(this, arg1, arg2);
1532
+ : useSubscribe(this, arg1);
1517
1533
  }
1518
1534
  }
1519
1535
 
package/package.json CHANGED
@@ -49,5 +49,5 @@
49
49
  "tslib": "^2.3.0",
50
50
  "typescript": "4.6.3"
51
51
  },
52
- "version": "0.15.1"
52
+ "version": "0.16.0"
53
53
  }