@expressive/mvc 0.8.2 → 0.9.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.esm.js CHANGED
@@ -17,44 +17,13 @@ function issues(register) {
17
17
  return Library;
18
18
  }
19
19
 
20
- const Oops$d = issues({
21
- BadCallback: () => `Callback for effect-callback may only return a function.`
22
- });
23
- const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
24
- function createEffect(callback) {
25
- let unSet;
26
- return function (value) {
27
- if (typeof unSet == "function")
28
- unSet();
29
- unSet = callback.call(this, value);
30
- if (unSet instanceof Promise)
31
- unSet = undefined;
32
- if (unSet && typeof unSet !== "function")
33
- throw Oops$d.BadCallback();
34
- };
35
- }
36
- function createValueEffect(callback) {
37
- let unSet;
38
- return function (value) {
39
- if (typeof unSet == "function")
40
- unSet(value);
41
- const out = callback.call(this, value, this);
42
- if (typeof out == "boolean")
43
- return out;
44
- if (!out || out instanceof Promise)
45
- return;
46
- if (typeof out == "function")
47
- unSet = out;
48
- else
49
- throw Oops$d.BadCallback();
50
- };
51
- }
20
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
52
21
 
53
- const Oops$c = issues({
22
+ const Oops$d = issues({
54
23
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
55
24
  });
56
25
  function suspend(source, key) {
57
- const error = Oops$c.NotReady(source.subject, key);
26
+ const error = Oops$d.NotReady(source.subject, key);
58
27
  const promise = new Promise(resolve => {
59
28
  const release = source.addListener(forKey => {
60
29
  if (forKey == key)
@@ -94,7 +63,7 @@ function mayRetry(fn) {
94
63
  return compute();
95
64
  }
96
65
 
97
- const Oops$b = issues({
66
+ const Oops$c = issues({
98
67
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
99
68
  });
100
69
  const INFO = new WeakMap();
@@ -111,7 +80,7 @@ function computeMode(self, source, setter, key, required) {
111
80
  return setter.call(sub.proxy, sub.proxy);
112
81
  }
113
82
  catch (err) {
114
- Oops$b.Failed(subject, key, initial).warn();
83
+ Oops$c.Failed(subject, key, initial).warn();
115
84
  throw err;
116
85
  }
117
86
  };
@@ -212,8 +181,8 @@ class Subscriber {
212
181
  notify = this.onUpdate(key, parent);
213
182
  if (!notify)
214
183
  return;
215
- parent.waiting.add(() => {
216
- this.latest = parent.latest.filter(k => watch.has(k));
184
+ parent.waiting.add(update => {
185
+ this.latest = update.filter(k => watch.has(k));
217
186
  });
218
187
  parent.waiting.add(notify);
219
188
  };
@@ -320,13 +289,13 @@ class Control {
320
289
  else if (!frame.size)
321
290
  setTimeout(() => {
322
291
  flush(this);
323
- this.latest = Array.from(frame);
292
+ const keys = this.latest = Array.from(frame);
324
293
  setTimeout(() => this.latest = undefined, 0);
325
294
  frame.clear();
326
295
  for (const notify of waiting)
327
296
  try {
328
297
  waiting.delete(notify);
329
- notify();
298
+ notify(keys);
330
299
  }
331
300
  catch (err) {
332
301
  console.error(err);
@@ -354,18 +323,21 @@ class Control {
354
323
  if (!control.state) {
355
324
  const { waiting } = control;
356
325
  if (cb) {
357
- let done;
326
+ let callback;
358
327
  waiting.add(() => {
359
- done = cb(control);
328
+ callback = cb(control);
360
329
  });
361
- return () => done && done();
330
+ return () => {
331
+ if (callback)
332
+ callback();
333
+ };
362
334
  }
363
335
  control.state = new Map();
364
336
  for (const key in subject)
365
337
  control.watch(key);
366
338
  control.waiting = new Set();
367
339
  for (const cb of waiting)
368
- cb();
340
+ cb([]);
369
341
  }
370
342
  return cb ? cb(control) : control;
371
343
  }
@@ -481,15 +453,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
481
453
  });
482
454
  }
483
455
 
484
- const Oops$a = issues({
456
+ const Oops$b = issues({
485
457
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
486
458
  });
487
- function act(task) {
488
- return add(function act(key) {
459
+ function run(task) {
460
+ return add(function run(key) {
489
461
  let pending = false;
490
462
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
491
463
  if (pending)
492
- return Promise.reject(Oops$a.DuplicatePending(key));
464
+ return Promise.reject(Oops$b.DuplicatePending(key));
493
465
  pending = true;
494
466
  this.update(key);
495
467
  try {
@@ -511,7 +483,7 @@ function act(task) {
511
483
  });
512
484
  }
513
485
 
514
- const Oops$9 = issues({
486
+ const Oops$a = issues({
515
487
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
516
488
  });
517
489
  function factoryMode(self, output, key, required) {
@@ -535,7 +507,7 @@ function factoryMode(self, output, key, required) {
535
507
  const suspend = () => {
536
508
  if (required === false)
537
509
  return undefined;
538
- const issue = Oops$9.NotReady(subject, key);
510
+ const issue = Oops$a.NotReady(subject, key);
539
511
  assign(pending, {
540
512
  message: issue.message,
541
513
  stack: issue.stack
@@ -552,7 +524,7 @@ function factoryMode(self, output, key, required) {
552
524
  };
553
525
  }
554
526
 
555
- const Oops$8 = issues({
527
+ const Oops$9 = issues({
556
528
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
557
529
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
558
530
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
@@ -565,7 +537,7 @@ function get(arg0, arg1, arg2) {
565
537
  let getter;
566
538
  // Easy mistake, using a peer, will always be unresolved.
567
539
  if (typeof arg0 == "symbol")
568
- throw Oops$8.PeerNotAllowed(subject, key);
540
+ throw Oops$9.PeerNotAllowed(subject, key);
569
541
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
570
542
  // throw Oops.BadSource(subject, key, arg0);
571
543
  this.state.set(key, undefined);
@@ -599,7 +571,7 @@ function get(arg0, arg1, arg2) {
599
571
  init();
600
572
  }
601
573
  catch (err) {
602
- Oops$8.ComputeFailed(subject, key).warn();
574
+ Oops$9.ComputeFailed(subject, key).warn();
603
575
  throw err;
604
576
  }
605
577
  return () => {
@@ -610,6 +582,67 @@ function get(arg0, arg1, arg2) {
610
582
  });
611
583
  }
612
584
 
585
+ const Oops$8 = issues({
586
+ BadCallback: () => `Callback for effect-callback may only return a function.`
587
+ });
588
+ function createEffect(model, callback, keys) {
589
+ return Control.for(model, control => {
590
+ let unSet;
591
+ let busy = false;
592
+ function effect(value) {
593
+ if (typeof unSet == "function")
594
+ unSet();
595
+ unSet = callback.call(this, value);
596
+ if (unSet instanceof Promise)
597
+ unSet = undefined;
598
+ if (unSet && typeof unSet !== "function")
599
+ throw Oops$8.BadCallback();
600
+ }
601
+ function invoke() {
602
+ if (busy)
603
+ return;
604
+ const output = mayRetry(() => {
605
+ effect.call(model, model);
606
+ });
607
+ if (output instanceof Promise) {
608
+ output.finally(() => busy = false);
609
+ busy = true;
610
+ }
611
+ }
612
+ if (Array.isArray(keys)) {
613
+ invoke();
614
+ return control.addListener(key => {
615
+ if (key === null) {
616
+ if (!keys.length)
617
+ invoke();
618
+ }
619
+ else if (keys.includes(key))
620
+ return invoke;
621
+ });
622
+ }
623
+ const sub = control.subscribe(() => invoke);
624
+ model = sub.proxy;
625
+ invoke();
626
+ return sub.commit();
627
+ });
628
+ }
629
+ function createValueEffect(callback) {
630
+ let unSet;
631
+ return function (value) {
632
+ if (typeof unSet == "function")
633
+ unSet(value);
634
+ const out = callback.call(this, value, this);
635
+ if (typeof out == "boolean")
636
+ return out;
637
+ if (!out || out instanceof Promise)
638
+ return;
639
+ if (typeof out == "function")
640
+ unSet = out;
641
+ else
642
+ throw Oops$8.BadCallback();
643
+ };
644
+ }
645
+
613
646
  function ref(arg, mapper) {
614
647
  return add(function ref(key) {
615
648
  let value = {};
@@ -903,8 +936,10 @@ function has(Type, required) {
903
936
  }
904
937
 
905
938
  const Oops$5 = issues({
906
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
907
- StrictUpdate: () => `Strict update() did not find pending updates.`,
939
+ Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
940
+ KeysExpected: (key) => `Key "${key}" was expected in current update.`,
941
+ StrictUpdate: () => `Expected update but none in progress.`,
942
+ StrictNoUpdate: () => `Update is not expected but one is in progress!`,
908
943
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
909
944
  });
910
945
  class Model {
@@ -912,91 +947,84 @@ class Model {
912
947
  new Control(this);
913
948
  defineProperty(this, "is", { value: this });
914
949
  }
915
- on(select, argument, once) {
916
- if (typeof select == "function")
917
- return Control.for(this, x => x.addListener(select));
918
- const single = typeof select == "string";
919
- let keys = single ? [select] : select;
920
- if (typeof argument !== "function")
921
- return new Promise((resolve, reject) => {
922
- const removeListener = Control.for(this, control => {
923
- for (const key of keys)
924
- try {
925
- void this[key];
926
- }
927
- catch (e) { }
928
- return control.addListener(key => {
929
- if (!key) {
930
- removeListener();
931
- reject(Oops$5.Timeout(select, `lifetime of ${this}`));
932
- }
933
- else if (keys.includes(key)) {
950
+ on(arg1, arg2, arg3) {
951
+ if (typeof arg1 == "function")
952
+ return createEffect(this.is, arg1, arg2);
953
+ const single = typeof arg1 == "string";
954
+ const keys = typeof arg1 == "string" ? [arg1] :
955
+ typeof arg1 == "object" ? arg1 :
956
+ undefined;
957
+ if (typeof arg2 == "function" && keys)
958
+ return Control.for(this, control => {
959
+ for (const key of keys)
960
+ try {
961
+ void this[key];
962
+ }
963
+ catch (e) { }
964
+ const invoke = single
965
+ ? () => arg2.call(this, control.state.get(arg1), arg1)
966
+ : (frame) => arg2.call(this, frame);
967
+ const removeListener = control.addListener(key => {
968
+ if (!key && !keys.length)
969
+ invoke([]);
970
+ if (keys.includes(key)) {
971
+ if (arg3)
934
972
  removeListener();
935
- return () => resolve(single ? control.state.get(key) : control.latest);
936
- }
937
- });
973
+ return invoke;
974
+ }
938
975
  });
939
- if (typeof argument == "number")
940
- setTimeout(() => {
941
- removeListener();
942
- reject(Oops$5.Timeout(select, `${argument}ms`));
943
- }, argument);
976
+ return removeListener;
944
977
  });
945
- return Control.for(this, control => {
946
- if (!keys.length)
947
- keys = [...control.state.keys()];
948
- for (const key of keys)
949
- try {
950
- void this[key];
978
+ return new Promise((resolve, reject) => {
979
+ const timeout = (message) => {
980
+ const k = keys ? keys.join(", ") : "any";
981
+ removeListener();
982
+ reject(Oops$5.Timeout(k, message));
983
+ };
984
+ const removeListener = Control.for(this, control => {
985
+ const pending = control.frame;
986
+ if (keys === null) {
987
+ if (pending.size)
988
+ reject(Oops$5.StrictNoUpdate());
989
+ else
990
+ resolve(null);
991
+ return;
951
992
  }
952
- catch (e) { }
953
- const callback = single
954
- ? () => { argument.call(this, control.state.get(select), select); }
955
- : () => { argument.call(this, control.latest); };
956
- const onEvent = once
957
- ? () => {
958
- removeListener();
959
- callback();
993
+ if (keys) {
994
+ if (keys.length) {
995
+ if (arg2 === 0 && single && !pending.has(keys[0])) {
996
+ reject(Oops$5.KeysExpected(keys[0]));
997
+ return;
998
+ }
999
+ else
1000
+ for (const key of keys)
1001
+ try {
1002
+ void this[key];
1003
+ }
1004
+ catch (e) { }
1005
+ }
960
1006
  }
961
- : callback;
962
- const removeListener = control.addListener(key => {
963
- if (keys.includes(key))
964
- return onEvent;
965
- });
966
- return removeListener;
967
- });
968
- }
969
- effect(callback, select) {
970
- const effect = createEffect(callback);
971
- return Control.for(this, control => {
972
- let busy = false;
973
- let inject = this.is;
974
- const invoke = () => {
975
- if (busy)
1007
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1008
+ reject(Oops$5.StrictUpdate());
976
1009
  return;
977
- const output = mayRetry(() => {
978
- effect.call(inject, inject);
979
- });
980
- if (output instanceof Promise) {
981
- output.finally(() => busy = false);
982
- busy = true;
983
1010
  }
984
- };
985
- if (!select) {
986
- const sub = control.subscribe(() => invoke);
987
- inject = sub.proxy;
988
- invoke();
989
- return sub.commit();
990
- }
991
- invoke();
992
- return control.addListener(key => {
993
- if (key === null) {
994
- if (!select.length)
995
- invoke();
996
- }
997
- else if (select.includes(key))
998
- return invoke;
1011
+ else
1012
+ control.waiting.add(resolve);
1013
+ return control.addListener(key => {
1014
+ if (!key) {
1015
+ if (keys && !keys.length)
1016
+ resolve([]);
1017
+ else
1018
+ timeout(`lifetime of ${this}`);
1019
+ }
1020
+ else if (!keys || keys.includes(key)) {
1021
+ removeListener();
1022
+ return keys => resolve(single ? control.state.get(key) : keys);
1023
+ }
1024
+ });
999
1025
  });
1026
+ if (arg2 && typeof arg2 == "number")
1027
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1000
1028
  });
1001
1029
  }
1002
1030
  export(subset) {
@@ -1009,13 +1037,10 @@ class Model {
1009
1037
  }
1010
1038
  update(arg1, arg2) {
1011
1039
  const control = Control.for(this);
1012
- const { frame, state, waiting } = control;
1013
- if (arg1 === true && !frame.size)
1014
- return Promise.reject(Oops$5.StrictUpdate());
1015
1040
  if (typeof arg1 == "object") {
1016
1041
  for (const key in arg1)
1017
- if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1018
- state.set(key, arg1[key]);
1042
+ if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1043
+ control.state.set(key, arg1[key]);
1019
1044
  control.update(key);
1020
1045
  }
1021
1046
  }
@@ -1029,25 +1054,19 @@ class Model {
1029
1054
  else if (arg2)
1030
1055
  method.call(this);
1031
1056
  }
1032
- arg1 = undefined;
1033
1057
  }
1034
1058
  return {
1035
1059
  then: (callback) => {
1036
1060
  if (!callback)
1037
1061
  throw Oops$5.NoChaining();
1038
- if (frame.size || arg1 !== false)
1039
- waiting.add(() => {
1040
- callback(control.latest);
1041
- });
1042
- else
1043
- callback(false);
1062
+ control.waiting.add(callback);
1044
1063
  }
1045
1064
  };
1046
1065
  }
1047
1066
  /**
1048
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
1067
+ * Clean up side effects and mark this instance for garbage-collection.
1049
1068
  */
1050
- kill() {
1069
+ destroy() {
1051
1070
  Control.for(this).clear();
1052
1071
  }
1053
1072
  /**
@@ -1124,7 +1143,7 @@ class Lookup {
1124
1143
  items.add(entry.value);
1125
1144
  }
1126
1145
  for (const model of items)
1127
- model.kill();
1146
+ model.destroy();
1128
1147
  }
1129
1148
  }
1130
1149
  const LookupContext = React.createContext(new Lookup());
@@ -1224,7 +1243,7 @@ function useModel(source, arg, arg2) {
1224
1243
  instance.on(arg, () => update(x => x + 1));
1225
1244
  return () => {
1226
1245
  if (Model.isTypeof(source))
1227
- instance.kill();
1246
+ instance.destroy();
1228
1247
  };
1229
1248
  }, []);
1230
1249
  return instance;
@@ -1237,15 +1256,15 @@ function useModel(source, arg, arg2) {
1237
1256
  return () => {
1238
1257
  local.release();
1239
1258
  if (Model.isTypeof(source))
1240
- instance.kill();
1259
+ instance.destroy();
1241
1260
  };
1242
1261
  }, []);
1243
1262
  return local.proxy;
1244
1263
  }
1245
1264
 
1246
- function useFrom(target, compute, suspend) {
1265
+ function useValue(source, compute, suspend) {
1247
1266
  const local = use(refresh => {
1248
- const sub = Control.for(target).subscribe(() => update);
1267
+ const sub = Control.for(source).subscribe(() => update);
1249
1268
  const spy = sub.proxy;
1250
1269
  let value = compute.call(spy, spy);
1251
1270
  let update;
@@ -1295,7 +1314,7 @@ function useTap(source, arg1, arg2) {
1295
1314
  return source;
1296
1315
  }, [])();
1297
1316
  if (typeof arg1 == "function")
1298
- return useFrom(instance, arg1, arg2);
1317
+ return useValue(instance, arg1, arg2);
1299
1318
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1300
1319
  React.useLayoutEffect(local.commit, []);
1301
1320
  if (arg1 === undefined)
@@ -1325,7 +1344,7 @@ const LOCAL = "$debug local";
1325
1344
  const STATE = "$debug state";
1326
1345
  const UPDATE = "$debug update";
1327
1346
  const CONTROL = "$debug controller";
1328
- Object.defineProperties(Model.prototype, {
1347
+ defineProperties(Model.prototype, {
1329
1348
  [CONTROL]: {
1330
1349
  get() {
1331
1350
  return Control.get(this);
@@ -1444,11 +1463,11 @@ class Global extends MVC {
1444
1463
  super();
1445
1464
  Active.set(this.constructor, this);
1446
1465
  }
1447
- kill(force) {
1466
+ destroy(force) {
1448
1467
  const type = this.constructor;
1449
1468
  if (type.keepAlive && !force)
1450
1469
  return false;
1451
- super.kill();
1470
+ Control.for(this).clear();
1452
1471
  Active.delete(type);
1453
1472
  return true;
1454
1473
  }
@@ -1482,10 +1501,10 @@ class Global extends MVC {
1482
1501
  static reset() {
1483
1502
  const current = Active.get(this);
1484
1503
  if (current)
1485
- current.kill(true);
1504
+ current.destroy(true);
1486
1505
  }
1487
1506
  }
1488
1507
  Global.keepAlive = true;
1489
1508
 
1490
- export { Consumer, Debug, Global, MVC, MVC as Model, Provider, act, add, MVC as default, get, has, ref, set, tap, use$1 as use, useContext, useFrom, useModel, useTap };
1509
+ export { Consumer, Debug, Global, MVC, MVC as Model, Provider, add, MVC as default, get, has, ref, run, set, tap, use$1 as use, useContext, useModel, useTap, useValue };
1491
1510
  //# sourceMappingURL=index.esm.js.map