@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.
- package/README.md +275 -65
- package/dist/cjs/ExampleFeature/index.js +23 -0
- package/dist/cjs/ExampleFeature/index.js.map +1 -0
- package/dist/esm/ExampleFeature/index.js +7 -0
- package/dist/esm/ExampleFeature/index.js.map +1 -0
- package/dist/types/ExampleFeature/index.d.ts +5 -0
- package/dist/types/ExampleFeature/index.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/AppObject/README.md +476 -0
- package/src/DomainFactories/README.md +154 -0
- package/src/Entities/README.md +340 -0
- package/src/ExampleFeature/README.md +804 -0
- package/src/Types/README.md +549 -0
- package/src/Utilities/README.md +478 -0
- package/src/ValueObjects/README.md +552 -0
|
@@ -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
|