@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.js CHANGED
@@ -25,44 +25,13 @@ function issues(register) {
25
25
  return Library;
26
26
  }
27
27
 
28
- const Oops$d = 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$d.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$d.BadCallback();
58
- };
59
- }
28
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
60
29
 
61
- const Oops$c = 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$c.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$b = 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();
@@ -112,14 +81,17 @@ function computeMode(self, source, setter, key, required) {
112
81
  const { subject, state } = self;
113
82
  let sub;
114
83
  let order = ORDER.get(self);
84
+ let pending = KEYS.get(self);
115
85
  if (!order)
116
- ORDER.set(self, order = []);
86
+ ORDER.set(self, order = new Map());
87
+ if (!pending)
88
+ KEYS.set(self, pending = new Set());
117
89
  const compute = (initial) => {
118
90
  try {
119
91
  return setter.call(sub.proxy, sub.proxy);
120
92
  }
121
93
  catch (err) {
122
- Oops$b.Failed(subject, key, initial).warn();
94
+ Oops$c.Failed(subject, key, initial).warn();
123
95
  throw err;
124
96
  }
125
97
  };
@@ -139,18 +111,13 @@ function computeMode(self, source, setter, key, required) {
139
111
  }
140
112
  }
141
113
  };
142
- const update = (_key, source) => {
143
- if (source !== self) {
144
- refresh();
145
- return;
146
- }
147
- let pending = KEYS.get(self);
148
- if (!pending)
149
- KEYS.set(self, pending = []);
150
- pending.push(refresh);
151
- };
152
114
  const create = () => {
153
- sub = source.subscribe(update);
115
+ sub = source.subscribe((_, source) => {
116
+ if (source !== self)
117
+ refresh();
118
+ else
119
+ pending.add(refresh);
120
+ });
154
121
  try {
155
122
  const value = compute(true);
156
123
  state.set(key, value);
@@ -161,11 +128,15 @@ function computeMode(self, source, setter, key, required) {
161
128
  }
162
129
  finally {
163
130
  sub.commit();
164
- order.push(refresh);
131
+ order.set(refresh, order.size);
165
132
  }
166
133
  };
167
134
  INFO.set(refresh, key);
168
135
  return () => {
136
+ if (pending.has(refresh)) {
137
+ pending.delete(refresh);
138
+ refresh();
139
+ }
169
140
  const value = sub ? state.get(key) : create();
170
141
  if (value === undefined && required)
171
142
  throw suspend(self, key);
@@ -174,18 +145,20 @@ function computeMode(self, source, setter, key, required) {
174
145
  }
175
146
  function flush(control) {
176
147
  const pending = KEYS.get(control);
177
- if (!pending)
148
+ if (!pending || !pending.size)
178
149
  return;
179
- const list = ORDER.get(control);
180
- while (pending.length) {
181
- const compute = pending
182
- .sort((a, b) => list.indexOf(b) - list.indexOf(a))
183
- .pop();
150
+ const priority = ORDER.get(control);
151
+ while (pending.size) {
152
+ let compute;
153
+ for (const item of pending)
154
+ if (!compute || priority.get(item) < priority.get(compute))
155
+ compute = item;
156
+ pending.delete(compute);
184
157
  const key = INFO.get(compute);
185
158
  if (!control.frame.has(key))
186
159
  compute();
187
160
  }
188
- KEYS.delete(control);
161
+ pending.clear();
189
162
  }
190
163
 
191
164
  const REGISTER$1 = new WeakMap();
@@ -220,8 +193,8 @@ class Subscriber {
220
193
  notify = this.onUpdate(key, parent);
221
194
  if (!notify)
222
195
  return;
223
- parent.waiting.add(() => {
224
- this.latest = parent.latest.filter(k => watch.has(k));
196
+ parent.waiting.add(update => {
197
+ this.latest = update.filter(k => watch.has(k));
225
198
  });
226
199
  parent.waiting.add(notify);
227
200
  };
@@ -328,13 +301,13 @@ class Control {
328
301
  else if (!frame.size)
329
302
  setTimeout(() => {
330
303
  flush(this);
331
- this.latest = Array.from(frame);
304
+ const keys = this.latest = Array.from(frame);
332
305
  setTimeout(() => this.latest = undefined, 0);
333
306
  frame.clear();
334
307
  for (const notify of waiting)
335
308
  try {
336
309
  waiting.delete(notify);
337
- notify();
310
+ notify(keys);
338
311
  }
339
312
  catch (err) {
340
313
  console.error(err);
@@ -362,18 +335,21 @@ class Control {
362
335
  if (!control.state) {
363
336
  const { waiting } = control;
364
337
  if (cb) {
365
- let done;
338
+ let callback;
366
339
  waiting.add(() => {
367
- done = cb(control);
340
+ callback = cb(control);
368
341
  });
369
- return () => done && done();
342
+ return () => {
343
+ if (callback)
344
+ callback();
345
+ };
370
346
  }
371
347
  control.state = new Map();
372
348
  for (const key in subject)
373
349
  control.watch(key);
374
350
  control.waiting = new Set();
375
351
  for (const cb of waiting)
376
- cb();
352
+ cb([]);
377
353
  }
378
354
  return cb ? cb(control) : control;
379
355
  }
@@ -489,15 +465,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
489
465
  });
490
466
  }
491
467
 
492
- const Oops$a = issues({
468
+ const Oops$b = issues({
493
469
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
494
470
  });
495
- function act(task) {
496
- return add(function act(key) {
471
+ function run(task) {
472
+ return add(function run(key) {
497
473
  let pending = false;
498
474
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
499
475
  if (pending)
500
- return Promise.reject(Oops$a.DuplicatePending(key));
476
+ return Promise.reject(Oops$b.DuplicatePending(key));
501
477
  pending = true;
502
478
  this.update(key);
503
479
  try {
@@ -519,7 +495,7 @@ function act(task) {
519
495
  });
520
496
  }
521
497
 
522
- const Oops$9 = issues({
498
+ const Oops$a = issues({
523
499
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
524
500
  });
525
501
  function factoryMode(self, output, key, required) {
@@ -543,7 +519,7 @@ function factoryMode(self, output, key, required) {
543
519
  const suspend = () => {
544
520
  if (required === false)
545
521
  return undefined;
546
- const issue = Oops$9.NotReady(subject, key);
522
+ const issue = Oops$a.NotReady(subject, key);
547
523
  assign(pending, {
548
524
  message: issue.message,
549
525
  stack: issue.stack
@@ -560,7 +536,7 @@ function factoryMode(self, output, key, required) {
560
536
  };
561
537
  }
562
538
 
563
- const Oops$8 = issues({
539
+ const Oops$9 = issues({
564
540
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
565
541
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
566
542
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
@@ -573,7 +549,7 @@ function get(arg0, arg1, arg2) {
573
549
  let getter;
574
550
  // Easy mistake, using a peer, will always be unresolved.
575
551
  if (typeof arg0 == "symbol")
576
- throw Oops$8.PeerNotAllowed(subject, key);
552
+ throw Oops$9.PeerNotAllowed(subject, key);
577
553
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
578
554
  // throw Oops.BadSource(subject, key, arg0);
579
555
  this.state.set(key, undefined);
@@ -607,7 +583,7 @@ function get(arg0, arg1, arg2) {
607
583
  init();
608
584
  }
609
585
  catch (err) {
610
- Oops$8.ComputeFailed(subject, key).warn();
586
+ Oops$9.ComputeFailed(subject, key).warn();
611
587
  throw err;
612
588
  }
613
589
  return () => {
@@ -618,6 +594,67 @@ function get(arg0, arg1, arg2) {
618
594
  });
619
595
  }
620
596
 
597
+ const Oops$8 = issues({
598
+ BadCallback: () => `Callback for effect-callback may only return a function.`
599
+ });
600
+ function createEffect(model, callback, keys) {
601
+ return Control.for(model, control => {
602
+ let unSet;
603
+ let busy = false;
604
+ function effect(value) {
605
+ if (typeof unSet == "function")
606
+ unSet();
607
+ unSet = callback.call(this, value);
608
+ if (unSet instanceof Promise)
609
+ unSet = undefined;
610
+ if (unSet && typeof unSet !== "function")
611
+ throw Oops$8.BadCallback();
612
+ }
613
+ function invoke() {
614
+ if (busy)
615
+ return;
616
+ const output = mayRetry(() => {
617
+ effect.call(model, model);
618
+ });
619
+ if (output instanceof Promise) {
620
+ output.finally(() => busy = false);
621
+ busy = true;
622
+ }
623
+ }
624
+ if (Array.isArray(keys)) {
625
+ invoke();
626
+ return control.addListener(key => {
627
+ if (key === null) {
628
+ if (!keys.length)
629
+ invoke();
630
+ }
631
+ else if (keys.includes(key))
632
+ return invoke;
633
+ });
634
+ }
635
+ const sub = control.subscribe(() => invoke);
636
+ model = sub.proxy;
637
+ invoke();
638
+ return sub.commit();
639
+ });
640
+ }
641
+ function createValueEffect(callback) {
642
+ let unSet;
643
+ return function (value) {
644
+ if (typeof unSet == "function")
645
+ unSet(value);
646
+ const out = callback.call(this, value, this);
647
+ if (typeof out == "boolean")
648
+ return out;
649
+ if (!out || out instanceof Promise)
650
+ return;
651
+ if (typeof out == "function")
652
+ unSet = out;
653
+ else
654
+ throw Oops$8.BadCallback();
655
+ };
656
+ }
657
+
621
658
  function ref(arg, mapper) {
622
659
  return add(function ref(key) {
623
660
  let value = {};
@@ -911,8 +948,10 @@ function has(Type, required) {
911
948
  }
912
949
 
913
950
  const Oops$5 = issues({
914
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
915
- StrictUpdate: () => `Strict update() did not find pending updates.`,
951
+ Timeout: (keys, timeout) => `No update seen for [${keys}] within ${timeout}.`,
952
+ KeysExpected: (key) => `Key "${key}" was expected in current update.`,
953
+ StrictUpdate: () => `Expected update but none in progress.`,
954
+ StrictNoUpdate: () => `Update is not expected but one is in progress!`,
916
955
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
917
956
  });
918
957
  class Model {
@@ -920,91 +959,84 @@ class Model {
920
959
  new Control(this);
921
960
  defineProperty(this, "is", { value: this });
922
961
  }
923
- on(select, argument, once) {
924
- if (typeof select == "function")
925
- return Control.for(this, x => x.addListener(select));
926
- const single = typeof select == "string";
927
- let keys = single ? [select] : select;
928
- if (typeof argument !== "function")
929
- return new Promise((resolve, reject) => {
930
- const removeListener = Control.for(this, control => {
931
- for (const key of keys)
932
- try {
933
- void this[key];
934
- }
935
- catch (e) { }
936
- return control.addListener(key => {
937
- if (!key) {
938
- removeListener();
939
- reject(Oops$5.Timeout(select, `lifetime of ${this}`));
940
- }
941
- else if (keys.includes(key)) {
962
+ on(arg1, arg2, arg3) {
963
+ if (typeof arg1 == "function")
964
+ return createEffect(this.is, arg1, arg2);
965
+ const single = typeof arg1 == "string";
966
+ const keys = typeof arg1 == "string" ? [arg1] :
967
+ typeof arg1 == "object" ? arg1 :
968
+ undefined;
969
+ if (typeof arg2 == "function" && keys)
970
+ return Control.for(this, control => {
971
+ for (const key of keys)
972
+ try {
973
+ void this[key];
974
+ }
975
+ catch (e) { }
976
+ const invoke = single
977
+ ? () => arg2.call(this, control.state.get(arg1), arg1)
978
+ : (frame) => arg2.call(this, frame);
979
+ const removeListener = control.addListener(key => {
980
+ if (!key && !keys.length)
981
+ invoke([]);
982
+ if (keys.includes(key)) {
983
+ if (arg3)
942
984
  removeListener();
943
- return () => resolve(single ? control.state.get(key) : control.latest);
944
- }
945
- });
985
+ return invoke;
986
+ }
946
987
  });
947
- if (typeof argument == "number")
948
- setTimeout(() => {
949
- removeListener();
950
- reject(Oops$5.Timeout(select, `${argument}ms`));
951
- }, argument);
988
+ return removeListener;
952
989
  });
953
- return Control.for(this, control => {
954
- if (!keys.length)
955
- keys = [...control.state.keys()];
956
- for (const key of keys)
957
- try {
958
- void this[key];
990
+ return new Promise((resolve, reject) => {
991
+ const timeout = (message) => {
992
+ const k = keys ? keys.join(", ") : "any";
993
+ removeListener();
994
+ reject(Oops$5.Timeout(k, message));
995
+ };
996
+ const removeListener = Control.for(this, control => {
997
+ const pending = control.frame;
998
+ if (keys === null) {
999
+ if (pending.size)
1000
+ reject(Oops$5.StrictNoUpdate());
1001
+ else
1002
+ resolve(null);
1003
+ return;
959
1004
  }
960
- catch (e) { }
961
- const callback = single
962
- ? () => { argument.call(this, control.state.get(select), select); }
963
- : () => { argument.call(this, control.latest); };
964
- const onEvent = once
965
- ? () => {
966
- removeListener();
967
- callback();
1005
+ if (keys) {
1006
+ if (keys.length) {
1007
+ if (arg2 === 0 && single && !pending.has(keys[0])) {
1008
+ reject(Oops$5.KeysExpected(keys[0]));
1009
+ return;
1010
+ }
1011
+ else
1012
+ for (const key of keys)
1013
+ try {
1014
+ void this[key];
1015
+ }
1016
+ catch (e) { }
1017
+ }
968
1018
  }
969
- : callback;
970
- const removeListener = control.addListener(key => {
971
- if (keys.includes(key))
972
- return onEvent;
973
- });
974
- return removeListener;
975
- });
976
- }
977
- effect(callback, select) {
978
- const effect = createEffect(callback);
979
- return Control.for(this, control => {
980
- let busy = false;
981
- let inject = this.is;
982
- const invoke = () => {
983
- if (busy)
1019
+ else if (!pending.size && arg1 === true || arg2 === 0) {
1020
+ reject(Oops$5.StrictUpdate());
984
1021
  return;
985
- const output = mayRetry(() => {
986
- effect.call(inject, inject);
987
- });
988
- if (output instanceof Promise) {
989
- output.finally(() => busy = false);
990
- busy = true;
991
- }
992
- };
993
- if (!select) {
994
- const sub = control.subscribe(() => invoke);
995
- inject = sub.proxy;
996
- invoke();
997
- return sub.commit();
998
- }
999
- invoke();
1000
- return control.addListener(key => {
1001
- if (key === null) {
1002
- if (!select.length)
1003
- invoke();
1004
1022
  }
1005
- else if (select.includes(key))
1006
- return invoke;
1023
+ else
1024
+ control.waiting.add(resolve);
1025
+ return control.addListener(key => {
1026
+ if (!key) {
1027
+ if (keys && !keys.length)
1028
+ resolve([]);
1029
+ else
1030
+ timeout(`lifetime of ${this}`);
1031
+ }
1032
+ else if (!keys || keys.includes(key)) {
1033
+ removeListener();
1034
+ return keys => resolve(single ? control.state.get(key) : keys);
1035
+ }
1036
+ });
1007
1037
  });
1038
+ if (arg2 && typeof arg2 == "number")
1039
+ setTimeout(() => timeout(`${arg2}ms`), arg2);
1008
1040
  });
1009
1041
  }
1010
1042
  export(subset) {
@@ -1017,13 +1049,10 @@ class Model {
1017
1049
  }
1018
1050
  update(arg1, arg2) {
1019
1051
  const control = Control.for(this);
1020
- const { frame, state, waiting } = control;
1021
- if (arg1 === true && !frame.size)
1022
- return Promise.reject(Oops$5.StrictUpdate());
1023
1052
  if (typeof arg1 == "object") {
1024
1053
  for (const key in arg1)
1025
- if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1026
- state.set(key, arg1[key]);
1054
+ if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1055
+ control.state.set(key, arg1[key]);
1027
1056
  control.update(key);
1028
1057
  }
1029
1058
  }
@@ -1037,25 +1066,19 @@ class Model {
1037
1066
  else if (arg2)
1038
1067
  method.call(this);
1039
1068
  }
1040
- arg1 = undefined;
1041
1069
  }
1042
1070
  return {
1043
1071
  then: (callback) => {
1044
1072
  if (!callback)
1045
1073
  throw Oops$5.NoChaining();
1046
- if (frame.size || arg1 !== false)
1047
- waiting.add(() => {
1048
- callback(control.latest);
1049
- });
1050
- else
1051
- callback(false);
1074
+ control.waiting.add(callback);
1052
1075
  }
1053
1076
  };
1054
1077
  }
1055
1078
  /**
1056
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
1079
+ * Clean up side effects and mark this instance for garbage-collection.
1057
1080
  */
1058
- kill() {
1081
+ destroy() {
1059
1082
  Control.for(this).clear();
1060
1083
  }
1061
1084
  /**
@@ -1132,7 +1155,7 @@ class Lookup {
1132
1155
  items.add(entry.value);
1133
1156
  }
1134
1157
  for (const model of items)
1135
- model.kill();
1158
+ model.destroy();
1136
1159
  }
1137
1160
  }
1138
1161
  const LookupContext = React__default["default"].createContext(new Lookup());
@@ -1232,7 +1255,7 @@ function useModel(source, arg, arg2) {
1232
1255
  instance.on(arg, () => update(x => x + 1));
1233
1256
  return () => {
1234
1257
  if (Model.isTypeof(source))
1235
- instance.kill();
1258
+ instance.destroy();
1236
1259
  };
1237
1260
  }, []);
1238
1261
  return instance;
@@ -1245,15 +1268,15 @@ function useModel(source, arg, arg2) {
1245
1268
  return () => {
1246
1269
  local.release();
1247
1270
  if (Model.isTypeof(source))
1248
- instance.kill();
1271
+ instance.destroy();
1249
1272
  };
1250
1273
  }, []);
1251
1274
  return local.proxy;
1252
1275
  }
1253
1276
 
1254
- function useFrom(target, compute, suspend) {
1277
+ function useValue(source, compute, suspend) {
1255
1278
  const local = use(refresh => {
1256
- const sub = Control.for(target).subscribe(() => update);
1279
+ const sub = Control.for(source).subscribe(() => update);
1257
1280
  const spy = sub.proxy;
1258
1281
  let value = compute.call(spy, spy);
1259
1282
  let update;
@@ -1303,7 +1326,7 @@ function useTap(source, arg1, arg2) {
1303
1326
  return source;
1304
1327
  }, [])();
1305
1328
  if (typeof arg1 == "function")
1306
- return useFrom(instance, arg1, arg2);
1329
+ return useValue(instance, arg1, arg2);
1307
1330
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1308
1331
  React__default["default"].useLayoutEffect(local.commit, []);
1309
1332
  if (arg1 === undefined)
@@ -1333,7 +1356,7 @@ const LOCAL = "$debug local";
1333
1356
  const STATE = "$debug state";
1334
1357
  const UPDATE = "$debug update";
1335
1358
  const CONTROL = "$debug controller";
1336
- Object.defineProperties(Model.prototype, {
1359
+ defineProperties(Model.prototype, {
1337
1360
  [CONTROL]: {
1338
1361
  get() {
1339
1362
  return Control.get(this);
@@ -1452,11 +1475,11 @@ class Global extends MVC {
1452
1475
  super();
1453
1476
  Active.set(this.constructor, this);
1454
1477
  }
1455
- kill(force) {
1478
+ destroy(force) {
1456
1479
  const type = this.constructor;
1457
1480
  if (type.keepAlive && !force)
1458
1481
  return false;
1459
- super.kill();
1482
+ Control.for(this).clear();
1460
1483
  Active.delete(type);
1461
1484
  return true;
1462
1485
  }
@@ -1490,7 +1513,7 @@ class Global extends MVC {
1490
1513
  static reset() {
1491
1514
  const current = Active.get(this);
1492
1515
  if (current)
1493
- current.kill(true);
1516
+ current.destroy(true);
1494
1517
  }
1495
1518
  }
1496
1519
  Global.keepAlive = true;
@@ -1501,17 +1524,17 @@ exports.Global = Global;
1501
1524
  exports.MVC = MVC;
1502
1525
  exports.Model = MVC;
1503
1526
  exports.Provider = Provider;
1504
- exports.act = act;
1505
1527
  exports.add = add;
1506
1528
  exports["default"] = MVC;
1507
1529
  exports.get = get;
1508
1530
  exports.has = has;
1509
1531
  exports.ref = ref;
1532
+ exports.run = run;
1510
1533
  exports.set = set;
1511
1534
  exports.tap = tap;
1512
1535
  exports.use = use$1;
1513
1536
  exports.useContext = useContext;
1514
- exports.useFrom = useFrom;
1515
1537
  exports.useModel = useModel;
1516
1538
  exports.useTap = useTap;
1539
+ exports.useValue = useValue;
1517
1540
  //# sourceMappingURL=index.js.map