@abstraks-dev/event-store
v1.0.0
Published
Bounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices
Maintainers
Readme
@abstraks-dev/event-store
Bounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices.
Features
- ✅ Bounded Size: Configurable maximum event count (default: 100)
- ✅ Time-to-Live (TTL): Automatic cleanup of old events
- ✅ Binary Search Optimization: O(log n) TTL cleanup performance
- ✅ Optional DynamoDB Persistence: Survive Lambda cold starts
- ✅ Automatic Cold-Start Loading: Populate cache from DynamoDB on first access
- ✅ Graceful Degradation: Falls back to in-memory if DynamoDB fails
- ✅ Zero Dependencies: DynamoDB SDK is optional peer dependency
- ✅ Lambda Container Aware: Efficient reuse across invocations
Installation
npm install @abstraks-dev/event-storeWith DynamoDB Support (Optional)
npm install @abstraks-dev/event-store @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbUsage
Simple In-Memory Store
import { createSimpleEventStore } from '@abstraks-dev/event-store';
// Create store with defaults (100 events, 24 hour TTL)
const eventStore = createSimpleEventStore();
// Add events
await eventStore.addEvent('UserCreated', {
userId: '123',
email: '[email protected]',
});
await eventStore.addEvent('PostCreated', {
postId: '456',
content: 'Hello world',
});
// Retrieve all events
const events = await eventStore.getEvents();
console.log(`Stored ${eventStore.getCount()} events`);Custom Configuration
import { createSimpleEventStore } from '@abstraks-dev/event-store';
const eventStore = createSimpleEventStore(
50, // Max 50 events
60 * 60 * 1000 // 1 hour TTL
);With DynamoDB Persistence
import { createEventStore } from '@abstraks-dev/event-store';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const dynamoClient = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(dynamoClient);
const eventStore = createEventStore({
maxEvents: 100,
maxAgeMs: 24 * 60 * 60 * 1000, // 24 hours
dynamodb: {
client: docClient,
tableName: 'EventStore-prod',
ttlDays: 30, // DynamoDB TTL (default: 30 days)
maxScanItems: 1000, // Max items to load on cold start
},
});
// Events are automatically persisted to DynamoDB
await eventStore.addEvent(
'OrderPlaced',
{
orderId: '789',
amount: 99.99,
},
{
source: 'order-service',
eventId: 'custom-event-id', // Optional
}
);
// On first call, automatically loads from DynamoDB
const events = await eventStore.getEvents();API
createSimpleEventStore(maxEvents?, maxAgeMs?)
Factory function for creating a simple in-memory event store.
Parameters:
maxEvents(number, optional): Maximum number of events (default: 100)maxAgeMs(number, optional): Maximum age in milliseconds (default: 24 hours)
Returns: Event store instance
createEventStore(options)
Create an event store with advanced configuration.
Options:
{
maxEvents?: number; // Max events in memory (default: 100)
maxAgeMs?: number; // Max age in ms (default: 24 hours)
dynamodb?: {
client: DynamoDBDocumentClient; // AWS SDK DocumentClient
tableName: string; // DynamoDB table name
ttlDays?: number; // TTL in days (default: 30)
maxScanItems?: number; // Max items for cold-start scan (default: 1000)
}
}Returns: Event store instance
Event Store Methods
addEvent(type, data, options?)
Add an event to the store.
Parameters:
type(string): Event type identifierdata(object): Event payloadoptions(object, optional):source(string): Event source identifier (default: 'unknown')eventId(string): Custom event ID (auto-generated if omitted)
Returns: Promise - Added event with metadata
const event = await eventStore.addEvent(
'UserLoggedIn',
{
userId: '123',
ipAddress: '192.168.1.1',
},
{
source: 'auth-service',
}
);
console.log(event.eventId); // Auto-generated or custom
console.log(event.timestamp); // ISO 8601 timestamp
console.log(event.timestampMs); // Unix timestamp in msgetEvents()
Retrieve all stored events (with automatic cleanup and DynamoDB loading).
Returns: Promise<Event[]>
const events = await eventStore.getEvents();
events.forEach((event) => {
console.log(`${event.type} at ${event.timestamp}`);
});getCount()
Get the current number of stored events.
Returns: number
console.log(`Store contains ${eventStore.getCount()} events`);clear()
Clear all events from the in-memory store.
Note: Does not affect DynamoDB persistence.
eventStore.clear();DynamoDB Table Schema
If using DynamoDB persistence, create a table with the following schema:
{
TableName: 'EventStore-prod',
KeySchema: [
{ AttributeName: 'eventId', KeyType: 'HASH' }, // Partition key
{ AttributeName: 'timestamp', KeyType: 'RANGE' } // Sort key
],
AttributeDefinitions: [
{ AttributeName: 'eventId', AttributeType: 'S' },
{ AttributeName: 'timestamp', AttributeType: 'S' }
],
BillingMode: 'PAY_PER_REQUEST',
TimeToLiveSpecification: {
Enabled: true,
AttributeName: 'ttl' // Unix timestamp for auto-deletion
}
}AWS CDK Example
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
const eventTable = new dynamodb.Table(this, 'EventStore', {
partitionKey: { name: 'eventId', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'timestamp', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
timeToLiveAttribute: 'ttl',
removalPolicy: RemovalPolicy.DESTROY, // For dev/test
});Performance Characteristics
| Operation | Time Complexity | Notes |
| -------------------------- | --------------- | ------------------------------------- |
| addEvent() | O(n) amortized | Binary search cleanup + FIFO eviction |
| getEvents() | O(n) | Binary search cleanup + array copy |
| getCount() | O(1) | Direct array length |
| clear() | O(1) | Array truncation |
| DynamoDB Scan (cold start) | O(n) | Limited by maxScanItems |
Binary Search Optimization: TTL cleanup uses binary search to find the cutoff index (O(log n)), then removes all old events in one splice operation (O(n)). This is significantly faster than iterating and removing individually (O(n²)).
Best Practices
1. Choose Appropriate Bounds
// For debugging/monitoring (small, short-lived)
const debugStore = createSimpleEventStore(50, 60 * 60 * 1000); // 50 events, 1 hour
// For event replay (large, persistent)
const replayStore = createEventStore({
maxEvents: 1000,
maxAgeMs: 7 * 24 * 60 * 60 * 1000, // 7 days
dynamodb: {
/* ... */
},
});2. Use DynamoDB for Production
In-memory storage is lost on Lambda cold starts. Use DynamoDB for:
- Event replay capabilities
- Cross-invocation persistence
- Audit trails
- Historical analysis
3. Monitor Memory Usage
Lambda memory limits apply. For large event counts or payloads:
- Adjust
maxEventsbased on Lambda memory allocation - Use
maxAgeMsto prevent unbounded growth - Consider separating hot/cold storage tiers
4. Handle DynamoDB Errors
The store degrades gracefully but log errors for monitoring:
// Custom error handling (optional)
const store = createEventStore({
maxEvents: 100,
dynamodb: {
client: docClient,
tableName: process.env.EVENT_TABLE,
},
});
// Monitor via CloudWatch Logs
await store.addEvent('CriticalEvent', data); // Logs DynamoDB errors internally5. Environment-Specific Configuration
const isDev = process.env.ENVIRONMENT === 'dev';
const eventStore = createEventStore({
maxEvents: isDev ? 50 : 500,
maxAgeMs: isDev ? 60 * 60 * 1000 : 24 * 60 * 60 * 1000,
dynamodb: isDev
? null
: {
client: docClient,
tableName: `EventStore-${process.env.ENVIRONMENT}`,
},
});Migration from Existing Event Stores
From Auth/Media Pattern
// Before
const events = [];
export const addEvent = (type, data) => {
events.push({ type, data, timestamp: new Date().toISOString() });
};
export const getEvents = () => events;
// After
import { createSimpleEventStore } from '@abstraks-dev/event-store';
const { addEvent, getEvents } = createSimpleEventStore();From Event-Bus Pattern (with DynamoDB)
// Before
const events = [];
// ... manual DynamoDB PutCommand logic
// After
import { createEventStore } from '@abstraks-dev/event-store';
const eventStore = createEventStore({
maxEvents: 100,
dynamodb: {
client: docClient,
tableName: process.env.EVENTS_TABLE_NAME,
ttlDays: 30,
},
});Troubleshooting
Events not persisting across Lambda invocations
- Cause: DynamoDB not configured or table doesn't exist
- Solution: Verify
dynamodb.clientanddynamodb.tableNameare set correctly
High memory usage
- Cause:
maxEventstoo high or large event payloads - Solution: Reduce
maxEventsor implement payload size limits
DynamoDB throttling
- Cause: High event volume with on-demand billing
- Solution: Use provisioned capacity or batch writes
Old events not cleaning up
- Cause:
getEvents()not called frequently (cleanup is lazy) - Solution: Cleanup runs automatically on
addEvent()andgetEvents()
License
MIT © Abstraks
