SessionTether API
import { SessionTether, SessionStatus } from 'session-tether';Use the exported SessionStatus enum for status comparisons, such as result.status === SessionStatus.GRACE. Its values remain strings (valid, grace, rotated, missing, revoked, consumed, expired) for serialization.
The core manages refresh sessions. It does not create or verify JWT access tokens. All durations below are in milliseconds.
Constructor
new SessionTether({ store, expiryTTL, reuseGraceTTL: 30_000 })| Option | Type | Meaning |
|---|---|---|
store | SessionStore | Required persistence adapter |
expiryTTL | number | Required lifetime for new and replacement tokens |
reuseGraceTTL | number | Optional consumed-token retry window; defaults to 30,000 ms. Set to 0 for immediate reuse rejection. |
reuseGraceTTL must be a non-negative safe integer or the constructor throws RangeError. The constructor does not validate expiryTTL; provide a finite positive lifetime appropriate for your application. There is no default expiry TTL.
createSession
createSession(input: {
userId: UUID;
sessionName?: string;
}): Promise<string>Creates a family and initial refresh token atomically in both SQL adapters. If either insert fails, the operation throws and neither record is committed. Returns the raw refresh-token string, not an object with access and refresh tokens. UUID is Node's node:crypto UUID type. SQLite defaults a missing session name to an empty string.
validateRefreshToken
validateRefreshToken(refreshToken: string): Promise<
| { status: SessionStatus.VALID; userId: UUID; familyId: string }
| { status: SessionStatus.MISSING }
| { status: SessionStatus.REVOKED }
| { status: SessionStatus.CONSUMED }
| { status: SessionStatus.EXPIRED }
>Checks existence and revocation first. A consumed token is accepted as valid while less than reuseGraceTTL milliseconds have elapsed since its original consumedAt, provided it has not expired. At or after that boundary, reuse revokes the family and returns consumed. An invalid expiration string is treated as expired. Retries do not extend the grace window or expiration.
rotateRefreshToken
rotateRefreshToken(refreshToken: string): Promise<
| { status: SessionStatus.ROTATED; refreshToken: string; userId: UUID }
| { status: SessionStatus.GRACE; userId: UUID }
| { status: SessionStatus.MISSING }
| { status: SessionStatus.REVOKED }
| { status: SessionStatus.CONSUMED }
| { status: SessionStatus.EXPIRED }
>Validates first, then generates and stores a replacement. SQL adapters set replacement expiration to the locked consumption decision timestamp plus expiryTTL. Replace the client's credential on rotated. A consumed-token retry within grace returns grace without another replacement or store mutation. Preserve the replacement received from the successful rotation; the store retains only hashes and cannot recover that credential if its response was lost. Both SQL adapters conditionally consume an unconsumed predecessor and insert its successor in one transaction. Only the winning update inserts a successor; an insertion failure rolls back consumption. Core catches TokenAlreadyConsumedError from a losing attempt, rechecks current token state, and applies the grace/reuse policy. Other storage errors propagate. Rotation now locks an active family before consuming its token, ordering the transaction 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.
revokeSession
revokeSession(familyTokenHash: string): Promise<void>Pass the stable family identifier from validateRefreshToken().familyId or a session listing's tokenHash. The current source names this parameter refreshToken, but forwards it without hashing; SQLite expects the family identifier. Passing a raw token will not select the intended family.
revokeAllSessionsForUser
revokeAllSessionsForUser(userId: UUID): Promise<void>Delegates to store.revokeAllForUser(). SQLite marks all unrevoked families for the user as revoked. Issued access tokens are unaffected.
listAllSessionsForUser
listAllSessionsForUser(userId: UUID): Promise<
ReadonlyArray<{ tokenHash: string; session_name: string }>
>Returns all retained families, including revoked and expired ones. Note the snake-case session_name field.
listActiveSessionsForUser
listActiveSessionsForUser(userId: UUID): Promise<
ReadonlyArray<{ tokenHash: string; sessionName: string }>
>Returns non-revoked families containing an unconsumed, unexpired token. Note the camel-case sessionName field.
pruneExpiredSessions
pruneExpiredSessions(options?: {
retentionTTL?: number;
}): Promise<{ deletedFamilies: number; deletedTokens: number }>Defaults to seven days of retention. Invalid retention values throw RangeError synchronously. See cleanup for eligibility rules.
Errors and exported types
Database errors propagate as exceptions; status results are validation outcomes, not a complete error channel. Core handles TokenAlreadyConsumedError by rechecking token state. The result shapes above describe the implementation; named ValidationResult and RotationResult aliases are not exported by the root entry point.
| Export | Purpose |
|---|---|
SessionTether | Session lifecycle manager |
SessionStatus | Validation and rotation status enum |
TokenAlreadyConsumedError | Store conflict signal handled by the core |
SessionRevokedError | Store revocation signal mapped to SessionStatus.REVOKED |
SessionStore | Persistence interface, type only |
RefreshTokenDetails | Token lookup shape, type only |
SessionSummary | Active family summary, type only |
PruneResult | Cleanup counts, type only |
