This document covers the TypeScript type system and code organization patterns used throughout the plugin.
Contains types specific to the Flex plugin application:
// src/types/reduxTypes.ts
export interface AppReduxState {
flex?: {
supervisor?: { workers?: unknown[] };
worker?: { tasks?: Map<string, unknown> };
};
'flex-sync'?: SyncToReduxState; // Custom sync state
[key: string]: unknown;
}
export type AppStore = Store<AppReduxState>;Purpose: Defines the full Redux state shape including Flex's built-in state and custom plugin state.
The SyncToRedux library maintains its own type definitions, separate from application concerns:
export interface SyncToReduxState {
connectionState: string | null;
error: string | null;
trackedMaps: Record<string, TrackedMapState>;
}
export interface TrackedMapState {
mode: 'metadata' | 'direct';
mapData: Record<string, unknown>;
mapItems: Record<string, unknown>;
syncObjects: Record<string, SyncObjectState>;
}Purpose: Defines only the state managed by the SyncToRedux library, with no application-specific knowledge.
export interface SyncMapItemAddedEvent<T = unknown> {
item: SyncMapItem<T>;
isLocal: boolean;
}
export interface SyncDocumentUpdatedEvent<T = unknown> {
data: T;
previousData: T;
isLocal: boolean;
}
// ... more event typesPurpose: Strongly typed event payloads for Twilio Sync event handlers, replacing any types with proper interfaces.
Each component domain has its own types file:
export interface SupervisorWorkerState {
worker: WorkerInfo;
tasks: ITask[];
}
export interface WorkerInfo {
workerId: string;
workerSid: string;
fullName: string;
// ...
}export interface OperatorResult {
result: any; // Varies by operator
dateCreated: string;
triggerOn: string;
displayName: string;
outputFormat: 'CLASSIFICATION' | 'TEXT' | 'JSON';
}export interface MemoraObservation {
id: string;
content: string;
createdAt: string;
// ...
}Common utilities used across multiple components are extracted to prevent duplication:
/**
* Generate the Sync Map name for a given call SID
*/
export function getMapName(callSid: string): string {
return `ai-playground-${callSid}`;
}Used by:
src/initCallSyncTracking.ts- Track/untrack on task lifecyclesrc/components/Supervisor/SupervisorCallTracker.tsx- Supervisor monitoringsrc/components/AiPlayground/RealtimeOperatorsTab.tsx- Operator data accesssrc/components/AiPlayground/PostCallOperatorsTab.tsx- Operator data accesssrc/components/Supervisor/SupervisorOperatorResultsTab.tsx- Supervisor operator viewsrc/components/RealTimeTranscription/RealTimeTranscriptionTab.tsx- Transcription data access
Why: Previously duplicated in 6 locations with potential for inconsistency.
The SyncToRedux library is designed as a standalone, reusable module:
- No application-specific types in library code
- Generic
Store<any>instead ofStore<AppReduxState> - Event types defined separately from business logic
- Documentation inside library directory (INDEX.md, README.md, QUICKSTART.md)
- Library could be extracted to a separate npm package
- Clear boundaries between framework and application code
- Easier to test in isolation
- Simpler to document and maintain
Previous implementation had 47+ instances of any:
// Before: No type safety
private store: Store<any> | null = null;
private listIndexMaps: Map<string, Map<number, any>> = new Map();
syncMap.on('itemAdded', (event: any) => {
// No type checking
});
catch (err: any) {
console.error(err.message);
}// After: Strong typing
private store: Store<any> | null = null; // Generic for library
private listIndexMaps: Map<string, Map<number, unknown>> = new Map();
syncMap.on('itemAdded', (event: SyncMapItemAddedEvent) => {
// Type-safe access to event.item.key, event.item.data, etc.
});
catch (err: unknown) {
const error = err instanceof Error ? err : new Error(String(err));
console.error(error.message);
}Standard pattern for catch blocks:
try {
// Operation
} catch (err: unknown) {
const error = err instanceof Error ? err : new Error(String(err));
// Now have typed error with .message, .stack, etc.
setError(error.message);
}Why: TypeScript 4.4+ best practice is to use unknown in catch blocks rather than any.
import { AppReduxState } from '../../types/reduxTypes';
import { getMapName } from '../../utils/syncMapHelpers';import { SyncToReduxState } from './utils/sync-to-redux/reduxTypes';
import { SyncMapItemAddedEvent } from './utils/sync-to-redux/syncEventTypes';
import SyncToReduxService from './utils/sync-to-redux/SyncToReduxService';import { SupervisorWorkerState } from './types';
import { OperatorResult } from '../AiPlayground/types';- Component files: PascalCase with
.tsxextension (AiPlaygroundPanel.tsx) - Utility files: camelCase with
.tsextension (syncMapHelpers.ts) - Type definition files: camelCase ending in
Types.ts(reduxTypes.ts,syncEventTypes.ts) - Barrel exports:
index.tsfor cleaner imports
// ✅ Good: Export types for reuse
export interface PublicInterface { ... }
// ✅ Good: Export type alias
export type AppStore = Store<AppReduxState>;// ✅ Good: Type narrowing with guards
if (error instanceof Error) {
console.error(error.message);
}
// ✅ Good: Custom type guard
function isCallTask(task: ITask): boolean {
return TaskHelper.isCallTask(task) && !!task.attributes?.call_sid;
}// ❌ Avoid: Type assertion bypasses checking
const data = response as MyType;
// ✅ Better: Runtime validation
function isMyType(data: unknown): data is MyType {
return typeof data === 'object' && data !== null && 'expectedField' in data;
}- Main README: Project overview and setup
- SyncToRedux Library: src/utils/sync-to-redux/INDEX.md
- AI Playground Panel: ai-playground-panel.md
- Supervisor Components: No dedicated doc yet, see component files