@expressive/mvc 0.35.6 → 0.36.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
@@ -54,7 +54,7 @@ interface Model {
54
54
  is: this;
55
55
  }
56
56
  declare class Model {
57
- constructor(id?: string | number);
57
+ constructor(arg?: string | (() => 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. */
@@ -298,4 +298,21 @@ declare class Context {
298
298
  pop(): void;
299
299
  }
300
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
+
301
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)
@@ -143,45 +156,28 @@ var ID = /* @__PURE__ */ new WeakMap();
143
156
  var PARENT = /* @__PURE__ */ new WeakMap();
144
157
  var STATE = /* @__PURE__ */ new WeakMap();
145
158
  var NOTIFY = /* @__PURE__ */ new WeakMap();
146
- var PENDING = /* @__PURE__ */ new WeakMap();
159
+ var PENDING2 = /* @__PURE__ */ new WeakMap();
147
160
  var define = Object.defineProperty;
148
161
  var Model = class _Model {
149
- constructor(id) {
162
+ constructor(arg) {
150
163
  let Type = this.constructor;
151
- const state = {};
152
164
  define(this, "is", { value: this });
153
- STATE.set(this, state);
154
- ID.set(this, id ? String(id) : `${Type}-${uid()}`);
165
+ STATE.set(this, {});
166
+ ID.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
155
167
  while (true) {
156
168
  new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
157
169
  if (Type === _Model)
158
170
  break;
159
171
  Type = Object.getPrototypeOf(Type);
160
172
  }
161
- const remove = addListener(this, (key) => {
162
- remove();
163
- if (key !== true)
164
- event(this, true);
165
- for (const key2 in this) {
166
- const desc = Object.getOwnPropertyDescriptor(this, key2);
167
- if ("value" in desc) {
168
- state[key2] = desc.value;
169
- define(this, key2, {
170
- enumerable: true,
171
- set: (x) => update(this, key2, x),
172
- get() {
173
- return watch(this, key2, state[key2]);
174
- }
175
- });
176
- }
177
- }
173
+ addListener(this, bootstrap);
174
+ if (typeof arg == "function")
178
175
  addListener(this, () => {
179
- for (const [_, value] of this)
180
- if (value instanceof _Model && PARENT.get(value) === this)
181
- value.set(null);
182
- Object.freeze(state);
183
- }, null);
184
- });
176
+ const cleanup = arg();
177
+ if (typeof cleanup == "function")
178
+ addListener(this, cleanup, null);
179
+ return null;
180
+ });
185
181
  }
186
182
  get(arg1, arg2) {
187
183
  const self = this.is;
@@ -215,13 +211,13 @@ var Model = class _Model {
215
211
  update(self, arg1, arg2, arg3);
216
212
  else
217
213
  push(self, arg1);
218
- if (PENDING.has(self))
214
+ if (PENDING2.has(self))
219
215
  return {
220
216
  then: (res) => new Promise((res2) => {
221
217
  const remove = addListener(self, (key) => {
222
218
  if (key !== true) {
223
219
  remove();
224
- return res2.bind(null, Array.from(PENDING.get(self)));
220
+ return res2.bind(null, Array.from(PENDING2.get(self)));
225
221
  }
226
222
  });
227
223
  }).then(res)
@@ -270,6 +266,29 @@ define(Model, "toString", {
270
266
  return this.name;
271
267
  }
272
268
  });
269
+ function bootstrap() {
270
+ const state = STATE.get(this);
271
+ for (const key in this) {
272
+ const desc = Object.getOwnPropertyDescriptor(this, key);
273
+ if ("value" in desc) {
274
+ state[key] = desc.value;
275
+ define(this, key, {
276
+ configurable: false,
277
+ set: (x) => update(this, key, x),
278
+ get() {
279
+ return watch(this, key, state[key]);
280
+ }
281
+ });
282
+ }
283
+ }
284
+ addListener(this, () => {
285
+ for (const [_, value] of this)
286
+ if (value instanceof Model && PARENT.get(value) === this)
287
+ value.set(null);
288
+ Object.freeze(state);
289
+ }, null);
290
+ return null;
291
+ }
273
292
  function fetch(subject, property, required) {
274
293
  const state = STATE.get(subject);
275
294
  if (property in state || required === false) {
@@ -308,12 +327,12 @@ function update(subject, key, value, silent) {
308
327
  push(subject, key);
309
328
  }
310
329
  function push(subject, key) {
311
- let pending = PENDING.get(subject);
330
+ let pending = PENDING2.get(subject);
312
331
  if (!pending) {
313
- PENDING.set(subject, pending = /* @__PURE__ */ new Set());
332
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
314
333
  queue(() => {
315
334
  event(subject, false);
316
- PENDING.delete(subject);
335
+ PENDING2.delete(subject);
317
336
  });
318
337
  }
319
338
  pending.add(key);
@@ -347,11 +366,10 @@ function add(instruction) {
347
366
  }
348
367
  Model.on((_, subject) => {
349
368
  const state = STATE.get(subject);
350
- for (const key in subject) {
351
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
369
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
352
370
  const instruction = INSTRUCT.get(value);
353
371
  if (!instruction)
354
- continue;
372
+ return;
355
373
  INSTRUCT.delete(value);
356
374
  delete subject[key];
357
375
  const output = instruction.call(subject, key, subject, state);
@@ -385,7 +403,7 @@ Model.on((_, subject) => {
385
403
  );
386
404
  }
387
405
  });
388
- }
406
+ });
389
407
  return null;
390
408
  });
391
409
 
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)
@@ -110,45 +123,28 @@ var ID = /* @__PURE__ */ new WeakMap();
110
123
  var PARENT = /* @__PURE__ */ new WeakMap();
111
124
  var STATE = /* @__PURE__ */ new WeakMap();
112
125
  var NOTIFY = /* @__PURE__ */ new WeakMap();
113
- var PENDING = /* @__PURE__ */ new WeakMap();
126
+ var PENDING2 = /* @__PURE__ */ new WeakMap();
114
127
  var define = Object.defineProperty;
115
128
  var Model = class _Model {
116
- constructor(id) {
129
+ constructor(arg) {
117
130
  let Type = this.constructor;
118
- const state = {};
119
131
  define(this, "is", { value: this });
120
- STATE.set(this, state);
121
- ID.set(this, id ? String(id) : `${Type}-${uid()}`);
132
+ STATE.set(this, {});
133
+ ID.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
122
134
  while (true) {
123
135
  new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
124
136
  if (Type === _Model)
125
137
  break;
126
138
  Type = Object.getPrototypeOf(Type);
127
139
  }
128
- const remove = addListener(this, (key) => {
129
- remove();
130
- if (key !== true)
131
- event(this, true);
132
- for (const key2 in this) {
133
- const desc = Object.getOwnPropertyDescriptor(this, key2);
134
- if ("value" in desc) {
135
- state[key2] = desc.value;
136
- define(this, key2, {
137
- enumerable: true,
138
- set: (x) => update(this, key2, x),
139
- get() {
140
- return watch(this, key2, state[key2]);
141
- }
142
- });
143
- }
144
- }
140
+ addListener(this, bootstrap);
141
+ if (typeof arg == "function")
145
142
  addListener(this, () => {
146
- for (const [_, value] of this)
147
- if (value instanceof _Model && PARENT.get(value) === this)
148
- value.set(null);
149
- Object.freeze(state);
150
- }, null);
151
- });
143
+ const cleanup = arg();
144
+ if (typeof cleanup == "function")
145
+ addListener(this, cleanup, null);
146
+ return null;
147
+ });
152
148
  }
153
149
  get(arg1, arg2) {
154
150
  const self = this.is;
@@ -182,13 +178,13 @@ var Model = class _Model {
182
178
  update(self, arg1, arg2, arg3);
183
179
  else
184
180
  push(self, arg1);
185
- if (PENDING.has(self))
181
+ if (PENDING2.has(self))
186
182
  return {
187
183
  then: (res) => new Promise((res2) => {
188
184
  const remove = addListener(self, (key) => {
189
185
  if (key !== true) {
190
186
  remove();
191
- return res2.bind(null, Array.from(PENDING.get(self)));
187
+ return res2.bind(null, Array.from(PENDING2.get(self)));
192
188
  }
193
189
  });
194
190
  }).then(res)
@@ -237,6 +233,29 @@ define(Model, "toString", {
237
233
  return this.name;
238
234
  }
239
235
  });
236
+ function bootstrap() {
237
+ const state = STATE.get(this);
238
+ for (const key in this) {
239
+ const desc = Object.getOwnPropertyDescriptor(this, key);
240
+ if ("value" in desc) {
241
+ state[key] = desc.value;
242
+ define(this, key, {
243
+ configurable: false,
244
+ set: (x) => update(this, key, x),
245
+ get() {
246
+ return watch(this, key, state[key]);
247
+ }
248
+ });
249
+ }
250
+ }
251
+ addListener(this, () => {
252
+ for (const [_, value] of this)
253
+ if (value instanceof Model && PARENT.get(value) === this)
254
+ value.set(null);
255
+ Object.freeze(state);
256
+ }, null);
257
+ return null;
258
+ }
240
259
  function fetch(subject, property, required) {
241
260
  const state = STATE.get(subject);
242
261
  if (property in state || required === false) {
@@ -275,12 +294,12 @@ function update(subject, key, value, silent) {
275
294
  push(subject, key);
276
295
  }
277
296
  function push(subject, key) {
278
- let pending = PENDING.get(subject);
297
+ let pending = PENDING2.get(subject);
279
298
  if (!pending) {
280
- PENDING.set(subject, pending = /* @__PURE__ */ new Set());
299
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
281
300
  queue(() => {
282
301
  event(subject, false);
283
- PENDING.delete(subject);
302
+ PENDING2.delete(subject);
284
303
  });
285
304
  }
286
305
  pending.add(key);
@@ -314,11 +333,10 @@ function add(instruction) {
314
333
  }
315
334
  Model.on((_, subject) => {
316
335
  const state = STATE.get(subject);
317
- for (const key in subject) {
318
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
336
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
319
337
  const instruction = INSTRUCT.get(value);
320
338
  if (!instruction)
321
- continue;
339
+ return;
322
340
  INSTRUCT.delete(value);
323
341
  delete subject[key];
324
342
  const output = instruction.call(subject, key, subject, state);
@@ -352,7 +370,7 @@ Model.on((_, subject) => {
352
370
  );
353
371
  }
354
372
  });
355
- }
373
+ });
356
374
  return null;
357
375
  });
358
376
 
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.6",
32
- "gitHead": "35d41f894a2cb042e7fdbb7fde16ec4eab76efdf"
31
+ "version": "0.36.1",
32
+ "gitHead": "032003a3354af0ad51cb539c6fc37e98697b77dc"
33
33
  }