Node.js SDK
Installation
Section titled “Installation”pnpm add @chronos.sh/sdknpm install @chronos.sh/sdkyarn add @chronos.sh/sdk- Requires Node.js 20+
- Dual module: ESM (
import) and CJS (require) both supported - Zero runtime dependencies
Chronos
Section titled “Chronos”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!,});Constructor
Section titled “Constructor”new Chronos(options: ChronosOptions)Throws ChronosConfigError if any option fails validation.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
worker |
Worker |
Read-only. The long-poll worker instance. All functionality lives here. |
Methods
Section titled “Methods”None. Chronos is a composition root. Use chronos.worker for all operations.
Worker
Section titled “Worker”Accessed via chronos.worker. Not directly importable.
.handle<TPayload>(name, handler)
Section titled “.handle<TPayload>(name, handler)”handle<TPayload = unknown>(name: string, handler: ChronosHandler<TPayload>): thisRegister 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()
Section titled “.start()”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()
Section titled “.stop()”stop(): Promise<void>Request graceful shutdown.
- Aborts the current long-poll immediately
- Waits for any in-flight handler to finish and report its result
- Resolves when complete
If not running, returns a resolved promise. Never throws.
ChronosOptions
Section titled “ChronosOptions”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. |
ChronosContext<TPayload>
Section titled “ChronosContext<TPayload>”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. |
ChronosSchedule
Section titled “ChronosSchedule”type ChronosSchedule = { id: string; name: string;};RateLimitInfo
Section titled “RateLimitInfo”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.
ChronosHandler<TPayload>
Section titled “ChronosHandler<TPayload>”type ChronosHandler<TPayload = unknown> = ( ctx: ChronosContext<TPayload>,) => ChronosHandlerResult | Promise<ChronosHandlerResult>;Can be sync or async.
ChronosHandlerResult
Section titled “ChronosHandlerResult”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
ChronosLogger
Section titled “ChronosLogger”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), },});FetchLike
Section titled “FetchLike”type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;Compatible with globalThis.fetch. Use cases: inject tracing headers, custom timeouts, test mocking.
Webhook
Section titled “Webhook”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);Constructor
Section titled “Constructor”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, headers)
Section titled “.verify(body, headers)”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.
WebhookOptions
Section titled “WebhookOptions”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. |
DEFAULT_TIMESTAMP_TOLERANCE_SECONDS
Section titled “DEFAULT_TIMESTAMP_TOLERANCE_SECONDS”const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;The default tolerance applied when toleranceSeconds is not specified.
Error hierarchy
Section titled “Error hierarchy”Error└── ChronosError ├── ChronosConfigError ├── ChronosApiError │ └── ChronosRateLimitError ├── ChronosNetworkError ├── ChronosHandlerError └── ChronosWebhookVerificationErrorChronosError
Section titled “ChronosError”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.
ChronosConfigError
Section titled “ChronosConfigError”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'
ChronosApiError
Section titled “ChronosApiError”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}`); }}ChronosRateLimitError
Section titled “ChronosRateLimitError”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.
ChronosNetworkError
Section titled “ChronosNetworkError”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).
ChronosHandlerError
Section titled “ChronosHandlerError”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.
ChronosWebhookVerificationError
Section titled “ChronosWebhookVerificationError”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); }}Exports
Section titled “Exports”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 |