import { Context, Effect, Exit, Fiber } from "effect" import { WorkspaceContext } from "@/control-plane/workspace-context" import type { WorkspaceID } from "@/control-plane/schema" 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 readonly bind: (fn: (...args: Args) => Result) => (...args: Args) => Result } function restoreWorkspace(workspace: WorkspaceID | undefined, fn: () => R): R { if (workspace !== undefined) return WorkspaceContext.restore(workspace, fn) return fn() } function captureSync() { const fiber = Fiber.getCurrent() const instance = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined const workspace = (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined) ?? WorkspaceContext.workspaceID return { instance, workspace } } export const bind = (fn: (...args: Args) => Result) => { const captured = captureSync() return (...args: Args) => restoreWorkspace(captured.workspace, () => Effect.runSync( attachWith( Effect.sync(() => fn(...args)), captured, ), ), ) } /** * Bridge from Effect into a Promise-returning JS callback while preserving * `WorkspaceContext` AsyncLocalStorage for callback code that still reads it. * `InstanceRef` is captured for effects run through the returned bridge APIs; * plain JS callbacks that need it should receive the ref explicitly. * * Mirrors `Effect.promise` but restores workspace ALS first. */ export const fromPromise = (fn: () => Promise | T): Effect.Effect => Effect.gen(function* () { const workspace = yield* WorkspaceRef return yield* Effect.promise(() => Promise.resolve(restoreWorkspace(workspace, () => fn()))) }) export function make(): Effect.Effect { return Effect.gen(function* () { const ctx = yield* Effect.context() const captured = captureSync() const instance = (yield* InstanceRef) ?? captured.instance const workspace = (yield* WorkspaceRef) ?? captured.workspace const wrap = (effect: Effect.Effect) => attachWith(effect.pipe(Effect.provide(ctx)) as Effect.Effect, { instance, workspace }) return { promise: (effect: Effect.Effect) => restoreWorkspace(workspace, () => Effect.runPromise(wrap(effect))), fork: (effect: Effect.Effect) => restoreWorkspace(workspace, () => Effect.runFork(wrap(effect))), run: (effect: Effect.Effect) => Effect.callback((resume) => { restoreWorkspace(workspace, () => Effect.runPromiseExit(wrap(effect)).then((exit) => resume(Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause)), ), ) }), bind: (fn: (...args: Args) => Result) => (...args: Args) => restoreWorkspace(workspace, () => Effect.runSync(wrap(Effect.sync(() => fn(...args))))), } satisfies Shape }) } export * as EffectBridge from "./bridge"