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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@open-ot/adapter-redis

v0.3.0

Published

Redis backend adapter for OpenOT, providing persistent operation history and document metadata storage.

Downloads

6

Readme

@open-ot/adapter-redis

Redis backend adapter for OpenOT, providing persistent operation history and document metadata storage.

Overview

@open-ot/adapter-redis implements the IBackendAdapter interface using Redis as the storage backend. It uses Redis lists for operation history and hashes for document metadata, with Lua scripts to ensure atomic commits.

Installation

npm install @open-ot/adapter-redis @open-ot/server

Quick Start

import { Server } from '@open-ot/server';
import { RedisAdapter } from '@open-ot/adapter-redis';
import { TextType } from '@open-ot/core';

// 1. Create Redis adapter
const adapter = new RedisAdapter('redis://localhost:6379');

// 2. Initialize a document
await adapter.createDocument('doc-1', 'text', 'Hello World');

// 3. Create server
const server = new Server(adapter);
server.registerType(TextType);

// 4. Submit operations
const result = await server.submitOperation('doc-1', op, revision);

Redis Data Structure

The adapter uses the following Redis keys:

Document Metadata

Key: doc:{docId}:metadata
Type: Hash
Fields:

  • type: The OT type name (e.g., "text", "json")
  • v: Current revision number

Example:

HGETALL doc:my-document:metadata
1) "type"
2) "text"
3) "v"
4) "42"

Document Snapshot

Key: doc:{docId}:data
Type: String (JSON-encoded)
Value: The current document snapshot

Example:

GET doc:my-document:data
"\"Hello World\""

Operation History

Key: doc:{docId}:history
Type: List
Value: JSON-encoded operations in chronological order

Example:

LRANGE doc:my-document:history 0 -1
1) "[{\"i\":\"Hello\"}]"
2) "[{\"r\":5},{\"i\":\" World\"}]"

API Reference

RedisAdapter

Constructor

new RedisAdapter(connectionString: string)

Parameters:

  • connectionString: Redis connection URL (e.g., redis://localhost:6379)

Example:

// Local Redis
const adapter = new RedisAdapter('redis://localhost:6379');

// Redis with authentication
const adapter = new RedisAdapter('redis://:password@localhost:6379');

// Redis Cloud / Upstash
const adapter = new RedisAdapter('rediss://default:password@host:port');

Methods

getRecord(docId: string): Promise<DocumentRecord>

Get the current document metadata and snapshot.

Returns:

{
  type: string;    // OT type name
  v: number;       // Current revision
  data: unknown;   // Snapshot (parsed from JSON)
}

Throws:

  • Error if the document doesn't exist.
getHistory(docId: string, start: number, end?: number): Promise<unknown[]>

Get a range of operations from the history.

Parameters:

  • start: Starting revision (inclusive)
  • end: Ending revision (exclusive, optional)

Returns:

  • Array of operations (parsed from JSON)

Example:

// Get operations from revision 5 to 10
const ops = await adapter.getHistory('doc-1', 5, 10);

// Get all operations from revision 5 onwards
const ops = await adapter.getHistory('doc-1', 5);
saveOperation(docId: string, op: unknown, newRevision: number): Promise<void>

Atomically append an operation to the history and increment the revision.

Parameters:

  • docId: Document ID
  • op: Operation to save
  • newRevision: Expected new revision number

Throws:

  • Error if the document doesn't exist.
  • Error if there's a concurrency conflict (revision mismatch).

Implementation:

Uses a Lua script to ensure atomicity:

local currentV = redis.call("HGET", metaKey, "v")
if tonumber(currentV) ~= newRevision - 1 then
  return "ERR_CONCURRENCY"
end

redis.call("RPUSH", historyKey, op)
redis.call("HSET", metaKey, "v", newRevision)
createDocument(docId: string, type: string, initialSnapshot: unknown): Promise<void>

Initialize a new document.

Parameters:

  • docId: Unique document identifier
  • type: OT type name (e.g., "text", "json")
  • initialSnapshot: Initial document state

Example:

await adapter.createDocument('doc-1', 'text', 'Hello World');
await adapter.createDocument('doc-2', 'json', { users: [] });
close(): Promise<void>

Close the Redis connection.

Example:

await adapter.close();

Concurrency Control

The adapter uses optimistic locking to prevent race conditions:

  1. Client submits operation with revision n
  2. Server checks if current revision is n
  3. If not, server transforms the operation against operations n to current
  4. Lua script ensures atomic commit:
    • Check current revision is newRevision - 1
    • Append operation to history
    • Increment revision to newRevision

This guarantees linearizability even with concurrent clients.

Production Deployment

Redis Configuration

For production use, configure Redis for persistence:

redis.conf:

# AOF persistence for durability
appendonly yes
appendfsync everysec

# RDB snapshots as backup
save 900 1
save 300 10
save 60 10000

Connection Pooling

The adapter uses ioredis, which handles connection pooling automatically. For high-traffic applications, consider:

import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  lazyConnect: true,
});

// Pass the Redis instance to the adapter
const adapter = new RedisAdapter(redis);

Scaling

For horizontal scaling:

  • Redis Cluster: Use Redis Cluster for sharding across multiple nodes.
  • Redis Sentinel: Use Sentinel for high availability and automatic failover.
  • Upstash: Use Upstash for serverless Redis with global replication.

Example (Upstash):

const adapter = new RedisAdapter(process.env.UPSTASH_REDIS_URL!);

Monitoring

Monitor key metrics:

  • Operation throughput: LLEN doc:{docId}:history
  • Document count: KEYS doc:*:metadata | wc -l
  • Memory usage: INFO memory

Cleanup

To delete a document and its history:

redis-cli DEL doc:my-document:metadata doc:my-document:data doc:my-document:history

Or programmatically:

await redis.del(
  `doc:${docId}:metadata`,
  `doc:${docId}:data`,
  `doc:${docId}:history`
);

Migration from MemoryBackend

import { MemoryBackend } from '@open-ot/server';
import { RedisAdapter } from '@open-ot/adapter-redis';

// Before
const backend = new MemoryBackend();

// After
const backend = new RedisAdapter('redis://localhost:6379');
await backend.createDocument('doc-1', 'text', 'Initial content');

License

MIT