@expressive/mvc 0.0.3 → 0.1.1

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.d.ts CHANGED
@@ -75,6 +75,11 @@ declare namespace Model {
75
75
  export type Methods<T> = {
76
76
  [K in keyof T]: T[K] extends Ref ? never : T[K] extends Function ? K : never;
77
77
  }[keyof T];
78
+ export type Refs<T extends Model> = {
79
+ <K extends Field<T>>(key: K, value: T[K]): void;
80
+ } & {
81
+ [P in Field<T>]: (next: any) => void;
82
+ };
78
83
  /**
79
84
  * Subset of `keyof T` which are not methods or defined by base Model U.
80
85
  *
@@ -110,11 +115,12 @@ interface Model extends Stateful {
110
115
  */
111
116
  get: this;
112
117
  /**
113
- * Circular reference to `this` controller.
118
+ * Setter utility
114
119
  *
115
- * Shortcut is mainly to update values, while having destructured already.
120
+ * Can be used to set (and force-update) values.
121
+ * Shortcut to update values, contains refs for all controlled values.
116
122
  */
117
- set: this;
123
+ set: Model.Refs<this>;
118
124
  }
119
125
  declare class Model {
120
126
  static [CONTROL]: Controller;
@@ -186,7 +192,7 @@ declare class Controller<T extends Stateful = any> {
186
192
  constructor(subject: T);
187
193
  start(): void;
188
194
  stop(): void;
189
- private manage;
195
+ manage(key: Model.Field<T>, value: any): void;
190
196
  ref(key: Model.Field<T>, handler?: Controller.OnValue<T>): (value: any) => void;
191
197
  addListener(listener: Controller.OnEvent<T>): () => void;
192
198
  emit(keys: readonly Model.Event<T>[]): void;
@@ -575,7 +581,7 @@ declare function tap<T extends Class>(Type: T, required?: boolean): InstanceOf<T
575
581
  declare function useFrom<T extends Stateful, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
576
582
  declare function useFrom<T extends Stateful, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect?: boolean): R;
577
583
 
578
- declare function useInContext<T extends typeof Model>(Type: T, arg?: boolean | string): any;
584
+ declare function useLocal<T extends typeof Model>(Type: T, arg?: boolean | string): any;
579
585
 
580
586
  declare function useModel<T extends Class, I extends InstanceOf<T>>(source: T, watch: Model.Field<I>[], callback?: (instance: I) => void): I;
581
587
  declare function useModel<T extends Class, I extends InstanceOf<T>>(source: T, callback?: (instance: I) => void): I;
@@ -597,4 +603,4 @@ declare function useTap<T extends Stateful, K extends Model.Field<T>>(source: T
597
603
  declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
598
604
  declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect?: boolean): R;
599
605
 
600
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use, useFrom, useInContext, useModel, useTap };
606
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use, useFrom, useLocal, useModel, useTap };
package/lib/index.esm.js CHANGED
@@ -1,5 +1,30 @@
1
1
  import React, { useState, useMemo } from 'react';
2
2
 
3
+ /*! *****************************************************************************
4
+ Copyright (c) Microsoft Corporation.
5
+
6
+ Permission to use, copy, modify, and/or distribute this software for any
7
+ purpose with or without fee is hereby granted.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
16
+ ***************************************************************************** */
17
+
18
+ function __awaiter(thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ }
27
+
3
28
  class Issue extends Error {
4
29
  constructor() {
5
30
  super(...arguments);
@@ -18,6 +43,50 @@ function issues(register) {
18
43
  }
19
44
 
20
45
  const Oops$c = issues({
46
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
47
+ });
48
+ function suspend(source, key) {
49
+ const error = Oops$c.ValueNotReady(source.subject, key);
50
+ const promise = new Promise(resolve => {
51
+ const release = source.addListener(forKey => {
52
+ if (forKey == key)
53
+ return () => {
54
+ if (source.state[key] !== undefined) {
55
+ release();
56
+ resolve();
57
+ }
58
+ };
59
+ });
60
+ });
61
+ return Object.assign(promise, {
62
+ toString: () => String(error),
63
+ name: "Suspense",
64
+ message: error.message,
65
+ stack: error.stack
66
+ });
67
+ }
68
+ function mayRetry(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();
87
+ }
88
+
89
+ const Oops$b = issues({
21
90
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
22
91
  });
23
92
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -43,7 +112,7 @@ function createEffect(callback) {
43
112
  if (unSet instanceof Promise)
44
113
  unSet = undefined;
45
114
  if (unSet && typeof unSet !== "function")
46
- throw Oops$c.BadEffectCallback();
115
+ throw Oops$b.BadEffectCallback();
47
116
  };
48
117
  }
49
118
  function createValueEffect(callback) {
@@ -59,28 +128,8 @@ function createValueEffect(callback) {
59
128
  if (typeof out == "function")
60
129
  unSet = out;
61
130
  else
62
- throw Oops$c.BadEffectCallback();
63
- };
64
- }
65
- function suspenseBoundary(fn) {
66
- const retry = (err) => {
67
- if (err instanceof Promise)
68
- return err.then(compute);
69
- else
70
- throw err;
71
- };
72
- const compute = () => {
73
- try {
74
- const output = fn();
75
- return output instanceof Promise
76
- ? output.catch(retry)
77
- : output;
78
- }
79
- catch (err) {
80
- return retry(err);
81
- }
131
+ throw Oops$b.BadEffectCallback();
82
132
  };
83
- return compute();
84
133
  }
85
134
 
86
135
  const UPDATE = new WeakMap();
@@ -99,9 +148,18 @@ const LOCAL = Symbol("LOCAL");
99
148
  const STATE = Symbol("STATE");
100
149
  class Model {
101
150
  constructor() {
102
- define(this, CONTROL, new Controller(this));
151
+ const control = new Controller(this);
152
+ define(this, CONTROL, control);
103
153
  define(this, "get", this);
104
- define(this, "set", this);
154
+ defineLazy(this, "set", () => {
155
+ const assign = (key, value) => {
156
+ control.update(key, value);
157
+ };
158
+ ready(control);
159
+ for (const key of getOwnPropertyNames(control.proxy))
160
+ define(assign, key, control.ref(key));
161
+ return assign;
162
+ });
105
163
  }
106
164
  get [STATE]() {
107
165
  return this[CONTROL].state;
@@ -142,18 +200,25 @@ class Model {
142
200
  effect(callback, select) {
143
201
  const effect = createEffect(callback);
144
202
  return control(this, control => {
203
+ let busy = false;
204
+ let inject = this.get;
205
+ const invoke = () => {
206
+ if (busy)
207
+ return;
208
+ const output = mayRetry(() => {
209
+ effect.call(inject, inject);
210
+ });
211
+ if (output instanceof Promise) {
212
+ output.finally(() => busy = false);
213
+ busy = true;
214
+ }
215
+ };
145
216
  if (!select) {
146
217
  const sub = new Subscriber(control, () => invoke);
147
- const invoke = () => {
148
- const x = sub.proxy;
149
- effect.call(x, x);
150
- };
218
+ inject = sub.proxy;
151
219
  invoke();
152
220
  return sub.commit();
153
221
  }
154
- const invoke = () => {
155
- effect.call(this.get, this.get);
156
- };
157
222
  invoke();
158
223
  if (!select.length) {
159
224
  control.onDestroy.add(invoke);
@@ -231,10 +296,7 @@ class Model {
231
296
  return (typeof maybe == "function" &&
232
297
  maybe.prototype instanceof this);
233
298
  }
234
- }
235
- defineLazy(Model, CONTROL, function () {
236
- return new Controller(this);
237
- });
299
+ }
238
300
 
239
301
  class Subscriber {
240
302
  constructor(target, onUpdate) {
@@ -282,30 +344,6 @@ class Subscriber {
282
344
  }
283
345
  }
284
346
 
285
- const Oops$b = issues({
286
- ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
287
- });
288
- function suspend(source, key) {
289
- const error = Oops$b.ValueNotReady(source.subject, key);
290
- const promise = new Promise(resolve => {
291
- const release = source.addListener(forKey => {
292
- if (forKey == key)
293
- return () => {
294
- if (source.state[key] !== undefined) {
295
- release();
296
- resolve();
297
- }
298
- };
299
- });
300
- });
301
- return Object.assign(promise, {
302
- toString: () => String(error),
303
- name: "Suspense",
304
- message: error.message,
305
- stack: error.stack
306
- });
307
- }
308
-
309
347
  const Oops$a = issues({
310
348
  BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
311
349
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
@@ -460,26 +498,28 @@ class Controller {
460
498
  this.proxy = Object.create(subject);
461
499
  }
462
500
  start() {
463
- for (const key in this.subject)
464
- this.manage(key);
501
+ READY.add(this);
502
+ for (const key in this.subject) {
503
+ const { value: entry } = getOwnPropertyDescriptor(this.subject, key);
504
+ if (typeof entry == "function")
505
+ continue;
506
+ if (typeof entry == "symbol") {
507
+ const instruction = PENDING.get(entry);
508
+ if (instruction)
509
+ instruction.call(this, key, this);
510
+ continue;
511
+ }
512
+ this.manage(key, entry);
513
+ }
465
514
  this.emit([]);
466
515
  }
467
516
  stop() {
468
517
  this.followers.clear();
469
518
  this.onDestroy.forEach(x => x());
470
519
  }
471
- manage(key) {
520
+ manage(key, value) {
472
521
  const { proxy, state, subject } = this;
473
- const { value: entry } = getOwnPropertyDescriptor(subject, key);
474
- if (typeof entry == "function")
475
- return;
476
- if (typeof entry == "symbol") {
477
- const instruction = PENDING.get(entry);
478
- if (instruction)
479
- instruction.call(this, key, this);
480
- return;
481
- }
482
- state[key] = entry;
522
+ state[key] = value;
483
523
  const set = this.ref(key);
484
524
  defineProperty(subject, key, {
485
525
  enumerable: false,
@@ -571,26 +611,23 @@ class Controller {
571
611
  }
572
612
  function control(subject, cb) {
573
613
  let control = subject[CONTROL];
574
- const ready = READY.has(control);
575
614
  if (!control) {
576
615
  control = new Controller(subject);
577
616
  define(subject, CONTROL, control);
578
617
  }
579
- if (!cb) {
580
- if (!ready) {
581
- READY.add(control);
582
- control.start();
583
- }
584
- return control;
585
- }
618
+ return ready(control, cb);
619
+ }
620
+ function ready(control, cb) {
621
+ const ready = READY.has(control);
586
622
  if (!ready) {
587
- let done;
588
- control.requestUpdate(() => {
589
- done = cb(control);
590
- });
591
- return () => done && done();
623
+ if (cb) {
624
+ let done;
625
+ control.requestUpdate(() => done = cb(control));
626
+ return () => done && done();
627
+ }
628
+ control.start();
592
629
  }
593
- return cb(control);
630
+ return cb ? cb(control) : control;
594
631
  }
595
632
 
596
633
  function apply(fn, label) {
@@ -683,18 +720,19 @@ const Oops$8 = issues({
683
720
  function act(task) {
684
721
  return apply(function act(key) {
685
722
  let pending = false;
686
- const invoke = (...args) => {
723
+ const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
687
724
  if (pending)
688
725
  return Promise.reject(Oops$8.DuplicateAction(key));
689
726
  pending = true;
690
727
  this.update(key);
691
- return new Promise(res => {
692
- res(task.apply(this.subject, args));
693
- }).finally(() => {
728
+ try {
729
+ return yield mayRetry(() => (task.apply(this.subject, args)));
730
+ }
731
+ finally {
694
732
  pending = false;
695
733
  this.update(key);
696
- });
697
- };
734
+ }
735
+ });
698
736
  this.state[key] = undefined;
699
737
  defineProperty(invoke, "active", {
700
738
  get: () => pending
@@ -824,7 +862,7 @@ function pendingFactory(parent, key, fn, required) {
824
862
  let pending;
825
863
  let error;
826
864
  const init = () => {
827
- const output = suspenseBoundary(() => {
865
+ const output = mayRetry(() => {
828
866
  return fn.call(subject, key, subject);
829
867
  });
830
868
  if (output instanceof Promise) {
@@ -993,7 +1031,7 @@ const useLookup = () => React.useContext(LookupContext);
993
1031
  const Oops$4 = issues({
994
1032
  NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
995
1033
  });
996
- function useInContext(Type, arg) {
1034
+ function useLocal(Type, arg) {
997
1035
  const instance = useLookup().get(Type);
998
1036
  if (!instance && arg !== false)
999
1037
  throw Oops$4.NothingInContext(Type.name);
@@ -1161,7 +1199,7 @@ function useTap(source, path, expect) {
1161
1199
  if ("get" in source)
1162
1200
  return () => source.get();
1163
1201
  if ("prototype" in source)
1164
- return () => useInContext(source);
1202
+ return () => useLocal(source);
1165
1203
  else
1166
1204
  return source;
1167
1205
  }, [])();
@@ -1196,7 +1234,7 @@ class MVC extends Model {
1196
1234
  return useModel(this, arg, callback);
1197
1235
  }
1198
1236
  static get(required) {
1199
- return useInContext(this, required);
1237
+ return useLocal(this, required);
1200
1238
  }
1201
1239
  static tap(key, expect) {
1202
1240
  return useTap(this, key, expect);
@@ -1250,7 +1288,7 @@ function Consumer(props) {
1250
1288
  return children(useTap(Control));
1251
1289
  const callback = has || get;
1252
1290
  if (typeof callback == "function")
1253
- callback(useInContext(Control, !!has));
1291
+ callback(useLocal(Control, !!has));
1254
1292
  else
1255
1293
  throw Oops$2.BadConsumerProps();
1256
1294
  return null;
@@ -1350,5 +1388,5 @@ class Global extends MVC {
1350
1388
  }
1351
1389
  }
1352
1390
 
1353
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom, useInContext, useModel, useTap };
1391
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1354
1392
  //# sourceMappingURL=index.esm.js.map