JS Workflows
Workflows orchestrate activities (and other workflows). They must be deterministic — given the same inputs and event history they always produce the same sequence of calls. Obelisk records every call and its result; on crash-recovery it replays the log, skipping already-completed calls. See Workflows for the general model.
[[workflow_js]]
name = "my_workflow"
location = "${DEPLOYMENT_DIR}/workflow/my_workflow.js"
ffqn = "myapp:demo/workflow.my-workflow"
params = []
return_type = "result<string, string>"
Do not use Date.now(), Math.random(), or other non-deterministic sources in workflow code. Use
obelisk.sleep for delays instead of setTimeout.
obelisk.call — submit and await
Submits a child execution (activity or workflow) and blocks until it completes. Returns the ok payload directly; throws the err payload string if the child permanently fails.
// ffqn: myapp:demo/workflow.serial() -> result<string, string>
export default function serial() {
let acc = 0;
for (let i = 0; i < 10; i++) {
obelisk.sleep({ seconds: 1 });
const result = obelisk.call("myapp:demo/activity.step", [i, i * 200]);
acc += Number(result);
console.log(`step(${i})=${result}`);
}
return String(acc);
}obelisk.sleep — persistent sleep
Pauses the workflow durably — the sleep position is saved to the execution log. If the server crashes mid-sleep and restarts, the sleep resumes where it left off.
obelisk.sleep({ milliseconds: 300 });
obelisk.sleep({ seconds: 1 });
obelisk.sleep({ minutes: 5 });Join sets — parallel submission
Join sets let you submit multiple child executions concurrently and await their results individually.
// ffqn: myapp:demo/workflow.parallel() -> result<string, string>
export default function parallel() {
const handles = [];
for (let i = 0; i < 10; i++) {
const js = obelisk.createJoinSet(); // optional: { name: "my-set" }
const execId = js.submit("myapp:demo/activity.step", [i, i * 200]);
handles.push({ i, js, execId });
}
let acc = 0;
for (const { i, js, execId } of handles) {
const response = js.joinNext(); // blocks until next result in this join set
if (!response.ok) throw `step ${i} failed`;
const result = obelisk.getResult(response.id); // { ok: value } or { err: value }
acc = 10 * acc + Number(result.ok);
obelisk.sleep({ milliseconds: 300 });
}
return String(acc);
}
Join set API:
| Call | Returns | Description |
|---|---|---|
obelisk.createJoinSet() | join set | Create a new join set (optionally { name: "…" }) |
js.submit(ffqn, args) | executionId (string) | Submit a child execution without blocking |
js.submitDelay(duration) | delayId (string) | Submit a timer (e.g. { milliseconds: 500 }) |
js.joinNext() | { ok: bool, id: string } | Block until the next result in this join set |
obelisk.getResult(id) | { ok: value } or { err: value } | Fetch a result after joinNext |
response.ok is true if the execution finished in the ok variant (or a delay elapsed);
response.id is the execution ID or delay ID to pass to obelisk.getResult.
Random values
Workflow-safe random functions (deterministic on replay — values are recorded in the execution log):
const n = obelisk.randomU64(0, 100); // u64 in [0, 100)
const n2 = obelisk.randomU64Inclusive(1, 6); // u64 in [1, 6]
const s = obelisk.randomString(8, 16); // alphanumeric, length in [8, 16)obelisk.schedule — fire-and-forget submission
Schedules a new top-level execution without blocking the workflow. Returns immediately; the scheduled execution runs independently.
const execId = obelisk.executionIdGenerate();
obelisk.schedule(execId, "myapp:demo/activity.send-email", ["user@example.com"]);
// optional schedule-at: obelisk.schedule(execId, ffqn, args, { seconds: 60 });obelisk.stub — inject a result for a stub activity
Stub activities have no implementation —
the result is supplied externally (via the CLI, Web UI, or obelisk.stub). This enables
human-in-the-loop workflows and test scenarios where a child result is provided without executing
real code.
const js = obelisk.createJoinSet();
const execId = js.submit("myapp:stubs/approval.approve", [requestId]);
obelisk.stub(execId, { ok: "approved" }); // inject result (idempotent for same value)
js.joinNext();
const result = obelisk.getResult(execId).ok; // "approved"