@rotorsh/sdk
v0.1.3
Published
Node.js SDK for Rotor — typed REST client + BullMQ Worker wrapper for Claude Code-native GTM engineers.
Maintainers
Readme
@rotorsh/sdk
Node.js SDK for Rotor — typed REST client + BullMQ Worker wrapper for Claude Code-native GTM engineers.
60-second quickstart — Node REST client
# 1. Install (peerDependencies are needed only if you'll run a Worker; REST-only callers can skip them)
npm i @rotorsh/sdkimport { Rotor } from "@rotorsh/sdk";
const rotor = new Rotor({ apiKey: process.env.ROTOR_API_KEY! }); // rt_ws_*
// Enqueue a job with a deterministic id (idempotent — see below)
const { jobId } = await rotor.jobs.enqueue("outbound", {
payload: { contactId: "c_123", campaignId: "spring-2026" },
jobId: `send:c_123:spring-2026`,
});
// Verify a callback signature inside your callback handler
import { verifyRotorSignature } from "@rotorsh/sdk";
const ok = verifyRotorSignature(rawBody, sigHeader, secret, { id, timestamp });That's it for the REST client. For Worker support and full reference, see → docs.rotor.sh/guides/node-quickstart.
Install
# peerDependencies: bullmq + ioredis must be installed alongside @rotorsh/sdk
pnpm add @rotorsh/sdk bullmq ioredisQuick Start — REST client
import { Rotor } from "@rotorsh/sdk";
const rotor = new Rotor({ apiKey: process.env.ROTOR_API_KEY! });
// Enqueue a job with a deterministic jobId (idempotency — see below)
const { jobId } = await rotor.jobs.enqueue("outbound", {
payload: { contactId: "c_123", campaignId: "spring-2026" },
jobId: `send:c_123:spring-2026`, // deterministic — prevents duplicate sends
});
// Batch enqueue
const { enqueued, jobIds } = await rotor.jobs.batch("outbound", [
{ payload: { contactId: "c_1", campaignId: "spring" }, jobId: "send:c_1:spring" },
{ payload: { contactId: "c_2", campaignId: "spring" }, jobId: "send:c_2:spring" },
]);
// List queues
const queues = await rotor.queues.list();
// Create a schedule
await rotor.schedules.create({
queueName: "digest",
name: "morning-digest",
cron: "0 9 * * 1-5",
timezone: "America/New_York", // REQUIRED
jobData: { type: "morning" },
});Quick Start — Worker
import { RotorWorker } from "@rotorsh/sdk";
import { createBlockingConnection } from "rotor-core";
const connection = createBlockingConnection(process.env.REDIS_URL!);
const worker = new RotorWorker({
workspaceId: process.env.WORKSPACE_ID!,
queueName: "outbound",
connection,
processor: async (job) => {
// Your job handler here
return { status: "done" };
},
});
worker.on("completed", (job, result) => console.log("done", job.id, result));
worker.on("failed", (job, err) => console.error("fail", job?.id, err.message));
worker.on("stalled", (jobId) => console.warn("stalled", jobId));IMPORTANT: At-Least-Once Delivery
BullMQ delivers jobs at-least-once. Your handler MUST be idempotent.
When a worker crashes mid-job or a pod restarts, BullMQ redelivers the job. Without idempotency guards, you will send duplicate emails, create duplicate records, or charge customers twice.
Two-layer defense:
Deterministic jobId at enqueue time prevents re-enqueue of the same logical job:
jobId: `send:${contactId}:${campaignId}` // stable, unique per logical operationSend log check in the handler catches redeliveries that slip through:
processor: async (job) => { const { contactId, campaignId } = job.data; const alreadySent = await db.sends.findUnique({ where: { contactId_campaignId: { contactId, campaignId } }, }); if (alreadySent) return { status: "skipped" }; // idempotent early return // ... do the work }
See docs.rotor.sh/guides/node-quickstart for a complete idempotent worker example.
SIGTERM / Graceful Drain
Workers automatically drain on SIGTERM and SIGINT via worker.close(). In-flight jobs complete before the process exits.
Ensure your deploy platform's grace period is >= your max job duration.
Railway default: 30s — works well for sub-5s jobs. For longer jobs, set RAILWAY_SHUTDOWN_TIMEOUT.
BullMQ Version Check
On new RotorWorker(...), the SDK reads rotor:server:bullmq_version from Redis (written by the Rotor API at startup) and rejects with BullMQVersionMismatchError if the SDK's bundled BullMQ minor version differs from the server's.
Why minor? BullMQ uses Lua scripts stored in Redis. A minor version bump may change script hashes, causing NOSCRIPT errors or silent behavior changes.
To upgrade: Update @rotorsh/sdk and the Rotor API simultaneously and redeploy.
BullMQ version mismatch: SDK=5.73.1, server=5.85.0.
Update the server or the SDK.You can skip the check during tests with skipVersionCheck: true.
Public API Reference
Full API documentation: api.rotor.sh/docs
new Rotor(opts: RotorOptions)
| Option | Type | Required | Default |
|--------|------|----------|---------|
| apiKey | string | Yes | — |
| baseUrl | string | No | https://api.rotor.sh |
| fetch | typeof fetch | No | globalThis.fetch |
Resources:
rotor.queues— list, create, get, update, delete, pause, resume, drain, retryFailedrotor.jobs— enqueue, batch, list, get, delete, logs, retryrotor.schedules— list, create, get, deleterotor.status— getrotor.usage— get
new RotorWorker(opts: RotorWorkerOptions)
| Option | Type | Required | Default |
|--------|------|----------|---------|
| workspaceId | string | Yes | — |
| queueName | string | Yes | — |
| connection | IORedis \| string | Yes | — |
| processor | Processor | Yes | — |
| concurrency | number | No | 10 |
| skipVersionCheck | boolean | No | false |
Methods: ready(), drain()
Events: completed(job, result), failed(job, err), stalled(jobId)
Errors
| Class | HTTP Status | When |
|-------|-------------|------|
| RotorAuthError | 401 | Invalid or missing API key |
| RotorQuotaError | 429 | Monthly execution cap reached |
| RotorValidationError | 422 | Request validation failed |
| RotorApiError | 5xx | Server error |
| BullMQVersionMismatchError | — | Worker connect: minor version mismatch |
Type Generation
Types in src/generated/api.d.ts are generated from the Rotor OpenAPI spec:
ROTOR_API_URL=https://api.rotor.sh pnpm --filter @rotorsh/sdk generate-typesThe snapshot is committed to the repo. Regenerate post-deploy when the API schema changes.
Examples
- Idempotent worker pattern — Canonical idempotent handler with send log check
- Batch enqueue with deterministic jobIds — Batch enqueue walkthrough
- Cron schedule with timezone — Schedule creation and management
Peer Dependencies
{
"peerDependencies": {
"bullmq": ">=5.73 <6",
"ioredis": ">=5 <6"
}
}These are required peer dependencies — Rotor pins the BullMQ minor range to ensure Lua script compatibility with the server. Do not use BullMQ outside this range.
Full reference
→ docs.rotor.sh/guides/node-quickstart
License
Closed-source — rotor.sh commercial license. Bug reports welcome at github.com/shyftai/rotor/issues.
