j-templates 7.0.74 → 7.0.76
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/DOM/createPropertyAssignment.js +14 -7
- package/DOM/elements.d.ts +24 -24
- package/DOM/index.d.ts +2 -0
- package/DOM/index.js +2 -2
- package/Node/vNode.js +4 -0
- package/Node/vNode.types.d.ts +2 -0
- package/Store/Diff/diffAsync.d.ts +28 -0
- package/Store/Diff/diffAsync.js +25 -0
- package/Store/Diff/diffSync.d.ts +22 -0
- package/Store/Diff/diffSync.js +22 -0
- package/Store/Diff/diffTree.d.ts +33 -0
- package/Store/Diff/diffTree.js +68 -0
- package/Store/Store/store.d.ts +38 -0
- package/Store/Store/store.js +25 -0
- package/Store/Tree/observableNode.js +5 -5
- package/Store/Tree/observableScope.js +99 -105
- package/Utils/asyncQueue.d.ts +20 -0
- package/Utils/asyncQueue.js +20 -0
- package/Utils/decorators.d.ts +674 -49
- package/Utils/decorators.js +674 -49
- package/Utils/emitter.d.ts +1 -3
- package/Utils/emitter.js +5 -23
- package/Utils/functions.d.ts +5 -0
- package/Utils/functions.js +5 -0
- package/Utils/json.d.ts +10 -0
- package/Utils/json.js +63 -1
- package/package.json +1 -1
- package/Utils/bitSet.d.ts +0 -23
- package/Utils/bitSet.js +0 -139
- package/Utils/jsonType.d.ts +0 -1
- package/Utils/jsonType.js +0 -5
- package/index.debug.d.ts +0 -0
- package/index.debug.js +0 -0
package/Utils/decorators.d.ts
CHANGED
|
@@ -8,87 +8,459 @@
|
|
|
8
8
|
* @see StoreAsync
|
|
9
9
|
* @see StoreSync
|
|
10
10
|
* @see Component
|
|
11
|
+
*
|
|
12
|
+
* Decorator Selection Guide
|
|
13
|
+
* ==========================
|
|
14
|
+
*
|
|
15
|
+
* | Your Value Type | Use | Why |
|
|
16
|
+
* |---------------------------|---------|-------------------------------|
|
|
17
|
+
* | number, string, boolean | @Value | Lightweight, no proxy overhead|
|
|
18
|
+
* | null, undefined | @Value | Simple scope storage |
|
|
19
|
+
* | { nested: objects } | @State | Deep reactivity needed |
|
|
20
|
+
* | arrays (User[], Todo[]) | @State | Array mutations tracked |
|
|
21
|
+
* | getters only (expensive) | @Computed | Cached + object reuse via Store |
|
|
22
|
+
* | getters only (simple) | @Scope | Cached, but new reference on update |
|
|
23
|
+
* | getters only (async) | @ComputedAsync | Async with caching + object reuse |
|
|
24
|
+
* | subscribe to changes | @Watch | Calls method when scope value changes |
|
|
25
|
+
* | dependency injection | @Inject | Gets value from component injector |
|
|
26
|
+
* | cleanup on destroy | @Destroy| Calls .Destroy() on component teardown |
|
|
27
|
+
*
|
|
28
|
+
* @Computed vs @Scope (Both are getters only, both cache):
|
|
29
|
+
* ---------------------------------------------------------
|
|
30
|
+
* | Use @Computed when: | Use @Scope when: |
|
|
31
|
+
* | - Computation is expensive | - Computation is cheap |
|
|
32
|
+
* | - Need object identity (===) | - Object identity doesn't matter|
|
|
33
|
+
* | - DOM reference preservation needed | - Minimal overhead preferred |
|
|
34
|
+
* | - Array/object transformations | - String/math operations |
|
|
35
|
+
*
|
|
36
|
+
* Key Difference: Object Identity on Update
|
|
37
|
+
* -----------------------------------------
|
|
38
|
+
* | Aspect | @Computed | @Scope |
|
|
39
|
+
* |--------|-----------|--------|
|
|
40
|
+
* | Caching | ✅ Yes | ✅ Yes |
|
|
41
|
+
* | Object identity preserved | ✅ Yes (ApplyDiff) | ❌ No (new object) |
|
|
42
|
+
* | Overhead | Store + diff | Single scope |
|
|
43
|
+
* | Best for | Complex objects | Primitives/simple values |
|
|
44
|
+
*
|
|
45
|
+
* Object Identity (Key Difference):
|
|
46
|
+
* ---------------------------------
|
|
47
|
+
* @Computed: Same object reference, in-place updates via ObservableNode.ApplyDiff
|
|
48
|
+
* @Scope: New object reference on every update
|
|
49
|
+
*
|
|
50
|
+
* Example:
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const list = this.items; // Reference A
|
|
53
|
+
* this.data.changed = true; // Triggers update
|
|
54
|
+
* const list2 = this.items; // @Computed: A, @Scope: B
|
|
55
|
+
* console.log(list === list2); // @Computed: true, @Scope: false
|
|
56
|
+
* ```
|
|
11
57
|
*/
|
|
12
58
|
import { IDestroyable } from "./utils.types";
|
|
13
59
|
import { Injector } from "./injector";
|
|
14
60
|
/**
|
|
15
|
-
* Computed decorator factory for creating synchronous computed properties.
|
|
61
|
+
* Computed decorator factory for creating synchronous computed properties with caching and object reuse.
|
|
16
62
|
* A computed property is derived from other properties and automatically updates when its dependencies change.
|
|
17
63
|
*
|
|
64
|
+
* Use @Computed for expensive computations that benefit from caching AND object identity preservation
|
|
65
|
+
* (filtering, sorting, aggregations, transformations of complex objects).
|
|
66
|
+
*
|
|
18
67
|
* @example
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // ✅ Good: Expensive computation accessed frequently
|
|
70
|
+
* @State()
|
|
71
|
+
* items: TodoItem[] = [];
|
|
72
|
+
*
|
|
73
|
+
* @Computed([])
|
|
74
|
+
* get completedItems(): TodoItem[] {
|
|
75
|
+
* // Expensive: filters entire array, cached until items change
|
|
76
|
+
* // Result object is REUSED - only changed properties are updated
|
|
77
|
+
* return this.items.filter(item => item.completed).sort(...);
|
|
24
78
|
* }
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* // ❌ Avoid: Cheap computation - use @Scope() instead
|
|
84
|
+
* @Value()
|
|
85
|
+
* firstName: string = "John";
|
|
86
|
+
*
|
|
87
|
+
* @Value()
|
|
88
|
+
* lastName: string = "Doe";
|
|
89
|
+
*
|
|
90
|
+
* @Computed("") // Overhead: creates StoreSync, watch cycle, diff computation
|
|
91
|
+
* get fullName(): string {
|
|
92
|
+
* return this.firstName + " " + this.lastName; // Cheap string concat
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* @Scope() // Better: direct scope, no store overhead
|
|
96
|
+
* get fullName(): string {
|
|
97
|
+
* return this.firstName + " " + this.lastName;
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
25
100
|
*
|
|
26
101
|
* @param defaultValue The default value to be used if the computed property is not defined.
|
|
27
102
|
* @returns A property decorator that can be applied to a getter method.
|
|
28
103
|
* @throws Will throw an error if the property is not a getter or if it has a setter.
|
|
29
|
-
* @remarks
|
|
104
|
+
* @remarks
|
|
105
|
+
* The @Computed decorator uses a two-phase caching system with diff-based updates:
|
|
106
|
+
* 1. Getter scope: Computes value and writes to StoreSync when dependencies change
|
|
107
|
+
* 2. StoreSync: Computes diff between old and new values
|
|
108
|
+
* 3. ObservableNode.ApplyDiff: Updates the EXISTING object with only changed properties
|
|
109
|
+
* 4. Property scope: Reads the updated (but same reference) value from StoreSync
|
|
110
|
+
*
|
|
111
|
+
* **Initialization**: @Computed uses lazy initialization - the scopes are created on first access:
|
|
112
|
+
* ```typescript
|
|
113
|
+
* @Computed([])
|
|
114
|
+
* get completedItems(): TodoItem[] {
|
|
115
|
+
* return this.items.filter(i => i.completed);
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* // Scopes created here, on first access:
|
|
119
|
+
* const items = this.completedItems;
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* **Caching**: Like @Scope, @Computed caches the computed value and only re-evaluates when
|
|
123
|
+
* dependencies change. The key difference is what happens during re-evaluation:
|
|
124
|
+
*
|
|
125
|
+
* **Key benefit - Object Reuse**: The returned object maintains its identity across updates.
|
|
126
|
+
* Only the changed properties are modified in-place using ObservableNode.ApplyDiff. This enables:
|
|
127
|
+
* - DOM reference preservation (no unnecessary re-renders)
|
|
128
|
+
* - Existing proxy reuse (no proxy recreation overhead)
|
|
129
|
+
* - Identity checks (===) remain stable across updates
|
|
130
|
+
* - Efficient propagation (only changed properties trigger downstream updates)
|
|
131
|
+
*
|
|
132
|
+
* **Example of object reuse**:
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const list = this.completedItems; // Reference A
|
|
135
|
+
* this.items[0].completed = true; // Triggers re-computation
|
|
136
|
+
* const list2 = this.completedItems; // Same reference A, not a new array
|
|
137
|
+
* console.log(list === list2); // true - @Computed preserves identity
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* **@Computed vs @Scope caching**:
|
|
141
|
+
* | Aspect | @Computed | @Scope |
|
|
142
|
+
* |--------|-----------|--------|
|
|
143
|
+
* | Caches value | ✅ Yes | ✅ Yes |
|
|
144
|
+
* | Re-evaluates on dep change | ✅ Yes | ✅ Yes |
|
|
145
|
+
* | Object identity preserved | ✅ Yes | ❌ No |
|
|
146
|
+
* | Returns new object on update | ❌ No | ✅ Yes |
|
|
147
|
+
*
|
|
148
|
+
* **Performance note**:
|
|
149
|
+
* - Use @Computed for expensive operations on complex objects where object reuse matters
|
|
150
|
+
* - Use @Scope() for simple computations where object identity doesn't matter
|
|
151
|
+
* - The diff computation overhead is justified when you need object reuse
|
|
152
|
+
* @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
|
|
153
|
+
* @see {@link ComputedAsync} for async computed properties
|
|
154
|
+
* @see {@link ObservableNode.ApplyDiff} for how diffs are applied to maintain object identity
|
|
155
|
+
* @see {@link StoreSync} for sync store implementation
|
|
30
156
|
*/
|
|
31
157
|
export declare function Computed<T extends WeakKey, K extends keyof T, V extends T[K]>(defaultValue: V): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
32
158
|
/**
|
|
33
|
-
* ComputedAsync decorator factory for creating asynchronous computed properties.
|
|
159
|
+
* ComputedAsync decorator factory for creating asynchronous computed properties with caching.
|
|
34
160
|
* A computed property is derived from other properties and automatically updates when its dependencies change.
|
|
35
161
|
*
|
|
162
|
+
* Use @ComputedAsync for expensive async operations (API calls, file reads, database queries).
|
|
163
|
+
*
|
|
36
164
|
* @example
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
165
|
+
* ```typescript
|
|
166
|
+
* // ✅ Good: Async operation with caching
|
|
167
|
+
* @Value()
|
|
168
|
+
* userId: string = "123";
|
|
169
|
+
*
|
|
170
|
+
* @ComputedAsync(null)
|
|
171
|
+
* async getUser(): Promise<User | null> {
|
|
172
|
+
* // Expensive: API call, cached until userId changes
|
|
173
|
+
* return await fetch(`/api/users/${this.userId}`);
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* // ❌ Avoid: Simple/synchronous computation - use @Computed() instead
|
|
180
|
+
* @Value()
|
|
181
|
+
* count: number = 0;
|
|
182
|
+
*
|
|
183
|
+
* @ComputedAsync(0) // Overhead: async wrapper, StoreAsync
|
|
184
|
+
* get doubled(): number {
|
|
185
|
+
* return this.count * 2; // Sync operation
|
|
186
|
+
* }
|
|
187
|
+
*
|
|
188
|
+
* @Computed(0) // Better: synchronous caching
|
|
189
|
+
* get doubled(): number {
|
|
190
|
+
* return this.count * 2;
|
|
42
191
|
* }
|
|
192
|
+
* ```
|
|
43
193
|
*
|
|
44
194
|
* @param defaultValue The default value to be used if the computed property is not defined.
|
|
45
195
|
* @returns A property decorator that can be applied to a getter method.
|
|
46
196
|
* @throws Will throw an error if the property is not a getter or if it has a setter.
|
|
47
|
-
* @remarks
|
|
197
|
+
* @remarks
|
|
198
|
+
* The @ComputedAsync decorator uses StoreAsync for caching async results. The getter must be
|
|
199
|
+
* an async function or return a Promise. While waiting for the async operation, the defaultValue
|
|
200
|
+
* is returned. When the Promise resolves, the value is cached and dependent scopes update.
|
|
201
|
+
*
|
|
202
|
+
* **Initialization**: @ComputedAsync uses lazy initialization - the scopes are created on first access:
|
|
203
|
+
* ```typescript
|
|
204
|
+
* @ComputedAsync(null)
|
|
205
|
+
* async getUser(): Promise<User> {
|
|
206
|
+
* return fetch('/api/user');
|
|
207
|
+
* }
|
|
208
|
+
*
|
|
209
|
+
* // Scopes created here, on first access:
|
|
210
|
+
* const user = await this.getUser;
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* **Caching**: Like @Computed, @ComputedAsync caches the result and maintains object identity
|
|
214
|
+
* through diff-based updates. The async getter only runs when dependencies change.
|
|
215
|
+
*
|
|
216
|
+
* **Object Reuse**: Like @Computed, the resolved value maintains its identity across updates.
|
|
217
|
+
* Only changed properties are modified in-place, preserving object references.
|
|
218
|
+
*
|
|
219
|
+
* **Important**: Only use this for truly async operations. For sync computations, use @Computed()
|
|
220
|
+
* which has less overhead and provides synchronous values.
|
|
221
|
+
*
|
|
222
|
+
* **Comparison**:
|
|
223
|
+
* | Aspect | @Scope | @Computed | @ComputedAsync |
|
|
224
|
+
* |--------|--------|-----------|----------------|
|
|
225
|
+
* | Caches value | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
226
|
+
* | Sync/Async | Sync | Sync | Async |
|
|
227
|
+
* | Object identity | ❌ New reference | ✅ Same reference | ✅ Same reference |
|
|
228
|
+
* | Best for | Simple sync values | Complex sync values | Async operations |
|
|
229
|
+
*
|
|
230
|
+
* **Error handling**: If the async getter throws, the error is stored in the StoreAsync.
|
|
231
|
+
* You can handle errors in the getter itself or by watching for changes and checking the value.
|
|
232
|
+
*
|
|
233
|
+
* @see {@link Computed} for synchronous computed properties with object reuse
|
|
234
|
+
* @see {@link Scope} for simple getter-based reactive properties (caches, new reference)
|
|
235
|
+
* @see {@link StoreAsync} for async store implementation
|
|
48
236
|
*/
|
|
49
237
|
export declare function ComputedAsync<T extends WeakKey, K extends keyof T, V extends T[K]>(defaultValue: V): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
50
238
|
/**
|
|
51
|
-
* State decorator factory for creating
|
|
52
|
-
*
|
|
239
|
+
* State decorator factory for creating reactive properties backed by ObservableNode proxies.
|
|
240
|
+
*
|
|
241
|
+
* Use this decorator for complex data structures (objects, arrays) that need deep reactivity.
|
|
53
242
|
*
|
|
54
243
|
* @example
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* }
|
|
244
|
+
* ```typescript
|
|
245
|
+
* // ✅ Good: Complex objects/arrays
|
|
246
|
+
* @State()
|
|
247
|
+
* user: { name: string; email: string } = { name: "", email: "" };
|
|
248
|
+
*
|
|
249
|
+
* @State()
|
|
250
|
+
* items: TodoItem[] = [];
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* // ❌ Avoid: Simple primitives - use @Value() instead for better performance
|
|
256
|
+
* @State()
|
|
257
|
+
* count: number = 0; // Overhead: creates proxy, leaf scopes, caches
|
|
258
|
+
*
|
|
259
|
+
* @Value()
|
|
260
|
+
* count: number = 0; // Better: simple scope with direct value storage
|
|
261
|
+
* ```
|
|
59
262
|
*
|
|
60
263
|
* @returns A property decorator that can be applied to a property.
|
|
61
|
-
* @remarks
|
|
264
|
+
* @remarks
|
|
265
|
+
* The @State decorator wraps the value in an ObservableNode proxy, enabling:
|
|
266
|
+
* - Deep reactivity for nested properties and array mutations
|
|
267
|
+
* - Automatic proxy creation for all nested objects/arrays
|
|
268
|
+
* - Fine-grained per-property dependency tracking
|
|
269
|
+
*
|
|
270
|
+
* **Initialization**: @State uses lazy initialization - the proxy is created on first access,
|
|
271
|
+
* not at construction time. This means:
|
|
272
|
+
* ```typescript
|
|
273
|
+
* @State()
|
|
274
|
+
* data: { items: string[] } = { items: [] };
|
|
275
|
+
*
|
|
276
|
+
* // Proxy created here, on first access:
|
|
277
|
+
* console.log(this.data.items);
|
|
278
|
+
* ```
|
|
279
|
+
*
|
|
280
|
+
* **Performance note**: This overhead is beneficial for complex structures but wasteful for primitives.
|
|
281
|
+
* Use @Value() for simple types (number, string, boolean) that don't need deep reactivity.
|
|
282
|
+
*
|
|
283
|
+
* **Default value handling**: The value from the class field initializer is used on first access.
|
|
284
|
+
* If no initializer is provided, the initial value is `{ root: undefined }`.
|
|
285
|
+
* ```typescript
|
|
286
|
+
* @State()
|
|
287
|
+
* data: { items: string[] }; // Initial value: { root: undefined }
|
|
288
|
+
*
|
|
289
|
+
* @State()
|
|
290
|
+
* data2: { items: string[] } = { items: [] }; // Initial value: { root: { items: [] } }
|
|
291
|
+
* ```
|
|
292
|
+
* @see {@link Value} for simple primitive values
|
|
293
|
+
* @see {@link Computed} for derived/read-only values
|
|
294
|
+
* @see {@link ObservableNode} for the proxy-based reactivity system
|
|
62
295
|
*/
|
|
63
296
|
export declare function State(): any;
|
|
64
297
|
/**
|
|
65
|
-
* Value decorator factory for creating
|
|
66
|
-
*
|
|
298
|
+
* Value decorator factory for creating reactive properties backed by ObservableScope.
|
|
299
|
+
*
|
|
300
|
+
* Use this decorator for simple primitive values (number, string, boolean, null, undefined).
|
|
67
301
|
*
|
|
68
302
|
* @example
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
303
|
+
* ```typescript
|
|
304
|
+
* // ✅ Good: Simple primitives
|
|
305
|
+
* @Value()
|
|
306
|
+
* count: number = 0;
|
|
307
|
+
*
|
|
308
|
+
* @Value()
|
|
309
|
+
* isLoading: boolean = false;
|
|
310
|
+
*
|
|
311
|
+
* @Value()
|
|
312
|
+
* title: string = "My App";
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* // ❌ Avoid: Complex objects/arrays - use @State() instead for deep reactivity
|
|
318
|
+
* @Value()
|
|
319
|
+
* user: { name: string } = { name: "" }; // Nested changes won't trigger updates
|
|
320
|
+
*
|
|
321
|
+
* @State()
|
|
322
|
+
* user: { name: string } = { name: "" }; // Better: nested changes are tracked
|
|
323
|
+
* ```
|
|
73
324
|
*
|
|
74
325
|
* @returns A property decorator that can be applied to a property.
|
|
75
|
-
* @remarks
|
|
326
|
+
* @remarks
|
|
327
|
+
* The @Value decorator creates a lightweight ObservableScope to store the value.
|
|
328
|
+
* This is more efficient than @State() for primitives because it:
|
|
329
|
+
* - Avoids proxy creation overhead
|
|
330
|
+
* - Uses direct value comparison instead of deep diffing
|
|
331
|
+
* - Stores the value directly without nested scope caches
|
|
332
|
+
*
|
|
333
|
+
* **Initialization**: @Value uses lazy initialization - the scope is created on first access:
|
|
334
|
+
* ```typescript
|
|
335
|
+
* @Value()
|
|
336
|
+
* count: number = 0;
|
|
337
|
+
*
|
|
338
|
+
* // Scope created here, on first access:
|
|
339
|
+
* console.log(this.count);
|
|
340
|
+
* ```
|
|
341
|
+
*
|
|
342
|
+
* **Limitation**: Only top-level changes trigger updates. Nested object/array mutations
|
|
343
|
+
* will not be detected. Use @State() for complex structures.
|
|
344
|
+
*
|
|
345
|
+
* **Default value handling**: The value from the class field initializer is used on first access.
|
|
346
|
+
* If no initializer is provided, the initial value is `undefined`.
|
|
347
|
+
* ```typescript
|
|
348
|
+
* @Value()
|
|
349
|
+
* count: number; // Initial value: undefined
|
|
350
|
+
*
|
|
351
|
+
* @Value()
|
|
352
|
+
* count2: number = 0; // Initial value: 0
|
|
353
|
+
* ```
|
|
354
|
+
* @see {@link State} for complex objects/arrays needing deep reactivity
|
|
355
|
+
* @see {@link Computed} for derived/read-only values
|
|
356
|
+
* @see {@link ObservableScope} for the scope-based reactivity system
|
|
76
357
|
*/
|
|
77
358
|
export declare function Value(): any;
|
|
78
359
|
/**
|
|
79
|
-
* Scope decorator factory for creating
|
|
80
|
-
* A scope property is
|
|
360
|
+
* Scope decorator factory for creating reactive getter properties without caching overhead.
|
|
361
|
+
* A scope property is derived from other properties and automatically updates when its dependencies change.
|
|
362
|
+
*
|
|
363
|
+
* Use @Scope for simple, cheap computations where object identity preservation isn't needed.
|
|
81
364
|
*
|
|
82
365
|
* @example
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
366
|
+
* ```typescript
|
|
367
|
+
* // ✅ Good: Simple, cheap computation
|
|
368
|
+
* @Value()
|
|
369
|
+
* firstName: string = "John";
|
|
370
|
+
*
|
|
371
|
+
* @Value()
|
|
372
|
+
* lastName: string = "Doe";
|
|
373
|
+
*
|
|
374
|
+
* @Scope()
|
|
375
|
+
* get fullName(): string {
|
|
376
|
+
* // Cheap: string concatenation, no caching overhead
|
|
377
|
+
* return this.firstName + " " + this.lastName;
|
|
88
378
|
* }
|
|
379
|
+
* ```
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```typescript
|
|
383
|
+
* // ❌ Avoid: Expensive computation - use @Computed() instead
|
|
384
|
+
* @State()
|
|
385
|
+
* items: TodoItem[] = [];
|
|
386
|
+
*
|
|
387
|
+
* @Scope() // New array object on every update, no caching
|
|
388
|
+
* get completedItems(): TodoItem[] {
|
|
389
|
+
* return this.items.filter(item => item.completed).sort(...);
|
|
390
|
+
* }
|
|
391
|
+
*
|
|
392
|
+
* @Computed([]) // Better: cached + object reuse via StoreSync
|
|
393
|
+
* get completedItems(): TodoItem[] {
|
|
394
|
+
* return this.items.filter(item => item.completed).sort(...);
|
|
395
|
+
* }
|
|
396
|
+
* ```
|
|
89
397
|
*
|
|
90
398
|
* @returns A property decorator that can be applied to a getter method.
|
|
91
399
|
* @throws Will throw an error if the property is not a getter or if it has a setter.
|
|
400
|
+
* @remarks
|
|
401
|
+
* The @Scope decorator creates a lightweight ObservableScope directly from the getter.
|
|
402
|
+
* Like @Computed, it caches the computed value and only re-evaluates when dependencies change.
|
|
403
|
+
* The key difference is that @Scope returns a NEW object reference on each re-evaluation.
|
|
404
|
+
*
|
|
405
|
+
* **Initialization**: @Scope uses lazy initialization - the scope is created on first access:
|
|
406
|
+
* ```typescript
|
|
407
|
+
* @Scope()
|
|
408
|
+
* get displayName(): string {
|
|
409
|
+
* return this.firstName + " " + this.lastName;
|
|
410
|
+
* }
|
|
411
|
+
*
|
|
412
|
+
* // Scope created here, on first access:
|
|
413
|
+
* console.log(this.displayName);
|
|
414
|
+
* ```
|
|
415
|
+
*
|
|
416
|
+
* **Caching**: @Scope caches the computed value just like @Computed. The getter only runs
|
|
417
|
+
* when dependencies change, not on every access. However, each re-evaluation produces
|
|
418
|
+
* a new object reference.
|
|
419
|
+
*
|
|
420
|
+
* **Key difference from @Computed**: When dependencies change, @Scope returns a NEW object
|
|
421
|
+
* reference, while @Computed updates the EXISTING object in-place using diff-based updates.
|
|
422
|
+
*
|
|
423
|
+
* **Example of caching (shared with @Computed)**:
|
|
424
|
+
* ```typescript
|
|
425
|
+
* const result1 = this.fullName; // Computes: "John Doe"
|
|
426
|
+
* const result2 = this.fullName; // Cached: "John Doe" (same value)
|
|
427
|
+
* this.firstName = "Jane"; // Triggers re-computation
|
|
428
|
+
* const result3 = this.fullName; // Computes: "Jane Doe" (new value)
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* **Example of no object reuse (differs from @Computed)**:
|
|
432
|
+
* ```typescript
|
|
433
|
+
* const list = this.completedItems; // Reference A
|
|
434
|
+
* this.items[0].completed = true; // Triggers re-computation
|
|
435
|
+
* const list2 = this.completedItems; // NEW reference B
|
|
436
|
+
* console.log(list === list2); // false - @Scope creates new object
|
|
437
|
+
* ```
|
|
438
|
+
*
|
|
439
|
+
* **When to use @Scope**:
|
|
440
|
+
* - Computation is cheap (string ops, simple math, primitive values)
|
|
441
|
+
* - Object identity doesn't matter (primitives, one-time use values)
|
|
442
|
+
* - Memory overhead should be minimal
|
|
443
|
+
* - You don't need to preserve DOM references
|
|
444
|
+
*
|
|
445
|
+
* **Use @Computed instead when**:
|
|
446
|
+
* - Computation is expensive (array transformations, complex calculations)
|
|
447
|
+
* - Value is accessed frequently without dependency changes
|
|
448
|
+
* - Object identity matters (array/object references used in templates)
|
|
449
|
+
* - You need DOM reference preservation to avoid re-renders
|
|
450
|
+
*
|
|
451
|
+
* **Performance comparison**:
|
|
452
|
+
* | Aspect | @Scope | @Computed |
|
|
453
|
+
* |--------|--------|-----------|
|
|
454
|
+
* | Caches value | ✅ Yes | ✅ Yes |
|
|
455
|
+
* | Re-evaluates on dep change | ✅ Yes | ✅ Yes |
|
|
456
|
+
* | Object identity | ❌ New reference | ✅ Same reference |
|
|
457
|
+
* | Overhead | Minimal (single scope) | Higher (Store + diff) |
|
|
458
|
+
* | Best for | Primitives, cheap ops | Complex objects, expensive ops |
|
|
459
|
+
*
|
|
460
|
+
* @see {@link Computed} for cached computed properties with object reuse
|
|
461
|
+
* @see {@link ComputedAsync} for async computed properties
|
|
462
|
+
* @see {@link ObservableNode.ApplyDiff} for how @Computed maintains object identity
|
|
463
|
+
* @see {@link ObservableScope} for the scope-based reactivity system
|
|
92
464
|
*/
|
|
93
465
|
export declare function Scope(): typeof ScopeDecorator;
|
|
94
466
|
/**
|
|
@@ -101,51 +473,304 @@ export declare function Scope(): typeof ScopeDecorator;
|
|
|
101
473
|
* @throws Will throw an error if the property is not a getter or if it has a setter.
|
|
102
474
|
*/
|
|
103
475
|
declare function ScopeDecorator<T, K extends string>(target: T, propertyKey: K, descriptor: PropertyDescriptor): PropertyDescriptor;
|
|
476
|
+
/**
|
|
477
|
+
* Watch decorator factory for subscribing to observable changes and invoking a handler method.
|
|
478
|
+
* The watched value is computed from a function that accesses reactive properties, and the
|
|
479
|
+
* decorated method is called whenever that value changes.
|
|
480
|
+
*
|
|
481
|
+
* Use @Watch to react to changes in computed values or state without manual subscription management.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* class MyComponent extends Component {
|
|
486
|
+
* @Value()
|
|
487
|
+
* count: number = 0;
|
|
488
|
+
*
|
|
489
|
+
* @Watch((self) => self.count * 2)
|
|
490
|
+
* handleDoubled(value: number) {
|
|
491
|
+
* console.log('Doubled count:', value); // Called whenever count changes
|
|
492
|
+
* }
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```typescript
|
|
498
|
+
* class MyComponent extends Component {
|
|
499
|
+
* @State()
|
|
500
|
+
* user: { name: string; email: string } = { name: "", email: "" };
|
|
501
|
+
*
|
|
502
|
+
* @Watch((self) => self.user.name)
|
|
503
|
+
* onUserNameChanged(newName: string) {
|
|
504
|
+
* // Called when user.name changes
|
|
505
|
+
* this.saveToAnalytics(newName);
|
|
506
|
+
* }
|
|
507
|
+
* }
|
|
508
|
+
* ```
|
|
509
|
+
*
|
|
510
|
+
* @param scope A function that takes the component instance and returns the value to watch.
|
|
511
|
+
* The function should access reactive properties (@State, @Value, @Scope, @Computed).
|
|
512
|
+
* @returns A method decorator that subscribes to the watched value and invokes the method on changes.
|
|
513
|
+
* @throws Will throw an error if the decorated member is not a method.
|
|
514
|
+
*
|
|
515
|
+
* @remarks
|
|
516
|
+
* The @Watch decorator provides:
|
|
517
|
+
* - **Automatic subscription**: No manual ObservableScope.Watch() calls needed
|
|
518
|
+
* - **Immediate invocation**: The handler is called immediately with the initial value
|
|
519
|
+
* - **Cleanup on destroy**: Subscriptions are automatically removed when the component is destroyed
|
|
520
|
+
* - **Batched updates**: Uses greedy scopes to batch rapid changes efficiently
|
|
521
|
+
*
|
|
522
|
+
* **Lifecycle**: The watch subscription is established when the component's Bound() method is called,
|
|
523
|
+
* which happens after the component is constructed and attached to the DOM.
|
|
524
|
+
*
|
|
525
|
+
* **Performance**: Watched scopes are greedy (batched via microtask queue), so rapid changes
|
|
526
|
+
* to the watched value will only trigger one handler invocation.
|
|
527
|
+
*
|
|
528
|
+
* @see {@link Bound} for when the watch subscription is established
|
|
529
|
+
* @see {@link Destroy} for automatic cleanup on component teardown
|
|
530
|
+
* @see {@link Scope} for creating computed values to watch
|
|
531
|
+
*/
|
|
104
532
|
export declare function Watch<S extends (instance: T) => any, T extends Record<K, (value: ReturnType<S>) => any>, K extends string>(scope: S): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => void;
|
|
105
533
|
type ConstructorToken<I> = {
|
|
106
534
|
new (...args: any[]): I;
|
|
107
535
|
} | (abstract new (...args: any[]) => I);
|
|
108
536
|
/**
|
|
109
|
-
* Inject decorator factory for
|
|
110
|
-
*
|
|
537
|
+
* Inject decorator factory for dependency injection from the component's injector.
|
|
538
|
+
* Properties decorated with @Inject are lazily resolved from the component's Injector
|
|
539
|
+
* on first access, enabling loose coupling and testability.
|
|
540
|
+
*
|
|
541
|
+
* Use @Inject to access services, other components, or shared resources without direct instantiation.
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* ```typescript
|
|
545
|
+
* // Basic injection
|
|
546
|
+
* class MyComponent extends Component {
|
|
547
|
+
* @Inject(LoggerService)
|
|
548
|
+
* logger: LoggerService;
|
|
549
|
+
*
|
|
550
|
+
* doWork() {
|
|
551
|
+
* this.logger.log('Working...'); // Resolved from injector on first access
|
|
552
|
+
* }
|
|
553
|
+
* }
|
|
554
|
+
* ```
|
|
111
555
|
*
|
|
112
556
|
* @example
|
|
557
|
+
* ```typescript
|
|
558
|
+
* // Injection with default value (fallback if not in injector)
|
|
113
559
|
* class MyComponent extends Component {
|
|
114
|
-
* @Inject(
|
|
115
|
-
*
|
|
560
|
+
* @Inject(ConfigService)
|
|
561
|
+
* config: ConfigService = new ConfigService({}); // Default if not injected
|
|
116
562
|
* }
|
|
563
|
+
* ```
|
|
117
564
|
*
|
|
118
|
-
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```typescript
|
|
567
|
+
* // Setting injected values programmatically
|
|
119
568
|
* class MyComponent extends Component {
|
|
120
|
-
* @Inject(
|
|
121
|
-
*
|
|
569
|
+
* @Inject(ApiService)
|
|
570
|
+
* api: ApiService;
|
|
571
|
+
*
|
|
572
|
+
* constructor() {
|
|
573
|
+
* super();
|
|
574
|
+
* // Override injection for testing
|
|
575
|
+
* this.Injector.Set(ApiService, mockApiService);
|
|
576
|
+
* }
|
|
122
577
|
* }
|
|
578
|
+
* ```
|
|
123
579
|
*
|
|
124
|
-
* @param type The constructor type of the dependency to
|
|
580
|
+
* @param type The constructor type (or token) of the dependency to inject.
|
|
581
|
+
* Used as the key to look up the dependency in the injector.
|
|
125
582
|
* @returns A property decorator that can be applied to a property.
|
|
583
|
+
*
|
|
584
|
+
* @remarks
|
|
585
|
+
* The @Inject decorator provides:
|
|
586
|
+
* - **Lazy resolution**: Dependency is resolved on first access, not at construction
|
|
587
|
+
* - **Read-write access**: Both getter and setter are provided
|
|
588
|
+
* - **Injector hierarchy**: Resolves through parent injectors if not found locally
|
|
589
|
+
* - **Testability**: Easy to mock by setting values in the injector
|
|
590
|
+
*
|
|
591
|
+
* **Resolution flow**:
|
|
592
|
+
* 1. Access property → calls Injector.Get(type)
|
|
593
|
+
* 2. If found in current injector, return the value
|
|
594
|
+
* 3. If not found, traverse up parent injectors
|
|
595
|
+
* 4. If still not found, returns undefined (use default value if provided)
|
|
596
|
+
*
|
|
597
|
+
* **Setting values**: You can also set injected values directly:
|
|
598
|
+
* ```typescript
|
|
599
|
+
* this.Injector.Set(ApiService, customService);
|
|
600
|
+
* // Now @Inject(ApiService) will return customService
|
|
601
|
+
* ```
|
|
602
|
+
*
|
|
603
|
+
* **Lifecycle**: The injector is set up during component construction. Injected values
|
|
604
|
+
* can be accessed anytime after construction, but typically in lifecycle methods like Bound().
|
|
605
|
+
*
|
|
606
|
+
* @see {@link Injector} for the dependency injection system
|
|
607
|
+
* @see {@link Component.Injector} for accessing the component's injector
|
|
126
608
|
*/
|
|
127
609
|
export declare function Inject<I, T extends Record<K, I> & {
|
|
128
610
|
Injector: Injector;
|
|
129
611
|
}, K extends string>(type: ConstructorToken<I>): (target: T, propertyKey: K, descriptor?: PropertyDescriptor) => void;
|
|
130
612
|
/**
|
|
131
|
-
* Destroy decorator factory for marking
|
|
613
|
+
* Destroy decorator factory for marking properties that need cleanup on component teardown.
|
|
614
|
+
* Properties decorated with @Destroy will have their .Destroy() method automatically called
|
|
615
|
+
* when the component is destroyed, preventing memory leaks and resource leaks.
|
|
616
|
+
*
|
|
617
|
+
* Use @Destroy for managing disposable resources like timers, subscriptions, event listeners,
|
|
618
|
+
* or any object with a cleanup method.
|
|
619
|
+
*
|
|
132
620
|
* @example
|
|
621
|
+
* ```typescript
|
|
133
622
|
* class MyComponent extends Component {
|
|
134
623
|
* @Destroy()
|
|
135
|
-
* timer: Timer;
|
|
624
|
+
* timer: Timer = new Timer();
|
|
625
|
+
*
|
|
626
|
+
* startCountdown() {
|
|
627
|
+
* this.timer.start(1000); // Automatically stopped when component destroys
|
|
628
|
+
* }
|
|
136
629
|
* }
|
|
630
|
+
* ```
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```typescript
|
|
634
|
+
* class MyComponent extends Component {
|
|
635
|
+
* @Destroy()
|
|
636
|
+
* subscription: Subscription = someObservable.subscribe(...);
|
|
637
|
+
*
|
|
638
|
+
* // No manual unsubscribe needed - handled automatically
|
|
639
|
+
* }
|
|
640
|
+
* ```
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```typescript
|
|
644
|
+
* class MyComponent extends Component {
|
|
645
|
+
* @Destroy()
|
|
646
|
+
* eventListener: EventListener = new EventListener();
|
|
647
|
+
*
|
|
648
|
+
* bound() {
|
|
649
|
+
* document.addEventListener('click', this.eventListener);
|
|
650
|
+
* }
|
|
651
|
+
* // Automatically cleaned up when component is destroyed
|
|
652
|
+
* }
|
|
653
|
+
* ```
|
|
654
|
+
*
|
|
655
|
+
* @returns A property decorator that marks the property for automatic cleanup.
|
|
656
|
+
*
|
|
657
|
+
* @remarks
|
|
658
|
+
* The @Destroy decorator provides:
|
|
659
|
+
* - **Automatic cleanup**: Calls .Destroy() on the property when component is destroyed
|
|
660
|
+
* - **Null-safe**: Does nothing if the property is undefined/null
|
|
661
|
+
* - **Ordered cleanup**: Runs after observable scopes are destroyed
|
|
662
|
+
*
|
|
663
|
+
* **Requirements**: The decorated property must:
|
|
664
|
+
* 1. Be an object with a .Destroy() method
|
|
665
|
+
* 2. Have the method be callable with no arguments
|
|
666
|
+
* 3. Be assigned before component destruction
|
|
667
|
+
*
|
|
668
|
+
* **Lifecycle timing**:
|
|
669
|
+
* ```typescript
|
|
670
|
+
* component.Destroy() calls:
|
|
671
|
+
* 1. ObservableScope.DestroyAll() - Cleanup reactive scopes
|
|
672
|
+
* 2. Destroy.All() - Calls .Destroy() on all @Destroy()-marked properties
|
|
673
|
+
* ```
|
|
674
|
+
*
|
|
675
|
+
* **Common use cases**:
|
|
676
|
+
* - Timers and intervals
|
|
677
|
+
* - Observable subscriptions
|
|
678
|
+
* - Event listeners
|
|
679
|
+
* - WebSocket connections
|
|
680
|
+
* - File handles (Node.js)
|
|
681
|
+
* - Canvas contexts
|
|
682
|
+
*
|
|
683
|
+
* **What happens if .Destroy() throws?**: Errors are not caught - ensure your
|
|
684
|
+
* .Destroy() methods are robust and handle edge cases gracefully.
|
|
685
|
+
*
|
|
686
|
+
* @see {@link Destroy.All} for the cleanup implementation
|
|
687
|
+
* @see {@link Component.Destroy} for component lifecycle
|
|
137
688
|
*/
|
|
138
689
|
export declare function Destroy(): typeof DestroyDecorator;
|
|
690
|
+
/**
|
|
691
|
+
* Bound namespace provides lifecycle hooks for component initialization.
|
|
692
|
+
* Methods in this namespace are called after a component is attached to the DOM.
|
|
693
|
+
*/
|
|
139
694
|
export declare namespace Bound {
|
|
695
|
+
/**
|
|
696
|
+
* Binds all @Watch-decorated methods on a component instance.
|
|
697
|
+
* Called automatically by Component.Bound() after the component is attached to the DOM.
|
|
698
|
+
*
|
|
699
|
+
* @param value The component instance to bind.
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* ```typescript
|
|
703
|
+
* class MyComponent extends Component {
|
|
704
|
+
* // Bound.All(this) is called automatically in Component.Bound()
|
|
705
|
+
* public Bound() {
|
|
706
|
+
* super.Bound(); // This calls Bound.All(this)
|
|
707
|
+
* // Now @Watch handlers are active
|
|
708
|
+
* }
|
|
709
|
+
* }
|
|
710
|
+
* ```
|
|
711
|
+
*
|
|
712
|
+
* @remarks
|
|
713
|
+
* This method:
|
|
714
|
+
* - Finds all @Watch-decorated methods on the component's prototype chain
|
|
715
|
+
* - Invokes the binding function for each @Watch decorator
|
|
716
|
+
* - Establishes watch subscriptions for all decorated methods
|
|
717
|
+
*
|
|
718
|
+
* **Timing**: Called after component construction and DOM attachment, but before
|
|
719
|
+
* any user-defined Bound() logic runs (if you override Bound()).
|
|
720
|
+
*
|
|
721
|
+
* **Idempotent**: Safe to call multiple times - @Watch bindings are set up once.
|
|
722
|
+
*/
|
|
140
723
|
function All<T extends WeakKey>(value: T): void;
|
|
141
724
|
}
|
|
142
725
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* Destroy.All(this);
|
|
726
|
+
* Destroy namespace provides cleanup utilities for component teardown.
|
|
727
|
+
* Methods in this namespace are called when a component is destroyed to prevent memory leaks.
|
|
147
728
|
*/
|
|
148
729
|
export declare namespace Destroy {
|
|
730
|
+
/**
|
|
731
|
+
* Destroys all observable scopes and invokes .Destroy() on all @Destroy()-marked properties.
|
|
732
|
+
* Called automatically by Component.Destroy() during component teardown.
|
|
733
|
+
*
|
|
734
|
+
* @param value The component instance to destroy.
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* class MyComponent extends Component {
|
|
739
|
+
* @Destroy()
|
|
740
|
+
* timer: Timer = new Timer();
|
|
741
|
+
*
|
|
742
|
+
* // Destroy.All(this) is called automatically in Component.Destroy()
|
|
743
|
+
* public Destroy() {
|
|
744
|
+
* super.Destroy(); // This calls Destroy.All(this)
|
|
745
|
+
* // timer.Destroy() has been called, scopes are destroyed
|
|
746
|
+
* }
|
|
747
|
+
* }
|
|
748
|
+
* ```
|
|
749
|
+
*
|
|
750
|
+
* @remarks
|
|
751
|
+
* This method performs cleanup in the following order:
|
|
752
|
+
* 1. **ObservableScope.DestroyAll()**: Destroys all scopes created by @Value, @Scope, @Computed
|
|
753
|
+
* 2. **@Destroy cleanup**: Calls .Destroy() on each property marked with @Destroy()
|
|
754
|
+
*
|
|
755
|
+
* **Timing**: Called during component destruction, after unbinding from the DOM but before
|
|
756
|
+
* the component is fully garbage collected.
|
|
757
|
+
*
|
|
758
|
+
* **Idempotent**: Safe to call multiple times - destroyed scopes are marked and ignored.
|
|
759
|
+
*
|
|
760
|
+
* **Error handling**: If any .Destroy() method throws, the error propagates. Ensure your
|
|
761
|
+
* cleanup methods are robust and handle edge cases gracefully.
|
|
762
|
+
*
|
|
763
|
+
* **What gets destroyed**:
|
|
764
|
+
* - All @Value-decorated property scopes
|
|
765
|
+
* - All @Scope-decorated property scopes
|
|
766
|
+
* - All @Computed-decorated property scopes (both getter and property scopes)
|
|
767
|
+
* - All @ComputedAsync-decorated property scopes (including StoreAsync)
|
|
768
|
+
* - All @Destroy-marked properties (calls .Destroy() method)
|
|
769
|
+
* - All @Watch subscriptions (via scope destruction)
|
|
770
|
+
*
|
|
771
|
+
* @see {@link Bound.All} for initialization counterpart
|
|
772
|
+
* @see {@link Component.Destroy} for component lifecycle
|
|
773
|
+
*/
|
|
149
774
|
function All<T extends WeakKey>(value: T): void;
|
|
150
775
|
}
|
|
151
776
|
declare function DestroyDecorator<T extends Record<K, IDestroyable>, K extends string>(target: T, propertyKey: K): any;
|