@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.esm.js CHANGED
@@ -17,44 +17,13 @@ function issues(register) {
17
17
  return Library;
18
18
  }
19
19
 
20
- const Oops$c = 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$c.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$c.BadCallback();
50
- };
51
- }
20
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
52
21
 
53
- const Oops$b = 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$b.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$a = 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$a.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
  };
@@ -253,6 +222,7 @@ class Subscriber {
253
222
  }
254
223
 
255
224
  const REGISTER = new WeakMap();
225
+ const PENDING = new Map();
256
226
  class Control {
257
227
  constructor(subject) {
258
228
  this.subject = subject;
@@ -319,13 +289,13 @@ class Control {
319
289
  else if (!frame.size)
320
290
  setTimeout(() => {
321
291
  flush(this);
322
- this.latest = Array.from(frame);
292
+ const keys = this.latest = Array.from(frame);
323
293
  setTimeout(() => this.latest = undefined, 0);
324
294
  frame.clear();
325
295
  for (const notify of waiting)
326
296
  try {
327
297
  waiting.delete(notify);
328
- notify();
298
+ notify(keys);
329
299
  }
330
300
  catch (err) {
331
301
  console.error(err);
@@ -353,24 +323,26 @@ class Control {
353
323
  if (!control.state) {
354
324
  const { waiting } = control;
355
325
  if (cb) {
356
- let done;
326
+ let callback;
357
327
  waiting.add(() => {
358
- done = cb(control);
328
+ callback = cb(control);
359
329
  });
360
- return () => done && done();
330
+ return () => {
331
+ if (callback)
332
+ callback();
333
+ };
361
334
  }
362
335
  control.state = new Map();
363
336
  for (const key in subject)
364
337
  control.watch(key);
365
338
  control.waiting = new Set();
366
339
  for (const cb of waiting)
367
- cb();
340
+ cb([]);
368
341
  }
369
342
  return cb ? cb(control) : control;
370
343
  }
371
344
  }
372
345
 
373
- const PENDING = new Map();
374
346
  /**
375
347
  * Run instruction as controller sets itself up.
376
348
  * This will specialize the behavior of a given property.
@@ -481,15 +453,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
481
453
  });
482
454
  }
483
455
 
484
- const Oops$9 = 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$9.DuplicatePending(key));
464
+ return Promise.reject(Oops$b.DuplicatePending(key));
493
465
  pending = true;
494
466
  this.update(key);
495
467
  try {
@@ -511,6 +483,9 @@ function act(task) {
511
483
  });
512
484
  }
513
485
 
486
+ const Oops$a = issues({
487
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
488
+ });
514
489
  function factoryMode(self, output, key, required) {
515
490
  const { subject, state } = self;
516
491
  let pending;
@@ -532,7 +507,7 @@ function factoryMode(self, output, key, required) {
532
507
  const suspend = () => {
533
508
  if (required === false)
534
509
  return undefined;
535
- const issue = Oops$8.NotReady(subject, key);
510
+ const issue = Oops$a.NotReady(subject, key);
536
511
  assign(pending, {
537
512
  message: issue.message,
538
513
  stack: issue.stack
@@ -549,13 +524,9 @@ function factoryMode(self, output, key, required) {
549
524
  };
550
525
  }
551
526
 
552
- const Oops$8 = issues({
553
- // BadSource: (model, property, got) =>
554
- // `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
527
+ const Oops$9 = issues({
555
528
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
556
529
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
557
- NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
558
- FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
559
530
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
560
531
  });
561
532
  function get(arg0, arg1, arg2) {
@@ -566,7 +537,7 @@ function get(arg0, arg1, arg2) {
566
537
  let getter;
567
538
  // Easy mistake, using a peer, will always be unresolved.
568
539
  if (typeof arg0 == "symbol")
569
- throw Oops$8.PeerNotAllowed(subject, key);
540
+ throw Oops$9.PeerNotAllowed(subject, key);
570
541
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
571
542
  // throw Oops.BadSource(subject, key, arg0);
572
543
  this.state.set(key, undefined);
@@ -600,7 +571,7 @@ function get(arg0, arg1, arg2) {
600
571
  init();
601
572
  }
602
573
  catch (err) {
603
- Oops$8.ComputeFailed(subject, key).warn();
574
+ Oops$9.ComputeFailed(subject, key).warn();
604
575
  throw err;
605
576
  }
606
577
  return () => {
@@ -611,6 +582,67 @@ function get(arg0, arg1, arg2) {
611
582
  });
612
583
  }
613
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
+
614
646
  function ref(arg, mapper) {
615
647
  return add(function ref(key) {
616
648
  let value = {};
@@ -904,8 +936,10 @@ function has(Type, required) {
904
936
  }
905
937
 
906
938
  const Oops$5 = issues({
907
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
908
- 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!`,
909
943
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
910
944
  });
911
945
  class Model {
@@ -913,91 +947,84 @@ class Model {
913
947
  new Control(this);
914
948
  defineProperty(this, "is", { value: this });
915
949
  }
916
- on(select, argument, once) {
917
- if (typeof select == "function")
918
- return Control.for(this, x => x.addListener(select));
919
- const single = typeof select == "string";
920
- let keys = single ? [select] : select;
921
- if (typeof argument !== "function")
922
- return new Promise((resolve, reject) => {
923
- const removeListener = Control.for(this, control => {
924
- for (const key of keys)
925
- try {
926
- void this[key];
927
- }
928
- catch (e) { }
929
- return control.addListener(key => {
930
- if (!key) {
931
- removeListener();
932
- reject(Oops$5.Timeout(select, `lifetime of ${this}`));
933
- }
934
- 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)
935
972
  removeListener();
936
- return () => resolve(single ? control.state.get(key) : control.latest);
937
- }
938
- });
973
+ return invoke;
974
+ }
939
975
  });
940
- if (typeof argument == "number")
941
- setTimeout(() => {
942
- removeListener();
943
- reject(Oops$5.Timeout(select, `${argument}ms`));
944
- }, argument);
976
+ return removeListener;
945
977
  });
946
- return Control.for(this, control => {
947
- if (!keys.length)
948
- keys = [...control.state.keys()];
949
- for (const key of keys)
950
- try {
951
- 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;
952
992
  }
953
- catch (e) { }
954
- const callback = single
955
- ? () => { argument.call(this, control.state.get(select), select); }
956
- : () => { argument.call(this, control.latest); };
957
- const onEvent = once
958
- ? () => {
959
- removeListener();
960
- 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
+ }
961
1006
  }
962
- : callback;
963
- const removeListener = control.addListener(key => {
964
- if (keys.includes(key))
965
- return onEvent;
966
- });
967
- return removeListener;
968
- });
969
- }
970
- effect(callback, select) {
971
- const effect = createEffect(callback);
972
- return Control.for(this, control => {
973
- let busy = false;
974
- let inject = this.is;
975
- const invoke = () => {
976
- if (busy)
1007
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1008
+ reject(Oops$5.StrictUpdate());
977
1009
  return;
978
- const output = mayRetry(() => {
979
- effect.call(inject, inject);
980
- });
981
- if (output instanceof Promise) {
982
- output.finally(() => busy = false);
983
- busy = true;
984
- }
985
- };
986
- if (!select) {
987
- const sub = control.subscribe(() => invoke);
988
- inject = sub.proxy;
989
- invoke();
990
- return sub.commit();
991
- }
992
- invoke();
993
- return control.addListener(key => {
994
- if (key === null) {
995
- if (!select.length)
996
- invoke();
997
1010
  }
998
- else if (select.includes(key))
999
- 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
+ });
1000
1025
  });
1026
+ if (arg2 && typeof arg2 == "number")
1027
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1001
1028
  });
1002
1029
  }
1003
1030
  export(subset) {
@@ -1010,13 +1037,10 @@ class Model {
1010
1037
  }
1011
1038
  update(arg1, arg2) {
1012
1039
  const control = Control.for(this);
1013
- const { frame, state, waiting } = control;
1014
- if (arg1 === true && !frame.size)
1015
- return Promise.reject(Oops$5.StrictUpdate());
1016
1040
  if (typeof arg1 == "object") {
1017
1041
  for (const key in arg1)
1018
- if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1019
- state.set(key, arg1[key]);
1042
+ if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1043
+ control.state.set(key, arg1[key]);
1020
1044
  control.update(key);
1021
1045
  }
1022
1046
  }
@@ -1030,25 +1054,19 @@ class Model {
1030
1054
  else if (arg2)
1031
1055
  method.call(this);
1032
1056
  }
1033
- arg1 = undefined;
1034
1057
  }
1035
1058
  return {
1036
1059
  then: (callback) => {
1037
1060
  if (!callback)
1038
1061
  throw Oops$5.NoChaining();
1039
- if (frame.size || arg1 !== false)
1040
- waiting.add(() => {
1041
- callback(control.latest);
1042
- });
1043
- else
1044
- callback(false);
1062
+ control.waiting.add(callback);
1045
1063
  }
1046
1064
  };
1047
1065
  }
1048
1066
  /**
1049
- * 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.
1050
1068
  */
1051
- kill() {
1069
+ destroy() {
1052
1070
  Control.for(this).clear();
1053
1071
  }
1054
1072
  /**
@@ -1125,7 +1143,7 @@ class Lookup {
1125
1143
  items.add(entry.value);
1126
1144
  }
1127
1145
  for (const model of items)
1128
- model.kill();
1146
+ model.destroy();
1129
1147
  }
1130
1148
  }
1131
1149
  const LookupContext = React.createContext(new Lookup());
@@ -1225,7 +1243,7 @@ function useModel(source, arg, arg2) {
1225
1243
  instance.on(arg, () => update(x => x + 1));
1226
1244
  return () => {
1227
1245
  if (Model.isTypeof(source))
1228
- instance.kill();
1246
+ instance.destroy();
1229
1247
  };
1230
1248
  }, []);
1231
1249
  return instance;
@@ -1238,15 +1256,15 @@ function useModel(source, arg, arg2) {
1238
1256
  return () => {
1239
1257
  local.release();
1240
1258
  if (Model.isTypeof(source))
1241
- instance.kill();
1259
+ instance.destroy();
1242
1260
  };
1243
1261
  }, []);
1244
1262
  return local.proxy;
1245
1263
  }
1246
1264
 
1247
- function useFrom(target, compute, suspend) {
1265
+ function useValue(source, compute, suspend) {
1248
1266
  const local = use(refresh => {
1249
- const sub = Control.for(target).subscribe(() => update);
1267
+ const sub = Control.for(source).subscribe(() => update);
1250
1268
  const spy = sub.proxy;
1251
1269
  let value = compute.call(spy, spy);
1252
1270
  let update;
@@ -1289,14 +1307,14 @@ function useTap(source, arg1, arg2) {
1289
1307
  const instance = useMemo(() => {
1290
1308
  if (typeof source == "object")
1291
1309
  return () => source;
1292
- if (MVC.isTypeof(source))
1293
- return () => source.get(true);
1294
1310
  if (Model.isTypeof(source))
1295
- return () => useContext(source);
1311
+ return "get" in source
1312
+ ? () => source.get(true)
1313
+ : () => useContext(source);
1296
1314
  return source;
1297
1315
  }, [])();
1298
1316
  if (typeof arg1 == "function")
1299
- return useFrom(instance, arg1, arg2);
1317
+ return useValue(instance, arg1, arg2);
1300
1318
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1301
1319
  React.useLayoutEffect(local.commit, []);
1302
1320
  if (arg1 === undefined)
@@ -1326,7 +1344,7 @@ const LOCAL = "$debug local";
1326
1344
  const STATE = "$debug state";
1327
1345
  const UPDATE = "$debug update";
1328
1346
  const CONTROL = "$debug controller";
1329
- Object.defineProperties(Model.prototype, {
1347
+ defineProperties(Model.prototype, {
1330
1348
  [CONTROL]: {
1331
1349
  get() {
1332
1350
  return Control.get(this);
@@ -1445,11 +1463,11 @@ class Global extends MVC {
1445
1463
  super();
1446
1464
  Active.set(this.constructor, this);
1447
1465
  }
1448
- kill(force) {
1466
+ destroy(force) {
1449
1467
  const type = this.constructor;
1450
1468
  if (type.keepAlive && !force)
1451
1469
  return false;
1452
- super.kill();
1470
+ Control.for(this).clear();
1453
1471
  Active.delete(type);
1454
1472
  return true;
1455
1473
  }
@@ -1483,10 +1501,10 @@ class Global extends MVC {
1483
1501
  static reset() {
1484
1502
  const current = Active.get(this);
1485
1503
  if (current)
1486
- current.kill(true);
1504
+ current.destroy(true);
1487
1505
  }
1488
1506
  }
1489
1507
  Global.keepAlive = true;
1490
1508
 
1491
- 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 };
1492
1510
  //# sourceMappingURL=index.esm.js.map