effect-orpc 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@ Runnable demos live in the repository's `examples/` directory.
30
30
  ```ts
31
31
  import { os } from "@orpc/server";
32
32
  import { Effect, ManagedRuntime } from "effect";
33
- import { makeEffectORPC, ORPCTaggedError } from "effect-orpc";
33
+ import { eos, makeEffectORPC, ORPCTaggedError } from "effect-orpc";
34
34
 
35
35
  interface User {
36
36
  id: number;
@@ -43,15 +43,6 @@ let users: User[] = [
43
43
  { id: 3, name: "James Dane" },
44
44
  ];
45
45
 
46
- // Authenticated os with initial context & errors set
47
- const authedOs = os
48
- .errors({ UNAUTHORIZED: { status: 401 } })
49
- .$context<{ userId?: number }>()
50
- .use(({ context, errors, next }) => {
51
- if (context.userId === undefined) throw errors.UNAUTHORIZED();
52
- return next({ context: { ...context, userId: context.userId } });
53
- });
54
-
55
46
  // Define your services
56
47
  class UsersRepo extends Effect.Service<UsersRepo>()("UsersRepo", {
57
48
  accessors: true,
@@ -65,26 +56,27 @@ class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
65
56
  status: 404,
66
57
  }) {}
67
58
 
68
- // Create an Effect-aware oRPC builder with your service layer, optionally from
69
- // another base oRPC builder, and provide tagged errors.
70
- const effectOs = makeEffectORPC(UsersRepo.Default, authedOs).errors({
71
- UserNotFoundError,
72
- });
59
+ // Create an Effect-aware oRPC builder with your service layer, context, errors,
60
+ // and middleware.
61
+ const effectProcedure = eos
62
+ .provide(UsersRepo.Default)
63
+ .errors({ UNAUTHORIZED: { status: 401 }, UserNotFoundError })
64
+ .$context<{ userId?: number }>()
65
+ .use(({ context, errors, next }) => {
66
+ if (context.userId === undefined) throw errors.UNAUTHORIZED();
67
+ return next({ context: { ...context, userId: context.userId } });
68
+ });
73
69
 
74
- // You can also pass an explicit ManagedRuntime if you need lifecycle control:
70
+ // Use ManagedRuntime only when scoped resources should be acquired once and
71
+ // released on shutdown, for example a shared cache, database pool, or telemetry SDK:
75
72
  // const runtime = ManagedRuntime.make(UsersRepo.Default);
76
- // const effectOs = makeEffectORPC(runtime, authedOs).errors({ UserNotFoundError });
77
-
78
- // Or start with only the builder and provide the layer later:
79
- // const effectOs = makeEffectORPC(authedOs)
80
- // .provide(UsersRepo.Default)
81
- // .errors({ UserNotFoundError });
73
+ // const effectProcedure = makeEffectORPC(runtime).errors({ UserNotFoundError });
82
74
 
83
75
  // Create the router with mixed procedures
84
76
  export const router = {
85
77
  health: os.handler(() => "ok"),
86
78
  users: {
87
- me: effectOs.effect(function* ({ context: { userId } }) {
79
+ me: effectProcedure.effect(function* ({ context: { userId } }) {
88
80
  const user = yield* UsersRepo.get(userId);
89
81
  if (!user) {
90
82
  return yield* new UserNotFoundError();
@@ -103,7 +95,7 @@ The wrapper enforces that Effect procedures only use services provided by `.prov
103
95
 
104
96
  ```ts
105
97
  import { Context, Effect, Layer } from "effect";
106
- import { makeEffectORPC } from "effect-orpc";
98
+ import { eos } from "effect-orpc";
107
99
 
108
100
  class ProvidedService extends Context.Tag("ProvidedService")<
109
101
  ProvidedService,
@@ -119,16 +111,16 @@ const AppLive = Layer.succeed(ProvidedService, {
119
111
  doSomething: () => Effect.succeed("ok"),
120
112
  });
121
113
 
122
- const effectOs = makeEffectORPC(AppLive);
114
+ const effectProcedure = eos.provide(AppLive);
123
115
 
124
116
  // ✅ This compiles - ProvidedService is provided by AppLive
125
- const works = effectOs.effect(function* () {
117
+ const works = effectProcedure.effect(function* () {
126
118
  const service = yield* ProvidedService;
127
119
  return yield* service.doSomething();
128
120
  });
129
121
 
130
122
  // ❌ This fails to compile - MissingService is not provided
131
- const fails = effectOs.effect(function* () {
123
+ const fails = effectProcedure.effect(function* () {
132
124
  const service = yield* MissingService; // Type error!
133
125
  return yield* service.doSomething();
134
126
  });
@@ -145,7 +137,7 @@ const fails = effectOs.effect(function* () {
145
137
  Make 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.
146
138
 
147
139
  ```ts
148
- const getUser = effectOs
140
+ const getUser = effectProcedure
149
141
  // Mixed error maps
150
142
  .errors({
151
143
  // Regular oRPC error
@@ -214,19 +206,19 @@ All Effect procedures are automatically traced with `Effect.withSpan`. By defaul
214
206
  const router = {
215
207
  users: {
216
208
  // Span name: "users.get"
217
- get: effectOs.input(z.object({ id: z.string() })).effect(function* ({
209
+ get: effectProcedure.input(z.object({ id: z.string() })).effect(function* ({
218
210
  input,
219
211
  }) {
220
212
  const userService = yield* UserService;
221
213
  return yield* userService.findById(input.id);
222
214
  }),
223
215
  // Span name: "users.create"
224
- create: effectOs.input(z.object({ name: z.string() })).effect(function* ({
225
- input,
226
- }) {
227
- const userService = yield* UserService;
228
- return yield* userService.create(input.name);
229
- }),
216
+ create: effectProcedure
217
+ .input(z.object({ name: z.string() }))
218
+ .effect(function* ({ input }) {
219
+ const userService = yield* UserService;
220
+ return yield* userService.create(input.name);
221
+ }),
230
222
  },
231
223
  };
232
224
  ```
@@ -234,7 +226,7 @@ const router = {
234
226
  Use `.traced()` to override the default span name:
235
227
 
236
228
  ```ts
237
- const getUser = effectOs
229
+ const getUser = effectProcedure
238
230
  .input(z.object({ id: z.string() }))
239
231
  .traced("custom.span.name") // Override the default path-based name
240
232
  .effect(function* ({ input }) {
@@ -261,7 +253,7 @@ const TracingLive = NodeSdk.layer(
261
253
 
262
254
  const AppLive = Layer.mergeAll(UserServiceLive, TracingLive);
263
255
 
264
- const effectOs = makeEffectORPC(AppLive);
256
+ const effectProcedure = eos.provide(AppLive);
265
257
  ```
266
258
 
267
259
  ### Error Stack Traces
@@ -276,14 +268,14 @@ MyCustomError: Something went wrong
276
268
 
277
269
  ## Effect middleware
278
270
 
279
- `.use(...)` accepts generator-based Effect middleware in addition to native oRPC
280
- middleware. Two patterns are supported:
271
+ `.use(...)` accepts generator-based and Effect-returning middleware in addition
272
+ to native oRPC middleware. Two patterns are supported:
281
273
 
282
274
  **Gate** — run auth or validation side effects, then let the pipeline continue
283
275
  automatically (no need to call `next`):
284
276
 
285
277
  ```ts
286
- effectOs.use(function* () {
278
+ effectProcedure.use(function* () {
287
279
  const user = yield* CurrentUser;
288
280
  yield* requireActiveUser(user);
289
281
  });
@@ -293,7 +285,7 @@ effectOs.use(function* () {
293
285
  middleware that uses `return next(...)`, use `return yield* next(...)`:
294
286
 
295
287
  ```ts
296
- effectOs.use(function* ({ next }) {
288
+ effectProcedure.use(function* ({ next }) {
297
289
  const user = yield* CurrentUser;
298
290
  yield* requireActiveUser(user);
299
291
 
@@ -306,25 +298,45 @@ effectOs.use(function* ({ next }) {
306
298
  To transform the downstream output, capture `next()` and pass through `output`:
307
299
 
308
300
  ```ts
309
- effectOs.use(function* ({ next }, _input, output) {
301
+ effectProcedure.use(function* ({ next }, _input, output) {
310
302
  const result = yield* next();
311
303
  return yield* output(`${result.output}-wrapped`);
312
304
  });
313
305
  ```
314
306
 
315
- Calling `yield* next()` without returning its result still runs the handler once,
316
- but prefer `return yield* next(...)` so the pipeline receives your middleware
317
- result explicitly.
307
+ Calling `yield* next()` without returning its result still runs the handler once, but prefer `return yield* next(...)` so the pipeline receives your middleware result explicitly.
308
+
309
+ Effect-returning middleware is also supported, including `Effect.fn(...)` and
310
+ `() => Effect.gen(...)`:
311
+
312
+ ```ts
313
+ effectProcedure.use(
314
+ Effect.fn("middleware.auth")(function* ({ next }) {
315
+ const user = yield* CurrentUser;
316
+ return yield* next({ context: { userId: user.id } });
317
+ }),
318
+ );
319
+ ```
320
+
321
+ Request-scoped providers support the same generator or Effect-returning style:
322
+
323
+ ```ts
324
+ effectProcedure.provide(CurrentUser, function* ({ context }) {
325
+ yield* Effect.logDebug("resolving current user");
326
+ return context.user;
327
+ });
328
+ ```
318
329
 
319
330
  ### Runtime boundaries and fiber context continuity
320
331
 
321
- `effect-orpc` batches contiguous Effect-native steps into one runtime boundary.
322
- Effect-native steps are `.provide(...)`, `.provideOptional(...)`, generator
323
- `.use(function* ...)`, and `.effect(function* ...)`.
332
+ `effect-orpc` batches contiguous Effect-native steps into one runtime boundary. Effect-native steps are `.provide(...)`, `.provideOptional(...)`, generator `.use(function* ...)`, and `.effect(function* ...)`. Effect-returning handlers, providers, and middleware are supported too; named `Effect.fn(...)` callbacks keep their own spans rather than being wrapped as generators.
324
333
 
325
334
  ```ts
326
- makeEffectORPC(AppLive)
327
- .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
335
+ eos
336
+ .provide(AppLive)
337
+ .provide(CurrentUser, function* ({ context }) {
338
+ return context.user;
339
+ })
328
340
  .use(function* ({ next }) {
329
341
  const user = yield* CurrentUser;
330
342
  return yield* next({ context: { userId: user.id } });
@@ -335,14 +347,13 @@ makeEffectORPC(AppLive)
335
347
  });
336
348
  ```
337
349
 
338
- The example above runs the provider, middleware, and handler inside a single
339
- internal `runtime.runPromiseExit(...)` call.
350
+ The example above runs the provider, middleware, and handler inside a single Effect execution boundary.
340
351
 
341
- A native oRPC middleware breaks the contiguous Effect pipeline. Pending Effect
342
- steps are flushed into one generated oRPC middleware before the native middleware:
352
+ A native oRPC middleware breaks the contiguous Effect pipeline. Pending Effect steps are flushed into one generated oRPC middleware before the native middleware:
343
353
 
344
354
  ```ts
345
- makeEffectORPC(AppLive)
355
+ eos
356
+ .provide(AppLive)
346
357
  .provide(CurrentUser, getCurrentUser) // Effect group #1
347
358
  .use(function* ({ next }) {
348
359
  return yield* next();
@@ -356,51 +367,41 @@ makeEffectORPC(AppLive)
356
367
  });
357
368
  ```
358
369
 
359
- That split still creates multiple runtime boundaries. If the Node bridge is
360
- installed, however, `effect-orpc` carries the current `FiberRefs` through the
361
- native oRPC continuation and merges them into the next Effect boundary:
370
+ That split still creates multiple runtime boundaries. If the Node bridge is installed, however, `effect-orpc` carries the current `FiberRefs` through the native oRPC continuation and merges them into the next Effect boundary:
362
371
 
363
372
  ```ts
364
373
  import "effect-orpc/node";
365
374
  ```
366
375
 
367
- Use the side-effect import when you only need continuity across internal
368
- `effect-orpc` boundaries, such as Effect group #1 → native oRPC middleware →
369
- Effect group #2.
376
+ Use the side-effect import when you only need continuity across internal `effect-orpc` boundaries, such as Effect group #1 → native oRPC middleware → Effect group #2.
370
377
 
371
- Procedure-level `.provide*` after a native `.handler(...)` has no Effect handler
372
- boundary to attach to, so it is installed as an oRPC middleware that runs its
373
- provider Effect through the runtime:
378
+ Procedure-level `.provide*` after a native `.handler(...)` has no Effect handler boundary to attach to, so it is installed as an oRPC middleware that runs its provider Effect through the configured runtime source:
374
379
 
375
380
  ```ts
376
- makeEffectORPC(AppLive)
381
+ eos
382
+ .provide(AppLive)
377
383
  .handler(() => "ok") // native oRPC handler
378
384
  .provide(CurrentUser, getCurrentUser); // fallback provider middleware
379
385
  ```
380
386
 
381
- If you want `.provide*` and Effect middleware to batch with the handler, use
382
- `.effect(function* ...)` instead of `.handler(...)`.
387
+ If you want `.provide*` and Effect middleware to batch with the handler, use `.effect(function* ...)` instead of `.handler(...)`.
383
388
 
384
389
  ## Request-Scoped Fiber Context
385
390
 
386
- The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has
387
- two uses:
391
+ The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has two uses:
388
392
 
389
- - `import "effect-orpc/node"` installs the bridge passively. This is enough for
390
- `effect-orpc` to propagate `FiberRefs` across its own split runtime boundaries.
391
- - `withFiberContext(() => next())` actively seeds the bridge from an external
392
- Effect scope, such as framework middleware wrapping an oRPC handler.
393
+ - `import "effect-orpc/node"` installs the bridge passively. This is enough for `effect-orpc` to propagate `FiberRefs` across its own split runtime boundaries.
394
+ - `withFiberContext(() => next())` actively seeds the bridge from an external Effect scope, such as framework middleware wrapping an oRPC handler.
393
395
 
394
- Use `withFiberContext` when request-local `FiberRef` state is created outside the
395
- oRPC pipeline and should be visible inside handlers:
396
+ Use `withFiberContext` when request-local `FiberRef` state is created outside the oRPC pipeline and should be visible inside handlers:
396
397
 
397
398
  ```ts
398
399
  import { Hono } from "hono";
399
400
  import { Effect } from "effect";
400
- import { makeEffectORPC } from "effect-orpc";
401
+ import { eos } from "effect-orpc";
401
402
  import { withFiberContext } from "effect-orpc/node";
402
403
 
403
- const effectOs = makeEffectORPC(AppLive);
404
+ const effectProcedure = eos.provide(AppLive);
404
405
  const app = new Hono();
405
406
 
406
407
  app.use("*", async (c, next) => {
@@ -416,24 +417,16 @@ app.use("*", async (c, next) => {
416
417
  });
417
418
  ```
418
419
 
419
- Importing `withFiberContext` from `effect-orpc/node` also installs the bridge, so
420
- you do not need a separate side-effect import.
420
+ Importing `withFiberContext` from `effect-orpc/node` also installs the bridge, so you do not need a separate side-effect import.
421
421
 
422
- When a captured fiber context and the application `Layer` / `ManagedRuntime`
423
- both provide the same service, `effect-orpc` prioritizes the captured context.
424
- The application layer is treated as the base layer, while the bridge preserves more specific
425
- request-scoped values such as request IDs, logging annotations, tracing context,
426
- or scoped overrides when crossing runtime boundaries.
422
+ When a captured fiber context and the application `Layer` / `ManagedRuntime` both provide the same service, `effect-orpc` prioritizes the captured context.
423
+ The application layer is treated as the base layer, while the bridge preserves more specific request-scoped values such as request IDs, logging annotations, tracing context, or scoped overrides when crossing runtime boundaries.
427
424
 
428
- The main package stays runtime-agnostic; `/node` is separate because the bridge
429
- relies on `AsyncLocalStorage` from `node:async_hooks`.
425
+ The main package stays runtime-agnostic; `/node` is separate because the bridge relies on `AsyncLocalStorage` from `node:async_hooks`.
430
426
 
431
427
  ## Contract-First Usage
432
428
 
433
- Use `implementEffect(contract, layerOrRuntime)` when you already have an oRPC
434
- contract and want to keep contract-first enforcement while adding Effect-native
435
- handlers. Use `makeEffectORPC(layerOrRuntime, builder?)` when you want to build
436
- procedures directly from an oRPC builder.
429
+ Use `implementEffect(contract, layerOrRuntime)` when you already have an oRPC contract and want to keep contract-first enforcement while adding Effect-native handlers. Use `eos.provide(layer)` when you want to build procedures directly from the default Effect-aware builder.
437
430
 
438
431
  ```ts
439
432
  import { Effect } from "effect";
@@ -467,40 +460,35 @@ export const router = oe.router({
467
460
  });
468
461
  ```
469
462
 
470
- Contract leaves keep the contract-defined input, output, and error surface.
471
- They add `.effect(...)` alongside existing implementer methods such as
472
- `.handler(...)` and `.use(...)`, but do not expose contract-changing builder
473
- methods like `.input(...)` or `.output(...)`.
463
+ Contract leaves keep the contract-defined input, output, and error surface. They add `.effect(...)` alongside existing implementer methods such as `.handler(...)` and `.use(...)`, but do not expose contract-changing builder methods like `.input(...)` or `.output(...)`.
474
464
 
475
- If your contract declares tagged Effect error classes, prefer `eoc.errors(...)`
476
- instead of raw `oc.errors(...)` so the error schema and metadata are derived
477
- directly from the `ORPCTaggedError` class.
465
+ If your contract declares tagged Effect error classes, prefer `eoc.errors(...)` instead of raw `oc.errors(...)` so the error schema and metadata are derived directly from the `ORPCTaggedError` class.
478
466
 
479
467
  ## API Reference
480
468
 
481
- ### `makeEffectORPC(layerOrRuntime, builder?)`
469
+ ### `eos`
482
470
 
483
- Creates an Effect-aware procedure builder. The recommended default is to pass
484
- your application `Layer` up front.
471
+ The default Effect-aware procedure builder. Provide your application services with `.provide(layer)`:
485
472
 
486
- Returns an `EffectBuilder` instance.
473
+ ```ts
474
+ const effectProcedure = eos.provide(AppLive);
475
+ ```
476
+
477
+ Use `makeEffectORPC(runtime)` when a scoped `Layer` should be acquired once and released by your application shutdown path, such as a shared cache, database pool, HTTP client, or telemetry SDK:
487
478
 
488
479
  ```ts
489
- // With default builder
490
- const effectOs = makeEffectORPC(AppLive);
480
+ const runtime = ManagedRuntime.make(AppLive);
481
+
482
+ const effectProcedure = makeEffectORPC(runtime);
491
483
 
492
- // With customized builder
493
- const effectAuthedOs = makeEffectORPC(AppLive, authedBuilder);
484
+ // later, during app shutdown
485
+ await runtime.dispose();
494
486
  ```
495
487
 
496
- You can also start from a builder and provide the layer later, or pass a
497
- `ManagedRuntime` when you need explicit runtime lifecycle control:
488
+ `makeEffectORPC(builder)` is also available when you need to wrap an existing oRPC builder:
498
489
 
499
490
  ```ts
500
- const effectOsWithProvidedLayer = makeEffectORPC().provide(AppLive);
501
- const effectAuthedOsWithProvidedLayer =
502
- makeEffectORPC(authedBuilder).provide(AppLive);
503
- const effectOsFromRuntime = makeEffectORPC(runtime);
491
+ const effectAuthedOs = makeEffectORPC(authedBuilder).provide(AppLive);
504
492
  ```
505
493
 
506
494
  ### `implementEffect(contract, layerOrRuntime)`
@@ -508,7 +496,7 @@ const effectOsFromRuntime = makeEffectORPC(runtime);
508
496
  Creates an Effect-aware contract implementer.
509
497
 
510
498
  - `contract` - An oRPC contract router built with `oc`
511
- - `layerOrRuntime` - A `Layer<R, E, never>` or `ManagedRuntime<R, E>` that provides services for Effect procedures
499
+ - `layerOrRuntime` - A `Layer<R, E, never>` provided per call, or a user-owned `ManagedRuntime<R, E>` when the application should control acquisition and release (for example, a shared cache, database pool, or telemetry SDK)
512
500
 
513
501
  Returns a contract-shaped implementer tree whose leaves support `.effect(...)`.
514
502
 
@@ -528,8 +516,7 @@ const router = oe.router({
528
516
 
529
517
  An Effect-aware wrapper around oRPC's `oc` contract builder.
530
518
 
531
- Use it when you want contract definitions to accept `ORPCTaggedError` classes
532
- directly in `.errors(...)` without duplicating the error schema.
519
+ Use it when you want contract definitions to accept `ORPCTaggedError` classes directly in `.errors(...)` without duplicating the error schema.
533
520
 
534
521
  ```ts
535
522
  class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
@@ -553,43 +540,43 @@ const contract = {
553
540
 
554
541
  Wraps an oRPC Builder with Effect support. Available methods:
555
542
 
556
- | Method | Description |
557
- | ------------------- | ------------------------------------------------------------------------------------ |
558
- | `.$config(config)` | Set or override the builder config |
559
- | `.$context<U>()` | Set or override the initial context type |
560
- | `.$meta(meta)` | Set or override the initial metadata |
561
- | `.$route(route)` | Set or override the initial route configuration |
562
- | `.$input(schema)` | Set or override the initial input schema |
563
- | `.errors(map)` | Add type-safe custom errors |
564
- | `.meta(meta)` | Set procedure metadata (merged with existing) |
565
- | `.route(route)` | Configure OpenAPI route (merged with existing) |
566
- | `.input(schema)` | Define input validation schema |
567
- | `.output(schema)` | Define output validation schema |
568
- | `.provide(layer)` | Provide a base Effect layer to downstream Effect middleware and handlers |
569
- | `.provide(tag, fn)` | Provide a request-scoped Effect service to downstream Effect middleware and handlers |
570
- | `.use(middleware)` | Add middleware |
571
- | `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
572
- | `.handler(handler)` | Define a non-Effect handler (standard oRPC handler) |
573
- | `.effect(handler)` | Define the Effect handler |
574
- | `.prefix(prefix)` | Prefix all procedures in the router (for OpenAPI) |
575
- | `.tag(...tags)` | Add tags to all procedures in the router (for OpenAPI) |
576
- | `.router(router)` | Apply all options to a router |
577
- | `.lazy(loader)` | Create and apply options to a lazy-loaded router |
543
+ | Method | Description |
544
+ | ------------------------- | ------------------------------------------------------------------------------------ |
545
+ | `.$config(config)` | Set or override the builder config |
546
+ | `.$context<U>()` | Set or override the initial context type |
547
+ | `.$meta(meta)` | Set or override the initial metadata |
548
+ | `.$route(route)` | Set or override the initial route configuration |
549
+ | `.$input(schema)` | Set or override the initial input schema |
550
+ | `.errors(map)` | Add type-safe custom errors |
551
+ | `.meta(meta)` | Set procedure metadata (merged with existing) |
552
+ | `.route(route)` | Configure OpenAPI route (merged with existing) |
553
+ | `.input(schema)` | Define input validation schema |
554
+ | `.output(schema)` | Define output validation schema |
555
+ | `.provide(layer)` | Provide a base Effect layer to downstream Effect middleware and handlers |
556
+ | `.provide(tag, provider)` | Provide a request-scoped Effect service to downstream Effect middleware and handlers |
557
+ | `.use(middleware)` | Add middleware |
558
+ | `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
559
+ | `.handler(handler)` | Define a non-Effect handler (standard oRPC handler) |
560
+ | `.effect(handler)` | Define the Effect handler |
561
+ | `.prefix(prefix)` | Prefix all procedures in the router (for OpenAPI) |
562
+ | `.tag(...tags)` | Add tags to all procedures in the router (for OpenAPI) |
563
+ | `.router(router)` | Apply all options to a router |
564
+ | `.lazy(loader)` | Create and apply options to a lazy-loaded router |
578
565
 
579
566
  ### `EffectDecoratedProcedure`
580
567
 
581
568
  The result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` with Effect type preservation.
582
569
 
583
- | Method | Description |
584
- | ----------------------- | --------------------------------------------- |
585
- | `.errors(map)` | Add more custom errors |
586
- | `.meta(meta)` | Update metadata (merged with existing) |
587
- | `.route(route)` | Update route configuration (merged) |
588
- | `.provide(layer)` | Provide a base Effect layer |
589
- | `.provide(tag, fn)` | Provide a request-scoped Effect service |
590
- | `.use(middleware)` | Add middleware |
591
- | `.callable(options?)` | Make procedure directly invocable |
592
- | `.actionable(options?)` | Make procedure compatible with server actions |
570
+ | Method | Description |
571
+ | ------------------------- | --------------------------------------------- |
572
+ | `.errors(map)` | Add more custom errors |
573
+ | `.meta(meta)` | Update metadata (merged with existing) |
574
+ | `.route(route)` | Update route configuration (merged) |
575
+ | `.provide(layer)` | Provide a base Effect layer |
576
+ | `.provide(tag, provider)` | Provide a request-scoped Effect service |
577
+ | `.use(middleware)` | Add middleware |
578
+ | `.callable(options?)` | Make procedure directly invocable |
579
+ | `.actionable(options?)` | Make procedure compatible with server actions |
593
580
 
594
581
  ### `ORPCTaggedError(tag, options?)`
595
582