@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.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,362 @@ 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
+ let notify;
306
+ if (typeof handler == "function") {
307
+ const callback = handler();
308
+ if (typeof callback == "function")
309
+ notify = callback;
310
+ else if (callback === true)
311
+ notify = this.onUpdate(key, parent);
312
+ }
313
+ else
314
+ notify = this.onUpdate(key, parent);
315
+ const getWhy = () => {
316
+ const update = getUpdate(parent.subject);
317
+ const applicable = update.filter(k => using.has(k));
318
+ UPDATE.set(proxy, applicable);
319
+ };
320
+ if (notify) {
321
+ parent.waiting.add(getWhy);
322
+ parent.waiting.add(notify);
323
+ }
324
+ });
325
+ this.commit = () => {
326
+ this.active = true;
327
+ this.dependant.forEach(x => x.commit());
328
+ return this.release;
329
+ };
330
+ this.release = () => {
331
+ this.dependant.forEach(x => x.release());
332
+ release();
333
+ };
334
+ this.add = (key, value) => {
335
+ if (value)
336
+ using.set(key, value);
337
+ else if (!using.has(key))
338
+ using.set(key, true);
339
+ };
340
+ }
341
+ }
342
+
343
+ const UPDATE = new WeakMap();
344
+ function getUpdate(subject) {
345
+ return UPDATE.get(subject);
346
+ }
511
347
  class Controller {
512
348
  constructor(subject) {
513
349
  this.subject = subject;
514
- this.state = {};
515
350
  this.frame = new Set();
516
- this.onDestroy = new Set();
517
351
  this.waiting = new Set();
518
352
  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
353
  }
537
- stop() {
538
- this.followers.clear();
539
- this.onDestroy.forEach(x => x());
354
+ subscribe(callback) {
355
+ return new Subscriber(this, callback);
540
356
  }
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, {
357
+ add(key) {
358
+ const { subject, state } = this;
359
+ const { value } = getOwnPropertyDescriptor(subject, key);
360
+ if (typeof value == "function")
361
+ return;
362
+ if (typeof value == "symbol") {
363
+ const instruction = PENDING.get(value);
364
+ if (instruction)
365
+ instruction.call(this, key, this);
366
+ return;
367
+ }
368
+ state.set(key, value);
369
+ defineProperty(subject, key, {
551
370
  enumerable: false,
371
+ set: this.ref(key),
552
372
  get() {
553
373
  const local = this[LOCAL];
554
- if (local && !local.watch[key])
555
- local.watch[key] = true;
556
- return state[key];
557
- },
558
- set
374
+ if (local)
375
+ local.add(key);
376
+ return state.get(key);
377
+ }
559
378
  });
560
379
  }
380
+ addListener(listener) {
381
+ this.followers.add(listener);
382
+ return () => {
383
+ this.followers.delete(listener);
384
+ };
385
+ }
561
386
  ref(key, handler) {
562
- const { state, subject } = this;
387
+ const { subject, state } = this;
563
388
  return (value) => {
564
- if (state[key] == value)
389
+ if (state.get(key) == value)
565
390
  return;
566
391
  if (handler)
567
- switch (handler.call(subject, value, state)) {
392
+ switch (handler.call(subject, value)) {
568
393
  case true:
569
394
  this.update(key);
570
395
  case false:
571
396
  return;
572
397
  }
573
- this.update(key, value);
574
- };
575
- }
576
- addListener(listener) {
577
- this.followers.add(listener);
578
- return () => {
579
- this.followers.delete(listener);
398
+ state.set(key, value);
399
+ this.update(key);
580
400
  };
581
401
  }
582
- update(key, value) {
583
- const { frame } = this;
584
- if (1 in arguments)
585
- this.state[key] = value;
586
- if (!frame.size)
402
+ update(key) {
403
+ const { followers, frame, subject, waiting } = this;
404
+ if (frame.has(key))
405
+ return;
406
+ else if (!frame.size)
587
407
  setTimeout(() => {
588
- flush(frame);
589
- this.flush();
408
+ flush(this);
409
+ const callback = Array.from(waiting);
410
+ const keys = Array.from(frame);
411
+ frame.clear();
412
+ waiting.clear();
413
+ UPDATE.set(subject, keys);
414
+ setTimeout(() => {
415
+ UPDATE.delete(subject);
416
+ }, 0);
417
+ for (const cb of callback)
418
+ try {
419
+ cb();
420
+ }
421
+ catch (err) {
422
+ console.error(err);
423
+ }
590
424
  }, 0);
591
- else if (frame.has(key))
592
- return;
593
425
  frame.add(key);
594
- for (const callback of this.followers) {
426
+ for (const callback of followers) {
595
427
  const event = callback(key, this);
596
428
  if (typeof event == "function")
597
- this.waiting.add(event);
429
+ waiting.add(event);
598
430
  }
599
431
  }
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);
432
+ clear() {
433
+ const listeners = [...this.followers];
434
+ this.followers.clear();
435
+ listeners.forEach(x => x(null, this));
436
+ }
437
+ }
438
+
439
+ const Oops$9 = issues({
440
+ Timeout: (keys, timeout) => `No update for [${keys}] within ${timeout}.`,
441
+ StrictUpdate: () => `Strict update() did not find pending updates.`,
442
+ NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
443
+ });
444
+ const CONTROL = Symbol("CONTROL");
445
+ const WHY = Symbol("UPDATE");
446
+ const LOCAL = Symbol("LOCAL");
447
+ const STATE = Symbol("STATE");
448
+ class Model {
449
+ constructor() {
450
+ define(this, CONTROL, new Controller(this));
451
+ define(this, "get", this);
452
+ define(this, "is", this);
453
+ defineLazy(this, "set", () => {
454
+ const controller = ensure(this);
455
+ const assign = (key, value) => {
456
+ controller.state.set(key, value);
457
+ controller.update(key);
458
+ };
459
+ for (const key of controller.state.keys())
460
+ define(assign, key, controller.ref(key));
461
+ return assign;
462
+ });
463
+ }
464
+ get [STATE]() {
465
+ return this.export();
466
+ }
467
+ get [WHY]() {
468
+ return UPDATE.get(this);
469
+ }
470
+ on(select, handler, squash, once) {
471
+ return ensure(this, control => {
472
+ if (typeof select == "function")
473
+ return control.addListener(select);
474
+ const keys = typeof select == "string" ? [select] :
475
+ select.length ? select : [...control.state.keys()];
476
+ for (const key of keys)
477
+ try {
478
+ void this[key];
479
+ }
480
+ catch (e) { }
481
+ const callback = squash
482
+ ? () => {
483
+ handler.call(this, getUpdate(this));
484
+ }
485
+ : () => {
486
+ getUpdate(this)
487
+ .filter(k => keys.includes(k))
488
+ .forEach(k => {
489
+ handler.call(this, control.state.get(k), k);
490
+ });
491
+ };
492
+ const onEvent = once
493
+ ? () => {
494
+ remove();
495
+ callback();
496
+ }
497
+ : callback;
498
+ const remove = control.addListener(key => {
499
+ if (keys.includes(key))
500
+ return onEvent;
501
+ });
502
+ return remove;
503
+ });
504
+ }
505
+ once(select, argument, squash) {
506
+ if (typeof argument == "function")
507
+ return this.on(select, argument, squash, true);
508
+ if (typeof select == "string")
509
+ select = [select];
510
+ return new Promise((resolve, reject) => {
511
+ const invoke = (key) => {
512
+ if (!key) {
513
+ clear();
514
+ reject(Oops$9.Timeout(select, typeof argument == "number"
515
+ ? `${argument}ms`
516
+ : `lifetime of ${this}`));
517
+ }
518
+ else if (select.includes(key)) {
519
+ clear();
520
+ return resolve;
521
+ }
522
+ };
523
+ const clear = ensure(this, x => {
524
+ for (const key of select)
525
+ try {
526
+ void this[key];
527
+ }
528
+ catch (e) { }
529
+ return x.addListener(invoke);
530
+ });
531
+ if (typeof argument == "number")
532
+ setTimeout(invoke, argument);
533
+ });
534
+ }
535
+ effect(callback, select) {
536
+ const effect = createEffect(callback);
537
+ return ensure(this, control => {
538
+ let busy = false;
539
+ let inject = this.is;
540
+ const invoke = () => {
541
+ if (busy)
542
+ return;
543
+ const output = mayRetry(() => {
544
+ effect.call(inject, inject);
545
+ });
546
+ if (output instanceof Promise) {
547
+ output.finally(() => busy = false);
548
+ busy = true;
549
+ }
550
+ };
551
+ if (!select) {
552
+ const sub = control.subscribe(() => invoke);
553
+ inject = sub.proxy;
554
+ invoke();
555
+ return sub.commit();
612
556
  }
557
+ invoke();
558
+ return control.addListener(key => {
559
+ if (key === null) {
560
+ if (!select.length)
561
+ invoke();
562
+ }
563
+ else if (select.includes(key))
564
+ return invoke;
565
+ });
566
+ });
613
567
  }
614
- requestUpdate(arg) {
615
- const { frame, subject, waiting } = this;
616
- if (typeof arg == "function") {
617
- waiting.add(arg);
618
- return;
568
+ // TODO: account for exotic properties
569
+ import(source, select) {
570
+ const { subject } = ensure(this);
571
+ if (!select)
572
+ select = getOwnPropertyNames(subject);
573
+ for (const key of select)
574
+ if (key in source)
575
+ this[key] = source[key];
576
+ }
577
+ export(subset) {
578
+ const { state } = ensure(this);
579
+ const output = {};
580
+ const keys = subset || state.keys();
581
+ for (const key of keys)
582
+ output[key] = state.get(key);
583
+ return output;
584
+ }
585
+ update(arg, tag) {
586
+ const target = ensure(this);
587
+ const { frame, waiting } = target;
588
+ if (typeof arg == "string") {
589
+ target.update(arg);
590
+ if (1 in arguments && arg in this) {
591
+ const method = this[arg];
592
+ if (typeof method == "function")
593
+ if (typeof tag != "boolean")
594
+ method.call(this, tag);
595
+ else if (tag)
596
+ method.call(this);
597
+ }
598
+ arg = undefined;
619
599
  }
620
600
  if (!frame.size && arg === true)
621
601
  return Promise.reject(Oops$9.StrictUpdate());
@@ -625,37 +605,51 @@ class Controller {
625
605
  throw Oops$9.NoChaining();
626
606
  if (frame.size || arg !== false)
627
607
  waiting.add(() => {
628
- callback(getUpdate(subject));
608
+ callback(getUpdate(this));
629
609
  });
630
610
  else
631
611
  callback(false);
632
612
  }
633
613
  };
634
614
  }
635
- }
636
- function control(subject, cb) {
637
- let control = subject[CONTROL];
638
- if (!control) {
639
- control = new Controller(subject);
640
- define(subject, CONTROL, control);
615
+ /**
616
+ * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
617
+ */
618
+ destroy() {
619
+ ensure(this).clear();
641
620
  }
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();
621
+ toString() {
622
+ return this.constructor.name;
623
+ }
624
+ /**
625
+ * Creates a new instance of this controller.
626
+ *
627
+ * Beyond `new this(...)`, method will activate managed-state.
628
+ *
629
+ * @param args - arguments sent to constructor
630
+ */
631
+ static create(...args) {
632
+ const instance = new this(...args);
633
+ ensure(instance);
634
+ return instance;
635
+ }
636
+ /**
637
+ * Static equivalent of `x instanceof this`.
638
+ *
639
+ * Will determine if provided class is a subtype of this one.
640
+ */
641
+ static isTypeof(maybe) {
642
+ return (typeof maybe == "function" &&
643
+ maybe.prototype instanceof this);
650
644
  }
651
- return cb ? cb(control) : control;
652
645
  }
653
646
 
647
+ const PENDING = new Map();
654
648
  function apply(fn, label) {
655
649
  const name = label || fn.name || "pending";
656
650
  const placeholder = Symbol(`${name} instruction`);
657
651
  function setup(key) {
658
- const { proxy, state, subject } = this;
652
+ const { subject, state } = this;
659
653
  const control = this;
660
654
  PENDING.delete(placeholder);
661
655
  delete subject[key];
@@ -669,67 +663,56 @@ function apply(fn, label) {
669
663
  return false;
670
664
  }
671
665
  if ("recursive" in output && output.recursive)
672
- output = Object.assign(Object.assign({}, output), { get: recursive(this, key) });
666
+ output = Object.assign(Object.assign({}, output), { get: getRecursive(key, this) });
673
667
  const desc = output;
674
668
  if ("value" in desc)
675
- state[key] = desc.value;
669
+ state.set(key, desc.value);
676
670
  const { get: onGet, set: onSet, enumerable, suspense } = desc;
677
- let set = onSet === false
678
- ? undefined
679
- : this.ref(key, onSet);
671
+ const set = onSet !== false
672
+ ? this.ref(key, onSet)
673
+ : undefined;
680
674
  function get() {
681
- if (!(key in state) && suspense)
675
+ if (!state.has(key) && suspense)
682
676
  throw suspend(control, key);
683
677
  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);
678
+ if (local)
679
+ local.add(key);
680
+ return onGet
681
+ ? onGet(local)
682
+ : state.get(key);
692
683
  }
693
- for (const x of [subject, proxy])
694
- defineProperty(x, key, { enumerable, get, set });
684
+ defineProperty(subject, key, { enumerable, get, set });
695
685
  }
696
686
  PENDING.set(placeholder, setup);
697
687
  return placeholder;
698
688
  }
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) {
689
+ function getRecursive(key, from) {
723
690
  const context = new WeakMap();
724
- return (value, local) => {
691
+ const { state } = from;
692
+ return (local) => {
725
693
  if (!local)
726
- return value;
694
+ return state.get(key);
727
695
  if (!context.has(local)) {
728
- const output = fn(local, context);
729
- if (output) {
730
- context.set(local, output);
731
- return output;
732
- }
696
+ let child;
697
+ const init = () => {
698
+ if (child) {
699
+ child.release();
700
+ local.dependant.delete(child);
701
+ context.set(local, undefined);
702
+ child = undefined;
703
+ }
704
+ const value = state.get(key);
705
+ if (value && CONTROL in value) {
706
+ child = ensure(value).subscribe(local.onUpdate);
707
+ if (local.active)
708
+ child.commit();
709
+ local.dependant.add(child);
710
+ context.set(local, child.proxy);
711
+ }
712
+ return true;
713
+ };
714
+ init();
715
+ local.add(key, init);
733
716
  }
734
717
  return context.get(local);
735
718
  };
@@ -754,7 +737,7 @@ function act(task) {
754
737
  this.update(key);
755
738
  }
756
739
  });
757
- this.state[key] = undefined;
740
+ this.state.set(key, undefined);
758
741
  defineProperty(invoke, "active", {
759
742
  get: () => pending
760
743
  });
@@ -775,7 +758,7 @@ class Managed extends Array {
775
758
  };
776
759
  }
777
760
  push(...items) {
778
- const length = this.length;
761
+ const { length } = this;
779
762
  const end = super.push(...items);
780
763
  this.update(length, end);
781
764
  return end;
@@ -786,64 +769,65 @@ class Managed extends Array {
786
769
  return end;
787
770
  }
788
771
  splice(start, deleteCount, ...rest) {
789
- this.update(start, deleteCount !== rest.length ? Infinity : deleteCount);
772
+ const end = deleteCount !== rest.length ? Infinity : deleteCount;
773
+ this.update(start, end);
790
774
  return super.splice(start, deleteCount, ...rest);
791
775
  }
792
776
  }
793
777
  function array() {
794
778
  return apply(function array(key) {
779
+ const context = new WeakMap();
795
780
  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);
781
+ const getLocal = (context) => {
782
+ const proxy = Object.create(array);
799
783
  let rangeA = 0;
800
784
  let rangeB = 0;
801
- function onUpdate() {
785
+ const onUpdate = () => {
802
786
  const [lastA, lastB] = array.lastUpdate;
803
787
  if (rangeA <= lastB && rangeB >= lastA)
804
- return update;
805
- }
806
- function listen(eventA, eventB) {
788
+ return true;
789
+ };
790
+ const listen = (eventA, eventB) => {
807
791
  if (context.active)
808
792
  return;
809
- context.watch[key] = onUpdate;
793
+ context.add(key, onUpdate);
810
794
  rangeA = Math.min(rangeA, eventA);
811
795
  rangeB = Math.max(rangeB, eventB);
812
- }
813
- local[Symbol.iterator] = () => {
796
+ };
797
+ proxy[Symbol.iterator] = () => {
814
798
  listen(0, Infinity);
815
799
  return array[Symbol.iterator]();
816
800
  };
817
- return local;
818
- });
801
+ return proxy;
802
+ };
819
803
  return {
820
- get: access,
804
+ value: array,
821
805
  set: false,
822
- value: array
806
+ get: (local) => {
807
+ if (!local)
808
+ return this.state.get(key);
809
+ if (context.has(local))
810
+ return context.get(local);
811
+ const output = getLocal(local);
812
+ context.set(local, output);
813
+ return output;
814
+ }
823
815
  };
824
816
  });
825
817
  }
826
818
 
827
- function ref(arg) {
819
+ function ref(callback) {
828
820
  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]
821
+ const value = this.ref(key, callback && createValueEffect(callback));
822
+ defineProperty(value, "current", {
823
+ set: value,
824
+ get: () => this.state.get(key)
825
+ });
826
+ return {
827
+ value,
828
+ explicit: true
829
+ };
845
830
  });
846
- return refObjectFunction;
847
831
  }
848
832
 
849
833
  const Oops$7 = issues({
@@ -866,14 +850,18 @@ function put(factory, argument) {
866
850
  if (output instanceof Promise) {
867
851
  pending = output
868
852
  .catch(err => error = err)
869
- .then(val => state[key] = val)
853
+ .then(val => {
854
+ state.set(key, val);
855
+ return val;
856
+ })
870
857
  .finally(() => {
871
858
  pending = undefined;
872
859
  this.update(key);
873
860
  });
874
861
  return;
875
862
  }
876
- return state[key] = output;
863
+ state.set(key, output);
864
+ return output;
877
865
  };
878
866
  const suspend = () => {
879
867
  if (required === false)
@@ -898,8 +886,8 @@ function put(factory, argument) {
898
886
  return suspend();
899
887
  if (error)
900
888
  throw error;
901
- if (key in state)
902
- return state[key];
889
+ if (state.has(key))
890
+ return state.get(key);
903
891
  let output = init();
904
892
  return pending
905
893
  ? suspend()
@@ -926,7 +914,7 @@ function set(value, argument) {
926
914
  }
927
915
  return apply(function set(key) {
928
916
  if (value !== undefined)
929
- this.state[key] = value;
917
+ this.state.set(key, value);
930
918
  return {
931
919
  suspense: true,
932
920
  set: typeof argument == "function"
@@ -956,35 +944,234 @@ function parent(Expects, required) {
956
944
  });
957
945
  }
958
946
 
947
+ const ANY$1 = Symbol("any");
948
+ function managedMap(control, property, initial) {
949
+ const lastUpdate = new Set();
950
+ const context = new WeakMap();
951
+ const assoc = new WeakMap();
952
+ let managed;
953
+ let value;
954
+ function reset() {
955
+ lastUpdate.clear();
956
+ }
957
+ function emit(key) {
958
+ lastUpdate.add(key);
959
+ control.update(property);
960
+ control.waiting.add(reset);
961
+ }
962
+ function setValue(next, initial) {
963
+ value = next;
964
+ managed = create(next);
965
+ control.state.set(property, next);
966
+ if (!initial)
967
+ emit(ANY$1);
968
+ const watch = (key, on) => {
969
+ const local = assoc.get(on);
970
+ if (local)
971
+ local(key);
972
+ };
973
+ managed.set = (k, v) => {
974
+ emit(k);
975
+ return value.set(k, v);
976
+ };
977
+ managed.delete = (k) => {
978
+ emit(k);
979
+ return value.delete(k);
980
+ };
981
+ managed.clear = () => {
982
+ emit(ANY$1);
983
+ value.clear();
984
+ };
985
+ managed.get = function (key) {
986
+ watch(key, this);
987
+ return value.get(key);
988
+ };
989
+ managed.has = function (key) {
990
+ watch(key, this);
991
+ return value.has(key);
992
+ };
993
+ managed.values = function () {
994
+ watch(ANY$1, this);
995
+ return value.values();
996
+ };
997
+ managed.keys = function () {
998
+ watch(ANY$1, this);
999
+ return value.keys();
1000
+ };
1001
+ managed.entries = function () {
1002
+ watch(ANY$1, this);
1003
+ return value.entries();
1004
+ };
1005
+ managed[Symbol.iterator] = function () {
1006
+ watch(ANY$1, this);
1007
+ return value[Symbol.iterator]();
1008
+ };
1009
+ defineProperty(managed, "size", {
1010
+ get() {
1011
+ watch(ANY$1, this);
1012
+ return value.size;
1013
+ }
1014
+ });
1015
+ }
1016
+ function getValue(local) {
1017
+ if (!local)
1018
+ return managed;
1019
+ if (context.has(local))
1020
+ return context.get(local);
1021
+ const refresh = local.onUpdate(property, control);
1022
+ const proxy = Object.create(managed);
1023
+ const using = new Set();
1024
+ const update = () => {
1025
+ if (using.has(ANY$1) || lastUpdate.has(ANY$1) && using.size)
1026
+ refresh();
1027
+ else
1028
+ for (const key of lastUpdate)
1029
+ if (using.has(key)) {
1030
+ refresh();
1031
+ break;
1032
+ }
1033
+ };
1034
+ context.set(local, proxy);
1035
+ assoc.set(proxy, (key) => {
1036
+ if (!local.active)
1037
+ using.add(key);
1038
+ });
1039
+ local.add(property, () => update);
1040
+ return proxy;
1041
+ }
1042
+ setValue(initial, true);
1043
+ return {
1044
+ set: setValue,
1045
+ get: getValue
1046
+ };
1047
+ }
1048
+
1049
+ const ANY = Symbol("any");
1050
+ function managedSet(control, property, initial) {
1051
+ const lastUpdate = new Set();
1052
+ const context = new WeakMap();
1053
+ const assoc = new WeakMap();
1054
+ let managed;
1055
+ let value;
1056
+ function reset() {
1057
+ lastUpdate.clear();
1058
+ }
1059
+ function emit(key) {
1060
+ lastUpdate.add(key);
1061
+ control.update(property);
1062
+ control.waiting.add(reset);
1063
+ }
1064
+ function setValue(next, initial) {
1065
+ value = next;
1066
+ managed = create(next);
1067
+ control.state.set(property, next);
1068
+ if (!initial)
1069
+ emit(ANY);
1070
+ const watch = (key, on) => {
1071
+ const local = assoc.get(on);
1072
+ if (local)
1073
+ local(key);
1074
+ };
1075
+ managed.add = (k) => {
1076
+ emit(k);
1077
+ return value.add(k);
1078
+ };
1079
+ managed.delete = (k) => {
1080
+ emit(k);
1081
+ return value.delete(k);
1082
+ };
1083
+ managed.clear = () => {
1084
+ emit(ANY);
1085
+ value.clear();
1086
+ };
1087
+ managed.has = function (key) {
1088
+ watch(key, this);
1089
+ return value.has(key);
1090
+ };
1091
+ managed.values = function () {
1092
+ watch(ANY, this);
1093
+ return value.values();
1094
+ };
1095
+ managed.keys = function () {
1096
+ watch(ANY, this);
1097
+ return value.keys();
1098
+ };
1099
+ managed.entries = function () {
1100
+ watch(ANY, this);
1101
+ return value.entries();
1102
+ };
1103
+ managed[Symbol.iterator] = function () {
1104
+ watch(ANY, this);
1105
+ return value[Symbol.iterator]();
1106
+ };
1107
+ defineProperty(managed, "size", {
1108
+ get() {
1109
+ watch(ANY, this);
1110
+ return value.size;
1111
+ }
1112
+ });
1113
+ }
1114
+ function getValue(local) {
1115
+ if (!local)
1116
+ return managed;
1117
+ if (context.has(local))
1118
+ return context.get(local);
1119
+ const refresh = local.onUpdate(property, control);
1120
+ const proxy = Object.create(managed);
1121
+ const using = new Set();
1122
+ const update = () => {
1123
+ if (using.has(ANY) || lastUpdate.has(ANY) && using.size)
1124
+ refresh();
1125
+ else
1126
+ for (const key of lastUpdate)
1127
+ if (using.has(key)) {
1128
+ refresh();
1129
+ break;
1130
+ }
1131
+ };
1132
+ context.set(local, proxy);
1133
+ assoc.set(proxy, (key) => {
1134
+ if (!local.active)
1135
+ using.add(key);
1136
+ });
1137
+ local.add(property, () => update);
1138
+ return proxy;
1139
+ }
1140
+ setValue(initial, true);
1141
+ return {
1142
+ set: setValue,
1143
+ get: getValue
1144
+ };
1145
+ }
1146
+
959
1147
  const Oops$5 = issues({
960
1148
  BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`,
961
1149
  });
962
1150
  function use$1(input, argument) {
963
1151
  return apply(function use(key) {
964
- const { subject } = this;
965
- const onUpdate = (next) => {
966
- this.state[key] = next;
1152
+ const { state, subject } = this;
1153
+ if (typeof input === "function")
1154
+ input = Model.isTypeof(input)
1155
+ ? new input()
1156
+ : input();
1157
+ else if (input && typeof input !== "object")
1158
+ throw Oops$5.BadArgument(typeof input);
1159
+ if (input instanceof Map)
1160
+ return managedMap(this, key, input);
1161
+ if (input instanceof Set)
1162
+ return managedSet(this, key, input);
1163
+ function onUpdate(next) {
1164
+ state.set(key, next);
967
1165
  if (next) {
968
1166
  Parent.set(next, subject);
969
- control(next);
1167
+ ensure(next);
970
1168
  }
971
1169
  if (typeof argument == "function")
972
1170
  argument(next);
973
1171
  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
1172
  }
1173
+ if (input)
1174
+ onUpdate(input);
988
1175
  return {
989
1176
  set: onUpdate,
990
1177
  recursive: true
@@ -1096,7 +1283,8 @@ function tap(type, argument) {
1096
1283
  }
1097
1284
  else if (!instance && argument)
1098
1285
  throw Oops$3.AmbientRequired(type.name, subject, key);
1099
- this.update(key, instance);
1286
+ this.state.set(key, instance);
1287
+ this.update(key);
1100
1288
  });
1101
1289
  return {
1102
1290
  recursive: true,
@@ -1141,7 +1329,7 @@ function useModel(source, arg, arg2) {
1141
1329
  new source() :
1142
1330
  source() :
1143
1331
  source;
1144
- control(instance);
1332
+ ensure(instance);
1145
1333
  if (typeof cb == "function")
1146
1334
  cb(instance);
1147
1335
  return instance;
@@ -1159,7 +1347,7 @@ function useModel(source, arg, arg2) {
1159
1347
  return instance;
1160
1348
  }
1161
1349
  else {
1162
- const local = use(refresh => (new Subscriber(instance, () => refresh)));
1350
+ const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
1163
1351
  if (typeof arg == "object") {
1164
1352
  local.active = false;
1165
1353
  if (typeof arg2 !== "object")
@@ -1185,7 +1373,7 @@ function useModel(source, arg, arg2) {
1185
1373
 
1186
1374
  function useFrom(target, compute, suspend) {
1187
1375
  const local = use(refresh => {
1188
- const sub = new Subscriber(target, () => update);
1376
+ const sub = ensure(target).subscribe(() => update);
1189
1377
  const spy = sub.proxy;
1190
1378
  let value = compute.call(spy, spy);
1191
1379
  let update;
@@ -1237,23 +1425,14 @@ function useTap(source, path, expect) {
1237
1425
  }, [])();
1238
1426
  if (typeof path == "function")
1239
1427
  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
- });
1428
+ const local = use(refresh => (ensure(instance).subscribe(() => refresh)));
1256
1429
  React__default["default"].useLayoutEffect(local.commit, []);
1430
+ if (typeof path !== "undefined") {
1431
+ const value = local.proxy[path];
1432
+ if (value === undefined && expect)
1433
+ throw suspend(ensure(instance), path);
1434
+ return value;
1435
+ }
1257
1436
  return local.proxy;
1258
1437
  }
1259
1438
 
@@ -1274,7 +1453,7 @@ class MVC extends Model {
1274
1453
  static new(arg) {
1275
1454
  const instance = React__default["default"].useMemo(() => {
1276
1455
  const instance = new this();
1277
- control(instance);
1456
+ ensure(instance);
1278
1457
  if (typeof arg == "function")
1279
1458
  arg(instance);
1280
1459
  else if (arg)
@@ -1359,7 +1538,7 @@ function useAppliedProps(within, props) {
1359
1538
  const targets = within.local;
1360
1539
  return function integrate(props) {
1361
1540
  for (const [key, value] of entries(props))
1362
- if (["of", "for", "children"].indexOf(key) < 0)
1541
+ if (!["of", "for", "children"].includes(key))
1363
1542
  for (const into of targets)
1364
1543
  if (into && key in into)
1365
1544
  into[key] = value;
@@ -1371,7 +1550,7 @@ function RenderFunction(props) {
1371
1550
  const hook = use(refresh => {
1372
1551
  const targets = props.context.local;
1373
1552
  if (targets.length == 1)
1374
- return new Subscriber(targets[0], () => refresh);
1553
+ return ensure(targets[0]).subscribe(() => refresh);
1375
1554
  return {};
1376
1555
  });
1377
1556
  if (hook.commit)