import { WorkerError } from "@effect/platform/WorkerError" import * as Runner from "@effect/platform/WorkerRunner" import * as Cause from "effect/Cause" import * as Context from "effect/Context" import * as Deferred from "effect/Deferred" import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as FiberSet from "effect/FiberSet" import * as Layer from "effect/Layer" import * as Runtime from "effect/Runtime" import * as Scope from "effect/Scope" declare const self: MessagePort const platformRunnerImpl = Runner.PlatformRunner.of({ [Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId, start: Effect.fnUntraced(function*(closeLatch: Deferred.Deferred) { if (!("postMessage" in self)) { return yield* new WorkerError({ reason: "spawn", cause: new Error("not in a Worker context") }) } const port = self const run = Effect.fnUntraced(function*( handler: (portId: number, message: any) => Effect.Effect | void ) { const scope = yield* Effect.scope const runtime = (yield* Effect.runtime().pipe( Effect.interruptible )).pipe( Runtime.updateContext(Context.omit(Scope.Scope)) ) as Runtime.Runtime const fiberSet = yield* FiberSet.make() const runFork = Runtime.runFork(runtime) const onExit = (exit: Exit.Exit) => { if (exit._tag === "Failure" && !Cause.isInterruptedOnly(exit.cause)) { Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause))) } } function onMessage(event: MessageEvent) { const message = (event as MessageEvent).data as Runner.BackingRunner.Message if (message[0] === 0) { const result = handler(0, message[1]) if (Effect.isEffect(result)) { const fiber = runFork(result) fiber.addObserver(onExit) FiberSet.unsafeAdd(fiberSet, fiber) } } else { port.close() Deferred.unsafeDone(closeLatch, Exit.void) } } function onMessageError(error: MessageEvent) { Deferred.unsafeDone( closeLatch, new WorkerError({ reason: "decode", cause: error.data }) ) } function onError(error: MessageEvent) { Deferred.unsafeDone( closeLatch, new WorkerError({ reason: "unknown", cause: error.data }) ) } yield* Scope.addFinalizer( scope, Effect.sync(() => { port.removeEventListener("message", onMessage) port.removeEventListener("messageerror", onError) }) ) port.addEventListener("message", onMessage) port.addEventListener("messageerror", onMessageError) port.postMessage([0]) }) const send = (_portId: number, message: any, transfer?: ReadonlyArray) => Effect.sync(() => port.postMessage([1, message], { transfer: transfer as any }) ) return { run, send } }) }) /** @internal */ export const layer = Layer.succeed(Runner.PlatformRunner, platformRunnerImpl)