Quickstart
Get started with Invariant in under 5 minutes. In this guide, you will install the framework, initialize a PostgreSQL runtime, define a durable AI workflow, and execute it.
1. Installation
Install the core Invariant packages into your TypeScript project:
bash
npm install @invariant/runtime @invariant/postgres @invariant/sdkEnsure you have a running PostgreSQL database (v14+ recommended) and a TypeScript configuration (tsconfig.json) targeting ES2022 or higher.
2. Initialize the Invariant Runtime
Create an instance of Invariant backed by @invariant/postgres:
ts
import { invariant } from "@invariant/sdk";
import { postgres } from "@invariant/postgres";
import pg from "pg";
// 1. Setup PostgreSQL pool
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/invariant",
});
// 2. Initialize Invariant Runtime
export const app = invariant({
store: postgres({ pool }),
});3. Define Your First Workflow
Use the Fluent SDK (@invariant/sdk) to build a durable workflow. Each step explicitly declares its computation boundary:
ts
import { z } from "zod";
import { app } from "./runtime";
// Schema definitions
const UserInputSchema = z.object({
userId: z.string(),
refundReason: z.string(),
});
const PolicyDecisionSchema = z.object({
approved: z.boolean(),
explanation: z.string(),
refundAmount: z.number(),
});
export const refundWorkflow = app.workflow("customer-refund")
// 1. Deterministic Step (0 I/O)
.step("load-customer-data", async ({ input }) => {
return {
customerId: input.userId,
accountTier: "VIP",
};
})
// 2. Probabilistic AI Reasoning Boundary
.reason("evaluate-refund-policy", {
prompt: ({ state }) => `
Customer Tier: ${state.accountTier}
Refund Reason: ${state.refundReason}
Evaluate whether this customer is eligible for a full refund based on VIP policies.
`,
schema: PolicyDecisionSchema,
})
// 3. Durable Capability (Side Effect with Idempotency Key)
.capability("issue-stripe-refund", {
idempotencyKey: ({ execution }) => `stripe-refund:${execution.id}`,
handler: async ({ state }) => {
if (!state.approved) {
return { status: "SKIPPED", reason: "Policy denied" };
}
// Simulate external API call
return {
status: "SUCCESS",
transactionId: `ch_${Date.now()}`,
amount: state.refundAmount,
};
},
})
// 4. Durable Wait Boundary (Suspends until event received)
.wait("wait-for-customer-feedback", {
timeout: "24h",
});4. Register and Start the Runtime Worker
Start the runtime worker to process queued executions, handle Outbox side-effects, and reclaim crashed runs:
ts
import { app } from "./runtime";
import { refundWorkflow } from "./workflows/refund";
async function main() {
// Register workflow
app.register(refundWorkflow);
// Start background worker loop (Fast-path + Reclaimer)
await app.start();
console.log("🚀 Invariant Runtime active and listening for events.");
}
main().catch(console.error);5. Dispatch an Event
Dispatch a RuntimeEvent to trigger your workflow:
ts
const runId = await app.dispatch({
workflow: "customer-refund",
event: {
type: "RUN_CREATED",
payload: {
userId: "usr_9981",
refundReason: "Item arrived damaged during shipping.",
},
},
});
console.log(`Started workflow run: ${runId}`);What Happens Behind the Scenes?
- Transactional Ingestion: The
RUN_CREATEDevent is persisted to PostgreSQL insideworkflow_eventswith sequenceseq = 1. - Deterministic Reduction:
ExecutionEngine.transition()computes the initial state and routes toload-customer-data. - Reasoning Boundary: The engine emits a
ReasonCommand. The runtime executes the LLM prompt and feeds the structured JSON back as aCAPABILITY_COMPLETEDRuntimeEvent. - Atomic Outbox Commit: The decision triggers
issue-stripe-refund. State, event log, and the Outbox command commit in a singleFOR UPDATEPostgreSQL transaction. - Suspension: The workflow reaches
.wait()and safely suspends withstatus = 'suspended'. Zero CPU/memory overhead while waiting.
Next Steps
- Explore Core Concepts to understand
.step,.reason,.capability, and.wait. - Read about Durability Guarantees to see how Invariant handles server crashes.