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