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; 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 | Soft timeout in seconds. Informational only. The SDK does not enforce it. |
schedule | ChronosSchedule | null | Parent schedule, or null for one-off jobs. |
ChronosSchedule
Section titled “ChronosSchedule”type ChronosSchedule = { id: string; name: string;};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.
Error hierarchy
Section titled “Error hierarchy”Error└── ChronosError ├── ChronosConfigError ├── ChronosApiError ├── ChronosNetworkError └── ChronosHandlerErrorChronosError
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; 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. |
Thrown when the API responds with a non-2xx status or a 2xx response with { success: false }.
try { await chronos.worker.start();} catch (err) { if (err instanceof ChronosApiError) { console.error(`API error ${err.status}: ${err.code}`); }}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.
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. |
ChronosApiErrorOptions | Type | Options for constructing ChronosApiError. |
ChronosNetworkError | Class | Network/transport failure. |
ChronosHandlerError | Class | Handler threw an exception. |
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. |
DEFAULT_BASE_URL | Constant | 'https://api.chronos.sh' |
DEFAULT_POLL_WAIT_TIME_SECONDS | Constant | 20 |