@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.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[key] !== undefined) {
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
- const UPDATE = new WeakMap();
136
- function applyUpdate(subject, keys) {
137
- UPDATE.set(subject, keys);
138
- return () => {
139
- setTimeout(() => {
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
- effect(callback, select) {
213
- const effect = createEffect(callback);
214
- return control(this, control => {
215
- let busy = false;
216
- let inject = this.is;
217
- const invoke = () => {
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
- return target.requestUpdate(arg);
280
- }
281
- /**
282
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
283
- */
284
- destroy() {
285
- control(this).stop();
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 = () => control(source);
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
- USED.set(parent, register);
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, !!initial).warn();
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[key] !== value) {
422
- parent.update(key, value);
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.frame);
225
+ let pending = KEYS.get(from);
429
226
  if (!pending)
430
- KEYS.set(from.frame, pending = []);
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(early) {
439
- sub = new Subscriber(getSource(), defer);
440
- defineProperty(state, key, {
441
- value: undefined,
442
- writable: true
443
- });
235
+ function create() {
236
+ const control = getSource();
237
+ sub = control.subscribe(defer);
444
238
  try {
445
- return state[key] = compute(true);
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 in sub.watch) {
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 = parent.state[key];
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 ensure(on, keys) {
477
- for (const key of keys) {
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(frame);
274
+ KEYS.delete(control);
495
275
  }
496
276
 
497
- const Oops$9 = issues({
498
- StrictUpdate: () => `Strict update() did not find pending updates.`,
499
- NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
500
- });
501
- const PENDING = new Map();
502
- const READY = new WeakSet();
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
- stop() {
530
- this.followers.clear();
531
- this.onDestroy.forEach(x => x());
346
+ subscribe(callback) {
347
+ return new Subscriber(this, callback);
532
348
  }
533
- manage(key, value) {
534
- const { state } = this;
535
- const set = this.ref(key);
536
- state[key] = value;
537
- defineProperty(this.subject, key, {
538
- enumerable: false,
539
- get: () => state[key],
540
- set
541
- });
542
- defineProperty(this.proxy, key, {
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 && !local.watch[key])
547
- local.watch[key] = true;
548
- return state[key];
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 { state, subject } = this;
379
+ const { subject, state } = this;
555
380
  return (value) => {
556
- if (state[key] == value)
381
+ if (state.get(key) == value)
557
382
  return;
558
383
  if (handler)
559
- switch (handler.call(subject, value, state)) {
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
- this.update(key, value);
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, value) {
575
- const { frame } = this;
576
- if (1 in arguments)
577
- this.state[key] = value;
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(frame);
581
- this.flush();
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 this.followers) {
418
+ for (const callback of followers) {
587
419
  const event = callback(key, this);
588
420
  if (typeof event == "function")
589
- this.waiting.add(event);
421
+ waiting.add(event);
590
422
  }
591
423
  }
592
- flush() {
593
- const waiting = Array.from(this.waiting);
594
- const keys = Array.from(this.frame);
595
- this.frame.clear();
596
- this.waiting.clear();
597
- applyUpdate(this.subject, keys)();
598
- for (const callback of waiting)
599
- try {
600
- callback();
601
- }
602
- catch (err) {
603
- console.error(err);
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
- requestUpdate(arg) {
607
- const { frame, subject, waiting } = this;
608
- if (typeof arg == "function") {
609
- waiting.add(arg);
610
- return;
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(subject));
600
+ callback(getUpdate(this));
621
601
  });
622
602
  else
623
603
  callback(false);
624
604
  }
625
605
  };
626
606
  }
627
- }
628
- function control(subject, cb) {
629
- let control = subject[CONTROL];
630
- if (!control) {
631
- control = new Controller(subject);
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
- const ready = READY.has(control);
635
- if (!ready) {
636
- if (cb) {
637
- let done;
638
- control.requestUpdate(() => done = cb(control));
639
- return () => done && done();
640
- }
641
- control.start();
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 { proxy, state, subject } = this;
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: recursive(this, key) });
658
+ output = Object.assign(Object.assign({}, output), { get: getRecursive(key, this) });
665
659
  const desc = output;
666
660
  if ("value" in desc)
667
- state[key] = desc.value;
661
+ state.set(key, desc.value);
668
662
  const { get: onGet, set: onSet, enumerable, suspense } = desc;
669
- let set = onSet === false
670
- ? undefined
671
- : this.ref(key, onSet);
663
+ const set = onSet !== false
664
+ ? this.ref(key, onSet)
665
+ : undefined;
672
666
  function get() {
673
- if (!(key in state) && suspense)
667
+ if (!state.has(key) && suspense)
674
668
  throw suspend(control, key);
675
669
  const local = this[LOCAL];
676
- if (local && !(key in local.watch))
677
- local.watch[key] = true;
678
- const value = state[key];
679
- if (!onGet)
680
- return value;
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
- for (const x of [subject, proxy])
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 recursive(source, key) {
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
- return (value, local) => {
683
+ const { state } = from;
684
+ return (local) => {
717
685
  if (!local)
718
- return value;
686
+ return state.get(key);
719
687
  if (!context.has(local)) {
720
- const output = fn(local, context);
721
- if (output) {
722
- context.set(local, output);
723
- return output;
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[key] = undefined;
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.length;
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
- this.update(start, deleteCount !== rest.length ? Infinity : deleteCount);
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 access = forSubscriber(context => {
789
- const local = Object.create(array);
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
- function onUpdate() {
777
+ const onUpdate = () => {
794
778
  const [lastA, lastB] = array.lastUpdate;
795
779
  if (rangeA <= lastB && rangeB >= lastA)
796
- return update;
797
- }
798
- function listen(eventA, eventB) {
780
+ return true;
781
+ };
782
+ const listen = (eventA, eventB) => {
799
783
  if (context.active)
800
784
  return;
801
- context.watch[key] = onUpdate;
785
+ context.add(key, onUpdate);
802
786
  rangeA = Math.min(rangeA, eventA);
803
787
  rangeB = Math.max(rangeB, eventB);
804
- }
805
- local[Symbol.iterator] = () => {
788
+ };
789
+ proxy[Symbol.iterator] = () => {
806
790
  listen(0, Infinity);
807
791
  return array[Symbol.iterator]();
808
792
  };
809
- return local;
810
- });
793
+ return proxy;
794
+ };
811
795
  return {
812
- get: access,
796
+ value: array,
813
797
  set: false,
814
- value: array
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(arg) {
811
+ function ref(callback) {
820
812
  return apply(function ref(key) {
821
- let value = {};
822
- if (typeof arg == "object") {
823
- const source = control(arg);
824
- for (const key in source.state)
825
- defineLazy(value, key, createRef.bind(source, key));
826
- }
827
- else
828
- value = createRef.call(this, key, arg);
829
- return { value, explicit: true };
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 => state[key] = 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
- return state[key] = output;
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 in state)
894
- return state[key];
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[key] = value;
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
- const onUpdate = (next) => {
958
- this.state[key] = next;
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
- control(next);
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.update(key, instance);
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
- control(instance);
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 => (new Subscriber(instance, () => 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 = new Subscriber(target, () => update);
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
- control(instance);
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"].indexOf(key) < 0)
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 new Subscriber(targets[0], () => refresh);
1545
+ return ensure(targets[0]).subscribe(() => refresh);
1367
1546
  return {};
1368
1547
  });
1369
1548
  if (hook.commit)