@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,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
+ ```