@expressive/mvc 0.52.1 → 0.52.2-alpha.74

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
@@ -133,29 +133,17 @@ declare abstract class Model {
133
133
  /**
134
134
  * Get update in progress.
135
135
  *
136
- * @returns Promise which resolves object with updated values. Is `undefined` if there is no update.
136
+ * @returns Promise which resolves object with updated values, `undefined` if there no update is pending.
137
137
  **/
138
138
  set(): PromiseLike<Model.Event<this>[]> | undefined;
139
139
  /**
140
- * Call a function when update occurs.
141
- *
142
- * Given function is called for every assignment (which changes value) or explicit `set`.
143
- *
144
- * To run logic on final value only, callback may return a function. Using the same
145
- * function for one or more events will ensure it is called only when events are settled.
146
- *
147
- * @param callback - Function to call when update occurs.
148
- * @returns Function to remove listener. Will return `true` if removed, `false` if inactive already.
149
- *
150
- */
151
- set(callback: Model.OnEvent<this>): () => boolean;
152
- /**
153
- * Declare an end to updates. This event is final and will freeze state.
154
- * This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
140
+ * Update mulitple properties at once. Merges argument with current state.
141
+ * Properties which are not managed by this model will be ignored.
155
142
  *
156
- * @param status - `null` to end updates.
143
+ * @param assign - Object with properties to update.
144
+ * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
157
145
  */
158
- set(status: null): void;
146
+ set(assign: Model.Assign<this>): PromiseLike<Model.Event<this>[]> | undefined;
159
147
  /**
160
148
  * Push an update. This will not change the value of associated property.
161
149
  *
@@ -171,14 +159,6 @@ declare abstract class Model {
171
159
  * @returns Promise resolves an array of keys updated.
172
160
  */
173
161
  set(key: Model.Event<this>): PromiseLike<Model.Event<this>[]>;
174
- /**
175
- * Update mulitple properties at once. Merges argument with current state.
176
- * Properties which are not managed by this model will be ignored.
177
- *
178
- * @param assign - Object with properties to update.
179
- * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
180
- */
181
- set(assign: Model.Assign<this>): PromiseLike<Model.Event<this>[]> | undefined;
182
162
  /**
183
163
  * Update a property with value.
184
164
  *
@@ -188,6 +168,26 @@ declare abstract class Model {
188
168
  * @returns Promise resolving an array of keys updated, `undefined` (immediately) if a noop.
189
169
  */
190
170
  set<K extends string>(key: K, value: Model.Value<this, K>, silent?: boolean): PromiseLike<Model.Event<this>[]> | undefined;
171
+ /**
172
+ * Call a function when update occurs.
173
+ *
174
+ * Given function is called for every assignment (which changes value) or explicit `set`.
175
+ *
176
+ * To run logic on final value only, callback may return a function. Using the same
177
+ * function for one or more events will ensure it is called only when events are settled.
178
+ *
179
+ * @param callback - Function to call when update occurs.
180
+ * @returns Function to remove listener. Will return `true` if removed, `false` if inactive already.
181
+ *
182
+ */
183
+ set(callback: Model.OnEvent<this>): () => boolean;
184
+ /**
185
+ * Declare an end to updates. This event is final and will freeze state.
186
+ * This event can be watched for as well, to run cleanup logic and internally will remove all listeners.
187
+ *
188
+ * @param status - `null` to end updates.
189
+ */
190
+ set(status: null): void;
191
191
  /**
192
192
  * Iterate over managed properties in this instance of Model.
193
193
  * Yeilds the key and underlying value for each property.
@@ -326,10 +326,10 @@ declare namespace set {
326
326
  type Factory<T, S = any> = (this: S, key: string) => Promise<T> | T;
327
327
  }
328
328
  /**
329
- * Set property with a placeholder.
329
+ * Set property as `undefined` but required.
330
330
  *
331
331
  * Property cannot be accessed until it is defined. If accessed while undefined, a hybrid
332
- * `Promise`/`Error` (ala: [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html)) will be thrown.
332
+ * `Promise`/`Error` (aka: [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html)) will be thrown.
333
333
  */
334
334
  declare function set<T = any>(): T;
335
335
  /**
@@ -367,6 +367,21 @@ declare namespace has {
367
367
  declare function has<T extends Model>(type: Model.Type<T>, callback?: has.Callback<T>): readonly T[];
368
368
  declare function has(callback?: has.Callback): readonly Model[];
369
369
 
370
+ /**
371
+ * Update callback function.
372
+ *
373
+ * @param key -
374
+ * - `string` - property which has updated.
375
+ * - `false` - a normal update has completed.
376
+ * - `true` - initial event; instance is ready.
377
+ * - `null` - terminal event; instance is expired.
378
+ * @param source - Instance of Model for which update has occured.
379
+ */
380
+ type OnUpdate<T = any> = (this: T, key: unknown, source: T) => (() => void) | null | void;
381
+ type OnAccess<T, R = unknown> = (from: T, key: string | number, value: R) => unknown;
382
+ type Event = number | string | null | boolean | symbol;
383
+ declare function addListener(subject: {}, callback: OnUpdate, select?: Event): () => boolean;
384
+ declare function createProxy<T extends {}>(from: T, observer: OnAccess<T>): any;
370
385
  type Effect<T extends {}> = (this: T, argument: T) => ((update: boolean | null) => void) | Promise<void> | null | void;
371
386
  /**
372
387
  * Create a side-effect which will update whenever values accessed change.
@@ -376,11 +391,15 @@ type Effect<T extends {}> = (this: T, argument: T) => ((update: boolean | null)
376
391
  * @param callback - Function to invoke when values change.
377
392
  * @param requireValues - If `true` will throw if accessing a value which is `undefined`.
378
393
  */
379
- declare function effect<T extends {}>(target: T, callback: Effect<Required<T>>, requireValues: true): () => void;
380
- declare function effect<T extends {}>(target: T, callback: Effect<T>, requireValues?: boolean): () => void;
394
+ declare function createEffect<T extends {}>(target: T, callback: Effect<Required<T>>, requireValues: true): () => void;
395
+ declare function createEffect<T extends {}>(target: T, callback: Effect<T>, requireValues?: boolean): () => void;
381
396
 
382
397
  declare namespace Context {
383
- type Input = Record<string | number, Model | Model.Init>;
398
+ type Multiple<T extends Model> = {
399
+ [key: string | number]: Model.Init<T> | T;
400
+ };
401
+ type Accept<T extends Model = Model> = T | Model.Init<T> | Multiple<T>;
402
+ type ForEach = (added: Model, explicit: boolean) => void;
384
403
  type Expect = (model: Model) => (() => void) | void;
385
404
  }
386
405
  interface Context {
@@ -392,13 +411,15 @@ declare class Context {
392
411
  id: string;
393
412
  protected layer: Map<string | number, Model | Model.Type<Model>>;
394
413
  protected cleanup: Set<() => void>;
395
- constructor(inputs?: Context.Input);
414
+ constructor(inputs?: Context.Accept);
415
+ /** Run callback when a specified type is registered to a **child** context. */
416
+ has<T extends Model>(Type: Model.Type<T>, callback: (model: T) => void): void;
417
+ /** Find specified type registered to a parent context. Returns undefined if none are found. */
396
418
  get<T extends Model>(Type: Model.Type<T>): T | undefined;
397
- get<T extends Model>(Type: Model.Type<T>, callback: (model: T) => void): void;
398
- push(inputs?: Context.Input): this;
419
+ push(inputs?: Context.Accept): this;
399
420
  pop(): void;
400
- include(inputs: Context.Input, forEach?: (added: Model, explicit: boolean) => void): void;
421
+ include<T extends Model>(inputs: Context.Accept<T>, forEach?: Context.ForEach | Model.Assign<T>): void;
401
422
  protected add<T extends Model>(input: T | Model.Init<T>, implicit?: boolean): T;
402
423
  }
403
424
 
404
- export { Context, Model, Model as default, effect, get, has, ref, set, use };
425
+ export { Context, Model, addListener, createEffect, createProxy, Model as default, get, has, ref, set, use };
package/dist/index.js CHANGED
@@ -22,8 +22,10 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  Context: () => Context,
24
24
  Model: () => Model,
25
+ addListener: () => addListener,
26
+ createEffect: () => createEffect,
27
+ createProxy: () => createProxy,
25
28
  default: () => Model,
26
- effect: () => effect,
27
29
  get: () => get,
28
30
  has: () => has,
29
31
  ref: () => ref,
@@ -35,25 +37,30 @@ module.exports = __toCommonJS(src_exports);
35
37
  // src/control.ts
36
38
  var DISPATCH = /* @__PURE__ */ new Set();
37
39
  var OBSERVER = /* @__PURE__ */ new WeakMap();
38
- var LISTENER = /* @__PURE__ */ new WeakMap();
40
+ var LISTENERS = /* @__PURE__ */ new WeakMap();
41
+ var PENDING = /* @__PURE__ */ new WeakMap();
39
42
  var onReady = () => null;
40
43
  function addListener(subject, callback, select) {
41
- let subs = LISTENER.get(subject);
44
+ let subs = LISTENERS.get(subject);
42
45
  if (!subs)
43
- LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
44
- const filter = select !== void 0 && /* @__PURE__ */ new Set([select]);
46
+ LISTENERS.set(subject, subs = /* @__PURE__ */ new Map([[onReady, void 0]]));
47
+ const filter = select === void 0 ? void 0 : /* @__PURE__ */ new Set([select]);
45
48
  if (!subs.has(onReady) && !filter)
46
49
  callback(true, subject);
47
50
  subs.set(callback, filter);
48
51
  return () => subs.delete(callback);
49
52
  }
53
+ function createProxy(from, observer) {
54
+ const proxy = Object.create(from);
55
+ OBSERVER.set(proxy, observer);
56
+ return proxy;
57
+ }
50
58
  function watch(from, key2, value) {
51
59
  const access = OBSERVER.get(from);
52
60
  return access ? access(from, key2, value) : value;
53
61
  }
54
- var PENDING = /* @__PURE__ */ new WeakMap();
55
62
  function emit(source, key2) {
56
- const subs = LISTENER.get(source);
63
+ const subs = LISTENERS.get(source);
57
64
  const ready = !subs.has(onReady);
58
65
  if (key2 === true && ready)
59
66
  return;
@@ -79,27 +86,28 @@ function emit(source, key2) {
79
86
  });
80
87
  PENDING.delete(source);
81
88
  }
82
- function queue(event2) {
89
+ function queue(eventHandler) {
83
90
  if (!DISPATCH.size)
84
91
  setTimeout(() => {
85
- DISPATCH.forEach((event3) => {
92
+ DISPATCH.forEach((event2) => {
86
93
  try {
87
- event3();
94
+ event2();
88
95
  } catch (err) {
89
96
  console.error(err);
90
97
  }
91
98
  });
92
99
  DISPATCH.clear();
93
100
  }, 0);
94
- DISPATCH.add(event2);
101
+ DISPATCH.add(eventHandler);
95
102
  }
96
- function effect(target, callback, requireValues) {
97
- const listeners = LISTENER.get(target);
103
+ function createEffect(target, callback, requireValues) {
104
+ const listeners = LISTENERS.get(target);
98
105
  let unset;
99
106
  let reset;
100
107
  function invoke() {
101
108
  let stale;
102
- const subscriber = Object.create(target);
109
+ const subscriber = createProxy(target, access);
110
+ LISTENERS.set(subscriber, listeners);
103
111
  function onUpdate() {
104
112
  if (stale)
105
113
  return null;
@@ -113,20 +121,19 @@ function effect(target, callback, requireValues) {
113
121
  function access(from, key2, value) {
114
122
  if (value === void 0 && requireValues)
115
123
  throw new Error(`${from}.${key2} is required in this context.`);
116
- const listeners2 = LISTENER.get(from);
124
+ const listeners2 = LISTENERS.get(from);
117
125
  let listener = listeners2.get(onUpdate);
118
126
  if (!listener)
119
127
  listeners2.set(onUpdate, listener = /* @__PURE__ */ new Set());
120
128
  listener.add(key2);
121
- const nested = LISTENER.get(value);
122
- if (nested) {
123
- LISTENER.set(value = Object.create(value), nested);
124
- OBSERVER.set(value, access);
125
- }
129
+ const nested = LISTENERS.get(value);
130
+ if (nested)
131
+ LISTENERS.set(
132
+ value = createProxy(value, access),
133
+ nested
134
+ );
126
135
  return value;
127
136
  }
128
- LISTENER.set(subscriber, listeners);
129
- OBSERVER.set(subscriber, access);
130
137
  try {
131
138
  const out = callback.call(subscriber, subscriber);
132
139
  unset = typeof out == "function" ? out : void 0;
@@ -163,27 +170,24 @@ var PENDING2 = /* @__PURE__ */ new WeakMap();
163
170
  var PARENT = /* @__PURE__ */ new WeakMap();
164
171
  var METHODS = /* @__PURE__ */ new WeakMap();
165
172
  var METHOD = /* @__PURE__ */ new WeakMap();
166
- var EXPORT;
167
- var Model = class _Model {
173
+ var Model = class {
168
174
  constructor(...args) {
169
- define(this, "is", { value: this });
170
- ID.set(this, `${this.constructor}-${uid()}`);
171
- STATE.set(this, {});
172
175
  prepare(this);
176
+ define(this, "is", { value: this });
173
177
  init(this, args);
174
178
  }
175
179
  get(arg1, arg2) {
176
180
  const self = this.is;
177
181
  if (arg1 === void 0)
178
- return values(self);
182
+ return snapshot(self);
179
183
  if (typeof arg1 == "function") {
180
184
  let pending = /* @__PURE__ */ new Set();
181
- return effect(self, (state) => {
185
+ return createEffect(self, (state) => {
182
186
  const cb = arg1.call(state, state, pending);
183
187
  return cb === null ? cb : (update2) => {
184
188
  pending = PENDING2.get(self);
185
189
  if (typeof cb == "function")
186
- return cb(update2);
190
+ cb(update2);
187
191
  };
188
192
  });
189
193
  }
@@ -245,7 +249,6 @@ var Model = class _Model {
245
249
  * @param args - arguments sent to constructor
246
250
  */
247
251
  static new(...args) {
248
- _Model.is(this);
249
252
  const instance = new this(...args);
250
253
  emit(instance, true);
251
254
  return instance;
@@ -257,9 +260,7 @@ var Model = class _Model {
257
260
  * methods and properties of this class.
258
261
  */
259
262
  static is(maybe) {
260
- if (maybe === _Model)
261
- throw new Error("Model is abstract and not a valid type.");
262
- return typeof maybe == "function" && maybe.prototype instanceof this;
263
+ return maybe === this || typeof maybe == "function" && maybe.prototype instanceof this;
263
264
  }
264
265
  /**
265
266
  * Register a callback to run when any instance of this Model is updated.
@@ -283,48 +284,50 @@ define(Model, "toString", {
283
284
  }
284
285
  });
285
286
  function prepare(model) {
287
+ let T = model.constructor;
288
+ if (T === Model)
289
+ throw new Error("Cannot create base Model.");
286
290
  const chain = [];
287
- let type = model.constructor;
288
291
  let keys2 = /* @__PURE__ */ new Set();
289
- while (type.name) {
290
- chain.unshift(type);
291
- type = Object.getPrototypeOf(type);
292
- }
293
- for (const type2 of chain) {
294
- for (const cb of NOTIFY.get(type2) || [])
292
+ ID.set(model, `${T}-${uid()}`);
293
+ while (T.name) {
294
+ for (const cb of NOTIFY.get(T) || [])
295
295
  addListener(model, cb);
296
- if (type2 == Model)
297
- continue;
298
- if (METHODS.has(type2)) {
299
- keys2 = METHODS.get(type2);
296
+ if (T === Model)
297
+ break;
298
+ else
299
+ chain.unshift(T);
300
+ T = Object.getPrototypeOf(T);
301
+ }
302
+ for (const type of chain) {
303
+ if (METHODS.has(type)) {
304
+ keys2 = METHODS.get(type);
300
305
  continue;
301
306
  }
302
- METHODS.set(type2, keys2 = new Set(keys2));
307
+ METHODS.set(type, keys2 = new Set(keys2));
303
308
  for (const [key2, { value }] of Object.entries(
304
- Object.getOwnPropertyDescriptors(type2.prototype)
309
+ Object.getOwnPropertyDescriptors(type.prototype)
305
310
  )) {
306
- let set3 = function(method) {
311
+ let get3 = function() {
312
+ return this.hasOwnProperty(key2) ? value : set3.call(this, value);
313
+ }, set3 = function(method) {
307
314
  const value2 = method.bind(this.is);
308
315
  METHOD.set(value2, method);
309
316
  define(this.is, key2, { value: value2, writable: true });
310
317
  return value2;
311
318
  };
312
- var set2 = set3;
319
+ var get2 = get3, set2 = set3;
313
320
  if (!value || key2 == "constructor")
314
321
  continue;
315
322
  keys2.add(key2);
316
- Object.defineProperty(type2.prototype, key2, {
317
- set: set3,
318
- get() {
319
- return this.hasOwnProperty(key2) ? value : set3.call(this, value);
320
- }
321
- });
323
+ define(type.prototype, key2, { get: get3, set: set3 });
322
324
  }
323
325
  }
324
326
  }
325
327
  function init(model, args) {
326
- const done = /* @__PURE__ */ new Set();
327
- const state = STATE.get(model);
328
+ const state = {};
329
+ const clean = /* @__PURE__ */ new Set();
330
+ STATE.set(model, state);
328
331
  args = args.flat().filter((arg) => {
329
332
  if (typeof arg != "string")
330
333
  return true;
@@ -340,10 +343,10 @@ function init(model, args) {
340
343
  console.error(`Async error in constructor for ${model}:`);
341
344
  console.error(err);
342
345
  });
343
- else if (typeof use2 == "function")
344
- done.add(use2);
345
346
  else if (typeof use2 == "object")
346
347
  assign(model, use2);
348
+ else if (typeof use2 == "function")
349
+ clean.add(use2);
347
350
  });
348
351
  for (const key2 in model) {
349
352
  const desc = Object.getOwnPropertyDescriptor(model, key2);
@@ -361,7 +364,7 @@ function init(model, args) {
361
364
  return null;
362
365
  });
363
366
  addListener(model, () => {
364
- done.forEach((x) => x());
367
+ clean.forEach((x) => x());
365
368
  for (const [_, value] of model)
366
369
  if (value instanceof Model && PARENT.get(value) === model)
367
370
  value.set(null);
@@ -393,19 +396,24 @@ function fetch(subject, property, required) {
393
396
  stack: error.stack
394
397
  });
395
398
  }
396
- function values(model) {
397
- const values2 = {};
398
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
399
+ var EXPORT;
400
+ function snapshot(model) {
401
+ const values = {};
402
+ let isNotRecursive;
403
+ if (!EXPORT) {
404
+ isNotRecursive = true;
405
+ EXPORT = /* @__PURE__ */ new Map([[model, values]]);
406
+ }
399
407
  for (let [key2, value] of model) {
400
408
  if (EXPORT.has(value))
401
409
  value = EXPORT.get(value);
402
- else if (value && typeof value.get === "function")
410
+ else if (value && typeof value == "object" && "get" in value && typeof value.get === "function")
403
411
  EXPORT.set(value, value = value.get());
404
- values2[key2] = value;
412
+ values[key2] = value;
405
413
  }
406
- if (current)
414
+ if (isNotRecursive)
407
415
  EXPORT = void 0;
408
- return Object.freeze(values2);
416
+ return Object.freeze(values);
409
417
  }
410
418
  function update(subject, key2, value, arg) {
411
419
  const state = STATE.get(subject);
@@ -443,23 +451,23 @@ function event(subject, key2, silent) {
443
451
  if (!silent)
444
452
  emit(subject, key2);
445
453
  }
446
- function assign(to, values2) {
454
+ function assign(to, values) {
447
455
  const methods = METHODS.get(to.constructor);
448
- for (const key2 in values2)
456
+ for (const key2 in values)
449
457
  if (key2 in to || methods.has(key2))
450
- to[key2] = values2[key2];
458
+ to[key2] = values[key2];
451
459
  }
452
460
  function uid() {
453
461
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
454
462
  }
455
463
 
456
464
  // src/context.ts
457
- var Register = /* @__PURE__ */ new WeakMap();
458
- var Table = /* @__PURE__ */ new Map();
465
+ var LOOKUP = /* @__PURE__ */ new WeakMap();
466
+ var KEYS = /* @__PURE__ */ new Map();
459
467
  function key(T, upstream) {
460
- let K = Table.get(T);
468
+ let K = KEYS.get(T);
461
469
  if (!K)
462
- Table.set(T, K = Symbol(
470
+ KEYS.set(T, K = Symbol(
463
471
  typeof T == "symbol" ? "get " + T.description : String(T)
464
472
  ));
465
473
  return upstream ? key(K) : K;
@@ -479,7 +487,7 @@ var Context = class _Context {
479
487
  this.include(inputs);
480
488
  }
481
489
  static get(from, callback) {
482
- const waiting = Register.get(from);
490
+ const waiting = LOOKUP.get(from);
483
491
  if (waiting instanceof _Context) {
484
492
  if (callback)
485
493
  callback(waiting);
@@ -489,11 +497,14 @@ var Context = class _Context {
489
497
  if (waiting)
490
498
  waiting.push(callback);
491
499
  else
492
- Register.set(from, [callback]);
500
+ LOOKUP.set(from, [callback]);
493
501
  }
494
- get(Type, callback) {
495
- if (callback)
496
- Object.defineProperty(this, key(Type, true), { value: callback });
502
+ /** Run callback when a specified type is registered to a **child** context. */
503
+ has(Type, callback) {
504
+ define(this, key(Type, true), { value: callback });
505
+ }
506
+ /** Find specified type registered to a parent context. Returns undefined if none are found. */
507
+ get(Type) {
497
508
  const result = this[key(Type)];
498
509
  if (result === null)
499
510
  throw new Error(`Did find ${Type} in context, but multiple were defined.`);
@@ -515,9 +526,16 @@ var Context = class _Context {
515
526
  }
516
527
  include(inputs, forEach) {
517
528
  const init2 = /* @__PURE__ */ new Map();
529
+ if (typeof inputs == "function" || inputs instanceof Model)
530
+ inputs = { 0: inputs };
518
531
  Object.entries(inputs).forEach(([K, V]) => {
519
- if (!(Model.is(V) || V instanceof Model))
520
- throw new Error(`Context can only include Model or instance but got ${V}${K && " as " + K}.`);
532
+ if (Model.is(V) || V instanceof Model)
533
+ return;
534
+ if (K && K != V)
535
+ V = `${V} (as '${K}')`;
536
+ throw new Error(
537
+ `Context may only include instance or class \`extends Model\` but got ${V}.`
538
+ );
521
539
  });
522
540
  for (const key2 in inputs) {
523
541
  const input = inputs[key2];
@@ -533,9 +551,11 @@ var Context = class _Context {
533
551
  }
534
552
  }
535
553
  for (const [model, explicit] of init2) {
536
- model.set();
537
- if (forEach)
554
+ emit(model, true);
555
+ if (typeof forEach == "function")
538
556
  forEach(model, explicit);
557
+ else if (forEach && explicit)
558
+ assign(model, forEach);
539
559
  for (const [_key, value] of model)
540
560
  if (PARENT.get(value) === model) {
541
561
  this.add(value, true);
@@ -566,15 +586,15 @@ var Context = class _Context {
566
586
  keys(T).forEach((K) => {
567
587
  const value = this.hasOwnProperty(K) ? null : I;
568
588
  if (value || this[K] !== I && !implicit)
569
- Object.defineProperty(this, K, {
589
+ define(this, K, {
570
590
  configurable: true,
571
591
  value
572
592
  });
573
593
  });
574
- const waiting = Register.get(I);
594
+ const waiting = LOOKUP.get(I);
575
595
  if (waiting instanceof Array)
576
596
  waiting.forEach((cb) => cb(this));
577
- Register.set(I, this);
597
+ LOOKUP.set(I, this);
578
598
  return I;
579
599
  }
580
600
  };
@@ -622,7 +642,7 @@ Model.on((_, model) => {
622
642
  const desc = typeof output == "object" ? output : { get: output };
623
643
  if ("value" in desc)
624
644
  state[key2] = desc.value;
625
- Object.defineProperty(model, key2, {
645
+ define(model, key2, {
626
646
  enumerable: desc.enumerable !== false,
627
647
  get() {
628
648
  return watch(
@@ -675,7 +695,7 @@ function get(arg0, arg1) {
675
695
  }
676
696
  if (typeof arg1 == "function") {
677
697
  let connect2 = function(model) {
678
- reset = effect(model, (current) => {
698
+ reset = createEffect(model, (current) => {
679
699
  proxy = current;
680
700
  if (!(key2 in state) || STALE.delete(compute2))
681
701
  compute2(!reset);
@@ -719,7 +739,6 @@ function get(arg0, arg1) {
719
739
  }
720
740
 
721
741
  // src/instruction/ref.ts
722
- var define2 = Object.defineProperty;
723
742
  function ref(arg, arg2) {
724
743
  return use((key2, subject, state) => {
725
744
  let value = {};
@@ -727,20 +746,20 @@ function ref(arg, arg2) {
727
746
  subject.get(() => {
728
747
  for (const key3 in state)
729
748
  if (typeof arg2 == "function")
730
- define2(value, key3, {
749
+ define(value, key3, {
731
750
  configurable: true,
732
751
  get() {
733
752
  const out = arg2(key3);
734
- define2(value, key3, { value: out });
753
+ define(value, key3, { value: out });
735
754
  return out;
736
755
  }
737
756
  });
738
757
  else {
739
758
  const get2 = () => state[key3];
740
759
  const set2 = (value2) => subject.set(key3, value2);
741
- define2(set2, "current", { get: get2, set: set2 });
742
- define2(set2, "get", { value: get2 });
743
- define2(value, key3, { value: set2 });
760
+ define(set2, "current", { get: get2, set: set2 });
761
+ define(set2, "get", { value: get2 });
762
+ define(value, key3, { value: set2 });
744
763
  }
745
764
  });
746
765
  else if (typeof arg == "object")
@@ -765,7 +784,7 @@ function ref(arg, arg2) {
765
784
  get: { value: get2 }
766
785
  });
767
786
  }
768
- define2(subject, key2, { value });
787
+ define(subject, key2, { value });
769
788
  });
770
789
  }
771
790
 
@@ -852,7 +871,7 @@ function has(arg1, arg2) {
852
871
  };
853
872
  if (Model.is(arg1))
854
873
  Context.get(subject, (context) => {
855
- context.get(arg1, (got) => {
874
+ context.has(arg1, (got) => {
856
875
  let remove;
857
876
  let disconnect;
858
877
  if (applied.has(got))
@@ -916,7 +935,9 @@ function has(arg1, arg2) {
916
935
  0 && (module.exports = {
917
936
  Context,
918
937
  Model,
919
- effect,
938
+ addListener,
939
+ createEffect,
940
+ createProxy,
920
941
  get,
921
942
  has,
922
943
  ref,
package/dist/index.mjs CHANGED
@@ -1,25 +1,30 @@
1
1
  // src/control.ts
2
2
  var DISPATCH = /* @__PURE__ */ new Set();
3
3
  var OBSERVER = /* @__PURE__ */ new WeakMap();
4
- var LISTENER = /* @__PURE__ */ new WeakMap();
4
+ var LISTENERS = /* @__PURE__ */ new WeakMap();
5
+ var PENDING = /* @__PURE__ */ new WeakMap();
5
6
  var onReady = () => null;
6
7
  function addListener(subject, callback, select) {
7
- let subs = LISTENER.get(subject);
8
+ let subs = LISTENERS.get(subject);
8
9
  if (!subs)
9
- LISTENER.set(subject, subs = /* @__PURE__ */ new Map([[onReady, false]]));
10
- const filter = select !== void 0 && /* @__PURE__ */ new Set([select]);
10
+ LISTENERS.set(subject, subs = /* @__PURE__ */ new Map([[onReady, void 0]]));
11
+ const filter = select === void 0 ? void 0 : /* @__PURE__ */ new Set([select]);
11
12
  if (!subs.has(onReady) && !filter)
12
13
  callback(true, subject);
13
14
  subs.set(callback, filter);
14
15
  return () => subs.delete(callback);
15
16
  }
17
+ function createProxy(from, observer) {
18
+ const proxy = Object.create(from);
19
+ OBSERVER.set(proxy, observer);
20
+ return proxy;
21
+ }
16
22
  function watch(from, key2, value) {
17
23
  const access = OBSERVER.get(from);
18
24
  return access ? access(from, key2, value) : value;
19
25
  }
20
- var PENDING = /* @__PURE__ */ new WeakMap();
21
26
  function emit(source, key2) {
22
- const subs = LISTENER.get(source);
27
+ const subs = LISTENERS.get(source);
23
28
  const ready = !subs.has(onReady);
24
29
  if (key2 === true && ready)
25
30
  return;
@@ -45,27 +50,28 @@ function emit(source, key2) {
45
50
  });
46
51
  PENDING.delete(source);
47
52
  }
48
- function queue(event2) {
53
+ function queue(eventHandler) {
49
54
  if (!DISPATCH.size)
50
55
  setTimeout(() => {
51
- DISPATCH.forEach((event3) => {
56
+ DISPATCH.forEach((event2) => {
52
57
  try {
53
- event3();
58
+ event2();
54
59
  } catch (err) {
55
60
  console.error(err);
56
61
  }
57
62
  });
58
63
  DISPATCH.clear();
59
64
  }, 0);
60
- DISPATCH.add(event2);
65
+ DISPATCH.add(eventHandler);
61
66
  }
62
- function effect(target, callback, requireValues) {
63
- const listeners = LISTENER.get(target);
67
+ function createEffect(target, callback, requireValues) {
68
+ const listeners = LISTENERS.get(target);
64
69
  let unset;
65
70
  let reset;
66
71
  function invoke() {
67
72
  let stale;
68
- const subscriber = Object.create(target);
73
+ const subscriber = createProxy(target, access);
74
+ LISTENERS.set(subscriber, listeners);
69
75
  function onUpdate() {
70
76
  if (stale)
71
77
  return null;
@@ -79,20 +85,19 @@ function effect(target, callback, requireValues) {
79
85
  function access(from, key2, value) {
80
86
  if (value === void 0 && requireValues)
81
87
  throw new Error(`${from}.${key2} is required in this context.`);
82
- const listeners2 = LISTENER.get(from);
88
+ const listeners2 = LISTENERS.get(from);
83
89
  let listener = listeners2.get(onUpdate);
84
90
  if (!listener)
85
91
  listeners2.set(onUpdate, listener = /* @__PURE__ */ new Set());
86
92
  listener.add(key2);
87
- const nested = LISTENER.get(value);
88
- if (nested) {
89
- LISTENER.set(value = Object.create(value), nested);
90
- OBSERVER.set(value, access);
91
- }
93
+ const nested = LISTENERS.get(value);
94
+ if (nested)
95
+ LISTENERS.set(
96
+ value = createProxy(value, access),
97
+ nested
98
+ );
92
99
  return value;
93
100
  }
94
- LISTENER.set(subscriber, listeners);
95
- OBSERVER.set(subscriber, access);
96
101
  try {
97
102
  const out = callback.call(subscriber, subscriber);
98
103
  unset = typeof out == "function" ? out : void 0;
@@ -129,27 +134,24 @@ var PENDING2 = /* @__PURE__ */ new WeakMap();
129
134
  var PARENT = /* @__PURE__ */ new WeakMap();
130
135
  var METHODS = /* @__PURE__ */ new WeakMap();
131
136
  var METHOD = /* @__PURE__ */ new WeakMap();
132
- var EXPORT;
133
- var Model = class _Model {
137
+ var Model = class {
134
138
  constructor(...args) {
135
- define(this, "is", { value: this });
136
- ID.set(this, `${this.constructor}-${uid()}`);
137
- STATE.set(this, {});
138
139
  prepare(this);
140
+ define(this, "is", { value: this });
139
141
  init(this, args);
140
142
  }
141
143
  get(arg1, arg2) {
142
144
  const self = this.is;
143
145
  if (arg1 === void 0)
144
- return values(self);
146
+ return snapshot(self);
145
147
  if (typeof arg1 == "function") {
146
148
  let pending = /* @__PURE__ */ new Set();
147
- return effect(self, (state) => {
149
+ return createEffect(self, (state) => {
148
150
  const cb = arg1.call(state, state, pending);
149
151
  return cb === null ? cb : (update2) => {
150
152
  pending = PENDING2.get(self);
151
153
  if (typeof cb == "function")
152
- return cb(update2);
154
+ cb(update2);
153
155
  };
154
156
  });
155
157
  }
@@ -211,7 +213,6 @@ var Model = class _Model {
211
213
  * @param args - arguments sent to constructor
212
214
  */
213
215
  static new(...args) {
214
- _Model.is(this);
215
216
  const instance = new this(...args);
216
217
  emit(instance, true);
217
218
  return instance;
@@ -223,9 +224,7 @@ var Model = class _Model {
223
224
  * methods and properties of this class.
224
225
  */
225
226
  static is(maybe) {
226
- if (maybe === _Model)
227
- throw new Error("Model is abstract and not a valid type.");
228
- return typeof maybe == "function" && maybe.prototype instanceof this;
227
+ return maybe === this || typeof maybe == "function" && maybe.prototype instanceof this;
229
228
  }
230
229
  /**
231
230
  * Register a callback to run when any instance of this Model is updated.
@@ -249,48 +248,50 @@ define(Model, "toString", {
249
248
  }
250
249
  });
251
250
  function prepare(model) {
251
+ let T = model.constructor;
252
+ if (T === Model)
253
+ throw new Error("Cannot create base Model.");
252
254
  const chain = [];
253
- let type = model.constructor;
254
255
  let keys2 = /* @__PURE__ */ new Set();
255
- while (type.name) {
256
- chain.unshift(type);
257
- type = Object.getPrototypeOf(type);
258
- }
259
- for (const type2 of chain) {
260
- for (const cb of NOTIFY.get(type2) || [])
256
+ ID.set(model, `${T}-${uid()}`);
257
+ while (T.name) {
258
+ for (const cb of NOTIFY.get(T) || [])
261
259
  addListener(model, cb);
262
- if (type2 == Model)
263
- continue;
264
- if (METHODS.has(type2)) {
265
- keys2 = METHODS.get(type2);
260
+ if (T === Model)
261
+ break;
262
+ else
263
+ chain.unshift(T);
264
+ T = Object.getPrototypeOf(T);
265
+ }
266
+ for (const type of chain) {
267
+ if (METHODS.has(type)) {
268
+ keys2 = METHODS.get(type);
266
269
  continue;
267
270
  }
268
- METHODS.set(type2, keys2 = new Set(keys2));
271
+ METHODS.set(type, keys2 = new Set(keys2));
269
272
  for (const [key2, { value }] of Object.entries(
270
- Object.getOwnPropertyDescriptors(type2.prototype)
273
+ Object.getOwnPropertyDescriptors(type.prototype)
271
274
  )) {
272
- let set3 = function(method) {
275
+ let get3 = function() {
276
+ return this.hasOwnProperty(key2) ? value : set3.call(this, value);
277
+ }, set3 = function(method) {
273
278
  const value2 = method.bind(this.is);
274
279
  METHOD.set(value2, method);
275
280
  define(this.is, key2, { value: value2, writable: true });
276
281
  return value2;
277
282
  };
278
- var set2 = set3;
283
+ var get2 = get3, set2 = set3;
279
284
  if (!value || key2 == "constructor")
280
285
  continue;
281
286
  keys2.add(key2);
282
- Object.defineProperty(type2.prototype, key2, {
283
- set: set3,
284
- get() {
285
- return this.hasOwnProperty(key2) ? value : set3.call(this, value);
286
- }
287
- });
287
+ define(type.prototype, key2, { get: get3, set: set3 });
288
288
  }
289
289
  }
290
290
  }
291
291
  function init(model, args) {
292
- const done = /* @__PURE__ */ new Set();
293
- const state = STATE.get(model);
292
+ const state = {};
293
+ const clean = /* @__PURE__ */ new Set();
294
+ STATE.set(model, state);
294
295
  args = args.flat().filter((arg) => {
295
296
  if (typeof arg != "string")
296
297
  return true;
@@ -306,10 +307,10 @@ function init(model, args) {
306
307
  console.error(`Async error in constructor for ${model}:`);
307
308
  console.error(err);
308
309
  });
309
- else if (typeof use2 == "function")
310
- done.add(use2);
311
310
  else if (typeof use2 == "object")
312
311
  assign(model, use2);
312
+ else if (typeof use2 == "function")
313
+ clean.add(use2);
313
314
  });
314
315
  for (const key2 in model) {
315
316
  const desc = Object.getOwnPropertyDescriptor(model, key2);
@@ -327,7 +328,7 @@ function init(model, args) {
327
328
  return null;
328
329
  });
329
330
  addListener(model, () => {
330
- done.forEach((x) => x());
331
+ clean.forEach((x) => x());
331
332
  for (const [_, value] of model)
332
333
  if (value instanceof Model && PARENT.get(value) === model)
333
334
  value.set(null);
@@ -359,19 +360,24 @@ function fetch(subject, property, required) {
359
360
  stack: error.stack
360
361
  });
361
362
  }
362
- function values(model) {
363
- const values2 = {};
364
- const current = !EXPORT && (EXPORT = /* @__PURE__ */ new Map([[model, values2]]));
363
+ var EXPORT;
364
+ function snapshot(model) {
365
+ const values = {};
366
+ let isNotRecursive;
367
+ if (!EXPORT) {
368
+ isNotRecursive = true;
369
+ EXPORT = /* @__PURE__ */ new Map([[model, values]]);
370
+ }
365
371
  for (let [key2, value] of model) {
366
372
  if (EXPORT.has(value))
367
373
  value = EXPORT.get(value);
368
- else if (value && typeof value.get === "function")
374
+ else if (value && typeof value == "object" && "get" in value && typeof value.get === "function")
369
375
  EXPORT.set(value, value = value.get());
370
- values2[key2] = value;
376
+ values[key2] = value;
371
377
  }
372
- if (current)
378
+ if (isNotRecursive)
373
379
  EXPORT = void 0;
374
- return Object.freeze(values2);
380
+ return Object.freeze(values);
375
381
  }
376
382
  function update(subject, key2, value, arg) {
377
383
  const state = STATE.get(subject);
@@ -409,23 +415,23 @@ function event(subject, key2, silent) {
409
415
  if (!silent)
410
416
  emit(subject, key2);
411
417
  }
412
- function assign(to, values2) {
418
+ function assign(to, values) {
413
419
  const methods = METHODS.get(to.constructor);
414
- for (const key2 in values2)
420
+ for (const key2 in values)
415
421
  if (key2 in to || methods.has(key2))
416
- to[key2] = values2[key2];
422
+ to[key2] = values[key2];
417
423
  }
418
424
  function uid() {
419
425
  return (Math.random() * 0.722 + 0.278).toString(36).substring(2, 8).toUpperCase();
420
426
  }
421
427
 
422
428
  // src/context.ts
423
- var Register = /* @__PURE__ */ new WeakMap();
424
- var Table = /* @__PURE__ */ new Map();
429
+ var LOOKUP = /* @__PURE__ */ new WeakMap();
430
+ var KEYS = /* @__PURE__ */ new Map();
425
431
  function key(T, upstream) {
426
- let K = Table.get(T);
432
+ let K = KEYS.get(T);
427
433
  if (!K)
428
- Table.set(T, K = Symbol(
434
+ KEYS.set(T, K = Symbol(
429
435
  typeof T == "symbol" ? "get " + T.description : String(T)
430
436
  ));
431
437
  return upstream ? key(K) : K;
@@ -445,7 +451,7 @@ var Context = class _Context {
445
451
  this.include(inputs);
446
452
  }
447
453
  static get(from, callback) {
448
- const waiting = Register.get(from);
454
+ const waiting = LOOKUP.get(from);
449
455
  if (waiting instanceof _Context) {
450
456
  if (callback)
451
457
  callback(waiting);
@@ -455,11 +461,14 @@ var Context = class _Context {
455
461
  if (waiting)
456
462
  waiting.push(callback);
457
463
  else
458
- Register.set(from, [callback]);
464
+ LOOKUP.set(from, [callback]);
459
465
  }
460
- get(Type, callback) {
461
- if (callback)
462
- Object.defineProperty(this, key(Type, true), { value: callback });
466
+ /** Run callback when a specified type is registered to a **child** context. */
467
+ has(Type, callback) {
468
+ define(this, key(Type, true), { value: callback });
469
+ }
470
+ /** Find specified type registered to a parent context. Returns undefined if none are found. */
471
+ get(Type) {
463
472
  const result = this[key(Type)];
464
473
  if (result === null)
465
474
  throw new Error(`Did find ${Type} in context, but multiple were defined.`);
@@ -481,9 +490,16 @@ var Context = class _Context {
481
490
  }
482
491
  include(inputs, forEach) {
483
492
  const init2 = /* @__PURE__ */ new Map();
493
+ if (typeof inputs == "function" || inputs instanceof Model)
494
+ inputs = { 0: inputs };
484
495
  Object.entries(inputs).forEach(([K, V]) => {
485
- if (!(Model.is(V) || V instanceof Model))
486
- throw new Error(`Context can only include Model or instance but got ${V}${K && " as " + K}.`);
496
+ if (Model.is(V) || V instanceof Model)
497
+ return;
498
+ if (K && K != V)
499
+ V = `${V} (as '${K}')`;
500
+ throw new Error(
501
+ `Context may only include instance or class \`extends Model\` but got ${V}.`
502
+ );
487
503
  });
488
504
  for (const key2 in inputs) {
489
505
  const input = inputs[key2];
@@ -499,9 +515,11 @@ var Context = class _Context {
499
515
  }
500
516
  }
501
517
  for (const [model, explicit] of init2) {
502
- model.set();
503
- if (forEach)
518
+ emit(model, true);
519
+ if (typeof forEach == "function")
504
520
  forEach(model, explicit);
521
+ else if (forEach && explicit)
522
+ assign(model, forEach);
505
523
  for (const [_key, value] of model)
506
524
  if (PARENT.get(value) === model) {
507
525
  this.add(value, true);
@@ -532,15 +550,15 @@ var Context = class _Context {
532
550
  keys(T).forEach((K) => {
533
551
  const value = this.hasOwnProperty(K) ? null : I;
534
552
  if (value || this[K] !== I && !implicit)
535
- Object.defineProperty(this, K, {
553
+ define(this, K, {
536
554
  configurable: true,
537
555
  value
538
556
  });
539
557
  });
540
- const waiting = Register.get(I);
558
+ const waiting = LOOKUP.get(I);
541
559
  if (waiting instanceof Array)
542
560
  waiting.forEach((cb) => cb(this));
543
- Register.set(I, this);
561
+ LOOKUP.set(I, this);
544
562
  return I;
545
563
  }
546
564
  };
@@ -588,7 +606,7 @@ Model.on((_, model) => {
588
606
  const desc = typeof output == "object" ? output : { get: output };
589
607
  if ("value" in desc)
590
608
  state[key2] = desc.value;
591
- Object.defineProperty(model, key2, {
609
+ define(model, key2, {
592
610
  enumerable: desc.enumerable !== false,
593
611
  get() {
594
612
  return watch(
@@ -641,7 +659,7 @@ function get(arg0, arg1) {
641
659
  }
642
660
  if (typeof arg1 == "function") {
643
661
  let connect2 = function(model) {
644
- reset = effect(model, (current) => {
662
+ reset = createEffect(model, (current) => {
645
663
  proxy = current;
646
664
  if (!(key2 in state) || STALE.delete(compute2))
647
665
  compute2(!reset);
@@ -685,7 +703,6 @@ function get(arg0, arg1) {
685
703
  }
686
704
 
687
705
  // src/instruction/ref.ts
688
- var define2 = Object.defineProperty;
689
706
  function ref(arg, arg2) {
690
707
  return use((key2, subject, state) => {
691
708
  let value = {};
@@ -693,20 +710,20 @@ function ref(arg, arg2) {
693
710
  subject.get(() => {
694
711
  for (const key3 in state)
695
712
  if (typeof arg2 == "function")
696
- define2(value, key3, {
713
+ define(value, key3, {
697
714
  configurable: true,
698
715
  get() {
699
716
  const out = arg2(key3);
700
- define2(value, key3, { value: out });
717
+ define(value, key3, { value: out });
701
718
  return out;
702
719
  }
703
720
  });
704
721
  else {
705
722
  const get2 = () => state[key3];
706
723
  const set2 = (value2) => subject.set(key3, value2);
707
- define2(set2, "current", { get: get2, set: set2 });
708
- define2(set2, "get", { value: get2 });
709
- define2(value, key3, { value: set2 });
724
+ define(set2, "current", { get: get2, set: set2 });
725
+ define(set2, "get", { value: get2 });
726
+ define(value, key3, { value: set2 });
710
727
  }
711
728
  });
712
729
  else if (typeof arg == "object")
@@ -731,7 +748,7 @@ function ref(arg, arg2) {
731
748
  get: { value: get2 }
732
749
  });
733
750
  }
734
- define2(subject, key2, { value });
751
+ define(subject, key2, { value });
735
752
  });
736
753
  }
737
754
 
@@ -818,7 +835,7 @@ function has(arg1, arg2) {
818
835
  };
819
836
  if (Model.is(arg1))
820
837
  Context.get(subject, (context) => {
821
- context.get(arg1, (got) => {
838
+ context.has(arg1, (got) => {
822
839
  let remove;
823
840
  let disconnect;
824
841
  if (applied.has(got))
@@ -881,8 +898,10 @@ function has(arg1, arg2) {
881
898
  export {
882
899
  Context,
883
900
  Model,
901
+ addListener,
902
+ createEffect,
903
+ createProxy,
884
904
  Model as default,
885
- effect,
886
905
  get,
887
906
  has,
888
907
  ref,
package/package.json CHANGED
@@ -32,6 +32,6 @@
32
32
  "jest": "^27.0.6",
33
33
  "tsup": "^8.0.2"
34
34
  },
35
- "version": "0.52.1",
36
- "gitHead": "71f17a90b44bd4b969a56b38d629f32b289c77b7"
35
+ "version": "0.52.2-alpha.74+088f4ba5",
36
+ "gitHead": "088f4ba53762f0b185871570c58f349084eb29af"
37
37
  }