Examples: Human Approval Flow
Suspend workflow execution for human sign-off, inspect the wait boundary, and submit a schema-validated decision. Replace the local slack stub with your application-owned client.
ts
import { invariant } from "@invariant-tech/sdk";
import { sqlite } from "@invariant-tech/sqlite";
import { z } from "zod";
type ApprovalDecision = {
approved: boolean;
approverId: string;
};
const slack = {
postMessage: async (message: string) => console.log(message),
};
const app = invariant({ storage: sqlite("./data/approval.db") });
const ApprovalInputSchema = z.object({
approved: z.boolean(),
approverId: z.string(),
});
export const humanApproval = app.workflow("human-approval")
.capability("notify-slack", {
idempotencyKey: "approval-request:{{runId}}",
handler: async () => {
await slack.postMessage("Approval requested");
return { notified: true };
},
})
.wait<ApprovalDecision>("wait-approval", {
schema: ApprovalInputSchema,
})
.step("record-decision", ({ state }) => ({
finalStatus: state.approved ? "APPROVED" as const : "REJECTED" as const,
approvedBy: state.approverId,
}));
async function main() {
const session = await app.sessions.loadOrCreateSession("sess_approval_1", "usr_requester_1");
await session.startWorkflow(humanApproval, {});
console.log(session.snapshot().activeWorkflow?.status); // waiting_input
await session.submitInput({ approved: true, approverId: "mgr_42" });
console.log(session.snapshot().projectedState.finalStatus); // APPROVED
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Invalid input rejects with WaitInputValidationError and leaves the workflow at the same wait boundary. In a real Slack integration, authenticate the callback, restore the owned durable Session if necessary, and pass the normalized payload to submitInput(). A registered graph paused at .wait() can reattach after restart; interrupted capability work is not automatically redispatched.