@expressive/mvc 0.8.2 → 0.9.1

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();
@@ -104,14 +73,17 @@ function computeMode(self, source, setter, key, required) {
104
73
  const { subject, state } = self;
105
74
  let sub;
106
75
  let order = ORDER.get(self);
76
+ let pending = KEYS.get(self);
107
77
  if (!order)
108
- ORDER.set(self, order = []);
78
+ ORDER.set(self, order = new Map());
79
+ if (!pending)
80
+ KEYS.set(self, pending = new Set());
109
81
  const compute = (initial) => {
110
82
  try {
111
83
  return setter.call(sub.proxy, sub.proxy);
112
84
  }
113
85
  catch (err) {
114
- Oops$b.Failed(subject, key, initial).warn();
86
+ Oops$c.Failed(subject, key, initial).warn();
115
87
  throw err;
116
88
  }
117
89
  };
@@ -131,18 +103,13 @@ function computeMode(self, source, setter, key, required) {
131
103
  }
132
104
  }
133
105
  };
134
- const update = (_key, source) => {
135
- if (source !== self) {
136
- refresh();
137
- return;
138
- }
139
- let pending = KEYS.get(self);
140
- if (!pending)
141
- KEYS.set(self, pending = []);
142
- pending.push(refresh);
143
- };
144
106
  const create = () => {
145
- sub = source.subscribe(update);
107
+ sub = source.subscribe((_, source) => {
108
+ if (source !== self)
109
+ refresh();
110
+ else
111
+ pending.add(refresh);
112
+ });
146
113
  try {
147
114
  const value = compute(true);
148
115
  state.set(key, value);
@@ -153,11 +120,15 @@ function computeMode(self, source, setter, key, required) {
153
120
  }
154
121
  finally {
155
122
  sub.commit();
156
- order.push(refresh);
123
+ order.set(refresh, order.size);
157
124
  }
158
125
  };
159
126
  INFO.set(refresh, key);
160
127
  return () => {
128
+ if (pending.has(refresh)) {
129
+ pending.delete(refresh);
130
+ refresh();
131
+ }
161
132
  const value = sub ? state.get(key) : create();
162
133
  if (value === undefined && required)
163
134
  throw suspend(self, key);
@@ -166,18 +137,20 @@ function computeMode(self, source, setter, key, required) {
166
137
  }
167
138
  function flush(control) {
168
139
  const pending = KEYS.get(control);
169
- if (!pending)
140
+ if (!pending || !pending.size)
170
141
  return;
171
- const list = ORDER.get(control);
172
- while (pending.length) {
173
- const compute = pending
174
- .sort((a, b) => list.indexOf(b) - list.indexOf(a))
175
- .pop();
142
+ const priority = ORDER.get(control);
143
+ while (pending.size) {
144
+ let compute;
145
+ for (const item of pending)
146
+ if (!compute || priority.get(item) < priority.get(compute))
147
+ compute = item;
148
+ pending.delete(compute);
176
149
  const key = INFO.get(compute);
177
150
  if (!control.frame.has(key))
178
151
  compute();
179
152
  }
180
- KEYS.delete(control);
153
+ pending.clear();
181
154
  }
182
155
 
183
156
  const REGISTER$1 = new WeakMap();
@@ -212,8 +185,8 @@ class Subscriber {
212
185
  notify = this.onUpdate(key, parent);
213
186
  if (!notify)
214
187
  return;
215
- parent.waiting.add(() => {
216
- this.latest = parent.latest.filter(k => watch.has(k));
188
+ parent.waiting.add(update => {
189
+ this.latest = update.filter(k => watch.has(k));
217
190
  });
218
191
  parent.waiting.add(notify);
219
192
  };
@@ -320,13 +293,13 @@ class Control {
320
293
  else if (!frame.size)
321
294
  setTimeout(() => {
322
295
  flush(this);
323
- this.latest = Array.from(frame);
296
+ const keys = this.latest = Array.from(frame);
324
297
  setTimeout(() => this.latest = undefined, 0);
325
298
  frame.clear();
326
299
  for (const notify of waiting)
327
300
  try {
328
301
  waiting.delete(notify);
329
- notify();
302
+ notify(keys);
330
303
  }
331
304
  catch (err) {
332
305
  console.error(err);
@@ -354,18 +327,21 @@ class Control {
354
327
  if (!control.state) {
355
328
  const { waiting } = control;
356
329
  if (cb) {
357
- let done;
330
+ let callback;
358
331
  waiting.add(() => {
359
- done = cb(control);
332
+ callback = cb(control);
360
333
  });
361
- return () => done && done();
334
+ return () => {
335
+ if (callback)
336
+ callback();
337
+ };
362
338
  }
363
339
  control.state = new Map();
364
340
  for (const key in subject)
365
341
  control.watch(key);
366
342
  control.waiting = new Set();
367
343
  for (const cb of waiting)
368
- cb();
344
+ cb([]);
369
345
  }
370
346
  return cb ? cb(control) : control;
371
347
  }
@@ -481,15 +457,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
481
457
  });
482
458
  }
483
459
 
484
- const Oops$a = issues({
460
+ const Oops$b = issues({
485
461
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
486
462
  });
487
- function act(task) {
488
- return add(function act(key) {
463
+ function run(task) {
464
+ return add(function run(key) {
489
465
  let pending = false;
490
466
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
491
467
  if (pending)
492
- return Promise.reject(Oops$a.DuplicatePending(key));
468
+ return Promise.reject(Oops$b.DuplicatePending(key));
493
469
  pending = true;
494
470
  this.update(key);
495
471
  try {
@@ -511,7 +487,7 @@ function act(task) {
511
487
  });
512
488
  }
513
489
 
514
- const Oops$9 = issues({
490
+ const Oops$a = issues({
515
491
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
516
492
  });
517
493
  function factoryMode(self, output, key, required) {
@@ -535,7 +511,7 @@ function factoryMode(self, output, key, required) {
535
511
  const suspend = () => {
536
512
  if (required === false)
537
513
  return undefined;
538
- const issue = Oops$9.NotReady(subject, key);
514
+ const issue = Oops$a.NotReady(subject, key);
539
515
  assign(pending, {
540
516
  message: issue.message,
541
517
  stack: issue.stack
@@ -552,7 +528,7 @@ function factoryMode(self, output, key, required) {
552
528
  };
553
529
  }
554
530
 
555
- const Oops$8 = issues({
531
+ const Oops$9 = issues({
556
532
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
557
533
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
558
534
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
@@ -565,7 +541,7 @@ function get(arg0, arg1, arg2) {
565
541
  let getter;
566
542
  // Easy mistake, using a peer, will always be unresolved.
567
543
  if (typeof arg0 == "symbol")
568
- throw Oops$8.PeerNotAllowed(subject, key);
544
+ throw Oops$9.PeerNotAllowed(subject, key);
569
545
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
570
546
  // throw Oops.BadSource(subject, key, arg0);
571
547
  this.state.set(key, undefined);
@@ -599,7 +575,7 @@ function get(arg0, arg1, arg2) {
599
575
  init();
600
576
  }
601
577
  catch (err) {
602
- Oops$8.ComputeFailed(subject, key).warn();
578
+ Oops$9.ComputeFailed(subject, key).warn();
603
579
  throw err;
604
580
  }
605
581
  return () => {
@@ -610,6 +586,67 @@ function get(arg0, arg1, arg2) {
610
586
  });
611
587
  }
612
588
 
589
+ const Oops$8 = issues({
590
+ BadCallback: () => `Callback for effect-callback may only return a function.`
591
+ });
592
+ function createEffect(model, callback, keys) {
593
+ return Control.for(model, control => {
594
+ let unSet;
595
+ let busy = false;
596
+ function effect(value) {
597
+ if (typeof unSet == "function")
598
+ unSet();
599
+ unSet = callback.call(this, value);
600
+ if (unSet instanceof Promise)
601
+ unSet = undefined;
602
+ if (unSet && typeof unSet !== "function")
603
+ throw Oops$8.BadCallback();
604
+ }
605
+ function invoke() {
606
+ if (busy)
607
+ return;
608
+ const output = mayRetry(() => {
609
+ effect.call(model, model);
610
+ });
611
+ if (output instanceof Promise) {
612
+ output.finally(() => busy = false);
613
+ busy = true;
614
+ }
615
+ }
616
+ if (Array.isArray(keys)) {
617
+ invoke();
618
+ return control.addListener(key => {
619
+ if (key === null) {
620
+ if (!keys.length)
621
+ invoke();
622
+ }
623
+ else if (keys.includes(key))
624
+ return invoke;
625
+ });
626
+ }
627
+ const sub = control.subscribe(() => invoke);
628
+ model = sub.proxy;
629
+ invoke();
630
+ return sub.commit();
631
+ });
632
+ }
633
+ function createValueEffect(callback) {
634
+ let unSet;
635
+ return function (value) {
636
+ if (typeof unSet == "function")
637
+ unSet(value);
638
+ const out = callback.call(this, value, this);
639
+ if (typeof out == "boolean")
640
+ return out;
641
+ if (!out || out instanceof Promise)
642
+ return;
643
+ if (typeof out == "function")
644
+ unSet = out;
645
+ else
646
+ throw Oops$8.BadCallback();
647
+ };
648
+ }
649
+
613
650
  function ref(arg, mapper) {
614
651
  return add(function ref(key) {
615
652
  let value = {};
@@ -903,8 +940,10 @@ function has(Type, required) {
903
940
  }
904
941
 
905
942
  const Oops$5 = issues({
906
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
907
- StrictUpdate: () => `Strict update() did not find pending updates.`,
943
+ Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
944
+ KeysExpected: (key) => `Key "${key}" was expected in current update.`,
945
+ StrictUpdate: () => `Expected update but none in progress.`,
946
+ StrictNoUpdate: () => `Update is not expected but one is in progress!`,
908
947
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
909
948
  });
910
949
  class Model {
@@ -912,91 +951,84 @@ class Model {
912
951
  new Control(this);
913
952
  defineProperty(this, "is", { value: this });
914
953
  }
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)) {
954
+ on(arg1, arg2, arg3) {
955
+ if (typeof arg1 == "function")
956
+ return createEffect(this.is, arg1, arg2);
957
+ const single = typeof arg1 == "string";
958
+ const keys = typeof arg1 == "string" ? [arg1] :
959
+ typeof arg1 == "object" ? arg1 :
960
+ undefined;
961
+ if (typeof arg2 == "function" && keys)
962
+ return Control.for(this, control => {
963
+ for (const key of keys)
964
+ try {
965
+ void this[key];
966
+ }
967
+ catch (e) { }
968
+ const invoke = single
969
+ ? () => arg2.call(this, control.state.get(arg1), arg1)
970
+ : (frame) => arg2.call(this, frame);
971
+ const removeListener = control.addListener(key => {
972
+ if (!key && !keys.length)
973
+ invoke([]);
974
+ if (keys.includes(key)) {
975
+ if (arg3)
934
976
  removeListener();
935
- return () => resolve(single ? control.state.get(key) : control.latest);
936
- }
937
- });
977
+ return invoke;
978
+ }
938
979
  });
939
- if (typeof argument == "number")
940
- setTimeout(() => {
941
- removeListener();
942
- reject(Oops$5.Timeout(select, `${argument}ms`));
943
- }, argument);
980
+ return removeListener;
944
981
  });
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];
982
+ return new Promise((resolve, reject) => {
983
+ const timeout = (message) => {
984
+ const k = keys ? keys.join(", ") : "any";
985
+ removeListener();
986
+ reject(Oops$5.Timeout(k, message));
987
+ };
988
+ const removeListener = Control.for(this, control => {
989
+ const pending = control.frame;
990
+ if (keys === null) {
991
+ if (pending.size)
992
+ reject(Oops$5.StrictNoUpdate());
993
+ else
994
+ resolve(null);
995
+ return;
951
996
  }
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();
997
+ if (keys) {
998
+ if (keys.length) {
999
+ if (arg2 === 0 && single && !pending.has(keys[0])) {
1000
+ reject(Oops$5.KeysExpected(keys[0]));
1001
+ return;
1002
+ }
1003
+ else
1004
+ for (const key of keys)
1005
+ try {
1006
+ void this[key];
1007
+ }
1008
+ catch (e) { }
1009
+ }
960
1010
  }
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)
1011
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1012
+ reject(Oops$5.StrictUpdate());
976
1013
  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
- }
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
1014
  }
997
- else if (select.includes(key))
998
- return invoke;
1015
+ else
1016
+ control.waiting.add(resolve);
1017
+ return control.addListener(key => {
1018
+ if (!key) {
1019
+ if (keys && !keys.length)
1020
+ resolve([]);
1021
+ else
1022
+ timeout(`lifetime of ${this}`);
1023
+ }
1024
+ else if (!keys || keys.includes(key)) {
1025
+ removeListener();
1026
+ return keys => resolve(single ? control.state.get(key) : keys);
1027
+ }
1028
+ });
999
1029
  });
1030
+ if (arg2 && typeof arg2 == "number")
1031
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1000
1032
  });
1001
1033
  }
1002
1034
  export(subset) {
@@ -1009,13 +1041,10 @@ class Model {
1009
1041
  }
1010
1042
  update(arg1, arg2) {
1011
1043
  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
1044
  if (typeof arg1 == "object") {
1016
1045
  for (const key in arg1)
1017
- if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1018
- state.set(key, arg1[key]);
1046
+ if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1047
+ control.state.set(key, arg1[key]);
1019
1048
  control.update(key);
1020
1049
  }
1021
1050
  }
@@ -1029,25 +1058,19 @@ class Model {
1029
1058
  else if (arg2)
1030
1059
  method.call(this);
1031
1060
  }
1032
- arg1 = undefined;
1033
1061
  }
1034
1062
  return {
1035
1063
  then: (callback) => {
1036
1064
  if (!callback)
1037
1065
  throw Oops$5.NoChaining();
1038
- if (frame.size || arg1 !== false)
1039
- waiting.add(() => {
1040
- callback(control.latest);
1041
- });
1042
- else
1043
- callback(false);
1066
+ control.waiting.add(callback);
1044
1067
  }
1045
1068
  };
1046
1069
  }
1047
1070
  /**
1048
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
1071
+ * Clean up side effects and mark this instance for garbage-collection.
1049
1072
  */
1050
- kill() {
1073
+ destroy() {
1051
1074
  Control.for(this).clear();
1052
1075
  }
1053
1076
  /**
@@ -1124,7 +1147,7 @@ class Lookup {
1124
1147
  items.add(entry.value);
1125
1148
  }
1126
1149
  for (const model of items)
1127
- model.kill();
1150
+ model.destroy();
1128
1151
  }
1129
1152
  }
1130
1153
  const LookupContext = React.createContext(new Lookup());
@@ -1224,7 +1247,7 @@ function useModel(source, arg, arg2) {
1224
1247
  instance.on(arg, () => update(x => x + 1));
1225
1248
  return () => {
1226
1249
  if (Model.isTypeof(source))
1227
- instance.kill();
1250
+ instance.destroy();
1228
1251
  };
1229
1252
  }, []);
1230
1253
  return instance;
@@ -1237,15 +1260,15 @@ function useModel(source, arg, arg2) {
1237
1260
  return () => {
1238
1261
  local.release();
1239
1262
  if (Model.isTypeof(source))
1240
- instance.kill();
1263
+ instance.destroy();
1241
1264
  };
1242
1265
  }, []);
1243
1266
  return local.proxy;
1244
1267
  }
1245
1268
 
1246
- function useFrom(target, compute, suspend) {
1269
+ function useValue(source, compute, suspend) {
1247
1270
  const local = use(refresh => {
1248
- const sub = Control.for(target).subscribe(() => update);
1271
+ const sub = Control.for(source).subscribe(() => update);
1249
1272
  const spy = sub.proxy;
1250
1273
  let value = compute.call(spy, spy);
1251
1274
  let update;
@@ -1295,7 +1318,7 @@ function useTap(source, arg1, arg2) {
1295
1318
  return source;
1296
1319
  }, [])();
1297
1320
  if (typeof arg1 == "function")
1298
- return useFrom(instance, arg1, arg2);
1321
+ return useValue(instance, arg1, arg2);
1299
1322
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1300
1323
  React.useLayoutEffect(local.commit, []);
1301
1324
  if (arg1 === undefined)
@@ -1325,7 +1348,7 @@ const LOCAL = "$debug local";
1325
1348
  const STATE = "$debug state";
1326
1349
  const UPDATE = "$debug update";
1327
1350
  const CONTROL = "$debug controller";
1328
- Object.defineProperties(Model.prototype, {
1351
+ defineProperties(Model.prototype, {
1329
1352
  [CONTROL]: {
1330
1353
  get() {
1331
1354
  return Control.get(this);
@@ -1444,11 +1467,11 @@ class Global extends MVC {
1444
1467
  super();
1445
1468
  Active.set(this.constructor, this);
1446
1469
  }
1447
- kill(force) {
1470
+ destroy(force) {
1448
1471
  const type = this.constructor;
1449
1472
  if (type.keepAlive && !force)
1450
1473
  return false;
1451
- super.kill();
1474
+ Control.for(this).clear();
1452
1475
  Active.delete(type);
1453
1476
  return true;
1454
1477
  }
@@ -1482,10 +1505,10 @@ class Global extends MVC {
1482
1505
  static reset() {
1483
1506
  const current = Active.get(this);
1484
1507
  if (current)
1485
- current.kill(true);
1508
+ current.destroy(true);
1486
1509
  }
1487
1510
  }
1488
1511
  Global.keepAlive = true;
1489
1512
 
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 };
1513
+ 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
1514
  //# sourceMappingURL=index.esm.js.map