@expressive/mvc 0.35.1 → 0.35.2

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
@@ -1,4 +1,3 @@
1
- type Predicate = (key: string) => boolean | void;
2
1
  declare namespace Model {
3
2
  /** Any type of Model, using own class constructor as its identifier. */
4
3
  type Type<T extends Model = Model> = abstract new (...args: any[]) => T;
@@ -10,6 +9,7 @@ declare namespace Model {
10
9
  /** Actual value stored in state. */
11
10
  type Value<R> = R extends Ref<infer T> ? T : R extends Model ? Export<R> : R;
12
11
  type ValueOf<T extends Model, K extends Any<T>> = K extends keyof T ? T[K] extends Ref<infer V> ? V : T[K] : unknown;
12
+ type ValueCallback<T extends Model, K extends Any<T>> = (this: T, value: Model.ValueOf<T, K>, key: K, thisArg: K) => void;
13
13
  /**
14
14
  * Values from current state of given controller.
15
15
  * Differs from `Values` as values here will drill
@@ -29,7 +29,7 @@ declare namespace Model {
29
29
  };
30
30
  /** A callback function which is subscribed to parent and updates when values change. */
31
31
  type Effect<T> = (this: T, argument: T) => (() => void) | Promise<void> | null | void;
32
- type Event = (this: Model, key: string, value: unknown) => (() => void) | void;
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;
35
35
  type Setter<T> = (value: T, previous: T) => boolean | void | (() => T);
@@ -59,40 +59,32 @@ declare class Model {
59
59
  get(): Model.Export<this>;
60
60
  /** Run a function which will run automatically when accessed values change. */
61
61
  get(effect: Model.Effect<this>): () => void;
62
- get<T extends string>(key: T, required: true): Exclude<Model.ValueOf<this, T>, undefined>;
63
62
  get<T extends string>(key: T, required?: boolean): Model.ValueOf<this, T>;
63
+ get<T extends string>(key: T, callback: Model.ValueCallback<this, T>): () => void;
64
64
  /**
65
- * Check if update is in progress.
66
- * Returns a promise which resolves updated keys. If no update, will resolve to `false`.
67
- **/
68
- set(): Promise<Model.Values<this> | false>;
69
- /**
70
- * Expect an update to state within a set time.
71
- *
72
- * @param timeout - milliseconds to wait for update
73
- * @param predicate - optional function to filter for specific updates. If returns true, update is accepted.
65
+ * Get update in progress.
74
66
  *
75
- * @returns a promise to resolve when next accepted update has occured.
76
- */
77
- set(timeout: number, predicate?: Predicate): Promise<Model.Values<this>>;
67
+ * @returns a promise which resolves object with updated values. Is `undefined` if no update.
68
+ **/
69
+ set(): Promise<Model.Values<this>> | undefined;
78
70
  /**
79
71
  * Call a function whenever an update occurs.
80
72
  *
81
73
  * @param callback
82
74
  * Note: This will be called for every assignment which changes the value.
83
75
  * To run logic on final value only, this callback may return a function.
84
- * Returning the same function for a key will ensure it is only called once.
76
+ * Returning the same function for one or more will ensure it is only called once.
85
77
  *
86
- * @returns a function to remove listener. Returns true if listener isn't removed already.
78
+ * @returns a function to remove listener. Will return `true` if removed, `false` if inactive already.
87
79
  */
88
- set(callback: Model.Event): () => boolean;
80
+ set(callback: Model.Event<this>): () => boolean;
89
81
  /**
90
82
  * Push an update without changing the value of associated property.
91
83
  *
92
84
  * This is useful where a property value internally has changed, but the object remains the same.
93
85
  * For example: An array which has pushed a new value, or a change to nested property.
94
86
  */
95
- set(key: string): void;
87
+ set(key: string | symbol): void;
96
88
  /**
97
89
  * Declare an end to updates. This event will freeze state.
98
90
  */
@@ -122,9 +114,10 @@ declare class Model {
122
114
  * methods and properties of this class.
123
115
  */
124
116
  static is<T extends Model.Type>(this: T, maybe: any): maybe is T;
117
+ static on<T extends Model>(this: Model.Type<T>, event: Model.Event<T>): () => boolean;
125
118
  }
126
119
 
127
- declare function add<T = any, M extends Model = any>(instruction: Model.Instruction): T;
120
+ declare function add<T = any>(instruction: Model.Instruction): T;
128
121
 
129
122
  declare namespace get {
130
123
  type Function<T, S = any> = (this: S, on: S) => T;
package/dist/index.js CHANGED
@@ -41,10 +41,10 @@ function onReady() {
41
41
  function addListener(to, fn) {
42
42
  let subs = LISTENER.get(to);
43
43
  if (!subs)
44
- LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, null));
44
+ LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, void 0));
45
45
  if (!subs.has(onReady))
46
46
  fn(true, to);
47
- subs.set(fn, null);
47
+ subs.set(fn, void 0);
48
48
  return () => subs.delete(fn);
49
49
  }
50
50
  function watch(from, key, value) {
@@ -138,28 +138,32 @@ function effect(target, callback) {
138
138
  // src/model.ts
139
139
  var ID = /* @__PURE__ */ new WeakMap();
140
140
  var PARENT = /* @__PURE__ */ new WeakMap();
141
- var INSTRUCT = /* @__PURE__ */ new Map();
142
- var PENDING = /* @__PURE__ */ new WeakMap();
143
141
  var STATE = /* @__PURE__ */ new WeakMap();
142
+ var NOTIFY = /* @__PURE__ */ new WeakMap();
143
+ var PENDING = /* @__PURE__ */ new WeakMap();
144
144
  var define = Object.defineProperty;
145
145
  var Model = class _Model {
146
146
  constructor(id) {
147
+ let Type = this.constructor;
147
148
  const state = {};
148
149
  define(this, "is", { value: this });
149
- ID.set(this, `${this.constructor}-${id ? String(id) : uid()}`);
150
150
  STATE.set(this, state);
151
+ ID.set(this, `${Type}-${id ? String(id) : uid()}`);
151
152
  function onNull(key) {
152
153
  if (key === null)
153
154
  Object.freeze(state);
154
155
  }
156
+ while (true) {
157
+ new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
158
+ if (Type === _Model)
159
+ break;
160
+ Type = Object.getPrototypeOf(Type);
161
+ }
155
162
  addListener(this, () => {
156
163
  for (const key in this) {
157
- const property = Object.getOwnPropertyDescriptor(this, key);
158
- const instruction = INSTRUCT.get(property.value);
159
- if (instruction)
160
- instruction(this, key, state);
161
- else {
162
- state[key] = property.value;
164
+ const desc = Object.getOwnPropertyDescriptor(this, key);
165
+ if ("value" in desc) {
166
+ state[key] = desc.value;
163
167
  define(this, key, {
164
168
  enumerable: true,
165
169
  set: (x) => update(this, key, x),
@@ -176,7 +180,16 @@ var Model = class _Model {
176
180
  get(arg1, arg2) {
177
181
  const self = this.is;
178
182
  if (typeof arg1 == "string")
179
- return fetch(self, arg1, arg2);
183
+ if (typeof arg2 == "function") {
184
+ const state = STATE.get(self);
185
+ if (arg1 in state)
186
+ arg2.call(this, state[arg1], arg1, this);
187
+ return addListener(self, (key) => {
188
+ if (key === arg1)
189
+ arg2.call(this, arg1 in state ? state[key] : key, key, this);
190
+ });
191
+ } else
192
+ return fetch(self, arg1, arg2);
180
193
  if (arg1)
181
194
  return effect(self, arg1);
182
195
  const cache = /* @__PURE__ */ new WeakMap();
@@ -198,27 +211,19 @@ var Model = class _Model {
198
211
  if (typeof arg1 == "function")
199
212
  return addListener(self, (key) => {
200
213
  if (typeof key == "string")
201
- return arg1.call(this, key, STATE.get(self)[key]);
214
+ return arg1.call(self, key, self);
202
215
  });
203
- if (typeof arg1 == "string" || arg1 === null)
216
+ if (arg1 !== void 0)
204
217
  return 1 in arguments ? update(self, arg1, arg2, arg3) : update(self, arg1);
205
- return new Promise((resolve, reject) => {
206
- if (typeof arg1 != "number" && !PENDING.has(self)) {
207
- resolve(false);
208
- return;
209
- }
218
+ if (!PENDING.has(self))
219
+ return;
220
+ return new Promise((resolve) => {
210
221
  const remove = addListener(this, (key) => {
211
- if (key === true || typeof arg2 == "function" && typeof key == "string" && arg2(key) !== true)
212
- return;
213
- if (timeout)
214
- clearTimeout(timeout);
215
- remove();
216
- return resolve.bind(null, PENDING.get(self));
222
+ if (key !== true) {
223
+ remove();
224
+ return resolve.bind(null, PENDING.get(self));
225
+ }
217
226
  });
218
- const timeout = typeof arg1 == "number" && setTimeout(() => {
219
- remove();
220
- reject(arg1);
221
- }, arg1);
222
227
  });
223
228
  }
224
229
  /** Iterate over managed properties in this instance of Model. */
@@ -246,6 +251,13 @@ var Model = class _Model {
246
251
  static is(maybe) {
247
252
  return typeof maybe == "function" && maybe.prototype instanceof this;
248
253
  }
254
+ static on(event2) {
255
+ let notify = NOTIFY.get(this);
256
+ if (!notify)
257
+ NOTIFY.set(this, notify = /* @__PURE__ */ new Set());
258
+ notify.add(event2);
259
+ return () => notify.delete(event2);
260
+ }
249
261
  };
250
262
  define(Model.prototype, "toString", {
251
263
  value() {
@@ -285,15 +297,15 @@ function fetch(subject, property, required) {
285
297
  function update(subject, key, value, silent) {
286
298
  const state = STATE.get(subject);
287
299
  let pending = PENDING.get(subject);
288
- if (typeof key == "string") {
289
- if (2 in arguments) {
290
- const previous = state[key];
291
- if (value === previous)
292
- return true;
293
- state[key] = value;
294
- if (silent === true)
295
- return;
296
- }
300
+ if (typeof key == "string" && 2 in arguments) {
301
+ const previous = state[key];
302
+ if (value === previous)
303
+ return true;
304
+ state[key] = value;
305
+ if (silent === true)
306
+ return;
307
+ }
308
+ if (key !== true && key) {
297
309
  if (!pending) {
298
310
  PENDING.set(subject, pending = {});
299
311
  queue(() => {
@@ -301,7 +313,7 @@ function update(subject, key, value, silent) {
301
313
  PENDING.delete(subject);
302
314
  });
303
315
  }
304
- pending[key] = value;
316
+ pending[key] = key in state ? value : true;
305
317
  }
306
318
  event(subject, key);
307
319
  }
@@ -310,10 +322,20 @@ function uid() {
310
322
  }
311
323
 
312
324
  // src/instruction/add.ts
325
+ var INSTRUCT = /* @__PURE__ */ new Map();
313
326
  function add(instruction) {
314
- const placeholder = Symbol("instruction");
315
- INSTRUCT.set(placeholder, (subject, key, state) => {
316
- INSTRUCT.delete(placeholder);
327
+ const token = Symbol("instruction");
328
+ INSTRUCT.set(token, instruction);
329
+ return token;
330
+ }
331
+ Model.on((_, subject) => {
332
+ const state = STATE.get(subject);
333
+ for (const key in subject) {
334
+ const { value } = Object.getOwnPropertyDescriptor(subject, key);
335
+ const instruction = INSTRUCT.get(value);
336
+ if (!instruction)
337
+ continue;
338
+ INSTRUCT.delete(value);
317
339
  delete subject[key];
318
340
  const output = instruction.call(subject, key, subject, state);
319
341
  if (!output)
@@ -346,9 +368,9 @@ function add(instruction) {
346
368
  );
347
369
  }
348
370
  });
349
- });
350
- return placeholder;
351
- }
371
+ }
372
+ return null;
373
+ });
352
374
 
353
375
  // src/context.ts
354
376
  var Context = class {
@@ -416,8 +438,8 @@ var Context = class {
416
438
  if (value || I !== this[key] && !implicit)
417
439
  Object.defineProperty(this, key, {
418
440
  configurable: true,
419
- value,
420
- writable
441
+ writable,
442
+ value
421
443
  });
422
444
  T = Object.getPrototypeOf(T);
423
445
  } while (T !== Model);
@@ -524,24 +546,23 @@ function ref(arg, arg2) {
524
546
  return add((key, subject, state) => {
525
547
  let value = {};
526
548
  if (arg === subject)
527
- for (const key2 in state)
528
- if (typeof arg2 == "function")
529
- define2(value, key2, {
530
- configurable: true,
531
- get() {
532
- const out = arg2(key2);
533
- define2(value, key2, { value: out });
534
- return out;
535
- }
536
- });
537
- else {
538
- const ref2 = (value2) => subject.set(key2, value2);
539
- define2(ref2, "current", {
540
- get: () => state[key2],
541
- set: ref2
542
- });
543
- define2(value, key2, { value: ref2 });
544
- }
549
+ subject.get(() => {
550
+ for (const key2 in state)
551
+ if (typeof arg2 == "function")
552
+ define2(value, key2, {
553
+ configurable: true,
554
+ get() {
555
+ const out = arg2(key2);
556
+ define2(value, key2, { value: out });
557
+ return out;
558
+ }
559
+ });
560
+ else {
561
+ const set2 = (value2) => subject.set(key2, value2);
562
+ define2(set2, "current", { get: () => state[key2], set: set2 });
563
+ define2(value, key2, { value: set2 });
564
+ }
565
+ });
545
566
  else if (typeof arg == "object")
546
567
  throw new Error("ref instruction does not support object which is not 'this'");
547
568
  else {
package/dist/index.mjs CHANGED
@@ -8,10 +8,10 @@ function onReady() {
8
8
  function addListener(to, fn) {
9
9
  let subs = LISTENER.get(to);
10
10
  if (!subs)
11
- LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, null));
11
+ LISTENER.set(to, subs = (/* @__PURE__ */ new Map()).set(onReady, void 0));
12
12
  if (!subs.has(onReady))
13
13
  fn(true, to);
14
- subs.set(fn, null);
14
+ subs.set(fn, void 0);
15
15
  return () => subs.delete(fn);
16
16
  }
17
17
  function watch(from, key, value) {
@@ -105,28 +105,32 @@ function effect(target, callback) {
105
105
  // src/model.ts
106
106
  var ID = /* @__PURE__ */ new WeakMap();
107
107
  var PARENT = /* @__PURE__ */ new WeakMap();
108
- var INSTRUCT = /* @__PURE__ */ new Map();
109
- var PENDING = /* @__PURE__ */ new WeakMap();
110
108
  var STATE = /* @__PURE__ */ new WeakMap();
109
+ var NOTIFY = /* @__PURE__ */ new WeakMap();
110
+ var PENDING = /* @__PURE__ */ new WeakMap();
111
111
  var define = Object.defineProperty;
112
112
  var Model = class _Model {
113
113
  constructor(id) {
114
+ let Type = this.constructor;
114
115
  const state = {};
115
116
  define(this, "is", { value: this });
116
- ID.set(this, `${this.constructor}-${id ? String(id) : uid()}`);
117
117
  STATE.set(this, state);
118
+ ID.set(this, `${Type}-${id ? String(id) : uid()}`);
118
119
  function onNull(key) {
119
120
  if (key === null)
120
121
  Object.freeze(state);
121
122
  }
123
+ while (true) {
124
+ new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
125
+ if (Type === _Model)
126
+ break;
127
+ Type = Object.getPrototypeOf(Type);
128
+ }
122
129
  addListener(this, () => {
123
130
  for (const key in this) {
124
- const property = Object.getOwnPropertyDescriptor(this, key);
125
- const instruction = INSTRUCT.get(property.value);
126
- if (instruction)
127
- instruction(this, key, state);
128
- else {
129
- state[key] = property.value;
131
+ const desc = Object.getOwnPropertyDescriptor(this, key);
132
+ if ("value" in desc) {
133
+ state[key] = desc.value;
130
134
  define(this, key, {
131
135
  enumerable: true,
132
136
  set: (x) => update(this, key, x),
@@ -143,7 +147,16 @@ var Model = class _Model {
143
147
  get(arg1, arg2) {
144
148
  const self = this.is;
145
149
  if (typeof arg1 == "string")
146
- return fetch(self, arg1, arg2);
150
+ if (typeof arg2 == "function") {
151
+ const state = STATE.get(self);
152
+ if (arg1 in state)
153
+ arg2.call(this, state[arg1], arg1, this);
154
+ return addListener(self, (key) => {
155
+ if (key === arg1)
156
+ arg2.call(this, arg1 in state ? state[key] : key, key, this);
157
+ });
158
+ } else
159
+ return fetch(self, arg1, arg2);
147
160
  if (arg1)
148
161
  return effect(self, arg1);
149
162
  const cache = /* @__PURE__ */ new WeakMap();
@@ -165,27 +178,19 @@ var Model = class _Model {
165
178
  if (typeof arg1 == "function")
166
179
  return addListener(self, (key) => {
167
180
  if (typeof key == "string")
168
- return arg1.call(this, key, STATE.get(self)[key]);
181
+ return arg1.call(self, key, self);
169
182
  });
170
- if (typeof arg1 == "string" || arg1 === null)
183
+ if (arg1 !== void 0)
171
184
  return 1 in arguments ? update(self, arg1, arg2, arg3) : update(self, arg1);
172
- return new Promise((resolve, reject) => {
173
- if (typeof arg1 != "number" && !PENDING.has(self)) {
174
- resolve(false);
175
- return;
176
- }
185
+ if (!PENDING.has(self))
186
+ return;
187
+ return new Promise((resolve) => {
177
188
  const remove = addListener(this, (key) => {
178
- if (key === true || typeof arg2 == "function" && typeof key == "string" && arg2(key) !== true)
179
- return;
180
- if (timeout)
181
- clearTimeout(timeout);
182
- remove();
183
- return resolve.bind(null, PENDING.get(self));
189
+ if (key !== true) {
190
+ remove();
191
+ return resolve.bind(null, PENDING.get(self));
192
+ }
184
193
  });
185
- const timeout = typeof arg1 == "number" && setTimeout(() => {
186
- remove();
187
- reject(arg1);
188
- }, arg1);
189
194
  });
190
195
  }
191
196
  /** Iterate over managed properties in this instance of Model. */
@@ -213,6 +218,13 @@ var Model = class _Model {
213
218
  static is(maybe) {
214
219
  return typeof maybe == "function" && maybe.prototype instanceof this;
215
220
  }
221
+ static on(event2) {
222
+ let notify = NOTIFY.get(this);
223
+ if (!notify)
224
+ NOTIFY.set(this, notify = /* @__PURE__ */ new Set());
225
+ notify.add(event2);
226
+ return () => notify.delete(event2);
227
+ }
216
228
  };
217
229
  define(Model.prototype, "toString", {
218
230
  value() {
@@ -252,15 +264,15 @@ function fetch(subject, property, required) {
252
264
  function update(subject, key, value, silent) {
253
265
  const state = STATE.get(subject);
254
266
  let pending = PENDING.get(subject);
255
- if (typeof key == "string") {
256
- if (2 in arguments) {
257
- const previous = state[key];
258
- if (value === previous)
259
- return true;
260
- state[key] = value;
261
- if (silent === true)
262
- return;
263
- }
267
+ if (typeof key == "string" && 2 in arguments) {
268
+ const previous = state[key];
269
+ if (value === previous)
270
+ return true;
271
+ state[key] = value;
272
+ if (silent === true)
273
+ return;
274
+ }
275
+ if (key !== true && key) {
264
276
  if (!pending) {
265
277
  PENDING.set(subject, pending = {});
266
278
  queue(() => {
@@ -268,7 +280,7 @@ function update(subject, key, value, silent) {
268
280
  PENDING.delete(subject);
269
281
  });
270
282
  }
271
- pending[key] = value;
283
+ pending[key] = key in state ? value : true;
272
284
  }
273
285
  event(subject, key);
274
286
  }
@@ -277,10 +289,20 @@ function uid() {
277
289
  }
278
290
 
279
291
  // src/instruction/add.ts
292
+ var INSTRUCT = /* @__PURE__ */ new Map();
280
293
  function add(instruction) {
281
- const placeholder = Symbol("instruction");
282
- INSTRUCT.set(placeholder, (subject, key, state) => {
283
- INSTRUCT.delete(placeholder);
294
+ const token = Symbol("instruction");
295
+ INSTRUCT.set(token, instruction);
296
+ return token;
297
+ }
298
+ Model.on((_, subject) => {
299
+ const state = STATE.get(subject);
300
+ for (const key in subject) {
301
+ const { value } = Object.getOwnPropertyDescriptor(subject, key);
302
+ const instruction = INSTRUCT.get(value);
303
+ if (!instruction)
304
+ continue;
305
+ INSTRUCT.delete(value);
284
306
  delete subject[key];
285
307
  const output = instruction.call(subject, key, subject, state);
286
308
  if (!output)
@@ -313,9 +335,9 @@ function add(instruction) {
313
335
  );
314
336
  }
315
337
  });
316
- });
317
- return placeholder;
318
- }
338
+ }
339
+ return null;
340
+ });
319
341
 
320
342
  // src/context.ts
321
343
  var Context = class {
@@ -383,8 +405,8 @@ var Context = class {
383
405
  if (value || I !== this[key] && !implicit)
384
406
  Object.defineProperty(this, key, {
385
407
  configurable: true,
386
- value,
387
- writable
408
+ writable,
409
+ value
388
410
  });
389
411
  T = Object.getPrototypeOf(T);
390
412
  } while (T !== Model);
@@ -491,24 +513,23 @@ function ref(arg, arg2) {
491
513
  return add((key, subject, state) => {
492
514
  let value = {};
493
515
  if (arg === subject)
494
- for (const key2 in state)
495
- if (typeof arg2 == "function")
496
- define2(value, key2, {
497
- configurable: true,
498
- get() {
499
- const out = arg2(key2);
500
- define2(value, key2, { value: out });
501
- return out;
502
- }
503
- });
504
- else {
505
- const ref2 = (value2) => subject.set(key2, value2);
506
- define2(ref2, "current", {
507
- get: () => state[key2],
508
- set: ref2
509
- });
510
- define2(value, key2, { value: ref2 });
511
- }
516
+ subject.get(() => {
517
+ for (const key2 in state)
518
+ if (typeof arg2 == "function")
519
+ define2(value, key2, {
520
+ configurable: true,
521
+ get() {
522
+ const out = arg2(key2);
523
+ define2(value, key2, { value: out });
524
+ return out;
525
+ }
526
+ });
527
+ else {
528
+ const set2 = (value2) => subject.set(key2, value2);
529
+ define2(set2, "current", { get: () => state[key2], set: set2 });
530
+ define2(value, key2, { value: set2 });
531
+ }
532
+ });
512
533
  else if (typeof arg == "object")
513
534
  throw new Error("ref instruction does not support object which is not 'this'");
514
535
  else {
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.1",
32
- "gitHead": "cca282d36102e4691e3d24a07f95e7652b27f5c3"
31
+ "version": "0.35.2",
32
+ "gitHead": "3ea5cde2819a66601be642a9befd8e4477caa0b8"
33
33
  }