madapes

@aurora/runtime-projection-state (0.3.3)

Published 2025-12-27 10:44:44 +00:00 by vlad

Installation

@aurora:registry=
npm install @aurora/runtime-projection-state@0.3.3
"@aurora/runtime-projection-state": "0.3.3"

About this package

@aurora/runtime-storage-lmdbx

LMDBX-based storage adapter for Aurora projections.

Overview

This package provides a high-performance, embedded key-value store implementation using LMDB (Lightning Memory-Mapped Database). LMDBX is optimized for read-heavy workloads and provides ACID guarantees with zero-copy reads via memory-mapped files.

Use cases:

  • Hot projection data (current state)
  • Read-optimized caching layers
  • Fast lookups with sub-millisecond latency

Not suitable for:

  • Write-heavy workloads (use RocksDB instead)
  • Large values (>1MB)
  • Event history (use RocksDB instead)

Installation

bun add @aurora/runtime-storage-lmdbx

Usage

Basic Example

import { createLMDBXRepository } from "@aurora/runtime-storage-lmdbx";

// Create repository
const repo = createLMDBXRepository<string, { name: string; email: string }>({
  path: "./data/users",
  encoding: "msgpack",
});

// Put a value
await repo.put("tenant1", "user:123", { name: "Alice", email: "alice@example.com" });

// Get a value
const user = await repo.get("tenant1", "user:123");
console.log(user); // { name: "Alice", email: "alice@example.com" }

// Delete a value
await repo.delete("tenant1", "user:123");

// Check if key exists
const exists = await repo.has("tenant1", "user:123");
console.log(exists); // false (just deleted)

// List all keys for a tenant
const keys = await repo.list("tenant1");
console.log(keys); // ["user:456", "user:789"]

// Close database
await repo.close();

Configuration

interface LMDBXConfig {
  /** Path to the LMDBX database directory */
  path: string;
  
  /** Database name (for multiple databases in same environment) */
  name?: string;
  
  /** Enable compression (default: false) */
  compression?: boolean;
  
  /** Encoding format (default: "msgpack") */
  encoding?: "msgpack" | "json" | "string";
  
  /** Maximum database size in bytes (default: auto) */
  mapSize?: number;
}

Encoding options:

  • msgpack (default): Binary encoding, compact, fast
  • json: Human-readable, larger size
  • string: For string-only values

Multi-Tenant Isolation

Keys are automatically scoped by tenant ID:

// Different tenants, same key
await repo.put("tenant1", "counter", { value: 10 });
await repo.put("tenant2", "counter", { value: 20 });

const t1 = await repo.get("tenant1", "counter"); // { value: 10 }
const t2 = await repo.get("tenant2", "counter"); // { value: 20 }

Complex Keys

Keys can be any JSON-serializable type:

type TimeSeriesKey = { userId: string; timestamp: number };
type Metric = { value: number; tags: string[] };

const repo = new LMDBXRepository<TimeSeriesKey, Metric>({
  path: "./data/metrics",
});

await repo.put(
  "tenant1", 
  { userId: "user123", timestamp: Date.now() }, 
  { value: 42, tags: ["cpu"] }
);

Architecture

Key Encoding

Keys are encoded as tenantId:JSON(key) to provide tenant isolation and support arbitrary key types.

Example:

  • Input: tenantId="acme", key="user:123"
  • Stored as: acme:"user:123"

Storage Layout

data/
└── users/          # LMDBX database directory
    ├── data.mdb    # Memory-mapped data file
    └── lock.mdb    # Lock file

Performance Characteristics

  • Reads: O(log n) with memory-mapped access (sub-millisecond)
  • Writes: O(log n) with ACID guarantees
  • Space: Compact binary encoding (msgpack)
  • Concurrency: Multiple readers, single writer (MVCC)

Memory-Mapped I/O

LMDBX uses memory-mapped files for zero-copy reads:

  • OS manages caching automatically
  • No serialization overhead on reads
  • Writes are still transactional and durable

Integration with Aurora

Projection Storage

import { createLMDBXRepository } from "@aurora/runtime-storage-lmdbx";
import type { Repository } from "@aurora/runtime-storage";

// Hot projection data
const ordersRepo: Repository<string, Order> = new LMDBXRepository({
  path: "./data/projections/orders",
  encoding: "msgpack",
});

// Use in projection workers
async function updateProjection(event: OrderCreated) {
  const order = {
    id: event.payload.orderId,
    status: "pending",
    createdAt: event.timestamp,
  };
  
  await ordersRepo.put(
    event.payload.tenantId, 
    event.payload.orderId, 
    order
  );
}

Comparison with RocksDB

Feature LMDBX RocksDB
Read performance ️ Faster (mmap) Fast (cached)
Write performance Good ️ Faster (LSM)
Space efficiency Good ️ Better (compression)
Use case Hot projections Event history, write-heavy

Rule of thumb:

  • LMDBX: Current state, frequent reads
  • RocksDB: Historical data, frequent writes

Limitations

  • Single writer: Only one process can write at a time (multiple readers allowed)
  • Database size: Must be pre-allocated or will auto-grow (configurable via mapSize)
  • Value size: Best for values < 1MB (no hard limit, but performance degrades)
  • Not distributed: Embedded storage only (use NATS JetStream for replication)

Testing

bun test

Tests use a temporary database directory that's cleaned up after each test.

License

MIT

Dependencies

Dependencies

ID Version
@aurora/runtime-effect 0.3.3
@aurora/runtime-observability 0.3.3
@aurora/runtime-storage 0.3.3
lmdb ^3.2.2
Details
npm
2025-12-27 10:44:44 +00:00
3
latest
5.6 KiB
Assets (1)
Versions (3) View all
0.3.3 2025-12-27
0.3.2 2025-12-27
0.3.1 2025-12-27