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

@devcoons/redis-session-store

v0.1.0

Published

> A Redis-backed session lineage manager for refresh token rotation, revocation, and device-level logout.

Readme

redis-session-store

A Redis-backed session lineage manager for refresh token rotation, revocation, and device-level logout.


Overview

redis-session-store provides a minimal, atomic, and framework-agnostic session store for modern authentication systems.
It is designed to manage rotating refresh tokens, device binding, and secure logout semantics - backed by Redis and Lua for atomicity.

Key design principles:

  • ⚙️ Atomic operations (via Lua)
  • 🔗 Lineage tracking (rid → parentRid)
  • 💻 Device and user-agent binding
  • ⏱️ Hard absolute TTLs for security
  • 🚪 Explicit logout (single, device, or all sessions)
  • 🧹 Garbage collection of orphaned or expired RIDs

Installation

# npm
npm install @devcoons/redis-session-store

# or pnpm
pnpm add @devcoons/redis-session-store

Basic Usage

import { createRedisStore } from '@devcoons/redis-session-store';

const store = createRedisStore({
    url: process.env.REDIS_URL ?? 'redis://localhost:6379',
    absTtlSeconds: 30 * 24 * 3600, // 30 days
});

// Issue an initial session lineage
const session = await store.issueInitial('user-123', {
    now: Math.floor(Date.now() / 1000),
    deviceId: 'chrome-laptop',
    uaHash: 'abc123',
    ipHash: 'xyz789',
    absTtlSeconds: 30 * 24 * 3600,
});

// Verify and rotate a refresh token
const rotated = await store.verifyAndRotateRefresh({
    rid: session.rid,
    now: Math.floor(Date.now() / 1000) + 3600,
    deviceId: 'chrome-laptop',
    uaHash: 'abc123',
    ipHash: 'xyz789',
});

console.log('New RID:', rotated.newRid);

// Logout a single session
await store.logout(session.rid);

// Logout all sessions for this user
await store.logoutAll('user-123');

// Garbage collect expired or orphaned sessions
await store.gcOrphans({ graceSeconds: 60 });

API Reference

createRedisStore(config: Cfg): SessionStore

Creates a Redis-backed store instance.

type Cfg = {
    url: string;             // Redis connection URL
    prefix?: string;         // Default: "auth"
    absTtlSeconds: number;   // Hard expiry for session lineage
};

Interface: SessionStore

export interface SessionStore {
    issueInitial(
        userId: UserId,
        input: Omit<VerifyRotateInput, 'rid'> & { absTtlSeconds: number }
    ): Promise<RefreshIssue>;

    verifyAndRotateRefresh(
        input: VerifyRotateInput
    ): Promise<{ userId: UserId; newRid: RefreshIssue }>;

    tombstoneLineage(rid: string): Promise<void>;

    logout(rid: string): Promise<void>;

    logoutDevice(userId: string, deviceId: string): Promise<void>;

    logoutAll(userId: UserId): Promise<void>;

    getRid(rid: string): Promise<Record<string, unknown> | null>;

    scanRids(userId: UserId): Promise<string[]>;

    gcOrphans(opts?: { dryRun?: boolean; graceSeconds?: number }): Promise<GCReport>;
}

Supporting Types

type UserId = string;

interface VerifyRotateInput {
    rid: string;
    now: number;
    deviceId?: string;
    uaHash?: string;
    ipHash?: string;
}

interface RefreshIssue {
    rid: string;
    absExp: number;
}

interface GCReport {
    scannedRids: number;
    removedRids: number;
    removedFromSets: number;
    dryRun?: boolean;
}

API Methods Overview

| Method | Description | |--------|--------------| | issueInitial(userId, input) | Creates a new refresh lineage (first RID) for a user. | | verifyAndRotateRefresh(input) | Atomically verifies and rotates an existing refresh RID, returning the new one. | | tombstoneLineage(rid) | Marks a lineage as inactive, used internally when invalidating refresh chains. | | logout(rid) | Logs out a single session (marks the RID tombstoned and removes it from its user set). | | logoutDevice(userId, deviceId) | Logs out all sessions linked to a specific device ID. | | logoutAll(userId) | Logs out all sessions belonging to a given user. | | getRid(rid) | Returns the stored metadata for a given RID, or null if missing. | | scanRids(userId) | Returns all active RIDs for the user. | | gcOrphans(opts) | Removes expired or orphaned RIDs from Redis safely. |


Redis Data Model

| Key | Type | Description | |-----|------|--------------| | auth:rid:<rid> | Hash | Stores session info and lineage metadata | | auth:user:<userId>:rids | Set | Tracks all RIDs belonging to a user | | auth:device:<userId>:<deviceId> | Set | Tracks RIDs associated with a specific device | | auth:... | Configurable prefix via prefix option |

RID Hash Fields

| Field | Meaning | |--------|----------| | userId | Owner of the session | | parentRid | Previous RID (for lineage tracking) | | createdAt | Creation time (epoch seconds) | | expAt | Rolling expiration (soft cap) | | absExpAt | Absolute expiration (hard cap) | | deviceId / uaHash / ipHash | Device binding info | | status | "active", "used", or "tombstoned" |


Garbage Collection

Call periodically to clean up expired or orphaned entries:

await store.gcOrphans({ graceSeconds: 60 });

Or dry-run mode for inspection:

const report = await store.gcOrphans({ dryRun: true });
console.log(report);

Recommended frequency: every hour via cron or worker.


Development

Start Redis with Docker:

pnpm dev:up

Inspect with RedisInsight at:

localhost:6379

Debug commands:

pnpm redis:issue
pnpm redis:get <rid>
pnpm redis:rotate <rid>
pnpm redis:gc

Shutdown Redis:

pnpm dev:down

License

MIT © 2025 Io .D (devcoons)