@expressive/mvc 1.0.0-beta.26 → 1.0.0-beta.27

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
@@ -92,14 +92,34 @@ class Subscriber {
92
92
  constructor(parent, onUpdate) {
93
93
  this.onUpdate = onUpdate;
94
94
  this.active = false;
95
- this.follows = {};
95
+ this.handle = {};
96
96
  this.dependant = new Set();
97
+ this.debug = () => {
98
+ const update = [];
99
+ this.notify = keys => {
100
+ update.splice(0, update.length, ...keys.filter(k => k in this.handle));
101
+ };
102
+ return update;
103
+ };
104
+ this.listen = (key, from) => {
105
+ const handler = this.handle[key];
106
+ if (!handler)
107
+ return;
108
+ if (typeof handler == "function")
109
+ handler();
110
+ const notify = this.onUpdate(key, from);
111
+ if (notify)
112
+ from.waiting.add(notify);
113
+ if (this.notify)
114
+ from.waiting.add(this.notify);
115
+ };
97
116
  if (!(parent instanceof Controller))
98
117
  parent = manage(parent);
99
118
  this.parent = parent;
100
119
  this.source = parent.subject;
101
120
  this.proxy = create(parent.subject);
102
121
  define(this.proxy, LOCAL, this);
122
+ defineLazy(this.proxy, UPDATE, this.debug);
103
123
  for (const key in parent.state)
104
124
  this.spy(key);
105
125
  }
@@ -119,16 +139,15 @@ class Subscriber {
119
139
  enumerable: true
120
140
  });
121
141
  }
122
- follow(key, cb) {
123
- this.follows[key] = cb || this.onUpdate;
142
+ follow(key, callback) {
143
+ this.handle[key] = callback || true;
124
144
  }
125
145
  commit() {
126
- const { dependant, follows, parent } = this;
127
- const onDone = parent.addListener(follows);
146
+ const onDone = this.parent.addListener(this.listen);
128
147
  this.active = true;
129
- dependant.forEach(x => x.commit());
148
+ this.dependant.forEach(x => x.commit());
130
149
  return this.release = () => {
131
- dependant.forEach(x => x.release());
150
+ this.dependant.forEach(x => x.release());
132
151
  onDone();
133
152
  };
134
153
  }
@@ -138,18 +157,23 @@ const Oops$8 = issues({
138
157
  ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
139
158
  ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
140
159
  });
141
- const ComputedInit = new WeakSet();
142
- const ComputedInfo = new WeakMap();
143
- const ComputedUsed = new WeakMap();
144
- const ComputedKeys = new WeakMap();
160
+ const INIT = new WeakSet();
161
+ const INFO = new WeakMap();
162
+ const USED = new WeakMap();
163
+ const KEYS = new WeakMap();
145
164
  function prepare(parent, key, source, setter, getter) {
146
165
  let sub;
147
166
  const { state, subject } = parent;
148
167
  const info = { key, parent, priority: 1 };
168
+ let register = USED.get(parent);
169
+ if (!register) {
170
+ register = new Map();
171
+ USED.set(parent, register);
172
+ }
173
+ register.set(key, info);
149
174
  const current = getter
150
175
  ? () => getter.call(subject, state[key], key)
151
176
  : () => state[key];
152
- const prioritize = register(parent, key, info);
153
177
  function compute(initial) {
154
178
  try {
155
179
  return setter.call(sub.proxy, sub.proxy);
@@ -174,8 +198,19 @@ function prepare(parent, key, source, setter, getter) {
174
198
  }
175
199
  }
176
200
  }
201
+ function defer(_key, from) {
202
+ let pending = KEYS.get(from);
203
+ if (!pending)
204
+ KEYS.set(from, pending = []);
205
+ if (info.parent !== from)
206
+ update();
207
+ else {
208
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
209
+ pending.splice(after + 1, 0, update);
210
+ }
211
+ }
177
212
  function create(early) {
178
- sub = new Subscriber(source(), update);
213
+ sub = new Subscriber(source(), defer);
179
214
  defineProperty(state, key, {
180
215
  value: undefined,
181
216
  writable: true
@@ -195,16 +230,19 @@ function prepare(parent, key, source, setter, getter) {
195
230
  }
196
231
  finally {
197
232
  sub.commit();
198
- for (const key in sub.follows)
199
- prioritize(key);
233
+ for (const key in sub.handle) {
234
+ const peer = register.get(key);
235
+ if (peer && peer.priority >= info.priority)
236
+ info.priority = peer.priority + 1;
237
+ }
200
238
  }
201
239
  return current();
202
240
  }
203
241
  setAlias(update, `try ${key}`);
204
242
  setAlias(create, `new ${key}`);
205
243
  setAlias(setter, `run ${key}`);
206
- ComputedInit.add(create);
207
- ComputedInfo.set(update, info);
244
+ INIT.add(create);
245
+ INFO.set(update, info);
208
246
  for (const on of [state, subject])
209
247
  defineProperty(on, key, {
210
248
  get: create,
@@ -212,53 +250,27 @@ function prepare(parent, key, source, setter, getter) {
212
250
  enumerable: true
213
251
  });
214
252
  }
215
- function register(on, key, info) {
216
- let register = ComputedUsed.get(on);
217
- if (!register)
218
- ComputedUsed.set(on, register = new Map());
219
- register.set(key, info);
220
- return (key) => {
221
- const peer = register.get(key);
222
- if (peer && peer.priority >= info.priority)
223
- info.priority = peer.priority + 1;
224
- };
225
- }
226
253
  function ensure(on, keys) {
227
254
  for (const key of keys) {
228
255
  const desc = getOwnPropertyDescriptor(on.subject, key);
229
256
  const getter = desc && desc.get;
230
- if (ComputedInit.has(getter))
257
+ if (INIT.has(getter))
231
258
  getter(true);
232
259
  }
233
260
  }
234
- function capture(on, request) {
235
- const compute = ComputedInfo.get(request);
236
- const callback = request;
237
- if (!compute)
238
- return;
239
- let pending = ComputedKeys.get(on);
240
- if (!pending)
241
- ComputedKeys.set(on, pending = []);
242
- if (compute.parent !== on)
243
- callback();
244
- else {
245
- const queue = pending.findIndex(peer => compute.priority > ComputedInfo.get(peer).priority);
246
- pending.splice(queue + 1, 0, callback);
247
- }
248
- return true;
249
- }
250
261
  function flush(on) {
251
262
  const handled = on.frame;
252
- let pending = ComputedKeys.get(on);
253
- if (pending) {
254
- while (pending.length) {
255
- const compute = pending.shift();
256
- const { key } = ComputedInfo.get(compute);
257
- if (!handled.has(key))
258
- compute();
259
- }
260
- ComputedKeys.delete(on);
261
- }
263
+ const pending = KEYS.get(on);
264
+ if (!pending)
265
+ return;
266
+ while (pending.length) {
267
+ const why = [...handled];
268
+ const compute = pending.shift();
269
+ const { key } = INFO.get(compute);
270
+ if (!handled.has(key))
271
+ compute(why);
272
+ }
273
+ KEYS.delete(on);
262
274
  }
263
275
 
264
276
  const Lifecycle = {
@@ -309,6 +321,7 @@ function eventsFor(prefix) {
309
321
  }
310
322
 
311
323
  const CONTROL = Symbol("control");
324
+ const UPDATE = Symbol("update");
312
325
  const LOCAL = Symbol("local");
313
326
  const STATE = Symbol("state");
314
327
  function manage(src) {
@@ -337,8 +350,8 @@ class Controller {
337
350
  this.subject = subject;
338
351
  this.state = {};
339
352
  this.frame = new Set();
340
- this.handles = new Set();
341
- this.waiting = [];
353
+ this.waiting = new Set();
354
+ this.followers = new Set();
342
355
  }
343
356
  static setup(onto) {
344
357
  const control = new this(onto);
@@ -404,18 +417,12 @@ class Controller {
404
417
  this.update(key, value);
405
418
  };
406
419
  }
407
- addListener(batch) {
408
- this.handles.add(batch);
420
+ addListener(listener) {
421
+ this.followers.add(listener);
409
422
  return () => {
410
- this.handles.delete(batch);
423
+ this.followers.delete(listener);
411
424
  };
412
425
  }
413
- include(to) {
414
- if (capture(this, to))
415
- return;
416
- else
417
- this.waiting.push(to);
418
- }
419
426
  update(key, value) {
420
427
  if (1 in arguments)
421
428
  this.state[key] = value;
@@ -424,15 +431,22 @@ class Controller {
424
431
  if (!this.frame.size)
425
432
  setTimeout(() => this.emit(), 0);
426
433
  this.frame.add(key);
427
- for (const subscription of this.handles)
428
- if (key in subscription)
429
- this.include(subscription[key]);
434
+ for (const subscription of this.followers) {
435
+ const notify = subscription(key, this);
436
+ if (notify)
437
+ this.waiting.add(notify);
438
+ }
430
439
  }
431
440
  emit() {
432
441
  flush(this);
433
- const keys = Array.from(this.frame);
434
- const handle = new Set(this.waiting.splice(0));
442
+ const keys = Object.freeze([...this.frame]);
443
+ const handle = new Set(this.waiting);
444
+ this.waiting.clear();
435
445
  this.frame.clear();
446
+ defineProperty(this.subject, UPDATE, {
447
+ configurable: true,
448
+ value: keys
449
+ });
436
450
  handle.forEach(callback => {
437
451
  try {
438
452
  callback(keys);
@@ -454,43 +468,42 @@ function keys(from, using) {
454
468
  return from.select(using);
455
469
  }
456
470
 
457
- function child(from, name) {
471
+ function child(source, name) {
458
472
  return apply(function child(key) {
459
- const proxyCache = new WeakMap();
460
- const getCurrent = from.call(this, key);
473
+ const context = new WeakMap();
474
+ const current = source.call(this, key);
461
475
  function subscribe(sub) {
462
476
  let child;
463
477
  function start() {
464
- const instance = getCurrent();
478
+ const instance = current();
465
479
  if (instance) {
466
480
  child = new Subscriber(instance, sub.onUpdate);
467
481
  if (sub.active)
468
482
  child.commit();
469
483
  sub.dependant.add(child);
470
- proxyCache.set(sub, child.proxy);
484
+ context.set(sub, child.proxy);
471
485
  }
472
486
  }
473
- function refresh() {
487
+ function restart() {
474
488
  if (child) {
475
489
  child.release();
476
490
  sub.dependant.delete(child);
477
- proxyCache.set(sub, undefined);
491
+ context.set(sub, undefined);
478
492
  child = undefined;
479
493
  }
480
494
  start();
481
- sub.onUpdate();
482
495
  }
483
496
  start();
484
- sub.follow(key, refresh);
497
+ sub.follow(key, restart);
485
498
  }
486
499
  return (local) => {
487
500
  if (!local)
488
- return getCurrent();
489
- if (!proxyCache.has(local))
501
+ return current();
502
+ if (!context.has(local))
490
503
  subscribe(local);
491
- return proxyCache.get(local);
504
+ return context.get(local);
492
505
  };
493
- }, name || from.name);
506
+ }, name || source.name);
494
507
  }
495
508
 
496
509
  const Oops$7 = issues({
@@ -514,7 +527,7 @@ function useLazy(Type, callback) {
514
527
  }
515
528
  function useWatcher(target, focus, expected) {
516
529
  const hook = use$1(refresh => {
517
- const sub = new Subscriber(target, refresh);
530
+ const sub = new Subscriber(target, () => refresh);
518
531
  if (focus) {
519
532
  const src = sub.proxy;
520
533
  const key = keys(sub.parent, focus)[0];
@@ -535,12 +548,13 @@ function useModel(Type, callback) {
535
548
  const hook = use$1(refresh => {
536
549
  const instance = new Type();
537
550
  const control = manage(instance);
538
- const sub = new Subscriber(control, refresh);
539
- const release = control.addListener({
540
- [Lifecycle.WILL_UNMOUNT]() {
541
- instance.destroy();
542
- release();
543
- }
551
+ const sub = new Subscriber(control, () => refresh);
552
+ const release = control.addListener((key) => {
553
+ if (key == Lifecycle.WILL_UNMOUNT)
554
+ return () => {
555
+ instance.destroy();
556
+ release();
557
+ };
544
558
  });
545
559
  if (callback)
546
560
  callback(instance);
@@ -706,7 +720,7 @@ function RenderFunction(props) {
706
720
  const hook = use$1(refresh => {
707
721
  const targets = props.context.local;
708
722
  if (targets.length == 1)
709
- return new Subscriber(targets[0], refresh);
723
+ return new Subscriber(targets[0], () => refresh);
710
724
  return {};
711
725
  });
712
726
  if (hook.commit)
@@ -737,7 +751,7 @@ class Model {
737
751
  define(this, "set", this);
738
752
  defer(control, "on");
739
753
  defer(control, "effect");
740
- control.include(() => {
754
+ control.waiting.add(() => {
741
755
  delete this.on;
742
756
  delete this.effect;
743
757
  });
@@ -749,7 +763,7 @@ class Model {
749
763
  }
750
764
  tag(id) {
751
765
  const hook = use$1(refresh => {
752
- return new Subscriber(this, refresh);
766
+ return new Subscriber(this, () => refresh);
753
767
  });
754
768
  useElementLifecycle(hook, id || 0);
755
769
  return hook.proxy;
@@ -758,29 +772,27 @@ class Model {
758
772
  const hook = use$1(refresh => {
759
773
  if (callback)
760
774
  callback(this);
761
- return new Subscriber(this, refresh);
775
+ return new Subscriber(this, () => refresh);
762
776
  });
763
777
  useComponentLifecycle(hook);
764
778
  return hook.proxy;
765
779
  }
766
780
  on(subset, handler, squash, once) {
767
781
  const control = manage(this);
768
- const set = keys(control, subset);
769
- const batch = {};
770
- const remove = control.addListener(batch);
782
+ const request = keys(control, subset);
771
783
  const callback = squash
772
784
  ? handler.bind(this)
773
- : (frame) => {
774
- for (const key of frame)
775
- if (set.includes(key))
776
- handler.call(this, control.state[key], key);
777
- };
778
- const handle = once
779
- ? (k) => { remove(); callback(k); }
785
+ : frame => frame
786
+ .filter(k => request.includes(k))
787
+ .forEach(k => handler.call(this, control.state[k], k));
788
+ const trigger = once
789
+ ? frame => { remove(); callback(frame); }
780
790
  : callback;
781
- for (const key of set)
782
- batch[key] = handle;
783
- ensure(control, set);
791
+ ensure(control, request);
792
+ const remove = control.addListener(key => {
793
+ if (request.includes(key))
794
+ return trigger;
795
+ });
784
796
  return remove;
785
797
  }
786
798
  once(select, callback, squash) {
@@ -801,7 +813,7 @@ class Model {
801
813
  return this.on(select, invoke, true);
802
814
  }
803
815
  else {
804
- const sub = new Subscriber(control, invoke);
816
+ const sub = new Subscriber(control, () => invoke);
805
817
  target = sub.proxy;
806
818
  invoke();
807
819
  return sub.commit();
@@ -842,7 +854,7 @@ class Model {
842
854
  then(callback) {
843
855
  if (callback)
844
856
  if (control.pending)
845
- control.include(callback);
857
+ control.waiting.add(callback);
846
858
  else
847
859
  callback(false);
848
860
  else
@@ -910,6 +922,7 @@ class Model {
910
922
  Model.CONTROL = CONTROL;
911
923
  Model.STATE = STATE;
912
924
  Model.LOCAL = LOCAL;
925
+ Model.WHY = UPDATE;
913
926
  defineLazy(Model, CONTROL, function () {
914
927
  return new Controller(this).start();
915
928
  });
@@ -918,7 +931,7 @@ function defer(on, method) {
918
931
  const real = subject[method];
919
932
  subject[method] = (...args) => {
920
933
  let done;
921
- waiting.push(() => {
934
+ waiting.add(() => {
922
935
  done = real.apply(subject, args);
923
936
  });
924
937
  return () => done();
@@ -998,6 +1011,31 @@ function parent(Expects, required) {
998
1011
  });
999
1012
  }
1000
1013
 
1014
+ function on(initial, cb) {
1015
+ return apply(function on(key) {
1016
+ this.manage(key, initial, cb && createValueEffect(cb));
1017
+ });
1018
+ }
1019
+ function memo(factory, defer) {
1020
+ return apply(function memo(key) {
1021
+ const source = this.subject;
1022
+ const get = () => factory.call(source);
1023
+ if (defer)
1024
+ defineLazy(source, key, get);
1025
+ else
1026
+ define(source, key, get());
1027
+ });
1028
+ }
1029
+ function lazy(value) {
1030
+ return apply(function lazy(key) {
1031
+ const source = this.subject;
1032
+ source[key] = value;
1033
+ defineProperty(this.state, key, {
1034
+ get: () => source[key]
1035
+ });
1036
+ });
1037
+ }
1038
+
1001
1039
  const Oops$2 = issues({
1002
1040
  DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
1003
1041
  BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
@@ -1024,30 +1062,6 @@ function ref(arg) {
1024
1062
  return { value };
1025
1063
  });
1026
1064
  }
1027
- function on(initial, cb) {
1028
- return apply(function on(key) {
1029
- this.manage(key, initial, cb && createValueEffect(cb));
1030
- });
1031
- }
1032
- function memo(factory, defer) {
1033
- return apply(function memo(key) {
1034
- const source = this.subject;
1035
- const get = () => factory.call(source);
1036
- if (defer)
1037
- defineLazy(source, key, get);
1038
- else
1039
- define(source, key, get());
1040
- });
1041
- }
1042
- function lazy(value) {
1043
- return apply(function lazy(key) {
1044
- const source = this.subject;
1045
- source[key] = value;
1046
- defineProperty(this.state, key, {
1047
- get: () => source[key]
1048
- });
1049
- });
1050
- }
1051
1065
  function act(task) {
1052
1066
  return apply(function act(key) {
1053
1067
  let pending = false;
@@ -1120,13 +1134,14 @@ function pendingValue(via, key) {
1120
1134
  return value;
1121
1135
  const error = Oops$1.ValueNotReady(via.subject, key);
1122
1136
  const promise = new Promise(resolve => {
1123
- const release = via.addListener({
1124
- [key](value) {
1125
- if (value !== undefined) {
1126
- release();
1127
- resolve();
1128
- }
1129
- }
1137
+ const release = via.addListener(forKey => {
1138
+ if (forKey == key)
1139
+ return () => {
1140
+ if (via.state[key] !== undefined) {
1141
+ release();
1142
+ resolve();
1143
+ }
1144
+ };
1130
1145
  });
1131
1146
  });
1132
1147
  throw Object.assign(promise, {