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.
- package/SYNTAX_PRIMER.md +181 -101
- package/Store/Diff/channelClient.d.ts +104 -0
- package/Store/Diff/channelClient.js +225 -0
- package/Store/Diff/defaultDiffTreeWorker.js +3 -4
- package/Store/Diff/diffTree.d.ts +56 -0
- package/Store/Diff/diffTree.js +110 -4
- package/Store/Diff/diffTree.types.d.ts +186 -21
- package/Store/Diff/diffTree.types.js +2 -10
- package/Store/Diff/diffTreeEndpoint.d.ts +101 -0
- package/Store/Diff/diffTreeEndpoint.js +173 -0
- package/Store/Diff/diffTreeEndpoint.types.d.ts +71 -0
- package/Store/Diff/diffTreeEndpoint.types.js +1 -0
- package/Store/Diff/diffTreeHub.d.ts +139 -0
- package/Store/Diff/diffTreeHub.js +346 -0
- package/Store/Diff/hostPublisher.d.ts +38 -0
- package/Store/Diff/hostPublisher.js +147 -0
- package/Store/Diff/index.d.ts +3 -0
- package/Store/Diff/index.js +3 -0
- package/Store/Diff/observableDiffTree.d.ts +93 -0
- package/Store/Diff/observableDiffTree.js +200 -0
- package/Store/Diff/onConnect.d.ts +33 -0
- package/Store/Diff/onConnect.js +83 -0
- package/Store/Diff/utils/diffTree.types.d.ts +9 -0
- package/Store/Diff/utils/homes.d.ts +12 -0
- package/Store/Diff/utils/homes.js +40 -0
- package/Store/Diff/utils/paths.d.ts +2 -1
- package/Store/Diff/utils/paths.js +3 -2
- package/Store/Diff/utils/routes.d.ts +18 -0
- package/Store/Diff/utils/routes.js +33 -2
- package/Store/Diff/utils/state.js +4 -3
- package/Store/Store/store.d.ts +41 -4
- package/Store/Store/store.js +62 -7
- package/Store/Store/storeAsync.d.ts +134 -25
- package/Store/Store/storeAsync.js +235 -33
- package/Store/Store/storeSync.d.ts +91 -11
- package/Store/Store/storeSync.js +107 -12
- package/Store/Tree/observableNode.d.ts +3 -0
- package/Store/Tree/observableNode.js +87 -13
- package/Store/Tree/observableScope.d.ts +3 -1
- package/Store/Tree/observableScope.js +12 -0
- package/Store/Tree/opaque.d.ts +30 -0
- package/Store/Tree/opaque.js +35 -0
- package/Store/Tree/snapshot.js +5 -4
- package/Store/index.d.ts +4 -1
- package/Store/index.js +1 -1
- package/Utils/decorators.d.ts +0 -98
- package/Utils/decorators.js +0 -156
- package/Utils/emitter.d.ts +11 -4
- package/Utils/emitter.js +25 -2
- package/Utils/index.d.ts +2 -0
- package/Utils/index.js +1 -0
- package/Utils/pattern.d.ts +14 -0
- package/Utils/pattern.js +20 -0
- package/Utils/subscriptionManager.d.ts +78 -0
- package/Utils/subscriptionManager.js +159 -0
- package/package.json +1 -1
- package/Store/Diff/diffTreeWorker.d.ts +0 -6
- package/Store/Diff/diffTreeWorker.js +0 -22
- package/Store/Diff/diffTreeWriter.d.ts +0 -92
- package/Store/Diff/diffTreeWriter.js +0 -137
- package/Store/Diff/workerQueue.d.ts +0 -7
- package/Store/Diff/workerQueue.js +0 -29
- package/Store/Store/pushStore.d.ts +0 -53
- 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:
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
276
|
+
return result === array ? object[NODE_PROXY] : result;
|
|
277
|
+
}
|
|
227
278
|
}
|
|
228
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/Store/Tree/snapshot.js
CHANGED
|
@@ -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;
|
|
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";
|
package/Utils/decorators.d.ts
CHANGED
|
@@ -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
|
*
|
package/Utils/decorators.js
CHANGED
|
@@ -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
|
*
|