import { Effect, Exit, Fiber } from "effect" import { WorkspaceContext } from "@/control-plane/workspace-context" import { Instance, type InstanceContext } from "@/project/instance" import type { WorkspaceID } from "@/control-plane/schema" import { LocalContext } from "@/util/local-context" import { InstanceRef, WorkspaceRef } from "./instance-ref" import { attachWith } from "./run-service" export interface Shape { readonly promise: (effect: Effect.Effect) => Promise readonly fork: (effect: Effect.Effect) => Fiber.Fiber readonly run: (effect: Effect.Effect) => Effect.Effect } function restore(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R { if (instance && workspace !== undefined) { return WorkspaceContext.restore(workspace, () => Instance.restore(instance, fn)) } if (instance) return Instance.restore(instance, fn) if (workspace !== undefined) return WorkspaceContext.restore(workspace, fn) return fn() } /** * Bridge from Effect into a Promise-returning JS callback while installing * legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for * the duration of the callback. Effect's `InstanceRef`/`WorkspaceRef` do * not propagate across async/await boundaries inside `Effect.promise(() => * async fn)` callbacks that re-enter Effect via `AppRuntime.runPromise`, * but Node's AsyncLocalStorage does. Use this whenever an Effect crosses * into JS that may itself spawn new Effect runtimes (workspace adapters, * legacy plugins, etc.). * * Mirrors `Effect.promise` but restores legacy ALS first. */ export const fromPromise = (fn: () => Promise | T): Effect.Effect => Effect.gen(function* () { const instance = yield* InstanceRef const workspace = yield* WorkspaceRef return yield* Effect.promise(() => Promise.resolve(restore(instance, workspace, () => fn()))) }) export function make(): Effect.Effect { return Effect.gen(function* () { const ctx = yield* Effect.context() const value = yield* InstanceRef const instance = value ?? (() => { try { return Instance.current } catch (err) { if (!(err instanceof LocalContext.NotFound)) throw err } })() const workspace = (yield* WorkspaceRef) ?? WorkspaceContext.workspaceID const attach = (effect: Effect.Effect) => attachWith(effect, { instance, workspace }) const wrap = (effect: Effect.Effect) => attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect return { promise: (effect: Effect.Effect) => restore(instance, workspace, () => Effect.runPromise(wrap(effect))), fork: (effect: Effect.Effect) => restore(instance, workspace, () => Effect.runFork(wrap(effect))), run: (effect: Effect.Effect) => Effect.callback((resume) => { restore(instance, workspace, () => Effect.runPromiseExit(wrap(effect)).then((exit) => resume(Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause)), ), ) }), } satisfies Shape }) } export * as EffectBridge from "./bridge"