@expressive/mvc 0.0.3 → 0.1.1
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 +12 -6
- package/lib/index.esm.js +136 -98
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +136 -98
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -75,6 +75,11 @@ declare namespace Model {
|
|
|
75
75
|
export type Methods<T> = {
|
|
76
76
|
[K in keyof T]: T[K] extends Ref ? never : T[K] extends Function ? K : never;
|
|
77
77
|
}[keyof T];
|
|
78
|
+
export type Refs<T extends Model> = {
|
|
79
|
+
<K extends Field<T>>(key: K, value: T[K]): void;
|
|
80
|
+
} & {
|
|
81
|
+
[P in Field<T>]: (next: any) => void;
|
|
82
|
+
};
|
|
78
83
|
/**
|
|
79
84
|
* Subset of `keyof T` which are not methods or defined by base Model U.
|
|
80
85
|
*
|
|
@@ -110,11 +115,12 @@ interface Model extends Stateful {
|
|
|
110
115
|
*/
|
|
111
116
|
get: this;
|
|
112
117
|
/**
|
|
113
|
-
*
|
|
118
|
+
* Setter utility
|
|
114
119
|
*
|
|
115
|
-
*
|
|
120
|
+
* Can be used to set (and force-update) values.
|
|
121
|
+
* Shortcut to update values, contains refs for all controlled values.
|
|
116
122
|
*/
|
|
117
|
-
set: this
|
|
123
|
+
set: Model.Refs<this>;
|
|
118
124
|
}
|
|
119
125
|
declare class Model {
|
|
120
126
|
static [CONTROL]: Controller;
|
|
@@ -186,7 +192,7 @@ declare class Controller<T extends Stateful = any> {
|
|
|
186
192
|
constructor(subject: T);
|
|
187
193
|
start(): void;
|
|
188
194
|
stop(): void;
|
|
189
|
-
|
|
195
|
+
manage(key: Model.Field<T>, value: any): void;
|
|
190
196
|
ref(key: Model.Field<T>, handler?: Controller.OnValue<T>): (value: any) => void;
|
|
191
197
|
addListener(listener: Controller.OnEvent<T>): () => void;
|
|
192
198
|
emit(keys: readonly Model.Event<T>[]): void;
|
|
@@ -575,7 +581,7 @@ declare function tap<T extends Class>(Type: T, required?: boolean): InstanceOf<T
|
|
|
575
581
|
declare function useFrom<T extends Stateful, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
|
|
576
582
|
declare function useFrom<T extends Stateful, R>(source: (() => T) | T, compute: (this: T, from: T) => R, expect?: boolean): R;
|
|
577
583
|
|
|
578
|
-
declare function
|
|
584
|
+
declare function useLocal<T extends typeof Model>(Type: T, arg?: boolean | string): any;
|
|
579
585
|
|
|
580
586
|
declare function useModel<T extends Class, I extends InstanceOf<T>>(source: T, watch: Model.Field<I>[], callback?: (instance: I) => void): I;
|
|
581
587
|
declare function useModel<T extends Class, I extends InstanceOf<T>>(source: T, callback?: (instance: I) => void): I;
|
|
@@ -597,4 +603,4 @@ declare function useTap<T extends Stateful, K extends Model.Field<T>>(source: T
|
|
|
597
603
|
declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect: true): Exclude<R, undefined>;
|
|
598
604
|
declare function useTap<T extends Stateful, R>(source: T | (() => T) | useTap.Tappable, compute: (this: T, from: T) => R, expect?: boolean): R;
|
|
599
605
|
|
|
600
|
-
export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use, useFrom,
|
|
606
|
+
export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, Stateful, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use, useFrom, useLocal, useModel, useTap };
|
package/lib/index.esm.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
+
/*! *****************************************************************************
|
|
4
|
+
Copyright (c) Microsoft Corporation.
|
|
5
|
+
|
|
6
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
7
|
+
purpose with or without fee is hereby granted.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
***************************************************************************** */
|
|
17
|
+
|
|
18
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
class Issue extends Error {
|
|
4
29
|
constructor() {
|
|
5
30
|
super(...arguments);
|
|
@@ -18,6 +43,50 @@ function issues(register) {
|
|
|
18
43
|
}
|
|
19
44
|
|
|
20
45
|
const Oops$c = issues({
|
|
46
|
+
ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
|
|
47
|
+
});
|
|
48
|
+
function suspend(source, key) {
|
|
49
|
+
const error = Oops$c.ValueNotReady(source.subject, key);
|
|
50
|
+
const promise = new Promise(resolve => {
|
|
51
|
+
const release = source.addListener(forKey => {
|
|
52
|
+
if (forKey == key)
|
|
53
|
+
return () => {
|
|
54
|
+
if (source.state[key] !== undefined) {
|
|
55
|
+
release();
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
return Object.assign(promise, {
|
|
62
|
+
toString: () => String(error),
|
|
63
|
+
name: "Suspense",
|
|
64
|
+
message: error.message,
|
|
65
|
+
stack: error.stack
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function mayRetry(fn) {
|
|
69
|
+
const retry = (err) => {
|
|
70
|
+
if (err instanceof Promise)
|
|
71
|
+
return err.then(compute);
|
|
72
|
+
else
|
|
73
|
+
throw err;
|
|
74
|
+
};
|
|
75
|
+
const compute = () => {
|
|
76
|
+
try {
|
|
77
|
+
const output = fn();
|
|
78
|
+
return output instanceof Promise
|
|
79
|
+
? output.catch(retry)
|
|
80
|
+
: output;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
return retry(err);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return compute();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const Oops$b = issues({
|
|
21
90
|
BadEffectCallback: () => `Callback for effect-callback may only return a function.`
|
|
22
91
|
});
|
|
23
92
|
const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
|
|
@@ -43,7 +112,7 @@ function createEffect(callback) {
|
|
|
43
112
|
if (unSet instanceof Promise)
|
|
44
113
|
unSet = undefined;
|
|
45
114
|
if (unSet && typeof unSet !== "function")
|
|
46
|
-
throw Oops$
|
|
115
|
+
throw Oops$b.BadEffectCallback();
|
|
47
116
|
};
|
|
48
117
|
}
|
|
49
118
|
function createValueEffect(callback) {
|
|
@@ -59,28 +128,8 @@ function createValueEffect(callback) {
|
|
|
59
128
|
if (typeof out == "function")
|
|
60
129
|
unSet = out;
|
|
61
130
|
else
|
|
62
|
-
throw Oops$
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function suspenseBoundary(fn) {
|
|
66
|
-
const retry = (err) => {
|
|
67
|
-
if (err instanceof Promise)
|
|
68
|
-
return err.then(compute);
|
|
69
|
-
else
|
|
70
|
-
throw err;
|
|
71
|
-
};
|
|
72
|
-
const compute = () => {
|
|
73
|
-
try {
|
|
74
|
-
const output = fn();
|
|
75
|
-
return output instanceof Promise
|
|
76
|
-
? output.catch(retry)
|
|
77
|
-
: output;
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
return retry(err);
|
|
81
|
-
}
|
|
131
|
+
throw Oops$b.BadEffectCallback();
|
|
82
132
|
};
|
|
83
|
-
return compute();
|
|
84
133
|
}
|
|
85
134
|
|
|
86
135
|
const UPDATE = new WeakMap();
|
|
@@ -99,9 +148,18 @@ const LOCAL = Symbol("LOCAL");
|
|
|
99
148
|
const STATE = Symbol("STATE");
|
|
100
149
|
class Model {
|
|
101
150
|
constructor() {
|
|
102
|
-
|
|
151
|
+
const control = new Controller(this);
|
|
152
|
+
define(this, CONTROL, control);
|
|
103
153
|
define(this, "get", this);
|
|
104
|
-
|
|
154
|
+
defineLazy(this, "set", () => {
|
|
155
|
+
const assign = (key, value) => {
|
|
156
|
+
control.update(key, value);
|
|
157
|
+
};
|
|
158
|
+
ready(control);
|
|
159
|
+
for (const key of getOwnPropertyNames(control.proxy))
|
|
160
|
+
define(assign, key, control.ref(key));
|
|
161
|
+
return assign;
|
|
162
|
+
});
|
|
105
163
|
}
|
|
106
164
|
get [STATE]() {
|
|
107
165
|
return this[CONTROL].state;
|
|
@@ -142,18 +200,25 @@ class Model {
|
|
|
142
200
|
effect(callback, select) {
|
|
143
201
|
const effect = createEffect(callback);
|
|
144
202
|
return control(this, control => {
|
|
203
|
+
let busy = false;
|
|
204
|
+
let inject = this.get;
|
|
205
|
+
const invoke = () => {
|
|
206
|
+
if (busy)
|
|
207
|
+
return;
|
|
208
|
+
const output = mayRetry(() => {
|
|
209
|
+
effect.call(inject, inject);
|
|
210
|
+
});
|
|
211
|
+
if (output instanceof Promise) {
|
|
212
|
+
output.finally(() => busy = false);
|
|
213
|
+
busy = true;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
145
216
|
if (!select) {
|
|
146
217
|
const sub = new Subscriber(control, () => invoke);
|
|
147
|
-
|
|
148
|
-
const x = sub.proxy;
|
|
149
|
-
effect.call(x, x);
|
|
150
|
-
};
|
|
218
|
+
inject = sub.proxy;
|
|
151
219
|
invoke();
|
|
152
220
|
return sub.commit();
|
|
153
221
|
}
|
|
154
|
-
const invoke = () => {
|
|
155
|
-
effect.call(this.get, this.get);
|
|
156
|
-
};
|
|
157
222
|
invoke();
|
|
158
223
|
if (!select.length) {
|
|
159
224
|
control.onDestroy.add(invoke);
|
|
@@ -231,10 +296,7 @@ class Model {
|
|
|
231
296
|
return (typeof maybe == "function" &&
|
|
232
297
|
maybe.prototype instanceof this);
|
|
233
298
|
}
|
|
234
|
-
}
|
|
235
|
-
defineLazy(Model, CONTROL, function () {
|
|
236
|
-
return new Controller(this);
|
|
237
|
-
});
|
|
299
|
+
}
|
|
238
300
|
|
|
239
301
|
class Subscriber {
|
|
240
302
|
constructor(target, onUpdate) {
|
|
@@ -282,30 +344,6 @@ class Subscriber {
|
|
|
282
344
|
}
|
|
283
345
|
}
|
|
284
346
|
|
|
285
|
-
const Oops$b = issues({
|
|
286
|
-
ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
|
|
287
|
-
});
|
|
288
|
-
function suspend(source, key) {
|
|
289
|
-
const error = Oops$b.ValueNotReady(source.subject, key);
|
|
290
|
-
const promise = new Promise(resolve => {
|
|
291
|
-
const release = source.addListener(forKey => {
|
|
292
|
-
if (forKey == key)
|
|
293
|
-
return () => {
|
|
294
|
-
if (source.state[key] !== undefined) {
|
|
295
|
-
release();
|
|
296
|
-
resolve();
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
return Object.assign(promise, {
|
|
302
|
-
toString: () => String(error),
|
|
303
|
-
name: "Suspense",
|
|
304
|
-
message: error.message,
|
|
305
|
-
stack: error.stack
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
347
|
const Oops$a = issues({
|
|
310
348
|
BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
|
|
311
349
|
PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
|
|
@@ -460,26 +498,28 @@ class Controller {
|
|
|
460
498
|
this.proxy = Object.create(subject);
|
|
461
499
|
}
|
|
462
500
|
start() {
|
|
463
|
-
|
|
464
|
-
|
|
501
|
+
READY.add(this);
|
|
502
|
+
for (const key in this.subject) {
|
|
503
|
+
const { value: entry } = getOwnPropertyDescriptor(this.subject, key);
|
|
504
|
+
if (typeof entry == "function")
|
|
505
|
+
continue;
|
|
506
|
+
if (typeof entry == "symbol") {
|
|
507
|
+
const instruction = PENDING.get(entry);
|
|
508
|
+
if (instruction)
|
|
509
|
+
instruction.call(this, key, this);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
this.manage(key, entry);
|
|
513
|
+
}
|
|
465
514
|
this.emit([]);
|
|
466
515
|
}
|
|
467
516
|
stop() {
|
|
468
517
|
this.followers.clear();
|
|
469
518
|
this.onDestroy.forEach(x => x());
|
|
470
519
|
}
|
|
471
|
-
manage(key) {
|
|
520
|
+
manage(key, value) {
|
|
472
521
|
const { proxy, state, subject } = this;
|
|
473
|
-
|
|
474
|
-
if (typeof entry == "function")
|
|
475
|
-
return;
|
|
476
|
-
if (typeof entry == "symbol") {
|
|
477
|
-
const instruction = PENDING.get(entry);
|
|
478
|
-
if (instruction)
|
|
479
|
-
instruction.call(this, key, this);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
state[key] = entry;
|
|
522
|
+
state[key] = value;
|
|
483
523
|
const set = this.ref(key);
|
|
484
524
|
defineProperty(subject, key, {
|
|
485
525
|
enumerable: false,
|
|
@@ -571,26 +611,23 @@ class Controller {
|
|
|
571
611
|
}
|
|
572
612
|
function control(subject, cb) {
|
|
573
613
|
let control = subject[CONTROL];
|
|
574
|
-
const ready = READY.has(control);
|
|
575
614
|
if (!control) {
|
|
576
615
|
control = new Controller(subject);
|
|
577
616
|
define(subject, CONTROL, control);
|
|
578
617
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
return control;
|
|
585
|
-
}
|
|
618
|
+
return ready(control, cb);
|
|
619
|
+
}
|
|
620
|
+
function ready(control, cb) {
|
|
621
|
+
const ready = READY.has(control);
|
|
586
622
|
if (!ready) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
done = cb(control);
|
|
590
|
-
|
|
591
|
-
|
|
623
|
+
if (cb) {
|
|
624
|
+
let done;
|
|
625
|
+
control.requestUpdate(() => done = cb(control));
|
|
626
|
+
return () => done && done();
|
|
627
|
+
}
|
|
628
|
+
control.start();
|
|
592
629
|
}
|
|
593
|
-
return cb(control);
|
|
630
|
+
return cb ? cb(control) : control;
|
|
594
631
|
}
|
|
595
632
|
|
|
596
633
|
function apply(fn, label) {
|
|
@@ -683,18 +720,19 @@ const Oops$8 = issues({
|
|
|
683
720
|
function act(task) {
|
|
684
721
|
return apply(function act(key) {
|
|
685
722
|
let pending = false;
|
|
686
|
-
const invoke = (...args) => {
|
|
723
|
+
const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
687
724
|
if (pending)
|
|
688
725
|
return Promise.reject(Oops$8.DuplicateAction(key));
|
|
689
726
|
pending = true;
|
|
690
727
|
this.update(key);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
728
|
+
try {
|
|
729
|
+
return yield mayRetry(() => (task.apply(this.subject, args)));
|
|
730
|
+
}
|
|
731
|
+
finally {
|
|
694
732
|
pending = false;
|
|
695
733
|
this.update(key);
|
|
696
|
-
}
|
|
697
|
-
};
|
|
734
|
+
}
|
|
735
|
+
});
|
|
698
736
|
this.state[key] = undefined;
|
|
699
737
|
defineProperty(invoke, "active", {
|
|
700
738
|
get: () => pending
|
|
@@ -824,7 +862,7 @@ function pendingFactory(parent, key, fn, required) {
|
|
|
824
862
|
let pending;
|
|
825
863
|
let error;
|
|
826
864
|
const init = () => {
|
|
827
|
-
const output =
|
|
865
|
+
const output = mayRetry(() => {
|
|
828
866
|
return fn.call(subject, key, subject);
|
|
829
867
|
});
|
|
830
868
|
if (output instanceof Promise) {
|
|
@@ -993,7 +1031,7 @@ const useLookup = () => React.useContext(LookupContext);
|
|
|
993
1031
|
const Oops$4 = issues({
|
|
994
1032
|
NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
|
|
995
1033
|
});
|
|
996
|
-
function
|
|
1034
|
+
function useLocal(Type, arg) {
|
|
997
1035
|
const instance = useLookup().get(Type);
|
|
998
1036
|
if (!instance && arg !== false)
|
|
999
1037
|
throw Oops$4.NothingInContext(Type.name);
|
|
@@ -1161,7 +1199,7 @@ function useTap(source, path, expect) {
|
|
|
1161
1199
|
if ("get" in source)
|
|
1162
1200
|
return () => source.get();
|
|
1163
1201
|
if ("prototype" in source)
|
|
1164
|
-
return () =>
|
|
1202
|
+
return () => useLocal(source);
|
|
1165
1203
|
else
|
|
1166
1204
|
return source;
|
|
1167
1205
|
}, [])();
|
|
@@ -1196,7 +1234,7 @@ class MVC extends Model {
|
|
|
1196
1234
|
return useModel(this, arg, callback);
|
|
1197
1235
|
}
|
|
1198
1236
|
static get(required) {
|
|
1199
|
-
return
|
|
1237
|
+
return useLocal(this, required);
|
|
1200
1238
|
}
|
|
1201
1239
|
static tap(key, expect) {
|
|
1202
1240
|
return useTap(this, key, expect);
|
|
@@ -1250,7 +1288,7 @@ function Consumer(props) {
|
|
|
1250
1288
|
return children(useTap(Control));
|
|
1251
1289
|
const callback = has || get;
|
|
1252
1290
|
if (typeof callback == "function")
|
|
1253
|
-
callback(
|
|
1291
|
+
callback(useLocal(Control, !!has));
|
|
1254
1292
|
else
|
|
1255
1293
|
throw Oops$2.BadConsumerProps();
|
|
1256
1294
|
return null;
|
|
@@ -1350,5 +1388,5 @@ class Global extends MVC {
|
|
|
1350
1388
|
}
|
|
1351
1389
|
}
|
|
1352
1390
|
|
|
1353
|
-
export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom,
|
|
1391
|
+
export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, array, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom, useLocal, useModel, useTap };
|
|
1354
1392
|
//# sourceMappingURL=index.esm.js.map
|