import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
// Cloudflare must see a rejected callback to roll back. Carry the local Effect
// cause through that Promise boundary without flattening typed failures into
// defects or committing a failed program's partial writes.
export function transaction(
storage: DurableObjectStorage,
action: (tx: DurableObjectTransaction) => Effect.Effect,
) {
class Rollback {
constructor(readonly cause: Cause.Cause) {}
}
return Effect.tryPromise({
try: (signal) =>
storage.transaction(async (tx) => {
const exit = await Effect.runPromiseExit(action(tx), { signal });
if (Exit.isSuccess(exit)) return exit.value;
throw new Rollback(exit.cause);
}),
catch: (error) => error,
}).pipe(
Effect.catch((error) =>
error instanceof Rollback
? Effect.failCause(error.cause)
: Effect.die(error),
),
);
}