Return a checked result from a JavaScript job
A JavaScript code execution API schedules source code and records the process outcome. cpuOS accepts a Node.js job with POST /v1/jobs, then exposes its status through GET /v1/jobs/:id. HTTP 202 means the job was accepted or replayed, not that its calculation succeeded. This guide supplies a deterministic event transform and a reusable Node.js client to submit it.
The task validates seven synthetic events, removes an identical duplicate and totals units by event type and purchased SKU. A conflicting duplicate ID fails explicitly. It uses Node.js 24 and the standard library, with no packages, data downloads or filesystem access inside the job.
Use trusted team tasks on an allocated pilot worker. cpuOS currently uses Docker, which shares the host kernel. The Node.js vm module is also not a security boundary for hostile code. See the Node.js VM documentation and cpuOS execution limits.
Separate the client environment from the execution worker
- Create a workspace API key and connect a Docker worker with Node 24 available, following the quickstart. A workspace key and a worker enrollment token have different roles.
- Run the HTTP client below on your development machine or application server using Node.js 24. It reads the local source file and submits its contents, not the file itself.
- Keep the API key in the client environment. Submitted jobs do not need that key, and it is never copied into the source by this example.
- Keep inline input synthetic or approved. Code and output pass through the EU-hosted cpuOS control plane; execution occurs on the worker you connect.
The current worker supports network-disabled standard-library Node 24 and Python 3.13 jobs. Package installation, file transfer, browser sessions and persistent state are unavailable. Reading a local file in the submission client does not make it accessible inside the worker container.
Validate, deduplicate and aggregate synthetic events
Save this as job.mjs. IDs and SKUs are bounded strings, quantities are positive safe integers, and only the four declared fields are accepted. Identical rows with the same ID count once; different content under the same ID is an error. Bounds on row count and quantity keep the integer totals within a predictable range.
const events = [ { id: "e1", type: "viewed", sku: "A", quantity: 1 }, { id: "e2", type: "added", sku: "A", quantity: 2 }, { id: "e1", type: "viewed", sku: "A", quantity: 1 }, { id: "e3", type: "added", sku: "B", quantity: 1 }, { id: "e4", type: "purchased", sku: "A", quantity: 2 }, { id: "e5", type: "purchased", sku: "B", quantity: 1 }, { id: "e6", type: "viewed", sku: "B", quantity: 1 },]function summarize(input) { if (!Array.isArray(input) || input.length > 1000) { throw new Error("Expected at most 1000 events") } const seen = new Map() const unitsByType = { viewed: 0, added: 0, purchased: 0 } const purchasedBySku = new Map() for (const event of input) { if (!event || Array.isArray(event) || typeof event !== "object" || Object.keys(event).sort().join(",") !== "id,quantity,sku,type" || typeof event.id !== "string" || event.id.length < 1 || event.id.length > 64 || /[^A-Za-z0-9_-]/.test(event.id) || typeof event.type !== "string" || !Object.hasOwn(unitsByType, event.type) || typeof event.sku !== "string" || !/^[A-Z][A-Z0-9-]{0,31}$/.test(event.sku) || /[^A-Z0-9-]/.test(event.sku) || !Number.isSafeInteger(event.quantity) || event.quantity < 1 || event.quantity > 10000) { throw new Error("Invalid event contract") } const fingerprint = JSON.stringify([event.type, event.sku, event.quantity]) if (seen.has(event.id)) { if (seen.get(event.id) !== fingerprint) { throw new Error("Conflicting records use the same event id") } continue } seen.set(event.id, fingerprint) unitsByType[event.type] += event.quantity if (event.type === "purchased") { purchasedBySku.set(event.sku, (purchasedBySku.get(event.sku) ?? 0) + event.quantity) } } return { inputRows: input.length, uniqueEvents: seen.size, duplicatesRemoved: input.length - seen.size, unitsByType, purchasedBySku: Object.fromEntries([...purchasedBySku].sort()), }}console.log(JSON.stringify(summarize(events))){ "inputRows": 7, "uniqueEvents": 6, "duplicatesRemoved": 1, "unitsByType": { "viewed": 2, "added": 3, "purchased": 3 }, "purchasedBySku": { "A": 2, "B": 1 }}Run node job.mjs locally to check the result first. There are six distinct events: two viewed units, three added units and three purchased units. The repeated e1 row is removed. After remote execution, compare the parsed result with this contract before returning it to an application or agent.
Submit a local source file with a complete Node.js client
Save the following as run-job.mjs. It accepts node or python and one local UTF-8 source file. The job requests one CPU, 256 MiB and a 30-second runtime limit. The client allows 120 seconds for HTTP attempts and polling; each fetch has a timer of at most ten seconds that remains active while reading the response body.
import { readFile } from "node:fs/promises"import { randomUUID } from "node:crypto"import { setTimeout as sleep } from "node:timers/promises"const WAIT_MS = 120_000const POLL_MS = 2_000const HTTP_MS = 10_000const TERMINAL = new Set(["completed", "failed", "cancelled"])const STATES = new Set(["queued", "running", ...TERMINAL])const ERROR_CODES = new Set(["invalid_input", "invalid_json", "invalid_api_key", "job_not_found", "idempotency_conflict", "rate_limit_exceeded", "queue_full", "no_online_nodes", "body_too_large", "internal_error"])class ClientError extends Error {}const fail = (message) => { throw new ClientError(message) }const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)async function main() { const args = process.argv.slice(2) if (args.length !== 2 || !["node", "python"].includes(args[0])) { fail("Usage: node run-job.mjs <node|python> <source-file>") } const [template, filename] = args const key = process.env.CPUOS_API_KEY ?? "" if (key.length !== 50 || !/^cpuos_key_[A-Za-z0-9]{40}$/.test(key)) { fail("Set CPUOS_API_KEY to a valid workspace API key") } const intent = process.env.CPUOS_IDEMPOTENCY_KEY ?? randomUUID() if (intent.length < 1 || intent.length > 128 || /[^!-~]/.test(intent)) { fail("CPUOS_IDEMPOTENCY_KEY must contain 1 to 128 visible ASCII characters") } let base try { base = new URL(process.env.CPUOS_BASE_URL ?? "https://cpuos.si") } catch { fail("CPUOS_BASE_URL must be an HTTPS origin") } const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(base.hostname) if (base.username || base.password || base.pathname !== "/" || base.search || base.hash || (base.protocol !== "https:" && !(base.protocol === "http:" && loopback))) { fail("CPUOS_BASE_URL must be an HTTPS origin (loopback HTTP is allowed)") } let bytes, code try { bytes = await readFile(filename) code = new TextDecoder("utf-8", { fatal: true }).decode(bytes) } catch { fail("Cannot read the source file as UTF-8") } if (!bytes.length || !code.length || bytes.length > 65_536 || code.includes("\0")) { fail("Source must be nonempty, at most 64 KiB, with no NUL bytes") } const payload = JSON.stringify({ name: "Standard-library " + template + " task", template, code, vcpu: 1, memoryMb: 256, timeoutSeconds: 30 }) const deadline = performance.now() + WAIT_MS const remaining = () => { const ms = deadline - performance.now() if (ms <= 0) fail("Client waiting budget exhausted; inspect the existing job") return ms } const pause = async (ms) => { if (!Number.isFinite(ms) || ms < 0 || ms >= remaining()) { fail("Next wait exceeds the client budget; inspect the existing job") } await sleep(ms) remaining() } async function request(method, path, body) { const headers = { Authorization: "Bearer " + key, "Content-Type": "application/json" } if (method === "POST") headers["Idempotency-Key"] = intent for (let attempt = 0; attempt < 3; attempt++) { let response, data const signal = AbortSignal.timeout(Math.max(1, Math.ceil(Math.min(HTTP_MS, remaining())))) try { response = await fetch(base.origin + path, { method, headers, body, redirect: "error", signal }) data = await response.json() signal.throwIfAborted() remaining() } catch (error) { if (error instanceof ClientError) throw error remaining() if (error instanceof SyntaxError) fail("The jobs API returned invalid JSON") if (attempt === 2) fail("Transport failed after three attempts; submission may exist") await pause(1000 * 2 ** attempt) continue } if (!object(data)) fail("Expected a JSON object from the jobs API") if (response.ok) { if (response.status !== (method === "POST" ? 202 : 200)) { fail("Unexpected success status from the jobs API") } return data } const apiCode = object(data.error) && ERROR_CODES.has(data.error.code) ? data.error.code : "request_failed" if (attempt === 2 || ["queue_full", "no_online_nodes"].includes(apiCode) || ![429, 500, 502, 503, 504].includes(response.status)) { fail("HTTP " + response.status + " (" + apiCode + ")") } let delay = 1000 * 2 ** attempt if (response.status === 429) { const retryAfter = response.headers.get("Retry-After") ?? "60" delay = /^\d+$/.test(retryAfter) ? Number(retryAfter) * 1000 : 60_000 } await pause(delay) } } function checkJob(job, expectedId) { if (typeof job.id !== "string" || job.id.length !== 20 || !/^[A-Za-z0-9]{20}$/.test(job.id) || !STATES.has(job.status) || (expectedId && job.id !== expectedId)) { fail("Invalid job identifier or state in the API response") } return job } console.error("Idempotency key: " + intent) let job = checkJob(await request("POST", "/v1/jobs", payload)) const id = job.id console.error("Job ID: " + id) while (!TERMINAL.has(job.status)) { await pause(POLL_MS) job = checkJob(await request("GET", "/v1/jobs/" + id), id) } if (job.status !== "completed" || job.exitCode !== 0 || job.error) { fail("Job " + job.status + "; inspect the recorded outcome in your workspace") } if (job.outputTruncated !== false || typeof job.stdout !== "string") { fail("Output is missing or truncated; do not accept a partial result") } let result try { result = JSON.parse(job.stdout) } catch { fail("Job output is not one complete JSON value") } console.log(JSON.stringify(result))}main().catch((error) => { console.error(error instanceof ClientError ? error.message : "Client failed; inspect the existing job") process.exitCode = 1})# Bash: keep the real key out of shell history and source files.set +xread -r -s -p 'cpuOS API key: ' CPUOS_API_KEY; printf '\n'export CPUOS_API_KEYexport CPUOS_BASE_URL='https://cpuos.si'node run-job.mjs node job.mjs > result.json && node verify-result.mjs# The same client can submit a standard-library Python source file:# node run-job.mjs python analyze-csv.py > result.jsonThe source file is read by Node.js filesystem APIs. The HTTP request uses built-in fetch and AbortSignal, so no SDK or npm install is needed. Successful stdout is one parsed JSON value. Stderr contains the validated idempotency key and job ID; save them when recovering an uncertain submission.
Check transport, process outcome and result separately
Scroll horizontally to see every column.
| Layer | Required check |
|---|---|
| Submission | HTTP 202, an alphanumeric 20-character ID and a known state |
| Polling | HTTP 200 for the same ID; queued/running remain unfinished |
| Process | completed, exitCode zero and no recorded execution error |
| Output | outputTruncated is false and stdout is one complete JSON value |
| Your result contract | Expected fields, types and domain values for this task |
The reusable client parses JSON but does not know your task's business rules. For this example, check seven input rows, six unique events, one duplicate and the displayed unit totals. A different task needs its own schema and invariants. Do not mistake a JSON value for a correct answer.
Save the verifier below as verify-result.mjs before running the shell workflow. The && operator runs it only when the submission client exits successfully. It compares the entire parsed result with this task's expected object; neither failure messages nor assertion values are echoed. A different recipe needs its own verifier.
import { readFile } from "node:fs/promises"import { deepStrictEqual } from "node:assert/strict"const expected = {"inputRows":7,"uniqueEvents":6,"duplicatesRemoved":1,"unitsByType":{"viewed":2,"added":3,"purchased":3},"purchasedBySku":{"A":2,"B":1}}try { const actual = JSON.parse(await readFile("result.json", "utf8")) deepStrictEqual(actual, expected) console.log("Result contract verified")} catch { console.error("Result did not match the expected event contract") process.exitCode = 1}Source is limited to 64 KiB with no NUL bytes; stdout and stderr capture are bounded at 64 KiB per stream. The API accepts one or two CPUs, 128–2,048 MiB and one to 120 runtime seconds. This client uses smaller fixed allocations. Failed, cancelled, malformed or truncated results exit with an error, and failure diagnostics do not print the job's stdout, stderr or reflected server messages.
Keep one submission intent through retries
Every HTTP request gets at most three attempts. POST retries use the same source, allocations and Idempotency-Key. A valid key already used for the same normalized parameters returns that workspace job. Changing the source or parameters under the same key returns HTTP 409 idempotency_conflict.
A fresh CLI invocation generates a new intent and requests a new execution. To recover an uncertain POST, set CPUOS_IDEMPOTENCY_KEY to the previous run's displayed key and leave the source and template unchanged. Replaying a failed or cancelled job retrieves that attempt; it does not restart the computation.
Polling every two seconds uses about 30 requests per minute for one active job. The workspace limit is 60 API requests or submissions per minute across clients. HTTP 429 honors a numeric Retry-After within the remaining budget. queue_full and no_online_nodes stop immediately so an application can resolve capacity instead of blindly retrying.
The base URL must be an HTTPS origin, with loopback HTTP allowed for local development. Redirects are rejected, keeping the Bearer key on that configured origin. Credential and intent validation run before any request. Error messages use fixed text and known error codes, rather than copying untrusted response text into logs.
Stop waiting and cancel execution as separate actions
Client timeout or Ctrl-C stops local waiting; this reusable client does not automatically cancel the remote job. Retain its ID and inspect the existing attempt. If you intentionally want to stop it, use DELETE with the same workspace key. The independent 30-second job timeout still applies after your client stops.
# Copy the 20-character job ID printed by the client.JOB_ID='replace-with-job-id'curl --fail-with-body -X DELETE "https://cpuos.si/v1/jobs/$JOB_ID" \ -H "Authorization: Bearer $CPUOS_API_KEY"Queued work is marked cancelled; running work receives a cancellation instruction through the worker's polling loop. Inspect the returned state. If completion won the race, DELETE returns its terminal result instead. The response does not promise instantaneous container termination. If you have no ID after an uncertain POST, recover using the original idempotency key before deciding what to cancel.
Exercise process failure, oversized output, waiting timeout and cancellation in a controlled worker pilot before replacing the synthetic task. The published job and client are tested locally against HTTP fixtures; those tests do not measure production capacity or replace validation on your Docker worker. Continue with Python code execution, LangChain tools or n8n HTTP jobs.