Skip to content

feat(effect): enhance effect/wrap with discriminated options and context passing - #2262

Merged
dinwwwh merged 7 commits into
mainfrom
claude/festive-ride-65hat8
Oct 18, 2026
Merged

dinwwwh merged 7 commits into
mainfrom
claude/festive-ride-65hat8

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 18, 2026

Copy link
Copy Markdown
Member

Summary

Enhances the Effect integration's effect/wrap hook 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/wrap now receives EffectWrapOptions discriminated by kind ('middleware' or 'handler'), providing:

    • kind: discriminator for middleware vs handler wrapping
    • input: the validated input at that point in the pipeline
    • errors: the error constructor map for that procedure
    • context, path, procedure, signal, lastEventId: existing options
  • Effect context propagation: The effect/context from the oRPC context is now properly passed to Effect.runPromiseExitWith(), allowing services and references (Tracer, Scheduler, Logger, etc.) to be available throughout effect execution

  • Wrap invocation semantics: effect/wrap is now called once per middlewareGen and handlerGen, with failures from inner layers passing through it again (idempotent design)

  • Runtime improvements: Updated runPromise() to accept and use Effect.RunOptions including the context parameter

  • Type safety: Added comprehensive type definitions (EffectWrapMiddlewareOptions, EffectWrapHandlerOptions, EffectWrapOptions) and type tests in context.test-d.ts

Notable Implementation Details

  • Services provided by effect/wrap take precedence over those in effect/context (via Effect's service layering)
  • The wrap function runs with effect/context available, allowing wrap implementations to access services via Effect.serviceOption()
  • Removed the test case for "throw original errors without fiber failure error wrapper" as error handling is now delegated to effect/wrap
  • Updated documentation with practical examples of error mapping and OpenTelemetry integration patterns

https://claude.ai/code/session_01BQ43QtcK3ZzzFbbxjAGFeg

…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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • effect/wrap type redesign (packages/effect/src/context.ts) — signature is now <A>(effect: Effect.Effect<A, unknown>, opts: EffectWrapOptions) => Effect.Effect<A, unknown>; new EffectWrapOptions discriminated by kind (EffectWrapMiddlewareOptions / EffectWrapHandlerOptions) carrying input, errors, context, path, procedure, signal, lastEventId.
  • Context propagation through the runtime (runtime.ts, handler.ts, middleware.ts) — dropped Effect.provide(effectContext); runPromise now runs via Effect.runPromiseExitWith(effectContext ?? Context.empty()), making effect/context the 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 per handlerGen/middlewareGen with 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 from effect/wrap + TracingLive to effect/context via ManagedRuntime.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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@codecov

codecov Bot commented Oct 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new

pkg-pr-new Bot commented Oct 18, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2262

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2262

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2262

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2262

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2262

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2262

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2262

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2262

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2262

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2262

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2262

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2262

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2262

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2262

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2262

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2262

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2262

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2262

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2262

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2262

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2262

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2262

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2262

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2262

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2262

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2262

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2262

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2262

commit: 01b5f6c

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.ts rework, workspace package.json/pnpm-lock.yaml bumps) and newer apps/content/docs/integrations/effect.mdx / context.ts JSDoc.
  • No source change to this PR's code — handler.ts, middleware.ts, runtime.ts and every *.test.ts / *.test-d.ts touched by the PR are byte-identical to the previously reviewed revision (git diff 1c57cd3 01b5f6c --stat confirms only context.ts and effect.mdx moved, and context.ts changed in its JSDoc only). The wrap/context behavior reviewed at 1c57cd3 still 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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@codspeed

codspeed Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 5 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/festive-ride-65hat8 (01b5f6c) with main (23f55e9)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

@dinwwwh
dinwwwh merged commit eed2b14 into main Oct 18, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants