@expressive/mvc 0.7.2 → 0.8.0

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
@@ -1,4 +1,4 @@
1
- import React, { useLayoutEffect, useState, useMemo } from 'react';
1
+ import React, { useState, useMemo } from 'react';
2
2
 
3
3
  class Issue extends Error {
4
4
  constructor() {
@@ -18,10 +18,43 @@ function issues(register) {
18
18
  }
19
19
 
20
20
  const Oops$c = issues({
21
+ BadCallback: () => `Callback for effect-callback may only return a function.`
22
+ });
23
+ const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
24
+ function createEffect(callback) {
25
+ let unSet;
26
+ return function (value) {
27
+ if (typeof unSet == "function")
28
+ unSet();
29
+ unSet = callback.call(this, value);
30
+ if (unSet instanceof Promise)
31
+ unSet = undefined;
32
+ if (unSet && typeof unSet !== "function")
33
+ throw Oops$c.BadCallback();
34
+ };
35
+ }
36
+ function createValueEffect(callback) {
37
+ let unSet;
38
+ return function (value) {
39
+ if (typeof unSet == "function")
40
+ unSet(value);
41
+ const out = callback.call(this, value, this);
42
+ if (typeof out == "boolean")
43
+ return out;
44
+ if (!out || out instanceof Promise)
45
+ return;
46
+ if (typeof out == "function")
47
+ unSet = out;
48
+ else
49
+ throw Oops$c.BadCallback();
50
+ };
51
+ }
52
+
53
+ const Oops$b = issues({
21
54
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
22
55
  });
23
56
  function suspend(source, key) {
24
- const error = Oops$c.NotReady(source.subject, key);
57
+ const error = Oops$b.NotReady(source.subject, key);
25
58
  const promise = new Promise(resolve => {
26
59
  const release = source.addListener(forKey => {
27
60
  if (forKey == key)
@@ -33,7 +66,7 @@ function suspend(source, key) {
33
66
  };
34
67
  });
35
68
  });
36
- return Object.assign(promise, {
69
+ return assign(promise, {
37
70
  toString: () => String(error),
38
71
  name: "Suspense",
39
72
  message: error.message,
@@ -61,7 +94,7 @@ function mayRetry(fn) {
61
94
  return compute();
62
95
  }
63
96
 
64
- const Oops$b = issues({
97
+ const Oops$a = issues({
65
98
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
66
99
  });
67
100
  const INFO = new WeakMap();
@@ -78,7 +111,7 @@ function computeMode(self, source, setter, key, required) {
78
111
  return setter.call(sub.proxy, sub.proxy);
79
112
  }
80
113
  catch (err) {
81
- Oops$b.Failed(subject, key, initial).warn();
114
+ Oops$a.Failed(subject, key, initial).warn();
82
115
  throw err;
83
116
  }
84
117
  };
@@ -147,259 +180,20 @@ function flush(control) {
147
180
  KEYS.delete(control);
148
181
  }
149
182
 
150
- const Oops$a = issues({
151
- BadCallback: () => `Callback for effect-callback may only return a function.`
152
- });
153
- const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
154
- function createEffect(callback) {
155
- let unSet;
156
- return function (value) {
157
- if (typeof unSet == "function")
158
- unSet();
159
- unSet = callback.call(this, value);
160
- if (unSet instanceof Promise)
161
- unSet = undefined;
162
- if (unSet && typeof unSet !== "function")
163
- throw Oops$a.BadCallback();
164
- };
165
- }
166
- function createValueEffect(callback) {
167
- let unSet;
168
- return function (value) {
169
- if (typeof unSet == "function")
170
- unSet(value);
171
- const out = callback.call(this, value, this);
172
- if (typeof out == "boolean")
173
- return out;
174
- if (!out || out instanceof Promise)
175
- return;
176
- if (typeof out == "function")
177
- unSet = out;
178
- else
179
- throw Oops$a.BadCallback();
180
- };
181
- }
182
-
183
- const Oops$9 = issues({
184
- Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
185
- StrictUpdate: () => `Strict update() did not find pending updates.`,
186
- NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
187
- });
188
- const WHY = Symbol("UPDATE");
189
- const LOCAL = Symbol("LOCAL");
190
- const STATE = Symbol("STATE");
191
- class Model {
192
- constructor() {
193
- defineProperty(this, CONTROL, {
194
- value: new Controller(this)
195
- });
196
- defineProperty(this, "is", {
197
- value: this
198
- });
199
- }
200
- get [STATE]() {
201
- return this.export();
202
- }
203
- get [WHY]() {
204
- return UPDATE.get(this);
205
- }
206
- on(select, handler, squash, once) {
207
- return ensure(this, control => {
208
- if (typeof select == "function")
209
- return control.addListener(select);
210
- const keys = typeof select == "string" ? [select] :
211
- select.length ? select : [...control.state.keys()];
212
- for (const key of keys)
213
- try {
214
- void this[key];
215
- }
216
- catch (e) { }
217
- const callback = squash
218
- ? () => {
219
- handler.call(this, getUpdate(this));
220
- }
221
- : () => {
222
- getUpdate(this)
223
- .filter(k => keys.includes(k))
224
- .forEach(k => {
225
- handler.call(this, control.state.get(k), k);
226
- });
227
- };
228
- const onEvent = once
229
- ? () => {
230
- remove();
231
- callback();
232
- }
233
- : callback;
234
- const remove = control.addListener(key => {
235
- if (keys.includes(key))
236
- return onEvent;
237
- });
238
- return remove;
239
- });
240
- }
241
- once(select, argument, squash) {
242
- if (typeof argument == "function")
243
- return this.on(select, argument, squash, true);
244
- if (typeof select == "string")
245
- select = [select];
246
- return new Promise((resolve, reject) => {
247
- const invoke = (key) => {
248
- if (!key) {
249
- clear();
250
- reject(Oops$9.Timeout(select, typeof argument == "number"
251
- ? `${argument}ms`
252
- : `lifetime of ${this}`));
253
- }
254
- else if (select.includes(key)) {
255
- clear();
256
- return resolve;
257
- }
258
- };
259
- const clear = ensure(this, x => {
260
- for (const key of select)
261
- try {
262
- void this[key];
263
- }
264
- catch (e) { }
265
- return x.addListener(invoke);
266
- });
267
- if (typeof argument == "number")
268
- setTimeout(invoke, argument);
269
- });
270
- }
271
- effect(callback, select) {
272
- const effect = createEffect(callback);
273
- return ensure(this, control => {
274
- let busy = false;
275
- let inject = this.is;
276
- const invoke = () => {
277
- if (busy)
278
- return;
279
- const output = mayRetry(() => {
280
- effect.call(inject, inject);
281
- });
282
- if (output instanceof Promise) {
283
- output.finally(() => busy = false);
284
- busy = true;
285
- }
286
- };
287
- if (!select) {
288
- const sub = control.subscribe(() => invoke);
289
- inject = sub.proxy;
290
- invoke();
291
- return sub.commit();
292
- }
293
- invoke();
294
- return control.addListener(key => {
295
- if (key === null) {
296
- if (!select.length)
297
- invoke();
298
- }
299
- else if (select.includes(key))
300
- return invoke;
301
- });
302
- });
303
- }
304
- // TODO: account for exotic properties
305
- import(source, select) {
306
- const { subject } = ensure(this);
307
- if (!select)
308
- select = getOwnPropertyNames(subject);
309
- for (const key of select)
310
- if (key in source)
311
- this[key] = source[key];
312
- }
313
- export(subset) {
314
- const { state } = ensure(this);
315
- const output = {};
316
- const keys = subset || state.keys();
317
- for (const key of keys)
318
- output[key] = state.get(key);
319
- return output;
320
- }
321
- update(arg, tag) {
322
- const target = ensure(this);
323
- const { frame, waiting } = target;
324
- if (typeof arg == "string") {
325
- target.update(arg);
326
- if (1 in arguments && arg in this) {
327
- const method = this[arg];
328
- if (typeof method == "function")
329
- if (typeof tag != "boolean")
330
- method.call(this, tag);
331
- else if (tag)
332
- method.call(this);
333
- }
334
- arg = undefined;
335
- }
336
- if (!frame.size && arg === true)
337
- return Promise.reject(Oops$9.StrictUpdate());
338
- return {
339
- then: (callback) => {
340
- if (!callback)
341
- throw Oops$9.NoChaining();
342
- if (frame.size || arg !== false)
343
- waiting.add(() => {
344
- callback(getUpdate(this));
345
- });
346
- else
347
- callback(false);
348
- }
349
- };
350
- }
351
- /**
352
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
353
- */
354
- destroy() {
355
- ensure(this).clear();
356
- }
357
- toString() {
358
- return this.constructor.name;
359
- }
360
- /**
361
- * Creates a new instance of this controller.
362
- *
363
- * Beyond `new this(...)`, method will activate managed-state.
364
- *
365
- * @param args - arguments sent to constructor
366
- */
367
- static create(...args) {
368
- const instance = new this(...args);
369
- ensure(instance);
370
- return instance;
371
- }
372
- /**
373
- * Static equivalent of `x instanceof this`.
374
- *
375
- * Will determine if provided class is a subtype of this one.
376
- */
377
- static isTypeof(maybe) {
378
- return (typeof maybe == "function" &&
379
- maybe.prototype instanceof this);
380
- }
381
- }
382
-
183
+ const REGISTER$1 = new WeakMap();
383
184
  class Subscriber {
384
185
  constructor(parent, onUpdate) {
186
+ this.parent = parent;
385
187
  this.onUpdate = onUpdate;
386
188
  this.active = false;
387
189
  this.dependant = new Set();
388
190
  const proxy = create(parent.subject);
389
191
  const using = new Map();
390
- const listen = this.add = (key, value) => {
391
- if (value !== undefined)
392
- using.set(key, value);
393
- else if (!using.has(key))
394
- using.set(key, true);
395
- };
396
- LISTEN.set(proxy, listen);
397
- defineProperty(proxy, LOCAL, { value: this });
192
+ REGISTER$1.set(proxy, this);
398
193
  defineProperty(this, "proxy", {
399
194
  configurable: true,
400
195
  get() {
401
- if (UPDATE.has(proxy))
402
- setTimeout(() => UPDATE.delete(proxy), 0);
196
+ hasUpdate(proxy);
403
197
  return proxy;
404
198
  }
405
199
  });
@@ -417,12 +211,15 @@ class Subscriber {
417
211
  notify = this.onUpdate(key, parent);
418
212
  if (!notify)
419
213
  return;
420
- const notate = () => {
421
- UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
422
- };
423
- parent.waiting.add(notate);
214
+ parent.waiting.add(() => addUpdate(proxy, using));
424
215
  parent.waiting.add(notify);
425
216
  });
217
+ this.add = (key, value) => {
218
+ if (value !== undefined)
219
+ using.set(key, value);
220
+ else if (!using.has(key))
221
+ using.set(key, true);
222
+ };
426
223
  this.commit = () => {
427
224
  this.active = true;
428
225
  this.dependant.forEach(x => x.commit());
@@ -433,25 +230,42 @@ class Subscriber {
433
230
  release();
434
231
  };
435
232
  }
233
+ static get(from) {
234
+ return REGISTER$1.get(from);
235
+ }
436
236
  }
437
237
 
438
- const UPDATE = new WeakMap();
439
- const LISTEN = new WeakMap();
440
- const CONTROL = Symbol("CONTROL");
238
+ const REGISTER = new WeakMap();
239
+ const UPDATE$1 = new WeakMap();
441
240
  function getUpdate(subject) {
442
- return UPDATE.get(subject);
241
+ return UPDATE$1.get(subject);
242
+ }
243
+ function setUpdate(subject, keys) {
244
+ UPDATE$1.set(subject, Array.from(keys));
245
+ setTimeout(() => UPDATE$1.delete(subject), 0);
246
+ }
247
+ /** Ensure a local update is dropped after use. */
248
+ function hasUpdate(proxy) {
249
+ if (UPDATE$1.has(proxy))
250
+ setTimeout(() => UPDATE$1.delete(proxy), 0);
251
+ }
252
+ /** Set local update for a subscribed context. */
253
+ function addUpdate(proxy, using) {
254
+ const parent = UPDATE$1.get(getPrototypeOf(proxy));
255
+ UPDATE$1.set(proxy, parent.filter(k => using.has(k)));
443
256
  }
444
- class Controller {
257
+ class Control {
445
258
  constructor(subject) {
446
259
  this.subject = subject;
447
260
  this.frame = new Set();
448
261
  this.waiting = new Set();
449
262
  this.followers = new Set();
263
+ REGISTER.set(subject, this);
450
264
  }
451
265
  subscribe(callback) {
452
266
  return new Subscriber(this, callback);
453
267
  }
454
- add(key) {
268
+ watch(key) {
455
269
  const { subject, state } = this;
456
270
  const { value } = getOwnPropertyDescriptor(subject, key);
457
271
  if (typeof value == "function")
@@ -470,9 +284,9 @@ class Controller {
470
284
  enumerable: false,
471
285
  set: this.ref(key),
472
286
  get() {
473
- const listen = LISTEN.get(this);
474
- if (listen)
475
- listen(key);
287
+ const sub = Subscriber.get(this);
288
+ if (sub)
289
+ sub.add(key);
476
290
  return state.get(key);
477
291
  }
478
292
  });
@@ -500,16 +314,13 @@ class Controller {
500
314
  };
501
315
  }
502
316
  update(key) {
503
- const { followers, frame, subject, waiting } = this;
317
+ const { followers, frame, waiting } = this;
504
318
  if (frame.has(key))
505
319
  return;
506
320
  else if (!frame.size)
507
321
  setTimeout(() => {
508
322
  flush(this);
509
- UPDATE.set(subject, Array.from(frame));
510
- setTimeout(() => {
511
- UPDATE.delete(subject);
512
- }, 0);
323
+ setUpdate(this.subject, frame);
513
324
  frame.clear();
514
325
  for (const notify of waiting)
515
326
  try {
@@ -532,36 +343,35 @@ class Controller {
532
343
  this.followers.clear();
533
344
  listeners.forEach(x => x(null, this));
534
345
  }
535
- }
536
- function ensure(subject, cb) {
537
- let control = subject[CONTROL];
538
- if (!control)
539
- defineProperty(subject, CONTROL, {
540
- value: control =
541
- new Controller(subject)
542
- });
543
- if (!control.state) {
544
- const { waiting } = control;
545
- if (cb) {
546
- let done;
547
- waiting.add(() => {
548
- done = cb(control);
549
- });
550
- return () => done && done();
346
+ static get(from) {
347
+ if (from && "is" in from)
348
+ from = from.is;
349
+ return REGISTER.get(from);
350
+ }
351
+ static for(subject, cb) {
352
+ const control = this.get(subject) || new this(subject);
353
+ if (!control.state) {
354
+ const { waiting } = control;
355
+ if (cb) {
356
+ let done;
357
+ waiting.add(() => {
358
+ done = cb(control);
359
+ });
360
+ return () => done && done();
361
+ }
362
+ control.state = new Map();
363
+ for (const key in subject)
364
+ control.watch(key);
365
+ control.waiting = new Set();
366
+ for (const cb of waiting)
367
+ cb();
551
368
  }
552
- control.state = new Map();
553
- for (const key in subject)
554
- control.add(key);
555
- const callback = Array.from(waiting);
556
- waiting.clear();
557
- for (const cb of callback)
558
- cb();
369
+ return cb ? cb(control) : control;
559
370
  }
560
- return cb ? cb(control) : control;
561
371
  }
562
372
 
563
373
  const PENDING = new Map();
564
- function apply(fn, label) {
374
+ function add(fn, label) {
565
375
  const name = label || fn.name || "pending";
566
376
  const placeholder = Symbol(`${name} instruction`);
567
377
  function setup(key) {
@@ -575,7 +385,7 @@ function apply(fn, label) {
575
385
  defineProperty(subject, key, output);
576
386
  return false;
577
387
  }
578
- let { get: onGet, set: onSet, suspense } = output;
388
+ let { get: onGet, set: onSet, suspend: shouldSuspend } = output;
579
389
  if ("value" in output)
580
390
  state.set(key, output.value);
581
391
  if ("recursive" in output && output.recursive)
@@ -595,12 +405,11 @@ function apply(fn, label) {
595
405
  ? this.ref(key, onSet)
596
406
  : undefined,
597
407
  get() {
598
- if (!state.has(key) && suspense)
408
+ if (!state.has(key) && shouldSuspend)
599
409
  throw suspend(control, key);
600
- const listen = LISTEN.get(this);
601
- const local = this[LOCAL];
602
- if (listen)
603
- listen(key);
410
+ const local = Subscriber.get(this);
411
+ if (local)
412
+ local.add(key);
604
413
  return onGet
605
414
  ? onGet(local)
606
415
  : state.get(key);
@@ -621,8 +430,8 @@ function getRecursive(key, from) {
621
430
  if (reset)
622
431
  reset();
623
432
  const value = from.state.get(key);
624
- if (value && CONTROL in value) {
625
- const child = ensure(value).subscribe(local.onUpdate);
433
+ if (Control.get(value)) {
434
+ const child = Control.for(value).subscribe(local.onUpdate);
626
435
  if (local.active)
627
436
  child.commit();
628
437
  local.dependant.add(child);
@@ -668,15 +477,15 @@ function __awaiter(thisArg, _arguments, P, generator) {
668
477
  });
669
478
  }
670
479
 
671
- const Oops$8 = issues({
480
+ const Oops$9 = issues({
672
481
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
673
482
  });
674
483
  function act(task) {
675
- return apply(function act(key) {
484
+ return add(function act(key) {
676
485
  let pending = false;
677
486
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
678
487
  if (pending)
679
- return Promise.reject(Oops$8.DuplicatePending(key));
488
+ return Promise.reject(Oops$9.DuplicatePending(key));
680
489
  pending = true;
681
490
  this.update(key);
682
491
  try {
@@ -719,8 +528,8 @@ function factoryMode(self, output, key, required) {
719
528
  const suspend = () => {
720
529
  if (required === false)
721
530
  return undefined;
722
- const issue = Oops$7.NotReady(subject, key);
723
- Object.assign(pending, {
531
+ const issue = Oops$8.NotReady(subject, key);
532
+ assign(pending, {
724
533
  message: issue.message,
725
534
  stack: issue.stack
726
535
  });
@@ -736,7 +545,7 @@ function factoryMode(self, output, key, required) {
736
545
  };
737
546
  }
738
547
 
739
- const Oops$7 = issues({
548
+ const Oops$8 = issues({
740
549
  // BadSource: (model, property, got) =>
741
550
  // `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
742
551
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
@@ -746,14 +555,14 @@ const Oops$7 = issues({
746
555
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
747
556
  });
748
557
  function get(arg0, arg1, arg2) {
749
- return apply(function get(key) {
558
+ return add(function get(key) {
750
559
  const { subject } = this;
751
560
  const computeRequired = arg1 === true || arg2 === true;
752
561
  const factoryRequired = arg1 !== false && arg2 !== false;
753
562
  let getter;
754
563
  // Easy mistake, using a peer, will always be unresolved.
755
564
  if (typeof arg0 == "symbol")
756
- throw Oops$7.PeerNotAllowed(subject, key);
565
+ throw Oops$8.PeerNotAllowed(subject, key);
757
566
  // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
758
567
  // throw Oops.BadSource(subject, key, arg0);
759
568
  this.state.set(key, undefined);
@@ -775,7 +584,7 @@ function get(arg0, arg1, arg2) {
775
584
  }
776
585
  else if (typeof arg1 == "function") {
777
586
  // replace source controller in-case it is different
778
- source = ensure(arg0);
587
+ source = Control.for(arg0);
779
588
  setter = arg1;
780
589
  }
781
590
  else
@@ -787,7 +596,7 @@ function get(arg0, arg1, arg2) {
787
596
  init();
788
597
  }
789
598
  catch (err) {
790
- Oops$7.ComputeFailed(subject, key).warn();
599
+ Oops$8.ComputeFailed(subject, key).warn();
791
600
  throw err;
792
601
  }
793
602
  return () => {
@@ -799,12 +608,12 @@ function get(arg0, arg1, arg2) {
799
608
  }
800
609
 
801
610
  function ref(arg, mapper) {
802
- return apply(function ref(key) {
611
+ return add(function ref(key) {
803
612
  let value = {};
804
613
  if (typeof arg != "object")
805
614
  value = createRef(this, key, arg);
806
615
  else
807
- ensure(arg).state.forEach((_val, key) => {
616
+ Control.for(arg).state.forEach((_val, key) => {
808
617
  defineProperty(value, key, mapper ? {
809
618
  configurable: true,
810
619
  get() {
@@ -833,11 +642,11 @@ function set(value, argument) {
833
642
  argument = value;
834
643
  value = undefined;
835
644
  }
836
- return apply(function set(key) {
645
+ return add(function set(key) {
837
646
  if (value !== undefined)
838
647
  this.state.set(key, value);
839
648
  return {
840
- suspense: true,
649
+ suspend: true,
841
650
  set: typeof argument == "function"
842
651
  ? createValueEffect(argument)
843
652
  : undefined
@@ -992,12 +801,12 @@ function createProxy(emit, watch) {
992
801
  }
993
802
 
994
803
  const Parent = new WeakMap();
995
- const Oops$6 = issues({
804
+ const Oops$7 = issues({
996
805
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
997
806
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
998
807
  });
999
808
  function use$1(input, argument) {
1000
- return apply(function use(key) {
809
+ return add(function use(key) {
1001
810
  const { state, subject } = this;
1002
811
  const required = argument !== false;
1003
812
  let pending;
@@ -1009,7 +818,7 @@ function use$1(input, argument) {
1009
818
  input = new input();
1010
819
  }
1011
820
  else if (input && typeof input !== "object")
1012
- throw Oops$6.BadArgument(typeof input);
821
+ throw Oops$7.BadArgument(typeof input);
1013
822
  if (input instanceof Map || input instanceof Set)
1014
823
  return keyed(this, key, input);
1015
824
  const onUpdate = (next) => {
@@ -1017,7 +826,7 @@ function use$1(input, argument) {
1017
826
  if (next) {
1018
827
  get = getRecursive(key, this);
1019
828
  Parent.set(next, subject);
1020
- ensure(next);
829
+ Control.for(next);
1021
830
  }
1022
831
  if (typeof argument == "function")
1023
832
  argument(next);
@@ -1026,8 +835,8 @@ function use$1(input, argument) {
1026
835
  const suspend = () => {
1027
836
  if (required === false)
1028
837
  return;
1029
- const issue = Oops$6.NotReady(subject, key);
1030
- Object.assign(pending, {
838
+ const issue = Oops$7.NotReady(subject, key);
839
+ assign(pending, {
1031
840
  message: issue.message,
1032
841
  stack: issue.stack
1033
842
  });
@@ -1071,25 +880,204 @@ function use$1(input, argument) {
1071
880
  });
1072
881
  }
1073
882
 
1074
- const Oops$5 = issues({
883
+ const Oops$6 = issues({
1075
884
  Required: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
1076
885
  Unexpected: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
1077
886
  });
1078
- function has(Expects, required) {
1079
- return apply(function parent() {
887
+ function has(Type, required) {
888
+ return add(function parent() {
1080
889
  const child = this.subject;
1081
890
  const value = Parent.get(child);
1082
- const expected = Expects.name;
891
+ const expected = Type.name;
1083
892
  if (!value) {
1084
893
  if (required !== false)
1085
- throw Oops$5.Required(expected, child);
894
+ throw Oops$6.Required(expected, child);
1086
895
  }
1087
- else if (!(value instanceof Expects))
1088
- throw Oops$5.Unexpected(expected, child, value);
896
+ else if (!(value instanceof Type))
897
+ throw Oops$6.Unexpected(expected, child, value);
1089
898
  return { value };
1090
899
  });
1091
900
  }
1092
901
 
902
+ const Oops$5 = issues({
903
+ Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
904
+ StrictUpdate: () => `Strict update() did not find pending updates.`,
905
+ NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
906
+ });
907
+ class Model {
908
+ constructor() {
909
+ new Control(this);
910
+ defineProperty(this, "is", { value: this });
911
+ }
912
+ on(select, argument, once) {
913
+ if (typeof select == "function")
914
+ return Control.for(this, x => x.addListener(select));
915
+ const single = typeof select == "string";
916
+ let keys = single ? [select] : select;
917
+ if (typeof argument !== "function")
918
+ return new Promise((resolve, reject) => {
919
+ const removeListener = Control.for(this, control => {
920
+ for (const key of keys)
921
+ try {
922
+ void this[key];
923
+ }
924
+ catch (e) { }
925
+ return control.addListener(key => {
926
+ if (!key) {
927
+ removeListener();
928
+ reject(Oops$5.Timeout(select, `lifetime of ${this}`));
929
+ }
930
+ else if (keys.includes(key)) {
931
+ removeListener();
932
+ return () => resolve(single ? control.state.get(key) : getUpdate(this));
933
+ }
934
+ });
935
+ });
936
+ if (typeof argument == "number")
937
+ setTimeout(() => {
938
+ removeListener();
939
+ reject(Oops$5.Timeout(select, `${argument}ms`));
940
+ }, argument);
941
+ });
942
+ return Control.for(this, control => {
943
+ if (!keys.length)
944
+ keys = [...control.state.keys()];
945
+ for (const key of keys)
946
+ try {
947
+ void this[key];
948
+ }
949
+ catch (e) { }
950
+ const callback = single
951
+ ? () => { argument.call(this, control.state.get(select), select); }
952
+ : () => { argument.call(this, getUpdate(this)); };
953
+ const onEvent = once
954
+ ? () => {
955
+ removeListener();
956
+ callback();
957
+ }
958
+ : callback;
959
+ const removeListener = control.addListener(key => {
960
+ if (keys.includes(key))
961
+ return onEvent;
962
+ });
963
+ return removeListener;
964
+ });
965
+ }
966
+ effect(callback, select) {
967
+ const effect = createEffect(callback);
968
+ return Control.for(this, control => {
969
+ let busy = false;
970
+ let inject = this.is;
971
+ const invoke = () => {
972
+ if (busy)
973
+ return;
974
+ const output = mayRetry(() => {
975
+ effect.call(inject, inject);
976
+ });
977
+ if (output instanceof Promise) {
978
+ output.finally(() => busy = false);
979
+ busy = true;
980
+ }
981
+ };
982
+ if (!select) {
983
+ const sub = control.subscribe(() => invoke);
984
+ inject = sub.proxy;
985
+ invoke();
986
+ return sub.commit();
987
+ }
988
+ invoke();
989
+ return control.addListener(key => {
990
+ if (key === null) {
991
+ if (!select.length)
992
+ invoke();
993
+ }
994
+ else if (select.includes(key))
995
+ return invoke;
996
+ });
997
+ });
998
+ }
999
+ export(subset) {
1000
+ const { state } = Control.for(this);
1001
+ const output = {};
1002
+ const keys = subset || state.keys();
1003
+ for (const key of keys)
1004
+ output[key] = state.get(key);
1005
+ return output;
1006
+ }
1007
+ update(arg1, arg2) {
1008
+ const control = Control.for(this);
1009
+ const { frame, state, waiting } = control;
1010
+ if (arg1 === true && !frame.size)
1011
+ return Promise.reject(Oops$5.StrictUpdate());
1012
+ if (typeof arg1 == "object") {
1013
+ for (const key in arg1)
1014
+ if (arg2 === true || (arg2 ? arg2.includes(key) : state.has(key))) {
1015
+ state.set(key, arg1[key]);
1016
+ control.update(key);
1017
+ }
1018
+ }
1019
+ else if (typeof arg1 == "string") {
1020
+ control.update(arg1);
1021
+ if (1 in arguments && arg1 in this) {
1022
+ const method = this[arg1];
1023
+ if (typeof method == "function")
1024
+ if (typeof arg2 != "boolean")
1025
+ method.call(this, arg2);
1026
+ else if (arg2)
1027
+ method.call(this);
1028
+ }
1029
+ arg1 = undefined;
1030
+ }
1031
+ return {
1032
+ then: (callback) => {
1033
+ if (!callback)
1034
+ throw Oops$5.NoChaining();
1035
+ if (frame.size || arg1 !== false)
1036
+ waiting.add(() => {
1037
+ callback(getUpdate(this));
1038
+ });
1039
+ else
1040
+ callback(false);
1041
+ }
1042
+ };
1043
+ }
1044
+ /**
1045
+ * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
1046
+ */
1047
+ kill() {
1048
+ Control.for(this).clear();
1049
+ }
1050
+ /**
1051
+ * Creates a new instance of this controller.
1052
+ *
1053
+ * Beyond `new this(...)`, method will activate managed-state.
1054
+ *
1055
+ * @param args - arguments sent to constructor
1056
+ */
1057
+ static new(...args) {
1058
+ const instance = new this(...args);
1059
+ Control.for(instance);
1060
+ return instance;
1061
+ }
1062
+ /**
1063
+ * Static equivalent of `x instanceof this`.
1064
+ *
1065
+ * Will determine if provided class is a subtype of this one.
1066
+ */
1067
+ static isTypeof(maybe) {
1068
+ return (typeof maybe == "function" &&
1069
+ maybe.prototype instanceof this);
1070
+ }
1071
+ }
1072
+ defineProperty(Model.prototype, "toString", {
1073
+ value() {
1074
+ return this.constructor.name;
1075
+ }
1076
+ });
1077
+
1078
+ const Oops$4 = issues({
1079
+ NotFound: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
1080
+ });
1093
1081
  class Lookup {
1094
1082
  constructor() {
1095
1083
  this.table = new Map();
@@ -1126,20 +1114,19 @@ class Lookup {
1126
1114
  } while (T !== Model);
1127
1115
  }
1128
1116
  pop() {
1117
+ const items = new Set();
1129
1118
  for (const key of getOwnPropertySymbols(this)) {
1130
1119
  const entry = getOwnPropertyDescriptor(this, key);
1131
1120
  if (entry.writable && entry.value)
1132
- entry.value.destroy();
1121
+ items.add(entry.value);
1133
1122
  }
1123
+ for (const model of items)
1124
+ model.kill();
1134
1125
  }
1135
- }
1136
-
1126
+ }
1137
1127
  const LookupContext = React.createContext(new Lookup());
1138
1128
  const useLookup = () => React.useContext(LookupContext);
1139
- const Oops$4 = issues({
1140
- NotFound: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
1141
- });
1142
- function useLocal(Type, arg) {
1129
+ function useContext(Type, arg) {
1143
1130
  const instance = useLookup().get(Type);
1144
1131
  if (!instance && arg !== false)
1145
1132
  throw Oops$4.NotFound(Type.name);
@@ -1147,7 +1134,7 @@ function useLocal(Type, arg) {
1147
1134
  case "string":
1148
1135
  return instance[arg];
1149
1136
  case "function":
1150
- useLayoutEffect(() => arg(instance), []);
1137
+ React.useLayoutEffect(() => arg(instance), []);
1151
1138
  default:
1152
1139
  return instance;
1153
1140
  }
@@ -1160,7 +1147,7 @@ const Oops$3 = issues({
1160
1147
  const PendingContext = new WeakMap();
1161
1148
  const ContextWasUsed = new WeakMap();
1162
1149
  function tap(type, argument) {
1163
- return apply(function tap(key) {
1150
+ return add(function tap(key) {
1164
1151
  if ("set" in type)
1165
1152
  return {
1166
1153
  recursive: true,
@@ -1182,7 +1169,7 @@ function tap(type, argument) {
1182
1169
  });
1183
1170
  return {
1184
1171
  recursive: true,
1185
- suspense: true
1172
+ suspend: true
1186
1173
  };
1187
1174
  });
1188
1175
  }
@@ -1217,57 +1204,51 @@ function use(init) {
1217
1204
 
1218
1205
  function useModel(source, arg, arg2) {
1219
1206
  const instance = React.useMemo(() => {
1220
- const cb = arg2 || arg;
1221
- const instance = typeof source == "function" ?
1222
- source.prototype instanceof Model ?
1223
- new source() :
1224
- source() :
1225
- source;
1226
- ensure(instance);
1227
- if (typeof cb == "function")
1228
- cb(instance);
1207
+ const callback = arg2 || arg;
1208
+ const instance = Model.isTypeof(source) ?
1209
+ source.new() :
1210
+ source();
1211
+ Control.for(instance);
1212
+ if (typeof callback == "function")
1213
+ callback(instance);
1229
1214
  return instance;
1230
1215
  }, []);
1216
+ usePeerContext(instance);
1231
1217
  if (Array.isArray(arg)) {
1232
1218
  const update = useState(0)[1];
1233
1219
  React.useLayoutEffect(() => {
1234
1220
  if (arg.length && instance instanceof Model)
1235
- instance.on(arg, () => update(x => x + 1), true);
1221
+ instance.on(arg, () => update(x => x + 1));
1236
1222
  return () => {
1237
1223
  if (Model.isTypeof(source))
1238
- instance.destroy();
1224
+ instance.kill();
1239
1225
  };
1240
1226
  }, []);
1241
1227
  return instance;
1242
1228
  }
1243
- else {
1244
- const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
1245
- if (typeof arg == "object") {
1246
- local.active = false;
1247
- if (typeof arg2 !== "object")
1248
- arg2 = getOwnPropertyNames(instance);
1249
- for (const key of arg2)
1250
- if (key in arg)
1251
- instance[key] = arg[key];
1252
- React.useLayoutEffect(() => {
1253
- local.active = true;
1254
- });
1255
- }
1256
- React.useLayoutEffect(() => {
1257
- local.commit();
1258
- return () => {
1259
- local.release();
1260
- if (Model.isTypeof(source))
1261
- instance.destroy();
1262
- };
1263
- }, []);
1264
- return local.proxy;
1229
+ const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1230
+ if (typeof arg == "object") {
1231
+ local.active = false;
1232
+ if (typeof arg2 !== "object")
1233
+ arg2 = getOwnPropertyNames(instance);
1234
+ for (const key of arg2)
1235
+ if (key in arg)
1236
+ instance[key] = arg[key];
1265
1237
  }
1238
+ React.useLayoutEffect(() => {
1239
+ local.commit();
1240
+ return () => {
1241
+ local.release();
1242
+ if (Model.isTypeof(source))
1243
+ instance.kill();
1244
+ };
1245
+ }, []);
1246
+ return local.proxy;
1266
1247
  }
1267
1248
 
1268
1249
  function useFrom(target, compute, suspend) {
1269
1250
  const local = use(refresh => {
1270
- const sub = ensure(target).subscribe(() => update);
1251
+ const sub = Control.for(target).subscribe(() => update);
1271
1252
  const spy = sub.proxy;
1272
1253
  let value = compute.call(spy, spy);
1273
1254
  let update;
@@ -1306,84 +1287,79 @@ function useFrom(target, compute, suspend) {
1306
1287
  return local.proxy;
1307
1288
  }
1308
1289
 
1309
- function useTap(source, path, expect) {
1290
+ function useTap(source, arg1, arg2) {
1310
1291
  const instance = useMemo(() => {
1311
1292
  if (typeof source == "object")
1312
1293
  return () => source;
1313
- if ("get" in source)
1314
- return () => source.get();
1315
- if ("prototype" in source)
1316
- return () => useLocal(source);
1317
- else
1318
- return source;
1294
+ if (MVC.isTypeof(source))
1295
+ return () => source.get(true);
1296
+ if (Model.isTypeof(source))
1297
+ return () => useContext(source);
1298
+ return source;
1319
1299
  }, [])();
1320
- if (typeof path == "function")
1321
- return useFrom(instance, path, expect);
1322
- const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
1300
+ if (typeof arg1 == "function")
1301
+ return useFrom(instance, arg1, arg2);
1302
+ const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1323
1303
  React.useLayoutEffect(local.commit, []);
1324
- if (typeof path !== "undefined") {
1325
- const value = local.proxy[path];
1326
- if (value === undefined && expect)
1327
- throw suspend(ensure(instance), path);
1328
- return value;
1329
- }
1330
- return local.proxy;
1304
+ if (arg1 === undefined)
1305
+ return local.proxy;
1306
+ const value = local.proxy[arg1];
1307
+ if (value === undefined && arg2)
1308
+ throw suspend(local.parent, arg1);
1309
+ return value;
1331
1310
  }
1332
1311
 
1333
- /* istanbul ignore file */
1334
1312
  class MVC extends Model {
1335
- tap(path, expect) {
1336
- return useTap(this, path, expect);
1337
- }
1338
- use(arg, callback) {
1339
- return useModel(this, arg, callback);
1340
- }
1341
1313
  static get(arg) {
1342
- return useLocal(this, arg);
1314
+ return useContext(this, arg);
1343
1315
  }
1344
1316
  static tap(key, expect) {
1345
1317
  return useTap(this, key, expect);
1346
1318
  }
1347
- static new(arg) {
1348
- const instance = React.useMemo(() => {
1349
- const instance = new this();
1350
- ensure(instance);
1351
- if (typeof arg == "function")
1352
- arg(instance);
1353
- else if (arg)
1354
- getOwnPropertyNames(instance).forEach(key => {
1355
- if (key in arg)
1356
- instance[key] = arg[key];
1357
- });
1358
- return instance;
1359
- }, []);
1360
- usePeerContext(instance.is);
1361
- return instance;
1362
- }
1363
1319
  static use(a, b) {
1364
- const instance = useModel(this, a, b);
1365
- usePeerContext(instance.is);
1366
- return instance;
1367
- }
1368
- /**
1369
- * @deprecated consider doing this manually - not worth minor efficiency gain over using.
1370
- */
1371
- static uses(apply, keys) {
1372
- return this.use(instance => {
1373
- instance.import(apply, keys);
1374
- });
1375
- }
1376
- /**
1377
- * @deprecated is now replaced by overload of `use` method.
1378
- */
1379
- static using(apply, keys) {
1380
- return this.use(apply, keys);
1320
+ return useModel(this, a, b);
1381
1321
  }
1382
1322
  static meta(path, expect) {
1383
1323
  return useTap(() => this, path, expect);
1384
1324
  }
1385
1325
  }
1386
1326
 
1327
+ const LOCAL = Symbol("LOCAL");
1328
+ const STATE = Symbol("STATE");
1329
+ const UPDATE = Symbol("UPDATE");
1330
+ const CONTROL = Symbol("CONTROL");
1331
+ Object.defineProperties(Model.prototype, {
1332
+ [CONTROL]: {
1333
+ get() {
1334
+ return Control.get(this);
1335
+ }
1336
+ },
1337
+ [LOCAL]: {
1338
+ get() {
1339
+ return Subscriber.get(this);
1340
+ }
1341
+ },
1342
+ [STATE]: {
1343
+ get() {
1344
+ const { state } = Control.get(this);
1345
+ const output = {};
1346
+ state.forEach((value, key) => output[key] = value);
1347
+ return output;
1348
+ }
1349
+ },
1350
+ [UPDATE]: {
1351
+ get() {
1352
+ return getUpdate(this);
1353
+ }
1354
+ }
1355
+ });
1356
+ const Debug = {
1357
+ CONTROL,
1358
+ LOCAL,
1359
+ STATE,
1360
+ UPDATE
1361
+ };
1362
+
1387
1363
  const Oops$2 = issues({
1388
1364
  BadProps: () => `Provider expects either a render function, 'get' or 'has' props.`
1389
1365
  });
@@ -1393,66 +1369,66 @@ function Consumer(props) {
1393
1369
  return children(useTap(type));
1394
1370
  const callback = has || get;
1395
1371
  if (typeof callback == "function")
1396
- callback(useLocal(type, !!has));
1372
+ callback(useContext(type, !!has));
1397
1373
  else
1398
1374
  throw Oops$2.BadProps();
1399
1375
  return null;
1400
1376
  }
1401
1377
 
1402
1378
  const Oops$1 = issues({
1403
- NoType: () => "Provider 'of' prop must be Model, typeof Model or a collection of them.",
1404
- PropDeprecated: () => "Provider `of` prop is deprecated, use `for` instead."
1379
+ NoType: () => "Provider 'for' prop must be Model, typeof Model or a collection of them."
1405
1380
  });
1406
1381
  function Provider(props) {
1407
- if ("of" in props)
1408
- throw Oops$1.PropDeprecated();
1409
- const from = useLookup();
1410
- const context = React.useMemo(() => {
1411
- const I = props.for;
1412
- if (!I)
1382
+ const context = useNewContext(props.for);
1383
+ const render = props.children;
1384
+ useAppliedProps(context, props);
1385
+ React.useLayoutEffect(() => () => context.pop(), []);
1386
+ return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1387
+ ? React.createElement(RenderFunction, { context, render })
1388
+ : render);
1389
+ }
1390
+ function useNewContext(include) {
1391
+ const ambient = useLookup();
1392
+ return React.useMemo(() => {
1393
+ if (!include)
1413
1394
  throw Oops$1.NoType();
1414
- const layer = from.push();
1395
+ const layer = ambient.push();
1415
1396
  function register(I) {
1416
1397
  if (I instanceof Model)
1417
1398
  layer.inject(I.constructor, I, false);
1418
1399
  else
1419
- layer.inject(I, I.create(), true);
1400
+ layer.inject(I, I.new(), true);
1420
1401
  }
1421
- if (I instanceof Model || typeof I == "function")
1422
- register(I);
1402
+ if (include instanceof Model || typeof include == "function")
1403
+ register(include);
1423
1404
  else
1424
- values(I).forEach(register);
1405
+ values(include).forEach(register);
1425
1406
  for (const instance of layer.local)
1426
1407
  if (instance)
1427
1408
  for (const apply of getPending(instance))
1428
1409
  apply(layer);
1429
1410
  return layer;
1430
1411
  }, []);
1431
- const render = props.children;
1432
- useAppliedProps(context, props);
1433
- React.useLayoutEffect(() => () => context.pop(), []);
1434
- return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1435
- ? React.createElement(RenderFunction, { context, render })
1436
- : render);
1437
1412
  }
1438
- function useAppliedProps(within, props) {
1439
- const update = React.useMemo(() => {
1440
- const targets = within.local;
1441
- return function integrate(props) {
1442
- for (const [key, value] of entries(props))
1443
- if (!["of", "for", "children"].includes(key))
1444
- for (const into of targets)
1445
- if (into && key in into)
1446
- into[key] = value;
1413
+ function useAppliedProps(context, props) {
1414
+ const apply = React.useMemo(() => {
1415
+ return (props) => {
1416
+ entries(props).forEach(([key, value]) => {
1417
+ if (key == "for" || key == "children")
1418
+ return;
1419
+ for (const into of context.local)
1420
+ if (into && key in into)
1421
+ into[key] = value;
1422
+ });
1447
1423
  };
1448
1424
  }, []);
1449
- update(props);
1425
+ apply(props);
1450
1426
  }
1451
1427
  function RenderFunction(props) {
1452
1428
  const hook = use(refresh => {
1453
1429
  const targets = props.context.local;
1454
1430
  if (targets.length == 1)
1455
- return ensure(targets[0]).subscribe(() => refresh);
1431
+ return Control.for(targets[0]).subscribe(() => refresh);
1456
1432
  return {};
1457
1433
  });
1458
1434
  if (hook.commit)
@@ -1461,17 +1437,31 @@ function RenderFunction(props) {
1461
1437
  }
1462
1438
 
1463
1439
  const Oops = issues({
1464
- DoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.create() before attempting to access, or consider using ${name}.use() instead.`,
1440
+ DoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.new() before attempting to access, or consider using ${name}.use() instead.`,
1465
1441
  AlreadyExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1466
1442
  });
1467
1443
  const Active = new WeakMap();
1468
1444
  class Global extends MVC {
1469
- static create(...args) {
1445
+ constructor() {
1446
+ super();
1447
+ Active.set(this.constructor, this);
1448
+ }
1449
+ kill(force) {
1450
+ const type = this.constructor;
1451
+ if (type.keepAlive && !force)
1452
+ return false;
1453
+ super.kill();
1454
+ Active.delete(type);
1455
+ return true;
1456
+ }
1457
+ static new(...args) {
1470
1458
  if (Active.has(this))
1471
- throw Oops.AlreadyExists(this.name);
1459
+ if (this.keepAlive)
1460
+ return Active.get(this);
1461
+ else
1462
+ throw Oops.AlreadyExists(this.name);
1472
1463
  const instance = new this(...args);
1473
- ensure(instance);
1474
- Active.set(this, instance);
1464
+ Control.for(instance);
1475
1465
  return instance;
1476
1466
  }
1477
1467
  /**
@@ -1479,10 +1469,8 @@ class Global extends MVC {
1479
1469
  * Returns a thenable; resolves after successful update.
1480
1470
  * If instance does not already exist, one will be created.
1481
1471
  */
1482
- static set(updates) {
1483
- const instance = Active.get(this) || this.create();
1484
- instance.import(updates);
1485
- return instance.update(false);
1472
+ static set(values) {
1473
+ return this.get(true).update(values);
1486
1474
  }
1487
1475
  static get(arg) {
1488
1476
  const instance = Active.get(this);
@@ -1496,13 +1484,10 @@ class Global extends MVC {
1496
1484
  static reset() {
1497
1485
  const current = Active.get(this);
1498
1486
  if (current)
1499
- current.destroy();
1487
+ current.kill(true);
1500
1488
  }
1501
- destroy() {
1502
- super.destroy();
1503
- Active.delete(this.constructor);
1504
- }
1505
- }
1489
+ }
1490
+ Global.keepAlive = true;
1506
1491
 
1507
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, MVC as default, get, has, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1492
+ export { Consumer, Debug, Global, MVC, MVC as Model, Provider, act, add, MVC as default, get, has, ref, set, tap, use$1 as use, useContext, useFrom, useModel, useTap };
1508
1493
  //# sourceMappingURL=index.esm.js.map