@expressive/mvc 0.5.0 → 0.6.0

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,11 +17,11 @@ function issues(register) {
17
17
  return Library;
18
18
  }
19
19
 
20
- const Oops$c = issues({
20
+ const Oops$b = issues({
21
21
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
22
22
  });
23
23
  function suspend(source, key) {
24
- const error = Oops$c.NotReady(source.subject, key);
24
+ const error = Oops$b.NotReady(source.subject, key);
25
25
  const promise = new Promise(resolve => {
26
26
  const release = source.addListener(forKey => {
27
27
  if (forKey == key)
@@ -61,101 +61,170 @@ function mayRetry(fn) {
61
61
  return compute();
62
62
  }
63
63
 
64
- const Oops$b = issues({
64
+ const Oops$a = issues({
65
65
  BadSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
66
66
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
67
- Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
67
+ Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
68
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
69
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
70
+ ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
68
71
  });
69
72
  const INFO = new WeakMap();
70
73
  const KEYS = new WeakMap();
71
74
  const ORDER = new WeakMap();
72
- function get(source, arg1, arg2) {
75
+ function get(arg0, arg1, arg2) {
73
76
  return apply(function get(key) {
74
- const { subject, state } = this;
75
- const required = arg2 === true || arg1 === true;
76
- let getSource;
77
- if (typeof arg1 == "boolean")
78
- arg1 = undefined;
77
+ const { subject } = this;
78
+ const computeRequired = arg1 === true || arg2 === true;
79
+ const factoryRequired = arg1 !== false && arg2 !== false;
80
+ let getter;
79
81
  // Easy mistake, using a peer, will always be unresolved.
80
- if (typeof source == "symbol")
81
- throw Oops$b.PeerNotAllowed(subject, key);
82
- // replace source controller in-case it is different
83
- if (typeof source == "object")
84
- getSource = () => ensure(source);
85
- // specifically an arrow function (getter factory)
86
- else if (!source.prototype) {
87
- arg1 = source.call(subject, key);
88
- getSource = () => this;
89
- }
90
- // Regular function is too ambiguous so not allowed.
91
- else
92
- throw Oops$b.BadSource(subject, key, source);
93
- const setter = arg1;
94
- const info = { key, parent: this };
95
- let sub;
96
- let order = ORDER.get(this);
97
- if (!order)
98
- ORDER.set(this, order = []);
99
- const compute = (initial) => {
100
- try {
101
- return setter.call(sub.proxy, sub.proxy);
102
- }
103
- catch (err) {
104
- Oops$b.Failed(subject, key, initial).warn();
105
- throw err;
106
- }
107
- };
108
- const refresh = () => {
109
- let value;
110
- try {
111
- value = compute(false);
112
- }
113
- catch (e) {
114
- console.error(e);
115
- }
116
- finally {
117
- if (state.get(key) !== value) {
118
- state.set(key, value);
119
- this.update(key);
120
- return value;
82
+ if (typeof arg0 == "symbol")
83
+ throw Oops$a.PeerNotAllowed(subject, key);
84
+ if (typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
85
+ throw Oops$a.BadSource(subject, key, arg0);
86
+ this.state.set(key, undefined);
87
+ const init = () => {
88
+ let source = this;
89
+ let setter;
90
+ if (typeof arg0 == "function") {
91
+ const result = mayRetry(() => arg0.call(subject, key, subject));
92
+ if (typeof result == "function")
93
+ setter = result;
94
+ else {
95
+ getter = factoryMode(this, result, key, factoryRequired);
96
+ return;
121
97
  }
122
98
  }
123
- };
124
- const update = (_key, source) => {
125
- if (source !== this) {
126
- refresh();
99
+ else if (arg0 instanceof Promise) {
100
+ getter = factoryMode(this, arg0, key, factoryRequired);
127
101
  return;
128
102
  }
129
- let pending = KEYS.get(this);
130
- if (!pending)
131
- KEYS.set(this, pending = []);
132
- pending.push(refresh);
103
+ else if (typeof arg1 == "function") {
104
+ // replace source controller in-case it is different
105
+ source = ensure(arg0);
106
+ setter = arg1;
107
+ }
108
+ else
109
+ throw new Error(`Factory argument cannot be ${arg1}`);
110
+ getter = computeMode(this, source, setter, key, computeRequired);
133
111
  };
134
- const create = () => {
135
- sub = getSource().subscribe(update);
112
+ if (typeof arg0 == "function" && factoryRequired)
136
113
  try {
137
- const value = compute(true);
138
- state.set(key, value);
139
- return value;
140
- }
141
- catch (e) {
142
- throw e;
114
+ init();
143
115
  }
144
- finally {
145
- sub.commit();
146
- order.push(refresh);
116
+ catch (err) {
117
+ Oops$a.ComputeFailed(subject, key).warn();
118
+ throw err;
147
119
  }
148
- };
149
- state.set(key, undefined);
150
- INFO.set(refresh, info);
151
120
  return () => {
152
- const value = sub ? state.get(key) : create();
153
- if (value === undefined && required)
154
- throw suspend(this, key);
155
- return value;
121
+ if (!getter)
122
+ init();
123
+ return getter();
156
124
  };
157
125
  });
158
126
  }
127
+ function factoryMode(self, output, key, required) {
128
+ const { subject, state } = self;
129
+ let pending;
130
+ let error;
131
+ if (output instanceof Promise) {
132
+ state.set(key, undefined);
133
+ pending = output
134
+ .catch(err => error = err)
135
+ .then(val => {
136
+ state.set(key, val);
137
+ return val;
138
+ })
139
+ .finally(() => {
140
+ pending = undefined;
141
+ self.update(key);
142
+ });
143
+ }
144
+ state.set(key, output);
145
+ const suspend = () => {
146
+ if (required === false)
147
+ return undefined;
148
+ const issue = Oops$a.NotReady(subject, key);
149
+ Object.assign(pending, {
150
+ message: issue.message,
151
+ stack: issue.stack
152
+ });
153
+ throw pending;
154
+ };
155
+ return () => {
156
+ if (error)
157
+ throw error;
158
+ if (pending)
159
+ return suspend();
160
+ if (state.has(key))
161
+ return state.get(key);
162
+ };
163
+ }
164
+ function computeMode(self, source, setter, key, required) {
165
+ const { subject, state } = self;
166
+ let sub;
167
+ let order = ORDER.get(self);
168
+ if (!order)
169
+ ORDER.set(self, order = []);
170
+ const compute = (initial) => {
171
+ try {
172
+ return setter.call(sub.proxy, sub.proxy);
173
+ }
174
+ catch (err) {
175
+ Oops$a.Failed(subject, key, initial).warn();
176
+ throw err;
177
+ }
178
+ };
179
+ const refresh = () => {
180
+ let value;
181
+ try {
182
+ value = compute(false);
183
+ }
184
+ catch (e) {
185
+ console.error(e);
186
+ }
187
+ finally {
188
+ if (state.get(key) !== value) {
189
+ state.set(key, value);
190
+ self.update(key);
191
+ return value;
192
+ }
193
+ }
194
+ };
195
+ const update = (_key, source) => {
196
+ if (source !== self) {
197
+ refresh();
198
+ return;
199
+ }
200
+ let pending = KEYS.get(self);
201
+ if (!pending)
202
+ KEYS.set(self, pending = []);
203
+ pending.push(refresh);
204
+ };
205
+ const create = () => {
206
+ sub = source.subscribe(update);
207
+ try {
208
+ const value = compute(true);
209
+ state.set(key, value);
210
+ return value;
211
+ }
212
+ catch (e) {
213
+ throw e;
214
+ }
215
+ finally {
216
+ sub.commit();
217
+ order.push(refresh);
218
+ }
219
+ };
220
+ INFO.set(refresh, key);
221
+ return () => {
222
+ const value = sub ? state.get(key) : create();
223
+ if (value === undefined && required)
224
+ throw suspend(self, key);
225
+ return value;
226
+ };
227
+ }
159
228
  function flush(control) {
160
229
  const pending = KEYS.get(control);
161
230
  if (!pending)
@@ -165,14 +234,14 @@ function flush(control) {
165
234
  const compute = pending
166
235
  .sort((a, b) => list.indexOf(b) - list.indexOf(a))
167
236
  .pop();
168
- const { key } = INFO.get(compute);
237
+ const key = INFO.get(compute);
169
238
  if (!control.frame.has(key))
170
239
  compute();
171
240
  }
172
241
  KEYS.delete(control);
173
242
  }
174
243
 
175
- const Oops$a = issues({
244
+ const Oops$9 = issues({
176
245
  BadCallback: () => `Callback for effect-callback may only return a function.`
177
246
  });
178
247
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -185,7 +254,7 @@ function createEffect(callback) {
185
254
  if (unSet instanceof Promise)
186
255
  unSet = undefined;
187
256
  if (unSet && typeof unSet !== "function")
188
- throw Oops$a.BadCallback();
257
+ throw Oops$9.BadCallback();
189
258
  };
190
259
  }
191
260
  function createValueEffect(callback) {
@@ -201,65 +270,11 @@ function createValueEffect(callback) {
201
270
  if (typeof out == "function")
202
271
  unSet = out;
203
272
  else
204
- throw Oops$a.BadCallback();
273
+ throw Oops$9.BadCallback();
205
274
  };
206
275
  }
207
276
 
208
- class Subscriber {
209
- constructor(parent, onUpdate) {
210
- this.onUpdate = onUpdate;
211
- this.active = false;
212
- this.dependant = new Set();
213
- const proxy = create(parent.subject);
214
- const using = new Map();
215
- defineProperty(proxy, LOCAL, { value: this });
216
- defineProperty(this, "proxy", {
217
- configurable: true,
218
- get() {
219
- if (UPDATE.has(proxy))
220
- setTimeout(() => UPDATE.delete(proxy), 0);
221
- return proxy;
222
- }
223
- });
224
- const release = parent.addListener(key => {
225
- if (!this.active)
226
- return;
227
- const handler = using.get(key);
228
- let notify;
229
- if (typeof handler == "function") {
230
- const callback = handler();
231
- if (callback === true)
232
- notify = this.onUpdate(key, parent);
233
- }
234
- else if (handler === true)
235
- notify = this.onUpdate(key, parent);
236
- if (!notify)
237
- return;
238
- const notate = () => {
239
- UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
240
- };
241
- parent.waiting.add(notate);
242
- parent.waiting.add(notify);
243
- });
244
- this.commit = () => {
245
- this.active = true;
246
- this.dependant.forEach(x => x.commit());
247
- return this.release;
248
- };
249
- this.release = () => {
250
- this.dependant.forEach(x => x.release());
251
- release();
252
- };
253
- this.add = (key, value) => {
254
- if (value !== undefined)
255
- using.set(key, value);
256
- else if (!using.has(key))
257
- using.set(key, true);
258
- };
259
- }
260
- }
261
-
262
- const Oops$9 = issues({
277
+ const Oops$8 = issues({
263
278
  Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
264
279
  StrictUpdate: () => `Strict update() did not find pending updates.`,
265
280
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
@@ -342,7 +357,7 @@ class Model {
342
357
  const invoke = (key) => {
343
358
  if (!key) {
344
359
  clear();
345
- reject(Oops$9.Timeout(select, typeof argument == "number"
360
+ reject(Oops$8.Timeout(select, typeof argument == "number"
346
361
  ? `${argument}ms`
347
362
  : `lifetime of ${this}`));
348
363
  }
@@ -429,11 +444,11 @@ class Model {
429
444
  arg = undefined;
430
445
  }
431
446
  if (!frame.size && arg === true)
432
- return Promise.reject(Oops$9.StrictUpdate());
447
+ return Promise.reject(Oops$8.StrictUpdate());
433
448
  return {
434
449
  then: (callback) => {
435
450
  if (!callback)
436
- throw Oops$9.NoChaining();
451
+ throw Oops$8.NoChaining();
437
452
  if (frame.size || arg !== false)
438
453
  waiting.add(() => {
439
454
  callback(getUpdate(this));
@@ -475,7 +490,63 @@ class Model {
475
490
  }
476
491
  }
477
492
 
493
+ class Subscriber {
494
+ constructor(parent, onUpdate) {
495
+ this.onUpdate = onUpdate;
496
+ this.active = false;
497
+ this.dependant = new Set();
498
+ const proxy = create(parent.subject);
499
+ const using = new Map();
500
+ const listen = this.add = (key, value) => {
501
+ if (value !== undefined)
502
+ using.set(key, value);
503
+ else if (!using.has(key))
504
+ using.set(key, true);
505
+ };
506
+ LISTEN.set(proxy, listen);
507
+ defineProperty(proxy, LOCAL, { value: this });
508
+ defineProperty(this, "proxy", {
509
+ configurable: true,
510
+ get() {
511
+ if (UPDATE.has(proxy))
512
+ setTimeout(() => UPDATE.delete(proxy), 0);
513
+ return proxy;
514
+ }
515
+ });
516
+ const release = parent.addListener(key => {
517
+ if (!this.active)
518
+ return;
519
+ const handler = using.get(key);
520
+ let notify;
521
+ if (typeof handler == "function") {
522
+ const callback = handler();
523
+ if (callback === true)
524
+ notify = this.onUpdate(key, parent);
525
+ }
526
+ else if (handler === true)
527
+ notify = this.onUpdate(key, parent);
528
+ if (!notify)
529
+ return;
530
+ const notate = () => {
531
+ UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
532
+ };
533
+ parent.waiting.add(notate);
534
+ parent.waiting.add(notify);
535
+ });
536
+ this.commit = () => {
537
+ this.active = true;
538
+ this.dependant.forEach(x => x.commit());
539
+ return this.release;
540
+ };
541
+ this.release = () => {
542
+ this.dependant.forEach(x => x.release());
543
+ release();
544
+ };
545
+ }
546
+ }
547
+
478
548
  const UPDATE = new WeakMap();
549
+ const LISTEN = new WeakMap();
479
550
  const CONTROL = Symbol("CONTROL");
480
551
  function getUpdate(subject) {
481
552
  return UPDATE.get(subject);
@@ -506,9 +577,9 @@ class Controller {
506
577
  enumerable: false,
507
578
  set: this.ref(key),
508
579
  get() {
509
- const local = this[LOCAL];
510
- if (local)
511
- local.add(key);
580
+ const listen = LISTEN.get(this);
581
+ if (listen)
582
+ listen(key);
512
583
  return state.get(key);
513
584
  }
514
585
  });
@@ -635,9 +706,10 @@ function apply(fn, label) {
635
706
  get() {
636
707
  if (!state.has(key) && suspense)
637
708
  throw suspend(control, key);
709
+ const listen = LISTEN.get(this);
638
710
  const local = this[LOCAL];
639
- if (local)
640
- local.add(key);
711
+ if (listen)
712
+ listen(key);
641
713
  return onGet
642
714
  ? onGet(local)
643
715
  : state.get(key);
@@ -705,7 +777,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
705
777
  });
706
778
  }
707
779
 
708
- const Oops$8 = issues({
780
+ const Oops$7 = issues({
709
781
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
710
782
  });
711
783
  function act(task) {
@@ -713,7 +785,7 @@ function act(task) {
713
785
  let pending = false;
714
786
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
715
787
  if (pending)
716
- return Promise.reject(Oops$8.DuplicatePending(key));
788
+ return Promise.reject(Oops$7.DuplicatePending(key));
717
789
  pending = true;
718
790
  this.update(key);
719
791
  try {
@@ -749,81 +821,6 @@ function ref(callback) {
749
821
  });
750
822
  }
751
823
 
752
- const Oops$7 = issues({
753
- NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
754
- NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
755
- Failed: (model, key) => `Generating initial value for ${model}.${key} failed.`
756
- });
757
- function put(factory, argument) {
758
- return apply(function set(key) {
759
- const { subject, state } = this;
760
- const required = argument === true ||
761
- argument === undefined;
762
- let pending;
763
- let error;
764
- const init = () => {
765
- const output = typeof factory == "function" ?
766
- mayRetry(() => factory.call(subject, key, subject)) :
767
- factory;
768
- if (output instanceof Promise) {
769
- pending = output
770
- .catch(err => error = err)
771
- .then(val => {
772
- state.set(key, val);
773
- return val;
774
- })
775
- .finally(() => {
776
- pending = undefined;
777
- this.update(key);
778
- });
779
- return;
780
- }
781
- state.set(key, output);
782
- return output;
783
- };
784
- if (required)
785
- try {
786
- init();
787
- }
788
- catch (err) {
789
- Oops$7.Failed(subject, key).warn();
790
- throw err;
791
- }
792
- const suspend = () => {
793
- if (required === false)
794
- return undefined;
795
- const issue = Oops$7.NotReady(subject, key);
796
- Object.assign(pending, {
797
- message: issue.message,
798
- stack: issue.stack
799
- });
800
- throw pending;
801
- };
802
- const get = () => {
803
- if (pending)
804
- return suspend();
805
- if (error)
806
- throw error;
807
- if (state.has(key))
808
- return state.get(key);
809
- const output = init();
810
- return pending
811
- ? suspend()
812
- : output;
813
- };
814
- const set = typeof argument == "function"
815
- ? createValueEffect(argument)
816
- : (value) => {
817
- if (value === undefined && required)
818
- throw Oops$7.NonOptional(subject, key);
819
- };
820
- return {
821
- set,
822
- get
823
- };
824
- });
825
- }
826
-
827
824
  function set(value, argument) {
828
825
  if (!(1 in arguments)) {
829
826
  argument = value;
@@ -987,31 +984,77 @@ function createProxy(emit, watch) {
987
984
  const Parent = new WeakMap();
988
985
  const Oops$6 = issues({
989
986
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
987
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
990
988
  });
991
989
  function use$1(input, argument) {
992
990
  return apply(function use(key) {
993
991
  const { state, subject } = this;
992
+ const required = argument !== false;
993
+ let pending;
994
+ let error;
995
+ let get = (_local) => state.get(key);
994
996
  if (typeof input === "function")
995
- input = "prototype" in input ? new input() : input();
997
+ input = /^[A-Z]/.test(input.name)
998
+ ? new input() : input;
996
999
  else if (input && typeof input !== "object")
997
1000
  throw Oops$6.BadArgument(typeof input);
998
1001
  if (input instanceof Map || input instanceof Set)
999
1002
  return keyed(this, key, input);
1000
- function onUpdate(next) {
1003
+ const onUpdate = (next) => {
1001
1004
  state.set(key, next);
1002
1005
  if (next) {
1006
+ get = getRecursive(key, this);
1003
1007
  Parent.set(next, subject);
1004
1008
  ensure(next);
1005
1009
  }
1006
1010
  if (typeof argument == "function")
1007
1011
  argument(next);
1008
1012
  return true;
1009
- }
1010
- if (input)
1011
- onUpdate(input);
1013
+ };
1014
+ const suspend = () => {
1015
+ if (required === false)
1016
+ return;
1017
+ const issue = Oops$6.NotReady(subject, key);
1018
+ Object.assign(pending, {
1019
+ message: issue.message,
1020
+ stack: issue.stack
1021
+ });
1022
+ throw pending;
1023
+ };
1024
+ const initialize = () => {
1025
+ const output = typeof input == "function" ?
1026
+ mayRetry(() => input.call(subject, subject)) :
1027
+ input;
1028
+ if (output instanceof Promise) {
1029
+ pending = output
1030
+ .catch(err => error = err)
1031
+ .then(val => {
1032
+ onUpdate(val);
1033
+ return val;
1034
+ })
1035
+ .finally(() => {
1036
+ pending = undefined;
1037
+ this.update(key);
1038
+ });
1039
+ return;
1040
+ }
1041
+ onUpdate(output);
1042
+ };
1043
+ if (input !== undefined && required)
1044
+ initialize();
1012
1045
  return {
1013
1046
  set: onUpdate,
1014
- recursive: true
1047
+ get(local) {
1048
+ if (pending)
1049
+ return suspend();
1050
+ if (error)
1051
+ throw error;
1052
+ if (!state.has(key))
1053
+ initialize();
1054
+ if (pending)
1055
+ return suspend();
1056
+ return get(local);
1057
+ }
1015
1058
  };
1016
1059
  });
1017
1060
  }
@@ -1458,5 +1501,5 @@ class Global extends MVC {
1458
1501
  }
1459
1502
  }
1460
1503
 
1461
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, MVC as default, get, has, put, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1504
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, MVC as default, get, has, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1462
1505
  //# sourceMappingURL=index.esm.js.map