@expressive/mvc 0.3.0 → 0.4.0

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