@shivaedev/effect-trpc 0.3.4 → 0.4.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/README.md +93 -0
  3. package/dist/adapter.d.ts +6 -5
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +2 -1
  6. package/dist/adapter.js.map +1 -1
  7. package/dist/client/rejection.d.ts +11 -0
  8. package/dist/client/rejection.d.ts.map +1 -0
  9. package/dist/client/rejection.js +9 -0
  10. package/dist/client/rejection.js.map +1 -0
  11. package/dist/client.d.ts +2 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +2 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js.map +1 -1
  17. package/dist/index.d.ts +9 -6
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/internal/context-bridge.d.ts.map +1 -1
  22. package/dist/internal/context-bridge.js +1 -2
  23. package/dist/internal/context-bridge.js.map +1 -1
  24. package/dist/internal/loose.d.ts +4 -0
  25. package/dist/internal/loose.d.ts.map +1 -0
  26. package/dist/internal/loose.js +7 -0
  27. package/dist/internal/loose.js.map +1 -0
  28. package/dist/internal/procedure-handler.d.ts +5 -12
  29. package/dist/internal/procedure-handler.d.ts.map +1 -1
  30. package/dist/internal/procedure-handler.js +3 -4
  31. package/dist/internal/procedure-handler.js.map +1 -1
  32. package/dist/internal/procedure-types.d.ts +17 -4
  33. package/dist/internal/procedure-types.d.ts.map +1 -1
  34. package/dist/internal/runtime.d.ts +4 -4
  35. package/dist/internal/runtime.d.ts.map +1 -1
  36. package/dist/internal/runtime.js +5 -13
  37. package/dist/internal/runtime.js.map +1 -1
  38. package/dist/internal/subscription-handler.d.ts +5 -12
  39. package/dist/internal/subscription-handler.d.ts.map +1 -1
  40. package/dist/internal/subscription-handler.js +5 -6
  41. package/dist/internal/subscription-handler.js.map +1 -1
  42. package/dist/procedure.d.ts +6 -8
  43. package/dist/procedure.d.ts.map +1 -1
  44. package/dist/procedure.js +8 -17
  45. package/dist/procedure.js.map +1 -1
  46. package/dist/rejection-formatter.d.ts +18 -0
  47. package/dist/rejection-formatter.d.ts.map +1 -0
  48. package/dist/rejection-formatter.js +24 -0
  49. package/dist/rejection-formatter.js.map +1 -0
  50. package/dist/rejection.d.ts +26 -0
  51. package/dist/rejection.d.ts.map +1 -0
  52. package/dist/rejection.js +32 -0
  53. package/dist/rejection.js.map +1 -0
  54. package/dist/request-services.d.ts.map +1 -1
  55. package/dist/request-services.js.map +1 -1
  56. package/dist/request-signal.d.ts.map +1 -1
  57. package/dist/request-signal.js.map +1 -1
  58. package/dist/testing/any-test-layer.d.ts +3 -0
  59. package/dist/testing/any-test-layer.d.ts.map +1 -0
  60. package/dist/testing/any-test-layer.js +2 -0
  61. package/dist/testing/any-test-layer.js.map +1 -0
  62. package/dist/testing/caller.d.ts +5 -3
  63. package/dist/testing/caller.d.ts.map +1 -1
  64. package/dist/testing/caller.js +12 -11
  65. package/dist/testing/caller.js.map +1 -1
  66. package/dist/testing/types.d.ts +5 -4
  67. package/dist/testing/types.d.ts.map +1 -1
  68. package/dist/testing/vitest.d.ts +4 -3
  69. package/dist/testing/vitest.d.ts.map +1 -1
  70. package/dist/testing/vitest.js +13 -14
  71. package/dist/testing/vitest.js.map +1 -1
  72. package/dist/testing.d.ts +3 -3
  73. package/dist/types.d.ts.map +1 -1
  74. package/package.json +12 -3
  75. package/src/adapter.ts +24 -76
  76. package/src/client/rejection.ts +19 -0
  77. package/src/client.ts +1 -0
  78. package/src/errors.ts +8 -19
  79. package/src/index.ts +9 -6
  80. package/src/internal/context-bridge.ts +3 -10
  81. package/src/internal/loose.ts +14 -0
  82. package/src/internal/procedure-handler.ts +11 -34
  83. package/src/internal/procedure-types.ts +37 -50
  84. package/src/internal/runtime.ts +14 -49
  85. package/src/internal/subscription-handler.ts +15 -47
  86. package/src/procedure.ts +33 -142
  87. package/src/rejection-formatter.ts +33 -0
  88. package/src/rejection.ts +60 -0
  89. package/src/request-services.ts +5 -25
  90. package/src/request-signal.ts +1 -4
  91. package/src/testing/any-test-layer.ts +3 -0
  92. package/src/testing/caller.ts +29 -32
  93. package/src/testing/types.ts +15 -47
  94. package/src/testing/vitest.ts +19 -51
  95. package/src/testing.ts +3 -3
  96. package/src/types.ts +3 -12
package/src/adapter.ts CHANGED
@@ -1,17 +1,12 @@
1
- import type {
2
- inferProcedureBuilderResolverOptions,
3
- TRPCProcedureBuilder,
4
- } from "@trpc/server";
1
+ import type { TRPCProcedureBuilder } from "@trpc/server";
5
2
  import type { Context, Effect, Exit } from "effect";
6
- import { makeContextBridge } from "./internal/context-bridge.js";
7
- import { makeRuntimeBridge } from "./internal/runtime.js";
8
- import { EffectProcedureBuilder } from "./procedure.js";
9
- import type { EffectProcedureRequestServices } from "./request-services.js";
10
- import type {
11
- EffectTRPCErrorMapper,
12
- EffectTRPCInstrument,
13
- EffectTRPCStreamInstrument,
14
- } from "./types.js";
3
+ import { makeContextBridge } from "./internal/context-bridge.ts";
4
+ import { looseBuilder } from "./internal/loose.ts";
5
+ import type { ResolverContext } from "./internal/procedure-types.ts";
6
+ import { makeRuntimeBridge } from "./internal/runtime.ts";
7
+ import { EffectProcedureBuilder } from "./procedure.ts";
8
+ import type { EffectProcedureRequestServices } from "./request-services.ts";
9
+ import type { EffectTRPCErrorMapper, EffectTRPCInstrument, EffectTRPCStreamInstrument } from "./types.ts";
15
10
 
16
11
  export interface MakeEffectTRPCOptions<Requirements, RuntimeError> {
17
12
  readonly instrument?: EffectTRPCInstrument;
@@ -21,10 +16,7 @@ export interface MakeEffectTRPCOptions<Requirements, RuntimeError> {
21
16
  }
22
17
 
23
18
  export interface EffectTRPCRuntime<Requirements, RuntimeError> {
24
- readonly contextEffect: Effect.Effect<
25
- Context.Context<Requirements>,
26
- RuntimeError
27
- >;
19
+ readonly contextEffect: Effect.Effect<Context.Context<Requirements>, RuntimeError>;
28
20
  readonly currentServices?: () => Context.Context<never> | undefined;
29
21
  readonly runPromise: <Value, Error>(
30
22
  effect: Effect.Effect<Value, Error, Requirements>,
@@ -34,66 +26,15 @@ export interface EffectTRPCRuntime<Requirements, RuntimeError> {
34
26
  effect: Effect.Effect<Value, Error, Requirements>,
35
27
  options?: { readonly signal?: AbortSignal },
36
28
  ) => Promise<Exit.Exit<Value, Error | RuntimeError>>;
37
- readonly runWithServices?: <Services, Value>(
38
- services: Context.Context<Services>,
39
- evaluate: () => Value,
40
- ) => Value;
29
+ readonly runWithServices?: <Services, Value>(services: Context.Context<Services>, evaluate: () => Value) => Value;
41
30
  }
42
31
 
43
32
  export interface EffectTRPCAdapter<Requirements> {
44
- readonly procedure: <
45
- Context,
46
- Meta,
47
- ContextOverrides,
48
- InputIn,
49
- InputOut,
50
- OutputIn,
51
- OutputOut,
52
- ProvidedServices,
53
- LayerError,
54
- >(
55
- builder: TRPCProcedureBuilder<
56
- Context,
57
- Meta,
58
- ContextOverrides,
59
- InputIn,
60
- InputOut,
61
- OutputIn,
62
- OutputOut,
63
- false
64
- >,
65
- requestServices: EffectProcedureRequestServices<
66
- inferProcedureBuilderResolverOptions<
67
- TRPCProcedureBuilder<
68
- Context,
69
- Meta,
70
- ContextOverrides,
71
- InputIn,
72
- InputOut,
73
- OutputIn,
74
- OutputOut,
75
- false
76
- >
77
- >["ctx"],
78
- ProvidedServices,
79
- LayerError
80
- >,
81
- ) => EffectProcedureBuilder<
82
- Context,
83
- Meta,
84
- ContextOverrides,
85
- InputIn,
86
- InputOut,
87
- OutputIn,
88
- OutputOut,
89
- ProvidedServices,
90
- LayerError,
91
- Requirements
92
- >;
93
- readonly runWithServices: <Services, Value>(
94
- services: Context.Context<Services>,
95
- evaluate: () => Value,
96
- ) => Value;
33
+ readonly procedure: <Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, ProvidedServices, LayerError>(
34
+ builder: TRPCProcedureBuilder<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, false>,
35
+ requestServices: EffectProcedureRequestServices<ResolverContext<Context, Meta, ContextOverrides>, ProvidedServices, LayerError>,
36
+ ) => EffectProcedureBuilder<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, ProvidedServices, LayerError, Requirements>;
37
+ readonly runWithServices: <Services, Value>(services: Context.Context<Services>, evaluate: () => Value) => Value;
97
38
  }
98
39
 
99
40
  export const makeEffectTRPC = <Requirements, RuntimeError = never>(
@@ -103,8 +44,15 @@ export const makeEffectTRPC = <Requirements, RuntimeError = never>(
103
44
  const runtime = makeRuntimeBridge(options.runtime, contextBridge, options);
104
45
 
105
46
  return {
106
- procedure: (builder, requestServices) =>
107
- new EffectProcedureBuilder(builder, requestServices, runtime) as never,
47
+ procedure: <Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, ProvidedServices, LayerError>(
48
+ builder: TRPCProcedureBuilder<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, false>,
49
+ requestServices: EffectProcedureRequestServices<ResolverContext<Context, Meta, ContextOverrides>, ProvidedServices, LayerError>,
50
+ ) =>
51
+ new EffectProcedureBuilder<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut, ProvidedServices, LayerError, Requirements>(
52
+ looseBuilder(builder),
53
+ requestServices,
54
+ runtime,
55
+ ),
108
56
  runWithServices: contextBridge.run,
109
57
  };
110
58
  };
@@ -0,0 +1,19 @@
1
+ import { Option, Schema } from "effect";
2
+
3
+ export const EncodedRejection = Schema.StructWithRest(
4
+ Schema.Struct({ _tag: Schema.String, invalidInput: Schema.optionalKey(Schema.Literal(true)) }),
5
+ [Schema.Record(Schema.String, Schema.Unknown)],
6
+ );
7
+
8
+ export type EncodedRejection = typeof EncodedRejection.Type;
9
+
10
+ const decodeCarrier = Schema.decodeUnknownOption(Schema.Struct({ data: Schema.Struct({ rejection: EncodedRejection }) }));
11
+
12
+ export const rejectionOf = (error: unknown): Option.Option<EncodedRejection> => Option.map(decodeCarrier(error), ({ data }) => data.rejection);
13
+
14
+ export const decodeRejection = <S extends Schema.ConstraintDecoder<{ readonly _tag: string }>>(
15
+ schema: S,
16
+ ): ((error: unknown) => Option.Option<S["Type"]>) => {
17
+ const decode = Schema.decodeUnknownOption(schema);
18
+ return (error) => Option.flatMap(rejectionOf(error), (rejection) => decode(rejection));
19
+ };
package/src/client.ts ADDED
@@ -0,0 +1 @@
1
+ export { decodeRejection, type EncodedRejection, rejectionOf } from "./client/rejection.ts";
package/src/errors.ts CHANGED
@@ -1,30 +1,19 @@
1
1
  import { TRPCError } from "@trpc/server";
2
2
  import { Effect } from "effect";
3
3
 
4
- export const fail = (
5
- code: TRPCError["code"],
6
- message: string,
7
- cause?: unknown,
8
- ): Effect.Effect<never, TRPCError> =>
4
+ export const fail = (code: TRPCError["code"], message: string, cause?: unknown): Effect.Effect<never, TRPCError> =>
9
5
  Effect.fail(new TRPCError({ cause, code, message }));
10
6
 
11
- export const badRequest = (message: string, cause?: unknown) =>
12
- fail("BAD_REQUEST", message, cause);
7
+ export const badRequest = (message: string, cause?: unknown) => fail("BAD_REQUEST", message, cause);
13
8
 
14
- export const unauthorized = (message: string, cause?: unknown) =>
15
- fail("UNAUTHORIZED", message, cause);
9
+ export const unauthorized = (message: string, cause?: unknown) => fail("UNAUTHORIZED", message, cause);
16
10
 
17
- export const forbidden = (message: string, cause?: unknown) =>
18
- fail("FORBIDDEN", message, cause);
11
+ export const forbidden = (message: string, cause?: unknown) => fail("FORBIDDEN", message, cause);
19
12
 
20
- export const notFound = (message: string, cause?: unknown) =>
21
- fail("NOT_FOUND", message, cause);
13
+ export const notFound = (message: string, cause?: unknown) => fail("NOT_FOUND", message, cause);
22
14
 
23
- export const conflict = (message: string, cause?: unknown) =>
24
- fail("CONFLICT", message, cause);
15
+ export const conflict = (message: string, cause?: unknown) => fail("CONFLICT", message, cause);
25
16
 
26
- export const preconditionFailed = (message: string, cause?: unknown) =>
27
- fail("PRECONDITION_FAILED", message, cause);
17
+ export const preconditionFailed = (message: string, cause?: unknown) => fail("PRECONDITION_FAILED", message, cause);
28
18
 
29
- export const internalServerError = (message: string, cause?: unknown) =>
30
- fail("INTERNAL_SERVER_ERROR", message, cause);
19
+ export const internalServerError = (message: string, cause?: unknown) => fail("INTERNAL_SERVER_ERROR", message, cause);
package/src/index.ts CHANGED
@@ -3,7 +3,8 @@ export {
3
3
  type EffectTRPCRuntime,
4
4
  type MakeEffectTRPCOptions,
5
5
  makeEffectTRPC,
6
- } from "./adapter.js";
6
+ } from "./adapter.ts";
7
+ export type { EncodedRejection } from "./client/rejection.ts";
7
8
  export {
8
9
  badRequest,
9
10
  conflict,
@@ -13,14 +14,16 @@ export {
13
14
  notFound,
14
15
  preconditionFailed,
15
16
  unauthorized,
16
- } from "./errors.js";
17
- export type { EffectProcedureBuilder } from "./procedure.js";
17
+ } from "./errors.ts";
18
+ export type { EffectProcedureBuilder } from "./procedure.ts";
19
+ export { RejectionError, type RejectWithOptions, rejectionCode, rejectWith } from "./rejection.ts";
20
+ export { type RejectionData, type RejectionErrorShape, rejectionFormatter, withRejection } from "./rejection-formatter.ts";
18
21
  export {
19
22
  type EffectProcedureRequestServices,
20
23
  extendRequestServices,
21
24
  makeRequestServices,
22
- } from "./request-services.js";
23
- export { RequestSignal } from "./request-signal.js";
25
+ } from "./request-services.ts";
26
+ export { RequestSignal } from "./request-signal.ts";
24
27
  export type {
25
28
  EffectTRPCErrorContext,
26
29
  EffectTRPCErrorMapper,
@@ -28,4 +31,4 @@ export type {
28
31
  EffectTRPCStreamInstrument,
29
32
  ProcedureInfo,
30
33
  ProcedureKind,
31
- } from "./types.js";
34
+ } from "./types.ts";
@@ -3,20 +3,14 @@ import type { Context } from "effect";
3
3
 
4
4
  export interface ContextBridge {
5
5
  readonly current: () => Context.Context<never> | undefined;
6
- readonly run: <Services, Value>(
7
- services: Context.Context<Services>,
8
- evaluate: () => Value,
9
- ) => Value;
6
+ readonly run: <Services, Value>(services: Context.Context<Services>, evaluate: () => Value) => Value;
10
7
  }
11
8
 
12
9
  export const makeContextBridge = (shared?: {
13
10
  readonly currentServices?: ContextBridge["current"];
14
11
  readonly runWithServices?: ContextBridge["run"];
15
12
  }): ContextBridge => {
16
- if (
17
- shared?.currentServices !== undefined &&
18
- shared.runWithServices !== undefined
19
- ) {
13
+ if (shared?.currentServices !== undefined && shared.runWithServices !== undefined) {
20
14
  return {
21
15
  current: shared.currentServices,
22
16
  run: shared.runWithServices,
@@ -27,7 +21,6 @@ export const makeContextBridge = (shared?: {
27
21
 
28
22
  return {
29
23
  current: () => storage.getStore(),
30
- run: (services, evaluate) =>
31
- storage.run(services as Context.Context<never>, evaluate),
24
+ run: (services, evaluate) => storage.run(services, evaluate),
32
25
  };
33
26
  };
@@ -0,0 +1,14 @@
1
+ import type { ProcedureBuilderOf, ProcedureBuilderSurface, ResolverContext } from "./procedure-types.ts";
2
+
3
+ export function looseBuilder<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut>(
4
+ builder: ProcedureBuilderOf<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut>,
5
+ ): ProcedureBuilderSurface<ResolverContext<Context, Meta, ContextOverrides>>;
6
+ export function looseBuilder(builder: unknown): unknown {
7
+ return builder;
8
+ }
9
+
10
+ // tRPC runs the procedure's input parsers before the resolver, so the resolver receives the input it declared.
11
+ export function acceptParsedInput<Result>(resolver: (input: never) => Result): (input: unknown) => Result;
12
+ export function acceptParsedInput(resolver: unknown): unknown {
13
+ return resolver;
14
+ }
@@ -1,45 +1,22 @@
1
1
  import { Effect } from "effect";
2
- import type { EffectProcedureRequestServices } from "../request-services.js";
3
- import type { ProcedureInfo } from "../types.js";
4
- import type { EffectProcedureResolver } from "./procedure-types.js";
5
- import type { RuntimeBridge } from "./runtime.js";
2
+ import type { EffectProcedureRequestServices } from "../request-services.ts";
3
+ import type { ProcedureInfo } from "../types.ts";
4
+ import { acceptParsedInput } from "./loose.ts";
5
+ import type { EffectProcedureResolver, ProcedureInvocation } from "./procedure-types.ts";
6
+ import type { RuntimeBridge } from "./runtime.ts";
6
7
 
7
- interface ProcedureInvocation<Context, Input> {
8
- readonly ctx: Context;
9
- readonly input: Input;
10
- readonly path: string;
11
- readonly signal?: AbortSignal;
12
- }
13
-
14
- export const makeProcedureHandler = <
15
- RuntimeRequirements,
16
- Context,
17
- Input,
18
- ProvidedServices,
19
- LayerError,
20
- Output,
21
- >(
8
+ export const makeProcedureHandler = <RuntimeRequirements, Context, ProvidedServices, LayerError>(
22
9
  runtime: RuntimeBridge<RuntimeRequirements>,
23
- resolver: EffectProcedureResolver<
24
- Input,
25
- ProvidedServices | NoInfer<RuntimeRequirements>,
26
- Output
27
- >,
28
- requestServices: EffectProcedureRequestServices<
29
- Context,
30
- ProvidedServices,
31
- LayerError
32
- >,
10
+ resolver: EffectProcedureResolver<never, ProvidedServices | NoInfer<RuntimeRequirements>, unknown>,
11
+ requestServices: EffectProcedureRequestServices<Context, ProvidedServices, LayerError>,
33
12
  procedure: Omit<ProcedureInfo, "path">,
34
13
  ) => {
35
- const tracedResolver = Effect.fnUntraced(resolver);
14
+ const tracedResolver = Effect.fnUntraced(acceptParsedInput(resolver));
36
15
 
37
- return async (invocation: ProcedureInvocation<Context, Input>) => {
16
+ return async (invocation: ProcedureInvocation<Context>) => {
38
17
  const info = { ...procedure, path: invocation.path };
39
18
  const provided = Effect.suspend(() =>
40
- runtime
41
- .instrument(tracedResolver(invocation.input), info)
42
- .pipe(Effect.provide(requestServices.layer(invocation.ctx))),
19
+ runtime.instrument(tracedResolver(invocation.input), info).pipe(Effect.provide(requestServices.layer(invocation.ctx))),
43
20
  );
44
21
  return await runtime.runEffect(provided, {
45
22
  procedure: info,
@@ -1,57 +1,44 @@
1
- import type {
2
- inferProcedureBuilderResolverOptions,
3
- TRPCProcedureBuilder,
4
- TRPCUnsetMarker,
5
- } from "@trpc/server";
6
- import type { Effect, Stream } from "effect";
7
-
8
- export type AnyProcedureBuilder = TRPCProcedureBuilder<
9
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
10
- any,
11
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
12
- any,
13
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
14
- any,
15
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
16
- any,
17
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
18
- any,
19
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
20
- any,
21
- // biome-ignore lint/suspicious/noExplicitAny: tRPC phantom slot extraction
22
- any,
1
+ import type { inferProcedureBuilderResolverOptions, TRPCProcedureBuilder, TRPCUnsetMarker } from "@trpc/server";
2
+ import type { Effect, StandardSchema, Stream } from "effect";
3
+
4
+ export type ProcedureBuilderOf<Context, Meta, ContextOverrides, InputIn, InputOut, OutputIn, OutputOut> = TRPCProcedureBuilder<
5
+ Context,
6
+ Meta,
7
+ ContextOverrides,
8
+ InputIn,
9
+ InputOut,
10
+ OutputIn,
11
+ OutputOut,
23
12
  false
24
13
  >;
25
14
 
26
- export type DefaultValue<Value, Fallback> = Value extends TRPCUnsetMarker
27
- ? Fallback
28
- : Value;
29
-
30
- export type IntersectIfDefined<Value, With> = Value extends TRPCUnsetMarker
31
- ? With
32
- : With extends TRPCUnsetMarker
33
- ? Value
34
- : Value & With;
35
-
36
- export type ResolverContext<Builder extends AnyProcedureBuilder> =
37
- inferProcedureBuilderResolverOptions<Builder>["ctx"];
38
-
39
- export type ProcedureEffect<Requirements> = Effect.Effect<
40
- // biome-ignore lint/suspicious/noExplicitAny: success is inferred from each yielded Effect
41
- any,
42
- // biome-ignore lint/suspicious/noExplicitAny: errors are inferred from each yielded Effect
43
- any,
44
- Requirements
45
- >;
15
+ export type DefaultValue<Value, Fallback> = Value extends TRPCUnsetMarker ? Fallback : Value;
46
16
 
47
- export type EffectProcedureResolver<Input, Requirements, Output> = (
48
- input: Input,
49
- ) => Generator<ProcedureEffect<Requirements>, Output, never>;
17
+ export type IntersectIfDefined<Value, With> = Value extends TRPCUnsetMarker ? With : With extends TRPCUnsetMarker ? Value : Value & With;
18
+
19
+ export type ResolverContext<Context, Meta, ContextOverrides> = inferProcedureBuilderResolverOptions<
20
+ ProcedureBuilderOf<Context, Meta, ContextOverrides, TRPCUnsetMarker, TRPCUnsetMarker, TRPCUnsetMarker, TRPCUnsetMarker>
21
+ >["ctx"];
22
+
23
+ export type ProcedureEffect<Requirements> = Effect.Effect<unknown, unknown, Requirements>;
24
+
25
+ export type EffectProcedureResolver<Input, Requirements, Output> = (input: Input) => Generator<ProcedureEffect<Requirements>, Output, never>;
50
26
 
51
27
  export type EffectSubscriptionResolver<Input, Requirements, Output> = (
52
28
  input: Input,
53
- ) => Generator<
54
- ProcedureEffect<Requirements>,
55
- Stream.Stream<Output, unknown, Requirements>,
56
- never
57
- >;
29
+ ) => Generator<ProcedureEffect<Requirements>, Stream.Stream<Output, unknown, Requirements>, never>;
30
+
31
+ export interface ProcedureInvocation<Context> {
32
+ readonly ctx: Context;
33
+ readonly input: unknown;
34
+ readonly path: string;
35
+ readonly signal: AbortSignal | undefined;
36
+ }
37
+
38
+ export interface ProcedureBuilderSurface<Context> {
39
+ input(schema: StandardSchema.StandardSchemaV1): ProcedureBuilderSurface<Context>;
40
+ output(schema: StandardSchema.StandardSchemaV1): ProcedureBuilderSurface<Context>;
41
+ query(resolver: (invocation: ProcedureInvocation<Context>) => Promise<unknown>): unknown;
42
+ mutation(resolver: (invocation: ProcedureInvocation<Context>) => Promise<unknown>): unknown;
43
+ subscription(resolver: (invocation: ProcedureInvocation<Context>) => Promise<AsyncIterable<unknown, void, unknown>>): unknown;
44
+ }
@@ -1,12 +1,8 @@
1
1
  import { TRPCError } from "@trpc/server";
2
2
  import { Cause, Context, Effect, Exit, Option, Result, Stream } from "effect";
3
- import type { EffectTRPCRuntime } from "../adapter.js";
4
- import type {
5
- EffectTRPCErrorMapper,
6
- EffectTRPCInstrument,
7
- ProcedureInfo,
8
- } from "../types.js";
9
- import type { ContextBridge } from "./context-bridge.js";
3
+ import type { EffectTRPCRuntime } from "../adapter.ts";
4
+ import type { EffectTRPCErrorMapper, EffectTRPCInstrument, ProcedureInfo } from "../types.ts";
5
+ import type { ContextBridge } from "./context-bridge.ts";
10
6
 
11
7
  interface RunEffectOptions {
12
8
  readonly procedure: ProcedureInfo;
@@ -15,14 +11,8 @@ interface RunEffectOptions {
15
11
 
16
12
  export interface RuntimeBridge<Requirements> {
17
13
  readonly instrument: EffectTRPCInstrument;
18
- readonly runEffect: <Value>(
19
- effect: Effect.Effect<Value, unknown, Requirements>,
20
- options: RunEffectOptions,
21
- ) => Promise<Value>;
22
- readonly runStream: <Value>(
23
- stream: Stream.Stream<Value, unknown, Requirements>,
24
- options: RunEffectOptions,
25
- ) => Promise<AsyncIterable<Value>>;
14
+ readonly runEffect: <Value>(effect: Effect.Effect<Value, unknown, Requirements>, options: RunEffectOptions) => Promise<Value>;
15
+ readonly runStream: <Value>(stream: Stream.Stream<Value, unknown, Requirements>, options: RunEffectOptions) => Promise<AsyncIterable<Value>>;
26
16
  }
27
17
 
28
18
  const internalError = (cause?: unknown): TRPCError =>
@@ -43,19 +33,13 @@ const mapError = (
43
33
  }
44
34
 
45
35
  try {
46
- return (
47
- consumerMapper?.(error, { ...procedure, origin }) ?? internalError(error)
48
- );
36
+ return consumerMapper?.(error, { ...procedure, origin }) ?? internalError(error);
49
37
  } catch (mapperDefect) {
50
38
  return internalError(mapperDefect);
51
39
  }
52
40
  };
53
41
 
54
- const mapCause = (
55
- cause: Cause.Cause<unknown>,
56
- procedure: ProcedureInfo,
57
- consumerMapper: EffectTRPCErrorMapper | undefined,
58
- ): TRPCError => {
42
+ const mapCause = (cause: Cause.Cause<unknown>, procedure: ProcedureInfo, consumerMapper: EffectTRPCErrorMapper | undefined): TRPCError => {
59
43
  if (Cause.hasInterruptsOnly(cause)) {
60
44
  return new TRPCError({
61
45
  code: "CLIENT_CLOSED_REQUEST",
@@ -94,21 +78,14 @@ export const makeRuntimeBridge = <Requirements, RuntimeError>(
94
78
  contextBridge: ContextBridge,
95
79
  options: {
96
80
  readonly instrument?: EffectTRPCInstrument;
97
- readonly instrumentStream?: import("../types.js").EffectTRPCStreamInstrument;
81
+ readonly instrumentStream?: import("../types.ts").EffectTRPCStreamInstrument;
98
82
  readonly mapError?: EffectTRPCErrorMapper;
99
83
  },
100
84
  ): RuntimeBridge<Requirements> => ({
101
- instrument: (effect, procedure) =>
102
- Effect.suspend(() =>
103
- options.instrument === undefined
104
- ? effect
105
- : options.instrument(effect, procedure),
106
- ),
85
+ instrument: (effect, procedure) => Effect.suspend(() => (options.instrument === undefined ? effect : options.instrument(effect, procedure))),
107
86
  runStream: async (stream, runOptions) => {
108
87
  const instrumented = Stream.suspend(() =>
109
- options.instrumentStream === undefined
110
- ? stream
111
- : options.instrumentStream(stream, runOptions.procedure),
88
+ options.instrumentStream === undefined ? stream : options.instrumentStream(stream, runOptions.procedure),
112
89
  ).pipe(
113
90
  Stream.withSpan(runOptions.procedure.path, {
114
91
  attributes: {
@@ -119,14 +96,11 @@ export const makeRuntimeBridge = <Requirements, RuntimeError>(
119
96
  captureStackTrace: runOptions.procedure.captureStackTrace,
120
97
  }),
121
98
  Stream.interruptWhen(interruptOn(runOptions.signal)),
122
- Stream.catchCause((cause) =>
123
- Stream.fail(mapCause(cause, runOptions.procedure, options.mapError)),
124
- ),
99
+ Stream.catchCause((cause) => Stream.fail(mapCause(cause, runOptions.procedure, options.mapError))),
125
100
  );
126
101
  const context = await runtime.runPromise(Effect.context<Requirements>());
127
102
  const ambient = contextBridge.current();
128
- const provided =
129
- ambient === undefined ? context : Context.merge(context, ambient);
103
+ const provided = ambient === undefined ? context : Context.merge(context, ambient);
130
104
  return Stream.toAsyncIterableWith(instrumented, provided);
131
105
  },
132
106
  runEffect: async (effect, runOptions) => {
@@ -147,17 +121,8 @@ export const makeRuntimeBridge = <Requirements, RuntimeError>(
147
121
  );
148
122
  const ambient = contextBridge.current();
149
123
  const runnable =
150
- ambient === undefined
151
- ? traced
152
- : Effect.flatMap(runtime.contextEffect, (base) =>
153
- Effect.provideContext(traced, Context.merge(base, ambient)),
154
- );
155
- const exit = await runtime.runPromiseExit(
156
- runnable,
157
- runOptions.signal === undefined
158
- ? undefined
159
- : { signal: runOptions.signal },
160
- );
124
+ ambient === undefined ? traced : Effect.flatMap(runtime.contextEffect, (base) => Effect.provideContext(traced, Context.merge(base, ambient)));
125
+ const exit = await runtime.runPromiseExit(runnable, runOptions.signal === undefined ? undefined : { signal: runOptions.signal });
161
126
 
162
127
  if (Exit.isSuccess(exit)) {
163
128
  return exit.value;
@@ -1,59 +1,27 @@
1
1
  import { Effect, Layer, Schema, Stream } from "effect";
2
- import type { EffectProcedureRequestServices } from "../request-services.js";
3
- import { RequestSignal } from "../request-signal.js";
4
- import type { ProcedureInfo } from "../types.js";
5
- import type { EffectSubscriptionResolver } from "./procedure-types.js";
6
- import type { RuntimeBridge } from "./runtime.js";
2
+ import type { EffectProcedureRequestServices } from "../request-services.ts";
3
+ import { RequestSignal } from "../request-signal.ts";
4
+ import type { ProcedureInfo } from "../types.ts";
5
+ import { acceptParsedInput } from "./loose.ts";
6
+ import type { EffectSubscriptionResolver, ProcedureInvocation } from "./procedure-types.ts";
7
+ import type { RuntimeBridge } from "./runtime.ts";
7
8
 
8
- interface SubscriptionInvocation<Context, Input> {
9
- readonly ctx: Context;
10
- readonly input: Input;
11
- readonly path: string;
12
- readonly signal?: AbortSignal;
13
- }
14
-
15
- export const makeSubscriptionHandler = <
16
- RuntimeRequirements,
17
- Context,
18
- Input,
19
- ProvidedServices,
20
- LayerError,
21
- Output,
22
- >(
9
+ export const makeSubscriptionHandler = <RuntimeRequirements, Context, ProvidedServices, LayerError>(
23
10
  runtime: RuntimeBridge<RuntimeRequirements>,
24
- resolver: EffectSubscriptionResolver<
25
- Input,
26
- ProvidedServices | NoInfer<RuntimeRequirements>,
27
- Output
28
- >,
29
- requestServices: EffectProcedureRequestServices<
30
- Context,
31
- ProvidedServices,
32
- LayerError
33
- >,
11
+ resolver: EffectSubscriptionResolver<never, ProvidedServices | NoInfer<RuntimeRequirements>, unknown>,
12
+ requestServices: EffectProcedureRequestServices<Context, ProvidedServices, LayerError>,
34
13
  procedure: Omit<ProcedureInfo, "path">,
35
14
  outputSchema?: Schema.ConstraintDecoder<unknown>,
36
15
  ) => {
37
- const tracedResolver = Effect.fnUntraced(resolver);
16
+ const tracedResolver = Effect.fnUntraced(acceptParsedInput(resolver));
38
17
 
39
- return async (invocation: SubscriptionInvocation<Context, Input>) => {
18
+ return async (invocation: ProcedureInvocation<Context>) => {
40
19
  const info = { ...procedure, path: invocation.path };
41
- const requestLayer = Layer.merge(
42
- requestServices.layer(invocation.ctx),
43
- Layer.succeed(RequestSignal, invocation.signal),
44
- );
45
- let stream = Stream.unwrap(
46
- Effect.suspend(() =>
47
- runtime.instrument(tracedResolver(invocation.input), info),
48
- ),
49
- ).pipe(Stream.provide(requestLayer));
50
- if (outputSchema !== undefined) {
51
- stream = Stream.mapEffect(stream, (value) =>
52
- Schema.decodeUnknownEffect(outputSchema)(value),
53
- ) as typeof stream;
54
- }
20
+ const requestLayer = Layer.merge(requestServices.layer(invocation.ctx), Layer.succeed(RequestSignal, invocation.signal));
21
+ const stream = Stream.unwrap(Effect.suspend(() => runtime.instrument(tracedResolver(invocation.input), info))).pipe(Stream.provide(requestLayer));
22
+ const decoded = outputSchema === undefined ? stream : Stream.mapEffect(stream, (value) => Schema.decodeUnknownEffect(outputSchema)(value));
55
23
 
56
- return await runtime.runStream(stream, {
24
+ return await runtime.runStream(decoded, {
57
25
  procedure: info,
58
26
  ...(invocation.signal === undefined ? {} : { signal: invocation.signal }),
59
27
  });