@expressive/mvc 1.0.0-beta.24 → 1.0.0-beta.28

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
@@ -24,7 +24,7 @@ function issues(register) {
24
24
  return Library;
25
25
  }
26
26
 
27
- const Oops$9 = issues({
27
+ const Oops$8 = issues({
28
28
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
29
29
  });
30
30
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, getOwnPropertySymbols, keys: keys$1, values } = Object;
@@ -53,7 +53,7 @@ function createEffect(callback) {
53
53
  if (unSet instanceof Promise)
54
54
  unSet = undefined;
55
55
  if (unSet && typeof unSet !== "function")
56
- throw Oops$9.BadEffectCallback();
56
+ throw Oops$8.BadEffectCallback();
57
57
  };
58
58
  }
59
59
  function createValueEffect(callback) {
@@ -69,7 +69,7 @@ function createValueEffect(callback) {
69
69
  if (typeof out == "function")
70
70
  unSet = out;
71
71
  else
72
- throw Oops$9.BadEffectCallback();
72
+ throw Oops$8.BadEffectCallback();
73
73
  };
74
74
  }
75
75
  function select(source, select) {
@@ -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,43 +143,47 @@ 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
  }
139
158
  }
140
159
 
141
- const Oops$8 = issues({
160
+ const Oops$7 = 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);
160
184
  }
161
185
  catch (err) {
162
- Oops$8.ComputeFailed(subject, key, !!initial).warn();
186
+ Oops$7.ComputeFailed(subject, key, !!initial).warn();
163
187
  throw err;
164
188
  }
165
189
  }
@@ -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
@@ -194,21 +229,24 @@ function prepare(parent, key, source, setter, getter) {
194
229
  }
195
230
  catch (e) {
196
231
  if (early)
197
- Oops$8.ComputedEarly(key).warn();
232
+ Oops$7.ComputedEarly(key).warn();
198
233
  throw e;
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);
@@ -448,104 +462,52 @@ class Controller {
448
462
  function keys(from, using) {
449
463
  if (typeof using == "string")
450
464
  return [using];
451
- if (typeof using == "object")
452
- return Array.from(using);
465
+ if (typeof using == "object") {
466
+ using = Array.from(using);
467
+ if (using.length === 0)
468
+ using = undefined;
469
+ else
470
+ return using;
471
+ }
453
472
  return from.select(using);
454
473
  }
455
474
 
456
- function child(from, name) {
475
+ function child(source, name) {
457
476
  return apply(function child(key) {
458
- const proxyCache = new WeakMap();
459
- const getCurrent = from.call(this, key);
477
+ const context = new WeakMap();
478
+ const current = source.call(this, key);
460
479
  function subscribe(sub) {
461
480
  let child;
462
481
  function start() {
463
- const instance = getCurrent();
482
+ const instance = current();
464
483
  if (instance) {
465
484
  child = new Subscriber(instance, sub.onUpdate);
466
485
  if (sub.active)
467
486
  child.commit();
468
487
  sub.dependant.add(child);
469
- proxyCache.set(sub, child.proxy);
488
+ context.set(sub, child.proxy);
470
489
  }
471
490
  }
472
- function refresh() {
491
+ function restart() {
473
492
  if (child) {
474
493
  child.release();
475
494
  sub.dependant.delete(child);
476
- proxyCache.set(sub, undefined);
495
+ context.set(sub, undefined);
477
496
  child = undefined;
478
497
  }
479
498
  start();
480
- sub.onUpdate();
481
499
  }
482
500
  start();
483
- sub.follow(key, refresh);
501
+ sub.follow(key, restart);
484
502
  }
485
503
  return (local) => {
486
504
  if (!local)
487
- return getCurrent();
488
- if (!proxyCache.has(local))
505
+ return current();
506
+ if (!context.has(local))
489
507
  subscribe(local);
490
- return proxyCache.get(local);
508
+ return context.get(local);
491
509
  };
492
- }, name || from.name);
493
- }
494
-
495
- const Oops$7 = issues({
496
- HasPropertyUndefined: (control, property) => `${control}.${property} is marked as required for this render.`
497
- });
498
- function use$1(init) {
499
- const [state, update] = react.useState(() => [
500
- init(() => update(state.concat()))
501
- ]);
502
- return state[0];
503
- }
504
- function useLazy(Type, callback) {
505
- const instance = react.useMemo(() => {
506
- const instance = Type.create();
507
- if (callback)
508
- callback(instance);
509
- return instance;
510
- }, []);
511
- react.useLayoutEffect(() => () => instance.destroy(), []);
512
- return instance;
513
- }
514
- function useWatcher(target, focus, expected) {
515
- const hook = use$1(refresh => {
516
- const sub = new Subscriber(target, refresh);
517
- if (focus) {
518
- const src = sub.proxy;
519
- const key = keys(sub.parent, focus)[0];
520
- defineProperty(sub, "proxy", {
521
- get() {
522
- if (src[key] === undefined && expected)
523
- throw Oops$7.HasPropertyUndefined(target, key);
524
- return src[key];
525
- }
526
- });
527
- }
528
- return sub;
529
- });
530
- react.useLayoutEffect(() => hook.commit(), []);
531
- return hook.proxy;
532
- }
533
- function useModel(Type, callback) {
534
- const hook = use$1(refresh => {
535
- const instance = new Type();
536
- const control = manage(instance);
537
- const sub = new Subscriber(control, refresh);
538
- const release = control.addListener({
539
- [Lifecycle.WILL_UNMOUNT]() {
540
- instance.destroy();
541
- release();
542
- }
543
- });
544
- if (callback)
545
- callback(instance);
546
- return sub;
547
- });
548
- return hook.proxy;
510
+ }, name || source.name);
549
511
  }
550
512
 
551
513
  const Oops$6 = issues({
@@ -598,6 +560,219 @@ function getPending(subject) {
598
560
  return pending;
599
561
  }
600
562
 
563
+ const Oops$5 = issues({
564
+ DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
565
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
566
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
567
+ });
568
+ function createRef(key, cb) {
569
+ const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
570
+ defineProperty(refObjectFunction, "current", {
571
+ set: refObjectFunction,
572
+ get: () => this.state[key]
573
+ });
574
+ return refObjectFunction;
575
+ }
576
+ function ref(arg) {
577
+ return apply(function ref(key) {
578
+ let value = {};
579
+ if (typeof arg == "object") {
580
+ const source = manage(arg);
581
+ for (const key in source.state)
582
+ defineLazy(value, key, createRef.bind(source, key));
583
+ }
584
+ else
585
+ value = createRef.call(this, key, arg);
586
+ return { value };
587
+ });
588
+ }
589
+ function act(task) {
590
+ return apply(function act(key) {
591
+ let pending = false;
592
+ const invoke = (...args) => {
593
+ if (pending)
594
+ return Promise.reject(Oops$5.DuplicateAction(key));
595
+ pending = true;
596
+ this.update(key);
597
+ return new Promise(res => {
598
+ res(task.apply(this.subject, args));
599
+ }).finally(() => {
600
+ pending = false;
601
+ this.update(key);
602
+ });
603
+ };
604
+ setAlias(invoke, `run ${key}`);
605
+ defineProperty(invoke, "active", {
606
+ get: () => pending
607
+ });
608
+ return {
609
+ value: invoke,
610
+ writable: false
611
+ };
612
+ });
613
+ }
614
+ function from(source, setter, getter) {
615
+ return apply(function from(key) {
616
+ const { subject } = this;
617
+ let getSource;
618
+ // Easy mistake, using a peer, will always be unresolved.
619
+ if (typeof source == "symbol")
620
+ throw Oops$5.PeerNotAllowed(subject, key);
621
+ // replace source controller in-case different
622
+ if (typeof source == "object")
623
+ getSource = () => manage(source);
624
+ // specifically an arrow function (getter factory)
625
+ else if (!source.prototype) {
626
+ setter = source.call(subject, key);
627
+ getSource = () => this;
628
+ }
629
+ // is a peer Model (constructor)
630
+ else if (Model.isTypeof(source))
631
+ getSource = pendingAccess(subject, source, key, true);
632
+ // Regular function is to ambiguous so not allowed.
633
+ else
634
+ throw Oops$5.BadComputedSource(subject, key, source);
635
+ prepare(this, key, getSource, setter, getter);
636
+ });
637
+ }
638
+
639
+ const Oops$4 = issues({
640
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
641
+ });
642
+ function pending(source, compute) {
643
+ return (source === undefined ?
644
+ suspendForValue() :
645
+ typeof source == "function" && !source.prototype ?
646
+ suspendForAsync(source) :
647
+ suspendForComputed(source, compute));
648
+ }
649
+ /**
650
+ * Get value, suspend instead if undefined.
651
+ *
652
+ * Throws suspense promise also interpretable as an error.
653
+ * React could handle it but other contexts probably not.
654
+ */
655
+ function pendingValue(via, key) {
656
+ const value = via.state[key];
657
+ if (value === undefined)
658
+ throw suspend(via, key);
659
+ return value;
660
+ }
661
+ function suspend(source, key) {
662
+ const error = Oops$4.ValueNotReady(source.subject, key);
663
+ const promise = new Promise(resolve => {
664
+ const release = source.addListener(forKey => {
665
+ if (forKey == key)
666
+ return () => {
667
+ if (source.state[key] !== undefined) {
668
+ release();
669
+ resolve();
670
+ }
671
+ };
672
+ });
673
+ });
674
+ return Object.assign(promise, {
675
+ toString: () => String(error),
676
+ name: "Suspense",
677
+ message: error.message,
678
+ stack: error.stack
679
+ });
680
+ }
681
+ function suspendForComputed(source, compute) {
682
+ function getter(_value, key) {
683
+ return pendingValue(manage(this), key);
684
+ }
685
+ return from(source, compute, getter);
686
+ }
687
+ function suspendForValue() {
688
+ return apply(function pending(key) {
689
+ this.state[key] = undefined;
690
+ return {
691
+ set: this.setter(key),
692
+ get: () => pendingValue(this, key)
693
+ };
694
+ });
695
+ }
696
+ function suspendForAsync(source) {
697
+ return apply(function suspense(key) {
698
+ const subject = this.subject;
699
+ let waiting = true;
700
+ let value;
701
+ let error;
702
+ return () => {
703
+ if (error)
704
+ throw error;
705
+ if (!waiting)
706
+ return value;
707
+ const issue = Oops$4.ValueNotReady(subject, key);
708
+ let suspend = source
709
+ .call(subject, key)
710
+ .catch(err => error = err)
711
+ .then(out => value = out)
712
+ .finally(() => waiting = false);
713
+ throw Object.assign(suspend, {
714
+ message: issue.message,
715
+ stack: issue.stack
716
+ });
717
+ };
718
+ });
719
+ }
720
+
721
+ function use$1(init) {
722
+ const [state, update] = react.useState(() => [
723
+ init(() => update(state.concat()))
724
+ ]);
725
+ return state[0];
726
+ }
727
+ function useLazy(Type, callback) {
728
+ const instance = react.useMemo(() => {
729
+ const instance = Type.create();
730
+ if (callback)
731
+ callback(instance);
732
+ return instance;
733
+ }, []);
734
+ react.useLayoutEffect(() => () => instance.destroy(), []);
735
+ return instance;
736
+ }
737
+ function useWatcher(target, focus, expected) {
738
+ const hook = use$1(refresh => {
739
+ const sub = new Subscriber(target, () => refresh);
740
+ if (focus) {
741
+ const source = sub.proxy;
742
+ const key = keys(sub.parent, focus)[0];
743
+ defineProperty(sub, "proxy", {
744
+ get() {
745
+ const value = source[key];
746
+ if (value === undefined && expected)
747
+ throw suspend(sub.parent, key);
748
+ return value;
749
+ }
750
+ });
751
+ }
752
+ return sub;
753
+ });
754
+ react.useLayoutEffect(() => hook.commit(), []);
755
+ return hook.proxy;
756
+ }
757
+ function useModel(Type, callback) {
758
+ const hook = use$1(refresh => {
759
+ const instance = new Type();
760
+ const control = manage(instance);
761
+ const sub = new Subscriber(control, () => refresh);
762
+ const release = control.addListener((key) => {
763
+ if (key == Lifecycle.WILL_UNMOUNT)
764
+ return () => {
765
+ instance.destroy();
766
+ release();
767
+ };
768
+ });
769
+ if (callback)
770
+ callback(instance);
771
+ return sub;
772
+ });
773
+ return hook.proxy;
774
+ }
775
+
601
776
  class Lookup {
602
777
  constructor() {
603
778
  this.table = new Map();
@@ -653,7 +828,7 @@ class Lookup {
653
828
  }
654
829
  }
655
830
 
656
- const Oops$5 = issues({
831
+ const Oops$3 = issues({
657
832
  NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`,
658
833
  NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`,
659
834
  BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
@@ -663,7 +838,7 @@ const useLookup = () => react.useContext(LookupContext);
663
838
  function useFromContext(Type, strict) {
664
839
  const instance = useLookup().get(Type);
665
840
  if (!instance && strict)
666
- throw Oops$5.NothingInContext(Type.name);
841
+ throw Oops$3.NothingInContext(Type.name);
667
842
  return instance;
668
843
  }
669
844
  function Consumer(props) {
@@ -674,13 +849,13 @@ function Consumer(props) {
674
849
  if (typeof callback == "function")
675
850
  callback(Control.get(!!has));
676
851
  else
677
- throw Oops$5.BadConsumerProps();
852
+ throw Oops$3.BadConsumerProps();
678
853
  return null;
679
854
  }
680
855
  function useNewContext(from, inject) {
681
856
  return react.useMemo(() => {
682
857
  if (!inject)
683
- throw Oops$5.NoProviderType();
858
+ throw Oops$3.NoProviderType();
684
859
  const context = from.push(inject);
685
860
  for (const instance of context.local)
686
861
  for (const apply of getPending(instance))
@@ -705,7 +880,7 @@ function RenderFunction(props) {
705
880
  const hook = use$1(refresh => {
706
881
  const targets = props.context.local;
707
882
  if (targets.length == 1)
708
- return new Subscriber(targets[0], refresh);
883
+ return new Subscriber(targets[0], () => refresh);
709
884
  return {};
710
885
  });
711
886
  if (hook.commit)
@@ -723,7 +898,7 @@ function Provider(props) {
723
898
  : render);
724
899
  }
725
900
 
726
- const Oops$4 = issues({
901
+ const Oops$2 = issues({
727
902
  StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
728
903
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
729
904
  });
@@ -736,7 +911,7 @@ class Model {
736
911
  define(this, "set", this);
737
912
  defer(control, "on");
738
913
  defer(control, "effect");
739
- control.include(() => {
914
+ control.waiting.add(() => {
740
915
  delete this.on;
741
916
  delete this.effect;
742
917
  });
@@ -748,7 +923,7 @@ class Model {
748
923
  }
749
924
  tag(id) {
750
925
  const hook = use$1(refresh => {
751
- return new Subscriber(this, refresh);
926
+ return new Subscriber(this, () => refresh);
752
927
  });
753
928
  useElementLifecycle(hook, id || 0);
754
929
  return hook.proxy;
@@ -757,29 +932,27 @@ class Model {
757
932
  const hook = use$1(refresh => {
758
933
  if (callback)
759
934
  callback(this);
760
- return new Subscriber(this, refresh);
935
+ return new Subscriber(this, () => refresh);
761
936
  });
762
937
  useComponentLifecycle(hook);
763
938
  return hook.proxy;
764
939
  }
765
940
  on(subset, handler, squash, once) {
766
941
  const control = manage(this);
767
- const set = keys(control, subset);
768
- const batch = {};
769
- const remove = control.addListener(batch);
942
+ const request = keys(control, subset);
770
943
  const callback = squash
771
944
  ? handler.bind(this)
772
- : (frame) => {
773
- for (const key of frame)
774
- if (set.includes(key))
775
- handler.call(this, control.state[key], key);
776
- };
777
- const handle = once
778
- ? (k) => { remove(); callback(k); }
945
+ : frame => frame
946
+ .filter(k => request.includes(k))
947
+ .forEach(k => handler.call(this, control.state[k], k));
948
+ const trigger = once
949
+ ? frame => { remove(); callback(frame); }
779
950
  : callback;
780
- for (const key of set)
781
- batch[key] = handle;
782
- ensure(control, set);
951
+ ensure(control, request);
952
+ const remove = control.addListener(key => {
953
+ if (request.includes(key))
954
+ return trigger;
955
+ });
783
956
  return remove;
784
957
  }
785
958
  once(select, callback, squash) {
@@ -800,7 +973,7 @@ class Model {
800
973
  return this.on(select, invoke, true);
801
974
  }
802
975
  else {
803
- const sub = new Subscriber(control, invoke);
976
+ const sub = new Subscriber(control, () => invoke);
804
977
  target = sub.proxy;
805
978
  invoke();
806
979
  return sub.commit();
@@ -824,7 +997,7 @@ class Model {
824
997
  arg = select(this, arg);
825
998
  if (typeof arg == "boolean") {
826
999
  if (!control.pending === arg)
827
- return Promise.reject(Oops$4.StrictUpdate(arg));
1000
+ return Promise.reject(Oops$2.StrictUpdate(arg));
828
1001
  }
829
1002
  else if (arg) {
830
1003
  control.update(arg);
@@ -841,11 +1014,11 @@ class Model {
841
1014
  then(callback) {
842
1015
  if (callback)
843
1016
  if (control.pending)
844
- control.include(callback);
1017
+ control.waiting.add(callback);
845
1018
  else
846
1019
  callback(false);
847
1020
  else
848
- throw Oops$4.NoChaining();
1021
+ throw Oops$2.NoChaining();
849
1022
  }
850
1023
  };
851
1024
  }
@@ -909,6 +1082,7 @@ class Model {
909
1082
  Model.CONTROL = CONTROL;
910
1083
  Model.STATE = STATE;
911
1084
  Model.LOCAL = LOCAL;
1085
+ Model.WHY = UPDATE;
912
1086
  defineLazy(Model, CONTROL, function () {
913
1087
  return new Controller(this).start();
914
1088
  });
@@ -917,7 +1091,7 @@ function defer(on, method) {
917
1091
  const real = subject[method];
918
1092
  subject[method] = (...args) => {
919
1093
  let done;
920
- waiting.push(() => {
1094
+ waiting.add(() => {
921
1095
  done = real.apply(subject, args);
922
1096
  });
923
1097
  return () => done();
@@ -925,11 +1099,13 @@ function defer(on, method) {
925
1099
  }
926
1100
 
927
1101
  const Parent = new WeakMap();
928
- const Oops$3 = issues({
1102
+ const Oops$1 = issues({
929
1103
  ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
930
1104
  UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
931
1105
  UndefinedNotAllowed: (key) => `Child property ${key} may not be undefined.`,
932
- BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`
1106
+ BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
1107
+ IsReadOnly: (owner, key) => `${owner}.${key} is set to read-only, but tried to reassign assign.`,
1108
+ MustBeDefined: (owner, key) => `${owner}.${key} must have initial value, as a read-only property.`
933
1109
  });
934
1110
  function bootstrap(object) {
935
1111
  const breakdown = getOwnPropertyDescriptors(object);
@@ -940,34 +1116,44 @@ function bootstrap(object) {
940
1116
  }
941
1117
  function use(input, argument) {
942
1118
  return child(function use(key) {
943
- let instance;
944
- const update = (next) => {
945
- instance =
946
- next instanceof Model ? next :
947
- next && bootstrap(next);
948
- if (instance) {
949
- Parent.set(instance, this.subject);
950
- manage(instance);
1119
+ const { subject } = this;
1120
+ // `true` => readonly
1121
+ // `false` => writable, may be undefined
1122
+ // `undefined` => writable (default)
1123
+ let mode = argument;
1124
+ let current;
1125
+ const onUpdate = (next, initial) => {
1126
+ if (mode === true && !initial)
1127
+ throw Oops$1.IsReadOnly(subject, key);
1128
+ if (next) {
1129
+ current = next instanceof Model
1130
+ ? next : bootstrap(next);
1131
+ Parent.set(current, subject);
1132
+ manage(current);
951
1133
  }
1134
+ else if (mode === undefined)
1135
+ throw Oops$1.UndefinedNotAllowed(key);
1136
+ else
1137
+ current = undefined;
952
1138
  if (typeof argument == "function")
953
- argument(instance);
954
- else if (!instance && argument !== false)
955
- throw Oops$3.UndefinedNotAllowed(key);
1139
+ return argument(current);
956
1140
  };
957
1141
  if (input) {
958
- instance =
1142
+ current =
959
1143
  Model.isTypeof(input) ? new input() :
960
1144
  input instanceof Model ? input :
961
1145
  typeof input === "function" ? input() :
962
1146
  typeof input === "object" ? bootstrap(input) :
963
- Oops$3.BadArgument(typeof input).throw();
964
- if (instance)
965
- update(instance);
1147
+ Oops$1.BadArgument(typeof input).throw();
1148
+ if (current)
1149
+ mode = onUpdate(current, true) || mode;
1150
+ else if (mode == true)
1151
+ throw Oops$1.MustBeDefined(subject, key);
966
1152
  }
967
1153
  else
968
- argument = false;
969
- this.manage(key, instance, update);
970
- return () => instance;
1154
+ mode = false;
1155
+ this.manage(key, current, onUpdate);
1156
+ return () => current;
971
1157
  });
972
1158
  }
973
1159
  function parent(Expects, required) {
@@ -977,40 +1163,14 @@ function parent(Expects, required) {
977
1163
  const value = Parent.get(this.subject);
978
1164
  if (!value) {
979
1165
  if (required)
980
- throw Oops$3.ParentRequired(expected, child);
1166
+ throw Oops$1.ParentRequired(expected, child);
981
1167
  }
982
1168
  else if (!(value instanceof Expects))
983
- throw Oops$3.UnexpectedParent(expected, child, value);
1169
+ throw Oops$1.UnexpectedParent(expected, child, value);
984
1170
  return { value };
985
1171
  });
986
1172
  }
987
1173
 
988
- const Oops$2 = issues({
989
- DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
990
- BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
991
- PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
992
- });
993
- function createRef(key, cb) {
994
- const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
995
- defineProperty(refObjectFunction, "current", {
996
- set: refObjectFunction,
997
- get: () => this.state[key]
998
- });
999
- return refObjectFunction;
1000
- }
1001
- function ref(arg) {
1002
- return apply(function ref(key) {
1003
- let value = {};
1004
- if (typeof arg == "object") {
1005
- const source = manage(arg);
1006
- for (const key in source.state)
1007
- defineLazy(value, key, createRef.bind(source, key));
1008
- }
1009
- else
1010
- value = createRef.call(this, key, arg);
1011
- return { value };
1012
- });
1013
- }
1014
1174
  function on(initial, cb) {
1015
1175
  return apply(function on(key) {
1016
1176
  this.manage(key, initial, cb && createValueEffect(cb));
@@ -1034,132 +1194,6 @@ function lazy(value) {
1034
1194
  get: () => source[key]
1035
1195
  });
1036
1196
  });
1037
- }
1038
- function act(task) {
1039
- return apply(function act(key) {
1040
- let pending = false;
1041
- const invoke = (...args) => {
1042
- if (pending)
1043
- return Promise.reject(Oops$2.DuplicateAction(key));
1044
- pending = true;
1045
- this.update(key);
1046
- return new Promise(res => {
1047
- res(task.apply(this.subject, args));
1048
- }).finally(() => {
1049
- pending = false;
1050
- this.update(key);
1051
- });
1052
- };
1053
- setAlias(invoke, `run ${key}`);
1054
- defineProperty(invoke, "active", {
1055
- get: () => pending
1056
- });
1057
- return {
1058
- value: invoke,
1059
- writable: false
1060
- };
1061
- });
1062
- }
1063
- function from(source, setter, getter) {
1064
- return apply(function from(key) {
1065
- const { subject } = this;
1066
- let getSource;
1067
- // Easy mistake, using a peer, will always be unresolved.
1068
- if (typeof source == "symbol")
1069
- throw Oops$2.PeerNotAllowed(subject, key);
1070
- // replace source controller in-case different
1071
- if (typeof source == "object")
1072
- getSource = () => manage(source);
1073
- // specifically an arrow function (getter factory)
1074
- else if (!source.prototype) {
1075
- setter = source.call(subject, key);
1076
- getSource = () => this;
1077
- }
1078
- // is a peer Model (constructor)
1079
- else if (Model.isTypeof(source))
1080
- getSource = pendingAccess(subject, source, key, true);
1081
- // Regular function is to ambiguous so not allowed.
1082
- else
1083
- throw Oops$2.BadComputedSource(subject, key, source);
1084
- prepare(this, key, getSource, setter, getter);
1085
- });
1086
- }
1087
-
1088
- const Oops$1 = issues({
1089
- ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
1090
- });
1091
- function pending(source, compute) {
1092
- return (source === undefined ?
1093
- suspendForValue() :
1094
- typeof source == "function" && !source.prototype ?
1095
- suspendForAsync(source) :
1096
- suspendForComputed(source, compute));
1097
- }
1098
- /**
1099
- * Get value, suspend instead if undefined.
1100
- *
1101
- * Throws suspense promise also interpretable as an error.
1102
- * React could handle it but other contexts probably not.
1103
- */
1104
- function pendingValue(via, key) {
1105
- const value = via.state[key];
1106
- if (value !== undefined)
1107
- return value;
1108
- const error = Oops$1.ValueNotReady(via.subject, key);
1109
- const promise = new Promise(resolve => {
1110
- const release = via.addListener({
1111
- [key](value) {
1112
- if (value !== undefined) {
1113
- release();
1114
- resolve();
1115
- }
1116
- }
1117
- });
1118
- });
1119
- throw Object.assign(promise, {
1120
- toString: () => String(error),
1121
- message: error.message,
1122
- stack: error.stack
1123
- });
1124
- }
1125
- function suspendForComputed(source, compute) {
1126
- function getter(_value, key) {
1127
- return pendingValue(manage(this), key);
1128
- }
1129
- return from(source, compute, getter);
1130
- }
1131
- function suspendForValue() {
1132
- return apply(function pending(key) {
1133
- this.state[key] = undefined;
1134
- return {
1135
- set: this.setter(key),
1136
- get: () => pendingValue(this, key)
1137
- };
1138
- });
1139
- }
1140
- function suspendForAsync(source) {
1141
- return apply(function suspense(key) {
1142
- const subject = this.subject;
1143
- let waiting = true;
1144
- let value;
1145
- let error;
1146
- return () => {
1147
- if (error)
1148
- throw error;
1149
- if (!waiting)
1150
- return value;
1151
- const issue = Oops$1.ValueNotReady(subject, key);
1152
- let suspend = source
1153
- .call(subject, key)
1154
- .catch(err => error = err)
1155
- .then(out => value = out)
1156
- .finally(() => waiting = false);
1157
- throw Object.assign(suspend, {
1158
- message: issue.message,
1159
- stack: issue.stack
1160
- });
1161
- };
1162
- });
1163
1197
  }
1164
1198
 
1165
1199
  const Oops = issues({