Repository navigation
feat(effect): enhance effect/wrap with discriminated options and context passing - #2262
Conversation
…fect/context - `effect/wrap` now returns `Effect<A, unknown>`, so a wrap can map failures or defects to `ORPCError` (e.g. `Effect.mapError`, `Effect.catchCause`). - Effects run with `Effect.runPromiseExitWith(effect/context)` instead of `Effect.provide`, so the wrap sees the same services, Tracer, Logger and Scheduler as the handler. Services provided by the wrap now take precedence over `effect/context` for the effect it wraps. - The wrap still runs once per `middlewareGen` and once per `handlerGen`; its options are now a discriminated union (`EffectWrapOptions`) on `kind`, carry the full layer options, and for middleware `input` is the middleware's validated input (and `next` is omitted). - `runPromise` accepts a `context` and Effect's `RunOptions`. - Docs: fix the Error Handling example (twoslash, real `catchCause` body), document wrap semantics, supplying references via `effect/context`, and update the OpenTelemetry example to `@effect/opentelemetry` v4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
- Type the wrap's input as `Effect<A, unknown>`: one wrap serves every handler, so the generic error type carried no information and forced a return annotation on multi-branch `catchCause` callbacks (as in the docs). Wraps written with `<A, E>` remain assignable. - `runPromise` makes a single `runPromiseExitWith(context ?? Context.empty())` call and passes options straight through. - `middlewareGen` drops `next` by destructuring instead of `omit`, avoiding a copy plus `delete` on every wrapped call. - Test `runPromise`'s `context` option (services, scheduler) in runtime.test.ts, use `vi.fn`/`vi.spyOn` instead of hand-rolled recorders, and drop duplicated tracer, scheduler and type cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
- handler: drop the plain-error passthrough test (covered by the first test and runtime.test.ts), fold "throws ORPCError failures from wrap as-is" into the mapError test, and stop the tracer test re-checking handler services. - middleware: keep only the downstream case of the error-mapping test; a middleware's own failure repeats the handler mapError test. - runtime: merge the two "throws the original Error from Effect.fail" tests. - types: drop the wrap cases in handler/middleware test-d that repeat context.test-d, and the single-branch catchCause / fail cases already type-checked by the runtime tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
The oRPC OpenTelemetry integration already creates spans for every middleware and handler, so a span in the wrap only duplicates them. Present the wrap as an error-handling hook in its JSDoc and example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 01b5f6c | Commit Preview URL Branch Preview URL |
Oct 18 2026, 07:25 AM |
Resolve conflicts with #2260: keep main's OpenTelemetry, Cloudflare Workers and layer warning docs, plus its typed effect/context JSDoc example; keep this branch's Error Handling example and the effect/wrap JSDoc with the mapError example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
effect/wraptype redesign (packages/effect/src/context.ts) — signature is now<A>(effect: Effect.Effect<A, unknown>, opts: EffectWrapOptions) => Effect.Effect<A, unknown>; newEffectWrapOptionsdiscriminated bykind(EffectWrapMiddlewareOptions/EffectWrapHandlerOptions) carryinginput,errors,context,path,procedure,signal,lastEventId.- Context propagation through the runtime (
runtime.ts,handler.ts,middleware.ts) — droppedEffect.provide(effectContext);runPromisenow runs viaEffect.runPromiseExitWith(effectContext ?? Context.empty()), makingeffect/contextthe fiber base context so services and references (Tracer/Scheduler/Logger) reach both the wrap and the handler. - Wrap invocation semantics (
handler.ts,middleware.ts) — wrap is invoked once perhandlerGen/middlewareGenwith the layer's validated input; downstream failures pass through it again, so the documented contract requires idempotency. - Docs (
apps/content/docs/integrations/effect.mdx) — reworked error-handling example (twoslash), added the references tip, and moved the OpenTelemetry setup fromeffect/wrap+TracingLivetoeffect/contextviaManagedRuntime.make(OtelTracer.layerGlobal).context(). - Tests — new
context.test-d.ts; handler/middleware/runtime tests updated for the discriminated options, cross-layer invocation counts, context/tracer propagation, and error pass-through.
Verified locally on this revision: vitest run packages/effect (91 passed), tsc -b packages/effect (exit 0), and eslint on all changed files (clean). I also confirmed Effect.runPromiseExitWith(Context.empty()) is equivalent to the previous Effect.runPromiseExit (effect/dist/internal/effect.js:2533), that Effect.RunOptions has no context field so RunPromiseOptions extends cleanly, and that Context<in Services> is contravariant so passing Context<Services> into Context<never> is sound. No other references to the old effect/wrap shape remain in source or docs.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Merge origin/main(01b5f6c54) — the only commit landed since the prior review. It pulls in main's unrelated work (packages/effect/src/error.ts+error.test-d.tsrework, workspacepackage.json/pnpm-lock.yamlbumps) and newerapps/content/docs/integrations/effect.mdx/context.tsJSDoc.- No source change to this PR's code —
handler.ts,middleware.ts,runtime.tsand every*.test.ts/*.test-d.tstouched by the PR are byte-identical to the previously reviewed revision (git diff 1c57cd3 01b5f6c --statconfirms onlycontext.tsandeffect.mdxmoved, andcontext.tschanged in its JSDoc only). The wrap/context behavior reviewed at1c57cd3still holds.
The net PR diff for effect.mdx remains the Error Handling twoslash example (fixing the previous empty Effect.catchCause placeholder), which already reviewed clean. The base branch independently ships the effect/wrap per-layer :::warning and the OtelTracer.layerGlobal / Scheduler.MixedScheduler guidance, so the merge simply adopted those over the branch's earlier :::tip; the references feature is still documented in the effect/context JSDoc. Nothing actionable.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Merging this PR will not alter performance
|
There was a problem hiding this comment.
🟢 Approval recommended
Static review found no blocking issues, with focused runtime and type coverage included; tests were not executed.
No open issues 🎉
What changed in this PR
Enhances oRPC’s Effect integration with richer wrap options and runtime context propagation.
Changes:
- Adds middleware/handler-discriminated wrap options.
- Passes Effect services and runtime references through execution.
- Expands runtime tests, type tests, and error-handling documentation.
| File | Description |
|---|---|
| packages/effect/src/runtime.ts | Runs effects with the supplied context and runtime options. |
| packages/effect/src/runtime.test.ts | Tests service and scheduler propagation. |
| packages/effect/src/middleware.ts | Supplies middleware wrap options and runtime context. |
| packages/effect/src/middleware.test.ts | Tests wrap invocation, options, and downstream failures. |
| packages/effect/src/handler.ts | Supplies handler wrap options and runtime context. |
| packages/effect/src/handler.test.ts | Tests error mapping, tracing, and service precedence. |
| packages/effect/src/context.ts | Defines discriminated wrap options and documents semantics. |
| packages/effect/src/context.test-d.ts | Adds wrap-contract and context type tests. |
| apps/content/docs/integrations/effect.mdx | Updates error-handling examples and wrap guidance. |
Review effort: Balanced

Summary
Enhances the Effect integration's
effect/wraphook to provide richer context about where it's being invoked and to properly pass the Effect context (including services and references like Tracer/Scheduler) through the effect execution pipeline.Key Changes
Discriminated wrap options:
effect/wrapnow receivesEffectWrapOptionsdiscriminated bykind('middleware' or 'handler'), providing:kind: discriminator for middleware vs handler wrappinginput: the validated input at that point in the pipelineerrors: the error constructor map for that procedurecontext,path,procedure,signal,lastEventId: existing optionsEffect context propagation: The
effect/contextfrom the oRPC context is now properly passed toEffect.runPromiseExitWith(), allowing services and references (Tracer, Scheduler, Logger, etc.) to be available throughout effect executionWrap invocation semantics:
effect/wrapis now called once permiddlewareGenandhandlerGen, with failures from inner layers passing through it again (idempotent design)Runtime improvements: Updated
runPromise()to accept and useEffect.RunOptionsincluding the context parameterType safety: Added comprehensive type definitions (
EffectWrapMiddlewareOptions,EffectWrapHandlerOptions,EffectWrapOptions) and type tests incontext.test-d.tsNotable Implementation Details
effect/wraptake precedence over those ineffect/context(via Effect's service layering)effect/contextavailable, allowing wrap implementations to access services viaEffect.serviceOption()effect/wraphttps://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg