Skip to content

Custom stores ​

Persistence is separated from session logic through the exported SessionStore interface.

ts
import { SessionTether, type SessionStore } from 'session-tether';

function createSessions(store: SessionStore) {
  return new SessionTether({
    store,
    expiryTTL: 30 * 24 * 60 * 60 * 1000,
  });
}

SQLite and PostgreSQL adapters are included. MongoDB, Redis, and memory implementations remain outside the current package.

Preserve family semantics ​

  • Persist the hash supplied to createSession(); do not require raw refresh tokens.
  • Commit the initial family and token together, leaving neither on failure.
  • Keep a stable family identifier through token replacements.
  • Return token state and family revocation state together from findToken().
  • Make family revocation apply to all tokens in that family.
  • Keep consumed history for retained families so replay remains detectable.

Match the current contract ​

The interface intentionally exposes both findSessionFamilies() and findActiveSessionFamilies(). Their field names differ: all-family results use session_name, while active-family results use sessionName.

Expiration enters the store as epoch milliseconds and comes back from findToken() as a string parsed by new Date(expiresAt). SQLite uses ISO strings.

Define concurrency behavior ​

An adapter must make consumption exclusive and commit it atomically with successor insertion. consumeAndReplace() returns Promise<void> and must throw the exported TokenAlreadyConsumedError when another rotation has already consumed the predecessor. Core rechecks state and applies grace/reuse policy to that conflict; other errors propagate.

Coordinate rotation with revokeSession() and revokeAllForUser(). Revocation ordered first must prevent consumption and successor creation; signal this with SessionRevokedError. The core maps that error directly to revoked. Rotation ordered first may commit, after which revocation must cover its successor.

The included SQL adapters use conditional consumption and transactional insertion to prevent duplicate successors. They lock an active family before consuming a token, synchronizing rotation with family and user-wide revocation. Expiration is rechecked under the family lock using a fresh application-clock timestamp as the consumption decision point. At or after expiry, TokenExpiredError maps to expired without consuming the token or creating a successor. No expiration grace applies; expiry after the decision does not abort commit.

Cleanup invariants ​

pruneSessionFamilies(cutoff) removes families revoked by the cutoff, or unrevoked families whose tokens have all expired by the cutoff. Preserve every token in retained families. Empty unrevoked families have no timestamp from which retention can be measured and are retained by the SQLite implementation.

See the complete SessionStore reference.

Conformance requirements ​

An adapter's database mechanism can vary, but its externally observable safety contract must remain consistent. Read Policies & Guarantees for the required invariants and proposed shared contract tests. The current interface and tests do not yet establish a conformance certification process.

Built for explicit authentication boundaries. Powered by VitePress.