import { Cause, Effect, Exit } from "effect";
import type { WorkerContextService, WorkerContextWaitUntilOptions } from "../Worker";
export type RunWaitUntilEffect = (
effect: Effect.Effect,
) => Promise>;
const causeError = (cause: Cause.Cause) => new Error(Cause.pretty(cause));
const failureHandler = (
cause: Cause.Cause,
options: WorkerContextWaitUntilOptions | undefined,
) =>
(
options?.onFailure?.(cause) ??
Effect.logError("WorkerContext.waitUntil failed", Cause.pretty(cause))
).pipe(
Effect.catchCause((handlerCause) =>
Effect.logError(
"WorkerContext.waitUntil failure handler failed",
Cause.pretty(cause),
Cause.pretty(handlerCause),
),
),
);
export const fromExecutionContext = (
ctx: globalThis.ExecutionContext,
runPromiseExit: RunWaitUntilEffect,
): WorkerContextService => {
const schedule = (
effect: Effect.Effect,
options: WorkerContextWaitUntilOptions | undefined,
mode: "observe" | "propagate",
) =>
Effect.context().pipe(
Effect.flatMap((context) =>
Effect.sync(() => {
const runHandler = (cause: Cause.Cause) =>
runPromiseExit(
Effect.scoped(Effect.provideContext(failureHandler(cause, options), context)),
).then((exit) => {
if (Exit.isFailure(exit)) {
console.error(
"WorkerContext.waitUntil failure handler failed",
Cause.pretty(exit.cause),
);
}
});
ctx.waitUntil(
runPromiseExit(Effect.scoped(Effect.provideContext(effect, context))).then(
async (exit) => {
if (Exit.isSuccess(exit)) {
return;
}
await runHandler(exit.cause as Cause.Cause);
if (mode === "propagate") {
throw causeError(exit.cause as Cause.Cause);
}
},
),
);
}),
),
);
return {
raw: ctx,
waitUntil: (
effect: Effect.Effect,
options?: WorkerContextWaitUntilOptions,
) => schedule(effect, options, options?.mode ?? "observe"),
waitUntilPropagating: (
effect: Effect.Effect,
options?: Omit, "mode">,
) => schedule(effect, options, "propagate"),
passThroughOnException: Effect.sync(() => ctx.passThroughOnException()),
};
};