@expressive/mvc 1.0.0-beta.2 → 1.0.0-beta.20

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
@@ -9,38 +9,25 @@ class Issue extends Error {
9
9
  super(...arguments);
10
10
  // drop first line (error factory) in stack trace.
11
11
  this.stack = this.stack.replace(/\n.+/, "");
12
- this.warn = () => console.warn(this.message);
13
12
  }
14
- static factory(register) {
15
- const Library = {};
16
- for (const name in register)
17
- Library[name] = (...args) => new this(register[name].apply(null, args));
18
- return Library;
13
+ warn() {
14
+ console.warn(this.message);
15
+ }
16
+ throw() {
17
+ throw this;
19
18
  }
20
19
  }
21
- var Oops = Issue.factory({
22
- NothingInContext: (name) => `Couldn't find controller for ${name} in context; did you forget to use a Provider?`,
23
- HasPropertyUndefined: (control, property) => `${control}.${property} is marked as required for this render.`,
24
- CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}'. This is not possible because '${child}' is not also a singleton.`,
25
- AssignToGetter: (name) => `Property ${name} is a getter only. Assignment was skipped.`,
26
- ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "getting initial value of" : "refreshing"} ${parent}.${property}. Property will be skipped.`,
27
- ComputedEarly: (property) => `Note: Computed values are usually only calculated after first access, except where accessed implicitly by "on" or "export". Your '${property}' getter may have run earlier than intended because of that.`,
28
- BadEffectCallback: () => `Callback for property-update may only return a function.`,
29
- DestroyNotPossible: (name) => `${name}.destory() was called on an instance which is not active. This is an antipattern and may caused unexpected behavior.`,
30
- GlobalExists: (type) => `Shared instance of ${type} already exists! '${type}.use(...)' may only be mounted once at any one time.`,
31
- GlobalDoesNotExist: (name) => `Tried to access singleton ${name} but one does not exist! Did you forget to initialize?\nCall ${name}.create() before attempting to access, or consider using ${name}.use() here instead.`,
32
- BadHOCArgument: () => `Argument for hoc() is not a component.`,
33
- StrictUpdate: () => `Strict requestUpdate() did not find pending updates.`,
34
- AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for ${requester}.${key} but could not be found.`,
35
- ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
36
- UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got} but must be instanceof ${expects}`,
37
- SetActionProperty: (key) => `Attempted assignment of ${key}. This is not allowed because an action-property.`,
38
- DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
39
- BadProviderProps: () => `Provider expects either 'of' or 'for' props.`,
40
- BindNotAvailable: () => `Bind proxies are only available from a subscriber. Did you accessnode outside of a component?`
41
- });
20
+ function issues(register) {
21
+ const Library = {};
22
+ for (const name in register)
23
+ Library[name] = (...args) => new Issue(register[name].apply(null, args));
24
+ return Library;
25
+ }
42
26
 
43
- const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, keys, values } = Object;
27
+ const Oops$9 = issues({
28
+ BadEffectCallback: () => `Callback for effect-callback may only return a function.`
29
+ });
30
+ const { assign, create, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, getOwnPropertySymbols, keys: keys$1, values } = Object;
44
31
  function define(target, key, value) {
45
32
  defineProperty(target, key, { value });
46
33
  }
@@ -54,38 +41,43 @@ function defineLazy(object, property, init) {
54
41
  }
55
42
  });
56
43
  }
57
- function fn(x) {
58
- return typeof x == "function";
59
- }
60
- function alias(fn, displayName) {
61
- assign(fn, { displayName });
62
- }
63
- function entriesIn(object) {
64
- return entries(getOwnPropertyDescriptors(object));
65
- }
66
- function debounce(callback) {
67
- let throttle;
68
- return () => {
69
- if (!throttle) {
70
- throttle = true;
71
- callback();
72
- setTimeout(() => {
73
- throttle = false;
74
- }, 0);
75
- }
76
- };
44
+ function setAlias(func, displayName) {
45
+ assign(func, { displayName });
77
46
  }
78
47
  function createEffect(callback) {
79
48
  let unSet;
80
- return (value, callee = value) => {
81
- typeof unSet == "function" && unSet();
82
- unSet = callback.call(callee, value);
49
+ return function (value) {
50
+ if (typeof unSet == "function")
51
+ unSet();
52
+ unSet = callback.call(this, value);
83
53
  if (unSet instanceof Promise)
84
54
  unSet = undefined;
85
- if (unSet && !fn(unSet))
86
- throw Oops.BadEffectCallback();
55
+ if (unSet && typeof unSet !== "function")
56
+ throw Oops$9.BadEffectCallback();
87
57
  };
88
58
  }
59
+ function createValueEffect(callback) {
60
+ let unSet;
61
+ return function (value) {
62
+ if (typeof unSet == "function")
63
+ unSet(value);
64
+ const out = callback.call(this, value);
65
+ if (typeof out == "boolean")
66
+ return out;
67
+ if (!out || out instanceof Promise)
68
+ return;
69
+ if (typeof out == "function")
70
+ unSet = out;
71
+ else
72
+ throw Oops$9.BadEffectCallback();
73
+ };
74
+ }
75
+ function select(source, select) {
76
+ const proxy = {};
77
+ for (const k in source)
78
+ proxy[k] = k;
79
+ return select(proxy);
80
+ }
89
81
  function selectRecursive(using, keys) {
90
82
  const found = new Set();
91
83
  const spy = {};
@@ -98,260 +90,179 @@ function selectRecursive(using, keys) {
98
90
  });
99
91
  using(spy);
100
92
  return Array.from(found);
101
- }
102
- function insertAfter(into, item, predicate) {
103
- const matchIndex = into.findIndex(predicate);
104
- into.splice(matchIndex + 1, 0, item);
105
93
  }
106
94
 
107
- const LOCAL = Symbol("current_subscriber");
108
95
  class Subscriber {
109
- constructor(subject, callback, metadata) {
110
- this.subject = subject;
111
- this.callback = callback;
112
- this.metadata = metadata;
96
+ constructor(parent, onUpdate) {
97
+ this.onUpdate = onUpdate;
98
+ this.active = false;
99
+ this.follows = {};
113
100
  this.dependant = new Set();
114
- this.following = {};
115
- this.listen = () => {
116
- this.dependant.forEach(x => x.listen());
117
- this.parent.listeners.add(this.following);
118
- // for(const key in this.proxy)
119
- // delete this.proxy[key];
120
- return () => this.release();
121
- };
122
- const { state } = this.parent = subject[CONTROL];
123
- this.proxy = create(subject);
101
+ if (!(parent instanceof Controller))
102
+ parent = manage(parent);
103
+ this.parent = parent;
104
+ this.source = parent.subject;
105
+ this.proxy = create(parent.subject);
124
106
  define(this.proxy, LOCAL, this);
125
- for (const key in state)
126
- this.spyOn(key);
127
- }
128
- release() {
129
- this.dependant.forEach(x => x.release());
130
- this.parent.listeners.delete(this.following);
131
- }
132
- spyOn(key) {
133
- const access = () => {
134
- let value = this.subject[key];
135
- if (value instanceof Model)
136
- return this.delegate(key);
137
- this.follow(key, this.callback);
138
- delete this.proxy[key];
139
- return value;
107
+ for (const key in parent.state)
108
+ this.spy(key);
109
+ }
110
+ spy(key) {
111
+ const { proxy, source } = this;
112
+ const { set } = getOwnPropertyDescriptor(source, key);
113
+ const intercept = () => {
114
+ this.follow(key);
115
+ delete proxy[key];
116
+ return proxy[key];
140
117
  };
141
- alias(access, `tap ${key}`);
142
- defineProperty(this.proxy, key, {
143
- get: access,
144
- set: this.parent.setter(key),
145
- configurable: true
118
+ setAlias(intercept, `tap ${key}`);
119
+ defineProperty(proxy, key, {
120
+ get: intercept,
121
+ set,
122
+ configurable: true,
123
+ enumerable: true
146
124
  });
147
125
  }
148
126
  follow(key, cb) {
149
- if (this.metadata)
150
- metaData(cb, this.metadata);
151
- this.following[key] = cb;
152
- }
153
- recursive(key, onUpdate) {
154
- let child;
155
- const start = (mounted) => {
156
- child = onUpdate();
157
- if (child) {
158
- this.dependant.add(child);
159
- if (mounted)
160
- child.listen();
161
- }
162
- };
163
- const reset = () => {
164
- if (child) {
165
- child.release();
166
- this.dependant.delete(child);
167
- }
168
- start(true);
169
- this.callback();
127
+ this.follows[key] = cb || this.onUpdate;
128
+ }
129
+ commit() {
130
+ const { dependant, follows, parent } = this;
131
+ const onDone = parent.addListener(follows);
132
+ this.active = true;
133
+ dependant.forEach(x => x.commit());
134
+ return this.release = () => {
135
+ dependant.forEach(x => x.release());
136
+ onDone();
170
137
  };
171
- this.follow(key, reset);
172
- start();
173
- }
174
- delegate(key) {
175
- let sub;
176
- this.recursive(key, () => {
177
- let value = this.subject[key];
178
- if (value instanceof Model) {
179
- let child = sub = new Subscriber(value, this.callback, this.metadata);
180
- defineProperty(this.proxy, key, {
181
- get: () => child.proxy,
182
- set: it => this.subject[key] = it,
183
- configurable: true
184
- });
185
- return child;
186
- }
187
- });
188
- return sub && sub.proxy;
189
138
  }
190
139
  }
191
140
 
192
- function createBindings(on) {
193
- const subscriber = on[LOCAL];
194
- if (!subscriber)
195
- throw Oops.BindNotAvailable();
196
- const agent = bindRefFunctions(subscriber.parent);
197
- subscriber.dependant.add(agent);
198
- return agent.proxy;
199
- }
200
- function bindRefFunctions(on) {
201
- const proxy = {};
202
- let index = 0;
203
- const gc = new Set();
204
- const refs = [];
205
- function bind(key) {
206
- let cleanup;
207
- return (e) => {
208
- if (cleanup) {
209
- cleanup();
210
- gc.delete(cleanup);
211
- cleanup = undefined;
212
- }
213
- if (e)
214
- gc.add(cleanup = createBinding(e, on, key));
215
- };
141
+ const Oops$8 = issues({
142
+ ComputeFailed: (parent, property, initial) => `An exception was thrown while ${initial ? "initializing" : "refreshing"} [${parent}.${property}].`,
143
+ ComputedEarly: (property) => `Note: Computed values don't run until accessed, except when subscribed to. '${property}' getter may have run earlier than intended.`
144
+ });
145
+ const ComputedInit = new WeakSet();
146
+ const ComputedInfo = new WeakMap();
147
+ const ComputedUsed = new WeakMap();
148
+ const ComputedKeys = new WeakMap();
149
+ function prepare(parent, key, source, setter, getter) {
150
+ let sub;
151
+ const { state, subject } = parent;
152
+ const info = { key, parent, priority: 1 };
153
+ const current = getter
154
+ ? () => getter.call(subject, state[key], key)
155
+ : () => state[key];
156
+ const prioritize = register(parent, key, info);
157
+ function compute(initial) {
158
+ try {
159
+ return setter.call(sub.proxy, sub.proxy);
160
+ }
161
+ catch (err) {
162
+ Oops$8.ComputeFailed(subject, key, !!initial).warn();
163
+ throw err;
164
+ }
216
165
  }
217
- for (const key in on.state)
218
- defineProperty(proxy, key, {
219
- get() {
220
- try {
221
- return refs[index] || (refs[index] = bind(key));
222
- }
223
- finally {
224
- if (++index > refs.length)
225
- index = 0;
226
- }
166
+ function update() {
167
+ let value;
168
+ try {
169
+ value = compute(false);
170
+ }
171
+ catch (e) {
172
+ console.error(e);
173
+ }
174
+ finally {
175
+ if (state[key] !== value) {
176
+ parent.update(key, value);
177
+ return value;
227
178
  }
228
- });
229
- return {
230
- proxy,
231
- listen() { },
232
- release() {
233
- gc.forEach(x => x());
234
179
  }
235
- };
236
- }
237
- function createBinding(e, control, key) {
238
- if (e instanceof HTMLInputElement)
239
- return createTwoWayBinding(e, control, key);
240
- else
241
- return createOneWayBinding(e, control, key);
242
- }
243
- function createOneWayBinding(element, parent, key) {
244
- function sync(to) {
245
- element.innerHTML = String(to);
246
180
  }
247
- sync(parent.state[key]);
248
- return parent.watch(key, sync);
249
- }
250
- function createTwoWayBinding(input, parent, key) {
251
- let last;
252
- function onUpdate() {
253
- last = parent.subject[key] = this.value;
254
- }
255
- function sync(to) {
256
- if (to !== last)
257
- input.value = String(to);
258
- }
259
- sync(parent.state[key]);
260
- const release = parent.watch(key, sync);
261
- input.addEventListener("input", onUpdate);
262
- return () => {
263
- release();
264
- input.removeEventListener("input", onUpdate);
265
- };
266
- }
267
-
268
- class Lookup {
269
- constructor() {
270
- this.table = new Map();
271
- }
272
- key(T) {
273
- let key = this.table.get(T);
274
- if (!key) {
275
- key = Symbol(T.name);
276
- this.table.set(T, key);
181
+ function create(early) {
182
+ sub = new Subscriber(source(), update);
183
+ defineProperty(state, key, {
184
+ value: undefined,
185
+ writable: true
186
+ });
187
+ defineProperty(subject, key, {
188
+ enumerable: true,
189
+ configurable: true,
190
+ get: current
191
+ });
192
+ try {
193
+ state[key] = compute(true);
277
194
  }
278
- return key;
279
- }
280
- get(T, strict) {
281
- const instance = this[this.key(T)];
282
- if (!instance && strict)
283
- throw Oops.NothingInContext(T.name);
284
- return instance;
285
- }
286
- push(items) {
287
- const next = create(this);
288
- for (const I of items) {
289
- let T = I.constructor;
290
- do {
291
- define(next, this.key(T), I);
292
- } while (T = T.inherits);
195
+ catch (e) {
196
+ if (early)
197
+ Oops$8.ComputedEarly(key).warn();
198
+ throw e;
293
199
  }
294
- return next;
295
- }
296
- pop() {
297
- for (const c of values(this))
298
- c.destroy();
200
+ finally {
201
+ sub.commit();
202
+ for (const key in sub.follows)
203
+ prioritize(key);
204
+ }
205
+ return current();
299
206
  }
207
+ setAlias(update, `try ${key}`);
208
+ setAlias(create, `new ${key}`);
209
+ setAlias(setter, `run ${key}`);
210
+ ComputedInit.add(create);
211
+ ComputedInfo.set(update, info);
212
+ for (const on of [state, subject])
213
+ defineProperty(on, key, {
214
+ get: create,
215
+ configurable: true,
216
+ enumerable: true
217
+ });
300
218
  }
301
- const LookupContext = react.createContext(new Lookup());
302
- const LookupProvider = LookupContext.Provider;
303
- function useLookup() {
304
- return react.useContext(LookupContext);
219
+ function register(on, key, info) {
220
+ let register = ComputedUsed.get(on);
221
+ if (!register)
222
+ ComputedUsed.set(on, register = new Map());
223
+ register.set(key, info);
224
+ return (key) => {
225
+ const peer = register.get(key);
226
+ if (peer && peer.priority >= info.priority)
227
+ info.priority = peer.priority + 1;
228
+ };
305
229
  }
306
- function useIncluding(insert, dependancy) {
307
- const current = useLookup();
308
- function next() {
309
- const provide = insert instanceof Model
310
- ? [insert] : values(insert).map(T => Model.isTypeof(T) ? T.create() : T);
311
- return current.push(provide);
230
+ function ensure(on, keys) {
231
+ for (const key of keys) {
232
+ const desc = getOwnPropertyDescriptor(on.subject, key);
233
+ const getter = desc && desc.get;
234
+ if (ComputedInit.has(getter))
235
+ getter(true);
312
236
  }
313
- return react.useMemo(next, [dependancy]);
314
237
  }
315
- const Consumer = (props) => {
316
- const { get, has, children: render, of: Control } = props;
317
- if (fn(render))
318
- return render(Control.tap());
319
- const callback = has || get;
320
- if (fn(callback))
321
- callback(Control.get(!!has));
322
- return null;
323
- };
324
- function Provider(props) {
325
- const { children, of: target } = props;
326
- const data = assign({}, props, { children: null, of: null });
327
- if (Model.isTypeof(target))
328
- return react.createElement(ParentProvider, { target, data }, children);
329
- else if (target instanceof Model)
330
- return react.createElement(DirectProvider, { target, data }, children);
331
- else if (typeof target == "object")
332
- return react.createElement(MultiProvider, { types: target }, children);
333
- else
334
- throw Oops.BadProviderProps();
335
- }
336
- function ParentProvider(props) {
337
- let { children, target, data } = props;
338
- const instance = target.using(data);
339
- const value = useIncluding(instance.get, target);
340
- if (fn(children))
341
- children = children(instance);
342
- return react.createElement(LookupProvider, { value }, children);
343
- }
344
- function DirectProvider(props) {
345
- const { children, data, target } = props;
346
- const value = useIncluding(target, target);
347
- target.import(data);
348
- return react.createElement(LookupProvider, { value }, children);
238
+ function capture(on, request) {
239
+ const compute = ComputedInfo.get(request);
240
+ const callback = request;
241
+ if (!compute)
242
+ return;
243
+ let pending = ComputedKeys.get(on);
244
+ if (!pending)
245
+ ComputedKeys.set(on, pending = []);
246
+ if (compute.parent !== on)
247
+ callback();
248
+ else {
249
+ const queue = pending.findIndex(peer => compute.priority > ComputedInfo.get(peer).priority);
250
+ pending.splice(queue + 1, 0, callback);
251
+ }
252
+ return true;
349
253
  }
350
- function MultiProvider(props) {
351
- const { children, types } = props;
352
- const value = useIncluding(types);
353
- react.useEffect(() => () => value.pop(), []);
354
- return react.createElement(LookupProvider, { value }, children);
254
+ function flush(on) {
255
+ const handled = on.frame;
256
+ let pending = ComputedKeys.get(on);
257
+ if (pending) {
258
+ while (pending.length) {
259
+ const compute = pending.shift();
260
+ const { key } = ComputedInfo.get(compute);
261
+ if (!handled.has(key))
262
+ compute();
263
+ }
264
+ ComputedKeys.delete(on);
265
+ }
355
266
  }
356
267
 
357
268
  const Lifecycle = {
@@ -366,698 +277,910 @@ const lifecycleEvents = [
366
277
  "willDestroy",
367
278
  ...values(Lifecycle)
368
279
  ];
369
- function forAlias(prefix) {
280
+ function lifecycle(prefix) {
281
+ const type = eventsFor(prefix);
282
+ return function useLifecycle(sub, tag) {
283
+ const emit = react.useMemo(() => {
284
+ const subject = sub.source;
285
+ const id = typeof tag == "function" ? tag(subject) : tag;
286
+ return (name) => {
287
+ for (const key of [name, type(name)])
288
+ subject.update(key, id);
289
+ };
290
+ }, [sub, tag]);
291
+ emit(Lifecycle.WILL_RENDER);
292
+ emit(sub.active
293
+ ? Lifecycle.WILL_UPDATE
294
+ : Lifecycle.WILL_MOUNT);
295
+ react.useLayoutEffect(() => {
296
+ sub.commit();
297
+ emit(Lifecycle.DID_MOUNT);
298
+ return () => {
299
+ emit(Lifecycle.WILL_UNMOUNT);
300
+ sub.release();
301
+ };
302
+ }, [sub]);
303
+ };
304
+ }
305
+ function eventsFor(prefix) {
370
306
  const map = new Map();
371
307
  for (const name of values(Lifecycle)) {
372
308
  const alias = prefix + name[0].toUpperCase() + name.slice(1);
373
309
  lifecycleEvents.push(alias);
374
310
  map.set(name, alias);
375
311
  }
376
- return (name) => map.get(name);
312
+ return (e) => map.get(e);
377
313
  }
378
314
 
379
- const Pending = new WeakSet();
380
- class Observer {
315
+ const CONTROL = Symbol("control");
316
+ const CREATE = Symbol("start");
317
+ const LOCAL = Symbol("local");
318
+ const STATE = Symbol("state");
319
+ const Pending = new Map();
320
+ function manage(src) {
321
+ return src[CONTROL];
322
+ }
323
+ function set(instruction, label) {
324
+ const name = label || instruction.name || "pending";
325
+ const placeholder = Symbol(`${name} instruction`);
326
+ Pending.set(placeholder, function set(key) {
327
+ let output = instruction.call(this, key, this);
328
+ if (typeof output == "function") {
329
+ const getter = output;
330
+ output = Object.assign(Object.assign({}, getOwnPropertyDescriptor(this.subject, key)), { get() {
331
+ return getter(this[LOCAL]);
332
+ } });
333
+ }
334
+ if (output)
335
+ defineProperty(this.subject, key, output);
336
+ });
337
+ return placeholder;
338
+ }
339
+ class Controller {
381
340
  constructor(subject) {
382
341
  this.subject = subject;
383
342
  this.state = {};
384
- this.listeners = new Set();
343
+ this.frame = new Set();
344
+ this.handles = new Set();
385
345
  this.waiting = [];
386
- implementGetters(this);
387
346
  }
388
- static define(fn) {
389
- Pending.add(fn);
390
- return fn;
347
+ static setup(onto) {
348
+ const create = onto[CREATE];
349
+ const control = new this(onto);
350
+ const after = create && create.call(onto, control);
351
+ defineLazy(onto, CONTROL, () => {
352
+ onto[STATE] = control.state;
353
+ control.start();
354
+ if (after)
355
+ after();
356
+ if (onto.didCreate)
357
+ onto.didCreate();
358
+ return control;
359
+ });
360
+ return control;
361
+ }
362
+ get pending() {
363
+ return this.frame.size > 0;
391
364
  }
392
365
  start() {
393
- for (const [key, desc] of entriesIn(this.subject)) {
394
- const { value, get, enumerable } = desc;
395
- if (Pending.has(value))
396
- value(key, this);
397
- else if (enumerable && !get && (!fn(value) || /^[A-Z]/.test(key)))
398
- this.register(key, value);
366
+ const { subject } = this;
367
+ for (const key in subject) {
368
+ const desc = getOwnPropertyDescriptor(subject, key);
369
+ if (desc && "value" in desc) {
370
+ const { value } = desc;
371
+ const instruction = Pending.get(value);
372
+ if (instruction) {
373
+ Pending.delete(value);
374
+ delete subject[key];
375
+ instruction.call(this, key, this);
376
+ }
377
+ else if (typeof value !== "function" || /^[A-Z]/.test(key))
378
+ this.manage(key, value);
379
+ }
399
380
  }
400
381
  this.emit();
382
+ return this;
401
383
  }
402
- register(key, initial, effect) {
403
- this.state[key] = initial;
404
- this.override(key, {
405
- get: () => this.state[key],
384
+ select(using) {
385
+ const keys = getOwnPropertyNames(this.state);
386
+ return using
387
+ ? selectRecursive(using, keys.concat(lifecycleEvents))
388
+ : keys;
389
+ }
390
+ manage(key, initial, effect) {
391
+ const { state, subject } = this;
392
+ state[key] = initial;
393
+ defineProperty(subject, key, {
394
+ enumerable: true,
395
+ configurable: true,
396
+ get: () => state[key],
406
397
  set: this.setter(key, effect)
407
398
  });
408
399
  }
409
- override(key, desc) {
410
- defineProperty(this.subject, key, Object.assign({ enumerable: true }, desc));
411
- }
412
- setter(key, effect) {
413
- const callback = effect && createEffect(effect);
400
+ setter(key, handler) {
401
+ const { state, subject } = this;
414
402
  return (value) => {
415
- if (this.state[key] == value)
416
- return;
417
- this.state[key] = value;
418
- if (callback)
419
- callback(value, this.subject);
420
- this.update(key);
421
- };
422
- }
423
- watch(target, handler, squash, once) {
424
- const keys = [].concat(target);
425
- const callback = squash
426
- ? handler.bind(this.subject)
427
- : (frame) => {
428
- for (const key of frame)
429
- if (keys.includes(key))
430
- handler.call(this.subject, this.state[key], key);
431
- };
432
- return this.addListener(keys, callback, once);
433
- }
434
- addListener(keys, callback, once) {
435
- const remove = () => { this.listeners.delete(listener); };
436
- const handler = once ? (k) => { remove(); callback(k); } : callback;
437
- const listener = {};
438
- for (const key of keys) {
439
- ensureValue(this.subject, key);
440
- listener[key] = handler;
441
- }
442
- this.listeners.add(listener);
443
- return remove;
444
- }
445
- update(key) {
446
- (this.pending || this.sync())(key);
447
- }
448
- emit(frame) {
449
- const { waiting } = this;
450
- this.waiting = [];
451
- this.pending = undefined;
452
- waiting.forEach(x => x(frame));
453
- }
454
- sync() {
455
- const handled = new Set();
456
- const { isComputed, flushComputed } = computeContext(this, handled);
457
- const include = (key) => {
458
- if (handled.has(key))
403
+ if (state[key] == value)
459
404
  return;
460
- handled.add(key);
461
- for (const subscription of this.listeners)
462
- if (key in subscription) {
463
- const request = subscription[key];
464
- isComputed(request) || this.waiting.push(request);
405
+ if (handler)
406
+ switch (handler.call(subject, value)) {
407
+ case true:
408
+ this.update(key);
409
+ case false:
410
+ return;
465
411
  }
412
+ this.update(key, value);
466
413
  };
467
- const close = () => {
468
- flushComputed();
469
- this.emit(Array.from(handled));
470
- };
471
- setTimeout(close, 0);
472
- return this.pending = include;
473
414
  }
474
- }
475
-
476
- class Controller extends Observer {
477
- constructor() {
478
- super(...arguments);
479
- this.on = (select, listener, squash, once) => {
480
- return this.watch(select, listener, squash, once);
481
- };
482
- this.once = (select, listener, squash) => {
483
- if (listener)
484
- return this.watch(select, listener, squash, true);
485
- else
486
- return new Promise(resolve => {
487
- this.watch(select, resolve, true, true);
488
- });
489
- };
490
- this.effect = (callback, select) => {
491
- let target = this.subject;
492
- const effect = createEffect(callback);
493
- const invoke = debounce(() => effect(target));
494
- if (select)
495
- return this.watch(select, invoke, true);
496
- return this.do(() => {
497
- const sub = new Subscriber(target, invoke);
498
- effect(target = sub.proxy);
499
- return sub.listen();
500
- });
415
+ addListener(batch) {
416
+ this.handles.add(batch);
417
+ return () => {
418
+ this.handles.delete(batch);
501
419
  };
502
- this.import = (from, select) => {
503
- const keys = this.select(select);
504
- for (const key of keys)
505
- if (key in from)
506
- this.subject[key] = from[key];
507
- };
508
- this.export = (select) => {
509
- const keys = this.select(select);
510
- const data = {};
511
- for (const key of keys)
512
- data[key] = this.subject[key];
513
- return data;
514
- };
515
- this.update = (select) => {
516
- for (const key of this.select(select))
517
- super.update(key);
518
- };
519
- this.requestUpdate = (argument) => {
520
- const { pending, waiting } = this;
521
- if (fn(argument))
522
- waiting.push(argument);
523
- else if (!pending === argument)
524
- return Promise.reject(Oops.StrictUpdate());
525
- else if (pending)
526
- return new Promise(cb => waiting.push(cb));
527
- else
528
- return Promise.resolve(false);
529
- };
530
- }
531
- do(fn) {
532
- return fn();
533
- }
534
- select(using) {
535
- const keys = getOwnPropertyNames(this.state);
536
- if (!using)
537
- return keys;
538
- if (typeof using == "string")
539
- return [using];
540
- if (fn(using))
541
- return selectRecursive(using, keys.concat(lifecycleEvents));
542
- return Array.from(using);
543
420
  }
544
- watch(target, handler, squash, once) {
545
- return this.do(() => super.watch(this.select(target), handler, squash, once));
421
+ include(to) {
422
+ if (capture(this, to))
423
+ return;
424
+ else
425
+ this.waiting.push(to);
546
426
  }
547
- }
548
-
549
- const PendingContext = new Set();
550
- const ContextWasUsed = new WeakMap();
551
- function setPeer(type, required) {
552
- return Observer.define((key, { subject }) => {
553
- const Self = subject.constructor.name;
554
- if ("current" in type)
555
- defineLazy(subject, key, () => type.current);
556
- else if ("current" in subject.constructor)
557
- throw Oops.CantAttachGlobal(subject.constructor.name, type.name);
558
- else {
559
- function insert(context) {
560
- const remote = context.get(type);
561
- if (!remote && required)
562
- throw Oops.AmbientRequired(type.name, Self, key);
563
- define(subject, key, remote);
427
+ update(key, value) {
428
+ if (1 in arguments)
429
+ this.state[key] = value;
430
+ if (this.frame.has(key))
431
+ return;
432
+ if (!this.frame.size)
433
+ setTimeout(() => this.emit(), 0);
434
+ this.frame.add(key);
435
+ for (const subscription of this.handles)
436
+ if (key in subscription)
437
+ this.include(subscription[key]);
438
+ }
439
+ emit() {
440
+ flush(this);
441
+ const keys = Array.from(this.frame);
442
+ const handle = new Set(this.waiting.splice(0));
443
+ this.frame.clear();
444
+ handle.forEach(callback => {
445
+ try {
446
+ callback(keys);
564
447
  }
565
- PendingContext.add(insert);
566
- define(subject, key, insert);
567
- }
568
- });
569
- }
570
- function usePeers(subject) {
571
- if (ContextWasUsed.has(subject)) {
572
- if (ContextWasUsed.get(subject))
573
- useLookup();
574
- return;
575
- }
576
- const pending = values(subject)
577
- .filter(x => PendingContext.has(x));
578
- const hasPeers = pending.length > 0;
579
- if (hasPeers) {
580
- const local = useLookup();
581
- for (const init of pending)
582
- init(local);
448
+ catch (e) { }
449
+ });
583
450
  }
584
- ContextWasUsed.set(subject, hasPeers);
451
+ }
452
+ function keys(from, using) {
453
+ if (typeof using == "string")
454
+ return [using];
455
+ if (typeof using == "object")
456
+ return Array.from(using);
457
+ return from.select(using);
585
458
  }
586
459
 
587
- const subscriberEvent = forAlias("element");
588
- const componentEvent = forAlias("component");
589
- class HookSubscriber extends Subscriber {
590
- constructor(subject, callback, tag) {
591
- super(subject, callback);
592
- this.subject = subject;
593
- this.callback = callback;
594
- this.tag = tag;
595
- this.alias = subscriberEvent;
596
- this.isMounted = false;
597
- this.useLifecycle = () => {
598
- this.at(Lifecycle.WILL_RENDER);
599
- this.at(this.isMounted
600
- ? Lifecycle.WILL_UPDATE
601
- : Lifecycle.WILL_MOUNT);
602
- react.useLayoutEffect(() => {
603
- this.listen();
604
- this.isMounted = true;
605
- this.at(Lifecycle.DID_MOUNT);
606
- return () => {
607
- this.at(Lifecycle.WILL_UNMOUNT);
608
- this.release();
609
- };
610
- });
611
- };
612
- }
613
- at(name) {
614
- const also = this.alias(name);
615
- for (const key of [name, also]) {
616
- const on = this.subject;
617
- const handle = on[key];
618
- handle && handle.call(on, this.tag);
619
- this.parent.update(key);
620
- }
621
- }
622
- }
623
- class ElementSubscriber extends HookSubscriber {
624
- focus(key, expect) {
625
- const source = this.subject;
626
- if (fn(key)) {
627
- const available = {};
628
- for (const key in source)
629
- available[key] = key;
630
- key = key(available);
460
+ const attach = (from, name) => set(function attach(key) {
461
+ const proxyCache = new WeakMap();
462
+ const getCurrent = from.call(this, key);
463
+ function subscribe(sub) {
464
+ let child;
465
+ function start() {
466
+ const instance = getCurrent();
467
+ if (instance) {
468
+ child = new Subscriber(instance, sub.onUpdate);
469
+ if (sub.active)
470
+ child.commit();
471
+ sub.dependant.add(child);
472
+ proxyCache.set(sub, child.proxy);
473
+ }
631
474
  }
632
- this.recursive(key, () => {
633
- let value = source[key];
634
- if (value instanceof Model) {
635
- const child = new Subscriber(value, this.callback);
636
- this.proxy = child.proxy;
637
- return child;
475
+ function refresh() {
476
+ if (child) {
477
+ child.release();
478
+ sub.dependant.delete(child);
479
+ proxyCache.set(sub, undefined);
480
+ child = undefined;
638
481
  }
639
- if (value === undefined && expect)
640
- throw Oops.HasPropertyUndefined(source.constructor.name, key);
641
- this.proxy = value;
642
- });
643
- }
644
- }
645
- class ComponentSubscriber extends HookSubscriber {
646
- constructor() {
647
- super(...arguments);
648
- this.alias = componentEvent;
649
- }
650
- release() {
651
- super.release();
652
- this.subject.destroy();
653
- }
654
- }
655
- function useRefresh(init) {
482
+ start();
483
+ sub.onUpdate();
484
+ }
485
+ start();
486
+ sub.follow(key, refresh);
487
+ }
488
+ return (local) => {
489
+ if (!local)
490
+ return getCurrent();
491
+ if (!proxyCache.has(local))
492
+ subscribe(local);
493
+ return proxyCache.get(local);
494
+ };
495
+ }, name || from.name);
496
+
497
+ const Oops$7 = issues({
498
+ HasPropertyUndefined: (control, property) => `${control}.${property} is marked as required for this render.`
499
+ });
500
+ const useElementLifecycle = lifecycle("element");
501
+ function use$1(init) {
656
502
  const [state, update] = react.useState(() => [
657
503
  init(() => update(state.concat()))
658
504
  ]);
659
505
  return state[0];
660
506
  }
661
- function useLazy(Type, args) {
662
- const instance = react.useMemo(() => Type.create(...args), []);
663
- react.useEffect(() => () => instance.destroy(), []);
664
- return instance;
665
- }
666
- function usePassive(target, select) {
667
- const instance = target.find(!!select);
668
- if (!instance)
669
- return;
670
- if (fn(select))
671
- return select(instance);
672
- if (typeof select == "string")
673
- return instance[select];
507
+ function useLazy(Type, args, callback) {
508
+ const instance = react.useMemo(() => {
509
+ const instance = Type.create(...args || []);
510
+ if (callback)
511
+ callback(instance);
512
+ return instance;
513
+ }, []);
514
+ react.useLayoutEffect(() => () => instance.destroy(), []);
674
515
  return instance;
675
516
  }
676
- function useWatcher(target, path, expected) {
677
- const hook = useRefresh(trigger => {
678
- const sub = new ElementSubscriber(target, trigger);
679
- if (path)
680
- sub.focus(path, expected);
517
+ function useWatcher(target, focus, expected) {
518
+ const hook = use$1(refresh => {
519
+ const sub = new Subscriber(target, refresh);
520
+ if (focus) {
521
+ const src = sub.proxy;
522
+ const key = keys(sub.parent, focus)[0];
523
+ defineProperty(sub, "proxy", {
524
+ get() {
525
+ if (src[key] === undefined && expected)
526
+ throw Oops$7.HasPropertyUndefined(target, key);
527
+ return src[key];
528
+ }
529
+ });
530
+ }
681
531
  return sub;
682
532
  });
683
- react.useEffect(hook.listen, []);
533
+ react.useLayoutEffect(() => hook.commit(), []);
684
534
  return hook.proxy;
685
535
  }
686
536
  function useSubscriber(target, tag) {
687
- const hook = useRefresh(trigger => {
688
- const key = fn(tag) ? tag(target) : tag || 0;
689
- return new HookSubscriber(target, trigger, key);
690
- });
691
- hook.useLifecycle();
537
+ const hook = use$1(refresh => new Subscriber(target, refresh));
538
+ useElementLifecycle(hook, tag || 0);
692
539
  return hook.proxy;
693
540
  }
694
541
  function useModel(Type, args, callback) {
695
- const hook = useRefresh(trigger => {
696
- const instance = Type.create(...args);
697
- const sub = new ComponentSubscriber(instance, trigger);
542
+ const hook = use$1(refresh => {
543
+ const instance = new Type(...args || []);
544
+ const control = manage(instance);
545
+ const sub = new Subscriber(control, refresh);
546
+ const release = control.addListener({
547
+ [Lifecycle.WILL_UNMOUNT]() {
548
+ instance.destroy();
549
+ release();
550
+ }
551
+ });
698
552
  if (callback)
699
553
  callback(instance);
700
554
  return sub;
701
555
  });
702
- usePeers(hook.subject);
703
- hook.useLifecycle();
704
556
  return hook.proxy;
705
557
  }
706
558
 
707
- const CONTROL = Symbol("controller");
708
- class Model {
559
+ class Lookup {
709
560
  constructor() {
710
- const control = new Controller(this);
711
- for (const [key, value] of entries(control))
712
- if (fn(value))
713
- define(this, key, value);
714
- control.do = (fn) => {
715
- let release;
716
- this.requestUpdate(() => release = fn());
717
- return () => release();
561
+ this.table = new Map();
562
+ }
563
+ get local() {
564
+ return getOwnPropertySymbols(this).map((symbol) => this[symbol]);
565
+ }
566
+ key(T) {
567
+ let key = this.table.get(T);
568
+ if (!key) {
569
+ key = Symbol(T.name);
570
+ this.table.set(T, key);
571
+ }
572
+ return key;
573
+ }
574
+ get(T) {
575
+ return this[this.key(T)];
576
+ }
577
+ push(I) {
578
+ const next = create(this);
579
+ if (I instanceof Model || typeof I == "function")
580
+ next.register(I);
581
+ else
582
+ for (const i of values(I))
583
+ next.register(i);
584
+ return next;
585
+ }
586
+ register(I) {
587
+ let writable = true;
588
+ let T;
589
+ if (I instanceof Model) {
590
+ T = I.constructor;
591
+ writable = false;
592
+ }
593
+ else {
594
+ T = I;
595
+ I = I.create();
596
+ }
597
+ do {
598
+ defineProperty(this, this.key(T), {
599
+ value: I, writable
600
+ });
601
+ T = getPrototypeOf(T);
602
+ } while (T !== Model);
603
+ return I;
604
+ }
605
+ pop() {
606
+ for (const key of getOwnPropertySymbols(this)) {
607
+ const entry = getOwnPropertyDescriptor(this, key);
608
+ if (entry.writable)
609
+ entry.value.destroy();
610
+ }
611
+ }
612
+ }
613
+
614
+ const Oops$6 = issues({
615
+ NoProviderType: () => `Provider 'of' prop must be Model, typeof Model or a collection of them.`,
616
+ NothingInContext: (name) => `Couldn't find ${name} in context; did you forget to use a Provider?`,
617
+ BadConsumerProps: () => `Provider expects either a render function, 'get' or 'has' props.`
618
+ });
619
+ const LookupContext = react.createContext(new Lookup());
620
+ const useLookup = () => react.useContext(LookupContext);
621
+ function useFromContext(Type, strict) {
622
+ const instance = useLookup().get(Type);
623
+ if (!instance && strict)
624
+ throw Oops$6.NothingInContext(Type.name);
625
+ return instance;
626
+ }
627
+ function Consumer(props) {
628
+ const { get, has, children, of: Control } = props;
629
+ if (typeof children == "function")
630
+ return children(Control.tap());
631
+ const callback = has || get;
632
+ if (typeof callback == "function")
633
+ callback(Control.get(!!has));
634
+ else
635
+ throw Oops$6.BadConsumerProps();
636
+ return null;
637
+ }
638
+ function useNewContext(from, inject) {
639
+ return react.useMemo(() => {
640
+ if (!inject)
641
+ throw Oops$6.NoProviderType();
642
+ const context = from.push(inject);
643
+ for (const instance of context.local)
644
+ for (const apply of getPending(instance))
645
+ apply(context);
646
+ return context;
647
+ }, []);
648
+ }
649
+ function useAppliedProps(within, props) {
650
+ const update = react.useMemo(() => {
651
+ const targets = within.local;
652
+ return function integrate(props) {
653
+ for (const [key, value] of entries(props))
654
+ if (key != "of" && key != "children")
655
+ for (const into of targets)
656
+ if (key in into)
657
+ into[key] = value;
718
658
  };
719
- defineLazy(this, CONTROL, () => {
720
- delete control.do;
721
- control.start();
722
- if (this.didCreate)
723
- this.didCreate();
724
- return control;
725
- });
659
+ }, []);
660
+ update(props);
661
+ }
662
+ function RenderFunction(props) {
663
+ const hook = use$1(refresh => {
664
+ const targets = props.context.local;
665
+ if (targets.length == 1)
666
+ return new Subscriber(targets[0], refresh);
667
+ return {};
668
+ });
669
+ if (hook.commit)
670
+ react.useLayoutEffect(() => hook.commit(), []);
671
+ return props.render(hook.proxy);
672
+ }
673
+ function Provider(props) {
674
+ const render = props.children;
675
+ const current = useLookup();
676
+ const context = useNewContext(current, props.of);
677
+ useAppliedProps(context, props);
678
+ react.useLayoutEffect(() => () => context.pop(), []);
679
+ return react.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
680
+ ? react.createElement(RenderFunction, { context, render })
681
+ : render);
682
+ }
683
+
684
+ const Oops$5 = issues({
685
+ CantAttachGlobal: (parent, child) => `Singleton '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
686
+ AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
687
+ });
688
+ const PendingContext = new WeakMap();
689
+ const ContextWasUsed = new WeakMap();
690
+ const tap = (type, argument) => attach(function tap(key) {
691
+ return pendingAccess(this.subject, type, key, argument);
692
+ });
693
+ function pendingAccess(from, type, key, argument) {
694
+ if ("current" in type)
695
+ return () => type.current;
696
+ if ("current" in from.constructor)
697
+ throw Oops$5.CantAttachGlobal(from, type.name);
698
+ let instance;
699
+ getPending(from).push(context => {
700
+ instance = context.get(type);
701
+ if (typeof argument == "function") {
702
+ if (argument(instance) === false)
703
+ instance = undefined;
704
+ }
705
+ else if (!instance && argument)
706
+ throw Oops$5.AmbientRequired(type.name, from, key);
707
+ });
708
+ return () => instance;
709
+ }
710
+ function usePeerContext(subject) {
711
+ if (ContextWasUsed.has(subject)) {
712
+ if (ContextWasUsed.get(subject))
713
+ useLookup();
714
+ return;
715
+ }
716
+ const pending = PendingContext.get(subject);
717
+ if (pending) {
718
+ const local = useLookup();
719
+ for (const init of pending)
720
+ init(local);
721
+ PendingContext.delete(subject);
722
+ }
723
+ ContextWasUsed.set(subject, !!pending);
724
+ }
725
+ function getPending(subject) {
726
+ let pending = PendingContext.get(subject);
727
+ if (!pending)
728
+ PendingContext.set(subject, pending = []);
729
+ return pending;
730
+ }
731
+
732
+ const Oops$4 = issues({
733
+ StrictUpdate: (expected) => `Strict update() did ${expected ? "not " : ""}find pending updates.`,
734
+ NoChaining: () => `Then called with undefined; update promise will never catch nor supports chaining.`
735
+ });
736
+ class State {
737
+ constructor() {
738
+ Controller.setup(this);
726
739
  define(this, "get", this);
727
740
  define(this, "set", this);
728
741
  }
729
- get bind() {
730
- const proxy = createBindings(this);
731
- define(this, "bind", proxy);
732
- return proxy;
742
+ import(from, subset) {
743
+ for (const key of keys(manage(this), subset))
744
+ if (key in from)
745
+ this[key] = from[key];
746
+ }
747
+ export(subset) {
748
+ const control = manage(this);
749
+ const output = {};
750
+ for (const key of keys(control, subset))
751
+ output[key] = control.state[key];
752
+ return output;
753
+ }
754
+ update(arg, tag) {
755
+ const control = manage(this);
756
+ if (typeof arg == "function")
757
+ arg = select(this, arg);
758
+ if (typeof arg == "boolean") {
759
+ if (!control.pending === arg)
760
+ return Promise.reject(Oops$4.StrictUpdate(arg));
761
+ }
762
+ else if (arg) {
763
+ control.update(arg);
764
+ if (1 in arguments && arg in this) {
765
+ const method = this[arg];
766
+ if (typeof method == "function")
767
+ if (typeof tag != "boolean")
768
+ method.call(this, tag);
769
+ else if (tag)
770
+ method.call(this);
771
+ }
772
+ }
773
+ return {
774
+ then(callback) {
775
+ if (callback)
776
+ if (control.pending)
777
+ control.include(callback);
778
+ else
779
+ callback(false);
780
+ else
781
+ throw Oops$4.NoChaining();
782
+ }
783
+ };
733
784
  }
734
- tap(path, expect) {
735
- return useWatcher(this, path, expect);
785
+ destroy() {
786
+ this.update("willDestroy", true);
736
787
  }
737
- tag(id) {
738
- return useSubscriber(this, id);
788
+ toString() {
789
+ return this.constructor.name;
739
790
  }
740
- destroy() {
741
- if (this.willDestroy)
742
- this.willDestroy();
791
+ tap(path, expect) {
792
+ const proxy = useWatcher(this, path, expect);
793
+ this.update("willRender", true);
794
+ return proxy;
743
795
  }
744
796
  static create(...args) {
745
797
  const instance = new this(...args);
746
- // evaluating CONTROL prevents compiler from dropping it.
747
- // Triggering get required to properly initialize state.
748
- return instance[CONTROL] && instance;
798
+ manage(instance);
799
+ return instance;
749
800
  }
750
- static use(...args) {
751
- return useModel(this, args);
801
+ static new(args, callback) {
802
+ const instance = useLazy(this, args, callback);
803
+ instance.update("willRender", true);
804
+ return instance;
805
+ }
806
+ static use(args, callback) {
807
+ return useModel(this, args, callback);
752
808
  }
753
809
  static uses(props, only) {
754
- return useModel(this, [], instance => {
810
+ return this.use([], instance => {
755
811
  instance.import(props, only);
756
812
  });
757
813
  }
758
814
  static using(props, only) {
759
- const instance = useModel(this, []);
815
+ const instance = this.use([]);
760
816
  instance.import(props, only);
761
817
  return instance;
762
818
  }
763
- static new(...args) {
764
- return useLazy(this, args);
819
+ static find(strict) {
820
+ return useFromContext(this, strict);
765
821
  }
766
822
  static get(key) {
767
- return usePassive(this, key);
823
+ const instance = this.find(!!key);
824
+ return (typeof key == "function" ?
825
+ key(instance) :
826
+ typeof key == "string" ?
827
+ instance[key] :
828
+ instance);
768
829
  }
769
830
  static tap(key, expect) {
770
- return useWatcher(this.find(true), key, expect);
771
- }
772
- static tag(id) {
773
- return useSubscriber(this.find(true), id);
774
- }
775
- static meta(path) {
776
- return useWatcher(this, path);
777
- }
778
- static find(strict) {
779
- return useLookup().get(this, strict);
831
+ return this.find(true).tap(key, expect);
780
832
  }
781
833
  static isTypeof(maybe) {
782
- return (fn(maybe) &&
834
+ return (typeof maybe == "function" &&
783
835
  maybe.prototype instanceof this);
784
836
  }
785
- static get inherits() {
786
- const I = getPrototypeOf(this);
787
- if (I !== Model)
788
- return I;
789
- }
790
837
  }
791
- defineLazy(Model, CONTROL, function () {
792
- const dispatch = new Controller(this);
793
- dispatch.start();
794
- return dispatch;
795
- });
838
+ State.CONTROL = CONTROL;
839
+ State.STATE = STATE;
840
+ State.INIT = CREATE;
841
+ State.LOCAL = LOCAL;
796
842
 
797
- const ComputedInfo = new WeakMap();
798
- const ComputedFor = new WeakMap();
799
- const ComputedInit = new WeakSet();
800
- function metaData(x, set) {
801
- if (set)
802
- ComputedInfo.set(x, set);
803
- else
804
- return ComputedInfo.get(x);
805
- }
806
- function implementGetters(on) {
807
- let scan = on.subject;
808
- while (scan !== Model && scan.constructor !== Model) {
809
- for (let [key, { get, set }] of entriesIn(scan))
810
- if (get)
811
- prepareComputed(on, key, get, set);
812
- scan = getPrototypeOf(scan);
843
+ const useComponentLifecycle = lifecycle("component");
844
+ class Model extends State {
845
+ [CREATE](using) {
846
+ defer(using, "on");
847
+ defer(using, "effect");
848
+ return () => {
849
+ delete this.on;
850
+ delete this.effect;
851
+ };
813
852
  }
814
- }
815
- function prepareComputed(on, key, getter, setter) {
816
- let sub;
817
- let defined = ComputedFor.get(on);
818
- if (!defined)
819
- ComputedFor.set(on, defined = new Map());
820
- if (defined.has(key))
821
- return;
822
- const { state, subject } = on;
823
- const info = {
824
- key,
825
- parent: on,
826
- priority: 1
827
- };
828
- defined.set(key, info);
829
- const update = (initial) => {
830
- try {
831
- const value = getter.call(sub.proxy, sub.proxy);
832
- if (state[key] == value)
833
- return;
834
- if (!initial)
835
- on.update(key);
836
- return state[key] = value;
837
- }
838
- catch (err) {
839
- Oops.ComputeFailed(subject.constructor.name, key, false).warn();
840
- throw err;
841
- }
842
- };
843
- const create = (early) => {
844
- sub = new Subscriber(subject, debounce(update), info);
845
- defineProperty(state, key, {
846
- value: undefined,
847
- writable: true
848
- });
849
- on.override(key, {
850
- get: () => state[key],
851
- set: setter
852
- });
853
- try {
854
- return update(true);
855
- }
856
- catch (e) {
857
- if (early)
858
- Oops.ComputedEarly(key).warn();
859
- throw e;
860
- }
861
- finally {
862
- sub.listen();
863
- for (const key in sub.following) {
864
- const compute = defined.get(key);
865
- if (compute && compute.priority >= info.priority)
866
- info.priority = compute.priority + 1;
867
- }
868
- }
869
- };
870
- alias(update, `try ${key}`);
871
- alias(create, `new ${key}`);
872
- alias(getter, `run ${key}`);
873
- if (!setter)
874
- setter = Oops.AssignToGetter(key).warn;
875
- for (const sub of on.listeners)
876
- if (key in sub) {
877
- on.waiting.push(() => create());
878
- return;
879
- }
880
- ComputedInit.add(create);
881
- defineProperty(state, key, {
882
- get: create,
883
- configurable: true,
884
- enumerable: true
885
- });
886
- on.override(key, {
887
- get: create,
888
- set: setter,
889
- configurable: true
890
- });
891
- }
892
- function ensureValue(from, key) {
893
- const desc = getOwnPropertyDescriptor(from, key);
894
- const getter = desc && desc.get;
895
- if (ComputedInit.has(getter))
896
- getter(true);
897
- }
898
- function computeContext(parent, handled) {
899
- const pending = [];
900
- function isComputed(request) {
901
- const compute = metaData(request);
902
- if (!compute)
903
- return false;
904
- if (compute.parent !== parent)
905
- request();
853
+ on(subset, handler, squash, once) {
854
+ const control = manage(this);
855
+ const set = keys(control, subset);
856
+ const batch = {};
857
+ const remove = control.addListener(batch);
858
+ const callback = squash
859
+ ? handler.bind(this)
860
+ : (frame) => {
861
+ for (const key of frame)
862
+ if (set.includes(key))
863
+ handler.call(this, control.state[key], key);
864
+ };
865
+ const handle = once
866
+ ? (k) => { remove(); callback(k); }
867
+ : callback;
868
+ for (const key of set)
869
+ batch[key] = handle;
870
+ ensure(control, set);
871
+ return remove;
872
+ }
873
+ once(select, callback, squash) {
874
+ if (callback)
875
+ return this.on(select, callback, squash, true);
906
876
  else
907
- insertAfter(pending, request, sib => compute.priority > metaData(sib).priority);
908
- return true;
877
+ return new Promise(resolve => {
878
+ this.on(select, resolve, true, true);
879
+ });
909
880
  }
910
- function flushComputed() {
911
- while (pending.length) {
912
- const compute = pending.shift();
913
- const { key } = metaData(compute);
914
- if (!handled.has(key))
915
- compute();
881
+ effect(callback, select) {
882
+ const control = manage(this);
883
+ let target = this;
884
+ const effect = createEffect(callback);
885
+ const invoke = () => effect.call(target, target);
886
+ if (select) {
887
+ invoke();
888
+ return this.on(select, invoke, true);
889
+ }
890
+ else {
891
+ const sub = new Subscriber(control, invoke);
892
+ target = sub.proxy;
893
+ invoke();
894
+ return sub.commit();
916
895
  }
917
896
  }
918
- return {
919
- isComputed,
920
- flushComputed
897
+ tag(id) {
898
+ return useSubscriber(this, id);
899
+ }
900
+ static use(args, callback) {
901
+ const instance = useModel(this, args, callback);
902
+ useComponentLifecycle(instance[LOCAL]);
903
+ usePeerContext(instance.get);
904
+ return instance;
905
+ }
906
+ static tag(id) {
907
+ return this.find(true).tag(id);
908
+ }
909
+ static meta(path) {
910
+ return useWatcher(this, path);
911
+ }
912
+ }
913
+ defineLazy(Model, CONTROL, function () {
914
+ return new Controller(this).start();
915
+ });
916
+ function defer(on, method) {
917
+ const { subject, waiting } = on;
918
+ const real = subject[method];
919
+ subject[method] = (...args) => {
920
+ let done;
921
+ waiting.push(() => {
922
+ done = real.apply(subject, args);
923
+ });
924
+ return () => done();
921
925
  };
922
926
  }
923
927
 
924
- const ParentRelationship = new WeakMap();
925
- function setChild(Peer, callback) {
926
- return Observer.define((key, on) => {
927
- const instance = new Peer();
928
- on.register(key, instance);
929
- ParentRelationship.set(instance, on.subject);
930
- // evaluating CONTROL to force inclusion by compiler
931
- if (instance[CONTROL] && callback)
932
- callback(instance);
933
- });
928
+ const Parent = new WeakMap();
929
+ const Oops$3 = issues({
930
+ ParentRequired: (expects, child) => `New ${child} created standalone but requires parent of type ${expects}. Did you remember to create via use(${child})?`,
931
+ UnexpectedParent: (expects, child, got) => `New ${child} created as child of ${got}, but must be instanceof ${expects}.`,
932
+ UndefinedNotAllowed: (key) => `Child property ${key} may not be undefined.`,
933
+ BadArgument: (type) => `Instruction \`use\` cannot accept argument type of ${type}.`
934
+ });
935
+ function bootstrap(object) {
936
+ const breakdown = getOwnPropertyDescriptors(object);
937
+ const control = new Model();
938
+ for (const key in breakdown)
939
+ defineProperty(control, key, breakdown[key]);
940
+ return control;
934
941
  }
935
- function setParent(Expects, required) {
936
- return Observer.define((key, { subject }) => {
937
- const expectsType = Expects.name;
938
- const onType = subject.constructor.name;
939
- const parent = ParentRelationship.get(subject);
940
- if (!parent) {
941
- if (required)
942
- throw Oops.ParentRequired(expectsType, onType);
943
- }
944
- else if (!(parent instanceof Expects)) {
945
- const gotType = parent.constructor.name;
946
- throw Oops.UnexpectedParent(expectsType, onType, gotType);
942
+ const use = (input, argument) => attach(function use(key) {
943
+ let instance;
944
+ const update = (next) => {
945
+ instance =
946
+ next instanceof Model ? next :
947
+ next && bootstrap(next);
948
+ if (instance) {
949
+ Parent.set(instance, this.subject);
950
+ manage(instance);
947
951
  }
948
- define(subject, key, parent);
949
- });
950
- }
951
- function setRefMediator(effect) {
952
- return Observer.define((key, on) => {
953
- const refObjectFunction = on.setter(key, effect);
952
+ if (typeof argument == "function")
953
+ argument(instance);
954
+ else if (!instance && argument !== false)
955
+ throw Oops$3.UndefinedNotAllowed(key);
956
+ };
957
+ if (input) {
958
+ instance =
959
+ Model.isTypeof(input) ? new input() :
960
+ input instanceof Model ? input :
961
+ typeof input === "function" ? input() :
962
+ typeof input === "object" ? bootstrap(input) :
963
+ Oops$3.BadArgument(typeof input).throw();
964
+ if (instance)
965
+ update(instance);
966
+ }
967
+ else
968
+ argument = false;
969
+ this.manage(key, instance, update);
970
+ return () => instance;
971
+ });
972
+ const parent = (Expects, required) => set(function parent() {
973
+ const child = this.subject;
974
+ const expected = Expects.name;
975
+ const value = Parent.get(this.subject);
976
+ if (!value) {
977
+ if (required)
978
+ throw Oops$3.ParentRequired(expected, child);
979
+ }
980
+ else if (!(value instanceof Expects))
981
+ throw Oops$3.UnexpectedParent(expected, child, value);
982
+ return { value };
983
+ });
984
+
985
+ const Oops$2 = issues({
986
+ DuplicateAction: (key) => `Invoked action ${key} but one is already active.`,
987
+ BadComputedSource: (model, property, got) => `Bad from-instruction provided to ${model}.${property}. Expects an arrow-function or a Model as source. Got ${got}.`,
988
+ PeerNotAllowed: (model, property) => `Attempted to use an instruction result (probably use or tap) as computed source for ${model}.${property}. This is not possible.`
989
+ });
990
+ function ref(cb) {
991
+ return set(function ref(key) {
992
+ const refObjectFunction = this.setter(key, cb && createValueEffect(cb));
954
993
  defineProperty(refObjectFunction, "current", {
955
994
  set: refObjectFunction,
956
- get: () => on.state[key]
995
+ get: () => this.state[key]
957
996
  });
958
- on.override(key, {
997
+ return {
959
998
  value: refObjectFunction
960
- });
999
+ };
961
1000
  });
962
1001
  }
963
- function setEffect(value, effect) {
964
- return Observer.define((key, on) => {
965
- if (!effect) {
966
- effect = value;
967
- value = undefined;
968
- }
969
- on.register(key, value, effect);
1002
+ function on(initial, cb) {
1003
+ return set(function on(key) {
1004
+ this.manage(key, initial, cb && createValueEffect(cb));
970
1005
  });
971
1006
  }
972
- function setMemo(factory, defer) {
973
- return Observer.define((key, { subject }) => {
974
- const get = () => factory.call(subject);
1007
+ function memo(factory, defer) {
1008
+ return set(function memo(key) {
1009
+ const source = this.subject;
1010
+ const get = () => factory.call(source);
975
1011
  if (defer)
976
- defineLazy(subject, key, get);
1012
+ defineLazy(source, key, get);
977
1013
  else
978
- define(subject, key, get());
1014
+ define(source, key, get());
979
1015
  });
980
1016
  }
981
- function setIgnored(value) {
982
- return Observer.define((key, on) => {
983
- const real = on.subject;
984
- real[key] = value;
985
- define(on.state, key, {
986
- get: () => real[key]
1017
+ function lazy(value) {
1018
+ return set(function lazy(key) {
1019
+ const source = this.subject;
1020
+ source[key] = value;
1021
+ defineProperty(this.state, key, {
1022
+ get: () => source[key]
987
1023
  });
988
1024
  });
989
1025
  }
990
- function setAction(action) {
991
- return Observer.define((key, on) => {
1026
+ function act(task) {
1027
+ return set(function act(key) {
992
1028
  let pending = false;
993
- function invoke(...args) {
1029
+ const invoke = (...args) => {
994
1030
  if (pending)
995
- return Promise.reject(Oops.DuplicateAction(key));
1031
+ return Promise.reject(Oops$2.DuplicateAction(key));
996
1032
  pending = true;
997
- on.update(key);
998
- return action
999
- .apply(on.subject, args)
1000
- .finally(() => {
1033
+ this.update(key);
1034
+ return new Promise(res => {
1035
+ res(task.apply(this.subject, args));
1036
+ }).finally(() => {
1001
1037
  pending = false;
1002
- on.update(key);
1038
+ this.update(key);
1003
1039
  });
1004
- }
1005
- alias(invoke, `run ${key}`);
1040
+ };
1041
+ setAlias(invoke, `run ${key}`);
1006
1042
  defineProperty(invoke, "active", {
1007
1043
  get: () => pending
1008
1044
  });
1009
- on.override(key, {
1010
- get: () => invoke,
1011
- set: () => {
1012
- throw Oops.SetActionProperty(key);
1013
- }
1045
+ return {
1046
+ value: invoke,
1047
+ writable: false
1048
+ };
1049
+ });
1050
+ }
1051
+ function from(source, setter, getter) {
1052
+ return set(function from(key) {
1053
+ const { subject } = this;
1054
+ let getSource;
1055
+ // Easy mistake, using a peer, will always be unresolved.
1056
+ if (typeof source == "symbol")
1057
+ throw Oops$2.PeerNotAllowed(subject, key);
1058
+ // replace source controller in-case different
1059
+ if (typeof source == "object")
1060
+ getSource = () => manage(source);
1061
+ // specifically an arrow function (getter factory)
1062
+ else if (!source.prototype) {
1063
+ setter = source.call(subject, key);
1064
+ getSource = () => this;
1065
+ }
1066
+ // is a peer Model (constructor)
1067
+ else if (Model.isTypeof(source))
1068
+ getSource = pendingAccess(subject, source, key, true);
1069
+ // Regular function is to ambiguous so not allowed.
1070
+ else
1071
+ throw Oops$2.BadComputedSource(subject, key, source);
1072
+ prepare(this, key, getSource, setter, getter);
1073
+ });
1074
+ }
1075
+
1076
+ const Oops$1 = issues({
1077
+ ValueNotReady: (model, key) => `Value ${model}.${key} value is not yet available.`
1078
+ });
1079
+ /**
1080
+ * Create suspense promise also interpretable as an error.
1081
+ * React will handle it but other contexts probably won't.
1082
+ */
1083
+ function suspense(model, key) {
1084
+ const { message, stack } = Oops$1.ValueNotReady(model, key);
1085
+ const promise = new Promise(resolve => {
1086
+ const release = model.on(key, (value) => {
1087
+ if (value !== undefined)
1088
+ release(), resolve();
1014
1089
  });
1015
1090
  });
1091
+ return Object.assign(promise, { message, stack });
1016
1092
  }
1017
- function setComputed(fn) {
1018
- return Observer.define((key, on) => {
1019
- prepareComputed(on, key, fn);
1093
+ function pending(source, compute) {
1094
+ if (typeof source == "function" && !source.prototype)
1095
+ return suspendForAsync(source);
1096
+ else
1097
+ return from(source, compute, function getter(value, key) {
1098
+ if (value === undefined)
1099
+ throw suspense(this, key);
1100
+ return value;
1101
+ });
1102
+ }
1103
+ function suspendForAsync(source) {
1104
+ return set(function suspense(key) {
1105
+ let waiting = true;
1106
+ let output;
1107
+ let error;
1108
+ return () => {
1109
+ if (waiting) {
1110
+ const issue = Oops$1.ValueNotReady(this.subject, key);
1111
+ const promise = source
1112
+ .call(this.subject, key)
1113
+ .catch(err => error = err)
1114
+ .then((value) => output = value)
1115
+ .finally(() => waiting = false);
1116
+ throw Object.assign(promise, {
1117
+ message: issue.message,
1118
+ stack: issue.stack
1119
+ });
1120
+ }
1121
+ if (error)
1122
+ throw error;
1123
+ return output;
1124
+ };
1020
1125
  });
1021
1126
  }
1022
1127
 
1128
+ const Oops = issues({
1129
+ GlobalDoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.create() before attempting to access, or consider using ${name}.use() instead.`,
1130
+ GlobalExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1131
+ });
1132
+ const Active = new WeakMap();
1023
1133
  class Singleton extends Model {
1134
+ static get current() {
1135
+ return Active.get(this);
1136
+ }
1024
1137
  static create(...args) {
1025
1138
  const Type = this;
1026
- let instance = Type.current;
1027
- if (instance)
1139
+ if (Active.has(Type))
1028
1140
  throw Oops.GlobalExists(this.name);
1029
- instance = super.create(...args);
1030
- return Type.current = instance;
1141
+ let instance = super.create(...args);
1142
+ Active.set(Type, instance);
1143
+ return instance;
1144
+ }
1145
+ static update(updates) {
1146
+ const instance = this.current || this.create();
1147
+ instance.import(updates);
1148
+ return instance.update();
1031
1149
  }
1032
- static find() {
1033
- const { current } = this;
1034
- if (!current)
1150
+ static is() {
1151
+ return this.find(true);
1152
+ }
1153
+ static find(strict) {
1154
+ const instance = this.current;
1155
+ if (!instance && strict)
1035
1156
  throw Oops.GlobalDoesNotExist(this.name);
1036
- else
1037
- return current;
1157
+ return instance;
1158
+ }
1159
+ static reset() {
1160
+ if (this.current)
1161
+ this.current.destroy();
1038
1162
  }
1039
1163
  destroy() {
1040
1164
  super.destroy();
1041
- const meta = this.constructor;
1042
- if (this === meta.current)
1043
- meta.current = undefined;
1044
- else
1045
- Oops.DestroyNotPossible(meta.name).warn();
1165
+ Active.delete(this.constructor);
1046
1166
  }
1047
- }
1048
- Singleton.current = undefined;
1167
+ }
1049
1168
 
1050
1169
  exports.Consumer = Consumer;
1051
1170
  exports.Model = Model;
1052
1171
  exports.Provider = Provider;
1053
1172
  exports.Singleton = Singleton;
1054
- exports.act = setAction;
1055
- exports.default = Model;
1056
- exports.from = setComputed;
1057
- exports.lazy = setIgnored;
1058
- exports.memo = setMemo;
1059
- exports.on = setEffect;
1060
- exports.parent = setParent;
1061
- exports.ref = setRefMediator;
1062
- exports.tap = setPeer;
1063
- exports.use = setChild;
1173
+ exports.State = State;
1174
+ exports.act = act;
1175
+ exports.attach = attach;
1176
+ exports["default"] = Model;
1177
+ exports.from = from;
1178
+ exports.lazy = lazy;
1179
+ exports.memo = memo;
1180
+ exports.on = on;
1181
+ exports.parent = parent;
1182
+ exports.pending = pending;
1183
+ exports.ref = ref;
1184
+ exports.set = set;
1185
+ exports.tap = tap;
1186
+ exports.use = use;