j-templates 9.0.0 → 10.0.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.
Files changed (57) hide show
  1. package/Node/vNode.js +2 -0
  2. package/SYNTAX_PRIMER.md +181 -101
  3. package/Store/Diff/channelClient.d.ts +106 -0
  4. package/Store/Diff/channelClient.js +228 -0
  5. package/Store/Diff/defaultDiffTreeWorker.js +3 -4
  6. package/Store/Diff/diffTree.d.ts +56 -0
  7. package/Store/Diff/diffTree.js +110 -4
  8. package/Store/Diff/diffTree.types.d.ts +191 -21
  9. package/Store/Diff/diffTree.types.js +2 -10
  10. package/Store/Diff/diffTreeEndpoint.d.ts +101 -0
  11. package/Store/Diff/diffTreeEndpoint.js +173 -0
  12. package/Store/Diff/diffTreeEndpoint.types.d.ts +71 -0
  13. package/Store/Diff/diffTreeEndpoint.types.js +1 -0
  14. package/Store/Diff/diffTreeHub.d.ts +139 -0
  15. package/Store/Diff/diffTreeHub.js +346 -0
  16. package/Store/Diff/hostPublisher.d.ts +39 -0
  17. package/Store/Diff/hostPublisher.js +151 -0
  18. package/Store/Diff/index.d.ts +3 -0
  19. package/Store/Diff/index.js +3 -0
  20. package/Store/Diff/observableDiffTree.d.ts +93 -0
  21. package/Store/Diff/observableDiffTree.js +199 -0
  22. package/Store/Diff/onConnect.d.ts +33 -0
  23. package/Store/Diff/onConnect.js +83 -0
  24. package/Store/Diff/utils/diffTree.types.d.ts +9 -0
  25. package/Store/Diff/utils/homes.d.ts +12 -0
  26. package/Store/Diff/utils/homes.js +40 -0
  27. package/Store/Diff/utils/routes.d.ts +18 -0
  28. package/Store/Diff/utils/routes.js +33 -2
  29. package/Store/Store/store.d.ts +36 -4
  30. package/Store/Store/store.js +47 -6
  31. package/Store/Store/storeAsync.d.ts +80 -25
  32. package/Store/Store/storeAsync.js +102 -33
  33. package/Store/Store/storeSync.d.ts +76 -11
  34. package/Store/Store/storeSync.js +105 -12
  35. package/Store/Tree/observableNode.js +68 -13
  36. package/Store/Tree/observableScope.d.ts +1 -0
  37. package/Store/Tree/observableScope.js +4 -0
  38. package/Store/Tree/opaque.d.ts +30 -0
  39. package/Store/Tree/opaque.js +35 -0
  40. package/Store/Tree/snapshot.js +5 -4
  41. package/Store/index.d.ts +4 -1
  42. package/Store/index.js +1 -1
  43. package/Utils/decorators.d.ts +0 -98
  44. package/Utils/decorators.js +0 -156
  45. package/Utils/index.d.ts +2 -0
  46. package/Utils/index.js +1 -0
  47. package/Utils/subscriptionManager.d.ts +78 -0
  48. package/Utils/subscriptionManager.js +162 -0
  49. package/package.json +1 -1
  50. package/Store/Diff/diffTreeWorker.d.ts +0 -6
  51. package/Store/Diff/diffTreeWorker.js +0 -22
  52. package/Store/Diff/diffTreeWriter.d.ts +0 -92
  53. package/Store/Diff/diffTreeWriter.js +0 -137
  54. package/Store/Diff/workerQueue.d.ts +0 -7
  55. package/Store/Diff/workerQueue.js +0 -29
  56. package/Store/Store/pushStore.d.ts +0 -53
  57. package/Store/Store/pushStore.js +0 -77
@@ -1,4 +1,4 @@
1
- import { DiffTree } from "../Diff/diffTree";
1
+ import { ObservableDiffTree } from "../Diff/observableDiffTree";
2
2
  import { Store } from "./store";
3
3
  /**
4
4
  * StoreSync class extends the base Store class to provide synchronous data management operations.
@@ -24,21 +24,74 @@ import { Store } from "./store";
24
24
  * // Splicing an array
25
25
  * store.Splice("user.array", 0, 1, { id: 3 });
26
26
  *
27
+ * The store subscribes to an `ObservableDiffTree` and applies every commit the tree delivers, so
28
+ * anything else on the thread can subscribe to the same tree (`Tree`), and a commit made on the
29
+ * tree directly reaches the store too. A change runs the tree's projections and commits
30
+ * before it returns.
31
+ *
32
+ * - **Its own tree** (`new StoreSync()`, `new StoreSync(undefined, { keyFunc })`): the store creates an
33
+ * empty tree, and it lives as long as the store.
34
+ * - **A tree it's given** (`new StoreSync(new ObservableDiffTree({ keyFunc, routes }))`): for a tree
35
+ * with routes and projections, whose keyFunc the store takes. Anything else on the thread may hold it too.
36
+ * - **A hub's tree** (`new StoreSync(hub.OpenLocal(name))`): a tree on this thread a `DiffTreeHub` runs
37
+ * for an endpoint, shared by every store opened on it and followed into by the hub. A change from
38
+ * any of them reaches all of them, and the trees that follow it, before it returns. The store
39
+ * owns the lease and releases it when it's destroyed. When the hub restarts the tree, the store
40
+ * moves to the new one, as when it first subscribed.
41
+ *
42
+ * @example
43
+ * // A store on a hub's tree: it lives as long as the component that holds it
44
+ * @Inject(DiffTreeHub) hub!: DiffTreeHub;
45
+ * @Destroy() budget = new StoreSync(this.hub.OpenLocal("budget"));
46
+ *
27
47
  * @see Store
28
48
  * @see StoreAsync
29
- * @see DiffTree
49
+ * @see ObservableDiffTree
30
50
  */
31
51
  export class StoreSync extends Store {
32
52
  /**
33
- * Creates an instance of StoreSync.
34
- * @param keyFunc Optional function to generate a key for a given data value.
35
- * @param routes Optional route definitions. A projection in the root route (`"/"`) is
36
- * readable through `Get` at `$projection_<key>`.
37
- * @throws When projections depend on each other in a cycle.
53
+ * Creates a store and subscribes it to its tree: the tree's state is the store's when this returns.
54
+ * @param tree The tree:
55
+ * - A lease from `DiffTreeHub.OpenLocal`, on a hub's tree on this thread. The store owns the
56
+ * lease from then on, and releases it when it's destroyed, and follows the tree across
57
+ * restarts. A lease on a worker's tree (`DiffTreeHub.Open`) is for a `StoreAsync`, and doesn't
58
+ * compile here.
59
+ * - An `ObservableDiffTree`, for a tree with routes (`new ObservableDiffTree({ keyFunc, routes })`).
60
+ * A projection in the root route (`"/"`) is readable through `Get` at `$projection_<key>`. The
61
+ * store doesn't own it: `Destroy` stops listening.
62
+ * - Left out, the store creates an empty tree of its own, with the store's `keyFunc` and `opaque`.
63
+ * @param options `keyFunc`, `roots` and `opaque` (`IStoreSyncOptions`). `keyFunc` is for the tree the
64
+ * store creates: a leased tree's is the endpoint's, and a given tree's its own. A tree given or
65
+ * leased must write the `opaque` keys whole already (`IObservableDiffTreeConfig.opaque`): share
66
+ * them from one file.
67
+ * @throws When `keyFunc` is given with a tree or a lease, which has its own.
68
+ */
69
+ constructor(tree, options = {}) {
70
+ const lease = tree && !(tree instanceof ObservableDiffTree) ? tree : undefined;
71
+ if (tree && options.keyFunc)
72
+ throw new Error(lease
73
+ ? `StoreSync: the endpoint ${lease.name} gives the store its keyFunc; leave it out of the options.`
74
+ : "StoreSync: the tree has its keyFunc, which the store takes; leave it out of the options.");
75
+ super(lease ? lease.endpoint.keyFunc : tree ? tree.KeyFunc : options.keyFunc, options.opaque);
76
+ /** Over a lease: the roots delivered and not deleted, to delete the ones a restarted tree doesn't hold */
77
+ this.delivered = new Set();
78
+ this.roots = options.roots;
79
+ if (lease) {
80
+ this.lease = lease;
81
+ this.Attach(lease.tree, false);
82
+ this.stopLease = lease.OnTree(tree => this.Attach(tree, true));
83
+ return;
84
+ }
85
+ this.diff = tree ?? new ObservableDiffTree({ keyFunc: options.keyFunc, opaque: options.opaque });
86
+ this.unsubscribe = this.diff.Subscribe({ roots: this.roots }, post => this.UpdateRootMap(post.diff));
87
+ }
88
+ /**
89
+ * The store's tree: its own, or the hub's. Subscribe to it to receive the
90
+ * same commits the store applies; a commit made on it reaches the store too. A subscriber can't
91
+ * write to the store during a delivery.
38
92
  */
39
- constructor(keyFunc, routes) {
40
- super(keyFunc);
41
- this.diff = new DiffTree(keyFunc, routes);
93
+ get Tree() {
94
+ return this.diff;
42
95
  }
43
96
  /**
44
97
  * Writes data to the store synchronously.
@@ -91,11 +144,51 @@ export class StoreSync extends Store {
91
144
  this.Sync();
92
145
  }
93
146
  /**
94
- * Runs projections and applies everything changed since the last sync to the store.
147
+ * Stops applying the tree's commits. Over a lease, the store then releases it: the last lease on
148
+ * a hub's tree ends it. The store's values stay readable, and stop changing.
149
+ */
150
+ Destroy() {
151
+ this.stopLease?.();
152
+ this.stopLease = undefined;
153
+ this.unsubscribe?.();
154
+ this.unsubscribe = undefined;
155
+ this.lease?.Release();
156
+ }
157
+ /**
158
+ * Subscribes to a hub's tree in place of the last. A restarted tree's state is applied in place, so
159
+ * readers keep their objects, with a delete for each root the last tree had and this one hasn't, in
160
+ * one update. The first delivery arrives during `Subscribe`.
161
+ */
162
+ Attach(tree, again) {
163
+ this.unsubscribe?.();
164
+ this.diff = tree;
165
+ const previous = this.delivered;
166
+ this.delivered = new Set();
167
+ let subscribing = true;
168
+ let first = [];
169
+ this.unsubscribe = tree.Subscribe({ roots: this.roots }, post => {
170
+ this.Track(post.diff);
171
+ subscribing ? (first = post.diff) : this.UpdateRootMap(post.diff);
172
+ });
173
+ subscribing = false;
174
+ const diff = [...first];
175
+ for (const key of previous)
176
+ this.delivered.has(key) || diff.push({ type: "delete", path: [key] });
177
+ diff.length > 0 && (again ? this.UpdateRootMapInPlace(diff) : this.UpdateRootMap(diff));
178
+ }
179
+ Track(diff) {
180
+ for (const record of diff) {
181
+ const key = String(record.path[0]);
182
+ record.type === "delete" && record.path.length === 1 ? this.delivered.delete(key) : this.delivered.add(key);
183
+ }
184
+ }
185
+ /**
186
+ * Runs projections and commits: the commit's delivery applies everything changed since the
187
+ * last commit to the store.
95
188
  * @private
96
189
  */
97
190
  Sync() {
98
191
  this.diff.RunProjections();
99
- this.UpdateRootMap(this.diff.Commit());
192
+ this.diff.Commit();
100
193
  }
101
194
  }
@@ -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) {
@@ -207,6 +207,7 @@ export declare namespace ObservableScope {
207
207
  function OnDestroyed<T>(scope: IObservableScope<T>, callback: {
208
208
  (lastValue: T): void;
209
209
  }): void;
210
+ function IsDestroyed(scope: IObservableScope<unknown>): boolean;
210
211
  /**
211
212
  * Marks a scope as dirty, triggering recomputation on next access or batch.
212
213
  * No-op for static scopes (created when a `Create` valueFunction reads no reactive
@@ -663,6 +663,10 @@ export var ObservableScope;
663
663
  Emitter.On(scope.onDestroyed, callback);
664
664
  }
665
665
  ObservableScope.OnDestroyed = OnDestroyed;
666
+ function IsDestroyed(scope) {
667
+ return scope.destroyed;
668
+ }
669
+ ObservableScope.IsDestroyed = IsDestroyed;
666
670
  /**
667
671
  * Marks a scope as dirty, triggering recomputation on next access or batch.
668
672
  * No-op for static scopes (created when a `Create` valueFunction reads no reactive
@@ -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
  *