Skip to content

Node.js SDK

Terminal window
pnpm add @chronos.sh/sdk
  • Requires Node.js 20+
  • Dual module: ESM (import) and CJS (require) both supported
  • Zero runtime dependencies

The top-level client. The only class you construct directly.

import { Chronos } from '@chronos.sh/sdk';
const chronos = new Chronos({
apiKey: process.env.CHRONOS_API_KEY!,
});
new Chronos(options: ChronosOptions)

Throws ChronosConfigError if any option fails validation.

Property Type Description
worker Worker Read-only. The long-poll worker instance. All functionality lives here.

None. Chronos is a composition root. Use chronos.worker for all operations.

Accessed via chronos.worker. Not directly importable.

handle<TPayload = unknown>(name: string, handler: ChronosHandler<TPayload>): this

Register a handler for a named job type. Returns this for chaining.

Parameter Type Description
name string Job type name. Trimmed internally. Must be 1–255 characters.
handler ChronosHandler<TPayload> Async or sync function that processes the job.

Throws ChronosError:

  • 'Handler name is required': empty or whitespace-only name
  • 'Handler name must be 255 characters or fewer'
  • 'Handler "{name}" is already registered': duplicate name
  • 'Handler "{name}" must be a function': non-function passed
start(): Promise<void>

Begin long-polling for jobs. The returned promise resolves when stop() is called and all in-flight work completes.

Throws ChronosError (synchronously):

  • 'Chronos worker is already started'
  • 'Register at least one handler before starting Chronos'
stop(): Promise<void>

Request graceful shutdown.

  1. Aborts the current long-poll immediately
  2. Waits for any in-flight handler to finish and report its result
  3. Resolves when complete

If not running, returns a resolved promise. Never throws.

type ChronosOptions = {
apiKey: string;
baseUrl?: string;
fetch?: FetchLike;
logger?: ChronosLogger;
pollWaitTimeSeconds?: number;
retryDelayMs?: number;
};
Option Type Default Validation
apiKey string — (required) Trimmed. Throws ChronosConfigError if empty.
baseUrl string 'https://api.chronos.sh' Trimmed, trailing slashes stripped. Throws if empty after trim.
fetch FetchLike globalThis.fetch None.
logger ChronosLogger Console-backed logger None.
pollWaitTimeSeconds number 20 Integer, 0–20 inclusive. Throws ChronosConfigError.
retryDelayMs number 1000 Finite, non-negative. Throws ChronosConfigError.
type ChronosContext<TPayload = unknown> = {
jobId: string;
executionId: string;
handler: string;
payload: TPayload;
scheduledFor: Date;
attempt: number;
timeout: number;
signal: AbortSignal;
schedule: ChronosSchedule | null;
};
Field Type Description
jobId string Stable job identifier. Same across retries. Use for idempotency keys in downstream calls.
executionId string Unique to this execution attempt. Use for per-attempt logs, metrics, and correlation.
handler string Handler name that matched this job.
payload TPayload Job payload. Typed via the generic on .handle<TPayload>().
scheduledFor Date When the job was originally scheduled. Parsed from ISO string.
attempt number Attempt number. 1 on first try, increments on retries.
timeout number Timeout for the handler in seconds. The SDK races the handler against this duration and aborts ctx.signal when it elapses.
signal AbortSignal Fires when timeout elapses. Pass to fetch, AbortSignal.any(), etc.
schedule ChronosSchedule | null Parent schedule, or null for one-off jobs.
type ChronosSchedule = {
id: string;
name: string;
};
type RateLimitInfo = {
limit: number;
remaining: number;
reset: number;
};
Field Type Description
limit number Maximum requests allowed per window.
remaining number Requests remaining in the current window.
reset number Seconds until the current window resets.

Attached to ChronosApiError.rateLimit on non-429 error responses. All three X-RateLimit-* headers must be present; if any is missing, rateLimit is undefined.

type ChronosHandler<TPayload = unknown> = (
ctx: ChronosContext<TPayload>,
) => ChronosHandlerResult | Promise<ChronosHandlerResult>;

Can be sync or async.

type ChronosHandlerResult = Record<string, unknown> | void;
Return value Effect
Plain object Recorded as execution result. Must be JSON-serializable.
undefined / void Execution marked completed, no result data.

Rejected at runtime (throws ChronosError):

  • Arrays
  • Class instances (non-plain-object prototypes)
  • Non-JSON-serializable values
type ChronosLogger = {
debug(message: string, meta?: Record<string, unknown>): void;
info(message: string, meta?: Record<string, unknown>): void;
warn(message: string, meta?: Record<string, unknown>): void;
error(message: string, meta?: Record<string, unknown>): void;
};

Signature is (message, meta?): message first, metadata second.

Adapting pino (which uses obj, message order):

import pino from 'pino';
const log = pino();
const chronos = new Chronos({
apiKey: process.env.CHRONOS_API_KEY!,
logger: {
debug: (msg, meta) => log.debug(meta ?? {}, msg),
info: (msg, meta) => log.info(meta ?? {}, msg),
warn: (msg, meta) => log.warn(meta ?? {}, msg),
error: (msg, meta) => log.error(meta ?? {}, msg),
},
});
type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;

Compatible with globalThis.fetch. Use cases: inject tracing headers, custom timeouts, test mocking.

Verifies signatures on push deliveries. Standalone: does not require a Chronos client or API key.

import { Webhook } from '@chronos.sh/sdk';
const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
const payload = await wh.verify(rawBody, req.headers);
new Webhook(secret: string | string[], options?: WebhookOptions)
Parameter Type Description
secret string | string[] Signing secret or array of secrets for key rotation. Blank/whitespace strings are filtered out.
options WebhookOptions Optional configuration.

Throws ChronosWebhookVerificationError if no valid (non-empty) secret is provided, or if toleranceSeconds is invalid.

verify(body: string, headers: Record<string, string | undefined> | Headers): Promise<unknown>

Verifies the signature and returns the parsed JSON body on success. Throws ChronosWebhookVerificationError on any failure.

Parameter Type Description
body string Raw request body. Must be the exact bytes received, not re-serialized.
headers Record<string, string | undefined> | Headers Request headers. Keys are matched case-insensitively. Accepts plain objects or the Web standard Headers class.

The method is async because it uses the Web Crypto API (crypto.subtle). Works in Node.js 18+, Bun, Deno, and edge runtimes.

Crypto keys are lazily imported on the first verify() call and cached for subsequent calls.

type WebhookOptions = {
toleranceSeconds?: number;
};
Option Type Default Description
toleranceSeconds number 300 Timestamp tolerance in seconds. Rejects requests with timestamps more than this many seconds from now (in either direction). Set to 0 to disable replay protection entirely. Must be finite and non-negative.
const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;

The default tolerance applied when toleranceSeconds is not specified.

Error
└── ChronosError
├── ChronosConfigError
├── ChronosApiError
│ └── ChronosRateLimitError
├── ChronosNetworkError
├── ChronosHandlerError
└── ChronosWebhookVerificationError
class ChronosError extends Error {
readonly cause?: unknown;
constructor(message: string, options?: { cause?: unknown });
}

Base class for all SDK errors. Catch this to handle any SDK error generically.

Thrown for operational issues: duplicate handler names, invalid return values, calling start() twice.

class ChronosConfigError extends ChronosError {
constructor(message: string);
}

Invalid options passed to new Chronos(). Only thrown during construction. If the constructor succeeds, config is valid.

Messages:

  • 'Chronos apiKey is required'
  • 'Chronos baseUrl is required'
  • 'pollWaitTimeSeconds must be an integer between 0 and 20'
  • 'retryDelayMs must be a non-negative number'
class ChronosApiError extends ChronosError {
readonly status: number;
readonly code?: string;
readonly body?: unknown;
readonly requestId?: string;
readonly retryAfterSeconds?: number;
readonly rateLimit?: RateLimitInfo;
constructor(message: string, options: ChronosApiErrorOptions);
}
Property Type Description
status number HTTP status code. Can be 200 if the API returned { success: false }.
code string | undefined Application error code (e.g., 'rate_limit_exceeded').
body unknown Full parsed response body.
requestId string | undefined Value of the X-Request-Id response header.
retryAfterSeconds number | undefined Seconds to wait before retrying. Parsed from the Retry-After header on 429 responses.
rateLimit RateLimitInfo | undefined Rate-limit budget from X-RateLimit-* headers. Present on non-429 error responses when all three headers exist.

Thrown when the API responds with a non-2xx status or a 2xx response with { success: false }. For 429 specifically, the SDK throws the ChronosRateLimitError subclass instead.

try {
await chronos.worker.start();
} catch (err) {
if (err instanceof ChronosApiError) {
console.error(`API error ${err.status}: ${err.code}`);
}
}
class ChronosRateLimitError extends ChronosApiError {
constructor(message: string, options: ChronosApiErrorOptions);
}

Thrown on 429 Too Many Requests. Subclass of ChronosApiError, so existing instanceof ChronosApiError catches still work.

retryAfterSeconds is set when the response includes a Retry-After header; otherwise undefined. rateLimit is always undefined on 429.

import { ChronosRateLimitError } from '@chronos.sh/sdk';
try {
await chronos.worker.start();
} catch (err) {
if (err instanceof ChronosRateLimitError && err.retryAfterSeconds) {
console.warn(`Rate limited. Retry after ${err.retryAfterSeconds}s`);
}
}

The worker handles 429s automatically. See Retries & Error Handling for details.

class ChronosNetworkError extends ChronosError {
readonly cause: unknown;
constructor(message: string, options: { cause: unknown });
}

Thrown when the HTTP request fails before reaching the server: DNS resolution failure, TCP connection refused, TLS errors.

cause contains the original error from fetch.

Not thrown for abort signals. If stop() aborts a poll, the raw abort error propagates (not wrapped in ChronosNetworkError).

class ChronosHandlerError extends ChronosError {
readonly cause: unknown;
constructor(message: string, options: { cause: unknown });
}

Wraps any exception thrown inside your handler. The message is copied from the original error (or stringified). If empty, defaults to 'Chronos handler failed'.

This error is never rethrown to your code. It’s logged internally and the failure is reported to the API. The error message (truncated to 4KB) is sent as the execution failure reason.

class ChronosWebhookVerificationError extends ChronosError {
constructor(message: string);
}

Thrown by Webhook when any verification step fails: missing headers, invalid timestamp, bad signature format, wrong secret, or non-JSON body. Also thrown by the Webhook constructor if no valid secret is provided.

The message indicates the specific failure. See Signature Verification for the full list of messages.

import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
try {
const payload = await wh.verify(rawBody, headers);
} catch (err) {
if (err instanceof ChronosWebhookVerificationError) {
console.error('Verification failed:', err.message);
}
}

Every named export from @chronos.sh/sdk:

Export Kind Description
Chronos Class Top-level client. The only class you construct.
ChronosError Class Base error class for all SDK errors.
ChronosConfigError Class Invalid constructor options.
ChronosApiError Class API responded with an error.
ChronosRateLimitError Class API responded with 429. Subclass of ChronosApiError.
ChronosApiErrorOptions Type Options for constructing ChronosApiError.
RateLimitInfo Type Rate-limit budget (limit, remaining, reset).
ChronosNetworkError Class Network/transport failure.
ChronosHandlerError Class Handler threw an exception.
ChronosWebhookVerificationError Class Webhook signature verification failed.
Webhook Class Webhook signature verifier for push deliveries.
ChronosContext Type Context object passed to handlers.
ChronosHandler Type Handler function signature.
ChronosHandlerResult Type Valid handler return types.
ChronosLogger Type Logger interface.
ChronosOptions Type Constructor options.
ChronosSchedule Type Schedule reference on context.
FetchLike Type Custom fetch signature.
WebhookOptions Type Options for Webhook constructor.
DEFAULT_BASE_URL Constant 'https://api.chronos.sh'
DEFAULT_POLL_WAIT_TIME_SECONDS Constant 20
DEFAULT_TIMESTAMP_TOLERANCE_SECONDS Constant 300