Examples: Durable Refund Flow
A runnable refund pattern showing bounded semantic extraction, deterministic policy evaluation, and a stable capability identity. The deterministic model fixture keeps the example offline; replace it with a configured provider adapter in production. The account and payment clients remain application-owned.
ts
import { invariant } from "@invariant-tech/sdk";
import { sqlite } from "@invariant-tech/sqlite";
import { z } from "zod";
type RefundClassification = {
reason: "DUPLICATE_CHARGE" | "SERVICE_FAILURE" | "OTHER";
requestedAmount: number;
};
const accountStore = {
load: async (userId: string) => ({
userId,
tier: "VIP" as const,
refundableBalance: 100,
}),
};
const refundModel = {
provider: "fixture",
defaultModel: "refund-fixture-v1",
async generateReasoning() {
return {
result: { reason: "DUPLICATE_CHARGE", requestedAmount: 75 },
};
},
};
const app = invariant({
storage: sqlite("./data/refunds.db"),
models: { default: refundModel },
});
export const durableRefund = app.workflow("durable-refund", {
inputSchema: z.object({
userId: z.string(),
request: z.string(),
}),
})
.capability("load-account", async ({ input }) => ({
account: await accountStore.load(input.userId),
}))
.reason<RefundClassification>("classify-request", {
instruction: "Extract the refund reason and requested amount from the customer request.",
schema: {
type: "object",
properties: {
reason: { type: "string", enum: ["DUPLICATE_CHARGE", "SERVICE_FAILURE", "OTHER"] },
requestedAmount: { type: "number", minimum: 0 },
},
required: ["reason", "requestedAmount"],
additionalProperties: false,
},
})
.step("check-policy", ({ state }) => {
const eligibleReason = state.reason !== "OTHER";
const amount = Math.min(state.requestedAmount, state.account.refundableBalance);
return { approved: eligibleReason && amount > 0, amount };
})
.capability("stripe-refund", {
idempotencyKey: "refund:{{runId}}",
handler: async ({ state }) => {
if (!state.approved) return { status: "DENIED" as const };
return { status: "SUCCESS" as const, amount: state.amount };
},
});
async function main() {
const session = await app.sessions.loadOrCreateSession("sess_refund_1", "usr_refund_1");
await session.startWorkflow(durableRefund, {
userId: "usr_refund_1",
request: "I was charged twice; refund $75.",
});
console.log(session.snapshot().projectedState);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Next Steps
- Capabilities and Effect Identity — Separate runtime command identity from provider deduplication.
- Model Adapters — Replace the deterministic fixture with a live provider.
- Durability Guarantees — Understand ambiguous external outcomes and retries.