@expressive/mvc 0.8.1 → 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.js CHANGED
@@ -25,44 +25,13 @@ function issues(register) {
25
25
  return Library;
26
26
  }
27
27
 
28
- const Oops$c = issues({
29
- BadCallback: () => `Callback for effect-callback may only return a function.`
30
- });
31
- const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
32
- function createEffect(callback) {
33
- let unSet;
34
- return function (value) {
35
- if (typeof unSet == "function")
36
- unSet();
37
- unSet = callback.call(this, value);
38
- if (unSet instanceof Promise)
39
- unSet = undefined;
40
- if (unSet && typeof unSet !== "function")
41
- throw Oops$c.BadCallback();
42
- };
43
- }
44
- function createValueEffect(callback) {
45
- let unSet;
46
- return function (value) {
47
- if (typeof unSet == "function")
48
- unSet(value);
49
- const out = callback.call(this, value, this);
50
- if (typeof out == "boolean")
51
- return out;
52
- if (!out || out instanceof Promise)
53
- return;
54
- if (typeof out == "function")
55
- unSet = out;
56
- else
57
- throw Oops$c.BadCallback();
58
- };
59
- }
28
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
60
29
 
61
- const Oops$b = issues({
30
+ const Oops$d = issues({
62
31
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
63
32
  });
64
33
  function suspend(source, key) {
65
- const error = Oops$b.NotReady(source.subject, key);
34
+ const error = Oops$d.NotReady(source.subject, key);
66
35
  const promise = new Promise(resolve => {
67
36
  const release = source.addListener(forKey => {
68
37
  if (forKey == key)
@@ -102,7 +71,7 @@ function mayRetry(fn) {
102
71
  return compute();
103
72
  }
104
73
 
105
- const Oops$a = issues({
74
+ const Oops$c = issues({
106
75
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
107
76
  });
108
77
  const INFO = new WeakMap();
@@ -119,7 +88,7 @@ function computeMode(self, source, setter, key, required) {
119
88
  return setter.call(sub.proxy, sub.proxy);
120
89
  }
121
90
  catch (err) {
122
- Oops$a.Failed(subject, key, initial).warn();
91
+ Oops$c.Failed(subject, key, initial).warn();
123
92
  throw err;
124
93
  }
125
94
  };
@@ -220,8 +189,8 @@ class Subscriber {
220
189
  notify = this.onUpdate(key, parent);
221
190
  if (!notify)
222
191
  return;
223
- parent.waiting.add(() => {
224
- this.latest = parent.latest.filter(k => watch.has(k));
192
+ parent.waiting.add(update => {
193
+ this.latest = update.filter(k => watch.has(k));
225
194
  });
226
195
  parent.waiting.add(notify);
227
196
  };
@@ -261,6 +230,7 @@ class Subscriber {
261
230
  }
262
231
 
263
232
  const REGISTER = new WeakMap();
233
+ const PENDING = new Map();
264
234
  class Control {
265
235
  constructor(subject) {
266
236
  this.subject = subject;
@@ -327,13 +297,13 @@ class Control {
327
297
  else if (!frame.size)
328
298
  setTimeout(() => {
329
299
  flush(this);
330
- this.latest = Array.from(frame);
300
+ const keys = this.latest = Array.from(frame);
331
301
  setTimeout(() => this.latest = undefined, 0);
332
302
  frame.clear();
333
303
  for (const notify of waiting)
334
304
  try {
335
305
  waiting.delete(notify);
336
- notify();
306
+ notify(keys);
337
307
  }
338
308
  catch (err) {
339
309
  console.error(err);
@@ -361,24 +331,26 @@ class Control {
361
331
  if (!control.state) {
362
332
  const { waiting } = control;
363
333
  if (cb) {
364
- let done;
334
+ let callback;
365
335
  waiting.add(() => {
366
- done = cb(control);
336
+ callback = cb(control);
367
337
  });
368
- return () => done && done();
338
+ return () => {
339
+ if (callback)
340
+ callback();
341
+ };
369
342
  }
370
343
  control.state = new Map();
371
344
  for (const key in subject)
372
345
  control.watch(key);
373
346
  control.waiting = new Set();
374
347
  for (const cb of waiting)
375
- cb();
348
+ cb([]);
376
349
  }
377
350
  return cb ? cb(control) : control;
378
351
  }
379
352
  }
380
353
 
381
- const PENDING = new Map();
382
354
  /**
383
355
  * Run instruction as controller sets itself up.
384
356
  * This will specialize the behavior of a given property.
@@ -489,15 +461,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
489
461
  });
490
462
  }
491
463
 
492
- const Oops$9 = issues({
464
+ const Oops$b = issues({
493
465
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
494
466
  });
495
- function act(task) {
496
- return add(function act(key) {
467
+ function run(task) {
468
+ return add(function run(key) {
497
469
  let pending = false;
498
470
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
499
471
  if (pending)
500
- return Promise.reject(Oops$9.DuplicatePending(key));
472
+ return Promise.reject(Oops$b.DuplicatePending(key));
501
473
  pending = true;
502
474
  this.update(key);
503
475
  try {
@@ -519,6 +491,9 @@ function act(task) {
519
491
  });
520
492
  }
521
493
 
494
+ const Oops$a = issues({
495
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
496
+ });
522
497
  function factoryMode(self, output, key, required) {
523
498
  const { subject, state } = self;
524
499
  let pending;
@@ -540,7 +515,7 @@ function factoryMode(self, output, key, required) {
540
515
  const suspend = () => {
541
516
  if (required === false)
542
517
  return undefined;
543
- const issue = Oops$8.NotReady(subject, key);
518
+ const issue = Oops$a.NotReady(subject, key);
544
519
  assign(pending, {
545
520
  message: issue.message,
546
521
  stack: issue.stack
@@ -557,13 +532,9 @@ function factoryMode(self, output, key, required) {
557
532
  };
558
533
  }
559
534
 
560
- const Oops$8 = issues({
561
- // BadSource: (model, property, got) =>
562
- // `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
535
+ const Oops$9 = issues({
563
536
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
564
537
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
565
- NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
566
- FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
567
538
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
568
539
  });
569
540
  function get(arg0, arg1, arg2) {
@@ -574,7 +545,7 @@ function get(arg0, arg1, arg2) {
574
545
  let getter;
575
546
  // Easy mistake, using a peer, will always be unresolved.
576
547
  if (typeof arg0 == "symbol")
577
- throw Oops$8.PeerNotAllowed(subject, key);
548
+ throw Oops$9.PeerNotAllowed(subject, key);
578
549
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
579
550
  // throw Oops.BadSource(subject, key, arg0);
580
551
  this.state.set(key, undefined);
@@ -608,7 +579,7 @@ function get(arg0, arg1, arg2) {
608
579
  init();
609
580
  }
610
581
  catch (err) {
611
- Oops$8.ComputeFailed(subject, key).warn();
582
+ Oops$9.ComputeFailed(subject, key).warn();
612
583
  throw err;
613
584
  }
614
585
  return () => {
@@ -619,6 +590,67 @@ function get(arg0, arg1, arg2) {
619
590
  });
620
591
  }
621
592
 
593
+ const Oops$8 = issues({
594
+ BadCallback: () => `Callback for effect-callback may only return a function.`
595
+ });
596
+ function createEffect(model, callback, keys) {
597
+ return Control.for(model, control => {
598
+ let unSet;
599
+ let busy = false;
600
+ function effect(value) {
601
+ if (typeof unSet == "function")
602
+ unSet();
603
+ unSet = callback.call(this, value);
604
+ if (unSet instanceof Promise)
605
+ unSet = undefined;
606
+ if (unSet && typeof unSet !== "function")
607
+ throw Oops$8.BadCallback();
608
+ }
609
+ function invoke() {
610
+ if (busy)
611
+ return;
612
+ const output = mayRetry(() => {
613
+ effect.call(model, model);
614
+ });
615
+ if (output instanceof Promise) {
616
+ output.finally(() => busy = false);
617
+ busy = true;
618
+ }
619
+ }
620
+ if (Array.isArray(keys)) {
621
+ invoke();
622
+ return control.addListener(key => {
623
+ if (key === null) {
624
+ if (!keys.length)
625
+ invoke();
626
+ }
627
+ else if (keys.includes(key))
628
+ return invoke;
629
+ });
630
+ }
631
+ const sub = control.subscribe(() => invoke);
632
+ model = sub.proxy;
633
+ invoke();
634
+ return sub.commit();
635
+ });
636
+ }
637
+ function createValueEffect(callback) {
638
+ let unSet;
639
+ return function (value) {
640
+ if (typeof unSet == "function")
641
+ unSet(value);
642
+ const out = callback.call(this, value, this);
643
+ if (typeof out == "boolean")
644
+ return out;
645
+ if (!out || out instanceof Promise)
646
+ return;
647
+ if (typeof out == "function")
648
+ unSet = out;
649
+ else
650
+ throw Oops$8.BadCallback();
651
+ };
652
+ }
653
+
622
654
  function ref(arg, mapper) {
623
655
  return add(function ref(key) {
624
656
  let value = {};
@@ -912,8 +944,10 @@ function has(Type, required) {
912
944
  }
913
945
 
914
946
  const Oops$5 = issues({
915
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
916
- StrictUpdate: () => `Strict update() did not find pending updates.`,
947
+ Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
948
+ KeysExpected: (key) => `Key "${key}" was expected in current update.`,
949
+ StrictUpdate: () => `Expected update but none in progress.`,
950
+ StrictNoUpdate: () => `Update is not expected but one is in progress!`,
917
951
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
918
952
  });
919
953
  class Model {
@@ -921,91 +955,84 @@ class Model {
921
955
  new Control(this);
922
956
  defineProperty(this, "is", { value: this });
923
957
  }
924
- on(select, argument, once) {
925
- if (typeof select == "function")
926
- return Control.for(this, x => x.addListener(select));
927
- const single = typeof select == "string";
928
- let keys = single ? [select] : select;
929
- if (typeof argument !== "function")
930
- return new Promise((resolve, reject) => {
931
- const removeListener = Control.for(this, control => {
932
- for (const key of keys)
933
- try {
934
- void this[key];
935
- }
936
- catch (e) { }
937
- return control.addListener(key => {
938
- if (!key) {
939
- removeListener();
940
- reject(Oops$5.Timeout(select, `lifetime of ${this}`));
941
- }
942
- else if (keys.includes(key)) {
958
+ on(arg1, arg2, arg3) {
959
+ if (typeof arg1 == "function")
960
+ return createEffect(this.is, arg1, arg2);
961
+ const single = typeof arg1 == "string";
962
+ const keys = typeof arg1 == "string" ? [arg1] :
963
+ typeof arg1 == "object" ? arg1 :
964
+ undefined;
965
+ if (typeof arg2 == "function" && keys)
966
+ return Control.for(this, control => {
967
+ for (const key of keys)
968
+ try {
969
+ void this[key];
970
+ }
971
+ catch (e) { }
972
+ const invoke = single
973
+ ? () => arg2.call(this, control.state.get(arg1), arg1)
974
+ : (frame) => arg2.call(this, frame);
975
+ const removeListener = control.addListener(key => {
976
+ if (!key && !keys.length)
977
+ invoke([]);
978
+ if (keys.includes(key)) {
979
+ if (arg3)
943
980
  removeListener();
944
- return () => resolve(single ? control.state.get(key) : control.latest);
945
- }
946
- });
981
+ return invoke;
982
+ }
947
983
  });
948
- if (typeof argument == "number")
949
- setTimeout(() => {
950
- removeListener();
951
- reject(Oops$5.Timeout(select, `${argument}ms`));
952
- }, argument);
984
+ return removeListener;
953
985
  });
954
- return Control.for(this, control => {
955
- if (!keys.length)
956
- keys = [...control.state.keys()];
957
- for (const key of keys)
958
- try {
959
- void this[key];
986
+ return new Promise((resolve, reject) => {
987
+ const timeout = (message) => {
988
+ const k = keys ? keys.join(", ") : "any";
989
+ removeListener();
990
+ reject(Oops$5.Timeout(k, message));
991
+ };
992
+ const removeListener = Control.for(this, control => {
993
+ const pending = control.frame;
994
+ if (keys === null) {
995
+ if (pending.size)
996
+ reject(Oops$5.StrictNoUpdate());
997
+ else
998
+ resolve(null);
999
+ return;
960
1000
  }
961
- catch (e) { }
962
- const callback = single
963
- ? () => { argument.call(this, control.state.get(select), select); }
964
- : () => { argument.call(this, control.latest); };
965
- const onEvent = once
966
- ? () => {
967
- removeListener();
968
- callback();
1001
+ if (keys) {
1002
+ if (keys.length) {
1003
+ if (arg2 === 0 && single && !pending.has(keys[0])) {
1004
+ reject(Oops$5.KeysExpected(keys[0]));
1005
+ return;
1006
+ }
1007
+ else
1008
+ for (const key of keys)
1009
+ try {
1010
+ void this[key];
1011
+ }
1012
+ catch (e) { }
1013
+ }
969
1014
  }
970
- : callback;
971
- const removeListener = control.addListener(key => {
972
- if (keys.includes(key))
973
- return onEvent;
974
- });
975
- return removeListener;
976
- });
977
- }
978
- effect(callback, select) {
979
- const effect = createEffect(callback);
980
- return Control.for(this, control => {
981
- let busy = false;
982
- let inject = this.is;
983
- const invoke = () => {
984
- if (busy)
1015
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1016
+ reject(Oops$5.StrictUpdate());
985
1017
  return;
986
- const output = mayRetry(() => {
987
- effect.call(inject, inject);
988
- });
989
- if (output instanceof Promise) {
990
- output.finally(() => busy = false);
991
- busy = true;
992
- }
993
- };
994
- if (!select) {
995
- const sub = control.subscribe(() => invoke);
996
- inject = sub.proxy;
997
- invoke();
998
- return sub.commit();
999
- }
1000
- invoke();
1001
- return control.addListener(key => {
1002
- if (key === null) {
1003
- if (!select.length)
1004
- invoke();
1005
1018
  }
1006
- else if (select.includes(key))
1007
- return invoke;
1019
+ else
1020
+ control.waiting.add(resolve);
1021
+ return control.addListener(key => {
1022
+ if (!key) {
1023
+ if (keys && !keys.length)
1024
+ resolve([]);
1025
+ else
1026
+ timeout(`lifetime of ${this}`);
1027
+ }
1028
+ else if (!keys || keys.includes(key)) {
1029
+ removeListener();
1030
+ return keys => resolve(single ? control.state.get(key) : keys);
1031
+ }
1032
+ });
1008
1033
  });
1034
+ if (arg2 && typeof arg2 == "number")
1035
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1009
1036
  });
1010
1037
  }
1011
1038
  export(subset) {
@@ -1018,13 +1045,10 @@ class Model {
1018
1045
  }
1019
1046
  update(arg1, arg2) {
1020
1047
  const control = Control.for(this);
1021
- const { frame, state, waiting } = control;
1022
- if (arg1 === true && !frame.size)
1023
- return Promise.reject(Oops$5.StrictUpdate());
1024
1048
  if (typeof arg1 == "object") {
1025
1049
  for (const key in arg1)
1026
- if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1027
- state.set(key, arg1[key]);
1050
+ if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1051
+ control.state.set(key, arg1[key]);
1028
1052
  control.update(key);
1029
1053
  }
1030
1054
  }
@@ -1038,25 +1062,19 @@ class Model {
1038
1062
  else if (arg2)
1039
1063
  method.call(this);
1040
1064
  }
1041
- arg1 = undefined;
1042
1065
  }
1043
1066
  return {
1044
1067
  then: (callback) => {
1045
1068
  if (!callback)
1046
1069
  throw Oops$5.NoChaining();
1047
- if (frame.size || arg1 !== false)
1048
- waiting.add(() => {
1049
- callback(control.latest);
1050
- });
1051
- else
1052
- callback(false);
1070
+ control.waiting.add(callback);
1053
1071
  }
1054
1072
  };
1055
1073
  }
1056
1074
  /**
1057
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
1075
+ * Clean up side effects and mark this instance for garbage-collection.
1058
1076
  */
1059
- kill() {
1077
+ destroy() {
1060
1078
  Control.for(this).clear();
1061
1079
  }
1062
1080
  /**
@@ -1133,7 +1151,7 @@ class Lookup {
1133
1151
  items.add(entry.value);
1134
1152
  }
1135
1153
  for (const model of items)
1136
- model.kill();
1154
+ model.destroy();
1137
1155
  }
1138
1156
  }
1139
1157
  const LookupContext = React__default["default"].createContext(new Lookup());
@@ -1233,7 +1251,7 @@ function useModel(source, arg, arg2) {
1233
1251
  instance.on(arg, () => update(x => x + 1));
1234
1252
  return () => {
1235
1253
  if (Model.isTypeof(source))
1236
- instance.kill();
1254
+ instance.destroy();
1237
1255
  };
1238
1256
  }, []);
1239
1257
  return instance;
@@ -1246,15 +1264,15 @@ function useModel(source, arg, arg2) {
1246
1264
  return () => {
1247
1265
  local.release();
1248
1266
  if (Model.isTypeof(source))
1249
- instance.kill();
1267
+ instance.destroy();
1250
1268
  };
1251
1269
  }, []);
1252
1270
  return local.proxy;
1253
1271
  }
1254
1272
 
1255
- function useFrom(target, compute, suspend) {
1273
+ function useValue(source, compute, suspend) {
1256
1274
  const local = use(refresh => {
1257
- const sub = Control.for(target).subscribe(() => update);
1275
+ const sub = Control.for(source).subscribe(() => update);
1258
1276
  const spy = sub.proxy;
1259
1277
  let value = compute.call(spy, spy);
1260
1278
  let update;
@@ -1297,14 +1315,14 @@ function useTap(source, arg1, arg2) {
1297
1315
  const instance = React.useMemo(() => {
1298
1316
  if (typeof source == "object")
1299
1317
  return () => source;
1300
- if (MVC.isTypeof(source))
1301
- return () => source.get(true);
1302
1318
  if (Model.isTypeof(source))
1303
- return () => useContext(source);
1319
+ return "get" in source
1320
+ ? () => source.get(true)
1321
+ : () => useContext(source);
1304
1322
  return source;
1305
1323
  }, [])();
1306
1324
  if (typeof arg1 == "function")
1307
- return useFrom(instance, arg1, arg2);
1325
+ return useValue(instance, arg1, arg2);
1308
1326
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1309
1327
  React__default["default"].useLayoutEffect(local.commit, []);
1310
1328
  if (arg1 === undefined)
@@ -1334,7 +1352,7 @@ const LOCAL = "$debug local";
1334
1352
  const STATE = "$debug state";
1335
1353
  const UPDATE = "$debug update";
1336
1354
  const CONTROL = "$debug controller";
1337
- Object.defineProperties(Model.prototype, {
1355
+ defineProperties(Model.prototype, {
1338
1356
  [CONTROL]: {
1339
1357
  get() {
1340
1358
  return Control.get(this);
@@ -1453,11 +1471,11 @@ class Global extends MVC {
1453
1471
  super();
1454
1472
  Active.set(this.constructor, this);
1455
1473
  }
1456
- kill(force) {
1474
+ destroy(force) {
1457
1475
  const type = this.constructor;
1458
1476
  if (type.keepAlive && !force)
1459
1477
  return false;
1460
- super.kill();
1478
+ Control.for(this).clear();
1461
1479
  Active.delete(type);
1462
1480
  return true;
1463
1481
  }
@@ -1491,7 +1509,7 @@ class Global extends MVC {
1491
1509
  static reset() {
1492
1510
  const current = Active.get(this);
1493
1511
  if (current)
1494
- current.kill(true);
1512
+ current.destroy(true);
1495
1513
  }
1496
1514
  }
1497
1515
  Global.keepAlive = true;
@@ -1502,17 +1520,17 @@ exports.Global = Global;
1502
1520
  exports.MVC = MVC;
1503
1521
  exports.Model = MVC;
1504
1522
  exports.Provider = Provider;
1505
- exports.act = act;
1506
1523
  exports.add = add;
1507
1524
  exports["default"] = MVC;
1508
1525
  exports.get = get;
1509
1526
  exports.has = has;
1510
1527
  exports.ref = ref;
1528
+ exports.run = run;
1511
1529
  exports.set = set;
1512
1530
  exports.tap = tap;
1513
1531
  exports.use = use$1;
1514
1532
  exports.useContext = useContext;
1515
- exports.useFrom = useFrom;
1516
1533
  exports.useModel = useModel;
1517
1534
  exports.useTap = useTap;
1535
+ exports.useValue = useValue;
1518
1536
  //# sourceMappingURL=index.js.map