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.
@@ -9,6 +9,52 @@
9
9
  * @see StoreAsync
10
10
  * @see StoreSync
11
11
  * @see Component
12
+ *
13
+ * Decorator Selection Guide
14
+ * ==========================
15
+ *
16
+ * | Your Value Type | Use | Why |
17
+ * |---------------------------|---------|-------------------------------|
18
+ * | number, string, boolean | @Value | Lightweight, no proxy overhead|
19
+ * | null, undefined | @Value | Simple scope storage |
20
+ * | { nested: objects } | @State | Deep reactivity needed |
21
+ * | arrays (User[], Todo[]) | @State | Array mutations tracked |
22
+ * | getters only (expensive) | @Computed | Cached + object reuse via Store |
23
+ * | getters only (simple) | @Scope | Cached, but new reference on update |
24
+ * | getters only (async) | @ComputedAsync | Async with caching + object reuse |
25
+ * | subscribe to changes | @Watch | Calls method when scope value changes |
26
+ * | dependency injection | @Inject | Gets value from component injector |
27
+ * | cleanup on destroy | @Destroy| Calls .Destroy() on component teardown |
28
+ *
29
+ * @Computed vs @Scope (Both are getters only, both cache):
30
+ * ---------------------------------------------------------
31
+ * | Use @Computed when: | Use @Scope when: |
32
+ * | - Computation is expensive | - Computation is cheap |
33
+ * | - Need object identity (===) | - Object identity doesn't matter|
34
+ * | - DOM reference preservation needed | - Minimal overhead preferred |
35
+ * | - Array/object transformations | - String/math operations |
36
+ *
37
+ * Key Difference: Object Identity on Update
38
+ * -----------------------------------------
39
+ * | Aspect | @Computed | @Scope |
40
+ * |--------|-----------|--------|
41
+ * | Caching | ✅ Yes | ✅ Yes |
42
+ * | Object identity preserved | ✅ Yes (ApplyDiff) | ❌ No (new object) |
43
+ * | Overhead | Store + diff | Single scope |
44
+ * | Best for | Complex objects | Primitives/simple values |
45
+ *
46
+ * Object Identity (Key Difference):
47
+ * ---------------------------------
48
+ * @Computed: Same object reference, in-place updates via ObservableNode.ApplyDiff
49
+ * @Scope: New object reference on every update
50
+ *
51
+ * Example:
52
+ * ```typescript
53
+ * const list = this.items; // Reference A
54
+ * this.data.changed = true; // Triggers update
55
+ * const list2 = this.items; // @Computed: A, @Scope: B
56
+ * console.log(list === list2); // @Computed: true, @Scope: false
57
+ * ```
12
58
  */
13
59
  Object.defineProperty(exports, "__esModule", { value: true });
14
60
  exports.Bound = void 0;
@@ -102,21 +148,101 @@ function CreateComputedScope(getter, defaultValue, store) {
102
148
  return propertyScope;
103
149
  }
104
150
  /**
105
- * Computed decorator factory for creating synchronous computed properties.
151
+ * Computed decorator factory for creating synchronous computed properties with caching and object reuse.
106
152
  * A computed property is derived from other properties and automatically updates when its dependencies change.
107
153
  *
154
+ * Use @Computed for expensive computations that benefit from caching AND object identity preservation
155
+ * (filtering, sorting, aggregations, transformations of complex objects).
156
+ *
108
157
  * @example
109
- * class MyComponent extends Component {
110
- * @Computed({ count: 0 })
111
- * get myComputed(): { count: number } {
112
- * return { count: this.myStateValue };
113
- * }
158
+ * ```typescript
159
+ * // ✅ Good: Expensive computation accessed frequently
160
+ * @State()
161
+ * items: TodoItem[] = [];
162
+ *
163
+ * @Computed([])
164
+ * get completedItems(): TodoItem[] {
165
+ * // Expensive: filters entire array, cached until items change
166
+ * // Result object is REUSED - only changed properties are updated
167
+ * return this.items.filter(item => item.completed).sort(...);
114
168
  * }
169
+ * ```
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * // ❌ Avoid: Cheap computation - use @Scope() instead
174
+ * @Value()
175
+ * firstName: string = "John";
176
+ *
177
+ * @Value()
178
+ * lastName: string = "Doe";
179
+ *
180
+ * @Computed("") // Overhead: creates StoreSync, watch cycle, diff computation
181
+ * get fullName(): string {
182
+ * return this.firstName + " " + this.lastName; // Cheap string concat
183
+ * }
184
+ *
185
+ * @Scope() // Better: direct scope, no store overhead
186
+ * get fullName(): string {
187
+ * return this.firstName + " " + this.lastName;
188
+ * }
189
+ * ```
115
190
  *
116
191
  * @param defaultValue The default value to be used if the computed property is not defined.
117
192
  * @returns A property decorator that can be applied to a getter method.
118
193
  * @throws Will throw an error if the property is not a getter or if it has a setter.
119
- * @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.
194
+ * @remarks
195
+ * The @Computed decorator uses a two-phase caching system with diff-based updates:
196
+ * 1. Getter scope: Computes value and writes to StoreSync when dependencies change
197
+ * 2. StoreSync: Computes diff between old and new values
198
+ * 3. ObservableNode.ApplyDiff: Updates the EXISTING object with only changed properties
199
+ * 4. Property scope: Reads the updated (but same reference) value from StoreSync
200
+ *
201
+ * **Initialization**: @Computed uses lazy initialization - the scopes are created on first access:
202
+ * ```typescript
203
+ * @Computed([])
204
+ * get completedItems(): TodoItem[] {
205
+ * return this.items.filter(i => i.completed);
206
+ * }
207
+ *
208
+ * // Scopes created here, on first access:
209
+ * const items = this.completedItems;
210
+ * ```
211
+ *
212
+ * **Caching**: Like @Scope, @Computed caches the computed value and only re-evaluates when
213
+ * dependencies change. The key difference is what happens during re-evaluation:
214
+ *
215
+ * **Key benefit - Object Reuse**: The returned object maintains its identity across updates.
216
+ * Only the changed properties are modified in-place using ObservableNode.ApplyDiff. This enables:
217
+ * - DOM reference preservation (no unnecessary re-renders)
218
+ * - Existing proxy reuse (no proxy recreation overhead)
219
+ * - Identity checks (===) remain stable across updates
220
+ * - Efficient propagation (only changed properties trigger downstream updates)
221
+ *
222
+ * **Example of object reuse**:
223
+ * ```typescript
224
+ * const list = this.completedItems; // Reference A
225
+ * this.items[0].completed = true; // Triggers re-computation
226
+ * const list2 = this.completedItems; // Same reference A, not a new array
227
+ * console.log(list === list2); // true - @Computed preserves identity
228
+ * ```
229
+ *
230
+ * **@Computed vs @Scope caching**:
231
+ * | Aspect | @Computed | @Scope |
232
+ * |--------|-----------|--------|
233
+ * | Caches value | ✅ Yes | ✅ Yes |
234
+ * | Re-evaluates on dep change | ✅ Yes | ✅ Yes |
235
+ * | Object identity preserved | ✅ Yes | ❌ No |
236
+ * | Returns new object on update | ❌ No | ✅ Yes |
237
+ *
238
+ * **Performance note**:
239
+ * - Use @Computed for expensive operations on complex objects where object reuse matters
240
+ * - Use @Scope() for simple computations where object identity doesn't matter
241
+ * - The diff computation overhead is justified when you need object reuse
242
+ * @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
243
+ * @see {@link ComputedAsync} for async computed properties
244
+ * @see {@link ObservableNode.ApplyDiff} for how diffs are applied to maintain object identity
245
+ * @see {@link StoreSync} for sync store implementation
120
246
  */
121
247
  function Computed(defaultValue) {
122
248
  return function (target, propertyKey, descriptor) {
@@ -154,21 +280,83 @@ function ComputedDecorator(target, prop, descriptor, defaultValue) {
154
280
  };
155
281
  }
156
282
  /**
157
- * ComputedAsync decorator factory for creating asynchronous computed properties.
283
+ * ComputedAsync decorator factory for creating asynchronous computed properties with caching.
158
284
  * A computed property is derived from other properties and automatically updates when its dependencies change.
159
285
  *
286
+ * Use @ComputedAsync for expensive async operations (API calls, file reads, database queries).
287
+ *
160
288
  * @example
161
- * class MyComponent extends Component {
162
- * @ComputedAsync({ items: [] })
163
- * get myAsyncComputed(): { items: any[] } {
164
- * return { items: this.myStateValue };
165
- * }
289
+ * ```typescript
290
+ * // ✅ Good: Async operation with caching
291
+ * @Value()
292
+ * userId: string = "123";
293
+ *
294
+ * @ComputedAsync(null)
295
+ * async getUser(): Promise<User | null> {
296
+ * // Expensive: API call, cached until userId changes
297
+ * return await fetch(`/api/users/${this.userId}`);
298
+ * }
299
+ * ```
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * // ❌ Avoid: Simple/synchronous computation - use @Computed() instead
304
+ * @Value()
305
+ * count: number = 0;
306
+ *
307
+ * @ComputedAsync(0) // Overhead: async wrapper, StoreAsync
308
+ * get doubled(): number {
309
+ * return this.count * 2; // Sync operation
310
+ * }
311
+ *
312
+ * @Computed(0) // Better: synchronous caching
313
+ * get doubled(): number {
314
+ * return this.count * 2;
166
315
  * }
316
+ * ```
167
317
  *
168
318
  * @param defaultValue The default value to be used if the computed property is not defined.
169
319
  * @returns A property decorator that can be applied to a getter method.
170
320
  * @throws Will throw an error if the property is not a getter or if it has a setter.
171
- * @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.
321
+ * @remarks
322
+ * The @ComputedAsync decorator uses StoreAsync for caching async results. The getter must be
323
+ * an async function or return a Promise. While waiting for the async operation, the defaultValue
324
+ * is returned. When the Promise resolves, the value is cached and dependent scopes update.
325
+ *
326
+ * **Initialization**: @ComputedAsync uses lazy initialization - the scopes are created on first access:
327
+ * ```typescript
328
+ * @ComputedAsync(null)
329
+ * async getUser(): Promise<User> {
330
+ * return fetch('/api/user');
331
+ * }
332
+ *
333
+ * // Scopes created here, on first access:
334
+ * const user = await this.getUser;
335
+ * ```
336
+ *
337
+ * **Caching**: Like @Computed, @ComputedAsync caches the result and maintains object identity
338
+ * through diff-based updates. The async getter only runs when dependencies change.
339
+ *
340
+ * **Object Reuse**: Like @Computed, the resolved value maintains its identity across updates.
341
+ * Only changed properties are modified in-place, preserving object references.
342
+ *
343
+ * **Important**: Only use this for truly async operations. For sync computations, use @Computed()
344
+ * which has less overhead and provides synchronous values.
345
+ *
346
+ * **Comparison**:
347
+ * | Aspect | @Scope | @Computed | @ComputedAsync |
348
+ * |--------|--------|-----------|----------------|
349
+ * | Caches value | ✅ Yes | ✅ Yes | ✅ Yes |
350
+ * | Sync/Async | Sync | Sync | Async |
351
+ * | Object identity | ❌ New reference | ✅ Same reference | ✅ Same reference |
352
+ * | Best for | Simple sync values | Complex sync values | Async operations |
353
+ *
354
+ * **Error handling**: If the async getter throws, the error is stored in the StoreAsync.
355
+ * You can handle errors in the getter itself or by watching for changes and checking the value.
356
+ *
357
+ * @see {@link Computed} for synchronous computed properties with object reuse
358
+ * @see {@link Scope} for simple getter-based reactive properties (caches, new reference)
359
+ * @see {@link StoreAsync} for async store implementation
172
360
  */
173
361
  function ComputedAsync(defaultValue) {
174
362
  return function (target, propertyKey, descriptor) {
@@ -206,17 +394,62 @@ function ComputedAsyncDecorator(target, prop, descriptor, defaultValue) {
206
394
  };
207
395
  }
208
396
  /**
209
- * State decorator factory for creating state properties.
210
- * A state property is a reactive value that can be read and written synchronously.
397
+ * State decorator factory for creating reactive properties backed by ObservableNode proxies.
398
+ *
399
+ * Use this decorator for complex data structures (objects, arrays) that need deep reactivity.
211
400
  *
212
401
  * @example
213
- * class MyComponent extends Component {
214
- * @State()
215
- * myState: { count: number } = { count: 0 };
216
- * }
402
+ * ```typescript
403
+ * // ✅ Good: Complex objects/arrays
404
+ * @State()
405
+ * user: { name: string; email: string } = { name: "", email: "" };
406
+ *
407
+ * @State()
408
+ * items: TodoItem[] = [];
409
+ * ```
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * // ❌ Avoid: Simple primitives - use @Value() instead for better performance
414
+ * @State()
415
+ * count: number = 0; // Overhead: creates proxy, leaf scopes, caches
416
+ *
417
+ * @Value()
418
+ * count: number = 0; // Better: simple scope with direct value storage
419
+ * ```
217
420
  *
218
421
  * @returns A property decorator that can be applied to a property.
219
- * @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.
422
+ * @remarks
423
+ * The @State decorator wraps the value in an ObservableNode proxy, enabling:
424
+ * - Deep reactivity for nested properties and array mutations
425
+ * - Automatic proxy creation for all nested objects/arrays
426
+ * - Fine-grained per-property dependency tracking
427
+ *
428
+ * **Initialization**: @State uses lazy initialization - the proxy is created on first access,
429
+ * not at construction time. This means:
430
+ * ```typescript
431
+ * @State()
432
+ * data: { items: string[] } = { items: [] };
433
+ *
434
+ * // Proxy created here, on first access:
435
+ * console.log(this.data.items);
436
+ * ```
437
+ *
438
+ * **Performance note**: This overhead is beneficial for complex structures but wasteful for primitives.
439
+ * Use @Value() for simple types (number, string, boolean) that don't need deep reactivity.
440
+ *
441
+ * **Default value handling**: The value from the class field initializer is used on first access.
442
+ * If no initializer is provided, the initial value is `{ root: undefined }`.
443
+ * ```typescript
444
+ * @State()
445
+ * data: { items: string[] }; // Initial value: { root: undefined }
446
+ *
447
+ * @State()
448
+ * data2: { items: string[] } = { items: [] }; // Initial value: { root: { items: [] } }
449
+ * ```
450
+ * @see {@link Value} for simple primitive values
451
+ * @see {@link Computed} for derived/read-only values
452
+ * @see {@link ObservableNode} for the proxy-based reactivity system
220
453
  */
221
454
  function State() {
222
455
  return StateDecorator;
@@ -247,17 +480,65 @@ function StateDecorator(target, propertyKey) {
247
480
  };
248
481
  }
249
482
  /**
250
- * Value decorator factory for creating value properties.
251
- * A value property is a reactive value that can be read and written synchronously.
483
+ * Value decorator factory for creating reactive properties backed by ObservableScope.
484
+ *
485
+ * Use this decorator for simple primitive values (number, string, boolean, null, undefined).
252
486
  *
253
487
  * @example
254
- * class MyComponent extends Component {
255
- * @Value()
256
- * myValue: string = "Hello";
257
- * }
488
+ * ```typescript
489
+ * // ✅ Good: Simple primitives
490
+ * @Value()
491
+ * count: number = 0;
492
+ *
493
+ * @Value()
494
+ * isLoading: boolean = false;
495
+ *
496
+ * @Value()
497
+ * title: string = "My App";
498
+ * ```
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * // ❌ Avoid: Complex objects/arrays - use @State() instead for deep reactivity
503
+ * @Value()
504
+ * user: { name: string } = { name: "" }; // Nested changes won't trigger updates
505
+ *
506
+ * @State()
507
+ * user: { name: string } = { name: "" }; // Better: nested changes are tracked
508
+ * ```
258
509
  *
259
510
  * @returns A property decorator that can be applied to a property.
260
- * @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.
511
+ * @remarks
512
+ * The @Value decorator creates a lightweight ObservableScope to store the value.
513
+ * This is more efficient than @State() for primitives because it:
514
+ * - Avoids proxy creation overhead
515
+ * - Uses direct value comparison instead of deep diffing
516
+ * - Stores the value directly without nested scope caches
517
+ *
518
+ * **Initialization**: @Value uses lazy initialization - the scope is created on first access:
519
+ * ```typescript
520
+ * @Value()
521
+ * count: number = 0;
522
+ *
523
+ * // Scope created here, on first access:
524
+ * console.log(this.count);
525
+ * ```
526
+ *
527
+ * **Limitation**: Only top-level changes trigger updates. Nested object/array mutations
528
+ * will not be detected. Use @State() for complex structures.
529
+ *
530
+ * **Default value handling**: The value from the class field initializer is used on first access.
531
+ * If no initializer is provided, the initial value is `undefined`.
532
+ * ```typescript
533
+ * @Value()
534
+ * count: number; // Initial value: undefined
535
+ *
536
+ * @Value()
537
+ * count2: number = 0; // Initial value: 0
538
+ * ```
539
+ * @see {@link State} for complex objects/arrays needing deep reactivity
540
+ * @see {@link Computed} for derived/read-only values
541
+ * @see {@link ObservableScope} for the scope-based reactivity system
261
542
  */
262
543
  function Value() {
263
544
  return ValueDecorator;
@@ -300,19 +581,110 @@ function ValueDecorator(target, propertyKey) {
300
581
  };
301
582
  }
302
583
  /**
303
- * Scope decorator factory for creating scope properties.
304
- * A scope property is a reactive value that can be read and written synchronously.
584
+ * Scope decorator factory for creating reactive getter properties without caching overhead.
585
+ * A scope property is derived from other properties and automatically updates when its dependencies change.
586
+ *
587
+ * Use @Scope for simple, cheap computations where object identity preservation isn't needed.
305
588
  *
306
589
  * @example
307
- * class MyComponent extends Component {
308
- * @Scope()
309
- * get myScopeValue(): string {
310
- * return this.myStateValue + "!";
311
- * }
590
+ * ```typescript
591
+ * // ✅ Good: Simple, cheap computation
592
+ * @Value()
593
+ * firstName: string = "John";
594
+ *
595
+ * @Value()
596
+ * lastName: string = "Doe";
597
+ *
598
+ * @Scope()
599
+ * get fullName(): string {
600
+ * // Cheap: string concatenation, no caching overhead
601
+ * return this.firstName + " " + this.lastName;
312
602
  * }
603
+ * ```
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * // ❌ Avoid: Expensive computation - use @Computed() instead
608
+ * @State()
609
+ * items: TodoItem[] = [];
610
+ *
611
+ * @Scope() // New array object on every update, no caching
612
+ * get completedItems(): TodoItem[] {
613
+ * return this.items.filter(item => item.completed).sort(...);
614
+ * }
615
+ *
616
+ * @Computed([]) // Better: cached + object reuse via StoreSync
617
+ * get completedItems(): TodoItem[] {
618
+ * return this.items.filter(item => item.completed).sort(...);
619
+ * }
620
+ * ```
313
621
  *
314
622
  * @returns A property decorator that can be applied to a getter method.
315
623
  * @throws Will throw an error if the property is not a getter or if it has a setter.
624
+ * @remarks
625
+ * The @Scope decorator creates a lightweight ObservableScope directly from the getter.
626
+ * Like @Computed, it caches the computed value and only re-evaluates when dependencies change.
627
+ * The key difference is that @Scope returns a NEW object reference on each re-evaluation.
628
+ *
629
+ * **Initialization**: @Scope uses lazy initialization - the scope is created on first access:
630
+ * ```typescript
631
+ * @Scope()
632
+ * get displayName(): string {
633
+ * return this.firstName + " " + this.lastName;
634
+ * }
635
+ *
636
+ * // Scope created here, on first access:
637
+ * console.log(this.displayName);
638
+ * ```
639
+ *
640
+ * **Caching**: @Scope caches the computed value just like @Computed. The getter only runs
641
+ * when dependencies change, not on every access. However, each re-evaluation produces
642
+ * a new object reference.
643
+ *
644
+ * **Key difference from @Computed**: When dependencies change, @Scope returns a NEW object
645
+ * reference, while @Computed updates the EXISTING object in-place using diff-based updates.
646
+ *
647
+ * **Example of caching (shared with @Computed)**:
648
+ * ```typescript
649
+ * const result1 = this.fullName; // Computes: "John Doe"
650
+ * const result2 = this.fullName; // Cached: "John Doe" (same value)
651
+ * this.firstName = "Jane"; // Triggers re-computation
652
+ * const result3 = this.fullName; // Computes: "Jane Doe" (new value)
653
+ * ```
654
+ *
655
+ * **Example of no object reuse (differs from @Computed)**:
656
+ * ```typescript
657
+ * const list = this.completedItems; // Reference A
658
+ * this.items[0].completed = true; // Triggers re-computation
659
+ * const list2 = this.completedItems; // NEW reference B
660
+ * console.log(list === list2); // false - @Scope creates new object
661
+ * ```
662
+ *
663
+ * **When to use @Scope**:
664
+ * - Computation is cheap (string ops, simple math, primitive values)
665
+ * - Object identity doesn't matter (primitives, one-time use values)
666
+ * - Memory overhead should be minimal
667
+ * - You don't need to preserve DOM references
668
+ *
669
+ * **Use @Computed instead when**:
670
+ * - Computation is expensive (array transformations, complex calculations)
671
+ * - Value is accessed frequently without dependency changes
672
+ * - Object identity matters (array/object references used in templates)
673
+ * - You need DOM reference preservation to avoid re-renders
674
+ *
675
+ * **Performance comparison**:
676
+ * | Aspect | @Scope | @Computed |
677
+ * |--------|--------|-----------|
678
+ * | Caches value | ✅ Yes | ✅ Yes |
679
+ * | Re-evaluates on dep change | ✅ Yes | ✅ Yes |
680
+ * | Object identity | ❌ New reference | ✅ Same reference |
681
+ * | Overhead | Minimal (single scope) | Higher (Store + diff) |
682
+ * | Best for | Primitives, cheap ops | Complex objects, expensive ops |
683
+ *
684
+ * @see {@link Computed} for cached computed properties with object reuse
685
+ * @see {@link ComputedAsync} for async computed properties
686
+ * @see {@link ObservableNode.ApplyDiff} for how @Computed maintains object identity
687
+ * @see {@link ObservableScope} for the scope-based reactivity system
316
688
  */
317
689
  function Scope() {
318
690
  return ScopeDecorator;
@@ -345,6 +717,62 @@ function ScopeDecorator(target, propertyKey, descriptor) {
345
717
  },
346
718
  };
347
719
  }
720
+ /**
721
+ * Watch decorator factory for subscribing to observable changes and invoking a handler method.
722
+ * The watched value is computed from a function that accesses reactive properties, and the
723
+ * decorated method is called whenever that value changes.
724
+ *
725
+ * Use @Watch to react to changes in computed values or state without manual subscription management.
726
+ *
727
+ * @example
728
+ * ```typescript
729
+ * class MyComponent extends Component {
730
+ * @Value()
731
+ * count: number = 0;
732
+ *
733
+ * @Watch((self) => self.count * 2)
734
+ * handleDoubled(value: number) {
735
+ * console.log('Doubled count:', value); // Called whenever count changes
736
+ * }
737
+ * }
738
+ * ```
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * class MyComponent extends Component {
743
+ * @State()
744
+ * user: { name: string; email: string } = { name: "", email: "" };
745
+ *
746
+ * @Watch((self) => self.user.name)
747
+ * onUserNameChanged(newName: string) {
748
+ * // Called when user.name changes
749
+ * this.saveToAnalytics(newName);
750
+ * }
751
+ * }
752
+ * ```
753
+ *
754
+ * @param scope A function that takes the component instance and returns the value to watch.
755
+ * The function should access reactive properties (@State, @Value, @Scope, @Computed).
756
+ * @returns A method decorator that subscribes to the watched value and invokes the method on changes.
757
+ * @throws Will throw an error if the decorated member is not a method.
758
+ *
759
+ * @remarks
760
+ * The @Watch decorator provides:
761
+ * - **Automatic subscription**: No manual ObservableScope.Watch() calls needed
762
+ * - **Immediate invocation**: The handler is called immediately with the initial value
763
+ * - **Cleanup on destroy**: Subscriptions are automatically removed when the component is destroyed
764
+ * - **Batched updates**: Uses greedy scopes to batch rapid changes efficiently
765
+ *
766
+ * **Lifecycle**: The watch subscription is established when the component's Bound() method is called,
767
+ * which happens after the component is constructed and attached to the DOM.
768
+ *
769
+ * **Performance**: Watched scopes are greedy (batched via microtask queue), so rapid changes
770
+ * to the watched value will only trigger one handler invocation.
771
+ *
772
+ * @see {@link Bound} for when the watch subscription is established
773
+ * @see {@link Destroy} for automatic cleanup on component teardown
774
+ * @see {@link Scope} for creating computed values to watch
775
+ */
348
776
  function Watch(scope) {
349
777
  return function (target, propertyKey, descriptor) {
350
778
  return WatchDecorator(target, propertyKey, descriptor, scope);
@@ -370,23 +798,77 @@ function WatchDecorator(target, propertyKey, descriptor, scopeFunction) {
370
798
  boundArray.push(bindWatch);
371
799
  }
372
800
  /**
373
- * Inject decorator factory for creating dependency-injected properties.
374
- * An injected property is automatically provided by the framework's dependency injection system.
801
+ * Inject decorator factory for dependency injection from the component's injector.
802
+ * Properties decorated with @Inject are lazily resolved from the component's Injector
803
+ * on first access, enabling loose coupling and testability.
804
+ *
805
+ * Use @Inject to access services, other components, or shared resources without direct instantiation.
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * // Basic injection
810
+ * class MyComponent extends Component {
811
+ * @Inject(LoggerService)
812
+ * logger: LoggerService;
813
+ *
814
+ * doWork() {
815
+ * this.logger.log('Working...'); // Resolved from injector on first access
816
+ * }
817
+ * }
818
+ * ```
375
819
  *
376
820
  * @example
821
+ * ```typescript
822
+ * // Injection with default value (fallback if not in injector)
377
823
  * class MyComponent extends Component {
378
- * @Inject(Token)
379
- * property: ServiceForToken;
824
+ * @Inject(ConfigService)
825
+ * config: ConfigService = new ConfigService({}); // Default if not injected
380
826
  * }
827
+ * ```
381
828
  *
382
- * // Setting the value in the class definition
829
+ * @example
830
+ * ```typescript
831
+ * // Setting injected values programmatically
383
832
  * class MyComponent extends Component {
384
- * @Inject(Token)
385
- * property: ServiceForToken = new ServiceForToken();
833
+ * @Inject(ApiService)
834
+ * api: ApiService;
835
+ *
836
+ * constructor() {
837
+ * super();
838
+ * // Override injection for testing
839
+ * this.Injector.Set(ApiService, mockApiService);
840
+ * }
386
841
  * }
842
+ * ```
387
843
  *
388
- * @param type The constructor type of the dependency to be injected.
844
+ * @param type The constructor type (or token) of the dependency to inject.
845
+ * Used as the key to look up the dependency in the injector.
389
846
  * @returns A property decorator that can be applied to a property.
847
+ *
848
+ * @remarks
849
+ * The @Inject decorator provides:
850
+ * - **Lazy resolution**: Dependency is resolved on first access, not at construction
851
+ * - **Read-write access**: Both getter and setter are provided
852
+ * - **Injector hierarchy**: Resolves through parent injectors if not found locally
853
+ * - **Testability**: Easy to mock by setting values in the injector
854
+ *
855
+ * **Resolution flow**:
856
+ * 1. Access property → calls Injector.Get(type)
857
+ * 2. If found in current injector, return the value
858
+ * 3. If not found, traverse up parent injectors
859
+ * 4. If still not found, returns undefined (use default value if provided)
860
+ *
861
+ * **Setting values**: You can also set injected values directly:
862
+ * ```typescript
863
+ * this.Injector.Set(ApiService, customService);
864
+ * // Now @Inject(ApiService) will return customService
865
+ * ```
866
+ *
867
+ * **Lifecycle**: The injector is set up during component construction. Injected values
868
+ * can be accessed anytime after construction, but typically in lifecycle methods like Bound().
869
+ *
870
+ * @see {@link Injector} for the dependency injection system
871
+ * @see {@link Component.Injector} for accessing the component's injector
390
872
  */
391
873
  function Inject(type) {
392
874
  return function () {
@@ -406,18 +888,119 @@ function InjectDecorator(type) {
406
888
  };
407
889
  }
408
890
  /**
409
- * Destroy decorator factory for marking a property to be destroyed when the component is destroyed.
891
+ * Destroy decorator factory for marking properties that need cleanup on component teardown.
892
+ * Properties decorated with @Destroy will have their .Destroy() method automatically called
893
+ * when the component is destroyed, preventing memory leaks and resource leaks.
894
+ *
895
+ * Use @Destroy for managing disposable resources like timers, subscriptions, event listeners,
896
+ * or any object with a cleanup method.
897
+ *
410
898
  * @example
899
+ * ```typescript
411
900
  * class MyComponent extends Component {
412
901
  * @Destroy()
413
- * timer: Timer;
902
+ * timer: Timer = new Timer();
903
+ *
904
+ * startCountdown() {
905
+ * this.timer.start(1000); // Automatically stopped when component destroys
906
+ * }
414
907
  * }
908
+ * ```
909
+ *
910
+ * @example
911
+ * ```typescript
912
+ * class MyComponent extends Component {
913
+ * @Destroy()
914
+ * subscription: Subscription = someObservable.subscribe(...);
915
+ *
916
+ * // No manual unsubscribe needed - handled automatically
917
+ * }
918
+ * ```
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * class MyComponent extends Component {
923
+ * @Destroy()
924
+ * eventListener: EventListener = new EventListener();
925
+ *
926
+ * bound() {
927
+ * document.addEventListener('click', this.eventListener);
928
+ * }
929
+ * // Automatically cleaned up when component is destroyed
930
+ * }
931
+ * ```
932
+ *
933
+ * @returns A property decorator that marks the property for automatic cleanup.
934
+ *
935
+ * @remarks
936
+ * The @Destroy decorator provides:
937
+ * - **Automatic cleanup**: Calls .Destroy() on the property when component is destroyed
938
+ * - **Null-safe**: Does nothing if the property is undefined/null
939
+ * - **Ordered cleanup**: Runs after observable scopes are destroyed
940
+ *
941
+ * **Requirements**: The decorated property must:
942
+ * 1. Be an object with a .Destroy() method
943
+ * 2. Have the method be callable with no arguments
944
+ * 3. Be assigned before component destruction
945
+ *
946
+ * **Lifecycle timing**:
947
+ * ```typescript
948
+ * component.Destroy() calls:
949
+ * 1. ObservableScope.DestroyAll() - Cleanup reactive scopes
950
+ * 2. Destroy.All() - Calls .Destroy() on all @Destroy()-marked properties
951
+ * ```
952
+ *
953
+ * **Common use cases**:
954
+ * - Timers and intervals
955
+ * - Observable subscriptions
956
+ * - Event listeners
957
+ * - WebSocket connections
958
+ * - File handles (Node.js)
959
+ * - Canvas contexts
960
+ *
961
+ * **What happens if .Destroy() throws?**: Errors are not caught - ensure your
962
+ * .Destroy() methods are robust and handle edge cases gracefully.
963
+ *
964
+ * @see {@link Destroy.All} for the cleanup implementation
965
+ * @see {@link Component.Destroy} for component lifecycle
415
966
  */
416
967
  function Destroy() {
417
968
  return DestroyDecorator;
418
969
  }
970
+ /**
971
+ * Bound namespace provides lifecycle hooks for component initialization.
972
+ * Methods in this namespace are called after a component is attached to the DOM.
973
+ */
419
974
  var Bound;
420
975
  (function (Bound) {
976
+ /**
977
+ * Binds all @Watch-decorated methods on a component instance.
978
+ * Called automatically by Component.Bound() after the component is attached to the DOM.
979
+ *
980
+ * @param value The component instance to bind.
981
+ *
982
+ * @example
983
+ * ```typescript
984
+ * class MyComponent extends Component {
985
+ * // Bound.All(this) is called automatically in Component.Bound()
986
+ * public Bound() {
987
+ * super.Bound(); // This calls Bound.All(this)
988
+ * // Now @Watch handlers are active
989
+ * }
990
+ * }
991
+ * ```
992
+ *
993
+ * @remarks
994
+ * This method:
995
+ * - Finds all @Watch-decorated methods on the component's prototype chain
996
+ * - Invokes the binding function for each @Watch decorator
997
+ * - Establishes watch subscriptions for all decorated methods
998
+ *
999
+ * **Timing**: Called after component construction and DOM attachment, but before
1000
+ * any user-defined Bound() logic runs (if you override Bound()).
1001
+ *
1002
+ * **Idempotent**: Safe to call multiple times - @Watch bindings are set up once.
1003
+ */
421
1004
  function All(value) {
422
1005
  const array = GetBoundArrayForPrototype(Object.getPrototypeOf(value), false);
423
1006
  for (let x = 0; array && x < array.length; x++)
@@ -426,12 +1009,54 @@ var Bound;
426
1009
  Bound.All = All;
427
1010
  })(Bound || (exports.Bound = Bound = {}));
428
1011
  /**
429
- * Utility to destroy all observable scopes and invoke destroy on marked properties of an instance.
430
- * @param value The instance to clean up.
431
- * @example
432
- * Destroy.All(this);
1012
+ * Destroy namespace provides cleanup utilities for component teardown.
1013
+ * Methods in this namespace are called when a component is destroyed to prevent memory leaks.
433
1014
  */
434
1015
  (function (Destroy) {
1016
+ /**
1017
+ * Destroys all observable scopes and invokes .Destroy() on all @Destroy()-marked properties.
1018
+ * Called automatically by Component.Destroy() during component teardown.
1019
+ *
1020
+ * @param value The component instance to destroy.
1021
+ *
1022
+ * @example
1023
+ * ```typescript
1024
+ * class MyComponent extends Component {
1025
+ * @Destroy()
1026
+ * timer: Timer = new Timer();
1027
+ *
1028
+ * // Destroy.All(this) is called automatically in Component.Destroy()
1029
+ * public Destroy() {
1030
+ * super.Destroy(); // This calls Destroy.All(this)
1031
+ * // timer.Destroy() has been called, scopes are destroyed
1032
+ * }
1033
+ * }
1034
+ * ```
1035
+ *
1036
+ * @remarks
1037
+ * This method performs cleanup in the following order:
1038
+ * 1. **ObservableScope.DestroyAll()**: Destroys all scopes created by @Value, @Scope, @Computed
1039
+ * 2. **@Destroy cleanup**: Calls .Destroy() on each property marked with @Destroy()
1040
+ *
1041
+ * **Timing**: Called during component destruction, after unbinding from the DOM but before
1042
+ * the component is fully garbage collected.
1043
+ *
1044
+ * **Idempotent**: Safe to call multiple times - destroyed scopes are marked and ignored.
1045
+ *
1046
+ * **Error handling**: If any .Destroy() method throws, the error propagates. Ensure your
1047
+ * cleanup methods are robust and handle edge cases gracefully.
1048
+ *
1049
+ * **What gets destroyed**:
1050
+ * - All @Value-decorated property scopes
1051
+ * - All @Scope-decorated property scopes
1052
+ * - All @Computed-decorated property scopes (both getter and property scopes)
1053
+ * - All @ComputedAsync-decorated property scopes (including StoreAsync)
1054
+ * - All @Destroy-marked properties (calls .Destroy() method)
1055
+ * - All @Watch subscriptions (via scope destruction)
1056
+ *
1057
+ * @see {@link Bound.All} for initialization counterpart
1058
+ * @see {@link Component.Destroy} for component lifecycle
1059
+ */
435
1060
  function All(value) {
436
1061
  const scopeMap = scopeInstanceMap.get(value);
437
1062
  if (scopeMap !== undefined) {