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