@vived/core 2.0.1 → 2.0.2

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.
@@ -0,0 +1,804 @@
1
+ # ExampleFeature
2
+
3
+ The ExampleFeature folder provides a complete, working example of how to implement a domain feature using the AppObject architecture and Clean Code dependency rules. This example demonstrates the proper structure, patterns, and practices for building maintainable, testable features.
4
+
5
+ ## Overview
6
+
7
+ This feature demonstrates:
8
+ - **Clean Architecture layers** - Entities, Use Cases, Presentation Managers, Controllers, and Adapters
9
+ - **AppObject pattern** - Component-based architecture with dependency injection
10
+ - **Singleton vs Instance patterns** - When to use global vs per-instance components
11
+ - **Observable pattern** - Automatic UI updates when data changes
12
+ - **Domain-driven design** - Organized by feature, not by technical layer
13
+ - **Test-driven development** - Comprehensive test coverage for all components
14
+
15
+ ## Architecture Layers
16
+
17
+ ### Dependency Flow (Clean Architecture)
18
+
19
+ ```
20
+ UI Layer (React/Vue/etc.)
21
+ ↓ (depends on)
22
+ Adapters Layer
23
+ ↓ (depends on)
24
+ Controllers Layer
25
+ ↓ (depends on)
26
+ Presentation Managers (PMs)
27
+ ↓ (depends on)
28
+ Use Cases (UCs)
29
+ ↓ (depends on)
30
+ Entities Layer
31
+ ```
32
+
33
+ **Key Rule:** Dependencies only flow inward. Inner layers never depend on outer layers.
34
+
35
+ ## Folder Structure
36
+
37
+ ```
38
+ ExampleFeature/
39
+ ├── index.ts # Barrel file exporting all controllers and adapters
40
+ ├── Entities/ # Domain models (innermost layer)
41
+ ├── UCs/ # Business logic (Use Cases)
42
+ ├── PMs/ # Presentation logic (View Models)
43
+ ├── Controllers/ # Simplified API for UI
44
+ ├── Adapters/ # UI framework integration
45
+ ├── Factory/ # Domain initialization
46
+ └── Mocks/ # Test doubles
47
+ ```
48
+
49
+ **Note:** The root `index.ts` barrel file exports all controllers and adapters, providing a single import point for consuming code.
50
+
51
+ ## Layer-by-Layer Guide
52
+
53
+ ### 1. Entities Layer
54
+
55
+ Entities store and manage domain data, tracking state changes and notifying observers.
56
+
57
+ **ExampleEntity.ts** - Instance-based entity (multiple instances possible):
58
+
59
+ ```typescript
60
+ import { AppObject, AppObjectEntity } from "@vived/core";
61
+ import { MemoizedString } from "@vived/core";
62
+
63
+ export abstract class ExampleEntity extends AppObjectEntity
64
+ {
65
+ static readonly type = "ExampleEntityType";
66
+
67
+ abstract get aStringProperty(): string;
68
+ abstract set aStringProperty(val: string);
69
+
70
+ static getById(id: string, appObjects: AppObjectRepo): ExampleEntity | undefined
71
+ {
72
+ return appObjects.get(id)?.getComponent<ExampleEntity>(this.type);
73
+ }
74
+ }
75
+
76
+ export function makeExampleEntity(appObject: AppObject): ExampleEntity
77
+ {
78
+ return new ExampleEntityImp(appObject);
79
+ }
80
+
81
+ class ExampleEntityImp extends ExampleEntity
82
+ {
83
+ private memoizedString = new MemoizedString("", this.notifyOnChange);
84
+
85
+ get aStringProperty()
86
+ {
87
+ return this.memoizedString.val;
88
+ }
89
+
90
+ set aStringProperty(val: string)
91
+ {
92
+ this.memoizedString.val = val; // Automatically notifies observers
93
+ }
94
+
95
+ constructor(appObject: AppObject)
96
+ {
97
+ super(appObject, ExampleEntity.type);
98
+ }
99
+ }
100
+ ```
101
+
102
+ **ExampleSingletonEntity.ts** - Singleton entity (only one instance globally):
103
+
104
+ ```typescript
105
+ import { AppObjectSingletonEntity, getSingletonComponent } from "@vived/core";
106
+ import { MemoizedBoolean } from "@vived/core";
107
+
108
+ export abstract class SingletonEntityExample extends AppObjectSingletonEntity
109
+ {
110
+ static readonly type = "SingletonEntityExampleType";
111
+
112
+ abstract get aBoolProperty(): boolean;
113
+ abstract set aBoolProperty(val: boolean);
114
+
115
+ static get(appObjects: AppObjectRepo): SingletonEntityExample | undefined
116
+ {
117
+ return getSingletonComponent(SingletonEntityExample.type, appObjects);
118
+ }
119
+ }
120
+
121
+ class SingletonEntityExampleImp extends SingletonEntityExample
122
+ {
123
+ private memoizedBoolean = new MemoizedBoolean(false, this.notifyOnChange);
124
+
125
+ get aBoolProperty()
126
+ {
127
+ return this.memoizedBoolean.val;
128
+ }
129
+
130
+ set aBoolProperty(val: boolean)
131
+ {
132
+ this.memoizedBoolean.val = val;
133
+ }
134
+
135
+ constructor(appObject: AppObject)
136
+ {
137
+ super(appObject, SingletonEntityExample.type);
138
+ }
139
+ }
140
+ ```
141
+
142
+ **ExampleRepo.ts** - Repository for managing collections:
143
+
144
+ ```typescript
145
+ import { AppObjectEntityRepo } from "@vived/core";
146
+
147
+ export abstract class ExampleRepo extends AppObjectEntityRepo<ExampleEntity>
148
+ {
149
+ static readonly type = "ExampleRepoType";
150
+ abstract deleteExampleEntity(id: string): void;
151
+ }
152
+
153
+ class ExampleRepoImp extends ExampleRepo
154
+ {
155
+ entityFactory(appObject: AppObject): ExampleEntity
156
+ {
157
+ return makeExampleEntity(appObject);
158
+ }
159
+
160
+ deleteExampleEntity(id: string): void
161
+ {
162
+ this.remove(id);
163
+ }
164
+
165
+ constructor(appObject: AppObject)
166
+ {
167
+ super(appObject, ExampleRepo.type);
168
+ }
169
+ }
170
+ ```
171
+
172
+ **Key Concepts:**
173
+ - Use `MemoizedString`, `MemoizedBoolean`, etc. for automatic change detection
174
+ - Extend `AppObjectEntity` for instance-based entities
175
+ - Extend `AppObjectSingletonEntity` for global entities
176
+ - Use repositories to manage collections of entities
177
+
178
+ ### 2. Use Cases (UCs) Layer
179
+
180
+ UCs encapsulate business logic and operations that modify entities.
181
+
182
+ **EditExampleStringUC.ts** - Instance-based UC:
183
+
184
+ ```typescript
185
+ import { AppObjectUC } from "@vived/core";
186
+
187
+ export abstract class EditExampleStringUC extends AppObjectUC
188
+ {
189
+ static readonly type = "EditExampleStringUCType";
190
+ abstract editExampleString(text: string): void;
191
+
192
+ static getById(id: string, appObjects: AppObjectRepo): EditExampleStringUC | undefined
193
+ {
194
+ return appObjects.get(id)?.getComponent<EditExampleStringUC>(this.type);
195
+ }
196
+ }
197
+
198
+ class EditSlideTextUCImp extends EditExampleStringUC
199
+ {
200
+ private get exampleEntity()
201
+ {
202
+ return this.getCachedLocalComponent<ExampleEntity>(ExampleEntity.type);
203
+ }
204
+
205
+ editExampleString = (text: string) => {
206
+ this.exampleEntity.aStringProperty = text;
207
+ };
208
+
209
+ constructor(appObject: AppObject)
210
+ {
211
+ super(appObject, EditExampleStringUC.type);
212
+ }
213
+ }
214
+ ```
215
+
216
+ **ToggleExampleBooleanUC.ts** - Singleton UC:
217
+
218
+ ```typescript
219
+ import { AppObjectSingletonUC, getSingletonComponent } from "@vived/core";
220
+
221
+ export abstract class ToggleExampleBooleanUC extends AppObjectSingletonUC
222
+ {
223
+ static readonly type = "ToggleExampleBooleanUCType";
224
+ abstract toggleExampleBoolean(): void;
225
+
226
+ static get(appObjects: AppObjectRepo): ToggleExampleBooleanUC | undefined
227
+ {
228
+ return getSingletonComponent(ToggleExampleBooleanUC.type, appObjects);
229
+ }
230
+ }
231
+
232
+ class ToggleExampleBooleanUCImp extends ToggleExampleBooleanUC
233
+ {
234
+ private get singletonEntityExample()
235
+ {
236
+ return this.getCachedSingleton<SingletonEntityExample>(
237
+ SingletonEntityExample.type
238
+ );
239
+ }
240
+
241
+ toggleExampleBoolean = () => {
242
+ this.singletonEntityExample.aBoolProperty =
243
+ !this.singletonEntityExample.aBoolProperty;
244
+ };
245
+
246
+ constructor(appObject: AppObject)
247
+ {
248
+ super(appObject, ToggleExampleBooleanUC.type);
249
+ }
250
+ }
251
+ ```
252
+
253
+ **Key Concepts:**
254
+ - UCs contain business logic, not presentation logic
255
+ - Use `getCachedLocalComponent()` for components on the same AppObject
256
+ - Use `getCachedSingleton()` for singleton components
257
+ - Methods should be named after the action they perform
258
+
259
+ ### 3. Presentation Managers (PMs) Layer
260
+
261
+ PMs transform entity data into view models for the UI.
262
+
263
+ **ExamplePM.ts** - Instance-based PM:
264
+
265
+ ```typescript
266
+ import { AppObjectPM } from "@vived/core";
267
+
268
+ export abstract class ExamplePM extends AppObjectPM<string>
269
+ {
270
+ static readonly type = "ExamplePMType";
271
+
272
+ static getById(id: string, appObjects: AppObjectRepo): ExamplePM | undefined
273
+ {
274
+ return appObjects.get(id)?.getComponent<ExamplePM>(ExamplePM.type);
275
+ }
276
+ }
277
+
278
+ class ExamplePMImp extends ExamplePM
279
+ {
280
+ readonly defaultVM = "";
281
+
282
+ private get exampleEntity()
283
+ {
284
+ return this.getCachedLocalComponent<ExampleEntity>(ExampleEntity.type);
285
+ }
286
+
287
+ vmsAreEqual(a: string, b: string): boolean
288
+ {
289
+ return a === b;
290
+ }
291
+
292
+ formVM = () => {
293
+ // Transform entity data into view model
294
+ this.doUpdateView(this.exampleEntity.aStringProperty);
295
+ };
296
+
297
+ constructor(appObject: AppObject)
298
+ {
299
+ super(appObject, ExamplePM.type);
300
+ this.observeEntity(this.exampleEntity); // Subscribe to changes
301
+ this.formVM(); // Initial view model
302
+ }
303
+ }
304
+ ```
305
+
306
+ **ExampleSingletonPM.ts** - Singleton PM:
307
+
308
+ ```typescript
309
+ import { AppObjectSingletonPM, getSingletonComponent } from "@vived/core";
310
+
311
+ export interface ExampleVM
312
+ {
313
+ aBoolProperty: boolean;
314
+ }
315
+
316
+ export abstract class ExampleSingletonPM extends AppObjectSingletonPM<ExampleVM>
317
+ {
318
+ static readonly type = "ExampleSingletonPMType";
319
+
320
+ static get(appObjects: AppObjectRepo): ExampleSingletonPM | undefined
321
+ {
322
+ return getSingletonComponent(ExampleSingletonPM.type, appObjects);
323
+ }
324
+ }
325
+
326
+ export const defaultExampleVM: ExampleVM = {
327
+ aBoolProperty: true
328
+ };
329
+
330
+ class ExampleSingletonPMImp extends ExampleSingletonPM
331
+ {
332
+ private get exampleEntity()
333
+ {
334
+ return this.getCachedSingleton<SingletonEntityExample>(
335
+ SingletonEntityExample.type
336
+ );
337
+ }
338
+
339
+ formVM = () => {
340
+ const vm: ExampleVM = {
341
+ aBoolProperty: this.exampleEntity.aBoolProperty
342
+ };
343
+ this.doUpdateView(vm);
344
+ };
345
+
346
+ vmsAreEqual(a: ExampleVM, b: ExampleVM): boolean
347
+ {
348
+ return a.aBoolProperty === b.aBoolProperty;
349
+ }
350
+
351
+ constructor(appObject: AppObject)
352
+ {
353
+ super(appObject, ExampleSingletonPM.type);
354
+ this.observeEntity(this.exampleEntity);
355
+ this.formVM();
356
+ }
357
+ }
358
+ ```
359
+
360
+ **Key Concepts:**
361
+ - PMs observe entities and call `formVM()` when they change
362
+ - Define view model interfaces for complex data structures
363
+ - Implement `vmsAreEqual()` to prevent unnecessary UI updates
364
+ - Call `doUpdateView()` to notify UI components
365
+
366
+ ### 4. Controllers Layer
367
+
368
+ Controllers provide a simplified API for UI components.
369
+
370
+ **setExampleText.ts** - Instance-based controller:
371
+
372
+ ```typescript
373
+ import { AppObjectRepo } from "@vived/core";
374
+ import { EditExampleStringUC } from "../UCs/EditExampleStringUC";
375
+
376
+ export function setExampleText(
377
+ text: string,
378
+ id: string,
379
+ appObjects: AppObjectRepo
380
+ )
381
+ {
382
+ const uc = EditExampleStringUC.getById(id, appObjects);
383
+
384
+ if (!uc)
385
+ {
386
+ appObjects.submitWarning(
387
+ "setExampleText",
388
+ "Unable to find EditExampleStringUC"
389
+ );
390
+ return;
391
+ }
392
+
393
+ uc.editExampleString(text);
394
+ }
395
+ ```
396
+
397
+ **toggleExampleBoolean.ts** - Singleton controller:
398
+
399
+ ```typescript
400
+ import { AppObjectRepo } from "@vived/core";
401
+ import { ToggleExampleBooleanUC } from "../UCs/ToggleExampleBooleanUC";
402
+
403
+ export function toggleExampleBoolean(appObjects: AppObjectRepo)
404
+ {
405
+ const uc = ToggleExampleBooleanUC.get(appObjects);
406
+
407
+ if (!uc)
408
+ {
409
+ appObjects.submitWarning(
410
+ "toggleExampleBoolean",
411
+ "Unable to find ToggleExampleBooleanUC"
412
+ );
413
+ return;
414
+ }
415
+
416
+ uc.toggleExampleBoolean();
417
+ }
418
+ ```
419
+
420
+ **Key Concepts:**
421
+ - Controllers are simple functions, not classes
422
+ - They find the appropriate UC and call its methods
423
+ - They handle errors gracefully with warnings
424
+ - They simplify the API for UI components
425
+
426
+ ### 5. Adapters Layer
427
+
428
+ Adapters connect UI frameworks to PMs.
429
+
430
+ **examplePmAdapter.ts** - Instance-based adapter:
431
+
432
+ ```typescript
433
+ import { PmAdapter } from "@vived/core";
434
+ import { ExamplePM } from "../PMs/ExamplePM";
435
+
436
+ export const examplePmAdapter: PmAdapter<string> = {
437
+ defaultVM: "",
438
+
439
+ subscribe: (id: string, appObjects: AppObjectRepo, setVM: (vm: string) => void) => {
440
+ if (!id) return;
441
+
442
+ const pm = ExamplePM.getById(id, appObjects);
443
+ if (!pm)
444
+ {
445
+ appObjects.submitWarning("examplePmAdapter", "Unable to find ExamplePM");
446
+ return;
447
+ }
448
+
449
+ pm.addView(setVM);
450
+ },
451
+
452
+ unsubscribe: (id: string, appObjects: AppObjectRepo, setVM: (vm: string) => void) => {
453
+ ExamplePM.getById(id, appObjects)?.removeView(setVM);
454
+ }
455
+ };
456
+ ```
457
+
458
+ **exampleSingletonPmAdapter.ts** - Singleton adapter:
459
+
460
+ ```typescript
461
+ import { SingletonPmAdapter } from "@vived/core";
462
+ import { ExampleSingletonPM, ExampleVM, defaultExampleVM } from "../PMs/ExampleSingletonPM";
463
+
464
+ export const exampleSingletonPmAdapter: SingletonPmAdapter<ExampleVM> = {
465
+ defaultVM: defaultExampleVM,
466
+
467
+ subscribe: (appObjects: AppObjectRepo, setVM: (vm: ExampleVM) => void) => {
468
+ const pm = ExampleSingletonPM.get(appObjects);
469
+ if (!pm)
470
+ {
471
+ appObjects.submitWarning(
472
+ "exampleSingletonPmAdapter",
473
+ "Unable to find ExampleSingletonPM"
474
+ );
475
+ return;
476
+ }
477
+
478
+ pm.addView(setVM);
479
+ },
480
+
481
+ unsubscribe: (appObjects: AppObjectRepo, setVM: (vm: ExampleVM) => void) => {
482
+ ExampleSingletonPM.get(appObjects)?.removeView(setVM);
483
+ }
484
+ };
485
+ ```
486
+
487
+ **Key Concepts:**
488
+ - Adapters implement `PmAdapter<VM>` or `SingletonPmAdapter<VM>`
489
+ - They provide a `defaultVM` for initial rendering
490
+ - They handle subscription and unsubscription lifecycle
491
+
492
+ ### 6. Factory
493
+
494
+ The factory initializes all components in the correct order.
495
+
496
+ **ExampleFeatureFactory.ts**:
497
+
498
+ ```typescript
499
+ import { DomainFactory } from "@vived/core";
500
+
501
+ export class ExampleFeatureFactory extends DomainFactory
502
+ {
503
+ factoryName = "ExampleFeatureFactory";
504
+
505
+ setupEntities(): void
506
+ {
507
+ // Create singleton entities
508
+ makeSingletonEntityExample(this.appObject);
509
+ }
510
+
511
+ setupUCs(): void
512
+ {
513
+ // Create use cases
514
+ makeToggleExampleBooleanUC(this.appObject);
515
+ }
516
+
517
+ setupPMs(): void
518
+ {
519
+ // Create presentation managers
520
+ makeExampleSingletonPM(this.appObject);
521
+ }
522
+
523
+ finalSetup(): void
524
+ {
525
+ // Any final initialization
526
+ }
527
+ }
528
+
529
+ export function makeExampleFeatureFactory(appObjects: AppObjectRepo)
530
+ {
531
+ const appObject = appObjects.getOrCreate("ExampleFeature");
532
+ return new ExampleFeatureFactory(appObject);
533
+ }
534
+ ```
535
+
536
+ **Key Concepts:**
537
+ - Factories initialize components in order: Entities → UCs → PMs → Final
538
+ - This ensures dependencies are available when needed
539
+ - Each feature has its own factory
540
+
541
+ ## React Integration Example
542
+
543
+ ### Using Instance-Based Components
544
+
545
+ ```typescript
546
+ import { useEffect, useState } from "react";
547
+ import { examplePmAdapter, setExampleText } from "../ExampleFeature";
548
+
549
+ function ExampleTextInput({ id, appObjects })
550
+ {
551
+ const [viewModel, setViewModel] = useState(examplePmAdapter.defaultVM);
552
+
553
+ useEffect(() => {
554
+ // Subscribe to PM updates
555
+ examplePmAdapter.subscribe(id, appObjects, setViewModel);
556
+
557
+ // Cleanup on unmount
558
+ return () => {
559
+ examplePmAdapter.unsubscribe(id, appObjects, setViewModel);
560
+ };
561
+ }, [id]);
562
+
563
+ const handleChange = (e) => {
564
+ setExampleText(e.target.value, id, appObjects);
565
+ };
566
+
567
+ return (
568
+ <input
569
+ type="text"
570
+ value={viewModel}
571
+ onChange={handleChange}
572
+ />
573
+ );
574
+ }
575
+ ```
576
+
577
+ ### Using Singleton Components
578
+
579
+ ```typescript
580
+ import { useEffect, useState } from "react";
581
+ import { exampleSingletonPmAdapter } from "../Adapters";
582
+ import { toggleExampleBoolean } fr, toggleExampleBoolean } from "../ExampleFeature
583
+ function ExampleToggle({ appObjects })
584
+ {
585
+ const [viewModel, setViewModel] = useState(
586
+ exampleSingletonPmAdapter.defaultVM
587
+ );
588
+
589
+ useEffect(() => {
590
+ // Subscribe to singleton PM updates (no ID needed)
591
+ exampleSingletonPmAdapter.subscribe(appObjects, setViewModel);
592
+
593
+ // Cleanup on unmount
594
+ return () => {
595
+ exampleSingletonPmAdapter.unsubscribe(appObjects, setViewModel);
596
+ };
597
+ }, []);
598
+
599
+ const handleToggle = () => {
600
+ toggleExampleBoolean(appObjects);
601
+ };
602
+
603
+ return (
604
+ <div>
605
+ <p>Boolean value: {viewModel.aBoolProperty ? "True" : "False"}</p>
606
+ <button onClick={handleToggle}>Toggle</button>
607
+ </div>
608
+ );
609
+ }
610
+ ```
611
+
612
+ ## Testing Examples
613
+
614
+ ### Testing Entities
615
+
616
+ ```typescript
617
+ describe("ExampleEntity", () => {
618
+ it("notifies observers when property changes", () => {
619
+ const appObject = appObjects.getOrCreate("test");
620
+ const entity = makeExampleEntity(appObject);
621
+ const observer = jest.fn();
622
+
623
+ entity.addObserver(observer);
624
+ entity.aStringProperty = "new value";
625
+
626
+ expect(observer).toHaveBeenCalled();
627
+ });
628
+ });
629
+ ```
630
+
631
+ ### Testing Use Cases
632
+
633
+ ```typescript
634
+ describe("EditExampleStringUC", () => {
635
+ it("updates entity string property", () => {
636
+ const appObject = appObjects.getOrCreate("test");
637
+ const entity = makeExampleEntity(appObject);
638
+ const uc = makeEditSlideTextUC(appObject);
639
+
640
+ uc.editExampleString("test text");
641
+
642
+ expect(entity.aStringProperty).toBe("test text");
643
+ });
644
+ });
645
+ ```
646
+
647
+ ### Testing Presentation Managers
648
+
649
+ ```typescript
650
+ describe("ExamplePM", () => {
651
+ it("provides updated view model when entity changes", () => {
652
+ const appObject = appObjects.getOrCreate("test");
653
+ const entity = makeExampleEntity(appObject);
654
+ const pm = makeExamplePM(appObject);
655
+ const viewCallback = jest.fn();
656
+
657
+ pm.addView(viewCallback);
658
+ entity.aStringProperty = "new value";
659
+
660
+ expect(viewCallback).toHaveBeenCalledWith("new value");
661
+ });
662
+ });
663
+ ```
664
+
665
+ ## Design Patterns Used
666
+
667
+ ### 1. Clean Architecture
668
+ - Dependency inversion: inner layers don't depend on outer layers
669
+ - Separation of concerns: each layer has a specific responsibility
670
+
671
+ ### 2. Observer Pattern
672
+ - Entities notify observers when they change
673
+ - PMs observe entities and update views
674
+ - UI components observe PMs and update themselves
675
+
676
+ ### 3. Repository Pattern
677
+ - Repositories manage collections of entities
678
+ - Provide creation, retrieval, and deletion operations
679
+
680
+ ### 4. Factory Pattern
681
+ - Factories create and initialize domain components
682
+ - Ensure proper setup order and dependencies
683
+
684
+ ### 5. Adapter Pattern
685
+ - Adapters connect UI frameworks to the application
686
+ - Provide framework-agnostic integration
687
+
688
+ ## Best Practices
689
+
690
+ ### When to Use Singleton vs Instance
691
+
692
+ **Use Singleton When:**
693
+ - Only one instance needed globally (e.g., app settings, user session)
694
+ - Accessed from multiple unrelated parts of the app
695
+ - Represents global application state
696
+
697
+ **Use Instance When:**
698
+ - Multiple instances needed (e.g., multiple items in a list)
699
+ - Each instance has its own data and lifecycle
700
+ - Represents domain objects with identity
701
+
702
+ ### Component Organization
703
+
704
+ ```typescript
705
+ // ✅ Good: Abstract class + Factory + Private implementation
706
+ export abstract class MyEntity extends AppObjectEntity { }
707
+ export function makeMyEntity(appObject: AppObject): MyEntity { }
708
+ class MyEntityImp extends MyEntity { }
709
+
710
+ // ❌ Bad: Public implementation class
711
+ export class MyEntity extends AppObjectEntity { }
712
+ ```
713
+
714
+ ### Dependency Access
715
+
716
+ ```typescript
717
+ // ✅ Good: Use cached getters
718
+ private get myEntity()
719
+ {
720
+ return this.getCachedLocalComponent<MyEntity>(MyEntity.type);
721
+ }
722
+
723
+ // ❌ Bad: Repeated lookups
724
+ myMethod()
725
+ {
726
+ const entity = this.appObject.getComponent<MyEntity>(MyEntity.type);
727
+ }
728
+ ```
729
+
730
+ ### Error Handling
731
+
732
+ ```typescript
733
+ // ✅ Good: Graceful error handling with warnings
734
+ if (!uc)
735
+ {
736
+ appObjects.submitWarning("controllerName", "UC not found");
737
+ return;
738
+ }
739
+
740
+ // ❌ Bad: Throwing errors or failing silently
741
+ uc.doSomething(); // Crashes if uc is undefined
742
+ ```
743
+
744
+ ## Common Pitfalls to Avoid
745
+
746
+ 1. **Violating dependency rules** - Don't make entities depend on UCs or PMs
747
+ 2. **Forgetting to observe entities** - PMs won't update if they don't observe entities
748
+ 3. **Not implementing vmsAreEqual()** - Can cause unnecessary UI re-renders
749
+ 4. **Memory leaks** - Always unsubscribe views when components unmount
750
+ 5. **Missing error handling** - Always check if components exist before using them
751
+
752
+ ## Barrel File (index.ts)
753
+
754
+ The ExampleFeature includes a root barrel file that exports all controllers and adapters. This provides a single import point and clear public API:
755
+
756
+ **ExampleFeature/index.ts:**
757
+ ```typescript
758
+ // Controllers
759
+ export * from "./Controllers/setExampleText";
760
+ export * from "./Controllers/toggleExampleBoolean";
761
+
762
+ // Adapters
763
+ export * from "./Adapters/examplePmAdapter";
764
+ export * from "./Adapters/exampleSingletonPmAdapter";
765
+ ```
766
+
767
+ **Benefits:**
768
+ - Single import statement for all feature exports
769
+ - Clear public API surface for the entire feature
770
+ - Easier refactoring (internal structure can change without affecting consumers)
771
+ - Better code organization and encapsulation
772
+
773
+ **Usage:**
774
+ ```typescript
775
+ // ✅ Good: Import from feature barrel file
776
+ import {
777
+ setExampleText,
778
+ toggleExampleBoolean,
779
+ examplePmAdapter,
780
+ exampleSingletonPmAdapter
781
+ } from "../ExampleFeature";
782
+
783
+ // ❌ Avoid: Direct file imports
784
+ import { setExampleText } from "../ExampleFeature/Controllers/setExampleText";
785
+ import { examplePmAdapter } from "../ExampleFeature/Adapters/examplePmAdapter";
786
+ ```
787
+
788
+ ## Creating Your Own Feature
789
+
790
+ Follow these steps to create a new feature based on this example:
791
+
792
+ 1. **Create folder structure** matching ExampleFeature
793
+ 2. **Define entities** representing your domain data
794
+ 3. **Create repositories** if managing collections
795
+ 4. **Implement use cases** for business operations
796
+ 5. **Build presentation managers** to transform data for UI
797
+ 6. **Add controllers** to simplify the API
798
+ 7. **Create adapters** for UI framework integration
799
+ 8. **Add root barrel file** (`index.ts`) exporting all controllers and adapters
800
+ 9. **Write factory** to initialize everything
801
+ 10. **Add tests** for all components
802
+ 11. **Integrate** into your application via DomainFactoryRepo
803
+
804
+ This example provides a complete, production-ready pattern for building scalable, maintainable features using Clean Architecture principles.