@expressive/mvc 0.36.3 → 0.38.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
@@ -5,18 +5,20 @@ declare namespace Model {
5
5
  type New<T extends Model = Model> = (new () => T) & typeof 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
- /** Any valid key for controller, including but not limited to keyof T. */
8
+ /** Any valid key for controller, including but not limited to Field<T>. */
9
9
  type Key<T> = Field<T> | (string & {}) | number | symbol;
10
- /** Value for a given property managed by a controller. */
11
- type Value<T extends Model, K extends Key<T>> = K extends keyof T ? T[K] extends Ref<infer V> ? V : T[K] : unknown;
12
- type Event<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
13
- type OnUpdate<T extends Model, K extends Key<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
14
10
  /** Export/Import compatible value for a given property in a Model. */
15
- type Export<R> = R extends Ref<infer T> ? T : R extends Model ? State<R> : R;
11
+ type Export<R> = R extends {
12
+ get(): infer T;
13
+ } ? T : R;
14
+ /** Value for a property managed by a controller. */
15
+ type Value<T extends Model, K extends Key<T>> = K extends keyof T ? Export<T[K]> : unknown;
16
+ type OnEvent<T extends Model> = (this: T, key: unknown, source: T) => (() => void) | void | null;
17
+ type OnUpdate<T extends Model, K extends Key<T>> = (this: T, value: Value<T, K>, key: K, thisArg: K) => void;
16
18
  /**
17
19
  * Values from current state of given controller.
18
20
  * Differs from `Values` as values here will drill
19
- * into "real" values held by exotics like ref.
21
+ * into "real" values held by exotics like ref.Object.
20
22
  */
21
23
  type State<T> = {
22
24
  [P in Field<T>]: Export<T[P]>;
@@ -30,8 +32,23 @@ declare namespace Model {
30
32
  (next: T): void;
31
33
  current: T | null;
32
34
  };
33
- /** A callback function which is subscribed to parent and updates when values change. */
34
- type Effect<T> = (this: T, current: T, updated: Set<Model.Key<T>>) => ((update: boolean | null) => void) | Promise<void> | null | void;
35
+ /**
36
+ * A callback function which is subscribed to parent and updates when accessed properties change.
37
+ *
38
+ * @param current - Current state of this controller. This is a proxy which detects properties which
39
+ * where accessed, and thus depended upon to stay current.
40
+ *
41
+ * @param update - Set of properties which have changed, and events fired, since last update.
42
+ *
43
+ * @returns A callback function which will be called when this effect is stale.
44
+ */
45
+ type Effect<T> = (this: T, current: T, update: Set<Model.Key<T>>) => EffectCallback | Promise<void> | null | void;
46
+ /**
47
+ * A callback function returned by effect. Will be called when effect is stale.
48
+ *
49
+ * @param update - `true` if update is pending, `false` effect has been cancelled, `null` if controller is destroyed.
50
+ */
51
+ type EffectCallback = ((update: boolean | null) => void);
35
52
  namespace Instruction {
36
53
  type Getter<T> = (source: Model) => T;
37
54
  type Setter<T> = (value: T, previous: T) => boolean | void | (() => T);
@@ -59,13 +76,42 @@ declare class Model {
59
76
  constructor(arg?: string | (() => void | (() => void)));
60
77
  /** Pull current values from state. Flattens all models and exotic values recursively. */
61
78
  get(): Model.State<this>;
62
- /** Run a function which will run automatically when accessed values change. */
79
+ /**
80
+ * Run a function which will run automatically when accessed values change.
81
+ *
82
+ * @param effect - Function to run, and again whenever accessed values change.
83
+ * If effect returns a function, it will be called when a change occurs (syncronously),
84
+ * effect is cancelled, or parent controller is destroyed.
85
+ *
86
+ * @returns Function to cancel listener.
87
+ */
63
88
  get(effect: Model.Effect<this>): () => void;
89
+ /**
90
+ * Get value of a property.
91
+ *
92
+ * @param key - Property to get value of.
93
+ * @param required - If true, will throw an error if property is not available.
94
+ */
64
95
  get<T extends Model.Key<this>>(key: T, required?: boolean): Model.Value<this, T>;
96
+ /**
97
+ * Run a function when a property is updated.
98
+ *
99
+ * @param key - Property to watch for updates.
100
+ * @param callback - Function to call when property is updated.
101
+ */
65
102
  get<T extends Model.Key<this>>(key: T, callback: Model.OnUpdate<this, T>): () => void;
66
- /** Check if model is expired. */
103
+ /**
104
+ * Check if model is expired.
105
+ *
106
+ * @returns `true` if model is expired, `false` otherwise.
107
+ */
67
108
  get(status: null): boolean;
68
- /** Callback when model is to be destroyed. */
109
+ /**
110
+ * Callback when model is to be destroyed.
111
+ *
112
+ * @param callback - Function to call when model is destroyed.
113
+ * @returns Function to cancel listener.
114
+ */
69
115
  get(status: null, callback: () => void): () => void;
70
116
  /**
71
117
  * Get update in progress.
@@ -77,13 +123,15 @@ declare class Model {
77
123
  * Call a function when update occurs.
78
124
  *
79
125
  * Given function is called for every assignment (which changes value) or explicit `set`.
80
- * To run logic on final value only, callback itself may return a function.
81
- * Returning the same function for one or more events will ensure a side-effect is called only when settled.
126
+ *
127
+ * To run logic on final value only, callback may return a function. Using the same
128
+ * function for one or more events will ensure it is called only when events are settled.
82
129
  *
83
130
  * @param callback - Function to call when update occurs.
84
131
  * @returns Function to remove listener. Will return `true` if removed, `false` if inactive already.
132
+ *
85
133
  */
86
- set(callback: Model.Event<this>): () => boolean;
134
+ set(callback: Model.OnEvent<this>): () => boolean;
87
135
  /**
88
136
  * Declare an end to updates. This event is final and will freeze state.
89
137
  * This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
@@ -95,10 +143,13 @@ declare class Model {
95
143
  * Useful where a property value internally has changed, but the object is the same.
96
144
  * For example: An array has pushed a new value, or a nested property is updated.
97
145
  *
98
- * You can also use this to dispatch arbitrary events as well. Symbols are generally
99
- * recommended as non-property events, however you can use any string. If doing so,
100
- * be sure to avoid collisions with property names - an easy way to do this will be to
101
- * prefix your event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
146
+ * You can also use this to dispatch arbitrary events.
147
+ * Symbols are recommended as non-property events, however you can use any string.
148
+ * If doing so, be sure to avoid collisions with property names! An easy way to do this is
149
+ * to prefix an event with "!" and/or use dash-case. e.g. `set("!event")` or `set("my-event")`.
150
+ *
151
+ * @param key - Property or event to dispatch.
152
+ * @returns Promise which resolves an array of keys which were updated.
102
153
  */
103
154
  set(key: Model.Key<this>): PromiseLike<Model.Key<this>[]>;
104
155
  /**
@@ -126,7 +177,10 @@ declare class Model {
126
177
  * methods and properties of this class.
127
178
  */
128
179
  static is<T extends Model.Type>(this: T, maybe: any): maybe is T;
129
- static on<T extends Model>(this: Model.Type<T>, event: Model.Event<T>): () => boolean;
180
+ /**
181
+ * Register a callback to run when any instance of this Model is updated.
182
+ */
183
+ static on<T extends Model>(this: Model.Type<T>, listener: Model.OnEvent<T>): () => boolean;
130
184
  }
131
185
 
132
186
  declare function add<T = any>(instruction: Model.Instruction): T;
@@ -180,14 +234,22 @@ declare namespace ref {
180
234
  type Callback<T> = (argument: T) => ((next: T | null) => void) | Promise<void> | void | boolean;
181
235
  interface Object<T = any> {
182
236
  (next: T | null): void;
237
+ /** Current value held by this reference. */
183
238
  current: T | null;
239
+ /** Retrieve current value. */
240
+ get(): T | null;
184
241
  }
185
242
  /** Object with references to all managed values of `T`. */
243
+ /** Object with references to all managed values of `T`. */
186
244
  type Proxy<T extends Model> = {
187
- [P in Model.Field<T>]-?: Model.Ref<T[P]>;
245
+ [P in Model.Field<T>]-?: Object<T[P]>;
246
+ } & {
247
+ get(): T;
188
248
  };
189
249
  type CustomProxy<T extends Model, R> = {
190
250
  [P in Model.Field<T>]-?: R;
251
+ } & {
252
+ get(): T;
191
253
  };
192
254
  }
193
255
  /**
package/dist/index.js CHANGED
@@ -35,13 +35,11 @@ module.exports = __toCommonJS(src_exports);
35
35
  var DISPATCH = /* @__PURE__ */ new Set();
36
36
  var OBSERVER = /* @__PURE__ */ new WeakMap();
37
37
  var LISTENER = /* @__PURE__ */ new WeakMap();
38
- function onReady() {
39
- return null;
40
- }
38
+ var onReady = () => null;
41
39
  function addListener(subject, callback, select) {
42
40
  let subs = LISTENER.get(subject);
43
41
  if (!subs)
44
- LISTENER.set(subject, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
42
+ LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
45
43
  const filter = 2 in arguments && /* @__PURE__ */ new Set([select]);
46
44
  if (!filter && !subs.has(onReady))
47
45
  callback(true, subject);
@@ -49,9 +47,9 @@ function addListener(subject, callback, select) {
49
47
  return () => subs.delete(callback);
50
48
  }
51
49
  function watch(from, key, value) {
52
- const listeners = LISTENER.get(from);
53
50
  const observer = OBSERVER.get(from);
54
51
  if (observer) {
52
+ const listeners = LISTENER.get(from);
55
53
  listeners.set(
56
54
  observer,
57
55
  (listeners.get(observer) || /* @__PURE__ */ new Set()).add(key)
@@ -105,79 +103,97 @@ function queue(event2) {
105
103
  }
106
104
  function effect(target, callback) {
107
105
  const listeners = LISTENER.get(target);
108
- let refresh;
109
- let unSet;
106
+ let unset;
107
+ let reset;
110
108
  function invoke() {
111
- let expired;
112
- const local = Object.create(target);
113
- const update2 = () => {
114
- if (expired)
109
+ let stale;
110
+ const subscriber = Object.create(target);
111
+ LISTENER.set(subscriber, listeners);
112
+ OBSERVER.set(subscriber, () => {
113
+ if (stale)
115
114
  return null;
116
- expired = true;
117
- if (refresh && unSet) {
118
- unSet(true);
119
- unSet = void 0;
115
+ stale = true;
116
+ if (reset && unset) {
117
+ unset(true);
118
+ unset = void 0;
120
119
  }
121
- return refresh;
122
- };
123
- LISTENER.set(local, listeners);
124
- OBSERVER.set(local, update2);
120
+ return reset;
121
+ });
125
122
  try {
126
- const out = callback.call(local, local);
127
- unSet = typeof out == "function" && out;
128
- refresh = out === null ? out : invoke;
123
+ const out = callback.call(subscriber, subscriber);
124
+ unset = typeof out == "function" ? out : void 0;
125
+ reset = out === null ? out : invoke;
129
126
  } catch (err) {
130
127
  if (err instanceof Promise) {
131
- refresh = void 0;
132
- err.then(invoke).catch(console.error);
133
- } else if (refresh)
134
- console.error(err);
135
- else
128
+ reset = void 0;
129
+ err.then(invoke);
130
+ } else
136
131
  throw err;
137
132
  }
138
133
  }
139
134
  addListener(target, (key) => {
140
135
  if (key === true)
141
136
  invoke();
142
- else if (!refresh)
143
- return refresh;
144
- if (key === null && unSet)
145
- unSet(null);
137
+ else if (!reset)
138
+ return reset;
139
+ if (key === null && unset)
140
+ unset(null);
146
141
  });
147
142
  return () => {
148
- if (unSet)
149
- unSet(false);
150
- refresh = null;
143
+ if (unset)
144
+ unset(false);
145
+ reset = null;
151
146
  };
152
147
  }
153
148
 
154
149
  // src/model.ts
155
- var ID = /* @__PURE__ */ new WeakMap();
156
- var PARENT = /* @__PURE__ */ new WeakMap();
150
+ var FLATTEN;
151
+ var REGISTER = /* @__PURE__ */ new WeakMap();
157
152
  var STATE = /* @__PURE__ */ new WeakMap();
158
153
  var NOTIFY = /* @__PURE__ */ new WeakMap();
159
154
  var PENDING2 = /* @__PURE__ */ new WeakMap();
155
+ var PARENT = /* @__PURE__ */ new WeakMap();
160
156
  var define = Object.defineProperty;
161
157
  var Model = class _Model {
162
158
  constructor(arg) {
159
+ const state = {};
163
160
  let Type = this.constructor;
164
161
  define(this, "is", { value: this });
165
- STATE.set(this, {});
166
- ID.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
162
+ STATE.set(this, state);
163
+ REGISTER.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
167
164
  while (true) {
168
165
  new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
169
166
  if (Type === _Model)
170
167
  break;
171
168
  Type = Object.getPrototypeOf(Type);
172
169
  }
173
- addListener(this, bootstrap);
174
- if (typeof arg == "function")
170
+ addListener(this, () => {
171
+ let onDone;
172
+ for (const key in this) {
173
+ const desc = Object.getOwnPropertyDescriptor(this, key);
174
+ if ("value" in desc) {
175
+ state[key] = desc.value;
176
+ define(this, key, {
177
+ configurable: false,
178
+ set: (x) => update(this, key, x),
179
+ get() {
180
+ return watch(this, key, state[key]);
181
+ }
182
+ });
183
+ }
184
+ }
185
+ if (typeof arg == "function")
186
+ onDone = arg();
175
187
  addListener(this, () => {
176
- const cleanup = arg();
177
- if (typeof cleanup == "function")
178
- addListener(this, cleanup, null);
179
- return null;
180
- });
188
+ for (const [_, value] of this)
189
+ if (value instanceof _Model && PARENT.get(value) === this)
190
+ value.set(null);
191
+ if (typeof onDone == "function")
192
+ onDone();
193
+ Object.freeze(state);
194
+ }, null);
195
+ return null;
196
+ });
181
197
  }
182
198
  get(arg1, arg2) {
183
199
  const self = this.is;
@@ -185,15 +201,27 @@ var Model = class _Model {
185
201
  let pending = /* @__PURE__ */ new Set();
186
202
  return effect(self, (state) => {
187
203
  const cb = arg1.call(state, state, pending);
188
- return cb === null ? null : (update2) => {
204
+ return cb === null ? cb : (update2) => {
189
205
  pending = PENDING2.get(self);
190
206
  if (typeof cb == "function")
191
207
  return cb(update2);
192
208
  };
193
209
  });
194
210
  }
195
- if (arg1 === void 0)
196
- return extract(self);
211
+ if (arg1 === void 0) {
212
+ const values = {};
213
+ const current = !FLATTEN && (FLATTEN = /* @__PURE__ */ new Map([[self, values]]));
214
+ for (let [key, value] of self) {
215
+ if (FLATTEN.has(value))
216
+ value = FLATTEN.get(value);
217
+ else if (value && typeof value.get === "function")
218
+ FLATTEN.set(value, value = value.get());
219
+ values[key] = value;
220
+ }
221
+ if (current)
222
+ FLATTEN = void 0;
223
+ return Object.freeze(values);
224
+ }
197
225
  if (typeof arg2 == "function") {
198
226
  if (arg1 === null)
199
227
  return addListener(self, arg2.bind(this, this), null);
@@ -257,17 +285,20 @@ var Model = class _Model {
257
285
  static is(maybe) {
258
286
  return typeof maybe == "function" && maybe.prototype instanceof this;
259
287
  }
260
- static on(event2) {
288
+ /**
289
+ * Register a callback to run when any instance of this Model is updated.
290
+ */
291
+ static on(listener) {
261
292
  let notify = NOTIFY.get(this);
262
293
  if (!notify)
263
294
  NOTIFY.set(this, notify = /* @__PURE__ */ new Set());
264
- notify.add(event2);
265
- return () => notify.delete(event2);
295
+ notify.add(listener);
296
+ return () => notify.delete(listener);
266
297
  }
267
298
  };
268
299
  define(Model.prototype, "toString", {
269
300
  value() {
270
- return ID.get(this.is);
301
+ return REGISTER.get(this.is);
271
302
  }
272
303
  });
273
304
  define(Model, "toString", {
@@ -275,29 +306,6 @@ define(Model, "toString", {
275
306
  return this.name;
276
307
  }
277
308
  });
278
- function bootstrap() {
279
- const state = STATE.get(this);
280
- for (const key in this) {
281
- const desc = Object.getOwnPropertyDescriptor(this, key);
282
- if ("value" in desc) {
283
- state[key] = desc.value;
284
- define(this, key, {
285
- configurable: false,
286
- set: (x) => update(this, key, x),
287
- get() {
288
- return watch(this, key, state[key]);
289
- }
290
- });
291
- }
292
- }
293
- addListener(this, () => {
294
- for (const [_, value] of this)
295
- if (value instanceof Model && PARENT.get(value) === this)
296
- value.set(null);
297
- Object.freeze(state);
298
- }, null);
299
- return null;
300
- }
301
309
  function fetch(subject, property, required) {
302
310
  const state = STATE.get(subject);
303
311
  if (property in state || required === false) {
@@ -331,9 +339,8 @@ function update(subject, key, value, silent) {
331
339
  if (value === previous)
332
340
  return true;
333
341
  state[key] = value;
334
- if (silent === true)
335
- return;
336
- push(subject, key);
342
+ if (silent !== true)
343
+ push(subject, key);
337
344
  }
338
345
  function push(subject, key, silent) {
339
346
  let pending = PENDING2.get(subject);
@@ -349,21 +356,6 @@ function push(subject, key, silent) {
349
356
  if (!silent)
350
357
  event(subject, key);
351
358
  }
352
- function extract(from) {
353
- const cache = /* @__PURE__ */ new WeakMap();
354
- function get2(value) {
355
- if (value instanceof Model) {
356
- if (cache.has(value))
357
- return cache.get(value);
358
- const model = value;
359
- cache.set(value, value = {});
360
- for (const [key, val] of model)
361
- value[key] = get2(val);
362
- }
363
- return value;
364
- }
365
- return get2(from);
366
- }
367
359
  function uid() {
368
360
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
369
361
  }
@@ -621,8 +613,10 @@ function ref(arg, arg2) {
621
613
  }
622
614
  });
623
615
  else {
616
+ const get2 = () => state[key2];
624
617
  const set2 = (value2) => subject.set(key2, value2);
625
- define2(set2, "current", { get: () => state[key2], set: set2 });
618
+ define2(set2, "current", { get: get2, set: set2 });
619
+ define2(set2, "get", { value: get2 });
626
620
  define2(value, key2, { value: set2 });
627
621
  }
628
622
  });
@@ -630,6 +624,7 @@ function ref(arg, arg2) {
630
624
  throw new Error("ref instruction does not support object which is not 'this'");
631
625
  else {
632
626
  let unSet;
627
+ const get2 = () => state[key];
633
628
  const set2 = (value2) => {
634
629
  if (!subject.set(key, value2) || !arg)
635
630
  return;
@@ -641,9 +636,10 @@ function ref(arg, arg2) {
641
636
  unSet = out;
642
637
  }
643
638
  };
644
- value = define2(set2, "current", {
645
- get: () => state[key],
646
- set: set2
639
+ state[key] = null;
640
+ value = Object.defineProperties(set2, {
641
+ current: { get: get2, set: set2 },
642
+ get: { value: get2 }
647
643
  });
648
644
  }
649
645
  define2(subject, key, { value });
package/dist/index.mjs CHANGED
@@ -2,13 +2,11 @@
2
2
  var DISPATCH = /* @__PURE__ */ new Set();
3
3
  var OBSERVER = /* @__PURE__ */ new WeakMap();
4
4
  var LISTENER = /* @__PURE__ */ new WeakMap();
5
- function onReady() {
6
- return null;
7
- }
5
+ var onReady = () => null;
8
6
  function addListener(subject, callback, select) {
9
7
  let subs = LISTENER.get(subject);
10
8
  if (!subs)
11
- LISTENER.set(subject, subs = (/* @__PURE__ */ new Map()).set(onReady, false));
9
+ LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
12
10
  const filter = 2 in arguments && /* @__PURE__ */ new Set([select]);
13
11
  if (!filter && !subs.has(onReady))
14
12
  callback(true, subject);
@@ -16,9 +14,9 @@ function addListener(subject, callback, select) {
16
14
  return () => subs.delete(callback);
17
15
  }
18
16
  function watch(from, key, value) {
19
- const listeners = LISTENER.get(from);
20
17
  const observer = OBSERVER.get(from);
21
18
  if (observer) {
19
+ const listeners = LISTENER.get(from);
22
20
  listeners.set(
23
21
  observer,
24
22
  (listeners.get(observer) || /* @__PURE__ */ new Set()).add(key)
@@ -72,79 +70,97 @@ function queue(event2) {
72
70
  }
73
71
  function effect(target, callback) {
74
72
  const listeners = LISTENER.get(target);
75
- let refresh;
76
- let unSet;
73
+ let unset;
74
+ let reset;
77
75
  function invoke() {
78
- let expired;
79
- const local = Object.create(target);
80
- const update2 = () => {
81
- if (expired)
76
+ let stale;
77
+ const subscriber = Object.create(target);
78
+ LISTENER.set(subscriber, listeners);
79
+ OBSERVER.set(subscriber, () => {
80
+ if (stale)
82
81
  return null;
83
- expired = true;
84
- if (refresh && unSet) {
85
- unSet(true);
86
- unSet = void 0;
82
+ stale = true;
83
+ if (reset && unset) {
84
+ unset(true);
85
+ unset = void 0;
87
86
  }
88
- return refresh;
89
- };
90
- LISTENER.set(local, listeners);
91
- OBSERVER.set(local, update2);
87
+ return reset;
88
+ });
92
89
  try {
93
- const out = callback.call(local, local);
94
- unSet = typeof out == "function" && out;
95
- refresh = out === null ? out : invoke;
90
+ const out = callback.call(subscriber, subscriber);
91
+ unset = typeof out == "function" ? out : void 0;
92
+ reset = out === null ? out : invoke;
96
93
  } catch (err) {
97
94
  if (err instanceof Promise) {
98
- refresh = void 0;
99
- err.then(invoke).catch(console.error);
100
- } else if (refresh)
101
- console.error(err);
102
- else
95
+ reset = void 0;
96
+ err.then(invoke);
97
+ } else
103
98
  throw err;
104
99
  }
105
100
  }
106
101
  addListener(target, (key) => {
107
102
  if (key === true)
108
103
  invoke();
109
- else if (!refresh)
110
- return refresh;
111
- if (key === null && unSet)
112
- unSet(null);
104
+ else if (!reset)
105
+ return reset;
106
+ if (key === null && unset)
107
+ unset(null);
113
108
  });
114
109
  return () => {
115
- if (unSet)
116
- unSet(false);
117
- refresh = null;
110
+ if (unset)
111
+ unset(false);
112
+ reset = null;
118
113
  };
119
114
  }
120
115
 
121
116
  // src/model.ts
122
- var ID = /* @__PURE__ */ new WeakMap();
123
- var PARENT = /* @__PURE__ */ new WeakMap();
117
+ var FLATTEN;
118
+ var REGISTER = /* @__PURE__ */ new WeakMap();
124
119
  var STATE = /* @__PURE__ */ new WeakMap();
125
120
  var NOTIFY = /* @__PURE__ */ new WeakMap();
126
121
  var PENDING2 = /* @__PURE__ */ new WeakMap();
122
+ var PARENT = /* @__PURE__ */ new WeakMap();
127
123
  var define = Object.defineProperty;
128
124
  var Model = class _Model {
129
125
  constructor(arg) {
126
+ const state = {};
130
127
  let Type = this.constructor;
131
128
  define(this, "is", { value: this });
132
- STATE.set(this, {});
133
- ID.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
129
+ STATE.set(this, state);
130
+ REGISTER.set(this, typeof arg == "string" ? arg : `${Type}-${uid()}`);
134
131
  while (true) {
135
132
  new Set(NOTIFY.get(Type)).forEach((x) => addListener(this, x));
136
133
  if (Type === _Model)
137
134
  break;
138
135
  Type = Object.getPrototypeOf(Type);
139
136
  }
140
- addListener(this, bootstrap);
141
- if (typeof arg == "function")
137
+ addListener(this, () => {
138
+ let onDone;
139
+ for (const key in this) {
140
+ const desc = Object.getOwnPropertyDescriptor(this, key);
141
+ if ("value" in desc) {
142
+ state[key] = desc.value;
143
+ define(this, key, {
144
+ configurable: false,
145
+ set: (x) => update(this, key, x),
146
+ get() {
147
+ return watch(this, key, state[key]);
148
+ }
149
+ });
150
+ }
151
+ }
152
+ if (typeof arg == "function")
153
+ onDone = arg();
142
154
  addListener(this, () => {
143
- const cleanup = arg();
144
- if (typeof cleanup == "function")
145
- addListener(this, cleanup, null);
146
- return null;
147
- });
155
+ for (const [_, value] of this)
156
+ if (value instanceof _Model && PARENT.get(value) === this)
157
+ value.set(null);
158
+ if (typeof onDone == "function")
159
+ onDone();
160
+ Object.freeze(state);
161
+ }, null);
162
+ return null;
163
+ });
148
164
  }
149
165
  get(arg1, arg2) {
150
166
  const self = this.is;
@@ -152,15 +168,27 @@ var Model = class _Model {
152
168
  let pending = /* @__PURE__ */ new Set();
153
169
  return effect(self, (state) => {
154
170
  const cb = arg1.call(state, state, pending);
155
- return cb === null ? null : (update2) => {
171
+ return cb === null ? cb : (update2) => {
156
172
  pending = PENDING2.get(self);
157
173
  if (typeof cb == "function")
158
174
  return cb(update2);
159
175
  };
160
176
  });
161
177
  }
162
- if (arg1 === void 0)
163
- return extract(self);
178
+ if (arg1 === void 0) {
179
+ const values = {};
180
+ const current = !FLATTEN && (FLATTEN = /* @__PURE__ */ new Map([[self, values]]));
181
+ for (let [key, value] of self) {
182
+ if (FLATTEN.has(value))
183
+ value = FLATTEN.get(value);
184
+ else if (value && typeof value.get === "function")
185
+ FLATTEN.set(value, value = value.get());
186
+ values[key] = value;
187
+ }
188
+ if (current)
189
+ FLATTEN = void 0;
190
+ return Object.freeze(values);
191
+ }
164
192
  if (typeof arg2 == "function") {
165
193
  if (arg1 === null)
166
194
  return addListener(self, arg2.bind(this, this), null);
@@ -224,17 +252,20 @@ var Model = class _Model {
224
252
  static is(maybe) {
225
253
  return typeof maybe == "function" && maybe.prototype instanceof this;
226
254
  }
227
- static on(event2) {
255
+ /**
256
+ * Register a callback to run when any instance of this Model is updated.
257
+ */
258
+ static on(listener) {
228
259
  let notify = NOTIFY.get(this);
229
260
  if (!notify)
230
261
  NOTIFY.set(this, notify = /* @__PURE__ */ new Set());
231
- notify.add(event2);
232
- return () => notify.delete(event2);
262
+ notify.add(listener);
263
+ return () => notify.delete(listener);
233
264
  }
234
265
  };
235
266
  define(Model.prototype, "toString", {
236
267
  value() {
237
- return ID.get(this.is);
268
+ return REGISTER.get(this.is);
238
269
  }
239
270
  });
240
271
  define(Model, "toString", {
@@ -242,29 +273,6 @@ define(Model, "toString", {
242
273
  return this.name;
243
274
  }
244
275
  });
245
- function bootstrap() {
246
- const state = STATE.get(this);
247
- for (const key in this) {
248
- const desc = Object.getOwnPropertyDescriptor(this, key);
249
- if ("value" in desc) {
250
- state[key] = desc.value;
251
- define(this, key, {
252
- configurable: false,
253
- set: (x) => update(this, key, x),
254
- get() {
255
- return watch(this, key, state[key]);
256
- }
257
- });
258
- }
259
- }
260
- addListener(this, () => {
261
- for (const [_, value] of this)
262
- if (value instanceof Model && PARENT.get(value) === this)
263
- value.set(null);
264
- Object.freeze(state);
265
- }, null);
266
- return null;
267
- }
268
276
  function fetch(subject, property, required) {
269
277
  const state = STATE.get(subject);
270
278
  if (property in state || required === false) {
@@ -298,9 +306,8 @@ function update(subject, key, value, silent) {
298
306
  if (value === previous)
299
307
  return true;
300
308
  state[key] = value;
301
- if (silent === true)
302
- return;
303
- push(subject, key);
309
+ if (silent !== true)
310
+ push(subject, key);
304
311
  }
305
312
  function push(subject, key, silent) {
306
313
  let pending = PENDING2.get(subject);
@@ -316,21 +323,6 @@ function push(subject, key, silent) {
316
323
  if (!silent)
317
324
  event(subject, key);
318
325
  }
319
- function extract(from) {
320
- const cache = /* @__PURE__ */ new WeakMap();
321
- function get2(value) {
322
- if (value instanceof Model) {
323
- if (cache.has(value))
324
- return cache.get(value);
325
- const model = value;
326
- cache.set(value, value = {});
327
- for (const [key, val] of model)
328
- value[key] = get2(val);
329
- }
330
- return value;
331
- }
332
- return get2(from);
333
- }
334
326
  function uid() {
335
327
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
336
328
  }
@@ -588,8 +580,10 @@ function ref(arg, arg2) {
588
580
  }
589
581
  });
590
582
  else {
583
+ const get2 = () => state[key2];
591
584
  const set2 = (value2) => subject.set(key2, value2);
592
- define2(set2, "current", { get: () => state[key2], set: set2 });
585
+ define2(set2, "current", { get: get2, set: set2 });
586
+ define2(set2, "get", { value: get2 });
593
587
  define2(value, key2, { value: set2 });
594
588
  }
595
589
  });
@@ -597,6 +591,7 @@ function ref(arg, arg2) {
597
591
  throw new Error("ref instruction does not support object which is not 'this'");
598
592
  else {
599
593
  let unSet;
594
+ const get2 = () => state[key];
600
595
  const set2 = (value2) => {
601
596
  if (!subject.set(key, value2) || !arg)
602
597
  return;
@@ -608,9 +603,10 @@ function ref(arg, arg2) {
608
603
  unSet = out;
609
604
  }
610
605
  };
611
- value = define2(set2, "current", {
612
- get: () => state[key],
613
- set: set2
606
+ state[key] = null;
607
+ value = Object.defineProperties(set2, {
608
+ current: { get: get2, set: set2 },
609
+ get: { value: get2 }
614
610
  });
615
611
  }
616
612
  define2(subject, key, { value });
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.36.3",
32
- "gitHead": "68142bec2c7c9bf8b9cb62e655ac9b35d6a6134f"
31
+ "version": "0.38.0",
32
+ "gitHead": "8f3d4c7939e483043de4391ee0edb877bb7da63c"
33
33
  }