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

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
@@ -17,7 +17,7 @@ function issues(register) {
17
17
  return Library;
18
18
  }
19
19
 
20
- const Oops$d = issues({
20
+ const Oops$c = issues({
21
21
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
22
22
  });
23
23
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -46,7 +46,7 @@ function createEffect(callback) {
46
46
  if (unSet instanceof Promise)
47
47
  unSet = undefined;
48
48
  if (unSet && typeof unSet !== "function")
49
- throw Oops$d.BadEffectCallback();
49
+ throw Oops$c.BadEffectCallback();
50
50
  };
51
51
  }
52
52
  function createValueEffect(callback) {
@@ -62,8 +62,28 @@ function createValueEffect(callback) {
62
62
  if (typeof out == "function")
63
63
  unSet = out;
64
64
  else
65
- throw Oops$d.BadEffectCallback();
65
+ throw Oops$c.BadEffectCallback();
66
66
  };
67
+ }
68
+ function suspenseBoundary(fn) {
69
+ const retry = (err) => {
70
+ if (err instanceof Promise)
71
+ return err.then(compute);
72
+ else
73
+ throw err;
74
+ };
75
+ const compute = () => {
76
+ try {
77
+ const output = fn();
78
+ return output instanceof Promise
79
+ ? output.catch(retry)
80
+ : output;
81
+ }
82
+ catch (err) {
83
+ return retry(err);
84
+ }
85
+ };
86
+ return compute();
67
87
  }
68
88
 
69
89
  const UPDATE = new WeakMap();
@@ -76,7 +96,274 @@ function applyUpdate(subject, keys) {
76
96
  };
77
97
  }
78
98
 
79
- const Oops$c = issues({
99
+ class Subscriber {
100
+ constructor(target, onUpdate) {
101
+ this.onUpdate = onUpdate;
102
+ this.active = false;
103
+ this.dependant = new Set();
104
+ this.watch = {};
105
+ const parent = target instanceof Controller
106
+ ? target : control(target);
107
+ const proxy = create(parent.proxy);
108
+ let reset;
109
+ define(proxy, LOCAL, this);
110
+ defineProperty(this, "proxy", {
111
+ configurable: true,
112
+ get() {
113
+ if (reset)
114
+ setTimeout(reset, 0);
115
+ return proxy;
116
+ }
117
+ });
118
+ const release = parent.addListener(key => {
119
+ const handler = this.watch[key];
120
+ if (!handler || !this.active)
121
+ return;
122
+ if (typeof handler == "function")
123
+ handler();
124
+ const notify = this.onUpdate(key, parent);
125
+ const getWhy = (keys) => {
126
+ reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
127
+ };
128
+ if (notify) {
129
+ parent.requestUpdate(getWhy);
130
+ parent.requestUpdate(notify);
131
+ }
132
+ });
133
+ this.commit = () => {
134
+ this.active = true;
135
+ this.dependant.forEach(x => x.commit());
136
+ return this.release;
137
+ };
138
+ this.release = () => {
139
+ this.dependant.forEach(x => x.release());
140
+ release();
141
+ };
142
+ }
143
+ }
144
+
145
+ const Oops$b = issues({
146
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
147
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
148
+ });
149
+ /**
150
+ * Get value, suspend instead if undefined.
151
+ *
152
+ * Throws suspense promise also interpretable as an error.
153
+ * React could handle it but other contexts probably not.
154
+ */
155
+ function pendingValue(via, key) {
156
+ const value = via.state[key];
157
+ if (value === undefined)
158
+ throw suspend(via, key);
159
+ return value;
160
+ }
161
+ function pendingFactory(parent, key, fn, immediate) {
162
+ const { subject, state } = parent;
163
+ let pending;
164
+ let error;
165
+ const init = () => {
166
+ const output = suspenseBoundary(() => {
167
+ return fn.call(subject, key, subject);
168
+ });
169
+ if (output instanceof Promise) {
170
+ pending = output
171
+ .catch(err => error = err)
172
+ .then(val => state[key] = val)
173
+ .finally(() => {
174
+ pending = undefined;
175
+ parent.update(key);
176
+ });
177
+ const issue = Oops$b.ValueNotReady(subject, key);
178
+ Object.assign(pending, {
179
+ message: issue.message,
180
+ stack: issue.stack
181
+ });
182
+ }
183
+ return state[key] = output;
184
+ };
185
+ if (immediate)
186
+ try {
187
+ init();
188
+ }
189
+ catch (err) {
190
+ Oops$b.FactoryFailed(subject, key).warn();
191
+ throw err;
192
+ }
193
+ return () => {
194
+ if (pending)
195
+ throw pending;
196
+ if (error)
197
+ throw error;
198
+ if (key in state)
199
+ return state[key];
200
+ let output = init();
201
+ if (pending)
202
+ throw pending;
203
+ else
204
+ return output;
205
+ };
206
+ }
207
+ function suspend(source, key) {
208
+ const error = Oops$b.ValueNotReady(source.subject, key);
209
+ const promise = new Promise(resolve => {
210
+ const release = source.addListener(forKey => {
211
+ if (forKey == key)
212
+ return () => {
213
+ if (source.state[key] !== undefined) {
214
+ release();
215
+ resolve();
216
+ }
217
+ };
218
+ });
219
+ });
220
+ return Object.assign(promise, {
221
+ toString: () => String(error),
222
+ name: "Suspense",
223
+ message: error.message,
224
+ stack: error.stack
225
+ });
226
+ }
227
+
228
+ const Oops$a = issues({
229
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
230
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
231
+ ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
232
+ ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
233
+ });
234
+ const INIT = new WeakSet();
235
+ const INFO = new WeakMap();
236
+ const USED = new WeakMap();
237
+ const KEYS = new WeakMap();
238
+ function from(source, arg1, arg2) {
239
+ return apply(function from(key) {
240
+ const parent = this;
241
+ const { subject, state } = this;
242
+ let getSource;
243
+ let getter;
244
+ if (arg2 === true || arg1 === true)
245
+ getter = pendingValue;
246
+ if (typeof arg1 == "boolean")
247
+ arg1 = undefined;
248
+ // Easy mistake, using a peer, will always be unresolved.
249
+ if (typeof source == "symbol")
250
+ throw Oops$a.PeerNotAllowed(subject, key);
251
+ // replace source controller in-case it is different
252
+ if (typeof source == "object")
253
+ getSource = () => control(source);
254
+ // specifically an arrow function (getter factory)
255
+ else if (!source.prototype) {
256
+ arg1 = source.call(subject, key);
257
+ getSource = () => this;
258
+ }
259
+ // Regular function is too ambiguous so not allowed.
260
+ else
261
+ throw Oops$a.BadComputedSource(subject, key, source);
262
+ let sub;
263
+ const setter = arg1;
264
+ const info = { key, parent, priority: 1 };
265
+ let register = USED.get(parent);
266
+ if (!register) {
267
+ register = new Map();
268
+ USED.set(parent, register);
269
+ }
270
+ register.set(key, info);
271
+ function compute(initial) {
272
+ try {
273
+ return setter.call(sub.proxy, sub.proxy);
274
+ }
275
+ catch (err) {
276
+ Oops$a.ComputeFailed(subject, key, !!initial).warn();
277
+ throw err;
278
+ }
279
+ }
280
+ function update() {
281
+ let value;
282
+ try {
283
+ value = compute(false);
284
+ }
285
+ catch (e) {
286
+ console.error(e);
287
+ }
288
+ finally {
289
+ if (state[key] !== value) {
290
+ parent.update(key, value);
291
+ return value;
292
+ }
293
+ }
294
+ }
295
+ function defer(_key, from) {
296
+ let pending = KEYS.get(from);
297
+ if (!pending)
298
+ KEYS.set(from, pending = []);
299
+ if (info.parent !== from)
300
+ update();
301
+ else {
302
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
303
+ pending.splice(after + 1, 0, update);
304
+ }
305
+ }
306
+ function create(early) {
307
+ sub = new Subscriber(getSource(), defer);
308
+ defineProperty(state, key, {
309
+ value: undefined,
310
+ writable: true
311
+ });
312
+ try {
313
+ return state[key] = compute(true);
314
+ }
315
+ catch (e) {
316
+ if (early)
317
+ Oops$a.ComputedEarly(key).warn();
318
+ throw e;
319
+ }
320
+ finally {
321
+ sub.commit();
322
+ for (const key in sub.watch) {
323
+ const peer = register.get(key);
324
+ if (peer && peer.priority >= info.priority)
325
+ info.priority = peer.priority + 1;
326
+ }
327
+ }
328
+ }
329
+ setAlias(update, `try ${key}`);
330
+ setAlias(create, `new ${key}`);
331
+ setAlias(setter, `run ${key}`);
332
+ INIT.add(create);
333
+ INFO.set(update, info);
334
+ defineProperty(state, key, {
335
+ get: create,
336
+ configurable: true,
337
+ enumerable: true
338
+ });
339
+ if (getter)
340
+ return () => getter(parent, key);
341
+ });
342
+ }
343
+ function ensure(on, keys) {
344
+ for (const key of keys) {
345
+ const desc = getOwnPropertyDescriptor(on.state, key);
346
+ const getter = desc && desc.get;
347
+ if (INIT.has(getter))
348
+ getter(true);
349
+ }
350
+ }
351
+ function flush(on) {
352
+ const handled = on.frame;
353
+ const pending = KEYS.get(on);
354
+ if (!pending)
355
+ return;
356
+ while (pending.length) {
357
+ const why = [...handled];
358
+ const compute = pending.shift();
359
+ const { key } = INFO.get(compute);
360
+ if (!handled.has(key))
361
+ compute(why);
362
+ }
363
+ KEYS.delete(on);
364
+ }
365
+
366
+ const Oops$9 = issues({
80
367
  StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
81
368
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
82
369
  });
@@ -144,9 +431,7 @@ class Controller {
144
431
  : () => state[key]
145
432
  });
146
433
  defineProperty(proxy, key, {
147
- enumerable,
148
- set,
149
- get
434
+ enumerable, get, set
150
435
  });
151
436
  }
152
437
  ref(key, handler) {
@@ -207,7 +492,7 @@ class Controller {
207
492
  return;
208
493
  }
209
494
  if (typeof arg == "boolean" && arg !== this.frame.size > 0)
210
- return Promise.reject(Oops$c.StrictUpdate(arg));
495
+ return Promise.reject(Oops$9.StrictUpdate(arg));
211
496
  return {
212
497
  then: (callback) => {
213
498
  if (callback)
@@ -216,7 +501,7 @@ class Controller {
216
501
  else
217
502
  callback(false);
218
503
  else
219
- throw Oops$c.NoChaining();
504
+ throw Oops$9.NoChaining();
220
505
  }
221
506
  };
222
507
  }
@@ -245,164 +530,6 @@ function control(subject, cb) {
245
530
  return cb(control);
246
531
  }
247
532
 
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
533
  const CONTROL = Symbol("CONTROL");
407
534
  const WHY = Symbol("UPDATE");
408
535
  const LOCAL = Symbol("LOCAL");
@@ -599,7 +726,7 @@ function recursive(source, key) {
599
726
  };
600
727
  }
601
728
 
602
- const Oops$a = issues({
729
+ const Oops$8 = issues({
603
730
  DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
604
731
  });
605
732
  function act(task) {
@@ -607,7 +734,7 @@ function act(task) {
607
734
  let pending = false;
608
735
  const invoke = (...args) => {
609
736
  if (pending)
610
- return Promise.reject(Oops$a.DuplicateAction(key));
737
+ return Promise.reject(Oops$8.DuplicateAction(key));
611
738
  pending = true;
612
739
  this.update(key);
613
740
  return new Promise(res => {
@@ -629,107 +756,6 @@ function act(task) {
629
756
  });
630
757
  }
631
758
 
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
759
  function ref(arg) {
734
760
  return apply(function ref(key) {
735
761
  let value = {};
@@ -754,8 +780,7 @@ function createRef(key, cb) {
754
780
 
755
781
  const Oops$7 = issues({
756
782
  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.`
783
+ BadFactory: () => `Set instruction can only accept a factory or undefined.`
759
784
  });
760
785
  function set(factory, argument) {
761
786
  return apply(function set(key) {
@@ -767,19 +792,7 @@ function set(factory, argument) {
767
792
  else if (typeof factory !== "function")
768
793
  throw Oops$7.BadFactory();
769
794
  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
- }
795
+ get = pendingFactory(this, key, factory, required);
783
796
  }
784
797
  if (typeof argument == "function")
785
798
  set = createValueEffect(argument);
@@ -1120,15 +1133,21 @@ class MVC extends Model {
1120
1133
  static tap(key, expect) {
1121
1134
  return useTap(this, key, expect);
1122
1135
  }
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);
1136
+ static new(arg) {
1137
+ const instance = React.useMemo(() => {
1138
+ const instance = new this();
1139
+ control(instance);
1140
+ if (typeof arg == "function")
1141
+ arg(instance);
1142
+ else if (arg)
1143
+ getOwnPropertyNames(instance).forEach(key => {
1144
+ if (key in arg)
1145
+ instance[key] = arg[key];
1146
+ });
1147
+ return instance;
1148
+ }, []);
1149
+ usePeerContext(instance.get);
1150
+ return instance;
1132
1151
  }
1133
1152
  static use(a, b) {
1134
1153
  const instance = useModel(this, a, b);