@expressive/mvc 0.5.0 → 0.6.2

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
@@ -70,99 +70,74 @@ function mayRetry(fn) {
70
70
  }
71
71
 
72
72
  const Oops$b = issues({
73
- BadSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
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
73
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
76
74
  });
77
75
  const INFO = new WeakMap();
78
76
  const KEYS = new WeakMap();
79
77
  const ORDER = new WeakMap();
80
- function get(source, arg1, arg2) {
81
- 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;
87
- // 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;
78
+ function computeMode(self, source, setter, key, required) {
79
+ const { subject, state } = self;
80
+ let sub;
81
+ let order = ORDER.get(self);
82
+ if (!order)
83
+ ORDER.set(self, order = []);
84
+ const compute = (initial) => {
85
+ try {
86
+ return setter.call(sub.proxy, sub.proxy);
97
87
  }
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;
129
- }
130
- }
131
- };
132
- const update = (_key, source) => {
133
- if (source !== this) {
134
- refresh();
135
- return;
136
- }
137
- let pending = KEYS.get(this);
138
- if (!pending)
139
- KEYS.set(this, pending = []);
140
- pending.push(refresh);
141
- };
142
- const create = () => {
143
- sub = getSource().subscribe(update);
144
- try {
145
- const value = compute(true);
88
+ catch (err) {
89
+ Oops$b.Failed(subject, key, initial).warn();
90
+ throw err;
91
+ }
92
+ };
93
+ const refresh = () => {
94
+ let value;
95
+ try {
96
+ value = compute(false);
97
+ }
98
+ catch (e) {
99
+ console.error(e);
100
+ }
101
+ finally {
102
+ if (state.get(key) !== value) {
146
103
  state.set(key, value);
104
+ self.update(key);
147
105
  return value;
148
106
  }
149
- catch (e) {
150
- throw e;
151
- }
152
- finally {
153
- sub.commit();
154
- order.push(refresh);
155
- }
156
- };
157
- state.set(key, undefined);
158
- INFO.set(refresh, info);
159
- return () => {
160
- const value = sub ? state.get(key) : create();
161
- if (value === undefined && required)
162
- throw suspend(this, key);
107
+ }
108
+ };
109
+ const update = (_key, source) => {
110
+ if (source !== self) {
111
+ refresh();
112
+ return;
113
+ }
114
+ let pending = KEYS.get(self);
115
+ if (!pending)
116
+ KEYS.set(self, pending = []);
117
+ pending.push(refresh);
118
+ };
119
+ const create = () => {
120
+ sub = source.subscribe(update);
121
+ try {
122
+ const value = compute(true);
123
+ state.set(key, value);
163
124
  return value;
164
- };
165
- });
125
+ }
126
+ catch (e) {
127
+ throw e;
128
+ }
129
+ finally {
130
+ sub.commit();
131
+ order.push(refresh);
132
+ }
133
+ };
134
+ INFO.set(refresh, key);
135
+ return () => {
136
+ const value = sub ? state.get(key) : create();
137
+ if (value === undefined && required)
138
+ throw suspend(self, key);
139
+ return value;
140
+ };
166
141
  }
167
142
  function flush(control) {
168
143
  const pending = KEYS.get(control);
@@ -173,7 +148,7 @@ function flush(control) {
173
148
  const compute = pending
174
149
  .sort((a, b) => list.indexOf(b) - list.indexOf(a))
175
150
  .pop();
176
- const { key } = INFO.get(compute);
151
+ const key = INFO.get(compute);
177
152
  if (!control.frame.has(key))
178
153
  compute();
179
154
  }
@@ -213,60 +188,6 @@ function createValueEffect(callback) {
213
188
  };
214
189
  }
215
190
 
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
191
  const Oops$9 = issues({
271
192
  Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
272
193
  StrictUpdate: () => `Strict update() did not find pending updates.`,
@@ -483,7 +404,63 @@ class Model {
483
404
  }
484
405
  }
485
406
 
407
+ class Subscriber {
408
+ constructor(parent, onUpdate) {
409
+ this.onUpdate = onUpdate;
410
+ this.active = false;
411
+ this.dependant = new Set();
412
+ const proxy = create(parent.subject);
413
+ const using = new Map();
414
+ const listen = this.add = (key, value) => {
415
+ if (value !== undefined)
416
+ using.set(key, value);
417
+ else if (!using.has(key))
418
+ using.set(key, true);
419
+ };
420
+ LISTEN.set(proxy, listen);
421
+ defineProperty(proxy, LOCAL, { value: this });
422
+ defineProperty(this, "proxy", {
423
+ configurable: true,
424
+ get() {
425
+ if (UPDATE.has(proxy))
426
+ setTimeout(() => UPDATE.delete(proxy), 0);
427
+ return proxy;
428
+ }
429
+ });
430
+ const release = parent.addListener(key => {
431
+ if (!this.active)
432
+ return;
433
+ const handler = using.get(key);
434
+ let notify;
435
+ if (typeof handler == "function") {
436
+ const callback = handler();
437
+ if (callback === true)
438
+ notify = this.onUpdate(key, parent);
439
+ }
440
+ else if (handler === true)
441
+ notify = this.onUpdate(key, parent);
442
+ if (!notify)
443
+ return;
444
+ const notate = () => {
445
+ UPDATE.set(proxy, getUpdate(parent.subject).filter(k => using.has(k)));
446
+ };
447
+ parent.waiting.add(notate);
448
+ parent.waiting.add(notify);
449
+ });
450
+ this.commit = () => {
451
+ this.active = true;
452
+ this.dependant.forEach(x => x.commit());
453
+ return this.release;
454
+ };
455
+ this.release = () => {
456
+ this.dependant.forEach(x => x.release());
457
+ release();
458
+ };
459
+ }
460
+ }
461
+
486
462
  const UPDATE = new WeakMap();
463
+ const LISTEN = new WeakMap();
487
464
  const CONTROL = Symbol("CONTROL");
488
465
  function getUpdate(subject) {
489
466
  return UPDATE.get(subject);
@@ -514,9 +491,9 @@ class Controller {
514
491
  enumerable: false,
515
492
  set: this.ref(key),
516
493
  get() {
517
- const local = this[LOCAL];
518
- if (local)
519
- local.add(key);
494
+ const listen = LISTEN.get(this);
495
+ if (listen)
496
+ listen(key);
520
497
  return state.get(key);
521
498
  }
522
499
  });
@@ -643,9 +620,10 @@ function apply(fn, label) {
643
620
  get() {
644
621
  if (!state.has(key) && suspense)
645
622
  throw suspend(control, key);
623
+ const listen = LISTEN.get(this);
646
624
  const local = this[LOCAL];
647
- if (local)
648
- local.add(key);
625
+ if (listen)
626
+ listen(key);
649
627
  return onGet
650
628
  ? onGet(local)
651
629
  : state.get(key);
@@ -743,91 +721,116 @@ function act(task) {
743
721
  });
744
722
  }
745
723
 
746
- function ref(callback) {
747
- return apply(function ref(key) {
748
- const value = this.ref(key, callback && createValueEffect(callback));
749
- defineProperty(value, "current", {
750
- set: value,
751
- get: () => this.state.get(key)
724
+ function factoryMode(self, output, key, required) {
725
+ const { subject, state } = self;
726
+ let pending;
727
+ let error;
728
+ if (output instanceof Promise) {
729
+ state.set(key, undefined);
730
+ pending = output
731
+ .catch(err => error = err)
732
+ .then(val => {
733
+ state.set(key, val);
734
+ return val;
735
+ })
736
+ .finally(() => {
737
+ pending = undefined;
738
+ self.update(key);
752
739
  });
753
- return {
754
- value,
755
- explicit: true
756
- };
757
- });
740
+ }
741
+ state.set(key, output);
742
+ const suspend = () => {
743
+ if (required === false)
744
+ return undefined;
745
+ const issue = Oops$7.NotReady(subject, key);
746
+ Object.assign(pending, {
747
+ message: issue.message,
748
+ stack: issue.stack
749
+ });
750
+ throw pending;
751
+ };
752
+ return () => {
753
+ if (error)
754
+ throw error;
755
+ if (pending)
756
+ return suspend();
757
+ if (state.has(key))
758
+ return state.get(key);
759
+ };
758
760
  }
759
761
 
760
762
  const Oops$7 = issues({
761
- NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
763
+ // BadSource: (model, property, got) =>
764
+ // `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
765
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
766
+ ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
762
767
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
763
- Failed: (model, key) => `Generating initial value for ${model}.${key} failed.`
768
+ FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
769
+ Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
764
770
  });
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;
771
+ function get(arg0, arg1, arg2) {
772
+ return apply(function get(key) {
773
+ const { subject } = this;
774
+ const computeRequired = arg1 === true || arg2 === true;
775
+ const factoryRequired = arg1 !== false && arg2 !== false;
776
+ let getter;
777
+ // Easy mistake, using a peer, will always be unresolved.
778
+ if (typeof arg0 == "symbol")
779
+ throw Oops$7.PeerNotAllowed(subject, key);
780
+ // if(typeof arg0 == "function" && /^[A-Z]/.test(arg0.name))
781
+ // throw Oops.BadSource(subject, key, arg0);
782
+ this.state.set(key, undefined);
772
783
  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
- });
784
+ let source = this;
785
+ let setter;
786
+ if (typeof arg0 == "function") {
787
+ const result = mayRetry(() => arg0.call(subject, key, subject));
788
+ if (typeof result == "function")
789
+ setter = result;
790
+ else {
791
+ getter = factoryMode(this, result, key, factoryRequired);
792
+ return;
793
+ }
794
+ }
795
+ else if (arg0 instanceof Promise) {
796
+ getter = factoryMode(this, arg0, key, factoryRequired);
787
797
  return;
788
798
  }
789
- state.set(key, output);
790
- return output;
799
+ else if (typeof arg1 == "function") {
800
+ // replace source controller in-case it is different
801
+ source = ensure(arg0);
802
+ setter = arg1;
803
+ }
804
+ else
805
+ throw new Error(`Factory argument cannot be ${arg1}`);
806
+ getter = computeMode(this, source, setter, key, computeRequired);
791
807
  };
792
- if (required)
808
+ if (typeof arg0 == "function" && factoryRequired)
793
809
  try {
794
810
  init();
795
811
  }
796
812
  catch (err) {
797
- Oops$7.Failed(subject, key).warn();
813
+ Oops$7.ComputeFailed(subject, key).warn();
798
814
  throw err;
799
815
  }
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;
816
+ return () => {
817
+ if (!getter)
818
+ init();
819
+ return getter();
821
820
  };
822
- const set = typeof argument == "function"
823
- ? createValueEffect(argument)
824
- : (value) => {
825
- if (value === undefined && required)
826
- throw Oops$7.NonOptional(subject, key);
827
- };
821
+ });
822
+ }
823
+
824
+ function ref(callback) {
825
+ return apply(function ref(key) {
826
+ const value = this.ref(key, callback && createValueEffect(callback));
827
+ defineProperty(value, "current", {
828
+ set: value,
829
+ get: () => this.state.get(key)
830
+ });
828
831
  return {
829
- set,
830
- get
832
+ value,
833
+ explicit: true
831
834
  };
832
835
  });
833
836
  }
@@ -995,31 +998,77 @@ function createProxy(emit, watch) {
995
998
  const Parent = new WeakMap();
996
999
  const Oops$6 = issues({
997
1000
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
1001
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
998
1002
  });
999
1003
  function use$1(input, argument) {
1000
1004
  return apply(function use(key) {
1001
1005
  const { state, subject } = this;
1006
+ const required = argument !== false;
1007
+ let pending;
1008
+ let error;
1009
+ let get = (_local) => state.get(key);
1002
1010
  if (typeof input === "function")
1003
- input = "prototype" in input ? new input() : input();
1011
+ input = /^[A-Z]/.test(input.name)
1012
+ ? new input() : input;
1004
1013
  else if (input && typeof input !== "object")
1005
1014
  throw Oops$6.BadArgument(typeof input);
1006
1015
  if (input instanceof Map || input instanceof Set)
1007
1016
  return keyed(this, key, input);
1008
- function onUpdate(next) {
1017
+ const onUpdate = (next) => {
1009
1018
  state.set(key, next);
1010
1019
  if (next) {
1020
+ get = getRecursive(key, this);
1011
1021
  Parent.set(next, subject);
1012
1022
  ensure(next);
1013
1023
  }
1014
1024
  if (typeof argument == "function")
1015
1025
  argument(next);
1016
1026
  return true;
1017
- }
1018
- if (input)
1019
- onUpdate(input);
1027
+ };
1028
+ const suspend = () => {
1029
+ if (required === false)
1030
+ return;
1031
+ const issue = Oops$6.NotReady(subject, key);
1032
+ Object.assign(pending, {
1033
+ message: issue.message,
1034
+ stack: issue.stack
1035
+ });
1036
+ throw pending;
1037
+ };
1038
+ const initialize = () => {
1039
+ const output = typeof input == "function" ?
1040
+ mayRetry(() => input.call(subject, subject)) :
1041
+ input;
1042
+ if (output instanceof Promise) {
1043
+ pending = output
1044
+ .catch(err => error = err)
1045
+ .then(val => {
1046
+ onUpdate(val);
1047
+ return val;
1048
+ })
1049
+ .finally(() => {
1050
+ pending = undefined;
1051
+ this.update(key);
1052
+ });
1053
+ return;
1054
+ }
1055
+ onUpdate(output);
1056
+ };
1057
+ if (input !== undefined && required)
1058
+ initialize();
1020
1059
  return {
1021
1060
  set: onUpdate,
1022
- recursive: true
1061
+ get(local) {
1062
+ if (pending)
1063
+ return suspend();
1064
+ if (error)
1065
+ throw error;
1066
+ if (!state.has(key))
1067
+ initialize();
1068
+ if (pending)
1069
+ return suspend();
1070
+ return get(local);
1071
+ }
1023
1072
  };
1024
1073
  });
1025
1074
  }
@@ -1479,7 +1528,6 @@ exports.apply = apply;
1479
1528
  exports["default"] = MVC;
1480
1529
  exports.get = get;
1481
1530
  exports.has = has;
1482
- exports.put = put;
1483
1531
  exports.ref = ref;
1484
1532
  exports.set = set;
1485
1533
  exports.tap = tap;