JavaScript Worker
This guide shows you how to build a Priostack job worker in JavaScript/Node.js using the built-in fetch API (Node 18+).
Prerequisites
- Node.js 18 or later (built-in
fetchAPI) - A Priostack API key set as
PRIOSTACK_API_KEYenvironment variable
Complete Worker Implementation
// worker.mjs — Priostack Job Worker (Node.js / ESM)
"use strict";
const BASE_URL = "https://api.priostack.com";
const WORKER_TYPE = "payment-processor";
const MAX_JOBS = 5;
const POLL_TIMEOUT_MS = 30_000;
const apiKey = process.env.PRIOSTACK_API_KEY;
if (!apiKey) {
console.error("PRIOSTACK_API_KEY environment variable is required");
process.exit(1);
}
const headers = {
"X-API-Key": apiKey,
"Content-Type": "application/json",
};
// ── API helpers ────────────────────────────────────────────────
async function activateJobs() {
const resp = await fetch(`${BASE_URL}/api/v1/jobs/activate`, {
method: "POST",
headers,
body: JSON.stringify({
type: WORKER_TYPE,
maxJobsToActivate: MAX_JOBS,
requestTimeout: POLL_TIMEOUT_MS,
fetchVariables: ["orderId", "amount", "currency"],
}),
signal: AbortSignal.timeout(35_000),
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Activate failed ${resp.status}: ${text}`);
}
const data = await resp.json();
return data.jobs ?? [];
}
async function completeJob(jobKey, variables) {
const resp = await fetch(`${BASE_URL}/api/v1/jobs/${jobKey}/complete`, {
method: "POST",
headers,
body: JSON.stringify({ variables }),
signal: AbortSignal.timeout(10_000),
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Complete failed ${resp.status}: ${text}`);
}
}
async function failJob(jobKey, retries, errorMessage) {
await fetch(`${BASE_URL}/api/v1/jobs/${jobKey}/fail`, {
method: "POST",
headers,
body: JSON.stringify({
retries: Math.max(0, retries - 1),
errorMessage,
retryBackoff: 5000,
}),
signal: AbortSignal.timeout(10_000),
});
}
async function throwBpmnError(jobKey, errorCode, errorMessage) {
await fetch(`${BASE_URL}/api/v1/jobs/${jobKey}/error`, {
method: "POST",
headers,
body: JSON.stringify({ errorCode, errorMessage }),
signal: AbortSignal.timeout(10_000),
});
}
// ── Business logic ─────────────────────────────────────────────
async function processPayment(job) {
const { orderId, amount, currency = "EUR" } = job.variables ?? {};
console.log(`Processing payment for order ${orderId}, amount ${amount} ${currency}`);
// TODO: Call your actual payment gateway here
// const intent = await stripe.paymentIntents.create({ amount: Math.round(amount * 100), currency });
// Simulate async processing
await new Promise((resolve) => setTimeout(resolve, 100));
return {
transactionId: `txn_${orderId}_${Date.now()}`,
paymentStatus: "success",
processedAt: new Date().toISOString(),
};
}
// ── Worker loop ────────────────────────────────────────────────
async function handleJob(job) {
const { key: jobKey, retries = 3 } = job;
try {
const result = await processPayment(job);
await completeJob(jobKey, result);
console.log(`Job ${jobKey} completed successfully`);
} catch (err) {
console.error(`Job ${jobKey} failed:`, err.message);
try {
await failJob(jobKey, retries, err.message);
} catch (failErr) {
console.error(`Failed to report job failure:`, failErr.message);
}
}
}
let running = true;
process.on("SIGTERM", () => { console.log("SIGTERM received. Shutting down..."); running = false; });
process.on("SIGINT", () => { console.log("SIGINT received. Shutting down..."); running = false; });
async function main() {
console.log(`Worker started. Polling for jobs of type "${WORKER_TYPE}"...`);
while (running) {
try {
const jobs = await activateJobs();
for (const job of jobs) {
console.log(`Activated job ${job.key} (instance ${job.processInstanceKey})`);
// Process jobs concurrently
handleJob(job).catch((err) => console.error("Unhandled job error:", err));
}
} catch (err) {
if (!running) break;
if (err.name === "TimeoutError") {
// Long poll timed out — no jobs available, loop again
continue;
}
console.error("Worker error:", err.message, "— retrying in 5s");
await new Promise((resolve) => setTimeout(resolve, 5_000));
}
}
console.log("Worker stopped.");
}
main().catch((err) => {
console.error("Fatal:", err);
process.exit(1);
});
Running the Worker
# Set your API key
export PRIOSTACK_API_KEY=ps_live_your_key_here
# Run with Node.js (ESM)
node worker.mjs
# Or with tsx (TypeScript)
npx tsx worker.ts
# Output:
# Worker started. Polling for jobs of type "payment-processor"...
# Activated job 2251799813685290 (instance 2251799813685281)
# Processing payment for order ORD-001, amount 149.99 EUR
# Job 2251799813685290 completed successfully
TypeScript Version
To use TypeScript, add type annotations:
interface Job {
key: number;
type: string;
processInstanceKey: number;
variables: Record<string, unknown>;
retries: number;
deadline: number;
}
interface ActivateResponse {
jobs: Job[];
}
async function activateJobs(): Promise<Job[]> {
// same implementation, typed
}
Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY worker.mjs .
CMD ["node", "worker.mjs"]
Production Tips
| Concern | Recommendation |
|---|---|
| Concurrency | Use Promise.all or a worker pool (e.g., piscina) to process multiple jobs in parallel within a single process. |
| Graceful shutdown | Track in-flight job promises and await them before exiting on SIGTERM. |
| Error classification | Distinguish network errors (retry) from business errors (throw BPMN error) from programming errors (log and alert). |
| Monitoring | Use --inspect for debugging. Export metrics to Prometheus using prom-client. |