@expressive/mvc 1.0.0-beta.36 → 1.0.0-beta.37

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,31 +1,4 @@
1
- import React from 'react';
2
-
3
- /*! *****************************************************************************
4
- Copyright (c) Microsoft Corporation.
5
-
6
- Permission to use, copy, modify, and/or distribute this software for any
7
- purpose with or without fee is hereby granted.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
16
- ***************************************************************************** */
17
-
18
- function __rest(s, e) {
19
- var t = {};
20
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
21
- t[p] = s[p];
22
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
23
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
24
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
25
- t[p[i]] = s[p[i]];
26
- }
27
- return t;
28
- }
1
+ import React, { useState, useMemo } from 'react';
29
2
 
30
3
  class Issue extends Error {
31
4
  constructor() {
@@ -44,10 +17,10 @@ function issues(register) {
44
17
  return Library;
45
18
  }
46
19
 
47
- const Oops$9 = issues({
20
+ const Oops$d = issues({
48
21
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
49
22
  });
50
- const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, getOwnPropertySymbols, keys, values } = Object;
23
+ const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
51
24
  function define(target, key, value) {
52
25
  defineProperty(target, key, { value });
53
26
  }
@@ -73,7 +46,7 @@ function createEffect(callback) {
73
46
  if (unSet instanceof Promise)
74
47
  unSet = undefined;
75
48
  if (unSet && typeof unSet !== "function")
76
- throw Oops$9.BadEffectCallback();
49
+ throw Oops$d.BadEffectCallback();
77
50
  };
78
51
  }
79
52
  function createValueEffect(callback) {
@@ -89,52 +62,92 @@ function createValueEffect(callback) {
89
62
  if (typeof out == "function")
90
63
  unSet = out;
91
64
  else
92
- throw Oops$9.BadEffectCallback();
65
+ throw Oops$d.BadEffectCallback();
93
66
  };
94
67
  }
95
68
 
96
- const Oops$8 = issues({
69
+ const UPDATE = new WeakMap();
70
+ function applyUpdate(subject, keys) {
71
+ UPDATE.set(subject, keys);
72
+ return () => {
73
+ setTimeout(() => {
74
+ UPDATE.delete(subject);
75
+ }, 0);
76
+ };
77
+ }
78
+
79
+ const Oops$c = issues({
97
80
  StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
98
81
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
99
82
  });
83
+ const READY = new WeakSet();
100
84
  class Controller {
101
85
  constructor(subject) {
102
86
  this.subject = subject;
87
+ this.state = {};
103
88
  this.frame = new Set();
104
89
  this.waiting = new Set();
90
+ this.onDestroy = new Set();
105
91
  this.followers = new Set();
92
+ this.proxy = Object.create(subject);
106
93
  }
107
94
  start() {
108
- this.state = {};
109
95
  for (const key in this.subject)
110
96
  this.manage(key);
111
- const handle = new Set(this.waiting);
112
- this.waiting.clear();
113
- handle.forEach(cb => cb([]));
114
- return this;
97
+ this.emit([]);
115
98
  }
116
- manage(key, handler) {
117
- const { state, subject } = this;
118
- const desc = getOwnPropertyDescriptor(subject, key);
119
- if (desc && "value" in desc) {
120
- const { value } = desc;
121
- const instruction = Pending.get(value);
122
- if (instruction) {
123
- Pending.delete(value);
124
- delete subject[key];
125
- instruction.call(this, key, this);
126
- }
127
- else if (typeof value !== "function" || /^[A-Z]/.test(key)) {
128
- state[key] = value;
129
- defineProperty(subject, key, {
130
- enumerable: true,
131
- get: () => state[key],
132
- set: this.setter(key, handler)
133
- });
134
- }
99
+ stop() {
100
+ this.followers.clear();
101
+ this.onDestroy.forEach(x => x());
102
+ }
103
+ manage(key) {
104
+ const { proxy, state, subject } = this;
105
+ const { value: entry } = getOwnPropertyDescriptor(subject, key);
106
+ if (typeof entry == "function")
107
+ return;
108
+ let onGet;
109
+ let onSet;
110
+ let enumerable;
111
+ const instruction = Pending.get(entry);
112
+ if (instruction) {
113
+ Pending.delete(entry);
114
+ delete subject[key];
115
+ const desc = instruction.call(this, key, this);
116
+ if (!desc)
117
+ return;
118
+ if ("value" in desc)
119
+ state[key] = desc.value;
120
+ onSet = desc.set;
121
+ onGet = desc.get;
122
+ enumerable = desc.enumerable;
135
123
  }
124
+ else
125
+ state[key] = entry;
126
+ const set = onSet !== false
127
+ ? this.ref(key, onSet)
128
+ : undefined;
129
+ function get() {
130
+ const local = this[LOCAL];
131
+ const value = state[key];
132
+ if (local && !local.watch[key])
133
+ local.watch[key] = true;
134
+ return onGet ? onGet(value, local) : value;
135
+ }
136
+ setAlias(get, `tap ${key}`);
137
+ defineProperty(subject, key, {
138
+ enumerable,
139
+ set,
140
+ get: onGet
141
+ ? () => onGet(state[key])
142
+ : () => state[key]
143
+ });
144
+ defineProperty(proxy, key, {
145
+ enumerable,
146
+ set,
147
+ get
148
+ });
136
149
  }
137
- setter(key, handler) {
150
+ ref(key, handler) {
138
151
  const { state, subject } = this;
139
152
  return (value) => {
140
153
  if (state[key] == value)
@@ -155,6 +168,18 @@ class Controller {
155
168
  this.followers.delete(listener);
156
169
  };
157
170
  }
171
+ emit(keys) {
172
+ const waiting = [...this.waiting];
173
+ this.waiting.clear();
174
+ applyUpdate(this.subject, keys)();
175
+ for (const callback of waiting)
176
+ try {
177
+ callback(keys);
178
+ }
179
+ catch (err) {
180
+ console.error(err);
181
+ }
182
+ }
158
183
  update(key, value) {
159
184
  if (1 in arguments)
160
185
  this.state[key] = value;
@@ -164,19 +189,8 @@ class Controller {
164
189
  setTimeout(() => {
165
190
  flush(this);
166
191
  const keys = Object.freeze([...this.frame]);
167
- const handle = new Set(this.waiting);
168
- this.waiting.clear();
169
192
  this.frame.clear();
170
- UPDATE.set(this.subject, keys);
171
- setTimeout(() => {
172
- UPDATE.delete(this.subject);
173
- }, 0);
174
- handle.forEach(callback => {
175
- try {
176
- callback(keys);
177
- }
178
- catch (e) { }
179
- });
193
+ this.emit(keys);
180
194
  }, 0);
181
195
  this.frame.add(key);
182
196
  for (const callback of this.followers) {
@@ -191,7 +205,7 @@ class Controller {
191
205
  return;
192
206
  }
193
207
  if (typeof arg == "boolean" && arg !== this.frame.size > 0)
194
- return Promise.reject(Oops$8.StrictUpdate(arg));
208
+ return Promise.reject(Oops$c.StrictUpdate(arg));
195
209
  return {
196
210
  then: (callback) => {
197
211
  if (callback)
@@ -200,76 +214,83 @@ class Controller {
200
214
  else
201
215
  callback(false);
202
216
  else
203
- throw Oops$8.NoChaining();
217
+ throw Oops$c.NoChaining();
204
218
  }
205
219
  };
206
220
  }
221
+ }
222
+ function control(subject, cb) {
223
+ let control = subject[CONTROL];
224
+ const ready = READY.has(control);
225
+ if (!control) {
226
+ control = new Controller(subject);
227
+ define(subject, CONTROL, control);
228
+ }
229
+ if (!cb) {
230
+ if (!ready) {
231
+ READY.add(control);
232
+ control.start();
233
+ }
234
+ return control;
235
+ }
236
+ if (!ready) {
237
+ let done;
238
+ control.requestUpdate(() => {
239
+ done = cb(control);
240
+ });
241
+ return () => done && done();
242
+ }
243
+ return cb(control);
207
244
  }
208
245
 
209
246
  class Subscriber {
210
247
  constructor(parent, onUpdate) {
211
248
  this.onUpdate = onUpdate;
212
249
  this.active = false;
213
- this.watch = {};
214
250
  this.dependant = new Set();
251
+ this.watch = {};
215
252
  if (!(parent instanceof Controller))
216
- parent = getController(parent);
217
- this.parent = parent;
218
- this.source = parent.subject;
219
- const proxy = this.proxy = create(parent.subject);
220
- const DEBUG = (keys) => {
221
- UPDATE.set(proxy, keys.filter(k => k in this.watch));
222
- };
253
+ parent = control(parent);
254
+ let reset;
255
+ const proxy = create(parent.proxy);
256
+ const controller = parent;
223
257
  define(proxy, LOCAL, this);
224
258
  defineProperty(this, "proxy", {
225
- get: () => {
226
- if (UPDATE.has(proxy))
227
- setTimeout(() => {
228
- UPDATE.delete(proxy);
229
- }, 0);
259
+ configurable: true,
260
+ get() {
261
+ if (reset)
262
+ setTimeout(reset, 0);
230
263
  return proxy;
231
264
  }
232
265
  });
233
- for (const key in parent.state) {
234
- const existing = getOwnPropertyDescriptor(parent.subject, key);
235
- const isUsing = () => {
236
- this.watch[key] = true;
237
- delete proxy[key];
238
- return proxy[key];
266
+ const onEvent = (key) => {
267
+ const handler = this.watch[key];
268
+ if (!handler)
269
+ return;
270
+ if (typeof handler == "function")
271
+ handler();
272
+ const notify = this.onUpdate(key, controller);
273
+ const getWhy = (keys) => {
274
+ reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
239
275
  };
240
- setAlias(isUsing, `tap ${key}`);
241
- defineProperty(proxy, key, {
242
- set: existing.set,
243
- get: isUsing,
244
- configurable: true,
245
- enumerable: true
246
- });
247
- }
276
+ if (notify) {
277
+ controller.requestUpdate(getWhy);
278
+ controller.requestUpdate(notify);
279
+ }
280
+ };
248
281
  this.commit = () => {
249
- const { parent } = this;
250
- const onDone = parent.addListener(key => {
251
- const handler = this.watch[key];
252
- if (!handler)
253
- return;
254
- if (typeof handler == "function")
255
- handler();
256
- const notify = this.onUpdate(key, parent);
257
- if (notify) {
258
- parent.requestUpdate(DEBUG);
259
- parent.requestUpdate(notify);
260
- }
261
- });
282
+ const release = controller.addListener(onEvent);
262
283
  this.active = true;
263
284
  this.dependant.forEach(x => x.commit());
264
285
  return this.release = () => {
265
286
  this.dependant.forEach(x => x.release());
266
- onDone();
287
+ release();
267
288
  };
268
289
  };
269
290
  }
270
291
  }
271
292
 
272
- const Oops$7 = issues({
293
+ const Oops$b = issues({
273
294
  ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
274
295
  ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
275
296
  });
@@ -287,15 +308,12 @@ function prepare(parent, key, source, setter, getter) {
287
308
  USED.set(parent, register);
288
309
  }
289
310
  register.set(key, info);
290
- const current = getter
291
- ? () => getter(parent, key)
292
- : () => state[key];
293
311
  function compute(initial) {
294
312
  try {
295
313
  return setter.call(sub.proxy, sub.proxy);
296
314
  }
297
315
  catch (err) {
298
- Oops$7.ComputeFailed(subject, key, !!initial).warn();
316
+ Oops$b.ComputeFailed(subject, key, !!initial).warn();
299
317
  throw err;
300
318
  }
301
319
  }
@@ -331,17 +349,12 @@ function prepare(parent, key, source, setter, getter) {
331
349
  value: undefined,
332
350
  writable: true
333
351
  });
334
- defineProperty(subject, key, {
335
- enumerable: true,
336
- configurable: true,
337
- get: current
338
- });
339
352
  try {
340
- state[key] = compute(true);
353
+ return state[key] = compute(true);
341
354
  }
342
355
  catch (e) {
343
356
  if (early)
344
- Oops$7.ComputedEarly(key).warn();
357
+ Oops$b.ComputedEarly(key).warn();
345
358
  throw e;
346
359
  }
347
360
  finally {
@@ -352,23 +365,24 @@ function prepare(parent, key, source, setter, getter) {
352
365
  info.priority = peer.priority + 1;
353
366
  }
354
367
  }
355
- return current();
356
368
  }
357
369
  setAlias(update, `try ${key}`);
358
370
  setAlias(create, `new ${key}`);
359
371
  setAlias(setter, `run ${key}`);
360
372
  INIT.add(create);
361
373
  INFO.set(update, info);
362
- for (const on of [state, subject])
363
- defineProperty(on, key, {
364
- get: create,
365
- configurable: true,
366
- enumerable: true
367
- });
374
+ defineProperty(state, key, {
375
+ get: create,
376
+ configurable: true,
377
+ enumerable: true
378
+ });
379
+ return getter
380
+ ? () => getter(parent, key)
381
+ : {};
368
382
  }
369
- function ensure$1(on, keys) {
383
+ function ensure(on, keys) {
370
384
  for (const key of keys) {
371
- const desc = getOwnPropertyDescriptor(on.subject, key);
385
+ const desc = getOwnPropertyDescriptor(on.state, key);
372
386
  const getter = desc && desc.get;
373
387
  if (INIT.has(getter))
374
388
  getter(true);
@@ -393,26 +407,6 @@ const CONTROL = Symbol("CONTROL");
393
407
  const WHY = Symbol("UPDATE");
394
408
  const LOCAL = Symbol("LOCAL");
395
409
  const STATE = Symbol("STATE");
396
- const UPDATE = new WeakMap();
397
- function ensure(subject, callback) {
398
- const control = subject[CONTROL];
399
- if (!control.state) {
400
- let done;
401
- control.requestUpdate(() => {
402
- done = callback(control);
403
- });
404
- return () => done();
405
- }
406
- return callback(control);
407
- }
408
- function getController(subject) {
409
- const control = subject[CONTROL];
410
- if (!control.state)
411
- control.start();
412
- if (subject.didCreate)
413
- subject.didCreate();
414
- return control;
415
- }
416
410
  class Model {
417
411
  constructor() {
418
412
  define(this, CONTROL, new Controller(this));
@@ -426,11 +420,13 @@ class Model {
426
420
  return UPDATE.get(this);
427
421
  }
428
422
  on(select, handler, squash, once) {
429
- return ensure(this, control => {
423
+ return control(this, control => {
424
+ if (typeof select == "function")
425
+ return control.addListener(select);
430
426
  const keys = typeof select == "string" ? [select] :
431
427
  !select.length ? getOwnPropertyNames(control.state) :
432
428
  select;
433
- ensure$1(control, keys);
429
+ ensure(control, keys);
434
430
  const callback = squash
435
431
  ? handler.bind(this)
436
432
  : frame => frame
@@ -455,7 +451,7 @@ class Model {
455
451
  }
456
452
  effect(callback, select) {
457
453
  const effect = createEffect(callback);
458
- return ensure(this, control => {
454
+ return control(this, control => {
459
455
  if (!select) {
460
456
  const sub = new Subscriber(control, () => invoke);
461
457
  const invoke = () => {
@@ -469,30 +465,40 @@ class Model {
469
465
  effect.call(this.get, this.get);
470
466
  };
471
467
  invoke();
472
- if (!select.length)
473
- select.push("willDestroy");
468
+ if (!select.length) {
469
+ control.onDestroy.add(invoke);
470
+ return () => {
471
+ control.onDestroy.delete(invoke);
472
+ };
473
+ }
474
474
  return control.addListener(key => {
475
475
  if (select.includes(key))
476
476
  return invoke;
477
477
  });
478
478
  });
479
479
  }
480
- import(from, subset) {
481
- for (const key of subset || getOwnPropertyNames(this))
482
- if (key in from)
483
- this[key] = from[key];
480
+ // TODO: account for exotic properties
481
+ import(source, select) {
482
+ const { subject } = control(this);
483
+ if (!select)
484
+ select = getOwnPropertyNames(subject);
485
+ for (const key of select)
486
+ if (key in source)
487
+ this[key] = source[key];
484
488
  }
485
489
  export(subset) {
486
- const { state } = getController(this);
490
+ const { state } = control(this);
487
491
  const output = {};
488
- for (const key of subset || getOwnPropertyNames(state))
492
+ if (!subset)
493
+ subset = getOwnPropertyNames(state);
494
+ for (const key of subset)
489
495
  output[key] = state[key];
490
496
  return output;
491
497
  }
492
498
  update(arg, tag) {
493
- const control = getController(this);
499
+ const target = control(this);
494
500
  if (typeof arg == "string") {
495
- control.update(arg);
501
+ target.update(arg);
496
502
  if (1 in arguments && arg in this) {
497
503
  const method = this[arg];
498
504
  if (typeof method == "function")
@@ -503,28 +509,39 @@ class Model {
503
509
  }
504
510
  arg = undefined;
505
511
  }
506
- return control.requestUpdate(arg);
512
+ return target.requestUpdate(arg);
507
513
  }
514
+ /**
515
+ * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
516
+ */
508
517
  destroy() {
509
- this.update("willDestroy", true);
518
+ control(this).stop();
510
519
  }
511
520
  toString() {
512
521
  return this.constructor.name;
513
522
  }
523
+ /**
524
+ * Creates a new instance of this controller.
525
+ *
526
+ * Beyond `new this(...)`, method will activate managed-state.
527
+ *
528
+ * @param args - arguments sent to constructor
529
+ */
514
530
  static create(...args) {
515
531
  const instance = new this(...args);
516
- getController(instance);
532
+ control(instance);
517
533
  return instance;
518
534
  }
535
+ /**
536
+ * Static equivalent of `x instanceof this`.
537
+ *
538
+ * Will determine if provided class is a subtype of this one.
539
+ */
519
540
  static isTypeof(maybe) {
520
541
  return (typeof maybe == "function" &&
521
542
  maybe.prototype instanceof this);
522
543
  }
523
544
  }
524
- Model.CONTROL = CONTROL;
525
- Model.STATE = STATE;
526
- Model.LOCAL = LOCAL;
527
- Model.WHY = WHY;
528
545
  defineLazy(Model, CONTROL, function () {
529
546
  return new Controller(this);
530
547
  });
@@ -534,306 +551,302 @@ function apply(fn, label) {
534
551
  const name = label || fn.name || "pending";
535
552
  const placeholder = Symbol(`${name} instruction`);
536
553
  function setup(key) {
537
- const { subject, state } = this;
538
554
  let output = fn.call(this, key, this);
539
- let desc = {};
540
- let get;
541
- let set;
542
555
  if (!output)
543
556
  return;
544
- if (typeof output == "function")
545
- get = output;
546
- else {
547
- (({ get, set } = output), desc = __rest(output, ["get", "set"]));
548
- if ("value" in desc) {
549
- state[key] = desc.value;
550
- if (get || set)
551
- delete desc.value;
552
- else {
553
- defineProperty(subject, key, desc);
554
- return;
555
- }
556
- }
557
+ if ("explicit" in output) {
558
+ defineProperty(this.subject, key, output);
559
+ return;
557
560
  }
558
- defineProperty(subject, key, Object.assign(Object.assign({}, desc), { set: this.setter(key, set), get: get
559
- ? function () {
560
- return get(state[key], this[LOCAL]);
561
- }
562
- : () => state[key] }));
561
+ if ("recursive" in output)
562
+ output = Object.assign(Object.assign({}, output), { get: recursive(this, key) });
563
+ if (typeof output == "function")
564
+ return { get: output };
565
+ else
566
+ return output;
563
567
  }
564
568
  Pending.set(placeholder, setup);
565
569
  return placeholder;
566
- }
567
-
568
- //TODO: sync instruction
569
- function child(source, name) {
570
- return apply(function child(key) {
571
- const context = new WeakMap();
572
- const peer = source.call(this, key);
573
- function subscribe(sub) {
574
- let child;
575
- function init() {
576
- if (child) {
577
- child.release();
578
- sub.dependant.delete(child);
579
- context.set(sub, undefined);
580
- child = undefined;
581
- }
582
- const instance = peer.get();
583
- if (instance) {
584
- child = new Subscriber(instance, sub.onUpdate);
585
- if (sub.active)
586
- child.commit();
587
- sub.dependant.add(child);
588
- context.set(sub, child.proxy);
589
- }
570
+ }
571
+ function recursive(source, key) {
572
+ const context = new WeakMap();
573
+ const subscribe = (parent) => {
574
+ let child;
575
+ const init = () => {
576
+ if (child) {
577
+ child.release();
578
+ parent.dependant.delete(child);
579
+ context.set(parent, undefined);
580
+ child = undefined;
590
581
  }
591
- init();
592
- sub.watch[key] = init;
593
- }
594
- return {
595
- value: peer.get(),
596
- set: peer.set,
597
- get(_value, local) {
598
- if (!local)
599
- return peer.get();
600
- if (!context.has(local))
601
- subscribe(local);
602
- return context.get(local);
582
+ const value = source.state[key];
583
+ if (value && CONTROL in value) {
584
+ child = new Subscriber(value, parent.onUpdate);
585
+ if (parent.active)
586
+ child.commit();
587
+ parent.dependant.add(child);
588
+ context.set(parent, child.proxy);
603
589
  }
604
590
  };
605
- }, name || source.name);
591
+ init();
592
+ parent.watch[key] = init;
593
+ };
594
+ return (value, local) => {
595
+ if (!local)
596
+ return value;
597
+ if (!context.has(local))
598
+ subscribe(local);
599
+ return context.get(local);
600
+ };
606
601
  }
607
602
 
608
- const Parent = new WeakMap();
609
- const Oops$6 = issues({
610
- ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
611
- UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
612
- BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
603
+ const Oops$a = issues({
604
+ DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
613
605
  });
614
- function bootstrap(object) {
615
- const breakdown = getOwnPropertyDescriptors(object);
616
- const control = new Model();
617
- for (const key in breakdown)
618
- defineProperty(control, key, breakdown[key]);
619
- return control;
620
- }
621
- function use$1(input, argument) {
622
- return child(function use(key) {
623
- const { subject } = this;
624
- let current;
625
- const onUpdate = (next) => {
626
- if (next) {
627
- current = next instanceof Model
628
- ? next : bootstrap(next);
629
- Parent.set(current, subject);
630
- getController(current);
631
- }
632
- else
633
- current = undefined;
634
- if (typeof argument == "function")
635
- return argument(current);
606
+ function act(task) {
607
+ return apply(function act(key) {
608
+ let pending = false;
609
+ const invoke = (...args) => {
610
+ if (pending)
611
+ return Promise.reject(Oops$a.DuplicateAction(key));
612
+ pending = true;
613
+ this.update(key);
614
+ return new Promise(res => {
615
+ res(task.apply(this.subject, args));
616
+ }).finally(() => {
617
+ pending = false;
618
+ this.update(key);
619
+ });
636
620
  };
637
- if (input) {
638
- const initial = Model.isTypeof(input)
639
- ? new input() :
640
- input instanceof Model
641
- ? input :
642
- typeof input === "function"
643
- ? input() :
644
- typeof input === "object"
645
- ? bootstrap(input)
646
- : null;
647
- if (initial)
648
- onUpdate(initial);
649
- else if (initial === null)
650
- throw Oops$6.BadArgument(typeof input);
651
- }
621
+ this.state[key] = undefined;
622
+ setAlias(invoke, `run ${key}`);
623
+ defineProperty(invoke, "active", {
624
+ get: () => pending
625
+ });
652
626
  return {
653
- get: () => current,
654
- set: onUpdate
627
+ value: invoke,
628
+ set: false
655
629
  };
656
630
  });
657
- }
658
- function parent(Expects, required) {
659
- return apply(function parent() {
660
- const child = this.subject;
661
- const expected = Expects.name;
662
- const value = Parent.get(this.subject);
663
- if (!value) {
664
- if (required)
665
- throw Oops$6.ParentRequired(expected, child);
666
- }
667
- else if (!(value instanceof Expects))
668
- throw Oops$6.UnexpectedParent(expected, child, value);
669
- return { value };
670
- });
671
- }
672
-
673
- const Lifecycle = {
674
- WILL_RENDER: "willRender",
675
- WILL_UPDATE: "willUpdate",
676
- WILL_MOUNT: "willMount",
677
- WILL_UNMOUNT: "willUnmount",
678
- DID_MOUNT: "didMount"
679
- };
680
- [
681
- "didCreate",
682
- "willDestroy",
683
- ...values(Lifecycle)
684
- ];
685
- function lifecycle(prefix) {
686
- const type = eventsFor(prefix);
687
- return function useLifecycle(sub, tag) {
688
- const emit = React.useMemo(() => {
689
- const subject = sub.source;
690
- const id = typeof tag == "function" ? tag(subject) : tag;
691
- return (name) => {
692
- for (const key of [name, type(name)])
693
- subject.update(key, id);
694
- };
695
- }, [sub, tag]);
696
- emit(Lifecycle.WILL_RENDER);
697
- emit(sub.active
698
- ? Lifecycle.WILL_UPDATE
699
- : Lifecycle.WILL_MOUNT);
700
- React.useLayoutEffect(() => {
701
- sub.commit();
702
- emit(Lifecycle.DID_MOUNT);
703
- return () => {
704
- emit(Lifecycle.WILL_UNMOUNT);
705
- sub.release();
706
- };
707
- }, [sub]);
708
- };
709
- }
710
- function eventsFor(prefix) {
711
- const map = new Map();
712
- for (const name of values(Lifecycle)) {
713
- const alias = prefix + name[0].toUpperCase() + name.slice(1);
714
- map.set(name, alias);
715
- }
716
- return (e) => map.get(e);
717
631
  }
718
632
 
719
- const useElementLifecycle = lifecycle("element");
720
- const useComponentLifecycle = lifecycle("component");
721
- function use(init) {
722
- const [state, update] = React.useState(() => [
723
- init(() => update(state.concat()))
724
- ]);
725
- return state[0];
726
- }
727
- function useTap(model, path, expect) {
728
- if (typeof path == "function")
729
- return useComputed(model, path, expect);
730
- const proxy = useActive(model, path, expect);
731
- model.update("willRender", true);
732
- return proxy;
733
- }
734
- function useTag(model, id) {
735
- const hook = use(refresh => (new Subscriber(model, () => refresh)));
736
- useElementLifecycle(hook, id || 0);
737
- return hook.proxy;
738
- }
739
- function useNew(model, callback) {
740
- const hook = use(refresh => {
741
- if (callback)
742
- callback(model);
743
- return new Subscriber(model, () => refresh);
744
- });
745
- useComponentLifecycle(hook);
746
- return hook.proxy;
747
- }
748
- function usePassive(Type, callback) {
749
- const instance = React.useMemo(() => {
750
- const instance = Type.create();
751
- if (callback)
752
- callback(instance);
753
- return instance;
754
- }, []);
755
- React.useLayoutEffect(() => () => instance.destroy(), []);
756
- instance.update("willRender", true);
757
- return instance;
633
+ const Oops$9 = issues({
634
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
635
+ });
636
+ /**
637
+ * Get value, suspend instead if undefined.
638
+ *
639
+ * Throws suspense promise also interpretable as an error.
640
+ * React could handle it but other contexts probably not.
641
+ */
642
+ function pendingValue(via, key) {
643
+ const value = via.state[key];
644
+ if (value === undefined)
645
+ throw suspend(via, key);
646
+ return value;
758
647
  }
759
- function useActive(target, focus, expected) {
760
- const hook = use(refresh => {
761
- const sub = new Subscriber(target, () => refresh);
762
- if (focus) {
763
- const source = sub.proxy;
764
- defineProperty(sub, "proxy", {
765
- get() {
766
- const value = source[focus];
767
- if (value === undefined && expected)
768
- throw suspend(sub.parent, focus);
769
- return value;
770
- }
771
- });
648
+ function pendingFactory(via, key, fn) {
649
+ const { subject, state } = via;
650
+ let waiting;
651
+ let error;
652
+ return () => {
653
+ if (waiting)
654
+ throw waiting;
655
+ if (error)
656
+ throw error;
657
+ if (key in state)
658
+ return state[key];
659
+ let output;
660
+ try {
661
+ output = fn.call(subject, key, subject);
772
662
  }
773
- return sub;
774
- });
775
- React.useLayoutEffect(() => hook.commit(), []);
776
- return hook.proxy;
777
- }
778
- function useComputed(target, compute, expected) {
779
- const hook = use(refresh => {
780
- const sub = new Subscriber(target, () => update);
781
- const spy = sub.proxy;
782
- let value = compute.call(spy, spy);
783
- let update;
784
- let retry;
785
- const set = (next) => {
786
- value = next;
787
- if (retry) {
788
- retry();
789
- retry = undefined;
790
- }
791
- else
792
- refresh();
793
- };
794
- if (value instanceof Promise) {
795
- value.then(set);
796
- value = undefined;
663
+ catch (err) {
664
+ throw error = err;
665
+ }
666
+ if (output instanceof Promise) {
667
+ const issue = Oops$9.ValueNotReady(subject, key);
668
+ output
669
+ .catch(err => error = err)
670
+ .then(out => state[key] = out)
671
+ .finally(() => waiting = undefined);
672
+ throw waiting = Object.assign(output, {
673
+ message: issue.message,
674
+ stack: issue.stack
675
+ });
797
676
  }
798
677
  else {
799
- sub.commit();
800
- update = () => {
801
- const next = compute.call(spy, spy);
802
- if (value != next)
803
- set(next);
804
- };
678
+ return state[key] = output;
805
679
  }
806
- const get = expected
807
- ? () => {
808
- if (value === undefined)
809
- throw new Promise(res => retry = res);
810
- return value;
811
- }
812
- : () => value;
813
- defineProperty(sub, "proxy", { get });
814
- return sub;
815
- });
816
- React.useLayoutEffect(() => hook.release, []);
817
- return hook.proxy;
680
+ };
818
681
  }
819
- function useModel(Type, callback) {
820
- const hook = use(refresh => {
821
- const instance = new Type();
822
- const sub = new Subscriber(instance, () => refresh);
823
- const release = sub.parent.addListener((key) => {
824
- if (key == Lifecycle.WILL_UNMOUNT)
682
+ function suspend(source, key) {
683
+ const error = Oops$9.ValueNotReady(source.subject, key);
684
+ const promise = new Promise(resolve => {
685
+ const release = source.addListener(forKey => {
686
+ if (forKey == key)
825
687
  return () => {
826
- instance.destroy();
827
- release();
828
- };
688
+ if (source.state[key] !== undefined) {
689
+ release();
690
+ resolve();
691
+ }
692
+ };
829
693
  });
830
- if (callback)
831
- callback(instance);
832
- return sub;
833
694
  });
834
- useComponentLifecycle(hook);
835
- usePeerContext(hook.source);
836
- return hook.proxy;
695
+ return Object.assign(promise, {
696
+ toString: () => String(error),
697
+ name: "Suspense",
698
+ message: error.message,
699
+ stack: error.stack
700
+ });
701
+ }
702
+
703
+ const Oops$8 = issues({
704
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
705
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
706
+ });
707
+ function from(source, setter, arg) {
708
+ return apply(function from(key) {
709
+ const { subject } = this;
710
+ let getSource;
711
+ let getter;
712
+ if (arg === true || setter === true)
713
+ getter = pendingValue;
714
+ if (typeof setter == "boolean")
715
+ setter = undefined;
716
+ // Easy mistake, using a peer, will always be unresolved.
717
+ if (typeof source == "symbol")
718
+ throw Oops$8.PeerNotAllowed(subject, key);
719
+ // replace source controller in-case it is different
720
+ if (typeof source == "object")
721
+ getSource = () => control(source);
722
+ // specifically an arrow function (getter factory)
723
+ else if (!source.prototype) {
724
+ setter = source.call(subject, key);
725
+ getSource = () => this;
726
+ }
727
+ // Regular function is too ambiguous so not allowed.
728
+ else
729
+ throw Oops$8.BadComputedSource(subject, key, source);
730
+ return prepare(this, key, getSource, setter, getter);
731
+ });
732
+ }
733
+
734
+ function ref(arg) {
735
+ return apply(function ref(key) {
736
+ let value = {};
737
+ if (typeof arg == "object") {
738
+ const source = control(arg);
739
+ for (const key in source.state)
740
+ defineLazy(value, key, createRef.bind(source, key));
741
+ }
742
+ else
743
+ value = createRef.call(this, key, arg);
744
+ return { value, explicit: true };
745
+ });
746
+ }
747
+ function createRef(key, cb) {
748
+ const refObjectFunction = this.ref(key, cb && createValueEffect(cb));
749
+ defineProperty(refObjectFunction, "current", {
750
+ set: refObjectFunction,
751
+ get: () => this.state[key]
752
+ });
753
+ return refObjectFunction;
754
+ }
755
+
756
+ const Oops$7 = issues({
757
+ NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
758
+ BadFactory: () => `Set instruction can only accept a factory or undefined.`,
759
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
760
+ });
761
+ function set(factory, argument) {
762
+ return apply(function set(key) {
763
+ let set;
764
+ let get;
765
+ const required = argument === true || argument === undefined;
766
+ if (factory === undefined)
767
+ get = () => pendingValue(this, key);
768
+ else if (typeof factory !== "function")
769
+ throw Oops$7.BadFactory();
770
+ else {
771
+ get = pendingFactory(this, key, factory);
772
+ if (required)
773
+ try {
774
+ get();
775
+ }
776
+ catch (err) {
777
+ if (err instanceof Promise)
778
+ ;
779
+ else {
780
+ Oops$7.FactoryFailed(this.subject, key).warn();
781
+ throw err;
782
+ }
783
+ }
784
+ }
785
+ if (typeof argument == "function")
786
+ set = createValueEffect(argument);
787
+ else
788
+ set = (value) => {
789
+ if (value === undefined && required)
790
+ throw Oops$7.NonOptional(this.subject, key);
791
+ };
792
+ return { set, get };
793
+ });
794
+ }
795
+
796
+ const Parent = new WeakMap();
797
+ const Oops$6 = issues({
798
+ ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
799
+ UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
800
+ });
801
+ function parent(Expects, required) {
802
+ return apply(function parent() {
803
+ const child = this.subject;
804
+ const value = Parent.get(child);
805
+ const expected = Expects.name;
806
+ if (!value) {
807
+ if (required !== false)
808
+ throw Oops$6.ParentRequired(expected, child);
809
+ }
810
+ else if (!(value instanceof Expects))
811
+ throw Oops$6.UnexpectedParent(expected, child, value);
812
+ return { value };
813
+ });
814
+ }
815
+
816
+ const Oops$5 = issues({
817
+ BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
818
+ });
819
+ function use$1(input, argument) {
820
+ return apply(function use(key) {
821
+ const { subject } = this;
822
+ const onUpdate = (next) => {
823
+ this.state[key] = next;
824
+ if (next) {
825
+ Parent.set(next, subject);
826
+ control(next);
827
+ }
828
+ if (typeof argument == "function")
829
+ argument(next);
830
+ return true;
831
+ };
832
+ if (input) {
833
+ const value = Model.isTypeof(input)
834
+ ? new input() :
835
+ typeof input === "object"
836
+ ? input :
837
+ typeof input === "function"
838
+ ? input()
839
+ : null;
840
+ if (value === null)
841
+ throw Oops$5.BadArgument(typeof input);
842
+ if (value)
843
+ onUpdate(value);
844
+ }
845
+ return {
846
+ set: onUpdate,
847
+ recursive: true
848
+ };
849
+ });
837
850
  }
838
851
 
839
852
  class Lookup {
@@ -891,113 +904,56 @@ class Lookup {
891
904
  pop() {
892
905
  for (const key of getOwnPropertySymbols(this)) {
893
906
  const entry = getOwnPropertyDescriptor(this, key);
894
- if (entry.writable)
907
+ if (entry.writable && entry.value)
895
908
  entry.value.destroy();
896
909
  }
897
910
  }
898
911
  }
899
912
 
900
- const Oops$5 = issues({
901
- NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`,
902
- NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`,
903
- BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
904
- });
905
913
  const LookupContext = React.createContext(new Lookup());
906
914
  const useLookup = () => React.useContext(LookupContext);
907
- function useContext(Type, strict) {
915
+ const Oops$4 = issues({
916
+ NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
917
+ });
918
+ function useInContext(Type, arg) {
908
919
  const instance = useLookup().get(Type);
909
- if (!instance && strict)
910
- throw Oops$5.NothingInContext(Type.name);
911
- return instance;
912
- }
913
- function Consumer(props) {
914
- const { get, has, children, of: Control } = props;
915
- if (typeof children == "function")
916
- return children(useTap(useContext(Control)));
917
- const callback = has || get;
918
- if (typeof callback == "function")
919
- callback(useContext(Control, !!has));
920
- else
921
- throw Oops$5.BadConsumerProps();
922
- return null;
923
- }
924
- function useNewContext(inject) {
925
- const from = useLookup();
926
- return React.useMemo(() => {
927
- if (!inject)
928
- throw Oops$5.NoProviderType();
929
- const context = from.push(inject);
930
- for (const instance of context.local)
931
- if (instance)
932
- for (const apply of getPending(instance))
933
- apply(context);
934
- return context;
935
- }, []);
936
- }
937
- function useAppliedProps(within, props) {
938
- const update = React.useMemo(() => {
939
- const targets = within.local;
940
- return function integrate(props) {
941
- for (const [key, value] of entries(props))
942
- if (key != "of" && key != "children")
943
- for (const into of targets)
944
- if (into && key in into)
945
- into[key] = value;
946
- };
947
- }, []);
948
- update(props);
949
- }
950
- function RenderFunction(props) {
951
- const hook = use(refresh => {
952
- const targets = props.context.local;
953
- if (targets.length == 1)
954
- return new Subscriber(targets[0], () => refresh);
955
- return {};
956
- });
957
- if (hook.commit)
958
- React.useLayoutEffect(() => hook.commit(), []);
959
- return props.render(hook.proxy);
960
- }
961
- function Provider(props) {
962
- const context = useNewContext(props.of);
963
- const render = props.children;
964
- useAppliedProps(context, props);
965
- React.useLayoutEffect(() => () => context.pop(), []);
966
- return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
967
- ? React.createElement(RenderFunction, { context, render })
968
- : render);
920
+ if (!instance && arg !== false)
921
+ throw Oops$4.NothingInContext(Type.name);
922
+ return typeof arg == "string" ?
923
+ instance[arg] :
924
+ instance;
969
925
  }
970
926
 
971
- const Oops$4 = issues({
972
- CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
927
+ const Oops$3 = issues({
928
+ CantAttachGlobal: (parent, child) => `Global '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
973
929
  AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
974
930
  });
975
931
  const PendingContext = new WeakMap();
976
932
  const ContextWasUsed = new WeakMap();
977
933
  function tap(type, argument) {
978
- return child(function tap(key) {
934
+ return apply(function tap(key) {
935
+ if ("set" in type)
936
+ this.state[key] = type.get();
937
+ else {
938
+ const { subject } = this;
939
+ if ("set" in subject.constructor)
940
+ throw Oops$3.CantAttachGlobal(subject, type.name);
941
+ getPending(subject).push(context => {
942
+ let instance = context.get(type);
943
+ if (typeof argument == "function") {
944
+ if (argument(instance) === false)
945
+ instance = undefined;
946
+ }
947
+ else if (!instance && argument)
948
+ throw Oops$3.AmbientRequired(type.name, subject, key);
949
+ this.update(key, instance);
950
+ });
951
+ }
979
952
  return {
980
- get: pendingAccess(this.subject, type, key, argument)
953
+ recursive: true
981
954
  };
982
955
  });
983
956
  }
984
- function pendingAccess(from, type, key, argument) {
985
- if ("set" in type)
986
- return () => type.get();
987
- if ("set" in from.constructor)
988
- throw Oops$4.CantAttachGlobal(from, type.name);
989
- let instance;
990
- getPending(from).push(context => {
991
- instance = context.get(type);
992
- if (typeof argument == "function") {
993
- if (argument(instance) === false)
994
- instance = undefined;
995
- }
996
- else if (!instance && argument)
997
- throw Oops$4.AmbientRequired(type.name, from, key);
998
- });
999
- return () => instance;
1000
- }
1001
957
  function usePeerContext(subject) {
1002
958
  if (ContextWasUsed.has(subject)) {
1003
959
  if (ContextWasUsed.get(subject))
@@ -1020,268 +976,242 @@ function getPending(subject) {
1020
976
  return pending;
1021
977
  }
1022
978
 
1023
- const Oops$3 = issues({
1024
- DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
1025
- BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
1026
- PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
1027
- });
1028
- function createRef(key, cb) {
1029
- const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
1030
- defineProperty(refObjectFunction, "current", {
1031
- set: refObjectFunction,
1032
- get: () => this.state[key]
1033
- });
1034
- return refObjectFunction;
1035
- }
1036
- function ref(arg) {
1037
- return apply(function ref(key) {
1038
- let value = {};
1039
- if (typeof arg == "object") {
1040
- const source = getController(arg);
1041
- for (const key in source.state)
1042
- defineLazy(value, key, createRef.bind(source, key));
1043
- }
1044
- else
1045
- value = createRef.call(this, key, arg);
1046
- return { value };
1047
- });
1048
- }
1049
- function act(task) {
1050
- return apply(function act(key) {
1051
- let pending = false;
1052
- const invoke = (...args) => {
1053
- if (pending)
1054
- return Promise.reject(Oops$3.DuplicateAction(key));
1055
- pending = true;
1056
- this.update(key);
1057
- return new Promise(res => {
1058
- res(task.apply(this.subject, args));
1059
- }).finally(() => {
1060
- pending = false;
1061
- this.update(key);
1062
- });
1063
- };
1064
- this.state[key] = undefined;
1065
- setAlias(invoke, `run ${key}`);
1066
- defineProperty(invoke, "active", {
1067
- get: () => pending
979
+ function use(init) {
980
+ const [state, update] = React.useState(() => [
981
+ init(() => update(state.concat()))
982
+ ]);
983
+ return state[0];
984
+ }
985
+
986
+ function useModel(source, arg, callback) {
987
+ const instance = React.useMemo(() => {
988
+ const cb = callback || arg;
989
+ const instance = typeof source == "function" ?
990
+ source.prototype instanceof Model ?
991
+ new source() :
992
+ source() :
993
+ source;
994
+ control(instance);
995
+ if (typeof cb == "function")
996
+ cb(instance);
997
+ return instance;
998
+ }, []);
999
+ if (Array.isArray(arg)) {
1000
+ const update = useState(0)[1];
1001
+ React.useLayoutEffect(() => {
1002
+ if (arg.length && instance instanceof Model)
1003
+ instance.on(arg, () => update(x => x + 1), true);
1004
+ return () => {
1005
+ if (Model.isTypeof(source))
1006
+ instance.destroy();
1007
+ };
1008
+ }, []);
1009
+ return instance;
1010
+ }
1011
+ else {
1012
+ const local = use(refresh => {
1013
+ return new Subscriber(instance, () => refresh);
1068
1014
  });
1069
- return {
1070
- value: invoke,
1071
- writable: false
1072
- };
1073
- });
1074
- }
1075
- function from(source, setter, argument) {
1076
- return apply(function from(key) {
1077
- let getSource;
1078
- const { subject } = this;
1079
- const getter = argument === true
1080
- ? pendingValue
1081
- : argument || undefined;
1082
- // Easy mistake, using a peer, will always be unresolved.
1083
- if (typeof source == "symbol")
1084
- throw Oops$3.PeerNotAllowed(subject, key);
1085
- // replace source controller in-case different
1086
- if (typeof source == "object")
1087
- getSource = () => getController(source);
1088
- // specifically an arrow function (getter factory)
1089
- else if (!source.prototype) {
1090
- setter = source.call(subject, key);
1091
- getSource = () => this;
1092
- }
1093
- // is a peer Model (constructor)
1094
- else if (Model.isTypeof(source))
1095
- getSource = pendingAccess(subject, source, key, true);
1096
- // Regular function is too ambiguous so not allowed.
1097
- else
1098
- throw Oops$3.BadComputedSource(subject, key, source);
1099
- prepare(this, key, getSource, setter, getter);
1100
- });
1015
+ React.useLayoutEffect(() => {
1016
+ local.commit();
1017
+ return () => {
1018
+ local.release();
1019
+ if (Model.isTypeof(source))
1020
+ instance.destroy();
1021
+ };
1022
+ }, []);
1023
+ return local.proxy;
1024
+ }
1101
1025
  }
1102
1026
 
1103
- const Oops$2 = issues({
1104
- ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
1105
- });
1106
- /* deprecated - ignore coverage */
1107
- /* istanbul ignore next */
1108
- function pending(source, compute) {
1109
- if (source === undefined)
1110
- return set();
1111
- if (typeof source == "function" && !source.prototype)
1112
- return set(source);
1113
- return from(source, compute, true);
1114
- }
1115
- /**
1116
- * Get value, suspend instead if undefined.
1117
- *
1118
- * Throws suspense promise also interpretable as an error.
1119
- * React could handle it but other contexts probably not.
1120
- */
1121
- function pendingValue(via, key) {
1122
- const value = via.state[key];
1123
- if (value === undefined)
1124
- throw suspend(via, key);
1125
- return value;
1126
- }
1127
- function pendingFactory(via, key, fn) {
1128
- const { subject, state } = via;
1129
- let waiting;
1130
- let error;
1131
- return () => {
1132
- if (waiting)
1133
- throw waiting;
1134
- if (error)
1135
- throw error;
1136
- if (key in state)
1137
- return state[key];
1138
- let output;
1139
- try {
1140
- output = fn.call(subject, key, subject);
1141
- }
1142
- catch (err) {
1143
- throw error = err;
1144
- }
1145
- if (output instanceof Promise) {
1146
- const issue = Oops$2.ValueNotReady(subject, key);
1147
- output
1148
- .catch(err => error = err)
1149
- .then(out => state[key] = out)
1150
- .finally(() => waiting = undefined);
1151
- throw waiting = Object.assign(output, {
1152
- message: issue.message,
1153
- stack: issue.stack
1154
- });
1027
+ function useFrom(target, compute, suspend) {
1028
+ const local = use(refresh => {
1029
+ const sub = new Subscriber(target, () => update);
1030
+ const spy = sub.proxy;
1031
+ let value = compute.call(spy, spy);
1032
+ let update;
1033
+ let retry;
1034
+ const set = (next) => {
1035
+ value = next;
1036
+ if (retry) {
1037
+ retry();
1038
+ retry = undefined;
1039
+ }
1040
+ else
1041
+ refresh();
1042
+ };
1043
+ if (value instanceof Promise) {
1044
+ value.then(set);
1045
+ value = undefined;
1155
1046
  }
1156
1047
  else {
1157
- return state[key] = output;
1048
+ sub.commit();
1049
+ update = () => {
1050
+ const next = compute.call(spy, spy);
1051
+ if (value != next)
1052
+ set(next);
1053
+ };
1158
1054
  }
1159
- };
1160
- }
1161
- function suspend(source, key) {
1162
- const error = Oops$2.ValueNotReady(source.subject, key);
1163
- const promise = new Promise(resolve => {
1164
- const release = source.addListener(forKey => {
1165
- if (forKey == key)
1166
- return () => {
1167
- if (source.state[key] !== undefined) {
1168
- release();
1169
- resolve();
1170
- }
1171
- };
1055
+ defineProperty(sub, "proxy", {
1056
+ get() {
1057
+ if (value === undefined && suspend)
1058
+ throw new Promise(res => retry = res);
1059
+ return value;
1060
+ }
1172
1061
  });
1062
+ return sub;
1173
1063
  });
1174
- return Object.assign(promise, {
1175
- toString: () => String(error),
1176
- name: "Suspense",
1177
- message: error.message,
1178
- stack: error.stack
1179
- });
1064
+ React.useLayoutEffect(() => local.release, []);
1065
+ return local.proxy;
1180
1066
  }
1181
1067
 
1182
- const Oops$1 = issues({
1183
- NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
1184
- BadFactory: () => `Set instruction can only accept a factory or undefined.`,
1185
- FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
1186
- });
1187
- function lazy(value) {
1188
- return apply(function lazy(key) {
1189
- const source = this.subject;
1190
- source[key] = value;
1191
- defineProperty(this.state, key, {
1192
- get: () => source[key]
1193
- });
1194
- });
1195
- }
1196
- function set(factory, argument) {
1197
- return apply(function set(key) {
1198
- let set;
1199
- let get;
1200
- const required = argument === true || argument === undefined;
1201
- if (factory === undefined)
1202
- get = () => pendingValue(this, key);
1203
- else if (typeof factory !== "function")
1204
- throw Oops$1.BadFactory();
1205
- else {
1206
- get = pendingFactory(this, key, factory);
1207
- if (required)
1208
- try {
1209
- get();
1210
- }
1211
- catch (err) {
1212
- if (err instanceof Promise)
1213
- ;
1214
- else {
1215
- Oops$1.FactoryFailed(this.subject, key).warn();
1216
- throw err;
1217
- }
1068
+ function useTap(source, path, expect) {
1069
+ const instance = useMemo(() => {
1070
+ if (typeof source == "object")
1071
+ return () => source;
1072
+ if ("get" in source)
1073
+ return () => source.get();
1074
+ if ("prototype" in source)
1075
+ return () => useInContext(source);
1076
+ else
1077
+ return source;
1078
+ }, [])();
1079
+ if (typeof path == "function")
1080
+ return useFrom(instance, path, expect);
1081
+ const local = use(refresh => {
1082
+ const parent = control(instance);
1083
+ const sub = new Subscriber(parent, () => refresh);
1084
+ if (typeof path == "string") {
1085
+ const source = sub.proxy;
1086
+ defineProperty(sub, "proxy", {
1087
+ get() {
1088
+ const value = source[path];
1089
+ if (value === undefined && expect)
1090
+ throw suspend(parent, path);
1091
+ return value;
1218
1092
  }
1093
+ });
1219
1094
  }
1220
- if (typeof argument == "function")
1221
- set = createValueEffect(argument);
1222
- else
1223
- set = (value) => {
1224
- if (value === undefined && required)
1225
- throw Oops$1.NonOptional(this.subject, key);
1226
- };
1227
- return { set, get };
1095
+ return sub;
1228
1096
  });
1229
- }
1230
- function on(initial, onUpdate) {
1231
- return set(initial && (() => initial), onUpdate);
1097
+ React.useLayoutEffect(() => local.commit(), []);
1098
+ return local.proxy;
1232
1099
  }
1233
1100
 
1234
1101
  class MVC extends Model {
1235
1102
  tap(path, expect) {
1236
1103
  return useTap(this, path, expect);
1237
1104
  }
1238
- tag(id) {
1239
- return useTag(this, id);
1105
+ use(arg, callback) {
1106
+ return useModel(this, arg, callback);
1240
1107
  }
1241
- use(callback) {
1242
- return useNew(this, callback);
1243
- }
1244
- static new(callback) {
1245
- return usePassive(this, callback);
1246
- }
1247
- static get(key) {
1248
- const instance = useContext(this, key !== false);
1249
- return (typeof key == "string" ?
1250
- instance[key] :
1251
- instance);
1108
+ static get(required) {
1109
+ return useInContext(this, required);
1252
1110
  }
1253
1111
  static tap(key, expect) {
1254
- return this.get().tap(key, expect);
1112
+ return useTap(this, key, expect);
1255
1113
  }
1256
- static tag(id) {
1257
- return this.get().tag(id);
1114
+ /**
1115
+ * **React Hook** - Spawn and maintain a controller from within a component.
1116
+ *
1117
+ * More efficient than `use()` if you don't need hook-based features.
1118
+ *
1119
+ * @param callback - Run after creation of instance.
1120
+ */
1121
+ static new(callback) {
1122
+ return useModel(this, [], callback);
1258
1123
  }
1259
- static use(callback) {
1260
- return useModel(this, callback);
1124
+ static use(arg, callback) {
1125
+ const instance = useModel(this, arg, callback);
1126
+ usePeerContext(instance.get);
1127
+ return instance;
1261
1128
  }
1262
- static uses(props, only) {
1129
+ static uses(data, only) {
1263
1130
  return this.use(instance => {
1264
- instance.import(props, only);
1131
+ instance.import(data, only);
1265
1132
  });
1266
1133
  }
1267
- static using(props, only) {
1134
+ static using(data, only) {
1268
1135
  const instance = this.use();
1269
- instance.import(props, only);
1136
+ instance.import(data, only);
1270
1137
  return instance;
1271
1138
  }
1272
1139
  static meta(path, expect) {
1273
- return typeof path == "function"
1274
- ? useComputed(this, path, expect)
1275
- : useActive(this, path, expect);
1140
+ return useTap(() => this, path, expect);
1276
1141
  }
1277
1142
  }
1278
1143
 
1144
+ const Oops$2 = issues({
1145
+ BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
1146
+ });
1147
+ function Consumer(props) {
1148
+ const { get, has, children, of: Control } = props;
1149
+ if (typeof children == "function")
1150
+ return children(useTap(Control));
1151
+ const callback = has || get;
1152
+ if (typeof callback == "function")
1153
+ callback(useInContext(Control, !!has));
1154
+ else
1155
+ throw Oops$2.BadConsumerProps();
1156
+ return null;
1157
+ }
1158
+
1159
+ const Oops$1 = issues({
1160
+ NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`
1161
+ });
1162
+ function Provider(props) {
1163
+ const context = useNewContext(props.of);
1164
+ const render = props.children;
1165
+ useAppliedProps(context, props);
1166
+ React.useLayoutEffect(() => () => context.pop(), []);
1167
+ return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1168
+ ? React.createElement(RenderFunction, { context, render })
1169
+ : render);
1170
+ }
1171
+ function useNewContext(inject) {
1172
+ const from = useLookup();
1173
+ return React.useMemo(() => {
1174
+ if (!inject)
1175
+ throw Oops$1.NoProviderType();
1176
+ const context = from.push(inject);
1177
+ for (const instance of context.local)
1178
+ if (instance)
1179
+ for (const apply of getPending(instance))
1180
+ apply(context);
1181
+ return context;
1182
+ }, []);
1183
+ }
1184
+ function useAppliedProps(within, props) {
1185
+ const update = React.useMemo(() => {
1186
+ const targets = within.local;
1187
+ return function integrate(props) {
1188
+ for (const [key, value] of entries(props))
1189
+ if (key != "of" && key != "children")
1190
+ for (const into of targets)
1191
+ if (into && key in into)
1192
+ into[key] = value;
1193
+ };
1194
+ }, []);
1195
+ update(props);
1196
+ }
1197
+ function RenderFunction(props) {
1198
+ const hook = use(refresh => {
1199
+ const targets = props.context.local;
1200
+ if (targets.length == 1)
1201
+ return new Subscriber(targets[0], () => refresh);
1202
+ return {};
1203
+ });
1204
+ if (hook.commit)
1205
+ React.useLayoutEffect(() => hook.commit(), []);
1206
+ return props.render(hook.proxy);
1207
+ }
1208
+
1279
1209
  const Oops = issues({
1280
1210
  GlobalDoesNotExist: (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.`,
1281
1211
  GlobalExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1282
1212
  });
1283
1213
  const Active = new WeakMap();
1284
- class Singleton extends MVC {
1214
+ class Global extends MVC {
1285
1215
  static create(...args) {
1286
1216
  const Type = this;
1287
1217
  if (Active.has(Type))
@@ -1290,6 +1220,11 @@ class Singleton extends MVC {
1290
1220
  Active.set(Type, instance);
1291
1221
  return instance;
1292
1222
  }
1223
+ /**
1224
+ * Update the active instance of this class.
1225
+ * Returns a thenable; resolves after successful update.
1226
+ * If instance does not already exist, one will be created.
1227
+ */
1293
1228
  static set(updates) {
1294
1229
  const instance = Active.get(this) || this.create();
1295
1230
  instance.import(updates);
@@ -1303,6 +1238,7 @@ class Singleton extends MVC {
1303
1238
  instance[arg] :
1304
1239
  instance);
1305
1240
  }
1241
+ /** Destroy current instance of Global, if it exists. */
1306
1242
  static reset() {
1307
1243
  const current = Active.get(this);
1308
1244
  if (current)
@@ -1314,5 +1250,5 @@ class Singleton extends MVC {
1314
1250
  }
1315
1251
  }
1316
1252
 
1317
- export { Consumer, MVC as Model, Provider, Singleton, act, apply, child, MVC as default, from, lazy, set as memo, on, parent, pending, ref, set, tap, use$1 as use };
1253
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom, useInContext, useModel, useTap };
1318
1254
  //# sourceMappingURL=index.esm.js.map