j-templates 9.0.1 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/SYNTAX_PRIMER.md +181 -101
  2. package/Store/Diff/channelClient.d.ts +104 -0
  3. package/Store/Diff/channelClient.js +225 -0
  4. package/Store/Diff/defaultDiffTreeWorker.js +3 -4
  5. package/Store/Diff/diffTree.d.ts +56 -0
  6. package/Store/Diff/diffTree.js +110 -4
  7. package/Store/Diff/diffTree.types.d.ts +186 -21
  8. package/Store/Diff/diffTree.types.js +2 -10
  9. package/Store/Diff/diffTreeEndpoint.d.ts +101 -0
  10. package/Store/Diff/diffTreeEndpoint.js +173 -0
  11. package/Store/Diff/diffTreeEndpoint.types.d.ts +71 -0
  12. package/Store/Diff/diffTreeEndpoint.types.js +1 -0
  13. package/Store/Diff/diffTreeHub.d.ts +139 -0
  14. package/Store/Diff/diffTreeHub.js +346 -0
  15. package/Store/Diff/hostPublisher.d.ts +38 -0
  16. package/Store/Diff/hostPublisher.js +147 -0
  17. package/Store/Diff/index.d.ts +3 -0
  18. package/Store/Diff/index.js +3 -0
  19. package/Store/Diff/observableDiffTree.d.ts +93 -0
  20. package/Store/Diff/observableDiffTree.js +200 -0
  21. package/Store/Diff/onConnect.d.ts +33 -0
  22. package/Store/Diff/onConnect.js +83 -0
  23. package/Store/Diff/utils/diffTree.types.d.ts +9 -0
  24. package/Store/Diff/utils/homes.d.ts +12 -0
  25. package/Store/Diff/utils/homes.js +40 -0
  26. package/Store/Diff/utils/paths.d.ts +2 -1
  27. package/Store/Diff/utils/paths.js +3 -2
  28. package/Store/Diff/utils/routes.d.ts +18 -0
  29. package/Store/Diff/utils/routes.js +33 -2
  30. package/Store/Diff/utils/state.js +4 -3
  31. package/Store/Store/store.d.ts +41 -4
  32. package/Store/Store/store.js +62 -7
  33. package/Store/Store/storeAsync.d.ts +134 -25
  34. package/Store/Store/storeAsync.js +235 -33
  35. package/Store/Store/storeSync.d.ts +91 -11
  36. package/Store/Store/storeSync.js +107 -12
  37. package/Store/Tree/observableNode.d.ts +3 -0
  38. package/Store/Tree/observableNode.js +87 -13
  39. package/Store/Tree/observableScope.d.ts +3 -1
  40. package/Store/Tree/observableScope.js +12 -0
  41. package/Store/Tree/opaque.d.ts +30 -0
  42. package/Store/Tree/opaque.js +35 -0
  43. package/Store/Tree/snapshot.js +5 -4
  44. package/Store/index.d.ts +4 -1
  45. package/Store/index.js +1 -1
  46. package/Utils/decorators.d.ts +0 -98
  47. package/Utils/decorators.js +0 -156
  48. package/Utils/emitter.d.ts +11 -4
  49. package/Utils/emitter.js +25 -2
  50. package/Utils/index.d.ts +2 -0
  51. package/Utils/index.js +1 -0
  52. package/Utils/pattern.d.ts +14 -0
  53. package/Utils/pattern.js +20 -0
  54. package/Utils/subscriptionManager.d.ts +78 -0
  55. package/Utils/subscriptionManager.js +159 -0
  56. package/package.json +1 -1
  57. package/Store/Diff/diffTreeWorker.d.ts +0 -6
  58. package/Store/Diff/diffTreeWorker.js +0 -22
  59. package/Store/Diff/diffTreeWriter.d.ts +0 -92
  60. package/Store/Diff/diffTreeWriter.js +0 -137
  61. package/Store/Diff/workerQueue.d.ts +0 -7
  62. package/Store/Diff/workerQueue.js +0 -29
  63. package/Store/Store/pushStore.d.ts +0 -53
  64. package/Store/Store/pushStore.js +0 -77
@@ -1,6 +1,7 @@
1
1
  import { JsonApplyDiff, JsonDeepClone, JsonDiff } from "../../Utils/json";
2
2
  import { JsonType } from "../../Utils/json";
3
3
  import { ObservableScope } from "./observableScope";
4
+ import { Opaque } from "./opaque";
4
5
  import { Snapshot } from "./snapshot";
5
6
  const NODE_VALUE = Symbol("NODE_VALUE");
6
7
  const NODE_PROXY = Symbol("NODE_PROXY");
@@ -142,6 +143,45 @@ function AssignNodeValue(obj, prop, value, wrapper) {
142
143
  obj[prop] = value;
143
144
  NodeValueSet(obj, prop, added, wrapper);
144
145
  }
146
+ /** `Array.prototype.sort`'s order when it's given no compare function: by each value as a string */
147
+ function DefaultCompare(a, b) {
148
+ const x = String(a);
149
+ const y = String(b);
150
+ return x < y ? -1 : x > y ? 1 : 0;
151
+ }
152
+ /**
153
+ * Runs a mutating array method on a node's stored array. Values passed in are stored as
154
+ * `UnwrapProxy` leaves them, values handed out (removed elements, a sort's compared pair) are read
155
+ * as `read` gives them, so nothing already stored is rewritten. Returns the array itself for `sort`
156
+ * and `reverse`, which the caller hands out as the array's proxy.
157
+ */
158
+ function MutateArray(array, prop, args, read) {
159
+ switch (prop) {
160
+ case "push":
161
+ case "unshift":
162
+ return array[prop](...args.map(StoredValue));
163
+ case "splice": {
164
+ // Spread so a call without a delete count still deletes to the end
165
+ const stored = args.length > 2 ? [args[0], args[1], ...args.slice(2).map(StoredValue)] : args;
166
+ return array.splice(...stored).map(read);
167
+ }
168
+ case "pop":
169
+ case "shift": {
170
+ // An empty array returns undefined, which reads as itself
171
+ return read(array[prop]());
172
+ }
173
+ case "sort": {
174
+ const compare = args[0] ?? DefaultCompare;
175
+ return array.sort((a, b) => compare(read(a), read(b)));
176
+ }
177
+ case "reverse":
178
+ return array.reverse();
179
+ }
180
+ }
181
+ /** A value as a node stores it: proxies removed, an Opaque kept as its box */
182
+ function StoredValue(value) {
183
+ return UnwrapProxy(value);
184
+ }
145
185
  function CreateProxyFactory(alias) {
146
186
  // Keyed children are read through their root. Every factory shares the module-level
147
187
  // snapshotState, matching wrapperCache; only the resolver differs per factory.
@@ -150,6 +190,20 @@ function CreateProxyFactory(alias) {
150
190
  return Snapshot.Get(snapshotState, value, resolveSnapshot);
151
191
  }
152
192
  const readOnly = alias !== undefined;
193
+ /**
194
+ * A descriptor trap whose value is what a read gives: a proxy for an object or array, an
195
+ * Opaque's raw value, never the stored value itself
196
+ */
197
+ function ReadDescriptor(trap) {
198
+ return function (target, prop) {
199
+ const descriptor = trap(target, prop);
200
+ if (descriptor && "value" in descriptor)
201
+ descriptor.value = CreateProxyFromValue(descriptor.value);
202
+ return descriptor;
203
+ };
204
+ }
205
+ const readObjectDescriptor = ReadDescriptor(getOwnPropertyDescriptor);
206
+ const readArrayDescriptor = ReadDescriptor(getOwnPropertyDescriptorArray);
153
207
  function CreateArrayProxy(value) {
154
208
  const wrapper = Object.assign([], {
155
209
  [NODE_VALUE]: value,
@@ -161,7 +215,7 @@ function CreateProxyFactory(alias) {
161
215
  set: ArrayProxySetter,
162
216
  has: hasArray,
163
217
  ownKeys: ownKeysArray,
164
- getOwnPropertyDescriptor: getOwnPropertyDescriptorArray,
218
+ getOwnPropertyDescriptor: readArrayDescriptor,
165
219
  });
166
220
  wrapperCache.set(value, wrapper);
167
221
  return proxy;
@@ -206,10 +260,6 @@ function CreateProxyFactory(alias) {
206
260
  return arrayValue;
207
261
  if (typeof arrayValue === "function")
208
262
  return function ArrayFunction(...args) {
209
- const proxyArray = prop === "slice" ? array.slice(...args).map(CreateProxyFromValue) : array.map(CreateProxyFromValue);
210
- let result = prop === "slice"
211
- ? proxyArray
212
- : proxyArray[prop](...args);
213
263
  switch (prop) {
214
264
  case "push":
215
265
  case "unshift":
@@ -217,15 +267,19 @@ function CreateProxyFactory(alias) {
217
267
  case "pop":
218
268
  case "shift":
219
269
  case "sort":
220
- case "reverse":
221
- array.length = proxyArray.length;
222
- for (let x = 0; x < proxyArray.length; x++)
223
- array[x] = UnwrapProxy(proxyArray[x]);
270
+ case "reverse": {
271
+ // Run on the stored array, so what it holds (an Opaque's box included) stays as
272
+ // it is: arguments go in as stored values, results come out as read values
273
+ const result = MutateArray(array, prop, args, CreateProxyFromValue);
224
274
  InvalidateSnapshotForValue(object[NODE_VALUE]);
225
275
  ObservableScope.Update(scope);
226
- break;
276
+ return result === array ? object[NODE_PROXY] : result;
277
+ }
227
278
  }
228
- return result;
279
+ const proxyArray = prop === "slice" ? array.slice(...args).map(CreateProxyFromValue) : array.map(CreateProxyFromValue);
280
+ return prop === "slice"
281
+ ? proxyArray
282
+ : proxyArray[prop](...args);
229
283
  };
230
284
  const proxy = CreateProxyFromValue(arrayValue);
231
285
  return proxy;
@@ -243,7 +297,7 @@ function CreateProxyFactory(alias) {
243
297
  set: ObjectProxySetter,
244
298
  has,
245
299
  ownKeys,
246
- getOwnPropertyDescriptor,
300
+ getOwnPropertyDescriptor: readObjectDescriptor,
247
301
  });
248
302
  wrapperCache.set(value, wrapper);
249
303
  return proxy;
@@ -301,7 +355,8 @@ function CreateProxyFactory(alias) {
301
355
  return proxy;
302
356
  }
303
357
  default:
304
- return value;
358
+ // An Opaque is stored as its box and read as its raw value
359
+ return Opaque.Open(value);
305
360
  }
306
361
  }
307
362
  return function CreateProxy(value, unwrapValue = true) {
@@ -361,6 +416,25 @@ export var ObservableNode;
361
416
  return DefaultCreateProxy(value, unwrapValue);
362
417
  }
363
418
  ObservableNode.Create = Create;
419
+ function WatchProperty(proxy, prop, callback) {
420
+ if (!proxy[IS_NODE])
421
+ throw new Error("Parameter proxy is not an ObservableNode");
422
+ const wrapper = proxy[NODE_WRAPPER];
423
+ if (Array.isArray(wrapper))
424
+ throw new Error("Unable to watch individual property on array proxy");
425
+ const scope = GetPropertyScope(wrapper, prop);
426
+ ObservableScope.WatchSubscribers(scope, callback);
427
+ }
428
+ ObservableNode.WatchProperty = WatchProperty;
429
+ function UnwatchProperty(proxy, prop, callback) {
430
+ if (!proxy[IS_NODE])
431
+ throw new Error("Parameter proxy is not an ObservableNode");
432
+ const wrapper = proxy[NODE_WRAPPER];
433
+ if (Array.isArray(wrapper))
434
+ return;
435
+ Object.hasOwn(wrapper, prop) && ObservableScope.UnwatchSubscribers(wrapper[prop], callback);
436
+ }
437
+ ObservableNode.UnwatchProperty = UnwatchProperty;
364
438
  /**
365
439
  * Returns an immutable, cached snapshot of an observable node's current value (plain
366
440
  * objects/arrays, with keyed children read through their root). The same snapshot is
@@ -1,4 +1,4 @@
1
- import { Emitter, EmitterCallback } from "../../Utils/emitter";
1
+ import { Emitter, EmitterCallback, EmitterEventCallback } from "../../Utils/emitter";
2
2
  /**
3
3
  * Represents a static (non-reactive) observable scope.
4
4
  * Static scopes are immutable and do not track dependencies or emit updates.
@@ -189,6 +189,8 @@ export declare namespace ObservableScope {
189
189
  * @param callback The callback function to remove.
190
190
  */
191
191
  function Unwatch<T>(scope: IObservableScope<T> | IBasicObservableScope<T>, callback: (scope: IObservableScope<T>) => void): void;
192
+ function WatchSubscribers<T = any>(scope: IObservableScope<T> | IBasicObservableScope<T>, callback: EmitterEventCallback): void;
193
+ function UnwatchSubscribers<T = any>(scope: IObservableScope<T> | IBasicObservableScope<T>, callback: EmitterEventCallback): void;
192
194
  /**
193
195
  * Registers a callback to be invoked when the scope's value is updated.
194
196
  * Only applies to dynamic scopes; no-op for static scopes.
@@ -639,6 +639,18 @@ export var ObservableScope;
639
639
  Emitter.Remove(scope.emitter, callback);
640
640
  }
641
641
  ObservableScope.Unwatch = Unwatch;
642
+ function WatchSubscribers(scope, callback) {
643
+ if (!scope || scope.destroyed || scope.type === "static")
644
+ return;
645
+ Emitter.Watch(scope.emitter, callback);
646
+ }
647
+ ObservableScope.WatchSubscribers = WatchSubscribers;
648
+ function UnwatchSubscribers(scope, callback) {
649
+ if (!scope || scope.destroyed || scope.type === "static")
650
+ return;
651
+ Emitter.Unwatch(scope.emitter, callback);
652
+ }
653
+ ObservableScope.UnwatchSubscribers = UnwatchSubscribers;
642
654
  /**
643
655
  * Registers a callback to be invoked when the scope's value is updated.
644
656
  * Only applies to dynamic scopes; no-op for static scopes.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A value an ObservableNode holds whole: it's never proxied, diffed into or cloned. A node stores
3
+ * the box and reads open it, so a reader gets the raw value, the same reference until the box is
4
+ * replaced. The property holding the box is the only thing observed: replacing the box updates its
5
+ * readers, and a change made inside the raw value notifies no one, so treat it as read-only.
6
+ *
7
+ * - Write one into a node by assigning it (`node.view = new Opaque(view)`), pushing or splicing it
8
+ * into a node's array, or putting it in a value given to `ObservableNode.Create` or returned from
9
+ * a `@Computed` getter.
10
+ * - A store holds a root whole when the root's key is in the store's `opaque` option. The store
11
+ * boxes the root itself; don't pass an `Opaque` to a store's `Write`, `Patch`, `Push` or `Splice`.
12
+ * - Reads, array methods, property descriptors, snapshots and `JSON.stringify` give the raw value,
13
+ * so an object composed from a node's reads holds no box. `ObservableNode.Unwrap` gives a node's
14
+ * storage, boxes included, and `ObservableNode.Clone` keeps a box it's given: writing either back
15
+ * into a node keeps the value opaque, but don't pass them to a store.
16
+ *
17
+ * Reading a box's value through a node and writing it somewhere else writes the raw value, which is
18
+ * then plain data again: box it again (`new Opaque(value)`) to keep it whole.
19
+ */
20
+ export declare class Opaque<T = unknown> {
21
+ readonly value: T;
22
+ constructor(value: T);
23
+ /** The raw value, so `JSON.stringify` writes the value, not the box */
24
+ toJSON(): T;
25
+ /**
26
+ * The raw value of a box, or the value itself when it isn't one.
27
+ * @param value Any value.
28
+ */
29
+ static Open<V>(value: V | Opaque<V>): V;
30
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A value an ObservableNode holds whole: it's never proxied, diffed into or cloned. A node stores
3
+ * the box and reads open it, so a reader gets the raw value, the same reference until the box is
4
+ * replaced. The property holding the box is the only thing observed: replacing the box updates its
5
+ * readers, and a change made inside the raw value notifies no one, so treat it as read-only.
6
+ *
7
+ * - Write one into a node by assigning it (`node.view = new Opaque(view)`), pushing or splicing it
8
+ * into a node's array, or putting it in a value given to `ObservableNode.Create` or returned from
9
+ * a `@Computed` getter.
10
+ * - A store holds a root whole when the root's key is in the store's `opaque` option. The store
11
+ * boxes the root itself; don't pass an `Opaque` to a store's `Write`, `Patch`, `Push` or `Splice`.
12
+ * - Reads, array methods, property descriptors, snapshots and `JSON.stringify` give the raw value,
13
+ * so an object composed from a node's reads holds no box. `ObservableNode.Unwrap` gives a node's
14
+ * storage, boxes included, and `ObservableNode.Clone` keeps a box it's given: writing either back
15
+ * into a node keeps the value opaque, but don't pass them to a store.
16
+ *
17
+ * Reading a box's value through a node and writing it somewhere else writes the raw value, which is
18
+ * then plain data again: box it again (`new Opaque(value)`) to keep it whole.
19
+ */
20
+ export class Opaque {
21
+ constructor(value) {
22
+ this.value = value;
23
+ }
24
+ /** The raw value, so `JSON.stringify` writes the value, not the box */
25
+ toJSON() {
26
+ return this.value;
27
+ }
28
+ /**
29
+ * The raw value of a box, or the value itself when it isn't one.
30
+ * @param value Any value.
31
+ */
32
+ static Open(value) {
33
+ return value instanceof Opaque ? value.value : value;
34
+ }
35
+ }
@@ -1,4 +1,5 @@
1
1
  import { JsonType } from "../../Utils/json";
2
+ import { Opaque } from "./opaque";
2
3
  function CreateSnapshotLink(state, child, parent) {
3
4
  let parents = state.hierarchy.get(child);
4
5
  if (!parents)
@@ -21,15 +22,15 @@ function InvalidateValue(state, value) {
21
22
  InvalidateValue(state, parent);
22
23
  }
23
24
  /**
24
- * An immutable snapshot of a raw value (arrays and plain objects; other values are
25
- * returned as they are). A snapshot is cached per raw value and returned as-is until a
25
+ * An immutable snapshot of a raw value (arrays and plain objects; an Opaque gives its raw
26
+ * value, and other values are returned as they are). A snapshot is cached per raw value and returned as-is until a
26
27
  * write invalidates it. A rebuild makes new containers but reuses the cached snapshots of
27
28
  * unchanged children, so a snapshot handed out never changes and unchanged parts are
28
29
  * shared between snapshots. Keyed children are read through their root.
29
30
  */
30
31
  function CreateSnapshot(state, resolve, value) {
31
32
  if (JsonType(value) === "value")
32
- return value;
33
+ return Opaque.Open(value);
33
34
  const cached = state.rawToSnapshotCache.get(value);
34
35
  if (cached !== undefined)
35
36
  return cached;
@@ -57,7 +58,7 @@ function CreateSnapshot(state, resolve, value) {
57
58
  function ChildSnapshot(state, resolve, parent, key) {
58
59
  const child = parent[key];
59
60
  if (JsonType(child) === "value")
60
- return child;
61
+ return Opaque.Open(child);
61
62
  CreateSnapshotLink(state, child, parent);
62
63
  const resolved = resolve?.(child);
63
64
  if (resolved !== undefined && resolved !== child) {
package/Store/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export { ReadStore, Store } from "./Store/store";
2
+ export type { IStoreOptions } from "./Store/store";
3
+ export type { IStoreAsyncOptions } from "./Store/storeAsync";
4
+ export type { IStoreSyncOptions } from "./Store/storeSync";
2
5
  export { StoreSync } from "./Store/storeSync";
3
6
  export { StoreAsync } from "./Store/storeAsync";
4
- export { PushStore } from "./Store/pushStore";
5
7
  export { ObservableScope } from "./Tree/observableScope";
6
8
  export { ObservableNode } from "./Tree/observableNode";
9
+ export { Opaque } from "./Tree/opaque";
package/Store/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ReadStore, Store } from "./Store/store";
2
2
  export { StoreSync } from "./Store/storeSync";
3
3
  export { StoreAsync } from "./Store/storeAsync";
4
- export { PushStore } from "./Store/pushStore";
5
4
  export { ObservableScope } from "./Tree/observableScope";
6
5
  export { ObservableNode } from "./Tree/observableNode";
6
+ export { Opaque } from "./Tree/opaque";
@@ -5,7 +5,6 @@
5
5
  * @module Utils/Decorators
6
6
  * @see ObservableScope
7
7
  * @see ObservableNode
8
- * @see StoreAsync
9
8
  * @see StoreSync
10
9
  * @see Component
11
10
  *
@@ -20,7 +19,6 @@
20
19
  * | arrays (User[], Todo[]) | @State | Array mutations tracked |
21
20
  * | getters only (expensive) | @Computed | Cached + object reuse via Store |
22
21
  * | getters only (simple) | @Scope | Cached, but new reference on update |
23
- * | getters only (sync, StoreAsync) | @ComputedAsync | Sync getter, StoreAsync caching + object reuse |
24
22
  * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
25
23
  * | subscribe to changes | @Watch | Calls method when scope value changes |
26
24
  * | dependency injection | @Inject | Gets value from component injector |
@@ -157,104 +155,10 @@ import { Injector } from "./injector";
157
155
  * - Use @Scope() for simple computations where object identity doesn't matter
158
156
  * - The diff computation overhead is justified when you need object reuse
159
157
  * @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
160
- * @see {@link ComputedAsync} for async computed properties
161
158
  * @see {@link ObservableNode.Apply} for how diffs are applied in-place to maintain object identity
162
159
  * @see {@link ObservableScope} for the Gated scope mechanism (batched updates)
163
- * @see {@link StoreSync} for the store-backed variant used by @ComputedAsync
164
160
  */
165
161
  export declare function Computed<T extends WeakKey, K extends keyof T, D extends T[K]>(): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => PropertyDescriptor;
166
- /**
167
- * ComputedAsync decorator factory for creating synchronous computed properties with StoreAsync caching.
168
- * A computed property is derived from other properties and automatically updates when its dependencies change.
169
- *
170
- * IMPORTANT: Despite the name, @ComputedAsync expects a SYNC getter, NOT an async function.
171
- * The "Async" refers to the internal StoreAsync caching mechanism, not the getter signature.
172
- *
173
- * Use @ComputedAsync when you need synchronous caching with StoreAsync backend.
174
- * For direct async operations (no object reuse), use ObservableScope.Create(async () => ...) instead.
175
- *
176
- * @example
177
- * ```typescript
178
- * // ✅ Good: Synchronous getter with StoreAsync caching
179
- * @Value()
180
- * userId: string = "123";
181
- *
182
- * @ComputedAsync(null)
183
- * get userData(): User | null {
184
- * // Sync getter - returns value directly
185
- * return getUserSync(this.userId);
186
- * }
187
- * ```
188
- *
189
- * @example
190
- * ```typescript
191
- * // ❌ WRONG - Do NOT use async keyword or return Promise
192
- * @Value()
193
- * userId: string = "123";
194
- *
195
- * @ComputedAsync(null)
196
- * async getUser(): Promise<User> { // ERROR: getter must be synchronous!
197
- * return await fetch(`/api/users/${this.userId}`);
198
- * }
199
- *
200
- * // ✅ CORRECT - Use ObservableScope.Create for direct async operations
201
- * import { ObservableScope } from "j-templates/Store";
202
- *
203
- * @Value()
204
- * userId: string = "123";
205
- *
206
- * private userDataScope = ObservableScope.Create(async () => {
207
- * return await fetch(`/api/users/${this.userId}`); // Async supported here!
208
- * });
209
- *
210
- * get userData(): User | null {
211
- * return ObservableScope.Value(this.userDataScope); // null while loading
212
- * }
213
- * ```
214
- *
215
- * @param defaultValue The default value to be used if the computed property is not defined.
216
- * @returns A property decorator that can be applied to a getter method.
217
- * @throws Will throw an error if the property is not a getter or if it has a setter.
218
- * @remarks
219
- * The @ComputedAsync decorator uses StoreAsync for caching. The getter must be a synchronous function.
220
- * Unlike @Computed which uses StoreSync, @ComputedAsync uses StoreAsync internally for caching.
221
- *
222
- * **Initialization**: @ComputedAsync uses lazy initialization - the scopes are created on first access:
223
- * ```typescript
224
- * @ComputedAsync(null)
225
- * get userData(): User | null {
226
- * return getUserSync(this.userId);
227
- * }
228
- *
229
- * // Scope created here, on first access:
230
- * const user = this.userData;
231
- * ```
232
- *
233
- * **Caching**: Like @Computed, @ComputedAsync caches the result and maintains object identity
234
- * through diff-based updates. The getter only runs when dependencies change.
235
- *
236
- * **Object Reuse**: Like @Computed, the result maintains its identity across updates.
237
- * Only changed properties are modified in-place, preserving object references.
238
- *
239
- * **Key difference from @Computed**: Uses StoreAsync instead of StoreSync for caching.
240
- * Both use synchronous getters and provide object identity preservation.
241
- *
242
- * **Comparison**:
243
- * | Aspect | @Scope | @Computed | @ComputedAsync | scope(async) + @Scope |
244
- * |--------|--------|-----------|----------------|----------------------|
245
- * | Caches value | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes (memoized) |
246
- * | Getter type | Sync | Sync | Sync | Async supported |
247
- * | Object identity | ❌ New | ✅ Same | ✅ Same | ❌ New |
248
- * | Store backend | Single scope | StoreSync | StoreAsync | Direct scope |
249
- * | Initial value | First access | First access | defaultValue | Promise |
250
- * | Best for | Simple sync, async | Complex sync | StoreAsync sync | Component async ops |
251
- *
252
- * @see {@link Computed} for synchronous computed properties with StoreSync caching
253
- * @see {@link Scope} for simple getter-based reactive properties (caches, new reference)
254
- * @see {@link StoreAsync} for async store implementation
255
- * @see {@link ObservableScope.Create} for async function support
256
- */
257
- export declare function ComputedAsync<T extends WeakKey, K extends keyof T, V extends T[K]>(defaultValue: V): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => PropertyDescriptor;
258
162
  /**
259
163
  * State decorator factory for creating reactive properties backed by ObservableNode proxies.
260
164
  *
@@ -490,7 +394,6 @@ export declare function Value(): any;
490
394
  * | Best for | Primitives, cheap ops, async ops | Complex objects, expensive ops |
491
395
  *
492
396
  * @see {@link Computed} for cached computed properties with object reuse
493
- * @see {@link ComputedAsync} for sync getters with StoreAsync backend
494
397
  * @see {@link ObservableNode.Apply} for how @Computed maintains object identity
495
398
  * @see {@link ObservableScope} for the scope-based reactivity system
496
399
  * @see {@link scope} for inline computed scopes within @Scope
@@ -797,7 +700,6 @@ export declare namespace Destroy {
797
700
  * - All @Value-decorated property scopes
798
701
  * - All @Scope-decorated property scopes
799
702
  * - All @Computed-decorated property scopes (both getter and property scopes)
800
- * - All @ComputedAsync-decorated property scopes (including StoreAsync)
801
703
  * - All @Destroy-marked properties (calls .Destroy() method)
802
704
  * - All @Watch subscriptions (via scope destruction)
803
705
  *
@@ -5,7 +5,6 @@
5
5
  * @module Utils/Decorators
6
6
  * @see ObservableScope
7
7
  * @see ObservableNode
8
- * @see StoreAsync
9
8
  * @see StoreSync
10
9
  * @see Component
11
10
  *
@@ -20,7 +19,6 @@
20
19
  * | arrays (User[], Todo[]) | @State | Array mutations tracked |
21
20
  * | getters only (expensive) | @Computed | Cached + object reuse via Store |
22
21
  * | getters only (simple) | @Scope | Cached, but new reference on update |
23
- * | getters only (sync, StoreAsync) | @ComputedAsync | Sync getter, StoreAsync caching + object reuse |
24
22
  * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
25
23
  * | subscribe to changes | @Watch | Calls method when scope value changes |
26
24
  * | dependency injection | @Inject | Gets value from component injector |
@@ -58,7 +56,6 @@
58
56
  */
59
57
  import { ObservableScope, PeekCallback, } from "../Store/Tree/observableScope";
60
58
  import { ObservableNode } from "../Store/Tree/observableNode";
61
- import { StoreAsync } from "../Store";
62
59
  /**
63
60
  * WeakMap for storing node instances and their associated properties.
64
61
  * @private
@@ -122,27 +119,6 @@ function GetDestroyArrayForPrototype(prototype, create = true) {
122
119
  }
123
120
  return array;
124
121
  }
125
- function CreateStoreScope(getter, store, defaultValue) {
126
- const getterScope = ObservableScope.Gated(function () {
127
- const value = getter();
128
- return ObservableNode.Clone(value);
129
- });
130
- ObservableScope.Watch(getterScope, (scope) => {
131
- const data = ObservableScope.Peek(scope);
132
- store.Write(data, "root");
133
- });
134
- const data = ObservableScope.Peek(getterScope);
135
- store.Write(data, "root");
136
- const propertyScope = ObservableScope.Create(() => {
137
- return store.Get("root", defaultValue);
138
- });
139
- ObservableScope.OnDestroyed(propertyScope, function () {
140
- ObservableScope.Destroy(getterScope);
141
- if (store instanceof StoreAsync)
142
- store.Destroy();
143
- });
144
- return propertyScope;
145
- }
146
122
  /**
147
123
  * Creates an observable scope that exposes a getter's value through a persistent
148
124
  * observable node, preserving the value's object identity across updates.
@@ -281,10 +257,8 @@ function CreateNodeScope(getter) {
281
257
  * - Use @Scope() for simple computations where object identity doesn't matter
282
258
  * - The diff computation overhead is justified when you need object reuse
283
259
  * @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
284
- * @see {@link ComputedAsync} for async computed properties
285
260
  * @see {@link ObservableNode.Apply} for how diffs are applied in-place to maintain object identity
286
261
  * @see {@link ObservableScope} for the Gated scope mechanism (batched updates)
287
- * @see {@link StoreSync} for the store-backed variant used by @ComputedAsync
288
262
  */
289
263
  export function Computed() {
290
264
  return function (target, propertyKey, descriptor) {
@@ -320,134 +294,6 @@ function ComputedDecorator(target, prop, descriptor) {
320
294
  },
321
295
  };
322
296
  }
323
- /**
324
- * ComputedAsync decorator factory for creating synchronous computed properties with StoreAsync caching.
325
- * A computed property is derived from other properties and automatically updates when its dependencies change.
326
- *
327
- * IMPORTANT: Despite the name, @ComputedAsync expects a SYNC getter, NOT an async function.
328
- * The "Async" refers to the internal StoreAsync caching mechanism, not the getter signature.
329
- *
330
- * Use @ComputedAsync when you need synchronous caching with StoreAsync backend.
331
- * For direct async operations (no object reuse), use ObservableScope.Create(async () => ...) instead.
332
- *
333
- * @example
334
- * ```typescript
335
- * // ✅ Good: Synchronous getter with StoreAsync caching
336
- * @Value()
337
- * userId: string = "123";
338
- *
339
- * @ComputedAsync(null)
340
- * get userData(): User | null {
341
- * // Sync getter - returns value directly
342
- * return getUserSync(this.userId);
343
- * }
344
- * ```
345
- *
346
- * @example
347
- * ```typescript
348
- * // ❌ WRONG - Do NOT use async keyword or return Promise
349
- * @Value()
350
- * userId: string = "123";
351
- *
352
- * @ComputedAsync(null)
353
- * async getUser(): Promise<User> { // ERROR: getter must be synchronous!
354
- * return await fetch(`/api/users/${this.userId}`);
355
- * }
356
- *
357
- * // ✅ CORRECT - Use ObservableScope.Create for direct async operations
358
- * import { ObservableScope } from "j-templates/Store";
359
- *
360
- * @Value()
361
- * userId: string = "123";
362
- *
363
- * private userDataScope = ObservableScope.Create(async () => {
364
- * return await fetch(`/api/users/${this.userId}`); // Async supported here!
365
- * });
366
- *
367
- * get userData(): User | null {
368
- * return ObservableScope.Value(this.userDataScope); // null while loading
369
- * }
370
- * ```
371
- *
372
- * @param defaultValue The default value to be used if the computed property is not defined.
373
- * @returns A property decorator that can be applied to a getter method.
374
- * @throws Will throw an error if the property is not a getter or if it has a setter.
375
- * @remarks
376
- * The @ComputedAsync decorator uses StoreAsync for caching. The getter must be a synchronous function.
377
- * Unlike @Computed which uses StoreSync, @ComputedAsync uses StoreAsync internally for caching.
378
- *
379
- * **Initialization**: @ComputedAsync uses lazy initialization - the scopes are created on first access:
380
- * ```typescript
381
- * @ComputedAsync(null)
382
- * get userData(): User | null {
383
- * return getUserSync(this.userId);
384
- * }
385
- *
386
- * // Scope created here, on first access:
387
- * const user = this.userData;
388
- * ```
389
- *
390
- * **Caching**: Like @Computed, @ComputedAsync caches the result and maintains object identity
391
- * through diff-based updates. The getter only runs when dependencies change.
392
- *
393
- * **Object Reuse**: Like @Computed, the result maintains its identity across updates.
394
- * Only changed properties are modified in-place, preserving object references.
395
- *
396
- * **Key difference from @Computed**: Uses StoreAsync instead of StoreSync for caching.
397
- * Both use synchronous getters and provide object identity preservation.
398
- *
399
- * **Comparison**:
400
- * | Aspect | @Scope | @Computed | @ComputedAsync | scope(async) + @Scope |
401
- * |--------|--------|-----------|----------------|----------------------|
402
- * | Caches value | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes (memoized) |
403
- * | Getter type | Sync | Sync | Sync | Async supported |
404
- * | Object identity | ❌ New | ✅ Same | ✅ Same | ❌ New |
405
- * | Store backend | Single scope | StoreSync | StoreAsync | Direct scope |
406
- * | Initial value | First access | First access | defaultValue | Promise |
407
- * | Best for | Simple sync, async | Complex sync | StoreAsync sync | Component async ops |
408
- *
409
- * @see {@link Computed} for synchronous computed properties with StoreSync caching
410
- * @see {@link Scope} for simple getter-based reactive properties (caches, new reference)
411
- * @see {@link StoreAsync} for async store implementation
412
- * @see {@link ObservableScope.Create} for async function support
413
- */
414
- export function ComputedAsync(defaultValue) {
415
- return function (target, propertyKey, descriptor) {
416
- return ComputedAsyncDecorator(target, propertyKey, descriptor, defaultValue);
417
- };
418
- }
419
- /**
420
- * ComputedAsync decorator implementation for creating asynchronous computed properties.
421
- * @private
422
- * @param target The target object.
423
- * @param prop The property key.
424
- * @param descriptor The property descriptor.
425
- * @param defaultValue The default value to be used if the computed property is not defined.
426
- * @returns A property descriptor that replaces the original descriptor with a computed implementation.
427
- * @throws Will throw an error if the property is not a getter or if it has a setter.
428
- */
429
- function ComputedAsyncDecorator(target, prop, descriptor, defaultValue) {
430
- const propertyKey = prop;
431
- if (!(descriptor && descriptor.get))
432
- throw "Computed decorator requires a getter";
433
- if (descriptor && descriptor.set)
434
- throw "Computed decorator does not support setters";
435
- const getter = descriptor.get;
436
- return {
437
- configurable: false,
438
- enumerable: true,
439
- get: function () {
440
- const scopeMap = GetScopeMapForInstance(this);
441
- if (scopeMap[propertyKey] === undefined) {
442
- const worker = new Worker(new URL("../Store/Diff/defaultDiffTreeWorker.js", import.meta.url), { type: "module" });
443
- const storeAsync = new StoreAsync(worker);
444
- const propertyScope = CreateStoreScope(getter.bind(this), storeAsync, defaultValue);
445
- scopeMap[propertyKey] = [propertyScope, undefined];
446
- }
447
- return ObservableScope.Value(scopeMap[propertyKey][0]);
448
- },
449
- };
450
- }
451
297
  /**
452
298
  * State decorator factory for creating reactive properties backed by ObservableNode proxies.
453
299
  *
@@ -749,7 +595,6 @@ function ValueDecorator(target, propertyKey) {
749
595
  * | Best for | Primitives, cheap ops, async ops | Complex objects, expensive ops |
750
596
  *
751
597
  * @see {@link Computed} for cached computed properties with object reuse
752
- * @see {@link ComputedAsync} for sync getters with StoreAsync backend
753
598
  * @see {@link ObservableNode.Apply} for how @Computed maintains object identity
754
599
  * @see {@link ObservableScope} for the scope-based reactivity system
755
600
  * @see {@link scope} for inline computed scopes within @Scope
@@ -1118,7 +963,6 @@ export var Bound;
1118
963
  * - All @Value-decorated property scopes
1119
964
  * - All @Scope-decorated property scopes
1120
965
  * - All @Computed-decorated property scopes (both getter and property scopes)
1121
- * - All @ComputedAsync-decorated property scopes (including StoreAsync)
1122
966
  * - All @Destroy-marked properties (calls .Destroy() method)
1123
967
  * - All @Watch subscriptions (via scope destruction)
1124
968
  *