@expressive/mvc 0.8.0 → 0.8.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.d.ts CHANGED
@@ -11,14 +11,19 @@ declare type Listener = {
11
11
  declare class Subscriber<T extends {} = any> {
12
12
  parent: Control<T>;
13
13
  onUpdate: Control.OnEvent;
14
+ static get<T extends {}>(from: T): Model.Subscriber<T> | undefined;
14
15
  proxy: T;
15
- commit: () => Callback;
16
16
  release: Callback;
17
+ latest?: Model.Event<T>[];
17
18
  active: boolean;
18
19
  dependant: Set<Listener>;
19
- add: (key: any, value?: boolean | Callback) => void;
20
- static get<T extends {}>(from: T): Subscriber<T> | undefined;
20
+ watch: Map<any, boolean | (() => true | void)>;
21
21
  constructor(parent: Control<T>, onUpdate: Control.OnEvent);
22
+ get using(): Model.Values<T>;
23
+ assign(apply: Model.Compat<T>, keys?: any): void;
24
+ add(key: any, value?: boolean | Callback): void;
25
+ commit: () => () => void;
26
+ private event;
22
27
  }
23
28
 
24
29
  declare namespace Model {
@@ -133,6 +138,7 @@ declare class Control<T extends {} = any> {
133
138
  frame: Set<string>;
134
139
  waiting: Set<Callback>;
135
140
  followers: Set<Model.Control.OnEvent<any>>;
141
+ latest?: Model.Event<T>[];
136
142
  constructor(subject: T);
137
143
  subscribe(callback: Control.OnEvent<T>): Model.Subscriber<T>;
138
144
  watch(key: keyof T & string): void;
@@ -179,7 +185,7 @@ declare namespace Instruction {
179
185
  * Run instruction as controller sets itself up.
180
186
  * This will specialize the behavior of a given property.
181
187
  */
182
- declare function add<T = any>(instruction: Instruction<T>, name?: string): T;
188
+ declare function add<T = any>(instruction: Instruction<any>, label?: string): T;
183
189
 
184
190
  declare type Async<T = any> = (...args: any[]) => Promise<T>;
185
191
  /**
@@ -419,15 +425,15 @@ declare class MVC extends Model {
419
425
  static meta<T, M extends Class>(this: M, from: (this: M, state: M) => T, expect?: boolean): T;
420
426
  }
421
427
 
422
- declare const LOCAL: unique symbol;
423
- declare const STATE: unique symbol;
424
- declare const UPDATE: unique symbol;
425
- declare const CONTROL: unique symbol;
428
+ declare const LOCAL = "$debug local";
429
+ declare const STATE = "$debug state";
430
+ declare const UPDATE = "$debug update";
431
+ declare const CONTROL = "$debug controller";
426
432
  declare const Debug: {
427
- readonly CONTROL: typeof CONTROL;
428
- readonly LOCAL: typeof LOCAL;
429
- readonly STATE: typeof STATE;
430
- readonly UPDATE: typeof UPDATE;
433
+ readonly CONTROL: "$debug controller";
434
+ readonly LOCAL: "$debug local";
435
+ readonly STATE: "$debug state";
436
+ readonly UPDATE: "$debug update";
431
437
  };
432
438
  declare type Debug<T extends {}> = T & {
433
439
  /** Controller for this instance. */
@@ -573,12 +579,9 @@ declare function tap<T extends Model>(Type: Model.Type<T>, required?: boolean):
573
579
  declare function useFrom<T extends {}, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
574
580
  declare function useFrom<T extends {}, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect?: boolean): R;
575
581
 
576
- declare function useModel<T extends Model>(source: Model.Type<T>, watch: Model.Field<T>[], callback?: (instance: T) => void): T;
577
- declare function useModel<T extends Model>(source: Model.Type<T>, callback?: (instance: T) => void): T;
578
- declare function useModel<T extends Model>(source: Model.Type<T>, apply: Model.Compat<T>, keys?: Model.Event<T>[]): T;
579
- declare function useModel<T extends Model>(source: () => T, watch?: Model.Field<T>[], callback?: (instance: T) => void): T;
580
- declare function useModel<T extends Model>(source: () => T, callback?: (instance: T) => void): T;
581
- declare function useModel<T extends Model>(source: () => T, apply: Model.Compat<T>, keys?: Model.Event<T>[]): T;
582
+ declare function useModel<T extends Model>(source: Model.Type<T> | (() => T), callback?: (instance: T) => void): T;
583
+ declare function useModel<T extends Model>(source: Model.Type<T> | (() => T), watch: Model.Field<T>[], callback?: (instance: T) => void): T;
584
+ declare function useModel<T extends Model>(source: Model.Type<T> | (() => T), apply: Model.Compat<T>, keys?: Model.Event<T>[]): T;
582
585
 
583
586
  declare namespace useTap {
584
587
  type Source<T extends Model> = T | Model.Type<T> | (() => T | Model.Type<T>);
package/lib/index.esm.js CHANGED
@@ -17,7 +17,7 @@ function issues(register) {
17
17
  return Library;
18
18
  }
19
19
 
20
- const Oops$c = issues({
20
+ const Oops$d = issues({
21
21
  BadCallback: () => `Callback for effect-callback may only return a function.`
22
22
  });
23
23
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -30,7 +30,7 @@ function createEffect(callback) {
30
30
  if (unSet instanceof Promise)
31
31
  unSet = undefined;
32
32
  if (unSet && typeof unSet !== "function")
33
- throw Oops$c.BadCallback();
33
+ throw Oops$d.BadCallback();
34
34
  };
35
35
  }
36
36
  function createValueEffect(callback) {
@@ -46,15 +46,15 @@ function createValueEffect(callback) {
46
46
  if (typeof out == "function")
47
47
  unSet = out;
48
48
  else
49
- throw Oops$c.BadCallback();
49
+ throw Oops$d.BadCallback();
50
50
  };
51
51
  }
52
52
 
53
- const Oops$b = issues({
53
+ const Oops$c = issues({
54
54
  NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
55
55
  });
56
56
  function suspend(source, key) {
57
- const error = Oops$b.NotReady(source.subject, key);
57
+ const error = Oops$c.NotReady(source.subject, key);
58
58
  const promise = new Promise(resolve => {
59
59
  const release = source.addListener(forKey => {
60
60
  if (forKey == key)
@@ -94,7 +94,7 @@ function mayRetry(fn) {
94
94
  return compute();
95
95
  }
96
96
 
97
- const Oops$a = issues({
97
+ const Oops$b = issues({
98
98
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
99
99
  });
100
100
  const INFO = new WeakMap();
@@ -111,7 +111,7 @@ function computeMode(self, source, setter, key, required) {
111
111
  return setter.call(sub.proxy, sub.proxy);
112
112
  }
113
113
  catch (err) {
114
- Oops$a.Failed(subject, key, initial).warn();
114
+ Oops$b.Failed(subject, key, initial).warn();
115
115
  throw err;
116
116
  }
117
117
  };
@@ -187,20 +187,21 @@ class Subscriber {
187
187
  this.onUpdate = onUpdate;
188
188
  this.active = false;
189
189
  this.dependant = new Set();
190
- const proxy = create(parent.subject);
191
- const using = new Map();
192
- REGISTER$1.set(proxy, this);
193
- defineProperty(this, "proxy", {
194
- configurable: true,
195
- get() {
196
- hasUpdate(proxy);
197
- return proxy;
198
- }
199
- });
200
- const release = parent.addListener(key => {
190
+ this.watch = new Map();
191
+ this.commit = () => {
192
+ const release = this.parent.addListener(this.event);
193
+ this.active = true;
194
+ this.dependant.forEach(x => x.commit());
195
+ return this.release = () => {
196
+ this.dependant.forEach(x => x.release());
197
+ release();
198
+ };
199
+ };
200
+ this.event = (key) => {
201
201
  if (!this.active)
202
202
  return;
203
- const handler = using.get(key);
203
+ const { parent, watch } = this;
204
+ const handler = watch.get(key);
204
205
  let notify;
205
206
  if (typeof handler == "function") {
206
207
  const callback = handler();
@@ -211,49 +212,48 @@ class Subscriber {
211
212
  notify = this.onUpdate(key, parent);
212
213
  if (!notify)
213
214
  return;
214
- parent.waiting.add(() => addUpdate(proxy, using));
215
+ parent.waiting.add(() => {
216
+ this.latest = parent.latest.filter(k => watch.has(k));
217
+ });
215
218
  parent.waiting.add(notify);
216
- });
217
- this.add = (key, value) => {
218
- if (value !== undefined)
219
- using.set(key, value);
220
- else if (!using.has(key))
221
- using.set(key, true);
222
- };
223
- this.commit = () => {
224
- this.active = true;
225
- this.dependant.forEach(x => x.commit());
226
- return this.release;
227
- };
228
- this.release = () => {
229
- this.dependant.forEach(x => x.release());
230
- release();
231
219
  };
220
+ const proxy = create(parent.subject);
221
+ const reset = () => this.latest = undefined;
222
+ REGISTER$1.set(proxy, this);
223
+ defineProperty(this, "proxy", {
224
+ configurable: true,
225
+ get() {
226
+ setTimeout(reset, 0);
227
+ return proxy;
228
+ }
229
+ });
232
230
  }
233
231
  static get(from) {
234
232
  return REGISTER$1.get(from);
235
233
  }
234
+ get using() {
235
+ return Array.from(this.watch.keys());
236
+ }
237
+ assign(apply, keys) {
238
+ const { waiting, subject } = this.parent;
239
+ this.active = false;
240
+ if (typeof keys !== "object")
241
+ keys = getOwnPropertyNames(subject);
242
+ for (const key of keys)
243
+ if (key in apply)
244
+ subject[key] = apply[key];
245
+ waiting.add(() => this.active = true);
246
+ }
247
+ add(key, value) {
248
+ if (value !== undefined)
249
+ this.watch.set(key, value);
250
+ else if (!this.watch.has(key))
251
+ this.watch.set(key, true);
252
+ }
236
253
  }
237
254
 
238
255
  const REGISTER = new WeakMap();
239
- const UPDATE$1 = new WeakMap();
240
- function getUpdate(subject) {
241
- return UPDATE$1.get(subject);
242
- }
243
- function setUpdate(subject, keys) {
244
- UPDATE$1.set(subject, Array.from(keys));
245
- setTimeout(() => UPDATE$1.delete(subject), 0);
246
- }
247
- /** Ensure a local update is dropped after use. */
248
- function hasUpdate(proxy) {
249
- if (UPDATE$1.has(proxy))
250
- setTimeout(() => UPDATE$1.delete(proxy), 0);
251
- }
252
- /** Set local update for a subscribed context. */
253
- function addUpdate(proxy, using) {
254
- const parent = UPDATE$1.get(getPrototypeOf(proxy));
255
- UPDATE$1.set(proxy, parent.filter(k => using.has(k)));
256
- }
256
+ const PENDING = new Map();
257
257
  class Control {
258
258
  constructor(subject) {
259
259
  this.subject = subject;
@@ -320,7 +320,8 @@ class Control {
320
320
  else if (!frame.size)
321
321
  setTimeout(() => {
322
322
  flush(this);
323
- setUpdate(this.subject, frame);
323
+ this.latest = Array.from(frame);
324
+ setTimeout(() => this.latest = undefined, 0);
324
325
  frame.clear();
325
326
  for (const notify of waiting)
326
327
  try {
@@ -370,13 +371,16 @@ class Control {
370
371
  }
371
372
  }
372
373
 
373
- const PENDING = new Map();
374
- function add(fn, label) {
375
- const name = label || fn.name || "pending";
374
+ /**
375
+ * Run instruction as controller sets itself up.
376
+ * This will specialize the behavior of a given property.
377
+ */
378
+ function add(instruction, label) {
379
+ const name = label || instruction.name || "pending";
376
380
  const placeholder = Symbol(`${name} instruction`);
377
381
  function setup(key) {
378
382
  const { subject, state } = this;
379
- let output = fn.call(this, key, this);
383
+ let output = instruction.call(this, key, this);
380
384
  if (typeof output == "function")
381
385
  output = { get: output };
382
386
  if (typeof output != "object")
@@ -477,7 +481,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
477
481
  });
478
482
  }
479
483
 
480
- const Oops$9 = issues({
484
+ const Oops$a = issues({
481
485
  DuplicatePending: (key) => `Invoked action ${key} but one is already active.`
482
486
  });
483
487
  function act(task) {
@@ -485,7 +489,7 @@ function act(task) {
485
489
  let pending = false;
486
490
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
487
491
  if (pending)
488
- return Promise.reject(Oops$9.DuplicatePending(key));
492
+ return Promise.reject(Oops$a.DuplicatePending(key));
489
493
  pending = true;
490
494
  this.update(key);
491
495
  try {
@@ -507,6 +511,9 @@ function act(task) {
507
511
  });
508
512
  }
509
513
 
514
+ const Oops$9 = issues({
515
+ NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
516
+ });
510
517
  function factoryMode(self, output, key, required) {
511
518
  const { subject, state } = self;
512
519
  let pending;
@@ -528,7 +535,7 @@ function factoryMode(self, output, key, required) {
528
535
  const suspend = () => {
529
536
  if (required === false)
530
537
  return undefined;
531
- const issue = Oops$8.NotReady(subject, key);
538
+ const issue = Oops$9.NotReady(subject, key);
532
539
  assign(pending, {
533
540
  message: issue.message,
534
541
  stack: issue.stack
@@ -546,12 +553,8 @@ function factoryMode(self, output, key, required) {
546
553
  }
547
554
 
548
555
  const Oops$8 = issues({
549
- // BadSource: (model, property, got) =>
550
- // `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
551
556
  PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
552
557
  ComputeFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
553
- NotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
554
- FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`,
555
558
  Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
556
559
  });
557
560
  function get(arg0, arg1, arg2) {
@@ -929,7 +932,7 @@ class Model {
929
932
  }
930
933
  else if (keys.includes(key)) {
931
934
  removeListener();
932
- return () => resolve(single ? control.state.get(key) : getUpdate(this));
935
+ return () => resolve(single ? control.state.get(key) : control.latest);
933
936
  }
934
937
  });
935
938
  });
@@ -949,7 +952,7 @@ class Model {
949
952
  catch (e) { }
950
953
  const callback = single
951
954
  ? () => { argument.call(this, control.state.get(select), select); }
952
- : () => { argument.call(this, getUpdate(this)); };
955
+ : () => { argument.call(this, control.latest); };
953
956
  const onEvent = once
954
957
  ? () => {
955
958
  removeListener();
@@ -1034,7 +1037,7 @@ class Model {
1034
1037
  throw Oops$5.NoChaining();
1035
1038
  if (frame.size || arg1 !== false)
1036
1039
  waiting.add(() => {
1037
- callback(getUpdate(this));
1040
+ callback(control.latest);
1038
1041
  });
1039
1042
  else
1040
1043
  callback(false);
@@ -1227,14 +1230,8 @@ function useModel(source, arg, arg2) {
1227
1230
  return instance;
1228
1231
  }
1229
1232
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1230
- if (typeof arg == "object") {
1231
- local.active = false;
1232
- if (typeof arg2 !== "object")
1233
- arg2 = getOwnPropertyNames(instance);
1234
- for (const key of arg2)
1235
- if (key in arg)
1236
- instance[key] = arg[key];
1237
- }
1233
+ if (typeof arg == "object")
1234
+ local.assign(arg, arg2);
1238
1235
  React.useLayoutEffect(() => {
1239
1236
  local.commit();
1240
1237
  return () => {
@@ -1291,10 +1288,10 @@ function useTap(source, arg1, arg2) {
1291
1288
  const instance = useMemo(() => {
1292
1289
  if (typeof source == "object")
1293
1290
  return () => source;
1294
- if (MVC.isTypeof(source))
1295
- return () => source.get(true);
1296
1291
  if (Model.isTypeof(source))
1297
- return () => useContext(source);
1292
+ return "get" in source
1293
+ ? () => source.get(true)
1294
+ : () => useContext(source);
1298
1295
  return source;
1299
1296
  }, [])();
1300
1297
  if (typeof arg1 == "function")
@@ -1324,10 +1321,10 @@ class MVC extends Model {
1324
1321
  }
1325
1322
  }
1326
1323
 
1327
- const LOCAL = Symbol("LOCAL");
1328
- const STATE = Symbol("STATE");
1329
- const UPDATE = Symbol("UPDATE");
1330
- const CONTROL = Symbol("CONTROL");
1324
+ const LOCAL = "$debug local";
1325
+ const STATE = "$debug state";
1326
+ const UPDATE = "$debug update";
1327
+ const CONTROL = "$debug controller";
1331
1328
  Object.defineProperties(Model.prototype, {
1332
1329
  [CONTROL]: {
1333
1330
  get() {
@@ -1349,7 +1346,8 @@ Object.defineProperties(Model.prototype, {
1349
1346
  },
1350
1347
  [UPDATE]: {
1351
1348
  get() {
1352
- return getUpdate(this);
1349
+ const source = Subscriber.get(this) || Control.get(this);
1350
+ return source && source.latest;
1353
1351
  }
1354
1352
  }
1355
1353
  });