@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.js CHANGED
@@ -25,11 +25,11 @@ function issues(register) {
25
25
  return Library;
26
26
  }
27
27
 
28
- const Oops$c = issues({
28
+ const Oops$b = issues({
29
29
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
30
30
  });
31
31
  function suspend(source, key) {
32
- const error = Oops$c.NotReady(source.subject, key);
32
+ const error = Oops$b.NotReady(source.subject, key);
33
33
  const promise = new Promise(resolve => {
34
34
  const release = source.addListener(forKey => {
35
35
  if (forKey == key)
@@ -69,101 +69,170 @@ function mayRetry(fn) {
69
69
  return compute();
70
70
  }
71
71
 
72
- const Oops$b = issues({
72
+ const Oops$a = issues({
73
73
  BadSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
74
74
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
75
- Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
75
+ Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
76
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
77
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
78
+ ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
76
79
  });
77
80
  const INFO = new WeakMap();
78
81
  const KEYS = new WeakMap();
79
82
  const ORDER = new WeakMap();
80
- function get(source, arg1, arg2) {
83
+ function get(arg0, arg1, arg2) {
81
84
  return apply(function get(key) {
82
- const { subject, state } = this;
83
- const required = arg2 === true || arg1 === true;
84
- let getSource;
85
- if (typeof arg1 == "boolean")
86
- arg1 = undefined;
85
+ const { subject } = this;
86
+ const computeRequired = arg1 === true || arg2 === true;
87
+ const factoryRequired = arg1 !== false && arg2 !== false;
88
+ let getter;
87
89
  // Easy mistake, using a peer, will always be unresolved.
88
- if (typeof source == "symbol")
89
- throw Oops$b.PeerNotAllowed(subject, key);
90
- // replace source controller in-case it is different
91
- if (typeof source == "object")
92
- getSource = () => ensure(source);
93
- // specifically an arrow function (getter factory)
94
- else if (!source.prototype) {
95
- arg1 = source.call(subject, key);
96
- getSource = () => this;
97
- }
98
- // Regular function is too ambiguous so not allowed.
99
- else
100
- throw Oops$b.BadSource(subject, key, source);
101
- const setter = arg1;
102
- const info = { key, parent: this };
103
- let sub;
104
- let order = ORDER.get(this);
105
- if (!order)
106
- ORDER.set(this, order = []);
107
- const compute = (initial) => {
108
- try {
109
- return setter.call(sub.proxy, sub.proxy);
110
- }
111
- catch (err) {
112
- Oops$b.Failed(subject, key, initial).warn();
113
- throw err;
114
- }
115
- };
116
- const refresh = () => {
117
- let value;
118
- try {
119
- value = compute(false);
120
- }
121
- catch (e) {
122
- console.error(e);
123
- }
124
- finally {
125
- if (state.get(key) !== value) {
126
- state.set(key, value);
127
- this.update(key);
128
- return value;
90
+ if (typeof arg0 == "symbol")
91
+ throw Oops$a.PeerNotAllowed(subject, key);
92
+ if (typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
93
+ throw Oops$a.BadSource(subject, key, arg0);
94
+ this.state.set(key, undefined);
95
+ const init = () => {
96
+ let source = this;
97
+ let setter;
98
+ if (typeof arg0 == "function") {
99
+ const result = mayRetry(() => arg0.call(subject, key, subject));
100
+ if (typeof result == "function")
101
+ setter = result;
102
+ else {
103
+ getter = factoryMode(this, result, key, factoryRequired);
104
+ return;
129
105
  }
130
106
  }
131
- };
132
- const update = (_key, source) => {
133
- if (source !== this) {
134
- refresh();
107
+ else if (arg0 instanceof Promise) {
108
+ getter = factoryMode(this, arg0, key, factoryRequired);
135
109
  return;
136
110
  }
137
- let pending = KEYS.get(this);
138
- if (!pending)
139
- KEYS.set(this, pending = []);
140
- pending.push(refresh);
111
+ else if (typeof arg1 == "function") {
112
+ // replace source controller in-case it is different
113
+ source = ensure(arg0);
114
+ setter = arg1;
115
+ }
116
+ else
117
+ throw new Error(`Factory argument cannot be ${arg1}`);
118
+ getter = computeMode(this, source, setter, key, computeRequired);
141
119
  };
142
- const create = () => {
143
- sub = getSource().subscribe(update);
120
+ if (typeof arg0 == "function" && factoryRequired)
144
121
  try {
145
- const value = compute(true);
146
- state.set(key, value);
147
- return value;
148
- }
149
- catch (e) {
150
- throw e;
122
+ init();
151
123
  }
152
- finally {
153
- sub.commit();
154
- order.push(refresh);
124
+ catch (err) {
125
+ Oops$a.ComputeFailed(subject, key).warn();
126
+ throw err;
155
127
  }
156
- };
157
- state.set(key, undefined);
158
- INFO.set(refresh, info);
159
128
  return () => {
160
- const value = sub ? state.get(key) : create();
161
- if (value === undefined && required)
162
- throw suspend(this, key);
163
- return value;
129
+ if (!getter)
130
+ init();
131
+ return getter();
164
132
  };
165
133
  });
166
134
  }
135
+ function factoryMode(self, output, key, required) {
136
+ const { subject, state } = self;
137
+ let pending;
138
+ let error;
139
+ if (output instanceof Promise) {
140
+ state.set(key, undefined);
141
+ pending = output
142
+ .catch(err => error = err)
143
+ .then(val => {
144
+ state.set(key, val);
145
+ return val;
146
+ })
147
+ .finally(() => {
148
+ pending = undefined;
149
+ self.update(key);
150
+ });
151
+ }
152
+ state.set(key, output);
153
+ const suspend = () => {
154
+ if (required === false)
155
+ return undefined;
156
+ const issue = Oops$a.NotReady(subject, key);
157
+ Object.assign(pending, {
158
+ message: issue.message,
159
+ stack: issue.stack
160
+ });
161
+ throw pending;
162
+ };
163
+ return () => {
164
+ if (error)
165
+ throw error;
166
+ if (pending)
167
+ return suspend();
168
+ if (state.has(key))
169
+ return state.get(key);
170
+ };
171
+ }
172
+ function computeMode(self, source, setter, key, required) {
173
+ const { subject, state } = self;
174
+ let sub;
175
+ let order = ORDER.get(self);
176
+ if (!order)
177
+ ORDER.set(self, order = []);
178
+ const compute = (initial) => {
179
+ try {
180
+ return setter.call(sub.proxy, sub.proxy);
181
+ }
182
+ catch (err) {
183
+ Oops$a.Failed(subject, key, initial).warn();
184
+ throw err;
185
+ }
186
+ };
187
+ const refresh = () => {
188
+ let value;
189
+ try {
190
+ value = compute(false);
191
+ }
192
+ catch (e) {
193
+ console.error(e);
194
+ }
195
+ finally {
196
+ if (state.get(key) !== value) {
197
+ state.set(key, value);
198
+ self.update(key);
199
+ return value;
200
+ }
201
+ }
202
+ };
203
+ const update = (_key, source) => {
204
+ if (source !== self) {
205
+ refresh();
206
+ return;
207
+ }
208
+ let pending = KEYS.get(self);
209
+ if (!pending)
210
+ KEYS.set(self, pending = []);
211
+ pending.push(refresh);
212
+ };
213
+ const create = () => {
214
+ sub = source.subscribe(update);
215
+ try {
216
+ const value = compute(true);
217
+ state.set(key, value);
218
+ return value;
219
+ }
220
+ catch (e) {
221
+ throw e;
222
+ }
223
+ finally {
224
+ sub.commit();
225
+ order.push(refresh);
226
+ }
227
+ };
228
+ INFO.set(refresh, key);
229
+ return () => {
230
+ const value = sub ? state.get(key) : create();
231
+ if (value === undefined && required)
232
+ throw suspend(self, key);
233
+ return value;
234
+ };
235
+ }
167
236
  function flush(control) {
168
237
  const pending = KEYS.get(control);
169
238
  if (!pending)
@@ -173,14 +242,14 @@ function flush(control) {
173
242
  const compute = pending
174
243
  .sort((a, b) => list.indexOf(b) - list.indexOf(a))
175
244
  .pop();
176
- const { key } = INFO.get(compute);
245
+ const key = INFO.get(compute);
177
246
  if (!control.frame.has(key))
178
247
  compute();
179
248
  }
180
249
  KEYS.delete(control);
181
250
  }
182
251
 
183
- const Oops$a = issues({
252
+ const Oops$9 = issues({
184
253
  BadCallback: () => `Callback for effect-callback may only return a function.`
185
254
  });
186
255
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -193,7 +262,7 @@ function createEffect(callback) {
193
262
  if (unSet instanceof Promise)
194
263
  unSet = undefined;
195
264
  if (unSet && typeof unSet !== "function")
196
- throw Oops$a.BadCallback();
265
+ throw Oops$9.BadCallback();
197
266
  };
198
267
  }
199
268
  function createValueEffect(callback) {
@@ -209,65 +278,11 @@ function createValueEffect(callback) {
209
278
  if (typeof out == "function")
210
279
  unSet = out;
211
280
  else
212
- throw Oops$a.BadCallback();
281
+ throw Oops$9.BadCallback();
213
282
  };
214
283
  }
215
284
 
216
- class Subscriber {
217
- constructor(parent, onUpdate) {
218
- this.onUpdate = onUpdate;
219
- this.active = false;
220
- this.dependant = new Set();
221
- const proxy = create(parent.subject);
222
- const using = new Map();
223
- defineProperty(proxy, LOCAL, { value: this });
224
- defineProperty(this, "proxy", {
225
- configurable: true,
226
- get() {
227
- if (UPDATE.has(proxy))
228
- setTimeout(() => UPDATE.delete(proxy), 0);
229
- return proxy;
230
- }
231
- });
232
- const release = parent.addListener(key => {
233
- if (!this.active)
234
- return;
235
- const handler = using.get(key);
236
- let notify;
237
- if (typeof handler == "function") {
238
- const callback = handler();
239
- if (callback === true)
240
- notify = this.onUpdate(key, parent);
241
- }
242
- else if (handler === true)
243
- notify = this.onUpdate(key, parent);
244
- if (!notify)
245
- return;
246
- const notate = () => {
247
- UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
248
- };
249
- parent.waiting.add(notate);
250
- parent.waiting.add(notify);
251
- });
252
- this.commit = () => {
253
- this.active = true;
254
- this.dependant.forEach(x => x.commit());
255
- return this.release;
256
- };
257
- this.release = () => {
258
- this.dependant.forEach(x => x.release());
259
- release();
260
- };
261
- this.add = (key, value) => {
262
- if (value !== undefined)
263
- using.set(key, value);
264
- else if (!using.has(key))
265
- using.set(key, true);
266
- };
267
- }
268
- }
269
-
270
- const Oops$9 = issues({
285
+ const Oops$8 = issues({
271
286
  Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
272
287
  StrictUpdate: () => `Strict update() did not find pending updates.`,
273
288
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
@@ -350,7 +365,7 @@ class Model {
350
365
  const invoke = (key) => {
351
366
  if (!key) {
352
367
  clear();
353
- reject(Oops$9.Timeout(select, typeof argument == "number"
368
+ reject(Oops$8.Timeout(select, typeof argument == "number"
354
369
  ? `${argument}ms`
355
370
  : `lifetime of ${this}`));
356
371
  }
@@ -437,11 +452,11 @@ class Model {
437
452
  arg = undefined;
438
453
  }
439
454
  if (!frame.size && arg === true)
440
- return Promise.reject(Oops$9.StrictUpdate());
455
+ return Promise.reject(Oops$8.StrictUpdate());
441
456
  return {
442
457
  then: (callback) => {
443
458
  if (!callback)
444
- throw Oops$9.NoChaining();
459
+ throw Oops$8.NoChaining();
445
460
  if (frame.size || arg !== false)
446
461
  waiting.add(() => {
447
462
  callback(getUpdate(this));
@@ -483,7 +498,63 @@ class Model {
483
498
  }
484
499
  }
485
500
 
501
+ class Subscriber {
502
+ constructor(parent, onUpdate) {
503
+ this.onUpdate = onUpdate;
504
+ this.active = false;
505
+ this.dependant = new Set();
506
+ const proxy = create(parent.subject);
507
+ const using = new Map();
508
+ const listen = this.add = (key, value) => {
509
+ if (value !== undefined)
510
+ using.set(key, value);
511
+ else if (!using.has(key))
512
+ using.set(key, true);
513
+ };
514
+ LISTEN.set(proxy, listen);
515
+ defineProperty(proxy, LOCAL, { value: this });
516
+ defineProperty(this, "proxy", {
517
+ configurable: true,
518
+ get() {
519
+ if (UPDATE.has(proxy))
520
+ setTimeout(() => UPDATE.delete(proxy), 0);
521
+ return proxy;
522
+ }
523
+ });
524
+ const release = parent.addListener(key => {
525
+ if (!this.active)
526
+ return;
527
+ const handler = using.get(key);
528
+ let notify;
529
+ if (typeof handler == "function") {
530
+ const callback = handler();
531
+ if (callback === true)
532
+ notify = this.onUpdate(key, parent);
533
+ }
534
+ else if (handler === true)
535
+ notify = this.onUpdate(key, parent);
536
+ if (!notify)
537
+ return;
538
+ const notate = () => {
539
+ UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
540
+ };
541
+ parent.waiting.add(notate);
542
+ parent.waiting.add(notify);
543
+ });
544
+ this.commit = () => {
545
+ this.active = true;
546
+ this.dependant.forEach(x => x.commit());
547
+ return this.release;
548
+ };
549
+ this.release = () => {
550
+ this.dependant.forEach(x => x.release());
551
+ release();
552
+ };
553
+ }
554
+ }
555
+
486
556
  const UPDATE = new WeakMap();
557
+ const LISTEN = new WeakMap();
487
558
  const CONTROL = Symbol("CONTROL");
488
559
  function getUpdate(subject) {
489
560
  return UPDATE.get(subject);
@@ -514,9 +585,9 @@ class Controller {
514
585
  enumerable: false,
515
586
  set: this.ref(key),
516
587
  get() {
517
- const local = this[LOCAL];
518
- if (local)
519
- local.add(key);
588
+ const listen = LISTEN.get(this);
589
+ if (listen)
590
+ listen(key);
520
591
  return state.get(key);
521
592
  }
522
593
  });
@@ -643,9 +714,10 @@ function apply(fn, label) {
643
714
  get() {
644
715
  if (!state.has(key) && suspense)
645
716
  throw suspend(control, key);
717
+ const listen = LISTEN.get(this);
646
718
  const local = this[LOCAL];
647
- if (local)
648
- local.add(key);
719
+ if (listen)
720
+ listen(key);
649
721
  return onGet
650
722
  ? onGet(local)
651
723
  : state.get(key);
@@ -713,7 +785,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
713
785
  });
714
786
  }
715
787
 
716
- const Oops$8 = issues({
788
+ const Oops$7 = issues({
717
789
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
718
790
  });
719
791
  function act(task) {
@@ -721,7 +793,7 @@ function act(task) {
721
793
  let pending = false;
722
794
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
723
795
  if (pending)
724
- return Promise.reject(Oops$8.DuplicatePending(key));
796
+ return Promise.reject(Oops$7.DuplicatePending(key));
725
797
  pending = true;
726
798
  this.update(key);
727
799
  try {
@@ -757,81 +829,6 @@ function ref(callback) {
757
829
  });
758
830
  }
759
831
 
760
- const Oops$7 = issues({
761
- NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
762
- NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
763
- Failed: (model, key) => `Generating initial value for ${model}.${key} failed.`
764
- });
765
- function put(factory, argument) {
766
- return apply(function set(key) {
767
- const { subject, state } = this;
768
- const required = argument === true ||
769
- argument === undefined;
770
- let pending;
771
- let error;
772
- const init = () => {
773
- const output = typeof factory == "function" ?
774
- mayRetry(() => factory.call(subject, key, subject)) :
775
- factory;
776
- if (output instanceof Promise) {
777
- pending = output
778
- .catch(err => error = err)
779
- .then(val => {
780
- state.set(key, val);
781
- return val;
782
- })
783
- .finally(() => {
784
- pending = undefined;
785
- this.update(key);
786
- });
787
- return;
788
- }
789
- state.set(key, output);
790
- return output;
791
- };
792
- if (required)
793
- try {
794
- init();
795
- }
796
- catch (err) {
797
- Oops$7.Failed(subject, key).warn();
798
- throw err;
799
- }
800
- const suspend = () => {
801
- if (required === false)
802
- return undefined;
803
- const issue = Oops$7.NotReady(subject, key);
804
- Object.assign(pending, {
805
- message: issue.message,
806
- stack: issue.stack
807
- });
808
- throw pending;
809
- };
810
- const get = () => {
811
- if (pending)
812
- return suspend();
813
- if (error)
814
- throw error;
815
- if (state.has(key))
816
- return state.get(key);
817
- const output = init();
818
- return pending
819
- ? suspend()
820
- : output;
821
- };
822
- const set = typeof argument == "function"
823
- ? createValueEffect(argument)
824
- : (value) => {
825
- if (value === undefined && required)
826
- throw Oops$7.NonOptional(subject, key);
827
- };
828
- return {
829
- set,
830
- get
831
- };
832
- });
833
- }
834
-
835
832
  function set(value, argument) {
836
833
  if (!(1 in arguments)) {
837
834
  argument = value;
@@ -995,31 +992,77 @@ function createProxy(emit, watch) {
995
992
  const Parent = new WeakMap();
996
993
  const Oops$6 = issues({
997
994
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
995
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
998
996
  });
999
997
  function use$1(input, argument) {
1000
998
  return apply(function use(key) {
1001
999
  const { state, subject } = this;
1000
+ const required = argument !== false;
1001
+ let pending;
1002
+ let error;
1003
+ let get = (_local) => state.get(key);
1002
1004
  if (typeof input === "function")
1003
- input = "prototype" in input ? new input() : input();
1005
+ input = /^[A-Z]/.test(input.name)
1006
+ ? new input() : input;
1004
1007
  else if (input && typeof input !== "object")
1005
1008
  throw Oops$6.BadArgument(typeof input);
1006
1009
  if (input instanceof Map || input instanceof Set)
1007
1010
  return keyed(this, key, input);
1008
- function onUpdate(next) {
1011
+ const onUpdate = (next) => {
1009
1012
  state.set(key, next);
1010
1013
  if (next) {
1014
+ get = getRecursive(key, this);
1011
1015
  Parent.set(next, subject);
1012
1016
  ensure(next);
1013
1017
  }
1014
1018
  if (typeof argument == "function")
1015
1019
  argument(next);
1016
1020
  return true;
1017
- }
1018
- if (input)
1019
- onUpdate(input);
1021
+ };
1022
+ const suspend = () => {
1023
+ if (required === false)
1024
+ return;
1025
+ const issue = Oops$6.NotReady(subject, key);
1026
+ Object.assign(pending, {
1027
+ message: issue.message,
1028
+ stack: issue.stack
1029
+ });
1030
+ throw pending;
1031
+ };
1032
+ const initialize = () => {
1033
+ const output = typeof input == "function" ?
1034
+ mayRetry(() => input.call(subject, subject)) :
1035
+ input;
1036
+ if (output instanceof Promise) {
1037
+ pending = output
1038
+ .catch(err => error = err)
1039
+ .then(val => {
1040
+ onUpdate(val);
1041
+ return val;
1042
+ })
1043
+ .finally(() => {
1044
+ pending = undefined;
1045
+ this.update(key);
1046
+ });
1047
+ return;
1048
+ }
1049
+ onUpdate(output);
1050
+ };
1051
+ if (input !== undefined && required)
1052
+ initialize();
1020
1053
  return {
1021
1054
  set: onUpdate,
1022
- recursive: true
1055
+ get(local) {
1056
+ if (pending)
1057
+ return suspend();
1058
+ if (error)
1059
+ throw error;
1060
+ if (!state.has(key))
1061
+ initialize();
1062
+ if (pending)
1063
+ return suspend();
1064
+ return get(local);
1065
+ }
1023
1066
  };
1024
1067
  });
1025
1068
  }
@@ -1479,7 +1522,6 @@ exports.apply = apply;
1479
1522
  exports["default"] = MVC;
1480
1523
  exports.get = get;
1481
1524
  exports.has = has;
1482
- exports.put = put;
1483
1525
  exports.ref = ref;
1484
1526
  exports.set = set;
1485
1527
  exports.tap = tap;