Skip to content

Migrate Backend to Strict TypeScript #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
365 changes: 365 additions & 0 deletions EVENT_SYSTEM_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
# Type-Safe Event System

A comprehensive TypeScript event system implementation with mapped types for event payload definitions and generic event handler functions with proper type inference.

## Features

- **Type Safety**: Complete type safety with mapped types for event payloads
- **Type Inference**: Automatic type inference for event handlers and payloads
- **Middleware Support**: Event processing pipeline with middleware
- **Event Batching**: Efficient batch processing of events
- **Event Store**: Persistent event storage with querying capabilities
- **Event Projections**: Build read models from event streams
- **Event Aggregation**: Aggregate events for analytics and reporting
- **Handler Patterns**: Advanced handler patterns (throttling, debouncing, retry, filtering)
- **Utility Functions**: Comprehensive utility functions for event manipulation

## Core Types

### Event Payload Map

The `EventPayloadMap` interface defines all possible events and their payloads:

```typescript
export interface EventPayloadMap {
'user:created': {
readonly userKey: string;
readonly email: string;
readonly attributes: Record<string, unknown>;
};
'flag:evaluated': {
readonly flagKey: string;
readonly userKey: string;
readonly value: unknown;
readonly variation: number;
readonly reason: string;
readonly environment: string;
};
// ... more event types
}
```

### Typed Events

Events are automatically typed based on the payload map:

```typescript
type TypedEvent<T extends keyof EventPayloadMap> = BaseEvent & {
readonly type: T;
readonly payload: EventPayloadMap[T];
};
```

### Event Handlers

Event handlers have proper type inference:

```typescript
type EventHandler<T extends keyof EventPayloadMap> = (
event: TypedEvent<T>
) => void | Promise<void>;
```

## Basic Usage

### Creating an Event Emitter

```typescript
import { TypeSafeEventEmitter } from './event-system-implementation';

const emitter = new TypeSafeEventEmitter();
```

### Registering Event Handlers

```typescript
// Type-safe event handler - TypeScript knows the payload structure
const userCreatedHandler: EventHandler<'user:created'> = (event) => {
console.log(`User created: ${event.payload.userKey}`);
console.log(`Email: ${event.payload.email}`);
console.log(`Attributes:`, event.payload.attributes);
};

// Register the handler
const registration = emitter.on('user:created', userCreatedHandler);
```

### Emitting Events

```typescript
// Type-safe event emission - payload must match the defined structure
await emitter.emit('user:created', {
userKey: 'user123',
email: '[email protected]',
attributes: { plan: 'premium', region: 'us-east-1' }
});
```

### One-time Listeners

```typescript
// Register a one-time listener
emitter.once('user:created', (event) => {
console.log('This will only run once');
});
```

## Advanced Features

### Middleware

Add middleware to the event processing pipeline:

```typescript
// Logging middleware
emitter.use(async (event, next) => {
console.log(`Processing event: ${event.type}`);
await next();
console.log(`Completed event: ${event.type}`);
});

// Validation middleware
emitter.use(async (event, next) => {
if (isValidEvent(event)) {
await next();
} else {
console.error('Invalid event');
}
});
```

### Handler Factory Patterns

#### Filtering Handlers

```typescript
const premiumUserFilter: EventFilter<'user:created'> = {
type: 'user:created',
predicate: (payload) => payload.attributes.plan === 'premium'
};

const filteredHandler = EventHandlerFactory.createFilteringHandler(
baseHandler,
premiumUserFilter
);
```

#### Transforming Handlers

```typescript
const transformer: EventTransformer<'user:created', 'analytics:event'> = (event) => ({
type: 'analytics:event',
payload: {
eventName: 'user_registration',
userKey: event.payload.userKey,
eventData: { email: event.payload.email },
environment: 'production'
},
timestamp: event.timestamp,
metadata: event.metadata
});

const transformingHandler = EventHandlerFactory.createTransformingHandler(
transformer,
analyticsHandler
);
```

#### Throttled and Debounced Handlers

```typescript
// Throttled handler (max once per 1000ms)
const throttledHandler = EventHandlerFactory.createThrottledHandler(
baseHandler,
1000
);

// Debounced handler (waits 500ms after last event)
const debouncedHandler = EventHandlerFactory.createDebouncedHandler(
baseHandler,
500
);
```

#### Retry Handlers

```typescript
// Retry handler with exponential backoff
const retryHandler = EventHandlerFactory.createRetryHandler(
unreliableHandler,
3, // max retries
1000 // initial delay
);
```

#### Composable Handlers

```typescript
// Compose multiple handlers into one
const composedHandler = EventHandlerFactory.createComposableHandler(
logHandler,
emailHandler,
analyticsHandler
);
```

### Event Storage

Store and query events:

```typescript
import { InMemoryEventStore } from './event-system-implementation';

const store = new InMemoryEventStore();

// Store an event
await store.store(event);

// Query events by type
const userEvents = await store.getEventsByType('user:created', {
limit: 10,
fromTimestamp: new Date('2024-01-01')
});

// Query events by filter
const events = await store.getEvents({
type: 'flag:evaluated'
});
```

### Event Projections

Build read models from event streams:

```typescript
interface UserStats {
totalUsers: number;
premiumUsers: number;
usersByRegion: Record<string, number>;
}

const eventHandlers = new Map([
['user:created', (event, state: UserStats) => ({
...state,
totalUsers: state.totalUsers + 1,
premiumUsers: state.premiumUsers + (isPremium(event) ? 1 : 0)
})]
]);

const projection = new GenericEventProjection(initialState, eventHandlers);
const stats = projection.project(events);
```

### Event Aggregation

Aggregate events for analytics:

```typescript
import { EventAggregatorImpl } from './event-system-implementation';

const aggregator = new EventAggregatorImpl();

// Aggregate events by type
const aggregation = aggregator.aggregate(
'flag:evaluated',
flagEvents,
(events) => ({
totalEvaluations: events.length,
trueCount: events.filter(e => e.payload.value === true).length
})
);

// Aggregate by time window
const windowAggregations = aggregator.aggregateByTimeWindow(
'flag:evaluated',
flagEvents,
60000, // 1 minute windows
aggregationFunction
);
```

### Event Utilities

Utility functions for working with events:

```typescript
import { EventUtils } from './event-system-implementation';

// Create events
const event = EventUtils.createEvent('user:created', {
userKey: 'user123',
email: '[email protected]',
attributes: {}
});

// Group events by type
const grouped = EventUtils.groupEventsByType(events);

// Sort events by timestamp
const sorted = EventUtils.sortEventsByTimestamp(events);

// Filter events by time range
const recent = EventUtils.filterEventsByTimeRange(
events,
oneHourAgo,
now
);
```

## Error Handling

### Handler Error Handling

```typescript
emitter.on('user:created', handler, {
errorHandler: (error, event) => {
console.error(`Error processing ${event.type}:`, error);
// Send to error tracking service
}
});
```

### Conditional Handlers

```typescript
emitter.on('user:created', handler, {
condition: (event) => event.payload.attributes.plan === 'premium'
});
```

## Event Listener Options

```typescript
interface EventListenerOptions {
readonly once?: boolean; // One-time listener
readonly priority?: number; // Handler execution priority
readonly condition?: (event: AnyEvent) => boolean; // Conditional execution
readonly errorHandler?: (error: Error, event: AnyEvent) => void; // Error handling
}
```

## Best Practices

1. **Define Events Early**: Define all your events in the `EventPayloadMap` interface
2. **Use Descriptive Names**: Use clear, descriptive event names with namespaces
3. **Keep Payloads Simple**: Avoid complex nested structures in event payloads
4. **Handle Errors**: Always provide error handlers for critical event handlers
5. **Use Middleware**: Leverage middleware for cross-cutting concerns
6. **Batch Events**: Use event batching for high-volume scenarios
7. **Monitor Performance**: Use throttling and debouncing for performance-sensitive handlers

## Example Files

- `event-system-types.ts` - Core type definitions
- `event-system-implementation.ts` - Implementation classes
- `event-system-examples.ts` - Comprehensive usage examples

## Type Safety Benefits

- **Compile-time Validation**: Catch event structure mismatches at compile time
- **IntelliSense Support**: Full autocomplete and type hints in IDEs
- **Refactoring Safety**: Rename events and payloads with confidence
- **Documentation**: Self-documenting code through type definitions
- **Runtime Safety**: Prevent runtime errors from malformed events

This event system provides a robust, type-safe foundation for event-driven architectures in TypeScript applications.
Loading