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.
@@ -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
- * class MyComponent extends Component {
20
- * @Computed({ count: 0 })
21
- * get myComputed(): { count: number } {
22
- * return { count: this.myStateValue };
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 The computed value is backed by a StoreSync instance, which caches the latest result of the getter. When any of the getter's dependencies change, the StoreSync is updated and the computed property reflects the new value synchronously. The provided `defaultValue` is returned until the first evaluation completes.
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
- * class MyComponent extends Component {
38
- * @ComputedAsync({ items: [] })
39
- * get myAsyncComputed(): { items: any[] } {
40
- * return { items: this.myStateValue };
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 The computed value is backed by a StoreAsync instance, which caches the result of the asynchronous getter. When dependencies change, the StoreAsync updates and the computed property resolves to the latest value. The provided `defaultValue` is returned until the async computation completes.
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 state properties.
52
- * A state property is a reactive value that can be read and written synchronously.
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
- * class MyComponent extends Component {
56
- * @State()
57
- * myState: { count: number } = { count: 0 };
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 The State decorator creates an ObservableNode to store the value. Updates to the property automatically notify any watchers of dependent scopes, making it ideal for mutable complex data structures that need to trigger reactivity.
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 value properties.
66
- * A value property is a reactive value that can be read and written synchronously.
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
- * class MyComponent extends Component {
70
- * @Value()
71
- * myValue: string = "Hello";
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 The Value decorator lazily creates a scoped ObservableScope whose getter returns the stored value. The setter updates the stored value and invokes ObservableScope.Update on the scope, causing any dependent computed or scope decorators to re-evaluate.
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 scope properties.
80
- * A scope property is a reactive value that can be read and written synchronously.
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
- * class MyComponent extends Component {
84
- * @Scope()
85
- * get myScopeValue(): string {
86
- * return this.myStateValue + "!";
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 creating dependency-injected properties.
110
- * An injected property is automatically provided by the framework's dependency injection system.
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(Token)
115
- * property: ServiceForToken;
560
+ * @Inject(ConfigService)
561
+ * config: ConfigService = new ConfigService({}); // Default if not injected
116
562
  * }
563
+ * ```
117
564
  *
118
- * // Setting the value in the class definition
565
+ * @example
566
+ * ```typescript
567
+ * // Setting injected values programmatically
119
568
  * class MyComponent extends Component {
120
- * @Inject(Token)
121
- * property: ServiceForToken = new ServiceForToken();
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 be injected.
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 a property to be destroyed when the component is destroyed.
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
- * Utility to destroy all observable scopes and invoke destroy on marked properties of an instance.
144
- * @param value The instance to clean up.
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;