@expressive/mvc 0.3.0 → 0.4.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 +18 -35
- package/lib/index.esm.js +670 -491
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +670 -491
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/index.esm.js
CHANGED
|
@@ -51,7 +51,7 @@ function suspend(source, key) {
|
|
|
51
51
|
const release = source.addListener(forKey => {
|
|
52
52
|
if (forKey == key)
|
|
53
53
|
return () => {
|
|
54
|
-
if (source.state
|
|
54
|
+
if (source.state.get(key) !== undefined) {
|
|
55
55
|
release();
|
|
56
56
|
resolve();
|
|
57
57
|
}
|
|
@@ -132,240 +132,37 @@ function createValueEffect(callback) {
|
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
UPDATE.delete(subject);
|
|
141
|
-
}, 0);
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function getUpdate(subject) {
|
|
145
|
-
return UPDATE.get(subject);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const CONTROL = Symbol("CONTROL");
|
|
149
|
-
const WHY = Symbol("UPDATE");
|
|
150
|
-
const LOCAL = Symbol("LOCAL");
|
|
151
|
-
const STATE = Symbol("STATE");
|
|
152
|
-
class Model {
|
|
153
|
-
constructor() {
|
|
154
|
-
define(this, CONTROL, new Controller(this));
|
|
155
|
-
define(this, "get", this);
|
|
156
|
-
define(this, "is", this);
|
|
157
|
-
defineLazy(this, "set", () => {
|
|
158
|
-
const controller = control(this);
|
|
159
|
-
const assign = (key, value) => {
|
|
160
|
-
controller.update(key, value);
|
|
161
|
-
};
|
|
162
|
-
for (const key of getOwnPropertyNames(controller.proxy))
|
|
163
|
-
define(assign, key, controller.ref(key));
|
|
164
|
-
return assign;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
get [STATE]() {
|
|
168
|
-
return this[CONTROL].state;
|
|
169
|
-
}
|
|
170
|
-
get [WHY]() {
|
|
171
|
-
return UPDATE.get(this);
|
|
172
|
-
}
|
|
173
|
-
on(select, handler, squash, once) {
|
|
174
|
-
return control(this, control => {
|
|
175
|
-
if (typeof select == "function")
|
|
176
|
-
return control.addListener(select);
|
|
177
|
-
const selection = typeof select == "string" ? [select] :
|
|
178
|
-
!select.length ? getOwnPropertyNames(control.state) :
|
|
179
|
-
select;
|
|
180
|
-
ensure(control, selection);
|
|
181
|
-
const callback = squash
|
|
182
|
-
? () => {
|
|
183
|
-
handler.call(this, getUpdate(this));
|
|
184
|
-
}
|
|
185
|
-
: () => {
|
|
186
|
-
getUpdate(this)
|
|
187
|
-
.filter(k => selection.includes(k))
|
|
188
|
-
.forEach(k => {
|
|
189
|
-
handler.call(this, control.state[k], k);
|
|
190
|
-
});
|
|
191
|
-
};
|
|
192
|
-
const onEvent = once
|
|
193
|
-
? () => {
|
|
194
|
-
remove();
|
|
195
|
-
callback();
|
|
196
|
-
}
|
|
197
|
-
: callback;
|
|
198
|
-
const remove = control.addListener(key => {
|
|
199
|
-
if (selection.includes(key))
|
|
200
|
-
return onEvent;
|
|
201
|
-
});
|
|
202
|
-
return remove;
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
once(select, callback, squash) {
|
|
206
|
-
if (callback)
|
|
207
|
-
return this.on(select, callback, squash, true);
|
|
208
|
-
return new Promise(resolve => {
|
|
209
|
-
this.on(select, resolve, true, true);
|
|
210
|
-
});
|
|
135
|
+
function ensure(subject, cb) {
|
|
136
|
+
let control = subject[CONTROL];
|
|
137
|
+
if (!control) {
|
|
138
|
+
control = new Controller(subject);
|
|
139
|
+
define(subject, CONTROL, control);
|
|
211
140
|
}
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
let
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (busy)
|
|
219
|
-
return;
|
|
220
|
-
const output = mayRetry(() => {
|
|
221
|
-
effect.call(inject, inject);
|
|
222
|
-
});
|
|
223
|
-
if (output instanceof Promise) {
|
|
224
|
-
output.finally(() => busy = false);
|
|
225
|
-
busy = true;
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
if (!select) {
|
|
229
|
-
const sub = new Subscriber(control, () => invoke);
|
|
230
|
-
inject = sub.proxy;
|
|
231
|
-
invoke();
|
|
232
|
-
return sub.commit();
|
|
233
|
-
}
|
|
234
|
-
invoke();
|
|
235
|
-
if (!select.length) {
|
|
236
|
-
control.onDestroy.add(invoke);
|
|
237
|
-
return () => {
|
|
238
|
-
control.onDestroy.delete(invoke);
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
return control.addListener(key => {
|
|
242
|
-
if (select.includes(key))
|
|
243
|
-
return invoke;
|
|
141
|
+
if (!control.state) {
|
|
142
|
+
const { waiting } = control;
|
|
143
|
+
if (cb) {
|
|
144
|
+
let done;
|
|
145
|
+
waiting.add(() => {
|
|
146
|
+
done = cb(control);
|
|
244
147
|
});
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
// TODO: account for exotic properties
|
|
248
|
-
import(source, select) {
|
|
249
|
-
const { subject } = control(this);
|
|
250
|
-
if (!select)
|
|
251
|
-
select = getOwnPropertyNames(subject);
|
|
252
|
-
for (const key of select)
|
|
253
|
-
if (key in source)
|
|
254
|
-
this[key] = source[key];
|
|
255
|
-
}
|
|
256
|
-
export(subset) {
|
|
257
|
-
const { state } = control(this);
|
|
258
|
-
const output = {};
|
|
259
|
-
if (!subset)
|
|
260
|
-
subset = getOwnPropertyNames(state);
|
|
261
|
-
for (const key of subset)
|
|
262
|
-
output[key] = state[key];
|
|
263
|
-
return output;
|
|
264
|
-
}
|
|
265
|
-
update(arg, tag) {
|
|
266
|
-
const target = control(this);
|
|
267
|
-
if (typeof arg == "string") {
|
|
268
|
-
target.update(arg);
|
|
269
|
-
if (1 in arguments && arg in this) {
|
|
270
|
-
const method = this[arg];
|
|
271
|
-
if (typeof method == "function")
|
|
272
|
-
if (typeof tag != "boolean")
|
|
273
|
-
method.call(this, tag);
|
|
274
|
-
else if (tag)
|
|
275
|
-
method.call(this);
|
|
276
|
-
}
|
|
277
|
-
arg = undefined;
|
|
148
|
+
return () => done && done();
|
|
278
149
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
toString() {
|
|
288
|
-
return this.constructor.name;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Creates a new instance of this controller.
|
|
292
|
-
*
|
|
293
|
-
* Beyond `new this(...)`, method will activate managed-state.
|
|
294
|
-
*
|
|
295
|
-
* @param args - arguments sent to constructor
|
|
296
|
-
*/
|
|
297
|
-
static create(...args) {
|
|
298
|
-
const instance = new this(...args);
|
|
299
|
-
control(instance);
|
|
300
|
-
return instance;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Static equivalent of `x instanceof this`.
|
|
304
|
-
*
|
|
305
|
-
* Will determine if provided class is a subtype of this one.
|
|
306
|
-
*/
|
|
307
|
-
static isTypeof(maybe) {
|
|
308
|
-
return (typeof maybe == "function" &&
|
|
309
|
-
maybe.prototype instanceof this);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
class Subscriber {
|
|
314
|
-
constructor(target, onUpdate) {
|
|
315
|
-
this.onUpdate = onUpdate;
|
|
316
|
-
this.active = false;
|
|
317
|
-
this.dependant = new Set();
|
|
318
|
-
this.watch = {};
|
|
319
|
-
const parent = target instanceof Controller
|
|
320
|
-
? target : control(target);
|
|
321
|
-
const subject = parent.subject;
|
|
322
|
-
const proxy = create(parent.proxy);
|
|
323
|
-
let reset;
|
|
324
|
-
define(proxy, LOCAL, this);
|
|
325
|
-
defineProperty(this, "proxy", {
|
|
326
|
-
configurable: true,
|
|
327
|
-
get() {
|
|
328
|
-
if (reset)
|
|
329
|
-
setTimeout(reset, 0);
|
|
330
|
-
return proxy;
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
const release = parent.addListener(key => {
|
|
334
|
-
const handler = this.watch[key];
|
|
335
|
-
if (!handler || !this.active)
|
|
336
|
-
return;
|
|
337
|
-
if (typeof handler == "function")
|
|
338
|
-
handler();
|
|
339
|
-
const notify = this.onUpdate(key, parent);
|
|
340
|
-
const getWhy = () => {
|
|
341
|
-
const update = getUpdate(subject);
|
|
342
|
-
const applicable = update.filter(k => k in this.watch);
|
|
343
|
-
reset = applyUpdate(proxy, applicable);
|
|
344
|
-
};
|
|
345
|
-
if (notify) {
|
|
346
|
-
parent.requestUpdate(getWhy);
|
|
347
|
-
parent.requestUpdate(notify);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
this.commit = () => {
|
|
351
|
-
this.active = true;
|
|
352
|
-
this.dependant.forEach(x => x.commit());
|
|
353
|
-
return this.release;
|
|
354
|
-
};
|
|
355
|
-
this.release = () => {
|
|
356
|
-
this.dependant.forEach(x => x.release());
|
|
357
|
-
release();
|
|
358
|
-
};
|
|
150
|
+
control.state = new Map();
|
|
151
|
+
for (const key in subject)
|
|
152
|
+
control.add(key);
|
|
153
|
+
const callback = Array.from(waiting);
|
|
154
|
+
waiting.clear();
|
|
155
|
+
for (const cb of callback)
|
|
156
|
+
cb();
|
|
359
157
|
}
|
|
158
|
+
return cb ? cb(control) : control;
|
|
360
159
|
}
|
|
361
160
|
|
|
362
161
|
const Oops$a = issues({
|
|
363
162
|
BadSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
|
|
364
163
|
PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
|
|
365
|
-
Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}]
|
|
366
|
-
Early: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
|
|
164
|
+
Failed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`
|
|
367
165
|
});
|
|
368
|
-
const INIT = new WeakSet();
|
|
369
166
|
const INFO = new WeakMap();
|
|
370
167
|
const USED = new WeakMap();
|
|
371
168
|
const KEYS = new WeakMap();
|
|
@@ -373,8 +170,8 @@ function from(source, arg1, arg2) {
|
|
|
373
170
|
return apply(function from(key) {
|
|
374
171
|
const parent = this;
|
|
375
172
|
const { subject, state } = this;
|
|
173
|
+
const required = arg2 === true || arg1 === true;
|
|
376
174
|
let getSource;
|
|
377
|
-
let required = arg2 === true || arg1 === true;
|
|
378
175
|
if (typeof arg1 == "boolean")
|
|
379
176
|
arg1 = undefined;
|
|
380
177
|
// Easy mistake, using a peer, will always be unresolved.
|
|
@@ -382,7 +179,7 @@ function from(source, arg1, arg2) {
|
|
|
382
179
|
throw Oops$a.PeerNotAllowed(subject, key);
|
|
383
180
|
// replace source controller in-case it is different
|
|
384
181
|
if (typeof source == "object")
|
|
385
|
-
getSource = () =>
|
|
182
|
+
getSource = () => ensure(source);
|
|
386
183
|
// specifically an arrow function (getter factory)
|
|
387
184
|
else if (!source.prototype) {
|
|
388
185
|
arg1 = source.call(subject, key);
|
|
@@ -391,21 +188,20 @@ function from(source, arg1, arg2) {
|
|
|
391
188
|
// Regular function is too ambiguous so not allowed.
|
|
392
189
|
else
|
|
393
190
|
throw Oops$a.BadSource(subject, key, source);
|
|
394
|
-
let sub;
|
|
395
191
|
const setter = arg1;
|
|
396
192
|
const info = { key, parent, priority: 1 };
|
|
193
|
+
let sub;
|
|
397
194
|
let register = USED.get(parent);
|
|
398
|
-
if (!register)
|
|
399
|
-
register = new Map();
|
|
400
|
-
|
|
401
|
-
}
|
|
195
|
+
if (!register)
|
|
196
|
+
USED.set(parent, register = new Map());
|
|
197
|
+
state.set(key, undefined);
|
|
402
198
|
register.set(key, info);
|
|
403
199
|
function compute(initial) {
|
|
404
200
|
try {
|
|
405
201
|
return setter.call(sub.proxy, sub.proxy);
|
|
406
202
|
}
|
|
407
203
|
catch (err) {
|
|
408
|
-
Oops$a.Failed(subject, key,
|
|
204
|
+
Oops$a.Failed(subject, key, initial).warn();
|
|
409
205
|
throw err;
|
|
410
206
|
}
|
|
411
207
|
}
|
|
@@ -418,16 +214,17 @@ function from(source, arg1, arg2) {
|
|
|
418
214
|
console.error(e);
|
|
419
215
|
}
|
|
420
216
|
finally {
|
|
421
|
-
if (state
|
|
422
|
-
parent.
|
|
217
|
+
if (state.get(key) !== value) {
|
|
218
|
+
parent.state.set(key, value);
|
|
219
|
+
parent.update(key);
|
|
423
220
|
return value;
|
|
424
221
|
}
|
|
425
222
|
}
|
|
426
223
|
}
|
|
427
224
|
function defer(_key, from) {
|
|
428
|
-
let pending = KEYS.get(from
|
|
225
|
+
let pending = KEYS.get(from);
|
|
429
226
|
if (!pending)
|
|
430
|
-
KEYS.set(from
|
|
227
|
+
KEYS.set(from, pending = []);
|
|
431
228
|
if (info.parent !== from)
|
|
432
229
|
update();
|
|
433
230
|
else {
|
|
@@ -435,179 +232,362 @@ function from(source, arg1, arg2) {
|
|
|
435
232
|
pending.splice(after + 1, 0, update);
|
|
436
233
|
}
|
|
437
234
|
}
|
|
438
|
-
function create(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
value: undefined,
|
|
442
|
-
writable: true
|
|
443
|
-
});
|
|
235
|
+
function create() {
|
|
236
|
+
const control = getSource();
|
|
237
|
+
sub = control.subscribe(defer);
|
|
444
238
|
try {
|
|
445
|
-
|
|
239
|
+
const value = compute(true);
|
|
240
|
+
state.set(key, value);
|
|
241
|
+
return value;
|
|
446
242
|
}
|
|
447
243
|
catch (e) {
|
|
448
|
-
if (early)
|
|
449
|
-
Oops$a.Early(key).warn();
|
|
450
244
|
throw e;
|
|
451
245
|
}
|
|
452
246
|
finally {
|
|
453
247
|
sub.commit();
|
|
454
|
-
for (const key
|
|
248
|
+
for (const key of control.state.keys()) {
|
|
455
249
|
const peer = register.get(key);
|
|
456
250
|
if (peer && peer.priority >= info.priority)
|
|
457
251
|
info.priority = peer.priority + 1;
|
|
458
252
|
}
|
|
459
253
|
}
|
|
460
254
|
}
|
|
461
|
-
INIT.add(create);
|
|
462
255
|
INFO.set(update, info);
|
|
463
|
-
defineProperty(state, key, {
|
|
464
|
-
get: create,
|
|
465
|
-
configurable: true,
|
|
466
|
-
enumerable: true
|
|
467
|
-
});
|
|
468
256
|
return () => {
|
|
469
|
-
const value =
|
|
257
|
+
const value = sub ? state.get(key) : create();
|
|
470
258
|
if (value === undefined && required)
|
|
471
259
|
throw suspend(this, key);
|
|
472
260
|
return value;
|
|
473
261
|
};
|
|
474
262
|
});
|
|
475
263
|
}
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
const desc = getOwnPropertyDescriptor(on.state, key);
|
|
479
|
-
const getter = desc && desc.get;
|
|
480
|
-
if (INIT.has(getter))
|
|
481
|
-
getter(true);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
function flush(frame) {
|
|
485
|
-
const pending = KEYS.get(frame);
|
|
264
|
+
function flush(control) {
|
|
265
|
+
const pending = KEYS.get(control);
|
|
486
266
|
if (!pending)
|
|
487
267
|
return;
|
|
488
268
|
while (pending.length) {
|
|
489
269
|
const compute = pending.shift();
|
|
490
270
|
const { key } = INFO.get(compute);
|
|
491
|
-
if (!frame.has(key))
|
|
271
|
+
if (!control.frame.has(key))
|
|
492
272
|
compute();
|
|
493
273
|
}
|
|
494
|
-
KEYS.delete(
|
|
274
|
+
KEYS.delete(control);
|
|
495
275
|
}
|
|
496
276
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
277
|
+
class Subscriber {
|
|
278
|
+
constructor(parent, onUpdate) {
|
|
279
|
+
this.onUpdate = onUpdate;
|
|
280
|
+
this.active = false;
|
|
281
|
+
this.dependant = new Set();
|
|
282
|
+
const proxy = create(parent.subject);
|
|
283
|
+
const using = new Map();
|
|
284
|
+
define(proxy, LOCAL, this);
|
|
285
|
+
defineProperty(this, "proxy", {
|
|
286
|
+
configurable: true,
|
|
287
|
+
get() {
|
|
288
|
+
if (UPDATE.has(proxy))
|
|
289
|
+
setTimeout(() => UPDATE.delete(proxy), 0);
|
|
290
|
+
return proxy;
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const release = parent.addListener(key => {
|
|
294
|
+
if (!this.active || !using.has(key))
|
|
295
|
+
return;
|
|
296
|
+
const handler = using.get(key);
|
|
297
|
+
let notify;
|
|
298
|
+
if (typeof handler == "function") {
|
|
299
|
+
const callback = handler();
|
|
300
|
+
if (typeof callback == "function")
|
|
301
|
+
notify = callback;
|
|
302
|
+
else if (callback === true)
|
|
303
|
+
notify = this.onUpdate(key, parent);
|
|
304
|
+
}
|
|
305
|
+
else
|
|
306
|
+
notify = this.onUpdate(key, parent);
|
|
307
|
+
const getWhy = () => {
|
|
308
|
+
const update = getUpdate(parent.subject);
|
|
309
|
+
const applicable = update.filter(k => using.has(k));
|
|
310
|
+
UPDATE.set(proxy, applicable);
|
|
311
|
+
};
|
|
312
|
+
if (notify) {
|
|
313
|
+
parent.waiting.add(getWhy);
|
|
314
|
+
parent.waiting.add(notify);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
this.commit = () => {
|
|
318
|
+
this.active = true;
|
|
319
|
+
this.dependant.forEach(x => x.commit());
|
|
320
|
+
return this.release;
|
|
321
|
+
};
|
|
322
|
+
this.release = () => {
|
|
323
|
+
this.dependant.forEach(x => x.release());
|
|
324
|
+
release();
|
|
325
|
+
};
|
|
326
|
+
this.add = (key, value) => {
|
|
327
|
+
if (value)
|
|
328
|
+
using.set(key, value);
|
|
329
|
+
else if (!using.has(key))
|
|
330
|
+
using.set(key, true);
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const UPDATE = new WeakMap();
|
|
336
|
+
function getUpdate(subject) {
|
|
337
|
+
return UPDATE.get(subject);
|
|
338
|
+
}
|
|
503
339
|
class Controller {
|
|
504
340
|
constructor(subject) {
|
|
505
341
|
this.subject = subject;
|
|
506
|
-
this.state = {};
|
|
507
342
|
this.frame = new Set();
|
|
508
|
-
this.onDestroy = new Set();
|
|
509
343
|
this.waiting = new Set();
|
|
510
344
|
this.followers = new Set();
|
|
511
|
-
this.proxy = Object.create(subject);
|
|
512
|
-
}
|
|
513
|
-
start() {
|
|
514
|
-
READY.add(this);
|
|
515
|
-
for (const key in this.subject) {
|
|
516
|
-
const { value } = getOwnPropertyDescriptor(this.subject, key);
|
|
517
|
-
if (typeof value == "function")
|
|
518
|
-
continue;
|
|
519
|
-
if (typeof value == "symbol") {
|
|
520
|
-
const instruction = PENDING.get(value);
|
|
521
|
-
if (instruction)
|
|
522
|
-
instruction.call(this, key, this);
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
this.manage(key, value);
|
|
526
|
-
}
|
|
527
|
-
this.flush();
|
|
528
345
|
}
|
|
529
|
-
|
|
530
|
-
this
|
|
531
|
-
this.onDestroy.forEach(x => x());
|
|
346
|
+
subscribe(callback) {
|
|
347
|
+
return new Subscriber(this, callback);
|
|
532
348
|
}
|
|
533
|
-
|
|
534
|
-
const { state } = this;
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
get
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
349
|
+
add(key) {
|
|
350
|
+
const { subject, state } = this;
|
|
351
|
+
const { value } = getOwnPropertyDescriptor(subject, key);
|
|
352
|
+
if (typeof value == "function")
|
|
353
|
+
return;
|
|
354
|
+
if (typeof value == "symbol") {
|
|
355
|
+
const instruction = PENDING.get(value);
|
|
356
|
+
if (instruction)
|
|
357
|
+
instruction.call(this, key, this);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
state.set(key, value);
|
|
361
|
+
defineProperty(subject, key, {
|
|
543
362
|
enumerable: false,
|
|
363
|
+
set: this.ref(key),
|
|
544
364
|
get() {
|
|
545
365
|
const local = this[LOCAL];
|
|
546
|
-
if (local
|
|
547
|
-
local.
|
|
548
|
-
return state
|
|
549
|
-
}
|
|
550
|
-
set
|
|
366
|
+
if (local)
|
|
367
|
+
local.add(key);
|
|
368
|
+
return state.get(key);
|
|
369
|
+
}
|
|
551
370
|
});
|
|
552
371
|
}
|
|
372
|
+
addListener(listener) {
|
|
373
|
+
this.followers.add(listener);
|
|
374
|
+
return () => {
|
|
375
|
+
this.followers.delete(listener);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
553
378
|
ref(key, handler) {
|
|
554
|
-
const {
|
|
379
|
+
const { subject, state } = this;
|
|
555
380
|
return (value) => {
|
|
556
|
-
if (state
|
|
381
|
+
if (state.get(key) == value)
|
|
557
382
|
return;
|
|
558
383
|
if (handler)
|
|
559
|
-
switch (handler.call(subject, value
|
|
384
|
+
switch (handler.call(subject, value)) {
|
|
560
385
|
case true:
|
|
561
386
|
this.update(key);
|
|
562
387
|
case false:
|
|
563
388
|
return;
|
|
564
389
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
addListener(listener) {
|
|
569
|
-
this.followers.add(listener);
|
|
570
|
-
return () => {
|
|
571
|
-
this.followers.delete(listener);
|
|
390
|
+
state.set(key, value);
|
|
391
|
+
this.update(key);
|
|
572
392
|
};
|
|
573
393
|
}
|
|
574
|
-
update(key
|
|
575
|
-
const { frame } = this;
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
if (!frame.size)
|
|
394
|
+
update(key) {
|
|
395
|
+
const { followers, frame, subject, waiting } = this;
|
|
396
|
+
if (frame.has(key))
|
|
397
|
+
return;
|
|
398
|
+
else if (!frame.size)
|
|
579
399
|
setTimeout(() => {
|
|
580
|
-
flush(
|
|
581
|
-
|
|
400
|
+
flush(this);
|
|
401
|
+
const callback = Array.from(waiting);
|
|
402
|
+
const keys = Array.from(frame);
|
|
403
|
+
frame.clear();
|
|
404
|
+
waiting.clear();
|
|
405
|
+
UPDATE.set(subject, keys);
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
UPDATE.delete(subject);
|
|
408
|
+
}, 0);
|
|
409
|
+
for (const cb of callback)
|
|
410
|
+
try {
|
|
411
|
+
cb();
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
console.error(err);
|
|
415
|
+
}
|
|
582
416
|
}, 0);
|
|
583
|
-
else if (frame.has(key))
|
|
584
|
-
return;
|
|
585
417
|
frame.add(key);
|
|
586
|
-
for (const callback of
|
|
418
|
+
for (const callback of followers) {
|
|
587
419
|
const event = callback(key, this);
|
|
588
420
|
if (typeof event == "function")
|
|
589
|
-
|
|
421
|
+
waiting.add(event);
|
|
590
422
|
}
|
|
591
423
|
}
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
424
|
+
clear() {
|
|
425
|
+
const listeners = [...this.followers];
|
|
426
|
+
this.followers.clear();
|
|
427
|
+
listeners.forEach(x => x(null, this));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const Oops$9 = issues({
|
|
432
|
+
Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
|
|
433
|
+
StrictUpdate: () => `Strict update() did not find pending updates.`,
|
|
434
|
+
NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
|
|
435
|
+
});
|
|
436
|
+
const CONTROL = Symbol("CONTROL");
|
|
437
|
+
const WHY = Symbol("UPDATE");
|
|
438
|
+
const LOCAL = Symbol("LOCAL");
|
|
439
|
+
const STATE = Symbol("STATE");
|
|
440
|
+
class Model {
|
|
441
|
+
constructor() {
|
|
442
|
+
define(this, CONTROL, new Controller(this));
|
|
443
|
+
define(this, "get", this);
|
|
444
|
+
define(this, "is", this);
|
|
445
|
+
defineLazy(this, "set", () => {
|
|
446
|
+
const controller = ensure(this);
|
|
447
|
+
const assign = (key, value) => {
|
|
448
|
+
controller.state.set(key, value);
|
|
449
|
+
controller.update(key);
|
|
450
|
+
};
|
|
451
|
+
for (const key of controller.state.keys())
|
|
452
|
+
define(assign, key, controller.ref(key));
|
|
453
|
+
return assign;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
get [STATE]() {
|
|
457
|
+
return this.export();
|
|
458
|
+
}
|
|
459
|
+
get [WHY]() {
|
|
460
|
+
return UPDATE.get(this);
|
|
461
|
+
}
|
|
462
|
+
on(select, handler, squash, once) {
|
|
463
|
+
return ensure(this, control => {
|
|
464
|
+
if (typeof select == "function")
|
|
465
|
+
return control.addListener(select);
|
|
466
|
+
const keys = typeof select == "string" ? [select] :
|
|
467
|
+
select.length ? select : [...control.state.keys()];
|
|
468
|
+
for (const key of keys)
|
|
469
|
+
try {
|
|
470
|
+
void this[key];
|
|
471
|
+
}
|
|
472
|
+
catch (e) { }
|
|
473
|
+
const callback = squash
|
|
474
|
+
? () => {
|
|
475
|
+
handler.call(this, getUpdate(this));
|
|
476
|
+
}
|
|
477
|
+
: () => {
|
|
478
|
+
getUpdate(this)
|
|
479
|
+
.filter(k => keys.includes(k))
|
|
480
|
+
.forEach(k => {
|
|
481
|
+
handler.call(this, control.state.get(k), k);
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
const onEvent = once
|
|
485
|
+
? () => {
|
|
486
|
+
remove();
|
|
487
|
+
callback();
|
|
488
|
+
}
|
|
489
|
+
: callback;
|
|
490
|
+
const remove = control.addListener(key => {
|
|
491
|
+
if (keys.includes(key))
|
|
492
|
+
return onEvent;
|
|
493
|
+
});
|
|
494
|
+
return remove;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
once(select, argument, squash) {
|
|
498
|
+
if (typeof argument == "function")
|
|
499
|
+
return this.on(select, argument, squash, true);
|
|
500
|
+
if (typeof select == "string")
|
|
501
|
+
select = [select];
|
|
502
|
+
return new Promise((resolve, reject) => {
|
|
503
|
+
const invoke = (key) => {
|
|
504
|
+
if (!key) {
|
|
505
|
+
clear();
|
|
506
|
+
reject(Oops$9.Timeout(select, typeof argument == "number"
|
|
507
|
+
? `${argument}ms`
|
|
508
|
+
: `lifetime of ${this}`));
|
|
509
|
+
}
|
|
510
|
+
else if (select.includes(key)) {
|
|
511
|
+
clear();
|
|
512
|
+
return resolve;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const clear = ensure(this, x => {
|
|
516
|
+
for (const key of select)
|
|
517
|
+
try {
|
|
518
|
+
void this[key];
|
|
519
|
+
}
|
|
520
|
+
catch (e) { }
|
|
521
|
+
return x.addListener(invoke);
|
|
522
|
+
});
|
|
523
|
+
if (typeof argument == "number")
|
|
524
|
+
setTimeout(invoke, argument);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
effect(callback, select) {
|
|
528
|
+
const effect = createEffect(callback);
|
|
529
|
+
return ensure(this, control => {
|
|
530
|
+
let busy = false;
|
|
531
|
+
let inject = this.is;
|
|
532
|
+
const invoke = () => {
|
|
533
|
+
if (busy)
|
|
534
|
+
return;
|
|
535
|
+
const output = mayRetry(() => {
|
|
536
|
+
effect.call(inject, inject);
|
|
537
|
+
});
|
|
538
|
+
if (output instanceof Promise) {
|
|
539
|
+
output.finally(() => busy = false);
|
|
540
|
+
busy = true;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
if (!select) {
|
|
544
|
+
const sub = control.subscribe(() => invoke);
|
|
545
|
+
inject = sub.proxy;
|
|
546
|
+
invoke();
|
|
547
|
+
return sub.commit();
|
|
604
548
|
}
|
|
549
|
+
invoke();
|
|
550
|
+
return control.addListener(key => {
|
|
551
|
+
if (key === null) {
|
|
552
|
+
if (!select.length)
|
|
553
|
+
invoke();
|
|
554
|
+
}
|
|
555
|
+
else if (select.includes(key))
|
|
556
|
+
return invoke;
|
|
557
|
+
});
|
|
558
|
+
});
|
|
605
559
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
560
|
+
// TODO: account for exotic properties
|
|
561
|
+
import(source, select) {
|
|
562
|
+
const { subject } = ensure(this);
|
|
563
|
+
if (!select)
|
|
564
|
+
select = getOwnPropertyNames(subject);
|
|
565
|
+
for (const key of select)
|
|
566
|
+
if (key in source)
|
|
567
|
+
this[key] = source[key];
|
|
568
|
+
}
|
|
569
|
+
export(subset) {
|
|
570
|
+
const { state } = ensure(this);
|
|
571
|
+
const output = {};
|
|
572
|
+
const keys = subset || state.keys();
|
|
573
|
+
for (const key of keys)
|
|
574
|
+
output[key] = state.get(key);
|
|
575
|
+
return output;
|
|
576
|
+
}
|
|
577
|
+
update(arg, tag) {
|
|
578
|
+
const target = ensure(this);
|
|
579
|
+
const { frame, waiting } = target;
|
|
580
|
+
if (typeof arg == "string") {
|
|
581
|
+
target.update(arg);
|
|
582
|
+
if (1 in arguments && arg in this) {
|
|
583
|
+
const method = this[arg];
|
|
584
|
+
if (typeof method == "function")
|
|
585
|
+
if (typeof tag != "boolean")
|
|
586
|
+
method.call(this, tag);
|
|
587
|
+
else if (tag)
|
|
588
|
+
method.call(this);
|
|
589
|
+
}
|
|
590
|
+
arg = undefined;
|
|
611
591
|
}
|
|
612
592
|
if (!frame.size && arg === true)
|
|
613
593
|
return Promise.reject(Oops$9.StrictUpdate());
|
|
@@ -617,37 +597,51 @@ class Controller {
|
|
|
617
597
|
throw Oops$9.NoChaining();
|
|
618
598
|
if (frame.size || arg !== false)
|
|
619
599
|
waiting.add(() => {
|
|
620
|
-
callback(getUpdate(
|
|
600
|
+
callback(getUpdate(this));
|
|
621
601
|
});
|
|
622
602
|
else
|
|
623
603
|
callback(false);
|
|
624
604
|
}
|
|
625
605
|
};
|
|
626
606
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
define(subject, CONTROL, control);
|
|
607
|
+
/**
|
|
608
|
+
* Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
|
|
609
|
+
*/
|
|
610
|
+
destroy() {
|
|
611
|
+
ensure(this).clear();
|
|
633
612
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
613
|
+
toString() {
|
|
614
|
+
return this.constructor.name;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Creates a new instance of this controller.
|
|
618
|
+
*
|
|
619
|
+
* Beyond `new this(...)`, method will activate managed-state.
|
|
620
|
+
*
|
|
621
|
+
* @param args - arguments sent to constructor
|
|
622
|
+
*/
|
|
623
|
+
static create(...args) {
|
|
624
|
+
const instance = new this(...args);
|
|
625
|
+
ensure(instance);
|
|
626
|
+
return instance;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Static equivalent of `x instanceof this`.
|
|
630
|
+
*
|
|
631
|
+
* Will determine if provided class is a subtype of this one.
|
|
632
|
+
*/
|
|
633
|
+
static isTypeof(maybe) {
|
|
634
|
+
return (typeof maybe == "function" &&
|
|
635
|
+
maybe.prototype instanceof this);
|
|
642
636
|
}
|
|
643
|
-
return cb ? cb(control) : control;
|
|
644
637
|
}
|
|
645
638
|
|
|
639
|
+
const PENDING = new Map();
|
|
646
640
|
function apply(fn, label) {
|
|
647
641
|
const name = label || fn.name || "pending";
|
|
648
642
|
const placeholder = Symbol(`${name} instruction`);
|
|
649
643
|
function setup(key) {
|
|
650
|
-
const {
|
|
644
|
+
const { subject, state } = this;
|
|
651
645
|
const control = this;
|
|
652
646
|
PENDING.delete(placeholder);
|
|
653
647
|
delete subject[key];
|
|
@@ -661,67 +655,56 @@ function apply(fn, label) {
|
|
|
661
655
|
return false;
|
|
662
656
|
}
|
|
663
657
|
if ("recursive" in output && output.recursive)
|
|
664
|
-
output = Object.assign(Object.assign({}, output), { get:
|
|
658
|
+
output = Object.assign(Object.assign({}, output), { get: getRecursive(key, this) });
|
|
665
659
|
const desc = output;
|
|
666
660
|
if ("value" in desc)
|
|
667
|
-
state
|
|
661
|
+
state.set(key, desc.value);
|
|
668
662
|
const { get: onGet, set: onSet, enumerable, suspense } = desc;
|
|
669
|
-
|
|
670
|
-
?
|
|
671
|
-
:
|
|
663
|
+
const set = onSet !== false
|
|
664
|
+
? this.ref(key, onSet)
|
|
665
|
+
: undefined;
|
|
672
666
|
function get() {
|
|
673
|
-
if (!(key
|
|
667
|
+
if (!state.has(key) && suspense)
|
|
674
668
|
throw suspend(control, key);
|
|
675
669
|
const local = this[LOCAL];
|
|
676
|
-
if (local
|
|
677
|
-
local.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return local
|
|
682
|
-
? onGet(value, local)
|
|
683
|
-
: onGet(value);
|
|
670
|
+
if (local)
|
|
671
|
+
local.add(key);
|
|
672
|
+
return onGet
|
|
673
|
+
? onGet(local)
|
|
674
|
+
: state.get(key);
|
|
684
675
|
}
|
|
685
|
-
|
|
686
|
-
defineProperty(x, key, { enumerable, get, set });
|
|
676
|
+
defineProperty(subject, key, { enumerable, get, set });
|
|
687
677
|
}
|
|
688
678
|
PENDING.set(placeholder, setup);
|
|
689
679
|
return placeholder;
|
|
690
680
|
}
|
|
691
|
-
function
|
|
692
|
-
return forSubscriber((parent, context) => {
|
|
693
|
-
let child;
|
|
694
|
-
function init() {
|
|
695
|
-
if (child) {
|
|
696
|
-
child.release();
|
|
697
|
-
parent.dependant.delete(child);
|
|
698
|
-
context.set(parent, undefined);
|
|
699
|
-
child = undefined;
|
|
700
|
-
}
|
|
701
|
-
const value = source.state[key];
|
|
702
|
-
if (value && CONTROL in value) {
|
|
703
|
-
child = new Subscriber(value, parent.onUpdate);
|
|
704
|
-
if (parent.active)
|
|
705
|
-
child.commit();
|
|
706
|
-
parent.dependant.add(child);
|
|
707
|
-
context.set(parent, child.proxy);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
init();
|
|
711
|
-
parent.watch[key] = init;
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
function forSubscriber(fn) {
|
|
681
|
+
function getRecursive(key, from) {
|
|
715
682
|
const context = new WeakMap();
|
|
716
|
-
|
|
683
|
+
const { state } = from;
|
|
684
|
+
return (local) => {
|
|
717
685
|
if (!local)
|
|
718
|
-
return
|
|
686
|
+
return state.get(key);
|
|
719
687
|
if (!context.has(local)) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
688
|
+
let child;
|
|
689
|
+
const init = () => {
|
|
690
|
+
if (child) {
|
|
691
|
+
child.release();
|
|
692
|
+
local.dependant.delete(child);
|
|
693
|
+
context.set(local, undefined);
|
|
694
|
+
child = undefined;
|
|
695
|
+
}
|
|
696
|
+
const value = state.get(key);
|
|
697
|
+
if (value && CONTROL in value) {
|
|
698
|
+
child = ensure(value).subscribe(local.onUpdate);
|
|
699
|
+
if (local.active)
|
|
700
|
+
child.commit();
|
|
701
|
+
local.dependant.add(child);
|
|
702
|
+
context.set(local, child.proxy);
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
};
|
|
706
|
+
init();
|
|
707
|
+
local.add(key, init);
|
|
725
708
|
}
|
|
726
709
|
return context.get(local);
|
|
727
710
|
};
|
|
@@ -746,7 +729,7 @@ function act(task) {
|
|
|
746
729
|
this.update(key);
|
|
747
730
|
}
|
|
748
731
|
});
|
|
749
|
-
this.state
|
|
732
|
+
this.state.set(key, undefined);
|
|
750
733
|
defineProperty(invoke, "active", {
|
|
751
734
|
get: () => pending
|
|
752
735
|
});
|
|
@@ -767,7 +750,7 @@ class Managed extends Array {
|
|
|
767
750
|
};
|
|
768
751
|
}
|
|
769
752
|
push(...items) {
|
|
770
|
-
const length = this
|
|
753
|
+
const { length } = this;
|
|
771
754
|
const end = super.push(...items);
|
|
772
755
|
this.update(length, end);
|
|
773
756
|
return end;
|
|
@@ -778,64 +761,65 @@ class Managed extends Array {
|
|
|
778
761
|
return end;
|
|
779
762
|
}
|
|
780
763
|
splice(start, deleteCount, ...rest) {
|
|
781
|
-
|
|
764
|
+
const end = deleteCount !== rest.length ? Infinity : deleteCount;
|
|
765
|
+
this.update(start, end);
|
|
782
766
|
return super.splice(start, deleteCount, ...rest);
|
|
783
767
|
}
|
|
784
768
|
}
|
|
785
769
|
function array() {
|
|
786
770
|
return apply(function array(key) {
|
|
771
|
+
const context = new WeakMap();
|
|
787
772
|
const array = new Managed(() => this.update(key));
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
const update = context.onUpdate(key, this);
|
|
773
|
+
const getLocal = (context) => {
|
|
774
|
+
const proxy = Object.create(array);
|
|
791
775
|
let rangeA = 0;
|
|
792
776
|
let rangeB = 0;
|
|
793
|
-
|
|
777
|
+
const onUpdate = () => {
|
|
794
778
|
const [lastA, lastB] = array.lastUpdate;
|
|
795
779
|
if (rangeA <= lastB && rangeB >= lastA)
|
|
796
|
-
return
|
|
797
|
-
}
|
|
798
|
-
|
|
780
|
+
return true;
|
|
781
|
+
};
|
|
782
|
+
const listen = (eventA, eventB) => {
|
|
799
783
|
if (context.active)
|
|
800
784
|
return;
|
|
801
|
-
context.
|
|
785
|
+
context.add(key, onUpdate);
|
|
802
786
|
rangeA = Math.min(rangeA, eventA);
|
|
803
787
|
rangeB = Math.max(rangeB, eventB);
|
|
804
|
-
}
|
|
805
|
-
|
|
788
|
+
};
|
|
789
|
+
proxy[Symbol.iterator] = () => {
|
|
806
790
|
listen(0, Infinity);
|
|
807
791
|
return array[Symbol.iterator]();
|
|
808
792
|
};
|
|
809
|
-
return
|
|
810
|
-
}
|
|
793
|
+
return proxy;
|
|
794
|
+
};
|
|
811
795
|
return {
|
|
812
|
-
|
|
796
|
+
value: array,
|
|
813
797
|
set: false,
|
|
814
|
-
|
|
798
|
+
get: (local) => {
|
|
799
|
+
if (!local)
|
|
800
|
+
return this.state.get(key);
|
|
801
|
+
if (context.has(local))
|
|
802
|
+
return context.get(local);
|
|
803
|
+
const output = getLocal(local);
|
|
804
|
+
context.set(local, output);
|
|
805
|
+
return output;
|
|
806
|
+
}
|
|
815
807
|
};
|
|
816
808
|
});
|
|
817
809
|
}
|
|
818
810
|
|
|
819
|
-
function ref(
|
|
811
|
+
function ref(callback) {
|
|
820
812
|
return apply(function ref(key) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
function createRef(key, cb) {
|
|
833
|
-
const refObjectFunction = this.ref(key, cb && createValueEffect(cb));
|
|
834
|
-
defineProperty(refObjectFunction, "current", {
|
|
835
|
-
set: refObjectFunction,
|
|
836
|
-
get: () => this.state[key]
|
|
813
|
+
const value = this.ref(key, callback && createValueEffect(callback));
|
|
814
|
+
defineProperty(value, "current", {
|
|
815
|
+
set: value,
|
|
816
|
+
get: () => this.state.get(key)
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
value,
|
|
820
|
+
explicit: true
|
|
821
|
+
};
|
|
837
822
|
});
|
|
838
|
-
return refObjectFunction;
|
|
839
823
|
}
|
|
840
824
|
|
|
841
825
|
const Oops$7 = issues({
|
|
@@ -858,14 +842,18 @@ function put(factory, argument) {
|
|
|
858
842
|
if (output instanceof Promise) {
|
|
859
843
|
pending = output
|
|
860
844
|
.catch(err => error = err)
|
|
861
|
-
.then(val =>
|
|
845
|
+
.then(val => {
|
|
846
|
+
state.set(key, val);
|
|
847
|
+
return val;
|
|
848
|
+
})
|
|
862
849
|
.finally(() => {
|
|
863
850
|
pending = undefined;
|
|
864
851
|
this.update(key);
|
|
865
852
|
});
|
|
866
853
|
return;
|
|
867
854
|
}
|
|
868
|
-
|
|
855
|
+
state.set(key, output);
|
|
856
|
+
return output;
|
|
869
857
|
};
|
|
870
858
|
const suspend = () => {
|
|
871
859
|
if (required === false)
|
|
@@ -890,8 +878,8 @@ function put(factory, argument) {
|
|
|
890
878
|
return suspend();
|
|
891
879
|
if (error)
|
|
892
880
|
throw error;
|
|
893
|
-
if (key
|
|
894
|
-
return state
|
|
881
|
+
if (state.has(key))
|
|
882
|
+
return state.get(key);
|
|
895
883
|
let output = init();
|
|
896
884
|
return pending
|
|
897
885
|
? suspend()
|
|
@@ -918,7 +906,7 @@ function set(value, argument) {
|
|
|
918
906
|
}
|
|
919
907
|
return apply(function set(key) {
|
|
920
908
|
if (value !== undefined)
|
|
921
|
-
this.state
|
|
909
|
+
this.state.set(key, value);
|
|
922
910
|
return {
|
|
923
911
|
suspense: true,
|
|
924
912
|
set: typeof argument == "function"
|
|
@@ -948,35 +936,234 @@ function parent(Expects, required) {
|
|
|
948
936
|
});
|
|
949
937
|
}
|
|
950
938
|
|
|
939
|
+
const ANY$1 = Symbol("any");
|
|
940
|
+
function managedMap(control, property, initial) {
|
|
941
|
+
const lastUpdate = new Set();
|
|
942
|
+
const context = new WeakMap();
|
|
943
|
+
const assoc = new WeakMap();
|
|
944
|
+
let managed;
|
|
945
|
+
let value;
|
|
946
|
+
function reset() {
|
|
947
|
+
lastUpdate.clear();
|
|
948
|
+
}
|
|
949
|
+
function emit(key) {
|
|
950
|
+
lastUpdate.add(key);
|
|
951
|
+
control.update(property);
|
|
952
|
+
control.waiting.add(reset);
|
|
953
|
+
}
|
|
954
|
+
function setValue(next, initial) {
|
|
955
|
+
value = next;
|
|
956
|
+
managed = create(next);
|
|
957
|
+
control.state.set(property, next);
|
|
958
|
+
if (!initial)
|
|
959
|
+
emit(ANY$1);
|
|
960
|
+
const watch = (key, on) => {
|
|
961
|
+
const local = assoc.get(on);
|
|
962
|
+
if (local)
|
|
963
|
+
local(key);
|
|
964
|
+
};
|
|
965
|
+
managed.set = (k, v) => {
|
|
966
|
+
emit(k);
|
|
967
|
+
return value.set(k, v);
|
|
968
|
+
};
|
|
969
|
+
managed.delete = (k) => {
|
|
970
|
+
emit(k);
|
|
971
|
+
return value.delete(k);
|
|
972
|
+
};
|
|
973
|
+
managed.clear = () => {
|
|
974
|
+
emit(ANY$1);
|
|
975
|
+
value.clear();
|
|
976
|
+
};
|
|
977
|
+
managed.get = function (key) {
|
|
978
|
+
watch(key, this);
|
|
979
|
+
return value.get(key);
|
|
980
|
+
};
|
|
981
|
+
managed.has = function (key) {
|
|
982
|
+
watch(key, this);
|
|
983
|
+
return value.has(key);
|
|
984
|
+
};
|
|
985
|
+
managed.values = function () {
|
|
986
|
+
watch(ANY$1, this);
|
|
987
|
+
return value.values();
|
|
988
|
+
};
|
|
989
|
+
managed.keys = function () {
|
|
990
|
+
watch(ANY$1, this);
|
|
991
|
+
return value.keys();
|
|
992
|
+
};
|
|
993
|
+
managed.entries = function () {
|
|
994
|
+
watch(ANY$1, this);
|
|
995
|
+
return value.entries();
|
|
996
|
+
};
|
|
997
|
+
managed[Symbol.iterator] = function () {
|
|
998
|
+
watch(ANY$1, this);
|
|
999
|
+
return value[Symbol.iterator]();
|
|
1000
|
+
};
|
|
1001
|
+
defineProperty(managed, "size", {
|
|
1002
|
+
get() {
|
|
1003
|
+
watch(ANY$1, this);
|
|
1004
|
+
return value.size;
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
function getValue(local) {
|
|
1009
|
+
if (!local)
|
|
1010
|
+
return managed;
|
|
1011
|
+
if (context.has(local))
|
|
1012
|
+
return context.get(local);
|
|
1013
|
+
const refresh = local.onUpdate(property, control);
|
|
1014
|
+
const proxy = Object.create(managed);
|
|
1015
|
+
const using = new Set();
|
|
1016
|
+
const update = () => {
|
|
1017
|
+
if (using.has(ANY$1) || lastUpdate.has(ANY$1) && using.size)
|
|
1018
|
+
refresh();
|
|
1019
|
+
else
|
|
1020
|
+
for (const key of lastUpdate)
|
|
1021
|
+
if (using.has(key)) {
|
|
1022
|
+
refresh();
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
context.set(local, proxy);
|
|
1027
|
+
assoc.set(proxy, (key) => {
|
|
1028
|
+
if (!local.active)
|
|
1029
|
+
using.add(key);
|
|
1030
|
+
});
|
|
1031
|
+
local.add(property, () => update);
|
|
1032
|
+
return proxy;
|
|
1033
|
+
}
|
|
1034
|
+
setValue(initial, true);
|
|
1035
|
+
return {
|
|
1036
|
+
set: setValue,
|
|
1037
|
+
get: getValue
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const ANY = Symbol("any");
|
|
1042
|
+
function managedSet(control, property, initial) {
|
|
1043
|
+
const lastUpdate = new Set();
|
|
1044
|
+
const context = new WeakMap();
|
|
1045
|
+
const assoc = new WeakMap();
|
|
1046
|
+
let managed;
|
|
1047
|
+
let value;
|
|
1048
|
+
function reset() {
|
|
1049
|
+
lastUpdate.clear();
|
|
1050
|
+
}
|
|
1051
|
+
function emit(key) {
|
|
1052
|
+
lastUpdate.add(key);
|
|
1053
|
+
control.update(property);
|
|
1054
|
+
control.waiting.add(reset);
|
|
1055
|
+
}
|
|
1056
|
+
function setValue(next, initial) {
|
|
1057
|
+
value = next;
|
|
1058
|
+
managed = create(next);
|
|
1059
|
+
control.state.set(property, next);
|
|
1060
|
+
if (!initial)
|
|
1061
|
+
emit(ANY);
|
|
1062
|
+
const watch = (key, on) => {
|
|
1063
|
+
const local = assoc.get(on);
|
|
1064
|
+
if (local)
|
|
1065
|
+
local(key);
|
|
1066
|
+
};
|
|
1067
|
+
managed.add = (k) => {
|
|
1068
|
+
emit(k);
|
|
1069
|
+
return value.add(k);
|
|
1070
|
+
};
|
|
1071
|
+
managed.delete = (k) => {
|
|
1072
|
+
emit(k);
|
|
1073
|
+
return value.delete(k);
|
|
1074
|
+
};
|
|
1075
|
+
managed.clear = () => {
|
|
1076
|
+
emit(ANY);
|
|
1077
|
+
value.clear();
|
|
1078
|
+
};
|
|
1079
|
+
managed.has = function (key) {
|
|
1080
|
+
watch(key, this);
|
|
1081
|
+
return value.has(key);
|
|
1082
|
+
};
|
|
1083
|
+
managed.values = function () {
|
|
1084
|
+
watch(ANY, this);
|
|
1085
|
+
return value.values();
|
|
1086
|
+
};
|
|
1087
|
+
managed.keys = function () {
|
|
1088
|
+
watch(ANY, this);
|
|
1089
|
+
return value.keys();
|
|
1090
|
+
};
|
|
1091
|
+
managed.entries = function () {
|
|
1092
|
+
watch(ANY, this);
|
|
1093
|
+
return value.entries();
|
|
1094
|
+
};
|
|
1095
|
+
managed[Symbol.iterator] = function () {
|
|
1096
|
+
watch(ANY, this);
|
|
1097
|
+
return value[Symbol.iterator]();
|
|
1098
|
+
};
|
|
1099
|
+
defineProperty(managed, "size", {
|
|
1100
|
+
get() {
|
|
1101
|
+
watch(ANY, this);
|
|
1102
|
+
return value.size;
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
function getValue(local) {
|
|
1107
|
+
if (!local)
|
|
1108
|
+
return managed;
|
|
1109
|
+
if (context.has(local))
|
|
1110
|
+
return context.get(local);
|
|
1111
|
+
const refresh = local.onUpdate(property, control);
|
|
1112
|
+
const proxy = Object.create(managed);
|
|
1113
|
+
const using = new Set();
|
|
1114
|
+
const update = () => {
|
|
1115
|
+
if (using.has(ANY) || lastUpdate.has(ANY) && using.size)
|
|
1116
|
+
refresh();
|
|
1117
|
+
else
|
|
1118
|
+
for (const key of lastUpdate)
|
|
1119
|
+
if (using.has(key)) {
|
|
1120
|
+
refresh();
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
context.set(local, proxy);
|
|
1125
|
+
assoc.set(proxy, (key) => {
|
|
1126
|
+
if (!local.active)
|
|
1127
|
+
using.add(key);
|
|
1128
|
+
});
|
|
1129
|
+
local.add(property, () => update);
|
|
1130
|
+
return proxy;
|
|
1131
|
+
}
|
|
1132
|
+
setValue(initial, true);
|
|
1133
|
+
return {
|
|
1134
|
+
set: setValue,
|
|
1135
|
+
get: getValue
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
951
1139
|
const Oops$5 = issues({
|
|
952
1140
|
BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
|
|
953
1141
|
});
|
|
954
1142
|
function use$1(input, argument) {
|
|
955
1143
|
return apply(function use(key) {
|
|
956
|
-
const { subject } = this;
|
|
957
|
-
|
|
958
|
-
|
|
1144
|
+
const { state, subject } = this;
|
|
1145
|
+
if (typeof input === "function")
|
|
1146
|
+
input = Model.isTypeof(input)
|
|
1147
|
+
? new input()
|
|
1148
|
+
: input();
|
|
1149
|
+
else if (input && typeof input !== "object")
|
|
1150
|
+
throw Oops$5.BadArgument(typeof input);
|
|
1151
|
+
if (input instanceof Map)
|
|
1152
|
+
return managedMap(this, key, input);
|
|
1153
|
+
if (input instanceof Set)
|
|
1154
|
+
return managedSet(this, key, input);
|
|
1155
|
+
function onUpdate(next) {
|
|
1156
|
+
state.set(key, next);
|
|
959
1157
|
if (next) {
|
|
960
1158
|
Parent.set(next, subject);
|
|
961
|
-
|
|
1159
|
+
ensure(next);
|
|
962
1160
|
}
|
|
963
1161
|
if (typeof argument == "function")
|
|
964
1162
|
argument(next);
|
|
965
1163
|
return true;
|
|
966
|
-
};
|
|
967
|
-
if (input) {
|
|
968
|
-
const value = Model.isTypeof(input)
|
|
969
|
-
? new input() :
|
|
970
|
-
typeof input === "object"
|
|
971
|
-
? input :
|
|
972
|
-
typeof input === "function"
|
|
973
|
-
? input()
|
|
974
|
-
: null;
|
|
975
|
-
if (value === null)
|
|
976
|
-
throw Oops$5.BadArgument(typeof input);
|
|
977
|
-
if (value)
|
|
978
|
-
onUpdate(value);
|
|
979
1164
|
}
|
|
1165
|
+
if (input)
|
|
1166
|
+
onUpdate(input);
|
|
980
1167
|
return {
|
|
981
1168
|
set: onUpdate,
|
|
982
1169
|
recursive: true
|
|
@@ -1088,7 +1275,8 @@ function tap(type, argument) {
|
|
|
1088
1275
|
}
|
|
1089
1276
|
else if (!instance && argument)
|
|
1090
1277
|
throw Oops$3.AmbientRequired(type.name, subject, key);
|
|
1091
|
-
this.
|
|
1278
|
+
this.state.set(key, instance);
|
|
1279
|
+
this.update(key);
|
|
1092
1280
|
});
|
|
1093
1281
|
return {
|
|
1094
1282
|
recursive: true,
|
|
@@ -1133,7 +1321,7 @@ function useModel(source, arg, arg2) {
|
|
|
1133
1321
|
new source() :
|
|
1134
1322
|
source() :
|
|
1135
1323
|
source;
|
|
1136
|
-
|
|
1324
|
+
ensure(instance);
|
|
1137
1325
|
if (typeof cb == "function")
|
|
1138
1326
|
cb(instance);
|
|
1139
1327
|
return instance;
|
|
@@ -1151,7 +1339,7 @@ function useModel(source, arg, arg2) {
|
|
|
1151
1339
|
return instance;
|
|
1152
1340
|
}
|
|
1153
1341
|
else {
|
|
1154
|
-
const local = use(refresh => (
|
|
1342
|
+
const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
|
|
1155
1343
|
if (typeof arg == "object") {
|
|
1156
1344
|
local.active = false;
|
|
1157
1345
|
if (typeof arg2 !== "object")
|
|
@@ -1177,7 +1365,7 @@ function useModel(source, arg, arg2) {
|
|
|
1177
1365
|
|
|
1178
1366
|
function useFrom(target, compute, suspend) {
|
|
1179
1367
|
const local = use(refresh => {
|
|
1180
|
-
const sub =
|
|
1368
|
+
const sub = ensure(target).subscribe(() => update);
|
|
1181
1369
|
const spy = sub.proxy;
|
|
1182
1370
|
let value = compute.call(spy, spy);
|
|
1183
1371
|
let update;
|
|
@@ -1229,23 +1417,14 @@ function useTap(source, path, expect) {
|
|
|
1229
1417
|
}, [])();
|
|
1230
1418
|
if (typeof path == "function")
|
|
1231
1419
|
return useFrom(instance, path, expect);
|
|
1232
|
-
const local = use(refresh =>
|
|
1233
|
-
const parent = control(instance);
|
|
1234
|
-
const sub = new Subscriber(parent, () => refresh);
|
|
1235
|
-
if (typeof path == "string") {
|
|
1236
|
-
const source = sub.proxy;
|
|
1237
|
-
defineProperty(sub, "proxy", {
|
|
1238
|
-
get() {
|
|
1239
|
-
const value = source[path];
|
|
1240
|
-
if (value === undefined && expect)
|
|
1241
|
-
throw suspend(parent, path);
|
|
1242
|
-
return value;
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
return sub;
|
|
1247
|
-
});
|
|
1420
|
+
const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
|
|
1248
1421
|
React.useLayoutEffect(local.commit, []);
|
|
1422
|
+
if (typeof path !== "undefined") {
|
|
1423
|
+
const value = local.proxy[path];
|
|
1424
|
+
if (value === undefined && expect)
|
|
1425
|
+
throw suspend(ensure(instance), path);
|
|
1426
|
+
return value;
|
|
1427
|
+
}
|
|
1249
1428
|
return local.proxy;
|
|
1250
1429
|
}
|
|
1251
1430
|
|
|
@@ -1266,7 +1445,7 @@ class MVC extends Model {
|
|
|
1266
1445
|
static new(arg) {
|
|
1267
1446
|
const instance = React.useMemo(() => {
|
|
1268
1447
|
const instance = new this();
|
|
1269
|
-
|
|
1448
|
+
ensure(instance);
|
|
1270
1449
|
if (typeof arg == "function")
|
|
1271
1450
|
arg(instance);
|
|
1272
1451
|
else if (arg)
|
|
@@ -1351,7 +1530,7 @@ function useAppliedProps(within, props) {
|
|
|
1351
1530
|
const targets = within.local;
|
|
1352
1531
|
return function integrate(props) {
|
|
1353
1532
|
for (const [key, value] of entries(props))
|
|
1354
|
-
if (["of", "for", "children"].
|
|
1533
|
+
if (!["of", "for", "children"].includes(key))
|
|
1355
1534
|
for (const into of targets)
|
|
1356
1535
|
if (into && key in into)
|
|
1357
1536
|
into[key] = value;
|
|
@@ -1363,7 +1542,7 @@ function RenderFunction(props) {
|
|
|
1363
1542
|
const hook = use(refresh => {
|
|
1364
1543
|
const targets = props.context.local;
|
|
1365
1544
|
if (targets.length == 1)
|
|
1366
|
-
return
|
|
1545
|
+
return ensure(targets[0]).subscribe(() => refresh);
|
|
1367
1546
|
return {};
|
|
1368
1547
|
});
|
|
1369
1548
|
if (hook.commit)
|