@expressive/mvc 0.35.5 → 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 +45 -24
- package/dist/index.js +120 -81
- package/dist/index.mjs +120 -81
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,21 +6,21 @@ declare namespace Model {
|
|
|
6
6
|
/** Subset of `keyof T` which are not methods or defined by base Model U. **/
|
|
7
7
|
type Field<T> = Exclude<keyof T, keyof Model>;
|
|
8
8
|
type Key<T> = Field<T> | (string & {}) | number | symbol;
|
|
9
|
-
/** Actual value stored in state. */
|
|
10
|
-
type Actual<R> = R extends Ref<infer T> ? T : R extends Model ? State<R> : R;
|
|
11
9
|
type Value<T extends Model, K extends Key<T>> = K extends keyof T ? T[K] extends Ref<infer V> ? V : T[K] : unknown;
|
|
12
10
|
type OnUpdate<T extends Model, K extends Key<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
|
|
11
|
+
/** Export/Import compatible value for a given property in a Model. */
|
|
12
|
+
type Export<R> = R extends Ref<infer T> ? T : R extends Model ? State<R> : R;
|
|
13
13
|
/**
|
|
14
14
|
* Values from current state of given controller.
|
|
15
15
|
* Differs from `Values` as values here will drill
|
|
16
16
|
* into "real" values held by exotics like ref.
|
|
17
17
|
*/
|
|
18
18
|
type State<T> = {
|
|
19
|
-
[P in Field<T>]:
|
|
19
|
+
[P in Field<T>]: Export<T[P]>;
|
|
20
20
|
};
|
|
21
21
|
/** Object comperable to data found in T. */
|
|
22
22
|
type Values<T> = {
|
|
23
|
-
[P in Field<T>]?:
|
|
23
|
+
[P in Field<T>]?: Export<T[P]>;
|
|
24
24
|
};
|
|
25
25
|
/** Exotic value, where actual value is contained within. */
|
|
26
26
|
type Ref<T = any> = {
|
|
@@ -28,7 +28,7 @@ declare namespace Model {
|
|
|
28
28
|
current: T | null;
|
|
29
29
|
};
|
|
30
30
|
/** A callback function which is subscribed to parent and updates when values change. */
|
|
31
|
-
type Effect<T> = (this: T, argument: T) => (() => void) | Promise<void> | null | void;
|
|
31
|
+
type Effect<T> = (this: T, argument: T) => ((update: boolean | null) => void) | Promise<void> | null | void;
|
|
32
32
|
type Event<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
|
|
33
33
|
namespace Instruction {
|
|
34
34
|
type Getter<T> = (source: Model) => T;
|
|
@@ -54,23 +54,23 @@ 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. */
|
|
61
61
|
get(effect: Model.Effect<this>): () => void;
|
|
62
|
-
/** Check if model is expired. */
|
|
63
|
-
get(event: null): boolean;
|
|
64
|
-
/** Callback when model is to be destroyed. */
|
|
65
|
-
get(event: null, callback: () => void): () => void;
|
|
66
62
|
get<T extends Model.Key<this>>(key: T, required?: boolean): Model.Value<this, T>;
|
|
67
63
|
get<T extends Model.Key<this>>(key: T, callback: Model.OnUpdate<this, T>): () => void;
|
|
64
|
+
/** Check if model is expired. */
|
|
65
|
+
get(status: null): boolean;
|
|
66
|
+
/** Callback when model is to be destroyed. */
|
|
67
|
+
get(status: null, callback: () => void): () => void;
|
|
68
68
|
/**
|
|
69
69
|
* Get update in progress.
|
|
70
70
|
*
|
|
71
71
|
* @returns Promise which resolves object with updated values. Is `undefined` if there is no update.
|
|
72
72
|
**/
|
|
73
|
-
set():
|
|
73
|
+
set(): PromiseLike<Model.Key<this>[]> | undefined;
|
|
74
74
|
/**
|
|
75
75
|
* Call a function when update occurs.
|
|
76
76
|
*
|
|
@@ -84,15 +84,21 @@ declare class Model {
|
|
|
84
84
|
set(callback: Model.Event<this>): () => boolean;
|
|
85
85
|
/**
|
|
86
86
|
* Declare an end to updates. This event is final and will freeze state.
|
|
87
|
+
* This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
|
|
87
88
|
*/
|
|
88
|
-
set(
|
|
89
|
+
set(status: null): void;
|
|
89
90
|
/**
|
|
90
91
|
* Push an update. This will not change the value of associated property.
|
|
91
92
|
*
|
|
92
93
|
* Useful where a property value internally has changed, but the object is the same.
|
|
93
94
|
* For example: An array has pushed a new value, or a nested property is updated.
|
|
95
|
+
*
|
|
96
|
+
* You can also use this to dispatch arbitrary events as well. Symbols are generally
|
|
97
|
+
* recommended as non-property events, however you can use any string. If doing so,
|
|
98
|
+
* be sure to avoid collisions with property names - an easy way to do this will be to
|
|
99
|
+
* prefix your event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
|
|
94
100
|
*/
|
|
95
|
-
set(key: Model.Key<this>):
|
|
101
|
+
set(key: Model.Key<this>): PromiseLike<Model.Key<this>[]>;
|
|
96
102
|
/**
|
|
97
103
|
* Update a property with value.
|
|
98
104
|
*
|
|
@@ -100,7 +106,7 @@ declare class Model {
|
|
|
100
106
|
* @param value - value to update property with (if the same as current, no update will occur)
|
|
101
107
|
* @param silent - if true, will not notify listeners of an update
|
|
102
108
|
*/
|
|
103
|
-
set<K extends string>(key: K, value?: Model.Value<this, K>, silent?: boolean):
|
|
109
|
+
set<K extends string>(key: K, value?: Model.Value<this, K>, silent?: boolean): PromiseLike<Model.Key<this>[]> | undefined;
|
|
104
110
|
/** Iterate over managed properties in this instance of Model. */
|
|
105
111
|
[Symbol.iterator](): Iterator<[string, unknown]>;
|
|
106
112
|
/**
|
|
@@ -276,22 +282,37 @@ declare function use<T extends Model>(model: T, ready?: (i: T) => void): T;
|
|
|
276
282
|
declare function use<T extends {}, O = use.Object<T>>(data: T, ready?: (object: O) => void): O;
|
|
277
283
|
|
|
278
284
|
declare namespace Context {
|
|
279
|
-
type
|
|
280
|
-
[key: string | number]: Model | Model.New;
|
|
281
|
-
};
|
|
285
|
+
type Input = Model | Model.New<Model> | Record<string | number, Model | Model.New<Model>>;
|
|
282
286
|
}
|
|
283
287
|
declare class Context {
|
|
284
|
-
static
|
|
285
|
-
constructor(inputs?: Context.Inputs);
|
|
288
|
+
static get(from: Model, callback: (context: Context) => void): void;
|
|
286
289
|
key: string;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
protected table: WeakMap<Model.Type<Model>, symbol>;
|
|
291
|
+
protected layer: Map<string | number, Model | Model.Type<Model>>;
|
|
292
|
+
constructor(inputs?: Context.Input);
|
|
293
|
+
protected has(T: Model.Type): keyof this;
|
|
290
294
|
get<T extends Model>(Type: Model.Type<T>): T | undefined;
|
|
291
|
-
include(inputs: Context.
|
|
295
|
+
include(inputs: Context.Input): Map<Model, boolean>;
|
|
292
296
|
protected add<T extends Model>(input: T | Model.New<T>, implicit?: boolean): T;
|
|
293
|
-
push(): this;
|
|
297
|
+
push(inputs?: Context.Input): this;
|
|
294
298
|
pop(): void;
|
|
295
299
|
}
|
|
296
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
|
+
|
|
297
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)
|
|
@@ -102,7 +115,7 @@ function effect(target, callback) {
|
|
|
102
115
|
return null;
|
|
103
116
|
expired = true;
|
|
104
117
|
if (refresh && unSet) {
|
|
105
|
-
unSet();
|
|
118
|
+
unSet(true);
|
|
106
119
|
unSet = void 0;
|
|
107
120
|
}
|
|
108
121
|
return refresh;
|
|
@@ -129,9 +142,11 @@ function effect(target, callback) {
|
|
|
129
142
|
else if (!refresh)
|
|
130
143
|
return refresh;
|
|
131
144
|
if (key === null && unSet)
|
|
132
|
-
unSet();
|
|
145
|
+
unSet(null);
|
|
133
146
|
});
|
|
134
147
|
return () => {
|
|
148
|
+
if (unSet)
|
|
149
|
+
unSet(false);
|
|
135
150
|
refresh = null;
|
|
136
151
|
};
|
|
137
152
|
}
|
|
@@ -141,13 +156,18 @@ var ID = /* @__PURE__ */ new WeakMap();
|
|
|
141
156
|
var PARENT = /* @__PURE__ */ new WeakMap();
|
|
142
157
|
var STATE = /* @__PURE__ */ new WeakMap();
|
|
143
158
|
var NOTIFY = /* @__PURE__ */ new WeakMap();
|
|
144
|
-
var
|
|
159
|
+
var PENDING2 = /* @__PURE__ */ new WeakMap();
|
|
145
160
|
var define = Object.defineProperty;
|
|
146
161
|
var Model = class _Model {
|
|
147
162
|
constructor(id) {
|
|
148
163
|
let Type = this.constructor;
|
|
149
164
|
const state = {};
|
|
150
165
|
define(this, "is", { value: this });
|
|
166
|
+
if (typeof id == "function") {
|
|
167
|
+
const cb = id;
|
|
168
|
+
id = void 0;
|
|
169
|
+
this.get(() => cb());
|
|
170
|
+
}
|
|
151
171
|
STATE.set(this, state);
|
|
152
172
|
ID.set(this, id ? String(id) : `${Type}-${uid()}`);
|
|
153
173
|
while (true) {
|
|
@@ -183,36 +203,19 @@ var Model = class _Model {
|
|
|
183
203
|
const self = this.is;
|
|
184
204
|
if (typeof arg1 == "function")
|
|
185
205
|
return effect(self, arg1);
|
|
186
|
-
if (arg1 === void 0)
|
|
187
|
-
|
|
188
|
-
if (value instanceof _Model) {
|
|
189
|
-
if (cache.has(value))
|
|
190
|
-
return cache.get(value);
|
|
191
|
-
const model = value;
|
|
192
|
-
cache.set(value, value = {});
|
|
193
|
-
for (const [key, val] of model)
|
|
194
|
-
value[key] = get3(val);
|
|
195
|
-
}
|
|
196
|
-
return value;
|
|
197
|
-
};
|
|
198
|
-
var get2 = get3;
|
|
199
|
-
const cache = /* @__PURE__ */ new WeakMap();
|
|
200
|
-
return get3(self);
|
|
201
|
-
}
|
|
202
|
-
if (arg1 === null)
|
|
203
|
-
if (typeof arg2 == "function")
|
|
204
|
-
return addListener(self, arg2.bind(this, this), null);
|
|
205
|
-
else
|
|
206
|
-
return Object.isFrozen(STATE.get(self));
|
|
206
|
+
if (arg1 === void 0)
|
|
207
|
+
return extract(self);
|
|
207
208
|
if (typeof arg2 == "function") {
|
|
209
|
+
if (arg1 === null)
|
|
210
|
+
return addListener(self, arg2.bind(this, this), null);
|
|
208
211
|
const state = STATE.get(self);
|
|
209
212
|
if (arg1 in state)
|
|
210
213
|
arg2.call(this, state[arg1], arg1, this);
|
|
211
214
|
return addListener(self, () => {
|
|
212
215
|
arg2.call(this, arg1 in state ? state[arg1] : arg1, arg1, this);
|
|
213
216
|
}, arg1);
|
|
214
|
-
}
|
|
215
|
-
|
|
217
|
+
}
|
|
218
|
+
return arg1 === null ? Object.isFrozen(STATE.get(self)) : fetch(self, arg1, arg2);
|
|
216
219
|
}
|
|
217
220
|
set(arg1, arg2, arg3) {
|
|
218
221
|
const self = this.is;
|
|
@@ -221,18 +224,24 @@ var Model = class _Model {
|
|
|
221
224
|
if (typeof key == "string")
|
|
222
225
|
return arg1.call(self, key, self);
|
|
223
226
|
});
|
|
227
|
+
if (arg1 === null)
|
|
228
|
+
return event(this, null);
|
|
224
229
|
if (arg1 !== void 0)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
if (1 in arguments)
|
|
231
|
+
update(self, arg1, arg2, arg3);
|
|
232
|
+
else
|
|
233
|
+
push(self, arg1);
|
|
234
|
+
if (PENDING2.has(self))
|
|
235
|
+
return {
|
|
236
|
+
then: (res) => new Promise((res2) => {
|
|
237
|
+
const remove = addListener(self, (key) => {
|
|
238
|
+
if (key !== true) {
|
|
239
|
+
remove();
|
|
240
|
+
return res2.bind(null, Array.from(PENDING2.get(self)));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}).then(res)
|
|
244
|
+
};
|
|
236
245
|
}
|
|
237
246
|
/** Iterate over managed properties in this instance of Model. */
|
|
238
247
|
[Symbol.iterator]() {
|
|
@@ -285,14 +294,14 @@ function fetch(subject, property, required) {
|
|
|
285
294
|
return value;
|
|
286
295
|
}
|
|
287
296
|
const error = new Error(`${subject}.${property} is not yet available.`);
|
|
288
|
-
const promise = new Promise((resolve,
|
|
297
|
+
const promise = new Promise((resolve, reject2) => {
|
|
289
298
|
addListener(subject, (key) => {
|
|
290
299
|
if (key === property) {
|
|
291
300
|
resolve(state[key]);
|
|
292
301
|
return null;
|
|
293
302
|
}
|
|
294
303
|
if (key === null)
|
|
295
|
-
|
|
304
|
+
reject2(new Error(`${subject} is destroyed.`));
|
|
296
305
|
});
|
|
297
306
|
});
|
|
298
307
|
throw Object.assign(promise, {
|
|
@@ -304,26 +313,42 @@ function fetch(subject, property, required) {
|
|
|
304
313
|
}
|
|
305
314
|
function update(subject, key, value, silent) {
|
|
306
315
|
const state = STATE.get(subject);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
316
|
+
if (Object.isFrozen(state))
|
|
317
|
+
throw new Error(`Tried to update ${String(key)} but ${subject} is destroyed.`);
|
|
318
|
+
const previous = state[key];
|
|
319
|
+
if (value === previous)
|
|
320
|
+
return true;
|
|
321
|
+
state[key] = value;
|
|
322
|
+
if (silent === true)
|
|
323
|
+
return;
|
|
324
|
+
push(subject, key);
|
|
325
|
+
}
|
|
326
|
+
function push(subject, key) {
|
|
327
|
+
let pending = PENDING2.get(subject);
|
|
328
|
+
if (!pending) {
|
|
329
|
+
PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
|
|
330
|
+
queue(() => {
|
|
331
|
+
event(subject, false);
|
|
332
|
+
PENDING2.delete(subject);
|
|
333
|
+
});
|
|
315
334
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
335
|
+
pending.add(key);
|
|
336
|
+
event(subject, key);
|
|
337
|
+
}
|
|
338
|
+
function extract(from) {
|
|
339
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
340
|
+
function get2(value) {
|
|
341
|
+
if (value instanceof Model) {
|
|
342
|
+
if (cache.has(value))
|
|
343
|
+
return cache.get(value);
|
|
344
|
+
const model = value;
|
|
345
|
+
cache.set(value, value = {});
|
|
346
|
+
for (const [key, val] of model)
|
|
347
|
+
value[key] = get2(val);
|
|
323
348
|
}
|
|
324
|
-
|
|
349
|
+
return value;
|
|
325
350
|
}
|
|
326
|
-
|
|
351
|
+
return get2(from);
|
|
327
352
|
}
|
|
328
353
|
function uid() {
|
|
329
354
|
return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
|
|
@@ -338,11 +363,10 @@ function add(instruction) {
|
|
|
338
363
|
}
|
|
339
364
|
Model.on((_, subject) => {
|
|
340
365
|
const state = STATE.get(subject);
|
|
341
|
-
|
|
342
|
-
const { value } = Object.getOwnPropertyDescriptor(subject, key);
|
|
366
|
+
Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
|
|
343
367
|
const instruction = INSTRUCT.get(value);
|
|
344
368
|
if (!instruction)
|
|
345
|
-
|
|
369
|
+
return;
|
|
346
370
|
INSTRUCT.delete(value);
|
|
347
371
|
delete subject[key];
|
|
348
372
|
const output = instruction.call(subject, key, subject, state);
|
|
@@ -376,7 +400,7 @@ Model.on((_, subject) => {
|
|
|
376
400
|
);
|
|
377
401
|
}
|
|
378
402
|
});
|
|
379
|
-
}
|
|
403
|
+
});
|
|
380
404
|
return null;
|
|
381
405
|
});
|
|
382
406
|
|
|
@@ -388,7 +412,7 @@ var Context = class {
|
|
|
388
412
|
if (inputs)
|
|
389
413
|
this.include(inputs);
|
|
390
414
|
}
|
|
391
|
-
static
|
|
415
|
+
static get() {
|
|
392
416
|
throw new Error(`Using context requires an adapter. If you are only testing, define \`get.context\` to simulate one.`);
|
|
393
417
|
}
|
|
394
418
|
has(T) {
|
|
@@ -407,6 +431,14 @@ var Context = class {
|
|
|
407
431
|
}
|
|
408
432
|
include(inputs) {
|
|
409
433
|
const init = /* @__PURE__ */ new Map();
|
|
434
|
+
if (typeof inputs == "function" || inputs instanceof Model)
|
|
435
|
+
inputs = { [0]: inputs };
|
|
436
|
+
else if (!inputs)
|
|
437
|
+
reject(inputs);
|
|
438
|
+
Object.entries(inputs).forEach(([K, V]) => {
|
|
439
|
+
if (!(Model.is(V) || V instanceof Model))
|
|
440
|
+
reject(`${V} as ${K}`);
|
|
441
|
+
});
|
|
410
442
|
for (const key in inputs) {
|
|
411
443
|
const input = inputs[key];
|
|
412
444
|
const exists = this.layer.get(key);
|
|
@@ -453,9 +485,11 @@ var Context = class {
|
|
|
453
485
|
} while (T !== Model);
|
|
454
486
|
return I;
|
|
455
487
|
}
|
|
456
|
-
push() {
|
|
488
|
+
push(inputs) {
|
|
457
489
|
const next = Object.create(this);
|
|
458
490
|
next.layer = /* @__PURE__ */ new Map();
|
|
491
|
+
if (inputs)
|
|
492
|
+
next.include(inputs);
|
|
459
493
|
return next;
|
|
460
494
|
}
|
|
461
495
|
pop() {
|
|
@@ -471,6 +505,9 @@ var Context = class {
|
|
|
471
505
|
this.layer.clear();
|
|
472
506
|
}
|
|
473
507
|
};
|
|
508
|
+
function reject(argument) {
|
|
509
|
+
throw new Error(`Context can only include Model or instance but got ${argument}.`);
|
|
510
|
+
}
|
|
474
511
|
|
|
475
512
|
// src/instruction/get.ts
|
|
476
513
|
function get(arg0, arg1) {
|
|
@@ -487,7 +524,7 @@ function get(arg0, arg1) {
|
|
|
487
524
|
if (arg1 === true)
|
|
488
525
|
throw new Error(`${from} may only exist as a child of type ${arg0}.`);
|
|
489
526
|
source = (resolve) => {
|
|
490
|
-
Context.
|
|
527
|
+
Context.get(from, (context) => {
|
|
491
528
|
const model = context.get(arg0);
|
|
492
529
|
if (model)
|
|
493
530
|
resolve(model);
|
|
@@ -519,9 +556,11 @@ function compute(subject, key, source, setter) {
|
|
|
519
556
|
compute2(true);
|
|
520
557
|
else if (STALE.delete(compute2))
|
|
521
558
|
compute2();
|
|
522
|
-
return () => {
|
|
523
|
-
|
|
524
|
-
|
|
559
|
+
return (reason) => {
|
|
560
|
+
if (reason) {
|
|
561
|
+
STALE.add(compute2);
|
|
562
|
+
subject.set(key);
|
|
563
|
+
}
|
|
525
564
|
};
|
|
526
565
|
});
|
|
527
566
|
}
|
|
@@ -576,7 +615,7 @@ function ref(arg, arg2) {
|
|
|
576
615
|
else {
|
|
577
616
|
let unSet;
|
|
578
617
|
const set2 = (value2) => {
|
|
579
|
-
if (subject.set(key, value2) || !arg)
|
|
618
|
+
if (!subject.set(key, value2) || !arg)
|
|
580
619
|
return;
|
|
581
620
|
if (unSet)
|
|
582
621
|
unSet = void unSet(value2);
|
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)
|
|
@@ -69,7 +82,7 @@ function effect(target, callback) {
|
|
|
69
82
|
return null;
|
|
70
83
|
expired = true;
|
|
71
84
|
if (refresh && unSet) {
|
|
72
|
-
unSet();
|
|
85
|
+
unSet(true);
|
|
73
86
|
unSet = void 0;
|
|
74
87
|
}
|
|
75
88
|
return refresh;
|
|
@@ -96,9 +109,11 @@ function effect(target, callback) {
|
|
|
96
109
|
else if (!refresh)
|
|
97
110
|
return refresh;
|
|
98
111
|
if (key === null && unSet)
|
|
99
|
-
unSet();
|
|
112
|
+
unSet(null);
|
|
100
113
|
});
|
|
101
114
|
return () => {
|
|
115
|
+
if (unSet)
|
|
116
|
+
unSet(false);
|
|
102
117
|
refresh = null;
|
|
103
118
|
};
|
|
104
119
|
}
|
|
@@ -108,13 +123,18 @@ var ID = /* @__PURE__ */ new WeakMap();
|
|
|
108
123
|
var PARENT = /* @__PURE__ */ new WeakMap();
|
|
109
124
|
var STATE = /* @__PURE__ */ new WeakMap();
|
|
110
125
|
var NOTIFY = /* @__PURE__ */ new WeakMap();
|
|
111
|
-
var
|
|
126
|
+
var PENDING2 = /* @__PURE__ */ new WeakMap();
|
|
112
127
|
var define = Object.defineProperty;
|
|
113
128
|
var Model = class _Model {
|
|
114
129
|
constructor(id) {
|
|
115
130
|
let Type = this.constructor;
|
|
116
131
|
const state = {};
|
|
117
132
|
define(this, "is", { value: this });
|
|
133
|
+
if (typeof id == "function") {
|
|
134
|
+
const cb = id;
|
|
135
|
+
id = void 0;
|
|
136
|
+
this.get(() => cb());
|
|
137
|
+
}
|
|
118
138
|
STATE.set(this, state);
|
|
119
139
|
ID.set(this, id ? String(id) : `${Type}-${uid()}`);
|
|
120
140
|
while (true) {
|
|
@@ -150,36 +170,19 @@ var Model = class _Model {
|
|
|
150
170
|
const self = this.is;
|
|
151
171
|
if (typeof arg1 == "function")
|
|
152
172
|
return effect(self, arg1);
|
|
153
|
-
if (arg1 === void 0)
|
|
154
|
-
|
|
155
|
-
if (value instanceof _Model) {
|
|
156
|
-
if (cache.has(value))
|
|
157
|
-
return cache.get(value);
|
|
158
|
-
const model = value;
|
|
159
|
-
cache.set(value, value = {});
|
|
160
|
-
for (const [key, val] of model)
|
|
161
|
-
value[key] = get3(val);
|
|
162
|
-
}
|
|
163
|
-
return value;
|
|
164
|
-
};
|
|
165
|
-
var get2 = get3;
|
|
166
|
-
const cache = /* @__PURE__ */ new WeakMap();
|
|
167
|
-
return get3(self);
|
|
168
|
-
}
|
|
169
|
-
if (arg1 === null)
|
|
170
|
-
if (typeof arg2 == "function")
|
|
171
|
-
return addListener(self, arg2.bind(this, this), null);
|
|
172
|
-
else
|
|
173
|
-
return Object.isFrozen(STATE.get(self));
|
|
173
|
+
if (arg1 === void 0)
|
|
174
|
+
return extract(self);
|
|
174
175
|
if (typeof arg2 == "function") {
|
|
176
|
+
if (arg1 === null)
|
|
177
|
+
return addListener(self, arg2.bind(this, this), null);
|
|
175
178
|
const state = STATE.get(self);
|
|
176
179
|
if (arg1 in state)
|
|
177
180
|
arg2.call(this, state[arg1], arg1, this);
|
|
178
181
|
return addListener(self, () => {
|
|
179
182
|
arg2.call(this, arg1 in state ? state[arg1] : arg1, arg1, this);
|
|
180
183
|
}, arg1);
|
|
181
|
-
}
|
|
182
|
-
|
|
184
|
+
}
|
|
185
|
+
return arg1 === null ? Object.isFrozen(STATE.get(self)) : fetch(self, arg1, arg2);
|
|
183
186
|
}
|
|
184
187
|
set(arg1, arg2, arg3) {
|
|
185
188
|
const self = this.is;
|
|
@@ -188,18 +191,24 @@ var Model = class _Model {
|
|
|
188
191
|
if (typeof key == "string")
|
|
189
192
|
return arg1.call(self, key, self);
|
|
190
193
|
});
|
|
194
|
+
if (arg1 === null)
|
|
195
|
+
return event(this, null);
|
|
191
196
|
if (arg1 !== void 0)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
if (1 in arguments)
|
|
198
|
+
update(self, arg1, arg2, arg3);
|
|
199
|
+
else
|
|
200
|
+
push(self, arg1);
|
|
201
|
+
if (PENDING2.has(self))
|
|
202
|
+
return {
|
|
203
|
+
then: (res) => new Promise((res2) => {
|
|
204
|
+
const remove = addListener(self, (key) => {
|
|
205
|
+
if (key !== true) {
|
|
206
|
+
remove();
|
|
207
|
+
return res2.bind(null, Array.from(PENDING2.get(self)));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}).then(res)
|
|
211
|
+
};
|
|
203
212
|
}
|
|
204
213
|
/** Iterate over managed properties in this instance of Model. */
|
|
205
214
|
[Symbol.iterator]() {
|
|
@@ -252,14 +261,14 @@ function fetch(subject, property, required) {
|
|
|
252
261
|
return value;
|
|
253
262
|
}
|
|
254
263
|
const error = new Error(`${subject}.${property} is not yet available.`);
|
|
255
|
-
const promise = new Promise((resolve,
|
|
264
|
+
const promise = new Promise((resolve, reject2) => {
|
|
256
265
|
addListener(subject, (key) => {
|
|
257
266
|
if (key === property) {
|
|
258
267
|
resolve(state[key]);
|
|
259
268
|
return null;
|
|
260
269
|
}
|
|
261
270
|
if (key === null)
|
|
262
|
-
|
|
271
|
+
reject2(new Error(`${subject} is destroyed.`));
|
|
263
272
|
});
|
|
264
273
|
});
|
|
265
274
|
throw Object.assign(promise, {
|
|
@@ -271,26 +280,42 @@ function fetch(subject, property, required) {
|
|
|
271
280
|
}
|
|
272
281
|
function update(subject, key, value, silent) {
|
|
273
282
|
const state = STATE.get(subject);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
if (Object.isFrozen(state))
|
|
284
|
+
throw new Error(`Tried to update ${String(key)} but ${subject} is destroyed.`);
|
|
285
|
+
const previous = state[key];
|
|
286
|
+
if (value === previous)
|
|
287
|
+
return true;
|
|
288
|
+
state[key] = value;
|
|
289
|
+
if (silent === true)
|
|
290
|
+
return;
|
|
291
|
+
push(subject, key);
|
|
292
|
+
}
|
|
293
|
+
function push(subject, key) {
|
|
294
|
+
let pending = PENDING2.get(subject);
|
|
295
|
+
if (!pending) {
|
|
296
|
+
PENDING2.set(subject, pending = /* @__PURE__ */ new Set());
|
|
297
|
+
queue(() => {
|
|
298
|
+
event(subject, false);
|
|
299
|
+
PENDING2.delete(subject);
|
|
300
|
+
});
|
|
282
301
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
302
|
+
pending.add(key);
|
|
303
|
+
event(subject, key);
|
|
304
|
+
}
|
|
305
|
+
function extract(from) {
|
|
306
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
307
|
+
function get2(value) {
|
|
308
|
+
if (value instanceof Model) {
|
|
309
|
+
if (cache.has(value))
|
|
310
|
+
return cache.get(value);
|
|
311
|
+
const model = value;
|
|
312
|
+
cache.set(value, value = {});
|
|
313
|
+
for (const [key, val] of model)
|
|
314
|
+
value[key] = get2(val);
|
|
290
315
|
}
|
|
291
|
-
|
|
316
|
+
return value;
|
|
292
317
|
}
|
|
293
|
-
|
|
318
|
+
return get2(from);
|
|
294
319
|
}
|
|
295
320
|
function uid() {
|
|
296
321
|
return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
|
|
@@ -305,11 +330,10 @@ function add(instruction) {
|
|
|
305
330
|
}
|
|
306
331
|
Model.on((_, subject) => {
|
|
307
332
|
const state = STATE.get(subject);
|
|
308
|
-
|
|
309
|
-
const { value } = Object.getOwnPropertyDescriptor(subject, key);
|
|
333
|
+
Object.entries(Object.getOwnPropertyDescriptors(subject)).forEach(([key, { value }]) => {
|
|
310
334
|
const instruction = INSTRUCT.get(value);
|
|
311
335
|
if (!instruction)
|
|
312
|
-
|
|
336
|
+
return;
|
|
313
337
|
INSTRUCT.delete(value);
|
|
314
338
|
delete subject[key];
|
|
315
339
|
const output = instruction.call(subject, key, subject, state);
|
|
@@ -343,7 +367,7 @@ Model.on((_, subject) => {
|
|
|
343
367
|
);
|
|
344
368
|
}
|
|
345
369
|
});
|
|
346
|
-
}
|
|
370
|
+
});
|
|
347
371
|
return null;
|
|
348
372
|
});
|
|
349
373
|
|
|
@@ -355,7 +379,7 @@ var Context = class {
|
|
|
355
379
|
if (inputs)
|
|
356
380
|
this.include(inputs);
|
|
357
381
|
}
|
|
358
|
-
static
|
|
382
|
+
static get() {
|
|
359
383
|
throw new Error(`Using context requires an adapter. If you are only testing, define \`get.context\` to simulate one.`);
|
|
360
384
|
}
|
|
361
385
|
has(T) {
|
|
@@ -374,6 +398,14 @@ var Context = class {
|
|
|
374
398
|
}
|
|
375
399
|
include(inputs) {
|
|
376
400
|
const init = /* @__PURE__ */ new Map();
|
|
401
|
+
if (typeof inputs == "function" || inputs instanceof Model)
|
|
402
|
+
inputs = { [0]: inputs };
|
|
403
|
+
else if (!inputs)
|
|
404
|
+
reject(inputs);
|
|
405
|
+
Object.entries(inputs).forEach(([K, V]) => {
|
|
406
|
+
if (!(Model.is(V) || V instanceof Model))
|
|
407
|
+
reject(`${V} as ${K}`);
|
|
408
|
+
});
|
|
377
409
|
for (const key in inputs) {
|
|
378
410
|
const input = inputs[key];
|
|
379
411
|
const exists = this.layer.get(key);
|
|
@@ -420,9 +452,11 @@ var Context = class {
|
|
|
420
452
|
} while (T !== Model);
|
|
421
453
|
return I;
|
|
422
454
|
}
|
|
423
|
-
push() {
|
|
455
|
+
push(inputs) {
|
|
424
456
|
const next = Object.create(this);
|
|
425
457
|
next.layer = /* @__PURE__ */ new Map();
|
|
458
|
+
if (inputs)
|
|
459
|
+
next.include(inputs);
|
|
426
460
|
return next;
|
|
427
461
|
}
|
|
428
462
|
pop() {
|
|
@@ -438,6 +472,9 @@ var Context = class {
|
|
|
438
472
|
this.layer.clear();
|
|
439
473
|
}
|
|
440
474
|
};
|
|
475
|
+
function reject(argument) {
|
|
476
|
+
throw new Error(`Context can only include Model or instance but got ${argument}.`);
|
|
477
|
+
}
|
|
441
478
|
|
|
442
479
|
// src/instruction/get.ts
|
|
443
480
|
function get(arg0, arg1) {
|
|
@@ -454,7 +491,7 @@ function get(arg0, arg1) {
|
|
|
454
491
|
if (arg1 === true)
|
|
455
492
|
throw new Error(`${from} may only exist as a child of type ${arg0}.`);
|
|
456
493
|
source = (resolve) => {
|
|
457
|
-
Context.
|
|
494
|
+
Context.get(from, (context) => {
|
|
458
495
|
const model = context.get(arg0);
|
|
459
496
|
if (model)
|
|
460
497
|
resolve(model);
|
|
@@ -486,9 +523,11 @@ function compute(subject, key, source, setter) {
|
|
|
486
523
|
compute2(true);
|
|
487
524
|
else if (STALE.delete(compute2))
|
|
488
525
|
compute2();
|
|
489
|
-
return () => {
|
|
490
|
-
|
|
491
|
-
|
|
526
|
+
return (reason) => {
|
|
527
|
+
if (reason) {
|
|
528
|
+
STALE.add(compute2);
|
|
529
|
+
subject.set(key);
|
|
530
|
+
}
|
|
492
531
|
};
|
|
493
532
|
});
|
|
494
533
|
}
|
|
@@ -543,7 +582,7 @@ function ref(arg, arg2) {
|
|
|
543
582
|
else {
|
|
544
583
|
let unSet;
|
|
545
584
|
const set2 = (value2) => {
|
|
546
|
-
if (subject.set(key, value2) || !arg)
|
|
585
|
+
if (!subject.set(key, value2) || !arg)
|
|
547
586
|
return;
|
|
548
587
|
if (unSet)
|
|
549
588
|
unSet = void unSet(value2);
|
package/package.json
CHANGED