npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@abstraks-dev/event-store

v1.0.0

Published

Bounded in-memory event store with optional DynamoDB persistence for AWS Lambda microservices

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-store

With DynamoDB Support (Optional)

npm install @abstraks-dev/event-store @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

Usage

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 identifier
  • data (object): Event payload
  • options (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 ms

getEvents()

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 maxEvents based on Lambda memory allocation
  • Use maxAgeMs to 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 internally

5. 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.client and dynamodb.tableName are set correctly

High memory usage

  • Cause: maxEvents too high or large event payloads
  • Solution: Reduce maxEvents or 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() and getEvents()

License

MIT © Abstraks