effect-orpc 0.0.1
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/.cursor/hooks.json +10 -0
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
- package/.github/workflows/publish.yml +45 -0
- package/.oxfmtrc.jsonc +23 -0
- package/.oxlintrc.json +4 -0
- package/.vscode/settings.json +59 -0
- package/LICENSE +21 -0
- package/README.md +538 -0
- package/bun.lock +403 -0
- package/package.json +55 -0
- package/src/effect-builder.ts +680 -0
- package/src/effect-procedure.ts +354 -0
- package/src/index.ts +36 -0
- package/src/tagged-error.ts +515 -0
- package/src/tests/effect-builder.test.ts +488 -0
- package/src/tests/effect-error-map.test.ts +313 -0
- package/src/tests/effect-procedure.test.ts +213 -0
- package/src/tests/shared.ts +79 -0
- package/src/tests/tagged-error.test.ts +311 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# effect-orpc
|
|
2
|
+
|
|
3
|
+
A type-safe integration between [oRPC](https://orpc.dev/) and [Effect](https://effect.website/), enabling Effect-native procedures with full service injection support.
|
|
4
|
+
|
|
5
|
+
Inspired by [effect-trpc](https://github.com/mikearnaldi/effect-trpc).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Effect-native procedures** - Write oRPC procedures using generators with `yield*` syntax
|
|
10
|
+
- **Type-safe service injection** - Use `ManagedRuntime<R>` to provide services to procedures with compile-time safety
|
|
11
|
+
- **Full oRPC compatibility** - Mix Effect procedures with standard oRPC procedures in the same router
|
|
12
|
+
- **Builder pattern preserved** - All oRPC builder methods (`.errors()`, `.meta()`, `.route()`, `.input()`, `.output()`, `.use()`) work seamlessly
|
|
13
|
+
- **Callable procedures** - Make procedures directly invocable while preserving Effect types
|
|
14
|
+
- **Server actions support** - Full compatibility with framework server actions
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install effect-orpc
|
|
20
|
+
# or
|
|
21
|
+
pnpm add effect-orpc
|
|
22
|
+
# or
|
|
23
|
+
bun add effect-orpc
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { makeEffectORPC } from 'effect-orpc'
|
|
30
|
+
import { os } from '@orpc/server'
|
|
31
|
+
import { Context, Effect, Layer, ManagedRuntime } from 'effect'
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
|
|
34
|
+
// Define your services
|
|
35
|
+
class UserService extends Context.Tag('UserService')<
|
|
36
|
+
UserService,
|
|
37
|
+
{
|
|
38
|
+
findById: (id: string) => Effect.Effect<User | undefined>
|
|
39
|
+
findAll: () => Effect.Effect<User[]>
|
|
40
|
+
create: (name: string) => Effect.Effect<User>
|
|
41
|
+
}
|
|
42
|
+
>() {}
|
|
43
|
+
|
|
44
|
+
// Create service implementation
|
|
45
|
+
const UserServiceLive = Layer.succeed(UserService, {
|
|
46
|
+
findById: id => Effect.succeed(users.find(u => u.id === id)),
|
|
47
|
+
findAll: () => Effect.succeed(users),
|
|
48
|
+
create: name => Effect.succeed({ id: crypto.randomUUID(), name })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Create runtime with your services
|
|
52
|
+
const runtime = ManagedRuntime.make(UserServiceLive)
|
|
53
|
+
|
|
54
|
+
// Create Effect-aware oRPC builder
|
|
55
|
+
const effectOs = makeEffectORPC(runtime)
|
|
56
|
+
|
|
57
|
+
// Define your procedures
|
|
58
|
+
const getUser = effectOs
|
|
59
|
+
.input(z.object({ id: z.string() }))
|
|
60
|
+
.effect(
|
|
61
|
+
Effect.fn(function* ({ input }) {
|
|
62
|
+
const userService = yield* UserService
|
|
63
|
+
return yield* userService.findById(input.id)
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const listUsers = effectOs
|
|
68
|
+
.effect(
|
|
69
|
+
Effect.fn(function* () {
|
|
70
|
+
const userService = yield* UserService
|
|
71
|
+
return yield* userService.findAll()
|
|
72
|
+
})
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const createUser = effectOs
|
|
76
|
+
.input(z.object({ name: z.string() }))
|
|
77
|
+
.effect(
|
|
78
|
+
Effect.fn(function* ({ input }) {
|
|
79
|
+
const userService = yield* UserService
|
|
80
|
+
return yield* userService.create(input.name)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Create router with mixed procedures
|
|
85
|
+
const router = os.router({
|
|
86
|
+
// Standard oRPC procedure
|
|
87
|
+
health: os.handler(() => 'ok'),
|
|
88
|
+
|
|
89
|
+
// Effect procedures
|
|
90
|
+
users: os.router({
|
|
91
|
+
get: getUser,
|
|
92
|
+
list: listUsers,
|
|
93
|
+
create: createUser,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export type Router = typeof router
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Type Safety
|
|
101
|
+
|
|
102
|
+
The wrapper enforces that Effect procedures only use services provided by the `ManagedRuntime`. If you try to use a service that isn't in the runtime, you'll get a compile-time error:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
class ProvidedService extends Context.Tag('ProvidedService')<
|
|
106
|
+
ProvidedService,
|
|
107
|
+
{ doSomething: () => Effect.Effect<string> }
|
|
108
|
+
>() {}
|
|
109
|
+
|
|
110
|
+
class MissingService extends Context.Tag('MissingService')<
|
|
111
|
+
MissingService,
|
|
112
|
+
{ doSomething: () => Effect.Effect<string> }
|
|
113
|
+
>() {}
|
|
114
|
+
|
|
115
|
+
const runtime = ManagedRuntime.make(Layer.succeed(ProvidedService, {
|
|
116
|
+
doSomething: () => Effect.succeed('ok')
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
const effectOs = makeEffectORPC(runtime)
|
|
120
|
+
|
|
121
|
+
// ✅ This compiles - ProvidedService is in the runtime
|
|
122
|
+
const works = effectOs
|
|
123
|
+
.effect(
|
|
124
|
+
Effect.fn(function* () {
|
|
125
|
+
const svc = yield* ProvidedService
|
|
126
|
+
return yield* svc.doSomething()
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// ❌ This fails to compile - MissingService is not in the runtime
|
|
131
|
+
const fails = effectOs
|
|
132
|
+
.effect(
|
|
133
|
+
Effect.fn(function* () {
|
|
134
|
+
const svc = yield* MissingService // Type error!
|
|
135
|
+
return yield* svc.doSomething()
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Using Services
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { makeEffectORPC } from 'effect-orpc'
|
|
144
|
+
import { Context, Effect, Layer, ManagedRuntime } from 'effect'
|
|
145
|
+
import { z } from 'zod'
|
|
146
|
+
|
|
147
|
+
// Define services
|
|
148
|
+
class DatabaseService extends Context.Tag('DatabaseService')<
|
|
149
|
+
DatabaseService,
|
|
150
|
+
{
|
|
151
|
+
query: <T>(sql: string) => Effect.Effect<T[]>
|
|
152
|
+
execute: (sql: string) => Effect.Effect<void>
|
|
153
|
+
}
|
|
154
|
+
>() {}
|
|
155
|
+
|
|
156
|
+
class CacheService extends Context.Tag('CacheService')<
|
|
157
|
+
CacheService,
|
|
158
|
+
{
|
|
159
|
+
get: <T>(key: string) => Effect.Effect<T | undefined>
|
|
160
|
+
set: <T>(key: string, value: T, ttl?: number) => Effect.Effect<void>
|
|
161
|
+
}
|
|
162
|
+
>() {}
|
|
163
|
+
|
|
164
|
+
// Create layers
|
|
165
|
+
const DatabaseServiceLive = Layer.succeed(DatabaseService, {
|
|
166
|
+
query: sql => Effect.succeed([]),
|
|
167
|
+
execute: sql => Effect.succeed(undefined),
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const CacheServiceLive = Layer.succeed(CacheService, {
|
|
171
|
+
get: key => Effect.succeed(undefined),
|
|
172
|
+
set: (key, value, ttl) => Effect.succeed(undefined),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Compose layers
|
|
176
|
+
const AppLive = Layer.mergeAll(DatabaseServiceLive, CacheServiceLive)
|
|
177
|
+
|
|
178
|
+
// Create runtime with all services
|
|
179
|
+
const runtime = ManagedRuntime.make(AppLive)
|
|
180
|
+
const effectOs = makeEffectORPC(runtime)
|
|
181
|
+
|
|
182
|
+
// Use multiple services in a procedure
|
|
183
|
+
const getUserWithCache = effectOs
|
|
184
|
+
.input(z.object({ id: z.string() }))
|
|
185
|
+
.effect(
|
|
186
|
+
Effect.fn(function* ({ input }) {
|
|
187
|
+
const cache = yield* CacheService
|
|
188
|
+
const db = yield* DatabaseService
|
|
189
|
+
|
|
190
|
+
// Try cache first
|
|
191
|
+
const cached = yield* cache.get<User>(`user:${input.id}`)
|
|
192
|
+
if (cached)
|
|
193
|
+
return cached
|
|
194
|
+
|
|
195
|
+
// Fall back to database
|
|
196
|
+
const [user] = yield* db.query<User>(`SELECT * FROM users WHERE id = '${input.id}'`)
|
|
197
|
+
if (user) {
|
|
198
|
+
yield* cache.set(`user:${input.id}`, user, 3600)
|
|
199
|
+
}
|
|
200
|
+
return user
|
|
201
|
+
})
|
|
202
|
+
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Wrapping a Customized Builder
|
|
206
|
+
|
|
207
|
+
You can pass a customized oRPC builder as the second argument to inherit middleware, errors, and configuration:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { makeEffectORPC } from 'effect-orpc'
|
|
211
|
+
import { ORPCError, os } from '@orpc/server'
|
|
212
|
+
import { Effect } from 'effect'
|
|
213
|
+
|
|
214
|
+
// Create a customized base builder with auth middleware
|
|
215
|
+
const authedOs = os
|
|
216
|
+
.errors({
|
|
217
|
+
UNAUTHORIZED: { message: 'Not authenticated' },
|
|
218
|
+
FORBIDDEN: { message: 'Access denied' },
|
|
219
|
+
})
|
|
220
|
+
.use(async ({ context, next, errors }) => {
|
|
221
|
+
if (!context.user) {
|
|
222
|
+
throw errors.UNAUTHORIZED()
|
|
223
|
+
}
|
|
224
|
+
return next({ context: { ...context, userId: context.user.id } })
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// Wrap the customized builder with Effect support
|
|
228
|
+
const effectAuthedOs = makeEffectORPC(runtime, authedOs)
|
|
229
|
+
|
|
230
|
+
// All procedures inherit the auth middleware and error definitions
|
|
231
|
+
const getProfile = effectAuthedOs
|
|
232
|
+
.effect(
|
|
233
|
+
Effect.fn(function* ({ context }) {
|
|
234
|
+
const userService = yield* UserService
|
|
235
|
+
return yield* userService.findById(context.userId)
|
|
236
|
+
})
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
const updateProfile = effectAuthedOs
|
|
240
|
+
.input(z.object({ name: z.string() }))
|
|
241
|
+
.effect(
|
|
242
|
+
Effect.fn(function* ({ context, input }) {
|
|
243
|
+
const userService = yield* UserService
|
|
244
|
+
return yield* userService.update(context.userId, input)
|
|
245
|
+
})
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Chaining Builder Methods
|
|
250
|
+
|
|
251
|
+
The `EffectBuilder` supports all standard oRPC builder methods:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const createPost = effectOs
|
|
255
|
+
// Add custom errors
|
|
256
|
+
.errors({
|
|
257
|
+
NOT_FOUND: { message: 'User not found' },
|
|
258
|
+
VALIDATION_ERROR: {
|
|
259
|
+
message: 'Invalid input',
|
|
260
|
+
data: z.object({ field: z.string(), issue: z.string() })
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
// Add metadata
|
|
264
|
+
.meta({ auth: true, rateLimit: 100 })
|
|
265
|
+
// Configure route for OpenAPI
|
|
266
|
+
.route({ method: 'POST', path: '/posts', tags: ['posts'] })
|
|
267
|
+
// Define input schema
|
|
268
|
+
.input(z.object({
|
|
269
|
+
title: z.string().min(1).max(200),
|
|
270
|
+
content: z.string(),
|
|
271
|
+
authorId: z.string(),
|
|
272
|
+
}))
|
|
273
|
+
// Define output schema
|
|
274
|
+
.output(z.object({
|
|
275
|
+
id: z.string(),
|
|
276
|
+
title: z.string(),
|
|
277
|
+
content: z.string(),
|
|
278
|
+
createdAt: z.date(),
|
|
279
|
+
}))
|
|
280
|
+
// Define Effect handler
|
|
281
|
+
.effect(({ input, errors }) =>
|
|
282
|
+
Effect.gen(function* () {
|
|
283
|
+
const userService = yield* UserService
|
|
284
|
+
const user = yield* userService.findById(input.authorId)
|
|
285
|
+
|
|
286
|
+
if (!user) {
|
|
287
|
+
throw errors.NOT_FOUND()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const postService = yield* PostService
|
|
291
|
+
return yield* postService.create({
|
|
292
|
+
title: input.title,
|
|
293
|
+
content: input.content,
|
|
294
|
+
authorId: input.authorId,
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Making Procedures Callable
|
|
301
|
+
|
|
302
|
+
Use `.callable()` to make procedures directly invocable:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const greet = effectOs
|
|
306
|
+
.input(z.object({ name: z.string() }))
|
|
307
|
+
.effect(({ input }) => Effect.succeed(`Hello, ${input.name}!`))
|
|
308
|
+
.callable()
|
|
309
|
+
|
|
310
|
+
// Can be called directly as a function
|
|
311
|
+
const result = await greet({ name: 'World' })
|
|
312
|
+
// => "Hello, World!"
|
|
313
|
+
|
|
314
|
+
// Still a valid procedure for routers
|
|
315
|
+
const router = os.router({ greet })
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Server Actions Support
|
|
319
|
+
|
|
320
|
+
Use `.actionable()` for framework server actions (Next.js, etc.):
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
const createTodo = effectOs
|
|
324
|
+
.input(z.object({ title: z.string() }))
|
|
325
|
+
.effect(
|
|
326
|
+
Effect.fn(function* ({ input }) {
|
|
327
|
+
const todoService = yield* TodoService
|
|
328
|
+
return yield* todoService.create(input.title)
|
|
329
|
+
})
|
|
330
|
+
)
|
|
331
|
+
.actionable({ context: async () => ({ user: await getSession() }) })
|
|
332
|
+
|
|
333
|
+
// Use in React Server Components
|
|
334
|
+
export async function TodoForm() {
|
|
335
|
+
return (
|
|
336
|
+
<form action={createTodo}>
|
|
337
|
+
<input name="title" />
|
|
338
|
+
<button type="submit">Add Todo</button>
|
|
339
|
+
</form>
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Error Handling
|
|
345
|
+
|
|
346
|
+
Effect errors are properly propagated through oRPC's error handling:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import { Effect } from 'effect'
|
|
350
|
+
|
|
351
|
+
class NotFoundError extends Effect.Tag('NotFoundError')<
|
|
352
|
+
NotFoundError,
|
|
353
|
+
{ readonly _tag: 'NotFoundError', readonly id: string }
|
|
354
|
+
>() {}
|
|
355
|
+
|
|
356
|
+
const getUser = effectOs
|
|
357
|
+
.errors({
|
|
358
|
+
NOT_FOUND: {
|
|
359
|
+
message: 'User not found',
|
|
360
|
+
data: z.object({ id: z.string() })
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
.input(z.object({ id: z.string() }))
|
|
364
|
+
.effect(({ input, errors }) =>
|
|
365
|
+
Effect.gen(function* () {
|
|
366
|
+
const userService = yield* UserService
|
|
367
|
+
const user = yield* userService.findById(input.id)
|
|
368
|
+
|
|
369
|
+
if (!user) {
|
|
370
|
+
// Use oRPC's type-safe errors
|
|
371
|
+
throw errors.NOT_FOUND({ id: input.id })
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return user
|
|
375
|
+
})
|
|
376
|
+
)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Using Effect.fn vs Effect.gen
|
|
380
|
+
|
|
381
|
+
Both generator syntaxes are supported:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
// Using Effect.fn (recommended for procedures)
|
|
385
|
+
const procedureWithFn = effectOs
|
|
386
|
+
.input(z.object({ id: z.string() }))
|
|
387
|
+
.effect(
|
|
388
|
+
Effect.fn(function* ({ input }) {
|
|
389
|
+
const service = yield* MyService
|
|
390
|
+
return yield* service.doSomething(input.id)
|
|
391
|
+
})
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
// Using Effect.gen with arrow function
|
|
395
|
+
const procedureWithGen = effectOs
|
|
396
|
+
.input(z.object({ id: z.string() }))
|
|
397
|
+
.effect(({ input }) =>
|
|
398
|
+
Effect.gen(function* () {
|
|
399
|
+
const service = yield* MyService
|
|
400
|
+
return yield* service.doSomething(input.id)
|
|
401
|
+
})
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
// Simple effects without generators
|
|
405
|
+
const simpleProcedure = effectOs
|
|
406
|
+
.input(z.object({ name: z.string() }))
|
|
407
|
+
.effect(({ input }) =>
|
|
408
|
+
Effect.succeed(`Hello, ${input.name}!`)
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Traceable Spans
|
|
413
|
+
|
|
414
|
+
All Effect procedures are automatically traced with `Effect.withSpan`. By default, the span name is the procedure path (e.g., `users.getUser`):
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
// Router structure determines span names automatically
|
|
418
|
+
const router = os.router({
|
|
419
|
+
users: os.router({
|
|
420
|
+
// Span name: "users.get"
|
|
421
|
+
get: effectOs
|
|
422
|
+
.input(z.object({ id: z.string() }))
|
|
423
|
+
.effect(
|
|
424
|
+
Effect.fn(function* ({ input }) {
|
|
425
|
+
const userService = yield* UserService
|
|
426
|
+
return yield* userService.findById(input.id)
|
|
427
|
+
})
|
|
428
|
+
),
|
|
429
|
+
// Span name: "users.create"
|
|
430
|
+
create: effectOs
|
|
431
|
+
.input(z.object({ name: z.string() }))
|
|
432
|
+
.effect(
|
|
433
|
+
Effect.fn(function* ({ input }) {
|
|
434
|
+
const userService = yield* UserService
|
|
435
|
+
return yield* userService.create(input.name)
|
|
436
|
+
})
|
|
437
|
+
),
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Use `.traced()` to override the default span name:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
const getUser = effectOs
|
|
446
|
+
.input(z.object({ id: z.string() }))
|
|
447
|
+
.traced('custom.span.name') // Override the default path-based name
|
|
448
|
+
.effect(
|
|
449
|
+
Effect.fn(function* ({ input }) {
|
|
450
|
+
const userService = yield* UserService
|
|
451
|
+
return yield* userService.findById(input.id)
|
|
452
|
+
})
|
|
453
|
+
)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Enabling OpenTelemetry
|
|
457
|
+
|
|
458
|
+
To enable tracing, include the OpenTelemetry layer in your runtime:
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { NodeSdk } from '@effect/opentelemetry'
|
|
462
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
463
|
+
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
464
|
+
|
|
465
|
+
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
|
|
466
|
+
resource: { serviceName: 'my-service' },
|
|
467
|
+
spanProcessor: [new SimpleSpanProcessor(new OTLPTraceExporter())]
|
|
468
|
+
})))
|
|
469
|
+
|
|
470
|
+
const AppLive = Layer.mergeAll(
|
|
471
|
+
UserServiceLive,
|
|
472
|
+
TracingLive
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
const runtime = ManagedRuntime.make(AppLive)
|
|
476
|
+
const effectOs = makeEffectORPC(runtime)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Error Stack Traces
|
|
480
|
+
|
|
481
|
+
When an Effect procedure fails, the span includes a properly formatted stack trace pointing to the definition site:
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
MyCustomError: Something went wrong
|
|
485
|
+
at <anonymous> (/app/src/procedures.ts:42:28)
|
|
486
|
+
at users.getById (/app/src/procedures.ts:41:35)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## API Reference
|
|
490
|
+
|
|
491
|
+
### `makeEffectORPC(runtime, builder?)`
|
|
492
|
+
|
|
493
|
+
Creates an Effect-aware procedure builder.
|
|
494
|
+
|
|
495
|
+
- `runtime` - A `ManagedRuntime<R, E>` instance that provides services for Effect procedures
|
|
496
|
+
- `builder` (optional) - An oRPC Builder instance to wrap. Defaults to `os` from `@orpc/server`
|
|
497
|
+
|
|
498
|
+
Returns an `EffectBuilder` instance.
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
// With default builder
|
|
502
|
+
const effectOs = makeEffectORPC(runtime)
|
|
503
|
+
|
|
504
|
+
// With customized builder
|
|
505
|
+
const effectAuthedOs = makeEffectORPC(runtime, authedBuilder)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### `EffectBuilder`
|
|
509
|
+
|
|
510
|
+
Wraps an oRPC Builder with Effect support. Available methods:
|
|
511
|
+
|
|
512
|
+
| Method | Description |
|
|
513
|
+
| ------------------ | ------------------------------------------------------------------------------- |
|
|
514
|
+
| `.errors(map)` | Add type-safe custom errors |
|
|
515
|
+
| `.meta(meta)` | Set procedure metadata |
|
|
516
|
+
| `.route(route)` | Configure OpenAPI route |
|
|
517
|
+
| `.input(schema)` | Define input validation schema |
|
|
518
|
+
| `.output(schema)` | Define output validation schema |
|
|
519
|
+
| `.use(middleware)` | Add middleware |
|
|
520
|
+
| `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
|
|
521
|
+
| `.effect(handler)` | Define the Effect handler |
|
|
522
|
+
|
|
523
|
+
### `EffectDecoratedProcedure`
|
|
524
|
+
|
|
525
|
+
The result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` with Effect type preservation.
|
|
526
|
+
|
|
527
|
+
| Method | Description |
|
|
528
|
+
| ----------------------- | --------------------------------------------- |
|
|
529
|
+
| `.errors(map)` | Add more custom errors |
|
|
530
|
+
| `.meta(meta)` | Update metadata |
|
|
531
|
+
| `.route(route)` | Update route configuration |
|
|
532
|
+
| `.use(middleware)` | Add middleware |
|
|
533
|
+
| `.callable(options?)` | Make procedure directly invocable |
|
|
534
|
+
| `.actionable(options?)` | Make procedure compatible with server actions |
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT
|