@expressive/mvc 0.52.0 → 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
@@ -5,16 +5,16 @@ declare namespace Model {
5
5
  type Init<T extends Model = Model> = (new (...args: Model.Args<T>) => T) & Omit<typeof Model, never>;
6
6
  /**
7
7
  * Model constructor callback - is called when Model finishes intializing.
8
- * Returned function will call when model is destroyed.
8
+ * Returned function will run when model is destroyed.
9
9
  */
10
10
  type Callback<T extends Model = Model> = (this: T, thisArg: T) => Promise<void> | (() => void) | Assign<T> | void;
11
11
  /** Model constructor argument */
12
12
  type Argument<T extends Model = Model> = Assign<T> | Callback<T> | string | void;
13
13
  /** Model constructor arguments */
14
- type Args<T extends Model = any> = Argument<T>[];
14
+ type Args<T extends Model = any> = (Argument<T> | Args<T>)[];
15
15
  /** Subset of `keyof T` which are not methods or defined by base Model U. **/
16
16
  type Field<T> = Exclude<keyof T, keyof Model>;
17
- /** 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>. */
18
18
  type Event<T> = Field<T> | (string & {}) | number | symbol;
19
19
  /** Object overlay to override values and methods on a model. */
20
20
  type Assign<T> = {
@@ -24,12 +24,12 @@ declare namespace Model {
24
24
  type Export<R> = R extends {
25
25
  get(): infer T;
26
26
  } ? T : R;
27
- /** Value for a property managed by a controller. */
27
+ /** Value for a property managed by a model. */
28
28
  type Value<T extends Model, K extends Event<T>> = K extends keyof T ? Export<T[K]> : unknown;
29
29
  type OnEvent<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
30
30
  type OnUpdate<T extends Model, K extends Event<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
31
31
  /**
32
- * Values from current state of given controller.
32
+ * Values from current state of given model.
33
33
  * Differs from `Values` as values here will drill
34
34
  * into "real" values held by exotics like ref.Object.
35
35
  */
@@ -48,7 +48,7 @@ declare namespace Model {
48
48
  /**
49
49
  * A callback function which is subscribed to parent and updates when accessed properties change.
50
50
  *
51
- * @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
52
52
  * where accessed, and thus depended upon to stay current.
53
53
  *
54
54
  * @param update - Set of properties which have changed, and events fired, since last update.
@@ -59,7 +59,7 @@ declare namespace Model {
59
59
  /**
60
60
  * A callback function returned by effect. Will be called when effect is stale.
61
61
  *
62
- * @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.
63
63
  */
64
64
  type EffectCallback = ((update: boolean | null) => void);
65
65
  /**
@@ -88,7 +88,7 @@ declare abstract class Model {
88
88
  /**
89
89
  * Pull current values from state. Flattens all models and exotic values recursively.
90
90
  *
91
- * @returns Object with all values from this controller.
91
+ * @returns Object with all values from this model.
92
92
  **/
93
93
  get(): Model.State<this>;
94
94
  /**
@@ -96,7 +96,7 @@ declare abstract class Model {
96
96
  *
97
97
  * @param effect Function to run, and again whenever accessed values change.
98
98
  * If effect returns a function, it will be called when a change occurs (syncronously),
99
- * effect is cancelled, or parent controller is destroyed.
99
+ * effect is cancelled, or parent model is destroyed.
100
100
  * @returns Function to cancel listener.
101
101
  */
102
102
  get(effect: Model.Effect<this>): () => void;
@@ -168,15 +168,15 @@ declare abstract class Model {
168
168
  * to prefix an event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
169
169
  *
170
170
  * @param key - Property or event to dispatch.
171
- * @returns Promise which resolves an array of keys which were updated.
171
+ * @returns Promise resolves an array of keys updated.
172
172
  */
173
173
  set(key: Model.Event<this>): PromiseLike<Model.Event<this>[]>;
174
174
  /**
175
175
  * Update mulitple properties at once. Merges argument with current state.
176
- * Properties which are not managed by this controller will be ignored.
176
+ * Properties which are not managed by this model will be ignored.
177
177
  *
178
178
  * @param assign - Object with properties to update.
179
- * @returns Promise which resolves an array of keys which were updated.
179
+ * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
180
180
  */
181
181
  set(assign: Model.Assign<this>): PromiseLike<Model.Event<this>[]> | undefined;
182
182
  /**
@@ -185,15 +185,16 @@ declare abstract class Model {
185
185
  * @param key - property to update
186
186
  * @param value - value to update property with (if the same as current, no update will occur)
187
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.
188
189
  */
189
- 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;
190
191
  /**
191
192
  * Iterate over managed properties in this instance of Model.
192
193
  * Yeilds the key and underlying value for each property.
193
194
  */
194
195
  [Symbol.iterator](): Iterator<[string, unknown]>;
195
196
  /**
196
- * Create and activate a new instance of this controller.
197
+ * Create and activate a new instance of this model.
197
198
  *
198
199
  * **Important** - Unlike `new this(...)` - this method also activates state.
199
200
  *
@@ -400,21 +401,4 @@ declare class Context {
400
401
  protected add<T extends Model>(input: T | Model.Init<T>, implicit?: boolean): T;
401
402
  }
402
403
 
403
- declare global {
404
- /**
405
- * Defines MVC specific matchers for [Jest](https://jestjs.io/) test suites.
406
- *
407
- * For these to work you will need to add `@expressive/mvc/jest`
408
- * under `setupFilesAfterEnv` in your Jest configuration.
409
- */
410
- namespace jest {
411
- interface Matchers<R> {
412
- /** Assert model does have one or more updates pending. */
413
- toUpdate(timeout?: number): Promise<R>;
414
- /** Assert model did update with keys specified. */
415
- toHaveUpdated<R>(...keys: (string | symbol | number)[]): Promise<R>;
416
- }
417
- }
418
- }
419
-
420
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);
@@ -166,11 +166,11 @@ var METHOD = /* @__PURE__ */ new WeakMap();
166
166
  var EXPORT;
167
167
  var Model = class _Model {
168
168
  constructor(...args) {
169
+ define(this, "is", { value: this });
169
170
  ID.set(this, `${this.constructor}-${uid()}`);
170
171
  STATE.set(this, {});
171
172
  prepare(this);
172
- define(this, "is", { value: this });
173
- apply(this, [...args, init]);
173
+ init(this, args);
174
174
  }
175
175
  get(arg1, arg2) {
176
176
  const self = this.is;
@@ -238,7 +238,7 @@ var Model = class _Model {
238
238
  return Object.entries(STATE.get(this.is))[Symbol.iterator]();
239
239
  }
240
240
  /**
241
- * Create and activate a new instance of this controller.
241
+ * Create and activate a new instance of this model.
242
242
  *
243
243
  * **Important** - Unlike `new this(...)` - this method also activates state.
244
244
  *
@@ -300,7 +300,9 @@ function prepare(model) {
300
300
  continue;
301
301
  }
302
302
  METHODS.set(type2, keys2 = new Set(keys2));
303
- for (const { key: key2, value } of entries(type2.prototype)) {
303
+ for (const [key2, { value }] of Object.entries(
304
+ Object.getOwnPropertyDescriptors(type2.prototype)
305
+ )) {
304
306
  let set3 = function(method) {
305
307
  const value2 = method.bind(this.is);
306
308
  METHOD.set(value2, method);
@@ -320,53 +322,51 @@ function prepare(model) {
320
322
  }
321
323
  }
322
324
  }
323
- function apply(model, args) {
324
- for (const arg of args)
325
- if (typeof arg == "string")
326
- ID.set(model, arg);
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
+ });
327
333
  addListener(model, () => {
328
- const done = /* @__PURE__ */ new Set();
329
- for (const arg of args) {
334
+ if (!PARENT.has(model))
335
+ PARENT.set(model, null);
336
+ args.forEach((arg) => {
330
337
  const use2 = typeof arg == "function" ? arg.call(model, model) : arg;
331
338
  if (use2 instanceof Promise)
332
339
  use2.catch((err) => {
333
340
  console.error(`Async error in constructor for ${model}:`);
334
341
  console.error(err);
335
342
  });
336
- else if (typeof use2 == "object")
337
- assign(model, use2);
338
343
  else if (typeof use2 == "function")
339
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
+ }
340
360
  }
341
- addListener(model, () => {
342
- done.forEach((x) => x());
343
- }, null);
344
361
  return null;
345
362
  });
346
- }
347
- function init(model) {
348
- const state = STATE.get(model);
349
- if (!PARENT.has(model))
350
- PARENT.set(model, null);
351
- for (const key2 in model) {
352
- const desc = Object.getOwnPropertyDescriptor(model, key2);
353
- if ("value" in desc) {
354
- update(model, key2, desc.value, true);
355
- define(model, key2, {
356
- configurable: false,
357
- set: (x) => update(model, key2, x),
358
- get() {
359
- return watch(this, key2, state[key2]);
360
- }
361
- });
362
- }
363
- }
364
- return () => {
363
+ addListener(model, () => {
364
+ done.forEach((x) => x());
365
365
  for (const [_, value] of model)
366
366
  if (value instanceof Model && PARENT.get(value) === model)
367
367
  value.set(null);
368
368
  Object.freeze(state);
369
- };
369
+ }, null);
370
370
  }
371
371
  function fetch(subject, property, required) {
372
372
  const state = STATE.get(subject);
@@ -393,6 +393,20 @@ function fetch(subject, property, required) {
393
393
  stack: error.stack
394
394
  });
395
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
+ }
396
410
  function update(subject, key2, value, arg) {
397
411
  const state = STATE.get(subject);
398
412
  if (Object.isFrozen(state))
@@ -435,28 +449,9 @@ function assign(to, values2) {
435
449
  if (key2 in to || methods.has(key2))
436
450
  to[key2] = values2[key2];
437
451
  }
438
- function values(model) {
439
- const values2 = {};
440
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
441
- for (let [key2, value] of model) {
442
- if (EXPORT.has(value))
443
- value = EXPORT.get(value);
444
- else if (value && typeof value.get === "function")
445
- EXPORT.set(value, value = value.get());
446
- values2[key2] = value;
447
- }
448
- if (current)
449
- EXPORT = void 0;
450
- return Object.freeze(values2);
451
- }
452
452
  function uid() {
453
453
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
454
454
  }
455
- function entries(object) {
456
- return Object.entries(
457
- Object.getOwnPropertyDescriptors(object)
458
- ).map(([key2, desc]) => ({ key: key2, ...desc }));
459
- }
460
455
 
461
456
  // src/context.ts
462
457
  var Register = /* @__PURE__ */ new WeakMap();
@@ -611,7 +606,8 @@ function use(arg1, arg2) {
611
606
  }
612
607
  Model.on((_, model) => {
613
608
  const state = STATE.get(model);
614
- for (const { key: key2, value } of entries(model)) {
609
+ for (const key2 in model) {
610
+ const { value } = Object.getOwnPropertyDescriptor(model, key2);
615
611
  const instruction = INSTRUCT.get(value);
616
612
  if (!instruction) {
617
613
  if (value instanceof Model && key2 !== "is")
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);
@@ -132,11 +132,11 @@ var METHOD = /* @__PURE__ */ new WeakMap();
132
132
  var EXPORT;
133
133
  var Model = class _Model {
134
134
  constructor(...args) {
135
+ define(this, "is", { value: this });
135
136
  ID.set(this, `${this.constructor}-${uid()}`);
136
137
  STATE.set(this, {});
137
138
  prepare(this);
138
- define(this, "is", { value: this });
139
- apply(this, [...args, init]);
139
+ init(this, args);
140
140
  }
141
141
  get(arg1, arg2) {
142
142
  const self = this.is;
@@ -204,7 +204,7 @@ var Model = class _Model {
204
204
  return Object.entries(STATE.get(this.is))[Symbol.iterator]();
205
205
  }
206
206
  /**
207
- * Create and activate a new instance of this controller.
207
+ * Create and activate a new instance of this model.
208
208
  *
209
209
  * **Important** - Unlike `new this(...)` - this method also activates state.
210
210
  *
@@ -266,7 +266,9 @@ function prepare(model) {
266
266
  continue;
267
267
  }
268
268
  METHODS.set(type2, keys2 = new Set(keys2));
269
- for (const { key: key2, value } of entries(type2.prototype)) {
269
+ for (const [key2, { value }] of Object.entries(
270
+ Object.getOwnPropertyDescriptors(type2.prototype)
271
+ )) {
270
272
  let set3 = function(method) {
271
273
  const value2 = method.bind(this.is);
272
274
  METHOD.set(value2, method);
@@ -286,53 +288,51 @@ function prepare(model) {
286
288
  }
287
289
  }
288
290
  }
289
- function apply(model, args) {
290
- for (const arg of args)
291
- if (typeof arg == "string")
292
- ID.set(model, arg);
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
+ });
293
299
  addListener(model, () => {
294
- const done = /* @__PURE__ */ new Set();
295
- for (const arg of args) {
300
+ if (!PARENT.has(model))
301
+ PARENT.set(model, null);
302
+ args.forEach((arg) => {
296
303
  const use2 = typeof arg == "function" ? arg.call(model, model) : arg;
297
304
  if (use2 instanceof Promise)
298
305
  use2.catch((err) => {
299
306
  console.error(`Async error in constructor for ${model}:`);
300
307
  console.error(err);
301
308
  });
302
- else if (typeof use2 == "object")
303
- assign(model, use2);
304
309
  else if (typeof use2 == "function")
305
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
+ }
306
326
  }
307
- addListener(model, () => {
308
- done.forEach((x) => x());
309
- }, null);
310
327
  return null;
311
328
  });
312
- }
313
- function init(model) {
314
- const state = STATE.get(model);
315
- if (!PARENT.has(model))
316
- PARENT.set(model, null);
317
- for (const key2 in model) {
318
- const desc = Object.getOwnPropertyDescriptor(model, key2);
319
- if ("value" in desc) {
320
- update(model, key2, desc.value, true);
321
- define(model, key2, {
322
- configurable: false,
323
- set: (x) => update(model, key2, x),
324
- get() {
325
- return watch(this, key2, state[key2]);
326
- }
327
- });
328
- }
329
- }
330
- return () => {
329
+ addListener(model, () => {
330
+ done.forEach((x) => x());
331
331
  for (const [_, value] of model)
332
332
  if (value instanceof Model && PARENT.get(value) === model)
333
333
  value.set(null);
334
334
  Object.freeze(state);
335
- };
335
+ }, null);
336
336
  }
337
337
  function fetch(subject, property, required) {
338
338
  const state = STATE.get(subject);
@@ -359,6 +359,20 @@ function fetch(subject, property, required) {
359
359
  stack: error.stack
360
360
  });
361
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
+ }
362
376
  function update(subject, key2, value, arg) {
363
377
  const state = STATE.get(subject);
364
378
  if (Object.isFrozen(state))
@@ -401,28 +415,9 @@ function assign(to, values2) {
401
415
  if (key2 in to || methods.has(key2))
402
416
  to[key2] = values2[key2];
403
417
  }
404
- function values(model) {
405
- const values2 = {};
406
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
407
- for (let [key2, value] of model) {
408
- if (EXPORT.has(value))
409
- value = EXPORT.get(value);
410
- else if (value && typeof value.get === "function")
411
- EXPORT.set(value, value = value.get());
412
- values2[key2] = value;
413
- }
414
- if (current)
415
- EXPORT = void 0;
416
- return Object.freeze(values2);
417
- }
418
418
  function uid() {
419
419
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
420
420
  }
421
- function entries(object) {
422
- return Object.entries(
423
- Object.getOwnPropertyDescriptors(object)
424
- ).map(([key2, desc]) => ({ key: key2, ...desc }));
425
- }
426
421
 
427
422
  // src/context.ts
428
423
  var Register = /* @__PURE__ */ new WeakMap();
@@ -577,7 +572,8 @@ function use(arg1, arg2) {
577
572
  }
578
573
  Model.on((_, model) => {
579
574
  const state = STATE.get(model);
580
- for (const { key: key2, value } of entries(model)) {
575
+ for (const key2 in model) {
576
+ const { value } = Object.getOwnPropertyDescriptor(model, key2);
581
577
  const instruction = INSTRUCT.get(value);
582
578
  if (!instruction) {
583
579
  if (value instanceof Model && key2 !== "is")
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.52.0",
36
- "gitHead": "b6b92114b8b0c69f1a36eae8c03fec2e068d03c4"
35
+ "version": "0.52.1",
36
+ "gitHead": "71f17a90b44bd4b969a56b38d629f32b289c77b7"
37
37
  }