@expressive/mvc 0.51.2 → 0.52.1

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
@@ -1,4 +1,3 @@
1
- type MaybeAsync<T> = T | Promise<T>;
2
1
  declare namespace Model {
3
2
  /** Any type of Model, using own class constructor as its identifier. */
4
3
  type Type<T extends Model = Model> = (abstract new (...args: any[]) => T) & typeof Model;
@@ -6,16 +5,16 @@ declare namespace Model {
6
5
  type Init<T extends Model = Model> = (new (...args: Model.Args<T>) => T) & Omit<typeof Model, never>;
7
6
  /**
8
7
  * Model constructor callback - is called when Model finishes intializing.
9
- * Returned function will call when model is destroyed.
8
+ * Returned function will run when model is destroyed.
10
9
  */
11
- type Callback<T extends Model = Model> = (this: T, thisArg: T) => MaybeAsync<(() => void) | Assign<T> | void>;
10
+ type Callback<T extends Model = Model> = (this: T, thisArg: T) => Promise<void> | (() => void) | Assign<T> | void;
12
11
  /** Model constructor argument */
13
12
  type Argument<T extends Model = Model> = Assign<T> | Callback<T> | string | void;
14
13
  /** Model constructor arguments */
15
- type Args<T extends Model = any> = Argument<T>[];
14
+ type Args<T extends Model = any> = (Argument<T> | Args<T>)[];
16
15
  /** Subset of `keyof T` which are not methods or defined by base Model U. **/
17
16
  type Field<T> = Exclude<keyof T, keyof Model>;
18
- /** Any valid key for controller, including but not limited to Field<T>. */
17
+ /** Any valid key for model, including but not limited to Field<T>. */
19
18
  type Event<T> = Field<T> | (string & {}) | number | symbol;
20
19
  /** Object overlay to override values and methods on a model. */
21
20
  type Assign<T> = {
@@ -25,12 +24,12 @@ declare namespace Model {
25
24
  type Export<R> = R extends {
26
25
  get(): infer T;
27
26
  } ? T : R;
28
- /** Value for a property managed by a controller. */
27
+ /** Value for a property managed by a model. */
29
28
  type Value<T extends Model, K extends Event<T>> = K extends keyof T ? Export<T[K]> : unknown;
30
29
  type OnEvent<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
31
30
  type OnUpdate<T extends Model, K extends Event<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
32
31
  /**
33
- * Values from current state of given controller.
32
+ * Values from current state of given model.
34
33
  * Differs from `Values` as values here will drill
35
34
  * into "real" values held by exotics like ref.Object.
36
35
  */
@@ -49,7 +48,7 @@ declare namespace Model {
49
48
  /**
50
49
  * A callback function which is subscribed to parent and updates when accessed properties change.
51
50
  *
52
- * @param current - Current state of this controller. This is a proxy which detects properties which
51
+ * @param current - Current state of this model. This is a proxy which detects properties which
53
52
  * where accessed, and thus depended upon to stay current.
54
53
  *
55
54
  * @param update - Set of properties which have changed, and events fired, since last update.
@@ -60,7 +59,7 @@ declare namespace Model {
60
59
  /**
61
60
  * A callback function returned by effect. Will be called when effect is stale.
62
61
  *
63
- * @param update - `true` if update is pending, `false` effect has been cancelled, `null` if controller is destroyed.
62
+ * @param update - `true` if update is pending, `false` effect has been cancelled, `null` if model is destroyed.
64
63
  */
65
64
  type EffectCallback = ((update: boolean | null) => void);
66
65
  /**
@@ -86,15 +85,18 @@ interface Model {
86
85
  }
87
86
  declare abstract class Model {
88
87
  constructor(...args: Model.Args);
89
- /** Pull current values from state. Flattens all models and exotic values recursively. */
88
+ /**
89
+ * Pull current values from state. Flattens all models and exotic values recursively.
90
+ *
91
+ * @returns Object with all values from this model.
92
+ **/
90
93
  get(): Model.State<this>;
91
94
  /**
92
95
  * Run a function which will run automatically when accessed values change.
93
96
  *
94
- * @param effect - Function to run, and again whenever accessed values change.
95
- * If effect returns a function, it will be called when a change occurs (syncronously),
96
- * effect is cancelled, or parent controller is destroyed.
97
- *
97
+ * @param effect Function to run, and again whenever accessed values change.
98
+ * If effect returns a function, it will be called when a change occurs (syncronously),
99
+ * effect is cancelled, or parent model is destroyed.
98
100
  * @returns Function to cancel listener.
99
101
  */
100
102
  get(effect: Model.Effect<this>): () => void;
@@ -103,6 +105,7 @@ declare abstract class Model {
103
105
  *
104
106
  * @param key - Property to get value of.
105
107
  * @param required - If true, will throw an error if property is not available.
108
+ * @returns Value of property.
106
109
  */
107
110
  get<T extends Model.Event<this>>(key: T, required?: boolean): Model.Value<this, T>;
108
111
  /**
@@ -110,11 +113,13 @@ declare abstract class Model {
110
113
  *
111
114
  * @param key - Property to watch for updates.
112
115
  * @param callback - Function to call when property is updated.
116
+ * @returns Function to cancel listener.
113
117
  */
114
118
  get<T extends Model.Event<this>>(key: T, callback: Model.OnUpdate<this, T>): () => void;
115
119
  /**
116
120
  * Check if model is expired.
117
121
  *
122
+ * @param status - `null` to check if model is expired.
118
123
  * @returns `true` if model is expired, `false` otherwise.
119
124
  */
120
125
  get(status: null): boolean;
@@ -147,6 +152,8 @@ declare abstract class Model {
147
152
  /**
148
153
  * Declare an end to updates. This event is final and will freeze state.
149
154
  * This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
155
+ *
156
+ * @param status - `null` to end updates.
150
157
  */
151
158
  set(status: null): void;
152
159
  /**
@@ -161,12 +168,15 @@ declare abstract class Model {
161
168
  * to prefix an event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
162
169
  *
163
170
  * @param key - Property or event to dispatch.
164
- * @returns Promise which resolves an array of keys which were updated.
171
+ * @returns Promise resolves an array of keys updated.
165
172
  */
166
173
  set(key: Model.Event<this>): PromiseLike<Model.Event<this>[]>;
167
174
  /**
168
175
  * Update mulitple properties at once. Merges argument with current state.
169
- * Properties which are not managed by this controller will be ignored.
176
+ * Properties which are not managed by this model will be ignored.
177
+ *
178
+ * @param assign - Object with properties to update.
179
+ * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
170
180
  */
171
181
  set(assign: Model.Assign<this>): PromiseLike<Model.Event<this>[]> | undefined;
172
182
  /**
@@ -175,15 +185,16 @@ declare abstract class Model {
175
185
  * @param key - property to update
176
186
  * @param value - value to update property with (if the same as current, no update will occur)
177
187
  * @param silent - if true, will not notify listeners of an update
188
+ * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
178
189
  */
179
- set<K extends string>(key: K, value?: Model.Value<this, K>, silent?: boolean): PromiseLike<Model.Event<this>[]> | undefined;
190
+ set<K extends string>(key: K, value: Model.Value<this, K>, silent?: boolean): PromiseLike<Model.Event<this>[]> | undefined;
180
191
  /**
181
192
  * Iterate over managed properties in this instance of Model.
182
193
  * Yeilds the key and underlying value for each property.
183
194
  */
184
195
  [Symbol.iterator](): Iterator<[string, unknown]>;
185
196
  /**
186
- * Create and activate a new instance of this controller.
197
+ * Create and activate a new instance of this model.
187
198
  *
188
199
  * **Important** - Unlike `new this(...)` - this method also activates state.
189
200
  *
@@ -390,21 +401,4 @@ declare class Context {
390
401
  protected add<T extends Model>(input: T | Model.Init<T>, implicit?: boolean): T;
391
402
  }
392
403
 
393
- declare global {
394
- /**
395
- * Defines MVC specific matchers for [Jest](https://jestjs.io/) test suites.
396
- *
397
- * For these to work you will need to add `@expressive/mvc/jest`
398
- * under `setupFilesAfterEnv` in your Jest configuration.
399
- */
400
- namespace jest {
401
- interface Matchers<R> {
402
- /** Assert model does have one or more updates pending. */
403
- toUpdate(timeout?: number): Promise<R>;
404
- /** Assert model did update with keys specified. */
405
- toHaveUpdated<R>(...keys: (string | symbol | number)[]): Promise<R>;
406
- }
407
- }
408
- }
409
-
410
404
  export { Context, Model, Model as default, effect, get, has, ref, set, use };
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ function addListener(subject, callback, select) {
42
42
  if (!subs)
43
43
  LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
44
44
  const filter = select !== void 0 && /* @__PURE__ */ new Set([select]);
45
- if (!filter && !subs.has(onReady))
45
+ if (!subs.has(onReady) && !filter)
46
46
  callback(true, subject);
47
47
  subs.set(callback, filter);
48
48
  return () => subs.delete(callback);
@@ -79,19 +79,19 @@ function emit(source, key2) {
79
79
  });
80
80
  PENDING.delete(source);
81
81
  }
82
- function queue(event) {
82
+ function queue(event2) {
83
83
  if (!DISPATCH.size)
84
84
  setTimeout(() => {
85
- DISPATCH.forEach((event2) => {
85
+ DISPATCH.forEach((event3) => {
86
86
  try {
87
- event2();
87
+ event3();
88
88
  } catch (err) {
89
89
  console.error(err);
90
90
  }
91
91
  });
92
92
  DISPATCH.clear();
93
93
  }, 0);
94
- DISPATCH.add(event);
94
+ DISPATCH.add(event2);
95
95
  }
96
96
  function effect(target, callback, requireValues) {
97
97
  const listeners = LISTENER.get(target);
@@ -155,78 +155,27 @@ function effect(target, callback, requireValues) {
155
155
  }
156
156
 
157
157
  // src/model.ts
158
- var NAMES = /* @__PURE__ */ new WeakMap();
158
+ var define = Object.defineProperty;
159
+ var ID = /* @__PURE__ */ new WeakMap();
159
160
  var STATE = /* @__PURE__ */ new WeakMap();
160
161
  var NOTIFY = /* @__PURE__ */ new WeakMap();
161
162
  var PENDING2 = /* @__PURE__ */ new WeakMap();
162
163
  var PARENT = /* @__PURE__ */ new WeakMap();
164
+ var METHODS = /* @__PURE__ */ new WeakMap();
163
165
  var METHOD = /* @__PURE__ */ new WeakMap();
164
166
  var EXPORT;
165
- var define = Object.defineProperty;
166
167
  var Model = class _Model {
167
168
  constructor(...args) {
168
- const state = {};
169
- let Type = this.constructor;
170
- bindMethods(Type);
171
169
  define(this, "is", { value: this });
172
- STATE.set(this, state);
173
- NAMES.set(this, `${Type}-${uid()}`);
174
- for (const arg of args)
175
- if (typeof arg == "string")
176
- NAMES.set(this, arg);
177
- while (true) {
178
- new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
179
- if (Type === _Model)
180
- break;
181
- Type = Object.getPrototypeOf(Type);
182
- }
183
- addListener(this, () => {
184
- let done;
185
- (async () => {
186
- const cleanup = /* @__PURE__ */ new Set();
187
- for (const arg of args) {
188
- let use2 = typeof arg == "function" ? arg.call(this, this) : arg;
189
- if (use2 instanceof Promise)
190
- use2 = await use2.catch((err) => {
191
- console.error(`Async error in constructor for ${this}:`);
192
- console.error(err);
193
- });
194
- if (typeof use2 == "object")
195
- assign(this, use2);
196
- else if (typeof use2 == "function")
197
- cleanup.add(use2);
198
- }
199
- done = cleanup;
200
- })();
201
- for (const key2 in this) {
202
- const desc = Object.getOwnPropertyDescriptor(this, key2);
203
- if ("value" in desc) {
204
- update(this, key2, desc.value, true);
205
- define(this, key2, {
206
- configurable: false,
207
- set: (x) => update(this, key2, x),
208
- get() {
209
- return watch(this, key2, state[key2]);
210
- }
211
- });
212
- }
213
- }
214
- if (!PARENT.has(this))
215
- PARENT.set(this, null);
216
- addListener(this, () => {
217
- if (!done)
218
- throw new Error(`Tried to destroy ${this} but not fully initialized.`);
219
- for (const [_, value] of this)
220
- if (value instanceof _Model && PARENT.get(value) === this)
221
- value.set(null);
222
- done.forEach((x) => x());
223
- Object.freeze(state);
224
- }, null);
225
- return null;
226
- });
170
+ ID.set(this, `${this.constructor}-${uid()}`);
171
+ STATE.set(this, {});
172
+ prepare(this);
173
+ init(this, args);
227
174
  }
228
175
  get(arg1, arg2) {
229
176
  const self = this.is;
177
+ if (arg1 === void 0)
178
+ return values(self);
230
179
  if (typeof arg1 == "function") {
231
180
  let pending = /* @__PURE__ */ new Set();
232
181
  return effect(self, (state) => {
@@ -238,20 +187,6 @@ var Model = class _Model {
238
187
  };
239
188
  });
240
189
  }
241
- if (arg1 === void 0) {
242
- const values = {};
243
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[self, values]]));
244
- for (let [key2, value] of self) {
245
- if (EXPORT.has(value))
246
- value = EXPORT.get(value);
247
- else if (value && typeof value.get === "function")
248
- EXPORT.set(value, value = value.get());
249
- values[key2] = value;
250
- }
251
- if (current)
252
- EXPORT = void 0;
253
- return Object.freeze(values);
254
- }
255
190
  if (typeof arg2 == "function") {
256
191
  if (arg1 === null)
257
192
  return addListener(self, arg2.bind(this, this), null);
@@ -282,7 +217,7 @@ var Model = class _Model {
282
217
  else if (1 in arguments)
283
218
  update(self, arg1, arg2, arg3);
284
219
  else
285
- push(self, arg1);
220
+ event(self, arg1);
286
221
  if (PENDING2.has(self))
287
222
  return {
288
223
  then: (res) => new Promise((res2) => {
@@ -303,7 +238,7 @@ var Model = class _Model {
303
238
  return Object.entries(STATE.get(this.is))[Symbol.iterator]();
304
239
  }
305
240
  /**
306
- * Create and activate a new instance of this controller.
241
+ * Create and activate a new instance of this model.
307
242
  *
308
243
  * **Important** - Unlike `new this(...)` - this method also activates state.
309
244
  *
@@ -339,7 +274,7 @@ var Model = class _Model {
339
274
  };
340
275
  define(Model.prototype, "toString", {
341
276
  value() {
342
- return NAMES.get(this.is);
277
+ return ID.get(this.is);
343
278
  }
344
279
  });
345
280
  define(Model, "toString", {
@@ -347,6 +282,92 @@ define(Model, "toString", {
347
282
  return this.name;
348
283
  }
349
284
  });
285
+ function prepare(model) {
286
+ const chain = [];
287
+ let type = model.constructor;
288
+ let keys2 = /* @__PURE__ */ new Set();
289
+ while (type.name) {
290
+ chain.unshift(type);
291
+ type = Object.getPrototypeOf(type);
292
+ }
293
+ for (const type2 of chain) {
294
+ for (const cb of NOTIFY.get(type2) || [])
295
+ addListener(model, cb);
296
+ if (type2 == Model)
297
+ continue;
298
+ if (METHODS.has(type2)) {
299
+ keys2 = METHODS.get(type2);
300
+ continue;
301
+ }
302
+ METHODS.set(type2, keys2 = new Set(keys2));
303
+ for (const [key2, { value }] of Object.entries(
304
+ Object.getOwnPropertyDescriptors(type2.prototype)
305
+ )) {
306
+ let set3 = function(method) {
307
+ const value2 = method.bind(this.is);
308
+ METHOD.set(value2, method);
309
+ define(this.is, key2, { value: value2, writable: true });
310
+ return value2;
311
+ };
312
+ var set2 = set3;
313
+ if (!value || key2 == "constructor")
314
+ continue;
315
+ keys2.add(key2);
316
+ Object.defineProperty(type2.prototype, key2, {
317
+ set: set3,
318
+ get() {
319
+ return this.hasOwnProperty(key2) ? value : set3.call(this, value);
320
+ }
321
+ });
322
+ }
323
+ }
324
+ }
325
+ function init(model, args) {
326
+ const done = /* @__PURE__ */ new Set();
327
+ const state = STATE.get(model);
328
+ args = args.flat().filter((arg) => {
329
+ if (typeof arg != "string")
330
+ return true;
331
+ ID.set(model, arg);
332
+ });
333
+ addListener(model, () => {
334
+ if (!PARENT.has(model))
335
+ PARENT.set(model, null);
336
+ args.forEach((arg) => {
337
+ const use2 = typeof arg == "function" ? arg.call(model, model) : arg;
338
+ if (use2 instanceof Promise)
339
+ use2.catch((err) => {
340
+ console.error(`Async error in constructor for ${model}:`);
341
+ console.error(err);
342
+ });
343
+ else if (typeof use2 == "function")
344
+ done.add(use2);
345
+ else if (typeof use2 == "object")
346
+ assign(model, use2);
347
+ });
348
+ for (const key2 in model) {
349
+ const desc = Object.getOwnPropertyDescriptor(model, key2);
350
+ if ("value" in desc) {
351
+ update(model, key2, desc.value, true);
352
+ define(model, key2, {
353
+ configurable: false,
354
+ set: (x) => update(model, key2, x),
355
+ get() {
356
+ return watch(this, key2, state[key2]);
357
+ }
358
+ });
359
+ }
360
+ }
361
+ return null;
362
+ });
363
+ addListener(model, () => {
364
+ done.forEach((x) => x());
365
+ for (const [_, value] of model)
366
+ if (value instanceof Model && PARENT.get(value) === model)
367
+ value.set(null);
368
+ Object.freeze(state);
369
+ }, null);
370
+ }
350
371
  function fetch(subject, property, required) {
351
372
  const state = STATE.get(subject);
352
373
  if (property in state || required === false) {
@@ -372,6 +393,20 @@ function fetch(subject, property, required) {
372
393
  stack: error.stack
373
394
  });
374
395
  }
396
+ function values(model) {
397
+ const values2 = {};
398
+ const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
399
+ for (let [key2, value] of model) {
400
+ if (EXPORT.has(value))
401
+ value = EXPORT.get(value);
402
+ else if (value && typeof value.get === "function")
403
+ EXPORT.set(value, value = value.get());
404
+ values2[key2] = value;
405
+ }
406
+ if (current)
407
+ EXPORT = void 0;
408
+ return Object.freeze(values2);
409
+ }
375
410
  function update(subject, key2, value, arg) {
376
411
  const state = STATE.get(subject);
377
412
  if (Object.isFrozen(state))
@@ -392,9 +427,9 @@ function update(subject, key2, value, arg) {
392
427
  }
393
428
  state[key2] = value;
394
429
  if (arg !== true)
395
- push(subject, key2);
430
+ event(subject, key2);
396
431
  }
397
- function push(subject, key2, silent) {
432
+ function event(subject, key2, silent) {
398
433
  let pending = PENDING2.get(subject);
399
434
  if (!pending) {
400
435
  PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
@@ -408,39 +443,11 @@ function push(subject, key2, silent) {
408
443
  if (!silent)
409
444
  emit(subject, key2);
410
445
  }
411
- var METHODS = new WeakMap([[Model, []]]);
412
- function assign(to, values) {
446
+ function assign(to, values2) {
413
447
  const methods = METHODS.get(to.constructor);
414
- const applicable = new Set(Object.keys(to).concat(methods));
415
- for (const key2 in values)
416
- if (applicable.has(key2))
417
- to[key2] = values[key2];
418
- }
419
- function bindMethods(type) {
420
- let keys2 = METHODS.get(type);
421
- if (!keys2) {
422
- METHODS.set(type, keys2 = bindMethods(Object.getPrototypeOf(type)).slice());
423
- const desc = Object.getOwnPropertyDescriptors(type.prototype);
424
- for (const key2 in desc)
425
- if (key2 != "constructor" && "value" in desc[key2]) {
426
- let bind2 = function(method) {
427
- const value2 = method.bind(this.is);
428
- METHOD.set(value2, method);
429
- define(this.is, key2, { value: value2, writable: true });
430
- return value2;
431
- };
432
- var bind = bind2;
433
- let { value } = desc[key2];
434
- keys2.push(key2);
435
- Object.defineProperty(type.prototype, key2, {
436
- set: bind2,
437
- get() {
438
- return this.hasOwnProperty(key2) ? value : bind2.call(this, value);
439
- }
440
- });
441
- }
442
- }
443
- return keys2;
448
+ for (const key2 in values2)
449
+ if (key2 in to || methods.has(key2))
450
+ to[key2] = values2[key2];
444
451
  }
445
452
  function uid() {
446
453
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
@@ -507,7 +514,7 @@ var Context = class _Context {
507
514
  this.layer.clear();
508
515
  }
509
516
  include(inputs, forEach) {
510
- const init = /* @__PURE__ */ new Map();
517
+ const init2 = /* @__PURE__ */ new Map();
511
518
  Object.entries(inputs).forEach(([K, V]) => {
512
519
  if (!(Model.is(V) || V instanceof Model))
513
520
  throw new Error(`Context can only include Model or instance but got ${V}${K && " as " + K}.`);
@@ -518,21 +525,21 @@ var Context = class _Context {
518
525
  if (!exists) {
519
526
  const instance = this.add(input);
520
527
  this.layer.set(key2, input);
521
- init.set(instance, true);
528
+ init2.set(instance, true);
522
529
  } else if (exists !== input) {
523
530
  this.pop();
524
531
  this.id = uid();
525
532
  this.include(inputs);
526
533
  }
527
534
  }
528
- for (const [model, explicit] of init) {
535
+ for (const [model, explicit] of init2) {
529
536
  model.set();
530
537
  if (forEach)
531
538
  forEach(model, explicit);
532
539
  for (const [_key, value] of model)
533
540
  if (PARENT.get(value) === model) {
534
541
  this.add(value, true);
535
- init.set(value, false);
542
+ init2.set(value, false);
536
543
  }
537
544
  }
538
545
  }
@@ -597,38 +604,37 @@ function use(arg1, arg2) {
597
604
  INSTRUCT.set(token, arg1);
598
605
  return token;
599
606
  }
600
- Model.on((_, subject) => {
601
- const state = STATE.get(subject);
602
- const props = Object.getOwnPropertyDescriptors(subject);
603
- for (const key2 in props) {
604
- const { value } = props[key2];
607
+ Model.on((_, model) => {
608
+ const state = STATE.get(model);
609
+ for (const key2 in model) {
610
+ const { value } = Object.getOwnPropertyDescriptor(model, key2);
605
611
  const instruction = INSTRUCT.get(value);
606
612
  if (!instruction) {
607
613
  if (value instanceof Model && key2 !== "is")
608
- update(subject, key2, value);
614
+ update(model, key2, value);
609
615
  continue;
610
616
  }
611
617
  INSTRUCT.delete(value);
612
- delete subject[key2];
613
- const output = instruction.call(subject, key2, subject, state);
618
+ delete model[key2];
619
+ const output = instruction.call(model, key2, model, state);
614
620
  if (!output)
615
621
  continue;
616
622
  const desc = typeof output == "object" ? output : { get: output };
617
623
  if ("value" in desc)
618
624
  state[key2] = desc.value;
619
- Object.defineProperty(subject, key2, {
625
+ Object.defineProperty(model, key2, {
620
626
  enumerable: desc.enumerable !== false,
621
627
  get() {
622
628
  return watch(
623
629
  this,
624
630
  key2,
625
- typeof desc.get == "function" ? desc.get(this) : fetch(subject, key2, desc.get)
631
+ typeof desc.get == "function" ? desc.get(this) : fetch(model, key2, desc.get)
626
632
  );
627
633
  },
628
634
  set(next) {
629
635
  if (desc.set === false)
630
- throw new Error(`${subject}.${key2} is read-only.`);
631
- update(subject, key2, next, desc.set);
636
+ throw new Error(`${model}.${key2} is read-only.`);
637
+ update(model, key2, next, desc.set);
632
638
  }
633
639
  });
634
640
  }
@@ -676,7 +682,7 @@ function get(arg0, arg1) {
676
682
  return (didUpdate) => {
677
683
  if (didUpdate) {
678
684
  STALE.add(compute2);
679
- push(subject, key2, true);
685
+ event(subject, key2, true);
680
686
  }
681
687
  };
682
688
  });
@@ -768,7 +774,7 @@ function set(value, argument) {
768
774
  return use((key2, subject) => {
769
775
  const output = {};
770
776
  if (typeof value == "function" || value instanceof Promise) {
771
- let init2 = function() {
777
+ let init3 = function() {
772
778
  if (typeof value == "function")
773
779
  try {
774
780
  value = attempt(value.bind(subject, key2));
@@ -792,11 +798,11 @@ function set(value, argument) {
792
798
  if (0 in arguments)
793
799
  return subject[key2];
794
800
  };
795
- var init = init2;
801
+ var init2 = init3;
796
802
  if (argument)
797
- init2();
803
+ init3();
798
804
  else
799
- output.get = init2;
805
+ output.get = init3;
800
806
  } else if (value !== void 0)
801
807
  output.value = value;
802
808
  if (typeof argument == "function") {
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ function addListener(subject, callback, select) {
8
8
  if (!subs)
9
9
  LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
10
10
  const filter = select !== void 0 && /* @__PURE__ */ new Set([select]);
11
- if (!filter && !subs.has(onReady))
11
+ if (!subs.has(onReady) && !filter)
12
12
  callback(true, subject);
13
13
  subs.set(callback, filter);
14
14
  return () => subs.delete(callback);
@@ -45,19 +45,19 @@ function emit(source, key2) {
45
45
  });
46
46
  PENDING.delete(source);
47
47
  }
48
- function queue(event) {
48
+ function queue(event2) {
49
49
  if (!DISPATCH.size)
50
50
  setTimeout(() => {
51
- DISPATCH.forEach((event2) => {
51
+ DISPATCH.forEach((event3) => {
52
52
  try {
53
- event2();
53
+ event3();
54
54
  } catch (err) {
55
55
  console.error(err);
56
56
  }
57
57
  });
58
58
  DISPATCH.clear();
59
59
  }, 0);
60
- DISPATCH.add(event);
60
+ DISPATCH.add(event2);
61
61
  }
62
62
  function effect(target, callback, requireValues) {
63
63
  const listeners = LISTENER.get(target);
@@ -121,78 +121,27 @@ function effect(target, callback, requireValues) {
121
121
  }
122
122
 
123
123
  // src/model.ts
124
- var NAMES = /* @__PURE__ */ new WeakMap();
124
+ var define = Object.defineProperty;
125
+ var ID = /* @__PURE__ */ new WeakMap();
125
126
  var STATE = /* @__PURE__ */ new WeakMap();
126
127
  var NOTIFY = /* @__PURE__ */ new WeakMap();
127
128
  var PENDING2 = /* @__PURE__ */ new WeakMap();
128
129
  var PARENT = /* @__PURE__ */ new WeakMap();
130
+ var METHODS = /* @__PURE__ */ new WeakMap();
129
131
  var METHOD = /* @__PURE__ */ new WeakMap();
130
132
  var EXPORT;
131
- var define = Object.defineProperty;
132
133
  var Model = class _Model {
133
134
  constructor(...args) {
134
- const state = {};
135
- let Type = this.constructor;
136
- bindMethods(Type);
137
135
  define(this, "is", { value: this });
138
- STATE.set(this, state);
139
- NAMES.set(this, `${Type}-${uid()}`);
140
- for (const arg of args)
141
- if (typeof arg == "string")
142
- NAMES.set(this, arg);
143
- while (true) {
144
- new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
145
- if (Type === _Model)
146
- break;
147
- Type = Object.getPrototypeOf(Type);
148
- }
149
- addListener(this, () => {
150
- let done;
151
- (async () => {
152
- const cleanup = /* @__PURE__ */ new Set();
153
- for (const arg of args) {
154
- let use2 = typeof arg == "function" ? arg.call(this, this) : arg;
155
- if (use2 instanceof Promise)
156
- use2 = await use2.catch((err) => {
157
- console.error(`Async error in constructor for ${this}:`);
158
- console.error(err);
159
- });
160
- if (typeof use2 == "object")
161
- assign(this, use2);
162
- else if (typeof use2 == "function")
163
- cleanup.add(use2);
164
- }
165
- done = cleanup;
166
- })();
167
- for (const key2 in this) {
168
- const desc = Object.getOwnPropertyDescriptor(this, key2);
169
- if ("value" in desc) {
170
- update(this, key2, desc.value, true);
171
- define(this, key2, {
172
- configurable: false,
173
- set: (x) => update(this, key2, x),
174
- get() {
175
- return watch(this, key2, state[key2]);
176
- }
177
- });
178
- }
179
- }
180
- if (!PARENT.has(this))
181
- PARENT.set(this, null);
182
- addListener(this, () => {
183
- if (!done)
184
- throw new Error(`Tried to destroy ${this} but not fully initialized.`);
185
- for (const [_, value] of this)
186
- if (value instanceof _Model && PARENT.get(value) === this)
187
- value.set(null);
188
- done.forEach((x) => x());
189
- Object.freeze(state);
190
- }, null);
191
- return null;
192
- });
136
+ ID.set(this, `${this.constructor}-${uid()}`);
137
+ STATE.set(this, {});
138
+ prepare(this);
139
+ init(this, args);
193
140
  }
194
141
  get(arg1, arg2) {
195
142
  const self = this.is;
143
+ if (arg1 === void 0)
144
+ return values(self);
196
145
  if (typeof arg1 == "function") {
197
146
  let pending = /* @__PURE__ */ new Set();
198
147
  return effect(self, (state) => {
@@ -204,20 +153,6 @@ var Model = class _Model {
204
153
  };
205
154
  });
206
155
  }
207
- if (arg1 === void 0) {
208
- const values = {};
209
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[self, values]]));
210
- for (let [key2, value] of self) {
211
- if (EXPORT.has(value))
212
- value = EXPORT.get(value);
213
- else if (value && typeof value.get === "function")
214
- EXPORT.set(value, value = value.get());
215
- values[key2] = value;
216
- }
217
- if (current)
218
- EXPORT = void 0;
219
- return Object.freeze(values);
220
- }
221
156
  if (typeof arg2 == "function") {
222
157
  if (arg1 === null)
223
158
  return addListener(self, arg2.bind(this, this), null);
@@ -248,7 +183,7 @@ var Model = class _Model {
248
183
  else if (1 in arguments)
249
184
  update(self, arg1, arg2, arg3);
250
185
  else
251
- push(self, arg1);
186
+ event(self, arg1);
252
187
  if (PENDING2.has(self))
253
188
  return {
254
189
  then: (res) => new Promise((res2) => {
@@ -269,7 +204,7 @@ var Model = class _Model {
269
204
  return Object.entries(STATE.get(this.is))[Symbol.iterator]();
270
205
  }
271
206
  /**
272
- * Create and activate a new instance of this controller.
207
+ * Create and activate a new instance of this model.
273
208
  *
274
209
  * **Important** - Unlike `new this(...)` - this method also activates state.
275
210
  *
@@ -305,7 +240,7 @@ var Model = class _Model {
305
240
  };
306
241
  define(Model.prototype, "toString", {
307
242
  value() {
308
- return NAMES.get(this.is);
243
+ return ID.get(this.is);
309
244
  }
310
245
  });
311
246
  define(Model, "toString", {
@@ -313,6 +248,92 @@ define(Model, "toString", {
313
248
  return this.name;
314
249
  }
315
250
  });
251
+ function prepare(model) {
252
+ const chain = [];
253
+ let type = model.constructor;
254
+ let keys2 = /* @__PURE__ */ new Set();
255
+ while (type.name) {
256
+ chain.unshift(type);
257
+ type = Object.getPrototypeOf(type);
258
+ }
259
+ for (const type2 of chain) {
260
+ for (const cb of NOTIFY.get(type2) || [])
261
+ addListener(model, cb);
262
+ if (type2 == Model)
263
+ continue;
264
+ if (METHODS.has(type2)) {
265
+ keys2 = METHODS.get(type2);
266
+ continue;
267
+ }
268
+ METHODS.set(type2, keys2 = new Set(keys2));
269
+ for (const [key2, { value }] of Object.entries(
270
+ Object.getOwnPropertyDescriptors(type2.prototype)
271
+ )) {
272
+ let set3 = function(method) {
273
+ const value2 = method.bind(this.is);
274
+ METHOD.set(value2, method);
275
+ define(this.is, key2, { value: value2, writable: true });
276
+ return value2;
277
+ };
278
+ var set2 = set3;
279
+ if (!value || key2 == "constructor")
280
+ continue;
281
+ keys2.add(key2);
282
+ Object.defineProperty(type2.prototype, key2, {
283
+ set: set3,
284
+ get() {
285
+ return this.hasOwnProperty(key2) ? value : set3.call(this, value);
286
+ }
287
+ });
288
+ }
289
+ }
290
+ }
291
+ function init(model, args) {
292
+ const done = /* @__PURE__ */ new Set();
293
+ const state = STATE.get(model);
294
+ args = args.flat().filter((arg) => {
295
+ if (typeof arg != "string")
296
+ return true;
297
+ ID.set(model, arg);
298
+ });
299
+ addListener(model, () => {
300
+ if (!PARENT.has(model))
301
+ PARENT.set(model, null);
302
+ args.forEach((arg) => {
303
+ const use2 = typeof arg == "function" ? arg.call(model, model) : arg;
304
+ if (use2 instanceof Promise)
305
+ use2.catch((err) => {
306
+ console.error(`Async error in constructor for ${model}:`);
307
+ console.error(err);
308
+ });
309
+ else if (typeof use2 == "function")
310
+ done.add(use2);
311
+ else if (typeof use2 == "object")
312
+ assign(model, use2);
313
+ });
314
+ for (const key2 in model) {
315
+ const desc = Object.getOwnPropertyDescriptor(model, key2);
316
+ if ("value" in desc) {
317
+ update(model, key2, desc.value, true);
318
+ define(model, key2, {
319
+ configurable: false,
320
+ set: (x) => update(model, key2, x),
321
+ get() {
322
+ return watch(this, key2, state[key2]);
323
+ }
324
+ });
325
+ }
326
+ }
327
+ return null;
328
+ });
329
+ addListener(model, () => {
330
+ done.forEach((x) => x());
331
+ for (const [_, value] of model)
332
+ if (value instanceof Model && PARENT.get(value) === model)
333
+ value.set(null);
334
+ Object.freeze(state);
335
+ }, null);
336
+ }
316
337
  function fetch(subject, property, required) {
317
338
  const state = STATE.get(subject);
318
339
  if (property in state || required === false) {
@@ -338,6 +359,20 @@ function fetch(subject, property, required) {
338
359
  stack: error.stack
339
360
  });
340
361
  }
362
+ function values(model) {
363
+ const values2 = {};
364
+ const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
365
+ for (let [key2, value] of model) {
366
+ if (EXPORT.has(value))
367
+ value = EXPORT.get(value);
368
+ else if (value && typeof value.get === "function")
369
+ EXPORT.set(value, value = value.get());
370
+ values2[key2] = value;
371
+ }
372
+ if (current)
373
+ EXPORT = void 0;
374
+ return Object.freeze(values2);
375
+ }
341
376
  function update(subject, key2, value, arg) {
342
377
  const state = STATE.get(subject);
343
378
  if (Object.isFrozen(state))
@@ -358,9 +393,9 @@ function update(subject, key2, value, arg) {
358
393
  }
359
394
  state[key2] = value;
360
395
  if (arg !== true)
361
- push(subject, key2);
396
+ event(subject, key2);
362
397
  }
363
- function push(subject, key2, silent) {
398
+ function event(subject, key2, silent) {
364
399
  let pending = PENDING2.get(subject);
365
400
  if (!pending) {
366
401
  PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
@@ -374,39 +409,11 @@ function push(subject, key2, silent) {
374
409
  if (!silent)
375
410
  emit(subject, key2);
376
411
  }
377
- var METHODS = new WeakMap([[Model, []]]);
378
- function assign(to, values) {
412
+ function assign(to, values2) {
379
413
  const methods = METHODS.get(to.constructor);
380
- const applicable = new Set(Object.keys(to).concat(methods));
381
- for (const key2 in values)
382
- if (applicable.has(key2))
383
- to[key2] = values[key2];
384
- }
385
- function bindMethods(type) {
386
- let keys2 = METHODS.get(type);
387
- if (!keys2) {
388
- METHODS.set(type, keys2 = bindMethods(Object.getPrototypeOf(type)).slice());
389
- const desc = Object.getOwnPropertyDescriptors(type.prototype);
390
- for (const key2 in desc)
391
- if (key2 != "constructor" && "value" in desc[key2]) {
392
- let bind2 = function(method) {
393
- const value2 = method.bind(this.is);
394
- METHOD.set(value2, method);
395
- define(this.is, key2, { value: value2, writable: true });
396
- return value2;
397
- };
398
- var bind = bind2;
399
- let { value } = desc[key2];
400
- keys2.push(key2);
401
- Object.defineProperty(type.prototype, key2, {
402
- set: bind2,
403
- get() {
404
- return this.hasOwnProperty(key2) ? value : bind2.call(this, value);
405
- }
406
- });
407
- }
408
- }
409
- return keys2;
414
+ for (const key2 in values2)
415
+ if (key2 in to || methods.has(key2))
416
+ to[key2] = values2[key2];
410
417
  }
411
418
  function uid() {
412
419
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
@@ -473,7 +480,7 @@ var Context = class _Context {
473
480
  this.layer.clear();
474
481
  }
475
482
  include(inputs, forEach) {
476
- const init = /* @__PURE__ */ new Map();
483
+ const init2 = /* @__PURE__ */ new Map();
477
484
  Object.entries(inputs).forEach(([K, V]) => {
478
485
  if (!(Model.is(V) || V instanceof Model))
479
486
  throw new Error(`Context can only include Model or instance but got ${V}${K && " as " + K}.`);
@@ -484,21 +491,21 @@ var Context = class _Context {
484
491
  if (!exists) {
485
492
  const instance = this.add(input);
486
493
  this.layer.set(key2, input);
487
- init.set(instance, true);
494
+ init2.set(instance, true);
488
495
  } else if (exists !== input) {
489
496
  this.pop();
490
497
  this.id = uid();
491
498
  this.include(inputs);
492
499
  }
493
500
  }
494
- for (const [model, explicit] of init) {
501
+ for (const [model, explicit] of init2) {
495
502
  model.set();
496
503
  if (forEach)
497
504
  forEach(model, explicit);
498
505
  for (const [_key, value] of model)
499
506
  if (PARENT.get(value) === model) {
500
507
  this.add(value, true);
501
- init.set(value, false);
508
+ init2.set(value, false);
502
509
  }
503
510
  }
504
511
  }
@@ -563,38 +570,37 @@ function use(arg1, arg2) {
563
570
  INSTRUCT.set(token, arg1);
564
571
  return token;
565
572
  }
566
- Model.on((_, subject) => {
567
- const state = STATE.get(subject);
568
- const props = Object.getOwnPropertyDescriptors(subject);
569
- for (const key2 in props) {
570
- const { value } = props[key2];
573
+ Model.on((_, model) => {
574
+ const state = STATE.get(model);
575
+ for (const key2 in model) {
576
+ const { value } = Object.getOwnPropertyDescriptor(model, key2);
571
577
  const instruction = INSTRUCT.get(value);
572
578
  if (!instruction) {
573
579
  if (value instanceof Model && key2 !== "is")
574
- update(subject, key2, value);
580
+ update(model, key2, value);
575
581
  continue;
576
582
  }
577
583
  INSTRUCT.delete(value);
578
- delete subject[key2];
579
- const output = instruction.call(subject, key2, subject, state);
584
+ delete model[key2];
585
+ const output = instruction.call(model, key2, model, state);
580
586
  if (!output)
581
587
  continue;
582
588
  const desc = typeof output == "object" ? output : { get: output };
583
589
  if ("value" in desc)
584
590
  state[key2] = desc.value;
585
- Object.defineProperty(subject, key2, {
591
+ Object.defineProperty(model, key2, {
586
592
  enumerable: desc.enumerable !== false,
587
593
  get() {
588
594
  return watch(
589
595
  this,
590
596
  key2,
591
- typeof desc.get == "function" ? desc.get(this) : fetch(subject, key2, desc.get)
597
+ typeof desc.get == "function" ? desc.get(this) : fetch(model, key2, desc.get)
592
598
  );
593
599
  },
594
600
  set(next) {
595
601
  if (desc.set === false)
596
- throw new Error(`${subject}.${key2} is read-only.`);
597
- update(subject, key2, next, desc.set);
602
+ throw new Error(`${model}.${key2} is read-only.`);
603
+ update(model, key2, next, desc.set);
598
604
  }
599
605
  });
600
606
  }
@@ -642,7 +648,7 @@ function get(arg0, arg1) {
642
648
  return (didUpdate) => {
643
649
  if (didUpdate) {
644
650
  STALE.add(compute2);
645
- push(subject, key2, true);
651
+ event(subject, key2, true);
646
652
  }
647
653
  };
648
654
  });
@@ -734,7 +740,7 @@ function set(value, argument) {
734
740
  return use((key2, subject) => {
735
741
  const output = {};
736
742
  if (typeof value == "function" || value instanceof Promise) {
737
- let init2 = function() {
743
+ let init3 = function() {
738
744
  if (typeof value == "function")
739
745
  try {
740
746
  value = attempt(value.bind(subject, key2));
@@ -758,11 +764,11 @@ function set(value, argument) {
758
764
  if (0 in arguments)
759
765
  return subject[key2];
760
766
  };
761
- var init = init2;
767
+ var init2 = init3;
762
768
  if (argument)
763
- init2();
769
+ init3();
764
770
  else
765
- output.get = init2;
771
+ output.get = init3;
766
772
  } else if (value !== void 0)
767
773
  output.value = value;
768
774
  if (typeof argument == "function") {
package/package.json CHANGED
@@ -32,6 +32,6 @@
32
32
  "jest": "^27.0.6",
33
33
  "tsup": "^8.0.2"
34
34
  },
35
- "version": "0.51.2",
36
- "gitHead": "fc4ca0a906025eb8a0dbed96887f2613782d567a"
35
+ "version": "0.52.1",
36
+ "gitHead": "71f17a90b44bd4b969a56b38d629f32b289c77b7"
37
37
  }