@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.js CHANGED
@@ -96,14 +96,34 @@ class Subscriber {
96
96
  constructor(parent, onUpdate) {
97
97
  this.onUpdate = onUpdate;
98
98
  this.active = false;
99
- this.follows = {};
99
+ this.handle = {};
100
100
  this.dependant = new Set();
101
+ this.debug = () => {
102
+ const update = [];
103
+ this.notify = keys => {
104
+ update.splice(0, update.length, ...keys.filter(k => k in this.handle));
105
+ };
106
+ return update;
107
+ };
108
+ this.listen = (key, from) => {
109
+ const handler = this.handle[key];
110
+ if (!handler)
111
+ return;
112
+ if (typeof handler == "function")
113
+ handler();
114
+ const notify = this.onUpdate(key, from);
115
+ if (notify)
116
+ from.waiting.add(notify);
117
+ if (this.notify)
118
+ from.waiting.add(this.notify);
119
+ };
101
120
  if (!(parent instanceof Controller))
102
121
  parent = manage(parent);
103
122
  this.parent = parent;
104
123
  this.source = parent.subject;
105
124
  this.proxy = create(parent.subject);
106
125
  define(this.proxy, LOCAL, this);
126
+ defineLazy(this.proxy, UPDATE, this.debug);
107
127
  for (const key in parent.state)
108
128
  this.spy(key);
109
129
  }
@@ -123,16 +143,15 @@ class Subscriber {
123
143
  enumerable: true
124
144
  });
125
145
  }
126
- follow(key, cb) {
127
- this.follows[key] = cb || this.onUpdate;
146
+ follow(key, callback) {
147
+ this.handle[key] = callback || true;
128
148
  }
129
149
  commit() {
130
- const { dependant, follows, parent } = this;
131
- const onDone = parent.addListener(follows);
150
+ const onDone = this.parent.addListener(this.listen);
132
151
  this.active = true;
133
- dependant.forEach(x => x.commit());
152
+ this.dependant.forEach(x => x.commit());
134
153
  return this.release = () => {
135
- dependant.forEach(x => x.release());
154
+ this.dependant.forEach(x => x.release());
136
155
  onDone();
137
156
  };
138
157
  }
@@ -142,18 +161,23 @@ const Oops$8 = issues({
142
161
  ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
143
162
  ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
144
163
  });
145
- const ComputedInit = new WeakSet();
146
- const ComputedInfo = new WeakMap();
147
- const ComputedUsed = new WeakMap();
148
- const ComputedKeys = new WeakMap();
164
+ const INIT = new WeakSet();
165
+ const INFO = new WeakMap();
166
+ const USED = new WeakMap();
167
+ const KEYS = new WeakMap();
149
168
  function prepare(parent, key, source, setter, getter) {
150
169
  let sub;
151
170
  const { state, subject } = parent;
152
171
  const info = { key, parent, priority: 1 };
172
+ let register = USED.get(parent);
173
+ if (!register) {
174
+ register = new Map();
175
+ USED.set(parent, register);
176
+ }
177
+ register.set(key, info);
153
178
  const current = getter
154
179
  ? () => getter.call(subject, state[key], key)
155
180
  : () => state[key];
156
- const prioritize = register(parent, key, info);
157
181
  function compute(initial) {
158
182
  try {
159
183
  return setter.call(sub.proxy, sub.proxy);
@@ -178,8 +202,19 @@ function prepare(parent, key, source, setter, getter) {
178
202
  }
179
203
  }
180
204
  }
205
+ function defer(_key, from) {
206
+ let pending = KEYS.get(from);
207
+ if (!pending)
208
+ KEYS.set(from, pending = []);
209
+ if (info.parent !== from)
210
+ update();
211
+ else {
212
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
213
+ pending.splice(after + 1, 0, update);
214
+ }
215
+ }
181
216
  function create(early) {
182
- sub = new Subscriber(source(), update);
217
+ sub = new Subscriber(source(), defer);
183
218
  defineProperty(state, key, {
184
219
  value: undefined,
185
220
  writable: true
@@ -199,16 +234,19 @@ function prepare(parent, key, source, setter, getter) {
199
234
  }
200
235
  finally {
201
236
  sub.commit();
202
- for (const key in sub.follows)
203
- prioritize(key);
237
+ for (const key in sub.handle) {
238
+ const peer = register.get(key);
239
+ if (peer && peer.priority >= info.priority)
240
+ info.priority = peer.priority + 1;
241
+ }
204
242
  }
205
243
  return current();
206
244
  }
207
245
  setAlias(update, `try ${key}`);
208
246
  setAlias(create, `new ${key}`);
209
247
  setAlias(setter, `run ${key}`);
210
- ComputedInit.add(create);
211
- ComputedInfo.set(update, info);
248
+ INIT.add(create);
249
+ INFO.set(update, info);
212
250
  for (const on of [state, subject])
213
251
  defineProperty(on, key, {
214
252
  get: create,
@@ -216,53 +254,27 @@ function prepare(parent, key, source, setter, getter) {
216
254
  enumerable: true
217
255
  });
218
256
  }
219
- function register(on, key, info) {
220
- let register = ComputedUsed.get(on);
221
- if (!register)
222
- ComputedUsed.set(on, register = new Map());
223
- register.set(key, info);
224
- return (key) => {
225
- const peer = register.get(key);
226
- if (peer && peer.priority >= info.priority)
227
- info.priority = peer.priority + 1;
228
- };
229
- }
230
257
  function ensure(on, keys) {
231
258
  for (const key of keys) {
232
259
  const desc = getOwnPropertyDescriptor(on.subject, key);
233
260
  const getter = desc && desc.get;
234
- if (ComputedInit.has(getter))
261
+ if (INIT.has(getter))
235
262
  getter(true);
236
263
  }
237
264
  }
238
- function capture(on, request) {
239
- const compute = ComputedInfo.get(request);
240
- const callback = request;
241
- if (!compute)
242
- return;
243
- let pending = ComputedKeys.get(on);
244
- if (!pending)
245
- ComputedKeys.set(on, pending = []);
246
- if (compute.parent !== on)
247
- callback();
248
- else {
249
- const queue = pending.findIndex(peer => compute.priority > ComputedInfo.get(peer).priority);
250
- pending.splice(queue + 1, 0, callback);
251
- }
252
- return true;
253
- }
254
265
  function flush(on) {
255
266
  const handled = on.frame;
256
- let pending = ComputedKeys.get(on);
257
- if (pending) {
258
- while (pending.length) {
259
- const compute = pending.shift();
260
- const { key } = ComputedInfo.get(compute);
261
- if (!handled.has(key))
262
- compute();
263
- }
264
- ComputedKeys.delete(on);
265
- }
267
+ const pending = KEYS.get(on);
268
+ if (!pending)
269
+ return;
270
+ while (pending.length) {
271
+ const why = [...handled];
272
+ const compute = pending.shift();
273
+ const { key } = INFO.get(compute);
274
+ if (!handled.has(key))
275
+ compute(why);
276
+ }
277
+ KEYS.delete(on);
266
278
  }
267
279
 
268
280
  const Lifecycle = {
@@ -313,6 +325,7 @@ function eventsFor(prefix) {
313
325
  }
314
326
 
315
327
  const CONTROL = Symbol("control");
328
+ const UPDATE = Symbol("update");
316
329
  const LOCAL = Symbol("local");
317
330
  const STATE = Symbol("state");
318
331
  function manage(src) {
@@ -341,8 +354,8 @@ class Controller {
341
354
  this.subject = subject;
342
355
  this.state = {};
343
356
  this.frame = new Set();
344
- this.handles = new Set();
345
- this.waiting = [];
357
+ this.waiting = new Set();
358
+ this.followers = new Set();
346
359
  }
347
360
  static setup(onto) {
348
361
  const control = new this(onto);
@@ -408,18 +421,12 @@ class Controller {
408
421
  this.update(key, value);
409
422
  };
410
423
  }
411
- addListener(batch) {
412
- this.handles.add(batch);
424
+ addListener(listener) {
425
+ this.followers.add(listener);
413
426
  return () => {
414
- this.handles.delete(batch);
427
+ this.followers.delete(listener);
415
428
  };
416
429
  }
417
- include(to) {
418
- if (capture(this, to))
419
- return;
420
- else
421
- this.waiting.push(to);
422
- }
423
430
  update(key, value) {
424
431
  if (1 in arguments)
425
432
  this.state[key] = value;
@@ -428,15 +435,22 @@ class Controller {
428
435
  if (!this.frame.size)
429
436
  setTimeout(() => this.emit(), 0);
430
437
  this.frame.add(key);
431
- for (const subscription of this.handles)
432
- if (key in subscription)
433
- this.include(subscription[key]);
438
+ for (const subscription of this.followers) {
439
+ const notify = subscription(key, this);
440
+ if (notify)
441
+ this.waiting.add(notify);
442
+ }
434
443
  }
435
444
  emit() {
436
445
  flush(this);
437
- const keys = Array.from(this.frame);
438
- const handle = new Set(this.waiting.splice(0));
446
+ const keys = Object.freeze([...this.frame]);
447
+ const handle = new Set(this.waiting);
448
+ this.waiting.clear();
439
449
  this.frame.clear();
450
+ defineProperty(this.subject, UPDATE, {
451
+ configurable: true,
452
+ value: keys
453
+ });
440
454
  handle.forEach(callback => {
441
455
  try {
442
456
  callback(keys);
@@ -458,43 +472,42 @@ function keys(from, using) {
458
472
  return from.select(using);
459
473
  }
460
474
 
461
- function child(from, name) {
475
+ function child(source, name) {
462
476
  return apply(function child(key) {
463
- const proxyCache = new WeakMap();
464
- const getCurrent = from.call(this, key);
477
+ const context = new WeakMap();
478
+ const current = source.call(this, key);
465
479
  function subscribe(sub) {
466
480
  let child;
467
481
  function start() {
468
- const instance = getCurrent();
482
+ const instance = current();
469
483
  if (instance) {
470
484
  child = new Subscriber(instance, sub.onUpdate);
471
485
  if (sub.active)
472
486
  child.commit();
473
487
  sub.dependant.add(child);
474
- proxyCache.set(sub, child.proxy);
488
+ context.set(sub, child.proxy);
475
489
  }
476
490
  }
477
- function refresh() {
491
+ function restart() {
478
492
  if (child) {
479
493
  child.release();
480
494
  sub.dependant.delete(child);
481
- proxyCache.set(sub, undefined);
495
+ context.set(sub, undefined);
482
496
  child = undefined;
483
497
  }
484
498
  start();
485
- sub.onUpdate();
486
499
  }
487
500
  start();
488
- sub.follow(key, refresh);
501
+ sub.follow(key, restart);
489
502
  }
490
503
  return (local) => {
491
504
  if (!local)
492
- return getCurrent();
493
- if (!proxyCache.has(local))
505
+ return current();
506
+ if (!context.has(local))
494
507
  subscribe(local);
495
- return proxyCache.get(local);
508
+ return context.get(local);
496
509
  };
497
- }, name || from.name);
510
+ }, name || source.name);
498
511
  }
499
512
 
500
513
  const Oops$7 = issues({
@@ -518,7 +531,7 @@ function useLazy(Type, callback) {
518
531
  }
519
532
  function useWatcher(target, focus, expected) {
520
533
  const hook = use$1(refresh => {
521
- const sub = new Subscriber(target, refresh);
534
+ const sub = new Subscriber(target, () => refresh);
522
535
  if (focus) {
523
536
  const src = sub.proxy;
524
537
  const key = keys(sub.parent, focus)[0];
@@ -539,12 +552,13 @@ function useModel(Type, callback) {
539
552
  const hook = use$1(refresh => {
540
553
  const instance = new Type();
541
554
  const control = manage(instance);
542
- const sub = new Subscriber(control, refresh);
543
- const release = control.addListener({
544
- [Lifecycle.WILL_UNMOUNT]() {
545
- instance.destroy();
546
- release();
547
- }
555
+ const sub = new Subscriber(control, () => refresh);
556
+ const release = control.addListener((key) => {
557
+ if (key == Lifecycle.WILL_UNMOUNT)
558
+ return () => {
559
+ instance.destroy();
560
+ release();
561
+ };
548
562
  });
549
563
  if (callback)
550
564
  callback(instance);
@@ -710,7 +724,7 @@ function RenderFunction(props) {
710
724
  const hook = use$1(refresh => {
711
725
  const targets = props.context.local;
712
726
  if (targets.length == 1)
713
- return new Subscriber(targets[0], refresh);
727
+ return new Subscriber(targets[0], () => refresh);
714
728
  return {};
715
729
  });
716
730
  if (hook.commit)
@@ -741,7 +755,7 @@ class Model {
741
755
  define(this, "set", this);
742
756
  defer(control, "on");
743
757
  defer(control, "effect");
744
- control.include(() => {
758
+ control.waiting.add(() => {
745
759
  delete this.on;
746
760
  delete this.effect;
747
761
  });
@@ -753,7 +767,7 @@ class Model {
753
767
  }
754
768
  tag(id) {
755
769
  const hook = use$1(refresh => {
756
- return new Subscriber(this, refresh);
770
+ return new Subscriber(this, () => refresh);
757
771
  });
758
772
  useElementLifecycle(hook, id || 0);
759
773
  return hook.proxy;
@@ -762,29 +776,27 @@ class Model {
762
776
  const hook = use$1(refresh => {
763
777
  if (callback)
764
778
  callback(this);
765
- return new Subscriber(this, refresh);
779
+ return new Subscriber(this, () => refresh);
766
780
  });
767
781
  useComponentLifecycle(hook);
768
782
  return hook.proxy;
769
783
  }
770
784
  on(subset, handler, squash, once) {
771
785
  const control = manage(this);
772
- const set = keys(control, subset);
773
- const batch = {};
774
- const remove = control.addListener(batch);
786
+ const request = keys(control, subset);
775
787
  const callback = squash
776
788
  ? handler.bind(this)
777
- : (frame) => {
778
- for (const key of frame)
779
- if (set.includes(key))
780
- handler.call(this, control.state[key], key);
781
- };
782
- const handle = once
783
- ? (k) => { remove(); callback(k); }
789
+ : frame => frame
790
+ .filter(k => request.includes(k))
791
+ .forEach(k => handler.call(this, control.state[k], k));
792
+ const trigger = once
793
+ ? frame => { remove(); callback(frame); }
784
794
  : callback;
785
- for (const key of set)
786
- batch[key] = handle;
787
- ensure(control, set);
795
+ ensure(control, request);
796
+ const remove = control.addListener(key => {
797
+ if (request.includes(key))
798
+ return trigger;
799
+ });
788
800
  return remove;
789
801
  }
790
802
  once(select, callback, squash) {
@@ -805,7 +817,7 @@ class Model {
805
817
  return this.on(select, invoke, true);
806
818
  }
807
819
  else {
808
- const sub = new Subscriber(control, invoke);
820
+ const sub = new Subscriber(control, () => invoke);
809
821
  target = sub.proxy;
810
822
  invoke();
811
823
  return sub.commit();
@@ -846,7 +858,7 @@ class Model {
846
858
  then(callback) {
847
859
  if (callback)
848
860
  if (control.pending)
849
- control.include(callback);
861
+ control.waiting.add(callback);
850
862
  else
851
863
  callback(false);
852
864
  else
@@ -914,6 +926,7 @@ class Model {
914
926
  Model.CONTROL = CONTROL;
915
927
  Model.STATE = STATE;
916
928
  Model.LOCAL = LOCAL;
929
+ Model.WHY = UPDATE;
917
930
  defineLazy(Model, CONTROL, function () {
918
931
  return new Controller(this).start();
919
932
  });
@@ -922,7 +935,7 @@ function defer(on, method) {
922
935
  const real = subject[method];
923
936
  subject[method] = (...args) => {
924
937
  let done;
925
- waiting.push(() => {
938
+ waiting.add(() => {
926
939
  done = real.apply(subject, args);
927
940
  });
928
941
  return () => done();
@@ -1002,6 +1015,31 @@ function parent(Expects, required) {
1002
1015
  });
1003
1016
  }
1004
1017
 
1018
+ function on(initial, cb) {
1019
+ return apply(function on(key) {
1020
+ this.manage(key, initial, cb && createValueEffect(cb));
1021
+ });
1022
+ }
1023
+ function memo(factory, defer) {
1024
+ return apply(function memo(key) {
1025
+ const source = this.subject;
1026
+ const get = () => factory.call(source);
1027
+ if (defer)
1028
+ defineLazy(source, key, get);
1029
+ else
1030
+ define(source, key, get());
1031
+ });
1032
+ }
1033
+ function lazy(value) {
1034
+ return apply(function lazy(key) {
1035
+ const source = this.subject;
1036
+ source[key] = value;
1037
+ defineProperty(this.state, key, {
1038
+ get: () => source[key]
1039
+ });
1040
+ });
1041
+ }
1042
+
1005
1043
  const Oops$2 = issues({
1006
1044
  DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
1007
1045
  BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
@@ -1028,30 +1066,6 @@ function ref(arg) {
1028
1066
  return { value };
1029
1067
  });
1030
1068
  }
1031
- function on(initial, cb) {
1032
- return apply(function on(key) {
1033
- this.manage(key, initial, cb && createValueEffect(cb));
1034
- });
1035
- }
1036
- function memo(factory, defer) {
1037
- return apply(function memo(key) {
1038
- const source = this.subject;
1039
- const get = () => factory.call(source);
1040
- if (defer)
1041
- defineLazy(source, key, get);
1042
- else
1043
- define(source, key, get());
1044
- });
1045
- }
1046
- function lazy(value) {
1047
- return apply(function lazy(key) {
1048
- const source = this.subject;
1049
- source[key] = value;
1050
- defineProperty(this.state, key, {
1051
- get: () => source[key]
1052
- });
1053
- });
1054
- }
1055
1069
  function act(task) {
1056
1070
  return apply(function act(key) {
1057
1071
  let pending = false;
@@ -1124,13 +1138,14 @@ function pendingValue(via, key) {
1124
1138
  return value;
1125
1139
  const error = Oops$1.ValueNotReady(via.subject, key);
1126
1140
  const promise = new Promise(resolve => {
1127
- const release = via.addListener({
1128
- [key](value) {
1129
- if (value !== undefined) {
1130
- release();
1131
- resolve();
1132
- }
1133
- }
1141
+ const release = via.addListener(forKey => {
1142
+ if (forKey == key)
1143
+ return () => {
1144
+ if (via.state[key] !== undefined) {
1145
+ release();
1146
+ resolve();
1147
+ }
1148
+ };
1134
1149
  });
1135
1150
  });
1136
1151
  throw Object.assign(promise, {