Skip to content

SessionStore ​

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

Implement this interface to supply persistence. The following expands nested shapes for clarity and mirrors src/type.ts.

Session creation ​

ts
createSession(input: {
  userId: UUID;
  tokenHash: string;
  expiresAt: number;
  sessionName?: string;
}): Promise<void>;

Persist the initial hash and session family atomically. If either write fails, leave neither record from the attempt committed and propagate the error. Both SQL adapters use one transaction for these inserts. expiresAt is an epoch timestamp in milliseconds.

Token lookup ​

ts
findToken(tokenHash: string): Promise<{
  readonly tokenHash: string;
  readonly consumedAt: number | null;
  readonly expiresAt: string;
  readonly family: {
    readonly tokenHash: string;
    readonly userId: UUID;
    readonly revokedAt: number | null;
    readonly sessionName: string;
  };
} | null>;

Return null for a missing token. The family tokenHash is stable across rotation. The expiration string must be parseable by new Date(); SQLite uses ISO format.

Token replacement ​

ts
consumeAndReplace(input: {
  oldTokenHash: string;
  newTokenHash: string;
  newExpiresAt: number;
  consumedAt: number;
}): Promise<void>;

Conditionally consume an unconsumed predecessor and persist its replacement in the same family and transaction. Input timestamps are milliseconds. If another rotation already consumed the token, throw the exported TokenAlreadyConsumedError without inserting another successor or changing the original consumption timestamp. Core rechecks token state and applies the grace/reuse policy; other database errors propagate. The return type remains Promise<void>.

Synchronize family eligibility with both revocation methods. Throw the exported SessionRevokedError if revocation wins, leaving consumption and successor state unchanged. The core returns SessionStatus.REVOKED. SQL adapters acquire a family write lock with a conditional no-op update before consumption and hold it through commit; custom stores must provide equivalent ordering.

Under that lock, read the predecessor and sample a fresh Date.now() as the consumption decision point. Reject invalid expiration or expiresAt <= now with the exported TokenExpiredError; Core returns SessionStatus.EXPIRED. Leave consumption, successor, and revocation state unchanged on rejection. Expiration after this decision does not abort commit. There is no expiration grace period. Persist this sample as consumedAt and set successor expiration to that sample plus the requested lifetime (input.newExpiresAt - input.consumedAt), so waits do not shorten the successor lifetime or reuse grace. Input timestamps describe the requested lifetime, not a trusted eligibility time. SQL adapters use the application clock; keep clocks synchronized across application instances.

Revocation ​

ts
revokeSession(tokenHash: string): Promise<void>;
revokeAllForUser(userId: UUID): Promise<void>;

The first method takes a family identifier. Revocation must apply to all tokens in that family. SQLite records a millisecond timestamp and leaves already-revoked rows unchanged.

Session queries ​

ts
findSessionFamilies(userId: UUID): Promise<
  ReadonlyArray<{ tokenHash: string; session_name: string }>
>;

findActiveSessionFamilies(userId: UUID): Promise<
  ReadonlyArray<{ readonly tokenHash: string; readonly sessionName: string }>
>;

Active means the family is not revoked and has an unconsumed, unexpired token. Keep the field-name difference when implementing the current interface.

Pruning ​

ts
pruneSessionFamilies(inactiveBefore: number): Promise<{
  readonly deletedFamilies: number;
  readonly deletedTokens: number;
}>;

Delete families revoked by the cutoff, or unrevoked families whose tokens have all expired by the cutoff. Preserve entire histories for retained families. The cutoff is epoch milliseconds; the core subtracts the retention duration before calling this method.

The root entry point exports all four store-related types:

ts
import type {
  SessionStore,
  RefreshTokenDetails,
  SessionSummary,
  PruneResult,
} from 'session-tether';

RefreshTokenDetails is the token-lookup result, SessionSummary is an active family summary, and PruneResult contains cleanup counts. Implementing the type signatures alone does not establish the concurrency contract; see custom stores.

Built for explicit authentication boundaries. Powered by VitePress.