effect-orpc 0.4.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
@@ -268,8 +268,8 @@ MyCustomError: Something went wrong
268
268
 
269
269
  ## Effect middleware
270
270
 
271
- `.use(...)` accepts generator-based Effect middleware in addition to native oRPC
272
- 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:
273
273
 
274
274
  **Gate** — run auth or validation side effects, then let the pipeline continue
275
275
  automatically (no need to call `next`):
@@ -304,20 +304,39 @@ effectProcedure.use(function* ({ next }, _input, output) {
304
304
  });
305
305
  ```
306
306
 
307
- Calling `yield* next()` without returning its result still runs the handler once,
308
- but prefer `return yield* next(...)` so the pipeline receives your middleware
309
- 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
+ ```
310
329
 
311
330
  ### Runtime boundaries and fiber context continuity
312
331
 
313
- `effect-orpc` batches contiguous Effect-native steps into one runtime boundary.
314
- Effect-native steps are `.provide(...)`, `.provideOptional(...)`, generator
315
- `.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.
316
333
 
317
334
  ```ts
318
335
  eos
319
336
  .provide(AppLive)
320
- .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
337
+ .provide(CurrentUser, function* ({ context }) {
338
+ return context.user;
339
+ })
321
340
  .use(function* ({ next }) {
322
341
  const user = yield* CurrentUser;
323
342
  return yield* next({ context: { userId: user.id } });
@@ -328,11 +347,9 @@ eos
328
347
  });
329
348
  ```
330
349
 
331
- The example above runs the provider, middleware, and handler inside a single
332
- Effect execution boundary.
350
+ The example above runs the provider, middleware, and handler inside a single Effect execution boundary.
333
351
 
334
- A native oRPC middleware breaks the contiguous Effect pipeline. Pending Effect
335
- 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:
336
353
 
337
354
  ```ts
338
355
  eos
@@ -350,21 +367,15 @@ eos
350
367
  });
351
368
  ```
352
369
 
353
- That split still creates multiple runtime boundaries. If the Node bridge is
354
- installed, however, `effect-orpc` carries the current `FiberRefs` through the
355
- 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:
356
371
 
357
372
  ```ts
358
373
  import "effect-orpc/node";
359
374
  ```
360
375
 
361
- Use the side-effect import when you only need continuity across internal
362
- `effect-orpc` boundaries, such as Effect group #1 → native oRPC middleware →
363
- 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.
364
377
 
365
- Procedure-level `.provide*` after a native `.handler(...)` has no Effect handler
366
- boundary to attach to, so it is installed as an oRPC middleware that runs its
367
- provider Effect through the configured runtime source:
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:
368
379
 
369
380
  ```ts
370
381
  eos
@@ -373,21 +384,16 @@ eos
373
384
  .provide(CurrentUser, getCurrentUser); // fallback provider middleware
374
385
  ```
375
386
 
376
- If you want `.provide*` and Effect middleware to batch with the handler, use
377
- `.effect(function* ...)` instead of `.handler(...)`.
387
+ If you want `.provide*` and Effect middleware to batch with the handler, use `.effect(function* ...)` instead of `.handler(...)`.
378
388
 
379
389
  ## Request-Scoped Fiber Context
380
390
 
381
- The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has
382
- two uses:
391
+ The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has two uses:
383
392
 
384
- - `import "effect-orpc/node"` installs the bridge passively. This is enough for
385
- `effect-orpc` to propagate `FiberRefs` across its own split runtime boundaries.
386
- - `withFiberContext(() => next())` actively seeds the bridge from an external
387
- 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.
388
395
 
389
- Use `withFiberContext` when request-local `FiberRef` state is created outside the
390
- 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:
391
397
 
392
398
  ```ts
393
399
  import { Hono } from "hono";
@@ -411,24 +417,16 @@ app.use("*", async (c, next) => {
411
417
  });
412
418
  ```
413
419
 
414
- Importing `withFiberContext` from `effect-orpc/node` also installs the bridge, so
415
- 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.
416
421
 
417
- When a captured fiber context and the application `Layer` / `ManagedRuntime`
418
- both provide the same service, `effect-orpc` prioritizes the captured context.
419
- The application layer is treated as the base layer, while the bridge preserves more specific
420
- request-scoped values such as request IDs, logging annotations, tracing context,
421
- 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.
422
424
 
423
- The main package stays runtime-agnostic; `/node` is separate because the bridge
424
- 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`.
425
426
 
426
427
  ## Contract-First Usage
427
428
 
428
- Use `implementEffect(contract, layerOrRuntime)` when you already have an oRPC
429
- contract and want to keep contract-first enforcement while adding Effect-native
430
- handlers. Use `eos.provide(layer)` when you want to build procedures directly
431
- from the default Effect-aware 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.
432
430
 
433
431
  ```ts
434
432
  import { Effect } from "effect";
@@ -462,29 +460,21 @@ export const router = oe.router({
462
460
  });
463
461
  ```
464
462
 
465
- Contract leaves keep the contract-defined input, output, and error surface.
466
- They add `.effect(...)` alongside existing implementer methods such as
467
- `.handler(...)` and `.use(...)`, but do not expose contract-changing builder
468
- 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(...)`.
469
464
 
470
- If your contract declares tagged Effect error classes, prefer `eoc.errors(...)`
471
- instead of raw `oc.errors(...)` so the error schema and metadata are derived
472
- 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.
473
466
 
474
467
  ## API Reference
475
468
 
476
469
  ### `eos`
477
470
 
478
- The default Effect-aware procedure builder. Provide your application services
479
- with `.provide(layer)`:
471
+ The default Effect-aware procedure builder. Provide your application services with `.provide(layer)`:
480
472
 
481
473
  ```ts
482
474
  const effectProcedure = eos.provide(AppLive);
483
475
  ```
484
476
 
485
- Use `makeEffectORPC(runtime)` when a scoped `Layer` should be acquired once and
486
- released by your application shutdown path, such as a shared cache, database
487
- pool, HTTP client, or telemetry SDK:
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:
488
478
 
489
479
  ```ts
490
480
  const runtime = ManagedRuntime.make(AppLive);
@@ -495,8 +485,7 @@ const effectProcedure = makeEffectORPC(runtime);
495
485
  await runtime.dispose();
496
486
  ```
497
487
 
498
- `makeEffectORPC(builder)` is also available when you need to wrap an existing
499
- oRPC builder:
488
+ `makeEffectORPC(builder)` is also available when you need to wrap an existing oRPC builder:
500
489
 
501
490
  ```ts
502
491
  const effectAuthedOs = makeEffectORPC(authedBuilder).provide(AppLive);
@@ -527,8 +516,7 @@ const router = oe.router({
527
516
 
528
517
  An Effect-aware wrapper around oRPC's `oc` contract builder.
529
518
 
530
- Use it when you want contract definitions to accept `ORPCTaggedError` classes
531
- 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.
532
520
 
533
521
  ```ts
534
522
  class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
@@ -552,43 +540,43 @@ const contract = {
552
540
 
553
541
  Wraps an oRPC Builder with Effect support. Available methods:
554
542
 
555
- | Method | Description |
556
- | ------------------- | ------------------------------------------------------------------------------------ |
557
- | `.$config(config)` | Set or override the builder config |
558
- | `.$context<U>()` | Set or override the initial context type |
559
- | `.$meta(meta)` | Set or override the initial metadata |
560
- | `.$route(route)` | Set or override the initial route configuration |
561
- | `.$input(schema)` | Set or override the initial input schema |
562
- | `.errors(map)` | Add type-safe custom errors |
563
- | `.meta(meta)` | Set procedure metadata (merged with existing) |
564
- | `.route(route)` | Configure OpenAPI route (merged with existing) |
565
- | `.input(schema)` | Define input validation schema |
566
- | `.output(schema)` | Define output validation schema |
567
- | `.provide(layer)` | Provide a base Effect layer to downstream Effect middleware and handlers |
568
- | `.provide(tag, fn)` | Provide a request-scoped Effect service to downstream Effect middleware and handlers |
569
- | `.use(middleware)` | Add middleware |
570
- | `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
571
- | `.handler(handler)` | Define a non-Effect handler (standard oRPC handler) |
572
- | `.effect(handler)` | Define the Effect handler |
573
- | `.prefix(prefix)` | Prefix all procedures in the router (for OpenAPI) |
574
- | `.tag(...tags)` | Add tags to all procedures in the router (for OpenAPI) |
575
- | `.router(router)` | Apply all options to a router |
576
- | `.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 |
577
565
 
578
566
  ### `EffectDecoratedProcedure`
579
567
 
580
568
  The result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` with Effect type preservation.
581
569
 
582
- | Method | Description |
583
- | ----------------------- | --------------------------------------------- |
584
- | `.errors(map)` | Add more custom errors |
585
- | `.meta(meta)` | Update metadata (merged with existing) |
586
- | `.route(route)` | Update route configuration (merged) |
587
- | `.provide(layer)` | Provide a base Effect layer |
588
- | `.provide(tag, fn)` | Provide a request-scoped Effect service |
589
- | `.use(middleware)` | Add middleware |
590
- | `.callable(options?)` | Make procedure directly invocable |
591
- | `.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 |
592
580
 
593
581
  ### `ORPCTaggedError(tag, options?)`
594
582