@expressive/mvc 1.0.0-beta.17 → 1.0.0-beta.20

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
@@ -24,7 +24,7 @@ function issues(register) {
24
24
  return Library;
25
25
  }
26
26
 
27
- const Oops$8 = issues({
27
+ const Oops$9 = issues({
28
28
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
29
29
  });
30
30
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, getOwnPropertySymbols, keys: keys$1, values } = Object;
@@ -53,7 +53,7 @@ function createEffect(callback) {
53
53
  if (unSet instanceof Promise)
54
54
  unSet = undefined;
55
55
  if (unSet && typeof unSet !== "function")
56
- throw Oops$8.BadEffectCallback();
56
+ throw Oops$9.BadEffectCallback();
57
57
  };
58
58
  }
59
59
  function createValueEffect(callback) {
@@ -69,7 +69,7 @@ function createValueEffect(callback) {
69
69
  if (typeof out == "function")
70
70
  unSet = out;
71
71
  else
72
- throw Oops$8.BadEffectCallback();
72
+ throw Oops$9.BadEffectCallback();
73
73
  };
74
74
  }
75
75
  function select(source, select) {
@@ -92,6 +92,179 @@ function selectRecursive(using, keys) {
92
92
  return Array.from(found);
93
93
  }
94
94
 
95
+ class Subscriber {
96
+ constructor(parent, onUpdate) {
97
+ this.onUpdate = onUpdate;
98
+ this.active = false;
99
+ this.follows = {};
100
+ this.dependant = new Set();
101
+ if (!(parent instanceof Controller))
102
+ parent = manage(parent);
103
+ this.parent = parent;
104
+ this.source = parent.subject;
105
+ this.proxy = create(parent.subject);
106
+ define(this.proxy, LOCAL, this);
107
+ for (const key in parent.state)
108
+ this.spy(key);
109
+ }
110
+ spy(key) {
111
+ const { proxy, source } = this;
112
+ const { set } = getOwnPropertyDescriptor(source, key);
113
+ const intercept = () => {
114
+ this.follow(key);
115
+ delete proxy[key];
116
+ return proxy[key];
117
+ };
118
+ setAlias(intercept, `tap ${key}`);
119
+ defineProperty(proxy, key, {
120
+ get: intercept,
121
+ set,
122
+ configurable: true,
123
+ enumerable: true
124
+ });
125
+ }
126
+ follow(key, cb) {
127
+ this.follows[key] = cb || this.onUpdate;
128
+ }
129
+ commit() {
130
+ const { dependant, follows, parent } = this;
131
+ const onDone = parent.addListener(follows);
132
+ this.active = true;
133
+ dependant.forEach(x => x.commit());
134
+ return this.release = () => {
135
+ dependant.forEach(x => x.release());
136
+ onDone();
137
+ };
138
+ }
139
+ }
140
+
141
+ const Oops$8 = issues({
142
+ ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
143
+ ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
144
+ });
145
+ const ComputedInit = new WeakSet();
146
+ const ComputedInfo = new WeakMap();
147
+ const ComputedUsed = new WeakMap();
148
+ const ComputedKeys = new WeakMap();
149
+ function prepare(parent, key, source, setter, getter) {
150
+ let sub;
151
+ const { state, subject } = parent;
152
+ const info = { key, parent, priority: 1 };
153
+ const current = getter
154
+ ? () => getter.call(subject, state[key], key)
155
+ : () => state[key];
156
+ const prioritize = register(parent, key, info);
157
+ function compute(initial) {
158
+ try {
159
+ return setter.call(sub.proxy, sub.proxy);
160
+ }
161
+ catch (err) {
162
+ Oops$8.ComputeFailed(subject, key, !!initial).warn();
163
+ throw err;
164
+ }
165
+ }
166
+ function update() {
167
+ let value;
168
+ try {
169
+ value = compute(false);
170
+ }
171
+ catch (e) {
172
+ console.error(e);
173
+ }
174
+ finally {
175
+ if (state[key] !== value) {
176
+ parent.update(key, value);
177
+ return value;
178
+ }
179
+ }
180
+ }
181
+ function create(early) {
182
+ sub = new Subscriber(source(), update);
183
+ defineProperty(state, key, {
184
+ value: undefined,
185
+ writable: true
186
+ });
187
+ defineProperty(subject, key, {
188
+ enumerable: true,
189
+ configurable: true,
190
+ get: current
191
+ });
192
+ try {
193
+ state[key] = compute(true);
194
+ }
195
+ catch (e) {
196
+ if (early)
197
+ Oops$8.ComputedEarly(key).warn();
198
+ throw e;
199
+ }
200
+ finally {
201
+ sub.commit();
202
+ for (const key in sub.follows)
203
+ prioritize(key);
204
+ }
205
+ return current();
206
+ }
207
+ setAlias(update, `try ${key}`);
208
+ setAlias(create, `new ${key}`);
209
+ setAlias(setter, `run ${key}`);
210
+ ComputedInit.add(create);
211
+ ComputedInfo.set(update, info);
212
+ for (const on of [state, subject])
213
+ defineProperty(on, key, {
214
+ get: create,
215
+ configurable: true,
216
+ enumerable: true
217
+ });
218
+ }
219
+ function register(on, key, info) {
220
+ let register = ComputedUsed.get(on);
221
+ if (!register)
222
+ ComputedUsed.set(on, register = new Map());
223
+ register.set(key, info);
224
+ return (key) => {
225
+ const peer = register.get(key);
226
+ if (peer && peer.priority >= info.priority)
227
+ info.priority = peer.priority + 1;
228
+ };
229
+ }
230
+ function ensure(on, keys) {
231
+ for (const key of keys) {
232
+ const desc = getOwnPropertyDescriptor(on.subject, key);
233
+ const getter = desc && desc.get;
234
+ if (ComputedInit.has(getter))
235
+ getter(true);
236
+ }
237
+ }
238
+ function capture(on, request) {
239
+ const compute = ComputedInfo.get(request);
240
+ const callback = request;
241
+ if (!compute)
242
+ return;
243
+ let pending = ComputedKeys.get(on);
244
+ if (!pending)
245
+ ComputedKeys.set(on, pending = []);
246
+ if (compute.parent !== on)
247
+ callback();
248
+ else {
249
+ const queue = pending.findIndex(peer => compute.priority > ComputedInfo.get(peer).priority);
250
+ pending.splice(queue + 1, 0, callback);
251
+ }
252
+ return true;
253
+ }
254
+ function flush(on) {
255
+ const handled = on.frame;
256
+ let pending = ComputedKeys.get(on);
257
+ if (pending) {
258
+ while (pending.length) {
259
+ const compute = pending.shift();
260
+ const { key } = ComputedInfo.get(compute);
261
+ if (!handled.has(key))
262
+ compute();
263
+ }
264
+ ComputedKeys.delete(on);
265
+ }
266
+ }
267
+
95
268
  const Lifecycle = {
96
269
  WILL_RENDER: "willRender",
97
270
  WILL_UPDATE: "willUpdate",
@@ -147,9 +320,20 @@ const Pending = new Map();
147
320
  function manage(src) {
148
321
  return src[CONTROL];
149
322
  }
150
- function does(instruction, label) {
151
- const placeholder = Symbol(`${label} instruction`);
152
- Pending.set(placeholder, instruction);
323
+ function set(instruction, label) {
324
+ const name = label || instruction.name || "pending";
325
+ const placeholder = Symbol(`${name} instruction`);
326
+ Pending.set(placeholder, function set(key) {
327
+ let output = instruction.call(this, key, this);
328
+ if (typeof output == "function") {
329
+ const getter = output;
330
+ output = Object.assign(Object.assign({}, getOwnPropertyDescriptor(this.subject, key)), { get() {
331
+ return getter(this[LOCAL]);
332
+ } });
333
+ }
334
+ if (output)
335
+ defineProperty(this.subject, key, output);
336
+ });
153
337
  return placeholder;
154
338
  }
155
339
  class Controller {
@@ -273,276 +457,6 @@ function keys(from, using) {
273
457
  return from.select(using);
274
458
  }
275
459
 
276
- class Subscriber {
277
- constructor(parent, onUpdate) {
278
- this.onUpdate = onUpdate;
279
- this.active = false;
280
- this.follows = {};
281
- this.dependant = new Set();
282
- if (!(parent instanceof Controller))
283
- parent = manage(parent);
284
- this.parent = parent;
285
- this.source = parent.subject;
286
- this.proxy = create(parent.subject);
287
- define(this.proxy, LOCAL, this);
288
- for (const key in parent.state)
289
- this.spy(key);
290
- }
291
- spy(key) {
292
- const { proxy, source } = this;
293
- const { set } = getOwnPropertyDescriptor(source, key);
294
- const intercept = () => {
295
- this.follow(key);
296
- delete proxy[key];
297
- return proxy[key];
298
- };
299
- setAlias(intercept, `tap ${key}`);
300
- defineProperty(proxy, key, {
301
- get: intercept,
302
- set,
303
- configurable: true,
304
- enumerable: true
305
- });
306
- }
307
- follow(key, cb) {
308
- this.follows[key] = cb || this.onUpdate;
309
- }
310
- commit() {
311
- const { dependant, follows, parent } = this;
312
- const onDone = parent.addListener(follows);
313
- this.active = true;
314
- dependant.forEach(x => x.commit());
315
- return this.release = () => {
316
- dependant.forEach(x => x.release());
317
- onDone();
318
- };
319
- }
320
- }
321
-
322
- const Oops$7 = issues({
323
- ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
324
- ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
325
- });
326
- const ComputedInit = new WeakSet();
327
- const ComputedInfo = new WeakMap();
328
- const ComputedUsed = new WeakMap();
329
- const ComputedKeys = new WeakMap();
330
- function getRegister(on) {
331
- let register = ComputedUsed.get(on);
332
- if (!register)
333
- ComputedUsed.set(on, register = new Map());
334
- return register;
335
- }
336
- function prepareGetters(on) {
337
- const defined = getRegister(on);
338
- for (let scan = on.subject; scan.constructor !== Object; scan = getPrototypeOf(scan))
339
- for (const key of getOwnPropertyNames(scan)) {
340
- const { get, set } = getOwnPropertyDescriptor(scan, key);
341
- if (get && !defined.has(key))
342
- prepare(on, key, get, set);
343
- }
344
- }
345
- function prepare(on, key, getter, setter) {
346
- const { state, subject } = on;
347
- const defined = getRegister(on);
348
- const info = {
349
- key, parent: on, priority: 1
350
- };
351
- let sub;
352
- defined.set(key, info);
353
- function compute(initial) {
354
- try {
355
- return getter.call(sub.proxy, sub.proxy);
356
- }
357
- catch (err) {
358
- Oops$7.ComputeFailed(subject, key, !!initial).warn();
359
- throw err;
360
- }
361
- }
362
- function update() {
363
- let value;
364
- try {
365
- value = compute(false);
366
- }
367
- catch (e) {
368
- console.error(e);
369
- }
370
- finally {
371
- if (state[key] !== value) {
372
- on.update(key, value);
373
- return value;
374
- }
375
- }
376
- }
377
- function create(early) {
378
- sub = new Subscriber(on, update);
379
- ComputedInfo.set(update, info);
380
- defineProperty(state, key, {
381
- value: undefined,
382
- writable: true
383
- });
384
- defineProperty(subject, key, {
385
- enumerable: true,
386
- configurable: true,
387
- get: () => state[key],
388
- set: setter
389
- });
390
- try {
391
- return state[key] = compute(true);
392
- }
393
- catch (e) {
394
- if (early)
395
- Oops$7.ComputedEarly(key).warn();
396
- throw e;
397
- }
398
- finally {
399
- sub.commit();
400
- for (const key in sub.follows) {
401
- const peer = defined.get(key);
402
- if (peer && peer.priority >= info.priority)
403
- info.priority = peer.priority + 1;
404
- }
405
- }
406
- }
407
- function revert(value) {
408
- delete state[key];
409
- defineProperty(subject, key, {
410
- enumerable: true,
411
- configurable: true,
412
- value
413
- });
414
- }
415
- setAlias(update, `try ${key}`);
416
- setAlias(create, `new ${key}`);
417
- setAlias(getter, `run ${key}`);
418
- ComputedInit.add(create);
419
- for (const on of [state, subject])
420
- defineProperty(on, key, {
421
- get: create,
422
- set: setter || revert,
423
- configurable: true,
424
- enumerable: true
425
- });
426
- return info;
427
- }
428
- function ensure(on, keys) {
429
- for (const key of keys) {
430
- const desc = getOwnPropertyDescriptor(on.subject, key);
431
- const getter = desc && desc.get;
432
- if (ComputedInit.has(getter))
433
- getter(true);
434
- }
435
- }
436
- function capture(on, request) {
437
- const compute = ComputedInfo.get(request);
438
- const callback = request;
439
- if (!compute)
440
- return;
441
- let pending = ComputedKeys.get(on);
442
- if (!pending)
443
- ComputedKeys.set(on, pending = []);
444
- if (compute.parent !== on)
445
- callback();
446
- else {
447
- const queue = pending.findIndex(peer => compute.priority > ComputedInfo.get(peer).priority);
448
- pending.splice(queue + 1, 0, callback);
449
- }
450
- return true;
451
- }
452
- function flush(on) {
453
- const handled = on.frame;
454
- let pending = ComputedKeys.get(on);
455
- if (pending) {
456
- while (pending.length) {
457
- const compute = pending.shift();
458
- const { key } = ComputedInfo.get(compute);
459
- if (!handled.has(key))
460
- compute();
461
- }
462
- ComputedKeys.delete(on);
463
- }
464
- }
465
-
466
- const Oops$6 = issues({
467
- DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
468
- SourceNotSupported: () => `Computed from-instruction does not support source other than 'this'.`
469
- });
470
- const set = (fn, name) => does(function set(key) {
471
- let output = fn.call(this, key, this);
472
- if (typeof output == "function") {
473
- const getter = output;
474
- output = Object.assign(Object.assign({}, getOwnPropertyDescriptor(this.subject, key)), { get() {
475
- return getter(this[LOCAL]);
476
- } });
477
- }
478
- if (output)
479
- defineProperty(this.subject, key, output);
480
- }, name || fn.name || "pending");
481
- const ref = (cb) => set(function ref(key) {
482
- const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
483
- defineProperty(refObjectFunction, "current", {
484
- set: refObjectFunction,
485
- get: () => this.state[key]
486
- });
487
- return {
488
- value: refObjectFunction
489
- };
490
- });
491
- const on = (initial, cb) => set(function on(key) {
492
- this.manage(key, initial, cb && createValueEffect(cb));
493
- });
494
- const memo = (factory, defer) => set(function memo(key) {
495
- const source = this.subject;
496
- const get = () => factory.call(source);
497
- if (defer)
498
- defineLazy(source, key, get);
499
- else
500
- define(source, key, get());
501
- });
502
- const lazy = (value) => set(function lazy(key) {
503
- const source = this.subject;
504
- source[key] = value;
505
- defineProperty(this.state, key, {
506
- get: () => source[key]
507
- });
508
- });
509
- const act = (task) => set(function act(key) {
510
- let pending = false;
511
- const invoke = (...args) => {
512
- if (pending)
513
- return Promise.reject(Oops$6.DuplicateAction(key));
514
- pending = true;
515
- this.update(key);
516
- return new Promise(res => {
517
- res(task.apply(this.subject, args));
518
- }).finally(() => {
519
- pending = false;
520
- this.update(key);
521
- });
522
- };
523
- setAlias(invoke, `run ${key}`);
524
- defineProperty(invoke, "active", {
525
- get: () => pending
526
- });
527
- return {
528
- value: invoke,
529
- writable: false
530
- };
531
- });
532
- function from(source, fn) {
533
- return set(function from(key) {
534
- let getter;
535
- if (typeof source == "function")
536
- getter = source.call(this.subject, key);
537
- else {
538
- if (source !== this.subject)
539
- throw Oops$6.SourceNotSupported();
540
- getter = fn;
541
- }
542
- prepare(this, key, getter);
543
- });
544
- }
545
-
546
460
  const attach = (from, name) => set(function attach(key) {
547
461
  const proxyCache = new WeakMap();
548
462
  const getCurrent = from.call(this, key);
@@ -580,7 +494,7 @@ const attach = (from, name) => set(function attach(key) {
580
494
  };
581
495
  }, name || from.name);
582
496
 
583
- const Oops$5 = issues({
497
+ const Oops$7 = issues({
584
498
  HasPropertyUndefined: (control, property) => `${control}.${property} is marked as required for this render.`
585
499
  });
586
500
  const useElementLifecycle = lifecycle("element");
@@ -609,7 +523,7 @@ function useWatcher(target, focus, expected) {
609
523
  defineProperty(sub, "proxy", {
610
524
  get() {
611
525
  if (src[key] === undefined && expected)
612
- throw Oops$5.HasPropertyUndefined(target, key);
526
+ throw Oops$7.HasPropertyUndefined(target, key);
613
527
  return src[key];
614
528
  }
615
529
  });
@@ -697,7 +611,7 @@ class Lookup {
697
611
  }
698
612
  }
699
613
 
700
- const Oops$4 = issues({
614
+ const Oops$6 = issues({
701
615
  NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`,
702
616
  NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`,
703
617
  BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
@@ -707,7 +621,7 @@ const useLookup = () => react.useContext(LookupContext);
707
621
  function useFromContext(Type, strict) {
708
622
  const instance = useLookup().get(Type);
709
623
  if (!instance && strict)
710
- throw Oops$4.NothingInContext(Type.name);
624
+ throw Oops$6.NothingInContext(Type.name);
711
625
  return instance;
712
626
  }
713
627
  function Consumer(props) {
@@ -718,13 +632,13 @@ function Consumer(props) {
718
632
  if (typeof callback == "function")
719
633
  callback(Control.get(!!has));
720
634
  else
721
- throw Oops$4.BadConsumerProps();
635
+ throw Oops$6.BadConsumerProps();
722
636
  return null;
723
637
  }
724
638
  function useNewContext(from, inject) {
725
639
  return react.useMemo(() => {
726
640
  if (!inject)
727
- throw Oops$4.NoProviderType();
641
+ throw Oops$6.NoProviderType();
728
642
  const context = from.push(inject);
729
643
  for (const instance of context.local)
730
644
  for (const apply of getPending(instance))
@@ -767,30 +681,32 @@ function Provider(props) {
767
681
  : render);
768
682
  }
769
683
 
770
- const Oops$3 = issues({
684
+ const Oops$5 = issues({
771
685
  CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
772
686
  AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
773
687
  });
774
688
  const PendingContext = new WeakMap();
775
689
  const ContextWasUsed = new WeakMap();
776
690
  const tap = (type, argument) => attach(function tap(key) {
691
+ return pendingAccess(this.subject, type, key, argument);
692
+ });
693
+ function pendingAccess(from, type, key, argument) {
777
694
  if ("current" in type)
778
695
  return () => type.current;
779
- const to = this.subject;
780
- if ("current" in to.constructor)
781
- throw Oops$3.CantAttachGlobal(to, type.name);
696
+ if ("current" in from.constructor)
697
+ throw Oops$5.CantAttachGlobal(from, type.name);
782
698
  let instance;
783
- getPending(to).push(context => {
699
+ getPending(from).push(context => {
784
700
  instance = context.get(type);
785
701
  if (typeof argument == "function") {
786
702
  if (argument(instance) === false)
787
703
  instance = undefined;
788
704
  }
789
705
  else if (!instance && argument)
790
- throw Oops$3.AmbientRequired(type.name, to, key);
706
+ throw Oops$5.AmbientRequired(type.name, from, key);
791
707
  });
792
708
  return () => instance;
793
- });
709
+ }
794
710
  function usePeerContext(subject) {
795
711
  if (ContextWasUsed.has(subject)) {
796
712
  if (ContextWasUsed.get(subject))
@@ -813,17 +729,15 @@ function getPending(subject) {
813
729
  return pending;
814
730
  }
815
731
 
816
- const Oops$2 = issues({
732
+ const Oops$4 = issues({
817
733
  StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
818
734
  NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
819
735
  });
820
736
  class State {
821
- constructor(computeGetters) {
822
- const control = Controller.setup(this);
737
+ constructor() {
738
+ Controller.setup(this);
823
739
  define(this, "get", this);
824
740
  define(this, "set", this);
825
- if (computeGetters !== false)
826
- prepareGetters(control);
827
741
  }
828
742
  import(from, subset) {
829
743
  for (const key of keys(manage(this), subset))
@@ -843,15 +757,18 @@ class State {
843
757
  arg = select(this, arg);
844
758
  if (typeof arg == "boolean") {
845
759
  if (!control.pending === arg)
846
- return Promise.reject(Oops$2.StrictUpdate(arg));
760
+ return Promise.reject(Oops$4.StrictUpdate(arg));
847
761
  }
848
762
  else if (arg) {
849
763
  control.update(arg);
850
- if (1 in arguments && arg in this)
851
- if (typeof tag != "boolean")
852
- this[arg](tag);
853
- else if (tag)
854
- this[arg]();
764
+ if (1 in arguments && arg in this) {
765
+ const method = this[arg];
766
+ if (typeof method == "function")
767
+ if (typeof tag != "boolean")
768
+ method.call(this, tag);
769
+ else if (tag)
770
+ method.call(this);
771
+ }
855
772
  }
856
773
  return {
857
774
  then(callback) {
@@ -861,7 +778,7 @@ class State {
861
778
  else
862
779
  callback(false);
863
780
  else
864
- throw Oops$2.NoChaining();
781
+ throw Oops$4.NoChaining();
865
782
  }
866
783
  };
867
784
  }
@@ -872,7 +789,9 @@ class State {
872
789
  return this.constructor.name;
873
790
  }
874
791
  tap(path, expect) {
875
- return useWatcher(this, path, expect);
792
+ const proxy = useWatcher(this, path, expect);
793
+ this.update("willRender", true);
794
+ return proxy;
876
795
  }
877
796
  static create(...args) {
878
797
  const instance = new this(...args);
@@ -880,7 +799,9 @@ class State {
880
799
  return instance;
881
800
  }
882
801
  static new(args, callback) {
883
- return useLazy(this, args, callback);
802
+ const instance = useLazy(this, args, callback);
803
+ instance.update("willRender", true);
804
+ return instance;
884
805
  }
885
806
  static use(args, callback) {
886
807
  return useModel(this, args, callback);
@@ -907,7 +828,7 @@ class State {
907
828
  instance);
908
829
  }
909
830
  static tap(key, expect) {
910
- return useWatcher(this.find(true), key, expect);
831
+ return this.find(true).tap(key, expect);
911
832
  }
912
833
  static isTypeof(maybe) {
913
834
  return (typeof maybe == "function" &&
@@ -921,9 +842,6 @@ State.LOCAL = LOCAL;
921
842
 
922
843
  const useComponentLifecycle = lifecycle("component");
923
844
  class Model extends State {
924
- constructor() {
925
- super(false);
926
- }
927
845
  [CREATE](using) {
928
846
  defer(using, "on");
929
847
  defer(using, "effect");
@@ -986,7 +904,7 @@ class Model extends State {
986
904
  return instance;
987
905
  }
988
906
  static tag(id) {
989
- return useSubscriber(this.find(true), id);
907
+ return this.find(true).tag(id);
990
908
  }
991
909
  static meta(path) {
992
910
  return useWatcher(this, path);
@@ -1008,7 +926,7 @@ function defer(on, method) {
1008
926
  }
1009
927
 
1010
928
  const Parent = new WeakMap();
1011
- const Oops$1 = issues({
929
+ const Oops$3 = issues({
1012
930
  ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
1013
931
  UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
1014
932
  UndefinedNotAllowed: (key) => `Child property ${key} may not be undefined.`,
@@ -1034,7 +952,7 @@ const use = (input, argument) => attach(function use(key) {
1034
952
  if (typeof argument == "function")
1035
953
  argument(instance);
1036
954
  else if (!instance && argument !== false)
1037
- throw Oops$1.UndefinedNotAllowed(key);
955
+ throw Oops$3.UndefinedNotAllowed(key);
1038
956
  };
1039
957
  if (input) {
1040
958
  instance =
@@ -1042,7 +960,7 @@ const use = (input, argument) => attach(function use(key) {
1042
960
  input instanceof Model ? input :
1043
961
  typeof input === "function" ? input() :
1044
962
  typeof input === "object" ? bootstrap(input) :
1045
- Oops$1.BadArgument(typeof input).throw();
963
+ Oops$3.BadArgument(typeof input).throw();
1046
964
  if (instance)
1047
965
  update(instance);
1048
966
  }
@@ -1057,13 +975,156 @@ const parent = (Expects, required) => set(function parent() {
1057
975
  const value = Parent.get(this.subject);
1058
976
  if (!value) {
1059
977
  if (required)
1060
- throw Oops$1.ParentRequired(expected, child);
978
+ throw Oops$3.ParentRequired(expected, child);
1061
979
  }
1062
980
  else if (!(value instanceof Expects))
1063
- throw Oops$1.UnexpectedParent(expected, child, value);
981
+ throw Oops$3.UnexpectedParent(expected, child, value);
1064
982
  return { value };
1065
983
  });
1066
984
 
985
+ const Oops$2 = issues({
986
+ DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
987
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
988
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
989
+ });
990
+ function ref(cb) {
991
+ return set(function ref(key) {
992
+ const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
993
+ defineProperty(refObjectFunction, "current", {
994
+ set: refObjectFunction,
995
+ get: () => this.state[key]
996
+ });
997
+ return {
998
+ value: refObjectFunction
999
+ };
1000
+ });
1001
+ }
1002
+ function on(initial, cb) {
1003
+ return set(function on(key) {
1004
+ this.manage(key, initial, cb && createValueEffect(cb));
1005
+ });
1006
+ }
1007
+ function memo(factory, defer) {
1008
+ return set(function memo(key) {
1009
+ const source = this.subject;
1010
+ const get = () => factory.call(source);
1011
+ if (defer)
1012
+ defineLazy(source, key, get);
1013
+ else
1014
+ define(source, key, get());
1015
+ });
1016
+ }
1017
+ function lazy(value) {
1018
+ return set(function lazy(key) {
1019
+ const source = this.subject;
1020
+ source[key] = value;
1021
+ defineProperty(this.state, key, {
1022
+ get: () => source[key]
1023
+ });
1024
+ });
1025
+ }
1026
+ function act(task) {
1027
+ return set(function act(key) {
1028
+ let pending = false;
1029
+ const invoke = (...args) => {
1030
+ if (pending)
1031
+ return Promise.reject(Oops$2.DuplicateAction(key));
1032
+ pending = true;
1033
+ this.update(key);
1034
+ return new Promise(res => {
1035
+ res(task.apply(this.subject, args));
1036
+ }).finally(() => {
1037
+ pending = false;
1038
+ this.update(key);
1039
+ });
1040
+ };
1041
+ setAlias(invoke, `run ${key}`);
1042
+ defineProperty(invoke, "active", {
1043
+ get: () => pending
1044
+ });
1045
+ return {
1046
+ value: invoke,
1047
+ writable: false
1048
+ };
1049
+ });
1050
+ }
1051
+ function from(source, setter, getter) {
1052
+ return set(function from(key) {
1053
+ const { subject } = this;
1054
+ let getSource;
1055
+ // Easy mistake, using a peer, will always be unresolved.
1056
+ if (typeof source == "symbol")
1057
+ throw Oops$2.PeerNotAllowed(subject, key);
1058
+ // replace source controller in-case different
1059
+ if (typeof source == "object")
1060
+ getSource = () => manage(source);
1061
+ // specifically an arrow function (getter factory)
1062
+ else if (!source.prototype) {
1063
+ setter = source.call(subject, key);
1064
+ getSource = () => this;
1065
+ }
1066
+ // is a peer Model (constructor)
1067
+ else if (Model.isTypeof(source))
1068
+ getSource = pendingAccess(subject, source, key, true);
1069
+ // Regular function is to ambiguous so not allowed.
1070
+ else
1071
+ throw Oops$2.BadComputedSource(subject, key, source);
1072
+ prepare(this, key, getSource, setter, getter);
1073
+ });
1074
+ }
1075
+
1076
+ const Oops$1 = issues({
1077
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
1078
+ });
1079
+ /**
1080
+ * Create suspense promise also interpretable as an error.
1081
+ * React will handle it but other contexts probably won't.
1082
+ */
1083
+ function suspense(model, key) {
1084
+ const { message, stack } = Oops$1.ValueNotReady(model, key);
1085
+ const promise = new Promise(resolve => {
1086
+ const release = model.on(key, (value) => {
1087
+ if (value !== undefined)
1088
+ release(), resolve();
1089
+ });
1090
+ });
1091
+ return Object.assign(promise, { message, stack });
1092
+ }
1093
+ function pending(source, compute) {
1094
+ if (typeof source == "function" && !source.prototype)
1095
+ return suspendForAsync(source);
1096
+ else
1097
+ return from(source, compute, function getter(value, key) {
1098
+ if (value === undefined)
1099
+ throw suspense(this, key);
1100
+ return value;
1101
+ });
1102
+ }
1103
+ function suspendForAsync(source) {
1104
+ return set(function suspense(key) {
1105
+ let waiting = true;
1106
+ let output;
1107
+ let error;
1108
+ return () => {
1109
+ if (waiting) {
1110
+ const issue = Oops$1.ValueNotReady(this.subject, key);
1111
+ const promise = source
1112
+ .call(this.subject, key)
1113
+ .catch(err => error = err)
1114
+ .then((value) => output = value)
1115
+ .finally(() => waiting = false);
1116
+ throw Object.assign(promise, {
1117
+ message: issue.message,
1118
+ stack: issue.stack
1119
+ });
1120
+ }
1121
+ if (error)
1122
+ throw error;
1123
+ return output;
1124
+ };
1125
+ });
1126
+ }
1127
+
1067
1128
  const Oops = issues({
1068
1129
  GlobalDoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.create() before attempting to access, or consider using ${name}.use() instead.`,
1069
1130
  GlobalExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
@@ -1112,12 +1173,13 @@ exports.Singleton = Singleton;
1112
1173
  exports.State = State;
1113
1174
  exports.act = act;
1114
1175
  exports.attach = attach;
1115
- exports.default = Model;
1176
+ exports["default"] = Model;
1116
1177
  exports.from = from;
1117
1178
  exports.lazy = lazy;
1118
1179
  exports.memo = memo;
1119
1180
  exports.on = on;
1120
1181
  exports.parent = parent;
1182
+ exports.pending = pending;
1121
1183
  exports.ref = ref;
1122
1184
  exports.set = set;
1123
1185
  exports.tap = tap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expressive/mvc",
3
- "version": "1.0.0-beta.17",
3
+ "version": "1.0.0-beta.20",
4
4
  "description": "Use classes to define state in React!",
5
5
  "author": {
6
6
  "name": "Gabe Klein",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "keywords": [
16
16
  "react",
17
- "hooks"
17
+ "mvc"
18
18
  ],
19
19
  "main": "lib/index.js",
20
20
  "types": "types/index.d.ts",
@@ -24,14 +24,9 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "rollup --config",
27
- "watch": "rollup --config --sourcemap --watch",
28
- "prebuild": "rm -rf lib/",
29
- "publish:pre": "npm version prerelease --preid=beta",
30
- "preversion": "jest --coverage",
31
- "postversion": "npm run build; npm publish",
32
- "postpublish": "git push; git push --tags; rm -rf lib",
33
- "coverage": "jest --collectCoverage",
34
- "test": "jest --watchAll"
27
+ "watch": "jest --watchAll",
28
+ "test": "jest --collectCoverage",
29
+ "prebuild": "rm -rf lib/"
35
30
  },
36
31
  "peerDependencies": {
37
32
  "react": ">=17.0.2"
@@ -1,4 +1,4 @@
1
- import Model, { Selector } from '.';
1
+ import Model from '.';
2
2
  import { Async, Class, InstanceOf, InterceptCallback } from './types';
3
3
 
4
4
  /**
@@ -143,16 +143,25 @@ export function act <T extends Async> (action: T): T & { active: boolean };
143
143
  *
144
144
  * @param fn - Factory function to generate a getter to subscribe dependancies.
145
145
  */
146
- export function from <R> (fn: (property: string) => (this: any, on: any) => R): R;
146
+ export function from <R> (fn: (property: string) => (this: any, state: any) => R): R;
147
147
 
148
148
  /**
149
149
  * Implement a computed value; output will be generated by provided function.
150
150
  *
151
- * @param source - Source model from which computed value will be subscribed.
152
- * **Note:** Currently will only support `this`.
151
+ * @param source - Source Model to find and subscribe to, for input.
153
152
  *
154
153
  * @param fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
155
- * Will update automatically as accessed values change.
154
+ * Will update automatically as input values change.
155
+ */
156
+ export function from <T extends Class, R> (source: T, fn: (this: InstanceOf<T>, on: InstanceOf<T>) => R): R;
157
+
158
+ /**
159
+ * Implement a computed value; output will be generated by provided function.
160
+ *
161
+ * @param source - Source model from which computed value will be a subscriber.
162
+ *
163
+ * @param fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
164
+ * Will update automatically as input values change.
156
165
  */
157
166
  export function from <T, R> (source: T, fn: (this: T, on: T) => R): R;
158
167
 
@@ -170,4 +179,24 @@ export function memo <T> (compute: () => T, lazy?: boolean): T;
170
179
  *
171
180
  * @param value - starting value of property.
172
181
  */
173
- export function lazy <T> (value?: T): T;
182
+ export function lazy <T> (value?: T): T;
183
+
184
+ /**
185
+ * Suspend during the execution of given function to obtain value.
186
+ *
187
+ * @param waitFor - Async function to suspend for.
188
+ */
189
+ export function pending (waitFor: () => Promise<void>): undefined;
190
+ export function pending <T> (waitFor: () => Promise<T>): T;
191
+
192
+ /**
193
+ * Suspend for a computed value; output will be generated by provided function.
194
+ * Will throw a promise if value is accessed while undefined, promise will
195
+ * resolve once value gains any value.
196
+ *
197
+ * @param source - Source model from which computed value will be a subscriber.
198
+ *
199
+ * @param fn - Compute function. Bound to a subscriber-proxy of source, returns output value.
200
+ * Will update automatically as input values change.
201
+ */
202
+ export function pending <T, R> (source: T, fn: (this: T, on: T) => R): Exclude<R, undefined>;
package/types/model.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import { Key } from 'react';
2
+
1
3
  import Lifecycle from './lifecycle';
2
4
  import { Select, Selector } from './selector';
3
- import { Class, InstanceOf, Key } from './types';
5
+ import { BunchOf, Callback, Class, InstanceOf, Query, RequestCallback, UpdateCallback } from './types';
4
6
 
5
7
  interface PropertyDescriptor<T> {
6
8
  configurable?: boolean;
@@ -428,7 +430,7 @@ export abstract class Model extends State {
428
430
  *
429
431
  * @param id - Argument passed to controller-lifecycle methods. Use to identify the consumer.
430
432
  */
431
- tag(id?: Key): this;
433
+ tag(id?: any): this;
432
434
 
433
435
  /**
434
436
  * **React Hook** - Attach to instance of this controller within ambient component.
@@ -437,7 +439,7 @@ export abstract class Model extends State {
437
439
  *
438
440
  * @param idFactory - Will be invoked with fetched instance. Use this to register a tag as-needed.
439
441
  */
440
- tag(idFactory: (idFactory: this) => Key | void): this;
442
+ tag(idFactory: (idFactory: this) => any): this;
441
443
 
442
444
 
443
445
  /**
package/types/types.d.ts CHANGED
@@ -12,4 +12,7 @@ export type EffectCallback<T, A = T> = (this: T, argument: A) => Callback | Prom
12
12
  export type UpdateCallback<T, P> = (this: T, value: IfApplicable<T, P>, changed: P) => void;
13
13
  export type InterceptCallback<T> = (argument: T) => ((next: T) => void) | Promise<any> | void | boolean;
14
14
 
15
- export type RefFunction = (e: HTMLElement | null) => void;
15
+ export type RefFunction = (e: HTMLElement | null) => void;
16
+
17
+ export type Recursive<T> = { [K in keyof T]: Recursive<Omit<T, K>> };
18
+ export type Query = (select: Recursive<{}>) => void;