@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.js CHANGED
@@ -25,7 +25,7 @@ function issues(register) {
25
25
  return Library;
26
26
  }
27
27
 
28
- const Oops$d = issues({
28
+ const Oops$c = issues({
29
29
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
30
30
  });
31
31
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -54,7 +54,7 @@ function createEffect(callback) {
54
54
  if (unSet instanceof Promise)
55
55
  unSet = undefined;
56
56
  if (unSet && typeof unSet !== "function")
57
- throw Oops$d.BadEffectCallback();
57
+ throw Oops$c.BadEffectCallback();
58
58
  };
59
59
  }
60
60
  function createValueEffect(callback) {
@@ -70,8 +70,28 @@ function createValueEffect(callback) {
70
70
  if (typeof out == "function")
71
71
  unSet = out;
72
72
  else
73
- throw Oops$d.BadEffectCallback();
73
+ throw Oops$c.BadEffectCallback();
74
74
  };
75
+ }
76
+ function suspenseBoundary(fn) {
77
+ const retry = (err) => {
78
+ if (err instanceof Promise)
79
+ return err.then(compute);
80
+ else
81
+ throw err;
82
+ };
83
+ const compute = () => {
84
+ try {
85
+ const output = fn();
86
+ return output instanceof Promise
87
+ ? output.catch(retry)
88
+ : output;
89
+ }
90
+ catch (err) {
91
+ return retry(err);
92
+ }
93
+ };
94
+ return compute();
75
95
  }
76
96
 
77
97
  const UPDATE = new WeakMap();
@@ -84,7 +104,274 @@ function applyUpdate(subject, keys) {
84
104
  };
85
105
  }
86
106
 
87
- const Oops$c = issues({
107
+ class Subscriber {
108
+ constructor(target, onUpdate) {
109
+ this.onUpdate = onUpdate;
110
+ this.active = false;
111
+ this.dependant = new Set();
112
+ this.watch = {};
113
+ const parent = target instanceof Controller
114
+ ? target : control(target);
115
+ const proxy = create(parent.proxy);
116
+ let reset;
117
+ define(proxy, LOCAL, this);
118
+ defineProperty(this, "proxy", {
119
+ configurable: true,
120
+ get() {
121
+ if (reset)
122
+ setTimeout(reset, 0);
123
+ return proxy;
124
+ }
125
+ });
126
+ const release = parent.addListener(key => {
127
+ const handler = this.watch[key];
128
+ if (!handler || !this.active)
129
+ return;
130
+ if (typeof handler == "function")
131
+ handler();
132
+ const notify = this.onUpdate(key, parent);
133
+ const getWhy = (keys) => {
134
+ reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
135
+ };
136
+ if (notify) {
137
+ parent.requestUpdate(getWhy);
138
+ parent.requestUpdate(notify);
139
+ }
140
+ });
141
+ this.commit = () => {
142
+ this.active = true;
143
+ this.dependant.forEach(x => x.commit());
144
+ return this.release;
145
+ };
146
+ this.release = () => {
147
+ this.dependant.forEach(x => x.release());
148
+ release();
149
+ };
150
+ }
151
+ }
152
+
153
+ const Oops$b = issues({
154
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
155
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
156
+ });
157
+ /**
158
+ * Get value, suspend instead if undefined.
159
+ *
160
+ * Throws suspense promise also interpretable as an error.
161
+ * React could handle it but other contexts probably not.
162
+ */
163
+ function pendingValue(via, key) {
164
+ const value = via.state[key];
165
+ if (value === undefined)
166
+ throw suspend(via, key);
167
+ return value;
168
+ }
169
+ function pendingFactory(parent, key, fn, immediate) {
170
+ const { subject, state } = parent;
171
+ let pending;
172
+ let error;
173
+ const init = () => {
174
+ const output = suspenseBoundary(() => {
175
+ return fn.call(subject, key, subject);
176
+ });
177
+ if (output instanceof Promise) {
178
+ pending = output
179
+ .catch(err => error = err)
180
+ .then(val => state[key] = val)
181
+ .finally(() => {
182
+ pending = undefined;
183
+ parent.update(key);
184
+ });
185
+ const issue = Oops$b.ValueNotReady(subject, key);
186
+ Object.assign(pending, {
187
+ message: issue.message,
188
+ stack: issue.stack
189
+ });
190
+ }
191
+ return state[key] = output;
192
+ };
193
+ if (immediate)
194
+ try {
195
+ init();
196
+ }
197
+ catch (err) {
198
+ Oops$b.FactoryFailed(subject, key).warn();
199
+ throw err;
200
+ }
201
+ return () => {
202
+ if (pending)
203
+ throw pending;
204
+ if (error)
205
+ throw error;
206
+ if (key in state)
207
+ return state[key];
208
+ let output = init();
209
+ if (pending)
210
+ throw pending;
211
+ else
212
+ return output;
213
+ };
214
+ }
215
+ function suspend(source, key) {
216
+ const error = Oops$b.ValueNotReady(source.subject, key);
217
+ const promise = new Promise(resolve => {
218
+ const release = source.addListener(forKey => {
219
+ if (forKey == key)
220
+ return () => {
221
+ if (source.state[key] !== undefined) {
222
+ release();
223
+ resolve();
224
+ }
225
+ };
226
+ });
227
+ });
228
+ return Object.assign(promise, {
229
+ toString: () => String(error),
230
+ name: "Suspense",
231
+ message: error.message,
232
+ stack: error.stack
233
+ });
234
+ }
235
+
236
+ const Oops$a = issues({
237
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
238
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
239
+ ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
240
+ ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
241
+ });
242
+ const INIT = new WeakSet();
243
+ const INFO = new WeakMap();
244
+ const USED = new WeakMap();
245
+ const KEYS = new WeakMap();
246
+ function from(source, arg1, arg2) {
247
+ return apply(function from(key) {
248
+ const parent = this;
249
+ const { subject, state } = this;
250
+ let getSource;
251
+ let getter;
252
+ if (arg2 === true || arg1 === true)
253
+ getter = pendingValue;
254
+ if (typeof arg1 == "boolean")
255
+ arg1 = undefined;
256
+ // Easy mistake, using a peer, will always be unresolved.
257
+ if (typeof source == "symbol")
258
+ throw Oops$a.PeerNotAllowed(subject, key);
259
+ // replace source controller in-case it is different
260
+ if (typeof source == "object")
261
+ getSource = () => control(source);
262
+ // specifically an arrow function (getter factory)
263
+ else if (!source.prototype) {
264
+ arg1 = source.call(subject, key);
265
+ getSource = () => this;
266
+ }
267
+ // Regular function is too ambiguous so not allowed.
268
+ else
269
+ throw Oops$a.BadComputedSource(subject, key, source);
270
+ let sub;
271
+ const setter = arg1;
272
+ const info = { key, parent, priority: 1 };
273
+ let register = USED.get(parent);
274
+ if (!register) {
275
+ register = new Map();
276
+ USED.set(parent, register);
277
+ }
278
+ register.set(key, info);
279
+ function compute(initial) {
280
+ try {
281
+ return setter.call(sub.proxy, sub.proxy);
282
+ }
283
+ catch (err) {
284
+ Oops$a.ComputeFailed(subject, key, !!initial).warn();
285
+ throw err;
286
+ }
287
+ }
288
+ function update() {
289
+ let value;
290
+ try {
291
+ value = compute(false);
292
+ }
293
+ catch (e) {
294
+ console.error(e);
295
+ }
296
+ finally {
297
+ if (state[key] !== value) {
298
+ parent.update(key, value);
299
+ return value;
300
+ }
301
+ }
302
+ }
303
+ function defer(_key, from) {
304
+ let pending = KEYS.get(from);
305
+ if (!pending)
306
+ KEYS.set(from, pending = []);
307
+ if (info.parent !== from)
308
+ update();
309
+ else {
310
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
311
+ pending.splice(after + 1, 0, update);
312
+ }
313
+ }
314
+ function create(early) {
315
+ sub = new Subscriber(getSource(), defer);
316
+ defineProperty(state, key, {
317
+ value: undefined,
318
+ writable: true
319
+ });
320
+ try {
321
+ return state[key] = compute(true);
322
+ }
323
+ catch (e) {
324
+ if (early)
325
+ Oops$a.ComputedEarly(key).warn();
326
+ throw e;
327
+ }
328
+ finally {
329
+ sub.commit();
330
+ for (const key in sub.watch) {
331
+ const peer = register.get(key);
332
+ if (peer && peer.priority >= info.priority)
333
+ info.priority = peer.priority + 1;
334
+ }
335
+ }
336
+ }
337
+ setAlias(update, `try ${key}`);
338
+ setAlias(create, `new ${key}`);
339
+ setAlias(setter, `run ${key}`);
340
+ INIT.add(create);
341
+ INFO.set(update, info);
342
+ defineProperty(state, key, {
343
+ get: create,
344
+ configurable: true,
345
+ enumerable: true
346
+ });
347
+ if (getter)
348
+ return () => getter(parent, key);
349
+ });
350
+ }
351
+ function ensure(on, keys) {
352
+ for (const key of keys) {
353
+ const desc = getOwnPropertyDescriptor(on.state, key);
354
+ const getter = desc && desc.get;
355
+ if (INIT.has(getter))
356
+ getter(true);
357
+ }
358
+ }
359
+ function flush(on) {
360
+ const handled = on.frame;
361
+ const pending = KEYS.get(on);
362
+ if (!pending)
363
+ return;
364
+ while (pending.length) {
365
+ const why = [...handled];
366
+ const compute = pending.shift();
367
+ const { key } = INFO.get(compute);
368
+ if (!handled.has(key))
369
+ compute(why);
370
+ }
371
+ KEYS.delete(on);
372
+ }
373
+
374
+ const Oops$9 = issues({
88
375
  StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
89
376
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
90
377
  });
@@ -152,9 +439,7 @@ class Controller {
152
439
  : () => state[key]
153
440
  });
154
441
  defineProperty(proxy, key, {
155
- enumerable,
156
- set,
157
- get
442
+ enumerable, get, set
158
443
  });
159
444
  }
160
445
  ref(key, handler) {
@@ -215,7 +500,7 @@ class Controller {
215
500
  return;
216
501
  }
217
502
  if (typeof arg == "boolean" && arg !== this.frame.size > 0)
218
- return Promise.reject(Oops$c.StrictUpdate(arg));
503
+ return Promise.reject(Oops$9.StrictUpdate(arg));
219
504
  return {
220
505
  then: (callback) => {
221
506
  if (callback)
@@ -224,7 +509,7 @@ class Controller {
224
509
  else
225
510
  callback(false);
226
511
  else
227
- throw Oops$c.NoChaining();
512
+ throw Oops$9.NoChaining();
228
513
  }
229
514
  };
230
515
  }
@@ -253,164 +538,6 @@ function control(subject, cb) {
253
538
  return cb(control);
254
539
  }
255
540
 
256
- class Subscriber {
257
- constructor(target, onUpdate) {
258
- this.onUpdate = onUpdate;
259
- this.active = false;
260
- this.dependant = new Set();
261
- this.watch = {};
262
- const parent = target instanceof Controller
263
- ? target : control(target);
264
- const proxy = create(parent.proxy);
265
- let reset;
266
- define(proxy, LOCAL, this);
267
- defineProperty(this, "proxy", {
268
- configurable: true,
269
- get() {
270
- if (reset)
271
- setTimeout(reset, 0);
272
- return proxy;
273
- }
274
- });
275
- const release = parent.addListener(key => {
276
- const handler = this.watch[key];
277
- if (!handler || !this.active)
278
- return;
279
- if (typeof handler == "function")
280
- handler();
281
- const notify = this.onUpdate(key, parent);
282
- const getWhy = (keys) => {
283
- reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
284
- };
285
- if (notify) {
286
- parent.requestUpdate(getWhy);
287
- parent.requestUpdate(notify);
288
- }
289
- });
290
- this.commit = () => {
291
- this.active = true;
292
- this.dependant.forEach(x => x.commit());
293
- return this.release;
294
- };
295
- this.release = () => {
296
- this.dependant.forEach(x => x.release());
297
- release();
298
- };
299
- }
300
- }
301
-
302
- const Oops$b = issues({
303
- ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
304
- ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
305
- });
306
- const INIT = new WeakSet();
307
- const INFO = new WeakMap();
308
- const USED = new WeakMap();
309
- const KEYS = new WeakMap();
310
- function prepare(parent, key, source, setter, getter) {
311
- let sub;
312
- const { state, subject } = parent;
313
- const info = { key, parent, priority: 1 };
314
- let register = USED.get(parent);
315
- if (!register) {
316
- register = new Map();
317
- USED.set(parent, register);
318
- }
319
- register.set(key, info);
320
- function compute(initial) {
321
- try {
322
- return setter.call(sub.proxy, sub.proxy);
323
- }
324
- catch (err) {
325
- Oops$b.ComputeFailed(subject, key, !!initial).warn();
326
- throw err;
327
- }
328
- }
329
- function update() {
330
- let value;
331
- try {
332
- value = compute(false);
333
- }
334
- catch (e) {
335
- console.error(e);
336
- }
337
- finally {
338
- if (state[key] !== value) {
339
- parent.update(key, value);
340
- return value;
341
- }
342
- }
343
- }
344
- function defer(_key, from) {
345
- let pending = KEYS.get(from);
346
- if (!pending)
347
- KEYS.set(from, pending = []);
348
- if (info.parent !== from)
349
- update();
350
- else {
351
- const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
352
- pending.splice(after + 1, 0, update);
353
- }
354
- }
355
- function create(early) {
356
- sub = new Subscriber(source(), defer);
357
- defineProperty(state, key, {
358
- value: undefined,
359
- writable: true
360
- });
361
- try {
362
- return state[key] = compute(true);
363
- }
364
- catch (e) {
365
- if (early)
366
- Oops$b.ComputedEarly(key).warn();
367
- throw e;
368
- }
369
- finally {
370
- sub.commit();
371
- for (const key in sub.watch) {
372
- const peer = register.get(key);
373
- if (peer && peer.priority >= info.priority)
374
- info.priority = peer.priority + 1;
375
- }
376
- }
377
- }
378
- setAlias(update, `try ${key}`);
379
- setAlias(create, `new ${key}`);
380
- setAlias(setter, `run ${key}`);
381
- INIT.add(create);
382
- INFO.set(update, info);
383
- defineProperty(state, key, {
384
- get: create,
385
- configurable: true,
386
- enumerable: true
387
- });
388
- if (getter)
389
- return () => getter(parent, key);
390
- }
391
- function ensure(on, keys) {
392
- for (const key of keys) {
393
- const desc = getOwnPropertyDescriptor(on.state, key);
394
- const getter = desc && desc.get;
395
- if (INIT.has(getter))
396
- getter(true);
397
- }
398
- }
399
- function flush(on) {
400
- const handled = on.frame;
401
- const pending = KEYS.get(on);
402
- if (!pending)
403
- return;
404
- while (pending.length) {
405
- const why = [...handled];
406
- const compute = pending.shift();
407
- const { key } = INFO.get(compute);
408
- if (!handled.has(key))
409
- compute(why);
410
- }
411
- KEYS.delete(on);
412
- }
413
-
414
541
  const CONTROL = Symbol("CONTROL");
415
542
  const WHY = Symbol("UPDATE");
416
543
  const LOCAL = Symbol("LOCAL");
@@ -607,7 +734,7 @@ function recursive(source, key) {
607
734
  };
608
735
  }
609
736
 
610
- const Oops$a = issues({
737
+ const Oops$8 = issues({
611
738
  DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
612
739
  });
613
740
  function act(task) {
@@ -615,7 +742,7 @@ function act(task) {
615
742
  let pending = false;
616
743
  const invoke = (...args) => {
617
744
  if (pending)
618
- return Promise.reject(Oops$a.DuplicateAction(key));
745
+ return Promise.reject(Oops$8.DuplicateAction(key));
619
746
  pending = true;
620
747
  this.update(key);
621
748
  return new Promise(res => {
@@ -637,107 +764,6 @@ function act(task) {
637
764
  });
638
765
  }
639
766
 
640
- const Oops$9 = issues({
641
- ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
642
- });
643
- /**
644
- * Get value, suspend instead if undefined.
645
- *
646
- * Throws suspense promise also interpretable as an error.
647
- * React could handle it but other contexts probably not.
648
- */
649
- function pendingValue(via, key) {
650
- const value = via.state[key];
651
- if (value === undefined)
652
- throw suspend(via, key);
653
- return value;
654
- }
655
- function pendingFactory(via, key, fn) {
656
- const { subject, state } = via;
657
- let waiting;
658
- let error;
659
- return () => {
660
- if (waiting)
661
- throw waiting;
662
- if (error)
663
- throw error;
664
- if (key in state)
665
- return state[key];
666
- let output;
667
- try {
668
- output = fn.call(subject, key, subject);
669
- }
670
- catch (err) {
671
- throw error = err;
672
- }
673
- if (output instanceof Promise) {
674
- const issue = Oops$9.ValueNotReady(subject, key);
675
- output
676
- .catch(err => error = err)
677
- .then(out => state[key] = out)
678
- .finally(() => waiting = undefined);
679
- throw waiting = Object.assign(output, {
680
- message: issue.message,
681
- stack: issue.stack
682
- });
683
- }
684
- else {
685
- return state[key] = output;
686
- }
687
- };
688
- }
689
- function suspend(source, key) {
690
- const error = Oops$9.ValueNotReady(source.subject, key);
691
- const promise = new Promise(resolve => {
692
- const release = source.addListener(forKey => {
693
- if (forKey == key)
694
- return () => {
695
- if (source.state[key] !== undefined) {
696
- release();
697
- resolve();
698
- }
699
- };
700
- });
701
- });
702
- return Object.assign(promise, {
703
- toString: () => String(error),
704
- name: "Suspense",
705
- message: error.message,
706
- stack: error.stack
707
- });
708
- }
709
-
710
- const Oops$8 = issues({
711
- BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
712
- PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
713
- });
714
- function from(source, setter, arg) {
715
- return apply(function from(key) {
716
- const { subject } = this;
717
- let getSource;
718
- let getter;
719
- if (arg === true || setter === true)
720
- getter = pendingValue;
721
- if (typeof setter == "boolean")
722
- setter = undefined;
723
- // Easy mistake, using a peer, will always be unresolved.
724
- if (typeof source == "symbol")
725
- throw Oops$8.PeerNotAllowed(subject, key);
726
- // replace source controller in-case it is different
727
- if (typeof source == "object")
728
- getSource = () => control(source);
729
- // specifically an arrow function (getter factory)
730
- else if (!source.prototype) {
731
- setter = source.call(subject, key);
732
- getSource = () => this;
733
- }
734
- // Regular function is too ambiguous so not allowed.
735
- else
736
- throw Oops$8.BadComputedSource(subject, key, source);
737
- return prepare(this, key, getSource, setter, getter);
738
- });
739
- }
740
-
741
767
  function ref(arg) {
742
768
  return apply(function ref(key) {
743
769
  let value = {};
@@ -762,8 +788,7 @@ function createRef(key, cb) {
762
788
 
763
789
  const Oops$7 = issues({
764
790
  NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
765
- BadFactory: () => `Set instruction can only accept a factory or undefined.`,
766
- FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
791
+ BadFactory: () => `Set instruction can only accept a factory or undefined.`
767
792
  });
768
793
  function set(factory, argument) {
769
794
  return apply(function set(key) {
@@ -775,19 +800,7 @@ function set(factory, argument) {
775
800
  else if (typeof factory !== "function")
776
801
  throw Oops$7.BadFactory();
777
802
  else {
778
- get = pendingFactory(this, key, factory);
779
- if (required)
780
- try {
781
- get();
782
- }
783
- catch (err) {
784
- if (err instanceof Promise)
785
- ;
786
- else {
787
- Oops$7.FactoryFailed(this.subject, key).warn();
788
- throw err;
789
- }
790
- }
803
+ get = pendingFactory(this, key, factory, required);
791
804
  }
792
805
  if (typeof argument == "function")
793
806
  set = createValueEffect(argument);
@@ -1128,15 +1141,21 @@ class MVC extends Model {
1128
1141
  static tap(key, expect) {
1129
1142
  return useTap(this, key, expect);
1130
1143
  }
1131
- /**
1132
- * **React Hook** - Spawn and maintain a controller from within a component.
1133
- *
1134
- * More efficient than `use()` if you don't need hook-based features.
1135
- *
1136
- * @param callback - Run after creation of instance.
1137
- */
1138
- static new(callback) {
1139
- return useModel(this, [], callback);
1144
+ static new(arg) {
1145
+ const instance = React__default["default"].useMemo(() => {
1146
+ const instance = new this();
1147
+ control(instance);
1148
+ if (typeof arg == "function")
1149
+ arg(instance);
1150
+ else if (arg)
1151
+ getOwnPropertyNames(instance).forEach(key => {
1152
+ if (key in arg)
1153
+ instance[key] = arg[key];
1154
+ });
1155
+ return instance;
1156
+ }, []);
1157
+ usePeerContext(instance.get);
1158
+ return instance;
1140
1159
  }
1141
1160
  static use(a, b) {
1142
1161
  const instance = useModel(this, a, b);