@expressive/mvc 1.0.0-beta.42 → 1.0.0-beta.45

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
@@ -25,7 +25,7 @@ function issues(register) {
25
25
  return Library;
26
26
  }
27
27
 
28
- const Oops$d = issues({
28
+ const Oops$c = issues({
29
29
  BadEffectCallback: () => `Callback for effect-callback may only return a function.`
30
30
  });
31
31
  const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
@@ -42,9 +42,6 @@ function defineLazy(object, property, init) {
42
42
  }
43
43
  });
44
44
  }
45
- function setAlias(func, displayName) {
46
- assign(func, { displayName });
47
- }
48
45
  function createEffect(callback) {
49
46
  let unSet;
50
47
  return function (value) {
@@ -54,7 +51,7 @@ function createEffect(callback) {
54
51
  if (unSet instanceof Promise)
55
52
  unSet = undefined;
56
53
  if (unSet && typeof unSet !== "function")
57
- throw Oops$d.BadEffectCallback();
54
+ throw Oops$c.BadEffectCallback();
58
55
  };
59
56
  }
60
57
  function createValueEffect(callback) {
@@ -70,8 +67,28 @@ function createValueEffect(callback) {
70
67
  if (typeof out == "function")
71
68
  unSet = out;
72
69
  else
73
- throw Oops$d.BadEffectCallback();
70
+ throw Oops$c.BadEffectCallback();
71
+ };
72
+ }
73
+ function suspenseBoundary(fn) {
74
+ const retry = (err) => {
75
+ if (err instanceof Promise)
76
+ return err.then(compute);
77
+ else
78
+ throw err;
79
+ };
80
+ const compute = () => {
81
+ try {
82
+ const output = fn();
83
+ return output instanceof Promise
84
+ ? output.catch(retry)
85
+ : output;
86
+ }
87
+ catch (err) {
88
+ return retry(err);
89
+ }
74
90
  };
91
+ return compute();
75
92
  }
76
93
 
77
94
  const UPDATE = new WeakMap();
@@ -84,174 +101,148 @@ function applyUpdate(subject, keys) {
84
101
  };
85
102
  }
86
103
 
87
- const Oops$c = issues({
88
- StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
89
- NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
90
- });
91
- const READY = new WeakSet();
92
- class Controller {
93
- constructor(subject) {
94
- this.subject = subject;
95
- this.state = {};
96
- this.frame = new Set();
97
- this.waiting = new Set();
98
- this.onDestroy = new Set();
99
- this.followers = new Set();
100
- this.proxy = Object.create(subject);
104
+ const CONTROL = Symbol("CONTROL");
105
+ const WHY = Symbol("UPDATE");
106
+ const LOCAL = Symbol("LOCAL");
107
+ const STATE = Symbol("STATE");
108
+ class Model {
109
+ constructor() {
110
+ define(this, CONTROL, new Controller(this));
111
+ define(this, "get", this);
112
+ define(this, "set", this);
101
113
  }
102
- start() {
103
- for (const key in this.subject)
104
- this.manage(key);
105
- this.emit([]);
114
+ get [STATE]() {
115
+ return this[CONTROL].state;
106
116
  }
107
- stop() {
108
- this.followers.clear();
109
- this.onDestroy.forEach(x => x());
117
+ get [WHY]() {
118
+ return UPDATE.get(this);
110
119
  }
111
- manage(key) {
112
- const { proxy, state, subject } = this;
113
- const { value: entry } = getOwnPropertyDescriptor(subject, key);
114
- if (typeof entry == "function")
115
- return;
116
- let onGet;
117
- let onSet;
118
- let enumerable;
119
- const instruction = Pending.get(entry);
120
- if (instruction) {
121
- Pending.delete(entry);
122
- delete subject[key];
123
- const desc = instruction.call(this, key, this);
124
- if (desc === false)
125
- return;
126
- if (typeof desc == "object") {
127
- if ("value" in desc)
128
- state[key] = desc.value;
129
- onSet = desc.set;
130
- onGet = desc.get;
131
- enumerable = desc.enumerable;
132
- }
133
- }
134
- else
135
- state[key] = entry;
136
- const set = onSet !== false
137
- ? this.ref(key, onSet)
138
- : undefined;
139
- function get() {
140
- const local = this[LOCAL];
141
- const value = state[key];
142
- if (local && !local.watch[key])
143
- local.watch[key] = true;
144
- return onGet ? onGet(value, local) : value;
145
- }
146
- setAlias(get, `tap ${key}`);
147
- defineProperty(subject, key, {
148
- enumerable,
149
- set,
150
- get: onGet
151
- ? () => onGet(state[key])
152
- : () => state[key]
153
- });
154
- defineProperty(proxy, key, {
155
- enumerable,
156
- set,
157
- get
120
+ on(select, handler, squash, once) {
121
+ return control(this, control => {
122
+ if (typeof select == "function")
123
+ return control.addListener(select);
124
+ const keys = typeof select == "string" ? [select] :
125
+ !select.length ? getOwnPropertyNames(control.state) :
126
+ select;
127
+ ensure(control, keys);
128
+ const callback = squash
129
+ ? handler.bind(this)
130
+ : frame => frame
131
+ .filter(k => keys.includes(k))
132
+ .forEach(k => handler.call(this, control.state[k], k));
133
+ const trigger = once
134
+ ? frame => { remove(); callback(frame); }
135
+ : callback;
136
+ const remove = control.addListener(key => {
137
+ if (keys.includes(key))
138
+ return trigger;
139
+ });
140
+ return remove;
158
141
  });
159
142
  }
160
- ref(key, handler) {
161
- const { state, subject } = this;
162
- return (value) => {
163
- if (state[key] == value)
164
- return;
165
- if (handler)
166
- switch (handler.call(subject, value, state)) {
167
- case true:
168
- this.update(key);
169
- case false:
170
- return;
171
- }
172
- this.update(key, value);
173
- };
174
- }
175
- addListener(listener) {
176
- this.followers.add(listener);
177
- return () => {
178
- this.followers.delete(listener);
179
- };
143
+ once(select, callback, squash) {
144
+ if (callback)
145
+ return this.on(select, callback, squash, true);
146
+ return new Promise(resolve => {
147
+ this.on(select, resolve, true, true);
148
+ });
180
149
  }
181
- emit(keys) {
182
- const waiting = [...this.waiting];
183
- this.waiting.clear();
184
- applyUpdate(this.subject, keys)();
185
- for (const callback of waiting)
186
- try {
187
- callback(keys);
150
+ effect(callback, select) {
151
+ const effect = createEffect(callback);
152
+ return control(this, control => {
153
+ if (!select) {
154
+ const sub = new Subscriber(control, () => invoke);
155
+ const invoke = () => {
156
+ const x = sub.proxy;
157
+ effect.call(x, x);
158
+ };
159
+ invoke();
160
+ return sub.commit();
188
161
  }
189
- catch (err) {
190
- console.error(err);
162
+ const invoke = () => {
163
+ effect.call(this.get, this.get);
164
+ };
165
+ invoke();
166
+ if (!select.length) {
167
+ control.onDestroy.add(invoke);
168
+ return () => {
169
+ control.onDestroy.delete(invoke);
170
+ };
191
171
  }
172
+ return control.addListener(key => {
173
+ if (select.includes(key))
174
+ return invoke;
175
+ });
176
+ });
192
177
  }
193
- update(key, value) {
194
- if (1 in arguments)
195
- this.state[key] = value;
196
- if (this.frame.has(key))
197
- return;
198
- if (!this.frame.size)
199
- setTimeout(() => {
200
- flush(this);
201
- const keys = Object.freeze([...this.frame]);
202
- this.frame.clear();
203
- this.emit(keys);
204
- }, 0);
205
- this.frame.add(key);
206
- for (const callback of this.followers) {
207
- const event = callback(key, this);
208
- if (typeof event == "function")
209
- this.waiting.add(event);
210
- }
178
+ // TODO: account for exotic properties
179
+ import(source, select) {
180
+ const { subject } = control(this);
181
+ if (!select)
182
+ select = getOwnPropertyNames(subject);
183
+ for (const key of select)
184
+ if (key in source)
185
+ this[key] = source[key];
211
186
  }
212
- requestUpdate(arg) {
213
- if (typeof arg == "function") {
214
- this.waiting.add(arg);
215
- return;
216
- }
217
- if (typeof arg == "boolean" && arg !== this.frame.size > 0)
218
- return Promise.reject(Oops$c.StrictUpdate(arg));
219
- return {
220
- then: (callback) => {
221
- if (callback)
222
- if (this.frame.size)
223
- this.waiting.add(callback);
224
- else
225
- callback(false);
226
- else
227
- throw Oops$c.NoChaining();
187
+ export(subset) {
188
+ const { state } = control(this);
189
+ const output = {};
190
+ if (!subset)
191
+ subset = getOwnPropertyNames(state);
192
+ for (const key of subset)
193
+ output[key] = state[key];
194
+ return output;
195
+ }
196
+ update(arg, tag) {
197
+ const target = control(this);
198
+ if (typeof arg == "string") {
199
+ target.update(arg);
200
+ if (1 in arguments && arg in this) {
201
+ const method = this[arg];
202
+ if (typeof method == "function")
203
+ if (typeof tag != "boolean")
204
+ method.call(this, tag);
205
+ else if (tag)
206
+ method.call(this);
228
207
  }
229
- };
208
+ arg = undefined;
209
+ }
210
+ return target.requestUpdate(arg);
230
211
  }
231
- }
232
- function control(subject, cb) {
233
- let control = subject[CONTROL];
234
- const ready = READY.has(control);
235
- if (!control) {
236
- control = new Controller(subject);
237
- define(subject, CONTROL, control);
212
+ /**
213
+ * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
214
+ */
215
+ destroy() {
216
+ control(this).stop();
238
217
  }
239
- if (!cb) {
240
- if (!ready) {
241
- READY.add(control);
242
- control.start();
243
- }
244
- return control;
218
+ toString() {
219
+ return this.constructor.name;
245
220
  }
246
- if (!ready) {
247
- let done;
248
- control.requestUpdate(() => {
249
- done = cb(control);
250
- });
251
- return () => done && done();
221
+ /**
222
+ * Creates a new instance of this controller.
223
+ *
224
+ * Beyond `new this(...)`, method will activate managed-state.
225
+ *
226
+ * @param args - arguments sent to constructor
227
+ */
228
+ static create(...args) {
229
+ const instance = new this(...args);
230
+ control(instance);
231
+ return instance;
252
232
  }
253
- return cb(control);
254
- }
233
+ /**
234
+ * Static equivalent of `x instanceof this`.
235
+ *
236
+ * Will determine if provided class is a subtype of this one.
237
+ */
238
+ static isTypeof(maybe) {
239
+ return (typeof maybe == "function" &&
240
+ maybe.prototype instanceof this);
241
+ }
242
+ }
243
+ defineLazy(Model, CONTROL, function () {
244
+ return new Controller(this);
245
+ });
255
246
 
256
247
  class Subscriber {
257
248
  constructor(target, onUpdate) {
@@ -300,6 +291,32 @@ class Subscriber {
300
291
  }
301
292
 
302
293
  const Oops$b = issues({
294
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
295
+ });
296
+ function suspend(source, key) {
297
+ const error = Oops$b.ValueNotReady(source.subject, key);
298
+ const promise = new Promise(resolve => {
299
+ const release = source.addListener(forKey => {
300
+ if (forKey == key)
301
+ return () => {
302
+ if (source.state[key] !== undefined) {
303
+ release();
304
+ resolve();
305
+ }
306
+ };
307
+ });
308
+ });
309
+ return Object.assign(promise, {
310
+ toString: () => String(error),
311
+ name: "Suspense",
312
+ message: error.message,
313
+ stack: error.stack
314
+ });
315
+ }
316
+
317
+ const Oops$a = issues({
318
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
319
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`,
303
320
  ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
304
321
  ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
305
322
  });
@@ -307,86 +324,109 @@ const INIT = new WeakSet();
307
324
  const INFO = new WeakMap();
308
325
  const USED = new WeakMap();
309
326
  const KEYS = new WeakMap();
310
- function prepare(parent, key, source, setter, getter) {
311
- let sub;
312
- const { state, subject } = parent;
313
- const info = { key, parent, priority: 1 };
314
- let register = USED.get(parent);
315
- if (!register) {
316
- register = new Map();
317
- USED.set(parent, register);
318
- }
319
- register.set(key, info);
320
- function compute(initial) {
321
- try {
322
- return setter.call(sub.proxy, sub.proxy);
327
+ function from(source, arg1, arg2) {
328
+ return apply(function from(key) {
329
+ const parent = this;
330
+ const { subject, state } = this;
331
+ let getSource;
332
+ let required = arg2 === true || arg1 === true;
333
+ if (typeof arg1 == "boolean")
334
+ arg1 = undefined;
335
+ // Easy mistake, using a peer, will always be unresolved.
336
+ if (typeof source == "symbol")
337
+ throw Oops$a.PeerNotAllowed(subject, key);
338
+ // replace source controller in-case it is different
339
+ if (typeof source == "object")
340
+ getSource = () => control(source);
341
+ // specifically an arrow function (getter factory)
342
+ else if (!source.prototype) {
343
+ arg1 = source.call(subject, key);
344
+ getSource = () => this;
323
345
  }
324
- catch (err) {
325
- Oops$b.ComputeFailed(subject, key, !!initial).warn();
326
- throw err;
346
+ // Regular function is too ambiguous so not allowed.
347
+ else
348
+ throw Oops$a.BadComputedSource(subject, key, source);
349
+ let sub;
350
+ const setter = arg1;
351
+ const info = { key, parent, priority: 1 };
352
+ let register = USED.get(parent);
353
+ if (!register) {
354
+ register = new Map();
355
+ USED.set(parent, register);
327
356
  }
328
- }
329
- function update() {
330
- let value;
331
- try {
332
- value = compute(false);
357
+ register.set(key, info);
358
+ function compute(initial) {
359
+ try {
360
+ return setter.call(sub.proxy, sub.proxy);
361
+ }
362
+ catch (err) {
363
+ Oops$a.ComputeFailed(subject, key, !!initial).warn();
364
+ throw err;
365
+ }
333
366
  }
334
- catch (e) {
335
- console.error(e);
367
+ function update() {
368
+ let value;
369
+ try {
370
+ value = compute(false);
371
+ }
372
+ catch (e) {
373
+ console.error(e);
374
+ }
375
+ finally {
376
+ if (state[key] !== value) {
377
+ parent.update(key, value);
378
+ return value;
379
+ }
380
+ }
336
381
  }
337
- finally {
338
- if (state[key] !== value) {
339
- parent.update(key, value);
340
- return value;
382
+ function defer(_key, from) {
383
+ let pending = KEYS.get(from);
384
+ if (!pending)
385
+ KEYS.set(from, pending = []);
386
+ if (info.parent !== from)
387
+ update();
388
+ else {
389
+ const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
390
+ pending.splice(after + 1, 0, update);
341
391
  }
342
392
  }
343
- }
344
- function defer(_key, from) {
345
- let pending = KEYS.get(from);
346
- if (!pending)
347
- KEYS.set(from, pending = []);
348
- if (info.parent !== from)
349
- update();
350
- else {
351
- const after = pending.findIndex(peer => (info.priority > INFO.get(peer).priority));
352
- pending.splice(after + 1, 0, update);
393
+ function create(early) {
394
+ sub = new Subscriber(getSource(), defer);
395
+ defineProperty(state, key, {
396
+ value: undefined,
397
+ writable: true
398
+ });
399
+ try {
400
+ return state[key] = compute(true);
401
+ }
402
+ catch (e) {
403
+ if (early)
404
+ Oops$a.ComputedEarly(key).warn();
405
+ throw e;
406
+ }
407
+ finally {
408
+ sub.commit();
409
+ for (const key in sub.watch) {
410
+ const peer = register.get(key);
411
+ if (peer && peer.priority >= info.priority)
412
+ info.priority = peer.priority + 1;
413
+ }
414
+ }
353
415
  }
354
- }
355
- function create(early) {
356
- sub = new Subscriber(source(), defer);
416
+ INIT.add(create);
417
+ INFO.set(update, info);
357
418
  defineProperty(state, key, {
358
- value: undefined,
359
- writable: true
419
+ get: create,
420
+ configurable: true,
421
+ enumerable: true
360
422
  });
361
- try {
362
- return state[key] = compute(true);
363
- }
364
- catch (e) {
365
- if (early)
366
- Oops$b.ComputedEarly(key).warn();
367
- throw e;
368
- }
369
- finally {
370
- sub.commit();
371
- for (const key in sub.watch) {
372
- const peer = register.get(key);
373
- if (peer && peer.priority >= info.priority)
374
- info.priority = peer.priority + 1;
375
- }
376
- }
377
- }
378
- setAlias(update, `try ${key}`);
379
- setAlias(create, `new ${key}`);
380
- setAlias(setter, `run ${key}`);
381
- INIT.add(create);
382
- INFO.set(update, info);
383
- defineProperty(state, key, {
384
- get: create,
385
- configurable: true,
386
- enumerable: true
423
+ return () => {
424
+ const value = parent.state[key];
425
+ if (value === undefined && required)
426
+ throw suspend(this, key);
427
+ return value;
428
+ };
387
429
  });
388
- if (getter)
389
- return () => getter(parent, key);
390
430
  }
391
431
  function ensure(on, keys) {
392
432
  for (const key of keys) {
@@ -411,175 +451,205 @@ function flush(on) {
411
451
  KEYS.delete(on);
412
452
  }
413
453
 
414
- const CONTROL = Symbol("CONTROL");
415
- const WHY = Symbol("UPDATE");
416
- const LOCAL = Symbol("LOCAL");
417
- const STATE = Symbol("STATE");
418
- class Model {
419
- constructor() {
420
- define(this, CONTROL, new Controller(this));
421
- define(this, "get", this);
422
- define(this, "set", this);
423
- }
424
- get [STATE]() {
425
- return this[CONTROL].state;
454
+ const Oops$9 = issues({
455
+ StrictUpdate: () => `Strict update() did not find pending updates.`,
456
+ NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
457
+ });
458
+ const PENDING = new Map();
459
+ const READY = new WeakSet();
460
+ class Controller {
461
+ constructor(subject) {
462
+ this.subject = subject;
463
+ this.state = {};
464
+ this.frame = new Set();
465
+ this.waiting = new Set();
466
+ this.onDestroy = new Set();
467
+ this.followers = new Set();
468
+ this.proxy = Object.create(subject);
426
469
  }
427
- get [WHY]() {
428
- return UPDATE.get(this);
470
+ start() {
471
+ for (const key in this.subject)
472
+ this.manage(key);
473
+ this.emit([]);
429
474
  }
430
- on(select, handler, squash, once) {
431
- return control(this, control => {
432
- if (typeof select == "function")
433
- return control.addListener(select);
434
- const keys = typeof select == "string" ? [select] :
435
- !select.length ? getOwnPropertyNames(control.state) :
436
- select;
437
- ensure(control, keys);
438
- const callback = squash
439
- ? handler.bind(this)
440
- : frame => frame
441
- .filter(k => keys.includes(k))
442
- .forEach(k => handler.call(this, control.state[k], k));
443
- const trigger = once
444
- ? frame => { remove(); callback(frame); }
445
- : callback;
446
- const remove = control.addListener(key => {
447
- if (keys.includes(key))
448
- return trigger;
449
- });
450
- return remove;
451
- });
475
+ stop() {
476
+ this.followers.clear();
477
+ this.onDestroy.forEach(x => x());
452
478
  }
453
- once(select, callback, squash) {
454
- if (callback)
455
- return this.on(select, callback, squash, true);
456
- return new Promise(resolve => {
457
- this.on(select, resolve, true, true);
479
+ manage(key) {
480
+ const { proxy, state, subject } = this;
481
+ const { value: entry } = getOwnPropertyDescriptor(subject, key);
482
+ if (typeof entry == "function")
483
+ return;
484
+ if (typeof entry == "symbol") {
485
+ const instruction = PENDING.get(entry);
486
+ if (instruction)
487
+ instruction.call(this, key, this);
488
+ return;
489
+ }
490
+ state[key] = entry;
491
+ const set = this.ref(key);
492
+ defineProperty(subject, key, {
493
+ enumerable: false,
494
+ get: () => state[key],
495
+ set
458
496
  });
459
- }
460
- effect(callback, select) {
461
- const effect = createEffect(callback);
462
- return control(this, control => {
463
- if (!select) {
464
- const sub = new Subscriber(control, () => invoke);
465
- const invoke = () => {
466
- const x = sub.proxy;
467
- effect.call(x, x);
468
- };
469
- invoke();
470
- return sub.commit();
471
- }
472
- const invoke = () => {
473
- effect.call(this.get, this.get);
474
- };
475
- invoke();
476
- if (!select.length) {
477
- control.onDestroy.add(invoke);
478
- return () => {
479
- control.onDestroy.delete(invoke);
480
- };
481
- }
482
- return control.addListener(key => {
483
- if (select.includes(key))
484
- return invoke;
485
- });
497
+ defineProperty(proxy, key, {
498
+ enumerable: false,
499
+ get() {
500
+ const local = this[LOCAL];
501
+ if (local && !local.watch[key])
502
+ local.watch[key] = true;
503
+ return state[key];
504
+ },
505
+ set
486
506
  });
487
507
  }
488
- // TODO: account for exotic properties
489
- import(source, select) {
490
- const { subject } = control(this);
491
- if (!select)
492
- select = getOwnPropertyNames(subject);
493
- for (const key of select)
494
- if (key in source)
495
- this[key] = source[key];
508
+ ref(key, handler) {
509
+ const { state, subject } = this;
510
+ return (value) => {
511
+ if (state[key] == value)
512
+ return;
513
+ if (handler)
514
+ switch (handler.call(subject, value, state)) {
515
+ case true:
516
+ this.update(key);
517
+ case false:
518
+ return;
519
+ }
520
+ this.update(key, value);
521
+ };
496
522
  }
497
- export(subset) {
498
- const { state } = control(this);
499
- const output = {};
500
- if (!subset)
501
- subset = getOwnPropertyNames(state);
502
- for (const key of subset)
503
- output[key] = state[key];
504
- return output;
523
+ addListener(listener) {
524
+ this.followers.add(listener);
525
+ return () => {
526
+ this.followers.delete(listener);
527
+ };
505
528
  }
506
- update(arg, tag) {
507
- const target = control(this);
508
- if (typeof arg == "string") {
509
- target.update(arg);
510
- if (1 in arguments && arg in this) {
511
- const method = this[arg];
512
- if (typeof method == "function")
513
- if (typeof tag != "boolean")
514
- method.call(this, tag);
515
- else if (tag)
516
- method.call(this);
529
+ emit(keys) {
530
+ const waiting = [...this.waiting];
531
+ this.waiting.clear();
532
+ applyUpdate(this.subject, keys)();
533
+ for (const callback of waiting)
534
+ try {
535
+ callback(keys);
517
536
  }
518
- arg = undefined;
537
+ catch (err) {
538
+ console.error(err);
539
+ }
540
+ }
541
+ update(key, value) {
542
+ if (1 in arguments)
543
+ this.state[key] = value;
544
+ if (this.frame.has(key))
545
+ return;
546
+ if (!this.frame.size)
547
+ setTimeout(() => {
548
+ flush(this);
549
+ const keys = Object.freeze([...this.frame]);
550
+ this.frame.clear();
551
+ this.emit(keys);
552
+ }, 0);
553
+ this.frame.add(key);
554
+ for (const callback of this.followers) {
555
+ const event = callback(key, this);
556
+ if (typeof event == "function")
557
+ this.waiting.add(event);
519
558
  }
520
- return target.requestUpdate(arg);
521
559
  }
522
- /**
523
- * Mark this instance for garbage-collection and send `willDestroy` event to all listeners.
524
- */
525
- destroy() {
526
- control(this).stop();
560
+ requestUpdate(arg) {
561
+ if (typeof arg == "function") {
562
+ this.waiting.add(arg);
563
+ return;
564
+ }
565
+ if (arg === true && this.frame.size < 1)
566
+ return Promise.reject(Oops$9.StrictUpdate());
567
+ return {
568
+ then: (callback) => {
569
+ if (callback)
570
+ if (this.frame.size || arg !== false)
571
+ this.waiting.add(callback);
572
+ else
573
+ callback(false);
574
+ else
575
+ throw Oops$9.NoChaining();
576
+ }
577
+ };
527
578
  }
528
- toString() {
529
- return this.constructor.name;
579
+ }
580
+ function control(subject, cb) {
581
+ let control = subject[CONTROL];
582
+ const ready = READY.has(control);
583
+ if (!control) {
584
+ control = new Controller(subject);
585
+ define(subject, CONTROL, control);
530
586
  }
531
- /**
532
- * Creates a new instance of this controller.
533
- *
534
- * Beyond `new this(...)`, method will activate managed-state.
535
- *
536
- * @param args - arguments sent to constructor
537
- */
538
- static create(...args) {
539
- const instance = new this(...args);
540
- control(instance);
541
- return instance;
587
+ if (!cb) {
588
+ if (!ready) {
589
+ READY.add(control);
590
+ control.start();
591
+ }
592
+ return control;
542
593
  }
543
- /**
544
- * Static equivalent of `x instanceof this`.
545
- *
546
- * Will determine if provided class is a subtype of this one.
547
- */
548
- static isTypeof(maybe) {
549
- return (typeof maybe == "function" &&
550
- maybe.prototype instanceof this);
594
+ if (!ready) {
595
+ let done;
596
+ control.requestUpdate(() => {
597
+ done = cb(control);
598
+ });
599
+ return () => done && done();
551
600
  }
552
- }
553
- defineLazy(Model, CONTROL, function () {
554
- return new Controller(this);
555
- });
601
+ return cb(control);
602
+ }
556
603
 
557
- const Pending = new Map();
558
604
  function apply(fn, label) {
559
605
  const name = label || fn.name || "pending";
560
606
  const placeholder = Symbol(`${name} instruction`);
561
607
  function setup(key) {
608
+ const { proxy, state, subject } = this;
609
+ const control = this;
610
+ PENDING.delete(placeholder);
611
+ delete subject[key];
562
612
  let output = fn.call(this, key, this);
563
613
  if (typeof output == "function")
564
- return { get: output };
565
- if (typeof output !== "object")
566
- return output || undefined;
614
+ output = { get: output };
615
+ if (typeof output != "object")
616
+ return;
567
617
  if ("explicit" in output && output.explicit) {
568
- defineProperty(this.subject, key, output);
618
+ defineProperty(subject, key, output);
569
619
  return false;
570
620
  }
571
- if ("recursive" in output)
621
+ if ("recursive" in output && output.recursive)
572
622
  output = Object.assign(Object.assign({}, output), { get: recursive(this, key) });
573
- return output;
623
+ const desc = output;
624
+ if ("value" in desc)
625
+ state[key] = desc.value;
626
+ const { get: onGet, set: onSet, enumerable, suspense } = desc;
627
+ let set = onSet === false
628
+ ? undefined
629
+ : this.ref(key, onSet);
630
+ function get() {
631
+ if (!(key in state) && suspense)
632
+ throw suspend(control, key);
633
+ const local = this[LOCAL];
634
+ if (local && !(key in local.watch))
635
+ local.watch[key] = true;
636
+ const value = state[key];
637
+ if (!onGet)
638
+ return value;
639
+ return local
640
+ ? onGet(value, local)
641
+ : onGet(value);
642
+ }
643
+ for (const x of [subject, proxy])
644
+ defineProperty(x, key, { enumerable, get, set });
574
645
  }
575
- Pending.set(placeholder, setup);
646
+ PENDING.set(placeholder, setup);
576
647
  return placeholder;
577
648
  }
578
649
  function recursive(source, key) {
579
- const context = new WeakMap();
580
- const subscribe = (parent) => {
650
+ return forSubscriber((parent, context) => {
581
651
  let child;
582
- const init = () => {
652
+ function init() {
583
653
  if (child) {
584
654
  child.release();
585
655
  parent.dependant.delete(child);
@@ -594,20 +664,28 @@ function recursive(source, key) {
594
664
  parent.dependant.add(child);
595
665
  context.set(parent, child.proxy);
596
666
  }
597
- };
667
+ }
598
668
  init();
599
669
  parent.watch[key] = init;
600
- };
670
+ });
671
+ }
672
+ function forSubscriber(fn) {
673
+ const context = new WeakMap();
601
674
  return (value, local) => {
602
675
  if (!local)
603
676
  return value;
604
- if (!context.has(local))
605
- subscribe(local);
677
+ if (!context.has(local)) {
678
+ const output = fn(local, context);
679
+ if (output) {
680
+ context.set(local, output);
681
+ return output;
682
+ }
683
+ }
606
684
  return context.get(local);
607
685
  };
608
686
  }
609
687
 
610
- const Oops$a = issues({
688
+ const Oops$8 = issues({
611
689
  DuplicateAction: (key) => `Invoked action ${key} but one is already active.`
612
690
  });
613
691
  function act(task) {
@@ -615,7 +693,7 @@ function act(task) {
615
693
  let pending = false;
616
694
  const invoke = (...args) => {
617
695
  if (pending)
618
- return Promise.reject(Oops$a.DuplicateAction(key));
696
+ return Promise.reject(Oops$8.DuplicateAction(key));
619
697
  pending = true;
620
698
  this.update(key);
621
699
  return new Promise(res => {
@@ -626,7 +704,6 @@ function act(task) {
626
704
  });
627
705
  };
628
706
  this.state[key] = undefined;
629
- setAlias(invoke, `run ${key}`);
630
707
  defineProperty(invoke, "active", {
631
708
  get: () => pending
632
709
  });
@@ -637,104 +714,62 @@ function act(task) {
637
714
  });
638
715
  }
639
716
 
640
- const Oops$9 = issues({
641
- ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
642
- });
643
- /**
644
- * Get value, suspend instead if undefined.
645
- *
646
- * Throws suspense promise also interpretable as an error.
647
- * React could handle it but other contexts probably not.
648
- */
649
- function pendingValue(via, key) {
650
- const value = via.state[key];
651
- if (value === undefined)
652
- throw suspend(via, key);
653
- return value;
654
- }
655
- function pendingFactory(via, key, fn) {
656
- const { subject, state } = via;
657
- let waiting;
658
- let error;
659
- return () => {
660
- if (waiting)
661
- throw waiting;
662
- if (error)
663
- throw error;
664
- if (key in state)
665
- return state[key];
666
- let output;
667
- try {
668
- output = fn.call(subject, key, subject);
669
- }
670
- catch (err) {
671
- throw error = err;
672
- }
673
- if (output instanceof Promise) {
674
- const issue = Oops$9.ValueNotReady(subject, key);
675
- output
676
- .catch(err => error = err)
677
- .then(out => state[key] = out)
678
- .finally(() => waiting = undefined);
679
- throw waiting = Object.assign(output, {
680
- message: issue.message,
681
- stack: issue.stack
682
- });
683
- }
684
- else {
685
- return state[key] = output;
686
- }
687
- };
717
+ class Managed extends Array {
718
+ constructor(onUpdate) {
719
+ super();
720
+ this.lastUpdate = [0, 0];
721
+ this.update = (from, to) => {
722
+ this.lastUpdate = [from, to];
723
+ onUpdate();
724
+ };
725
+ }
726
+ push(...items) {
727
+ const length = this.length;
728
+ const end = super.push(...items);
729
+ this.update(length, end);
730
+ return end;
731
+ }
732
+ unshift(...items) {
733
+ const end = super.push(...items);
734
+ this.update(0, end);
735
+ return end;
736
+ }
737
+ splice(start, deleteCount, ...rest) {
738
+ this.update(start, deleteCount !== rest.length ? Infinity : deleteCount);
739
+ return super.splice(start, deleteCount, ...rest);
740
+ }
688
741
  }
689
- function suspend(source, key) {
690
- const error = Oops$9.ValueNotReady(source.subject, key);
691
- const promise = new Promise(resolve => {
692
- const release = source.addListener(forKey => {
693
- if (forKey == key)
694
- return () => {
695
- if (source.state[key] !== undefined) {
696
- release();
697
- resolve();
698
- }
699
- };
742
+ function array() {
743
+ return apply(function array(key) {
744
+ const array = new Managed(() => this.update(key));
745
+ const access = forSubscriber(context => {
746
+ const local = Object.create(array);
747
+ const update = context.onUpdate(key, this);
748
+ let rangeA = 0;
749
+ let rangeB = 0;
750
+ function onUpdate() {
751
+ const [lastA, lastB] = array.lastUpdate;
752
+ if (rangeA <= lastB && rangeB >= lastA)
753
+ return update;
754
+ }
755
+ function listen(eventA, eventB) {
756
+ if (context.active)
757
+ return;
758
+ context.watch[key] = onUpdate;
759
+ rangeA = Math.min(rangeA, eventA);
760
+ rangeB = Math.max(rangeB, eventB);
761
+ }
762
+ local[Symbol.iterator] = () => {
763
+ listen(0, Infinity);
764
+ return array[Symbol.iterator]();
765
+ };
766
+ return local;
700
767
  });
701
- });
702
- return Object.assign(promise, {
703
- toString: () => String(error),
704
- name: "Suspense",
705
- message: error.message,
706
- stack: error.stack
707
- });
708
- }
709
-
710
- const Oops$8 = issues({
711
- BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
712
- PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
713
- });
714
- function from(source, setter, arg) {
715
- return apply(function from(key) {
716
- const { subject } = this;
717
- let getSource;
718
- let getter;
719
- if (arg === true || setter === true)
720
- getter = pendingValue;
721
- if (typeof setter == "boolean")
722
- setter = undefined;
723
- // Easy mistake, using a peer, will always be unresolved.
724
- if (typeof source == "symbol")
725
- throw Oops$8.PeerNotAllowed(subject, key);
726
- // replace source controller in-case it is different
727
- if (typeof source == "object")
728
- getSource = () => control(source);
729
- // specifically an arrow function (getter factory)
730
- else if (!source.prototype) {
731
- setter = source.call(subject, key);
732
- getSource = () => this;
733
- }
734
- // Regular function is too ambiguous so not allowed.
735
- else
736
- throw Oops$8.BadComputedSource(subject, key, source);
737
- return prepare(this, key, getSource, setter, getter);
768
+ return {
769
+ get: access,
770
+ set: false,
771
+ value: array
772
+ };
738
773
  });
739
774
  }
740
775
 
@@ -763,32 +798,21 @@ function createRef(key, cb) {
763
798
  const Oops$7 = issues({
764
799
  NonOptional: (Parent, key) => `Property ${Parent}.${key} is marked as required.`,
765
800
  BadFactory: () => `Set instruction can only accept a factory or undefined.`,
801
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`,
766
802
  FactoryFailed: (model, key) => `Generating initial value for ${model}.${key} failed.`
767
803
  });
768
804
  function set(factory, argument) {
769
805
  return apply(function set(key) {
770
806
  let set;
771
807
  let get;
808
+ let suspense;
772
809
  const required = argument === true || argument === undefined;
773
810
  if (factory === undefined)
774
- get = () => pendingValue(this, key);
775
- else if (typeof factory !== "function")
811
+ suspense = true;
812
+ else if (typeof factory === "function")
813
+ get = pendingFactory(this, key, factory, required);
814
+ else
776
815
  throw Oops$7.BadFactory();
777
- else {
778
- get = pendingFactory(this, key, factory);
779
- if (required)
780
- try {
781
- get();
782
- }
783
- catch (err) {
784
- if (err instanceof Promise)
785
- ;
786
- else {
787
- Oops$7.FactoryFailed(this.subject, key).warn();
788
- throw err;
789
- }
790
- }
791
- }
792
816
  if (typeof argument == "function")
793
817
  set = createValueEffect(argument);
794
818
  else
@@ -796,8 +820,63 @@ function set(factory, argument) {
796
820
  if (value === undefined && required)
797
821
  throw Oops$7.NonOptional(this.subject, key);
798
822
  };
799
- return { set, get };
823
+ return {
824
+ set,
825
+ get,
826
+ suspense
827
+ };
800
828
  });
829
+ }
830
+ function pendingFactory(parent, key, fn, required) {
831
+ const { subject, state } = parent;
832
+ let pending;
833
+ let error;
834
+ const init = () => {
835
+ const output = suspenseBoundary(() => {
836
+ return fn.call(subject, key, subject);
837
+ });
838
+ if (output instanceof Promise) {
839
+ pending = output
840
+ .catch(err => error = err)
841
+ .then(val => state[key] = val)
842
+ .finally(() => {
843
+ pending = undefined;
844
+ parent.update(key);
845
+ });
846
+ return;
847
+ }
848
+ return state[key] = output;
849
+ };
850
+ const suspend = () => {
851
+ if (required === false)
852
+ return undefined;
853
+ const issue = Oops$7.ValueNotReady(subject, key);
854
+ Object.assign(pending, {
855
+ message: issue.message,
856
+ stack: issue.stack
857
+ });
858
+ throw pending;
859
+ };
860
+ if (required)
861
+ try {
862
+ init();
863
+ }
864
+ catch (err) {
865
+ Oops$7.FactoryFailed(subject, key).warn();
866
+ throw err;
867
+ }
868
+ return () => {
869
+ if (pending)
870
+ return suspend();
871
+ if (error)
872
+ throw error;
873
+ if (key in state)
874
+ return state[key];
875
+ let output = init();
876
+ return pending
877
+ ? suspend()
878
+ : output;
879
+ };
801
880
  }
802
881
 
803
882
  const Parent = new WeakMap();
@@ -940,24 +1019,26 @@ const ContextWasUsed = new WeakMap();
940
1019
  function tap(type, argument) {
941
1020
  return apply(function tap(key) {
942
1021
  if ("set" in type)
943
- this.state[key] = type.get();
944
- else {
945
- const { subject } = this;
946
- if ("set" in subject.constructor)
947
- throw Oops$3.CantAttachGlobal(subject, type.name);
948
- getPending(subject).push(context => {
949
- let instance = context.get(type);
950
- if (typeof argument == "function") {
951
- if (argument(instance) === false)
952
- instance = undefined;
953
- }
954
- else if (!instance && argument)
955
- throw Oops$3.AmbientRequired(type.name, subject, key);
956
- this.update(key, instance);
957
- });
958
- }
1022
+ return {
1023
+ recursive: true,
1024
+ value: type.get()
1025
+ };
1026
+ const { subject } = this;
1027
+ if ("set" in subject.constructor)
1028
+ throw Oops$3.CantAttachGlobal(subject, type.name);
1029
+ getPending(subject).push(context => {
1030
+ let instance = context.get(type);
1031
+ if (typeof argument == "function") {
1032
+ if (argument(instance) === false)
1033
+ instance = undefined;
1034
+ }
1035
+ else if (!instance && argument)
1036
+ throw Oops$3.AmbientRequired(type.name, subject, key);
1037
+ this.update(key, instance);
1038
+ });
959
1039
  return {
960
- recursive: true
1040
+ recursive: true,
1041
+ suspense: true
961
1042
  };
962
1043
  });
963
1044
  }
@@ -1255,7 +1336,7 @@ class Global extends MVC {
1255
1336
  static set(updates) {
1256
1337
  const instance = Active.get(this) || this.create();
1257
1338
  instance.import(updates);
1258
- return instance.update();
1339
+ return instance.update(false);
1259
1340
  }
1260
1341
  static get(arg) {
1261
1342
  const instance = Active.get(this);
@@ -1287,6 +1368,7 @@ exports.STATE = STATE;
1287
1368
  exports.WHY = WHY;
1288
1369
  exports.act = act;
1289
1370
  exports.apply = apply;
1371
+ exports.array = array;
1290
1372
  exports["default"] = MVC;
1291
1373
  exports.from = from;
1292
1374
  exports.parent = parent;