{"_id":"@abstraks-dev/event-store","name":"@abstraks-dev/event-store","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@abstraks-dev/event-store","version":"1.0.0","description":"Bounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices","type":"module","main":"src/index.js","exports":{".":"./src/index.js"},"scripts":{"test":"NODE_OPTIONS=--experimental-vm-modules jest"},"keywords":["event-store","event-sourcing","aws-lambda","dynamodb","in-memory-cache","microservices","abstraks"],"author":{"name":"Abstraks"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Abstraks-co/shared-modules.git","directory":"packages/event-store"},"bugs":{"url":"https://github.com/Abstraks-co/shared-modules/issues"},"homepage":"https://github.com/Abstraks-co/shared-modules/tree/main/packages/event-store#readme","peerDependencies":{"@aws-sdk/client-dynamodb":"^3.0.0","@aws-sdk/lib-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"@aws-sdk/client-dynamodb":{"optional":true},"@aws-sdk/lib-dynamodb":{"optional":true}},"_id":"@abstraks-dev/event-store@1.0.0","gitHead":"f629da8e3ebc2d8fdc6f83a147ea8e50b8bc66b5","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-DE2KvT0XKYf3RJDqCopVHxC03qYqBlOKFjK0PHVxY0ws9+k94VThRUaaGpRqhgOsDTpfAiZ7pP98KB/O6lYI3w==","shasum":"b0e680e766c0991ce844c2b03ea3020b9906ea79","tarball":"https://registry.npmjs.org/@abstraks-dev/event-store/-/event-store-1.0.0.tgz","fileCount":5,"unpackedSize":25658,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDfvWrqvimDbvz06PqkASD6DwlWBlWQHKTKjdbIUemTzgIgZJPAWKKkQF0F/GBczQQ/L95g8jrP9apPtoQlenINSD8="}]},"_npmUser":{"name":"abstraks-dev","email":"contactabstraks@gmail.com"},"directories":{},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/event-store_1.0.0_1763675398552_0.8673790897728122"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-20T21:49:58.432Z","1.0.0":"2025-11-20T21:49:58.736Z","modified":"2025-11-20T21:49:59.016Z"},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"description":"Bounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices","homepage":"https://github.com/Abstraks-co/shared-modules/tree/main/packages/event-store#readme","keywords":["event-store","event-sourcing","aws-lambda","dynamodb","in-memory-cache","microservices","abstraks"],"repository":{"type":"git","url":"git+https://github.com/Abstraks-co/shared-modules.git","directory":"packages/event-store"},"author":{"name":"Abstraks"},"bugs":{"url":"https://github.com/Abstraks-co/shared-modules/issues"},"license":"MIT","readme":"# @abstraks-dev/event-store\n\nBounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices.\n\n## Features\n\n- ✅ **Bounded Size**: Configurable maximum event count (default: 100)\n- ✅ **Time-to-Live (TTL)**: Automatic cleanup of old events\n- ✅ **Binary Search Optimization**: O(log n) TTL cleanup performance\n- ✅ **Optional DynamoDB Persistence**: Survive Lambda cold starts\n- ✅ **Automatic Cold-Start Loading**: Populate cache from DynamoDB on first access\n- ✅ **Graceful Degradation**: Falls back to in-memory if DynamoDB fails\n- ✅ **Zero Dependencies**: DynamoDB SDK is optional peer dependency\n- ✅ **Lambda Container Aware**: Efficient reuse across invocations\n\n## Installation\n\n```bash\nnpm install @abstraks-dev/event-store\n```\n\n### With DynamoDB Support (Optional)\n\n```bash\nnpm install @abstraks-dev/event-store @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb\n```\n\n## Usage\n\n### Simple In-Memory Store\n\n```javascript\nimport { createSimpleEventStore } from '@abstraks-dev/event-store';\n\n// Create store with defaults (100 events, 24 hour TTL)\nconst eventStore = createSimpleEventStore();\n\n// Add events\nawait eventStore.addEvent('UserCreated', {\n\tuserId: '123',\n\temail: 'user@example.com',\n});\n\nawait eventStore.addEvent('PostCreated', {\n\tpostId: '456',\n\tcontent: 'Hello world',\n});\n\n// Retrieve all events\nconst events = await eventStore.getEvents();\nconsole.log(`Stored ${eventStore.getCount()} events`);\n```\n\n### Custom Configuration\n\n```javascript\nimport { createSimpleEventStore } from '@abstraks-dev/event-store';\n\nconst eventStore = createSimpleEventStore(\n\t50, // Max 50 events\n\t60 * 60 * 1000 // 1 hour TTL\n);\n```\n\n### With DynamoDB Persistence\n\n```javascript\nimport { createEventStore } from '@abstraks-dev/event-store';\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';\n\nconst dynamoClient = new DynamoDBClient({ region: 'us-west-2' });\nconst docClient = DynamoDBDocumentClient.from(dynamoClient);\n\nconst eventStore = createEventStore({\n\tmaxEvents: 100,\n\tmaxAgeMs: 24 * 60 * 60 * 1000, // 24 hours\n\tdynamodb: {\n\t\tclient: docClient,\n\t\ttableName: 'EventStore-prod',\n\t\tttlDays: 30, // DynamoDB TTL (default: 30 days)\n\t\tmaxScanItems: 1000, // Max items to load on cold start\n\t},\n});\n\n// Events are automatically persisted to DynamoDB\nawait eventStore.addEvent(\n\t'OrderPlaced',\n\t{\n\t\torderId: '789',\n\t\tamount: 99.99,\n\t},\n\t{\n\t\tsource: 'order-service',\n\t\teventId: 'custom-event-id', // Optional\n\t}\n);\n\n// On first call, automatically loads from DynamoDB\nconst events = await eventStore.getEvents();\n```\n\n## API\n\n### `createSimpleEventStore(maxEvents?, maxAgeMs?)`\n\nFactory function for creating a simple in-memory event store.\n\n**Parameters:**\n\n- `maxEvents` (number, optional): Maximum number of events (default: 100)\n- `maxAgeMs` (number, optional): Maximum age in milliseconds (default: 24 hours)\n\n**Returns:** Event store instance\n\n### `createEventStore(options)`\n\nCreate an event store with advanced configuration.\n\n**Options:**\n\n```typescript\n{\n\tmaxEvents?: number;        // Max events in memory (default: 100)\n\tmaxAgeMs?: number;         // Max age in ms (default: 24 hours)\n\tdynamodb?: {\n\t\tclient: DynamoDBDocumentClient;  // AWS SDK DocumentClient\n\t\ttableName: string;                // DynamoDB table name\n\t\tttlDays?: number;                 // TTL in days (default: 30)\n\t\tmaxScanItems?: number;            // Max items for cold-start scan (default: 1000)\n\t}\n}\n```\n\n**Returns:** Event store instance\n\n### Event Store Methods\n\n#### `addEvent(type, data, options?)`\n\nAdd an event to the store.\n\n**Parameters:**\n\n- `type` (string): Event type identifier\n- `data` (object): Event payload\n- `options` (object, optional):\n  - `source` (string): Event source identifier (default: 'unknown')\n  - `eventId` (string): Custom event ID (auto-generated if omitted)\n\n**Returns:** Promise<Event> - Added event with metadata\n\n```javascript\nconst event = await eventStore.addEvent(\n\t'UserLoggedIn',\n\t{\n\t\tuserId: '123',\n\t\tipAddress: '192.168.1.1',\n\t},\n\t{\n\t\tsource: 'auth-service',\n\t}\n);\n\nconsole.log(event.eventId); // Auto-generated or custom\nconsole.log(event.timestamp); // ISO 8601 timestamp\nconsole.log(event.timestampMs); // Unix timestamp in ms\n```\n\n#### `getEvents()`\n\nRetrieve all stored events (with automatic cleanup and DynamoDB loading).\n\n**Returns:** Promise<Event[]>\n\n```javascript\nconst events = await eventStore.getEvents();\nevents.forEach((event) => {\n\tconsole.log(`${event.type} at ${event.timestamp}`);\n});\n```\n\n#### `getCount()`\n\nGet the current number of stored events.\n\n**Returns:** number\n\n```javascript\nconsole.log(`Store contains ${eventStore.getCount()} events`);\n```\n\n#### `clear()`\n\nClear all events from the in-memory store.\n\n**Note:** Does not affect DynamoDB persistence.\n\n```javascript\neventStore.clear();\n```\n\n## DynamoDB Table Schema\n\nIf using DynamoDB persistence, create a table with the following schema:\n\n```javascript\n{\n\tTableName: 'EventStore-prod',\n\tKeySchema: [\n\t\t{ AttributeName: 'eventId', KeyType: 'HASH' },  // Partition key\n\t\t{ AttributeName: 'timestamp', KeyType: 'RANGE' } // Sort key\n\t],\n\tAttributeDefinitions: [\n\t\t{ AttributeName: 'eventId', AttributeType: 'S' },\n\t\t{ AttributeName: 'timestamp', AttributeType: 'S' }\n\t],\n\tBillingMode: 'PAY_PER_REQUEST',\n\tTimeToLiveSpecification: {\n\t\tEnabled: true,\n\t\tAttributeName: 'ttl'  // Unix timestamp for auto-deletion\n\t}\n}\n```\n\n### AWS CDK Example\n\n```typescript\nimport * as dynamodb from 'aws-cdk-lib/aws-dynamodb';\n\nconst eventTable = new dynamodb.Table(this, 'EventStore', {\n\tpartitionKey: { name: 'eventId', type: dynamodb.AttributeType.STRING },\n\tsortKey: { name: 'timestamp', type: dynamodb.AttributeType.STRING },\n\tbillingMode: dynamodb.BillingMode.PAY_PER_REQUEST,\n\ttimeToLiveAttribute: 'ttl',\n\tremovalPolicy: RemovalPolicy.DESTROY, // For dev/test\n});\n```\n\n## Performance Characteristics\n\n| Operation                  | Time Complexity | Notes                                 |\n| -------------------------- | --------------- | ------------------------------------- |\n| `addEvent()`               | O(n) amortized  | Binary search cleanup + FIFO eviction |\n| `getEvents()`              | O(n)            | Binary search cleanup + array copy    |\n| `getCount()`               | O(1)            | Direct array length                   |\n| `clear()`                  | O(1)            | Array truncation                      |\n| DynamoDB Scan (cold start) | O(n)            | Limited by `maxScanItems`             |\n\n**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²)).\n\n## Best Practices\n\n### 1. Choose Appropriate Bounds\n\n```javascript\n// For debugging/monitoring (small, short-lived)\nconst debugStore = createSimpleEventStore(50, 60 * 60 * 1000); // 50 events, 1 hour\n\n// For event replay (large, persistent)\nconst replayStore = createEventStore({\n\tmaxEvents: 1000,\n\tmaxAgeMs: 7 * 24 * 60 * 60 * 1000, // 7 days\n\tdynamodb: {\n\t\t/* ... */\n\t},\n});\n```\n\n### 2. Use DynamoDB for Production\n\nIn-memory storage is lost on Lambda cold starts. Use DynamoDB for:\n\n- Event replay capabilities\n- Cross-invocation persistence\n- Audit trails\n- Historical analysis\n\n### 3. Monitor Memory Usage\n\nLambda memory limits apply. For large event counts or payloads:\n\n- Adjust `maxEvents` based on Lambda memory allocation\n- Use `maxAgeMs` to prevent unbounded growth\n- Consider separating hot/cold storage tiers\n\n### 4. Handle DynamoDB Errors\n\nThe store degrades gracefully but log errors for monitoring:\n\n```javascript\n// Custom error handling (optional)\nconst store = createEventStore({\n\tmaxEvents: 100,\n\tdynamodb: {\n\t\tclient: docClient,\n\t\ttableName: process.env.EVENT_TABLE,\n\t},\n});\n\n// Monitor via CloudWatch Logs\nawait store.addEvent('CriticalEvent', data); // Logs DynamoDB errors internally\n```\n\n### 5. Environment-Specific Configuration\n\n```javascript\nconst isDev = process.env.ENVIRONMENT === 'dev';\n\nconst eventStore = createEventStore({\n\tmaxEvents: isDev ? 50 : 500,\n\tmaxAgeMs: isDev ? 60 * 60 * 1000 : 24 * 60 * 60 * 1000,\n\tdynamodb: isDev\n\t\t? null\n\t\t: {\n\t\t\t\tclient: docClient,\n\t\t\t\ttableName: `EventStore-${process.env.ENVIRONMENT}`,\n\t\t\t},\n});\n```\n\n## Migration from Existing Event Stores\n\n### From Auth/Media Pattern\n\n```javascript\n// Before\nconst events = [];\nexport const addEvent = (type, data) => {\n\tevents.push({ type, data, timestamp: new Date().toISOString() });\n};\nexport const getEvents = () => events;\n\n// After\nimport { createSimpleEventStore } from '@abstraks-dev/event-store';\nconst { addEvent, getEvents } = createSimpleEventStore();\n```\n\n### From Event-Bus Pattern (with DynamoDB)\n\n```javascript\n// Before\nconst events = [];\n// ... manual DynamoDB PutCommand logic\n\n// After\nimport { createEventStore } from '@abstraks-dev/event-store';\nconst eventStore = createEventStore({\n\tmaxEvents: 100,\n\tdynamodb: {\n\t\tclient: docClient,\n\t\ttableName: process.env.EVENTS_TABLE_NAME,\n\t\tttlDays: 30,\n\t},\n});\n```\n\n## Troubleshooting\n\n### Events not persisting across Lambda invocations\n\n- **Cause:** DynamoDB not configured or table doesn't exist\n- **Solution:** Verify `dynamodb.client` and `dynamodb.tableName` are set correctly\n\n### High memory usage\n\n- **Cause:** `maxEvents` too high or large event payloads\n- **Solution:** Reduce `maxEvents` or implement payload size limits\n\n### DynamoDB throttling\n\n- **Cause:** High event volume with on-demand billing\n- **Solution:** Use provisioned capacity or batch writes\n\n### Old events not cleaning up\n\n- **Cause:** `getEvents()` not called frequently (cleanup is lazy)\n- **Solution:** Cleanup runs automatically on `addEvent()` and `getEvents()`\n\n## License\n\nMIT © Abstraks\n","readmeFilename":"README.md","_rev":"1-2d1bbd6afd0e54fe315ebfcf159580c1"}