effect-orpc 1.0.0-effect-v4.4 → 1.0.0-effect-v4.6
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 +234 -123
- package/dist/{chunk-I5EWBI42.js → chunk-SXPXGZUW.js} +6 -2
- package/dist/chunk-SXPXGZUW.js.map +1 -0
- package/dist/index.js +1049 -493
- package/dist/index.js.map +1 -1
- package/dist/node.js +3 -2
- package/dist/node.js.map +1 -1
- package/package.json +3 -3
- package/src/contract.ts +46 -11
- package/src/effect-builder.ts +654 -433
- package/src/effect-enhance-router.ts +23 -24
- package/src/effect-procedure.ts +463 -214
- package/src/effect-runtime.ts +452 -24
- package/src/extension/compose-surfaces.ts +15 -0
- package/src/extension/create-node-proxy.ts +286 -0
- package/src/extension/state.ts +104 -0
- package/src/index.ts +3 -0
- package/src/node.ts +1 -0
- package/src/runtime-source.ts +107 -0
- package/src/service-context-bridge.ts +11 -0
- package/src/tagged-error.ts +1 -10
- package/src/tests/contract.test.ts +21 -0
- package/src/tests/effect-builder.proxy.test.ts +253 -0
- package/src/tests/effect-builder.test.ts +584 -86
- package/src/tests/effect-error-map.test.ts +10 -10
- package/src/tests/effect-procedure.test.ts +9 -6
- package/src/tests/node-side-effect.test.ts +80 -0
- package/src/tests/parity.effect-builder.test.ts +27 -3
- package/src/tests/parity.effect-procedure.test.ts +24 -8
- package/src/tests/shared.ts +1 -25
- package/src/types/effect-builder-surface.ts +557 -0
- package/src/types/effect-procedure-surface.ts +340 -0
- package/src/types/index.ts +300 -15
- package/src/types/variants.ts +422 -14
- package/dist/chunk-I5EWBI42.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Inspired by [effect-trpc](https://github.com/mikearnaldi/effect-trpc).
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Effect-native procedures** - Write oRPC procedures using generators with `yield*` syntax
|
|
10
|
-
- **Type-safe service injection** -
|
|
10
|
+
- **Type-safe service injection** - Add base services with `.provide(layer)` or pass a `Layer` / `ManagedRuntime<R>` directly
|
|
11
11
|
- **Tagged errors** - Create Effect-native error classes with `ORPCTaggedError` that integrate with oRPC's error handling
|
|
12
12
|
- **Full oRPC compatibility** - Mix Effect procedures with standard oRPC procedures in the same router
|
|
13
13
|
- **Telemetry support with automatic tracing** - Procedures are automatically traced with OpenTelemetry-compatible spans. Customize span names with `.traced()`.
|
|
@@ -29,8 +29,8 @@ Runnable demos live in the repository's `examples/` directory.
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
import { os } from "@orpc/server";
|
|
32
|
-
import { Effect,
|
|
33
|
-
import { makeEffectORPC, ORPCTaggedError } from "effect-orpc";
|
|
32
|
+
import { Effect, ManagedRuntime } from "effect";
|
|
33
|
+
import { eos, makeEffectORPC, ORPCTaggedError } from "effect-orpc";
|
|
34
34
|
|
|
35
35
|
interface User {
|
|
36
36
|
id: number;
|
|
@@ -43,46 +43,41 @@ 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
|
-
class UsersRepo extends
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
readonly get: (id: number) => User | undefined;
|
|
60
|
-
}
|
|
61
|
-
>()("UsersRepo") {
|
|
62
|
-
static readonly layer = Layer.succeed(this, {
|
|
47
|
+
class UsersRepo extends Effect.Service<UsersRepo>()("UsersRepo", {
|
|
48
|
+
accessors: true,
|
|
49
|
+
sync: () => ({
|
|
63
50
|
get: (id: number) => users.find((u) => u.id === id),
|
|
64
|
-
})
|
|
65
|
-
}
|
|
51
|
+
}),
|
|
52
|
+
}) {}
|
|
66
53
|
|
|
67
54
|
// Special yieldable oRPC error class
|
|
68
55
|
class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
|
|
69
56
|
status: 404,
|
|
70
57
|
}) {}
|
|
71
58
|
|
|
72
|
-
// Create
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
UserNotFoundError
|
|
77
|
-
})
|
|
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
|
+
});
|
|
69
|
+
|
|
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:
|
|
72
|
+
// const runtime = ManagedRuntime.make(UsersRepo.Default);
|
|
73
|
+
// const effectProcedure = makeEffectORPC(runtime).errors({ UserNotFoundError });
|
|
78
74
|
|
|
79
75
|
// Create the router with mixed procedures
|
|
80
76
|
export const router = {
|
|
81
77
|
health: os.handler(() => "ok"),
|
|
82
78
|
users: {
|
|
83
|
-
me:
|
|
84
|
-
const
|
|
85
|
-
const user = usersRepo.get(userId);
|
|
79
|
+
me: effectProcedure.effect(function* ({ context: { userId } }) {
|
|
80
|
+
const user = yield* UsersRepo.get(userId);
|
|
86
81
|
if (!user) {
|
|
87
82
|
return yield* new UserNotFoundError();
|
|
88
83
|
}
|
|
@@ -96,42 +91,36 @@ export type Router = typeof router;
|
|
|
96
91
|
|
|
97
92
|
## Type Safety
|
|
98
93
|
|
|
99
|
-
The wrapper enforces that Effect procedures only use services provided by
|
|
94
|
+
The 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:
|
|
100
95
|
|
|
101
96
|
```ts
|
|
102
|
-
import { Effect, Layer
|
|
103
|
-
import {
|
|
97
|
+
import { Context, Effect, Layer } from "effect";
|
|
98
|
+
import { eos } from "effect-orpc";
|
|
104
99
|
|
|
105
100
|
class ProvidedService extends Context.Service<
|
|
106
101
|
ProvidedService,
|
|
107
|
-
{
|
|
108
|
-
readonly doSomething: () => Effect.Effect<string>;
|
|
109
|
-
}
|
|
102
|
+
{ doSomething: () => Effect.Effect<string> }
|
|
110
103
|
>()("ProvidedService") {}
|
|
111
104
|
|
|
112
105
|
class MissingService extends Context.Service<
|
|
113
106
|
MissingService,
|
|
114
|
-
{
|
|
115
|
-
readonly doSomething: () => Effect.Effect<string>;
|
|
116
|
-
}
|
|
107
|
+
{ doSomething: () => Effect.Effect<string> }
|
|
117
108
|
>()("MissingService") {}
|
|
118
109
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}),
|
|
123
|
-
);
|
|
110
|
+
const AppLive = Layer.succeed(ProvidedService, {
|
|
111
|
+
doSomething: () => Effect.succeed("ok"),
|
|
112
|
+
});
|
|
124
113
|
|
|
125
|
-
const
|
|
114
|
+
const effectProcedure = eos.provide(AppLive);
|
|
126
115
|
|
|
127
|
-
// ✅ This compiles - ProvidedService is
|
|
128
|
-
const works =
|
|
116
|
+
// ✅ This compiles - ProvidedService is provided by AppLive
|
|
117
|
+
const works = effectProcedure.effect(function* () {
|
|
129
118
|
const service = yield* ProvidedService;
|
|
130
119
|
return yield* service.doSomething();
|
|
131
120
|
});
|
|
132
121
|
|
|
133
|
-
// ❌ This fails to compile - MissingService is not
|
|
134
|
-
const fails =
|
|
122
|
+
// ❌ This fails to compile - MissingService is not provided
|
|
123
|
+
const fails = effectProcedure.effect(function* () {
|
|
135
124
|
const service = yield* MissingService; // Type error!
|
|
136
125
|
return yield* service.doSomething();
|
|
137
126
|
});
|
|
@@ -148,7 +137,7 @@ const fails = effectOs.effect(function* () {
|
|
|
148
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.
|
|
149
138
|
|
|
150
139
|
```ts
|
|
151
|
-
const getUser =
|
|
140
|
+
const getUser = effectProcedure
|
|
152
141
|
// Mixed error maps
|
|
153
142
|
.errors({
|
|
154
143
|
// Regular oRPC error
|
|
@@ -217,19 +206,19 @@ All Effect procedures are automatically traced with `Effect.withSpan`. By defaul
|
|
|
217
206
|
const router = {
|
|
218
207
|
users: {
|
|
219
208
|
// Span name: "users.get"
|
|
220
|
-
get:
|
|
209
|
+
get: effectProcedure.input(z.object({ id: z.string() })).effect(function* ({
|
|
221
210
|
input,
|
|
222
211
|
}) {
|
|
223
212
|
const userService = yield* UserService;
|
|
224
213
|
return yield* userService.findById(input.id);
|
|
225
214
|
}),
|
|
226
215
|
// Span name: "users.create"
|
|
227
|
-
create:
|
|
228
|
-
input
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
}),
|
|
233
222
|
},
|
|
234
223
|
};
|
|
235
224
|
```
|
|
@@ -237,7 +226,7 @@ const router = {
|
|
|
237
226
|
Use `.traced()` to override the default span name:
|
|
238
227
|
|
|
239
228
|
```ts
|
|
240
|
-
const getUser =
|
|
229
|
+
const getUser = effectProcedure
|
|
241
230
|
.input(z.object({ id: z.string() }))
|
|
242
231
|
.traced("custom.span.name") // Override the default path-based name
|
|
243
232
|
.effect(function* ({ input }) {
|
|
@@ -248,7 +237,7 @@ const getUser = effectOs
|
|
|
248
237
|
|
|
249
238
|
### Enabling OpenTelemetry
|
|
250
239
|
|
|
251
|
-
To enable tracing, include the OpenTelemetry layer in your
|
|
240
|
+
To enable tracing, include the OpenTelemetry layer in your application layer:
|
|
252
241
|
|
|
253
242
|
```ts
|
|
254
243
|
import { NodeSdk } from "@effect/opentelemetry";
|
|
@@ -264,8 +253,7 @@ const TracingLive = NodeSdk.layer(
|
|
|
264
253
|
|
|
265
254
|
const AppLive = Layer.mergeAll(UserServiceLive, TracingLive);
|
|
266
255
|
|
|
267
|
-
const
|
|
268
|
-
const effectOs = makeEffectORPC(runtime);
|
|
256
|
+
const effectProcedure = eos.provide(AppLive);
|
|
269
257
|
```
|
|
270
258
|
|
|
271
259
|
### Error Stack Traces
|
|
@@ -278,24 +266,136 @@ MyCustomError: Something went wrong
|
|
|
278
266
|
at users.getById (/app/src/procedures.ts:41:35)
|
|
279
267
|
```
|
|
280
268
|
|
|
269
|
+
## Effect middleware
|
|
270
|
+
|
|
271
|
+
`.use(...)` accepts generator-based Effect middleware in addition to native oRPC
|
|
272
|
+
middleware. Two patterns are supported:
|
|
273
|
+
|
|
274
|
+
**Gate** — run auth or validation side effects, then let the pipeline continue
|
|
275
|
+
automatically (no need to call `next`):
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
effectProcedure.use(function* () {
|
|
279
|
+
const user = yield* CurrentUser;
|
|
280
|
+
yield* requireActiveUser(user);
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Wrap** — call downstream explicitly and return the result. When porting oRPC
|
|
285
|
+
middleware that uses `return next(...)`, use `return yield* next(...)`:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
effectProcedure.use(function* ({ next }) {
|
|
289
|
+
const user = yield* CurrentUser;
|
|
290
|
+
yield* requireActiveUser(user);
|
|
291
|
+
|
|
292
|
+
return yield* next({
|
|
293
|
+
context: { userId: user.id },
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
To transform the downstream output, capture `next()` and pass through `output`:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
effectProcedure.use(function* ({ next }, _input, output) {
|
|
302
|
+
const result = yield* next();
|
|
303
|
+
return yield* output(`${result.output}-wrapped`);
|
|
304
|
+
});
|
|
305
|
+
```
|
|
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.
|
|
310
|
+
|
|
311
|
+
### Runtime boundaries and fiber context continuity
|
|
312
|
+
|
|
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* ...)`.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
eos
|
|
319
|
+
.provide(AppLive)
|
|
320
|
+
.provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
|
|
321
|
+
.use(function* ({ next }) {
|
|
322
|
+
const user = yield* CurrentUser;
|
|
323
|
+
return yield* next({ context: { userId: user.id } });
|
|
324
|
+
})
|
|
325
|
+
.effect(function* ({ context }) {
|
|
326
|
+
const user = yield* CurrentUser;
|
|
327
|
+
return `${context.userId}:${user.id}`;
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The example above runs the provider, middleware, and handler inside a single
|
|
332
|
+
Effect execution boundary.
|
|
333
|
+
|
|
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:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
eos
|
|
339
|
+
.provide(AppLive)
|
|
340
|
+
.provide(CurrentUser, getCurrentUser) // Effect group #1
|
|
341
|
+
.use(function* ({ next }) {
|
|
342
|
+
return yield* next();
|
|
343
|
+
})
|
|
344
|
+
.use(({ next }) => next()) // native oRPC middleware; flushes group #1
|
|
345
|
+
.use(function* ({ next }) {
|
|
346
|
+
return yield* next();
|
|
347
|
+
}) // Effect group #2
|
|
348
|
+
.effect(function* () {
|
|
349
|
+
return "ok";
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
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:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import "effect-orpc/node";
|
|
359
|
+
```
|
|
360
|
+
|
|
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.
|
|
364
|
+
|
|
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:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
eos
|
|
371
|
+
.provide(AppLive)
|
|
372
|
+
.handler(() => "ok") // native oRPC handler
|
|
373
|
+
.provide(CurrentUser, getCurrentUser); // fallback provider middleware
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
If you want `.provide*` and Effect middleware to batch with the handler, use
|
|
377
|
+
`.effect(function* ...)` instead of `.handler(...)`.
|
|
378
|
+
|
|
281
379
|
## Request-Scoped Fiber Context
|
|
282
380
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
`FiberRef` state from outer middleware.
|
|
381
|
+
The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has
|
|
382
|
+
two uses:
|
|
286
383
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
`
|
|
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.
|
|
388
|
+
|
|
389
|
+
Use `withFiberContext` when request-local `FiberRef` state is created outside the
|
|
390
|
+
oRPC pipeline and should be visible inside handlers:
|
|
290
391
|
|
|
291
392
|
```ts
|
|
292
393
|
import { Hono } from "hono";
|
|
293
|
-
import { Effect
|
|
294
|
-
import {
|
|
394
|
+
import { Effect } from "effect";
|
|
395
|
+
import { eos } from "effect-orpc";
|
|
295
396
|
import { withFiberContext } from "effect-orpc/node";
|
|
296
397
|
|
|
297
|
-
const
|
|
298
|
-
const effectOs = makeEffectORPC(runtime);
|
|
398
|
+
const effectProcedure = eos.provide(AppLive);
|
|
299
399
|
const app = new Hono();
|
|
300
400
|
|
|
301
401
|
app.use("*", async (c, next) => {
|
|
@@ -311,31 +411,27 @@ app.use("*", async (c, next) => {
|
|
|
311
411
|
});
|
|
312
412
|
```
|
|
313
413
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
as the application-wide base layer, while `withFiberContext` preserves the
|
|
317
|
-
more specific request-scoped values from outer middleware. This prevents
|
|
318
|
-
request-local references such as request IDs, logging annotations, tracing
|
|
319
|
-
context, or scoped overrides from being replaced by runtime defaults when the
|
|
320
|
-
handler crosses the runtime boundary.
|
|
414
|
+
Importing `withFiberContext` from `effect-orpc/node` also installs the bridge, so
|
|
415
|
+
you do not need a separate side-effect import.
|
|
321
416
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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.
|
|
326
422
|
|
|
327
|
-
|
|
328
|
-
|
|
423
|
+
The main package stays runtime-agnostic; `/node` is separate because the bridge
|
|
424
|
+
relies on `AsyncLocalStorage` from `node:async_hooks`.
|
|
329
425
|
|
|
330
426
|
## Contract-First Usage
|
|
331
427
|
|
|
332
|
-
Use `implementEffect(contract,
|
|
333
|
-
and want to keep contract-first enforcement while adding Effect-native
|
|
334
|
-
Use `
|
|
335
|
-
|
|
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.
|
|
336
432
|
|
|
337
433
|
```ts
|
|
338
|
-
import { Effect
|
|
434
|
+
import { Effect } from "effect";
|
|
339
435
|
import { eoc, implementEffect } from "effect-orpc";
|
|
340
436
|
import z from "zod";
|
|
341
437
|
|
|
@@ -355,8 +451,7 @@ const contract = {
|
|
|
355
451
|
},
|
|
356
452
|
};
|
|
357
453
|
|
|
358
|
-
const
|
|
359
|
-
const oe = implementEffect(contract, runtime);
|
|
454
|
+
const oe = implementEffect(contract, UsersRepo.Default);
|
|
360
455
|
|
|
361
456
|
export const router = oe.router({
|
|
362
457
|
users: {
|
|
@@ -378,34 +473,46 @@ directly from the `ORPCTaggedError` class.
|
|
|
378
473
|
|
|
379
474
|
## API Reference
|
|
380
475
|
|
|
381
|
-
### `
|
|
476
|
+
### `eos`
|
|
382
477
|
|
|
383
|
-
|
|
478
|
+
The default Effect-aware procedure builder. Provide your application services
|
|
479
|
+
with `.provide(layer)`:
|
|
384
480
|
|
|
385
|
-
|
|
386
|
-
|
|
481
|
+
```ts
|
|
482
|
+
const effectProcedure = eos.provide(AppLive);
|
|
483
|
+
```
|
|
387
484
|
|
|
388
|
-
|
|
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:
|
|
389
488
|
|
|
390
489
|
```ts
|
|
391
|
-
|
|
392
|
-
const effectOs = makeEffectORPC(runtime);
|
|
490
|
+
const runtime = ManagedRuntime.make(AppLive);
|
|
393
491
|
|
|
394
|
-
|
|
395
|
-
|
|
492
|
+
const effectProcedure = makeEffectORPC(runtime);
|
|
493
|
+
|
|
494
|
+
// later, during app shutdown
|
|
495
|
+
await runtime.dispose();
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
`makeEffectORPC(builder)` is also available when you need to wrap an existing
|
|
499
|
+
oRPC builder:
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
const effectAuthedOs = makeEffectORPC(authedBuilder).provide(AppLive);
|
|
396
503
|
```
|
|
397
504
|
|
|
398
|
-
### `implementEffect(contract,
|
|
505
|
+
### `implementEffect(contract, layerOrRuntime)`
|
|
399
506
|
|
|
400
507
|
Creates an Effect-aware contract implementer.
|
|
401
508
|
|
|
402
509
|
- `contract` - An oRPC contract router built with `oc`
|
|
403
|
-
- `
|
|
510
|
+
- `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)
|
|
404
511
|
|
|
405
512
|
Returns a contract-shaped implementer tree whose leaves support `.effect(...)`.
|
|
406
513
|
|
|
407
514
|
```ts
|
|
408
|
-
const oe = implementEffect(contract,
|
|
515
|
+
const oe = implementEffect(contract, AppLive);
|
|
409
516
|
|
|
410
517
|
const router = oe.router({
|
|
411
518
|
users: {
|
|
@@ -445,26 +552,28 @@ const contract = {
|
|
|
445
552
|
|
|
446
553
|
Wraps an oRPC Builder with Effect support. Available methods:
|
|
447
554
|
|
|
448
|
-
| Method | Description
|
|
449
|
-
| ------------------- |
|
|
450
|
-
| `.$config(config)` | Set or override the builder config
|
|
451
|
-
| `.$context<U>()` | Set or override the initial context type
|
|
452
|
-
| `.$meta(meta)` | Set or override the initial metadata
|
|
453
|
-
| `.$route(route)` | Set or override the initial route configuration
|
|
454
|
-
| `.$input(schema)` | Set or override the initial input schema
|
|
455
|
-
| `.errors(map)` | Add type-safe custom errors
|
|
456
|
-
| `.meta(meta)` | Set procedure metadata (merged with existing)
|
|
457
|
-
| `.route(route)` | Configure OpenAPI route (merged with existing)
|
|
458
|
-
| `.input(schema)` | Define input validation schema
|
|
459
|
-
| `.output(schema)` | Define output validation schema
|
|
460
|
-
| `.
|
|
461
|
-
| `.
|
|
462
|
-
| `.
|
|
463
|
-
| `.
|
|
464
|
-
| `.
|
|
465
|
-
| `.
|
|
466
|
-
| `.
|
|
467
|
-
| `.
|
|
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 |
|
|
468
577
|
|
|
469
578
|
### `EffectDecoratedProcedure`
|
|
470
579
|
|
|
@@ -475,6 +584,8 @@ The result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` wi
|
|
|
475
584
|
| `.errors(map)` | Add more custom errors |
|
|
476
585
|
| `.meta(meta)` | Update metadata (merged with existing) |
|
|
477
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 |
|
|
478
589
|
| `.use(middleware)` | Add middleware |
|
|
479
590
|
| `.callable(options?)` | Make procedure directly invocable |
|
|
480
591
|
| `.actionable(options?)` | Make procedure compatible with server actions |
|
|
@@ -6,9 +6,13 @@ function installServiceContextBridge(nextBridge) {
|
|
|
6
6
|
function getCurrentServices() {
|
|
7
7
|
return bridge?.getCurrentServices();
|
|
8
8
|
}
|
|
9
|
+
function runWithServices(services, fn) {
|
|
10
|
+
return bridge?.runWithServices?.(services, fn) ?? fn();
|
|
11
|
+
}
|
|
9
12
|
|
|
10
13
|
export {
|
|
11
14
|
installServiceContextBridge,
|
|
12
|
-
getCurrentServices
|
|
15
|
+
getCurrentServices,
|
|
16
|
+
runWithServices
|
|
13
17
|
};
|
|
14
|
-
//# sourceMappingURL=chunk-
|
|
18
|
+
//# sourceMappingURL=chunk-SXPXGZUW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/service-context-bridge.ts"],"sourcesContent":["import type { Context } from \"effect\";\n\nexport interface ServiceContextBridge {\n readonly getCurrentServices: () => Context.Context<any> | undefined;\n readonly runWithServices?: <T>(\n services: Context.Context<any>,\n fn: () => Promise<T>,\n ) => Promise<T>;\n}\n\nlet bridge: ServiceContextBridge | undefined;\n\nexport function installServiceContextBridge(\n nextBridge: ServiceContextBridge | undefined,\n): void {\n bridge = nextBridge;\n}\n\nexport function getCurrentServices(): Context.Context<any> | undefined {\n return bridge?.getCurrentServices();\n}\n\nexport function runWithServices<T>(\n services: Context.Context<any>,\n fn: () => Promise<T>,\n): Promise<T> {\n return bridge?.runWithServices?.(services, fn) ?? fn();\n}\n"],"mappings":";AAUA,IAAI;AAEG,SAAS,4BACd,YACM;AACN,WAAS;AACX;AAEO,SAAS,qBAAuD;AACrE,SAAO,QAAQ,mBAAmB;AACpC;AAEO,SAAS,gBACd,UACA,IACY;AACZ,SAAO,QAAQ,kBAAkB,UAAU,EAAE,KAAK,GAAG;AACvD;","names":[]}
|