@vived/core 2.0.0 → 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,154 @@
1
+ # DomainFactories
2
+
3
+ The DomainFactories feature provides a structured approach for organizing and initializing application domains. It enforces a multi-phase setup sequence that ensures dependencies are properly resolved across different architectural layers.
4
+
5
+ ## Overview
6
+
7
+ A domain factory is responsible for setting up all components within a specific domain or feature of your application. The setup is orchestrated through a four-phase process:
8
+
9
+ 1. **Entities** - Data models and repositories
10
+ 2. **Use Cases (UCs)** - Business logic that operates on entities
11
+ 3. **Presentation Managers (PMs)** - View models that transform entity data
12
+ 4. **Final Setup** - Any remaining initialization after all components are ready
13
+
14
+ This phased approach ensures that components can depend on earlier phases being complete, both within a domain and across different domains.
15
+
16
+ ## Core Components
17
+
18
+ ### DomainFactory
19
+
20
+ Abstract base class that all domain factories must extend. Each concrete factory must implement four setup methods:
21
+
22
+ - `setupEntities()` - Initialize data models and repositories
23
+ - `setupUCs()` - Initialize business logic components
24
+ - `setupPMs()` - Initialize presentation layer components
25
+ - `finalSetup()` - Perform final initialization
26
+
27
+ Domain factories automatically register themselves with the `DomainFactoryRepo` upon construction.
28
+
29
+ ### DomainFactoryRepo
30
+
31
+ Singleton repository that manages all domain factories and orchestrates their initialization. It provides:
32
+
33
+ - **Phase coordination** - Executes setup phases across all factories in the correct order
34
+ - **Factory lookup** - Retrieve factories by name using `getByName()`
35
+ - **Global access** - Singleton accessible via `DomainFactoryRepo.get(appObjects)`
36
+
37
+ ## Usage
38
+
39
+ ### Creating a Domain Factory
40
+
41
+ ```typescript
42
+ import { DomainFactory } from "@vived/core";
43
+ import { AppObject } from "@vived/core";
44
+
45
+ export class MyFeatureDomainFactory extends DomainFactory
46
+ {
47
+ readonly factoryName = "MyFeatureDomainFactory";
48
+
49
+ setupEntities(): void
50
+ {
51
+ // Create and configure entities and repositories
52
+ // Example: new MyEntityRepo(this.appObjects.getOrCreate("MyEntityRepo"));
53
+ }
54
+
55
+ setupUCs(): void
56
+ {
57
+ // Create and configure use cases
58
+ // Example: new MyUseCase(this.appObjects.getOrCreate("MyUseCase"));
59
+ }
60
+
61
+ setupPMs(): void
62
+ {
63
+ // Create and configure presentation managers
64
+ // Example: new MyPM(this.appObjects.getOrCreate("MyPM"));
65
+ }
66
+
67
+ finalSetup(): void
68
+ {
69
+ // Perform any final initialization
70
+ // Example: Subscribe to events, configure cross-domain dependencies
71
+ }
72
+
73
+ constructor(appObject: AppObject)
74
+ {
75
+ super(appObject);
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Initializing the Domain Layer
81
+
82
+ ```typescript
83
+ import { makeAppObjectRepo, makeDomainFactoryRepo } from "@vived/core";
84
+ import { MyFeatureDomainFactory } from "./MyFeatureDomainFactory";
85
+ import { AnotherFeatureDomainFactory } from "./AnotherFeatureDomainFactory";
86
+
87
+ // Create the application object repository
88
+ const appObjects = makeAppObjectRepo();
89
+
90
+ // Create the domain factory repository
91
+ const domainFactoryRepo = makeDomainFactoryRepo(appObjects);
92
+
93
+ // Instantiate your domain factories (they auto-register)
94
+ new MyFeatureDomainFactory(appObjects.getOrCreate("MyFeatureDomainFactory"));
95
+ new AnotherFeatureDomainFactory(appObjects.getOrCreate("AnotherFeatureDomainFactory"));
96
+
97
+ // Execute the complete domain setup in proper sequence
98
+ domainFactoryRepo.setupDomain();
99
+ ```
100
+
101
+ ### Accessing a Domain Factory
102
+
103
+ ```typescript
104
+ // Retrieve a specific domain factory by name
105
+ const myFactory = domainFactoryRepo.getByName("MyFeatureDomainFactory");
106
+
107
+ // Access the singleton repo from anywhere
108
+ const repo = DomainFactoryRepo.get(appObjects);
109
+ ```
110
+
111
+ ## Setup Sequence
112
+
113
+ When `setupDomain()` is called, the repository executes phases in this order:
114
+
115
+ 1. All factories execute `setupEntities()`
116
+ 2. All factories execute `setupUCs()`
117
+ 3. All factories execute `setupPMs()`
118
+ 4. All factories execute `finalSetup()`
119
+
120
+ This ensures that if Factory B's use cases depend on Factory A's entities, those entities will already be initialized.
121
+
122
+ ## Testing
123
+
124
+ The `MockDomainFactory` class provides a test double with Jest mock functions for all setup methods:
125
+
126
+ ```typescript
127
+ import { MockDomainFactory } from "@vived/core";
128
+ import { makeAppObjectRepo } from "@vived/core";
129
+
130
+ const appObjects = makeAppObjectRepo();
131
+ const mockFactory = new MockDomainFactory(appObjects.getOrCreate("test"));
132
+
133
+ // Verify setup methods are called
134
+ expect(mockFactory.setupEntities).toHaveBeenCalled();
135
+
136
+ // Check call order
137
+ expect(mockFactory.setupEntities).toHaveBeenCalledBefore(mockFactory.setupUCs);
138
+ ```
139
+
140
+ ## Best Practices
141
+
142
+ - **Keep factories focused** - Each factory should manage a single domain or feature
143
+ - **Use meaningful names** - Set `factoryName` to something descriptive for easy retrieval
144
+ - **Respect phase boundaries** - Don't access components from later phases during earlier phases
145
+ - **Avoid cross-phase dependencies** - Entities shouldn't depend on UCs, UCs shouldn't depend on PMs
146
+ - **Use finalSetup sparingly** - Most initialization should happen in the appropriate phase
147
+
148
+ ## Architecture Benefits
149
+
150
+ - **Dependency management** - Clear initialization order prevents "component not found" errors
151
+ - **Modularity** - Each domain is self-contained in its own factory
152
+ - **Testability** - Mock factories enable isolated testing of the orchestration logic
153
+ - **Scalability** - Easy to add new domains without modifying existing code
154
+ - **Maintainability** - Clear structure makes it obvious where components should be initialized
@@ -0,0 +1,340 @@
1
+ # Entities
2
+
3
+ The Entities folder provides foundational classes for building reactive, observable data models. It includes the Observer pattern implementation and a suite of memoized wrapper classes for various data types.
4
+
5
+ ## Overview
6
+
7
+ This module solves two key problems in reactive application development:
8
+
9
+ 1. **Change Notification** - Notify observers when data changes (Observer pattern)
10
+ 2. **Performance Optimization** - Only trigger callbacks when values actually change (memoization)
11
+
12
+ ## Core Components
13
+
14
+ ### Observer Pattern
15
+
16
+ #### ObservableEntity
17
+
18
+ Abstract base class implementing the Observer pattern. Extend this to make any entity observable.
19
+
20
+ ```typescript
21
+ import { ObservableEntity } from "@vived/core";
22
+
23
+ export class MyEntity extends ObservableEntity
24
+ {
25
+ private _name: string = "";
26
+
27
+ get name(): string
28
+ {
29
+ return this._name;
30
+ }
31
+
32
+ set name(value: string)
33
+ {
34
+ this._name = value;
35
+ this.notify(); // Notify all observers
36
+ }
37
+ }
38
+
39
+ // Usage
40
+ const entity = new MyEntity();
41
+ entity.addObserver(() => console.log("Entity changed!"));
42
+ entity.name = "New Name"; // Logs: "Entity changed!"
43
+ ```
44
+
45
+ #### ObserverList
46
+
47
+ Generic implementation of the Observer pattern with typed messages. Use this when you need to pass data to observers.
48
+
49
+ ```typescript
50
+ import { ObserverList } from "@vived/core";
51
+
52
+ const observers = new ObserverList<string>();
53
+
54
+ observers.add((message) => console.log(`Received: ${message}`));
55
+ observers.notify("Hello World"); // Logs: "Received: Hello World"
56
+
57
+ // Check observer count
58
+ console.log(observers.length); // 1
59
+
60
+ // Clear all observers
61
+ observers.clear();
62
+ ```
63
+
64
+ ### Memoized Wrappers
65
+
66
+ Memoized classes wrap values and only trigger callbacks when the value actually changes. This prevents unnecessary re-renders and computations.
67
+
68
+ #### MemoizedBoolean
69
+
70
+ Wrapper for boolean values with change detection.
71
+
72
+ ```typescript
73
+ import { MemoizedBoolean } from "@vived/core";
74
+
75
+ const isVisible = new MemoizedBoolean(
76
+ false,
77
+ () => console.log("Visibility changed")
78
+ );
79
+
80
+ isVisible.val = true; // Logs: "Visibility changed"
81
+ isVisible.val = true; // No callback - value didn't change
82
+ isVisible.val = false; // Logs: "Visibility changed"
83
+
84
+ // Set without triggering callback
85
+ isVisible.setValQuietly(true);
86
+ ```
87
+
88
+ #### MemoizedNumber
89
+
90
+ Wrapper for numeric values with change detection.
91
+
92
+ ```typescript
93
+ import { MemoizedNumber } from "@vived/core";
94
+
95
+ const count = new MemoizedNumber(
96
+ 0,
97
+ () => console.log("Count changed")
98
+ );
99
+
100
+ count.val = 5; // Logs: "Count changed"
101
+ count.val = 5; // No callback - value didn't change
102
+ ```
103
+
104
+ #### MemoizedString
105
+
106
+ Wrapper for string values with change detection.
107
+
108
+ ```typescript
109
+ import { MemoizedString } from "@vived/core";
110
+
111
+ const title = new MemoizedString(
112
+ "Untitled",
113
+ () => console.log("Title changed")
114
+ );
115
+
116
+ title.val = "New Title"; // Logs: "Title changed"
117
+ title.val = "New Title"; // No callback - value didn't change
118
+ ```
119
+
120
+ #### MemoizedAngle
121
+
122
+ Wrapper for Angle value objects with change detection.
123
+
124
+ ```typescript
125
+ import { MemoizedAngle, Angle } from "@vived/core";
126
+
127
+ const rotation = new MemoizedAngle(
128
+ Angle.FromDegrees(0),
129
+ () => console.log("Rotation changed")
130
+ );
131
+
132
+ rotation.val = Angle.FromDegrees(45);
133
+ ```
134
+
135
+ #### MemoizedVector2
136
+
137
+ Wrapper for 2D vector value objects with change detection.
138
+
139
+ ```typescript
140
+ import { MemoizedVector2, Vector2 } from "@vived/core";
141
+
142
+ const position = new MemoizedVector2(
143
+ Vector2.Zero(),
144
+ () => console.log("Position changed")
145
+ );
146
+
147
+ position.val = new Vector2(10, 20);
148
+ ```
149
+
150
+ #### MemoizedVector3
151
+
152
+ Wrapper for 3D vector value objects with change detection.
153
+
154
+ ```typescript
155
+ import { MemoizedVector3, Vector3 } from "@vived/core";
156
+
157
+ const position = new MemoizedVector3(
158
+ Vector3.Zero(),
159
+ () => console.log("Position changed")
160
+ );
161
+
162
+ position.val = new Vector3(10, 20, 30);
163
+ ```
164
+
165
+ #### MemoizedQuaternion
166
+
167
+ Wrapper for quaternion rotation value objects with change detection.
168
+
169
+ ```typescript
170
+ import { MemoizedQuaternion, Quaternion } from "@vived/core";
171
+
172
+ const rotation = new MemoizedQuaternion(
173
+ Quaternion.Identity(),
174
+ () => console.log("Rotation changed")
175
+ );
176
+
177
+ rotation.val = Quaternion.FromEuler(0, Math.PI / 2, 0);
178
+ ```
179
+
180
+ #### MemoizedColor
181
+
182
+ Wrapper for color value objects with change detection.
183
+
184
+ ```typescript
185
+ import { MemoizedColor, Color } from "@vived/core";
186
+
187
+ const backgroundColor = new MemoizedColor(
188
+ Color.White(),
189
+ () => console.log("Color changed")
190
+ );
191
+
192
+ backgroundColor.val = Color.FromRGB(255, 0, 0);
193
+ ```
194
+
195
+ ### Range-Constrained Values
196
+
197
+ #### RangedNumber
198
+
199
+ Numeric value that is automatically clamped to a specified range.
200
+
201
+ ```typescript
202
+ import { RangedNumber } from "@vived/core";
203
+
204
+ const volume = new RangedNumber(
205
+ {
206
+ min: 0,
207
+ max: 100,
208
+ initialValue: 50
209
+ },
210
+ () => console.log("Volume changed")
211
+ );
212
+
213
+ volume.val = 75; // Sets to 75
214
+ volume.val = 150; // Clamped to 100
215
+ volume.val = -10; // Clamped to 0
216
+
217
+ console.log(volume.minValue); // 0
218
+ console.log(volume.maxValue); // 100
219
+ ```
220
+
221
+ ## Common Patterns
222
+
223
+ ### Building Reactive Entities
224
+
225
+ Combine memoized values within an observable entity:
226
+
227
+ ```typescript
228
+ import { ObservableEntity, MemoizedString, MemoizedNumber } from "@vived/core";
229
+
230
+ export class User extends ObservableEntity
231
+ {
232
+ readonly name: MemoizedString;
233
+ readonly age: MemoizedNumber;
234
+
235
+ constructor(name: string, age: number)
236
+ {
237
+ super();
238
+ this.name = new MemoizedString(name, () => this.notify());
239
+ this.age = new MemoizedNumber(age, () => this.notify());
240
+ }
241
+ }
242
+
243
+ // Usage
244
+ const user = new User("John", 30);
245
+ user.addObserver(() => console.log("User updated"));
246
+
247
+ user.name.val = "Jane"; // Triggers observer
248
+ user.age.val = 31; // Triggers observer
249
+ ```
250
+
251
+ ### Silent Updates
252
+
253
+ Use `setValQuietly()` when you need to update values without triggering side effects:
254
+
255
+ ```typescript
256
+ const count = new MemoizedNumber(0, () => updateUI());
257
+
258
+ // Update without triggering UI update
259
+ count.setValQuietly(10);
260
+ ```
261
+
262
+ ### Managing Observers
263
+
264
+ ```typescript
265
+ const entity = new MyEntity();
266
+
267
+ // Add observer
268
+ const observer = () => console.log("Changed");
269
+ entity.addObserver(observer);
270
+
271
+ // Remove specific observer
272
+ entity.removeObserver(observer);
273
+
274
+ // Using ObserverList directly
275
+ const observers = new ObserverList<void>();
276
+ observers.add(observer);
277
+ observers.remove(observer);
278
+ observers.clear(); // Remove all
279
+ ```
280
+
281
+ ## Design Principles
282
+
283
+ ### Memoization
284
+
285
+ All memoized classes compare values before triggering callbacks:
286
+ - **Primitives** (boolean, number, string) use strict equality (`===`)
287
+ - **Value Objects** (Vector, Color, etc.) use type-specific equality methods
288
+ - This prevents unnecessary callback execution and improves performance
289
+
290
+ ### Immutability Considerations
291
+
292
+ Value objects (Vector3, Color, etc.) should be treated as immutable. Always create new instances rather than mutating existing ones:
293
+
294
+ ```typescript
295
+ // Good
296
+ position.val = new Vector3(position.val.x + 1, position.val.y, position.val.z);
297
+
298
+ // Bad - mutation won't trigger change detection
299
+ position.val.x += 1;
300
+ ```
301
+
302
+ ## Testing
303
+
304
+ All memoized and observable classes include comprehensive test coverage demonstrating:
305
+ - Initial value storage
306
+ - Change detection
307
+ - Callback triggering
308
+ - Silent updates
309
+ - Range clamping (for RangedNumber)
310
+
311
+ Example test pattern:
312
+
313
+ ```typescript
314
+ it("only triggers callback when value changes", () => {
315
+ const callback = jest.fn();
316
+ const memoized = new MemoizedNumber(5, callback);
317
+
318
+ memoized.val = 5; // No change
319
+ expect(callback).not.toBeCalled();
320
+
321
+ memoized.val = 10; // Changed
322
+ expect(callback).toBeCalledTimes(1);
323
+ });
324
+ ```
325
+
326
+ ## Use Cases
327
+
328
+ - **State Management** - Track application state with automatic change notifications
329
+ - **View Models** - Build presentation models that notify views when data changes
330
+ - **Form Inputs** - Validate and constrain user input with RangedNumber
331
+ - **Animation** - Track animated values and trigger updates only when they change
332
+ - **Configuration** - Manage settings with type-safe, observable properties
333
+ - **Game Objects** - Track entity properties (position, rotation, color) with change detection
334
+
335
+ ## Performance Benefits
336
+
337
+ - **Reduced Re-renders** - UI updates only when values actually change
338
+ - **Efficient Validation** - Range constraints applied automatically
339
+ - **Lazy Computation** - Callbacks only fire when necessary
340
+ - **Memory Efficient** - Lightweight wrappers with minimal overhead