Build

Implement a workflow

Build typed steps, compensation payloads, and a composed in-process saga.

A step has a forward function and, when needed, a compensation function. The forward function returns StepResponse(output, compensationInput). Only the compensation input is passed to the rollback function.

import {
  Effect,
  StepResponse,
  createStep,
  createWorkflow,
  getModule,
} from "@damatjs/framework";

const createProfile = createStep<NewUser, User, string>(
  "create-profile",
  async (input) => {
    const users = getModule("user");
    const user = await users.users.create({
      data: input,
      returning: ["id", "email"],
    });
    return new StepResponse(user, user.id);
  },
  async (userId) => {
    const users = getModule("user");
    await users.users.delete({ where: { id: userId } });
  },
);

const createPreferences = createStep<User, Preferences, string>(
  "create-preferences",
  async (user) => {
    const users = getModule("user");
    const preferences = await users.preferences.create({
      data: { userId: user.id, locale: "en" },
    });
    return new StepResponse(preferences, preferences.id);
  },
  async (preferencesId) => {
    const users = getModule("user");
    await users.preferences.delete({ where: { id: preferencesId } });
  },
);

export const onboardUser = createWorkflow(
  "user-onboarding",
  (input: NewUser, context) =>
    Effect.gen(function* () {
      const user = yield* createProfile(input, context);
      const preferences = yield* createPreferences(user, context);
      return { user, preferences };
    }),
  { timeoutMs: 60_000 },
);

Run it and inspect the result instead of assuming an exception shape:

const result = await onboardUser.execute(input);
if (!result.success) {
  logger.error("onboarding failed", {
    code: result.error.code,
    compensated: result.compensated,
    compensationErrors: result.compensationErrors,
  });
}

Pass the AbortSignal supplied to a step into network or database calls that support cancellation. A timeout cannot stop work that ignores its signal.