effect-orpc 0.1.3 → 0.2.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.
@@ -0,0 +1,491 @@
1
+ import type {
2
+ AnyContractRouter,
3
+ AnySchema,
4
+ ContractProcedure,
5
+ ErrorMap,
6
+ InferContractRouterErrorMap,
7
+ InferContractRouterMeta,
8
+ InferSchemaInput,
9
+ InferSchemaOutput,
10
+ Meta,
11
+ } from "@orpc/contract";
12
+ import { isContractProcedure } from "@orpc/contract";
13
+ import type {
14
+ BuilderConfig,
15
+ BuilderDef,
16
+ Context,
17
+ DecoratedMiddleware,
18
+ ImplementedProcedure,
19
+ Lazy,
20
+ MapInputMiddleware,
21
+ MergedCurrentContext,
22
+ MergedInitialContext,
23
+ Middleware,
24
+ ORPCErrorConstructorMap,
25
+ ProcedureHandler,
26
+ Router,
27
+ } from "@orpc/server";
28
+ import { implement } from "@orpc/server";
29
+ import type { IntersectPick } from "@orpc/shared";
30
+ import type { ManagedRuntime } from "effect";
31
+
32
+ import { addSpanStackTrace } from "./effect-builder";
33
+ import { enhanceEffectRouter } from "./effect-enhance-router";
34
+ import { EffectDecoratedProcedure } from "./effect-procedure";
35
+ import { createEffectProcedureHandler } from "./effect-runtime";
36
+ import { effectContractSymbol, getEffectContractErrorMap } from "./eoc";
37
+ import type { EffectErrorMap } from "./tagged-error";
38
+ import { effectErrorMapToErrorMap } from "./tagged-error";
39
+ import type { EffectErrorMapToErrorMap, EffectProcedureHandler } from "./types";
40
+
41
+ type ContractLeafEffectHandler<
42
+ TCurrentContext extends Context,
43
+ TInputSchema extends AnySchema,
44
+ TOutputSchema extends AnySchema,
45
+ TErrorMap extends EffectErrorMap,
46
+ TRequirementsProvided,
47
+ TMeta extends Meta,
48
+ > = EffectProcedureHandler<
49
+ TCurrentContext,
50
+ InferSchemaOutput<TInputSchema>,
51
+ InferSchemaInput<TOutputSchema>,
52
+ TErrorMap,
53
+ TRequirementsProvided,
54
+ TMeta
55
+ >;
56
+
57
+ type InferContractLeafEffectErrorMap<
58
+ TContract,
59
+ TErrorMap extends ErrorMap,
60
+ > = TContract extends {
61
+ [effectContractSymbol]: {
62
+ errorMap: infer TEffectErrorMap extends EffectErrorMap;
63
+ };
64
+ }
65
+ ? TEffectErrorMap
66
+ : TErrorMap;
67
+
68
+ export interface EffectProcedureImplementer<
69
+ TInitialContext extends Context,
70
+ TCurrentContext extends Context,
71
+ TInputSchema extends AnySchema,
72
+ TOutputSchema extends AnySchema,
73
+ TErrorMap extends EffectErrorMap,
74
+ TMeta extends Meta,
75
+ TRequirementsProvided,
76
+ TRuntimeError,
77
+ > {
78
+ "~orpc": BuilderDef<
79
+ TInputSchema,
80
+ TOutputSchema,
81
+ EffectErrorMapToErrorMap<TErrorMap>,
82
+ TMeta
83
+ >;
84
+ use<
85
+ UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
86
+ UInContext extends Context = TCurrentContext,
87
+ >(
88
+ middleware: Middleware<
89
+ UInContext | TCurrentContext,
90
+ UOutContext,
91
+ InferSchemaOutput<TInputSchema>,
92
+ InferSchemaInput<TOutputSchema>,
93
+ ORPCErrorConstructorMap<EffectErrorMapToErrorMap<TErrorMap>>,
94
+ TMeta
95
+ >,
96
+ ): EffectProcedureImplementer<
97
+ MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
98
+ MergedCurrentContext<TCurrentContext, UOutContext>,
99
+ TInputSchema,
100
+ TOutputSchema,
101
+ TErrorMap,
102
+ TMeta,
103
+ TRequirementsProvided,
104
+ TRuntimeError
105
+ >;
106
+ use<
107
+ UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
108
+ UInput,
109
+ UInContext extends Context = TCurrentContext,
110
+ >(
111
+ middleware: Middleware<
112
+ UInContext | TCurrentContext,
113
+ UOutContext,
114
+ UInput,
115
+ InferSchemaInput<TOutputSchema>,
116
+ ORPCErrorConstructorMap<EffectErrorMapToErrorMap<TErrorMap>>,
117
+ TMeta
118
+ >,
119
+ mapInput: MapInputMiddleware<InferSchemaOutput<TInputSchema>, UInput>,
120
+ ): EffectProcedureImplementer<
121
+ MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
122
+ MergedCurrentContext<TCurrentContext, UOutContext>,
123
+ TInputSchema,
124
+ TOutputSchema,
125
+ TErrorMap,
126
+ TMeta,
127
+ TRequirementsProvided,
128
+ TRuntimeError
129
+ >;
130
+ handler(
131
+ handler: ProcedureHandler<
132
+ TCurrentContext,
133
+ InferSchemaOutput<TInputSchema>,
134
+ InferSchemaInput<TOutputSchema>,
135
+ EffectErrorMapToErrorMap<TErrorMap>,
136
+ TMeta
137
+ >,
138
+ ): ImplementedProcedure<
139
+ TInitialContext,
140
+ TCurrentContext,
141
+ TInputSchema,
142
+ TOutputSchema,
143
+ EffectErrorMapToErrorMap<TErrorMap>,
144
+ TMeta
145
+ >;
146
+ effect(
147
+ effectFn: ContractLeafEffectHandler<
148
+ TCurrentContext,
149
+ TInputSchema,
150
+ TOutputSchema,
151
+ TErrorMap,
152
+ TRequirementsProvided,
153
+ TMeta
154
+ >,
155
+ ): EffectDecoratedProcedure<
156
+ TInitialContext,
157
+ TCurrentContext,
158
+ TInputSchema,
159
+ TOutputSchema,
160
+ TErrorMap,
161
+ TMeta,
162
+ TRequirementsProvided,
163
+ TRuntimeError
164
+ >;
165
+ }
166
+
167
+ export type EffectImplementerInternal<
168
+ TContract extends AnyContractRouter,
169
+ TInitialContext extends Context,
170
+ TCurrentContext extends Context,
171
+ TRequirementsProvided,
172
+ TRuntimeError,
173
+ > =
174
+ TContract extends ContractProcedure<
175
+ infer TInputSchema,
176
+ infer TOutputSchema,
177
+ infer TErrorMap extends ErrorMap,
178
+ infer TMeta extends Meta
179
+ >
180
+ ? EffectProcedureImplementer<
181
+ TInitialContext,
182
+ TCurrentContext,
183
+ TInputSchema,
184
+ TOutputSchema,
185
+ InferContractLeafEffectErrorMap<TContract, TErrorMap>,
186
+ TMeta,
187
+ TRequirementsProvided,
188
+ TRuntimeError
189
+ >
190
+ : {
191
+ middleware<
192
+ UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
193
+ TInput,
194
+ TOutput = any,
195
+ >(
196
+ middleware: Middleware<
197
+ TInitialContext,
198
+ UOutContext,
199
+ TInput,
200
+ TOutput,
201
+ ORPCErrorConstructorMap<InferContractRouterErrorMap<TContract>>,
202
+ InferContractRouterMeta<TContract>
203
+ >,
204
+ ): DecoratedMiddleware<
205
+ TInitialContext,
206
+ UOutContext,
207
+ TInput,
208
+ TOutput,
209
+ any,
210
+ InferContractRouterMeta<TContract>
211
+ >;
212
+ use<
213
+ UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
214
+ UInContext extends Context = TCurrentContext,
215
+ >(
216
+ middleware: Middleware<
217
+ UInContext | TCurrentContext,
218
+ UOutContext,
219
+ unknown,
220
+ unknown,
221
+ ORPCErrorConstructorMap<InferContractRouterErrorMap<TContract>>,
222
+ InferContractRouterMeta<TContract>
223
+ >,
224
+ ): EffectImplementerInternal<
225
+ TContract,
226
+ MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
227
+ MergedCurrentContext<TCurrentContext, UOutContext>,
228
+ TRequirementsProvided,
229
+ TRuntimeError
230
+ >;
231
+ router<U extends Router<TContract, TCurrentContext>>(
232
+ router: U,
233
+ ): ReturnType<
234
+ typeof enhanceEffectRouter<
235
+ U,
236
+ TInitialContext,
237
+ TCurrentContext,
238
+ Record<never, never>,
239
+ TRequirementsProvided,
240
+ TRuntimeError
241
+ >
242
+ >;
243
+ lazy<U extends Router<TContract, TCurrentContext>>(
244
+ loader: () => Promise<{ default: U }>,
245
+ ): ReturnType<
246
+ typeof enhanceEffectRouter<
247
+ Lazy<U>,
248
+ TInitialContext,
249
+ TCurrentContext,
250
+ Record<never, never>,
251
+ TRequirementsProvided,
252
+ TRuntimeError
253
+ >
254
+ >;
255
+ } & {
256
+ [K in keyof TContract]: TContract[K] extends AnyContractRouter
257
+ ? EffectImplementerInternal<
258
+ TContract[K],
259
+ TInitialContext,
260
+ TCurrentContext,
261
+ TRequirementsProvided,
262
+ TRuntimeError
263
+ >
264
+ : never;
265
+ };
266
+
267
+ export type EffectImplementer<
268
+ TContract extends AnyContractRouter,
269
+ TInitialContext extends Context,
270
+ TCurrentContext extends Context,
271
+ TRequirementsProvided,
272
+ TRuntimeError,
273
+ > = {
274
+ $context<U extends Context>(): EffectImplementer<
275
+ TContract,
276
+ U & Record<never, never>,
277
+ U,
278
+ TRequirementsProvided,
279
+ TRuntimeError
280
+ >;
281
+ $config(
282
+ config: BuilderConfig,
283
+ ): EffectImplementer<
284
+ TContract,
285
+ TInitialContext,
286
+ TCurrentContext,
287
+ TRequirementsProvided,
288
+ TRuntimeError
289
+ >;
290
+ } & EffectImplementerInternal<
291
+ TContract,
292
+ TInitialContext,
293
+ TCurrentContext,
294
+ TRequirementsProvided,
295
+ TRuntimeError
296
+ >;
297
+
298
+ const CONTRACT_HIDDEN_METHODS = new Set([
299
+ "$config",
300
+ "$context",
301
+ "$input",
302
+ "$meta",
303
+ "$route",
304
+ "errors",
305
+ "input",
306
+ "lazy",
307
+ "meta",
308
+ "middleware",
309
+ "output",
310
+ "prefix",
311
+ "route",
312
+ "router",
313
+ "tag",
314
+ ]);
315
+
316
+ function makeEnhanceOptions<TRequirementsProvided, TRuntimeError>(
317
+ runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>,
318
+ ) {
319
+ return {
320
+ middlewares: [],
321
+ errorMap: {},
322
+ dedupeLeadingMiddlewares: true,
323
+ runtime,
324
+ } as const;
325
+ }
326
+
327
+ function wrapContractNode<
328
+ TContract extends AnyContractRouter,
329
+ TRequirementsProvided,
330
+ TRuntimeError,
331
+ >(
332
+ contract: TContract,
333
+ target: any,
334
+ runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>,
335
+ ): EffectImplementerInternal<
336
+ TContract,
337
+ Context,
338
+ Context,
339
+ TRequirementsProvided,
340
+ TRuntimeError
341
+ > {
342
+ const cache = new Map<PropertyKey, unknown>();
343
+
344
+ return new Proxy(target, {
345
+ get(currentTarget, prop, receiver) {
346
+ if (cache.has(prop)) {
347
+ return cache.get(prop);
348
+ }
349
+
350
+ if (isContractProcedure(contract)) {
351
+ if (prop === "effect") {
352
+ const effect = (
353
+ effectFn: ContractLeafEffectHandler<any, any, any, any, any, any>,
354
+ ) => {
355
+ const effectErrorMap =
356
+ getEffectContractErrorMap(contract) ??
357
+ currentTarget["~orpc"].errorMap;
358
+
359
+ return new EffectDecoratedProcedure({
360
+ ...currentTarget["~orpc"],
361
+ errorMap: effectErrorMapToErrorMap(effectErrorMap),
362
+ effectErrorMap,
363
+ runtime,
364
+ handler: createEffectProcedureHandler({
365
+ runtime,
366
+ effectErrorMap,
367
+ effectFn,
368
+ defaultCaptureStackTrace: addSpanStackTrace(),
369
+ }),
370
+ });
371
+ };
372
+
373
+ cache.set(prop, effect);
374
+ return effect;
375
+ }
376
+
377
+ if (prop === "use") {
378
+ const use = (...args: unknown[]) =>
379
+ wrapContractNode(
380
+ contract,
381
+ Reflect.apply(
382
+ Reflect.get(currentTarget, prop, currentTarget),
383
+ currentTarget,
384
+ args,
385
+ ),
386
+ runtime,
387
+ );
388
+
389
+ cache.set(prop, use);
390
+ return use;
391
+ }
392
+
393
+ if (CONTRACT_HIDDEN_METHODS.has(String(prop))) {
394
+ return undefined;
395
+ }
396
+ } else {
397
+ if (prop === "$context" || prop === "$config" || prop === "use") {
398
+ const wrappedMethod = (...args: unknown[]) =>
399
+ wrapContractNode(
400
+ contract,
401
+ Reflect.apply(
402
+ Reflect.get(currentTarget, prop, currentTarget),
403
+ currentTarget,
404
+ args,
405
+ ),
406
+ runtime,
407
+ );
408
+
409
+ cache.set(prop, wrappedMethod);
410
+ return wrappedMethod;
411
+ }
412
+
413
+ if (prop === "router" || prop === "lazy") {
414
+ const wrappedMethod = (...args: unknown[]) =>
415
+ enhanceEffectRouter(
416
+ Reflect.apply(
417
+ Reflect.get(currentTarget, prop, currentTarget),
418
+ currentTarget,
419
+ args,
420
+ ) as any,
421
+ makeEnhanceOptions(runtime),
422
+ );
423
+
424
+ cache.set(prop, wrappedMethod);
425
+ return wrappedMethod;
426
+ }
427
+
428
+ if (typeof prop === "string" && prop in contract) {
429
+ const child = wrapContractNode(
430
+ (contract as Record<string, AnyContractRouter>)[prop]!,
431
+ Reflect.get(currentTarget, prop, receiver),
432
+ runtime,
433
+ );
434
+
435
+ cache.set(prop, child);
436
+ return child;
437
+ }
438
+ }
439
+
440
+ const value = Reflect.get(currentTarget, prop, receiver);
441
+ return typeof value === "function" ? value.bind(currentTarget) : value;
442
+ },
443
+ has(currentTarget, prop) {
444
+ if (isContractProcedure(contract)) {
445
+ if (prop === "effect") {
446
+ return true;
447
+ }
448
+ if (CONTRACT_HIDDEN_METHODS.has(String(prop))) {
449
+ return false;
450
+ }
451
+ } else if (typeof prop === "string" && prop in contract) {
452
+ return true;
453
+ }
454
+
455
+ return Reflect.has(currentTarget, prop);
456
+ },
457
+ }) as EffectImplementerInternal<
458
+ TContract,
459
+ Context,
460
+ Context,
461
+ TRequirementsProvided,
462
+ TRuntimeError
463
+ >;
464
+ }
465
+
466
+ export function implementEffect<
467
+ TContract extends AnyContractRouter,
468
+ TRequirementsProvided,
469
+ TRuntimeError,
470
+ >(
471
+ contract: TContract,
472
+ runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>,
473
+ ): EffectImplementer<
474
+ TContract,
475
+ Record<never, never>,
476
+ Record<never, never>,
477
+ TRequirementsProvided,
478
+ TRuntimeError
479
+ > {
480
+ return wrapContractNode(
481
+ contract,
482
+ implement(contract),
483
+ runtime,
484
+ ) as EffectImplementer<
485
+ TContract,
486
+ Record<never, never>,
487
+ Record<never, never>,
488
+ TRequirementsProvided,
489
+ TRuntimeError
490
+ >;
491
+ }
@@ -8,25 +8,19 @@ import type {
8
8
  Route,
9
9
  Schema,
10
10
  } from "@orpc/contract";
11
- import {
12
- mergeMeta,
13
- mergePrefix,
14
- mergeRoute,
15
- mergeTags,
16
- ORPCError,
17
- } from "@orpc/contract";
11
+ import { mergeMeta, mergePrefix, mergeRoute, mergeTags } from "@orpc/contract";
18
12
  import type {
19
13
  AnyMiddleware,
20
14
  BuilderConfig,
21
15
  BuilderDef,
22
16
  Context,
17
+ DecoratedMiddleware,
23
18
  Lazy,
24
19
  MapInputMiddleware,
25
20
  MergedCurrentContext,
26
21
  MergedInitialContext,
27
22
  Middleware,
28
23
  ProcedureHandler,
29
- ProcedureHandlerOptions,
30
24
  Router,
31
25
  } from "@orpc/server";
32
26
  import {
@@ -38,21 +32,16 @@ import {
38
32
  } from "@orpc/server";
39
33
  import type { IntersectPick } from "@orpc/shared";
40
34
  import type { ManagedRuntime } from "effect";
41
- import { Cause, Effect, Exit, FiberRefs } from "effect";
42
35
 
43
36
  import { enhanceEffectRouter } from "./effect-enhance-router";
44
37
  import { EffectDecoratedProcedure } from "./effect-procedure";
45
- import { getCurrentFiberRefs } from "./fiber-context-bridge";
38
+ import { createEffectProcedureHandler } from "./effect-runtime";
46
39
  import type {
47
40
  EffectErrorConstructorMap,
48
41
  EffectErrorMap,
49
42
  MergedEffectErrorMap,
50
43
  } from "./tagged-error";
51
- import {
52
- createEffectErrorConstructorMap,
53
- effectErrorMapToErrorMap,
54
- isORPCTaggedError,
55
- } from "./tagged-error";
44
+ import { effectErrorMapToErrorMap } from "./tagged-error";
56
45
  import type {
57
46
  AnyBuilderLike,
58
47
  EffectBuilderDef,
@@ -306,6 +295,35 @@ export class EffectBuilder<
306
295
  });
307
296
  }
308
297
 
298
+ /**
299
+ * Creates a middleware.
300
+ *
301
+ * @see {@link https://orpc.dev/docs/middleware Middleware Docs}
302
+ */
303
+ middleware<
304
+ UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
305
+ TInput,
306
+ TOutput = any,
307
+ >(
308
+ middleware: Middleware<
309
+ TInitialContext,
310
+ UOutContext,
311
+ TInput,
312
+ TOutput,
313
+ EffectErrorConstructorMap<TEffectErrorMap>,
314
+ TMeta
315
+ >,
316
+ ): DecoratedMiddleware<
317
+ TInitialContext,
318
+ UOutContext,
319
+ TInput,
320
+ TOutput,
321
+ any,
322
+ TMeta
323
+ > {
324
+ return decorateMiddleware(middleware);
325
+ }
326
+
309
327
  /**
310
328
  * Adds type-safe custom errors.
311
329
  * Supports both traditional oRPC error definitions and ORPCTaggedError classes.
@@ -603,79 +621,13 @@ export class EffectBuilder<
603
621
  return new EffectDecoratedProcedure({
604
622
  ...this["~effect"],
605
623
  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;
624
+ return createEffectProcedureHandler({
625
+ runtime,
626
+ effectErrorMap: this["~effect"].effectErrorMap,
627
+ effectFn,
628
+ spanConfig,
629
+ defaultCaptureStackTrace,
630
+ })(opts as any);
679
631
  },
680
632
  });
681
633
  }