@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.
- package/README.md +275 -65
- package/dist/cjs/AppObject/AppObjectEntityRepo.js +16 -2
- package/dist/cjs/AppObject/AppObjectEntityRepo.js.map +1 -1
- package/dist/cjs/ExampleFeature/Entities/ExampleRepo.js +3 -4
- package/dist/cjs/ExampleFeature/Entities/ExampleRepo.js.map +1 -1
- package/dist/cjs/ExampleFeature/index.js +23 -0
- package/dist/cjs/ExampleFeature/index.js.map +1 -0
- package/dist/esm/AppObject/AppObjectEntityRepo.js +16 -2
- package/dist/esm/AppObject/AppObjectEntityRepo.js.map +1 -1
- package/dist/esm/ExampleFeature/Entities/ExampleRepo.js +3 -4
- package/dist/esm/ExampleFeature/Entities/ExampleRepo.js.map +1 -1
- package/dist/esm/ExampleFeature/index.js +7 -0
- package/dist/esm/ExampleFeature/index.js.map +1 -0
- package/dist/types/AppObject/AppObjectEntityRepo.d.ts +9 -1
- package/dist/types/AppObject/AppObjectEntityRepo.d.ts.map +1 -1
- package/dist/types/ExampleFeature/Entities/ExampleRepo.d.ts +1 -1
- package/dist/types/ExampleFeature/Entities/ExampleRepo.d.ts.map +1 -1
- 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,549 @@
|
|
|
1
|
+
# Types
|
|
2
|
+
|
|
3
|
+
The Types folder provides TypeScript type definitions and interfaces that establish contracts for key architectural patterns in the application. These types enable type-safe communication between different layers of the architecture.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This module contains:
|
|
8
|
+
- **Function Types** - Type aliases for common function signatures
|
|
9
|
+
- **Adapter Interfaces** - Contracts for connecting UI components to presentation managers
|
|
10
|
+
- **Application Boundaries** - Types defining external application interfaces
|
|
11
|
+
|
|
12
|
+
## Function Types
|
|
13
|
+
|
|
14
|
+
### EaseFn
|
|
15
|
+
|
|
16
|
+
Type definition for easing functions used in animations.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { EaseFn } from "@vived/core";
|
|
20
|
+
|
|
21
|
+
type EaseFn = (p: number) => number;
|
|
22
|
+
|
|
23
|
+
// Implementation example
|
|
24
|
+
const customEase: EaseFn = (p: number) => {
|
|
25
|
+
// Transform progress value (0-1)
|
|
26
|
+
return p * p; // Quadratic easing
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Usage with interpolation
|
|
30
|
+
function animate(start: number, end: number, progress: number, ease: EaseFn): number
|
|
31
|
+
{
|
|
32
|
+
const easedProgress = ease(progress);
|
|
33
|
+
return start + (end - start) * easedProgress;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// All built-in easing functions match this type
|
|
37
|
+
import { quadInOut, expoOut } from "@vived/core";
|
|
38
|
+
const ease1: EaseFn = quadInOut;
|
|
39
|
+
const ease2: EaseFn = expoOut;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Contract:**
|
|
43
|
+
- Input: Number from 0 to 1 representing linear progress
|
|
44
|
+
- Output: Number from 0 to 1 representing eased progress
|
|
45
|
+
- Should handle edge cases (values < 0 or > 1)
|
|
46
|
+
|
|
47
|
+
## Presentation Manager Adapters
|
|
48
|
+
|
|
49
|
+
Adapters bridge the gap between UI frameworks (React, Vue, Angular, etc.) and the application's presentation layer. They provide a framework-agnostic way to subscribe to view model updates.
|
|
50
|
+
|
|
51
|
+
### PmAdapter
|
|
52
|
+
|
|
53
|
+
Interface for connecting UI components to AppObject-specific presentation managers.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { PmAdapter, AppObjectRepo } from "@vived/core";
|
|
57
|
+
|
|
58
|
+
// Define your view model type
|
|
59
|
+
interface CounterViewModel
|
|
60
|
+
{
|
|
61
|
+
count: number;
|
|
62
|
+
label: string;
|
|
63
|
+
isEven: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Implement the adapter
|
|
67
|
+
class CounterPmAdapter implements PmAdapter<CounterViewModel>
|
|
68
|
+
{
|
|
69
|
+
// Default view model prevents null/undefined in UI
|
|
70
|
+
defaultVM: CounterViewModel = {
|
|
71
|
+
count: 0,
|
|
72
|
+
label: "Counter",
|
|
73
|
+
isEven: true
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
subscribe(
|
|
77
|
+
id: string,
|
|
78
|
+
appObjects: AppObjectRepo,
|
|
79
|
+
setVM: (vm: CounterViewModel) => void
|
|
80
|
+
): void
|
|
81
|
+
{
|
|
82
|
+
// Get the specific AppObject
|
|
83
|
+
const appObject = appObjects.get(id);
|
|
84
|
+
if (!appObject) return;
|
|
85
|
+
|
|
86
|
+
// Get its presentation manager
|
|
87
|
+
const pm = appObject.getComponent<CounterPM>("CounterPM");
|
|
88
|
+
if (!pm) return;
|
|
89
|
+
|
|
90
|
+
// Subscribe to updates
|
|
91
|
+
pm.addView(setVM);
|
|
92
|
+
|
|
93
|
+
// Provide initial value if available
|
|
94
|
+
if (pm.lastVM)
|
|
95
|
+
{
|
|
96
|
+
setVM(pm.lastVM);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
unsubscribe(
|
|
101
|
+
id: string,
|
|
102
|
+
appObjects: AppObjectRepo,
|
|
103
|
+
setVM: (vm: CounterViewModel) => void
|
|
104
|
+
): void
|
|
105
|
+
{
|
|
106
|
+
const appObject = appObjects.get(id);
|
|
107
|
+
if (!appObject) return;
|
|
108
|
+
|
|
109
|
+
const pm = appObject.getComponent<CounterPM>("CounterPM");
|
|
110
|
+
if (pm)
|
|
111
|
+
{
|
|
112
|
+
pm.removeView(setVM);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Usage in React:**
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { useEffect, useState } from "react";
|
|
122
|
+
|
|
123
|
+
function CounterComponent({ counterId, appObjects }: Props)
|
|
124
|
+
{
|
|
125
|
+
const adapter = new CounterPmAdapter();
|
|
126
|
+
const [vm, setVM] = useState(adapter.defaultVM);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
// Subscribe when component mounts
|
|
130
|
+
adapter.subscribe(counterId, appObjects, setVM);
|
|
131
|
+
|
|
132
|
+
// Unsubscribe when component unmounts
|
|
133
|
+
return () => {
|
|
134
|
+
adapter.unsubscribe(counterId, appObjects, setVM);
|
|
135
|
+
};
|
|
136
|
+
}, [counterId]);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
<h2>{vm.label}</h2>
|
|
141
|
+
<p>Count: {vm.count}</p>
|
|
142
|
+
<p>{vm.isEven ? "Even" : "Odd"}</p>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**When to use:**
|
|
149
|
+
- Multiple instances of the same UI component
|
|
150
|
+
- Each component subscribes to a different AppObject
|
|
151
|
+
- Dynamic component creation with different data sources
|
|
152
|
+
|
|
153
|
+
### SingletonPmAdapter
|
|
154
|
+
|
|
155
|
+
Interface for connecting UI components to singleton presentation managers (only one instance in the application).
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { SingletonPmAdapter, AppObjectRepo } from "@vived/core";
|
|
159
|
+
|
|
160
|
+
// Define your view model type
|
|
161
|
+
interface GlobalSettingsViewModel
|
|
162
|
+
{
|
|
163
|
+
theme: "light" | "dark";
|
|
164
|
+
fontSize: "small" | "medium" | "large";
|
|
165
|
+
language: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Implement the singleton adapter
|
|
169
|
+
class SettingsPmAdapter implements SingletonPmAdapter<GlobalSettingsViewModel>
|
|
170
|
+
{
|
|
171
|
+
defaultVM: GlobalSettingsViewModel = {
|
|
172
|
+
theme: "light",
|
|
173
|
+
fontSize: "medium",
|
|
174
|
+
language: "en"
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
subscribe(
|
|
178
|
+
appObjects: AppObjectRepo,
|
|
179
|
+
setVM: (vm: GlobalSettingsViewModel) => void
|
|
180
|
+
): void
|
|
181
|
+
{
|
|
182
|
+
// Get the singleton presentation manager
|
|
183
|
+
const settingsPM = SettingsPM.get(appObjects);
|
|
184
|
+
if (!settingsPM) return;
|
|
185
|
+
|
|
186
|
+
// Subscribe to updates
|
|
187
|
+
settingsPM.addView(setVM);
|
|
188
|
+
|
|
189
|
+
// Provide initial value
|
|
190
|
+
if (settingsPM.lastVM)
|
|
191
|
+
{
|
|
192
|
+
setVM(settingsPM.lastVM);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
unsubscribe(
|
|
197
|
+
appObjects: AppObjectRepo,
|
|
198
|
+
setVM: (vm: GlobalSettingsViewModel) => void
|
|
199
|
+
): void
|
|
200
|
+
{
|
|
201
|
+
const settingsPM = SettingsPM.get(appObjects);
|
|
202
|
+
if (settingsPM)
|
|
203
|
+
{
|
|
204
|
+
settingsPM.removeView(setVM);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Usage in React:**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { useEffect, useState } from "react";
|
|
214
|
+
|
|
215
|
+
function SettingsPanel({ appObjects }: Props)
|
|
216
|
+
{
|
|
217
|
+
const adapter = new SettingsPmAdapter();
|
|
218
|
+
const [vm, setVM] = useState(adapter.defaultVM);
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
// No ID needed - singleton PM
|
|
222
|
+
adapter.subscribe(appObjects, setVM);
|
|
223
|
+
|
|
224
|
+
return () => {
|
|
225
|
+
adapter.unsubscribe(appObjects, setVM);
|
|
226
|
+
};
|
|
227
|
+
}, []); // Empty deps - singleton never changes
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<div className={vm.theme}>
|
|
231
|
+
<select value={vm.fontSize}>
|
|
232
|
+
<option value="small">Small</option>
|
|
233
|
+
<option value="medium">Medium</option>
|
|
234
|
+
<option value="large">Large</option>
|
|
235
|
+
</select>
|
|
236
|
+
<select value={vm.language}>
|
|
237
|
+
<option value="en">English</option>
|
|
238
|
+
<option value="es">Spanish</option>
|
|
239
|
+
</select>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**When to use:**
|
|
246
|
+
- Global application state
|
|
247
|
+
- Settings or configuration that's app-wide
|
|
248
|
+
- Single-instance services (authentication, notifications, etc.)
|
|
249
|
+
|
|
250
|
+
**Key difference from PmAdapter:**
|
|
251
|
+
- No `id` parameter needed
|
|
252
|
+
- Subscribe directly to singleton PM
|
|
253
|
+
- Simpler API for single-instance scenarios
|
|
254
|
+
|
|
255
|
+
## Application Boundary Types
|
|
256
|
+
|
|
257
|
+
### AppBoundary Types
|
|
258
|
+
|
|
259
|
+
Types defining the contract between embedded applications and their host environments.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { Handler, Request, VIVEDApp_3 } from "@vived/core";
|
|
263
|
+
|
|
264
|
+
// Function that handles requests
|
|
265
|
+
type Handler = (request: Request) => void;
|
|
266
|
+
|
|
267
|
+
// Request structure for communication
|
|
268
|
+
interface Request
|
|
269
|
+
{
|
|
270
|
+
type: string; // Action identifier
|
|
271
|
+
version: number; // API version
|
|
272
|
+
payload?: unknown; // Optional data
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Application interface for embedding
|
|
276
|
+
interface VIVEDApp_3
|
|
277
|
+
{
|
|
278
|
+
// Mount app with bidirectional communication
|
|
279
|
+
mount(hostRequestHandler: Handler): Handler;
|
|
280
|
+
|
|
281
|
+
// Mount app in development mode
|
|
282
|
+
mountDev(container: HTMLElement): void;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Usage - Embedding an Application:**
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
class MyEmbeddedApp implements VIVEDApp_3
|
|
290
|
+
{
|
|
291
|
+
mount(hostRequestHandler: Handler): Handler
|
|
292
|
+
{
|
|
293
|
+
// Store host's handler for sending requests to host
|
|
294
|
+
this.hostHandler = hostRequestHandler;
|
|
295
|
+
|
|
296
|
+
// Return our handler for receiving requests from host
|
|
297
|
+
return (request: Request) => {
|
|
298
|
+
this.handleHostRequest(request);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
mountDev(container: HTMLElement): void
|
|
303
|
+
{
|
|
304
|
+
// Mount in standalone development mode
|
|
305
|
+
this.container = container;
|
|
306
|
+
this.initializeApp();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private handleHostRequest(request: Request): void
|
|
310
|
+
{
|
|
311
|
+
switch (request.type)
|
|
312
|
+
{
|
|
313
|
+
case "LOAD_DATA":
|
|
314
|
+
this.loadData(request.payload);
|
|
315
|
+
break;
|
|
316
|
+
case "UPDATE_SETTINGS":
|
|
317
|
+
this.updateSettings(request.payload);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private sendRequestToHost(type: string, payload?: unknown): void
|
|
323
|
+
{
|
|
324
|
+
if (this.hostHandler)
|
|
325
|
+
{
|
|
326
|
+
this.hostHandler({
|
|
327
|
+
type,
|
|
328
|
+
version: 3,
|
|
329
|
+
payload
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private hostHandler?: Handler;
|
|
335
|
+
private container?: HTMLElement;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Usage - Hosting an Application:**
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
function embedApplication(app: VIVEDApp_3)
|
|
343
|
+
{
|
|
344
|
+
// Create handler for app's requests
|
|
345
|
+
const hostHandler: Handler = (request: Request) => {
|
|
346
|
+
console.log(`App sent: ${request.type}`, request.payload);
|
|
347
|
+
|
|
348
|
+
switch (request.type)
|
|
349
|
+
{
|
|
350
|
+
case "READY":
|
|
351
|
+
console.log("App is ready");
|
|
352
|
+
break;
|
|
353
|
+
case "REQUEST_DATA":
|
|
354
|
+
sendDataToApp(request.payload);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Mount app and get its handler
|
|
360
|
+
const appHandler = app.mount(hostHandler);
|
|
361
|
+
|
|
362
|
+
// Send request to app
|
|
363
|
+
appHandler({
|
|
364
|
+
type: "INITIALIZE",
|
|
365
|
+
version: 3,
|
|
366
|
+
payload: { config: {...} }
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Development mode
|
|
371
|
+
function developApp(app: VIVEDApp_3)
|
|
372
|
+
{
|
|
373
|
+
const container = document.getElementById("app-root");
|
|
374
|
+
if (container)
|
|
375
|
+
{
|
|
376
|
+
app.mountDev(container);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Design Patterns
|
|
382
|
+
|
|
383
|
+
### Adapter Pattern
|
|
384
|
+
|
|
385
|
+
The PM adapters implement the Adapter pattern, allowing UI frameworks to work with the application's architecture without tight coupling.
|
|
386
|
+
|
|
387
|
+
**Benefits:**
|
|
388
|
+
- Framework independence
|
|
389
|
+
- Testable UI logic
|
|
390
|
+
- Consistent data flow
|
|
391
|
+
- Type safety
|
|
392
|
+
|
|
393
|
+
### Observer Pattern
|
|
394
|
+
|
|
395
|
+
Adapters facilitate the Observer pattern between presentation managers and UI components:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// PM notifies observers (UI components) when view model changes
|
|
399
|
+
class MyPM extends AppObjectPM<MyViewModel>
|
|
400
|
+
{
|
|
401
|
+
updateData(newData: Data): void
|
|
402
|
+
{
|
|
403
|
+
// Transform data to view model
|
|
404
|
+
const vm = this.createViewModel(newData);
|
|
405
|
+
|
|
406
|
+
// Notify all subscribed views
|
|
407
|
+
this.notifyViews(vm);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Adapter subscribes UI component as an observer
|
|
412
|
+
adapter.subscribe(id, appObjects, (vm) => {
|
|
413
|
+
// UI updates automatically when VM changes
|
|
414
|
+
updateUI(vm);
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Default View Model Pattern
|
|
419
|
+
|
|
420
|
+
The `defaultVM` property implements the Null Object pattern, providing a safe default state:
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// Instead of null checking
|
|
424
|
+
if (vm !== null)
|
|
425
|
+
{
|
|
426
|
+
render(vm.value);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Use default VM
|
|
430
|
+
const vm = adapter.defaultVM; // Always defined
|
|
431
|
+
render(vm.value); // Safe
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Best Practices
|
|
435
|
+
|
|
436
|
+
### Type Safety
|
|
437
|
+
|
|
438
|
+
Use generics to maintain type safety throughout the adapter chain:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
class TypeSafeAdapter implements PmAdapter<MyViewModel>
|
|
442
|
+
{
|
|
443
|
+
defaultVM: MyViewModel = { /* ... */ };
|
|
444
|
+
|
|
445
|
+
subscribe(
|
|
446
|
+
id: string,
|
|
447
|
+
appObjects: AppObjectRepo,
|
|
448
|
+
setVM: (vm: MyViewModel) => void // Type-safe callback
|
|
449
|
+
): void { /* ... */ }
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Memory Management
|
|
454
|
+
|
|
455
|
+
Always unsubscribe to prevent memory leaks:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
adapter.subscribe(id, appObjects, setVM);
|
|
460
|
+
|
|
461
|
+
// Critical: cleanup on unmount
|
|
462
|
+
return () => {
|
|
463
|
+
adapter.unsubscribe(id, appObjects, setVM);
|
|
464
|
+
};
|
|
465
|
+
}, [id]);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Error Handling
|
|
469
|
+
|
|
470
|
+
Adapters should handle missing components gracefully:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
subscribe(id: string, appObjects: AppObjectRepo, setVM: (vm: VM) => void): void
|
|
474
|
+
{
|
|
475
|
+
const appObject = appObjects.get(id);
|
|
476
|
+
if (!appObject)
|
|
477
|
+
{
|
|
478
|
+
console.warn(`AppObject not found: ${id}`);
|
|
479
|
+
return; // Fail gracefully
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const pm = appObject.getComponent<MyPM>("MyPM");
|
|
483
|
+
if (!pm)
|
|
484
|
+
{
|
|
485
|
+
console.warn(`PM not found in AppObject: ${id}`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
pm.addView(setVM);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Singleton Access
|
|
494
|
+
|
|
495
|
+
Use static accessor methods for singleton PMs:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
class MySingletonPM extends AppObjectSingletonPM<VM>
|
|
499
|
+
{
|
|
500
|
+
static get(appObjects: AppObjectRepo): MySingletonPM | undefined
|
|
501
|
+
{
|
|
502
|
+
return getSingletonComponent<MySingletonPM>("MySingletonPM", appObjects);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// In adapter
|
|
507
|
+
const pm = MySingletonPM.get(appObjects); // Type-safe access
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Use Cases
|
|
511
|
+
|
|
512
|
+
- **UI Framework Integration** - Connect React/Vue/Angular to presentation layer
|
|
513
|
+
- **Cross-Framework Compatibility** - Write adapters once, use in any framework
|
|
514
|
+
- **Animation Systems** - Type-safe easing functions
|
|
515
|
+
- **Embedded Applications** - Host/guest communication contracts
|
|
516
|
+
- **Global State Management** - Singleton adapters for app-wide state
|
|
517
|
+
- **Component Libraries** - Reusable adapters for common UI patterns
|
|
518
|
+
|
|
519
|
+
## Testing
|
|
520
|
+
|
|
521
|
+
Adapters are easily testable in isolation:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
describe("CounterPmAdapter", () => {
|
|
525
|
+
let adapter: CounterPmAdapter;
|
|
526
|
+
let mockAppObjects: AppObjectRepo;
|
|
527
|
+
let mockSetVM: jest.Mock;
|
|
528
|
+
|
|
529
|
+
beforeEach(() => {
|
|
530
|
+
adapter = new CounterPmAdapter();
|
|
531
|
+
mockAppObjects = createMockAppObjectRepo();
|
|
532
|
+
mockSetVM = jest.fn();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("subscribes to presentation manager", () => {
|
|
536
|
+
adapter.subscribe("counter-1", mockAppObjects, mockSetVM);
|
|
537
|
+
|
|
538
|
+
// Verify subscription happened
|
|
539
|
+
expect(mockSetVM).toHaveBeenCalledWith(expect.objectContaining({
|
|
540
|
+
count: expect.any(Number)
|
|
541
|
+
}));
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("provides default VM", () => {
|
|
545
|
+
expect(adapter.defaultVM).toBeDefined();
|
|
546
|
+
expect(adapter.defaultVM.count).toBe(0);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
```
|