{"id":35505108,"url":"https://github.com/utopyin/effect-orpc","last_synced_at":"2026-06-13T02:00:45.016Z","repository":{"id":331368218,"uuid":"1126324949","full_name":"utopyin/effect-orpc","owner":"utopyin","description":"Effect-TS integration for oRPC","archived":false,"fork":false,"pushed_at":"2026-05-18T16:19:11.000Z","size":5941,"stargazers_count":57,"open_issues_count":3,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T18:28:36.601Z","etag":null,"topics":["effect","effect-ts","orpc"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/effect-orpc","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/utopyin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"utopyin","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2026-01-01T17:00:49.000Z","updated_at":"2026-05-16T14:13:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/utopyin/effect-orpc","commit_stats":null,"previous_names":["utopyin/effect-orpc"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/utopyin/effect-orpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utopyin%2Feffect-orpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utopyin%2Feffect-orpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utopyin%2Feffect-orpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utopyin%2Feffect-orpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/utopyin","download_url":"https://codeload.github.com/utopyin/effect-orpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utopyin%2Feffect-orpc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34269364,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["effect","effect-ts","orpc"],"created_at":"2026-01-03T19:15:13.474Z","updated_at":"2026-06-13T02:00:45.002Z","avatar_url":"https://github.com/utopyin.png","language":"TypeScript","funding_links":["https://github.com/sponsors/utopyin"],"categories":[],"sub_categories":[],"readme":"# effect-orpc\n\nA type-safe integration between [oRPC](https://orpc.dev/) and [Effect](https://effect.website/), enabling Effect-native procedures with full service injection support, OpenTelemetry tracing support and typesafe Effect errors support.\n\nInspired by [effect-trpc](https://github.com/mikearnaldi/effect-trpc).\n\n## Features\n\n- **Effect-native procedures** - Write oRPC procedures using generators with `yield*` syntax\n- **Type-safe service injection** - Add base services with `.provide(layer)` or pass a `Layer` / `ManagedRuntime\u003cR\u003e` directly\n- **Tagged errors** - Create Effect-native error classes with `ORPCTaggedError` that integrate with oRPC's error handling\n- **Full oRPC compatibility** - Mix Effect procedures with standard oRPC procedures in the same router\n- **Telemetry support with automatic tracing** - Procedures are automatically traced with OpenTelemetry-compatible spans. Customize span names with `.traced()`.\n- **Builder pattern preserved** - oRPC builder methods (`.errors()`, `.meta()`, `.route()`, `.input()`, `.output()`, `.use()`) work seamlessly\n\n## Installation\n\n```bash\nnpm install effect-orpc\n# or\npnpm add effect-orpc\n# or\nbun add effect-orpc\n```\n\nRunnable demos live in the repository's `examples/` directory.\n\n## Demo\n\n```ts\nimport { os } from \"@orpc/server\";\nimport { Effect, ManagedRuntime } from \"effect\";\nimport { makeEffectORPC, ORPCTaggedError } from \"effect-orpc\";\n\ninterface User {\n  id: number;\n  name: string;\n}\n\nlet users: User[] = [\n  { id: 1, name: \"John Doe\" },\n  { id: 2, name: \"Jane Doe\" },\n  { id: 3, name: \"James Dane\" },\n];\n\n// Authenticated os with initial context \u0026 errors set\nconst authedOs = os\n  .errors({ UNAUTHORIZED: { status: 401 } })\n  .$context\u003c{ userId?: number }\u003e()\n  .use(({ context, errors, next }) =\u003e {\n    if (context.userId === undefined) throw errors.UNAUTHORIZED();\n    return next({ context: { ...context, userId: context.userId } });\n  });\n\n// Define your services\nclass UsersRepo extends Effect.Service\u003cUsersRepo\u003e()(\"UsersRepo\", {\n  accessors: true,\n  sync: () =\u003e ({\n    get: (id: number) =\u003e users.find((u) =\u003e u.id === id),\n  }),\n}) {}\n\n// Special yieldable oRPC error class\nclass UserNotFoundError extends ORPCTaggedError(\"UserNotFoundError\", {\n  status: 404,\n}) {}\n\n// Create an Effect-aware oRPC builder with your service layer, optionally from\n// another base oRPC builder, and provide tagged errors.\nconst effectOs = makeEffectORPC(UsersRepo.Default, authedOs).errors({\n  UserNotFoundError,\n});\n\n// You can also pass an explicit ManagedRuntime if you need lifecycle control:\n// const runtime = ManagedRuntime.make(UsersRepo.Default);\n// const effectOs = makeEffectORPC(runtime, authedOs).errors({ UserNotFoundError });\n\n// Or start with only the builder and provide the layer later:\n// const effectOs = makeEffectORPC(authedOs)\n//   .provide(UsersRepo.Default)\n//   .errors({ UserNotFoundError });\n\n// Create the router with mixed procedures\nexport const router = {\n  health: os.handler(() =\u003e \"ok\"),\n  users: {\n    me: effectOs.effect(function* ({ context: { userId } }) {\n      const user = yield* UsersRepo.get(userId);\n      if (!user) {\n        return yield* new UserNotFoundError();\n      }\n      return user;\n    }),\n  },\n};\n\nexport type Router = typeof router;\n```\n\n## Type Safety\n\nThe wrapper enforces that Effect procedures only use services provided by `.provide(layer)`, request-scoped `.provide(tag, provider)` calls, or an initial `Layer` / `ManagedRuntime`. If you try to use a service that isn't available, you'll get a compile-time error:\n\n```ts\nimport { Context, Effect, Layer } from \"effect\";\nimport { makeEffectORPC } from \"effect-orpc\";\n\nclass ProvidedService extends Context.Tag(\"ProvidedService\")\u003c\n  ProvidedService,\n  { doSomething: () =\u003e Effect.Effect\u003cstring\u003e }\n\u003e() {}\n\nclass MissingService extends Context.Tag(\"MissingService\")\u003c\n  MissingService,\n  { doSomething: () =\u003e Effect.Effect\u003cstring\u003e }\n\u003e() {}\n\nconst AppLive = Layer.succeed(ProvidedService, {\n  doSomething: () =\u003e Effect.succeed(\"ok\"),\n});\n\nconst effectOs = makeEffectORPC(AppLive);\n\n// ✅ This compiles - ProvidedService is provided by AppLive\nconst works = effectOs.effect(function* () {\n  const service = yield* ProvidedService;\n  return yield* service.doSomething();\n});\n\n// ❌ This fails to compile - MissingService is not provided\nconst fails = effectOs.effect(function* () {\n  const service = yield* MissingService; // Type error!\n  return yield* service.doSomething();\n});\n```\n\n## Error Handling\n\n`ORPCTaggedError` lets you create Effect-native error classes that integrate seamlessly with oRPC. These errors:\n\n- Can be yielded in Effect generators (`yield* new MyError()` or `yield* Effect.fail(errors.MyError)`)\n- Can be used in Effect builder's `.errors()` maps for type-safe error handling alongside regular oRPC errors\n- Automatically convert to ORPCError when thrown\n\nMake sure the tagged error class is passed to the effect `.errors()` to be able to yield the error class directly and make the client recognize it as defined.\n\n```ts\nconst getUser = effectOs\n  // Mixed error maps\n  .errors({\n    // Regular oRPC error\n    NOT_FOUND: {\n      message: \"User not found\",\n      data: z.object({ id: z.string() }),\n    },\n    // Effect oRPC tagged error\n    UserNotFoundError,\n    // Note: The key of an oRPC error is not used as the error code\n    // So the following will only change the key of the error when accessing it\n    // from the errors object passed to the handler, but not the actual error code itself.\n    // To change the error's code, please see the next section on creating tagged errors.\n    USER_NOT_FOUND: UserNotFoundError,\n    // ^^^ same code as the `UserNotFoundError` error key, defined at the class level\n  })\n  .effect(function* ({ input, errors }) {\n    const user = yield* UsersRepo.findById(input.id);\n    if (!user) {\n      return yield* new UserNotFoundError();\n      // or return `yield* Effect.fail(errors.USER_NOT_FOUND())`\n    }\n    return user;\n  });\n```\n\n### Creating Tagged Errors\n\n```ts\nimport { ORPCTaggedError } from \"effect-orpc\";\n\n// Basic tagged error - code defaults to 'USER_NOT_FOUND' (CONSTANT_CASE of tag)\nclass UserNotFound extends ORPCTaggedError(\"UserNotFound\") {}\n\n// With explicit code\nclass NotFound extends ORPCTaggedError(\"NotFound\", { code: \"NOT_FOUND\" }) {}\n\n// With default options (code defaults to 'VALIDATION_ERROR') (CONSTANT_CASE of tag)\nclass ValidationError extends ORPCTaggedError(\"ValidationError\", {\n  status: 400,\n  message: \"Validation failed\",\n}) {}\n\n// With all options\nclass ForbiddenError extends ORPCTaggedError(\"ForbiddenError\", {\n  code: \"FORBIDDEN\",\n  status: 403,\n  message: \"Access denied\",\n  schema: z.object({\n    reason: z.string(),\n  }),\n}) {}\n\n// With typed data using Standard Schema\nclass UserNotFoundWithData extends ORPCTaggedError(\"UserNotFoundWithData\", {\n  schema: z.object({ userId: z.string() }),\n}) {}\n```\n\n## Traceable Spans\n\nAll Effect procedures are automatically traced with `Effect.withSpan`. By default, the span name is the procedure path (e.g., `users.getUser`):\n\n```ts\n// Router structure determines span names automatically\nconst router = {\n  users: {\n    // Span name: \"users.get\"\n    get: effectOs.input(z.object({ id: z.string() })).effect(function* ({\n      input,\n    }) {\n      const userService = yield* UserService;\n      return yield* userService.findById(input.id);\n    }),\n    // Span name: \"users.create\"\n    create: effectOs.input(z.object({ name: z.string() })).effect(function* ({\n      input,\n    }) {\n      const userService = yield* UserService;\n      return yield* userService.create(input.name);\n    }),\n  },\n};\n```\n\nUse `.traced()` to override the default span name:\n\n```ts\nconst getUser = effectOs\n  .input(z.object({ id: z.string() }))\n  .traced(\"custom.span.name\") // Override the default path-based name\n  .effect(function* ({ input }) {\n    const userService = yield* UserService;\n    return yield* userService.findById(input.id);\n  });\n```\n\n### Enabling OpenTelemetry\n\nTo enable tracing, include the OpenTelemetry layer in your application layer:\n\n```ts\nimport { NodeSdk } from \"@effect/opentelemetry\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { SimpleSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\n\nconst TracingLive = NodeSdk.layer(\n  Effect.sync(() =\u003e ({\n    resource: { serviceName: \"my-service\" },\n    spanProcessor: [new SimpleSpanProcessor(new OTLPTraceExporter())],\n  })),\n);\n\nconst AppLive = Layer.mergeAll(UserServiceLive, TracingLive);\n\nconst effectOs = makeEffectORPC(AppLive);\n```\n\n### Error Stack Traces\n\nWhen an Effect procedure fails, the span includes a properly formatted stack trace pointing to the definition site:\n\n```\nMyCustomError: Something went wrong\n    at \u003canonymous\u003e (/app/src/procedures.ts:42:28)\n    at users.getById (/app/src/procedures.ts:41:35)\n```\n\n## Effect middleware\n\n`.use(...)` accepts generator-based Effect middleware in addition to native oRPC\nmiddleware. Two patterns are supported:\n\n**Gate** — run auth or validation side effects, then let the pipeline continue\nautomatically (no need to call `next`):\n\n```ts\neffectOs.use(function* () {\n  const user = yield* CurrentUser;\n  yield* requireActiveUser(user);\n});\n```\n\n**Wrap** — call downstream explicitly and return the result. When porting oRPC\nmiddleware that uses `return next(...)`, use `return yield* next(...)`:\n\n```ts\neffectOs.use(function* ({ next }) {\n  const user = yield* CurrentUser;\n  yield* requireActiveUser(user);\n\n  return yield* next({\n    context: { userId: user.id },\n  });\n});\n```\n\nTo transform the downstream output, capture `next()` and pass through `output`:\n\n```ts\neffectOs.use(function* ({ next }, _input, output) {\n  const result = yield* next();\n  return yield* output(`${result.output}-wrapped`);\n});\n```\n\nCalling `yield* next()` without returning its result still runs the handler once,\nbut prefer `return yield* next(...)` so the pipeline receives your middleware\nresult explicitly.\n\n### Runtime boundaries and fiber context continuity\n\n`effect-orpc` batches contiguous Effect-native steps into one runtime boundary.\nEffect-native steps are `.provide(...)`, `.provideOptional(...)`, generator\n`.use(function* ...)`, and `.effect(function* ...)`.\n\n```ts\nmakeEffectORPC(AppLive)\n  .provide(CurrentUser, ({ context }) =\u003e Effect.succeed(context.user))\n  .use(function* ({ next }) {\n    const user = yield* CurrentUser;\n    return yield* next({ context: { userId: user.id } });\n  })\n  .effect(function* ({ context }) {\n    const user = yield* CurrentUser;\n    return `${context.userId}:${user.id}`;\n  });\n```\n\nThe example above runs the provider, middleware, and handler inside a single\ninternal `runtime.runPromiseExit(...)` call.\n\nA native oRPC middleware breaks the contiguous Effect pipeline. Pending Effect\nsteps are flushed into one generated oRPC middleware before the native middleware:\n\n```ts\nmakeEffectORPC(AppLive)\n  .provide(CurrentUser, getCurrentUser) // Effect group #1\n  .use(function* ({ next }) {\n    return yield* next();\n  })\n  .use(({ next }) =\u003e next()) // native oRPC middleware; flushes group #1\n  .use(function* ({ next }) {\n    return yield* next();\n  }) // Effect group #2\n  .effect(function* () {\n    return \"ok\";\n  });\n```\n\nThat split still creates multiple runtime boundaries. If the Node bridge is\ninstalled, however, `effect-orpc` carries the current `FiberRefs` through the\nnative oRPC continuation and merges them into the next Effect boundary:\n\n```ts\nimport \"effect-orpc/node\";\n```\n\nUse the side-effect import when you only need continuity across internal\n`effect-orpc` boundaries, such as Effect group #1 → native oRPC middleware →\nEffect group #2.\n\nProcedure-level `.provide*` after a native `.handler(...)` has no Effect handler\nboundary to attach to, so it is installed as an oRPC middleware that runs its\nprovider Effect through the runtime:\n\n```ts\nmakeEffectORPC(AppLive)\n  .handler(() =\u003e \"ok\") // native oRPC handler\n  .provide(CurrentUser, getCurrentUser); // fallback provider middleware\n```\n\nIf you want `.provide*` and Effect middleware to batch with the handler, use\n`.effect(function* ...)` instead of `.handler(...)`.\n\n## Request-Scoped Fiber Context\n\nThe `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has\ntwo uses:\n\n- `import \"effect-orpc/node\"` installs the bridge passively. This is enough for\n  `effect-orpc` to propagate `FiberRefs` across its own split runtime boundaries.\n- `withFiberContext(() =\u003e next())` actively seeds the bridge from an external\n  Effect scope, such as framework middleware wrapping an oRPC handler.\n\nUse `withFiberContext` when request-local `FiberRef` state is created outside the\noRPC pipeline and should be visible inside handlers:\n\n```ts\nimport { Hono } from \"hono\";\nimport { Effect } from \"effect\";\nimport { makeEffectORPC } from \"effect-orpc\";\nimport { withFiberContext } from \"effect-orpc/node\";\n\nconst effectOs = makeEffectORPC(AppLive);\nconst app = new Hono();\n\napp.use(\"*\", async (c, next) =\u003e {\n  await Effect.runPromise(\n    Effect.gen(function* () {\n      yield* Effect.annotateLogsScoped({\n        requestId: c.get(\"requestId\"),\n      });\n\n      yield* withFiberContext(() =\u003e next());\n    }),\n  );\n});\n```\n\nImporting `withFiberContext` from `effect-orpc/node` also installs the bridge, so\nyou do not need a separate side-effect import.\n\nWhen a captured fiber context and the application `Layer` / `ManagedRuntime`\nboth provide the same service, `effect-orpc` prioritizes the captured context.\nThe application layer is treated as the base layer, while the bridge preserves more specific\nrequest-scoped values such as request IDs, logging annotations, tracing context,\nor scoped overrides when crossing runtime boundaries.\n\nThe main package stays runtime-agnostic; `/node` is separate because the bridge\nrelies on `AsyncLocalStorage` from `node:async_hooks`.\n\n## Contract-First Usage\n\nUse `implementEffect(contract, layerOrRuntime)` when you already have an oRPC\ncontract and want to keep contract-first enforcement while adding Effect-native\nhandlers. Use `makeEffectORPC(layerOrRuntime, builder?)` when you want to build\nprocedures directly from an oRPC builder.\n\n```ts\nimport { Effect } from \"effect\";\nimport { eoc, implementEffect } from \"effect-orpc\";\nimport z from \"zod\";\n\nclass UsersRepo extends Effect.Service\u003cUsersRepo\u003e()(\"UsersRepo\", {\n  accessors: true,\n  sync: () =\u003e ({\n    list: (amount: number) =\u003e\n      Array.from({ length: amount }, (_, index) =\u003e `user-${index + 1}`),\n  }),\n}) {}\n\nconst contract = {\n  users: {\n    list: eoc\n      .input(z.object({ amount: z.number().int().positive() }))\n      .output(z.array(z.string())),\n  },\n};\n\nconst oe = implementEffect(contract, UsersRepo.Default);\n\nexport const router = oe.router({\n  users: {\n    list: oe.users.list.effect(function* ({ input }) {\n      return yield* UsersRepo.list(input.amount);\n    }),\n  },\n});\n```\n\nContract leaves keep the contract-defined input, output, and error surface.\nThey add `.effect(...)` alongside existing implementer methods such as\n`.handler(...)` and `.use(...)`, but do not expose contract-changing builder\nmethods like `.input(...)` or `.output(...)`.\n\nIf your contract declares tagged Effect error classes, prefer `eoc.errors(...)`\ninstead of raw `oc.errors(...)` so the error schema and metadata are derived\ndirectly from the `ORPCTaggedError` class.\n\n## API Reference\n\n### `makeEffectORPC(layerOrRuntime, builder?)`\n\nCreates an Effect-aware procedure builder. The recommended default is to pass\nyour application `Layer` up front.\n\nReturns an `EffectBuilder` instance.\n\n```ts\n// With default builder\nconst effectOs = makeEffectORPC(AppLive);\n\n// With customized builder\nconst effectAuthedOs = makeEffectORPC(AppLive, authedBuilder);\n```\n\nYou can also start from a builder and provide the layer later, or pass a\n`ManagedRuntime` when you need explicit runtime lifecycle control:\n\n```ts\nconst effectOsWithProvidedLayer = makeEffectORPC().provide(AppLive);\nconst effectAuthedOsWithProvidedLayer =\n  makeEffectORPC(authedBuilder).provide(AppLive);\nconst effectOsFromRuntime = makeEffectORPC(runtime);\n```\n\n### `implementEffect(contract, layerOrRuntime)`\n\nCreates an Effect-aware contract implementer.\n\n- `contract` - An oRPC contract router built with `oc`\n- `layerOrRuntime` - A `Layer\u003cR, E, never\u003e` or `ManagedRuntime\u003cR, E\u003e` that provides services for Effect procedures\n\nReturns a contract-shaped implementer tree whose leaves support `.effect(...)`.\n\n```ts\nconst oe = implementEffect(contract, AppLive);\n\nconst router = oe.router({\n  users: {\n    list: oe.users.list.effect(function* ({ input }) {\n      return yield* UsersRepo.list(input.amount);\n    }),\n  },\n});\n```\n\n### `eoc`\n\nAn Effect-aware wrapper around oRPC's `oc` contract builder.\n\nUse it when you want contract definitions to accept `ORPCTaggedError` classes\ndirectly in `.errors(...)` without duplicating the error schema.\n\n```ts\nclass UserNotFoundError extends ORPCTaggedError(\"UserNotFoundError\", {\n  code: \"NOT_FOUND\",\n  schema: z.object({ userId: z.string() }),\n}) {}\n\nconst contract = {\n  users: {\n    find: eoc\n      .errors({\n        NOT_FOUND: UserNotFoundError,\n      })\n      .input(z.object({ userId: z.string() }))\n      .output(z.object({ userId: z.string() })),\n  },\n};\n```\n\n### `EffectBuilder`\n\nWraps an oRPC Builder with Effect support. Available methods:\n\n| Method              | Description                                                                          |\n| ------------------- | ------------------------------------------------------------------------------------ |\n| `.$config(config)`  | Set or override the builder config                                                   |\n| `.$context\u003cU\u003e()`    | Set or override the initial context type                                             |\n| `.$meta(meta)`      | Set or override the initial metadata                                                 |\n| `.$route(route)`    | Set or override the initial route configuration                                      |\n| `.$input(schema)`   | Set or override the initial input schema                                             |\n| `.errors(map)`      | Add type-safe custom errors                                                          |\n| `.meta(meta)`       | Set procedure metadata (merged with existing)                                        |\n| `.route(route)`     | Configure OpenAPI route (merged with existing)                                       |\n| `.input(schema)`    | Define input validation schema                                                       |\n| `.output(schema)`   | Define output validation schema                                                      |\n| `.provide(layer)`   | Provide a base Effect layer to downstream Effect middleware and handlers             |\n| `.provide(tag, fn)` | Provide a request-scoped Effect service to downstream Effect middleware and handlers |\n| `.use(middleware)`  | Add middleware                                                                       |\n| `.traced(name)`     | Add a traceable span for telemetry (optional, defaults to the procedure's path)      |\n| `.handler(handler)` | Define a non-Effect handler (standard oRPC handler)                                  |\n| `.effect(handler)`  | Define the Effect handler                                                            |\n| `.prefix(prefix)`   | Prefix all procedures in the router (for OpenAPI)                                    |\n| `.tag(...tags)`     | Add tags to all procedures in the router (for OpenAPI)                               |\n| `.router(router)`   | Apply all options to a router                                                        |\n| `.lazy(loader)`     | Create and apply options to a lazy-loaded router                                     |\n\n### `EffectDecoratedProcedure`\n\nThe result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` with Effect type preservation.\n\n| Method                  | Description                                   |\n| ----------------------- | --------------------------------------------- |\n| `.errors(map)`          | Add more custom errors                        |\n| `.meta(meta)`           | Update metadata (merged with existing)        |\n| `.route(route)`         | Update route configuration (merged)           |\n| `.provide(layer)`       | Provide a base Effect layer                   |\n| `.provide(tag, fn)`     | Provide a request-scoped Effect service       |\n| `.use(middleware)`      | Add middleware                                |\n| `.callable(options?)`   | Make procedure directly invocable             |\n| `.actionable(options?)` | Make procedure compatible with server actions |\n\n### `ORPCTaggedError(tag, options?)`\n\nFactory function to create Effect-native tagged error classes.\n\nThe options is an optional object containing:\n\n- `schema?` - Optional Standard Schema for the error's data payload (e.g., `z.object({ userId: z.string() })`)\n- `code?` - Optional ORPCErrorCode, defaults to CONSTANT_CASE of the tag (e.g., `UserNotFoundError` → `USER_NOT_FOUND_ERROR`).\n- `status?` - Sets the default status of the error\n- `message` - Sets the default message of the error\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Futopyin%2Feffect-orpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Futopyin%2Feffect-orpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Futopyin%2Feffect-orpc/lists"}