@expressive/mvc 1.0.0-beta.4 → 1.0.0-beta.40
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/README.md +10 -1
- package/lib/index.d.ts +592 -0
- package/lib/index.esm.js +1267 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +1117 -863
- package/lib/index.js.map +1 -0
- package/package.json +20 -20
- package/types/controller.d.ts +0 -56
- package/types/index.d.ts +0 -9
- package/types/instructions.d.ts +0 -106
- package/types/lifecycle.d.ts +0 -30
- package/types/model.d.ts +0 -336
- package/types/react.d.ts +0 -46
- package/types/selector.ts +0 -29
- package/types/types.d.ts +0 -14
package/lib/index.esm.js
ADDED
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
class Issue extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(...arguments);
|
|
6
|
+
// drop first line (error factory) in stack trace.
|
|
7
|
+
this.stack = this.stack.replace(/\n.+/, "");
|
|
8
|
+
}
|
|
9
|
+
warn() {
|
|
10
|
+
console.warn(this.message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function issues(register) {
|
|
14
|
+
const Library = {};
|
|
15
|
+
for (const name in register)
|
|
16
|
+
Library[name] = (...args) => new Issue(register[name].apply(null, args));
|
|
17
|
+
return Library;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Oops$d = issues({
|
|
21
|
+
BadEffectCallback: () => `Callback for effect-callback may only return a function.`
|
|
22
|
+
});
|
|
23
|
+
const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
|
|
24
|
+
function define(target, key, value) {
|
|
25
|
+
defineProperty(target, key, { value });
|
|
26
|
+
}
|
|
27
|
+
function defineLazy(object, property, init) {
|
|
28
|
+
defineProperty(object, property, {
|
|
29
|
+
configurable: true,
|
|
30
|
+
get() {
|
|
31
|
+
const value = init.call(this);
|
|
32
|
+
defineProperty(this, property, { value });
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function setAlias(func, displayName) {
|
|
38
|
+
assign(func, { displayName });
|
|
39
|
+
}
|
|
40
|
+
function createEffect(callback) {
|
|
41
|
+
let unSet;
|
|
42
|
+
return function (value) {
|
|
43
|
+
if (typeof unSet == "function")
|
|
44
|
+
unSet();
|
|
45
|
+
unSet = callback.call(this, value);
|
|
46
|
+
if (unSet instanceof Promise)
|
|
47
|
+
unSet = undefined;
|
|
48
|
+
if (unSet && typeof unSet !== "function")
|
|
49
|
+
throw Oops$d.BadEffectCallback();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createValueEffect(callback) {
|
|
53
|
+
let unSet;
|
|
54
|
+
return function (value) {
|
|
55
|
+
if (typeof unSet == "function")
|
|
56
|
+
unSet(value);
|
|
57
|
+
const out = callback.call(this, value, this);
|
|
58
|
+
if (typeof out == "boolean")
|
|
59
|
+
return out;
|
|
60
|
+
if (!out || out instanceof Promise)
|
|
61
|
+
return;
|
|
62
|
+
if (typeof out == "function")
|
|
63
|
+
unSet = out;
|
|
64
|
+
else
|
|
65
|
+
throw Oops$d.BadEffectCallback();
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const UPDATE = new WeakMap();
|
|
70
|
+
function applyUpdate(subject, keys) {
|
|
71
|
+
UPDATE.set(subject, keys);
|
|
72
|
+
return () => {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
UPDATE.delete(subject);
|
|
75
|
+
}, 0);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const Oops$c = issues({
|
|
80
|
+
StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
|
|
81
|
+
NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
|
|
82
|
+
});
|
|
83
|
+
const READY = new WeakSet();
|
|
84
|
+
class Controller {
|
|
85
|
+
constructor(subject) {
|
|
86
|
+
this.subject = subject;
|
|
87
|
+
this.state = {};
|
|
88
|
+
this.frame = new Set();
|
|
89
|
+
this.waiting = new Set();
|
|
90
|
+
this.onDestroy = new Set();
|
|
91
|
+
this.followers = new Set();
|
|
92
|
+
this.proxy = Object.create(subject);
|
|
93
|
+
}
|
|
94
|
+
start() {
|
|
95
|
+
for (const key in this.subject)
|
|
96
|
+
this.manage(key);
|
|
97
|
+
this.emit([]);
|
|
98
|
+
}
|
|
99
|
+
stop() {
|
|
100
|
+
this.followers.clear();
|
|
101
|
+
this.onDestroy.forEach(x => x());
|
|
102
|
+
}
|
|
103
|
+
manage(key) {
|
|
104
|
+
const { proxy, state, subject } = this;
|
|
105
|
+
const { value: entry } = getOwnPropertyDescriptor(subject, key);
|
|
106
|
+
if (typeof entry == "function")
|
|
107
|
+
return;
|
|
108
|
+
let onGet;
|
|
109
|
+
let onSet;
|
|
110
|
+
let enumerable;
|
|
111
|
+
const instruction = Pending.get(entry);
|
|
112
|
+
if (instruction) {
|
|
113
|
+
Pending.delete(entry);
|
|
114
|
+
delete subject[key];
|
|
115
|
+
const desc = instruction.call(this, key, this);
|
|
116
|
+
if (desc === false)
|
|
117
|
+
return;
|
|
118
|
+
if (typeof desc == "object") {
|
|
119
|
+
if ("value" in desc)
|
|
120
|
+
state[key] = desc.value;
|
|
121
|
+
onSet = desc.set;
|
|
122
|
+
onGet = desc.get;
|
|
123
|
+
enumerable = desc.enumerable;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
state[key] = entry;
|
|
128
|
+
const set = onSet !== false
|
|
129
|
+
? this.ref(key, onSet)
|
|
130
|
+
: undefined;
|
|
131
|
+
function get() {
|
|
132
|
+
const local = this[LOCAL];
|
|
133
|
+
const value = state[key];
|
|
134
|
+
if (local && !local.watch[key])
|
|
135
|
+
local.watch[key] = true;
|
|
136
|
+
return onGet ? onGet(value, local) : value;
|
|
137
|
+
}
|
|
138
|
+
setAlias(get, `tap ${key}`);
|
|
139
|
+
defineProperty(subject, key, {
|
|
140
|
+
enumerable,
|
|
141
|
+
set,
|
|
142
|
+
get: onGet
|
|
143
|
+
? () => onGet(state[key])
|
|
144
|
+
: () => state[key]
|
|
145
|
+
});
|
|
146
|
+
defineProperty(proxy, key, {
|
|
147
|
+
enumerable,
|
|
148
|
+
set,
|
|
149
|
+
get
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
ref(key, handler) {
|
|
153
|
+
const { state, subject } = this;
|
|
154
|
+
return (value) => {
|
|
155
|
+
if (state[key] == value)
|
|
156
|
+
return;
|
|
157
|
+
if (handler)
|
|
158
|
+
switch (handler.call(subject, value, state)) {
|
|
159
|
+
case true:
|
|
160
|
+
this.update(key);
|
|
161
|
+
case false:
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.update(key, value);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
addListener(listener) {
|
|
168
|
+
this.followers.add(listener);
|
|
169
|
+
return () => {
|
|
170
|
+
this.followers.delete(listener);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
emit(keys) {
|
|
174
|
+
const waiting = [...this.waiting];
|
|
175
|
+
this.waiting.clear();
|
|
176
|
+
applyUpdate(this.subject, keys)();
|
|
177
|
+
for (const callback of waiting)
|
|
178
|
+
try {
|
|
179
|
+
callback(keys);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error(err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
update(key, value) {
|
|
186
|
+
if (1 in arguments)
|
|
187
|
+
this.state[key] = value;
|
|
188
|
+
if (this.frame.has(key))
|
|
189
|
+
return;
|
|
190
|
+
if (!this.frame.size)
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
flush(this);
|
|
193
|
+
const keys = Object.freeze([...this.frame]);
|
|
194
|
+
this.frame.clear();
|
|
195
|
+
this.emit(keys);
|
|
196
|
+
}, 0);
|
|
197
|
+
this.frame.add(key);
|
|
198
|
+
for (const callback of this.followers) {
|
|
199
|
+
const event = callback(key, this);
|
|
200
|
+
if (typeof event == "function")
|
|
201
|
+
this.waiting.add(event);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
requestUpdate(arg) {
|
|
205
|
+
if (typeof arg == "function") {
|
|
206
|
+
this.waiting.add(arg);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (typeof arg == "boolean" && arg !== this.frame.size > 0)
|
|
210
|
+
return Promise.reject(Oops$c.StrictUpdate(arg));
|
|
211
|
+
return {
|
|
212
|
+
then: (callback) => {
|
|
213
|
+
if (callback)
|
|
214
|
+
if (this.frame.size)
|
|
215
|
+
this.waiting.add(callback);
|
|
216
|
+
else
|
|
217
|
+
callback(false);
|
|
218
|
+
else
|
|
219
|
+
throw Oops$c.NoChaining();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function control(subject, cb) {
|
|
225
|
+
let control = subject[CONTROL];
|
|
226
|
+
const ready = READY.has(control);
|
|
227
|
+
if (!control) {
|
|
228
|
+
control = new Controller(subject);
|
|
229
|
+
define(subject, CONTROL, control);
|
|
230
|
+
}
|
|
231
|
+
if (!cb) {
|
|
232
|
+
if (!ready) {
|
|
233
|
+
READY.add(control);
|
|
234
|
+
control.start();
|
|
235
|
+
}
|
|
236
|
+
return control;
|
|
237
|
+
}
|
|
238
|
+
if (!ready) {
|
|
239
|
+
let done;
|
|
240
|
+
control.requestUpdate(() => {
|
|
241
|
+
done = cb(control);
|
|
242
|
+
});
|
|
243
|
+
return () => done && done();
|
|
244
|
+
}
|
|
245
|
+
return cb(control);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
class Subscriber {
|
|
249
|
+
constructor(target, onUpdate) {
|
|
250
|
+
this.onUpdate = onUpdate;
|
|
251
|
+
this.active = false;
|
|
252
|
+
this.dependant = new Set();
|
|
253
|
+
this.watch = {};
|
|
254
|
+
const parent = target instanceof Controller
|
|
255
|
+
? target : control(target);
|
|
256
|
+
const proxy = create(parent.proxy);
|
|
257
|
+
let reset;
|
|
258
|
+
define(proxy, LOCAL, this);
|
|
259
|
+
defineProperty(this, "proxy", {
|
|
260
|
+
configurable: true,
|
|
261
|
+
get() {
|
|
262
|
+
if (reset)
|
|
263
|
+
setTimeout(reset, 0);
|
|
264
|
+
return proxy;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const release = parent.addListener(key => {
|
|
268
|
+
const handler = this.watch[key];
|
|
269
|
+
if (!handler || !this.active)
|
|
270
|
+
return;
|
|
271
|
+
if (typeof handler == "function")
|
|
272
|
+
handler();
|
|
273
|
+
const notify = this.onUpdate(key, parent);
|
|
274
|
+
const getWhy = (keys) => {
|
|
275
|
+
reset = applyUpdate(proxy, keys.filter(k => k in this.watch));
|
|
276
|
+
};
|
|
277
|
+
if (notify) {
|
|
278
|
+
parent.requestUpdate(getWhy);
|
|
279
|
+
parent.requestUpdate(notify);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
this.commit = () => {
|
|
283
|
+
this.active = true;
|
|
284
|
+
this.dependant.forEach(x => x.commit());
|
|
285
|
+
return this.release;
|
|
286
|
+
};
|
|
287
|
+
this.release = () => {
|
|
288
|
+
this.dependant.forEach(x => x.release());
|
|
289
|
+
release();
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const Oops$b = issues({
|
|
295
|
+
ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
|
|
296
|
+
ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
|
|
297
|
+
});
|
|
298
|
+
const INIT = new WeakSet();
|
|
299
|
+
const INFO = new WeakMap();
|
|
300
|
+
const USED = new WeakMap();
|
|
301
|
+
const KEYS = new WeakMap();
|
|
302
|
+
function prepare(parent, key, source, setter, getter) {
|
|
303
|
+
let sub;
|
|
304
|
+
const { state, subject } = parent;
|
|
305
|
+
const info = { key, parent, priority: 1 };
|
|
306
|
+
let register = USED.get(parent);
|
|
307
|
+
if (!register) {
|
|
308
|
+
register = new Map();
|
|
309
|
+
USED.set(parent, register);
|
|
310
|
+
}
|
|
311
|
+
register.set(key, info);
|
|
312
|
+
function compute(initial) {
|
|
313
|
+
try {
|
|
314
|
+
return setter.call(sub.proxy, sub.proxy);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
Oops$b.ComputeFailed(subject, key, !!initial).warn();
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function update() {
|
|
322
|
+
let value;
|
|
323
|
+
try {
|
|
324
|
+
value = compute(false);
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
console.error(e);
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
if (state[key] !== value) {
|
|
331
|
+
parent.update(key, value);
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function defer(_key, from) {
|
|
337
|
+
let pending = KEYS.get(from);
|
|
338
|
+
if (!pending)
|
|
339
|
+
KEYS.set(from, pending = []);
|
|
340
|
+
if (info.parent !== from)
|
|
341
|
+
update();
|
|
342
|
+
else {
|
|
343
|
+
const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
|
|
344
|
+
pending.splice(after + 1, 0, update);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function create(early) {
|
|
348
|
+
sub = new Subscriber(source(), defer);
|
|
349
|
+
defineProperty(state, key, {
|
|
350
|
+
value: undefined,
|
|
351
|
+
writable: true
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
return state[key] = compute(true);
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
if (early)
|
|
358
|
+
Oops$b.ComputedEarly(key).warn();
|
|
359
|
+
throw e;
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
sub.commit();
|
|
363
|
+
for (const key in sub.watch) {
|
|
364
|
+
const peer = register.get(key);
|
|
365
|
+
if (peer && peer.priority >= info.priority)
|
|
366
|
+
info.priority = peer.priority + 1;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
setAlias(update, `try ${key}`);
|
|
371
|
+
setAlias(create, `new ${key}`);
|
|
372
|
+
setAlias(setter, `run ${key}`);
|
|
373
|
+
INIT.add(create);
|
|
374
|
+
INFO.set(update, info);
|
|
375
|
+
defineProperty(state, key, {
|
|
376
|
+
get: create,
|
|
377
|
+
configurable: true,
|
|
378
|
+
enumerable: true
|
|
379
|
+
});
|
|
380
|
+
if (getter)
|
|
381
|
+
return () => getter(parent, key);
|
|
382
|
+
}
|
|
383
|
+
function ensure(on, keys) {
|
|
384
|
+
for (const key of keys) {
|
|
385
|
+
const desc = getOwnPropertyDescriptor(on.state, key);
|
|
386
|
+
const getter = desc && desc.get;
|
|
387
|
+
if (INIT.has(getter))
|
|
388
|
+
getter(true);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function flush(on) {
|
|
392
|
+
const handled = on.frame;
|
|
393
|
+
const pending = KEYS.get(on);
|
|
394
|
+
if (!pending)
|
|
395
|
+
return;
|
|
396
|
+
while (pending.length) {
|
|
397
|
+
const why = [...handled];
|
|
398
|
+
const compute = pending.shift();
|
|
399
|
+
const { key } = INFO.get(compute);
|
|
400
|
+
if (!handled.has(key))
|
|
401
|
+
compute(why);
|
|
402
|
+
}
|
|
403
|
+
KEYS.delete(on);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const CONTROL = Symbol("CONTROL");
|
|
407
|
+
const WHY = Symbol("UPDATE");
|
|
408
|
+
const LOCAL = Symbol("LOCAL");
|
|
409
|
+
const STATE = Symbol("STATE");
|
|
410
|
+
class Model {
|
|
411
|
+
constructor() {
|
|
412
|
+
define(this, CONTROL, new Controller(this));
|
|
413
|
+
define(this, "get", this);
|
|
414
|
+
define(this, "set", this);
|
|
415
|
+
}
|
|
416
|
+
get [STATE]() {
|
|
417
|
+
return this[CONTROL].state;
|
|
418
|
+
}
|
|
419
|
+
get [WHY]() {
|
|
420
|
+
return UPDATE.get(this);
|
|
421
|
+
}
|
|
422
|
+
on(select, handler, squash, once) {
|
|
423
|
+
return control(this, control => {
|
|
424
|
+
if (typeof select == "function")
|
|
425
|
+
return control.addListener(select);
|
|
426
|
+
const keys = typeof select == "string" ? [select] :
|
|
427
|
+
!select.length ? getOwnPropertyNames(control.state) :
|
|
428
|
+
select;
|
|
429
|
+
ensure(control, keys);
|
|
430
|
+
const callback = squash
|
|
431
|
+
? handler.bind(this)
|
|
432
|
+
: frame => frame
|
|
433
|
+
.filter(k => keys.includes(k))
|
|
434
|
+
.forEach(k => handler.call(this, control.state[k], k));
|
|
435
|
+
const trigger = once
|
|
436
|
+
? frame => { remove(); callback(frame); }
|
|
437
|
+
: callback;
|
|
438
|
+
const remove = control.addListener(key => {
|
|
439
|
+
if (keys.includes(key))
|
|
440
|
+
return trigger;
|
|
441
|
+
});
|
|
442
|
+
return remove;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
once(select, callback, squash) {
|
|
446
|
+
if (callback)
|
|
447
|
+
return this.on(select, callback, squash, true);
|
|
448
|
+
return new Promise(resolve => {
|
|
449
|
+
this.on(select, resolve, true, true);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
effect(callback, select) {
|
|
453
|
+
const effect = createEffect(callback);
|
|
454
|
+
return control(this, control => {
|
|
455
|
+
if (!select) {
|
|
456
|
+
const sub = new Subscriber(control, () => invoke);
|
|
457
|
+
const invoke = () => {
|
|
458
|
+
const x = sub.proxy;
|
|
459
|
+
effect.call(x, x);
|
|
460
|
+
};
|
|
461
|
+
invoke();
|
|
462
|
+
return sub.commit();
|
|
463
|
+
}
|
|
464
|
+
const invoke = () => {
|
|
465
|
+
effect.call(this.get, this.get);
|
|
466
|
+
};
|
|
467
|
+
invoke();
|
|
468
|
+
if (!select.length) {
|
|
469
|
+
control.onDestroy.add(invoke);
|
|
470
|
+
return () => {
|
|
471
|
+
control.onDestroy.delete(invoke);
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return control.addListener(key => {
|
|
475
|
+
if (select.includes(key))
|
|
476
|
+
return invoke;
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// TODO: account for exotic properties
|
|
481
|
+
import(source, select) {
|
|
482
|
+
const { subject } = control(this);
|
|
483
|
+
if (!select)
|
|
484
|
+
select = getOwnPropertyNames(subject);
|
|
485
|
+
for (const key of select)
|
|
486
|
+
if (key in source)
|
|
487
|
+
this[key] = source[key];
|
|
488
|
+
}
|
|
489
|
+
export(subset) {
|
|
490
|
+
const { state } = control(this);
|
|
491
|
+
const output = {};
|
|
492
|
+
if (!subset)
|
|
493
|
+
subset = getOwnPropertyNames(state);
|
|
494
|
+
for (const key of subset)
|
|
495
|
+
output[key] = state[key];
|
|
496
|
+
return output;
|
|
497
|
+
}
|
|
498
|
+
update(arg, tag) {
|
|
499
|
+
const target = control(this);
|
|
500
|
+
if (typeof arg == "string") {
|
|
501
|
+
target.update(arg);
|
|
502
|
+
if (1 in arguments && arg in this) {
|
|
503
|
+
const method = this[arg];
|
|
504
|
+
if (typeof method == "function")
|
|
505
|
+
if (typeof tag != "boolean")
|
|
506
|
+
method.call(this, tag);
|
|
507
|
+
else if (tag)
|
|
508
|
+
method.call(this);
|
|
509
|
+
}
|
|
510
|
+
arg = undefined;
|
|
511
|
+
}
|
|
512
|
+
return target.requestUpdate(arg);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
|
|
516
|
+
*/
|
|
517
|
+
destroy() {
|
|
518
|
+
control(this).stop();
|
|
519
|
+
}
|
|
520
|
+
toString() {
|
|
521
|
+
return this.constructor.name;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Creates a new instance of this controller.
|
|
525
|
+
*
|
|
526
|
+
* Beyond `new this(...)`, method will activate managed-state.
|
|
527
|
+
*
|
|
528
|
+
* @param args - arguments sent to constructor
|
|
529
|
+
*/
|
|
530
|
+
static create(...args) {
|
|
531
|
+
const instance = new this(...args);
|
|
532
|
+
control(instance);
|
|
533
|
+
return instance;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Static equivalent of `x instanceof this`.
|
|
537
|
+
*
|
|
538
|
+
* Will determine if provided class is a subtype of this one.
|
|
539
|
+
*/
|
|
540
|
+
static isTypeof(maybe) {
|
|
541
|
+
return (typeof maybe == "function" &&
|
|
542
|
+
maybe.prototype instanceof this);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
defineLazy(Model, CONTROL, function () {
|
|
546
|
+
return new Controller(this);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const Pending = new Map();
|
|
550
|
+
function apply(fn, label) {
|
|
551
|
+
const name = label || fn.name || "pending";
|
|
552
|
+
const placeholder = Symbol(`${name} instruction`);
|
|
553
|
+
function setup(key) {
|
|
554
|
+
let output = fn.call(this, key, this);
|
|
555
|
+
if (typeof output == "function")
|
|
556
|
+
return { get: output };
|
|
557
|
+
if (typeof output !== "object")
|
|
558
|
+
return output || undefined;
|
|
559
|
+
if ("explicit" in output && output.explicit) {
|
|
560
|
+
defineProperty(this.subject, key, output);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
if ("recursive" in output)
|
|
564
|
+
output = Object.assign(Object.assign({}, output), { get: recursive(this, key) });
|
|
565
|
+
return output;
|
|
566
|
+
}
|
|
567
|
+
Pending.set(placeholder, setup);
|
|
568
|
+
return placeholder;
|
|
569
|
+
}
|
|
570
|
+
function recursive(source, key) {
|
|
571
|
+
const context = new WeakMap();
|
|
572
|
+
const subscribe = (parent) => {
|
|
573
|
+
let child;
|
|
574
|
+
const init = () => {
|
|
575
|
+
if (child) {
|
|
576
|
+
child.release();
|
|
577
|
+
parent.dependant.delete(child);
|
|
578
|
+
context.set(parent, undefined);
|
|
579
|
+
child = undefined;
|
|
580
|
+
}
|
|
581
|
+
const value = source.state[key];
|
|
582
|
+
if (value && CONTROL in value) {
|
|
583
|
+
child = new Subscriber(value, parent.onUpdate);
|
|
584
|
+
if (parent.active)
|
|
585
|
+
child.commit();
|
|
586
|
+
parent.dependant.add(child);
|
|
587
|
+
context.set(parent, child.proxy);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
init();
|
|
591
|
+
parent.watch[key] = init;
|
|
592
|
+
};
|
|
593
|
+
return (value, local) => {
|
|
594
|
+
if (!local)
|
|
595
|
+
return value;
|
|
596
|
+
if (!context.has(local))
|
|
597
|
+
subscribe(local);
|
|
598
|
+
return context.get(local);
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const Oops$a = issues({
|
|
603
|
+
DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
|
|
604
|
+
});
|
|
605
|
+
function act(task) {
|
|
606
|
+
return apply(function act(key) {
|
|
607
|
+
let pending = false;
|
|
608
|
+
const invoke = (...args) => {
|
|
609
|
+
if (pending)
|
|
610
|
+
return Promise.reject(Oops$a.DuplicateAction(key));
|
|
611
|
+
pending = true;
|
|
612
|
+
this.update(key);
|
|
613
|
+
return new Promise(res => {
|
|
614
|
+
res(task.apply(this.subject, args));
|
|
615
|
+
}).finally(() => {
|
|
616
|
+
pending = false;
|
|
617
|
+
this.update(key);
|
|
618
|
+
});
|
|
619
|
+
};
|
|
620
|
+
this.state[key] = undefined;
|
|
621
|
+
setAlias(invoke, `run ${key}`);
|
|
622
|
+
defineProperty(invoke, "active", {
|
|
623
|
+
get: () => pending
|
|
624
|
+
});
|
|
625
|
+
return {
|
|
626
|
+
value: invoke,
|
|
627
|
+
set: false
|
|
628
|
+
};
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const Oops$9 = issues({
|
|
633
|
+
ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
|
|
634
|
+
});
|
|
635
|
+
/**
|
|
636
|
+
* Get value, suspend instead if undefined.
|
|
637
|
+
*
|
|
638
|
+
* Throws suspense promise also interpretable as an error.
|
|
639
|
+
* React could handle it but other contexts probably not.
|
|
640
|
+
*/
|
|
641
|
+
function pendingValue(via, key) {
|
|
642
|
+
const value = via.state[key];
|
|
643
|
+
if (value === undefined)
|
|
644
|
+
throw suspend(via, key);
|
|
645
|
+
return value;
|
|
646
|
+
}
|
|
647
|
+
function pendingFactory(via, key, fn) {
|
|
648
|
+
const { subject, state } = via;
|
|
649
|
+
let waiting;
|
|
650
|
+
let error;
|
|
651
|
+
return () => {
|
|
652
|
+
if (waiting)
|
|
653
|
+
throw waiting;
|
|
654
|
+
if (error)
|
|
655
|
+
throw error;
|
|
656
|
+
if (key in state)
|
|
657
|
+
return state[key];
|
|
658
|
+
let output;
|
|
659
|
+
try {
|
|
660
|
+
output = fn.call(subject, key, subject);
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
throw error = err;
|
|
664
|
+
}
|
|
665
|
+
if (output instanceof Promise) {
|
|
666
|
+
const issue = Oops$9.ValueNotReady(subject, key);
|
|
667
|
+
output
|
|
668
|
+
.catch(err => error = err)
|
|
669
|
+
.then(out => state[key] = out)
|
|
670
|
+
.finally(() => waiting = undefined);
|
|
671
|
+
throw waiting = Object.assign(output, {
|
|
672
|
+
message: issue.message,
|
|
673
|
+
stack: issue.stack
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
return state[key] = output;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function suspend(source, key) {
|
|
682
|
+
const error = Oops$9.ValueNotReady(source.subject, key);
|
|
683
|
+
const promise = new Promise(resolve => {
|
|
684
|
+
const release = source.addListener(forKey => {
|
|
685
|
+
if (forKey == key)
|
|
686
|
+
return () => {
|
|
687
|
+
if (source.state[key] !== undefined) {
|
|
688
|
+
release();
|
|
689
|
+
resolve();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
return Object.assign(promise, {
|
|
695
|
+
toString: () => String(error),
|
|
696
|
+
name: "Suspense",
|
|
697
|
+
message: error.message,
|
|
698
|
+
stack: error.stack
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const Oops$8 = issues({
|
|
703
|
+
BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
|
|
704
|
+
PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
|
|
705
|
+
});
|
|
706
|
+
function from(source, setter, arg) {
|
|
707
|
+
return apply(function from(key) {
|
|
708
|
+
const { subject } = this;
|
|
709
|
+
let getSource;
|
|
710
|
+
let getter;
|
|
711
|
+
if (arg === true || setter === true)
|
|
712
|
+
getter = pendingValue;
|
|
713
|
+
if (typeof setter == "boolean")
|
|
714
|
+
setter = undefined;
|
|
715
|
+
// Easy mistake, using a peer, will always be unresolved.
|
|
716
|
+
if (typeof source == "symbol")
|
|
717
|
+
throw Oops$8.PeerNotAllowed(subject, key);
|
|
718
|
+
// replace source controller in-case it is different
|
|
719
|
+
if (typeof source == "object")
|
|
720
|
+
getSource = () => control(source);
|
|
721
|
+
// specifically an arrow function (getter factory)
|
|
722
|
+
else if (!source.prototype) {
|
|
723
|
+
setter = source.call(subject, key);
|
|
724
|
+
getSource = () => this;
|
|
725
|
+
}
|
|
726
|
+
// Regular function is too ambiguous so not allowed.
|
|
727
|
+
else
|
|
728
|
+
throw Oops$8.BadComputedSource(subject, key, source);
|
|
729
|
+
return prepare(this, key, getSource, setter, getter);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function ref(arg) {
|
|
734
|
+
return apply(function ref(key) {
|
|
735
|
+
let value = {};
|
|
736
|
+
if (typeof arg == "object") {
|
|
737
|
+
const source = control(arg);
|
|
738
|
+
for (const key in source.state)
|
|
739
|
+
defineLazy(value, key, createRef.bind(source, key));
|
|
740
|
+
}
|
|
741
|
+
else
|
|
742
|
+
value = createRef.call(this, key, arg);
|
|
743
|
+
return { value, explicit: true };
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function createRef(key, cb) {
|
|
747
|
+
const refObjectFunction = this.ref(key, cb && createValueEffect(cb));
|
|
748
|
+
defineProperty(refObjectFunction, "current", {
|
|
749
|
+
set: refObjectFunction,
|
|
750
|
+
get: () => this.state[key]
|
|
751
|
+
});
|
|
752
|
+
return refObjectFunction;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const Oops$7 = issues({
|
|
756
|
+
NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
|
|
757
|
+
BadFactory: () => `Set instruction can only accept a factory or undefined.`,
|
|
758
|
+
FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
|
|
759
|
+
});
|
|
760
|
+
function set(factory, argument) {
|
|
761
|
+
return apply(function set(key) {
|
|
762
|
+
let set;
|
|
763
|
+
let get;
|
|
764
|
+
const required = argument === true || argument === undefined;
|
|
765
|
+
if (factory === undefined)
|
|
766
|
+
get = () => pendingValue(this, key);
|
|
767
|
+
else if (typeof factory !== "function")
|
|
768
|
+
throw Oops$7.BadFactory();
|
|
769
|
+
else {
|
|
770
|
+
get = pendingFactory(this, key, factory);
|
|
771
|
+
if (required)
|
|
772
|
+
try {
|
|
773
|
+
get();
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
if (err instanceof Promise)
|
|
777
|
+
;
|
|
778
|
+
else {
|
|
779
|
+
Oops$7.FactoryFailed(this.subject, key).warn();
|
|
780
|
+
throw err;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (typeof argument == "function")
|
|
785
|
+
set = createValueEffect(argument);
|
|
786
|
+
else
|
|
787
|
+
set = (value) => {
|
|
788
|
+
if (value === undefined && required)
|
|
789
|
+
throw Oops$7.NonOptional(this.subject, key);
|
|
790
|
+
};
|
|
791
|
+
return { set, get };
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const Parent = new WeakMap();
|
|
796
|
+
const Oops$6 = issues({
|
|
797
|
+
ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
|
|
798
|
+
UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
|
|
799
|
+
});
|
|
800
|
+
function parent(Expects, required) {
|
|
801
|
+
return apply(function parent() {
|
|
802
|
+
const child = this.subject;
|
|
803
|
+
const value = Parent.get(child);
|
|
804
|
+
const expected = Expects.name;
|
|
805
|
+
if (!value) {
|
|
806
|
+
if (required !== false)
|
|
807
|
+
throw Oops$6.ParentRequired(expected, child);
|
|
808
|
+
}
|
|
809
|
+
else if (!(value instanceof Expects))
|
|
810
|
+
throw Oops$6.UnexpectedParent(expected, child, value);
|
|
811
|
+
return { value };
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const Oops$5 = issues({
|
|
816
|
+
BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
|
|
817
|
+
});
|
|
818
|
+
function use$1(input, argument) {
|
|
819
|
+
return apply(function use(key) {
|
|
820
|
+
const { subject } = this;
|
|
821
|
+
const onUpdate = (next) => {
|
|
822
|
+
this.state[key] = next;
|
|
823
|
+
if (next) {
|
|
824
|
+
Parent.set(next, subject);
|
|
825
|
+
control(next);
|
|
826
|
+
}
|
|
827
|
+
if (typeof argument == "function")
|
|
828
|
+
argument(next);
|
|
829
|
+
return true;
|
|
830
|
+
};
|
|
831
|
+
if (input) {
|
|
832
|
+
const value = Model.isTypeof(input)
|
|
833
|
+
? new input() :
|
|
834
|
+
typeof input === "object"
|
|
835
|
+
? input :
|
|
836
|
+
typeof input === "function"
|
|
837
|
+
? input()
|
|
838
|
+
: null;
|
|
839
|
+
if (value === null)
|
|
840
|
+
throw Oops$5.BadArgument(typeof input);
|
|
841
|
+
if (value)
|
|
842
|
+
onUpdate(value);
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
set: onUpdate,
|
|
846
|
+
recursive: true
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
class Lookup {
|
|
852
|
+
constructor() {
|
|
853
|
+
this.table = new Map();
|
|
854
|
+
}
|
|
855
|
+
get local() {
|
|
856
|
+
return [
|
|
857
|
+
...new Set(getOwnPropertySymbols(this).map(symbol => this[symbol]))
|
|
858
|
+
];
|
|
859
|
+
}
|
|
860
|
+
key(T) {
|
|
861
|
+
let key = this.table.get(T);
|
|
862
|
+
if (!key) {
|
|
863
|
+
key = Symbol(T.name);
|
|
864
|
+
this.table.set(T, key);
|
|
865
|
+
}
|
|
866
|
+
return key;
|
|
867
|
+
}
|
|
868
|
+
get(T) {
|
|
869
|
+
return this[this.key(T)];
|
|
870
|
+
}
|
|
871
|
+
push(I) {
|
|
872
|
+
const next = create(this);
|
|
873
|
+
if (I instanceof Model || typeof I == "function")
|
|
874
|
+
next.register(I);
|
|
875
|
+
else
|
|
876
|
+
for (const i of values(I))
|
|
877
|
+
next.register(i);
|
|
878
|
+
return next;
|
|
879
|
+
}
|
|
880
|
+
register(I) {
|
|
881
|
+
let writable = true;
|
|
882
|
+
let T;
|
|
883
|
+
if (I instanceof Model) {
|
|
884
|
+
T = I.constructor;
|
|
885
|
+
writable = false;
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
T = I;
|
|
889
|
+
I = I.create();
|
|
890
|
+
}
|
|
891
|
+
do {
|
|
892
|
+
const key = this.key(T);
|
|
893
|
+
const conflict = this.hasOwnProperty(key);
|
|
894
|
+
defineProperty(this, key, {
|
|
895
|
+
value: conflict ? null : I,
|
|
896
|
+
configurable: true,
|
|
897
|
+
writable
|
|
898
|
+
});
|
|
899
|
+
T = getPrototypeOf(T);
|
|
900
|
+
} while (T !== Model);
|
|
901
|
+
return I;
|
|
902
|
+
}
|
|
903
|
+
pop() {
|
|
904
|
+
for (const key of getOwnPropertySymbols(this)) {
|
|
905
|
+
const entry = getOwnPropertyDescriptor(this, key);
|
|
906
|
+
if (entry.writable && entry.value)
|
|
907
|
+
entry.value.destroy();
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const LookupContext = React.createContext(new Lookup());
|
|
913
|
+
const useLookup = () => React.useContext(LookupContext);
|
|
914
|
+
const Oops$4 = issues({
|
|
915
|
+
NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`
|
|
916
|
+
});
|
|
917
|
+
function useInContext(Type, arg) {
|
|
918
|
+
const instance = useLookup().get(Type);
|
|
919
|
+
if (!instance && arg !== false)
|
|
920
|
+
throw Oops$4.NothingInContext(Type.name);
|
|
921
|
+
return typeof arg == "string" ?
|
|
922
|
+
instance[arg] :
|
|
923
|
+
instance;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const Oops$3 = issues({
|
|
927
|
+
CantAttachGlobal: (parent, child) => `Global '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
|
|
928
|
+
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.`
|
|
929
|
+
});
|
|
930
|
+
const PendingContext = new WeakMap();
|
|
931
|
+
const ContextWasUsed = new WeakMap();
|
|
932
|
+
function tap(type, argument) {
|
|
933
|
+
return apply(function tap(key) {
|
|
934
|
+
if ("set" in type)
|
|
935
|
+
this.state[key] = type.get();
|
|
936
|
+
else {
|
|
937
|
+
const { subject } = this;
|
|
938
|
+
if ("set" in subject.constructor)
|
|
939
|
+
throw Oops$3.CantAttachGlobal(subject, type.name);
|
|
940
|
+
getPending(subject).push(context => {
|
|
941
|
+
let instance = context.get(type);
|
|
942
|
+
if (typeof argument == "function") {
|
|
943
|
+
if (argument(instance) === false)
|
|
944
|
+
instance = undefined;
|
|
945
|
+
}
|
|
946
|
+
else if (!instance && argument)
|
|
947
|
+
throw Oops$3.AmbientRequired(type.name, subject, key);
|
|
948
|
+
this.update(key, instance);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
return {
|
|
952
|
+
recursive: true
|
|
953
|
+
};
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function usePeerContext(subject) {
|
|
957
|
+
if (ContextWasUsed.has(subject)) {
|
|
958
|
+
if (ContextWasUsed.get(subject))
|
|
959
|
+
useLookup();
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const pending = PendingContext.get(subject);
|
|
963
|
+
if (pending) {
|
|
964
|
+
const local = useLookup();
|
|
965
|
+
for (const init of pending)
|
|
966
|
+
init(local);
|
|
967
|
+
PendingContext.delete(subject);
|
|
968
|
+
}
|
|
969
|
+
ContextWasUsed.set(subject, !!pending);
|
|
970
|
+
}
|
|
971
|
+
function getPending(subject) {
|
|
972
|
+
let pending = PendingContext.get(subject);
|
|
973
|
+
if (!pending)
|
|
974
|
+
PendingContext.set(subject, pending = []);
|
|
975
|
+
return pending;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function use(init) {
|
|
979
|
+
const [state, update] = React.useState(() => [
|
|
980
|
+
init(() => update(state.concat()))
|
|
981
|
+
]);
|
|
982
|
+
return state[0];
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function useModel(source, arg, arg2) {
|
|
986
|
+
const instance = React.useMemo(() => {
|
|
987
|
+
const cb = arg2 || arg;
|
|
988
|
+
const instance = typeof source == "function" ?
|
|
989
|
+
source.prototype instanceof Model ?
|
|
990
|
+
new source() :
|
|
991
|
+
source() :
|
|
992
|
+
source;
|
|
993
|
+
control(instance);
|
|
994
|
+
if (typeof cb == "function")
|
|
995
|
+
cb(instance);
|
|
996
|
+
return instance;
|
|
997
|
+
}, []);
|
|
998
|
+
if (Array.isArray(arg)) {
|
|
999
|
+
const update = useState(0)[1];
|
|
1000
|
+
React.useLayoutEffect(() => {
|
|
1001
|
+
if (arg.length && instance instanceof Model)
|
|
1002
|
+
instance.on(arg, () => update(x => x + 1), true);
|
|
1003
|
+
return () => {
|
|
1004
|
+
if (Model.isTypeof(source))
|
|
1005
|
+
instance.destroy();
|
|
1006
|
+
};
|
|
1007
|
+
}, []);
|
|
1008
|
+
return instance;
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
const local = use(refresh => (new Subscriber(instance, () => refresh)));
|
|
1012
|
+
if (typeof arg == "object") {
|
|
1013
|
+
local.active = false;
|
|
1014
|
+
if (typeof arg2 !== "object")
|
|
1015
|
+
arg2 = getOwnPropertyNames(instance);
|
|
1016
|
+
for (const key of arg2)
|
|
1017
|
+
if (key in arg)
|
|
1018
|
+
instance[key] = arg[key];
|
|
1019
|
+
React.useLayoutEffect(() => {
|
|
1020
|
+
local.active = true;
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
React.useLayoutEffect(() => {
|
|
1024
|
+
local.commit();
|
|
1025
|
+
return () => {
|
|
1026
|
+
local.release();
|
|
1027
|
+
if (Model.isTypeof(source))
|
|
1028
|
+
instance.destroy();
|
|
1029
|
+
};
|
|
1030
|
+
}, []);
|
|
1031
|
+
return local.proxy;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function useFrom(target, compute, suspend) {
|
|
1036
|
+
const local = use(refresh => {
|
|
1037
|
+
const sub = new Subscriber(target, () => update);
|
|
1038
|
+
const spy = sub.proxy;
|
|
1039
|
+
let value = compute.call(spy, spy);
|
|
1040
|
+
let update;
|
|
1041
|
+
let retry;
|
|
1042
|
+
const set = (next) => {
|
|
1043
|
+
value = next;
|
|
1044
|
+
if (retry) {
|
|
1045
|
+
retry();
|
|
1046
|
+
retry = undefined;
|
|
1047
|
+
}
|
|
1048
|
+
else
|
|
1049
|
+
refresh();
|
|
1050
|
+
};
|
|
1051
|
+
if (value instanceof Promise) {
|
|
1052
|
+
value.then(set);
|
|
1053
|
+
value = undefined;
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
sub.commit();
|
|
1057
|
+
update = () => {
|
|
1058
|
+
const next = compute.call(spy, spy);
|
|
1059
|
+
if (value != next)
|
|
1060
|
+
set(next);
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
defineProperty(sub, "proxy", {
|
|
1064
|
+
get() {
|
|
1065
|
+
if (value === undefined && suspend)
|
|
1066
|
+
throw new Promise(res => retry = res);
|
|
1067
|
+
return value;
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
return sub;
|
|
1071
|
+
});
|
|
1072
|
+
React.useLayoutEffect(() => local.release, []);
|
|
1073
|
+
return local.proxy;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function useTap(source, path, expect) {
|
|
1077
|
+
const instance = useMemo(() => {
|
|
1078
|
+
if (typeof source == "object")
|
|
1079
|
+
return () => source;
|
|
1080
|
+
if ("get" in source)
|
|
1081
|
+
return () => source.get();
|
|
1082
|
+
if ("prototype" in source)
|
|
1083
|
+
return () => useInContext(source);
|
|
1084
|
+
else
|
|
1085
|
+
return source;
|
|
1086
|
+
}, [])();
|
|
1087
|
+
if (typeof path == "function")
|
|
1088
|
+
return useFrom(instance, path, expect);
|
|
1089
|
+
const local = use(refresh => {
|
|
1090
|
+
const parent = control(instance);
|
|
1091
|
+
const sub = new Subscriber(parent, () => refresh);
|
|
1092
|
+
if (typeof path == "string") {
|
|
1093
|
+
const source = sub.proxy;
|
|
1094
|
+
defineProperty(sub, "proxy", {
|
|
1095
|
+
get() {
|
|
1096
|
+
const value = source[path];
|
|
1097
|
+
if (value === undefined && expect)
|
|
1098
|
+
throw suspend(parent, path);
|
|
1099
|
+
return value;
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return sub;
|
|
1104
|
+
});
|
|
1105
|
+
React.useLayoutEffect(local.commit, []);
|
|
1106
|
+
return local.proxy;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* istanbul ignore file */
|
|
1110
|
+
class MVC extends Model {
|
|
1111
|
+
tap(path, expect) {
|
|
1112
|
+
return useTap(this, path, expect);
|
|
1113
|
+
}
|
|
1114
|
+
use(arg, callback) {
|
|
1115
|
+
return useModel(this, arg, callback);
|
|
1116
|
+
}
|
|
1117
|
+
static get(required) {
|
|
1118
|
+
return useInContext(this, required);
|
|
1119
|
+
}
|
|
1120
|
+
static tap(key, expect) {
|
|
1121
|
+
return useTap(this, key, expect);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* **React Hook** - Spawn and maintain a controller from within a component.
|
|
1125
|
+
*
|
|
1126
|
+
* More efficient than `use()` if you don't need hook-based features.
|
|
1127
|
+
*
|
|
1128
|
+
* @param callback - Run after creation of instance.
|
|
1129
|
+
*/
|
|
1130
|
+
static new(callback) {
|
|
1131
|
+
return useModel(this, [], callback);
|
|
1132
|
+
}
|
|
1133
|
+
static use(a, b) {
|
|
1134
|
+
const instance = useModel(this, a, b);
|
|
1135
|
+
usePeerContext(instance.get);
|
|
1136
|
+
return instance;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* @deprecated consider doing this manually - not worth minor efficiency gain over using.
|
|
1140
|
+
*/
|
|
1141
|
+
static uses(apply, keys) {
|
|
1142
|
+
return this.use(instance => {
|
|
1143
|
+
instance.import(apply, keys);
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* @deprecated is now replaced by overload of `use` method.
|
|
1148
|
+
*/
|
|
1149
|
+
static using(apply, keys) {
|
|
1150
|
+
return this.use(apply, keys);
|
|
1151
|
+
}
|
|
1152
|
+
static meta(path, expect) {
|
|
1153
|
+
return useTap(() => this, path, expect);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const Oops$2 = issues({
|
|
1158
|
+
BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
|
|
1159
|
+
});
|
|
1160
|
+
function Consumer(props) {
|
|
1161
|
+
const { get, has, children, of: Control } = props;
|
|
1162
|
+
if (typeof children == "function")
|
|
1163
|
+
return children(useTap(Control));
|
|
1164
|
+
const callback = has || get;
|
|
1165
|
+
if (typeof callback == "function")
|
|
1166
|
+
callback(useInContext(Control, !!has));
|
|
1167
|
+
else
|
|
1168
|
+
throw Oops$2.BadConsumerProps();
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const Oops$1 = issues({
|
|
1173
|
+
NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`
|
|
1174
|
+
});
|
|
1175
|
+
function Provider(props) {
|
|
1176
|
+
const context = useNewContext(props.of);
|
|
1177
|
+
const render = props.children;
|
|
1178
|
+
useAppliedProps(context, props);
|
|
1179
|
+
React.useLayoutEffect(() => () => context.pop(), []);
|
|
1180
|
+
return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
|
|
1181
|
+
? React.createElement(RenderFunction, { context, render })
|
|
1182
|
+
: render);
|
|
1183
|
+
}
|
|
1184
|
+
function useNewContext(inject) {
|
|
1185
|
+
const from = useLookup();
|
|
1186
|
+
return React.useMemo(() => {
|
|
1187
|
+
if (!inject)
|
|
1188
|
+
throw Oops$1.NoProviderType();
|
|
1189
|
+
const context = from.push(inject);
|
|
1190
|
+
for (const instance of context.local)
|
|
1191
|
+
if (instance)
|
|
1192
|
+
for (const apply of getPending(instance))
|
|
1193
|
+
apply(context);
|
|
1194
|
+
return context;
|
|
1195
|
+
}, []);
|
|
1196
|
+
}
|
|
1197
|
+
function useAppliedProps(within, props) {
|
|
1198
|
+
const update = React.useMemo(() => {
|
|
1199
|
+
const targets = within.local;
|
|
1200
|
+
return function integrate(props) {
|
|
1201
|
+
for (const [key, value] of entries(props))
|
|
1202
|
+
if (key != "of" && key != "children")
|
|
1203
|
+
for (const into of targets)
|
|
1204
|
+
if (into && key in into)
|
|
1205
|
+
into[key] = value;
|
|
1206
|
+
};
|
|
1207
|
+
}, []);
|
|
1208
|
+
update(props);
|
|
1209
|
+
}
|
|
1210
|
+
function RenderFunction(props) {
|
|
1211
|
+
const hook = use(refresh => {
|
|
1212
|
+
const targets = props.context.local;
|
|
1213
|
+
if (targets.length == 1)
|
|
1214
|
+
return new Subscriber(targets[0], () => refresh);
|
|
1215
|
+
return {};
|
|
1216
|
+
});
|
|
1217
|
+
if (hook.commit)
|
|
1218
|
+
React.useLayoutEffect(hook.commit, []);
|
|
1219
|
+
return props.render(hook.proxy);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const Oops = issues({
|
|
1223
|
+
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.`,
|
|
1224
|
+
GlobalExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
|
|
1225
|
+
});
|
|
1226
|
+
const Active = new WeakMap();
|
|
1227
|
+
class Global extends MVC {
|
|
1228
|
+
static create(...args) {
|
|
1229
|
+
const Type = this;
|
|
1230
|
+
if (Active.has(Type))
|
|
1231
|
+
throw Oops.GlobalExists(this.name);
|
|
1232
|
+
const instance = super.create(...args);
|
|
1233
|
+
Active.set(Type, instance);
|
|
1234
|
+
return instance;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Update the active instance of this class.
|
|
1238
|
+
* Returns a thenable; resolves after successful update.
|
|
1239
|
+
* If instance does not already exist, one will be created.
|
|
1240
|
+
*/
|
|
1241
|
+
static set(updates) {
|
|
1242
|
+
const instance = Active.get(this) || this.create();
|
|
1243
|
+
instance.import(updates);
|
|
1244
|
+
return instance.update();
|
|
1245
|
+
}
|
|
1246
|
+
static get(arg) {
|
|
1247
|
+
const instance = Active.get(this);
|
|
1248
|
+
if (!instance && arg !== false)
|
|
1249
|
+
throw Oops.GlobalDoesNotExist(this.name);
|
|
1250
|
+
return (typeof arg == "string" ?
|
|
1251
|
+
instance[arg] :
|
|
1252
|
+
instance);
|
|
1253
|
+
}
|
|
1254
|
+
/** Destroy current instance of Global, if it exists. */
|
|
1255
|
+
static reset() {
|
|
1256
|
+
const current = Active.get(this);
|
|
1257
|
+
if (current)
|
|
1258
|
+
current.destroy();
|
|
1259
|
+
}
|
|
1260
|
+
destroy() {
|
|
1261
|
+
super.destroy();
|
|
1262
|
+
Active.delete(this.constructor);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
export { CONTROL, Consumer, Global, LOCAL, MVC as Model, Provider, STATE, WHY, act, apply, MVC as default, from, parent, ref, set, tap, use$1 as use, useFrom, useInContext, useModel, useTap };
|
|
1267
|
+
//# sourceMappingURL=index.esm.js.map
|