@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 +21 -18
- package/lib/index.esm.js +82 -84
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +82 -84
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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<
|
|
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
|
|
423
|
-
declare const STATE
|
|
424
|
-
declare const UPDATE
|
|
425
|
-
declare const CONTROL
|
|
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:
|
|
428
|
-
readonly LOCAL:
|
|
429
|
-
readonly STATE:
|
|
430
|
-
readonly 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
|
|
577
|
-
declare function useModel<T extends Model>(source: Model.Type<T
|
|
578
|
-
declare function useModel<T extends Model>(source: Model.Type<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$
|
|
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$
|
|
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$
|
|
49
|
+
throw Oops$d.BadCallback();
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const Oops$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
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
|
|
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(() =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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 =
|
|
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$
|
|
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$
|
|
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$
|
|
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) :
|
|
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,
|
|
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(
|
|
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.
|
|
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
|
|
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 =
|
|
1328
|
-
const STATE =
|
|
1329
|
-
const UPDATE =
|
|
1330
|
-
const 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
|
-
|
|
1349
|
+
const source = Subscriber.get(this) || Control.get(this);
|
|
1350
|
+
return source && source.latest;
|
|
1353
1351
|
}
|
|
1354
1352
|
}
|
|
1355
1353
|
});
|