@expressive/mvc 0.4.3 → 0.4.6

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
@@ -35,7 +35,7 @@ declare class Subscriber<T extends Stateful = any> {
35
35
  release: Callback;
36
36
  active: boolean;
37
37
  dependant: Set<Listener>;
38
- add: (key: any, value?: Callback) => void;
38
+ add: (key: any, value?: boolean | Callback) => void;
39
39
  constructor(parent: Controller<T>, onUpdate: Controller.OnEvent);
40
40
  }
41
41
 
@@ -220,6 +220,7 @@ declare namespace Instruction {
220
220
  get?: Getter<T>;
221
221
  set?: Setter<T> | false;
222
222
  suspense?: boolean;
223
+ destroy?: () => void;
223
224
  }
224
225
  interface ExplicitDescriptor extends PropertyDescriptor {
225
226
  explicit: true;
@@ -238,7 +239,7 @@ declare namespace Instruction {
238
239
  */
239
240
  declare function apply<T = any>(instruction: Instruction<T>, name?: string): T;
240
241
 
241
- declare namespace from {
242
+ declare namespace get {
242
243
  type Function<T, O = any> = (this: O, on: O) => T;
243
244
  type Factory<T, O = any> = (key: string) => Function<T, O>;
244
245
  type Getter = (controller: Controller, key: string) => any;
@@ -250,16 +251,16 @@ declare namespace from {
250
251
  * @param compute - Compute function. Bound to a subscriber-proxy of source, returns output value. Will update automatically as input values change.
251
252
  * @param suspend - Value will throw suspense when evaulating to undefined.
252
253
  */
253
- declare function from<R, T>(source: T, compute: (this: T, on: T) => R, suspend: true): Exclude<R, undefined>;
254
- declare function from<R, T>(source: T, compute: (this: T, on: T) => R, suspend?: boolean): R;
254
+ declare function get<R, T>(source: T, compute: (this: T, on: T) => R, suspend: true): Exclude<R, undefined>;
255
+ declare function get<R, T>(source: T, compute: (this: T, on: T) => R, suspend?: boolean): R;
255
256
  /**
256
257
  * Implement a computed value; output is returned by function from provided factory.
257
258
  *
258
259
  * @param compute - Factory function to generate a getter to subscribe dependancies.
259
260
  * @param suspend - Value will throw suspense when evaulating to undefined.
260
261
  */
261
- declare function from<R, T>(compute: (property: string) => (this: T, state: T) => R, suspend: true): Exclude<R, undefined>;
262
- declare function from<R, T>(compute: (property: string) => (this: T, state: T) => R, suspend?: boolean): R;
262
+ declare function get<R, T>(compute: (property: string) => (this: T, state: T) => R, suspend: true): Exclude<R, undefined>;
263
+ declare function get<R, T>(compute: (property: string) => (this: T, state: T) => R, suspend?: boolean): R;
263
264
 
264
265
  declare namespace ref {
265
266
  type Callback<T, S = any> = (this: S, argument: T) => ((next: T) => void) | Promise<void> | void | boolean;
@@ -356,6 +357,10 @@ declare function set<T, S>(value: undefined, onUpdate: set.Callback<T, S>): T;
356
357
  declare function set<T>(value: T, onUpdate: set.Callback<T>): T;
357
358
  declare function set<T, S>(value: T, onUpdate: set.Callback<T, S>): T;
358
359
 
360
+ declare function use(initial: Set<unknown>): Set<any>;
361
+ declare function use(initial: Map<unknown, unknown>): Map<any, any>;
362
+ declare function use(from: () => Set<unknown>): Set<any>;
363
+ declare function use(from: () => Map<unknown, unknown>): Map<any, any>;
359
364
  /**
360
365
  * Create a placeholder for specified Model type.
361
366
  */
@@ -622,4 +627,4 @@ declare function useTap<T extends Stateful, K extends Model.Field<T>>(source: T
622
627
  declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
623
628
  declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect?: boolean): R;
624
629
 
625
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, from, parent, put, ref, set, tap, use, useFrom, useLocal, useModel, useTap };
630
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, get as from, get, parent, put, ref, set, tap, use, useFrom, useLocal, useModel, useTap };
package/lib/index.esm.js CHANGED
@@ -164,11 +164,10 @@ const Oops$a = issues({
164
164
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
165
165
  });
166
166
  const INFO = new WeakMap();
167
- const USED = new WeakMap();
168
167
  const KEYS = new WeakMap();
169
- function from(source, arg1, arg2) {
168
+ const ORDER = new WeakMap();
169
+ function get(source, arg1, arg2) {
170
170
  return apply(function from(key) {
171
- const parent = this;
172
171
  const { subject, state } = this;
173
172
  const required = arg2 === true || arg1 === true;
174
173
  let getSource;
@@ -189,14 +188,13 @@ function from(source, arg1, arg2) {
189
188
  else
190
189
  throw Oops$a.BadSource(subject, key, source);
191
190
  const setter = arg1;
192
- const info = { key, parent, priority: 1 };
191
+ const info = { key, parent: this };
193
192
  let sub;
194
- let register = USED.get(parent);
195
- if (!register)
196
- USED.set(parent, register = new Map());
193
+ let order = ORDER.get(this);
194
+ if (!order)
195
+ ORDER.set(this, order = []);
197
196
  state.set(key, undefined);
198
- register.set(key, info);
199
- function compute(initial) {
197
+ const compute = (initial) => {
200
198
  try {
201
199
  return setter.call(sub.proxy, sub.proxy);
202
200
  }
@@ -204,8 +202,8 @@ function from(source, arg1, arg2) {
204
202
  Oops$a.Failed(subject, key, initial).warn();
205
203
  throw err;
206
204
  }
207
- }
208
- function update() {
205
+ };
206
+ const refreshValue = () => {
209
207
  let value;
210
208
  try {
211
209
  value = compute(false);
@@ -215,26 +213,25 @@ function from(source, arg1, arg2) {
215
213
  }
216
214
  finally {
217
215
  if (state.get(key) !== value) {
218
- parent.state.set(key, value);
219
- parent.update(key);
216
+ state.set(key, value);
217
+ this.update(key);
220
218
  return value;
221
219
  }
222
220
  }
223
- }
224
- function defer(_key, from) {
225
- let pending = KEYS.get(from);
226
- if (!pending)
227
- KEYS.set(from, pending = []);
228
- if (info.parent !== from)
229
- update();
230
- else {
231
- const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
232
- pending.splice(after + 1, 0, update);
221
+ };
222
+ const dependancyDidUpdate = (_key, source) => {
223
+ if (source !== this) {
224
+ refreshValue();
225
+ return;
233
226
  }
234
- }
235
- function create() {
227
+ let pending = KEYS.get(this);
228
+ if (!pending)
229
+ KEYS.set(this, pending = []);
230
+ pending.push(refreshValue);
231
+ };
232
+ const create = () => {
236
233
  const control = getSource();
237
- sub = control.subscribe(defer);
234
+ sub = control.subscribe(dependancyDidUpdate);
238
235
  try {
239
236
  const value = compute(true);
240
237
  state.set(key, value);
@@ -245,14 +242,10 @@ function from(source, arg1, arg2) {
245
242
  }
246
243
  finally {
247
244
  sub.commit();
248
- for (const key of control.state.keys()) {
249
- const peer = register.get(key);
250
- if (peer && peer.priority >= info.priority)
251
- info.priority = peer.priority + 1;
252
- }
245
+ order.push(refreshValue);
253
246
  }
254
- }
255
- INFO.set(update, info);
247
+ };
248
+ INFO.set(refreshValue, info);
256
249
  return () => {
257
250
  const value = sub ? state.get(key) : create();
258
251
  if (value === undefined && required)
@@ -265,8 +258,11 @@ function flush(control) {
265
258
  const pending = KEYS.get(control);
266
259
  if (!pending)
267
260
  return;
261
+ const list = ORDER.get(control);
268
262
  while (pending.length) {
269
- const compute = pending.shift();
263
+ const compute = pending
264
+ .sort((a, b) => list.indexOf(b) - list.indexOf(a))
265
+ .pop();
270
266
  const { key } = INFO.get(compute);
271
267
  if (!control.frame.has(key))
272
268
  compute();
@@ -291,18 +287,16 @@ class Subscriber {
291
287
  }
292
288
  });
293
289
  const release = parent.addListener(key => {
294
- if (!this.active || !using.has(key))
290
+ if (!this.active)
295
291
  return;
296
292
  const handler = using.get(key);
297
293
  let notify;
298
294
  if (typeof handler == "function") {
299
295
  const callback = handler();
300
- if (typeof callback == "function")
301
- notify = callback;
302
- else if (callback === true)
296
+ if (callback === true)
303
297
  notify = this.onUpdate(key, parent);
304
298
  }
305
- else
299
+ else if (handler === true)
306
300
  notify = this.onUpdate(key, parent);
307
301
  const getWhy = () => {
308
302
  const update = getUpdate(parent.subject);
@@ -324,7 +318,7 @@ class Subscriber {
324
318
  release();
325
319
  };
326
320
  this.add = (key, value) => {
327
- if (value)
321
+ if (value !== undefined)
328
322
  using.set(key, value);
329
323
  else if (!using.has(key))
330
324
  using.set(key, true);
@@ -398,17 +392,15 @@ class Controller {
398
392
  else if (!frame.size)
399
393
  setTimeout(() => {
400
394
  flush(this);
401
- const callback = Array.from(waiting);
402
- const keys = Array.from(frame);
403
- frame.clear();
404
- waiting.clear();
405
- UPDATE.set(subject, keys);
395
+ UPDATE.set(subject, Array.from(frame));
406
396
  setTimeout(() => {
407
397
  UPDATE.delete(subject);
408
398
  }, 0);
409
- for (const cb of callback)
399
+ frame.clear();
400
+ for (const notify of waiting)
410
401
  try {
411
- cb();
402
+ waiting.delete(notify);
403
+ notify();
412
404
  }
413
405
  catch (err) {
414
406
  console.error(err);
@@ -659,6 +651,14 @@ function apply(fn, label) {
659
651
  const desc = output;
660
652
  if ("value" in desc)
661
653
  state.set(key, desc.value);
654
+ if ("destroy" in output) {
655
+ const { destroy } = output;
656
+ if (destroy)
657
+ this.addListener((key) => {
658
+ if (key == null)
659
+ destroy();
660
+ });
661
+ }
662
662
  const { get: onGet, set: onSet, enumerable, suspense } = desc;
663
663
  const set = onSet !== false
664
664
  ? this.ref(key, onSet)
@@ -936,172 +936,100 @@ function parent(Expects, required) {
936
936
  });
937
937
  }
938
938
 
939
- const ANY$1 = Symbol("any");
940
- function managedMap(control, property, initial) {
941
- const managed = new ManagedMap(initial, emit);
939
+ const ANY = Symbol("any");
940
+ function keyed(control, property, initial) {
941
+ const managed = new Stateful(initial, emit);
942
942
  const context = new WeakMap();
943
- const lastUpdate = new Set();
943
+ const observers = new Set();
944
+ const frozen = new Set();
944
945
  function reset() {
945
- lastUpdate.clear();
946
+ frozen.clear();
946
947
  }
947
948
  function emit(key) {
948
- lastUpdate.add(key);
949
- control.update(property);
950
949
  control.waiting.add(reset);
950
+ control.update(property);
951
+ observers.forEach(notify => notify(key));
951
952
  }
952
- function setValue(next) {
953
- control.state.set(property, next);
954
- managed.source = next;
955
- emit(ANY$1);
956
- }
957
- function getValue(local) {
958
- if (!local)
959
- return managed;
960
- if (context.has(local))
961
- return context.get(local);
962
- const refresh = local.onUpdate(property, control);
953
+ function init(local) {
963
954
  const proxy = create(managed);
964
955
  const using = new Set();
965
- const update = () => {
966
- if (using.has(ANY$1) || lastUpdate.has(ANY$1) && using.size)
967
- refresh();
968
- else
969
- for (const key of lastUpdate)
970
- if (using.has(key)) {
971
- refresh();
972
- break;
973
- }
974
- };
975
- local.add(property, () => update);
956
+ function onEvent(key) {
957
+ if (frozen.has(local))
958
+ return;
959
+ if (key === ANY || using.has(key) || using.has(ANY)) {
960
+ const refresh = local.onUpdate(property, control);
961
+ if (refresh) {
962
+ frozen.add(local);
963
+ refresh();
964
+ }
965
+ }
966
+ }
976
967
  context.set(local, proxy);
977
968
  proxy.watch = (key) => {
978
969
  if (!local.active)
979
970
  using.add(key);
980
971
  };
972
+ local.add(property, false);
973
+ local.dependant.add({
974
+ commit() {
975
+ if (using.size === 0)
976
+ using.add(ANY);
977
+ observers.add(onEvent);
978
+ },
979
+ release() {
980
+ observers.delete(onEvent);
981
+ }
982
+ });
981
983
  return proxy;
982
984
  }
983
985
  return {
984
986
  value: initial,
985
- set: setValue,
986
- get: getValue
987
+ get(local) {
988
+ return local ?
989
+ context.get(local) || init(local) :
990
+ managed;
991
+ },
992
+ set(next) {
993
+ managed.source = next;
994
+ control.state.set(property, next);
995
+ emit(ANY);
996
+ },
997
+ destroy() {
998
+ observers.clear();
999
+ }
987
1000
  };
988
1001
  }
989
- class ManagedMap {
1002
+ class Stateful {
990
1003
  constructor(source, emit) {
991
1004
  this.source = source;
992
1005
  this.emit = emit;
1006
+ if (source instanceof Set)
1007
+ assign(this, {
1008
+ add(key) {
1009
+ source.add(key);
1010
+ emit(key);
1011
+ return this;
1012
+ }
1013
+ });
1014
+ else
1015
+ assign(this, {
1016
+ get(key) {
1017
+ this.watch(key);
1018
+ return source.get(key);
1019
+ },
1020
+ set(key, value) {
1021
+ source.set(key, value);
1022
+ emit(key);
1023
+ return this;
1024
+ }
1025
+ });
993
1026
  }
994
1027
  watch(_key) { }
995
- set(key, value) {
996
- this.source.set(key, value);
997
- this.emit(key);
998
- return this;
999
- }
1000
- ;
1001
- delete(key) {
1002
- this.emit(key);
1003
- return this.source.delete(key);
1004
- }
1005
- ;
1006
- clear() {
1007
- this.emit(ANY$1);
1008
- this.source.clear();
1009
- }
1010
- get(key) {
1011
- this.watch(key);
1012
- return this.source.get(key);
1013
- }
1014
- ;
1015
1028
  has(key) {
1016
1029
  this.watch(key);
1017
1030
  return this.source.has(key);
1018
1031
  }
1019
1032
  ;
1020
- values() {
1021
- this.watch(ANY$1);
1022
- return this.source.values();
1023
- }
1024
- keys() {
1025
- this.watch(ANY$1);
1026
- return this.source.keys();
1027
- }
1028
- entries() {
1029
- this.watch(ANY$1);
1030
- return this.source.entries();
1031
- }
1032
- ;
1033
- [Symbol.iterator]() {
1034
- this.watch(ANY$1);
1035
- return this.source[Symbol.iterator]();
1036
- }
1037
- ;
1038
- get size() {
1039
- this.watch(ANY$1);
1040
- return this.source.size;
1041
- }
1042
- }
1043
-
1044
- const ANY = Symbol("any");
1045
- function managedSet(control, property, initial) {
1046
- const managed = new ManagedSet(initial, emit);
1047
- const context = new WeakMap();
1048
- const lastUpdate = new Set();
1049
- function reset() {
1050
- lastUpdate.clear();
1051
- }
1052
- function emit(key) {
1053
- lastUpdate.add(key);
1054
- control.update(property);
1055
- control.waiting.add(reset);
1056
- }
1057
- function setValue(next) {
1058
- managed.source = next;
1059
- control.state.set(property, next);
1060
- emit(ANY);
1061
- }
1062
- function getValue(local) {
1063
- if (!local)
1064
- return managed;
1065
- if (context.has(local))
1066
- return context.get(local);
1067
- const refresh = local.onUpdate(property, control);
1068
- const proxy = create(managed);
1069
- const using = new Set();
1070
- const update = () => {
1071
- if (using.has(ANY) || lastUpdate.has(ANY) && using.size)
1072
- refresh();
1073
- else
1074
- for (const key of lastUpdate)
1075
- if (using.has(key)) {
1076
- refresh();
1077
- break;
1078
- }
1079
- };
1080
- local.add(property, () => update);
1081
- context.set(local, proxy);
1082
- proxy.watch = (key) => {
1083
- if (!local.active)
1084
- using.add(key);
1085
- };
1086
- return proxy;
1087
- }
1088
- return {
1089
- value: initial,
1090
- set: setValue,
1091
- get: getValue
1092
- };
1093
- }
1094
- class ManagedSet {
1095
- constructor(source, emit) {
1096
- this.source = source;
1097
- this.emit = emit;
1098
- }
1099
- watch(_key) { }
1100
- add(key) {
1101
- this.emit(key);
1102
- return this.source.add(key);
1103
- }
1104
- ;
1105
1033
  delete(key) {
1106
1034
  this.emit(key);
1107
1035
  return this.source.delete(key);
@@ -1111,33 +1039,28 @@ class ManagedSet {
1111
1039
  this.emit(ANY);
1112
1040
  this.source.clear();
1113
1041
  }
1114
- has(key) {
1115
- this.watch(key);
1116
- return this.source.has(key);
1042
+ keys() {
1043
+ this.watch(ANY);
1044
+ return this.source.keys();
1117
1045
  }
1118
- ;
1119
1046
  values() {
1120
1047
  this.watch(ANY);
1121
1048
  return this.source.values();
1122
1049
  }
1123
- keys() {
1124
- this.watch(ANY);
1125
- return this.source.keys();
1126
- }
1127
1050
  entries() {
1128
1051
  this.watch(ANY);
1129
1052
  return this.source.entries();
1130
1053
  }
1131
1054
  ;
1055
+ get size() {
1056
+ this.watch(ANY);
1057
+ return this.source.size;
1058
+ }
1132
1059
  [Symbol.iterator]() {
1133
1060
  this.watch(ANY);
1134
1061
  return this.source[Symbol.iterator]();
1135
1062
  }
1136
1063
  ;
1137
- get size() {
1138
- this.watch(ANY);
1139
- return this.source.size;
1140
- }
1141
1064
  }
1142
1065
 
1143
1066
  const Oops$5 = issues({
@@ -1152,10 +1075,8 @@ function use$1(input, argument) {
1152
1075
  : input();
1153
1076
  else if (input && typeof input !== "object")
1154
1077
  throw Oops$5.BadArgument(typeof input);
1155
- if (input instanceof Map)
1156
- return managedMap(this, key, input);
1157
- if (input instanceof Set)
1158
- return managedSet(this, key, input);
1078
+ if (input instanceof Map || input instanceof Set)
1079
+ return keyed(this, key, input);
1159
1080
  function onUpdate(next) {
1160
1081
  state.set(key, next);
1161
1082
  if (next) {
@@ -1598,5 +1519,5 @@ class Global extends MVC {
1598
1519
  }
1599
1520
  }
1600
1521
 
1601
- export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, from, parent, put, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1522
+ export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, get as from, get, parent, put, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
1602
1523
  //# sourceMappingURL=index.esm.js.map