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 +81 -93
- package/dist/index.js +350 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/effect-builder.ts +36 -22
- package/src/effect-procedure.ts +31 -7
- package/src/effect-runtime.ts +622 -80
- package/src/tests/effect-builder.proxy.test.ts +15 -17
- package/src/tests/effect-builder.test.ts +296 -139
- package/src/tests/effect-callback-shapes.test.ts +410 -0
- package/src/tests/effect-error-map.test.ts +4 -6
- package/src/tests/effect-procedure.test.ts +44 -5
- package/src/tests/parity-shared.ts +2 -2
- package/src/types/index.ts +70 -40
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
|
|
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
|
-
|
|
309
|
-
|
|
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 })
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
556
|
-
|
|
|
557
|
-
| `.$config(config)`
|
|
558
|
-
| `.$context<U>()`
|
|
559
|
-
| `.$meta(meta)`
|
|
560
|
-
| `.$route(route)`
|
|
561
|
-
| `.$input(schema)`
|
|
562
|
-
| `.errors(map)`
|
|
563
|
-
| `.meta(meta)`
|
|
564
|
-
| `.route(route)`
|
|
565
|
-
| `.input(schema)`
|
|
566
|
-
| `.output(schema)`
|
|
567
|
-
| `.provide(layer)`
|
|
568
|
-
| `.provide(tag,
|
|
569
|
-
| `.use(middleware)`
|
|
570
|
-
| `.traced(name)`
|
|
571
|
-
| `.handler(handler)`
|
|
572
|
-
| `.effect(handler)`
|
|
573
|
-
| `.prefix(prefix)`
|
|
574
|
-
| `.tag(...tags)`
|
|
575
|
-
| `.router(router)`
|
|
576
|
-
| `.lazy(loader)`
|
|
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
|
|
583
|
-
|
|
|
584
|
-
| `.errors(map)`
|
|
585
|
-
| `.meta(meta)`
|
|
586
|
-
| `.route(route)`
|
|
587
|
-
| `.provide(layer)`
|
|
588
|
-
| `.provide(tag,
|
|
589
|
-
| `.use(middleware)`
|
|
590
|
-
| `.callable(options?)`
|
|
591
|
-
| `.actionable(options?)`
|
|
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
|
|