Nexus operations
Temporal Nexus connects workflows across namespaces and clusters through typed service contracts. effect-temporal bridges both directions: serving a workflow as a Nexus operation, and calling one from a workflow body — with the typed success/error channels surviving the crossing.
Requires the optional peer @temporalio/nexus, and a Temporal server with Nexus enabled (the time-skipping test server has no Nexus support; use a local dev server in tests).
Serving: a workflow as an operation
Register effectWorkflowRunOperation(MyWorkflow) in the Nexus service handler on the worker:
import * as nexus from "nexus-rpc";
import { effectWorkflowRunOperation } from "@springbird/effect-temporal/nexus";
export const provisioningHandler = nexus.serviceHandler(provisioningService, {
// sync operations stay plain async functions
lookup: async (_ctx, input) => findTenant(input),
// a shim workflow served as a workflow-backed operation
provision: effectWorkflowRunOperation(ProvisionFlow),
});
// worker: Worker.create({ ..., nexusServices: [provisioningHandler] })The operation's input is the workflow's typed payload; the started workflow keeps its digest execution id, so the addressing contract holds across the Nexus boundary.
One deliberate exception to the idempotency contract: a repeated operation call attaches to the running execution (USE_EXISTING), but against a closed one it fails loudly rather than silently starting a duplicate side-effecting run. Direct execute attaches to closed runs; the Nexus path refuses — a caller replaying an old operation months later should hear about it, not re-provision.
Calling: from a workflow body
import { createNexusServiceClient } from "@temporalio/workflow";
import {
callNexusWorkflowOperation,
type NexusOperationClient,
} from "@springbird/effect-temporal/engine-sandbox";
const nexusClient = createNexusServiceClient({
service: provisioningService,
endpoint: "provisioning",
});
const result = yield* callNexusWorkflowOperation({
client: nexusClient as NexusOperationClient,
operation: "provision",
workflow: ProvisionFlow, // the target's definition, for the codecs
payload: { tenantId },
scheduleToCloseTimeout: "10 minutes",
});
// success/error channels: ProvisionFlow's typed channelsThe payload is wire-encoded outward; the operation's result — the target workflow's encoded exit — decodes back into typed channels, including across cancellation: interrupting the calling fiber cancels the in-flight operation through the same per-call scope as activities, and the run's bounded settle wait keeps the caller open for the cancellation handshake.
Synchronous operations
Operations not backed by a shim workflow are plain workflow-API promises — call them with callRawActivity:
const info = yield* callRawActivity(() =>
nexusClient.executeOperation("lookup", { id }, { scheduleToCloseTimeout: "10 seconds" }),
);