@expressive/mvc 0.35.6 → 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
@@ -54,7 +54,7 @@ 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. */
@@ -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,13 +156,18 @@ 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
162
  constructor(id) {
150
163
  let Type = this.constructor;
151
164
  const state = {};
152
165
  define(this, "is", { value: this });
166
+ if (typeof id == "function") {
167
+ const cb = id;
168
+ id = void 0;
169
+ this.get(() => cb());
170
+ }
153
171
  STATE.set(this, state);
154
172
  ID.set(this, id ? String(id) : `${Type}-${uid()}`);
155
173
  while (true) {
@@ -158,19 +176,16 @@ var Model = class _Model {
158
176
  break;
159
177
  Type = Object.getPrototypeOf(Type);
160
178
  }
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);
179
+ addListener(this, () => {
180
+ for (const key in this) {
181
+ const desc = Object.getOwnPropertyDescriptor(this, key);
167
182
  if ("value" in desc) {
168
- state[key2] = desc.value;
169
- define(this, key2, {
183
+ state[key] = desc.value;
184
+ define(this, key, {
170
185
  enumerable: true,
171
- set: (x) => update(this, key2, x),
186
+ set: (x) => update(this, key, x),
172
187
  get() {
173
- return watch(this, key2, state[key2]);
188
+ return watch(this, key, state[key]);
174
189
  }
175
190
  });
176
191
  }
@@ -181,6 +196,7 @@ var Model = class _Model {
181
196
  value.set(null);
182
197
  Object.freeze(state);
183
198
  }, null);
199
+ return null;
184
200
  });
185
201
  }
186
202
  get(arg1, arg2) {
@@ -215,13 +231,13 @@ var Model = class _Model {
215
231
  update(self, arg1, arg2, arg3);
216
232
  else
217
233
  push(self, arg1);
218
- if (PENDING.has(self))
234
+ if (PENDING2.has(self))
219
235
  return {
220
236
  then: (res) => new Promise((res2) => {
221
237
  const remove = addListener(self, (key) => {
222
238
  if (key !== true) {
223
239
  remove();
224
- return res2.bind(null, Array.from(PENDING.get(self)));
240
+ return res2.bind(null, Array.from(PENDING2.get(self)));
225
241
  }
226
242
  });
227
243
  }).then(res)
@@ -308,12 +324,12 @@ function update(subject, key, value, silent) {
308
324
  push(subject, key);
309
325
  }
310
326
  function push(subject, key) {
311
- let pending = PENDING.get(subject);
327
+ let pending = PENDING2.get(subject);
312
328
  if (!pending) {
313
- PENDING.set(subject, pending = /* @__PURE__ */ new Set());
329
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
314
330
  queue(() => {
315
331
  event(subject, false);
316
- PENDING.delete(subject);
332
+ PENDING2.delete(subject);
317
333
  });
318
334
  }
319
335
  pending.add(key);
@@ -347,11 +363,10 @@ function add(instruction) {
347
363
  }
348
364
  Model.on((_, subject) => {
349
365
  const state = STATE.get(subject);
350
- for (const key in subject) {
351
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
366
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
352
367
  const instruction = INSTRUCT.get(value);
353
368
  if (!instruction)
354
- continue;
369
+ return;
355
370
  INSTRUCT.delete(value);
356
371
  delete subject[key];
357
372
  const output = instruction.call(subject, key, subject, state);
@@ -385,7 +400,7 @@ Model.on((_, subject) => {
385
400
  );
386
401
  }
387
402
  });
388
- }
403
+ });
389
404
  return null;
390
405
  });
391
406
 
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,13 +123,18 @@ 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
129
  constructor(id) {
117
130
  let Type = this.constructor;
118
131
  const state = {};
119
132
  define(this, "is", { value: this });
133
+ if (typeof id == "function") {
134
+ const cb = id;
135
+ id = void 0;
136
+ this.get(() => cb());
137
+ }
120
138
  STATE.set(this, state);
121
139
  ID.set(this, id ? String(id) : `${Type}-${uid()}`);
122
140
  while (true) {
@@ -125,19 +143,16 @@ var Model = class _Model {
125
143
  break;
126
144
  Type = Object.getPrototypeOf(Type);
127
145
  }
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);
146
+ addListener(this, () => {
147
+ for (const key in this) {
148
+ const desc = Object.getOwnPropertyDescriptor(this, key);
134
149
  if ("value" in desc) {
135
- state[key2] = desc.value;
136
- define(this, key2, {
150
+ state[key] = desc.value;
151
+ define(this, key, {
137
152
  enumerable: true,
138
- set: (x) => update(this, key2, x),
153
+ set: (x) => update(this, key, x),
139
154
  get() {
140
- return watch(this, key2, state[key2]);
155
+ return watch(this, key, state[key]);
141
156
  }
142
157
  });
143
158
  }
@@ -148,6 +163,7 @@ var Model = class _Model {
148
163
  value.set(null);
149
164
  Object.freeze(state);
150
165
  }, null);
166
+ return null;
151
167
  });
152
168
  }
153
169
  get(arg1, arg2) {
@@ -182,13 +198,13 @@ var Model = class _Model {
182
198
  update(self, arg1, arg2, arg3);
183
199
  else
184
200
  push(self, arg1);
185
- if (PENDING.has(self))
201
+ if (PENDING2.has(self))
186
202
  return {
187
203
  then: (res) => new Promise((res2) => {
188
204
  const remove = addListener(self, (key) => {
189
205
  if (key !== true) {
190
206
  remove();
191
- return res2.bind(null, Array.from(PENDING.get(self)));
207
+ return res2.bind(null, Array.from(PENDING2.get(self)));
192
208
  }
193
209
  });
194
210
  }).then(res)
@@ -275,12 +291,12 @@ function update(subject, key, value, silent) {
275
291
  push(subject, key);
276
292
  }
277
293
  function push(subject, key) {
278
- let pending = PENDING.get(subject);
294
+ let pending = PENDING2.get(subject);
279
295
  if (!pending) {
280
- PENDING.set(subject, pending = /* @__PURE__ */ new Set());
296
+ PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
281
297
  queue(() => {
282
298
  event(subject, false);
283
- PENDING.delete(subject);
299
+ PENDING2.delete(subject);
284
300
  });
285
301
  }
286
302
  pending.add(key);
@@ -314,11 +330,10 @@ function add(instruction) {
314
330
  }
315
331
  Model.on((_, subject) => {
316
332
  const state = STATE.get(subject);
317
- for (const key in subject) {
318
- const { value } = Object.getOwnPropertyDescriptor(subject, key);
333
+ Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
319
334
  const instruction = INSTRUCT.get(value);
320
335
  if (!instruction)
321
- continue;
336
+ return;
322
337
  INSTRUCT.delete(value);
323
338
  delete subject[key];
324
339
  const output = instruction.call(subject, key, subject, state);
@@ -352,7 +367,7 @@ Model.on((_, subject) => {
352
367
  );
353
368
  }
354
369
  });
355
- }
370
+ });
356
371
  return null;
357
372
  });
358
373
 
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.0",
32
+ "gitHead": "09b42668a6cdd0eb10be56985e0b72bbead26a88"
33
33
  }