@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 +18 -1
- package/dist/index.js +69 -51
- package/dist/index.mjs +69 -51
- package/package.json +2 -2
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(
|
|
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(
|
|
42
|
-
let subs = LISTENER.get(
|
|
41
|
+
function addListener(subject, callback, select) {
|
|
42
|
+
let subs = LISTENER.get(subject);
|
|
43
43
|
if (!subs)
|
|
44
|
-
LISTENER.set(
|
|
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
|
-
|
|
48
|
-
subs.set(
|
|
49
|
-
return () => subs.delete(
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
159
|
+
var PENDING2 = /* @__PURE__ */ new WeakMap();
|
|
147
160
|
var define = Object.defineProperty;
|
|
148
161
|
var Model = class _Model {
|
|
149
|
-
constructor(
|
|
162
|
+
constructor(arg) {
|
|
150
163
|
let Type = this.constructor;
|
|
151
|
-
const state = {};
|
|
152
164
|
define(this, "is", { value: this });
|
|
153
|
-
STATE.set(this,
|
|
154
|
-
ID.set(this,
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
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 (
|
|
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(
|
|
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 =
|
|
330
|
+
let pending = PENDING2.get(subject);
|
|
312
331
|
if (!pending) {
|
|
313
|
-
|
|
332
|
+
PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
|
|
314
333
|
queue(() => {
|
|
315
334
|
event(subject, false);
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
9
|
-
let subs = LISTENER.get(
|
|
8
|
+
function addListener(subject, callback, select) {
|
|
9
|
+
let subs = LISTENER.get(subject);
|
|
10
10
|
if (!subs)
|
|
11
|
-
LISTENER.set(
|
|
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
|
-
|
|
15
|
-
subs.set(
|
|
16
|
-
return () => subs.delete(
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
126
|
+
var PENDING2 = /* @__PURE__ */ new WeakMap();
|
|
114
127
|
var define = Object.defineProperty;
|
|
115
128
|
var Model = class _Model {
|
|
116
|
-
constructor(
|
|
129
|
+
constructor(arg) {
|
|
117
130
|
let Type = this.constructor;
|
|
118
|
-
const state = {};
|
|
119
131
|
define(this, "is", { value: this });
|
|
120
|
-
STATE.set(this,
|
|
121
|
-
ID.set(this,
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
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 (
|
|
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(
|
|
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 =
|
|
297
|
+
let pending = PENDING2.get(subject);
|
|
279
298
|
if (!pending) {
|
|
280
|
-
|
|
299
|
+
PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
|
|
281
300
|
queue(() => {
|
|
282
301
|
event(subject, false);
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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