effect-orpc 0.1.4 → 0.2.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.
@@ -2,66 +2,32 @@ import type {
2
2
  AnySchema,
3
3
  ContractRouter,
4
4
  ErrorMap,
5
- HTTPPath,
6
- InferSchemaOutput,
7
5
  Meta,
8
- Route,
9
6
  Schema,
10
7
  } from "@orpc/contract";
11
- import {
12
- mergeMeta,
13
- mergePrefix,
14
- mergeRoute,
15
- mergeTags,
16
- ORPCError,
17
- } from "@orpc/contract";
18
- import type {
19
- AnyMiddleware,
20
- BuilderConfig,
21
- BuilderDef,
22
- Context,
23
- Lazy,
24
- MapInputMiddleware,
25
- MergedCurrentContext,
26
- MergedInitialContext,
27
- Middleware,
28
- ProcedureHandler,
29
- ProcedureHandlerOptions,
30
- Router,
31
- } from "@orpc/server";
32
- import {
33
- addMiddleware,
34
- Builder,
35
- decorateMiddleware,
36
- fallbackConfig,
37
- lazy,
38
- } from "@orpc/server";
39
- import type { IntersectPick } from "@orpc/shared";
8
+ import type { Context, Router } from "@orpc/server";
9
+ import { Builder, fallbackConfig, lazy } from "@orpc/server";
40
10
  import type { ManagedRuntime } from "effect";
41
- import { Cause, Effect, Exit, FiberRefs } from "effect";
42
11
 
43
12
  import { enhanceEffectRouter } from "./effect-enhance-router";
44
13
  import { EffectDecoratedProcedure } from "./effect-procedure";
45
- import { getCurrentFiberRefs } from "./fiber-context-bridge";
46
- import type {
47
- EffectErrorConstructorMap,
48
- EffectErrorMap,
49
- MergedEffectErrorMap,
50
- } from "./tagged-error";
14
+ import { createEffectProcedureHandler } from "./effect-runtime";
15
+ import {
16
+ createNodeProxy,
17
+ unhandled,
18
+ type NodeProxyContext,
19
+ } from "./extension/create-node-proxy";
51
20
  import {
52
- createEffectErrorConstructorMap,
53
- effectErrorMapToErrorMap,
54
- isORPCTaggedError,
55
- } from "./tagged-error";
21
+ attachEffectState,
22
+ getEffectErrorMap,
23
+ unwrapEffectUpstream,
24
+ type EffectProxyTarget,
25
+ } from "./extension/state";
26
+ import type { EffectErrorMap, MergedEffectErrorMap } from "./tagged-error";
27
+ import { effectErrorMapToErrorMap } from "./tagged-error";
56
28
  import type {
57
29
  AnyBuilderLike,
58
30
  EffectBuilderDef,
59
- EffectErrorMapToErrorMap,
60
- EffectProcedureBuilderWithInput,
61
- EffectProcedureBuilderWithOutput,
62
- EffectProcedureHandler,
63
- EffectRouterBuilder,
64
- EnhancedEffectRouter,
65
31
  InferBuilderCurrentContext,
66
32
  InferBuilderErrorMap,
67
33
  InferBuilderInitialContext,
@@ -69,12 +35,215 @@ import type {
69
35
  InferBuilderMeta,
70
36
  InferBuilderOutputSchema,
71
37
  } from "./types";
38
+ import type { EffectBuilderSurface } from "./types/effect-builder-surface";
39
+
40
+ const builderVirtualDescriptors = {
41
+ "~effect": { enumerable: true },
42
+ effect: { enumerable: false },
43
+ errors: { enumerable: false },
44
+ handler: { enumerable: false },
45
+ lazy: { enumerable: false },
46
+ router: { enumerable: false },
47
+ traced: { enumerable: false },
48
+ } as const;
49
+
50
+ const builderVirtualKeys = [
51
+ "~effect",
52
+ "errors",
53
+ "effect",
54
+ "traced",
55
+ "handler",
56
+ "router",
57
+ "lazy",
58
+ ] as const;
59
+
60
+ type EffectBuilderTarget = EffectBuilder<
61
+ any,
62
+ any,
63
+ any,
64
+ any,
65
+ any,
66
+ any,
67
+ any,
68
+ any
69
+ > &
70
+ EffectProxyTarget<AnyBuilderLike>;
71
+
72
+ function isBuilderLike(value: unknown): value is AnyBuilderLike {
73
+ return typeof value === "object" && value !== null && "~orpc" in value;
74
+ }
75
+
76
+ function getOrCreateVirtualMethod<T>(
77
+ context: NodeProxyContext<EffectBuilderTarget, AnyBuilderLike>,
78
+ prop: PropertyKey,
79
+ factory: () => T,
80
+ ): T {
81
+ const cache = context.methodCache;
82
+ if (cache.has(prop)) {
83
+ return cache.get(prop) as T;
84
+ }
85
+
86
+ const value = factory();
87
+ cache.set(prop, value);
88
+ return value;
89
+ }
90
+
91
+ function getEffectBuilderDef(
92
+ context: NodeProxyContext<EffectBuilderTarget, AnyBuilderLike>,
93
+ ): EffectBuilderDef<any, any, any, any, any, any> {
94
+ return {
95
+ ...context.upstream["~orpc"],
96
+ effectErrorMap: context.state.effectErrorMap,
97
+ runtime: context.state.runtime,
98
+ spanConfig: context.state.spanConfig,
99
+ };
100
+ }
101
+
102
+ function wrapBuilderLike(
103
+ builder: AnyBuilderLike,
104
+ state: NodeProxyContext<EffectBuilderTarget, AnyBuilderLike>["state"],
105
+ ): EffectBuilder<any, any, any, any, any, any, any, any> {
106
+ return new EffectBuilder(
107
+ {
108
+ ...builder["~orpc"],
109
+ effectErrorMap: state.effectErrorMap,
110
+ runtime: state.runtime,
111
+ spanConfig: state.spanConfig,
112
+ },
113
+ unwrapEffectUpstream(builder),
114
+ );
115
+ }
116
+
117
+ function createEffectBuilderProxy(
118
+ target: EffectBuilderTarget,
119
+ ): EffectBuilderTarget {
120
+ return createNodeProxy<EffectBuilderTarget, AnyBuilderLike>(target, {
121
+ getVirtual(context, prop) {
122
+ const effectDef = getEffectBuilderDef(context);
123
+ if (prop === "~effect") {
124
+ return getEffectBuilderDef(context);
125
+ }
126
+
127
+ const { upstream: source, state } = context;
128
+
129
+ switch (prop) {
130
+ case "errors":
131
+ return getOrCreateVirtualMethod(context, prop, () => {
132
+ return <U extends EffectErrorMap>(errors: U) => {
133
+ const nextEffectErrorMap: MergedEffectErrorMap<
134
+ typeof state.effectErrorMap,
135
+ U
136
+ > = {
137
+ ...state.effectErrorMap,
138
+ ...errors,
139
+ };
140
+ const nextBuilder: AnyBuilderLike = Reflect.apply(
141
+ Reflect.get(source, "errors", source),
142
+ source,
143
+ [effectErrorMapToErrorMap(errors)],
144
+ );
145
+
146
+ return wrapBuilderLike(nextBuilder, {
147
+ ...state,
148
+ effectErrorMap: nextEffectErrorMap,
149
+ });
150
+ };
151
+ });
152
+ case "effect":
153
+ return getOrCreateVirtualMethod(context, prop, () => {
154
+ return (
155
+ effectFn: Parameters<
156
+ EffectBuilderSurface<
157
+ any,
158
+ any,
159
+ any,
160
+ any,
161
+ any,
162
+ any,
163
+ any,
164
+ any
165
+ >["effect"]
166
+ >[0],
167
+ ) => {
168
+ const defaultCaptureStackTrace = addSpanStackTrace();
169
+ return new EffectDecoratedProcedure({
170
+ ...effectDef,
171
+ handler: async (opts) => {
172
+ return createEffectProcedureHandler({
173
+ defaultCaptureStackTrace,
174
+ effectErrorMap: state.effectErrorMap,
175
+ effectFn,
176
+ runtime: state.runtime,
177
+ spanConfig: state.spanConfig,
178
+ })(opts as any);
179
+ },
180
+ });
181
+ };
182
+ });
183
+ case "traced":
184
+ return getOrCreateVirtualMethod(context, prop, () => {
185
+ return (spanName: string) =>
186
+ wrapBuilderLike(source, {
187
+ ...state,
188
+ spanConfig: {
189
+ captureStackTrace: addSpanStackTrace(),
190
+ name: spanName,
191
+ },
192
+ });
193
+ });
194
+ case "handler":
195
+ return getOrCreateVirtualMethod(context, prop, () => {
196
+ return (
197
+ handler: Parameters<
198
+ EffectBuilderSurface<
199
+ any,
200
+ any,
201
+ any,
202
+ any,
203
+ any,
204
+ any,
205
+ any,
206
+ any
207
+ >["handler"]
208
+ >[0],
209
+ ) =>
210
+ new EffectDecoratedProcedure({
211
+ ...effectDef,
212
+ handler,
213
+ });
214
+ });
215
+ case "router":
216
+ return getOrCreateVirtualMethod(context, prop, () => {
217
+ return (router: Router<ContractRouter<any>, any>) =>
218
+ enhanceEffectRouter(router, effectDef) as any;
219
+ });
220
+ case "lazy":
221
+ return getOrCreateVirtualMethod(context, prop, () => {
222
+ return (
223
+ loader: () => Promise<{
224
+ default: Router<ContractRouter<any>, any>;
225
+ }>,
226
+ ) => enhanceEffectRouter(lazy(loader), effectDef) as any;
227
+ });
228
+ default:
229
+ return unhandled();
230
+ }
231
+ },
232
+ virtualDescriptors: builderVirtualDescriptors,
233
+ virtualKeys: builderVirtualKeys,
234
+ wrapResult(context, _prop, result) {
235
+ if (!isBuilderLike(result)) {
236
+ return result;
237
+ }
238
+
239
+ return wrapBuilderLike(result, context.state);
240
+ },
241
+ });
242
+ }
72
243
 
73
244
  /**
74
245
  * Captures the stack trace at the call site for better error reporting in spans.
75
246
  * This is called at procedure definition time to capture where the procedure was defined.
76
- *
77
- * @returns A function that lazily extracts the relevant stack frame
78
247
  */
79
248
  export function addSpanStackTrace(): () => string | undefined {
80
249
  const ErrorConstructor = Error as typeof Error & {
@@ -113,49 +282,37 @@ export class EffectBuilder<
113
282
  TMeta extends Meta,
114
283
  TRequirementsProvided,
115
284
  TRuntimeError,
285
+ > implements EffectBuilderSurface<
286
+ TInitialContext,
287
+ TCurrentContext,
288
+ TInputSchema,
289
+ TOutputSchema,
290
+ TEffectErrorMap,
291
+ TMeta,
292
+ TRequirementsProvided,
293
+ TRuntimeError
116
294
  > {
117
- /**
118
- * This property holds the defined options and the effect-specific properties.
119
- */
120
- declare "~effect": EffectBuilderDef<
295
+ declare $config: EffectBuilderSurface<
296
+ TInitialContext,
297
+ TCurrentContext,
121
298
  TInputSchema,
122
299
  TOutputSchema,
123
300
  TEffectErrorMap,
124
301
  TMeta,
125
302
  TRequirementsProvided,
126
303
  TRuntimeError
127
- >;
128
- declare "~orpc": BuilderDef<
304
+ >["$config"];
305
+ declare $context: EffectBuilderSurface<
306
+ TInitialContext,
307
+ TCurrentContext,
129
308
  TInputSchema,
130
309
  TOutputSchema,
131
- EffectErrorMapToErrorMap<TEffectErrorMap>,
132
- TMeta
133
- >;
134
-
135
- constructor(
136
- def: EffectBuilderDef<
137
- TInputSchema,
138
- TOutputSchema,
139
- TEffectErrorMap,
140
- TMeta,
141
- TRequirementsProvided,
142
- TRuntimeError
143
- >,
144
- ) {
145
- const { runtime, spanConfig, effectErrorMap, ...orpcDef } = def;
146
- this["~orpc"] = orpcDef;
147
- this["~effect"] = { runtime, spanConfig, effectErrorMap, ...orpcDef };
148
- }
149
-
150
- /**
151
- * Sets or overrides the config.
152
- *
153
- * @see {@link https://orpc.dev/docs/client/server-side#middlewares-order Middlewares Order Docs}
154
- * @see {@link https://orpc.dev/docs/best-practices/dedupe-middleware#configuration Dedupe Middleware Docs}
155
- */
156
- $config(
157
- config: BuilderConfig,
158
- ): EffectBuilder<
310
+ TEffectErrorMap,
311
+ TMeta,
312
+ TRequirementsProvided,
313
+ TRuntimeError
314
+ >["$context"];
315
+ declare $meta: EffectBuilderSurface<
159
316
  TInitialContext,
160
317
  TCurrentContext,
161
318
  TInputSchema,
@@ -164,110 +321,36 @@ export class EffectBuilder<
164
321
  TMeta,
165
322
  TRequirementsProvided,
166
323
  TRuntimeError
167
- > {
168
- const inputValidationCount =
169
- this["~effect"].inputValidationIndex -
170
- fallbackConfig(
171
- "initialInputValidationIndex",
172
- this["~effect"].config.initialInputValidationIndex,
173
- );
174
- const outputValidationCount =
175
- this["~effect"].outputValidationIndex -
176
- fallbackConfig(
177
- "initialOutputValidationIndex",
178
- this["~effect"].config.initialOutputValidationIndex,
179
- );
180
-
181
- return new EffectBuilder({
182
- ...this["~effect"],
183
- config,
184
- dedupeLeadingMiddlewares: fallbackConfig(
185
- "dedupeLeadingMiddlewares",
186
- config.dedupeLeadingMiddlewares,
187
- ),
188
- inputValidationIndex:
189
- fallbackConfig(
190
- "initialInputValidationIndex",
191
- config.initialInputValidationIndex,
192
- ) + inputValidationCount,
193
- outputValidationIndex:
194
- fallbackConfig(
195
- "initialOutputValidationIndex",
196
- config.initialOutputValidationIndex,
197
- ) + outputValidationCount,
198
- });
199
- }
200
-
201
- /**
202
- * Set or override the initial context.
203
- *
204
- * @see {@link https://orpc.dev/docs/context Context Docs}
205
- */
206
- $context<U extends Context>(): EffectBuilder<
207
- U & Record<never, never>,
208
- U,
324
+ >["$meta"];
325
+ declare $route: EffectBuilderSurface<
326
+ TInitialContext,
327
+ TCurrentContext,
209
328
  TInputSchema,
210
329
  TOutputSchema,
211
330
  TEffectErrorMap,
212
331
  TMeta,
213
332
  TRequirementsProvided,
214
333
  TRuntimeError
215
- > {
216
- /**
217
- * We need `& Record<never, never>` to deal with `has no properties in common with type` error
218
- */
219
-
220
- return new EffectBuilder({
221
- ...this["~effect"],
222
- middlewares: [],
223
- inputValidationIndex: fallbackConfig(
224
- "initialInputValidationIndex",
225
- this["~effect"].config.initialInputValidationIndex,
226
- ),
227
- outputValidationIndex: fallbackConfig(
228
- "initialOutputValidationIndex",
229
- this["~effect"].config.initialOutputValidationIndex,
230
- ),
231
- });
232
- }
233
-
234
- /**
235
- * Sets or overrides the initial meta.
236
- *
237
- * @see {@link https://orpc.dev/docs/metadata Metadata Docs}
238
- */
239
- $meta<U extends Meta>(
240
- initialMeta: U,
241
- ): EffectBuilder<
334
+ >["$route"];
335
+ declare $input: EffectBuilderSurface<
242
336
  TInitialContext,
243
337
  TCurrentContext,
244
338
  TInputSchema,
245
339
  TOutputSchema,
246
340
  TEffectErrorMap,
247
- U & Record<never, never>,
341
+ TMeta,
248
342
  TRequirementsProvided,
249
343
  TRuntimeError
250
- > {
251
- /**
252
- * We need `& Record<never, never>` to deal with `has no properties in common with type` error
253
- */
254
-
255
- return new EffectBuilder({
256
- ...this["~effect"],
257
- meta: initialMeta,
258
- });
259
- }
260
-
261
- /**
262
- * Sets or overrides the initial route.
263
- * This option is typically relevant when integrating with OpenAPI.
264
- *
265
- * @see {@link https://orpc.dev/docs/openapi/routing OpenAPI Routing Docs}
266
- * @see {@link https://orpc.dev/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs}
267
- */
268
- $route(
269
- initialRoute: Route,
270
- ): EffectBuilder<
344
+ >["$input"];
345
+ declare "~effect": EffectBuilderDef<
346
+ TInputSchema,
347
+ TOutputSchema,
348
+ TEffectErrorMap,
349
+ TMeta,
350
+ TRequirementsProvided,
351
+ TRuntimeError
352
+ >;
353
+ declare "~orpc": EffectBuilderSurface<
271
354
  TInitialContext,
272
355
  TCurrentContext,
273
356
  TInputSchema,
@@ -276,133 +359,38 @@ export class EffectBuilder<
276
359
  TMeta,
277
360
  TRequirementsProvided,
278
361
  TRuntimeError
279
- > {
280
- return new EffectBuilder({
281
- ...this["~effect"],
282
- route: initialRoute,
283
- });
284
- }
285
-
286
- /**
287
- * Sets or overrides the initial input schema.
288
- *
289
- * @see {@link https://orpc.dev/docs/procedure#initial-configuration Initial Procedure Configuration Docs}
290
- */
291
- $input<U extends AnySchema>(
292
- initialInputSchema?: U,
293
- ): EffectBuilder<
362
+ >["~orpc"];
363
+ declare middleware: EffectBuilderSurface<
294
364
  TInitialContext,
295
365
  TCurrentContext,
296
- U,
366
+ TInputSchema,
297
367
  TOutputSchema,
298
368
  TEffectErrorMap,
299
369
  TMeta,
300
370
  TRequirementsProvided,
301
371
  TRuntimeError
302
- > {
303
- return new EffectBuilder({
304
- ...this["~effect"],
305
- inputSchema: initialInputSchema,
306
- });
307
- }
308
-
309
- /**
310
- * Adds type-safe custom errors.
311
- * Supports both traditional oRPC error definitions and ORPCTaggedError classes.
312
- *
313
- * @example
314
- * ```ts
315
- * // Traditional format
316
- * builder.errors({ BAD_REQUEST: { status: 400, message: 'Bad request' } })
317
- *
318
- * // Tagged error class
319
- * builder.errors({ USER_NOT_FOUND: UserNotFoundError })
320
- *
321
- * // Mixed
322
- * builder.errors({
323
- * BAD_REQUEST: { status: 400 },
324
- * USER_NOT_FOUND: UserNotFoundError,
325
- * })
326
- * ```
327
- *
328
- * @see {@link https://orpc.dev/docs/error-handling#type%E2%80%90safe-error-handling Type-Safe Error Handling Docs}
329
- */
330
- errors<U extends EffectErrorMap>(
331
- errors: U,
332
- ): EffectBuilder<
372
+ >["middleware"];
373
+ declare errors: EffectBuilderSurface<
333
374
  TInitialContext,
334
375
  TCurrentContext,
335
376
  TInputSchema,
336
377
  TOutputSchema,
337
- MergedEffectErrorMap<TEffectErrorMap, U>,
378
+ TEffectErrorMap,
338
379
  TMeta,
339
380
  TRequirementsProvided,
340
381
  TRuntimeError
341
- > {
342
- const newEffectErrorMap: MergedEffectErrorMap<TEffectErrorMap, U> = {
343
- ...this["~effect"].effectErrorMap,
344
- ...errors,
345
- };
346
- return new EffectBuilder({
347
- ...this["~effect"],
348
- errorMap: effectErrorMapToErrorMap(newEffectErrorMap),
349
- effectErrorMap: newEffectErrorMap,
350
- });
351
- }
352
-
353
- /**
354
- * Uses a middleware to modify the context or improve the pipeline.
355
- *
356
- * @info Supports both normal middleware and inline middleware implementations.
357
- * @note The current context must be satisfy middleware dependent-context
358
- * @see {@link https://orpc.dev/docs/middleware Middleware Docs}
359
- */
360
- use<
361
- UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
362
- UInContext extends Context = TCurrentContext,
363
- >(
364
- middleware: Middleware<
365
- UInContext | TCurrentContext,
366
- UOutContext,
367
- InferSchemaOutput<TInputSchema>,
368
- unknown,
369
- EffectErrorConstructorMap<TEffectErrorMap>,
370
- TMeta
371
- >,
372
- ): EffectBuilder<
373
- MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
374
- MergedCurrentContext<TCurrentContext, UOutContext>,
382
+ >["errors"];
383
+ declare use: EffectBuilderSurface<
384
+ TInitialContext,
385
+ TCurrentContext,
375
386
  TInputSchema,
376
387
  TOutputSchema,
377
388
  TEffectErrorMap,
378
389
  TMeta,
379
390
  TRequirementsProvided,
380
391
  TRuntimeError
381
- >;
382
-
383
- use(
384
- middleware: AnyMiddleware,
385
- mapInput?: MapInputMiddleware<any, any>,
386
- ): EffectBuilder<any, any, any, any, any, any, any, any> {
387
- const mapped = mapInput
388
- ? decorateMiddleware(middleware).mapInput(mapInput)
389
- : middleware;
390
-
391
- return new EffectBuilder({
392
- ...this["~effect"],
393
- middlewares: addMiddleware(this["~effect"].middlewares, mapped),
394
- });
395
- }
396
-
397
- /**
398
- * Sets or updates the metadata.
399
- * The provided metadata is spared-merged with any existing metadata.
400
- *
401
- * @see {@link https://orpc.dev/docs/metadata Metadata Docs}
402
- */
403
- meta(
404
- meta: TMeta,
405
- ): EffectBuilder<
392
+ >["use"];
393
+ declare meta: EffectBuilderSurface<
406
394
  TInitialContext,
407
395
  TCurrentContext,
408
396
  TInputSchema,
@@ -411,24 +399,8 @@ export class EffectBuilder<
411
399
  TMeta,
412
400
  TRequirementsProvided,
413
401
  TRuntimeError
414
- > {
415
- return new EffectBuilder({
416
- ...this["~effect"],
417
- meta: mergeMeta(this["~effect"].meta, meta),
418
- });
419
- }
420
-
421
- /**
422
- * Sets or updates the route definition.
423
- * The provided route is spared-merged with any existing route.
424
- * This option is typically relevant when integrating with OpenAPI.
425
- *
426
- * @see {@link https://orpc.dev/docs/openapi/routing OpenAPI Routing Docs}
427
- * @see {@link https://orpc.dev/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs}
428
- */
429
- route(
430
- route: Route,
431
- ): EffectBuilder<
402
+ >["meta"];
403
+ declare route: EffectBuilderSurface<
432
404
  TInitialContext,
433
405
  TCurrentContext,
434
406
  TInputSchema,
@@ -437,98 +409,28 @@ export class EffectBuilder<
437
409
  TMeta,
438
410
  TRequirementsProvided,
439
411
  TRuntimeError
440
- > {
441
- return new EffectBuilder({
442
- ...this["~effect"],
443
- route: mergeRoute(this["~effect"].route, route),
444
- });
445
- }
446
-
447
- /**
448
- * Defines the input validation schema.
449
- *
450
- * @see {@link https://orpc.dev/docs/procedure#input-output-validation Input Validation Docs}
451
- */
452
- input<USchema extends AnySchema>(
453
- schema: USchema,
454
- ): EffectProcedureBuilderWithInput<
412
+ >["route"];
413
+ declare input: EffectBuilderSurface<
455
414
  TInitialContext,
456
415
  TCurrentContext,
457
- USchema,
416
+ TInputSchema,
458
417
  TOutputSchema,
459
418
  TEffectErrorMap,
460
419
  TMeta,
461
420
  TRequirementsProvided,
462
421
  TRuntimeError
463
- > {
464
- return new EffectBuilder({
465
- ...this["~effect"],
466
- inputSchema: schema,
467
- inputValidationIndex:
468
- fallbackConfig(
469
- "initialInputValidationIndex",
470
- this["~effect"].config.initialInputValidationIndex,
471
- ) + this["~effect"].middlewares.length,
472
- // we cast to any because EffectProcedureBuilderWithInput is expecting
473
- // use() input type to be defined, and EffectBuilder types its use() input
474
- // to unknown to allow any middleware to be passed
475
- // ---
476
- // note: the original implentation of the builder also uses any for the same reason
477
- }) as any;
478
- }
479
-
480
- /**
481
- * Defines the output validation schema.
482
- *
483
- * @see {@link https://orpc.dev/docs/procedure#input-output-validation Output Validation Docs}
484
- */
485
- output<USchema extends AnySchema>(
486
- schema: USchema,
487
- ): EffectProcedureBuilderWithOutput<
422
+ >["input"];
423
+ declare output: EffectBuilderSurface<
488
424
  TInitialContext,
489
425
  TCurrentContext,
490
426
  TInputSchema,
491
- USchema,
427
+ TOutputSchema,
492
428
  TEffectErrorMap,
493
429
  TMeta,
494
430
  TRequirementsProvided,
495
431
  TRuntimeError
496
- > {
497
- // We cast to any because EffectProcedureBuilderWithOutput narrows
498
- // handler/effect output typing based on the declared output schema.
499
- return new EffectBuilder({
500
- ...this["~effect"],
501
- outputSchema: schema,
502
- outputValidationIndex:
503
- fallbackConfig(
504
- "initialOutputValidationIndex",
505
- this["~effect"].config.initialOutputValidationIndex,
506
- ) + this["~effect"].middlewares.length,
507
- }) as any;
508
- }
509
-
510
- /**
511
- * Adds a traceable span to the procedure for telemetry.
512
- * The span name is used for Effect tracing via `Effect.withSpan`.
513
- * Stack trace is captured at the call site for better error reporting.
514
- *
515
- * @param spanName - The name of the span for telemetry (e.g., 'users.getUser')
516
- * @returns An EffectBuilder with span tracing configured
517
- *
518
- * @example
519
- * ```ts
520
- * const getUser = effectOs
521
- * .input(z.object({ id: z.string() }))
522
- * .traced('users.getUser')
523
- * .effect(function* ({ input }) {
524
- * const userService = yield* UserService
525
- * return yield* userService.findById(input.id)
526
- * })
527
- * ```
528
- */
529
- traced(
530
- spanName: string,
531
- ): EffectBuilder<
432
+ >["output"];
433
+ declare traced: EffectBuilderSurface<
532
434
  TInitialContext,
533
435
  TCurrentContext,
534
436
  TInputSchema,
@@ -537,250 +439,97 @@ export class EffectBuilder<
537
439
  TMeta,
538
440
  TRequirementsProvided,
539
441
  TRuntimeError
540
- > {
541
- return new EffectBuilder({
542
- ...this["~effect"],
543
- spanConfig: {
544
- name: spanName,
545
- captureStackTrace: addSpanStackTrace(),
546
- },
547
- });
548
- }
549
-
550
- handler<UFuncOutput>(
551
- handler: ProcedureHandler<
552
- TCurrentContext,
553
- InferSchemaOutput<TInputSchema>,
554
- UFuncOutput,
555
- EffectErrorMapToErrorMap<TEffectErrorMap>,
556
- TMeta
557
- >,
558
- ): EffectDecoratedProcedure<
442
+ >["traced"];
443
+ declare handler: EffectBuilderSurface<
559
444
  TInitialContext,
560
445
  TCurrentContext,
561
446
  TInputSchema,
562
- Schema<UFuncOutput, UFuncOutput>,
447
+ TOutputSchema,
563
448
  TEffectErrorMap,
564
449
  TMeta,
565
450
  TRequirementsProvided,
566
451
  TRuntimeError
567
- > {
568
- return new EffectDecoratedProcedure({
569
- ...this["~effect"],
570
- handler,
571
- });
572
- }
573
-
574
- /**
575
- * Defines the handler of the procedure using an Effect.
576
- * The Effect is executed using the ManagedRuntime provided during builder creation.
577
- * The effect is automatically wrapped with `Effect.withSpan`.
578
- *
579
- * @see {@link https://orpc.dev/docs/procedure Procedure Docs}
580
- */
581
- effect<UFuncOutput>(
582
- effectFn: EffectProcedureHandler<
583
- TCurrentContext,
584
- TInputSchema,
585
- UFuncOutput,
586
- TEffectErrorMap,
587
- TRequirementsProvided,
588
- TMeta
589
- >,
590
- ): EffectDecoratedProcedure<
452
+ >["handler"];
453
+ declare effect: EffectBuilderSurface<
591
454
  TInitialContext,
592
455
  TCurrentContext,
593
456
  TInputSchema,
594
- Schema<UFuncOutput, UFuncOutput>,
457
+ TOutputSchema,
595
458
  TEffectErrorMap,
596
459
  TMeta,
597
460
  TRequirementsProvided,
598
461
  TRuntimeError
599
- > {
600
- const { runtime, spanConfig } = this["~effect"];
601
- // Capture stack trace at definition time for default tracing
602
- const defaultCaptureStackTrace = addSpanStackTrace();
603
- return new EffectDecoratedProcedure({
604
- ...this["~effect"],
605
- handler: async (opts) => {
606
- const effectOpts: ProcedureHandlerOptions<
607
- TCurrentContext,
608
- InferSchemaOutput<TInputSchema>,
609
- EffectErrorConstructorMap<TEffectErrorMap>,
610
- TMeta
611
- > = {
612
- context: opts.context,
613
- input: opts.input,
614
- path: opts.path,
615
- procedure: opts.procedure,
616
- signal: opts.signal,
617
- lastEventId: opts.lastEventId,
618
- errors: createEffectErrorConstructorMap(
619
- this["~effect"].effectErrorMap,
620
- ),
621
- };
622
- const spanName = spanConfig?.name ?? opts.path.join(".");
623
- const captureStackTrace =
624
- spanConfig?.captureStackTrace ?? defaultCaptureStackTrace;
625
- const resolver = Effect.fnUntraced(effectFn);
626
- const tracedEffect = Effect.withSpan(resolver(effectOpts), spanName, {
627
- captureStackTrace,
628
- });
629
- const parentFiberRefs = getCurrentFiberRefs();
630
- const effectWithRefs = parentFiberRefs
631
- ? Effect.fiberIdWith((fiberId) =>
632
- Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
633
- Effect.setFiberRefs(
634
- FiberRefs.joinAs(fiberRefs, fiberId, parentFiberRefs),
635
- ).pipe(Effect.andThen(tracedEffect)),
636
- ),
637
- )
638
- : tracedEffect;
639
- const exit = await runtime.runPromiseExit(effectWithRefs, {
640
- signal: opts.signal,
641
- });
642
-
643
- if (Exit.isFailure(exit)) {
644
- throw Cause.match(exit.cause, {
645
- onDie(defect) {
646
- return new ORPCError("INTERNAL_SERVER_ERROR", {
647
- cause: defect,
648
- });
649
- },
650
- onFail(error) {
651
- if (isORPCTaggedError(error)) {
652
- return error.toORPCError();
653
- }
654
- if (error instanceof ORPCError) {
655
- return error;
656
- }
657
- return new ORPCError("INTERNAL_SERVER_ERROR", {
658
- cause: error,
659
- });
660
- },
661
- onInterrupt(fiberId) {
662
- return new ORPCError("INTERNAL_SERVER_ERROR", {
663
- cause: new Error(`${fiberId} Interrupted`),
664
- });
665
- },
666
- onSequential(left) {
667
- return left;
668
- },
669
- onEmpty: new ORPCError("INTERNAL_SERVER_ERROR", {
670
- cause: new Error("Unknown error"),
671
- }),
672
- onParallel(left) {
673
- return left;
674
- },
675
- });
676
- }
677
-
678
- return exit.value;
679
- },
680
- });
681
- }
682
-
683
- /**
684
- * Prefixes all procedures in the router.
685
- * The provided prefix is post-appended to any existing router prefix.
686
- *
687
- * @note This option does not affect procedures that do not define a path in their route definition.
688
- *
689
- * @see {@link https://orpc.dev/docs/openapi/routing#route-prefixes OpenAPI Route Prefixes Docs}
690
- */
691
- prefix(
692
- prefix: HTTPPath,
693
- ): EffectRouterBuilder<
462
+ >["effect"];
463
+ declare prefix: EffectBuilderSurface<
694
464
  TInitialContext,
695
465
  TCurrentContext,
466
+ TInputSchema,
467
+ TOutputSchema,
696
468
  TEffectErrorMap,
697
469
  TMeta,
698
470
  TRequirementsProvided,
699
471
  TRuntimeError
700
- > {
701
- return new EffectBuilder({
702
- ...this["~effect"],
703
- prefix: mergePrefix(this["~effect"].prefix, prefix),
704
- }) as any;
705
- }
706
-
707
- /**
708
- * Adds tags to all procedures in the router.
709
- * This helpful when you want to group procedures together in the OpenAPI specification.
710
- *
711
- * @see {@link https://orpc.dev/docs/openapi/openapi-specification#operation-metadata OpenAPI Operation Metadata Docs}
712
- */
713
- tag(
714
- ...tags: string[]
715
- ): EffectRouterBuilder<
472
+ >["prefix"];
473
+ declare tag: EffectBuilderSurface<
716
474
  TInitialContext,
717
475
  TCurrentContext,
476
+ TInputSchema,
477
+ TOutputSchema,
718
478
  TEffectErrorMap,
719
479
  TMeta,
720
480
  TRequirementsProvided,
721
481
  TRuntimeError
722
- > {
723
- return new EffectBuilder({
724
- ...this["~effect"],
725
- tags: mergeTags(this["~effect"].tags, tags),
726
- }) as any;
727
- }
728
-
729
- /**
730
- * Applies all of the previously defined options to the specified router.
731
- *
732
- * @see {@link https://orpc.dev/docs/router#extending-router Extending Router Docs}
733
- */
734
- router<U extends Router<ContractRouter<TMeta>, TCurrentContext>>(
735
- router: U,
736
- ): EnhancedEffectRouter<
737
- U,
482
+ >["tag"];
483
+ declare router: EffectBuilderSurface<
738
484
  TInitialContext,
739
485
  TCurrentContext,
740
- TEffectErrorMap
741
- > {
742
- return enhanceEffectRouter(router, {
743
- ...this["~effect"],
744
- }) as any; // Type instantiation is excessively deep and possibly infinite
745
- }
746
-
747
- /**
748
- * Create a lazy router
749
- * And applies all of the previously defined options to the specified router.
750
- *
751
- * @see {@link https://orpc.dev/docs/router#extending-router Extending Router Docs}
752
- */
753
- lazy<U extends Router<ContractRouter<TMeta>, TCurrentContext>>(
754
- loader: () => Promise<{ default: U }>,
755
- ): EnhancedEffectRouter<
756
- Lazy<U>,
486
+ TInputSchema,
487
+ TOutputSchema,
488
+ TEffectErrorMap,
489
+ TMeta,
490
+ TRequirementsProvided,
491
+ TRuntimeError
492
+ >["router"];
493
+ declare lazy: EffectBuilderSurface<
757
494
  TInitialContext,
758
495
  TCurrentContext,
759
- TEffectErrorMap
760
- > {
761
- return enhanceEffectRouter(lazy(loader), {
762
- ...this["~effect"],
763
- }) as any; // Type instantiation is excessively deep and possibly infinite
496
+ TInputSchema,
497
+ TOutputSchema,
498
+ TEffectErrorMap,
499
+ TMeta,
500
+ TRequirementsProvided,
501
+ TRuntimeError
502
+ >["lazy"];
503
+
504
+ constructor(
505
+ def: EffectBuilderDef<
506
+ TInputSchema,
507
+ TOutputSchema,
508
+ TEffectErrorMap,
509
+ TMeta,
510
+ TRequirementsProvided,
511
+ TRuntimeError
512
+ >,
513
+ builder?: AnyBuilderLike,
514
+ ) {
515
+ const { runtime, spanConfig, effectErrorMap, ...orpcDef } = def;
516
+
517
+ attachEffectState(this, builder ?? new Builder(orpcDef), {
518
+ effectErrorMap,
519
+ runtime,
520
+ spanConfig,
521
+ });
522
+
523
+ return createEffectBuilderProxy(this);
764
524
  }
765
525
  }
766
526
 
767
527
  /**
768
528
  * Creates an Effect-aware procedure builder with the specified ManagedRuntime.
769
- * Uses the default `os` builder from `@orpc/server`.
529
+ * Uses the default builder shape from `@orpc/server`.
770
530
  *
771
531
  * @param runtime - The ManagedRuntime that provides services for Effect procedures
772
532
  * @returns An EffectBuilder instance for creating Effect-native procedures
773
- *
774
- * @example
775
- * ```ts
776
- * import { makeEffectORPC } from '@orpc/effect'
777
- * import { Effect, Layer, ManagedRuntime } from 'effect'
778
- *
779
- * const runtime = ManagedRuntime.make(Layer.empty)
780
- * const effectOs = makeEffectORPC(runtime)
781
- *
782
- * const hello = effectOs.effect(() => Effect.succeed('Hello!'))
783
- * ```
784
533
  */
785
534
  export function makeEffectORPC<TRequirementsProvided, TRuntimeError>(
786
535
  runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>,
@@ -800,31 +549,8 @@ export function makeEffectORPC<TRequirementsProvided, TRuntimeError>(
800
549
  * with the specified ManagedRuntime.
801
550
  *
802
551
  * @param runtime - The ManagedRuntime that provides services for Effect procedures
803
- * @param builder - The oRPC Builder instance to wrap (e.g., a customized `os`)
552
+ * @param builder - The oRPC Builder instance to wrap
804
553
  * @returns An EffectBuilder instance that extends the original builder with Effect support
805
- *
806
- * @example
807
- * ```ts
808
- * import { makeEffectORPC } from '@orpc/effect'
809
- * import { os } from '@orpc/server'
810
- * import { Effect, Layer, ManagedRuntime } from 'effect'
811
- *
812
- * // Create a customized builder
813
- * const authedOs = os.use(authMiddleware)
814
- *
815
- * // Wrap it with Effect support
816
- * const runtime = ManagedRuntime.make(UserServiceLive)
817
- * const effectOs = makeEffectORPC(runtime, authedOs)
818
- *
819
- * const getUser = effectOs
820
- * .input(z.object({ id: z.string() }))
821
- * .effect(
822
- * Effect.fn(function* ({ input }) {
823
- * const userService = yield* UserService
824
- * return yield* userService.findById(input.id)
825
- * })
826
- * )
827
- * ```
828
554
  */
829
555
  export function makeEffectORPC<
830
556
  TBuilder extends AnyBuilderLike<
@@ -867,23 +593,27 @@ export function makeEffectORPC<TRequirementsProvided, TRuntimeError>(
867
593
  TRuntimeError
868
594
  > {
869
595
  const resolvedBuilder = builder ?? emptyBuilder();
870
- return new EffectBuilder({
871
- ...resolvedBuilder["~orpc"],
872
- errorMap: effectErrorMapToErrorMap(resolvedBuilder["~orpc"].errorMap),
873
- effectErrorMap: resolvedBuilder["~orpc"].errorMap,
874
- runtime,
875
- });
596
+ const effectErrorMap = getEffectErrorMap(resolvedBuilder);
597
+ return new EffectBuilder(
598
+ {
599
+ ...resolvedBuilder["~orpc"],
600
+ effectErrorMap: effectErrorMap,
601
+ errorMap: effectErrorMapToErrorMap(effectErrorMap),
602
+ runtime,
603
+ },
604
+ unwrapEffectUpstream(resolvedBuilder),
605
+ );
876
606
  }
877
607
 
878
608
  function emptyBuilder(): AnyBuilderLike {
879
609
  return new Builder({
880
610
  config: {},
881
- route: {},
882
- meta: {},
611
+ dedupeLeadingMiddlewares: true,
883
612
  errorMap: {},
884
613
  inputValidationIndex: fallbackConfig("initialInputValidationIndex"),
885
- outputValidationIndex: fallbackConfig("initialOutputValidationIndex"),
614
+ meta: {},
886
615
  middlewares: [],
887
- dedupeLeadingMiddlewares: true,
616
+ outputValidationIndex: fallbackConfig("initialOutputValidationIndex"),
617
+ route: {},
888
618
  });
889
619
  }