@expressive/mvc 0.35.5 → 0.36.0

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/dist/index.d.ts CHANGED
@@ -6,21 +6,21 @@ declare namespace Model {
6
6
  /** Subset of `keyof T` which are not methods or defined by base Model U. **/
7
7
  type Field<T> = Exclude<keyof T, keyof Model>;
8
8
  type Key<T> = Field<T> | (string & {}) | number | symbol;
9
- /** Actual value stored in state. */
10
- type Actual<R> = R extends Ref<infer T> ? T : R extends Model ? State<R> : R;
11
9
  type Value<T extends Model, K extends Key<T>> = K extends keyof T ? T[K] extends Ref<infer V> ? V : T[K] : unknown;
12
10
  type OnUpdate<T extends Model, K extends Key<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
11
+ /** Export/Import compatible value for a given property in a Model. */
12
+ type Export<R> = R extends Ref<infer T> ? T : R extends Model ? State<R> : R;
13
13
  /**
14
14
  * Values from current state of given controller.
15
15
  * Differs from `Values` as values here will drill
16
16
  * into "real" values held by exotics like ref.
17
17
  */
18
18
  type State<T> = {
19
- [P in Field<T>]: Actual<T[P]>;
19
+ [P in Field<T>]: Export<T[P]>;
20
20
  };
21
21
  /** Object comperable to data found in T. */
22
22
  type Values<T> = {
23
- [P in Field<T>]?: Actual<T[P]>;
23
+ [P in Field<T>]?: Export<T[P]>;
24
24
  };
25
25
  /** Exotic value, where actual value is contained within. */
26
26
  type Ref<T = any> = {
@@ -28,7 +28,7 @@ declare namespace Model {
28
28
  current: T | null;
29
29
  };
30
30
  /** A callback function which is subscribed to parent and updates when values change. */
31
- type Effect<T> = (this: T, argument: T) => (() => void) | Promise<void> | null | void;
31
+ type Effect<T> = (this: T, argument: T) => ((update: boolean | null) => void) | Promise<void> | null | void;
32
32
  type Event<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
33
33
  namespace Instruction {
34
34
  type Getter<T> = (source: Model) => T;
@@ -54,23 +54,23 @@ interface Model {
54
54
  is: this;
55
55
  }
56
56
  declare class Model {
57
- constructor(id?: string | number);
57
+ constructor(id?: string | number | (() => void | (() => void)));
58
58
  /** Pull current values from state. Flattens all models and exotic values recursively. */
59
59
  get(): Model.State<this>;
60
60
  /** Run a function which will run automatically when accessed values change. */
61
61
  get(effect: Model.Effect<this>): () => void;
62
- /** Check if model is expired. */
63
- get(event: null): boolean;
64
- /** Callback when model is to be destroyed. */
65
- get(event: null, callback: () => void): () => void;
66
62
  get<T extends Model.Key<this>>(key: T, required?: boolean): Model.Value<this, T>;
67
63
  get<T extends Model.Key<this>>(key: T, callback: Model.OnUpdate<this, T>): () => void;
64
+ /** Check if model is expired. */
65
+ get(status: null): boolean;
66
+ /** Callback when model is to be destroyed. */
67
+ get(status: null, callback: () => void): () => void;
68
68
  /**
69
69
  * Get update in progress.
70
70
  *
71
71
  * @returns Promise which resolves object with updated values. Is `undefined` if there is no update.
72
72
  **/
73
- set(): Promise<Model.Values<this>> | undefined;
73
+ set(): PromiseLike<Model.Key<this>[]> | undefined;
74
74
  /**
75
75
  * Call a function when update occurs.
76
76
  *
@@ -84,15 +84,21 @@ declare class Model {
84
84
  set(callback: Model.Event<this>): () => boolean;
85
85
  /**
86
86
  * Declare an end to updates. This event is final and will freeze state.
87
+ * This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
87
88
  */
88
- set(event: null): void;
89
+ set(status: null): void;
89
90
  /**
90
91
  * Push an update. This will not change the value of associated property.
91
92
  *
92
93
  * Useful where a property value internally has changed, but the object is the same.
93
94
  * For example: An array has pushed a new value, or a nested property is updated.
95
+ *
96
+ * You can also use this to dispatch arbitrary events as well. Symbols are generally
97
+ * recommended as non-property events, however you can use any string. If doing so,
98
+ * be sure to avoid collisions with property names - an easy way to do this will be to
99
+ * prefix your event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
94
100
  */
95
- set(key: Model.Key<this>): void;
101
+ set(key: Model.Key<this>): PromiseLike<Model.Key<this>[]>;
96
102
  /**
97
103
  * Update a property with value.
98
104
  *
@@ -100,7 +106,7 @@ declare class Model {
100
106
  * @param value - value to update property with (if the same as current, no update will occur)
101
107
  * @param silent - if true, will not notify listeners of an update
102
108
  */
103
- set<K extends string>(key: K, value?: Model.Value<this, K>, silent?: boolean): void;
109
+ set<K extends string>(key: K, value?: Model.Value<this, K>, silent?: boolean): PromiseLike<Model.Key<this>[]> | undefined;
104
110
  /** Iterate over managed properties in this instance of Model. */
105
111
  [Symbol.iterator](): Iterator<[string, unknown]>;
106
112
  /**
@@ -276,22 +282,37 @@ declare function use<T extends Model>(model: T, ready?: (i: T) => void): T;
276
282
  declare function use<T extends {}, O = use.Object<T>>(data: T, ready?: (object: O) => void): O;
277
283
 
278
284
  declare namespace Context {
279
- type Inputs = {
280
- [key: string | number]: Model | Model.New;
281
- };
285
+ type Input = Model | Model.New<Model> | Record<string | number, Model | Model.New<Model>>;
282
286
  }
283
287
  declare class Context {
284
- static resolve(from: Model, callback: (context: Context) => void): void;
285
- constructor(inputs?: Context.Inputs);
288
+ static get(from: Model, callback: (context: Context) => void): void;
286
289
  key: string;
287
- private table;
288
- private layer;
289
- private has;
290
+ protected table: WeakMap<Model.Type<Model>, symbol>;
291
+ protected layer: Map<string | number, Model | Model.Type<Model>>;
292
+ constructor(inputs?: Context.Input);
293
+ protected has(T: Model.Type): keyof this;
290
294
  get<T extends Model>(Type: Model.Type<T>): T | undefined;
291
- include(inputs: Context.Inputs): Map<Model, boolean>;
295
+ include(inputs: Context.Input): Map<Model, boolean>;
292
296
  protected add<T extends Model>(input: T | Model.New<T>, implicit?: boolean): T;
293
- push(): this;
297
+ push(inputs?: Context.Input): this;
294
298
  pop(): void;
295
299
  }
296
300
 
301
+ declare global {
302
+ /**
303
+ * Defines MVC specific matchers for [Jest](https://jestjs.io/) test suites.
304
+ *
305
+ * For these to work you will need to add `@expressive/mvc/jest`
306
+ * under `setupFilesAfterEnv` in your Jest configuration.
307
+ */
308
+ namespace jest {
309
+ interface Matchers<R> {
310
+ /** Assert model does have one or more updates pending. */
311
+ toUpdate(timeout?: number): Promise<R>;
312
+ /** Assert model did update with keys specified. */
313
+ toHaveUpdated<R>(...keys: (string | symbol | number)[]): Promise<R>;
314
+ }
315
+ }
316
+ }
317
+
297
318
  export { Context, Model, add, Model as default, get, ref, set, use };
package/dist/index.js CHANGED
@@ -38,15 +38,15 @@ var LISTENER = /* @__PURE__ */ new WeakMap();
38
38
  function onReady() {
39
39
  return null;
40
40
  }
41
- function addListener(to, fn, select) {
42
- let subs = LISTENER.get(to);
41
+ function addListener(subject, callback, select) {
42
+ let subs = LISTENER.get(subject);
43
43
  if (!subs)
44
- LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
44
+ LISTENER.set(subject, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
45
45
  const filter = 2 in arguments && /* @__PURE__ */ new Set([select]);
46
46
  if (!filter && !subs.has(onReady))
47
- fn(true, to);
48
- subs.set(fn, filter);
49
- return () => subs.delete(fn);
47
+ callback(true, subject);
48
+ subs.set(callback, filter);
49
+ return () => subs.delete(callback);
50
50
  }
51
51
  function watch(from, key, value) {
52
52
  const listeners = LISTENER.get(from);
@@ -64,17 +64,30 @@ function watch(from, key, value) {
64
64
  }
65
65
  return value;
66
66
  }
67
+ var PENDING = /* @__PURE__ */ new WeakMap();
67
68
  function event(source, key) {
68
69
  const subs = LISTENER.get(source);
69
- subs.forEach((select, callback) => {
70
- let after;
71
- if (!select || select.has(key)) {
72
- if (after = callback.call(source, key, source))
73
- queue(after);
74
- }
75
- if (after === null || key === null)
76
- subs.delete(callback);
70
+ let pending = PENDING.get(source);
71
+ if (pending) {
72
+ pending.add(key);
73
+ return;
74
+ }
75
+ PENDING.set(source, pending = new Set(
76
+ key !== true && subs.has(onReady) ? [true, key] : [key]
77
+ ));
78
+ pending.forEach((key2) => {
79
+ pending.delete(key2);
80
+ subs.forEach((select, callback) => {
81
+ let after;
82
+ if (!select || select.has(key2)) {
83
+ if (after = callback.call(source, key2, source))
84
+ queue(after);
85
+ }
86
+ if (after === null || key2 === null)
87
+ subs.delete(callback);
88
+ });
77
89
  });
90
+ PENDING.delete(source);
78
91
  }
79
92
  function queue(event2) {
80
93
  if (!DISPATCH.size)
@@ -102,7 +115,7 @@ function effect(target, callback) {
102
115
  return null;
103
116
  expired = true;
104
117
  if (refresh && unSet) {
105
- unSet();
118
+ unSet(true);
106
119
  unSet = void 0;
107
120
  }
108
121
  return refresh;
@@ -129,9 +142,11 @@ function effect(target, callback) {
129
142
  else if (!refresh)
130
143
  return refresh;
131
144
  if (key === null && unSet)
132
- unSet();
145
+ unSet(null);
133
146
  });
134
147
  return () => {
148
+ if (unSet)
149
+ unSet(false);
135
150
  refresh = null;
136
151
  };
137
152
  }
@@ -141,13 +156,18 @@ var ID = /* @__PURE__ */ new WeakMap();
141
156
  var PARENT = /* @__PURE__ */ new WeakMap();
142
157
  var STATE = /* @__PURE__ */ new WeakMap();
143
158
  var NOTIFY = /* @__PURE__ */ new WeakMap();
144
- var PENDING = /* @__PURE__ */ new WeakMap();
159
+ var PENDING2 = /* @__PURE__ */ new WeakMap();
145
160
  var define = Object.defineProperty;
146
161
  var Model = class _Model {
147
162
  constructor(id) {
148
163
  let Type = this.constructor;
149
164
  const state = {};
150
165
  define(this, "is", { value: this });
166
+ if (typeof id == "function") {
167
+ const cb = id;
168
+ id = void 0;
169
+ this.get(() => cb());
170
+ }
151
171
  STATE.set(this, state);
152
172
  ID.set(this, id ? String(id) : `${Type}-${uid()}`);
153
173
  while (true) {
@@ -183,36 +203,19 @@ var Model = class _Model {
183
203
  const self = this.is;
184
204
  if (typeof arg1 == "function")
185
205
  return effect(self, arg1);
186
- if (arg1 === void 0) {
187
- let get3 = function(value) {
188
- if (value instanceof _Model) {
189
- if (cache.has(value))
190
- return cache.get(value);
191
- const model = value;
192
- cache.set(value, value = {});
193
- for (const [key, val] of model)
194
- value[key] = get3(val);
195
- }
196
- return value;
197
- };
198
- var get2 = get3;
199
- const cache = /* @__PURE__ */ new WeakMap();
200
- return get3(self);
201
- }
202
- if (arg1 === null)
203
- if (typeof arg2 == "function")
204
- return addListener(self, arg2.bind(this, this), null);
205
- else
206
- return Object.isFrozen(STATE.get(self));
206
+ if (arg1 === void 0)
207
+ return extract(self);
207
208
  if (typeof arg2 == "function") {
209
+ if (arg1 === null)
210
+ return addListener(self, arg2.bind(this, this), null);
208
211
  const state = STATE.get(self);
209
212
  if (arg1 in state)
210
213
  arg2.call(this, state[arg1], arg1, this);
211
214
  return addListener(self, () => {
212
215
  arg2.call(this, arg1 in state ? state[arg1] : arg1, arg1, this);
213
216
  }, arg1);
214
- } else
215
- return fetch(self, arg1, arg2);
217
+ }
218
+ return arg1 === null ? Object.isFrozen(STATE.get(self)) : fetch(self, arg1, arg2);
216
219
  }
217
220
  set(arg1, arg2, arg3) {
218
221
  const self = this.is;
@@ -221,18 +224,24 @@ var Model = class _Model {
221
224
  if (typeof key == "string")
222
225
  return arg1.call(self, key, self);
223
226
  });
227
+ if (arg1 === null)
228
+ return event(this, null);
224
229
  if (arg1 !== void 0)
225
- return 1 in arguments ? update(self, arg1, arg2, arg3) : update(self, arg1);
226
- if (!PENDING.has(self))
227
- return;
228
- return new Promise((resolve) => {
229
- const remove = addListener(this, (key) => {
230
- if (key !== true) {
231
- remove();
232
- return resolve.bind(null, PENDING.get(self));
233
- }
234
- });
235
- });
230
+ if (1 in arguments)
231
+ update(self, arg1, arg2, arg3);
232
+ else
233
+ push(self, arg1);
234
+ if (PENDING2.has(self))
235
+ return {
236
+ then: (res) => new Promise((res2) => {
237
+ const remove = addListener(self, (key) => {
238
+ if (key !== true) {
239
+ remove();
240
+ return res2.bind(null, Array.from(PENDING2.get(self)));
241
+ }
242
+ });
243
+ }).then(res)
244
+ };
236
245
  }
237
246
  /** Iterate over managed properties in this instance of Model. */
238
247
  [Symbol.iterator]() {
@@ -285,14 +294,14 @@ function fetch(subject, property, required) {
285
294
  return value;
286
295
  }
287
296
  const error = new Error(`${subject}.${property} is not yet available.`);
288
- const promise = new Promise((resolve, reject) => {
297
+ const promise = new Promise((resolve, reject2) => {
289
298
  addListener(subject, (key) => {
290
299
  if (key === property) {
291
300
  resolve(state[key]);
292
301
  return null;
293
302
  }
294
303
  if (key === null)
295
- reject(new Error(`${subject} is destroyed.`));
304
+ reject2(new Error(`${subject} is destroyed.`));
296
305
  });
297
306
  });
298
307
  throw Object.assign(promise, {
@@ -304,26 +313,42 @@ function fetch(subject, property, required) {
304
313
  }
305
314
  function update(subject, key, value, silent) {
306
315
  const state = STATE.get(subject);
307
- let pending = PENDING.get(subject);
308
- if (typeof key == "string" && 2 in arguments) {
309
- const previous = state[key];
310
- if (value === previous)
311
- return true;
312
- state[key] = value;
313
- if (silent === true)
314
- return;
316
+ if (Object.isFrozen(state))
317
+ throw new Error(`Tried to update ${String(key)} but ${subject} is destroyed.`);
318
+ const previous = state[key];
319
+ if (value === previous)
320
+ return true;
321
+ state[key] = value;
322
+ if (silent === true)
323
+ return;
324
+ push(subject, key);
325
+ }
326
+ function push(subject, key) {
327
+ let pending = PENDING2.get(subject);
328
+ if (!pending) {
329
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
330
+ queue(() => {
331
+ event(subject, false);
332
+ PENDING2.delete(subject);
333
+ });
315
334
  }
316
- if (key !== true && key) {
317
- if (!pending) {
318
- PENDING.set(subject, pending = {});
319
- queue(() => {
320
- event(subject, false);
321
- PENDING.delete(subject);
322
- });
335
+ pending.add(key);
336
+ event(subject, key);
337
+ }
338
+ function extract(from) {
339
+ const cache = /* @__PURE__ */ new WeakMap();
340
+ function get2(value) {
341
+ if (value instanceof Model) {
342
+ if (cache.has(value))
343
+ return cache.get(value);
344
+ const model = value;
345
+ cache.set(value, value = {});
346
+ for (const [key, val] of model)
347
+ value[key] = get2(val);
323
348
  }
324
- pending[key] = key in state ? value : true;
349
+ return value;
325
350
  }
326
- event(subject, key);
351
+ return get2(from);
327
352
  }
328
353
  function uid() {
329
354
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
@@ -338,11 +363,10 @@ function add(instruction) {
338
363
  }
339
364
  Model.on((_, subject) => {
340
365
  const state = STATE.get(subject);
341
- for (const key in subject) {
342
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
366
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
343
367
  const instruction = INSTRUCT.get(value);
344
368
  if (!instruction)
345
- continue;
369
+ return;
346
370
  INSTRUCT.delete(value);
347
371
  delete subject[key];
348
372
  const output = instruction.call(subject, key, subject, state);
@@ -376,7 +400,7 @@ Model.on((_, subject) => {
376
400
  );
377
401
  }
378
402
  });
379
- }
403
+ });
380
404
  return null;
381
405
  });
382
406
 
@@ -388,7 +412,7 @@ var Context = class {
388
412
  if (inputs)
389
413
  this.include(inputs);
390
414
  }
391
- static resolve(from, callback) {
415
+ static get() {
392
416
  throw new Error(`Using context requires an adapter. If you are only testing, define \`get.context\` to simulate one.`);
393
417
  }
394
418
  has(T) {
@@ -407,6 +431,14 @@ var Context = class {
407
431
  }
408
432
  include(inputs) {
409
433
  const init = /* @__PURE__ */ new Map();
434
+ if (typeof inputs == "function" || inputs instanceof Model)
435
+ inputs = { [0]: inputs };
436
+ else if (!inputs)
437
+ reject(inputs);
438
+ Object.entries(inputs).forEach(([K, V]) => {
439
+ if (!(Model.is(V) || V instanceof Model))
440
+ reject(`${V} as ${K}`);
441
+ });
410
442
  for (const key in inputs) {
411
443
  const input = inputs[key];
412
444
  const exists = this.layer.get(key);
@@ -453,9 +485,11 @@ var Context = class {
453
485
  } while (T !== Model);
454
486
  return I;
455
487
  }
456
- push() {
488
+ push(inputs) {
457
489
  const next = Object.create(this);
458
490
  next.layer = /* @__PURE__ */ new Map();
491
+ if (inputs)
492
+ next.include(inputs);
459
493
  return next;
460
494
  }
461
495
  pop() {
@@ -471,6 +505,9 @@ var Context = class {
471
505
  this.layer.clear();
472
506
  }
473
507
  };
508
+ function reject(argument) {
509
+ throw new Error(`Context can only include Model or instance but got ${argument}.`);
510
+ }
474
511
 
475
512
  // src/instruction/get.ts
476
513
  function get(arg0, arg1) {
@@ -487,7 +524,7 @@ function get(arg0, arg1) {
487
524
  if (arg1 === true)
488
525
  throw new Error(`${from} may only exist as a child of type ${arg0}.`);
489
526
  source = (resolve) => {
490
- Context.resolve(from, (context) => {
527
+ Context.get(from, (context) => {
491
528
  const model = context.get(arg0);
492
529
  if (model)
493
530
  resolve(model);
@@ -519,9 +556,11 @@ function compute(subject, key, source, setter) {
519
556
  compute2(true);
520
557
  else if (STALE.delete(compute2))
521
558
  compute2();
522
- return () => {
523
- STALE.add(compute2);
524
- subject.set(key);
559
+ return (reason) => {
560
+ if (reason) {
561
+ STALE.add(compute2);
562
+ subject.set(key);
563
+ }
525
564
  };
526
565
  });
527
566
  }
@@ -576,7 +615,7 @@ function ref(arg, arg2) {
576
615
  else {
577
616
  let unSet;
578
617
  const set2 = (value2) => {
579
- if (subject.set(key, value2) || !arg)
618
+ if (!subject.set(key, value2) || !arg)
580
619
  return;
581
620
  if (unSet)
582
621
  unSet = void unSet(value2);
package/dist/index.mjs CHANGED
@@ -5,15 +5,15 @@ var LISTENER = /* @__PURE__ */ new WeakMap();
5
5
  function onReady() {
6
6
  return null;
7
7
  }
8
- function addListener(to, fn, select) {
9
- let subs = LISTENER.get(to);
8
+ function addListener(subject, callback, select) {
9
+ let subs = LISTENER.get(subject);
10
10
  if (!subs)
11
- LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
11
+ LISTENER.set(subject, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
12
12
  const filter = 2 in arguments && /* @__PURE__ */ new Set([select]);
13
13
  if (!filter && !subs.has(onReady))
14
- fn(true, to);
15
- subs.set(fn, filter);
16
- return () => subs.delete(fn);
14
+ callback(true, subject);
15
+ subs.set(callback, filter);
16
+ return () => subs.delete(callback);
17
17
  }
18
18
  function watch(from, key, value) {
19
19
  const listeners = LISTENER.get(from);
@@ -31,17 +31,30 @@ function watch(from, key, value) {
31
31
  }
32
32
  return value;
33
33
  }
34
+ var PENDING = /* @__PURE__ */ new WeakMap();
34
35
  function event(source, key) {
35
36
  const subs = LISTENER.get(source);
36
- subs.forEach((select, callback) => {
37
- let after;
38
- if (!select || select.has(key)) {
39
- if (after = callback.call(source, key, source))
40
- queue(after);
41
- }
42
- if (after === null || key === null)
43
- subs.delete(callback);
37
+ let pending = PENDING.get(source);
38
+ if (pending) {
39
+ pending.add(key);
40
+ return;
41
+ }
42
+ PENDING.set(source, pending = new Set(
43
+ key !== true && subs.has(onReady) ? [true, key] : [key]
44
+ ));
45
+ pending.forEach((key2) => {
46
+ pending.delete(key2);
47
+ subs.forEach((select, callback) => {
48
+ let after;
49
+ if (!select || select.has(key2)) {
50
+ if (after = callback.call(source, key2, source))
51
+ queue(after);
52
+ }
53
+ if (after === null || key2 === null)
54
+ subs.delete(callback);
55
+ });
44
56
  });
57
+ PENDING.delete(source);
45
58
  }
46
59
  function queue(event2) {
47
60
  if (!DISPATCH.size)
@@ -69,7 +82,7 @@ function effect(target, callback) {
69
82
  return null;
70
83
  expired = true;
71
84
  if (refresh && unSet) {
72
- unSet();
85
+ unSet(true);
73
86
  unSet = void 0;
74
87
  }
75
88
  return refresh;
@@ -96,9 +109,11 @@ function effect(target, callback) {
96
109
  else if (!refresh)
97
110
  return refresh;
98
111
  if (key === null && unSet)
99
- unSet();
112
+ unSet(null);
100
113
  });
101
114
  return () => {
115
+ if (unSet)
116
+ unSet(false);
102
117
  refresh = null;
103
118
  };
104
119
  }
@@ -108,13 +123,18 @@ var ID = /* @__PURE__ */ new WeakMap();
108
123
  var PARENT = /* @__PURE__ */ new WeakMap();
109
124
  var STATE = /* @__PURE__ */ new WeakMap();
110
125
  var NOTIFY = /* @__PURE__ */ new WeakMap();
111
- var PENDING = /* @__PURE__ */ new WeakMap();
126
+ var PENDING2 = /* @__PURE__ */ new WeakMap();
112
127
  var define = Object.defineProperty;
113
128
  var Model = class _Model {
114
129
  constructor(id) {
115
130
  let Type = this.constructor;
116
131
  const state = {};
117
132
  define(this, "is", { value: this });
133
+ if (typeof id == "function") {
134
+ const cb = id;
135
+ id = void 0;
136
+ this.get(() => cb());
137
+ }
118
138
  STATE.set(this, state);
119
139
  ID.set(this, id ? String(id) : `${Type}-${uid()}`);
120
140
  while (true) {
@@ -150,36 +170,19 @@ var Model = class _Model {
150
170
  const self = this.is;
151
171
  if (typeof arg1 == "function")
152
172
  return effect(self, arg1);
153
- if (arg1 === void 0) {
154
- let get3 = function(value) {
155
- if (value instanceof _Model) {
156
- if (cache.has(value))
157
- return cache.get(value);
158
- const model = value;
159
- cache.set(value, value = {});
160
- for (const [key, val] of model)
161
- value[key] = get3(val);
162
- }
163
- return value;
164
- };
165
- var get2 = get3;
166
- const cache = /* @__PURE__ */ new WeakMap();
167
- return get3(self);
168
- }
169
- if (arg1 === null)
170
- if (typeof arg2 == "function")
171
- return addListener(self, arg2.bind(this, this), null);
172
- else
173
- return Object.isFrozen(STATE.get(self));
173
+ if (arg1 === void 0)
174
+ return extract(self);
174
175
  if (typeof arg2 == "function") {
176
+ if (arg1 === null)
177
+ return addListener(self, arg2.bind(this, this), null);
175
178
  const state = STATE.get(self);
176
179
  if (arg1 in state)
177
180
  arg2.call(this, state[arg1], arg1, this);
178
181
  return addListener(self, () => {
179
182
  arg2.call(this, arg1 in state ? state[arg1] : arg1, arg1, this);
180
183
  }, arg1);
181
- } else
182
- return fetch(self, arg1, arg2);
184
+ }
185
+ return arg1 === null ? Object.isFrozen(STATE.get(self)) : fetch(self, arg1, arg2);
183
186
  }
184
187
  set(arg1, arg2, arg3) {
185
188
  const self = this.is;
@@ -188,18 +191,24 @@ var Model = class _Model {
188
191
  if (typeof key == "string")
189
192
  return arg1.call(self, key, self);
190
193
  });
194
+ if (arg1 === null)
195
+ return event(this, null);
191
196
  if (arg1 !== void 0)
192
- return 1 in arguments ? update(self, arg1, arg2, arg3) : update(self, arg1);
193
- if (!PENDING.has(self))
194
- return;
195
- return new Promise((resolve) => {
196
- const remove = addListener(this, (key) => {
197
- if (key !== true) {
198
- remove();
199
- return resolve.bind(null, PENDING.get(self));
200
- }
201
- });
202
- });
197
+ if (1 in arguments)
198
+ update(self, arg1, arg2, arg3);
199
+ else
200
+ push(self, arg1);
201
+ if (PENDING2.has(self))
202
+ return {
203
+ then: (res) => new Promise((res2) => {
204
+ const remove = addListener(self, (key) => {
205
+ if (key !== true) {
206
+ remove();
207
+ return res2.bind(null, Array.from(PENDING2.get(self)));
208
+ }
209
+ });
210
+ }).then(res)
211
+ };
203
212
  }
204
213
  /** Iterate over managed properties in this instance of Model. */
205
214
  [Symbol.iterator]() {
@@ -252,14 +261,14 @@ function fetch(subject, property, required) {
252
261
  return value;
253
262
  }
254
263
  const error = new Error(`${subject}.${property} is not yet available.`);
255
- const promise = new Promise((resolve, reject) => {
264
+ const promise = new Promise((resolve, reject2) => {
256
265
  addListener(subject, (key) => {
257
266
  if (key === property) {
258
267
  resolve(state[key]);
259
268
  return null;
260
269
  }
261
270
  if (key === null)
262
- reject(new Error(`${subject} is destroyed.`));
271
+ reject2(new Error(`${subject} is destroyed.`));
263
272
  });
264
273
  });
265
274
  throw Object.assign(promise, {
@@ -271,26 +280,42 @@ function fetch(subject, property, required) {
271
280
  }
272
281
  function update(subject, key, value, silent) {
273
282
  const state = STATE.get(subject);
274
- let pending = PENDING.get(subject);
275
- if (typeof key == "string" && 2 in arguments) {
276
- const previous = state[key];
277
- if (value === previous)
278
- return true;
279
- state[key] = value;
280
- if (silent === true)
281
- return;
283
+ if (Object.isFrozen(state))
284
+ throw new Error(`Tried to update ${String(key)} but ${subject} is destroyed.`);
285
+ const previous = state[key];
286
+ if (value === previous)
287
+ return true;
288
+ state[key] = value;
289
+ if (silent === true)
290
+ return;
291
+ push(subject, key);
292
+ }
293
+ function push(subject, key) {
294
+ let pending = PENDING2.get(subject);
295
+ if (!pending) {
296
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
297
+ queue(() => {
298
+ event(subject, false);
299
+ PENDING2.delete(subject);
300
+ });
282
301
  }
283
- if (key !== true && key) {
284
- if (!pending) {
285
- PENDING.set(subject, pending = {});
286
- queue(() => {
287
- event(subject, false);
288
- PENDING.delete(subject);
289
- });
302
+ pending.add(key);
303
+ event(subject, key);
304
+ }
305
+ function extract(from) {
306
+ const cache = /* @__PURE__ */ new WeakMap();
307
+ function get2(value) {
308
+ if (value instanceof Model) {
309
+ if (cache.has(value))
310
+ return cache.get(value);
311
+ const model = value;
312
+ cache.set(value, value = {});
313
+ for (const [key, val] of model)
314
+ value[key] = get2(val);
290
315
  }
291
- pending[key] = key in state ? value : true;
316
+ return value;
292
317
  }
293
- event(subject, key);
318
+ return get2(from);
294
319
  }
295
320
  function uid() {
296
321
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
@@ -305,11 +330,10 @@ function add(instruction) {
305
330
  }
306
331
  Model.on((_, subject) => {
307
332
  const state = STATE.get(subject);
308
- for (const key in subject) {
309
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
333
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
310
334
  const instruction = INSTRUCT.get(value);
311
335
  if (!instruction)
312
- continue;
336
+ return;
313
337
  INSTRUCT.delete(value);
314
338
  delete subject[key];
315
339
  const output = instruction.call(subject, key, subject, state);
@@ -343,7 +367,7 @@ Model.on((_, subject) => {
343
367
  );
344
368
  }
345
369
  });
346
- }
370
+ });
347
371
  return null;
348
372
  });
349
373
 
@@ -355,7 +379,7 @@ var Context = class {
355
379
  if (inputs)
356
380
  this.include(inputs);
357
381
  }
358
- static resolve(from, callback) {
382
+ static get() {
359
383
  throw new Error(`Using context requires an adapter. If you are only testing, define \`get.context\` to simulate one.`);
360
384
  }
361
385
  has(T) {
@@ -374,6 +398,14 @@ var Context = class {
374
398
  }
375
399
  include(inputs) {
376
400
  const init = /* @__PURE__ */ new Map();
401
+ if (typeof inputs == "function" || inputs instanceof Model)
402
+ inputs = { [0]: inputs };
403
+ else if (!inputs)
404
+ reject(inputs);
405
+ Object.entries(inputs).forEach(([K, V]) => {
406
+ if (!(Model.is(V) || V instanceof Model))
407
+ reject(`${V} as ${K}`);
408
+ });
377
409
  for (const key in inputs) {
378
410
  const input = inputs[key];
379
411
  const exists = this.layer.get(key);
@@ -420,9 +452,11 @@ var Context = class {
420
452
  } while (T !== Model);
421
453
  return I;
422
454
  }
423
- push() {
455
+ push(inputs) {
424
456
  const next = Object.create(this);
425
457
  next.layer = /* @__PURE__ */ new Map();
458
+ if (inputs)
459
+ next.include(inputs);
426
460
  return next;
427
461
  }
428
462
  pop() {
@@ -438,6 +472,9 @@ var Context = class {
438
472
  this.layer.clear();
439
473
  }
440
474
  };
475
+ function reject(argument) {
476
+ throw new Error(`Context can only include Model or instance but got ${argument}.`);
477
+ }
441
478
 
442
479
  // src/instruction/get.ts
443
480
  function get(arg0, arg1) {
@@ -454,7 +491,7 @@ function get(arg0, arg1) {
454
491
  if (arg1 === true)
455
492
  throw new Error(`${from} may only exist as a child of type ${arg0}.`);
456
493
  source = (resolve) => {
457
- Context.resolve(from, (context) => {
494
+ Context.get(from, (context) => {
458
495
  const model = context.get(arg0);
459
496
  if (model)
460
497
  resolve(model);
@@ -486,9 +523,11 @@ function compute(subject, key, source, setter) {
486
523
  compute2(true);
487
524
  else if (STALE.delete(compute2))
488
525
  compute2();
489
- return () => {
490
- STALE.add(compute2);
491
- subject.set(key);
526
+ return (reason) => {
527
+ if (reason) {
528
+ STALE.add(compute2);
529
+ subject.set(key);
530
+ }
492
531
  };
493
532
  });
494
533
  }
@@ -543,7 +582,7 @@ function ref(arg, arg2) {
543
582
  else {
544
583
  let unSet;
545
584
  const set2 = (value2) => {
546
- if (subject.set(key, value2) || !arg)
585
+ if (!subject.set(key, value2) || !arg)
547
586
  return;
548
587
  if (unSet)
549
588
  unSet = void unSet(value2);
package/package.json CHANGED
@@ -28,6 +28,6 @@
28
28
  "prebuild": "rm -rf dist",
29
29
  "preversion": "npm run test && npm run build"
30
30
  },
31
- "version": "0.35.5",
32
- "gitHead": "8c45e620bda976235dcc4ffbb66c948db02650bc"
31
+ "version": "0.36.0",
32
+ "gitHead": "09b42668a6cdd0eb10be56985e0b72bbead26a88"
33
33
  }