@expressive/mvc 1.0.0-beta.4 → 1.0.0-beta.40

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.
@@ -0,0 +1,1267 @@
1
+ import React, { useState, useMemo } from 'react';
2
+
3
+ class Issue extends Error {
4
+ constructor() {
5
+ super(...arguments);
6
+ // drop first line (error factory) in stack trace.
7
+ this.stack = this.stack.replace(/\n.+/, "");
8
+ }
9
+ warn() {
10
+ console.warn(this.message);
11
+ }
12
+ }
13
+ function issues(register) {
14
+ const Library = {};
15
+ for (const name in register)
16
+ Library[name] = (...args) => new Issue(register[name].apply(null, args));
17
+ return Library;
18
+ }
19
+
20
+ const Oops$d = issues({
21
+ BadEffectCallback: () => `Callback for effect-callback may only return a function.`
22
+ });
23
+ const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
24
+ function define(target, key, value) {
25
+ defineProperty(target, key, { value });
26
+ }
27
+ function defineLazy(object, property, init) {
28
+ defineProperty(object, property, {
29
+ configurable: true,
30
+ get() {
31
+ const value = init.call(this);
32
+ defineProperty(this, property, { value });
33
+ return value;
34
+ }
35
+ });
36
+ }
37
+ function setAlias(func, displayName) {
38
+ assign(func, { displayName });
39
+ }
40
+ function createEffect(callback) {
41
+ let unSet;
42
+ return function (value) {
43
+ if (typeof unSet == "function")
44
+ unSet();
45
+ unSet = callback.call(this, value);
46
+ if (unSet instanceof Promise)
47
+ unSet = undefined;
48
+ if (unSet && typeof unSet !== "function")
49
+ throw Oops$d.BadEffectCallback();
50
+ };
51
+ }
52
+ function createValueEffect(callback) {
53
+ let unSet;
54
+ return function (value) {
55
+ if (typeof unSet == "function")
56
+ unSet(value);
57
+ const out = callback.call(this, value, this);
58
+ if (typeof out == "boolean")
59
+ return out;
60
+ if (!out || out instanceof Promise)
61
+ return;
62
+ if (typeof out == "function")
63
+ unSet = out;
64
+ else
65
+ throw Oops$d.BadEffectCallback();
66
+ };
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 === false)
117
+ return;
118
+ if (typeof desc == "object") {
119
+ if ("value" in desc)
120
+ state[key] = desc.value;
121
+ onSet = desc.set;
122
+ onGet = desc.get;
123
+ enumerable = desc.enumerable;
124
+ }
125
+ }
126
+ else
127
+ state[key] = entry;
128
+ const set = onSet !== false
129
+ ? this.ref(key, onSet)
130
+ : undefined;
131
+ function get() {
132
+ const local = this[LOCAL];
133
+ const value = state[key];
134
+ if (local && !local.watch[key])
135
+ local.watch[key] = true;
136
+ return onGet ? onGet(value, local) : value;
137
+ }
138
+ setAlias(get, `tap ${key}`);
139
+ defineProperty(subject, key, {
140
+ enumerable,
141
+ set,
142
+ get: onGet
143
+ ? () => onGet(state[key])
144
+ : () => state[key]
145
+ });
146
+ defineProperty(proxy, key, {
147
+ enumerable,
148
+ set,
149
+ get
150
+ });
151
+ }
152
+ ref(key, handler) {
153
+ const { state, subject } = this;
154
+ return (value) => {
155
+ if (state[key] == value)
156
+ return;
157
+ if (handler)
158
+ switch (handler.call(subject, value, state)) {
159
+ case true:
160
+ this.update(key);
161
+ case false:
162
+ return;
163
+ }
164
+ this.update(key, value);
165
+ };
166
+ }
167
+ addListener(listener) {
168
+ this.followers.add(listener);
169
+ return () => {
170
+ this.followers.delete(listener);
171
+ };
172
+ }
173
+ emit(keys) {
174
+ const waiting = [...this.waiting];
175
+ this.waiting.clear();
176
+ applyUpdate(this.subject, keys)();
177
+ for (const callback of waiting)
178
+ try {
179
+ callback(keys);
180
+ }
181
+ catch (err) {
182
+ console.error(err);
183
+ }
184
+ }
185
+ update(key, value) {
186
+ if (1 in arguments)
187
+ this.state[key] = value;
188
+ if (this.frame.has(key))
189
+ return;
190
+ if (!this.frame.size)
191
+ setTimeout(() => {
192
+ flush(this);
193
+ const keys = Object.freeze([...this.frame]);
194
+ this.frame.clear();
195
+ this.emit(keys);
196
+ }, 0);
197
+ this.frame.add(key);
198
+ for (const callback of this.followers) {
199
+ const event = callback(key, this);
200
+ if (typeof event == "function")
201
+ this.waiting.add(event);
202
+ }
203
+ }
204
+ requestUpdate(arg) {
205
+ if (typeof arg == "function") {
206
+ this.waiting.add(arg);
207
+ return;
208
+ }
209
+ if (typeof arg == "boolean" && arg !== this.frame.size > 0)
210
+ return Promise.reject(Oops$c.StrictUpdate(arg));
211
+ return {
212
+ then: (callback) => {
213
+ if (callback)
214
+ if (this.frame.size)
215
+ this.waiting.add(callback);
216
+ else
217
+ callback(false);
218
+ else
219
+ throw Oops$c.NoChaining();
220
+ }
221
+ };
222
+ }
223
+ }
224
+ function control(subject, cb) {
225
+ let control = subject[CONTROL];
226
+ const ready = READY.has(control);
227
+ if (!control) {
228
+ control = new Controller(subject);
229
+ define(subject, CONTROL, control);
230
+ }
231
+ if (!cb) {
232
+ if (!ready) {
233
+ READY.add(control);
234
+ control.start();
235
+ }
236
+ return control;
237
+ }
238
+ if (!ready) {
239
+ let done;
240
+ control.requestUpdate(() => {
241
+ done = cb(control);
242
+ });
243
+ return () => done && done();
244
+ }
245
+ return cb(control);
246
+ }
247
+
248
+ class Subscriber {
249
+ constructor(target, onUpdate) {
250
+ this.onUpdate = onUpdate;
251
+ this.active = false;
252
+ this.dependant = new Set();
253
+ this.watch = {};
254
+ const parent = target instanceof Controller
255
+ ? target : control(target);
256
+ const proxy = create(parent.proxy);
257
+ let reset;
258
+ define(proxy, LOCAL, this);
259
+ defineProperty(this, "proxy", {
260
+ configurable: true,
261
+ get() {
262
+ if (reset)
263
+ setTimeout(reset, 0);
264
+ return proxy;
265
+ }
266
+ });
267
+ const release = parent.addListener(key => {
268
+ const handler = this.watch[key];
269
+ if (!handler || !this.active)
270
+ return;
271
+ if (typeof handler == "function")
272
+ handler();
273
+ const notify = this.onUpdate(key, parent);
274
+ const getWhy = (keys) => {
275
+ reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
276
+ };
277
+ if (notify) {
278
+ parent.requestUpdate(getWhy);
279
+ parent.requestUpdate(notify);
280
+ }
281
+ });
282
+ this.commit = () => {
283
+ this.active = true;
284
+ this.dependant.forEach(x => x.commit());
285
+ return this.release;
286
+ };
287
+ this.release = () => {
288
+ this.dependant.forEach(x => x.release());
289
+ release();
290
+ };
291
+ }
292
+ }
293
+
294
+ const Oops$b = issues({
295
+ ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
296
+ ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
297
+ });
298
+ const INIT = new WeakSet();
299
+ const INFO = new WeakMap();
300
+ const USED = new WeakMap();
301
+ const KEYS = new WeakMap();
302
+ function prepare(parent, key, source, setter, getter) {
303
+ let sub;
304
+ const { state, subject } = parent;
305
+ const info = { key, parent, priority: 1 };
306
+ let register = USED.get(parent);
307
+ if (!register) {
308
+ register = new Map();
309
+ USED.set(parent, register);
310
+ }
311
+ register.set(key, info);
312
+ function compute(initial) {
313
+ try {
314
+ return setter.call(sub.proxy, sub.proxy);
315
+ }
316
+ catch (err) {
317
+ Oops$b.ComputeFailed(subject, key, !!initial).warn();
318
+ throw err;
319
+ }
320
+ }
321
+ function update() {
322
+ let value;
323
+ try {
324
+ value = compute(false);
325
+ }
326
+ catch (e) {
327
+ console.error(e);
328
+ }
329
+ finally {
330
+ if (state[key] !== value) {
331
+ parent.update(key, value);
332
+ return value;
333
+ }
334
+ }
335
+ }
336
+ function defer(_key, from) {
337
+ let pending = KEYS.get(from);
338
+ if (!pending)
339
+ KEYS.set(from, pending = []);
340
+ if (info.parent !== from)
341
+ update();
342
+ else {
343
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
344
+ pending.splice(after + 1, 0, update);
345
+ }
346
+ }
347
+ function create(early) {
348
+ sub = new Subscriber(source(), defer);
349
+ defineProperty(state, key, {
350
+ value: undefined,
351
+ writable: true
352
+ });
353
+ try {
354
+ return state[key] = compute(true);
355
+ }
356
+ catch (e) {
357
+ if (early)
358
+ Oops$b.ComputedEarly(key).warn();
359
+ throw e;
360
+ }
361
+ finally {
362
+ sub.commit();
363
+ for (const key in sub.watch) {
364
+ const peer = register.get(key);
365
+ if (peer && peer.priority >= info.priority)
366
+ info.priority = peer.priority + 1;
367
+ }
368
+ }
369
+ }
370
+ setAlias(update, `try ${key}`);
371
+ setAlias(create, `new ${key}`);
372
+ setAlias(setter, `run ${key}`);
373
+ INIT.add(create);
374
+ INFO.set(update, info);
375
+ defineProperty(state, key, {
376
+ get: create,
377
+ configurable: true,
378
+ enumerable: true
379
+ });
380
+ if (getter)
381
+ return () => getter(parent, key);
382
+ }
383
+ function ensure(on, keys) {
384
+ for (const key of keys) {
385
+ const desc = getOwnPropertyDescriptor(on.state, key);
386
+ const getter = desc && desc.get;
387
+ if (INIT.has(getter))
388
+ getter(true);
389
+ }
390
+ }
391
+ function flush(on) {
392
+ const handled = on.frame;
393
+ const pending = KEYS.get(on);
394
+ if (!pending)
395
+ return;
396
+ while (pending.length) {
397
+ const why = [...handled];
398
+ const compute = pending.shift();
399
+ const { key } = INFO.get(compute);
400
+ if (!handled.has(key))
401
+ compute(why);
402
+ }
403
+ KEYS.delete(on);
404
+ }
405
+
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);
415
+ }
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
+ });
444
+ }
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
+ });
451
+ }
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
+ });
478
+ });
479
+ }
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];
488
+ }
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;
497
+ }
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);
509
+ }
510
+ arg = undefined;
511
+ }
512
+ return target.requestUpdate(arg);
513
+ }
514
+ /**
515
+ * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
516
+ */
517
+ destroy() {
518
+ control(this).stop();
519
+ }
520
+ toString() {
521
+ return this.constructor.name;
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
+ */
530
+ static create(...args) {
531
+ const instance = new this(...args);
532
+ control(instance);
533
+ return instance;
534
+ }
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);
543
+ }
544
+ }
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 (typeof output == "function")
556
+ return { get: output };
557
+ if (typeof output !== "object")
558
+ return output || undefined;
559
+ if ("explicit" in output && output.explicit) {
560
+ defineProperty(this.subject, key, output);
561
+ return false;
562
+ }
563
+ if ("recursive" in output)
564
+ output = Object.assign(Object.assign({}, output), { get: recursive(this, key) });
565
+ return output;
566
+ }
567
+ Pending.set(placeholder, setup);
568
+ return placeholder;
569
+ }
570
+ function recursive(source, key) {
571
+ const context = new WeakMap();
572
+ const subscribe = (parent) => {
573
+ let child;
574
+ const init = () => {
575
+ if (child) {
576
+ child.release();
577
+ parent.dependant.delete(child);
578
+ context.set(parent, undefined);
579
+ child = undefined;
580
+ }
581
+ const value = source.state[key];
582
+ if (value && CONTROL in value) {
583
+ child = new Subscriber(value, parent.onUpdate);
584
+ if (parent.active)
585
+ child.commit();
586
+ parent.dependant.add(child);
587
+ context.set(parent, child.proxy);
588
+ }
589
+ };
590
+ init();
591
+ parent.watch[key] = init;
592
+ };
593
+ return (value, local) => {
594
+ if (!local)
595
+ return value;
596
+ if (!context.has(local))
597
+ subscribe(local);
598
+ return context.get(local);
599
+ };
600
+ }
601
+
602
+ const Oops$a = issues({
603
+ DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
604
+ });
605
+ function act(task) {
606
+ return apply(function act(key) {
607
+ let pending = false;
608
+ const invoke = (...args) => {
609
+ if (pending)
610
+ return Promise.reject(Oops$a.DuplicateAction(key));
611
+ pending = true;
612
+ this.update(key);
613
+ return new Promise(res => {
614
+ res(task.apply(this.subject, args));
615
+ }).finally(() => {
616
+ pending = false;
617
+ this.update(key);
618
+ });
619
+ };
620
+ this.state[key] = undefined;
621
+ setAlias(invoke, `run ${key}`);
622
+ defineProperty(invoke, "active", {
623
+ get: () => pending
624
+ });
625
+ return {
626
+ value: invoke,
627
+ set: false
628
+ };
629
+ });
630
+ }
631
+
632
+ const Oops$9 = issues({
633
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
634
+ });
635
+ /**
636
+ * Get value, suspend instead if undefined.
637
+ *
638
+ * Throws suspense promise also interpretable as an error.
639
+ * React could handle it but other contexts probably not.
640
+ */
641
+ function pendingValue(via, key) {
642
+ const value = via.state[key];
643
+ if (value === undefined)
644
+ throw suspend(via, key);
645
+ return value;
646
+ }
647
+ function pendingFactory(via, key, fn) {
648
+ const { subject, state } = via;
649
+ let waiting;
650
+ let error;
651
+ return () => {
652
+ if (waiting)
653
+ throw waiting;
654
+ if (error)
655
+ throw error;
656
+ if (key in state)
657
+ return state[key];
658
+ let output;
659
+ try {
660
+ output = fn.call(subject, key, subject);
661
+ }
662
+ catch (err) {
663
+ throw error = err;
664
+ }
665
+ if (output instanceof Promise) {
666
+ const issue = Oops$9.ValueNotReady(subject, key);
667
+ output
668
+ .catch(err => error = err)
669
+ .then(out => state[key] = out)
670
+ .finally(() => waiting = undefined);
671
+ throw waiting = Object.assign(output, {
672
+ message: issue.message,
673
+ stack: issue.stack
674
+ });
675
+ }
676
+ else {
677
+ return state[key] = output;
678
+ }
679
+ };
680
+ }
681
+ function suspend(source, key) {
682
+ const error = Oops$9.ValueNotReady(source.subject, key);
683
+ const promise = new Promise(resolve => {
684
+ const release = source.addListener(forKey => {
685
+ if (forKey == key)
686
+ return () => {
687
+ if (source.state[key] !== undefined) {
688
+ release();
689
+ resolve();
690
+ }
691
+ };
692
+ });
693
+ });
694
+ return Object.assign(promise, {
695
+ toString: () => String(error),
696
+ name: "Suspense",
697
+ message: error.message,
698
+ stack: error.stack
699
+ });
700
+ }
701
+
702
+ const Oops$8 = issues({
703
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
704
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
705
+ });
706
+ function from(source, setter, arg) {
707
+ return apply(function from(key) {
708
+ const { subject } = this;
709
+ let getSource;
710
+ let getter;
711
+ if (arg === true || setter === true)
712
+ getter = pendingValue;
713
+ if (typeof setter == "boolean")
714
+ setter = undefined;
715
+ // Easy mistake, using a peer, will always be unresolved.
716
+ if (typeof source == "symbol")
717
+ throw Oops$8.PeerNotAllowed(subject, key);
718
+ // replace source controller in-case it is different
719
+ if (typeof source == "object")
720
+ getSource = () => control(source);
721
+ // specifically an arrow function (getter factory)
722
+ else if (!source.prototype) {
723
+ setter = source.call(subject, key);
724
+ getSource = () => this;
725
+ }
726
+ // Regular function is too ambiguous so not allowed.
727
+ else
728
+ throw Oops$8.BadComputedSource(subject, key, source);
729
+ return prepare(this, key, getSource, setter, getter);
730
+ });
731
+ }
732
+
733
+ function ref(arg) {
734
+ return apply(function ref(key) {
735
+ let value = {};
736
+ if (typeof arg == "object") {
737
+ const source = control(arg);
738
+ for (const key in source.state)
739
+ defineLazy(value, key, createRef.bind(source, key));
740
+ }
741
+ else
742
+ value = createRef.call(this, key, arg);
743
+ return { value, explicit: true };
744
+ });
745
+ }
746
+ function createRef(key, cb) {
747
+ const refObjectFunction = this.ref(key, cb && createValueEffect(cb));
748
+ defineProperty(refObjectFunction, "current", {
749
+ set: refObjectFunction,
750
+ get: () => this.state[key]
751
+ });
752
+ return refObjectFunction;
753
+ }
754
+
755
+ const Oops$7 = issues({
756
+ NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
757
+ BadFactory: () => `Set instruction can only accept a factory or undefined.`,
758
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
759
+ });
760
+ function set(factory, argument) {
761
+ return apply(function set(key) {
762
+ let set;
763
+ let get;
764
+ const required = argument === true || argument === undefined;
765
+ if (factory === undefined)
766
+ get = () => pendingValue(this, key);
767
+ else if (typeof factory !== "function")
768
+ throw Oops$7.BadFactory();
769
+ else {
770
+ get = pendingFactory(this, key, factory);
771
+ if (required)
772
+ try {
773
+ get();
774
+ }
775
+ catch (err) {
776
+ if (err instanceof Promise)
777
+ ;
778
+ else {
779
+ Oops$7.FactoryFailed(this.subject, key).warn();
780
+ throw err;
781
+ }
782
+ }
783
+ }
784
+ if (typeof argument == "function")
785
+ set = createValueEffect(argument);
786
+ else
787
+ set = (value) => {
788
+ if (value === undefined && required)
789
+ throw Oops$7.NonOptional(this.subject, key);
790
+ };
791
+ return { set, get };
792
+ });
793
+ }
794
+
795
+ const Parent = new WeakMap();
796
+ const Oops$6 = issues({
797
+ ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
798
+ UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
799
+ });
800
+ function parent(Expects, required) {
801
+ return apply(function parent() {
802
+ const child = this.subject;
803
+ const value = Parent.get(child);
804
+ const expected = Expects.name;
805
+ if (!value) {
806
+ if (required !== false)
807
+ throw Oops$6.ParentRequired(expected, child);
808
+ }
809
+ else if (!(value instanceof Expects))
810
+ throw Oops$6.UnexpectedParent(expected, child, value);
811
+ return { value };
812
+ });
813
+ }
814
+
815
+ const Oops$5 = issues({
816
+ BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
817
+ });
818
+ function use$1(input, argument) {
819
+ return apply(function use(key) {
820
+ const { subject } = this;
821
+ const onUpdate = (next) => {
822
+ this.state[key] = next;
823
+ if (next) {
824
+ Parent.set(next, subject);
825
+ control(next);
826
+ }
827
+ if (typeof argument == "function")
828
+ argument(next);
829
+ return true;
830
+ };
831
+ if (input) {
832
+ const value = Model.isTypeof(input)
833
+ ? new input() :
834
+ typeof input === "object"
835
+ ? input :
836
+ typeof input === "function"
837
+ ? input()
838
+ : null;
839
+ if (value === null)
840
+ throw Oops$5.BadArgument(typeof input);
841
+ if (value)
842
+ onUpdate(value);
843
+ }
844
+ return {
845
+ set: onUpdate,
846
+ recursive: true
847
+ };
848
+ });
849
+ }
850
+
851
+ class Lookup {
852
+ constructor() {
853
+ this.table = new Map();
854
+ }
855
+ get local() {
856
+ return [
857
+ ...new Set(getOwnPropertySymbols(this).map(symbol => this[symbol]))
858
+ ];
859
+ }
860
+ key(T) {
861
+ let key = this.table.get(T);
862
+ if (!key) {
863
+ key = Symbol(T.name);
864
+ this.table.set(T, key);
865
+ }
866
+ return key;
867
+ }
868
+ get(T) {
869
+ return this[this.key(T)];
870
+ }
871
+ push(I) {
872
+ const next = create(this);
873
+ if (I instanceof Model || typeof I == "function")
874
+ next.register(I);
875
+ else
876
+ for (const i of values(I))
877
+ next.register(i);
878
+ return next;
879
+ }
880
+ register(I) {
881
+ let writable = true;
882
+ let T;
883
+ if (I instanceof Model) {
884
+ T = I.constructor;
885
+ writable = false;
886
+ }
887
+ else {
888
+ T = I;
889
+ I = I.create();
890
+ }
891
+ do {
892
+ const key = this.key(T);
893
+ const conflict = this.hasOwnProperty(key);
894
+ defineProperty(this, key, {
895
+ value: conflict ? null : I,
896
+ configurable: true,
897
+ writable
898
+ });
899
+ T = getPrototypeOf(T);
900
+ } while (T !== Model);
901
+ return I;
902
+ }
903
+ pop() {
904
+ for (const key of getOwnPropertySymbols(this)) {
905
+ const entry = getOwnPropertyDescriptor(this, key);
906
+ if (entry.writable && entry.value)
907
+ entry.value.destroy();
908
+ }
909
+ }
910
+ }
911
+
912
+ const LookupContext = React.createContext(new Lookup());
913
+ const useLookup = () => React.useContext(LookupContext);
914
+ const Oops$4 = issues({
915
+ NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
916
+ });
917
+ function useInContext(Type, arg) {
918
+ const instance = useLookup().get(Type);
919
+ if (!instance && arg !== false)
920
+ throw Oops$4.NothingInContext(Type.name);
921
+ return typeof arg == "string" ?
922
+ instance[arg] :
923
+ instance;
924
+ }
925
+
926
+ const Oops$3 = issues({
927
+ CantAttachGlobal: (parent, child) => `Global '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
928
+ 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.`
929
+ });
930
+ const PendingContext = new WeakMap();
931
+ const ContextWasUsed = new WeakMap();
932
+ function tap(type, argument) {
933
+ return apply(function tap(key) {
934
+ if ("set" in type)
935
+ this.state[key] = type.get();
936
+ else {
937
+ const { subject } = this;
938
+ if ("set" in subject.constructor)
939
+ throw Oops$3.CantAttachGlobal(subject, type.name);
940
+ getPending(subject).push(context => {
941
+ let instance = context.get(type);
942
+ if (typeof argument == "function") {
943
+ if (argument(instance) === false)
944
+ instance = undefined;
945
+ }
946
+ else if (!instance && argument)
947
+ throw Oops$3.AmbientRequired(type.name, subject, key);
948
+ this.update(key, instance);
949
+ });
950
+ }
951
+ return {
952
+ recursive: true
953
+ };
954
+ });
955
+ }
956
+ function usePeerContext(subject) {
957
+ if (ContextWasUsed.has(subject)) {
958
+ if (ContextWasUsed.get(subject))
959
+ useLookup();
960
+ return;
961
+ }
962
+ const pending = PendingContext.get(subject);
963
+ if (pending) {
964
+ const local = useLookup();
965
+ for (const init of pending)
966
+ init(local);
967
+ PendingContext.delete(subject);
968
+ }
969
+ ContextWasUsed.set(subject, !!pending);
970
+ }
971
+ function getPending(subject) {
972
+ let pending = PendingContext.get(subject);
973
+ if (!pending)
974
+ PendingContext.set(subject, pending = []);
975
+ return pending;
976
+ }
977
+
978
+ function use(init) {
979
+ const [state, update] = React.useState(() => [
980
+ init(() => update(state.concat()))
981
+ ]);
982
+ return state[0];
983
+ }
984
+
985
+ function useModel(source, arg, arg2) {
986
+ const instance = React.useMemo(() => {
987
+ const cb = arg2 || arg;
988
+ const instance = typeof source == "function" ?
989
+ source.prototype instanceof Model ?
990
+ new source() :
991
+ source() :
992
+ source;
993
+ control(instance);
994
+ if (typeof cb == "function")
995
+ cb(instance);
996
+ return instance;
997
+ }, []);
998
+ if (Array.isArray(arg)) {
999
+ const update = useState(0)[1];
1000
+ React.useLayoutEffect(() => {
1001
+ if (arg.length && instance instanceof Model)
1002
+ instance.on(arg, () => update(x => x + 1), true);
1003
+ return () => {
1004
+ if (Model.isTypeof(source))
1005
+ instance.destroy();
1006
+ };
1007
+ }, []);
1008
+ return instance;
1009
+ }
1010
+ else {
1011
+ const local = use(refresh => (new Subscriber(instance, () => refresh)));
1012
+ if (typeof arg == "object") {
1013
+ local.active = false;
1014
+ if (typeof arg2 !== "object")
1015
+ arg2 = getOwnPropertyNames(instance);
1016
+ for (const key of arg2)
1017
+ if (key in arg)
1018
+ instance[key] = arg[key];
1019
+ React.useLayoutEffect(() => {
1020
+ local.active = true;
1021
+ });
1022
+ }
1023
+ React.useLayoutEffect(() => {
1024
+ local.commit();
1025
+ return () => {
1026
+ local.release();
1027
+ if (Model.isTypeof(source))
1028
+ instance.destroy();
1029
+ };
1030
+ }, []);
1031
+ return local.proxy;
1032
+ }
1033
+ }
1034
+
1035
+ function useFrom(target, compute, suspend) {
1036
+ const local = use(refresh => {
1037
+ const sub = new Subscriber(target, () => update);
1038
+ const spy = sub.proxy;
1039
+ let value = compute.call(spy, spy);
1040
+ let update;
1041
+ let retry;
1042
+ const set = (next) => {
1043
+ value = next;
1044
+ if (retry) {
1045
+ retry();
1046
+ retry = undefined;
1047
+ }
1048
+ else
1049
+ refresh();
1050
+ };
1051
+ if (value instanceof Promise) {
1052
+ value.then(set);
1053
+ value = undefined;
1054
+ }
1055
+ else {
1056
+ sub.commit();
1057
+ update = () => {
1058
+ const next = compute.call(spy, spy);
1059
+ if (value != next)
1060
+ set(next);
1061
+ };
1062
+ }
1063
+ defineProperty(sub, "proxy", {
1064
+ get() {
1065
+ if (value === undefined && suspend)
1066
+ throw new Promise(res => retry = res);
1067
+ return value;
1068
+ }
1069
+ });
1070
+ return sub;
1071
+ });
1072
+ React.useLayoutEffect(() => local.release, []);
1073
+ return local.proxy;
1074
+ }
1075
+
1076
+ function useTap(source, path, expect) {
1077
+ const instance = useMemo(() => {
1078
+ if (typeof source == "object")
1079
+ return () => source;
1080
+ if ("get" in source)
1081
+ return () => source.get();
1082
+ if ("prototype" in source)
1083
+ return () => useInContext(source);
1084
+ else
1085
+ return source;
1086
+ }, [])();
1087
+ if (typeof path == "function")
1088
+ return useFrom(instance, path, expect);
1089
+ const local = use(refresh => {
1090
+ const parent = control(instance);
1091
+ const sub = new Subscriber(parent, () => refresh);
1092
+ if (typeof path == "string") {
1093
+ const source = sub.proxy;
1094
+ defineProperty(sub, "proxy", {
1095
+ get() {
1096
+ const value = source[path];
1097
+ if (value === undefined && expect)
1098
+ throw suspend(parent, path);
1099
+ return value;
1100
+ }
1101
+ });
1102
+ }
1103
+ return sub;
1104
+ });
1105
+ React.useLayoutEffect(local.commit, []);
1106
+ return local.proxy;
1107
+ }
1108
+
1109
+ /* istanbul ignore file */
1110
+ class MVC extends Model {
1111
+ tap(path, expect) {
1112
+ return useTap(this, path, expect);
1113
+ }
1114
+ use(arg, callback) {
1115
+ return useModel(this, arg, callback);
1116
+ }
1117
+ static get(required) {
1118
+ return useInContext(this, required);
1119
+ }
1120
+ static tap(key, expect) {
1121
+ return useTap(this, key, expect);
1122
+ }
1123
+ /**
1124
+ * **React Hook** - Spawn and maintain a controller from within a component.
1125
+ *
1126
+ * More efficient than `use()` if you don't need hook-based features.
1127
+ *
1128
+ * @param callback - Run after creation of instance.
1129
+ */
1130
+ static new(callback) {
1131
+ return useModel(this, [], callback);
1132
+ }
1133
+ static use(a, b) {
1134
+ const instance = useModel(this, a, b);
1135
+ usePeerContext(instance.get);
1136
+ return instance;
1137
+ }
1138
+ /**
1139
+ * @deprecated consider doing this manually - not worth minor efficiency gain over using.
1140
+ */
1141
+ static uses(apply, keys) {
1142
+ return this.use(instance => {
1143
+ instance.import(apply, keys);
1144
+ });
1145
+ }
1146
+ /**
1147
+ * @deprecated is now replaced by overload of `use` method.
1148
+ */
1149
+ static using(apply, keys) {
1150
+ return this.use(apply, keys);
1151
+ }
1152
+ static meta(path, expect) {
1153
+ return useTap(() => this, path, expect);
1154
+ }
1155
+ }
1156
+
1157
+ const Oops$2 = issues({
1158
+ BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
1159
+ });
1160
+ function Consumer(props) {
1161
+ const { get, has, children, of: Control } = props;
1162
+ if (typeof children == "function")
1163
+ return children(useTap(Control));
1164
+ const callback = has || get;
1165
+ if (typeof callback == "function")
1166
+ callback(useInContext(Control, !!has));
1167
+ else
1168
+ throw Oops$2.BadConsumerProps();
1169
+ return null;
1170
+ }
1171
+
1172
+ const Oops$1 = issues({
1173
+ NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`
1174
+ });
1175
+ function Provider(props) {
1176
+ const context = useNewContext(props.of);
1177
+ const render = props.children;
1178
+ useAppliedProps(context, props);
1179
+ React.useLayoutEffect(() => () => context.pop(), []);
1180
+ return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1181
+ ? React.createElement(RenderFunction, { context, render })
1182
+ : render);
1183
+ }
1184
+ function useNewContext(inject) {
1185
+ const from = useLookup();
1186
+ return React.useMemo(() => {
1187
+ if (!inject)
1188
+ throw Oops$1.NoProviderType();
1189
+ const context = from.push(inject);
1190
+ for (const instance of context.local)
1191
+ if (instance)
1192
+ for (const apply of getPending(instance))
1193
+ apply(context);
1194
+ return context;
1195
+ }, []);
1196
+ }
1197
+ function useAppliedProps(within, props) {
1198
+ const update = React.useMemo(() => {
1199
+ const targets = within.local;
1200
+ return function integrate(props) {
1201
+ for (const [key, value] of entries(props))
1202
+ if (key != "of" && key != "children")
1203
+ for (const into of targets)
1204
+ if (into && key in into)
1205
+ into[key] = value;
1206
+ };
1207
+ }, []);
1208
+ update(props);
1209
+ }
1210
+ function RenderFunction(props) {
1211
+ const hook = use(refresh => {
1212
+ const targets = props.context.local;
1213
+ if (targets.length == 1)
1214
+ return new Subscriber(targets[0], () => refresh);
1215
+ return {};
1216
+ });
1217
+ if (hook.commit)
1218
+ React.useLayoutEffect(hook.commit, []);
1219
+ return props.render(hook.proxy);
1220
+ }
1221
+
1222
+ const Oops = issues({
1223
+ 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.`,
1224
+ GlobalExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1225
+ });
1226
+ const Active = new WeakMap();
1227
+ class Global extends MVC {
1228
+ static create(...args) {
1229
+ const Type = this;
1230
+ if (Active.has(Type))
1231
+ throw Oops.GlobalExists(this.name);
1232
+ const instance = super.create(...args);
1233
+ Active.set(Type, instance);
1234
+ return instance;
1235
+ }
1236
+ /**
1237
+ * Update the active instance of this class.
1238
+ * Returns a thenable; resolves after successful update.
1239
+ * If instance does not already exist, one will be created.
1240
+ */
1241
+ static set(updates) {
1242
+ const instance = Active.get(this) || this.create();
1243
+ instance.import(updates);
1244
+ return instance.update();
1245
+ }
1246
+ static get(arg) {
1247
+ const instance = Active.get(this);
1248
+ if (!instance && arg !== false)
1249
+ throw Oops.GlobalDoesNotExist(this.name);
1250
+ return (typeof arg == "string" ?
1251
+ instance[arg] :
1252
+ instance);
1253
+ }
1254
+ /** Destroy current instance of Global, if it exists. */
1255
+ static reset() {
1256
+ const current = Active.get(this);
1257
+ if (current)
1258
+ current.destroy();
1259
+ }
1260
+ destroy() {
1261
+ super.destroy();
1262
+ Active.delete(this.constructor);
1263
+ }
1264
+ }
1265
+
1266
+ 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 };
1267
+ //# sourceMappingURL=index.esm.js.map