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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@access-tokens/client

v1.0.0

Published

Client library for personal access token authentication service

Readme

@access-tokens/client

npm License: ISC

Type-safe HTTP client for Personal Access Token (PAT) authentication services built with @access-tokens/express.

Features

  • Type-Safe API: Full TypeScript support with Zod schema validation
  • Automatic JWT Management: Handles PAT-to-JWT exchange and renewal
  • Retry Logic: Built-in exponential backoff with fetch-retry
  • Error Handling: Comprehensive error types with detailed messages
  • Admin Operations: Full token lifecycle management
  • Zero Dependencies: Uses native fetch (Node.js 18+)

Installation

npm install @access-tokens/client

Quick Start

import { AccessTokensClient } from "@access-tokens/client";

// Initialize client with PAT
const client = new AccessTokensClient({
  endpoint: "https://api.example.com",
  apiKey: "pat_abc123...", // Your Personal Access Token
});

// List all tokens (requires admin PAT)
const tokens = await client.list();
console.log("Total tokens:", tokens.length);

// Issue a new token
const { token, record } = await client.issue({
  owner: "[email protected]",
  isAdmin: false,
  expiresAt: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, // 1 year
});

console.log("New token:", token);
console.log("Token ID:", record.tokenId);

API Reference

Constructor

new AccessTokensClient(options)

Creates a new client instance.

Options:

{
  endpoint: string;        // Base URL of your PAT API (e.g., "https://api.example.com")
  apiKey: string;          // Your Personal Access Token
  authPath?: string;       // Auth endpoint path (default: "/auth")
  adminPath?: string;      // Admin endpoint path (default: "/admin")
  fetch?: typeof fetch;    // Custom fetch implementation (optional)
}

Example:

const client = new AccessTokensClient({
  endpoint: "https://api.example.com",
  apiKey: process.env.PAT_TOKEN!,
  authPath: "/auth", // optional, default is "/auth"
  adminPath: "/admin", // optional, default is "/admin"
});

Token Operations

All methods require an admin PAT unless otherwise noted.

list(options?): Promise<PatRecord[]>

Lists all tokens.

Options:

{
  includeRevoked?: boolean;     // Include revoked tokens (default: false)
  includeExpired?: boolean;     // Include expired tokens (default: false)
  includeSecretPhc?: boolean;   // Include secret hashes (default: false)
  limit?: number;               // Max results per page
  afterTokenId?: string;        // Pagination token (tokenId to start after)
}

Example:

// List all active tokens
const tokens = await client.list();

// List all tokens including revoked and expired
const allTokens = await client.list({
  includeRevoked: true,
  includeExpired: true,
});

// Paginated listing
const page1 = await client.list({ limit: 10 });
const page2 = await client.list({
  limit: 10,
  afterTokenId: page1[page1.length - 1].tokenId, // Use last token ID from previous page
});

issue(params): Promise<{ token: string; record: PatRecord }>

Issues a new token.

Parameters:

{
  owner: string;           // Token owner (e.g., email address)
  isAdmin?: boolean;       // Whether token has admin privileges (default: false)
  expiresAt?: number;      // Unix timestamp for expiration (optional)
}

Example:

const { token, record } = await client.issue({
  owner: "[email protected]",
  isAdmin: false,
  expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days
});

// Give token to user securely
console.log("Token (show once):", token);
console.log("Token ID:", record.tokenId);

register(params): Promise<TokenRecord>

Registers a pre-generated token.

Parameters:

{
  tokenId: string;         // Pre-generated token ID
  secretPhc: string;       // PHC-formatted secret hash
  owner: string;           // Token owner
  isAdmin?: boolean;       // Admin status (default: false)
  expiresAt?: number;      // Expiration timestamp (optional)
}

Example:

await client.register({
  tokenId: "pregenerated123",
  secretPhc: "$scrypt$...",
  owner: "[email protected]",
  isAdmin: false,
});

update(tokenId: string, updates): Promise<void>

Updates an existing token.

Updates:

{
  owner?: string;          // New owner
  isAdmin?: boolean;       // New admin status
  secretPhc?: string;      // New secret hash
  expiresAt?: number | null; // New expiration or null to remove
}

Example:

// Promote user to admin
await client.update("34NwRzvnBbgI3uedkrQ3Q", {
  isAdmin: true,
});

// Change owner
await client.update("34NwRzvnBbgI3uedkrQ3Q", {
  owner: "[email protected]",
});

// Remove expiration
await client.update("34NwRzvnBbgI3uedkrQ3Q", {
  expiresAt: null,
});

revoke(tokenId: string, options?: { expiresAt?: number }): Promise<void>

Revokes a token. Optionally sets an expiration for automatic cleanup.

Example:

// Revoke immediately
await client.revoke("34NwRzvnBbgI3uedkrQ3Q");

// Revoke with cleanup in 30 days
await client.revoke("34NwRzvnBbgI3uedkrQ3Q", {
  expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
});

restore(tokenId: string): Promise<void>

Restores a previously revoked token.

Example:

await client.restore("34NwRzvnBbgI3uedkrQ3Q");

JWT Token Exchange

The client automatically handles JWT token exchange:

  1. On first API call, the client exchanges your PAT for a JWT
  2. JWT is used for subsequent requests
  3. When JWT expires (default: 1 hour), client automatically requests a new one
  4. Your PAT is only sent during token exchange

This design provides:

  • Performance: No DynamoDB lookup and scrypt operation on every request
  • Security: Short-lived JWTs reduce risk if compromised
  • Transparency: Automatic, no manual JWT management required

Error Handling

The client throws standard Error objects. The error.cause property may contain additional details:

import { AccessTokensClient, isApiError } from "@access-tokens/client";

try {
  await client.revoke("invalid-token-id");
} catch (error) {
  if (error instanceof Error) {
    console.error("Message:", error.message); // "Failed to revoke token"

    // error.cause may be an ApiError object or a string
    if (isApiError(error.cause)) {
      console.error("API Error:", error.cause.error.message);
      console.error("Code:", error.cause.error.code);
      console.error("Details:", error.cause.error.details);
    } else if (typeof error.cause === "string") {
      console.error("Status text:", error.cause);
    }
  }
}

Common Error Status Codes:

  • 400 - Bad request (invalid parameters)
  • 401 - Unauthorized (invalid or missing PAT/JWT)
  • 403 - Forbidden (insufficient permissions, not admin)
  • 404 - Not found (token doesn't exist)
  • 500 - Internal server error

Retry Behavior

The client uses exponential backoff for retries:

  • Retryable errors: 408, 429, 500, 502, 503, 504 status codes
  • Non-retryable: All other status codes
  • Fixed policy: 3 retries with exponential backoff starting at 1s, capped at 30s
  • 429 Rate Limits: Respects Retry-After header when present

The retry policy is built-in and cannot be customized. If you need custom retry behavior, provide your own fetch implementation in the constructor options.

Types

PatRecord

interface PatRecord {
  tokenId: string; // Unique token identifier (21 chars, alphanumeric)
  owner: string; // Token owner
  isAdmin: boolean; // Admin privileges
  secretPhc?: string; // PHC hash (only if includeSecretPhc=true)
  createdAt: number; // Unix timestamp
  lastUsedAt?: number | null; // Unix timestamp of last use
  expiresAt?: number | null; // Unix timestamp for expiration
  revokedAt?: number | null; // Unix timestamp when revoked (null if not revoked)
}

ApiError

When API errors occur, they are provided as the cause property of the thrown Error:

type ApiError = {
  error: {
    message: string; // Error message
    code?: string; // Optional error code
    details?: string | Record<string, unknown>; // Additional error details
  };
};

Usage:

import { isApiError } from "@access-tokens/client";

try {
  await client.list();
} catch (error) {
  if (error instanceof Error && isApiError(error.cause)) {
    console.error(error.cause.error.message);
  }
}

Requirements

  • Node.js 20+ (native fetch support)
  • @access-tokens/express server

Related Packages

License

ISC © 2025 Loan Crate, Inc.

Links