@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/procedure.ts CHANGED
@@ -1,41 +1,18 @@
1
- import type {
2
- TRPCMutationProcedure,
3
- TRPCProcedureBuilder,
4
- TRPCQueryProcedure,
5
- TRPCSubscriptionProcedure,
6
- } from "@trpc/server";
1
+ import type { TRPCMutationProcedure, TRPCQueryProcedure, TRPCSubscriptionProcedure } from "@trpc/server";
7
2
  import { Schema } from "effect";
8
- import { makeProcedureHandler } from "./internal/procedure-handler.js";
3
+ import { makeProcedureHandler } from "./internal/procedure-handler.ts";
9
4
  import type {
10
5
  DefaultValue,
11
6
  EffectProcedureResolver,
12
7
  EffectSubscriptionResolver,
13
8
  IntersectIfDefined,
9
+ ProcedureBuilderSurface,
14
10
  ResolverContext,
15
- } from "./internal/procedure-types.js";
16
- import type { RuntimeBridge } from "./internal/runtime.js";
17
- import { captureStackTrace } from "./internal/stack-trace.js";
18
- import { makeSubscriptionHandler } from "./internal/subscription-handler.js";
19
- import type { EffectProcedureRequestServices } from "./request-services.js";
20
-
21
- type Builder<
22
- Context,
23
- Meta,
24
- ContextOverrides,
25
- InputIn,
26
- InputOut,
27
- OutputIn,
28
- OutputOut,
29
- > = TRPCProcedureBuilder<
30
- Context,
31
- Meta,
32
- ContextOverrides,
33
- InputIn,
34
- InputOut,
35
- OutputIn,
36
- OutputOut,
37
- false
38
- >;
11
+ } from "./internal/procedure-types.ts";
12
+ import type { RuntimeBridge } from "./internal/runtime.ts";
13
+ import { captureStackTrace } from "./internal/stack-trace.ts";
14
+ import { makeSubscriptionHandler } from "./internal/subscription-handler.ts";
15
+ import type { EffectProcedureRequestServices } from "./request-services.ts";
39
16
 
40
17
  export class EffectProcedureBuilder<
41
18
  Context,
@@ -50,30 +27,8 @@ export class EffectProcedureBuilder<
50
27
  RuntimeRequirements,
51
28
  > {
52
29
  constructor(
53
- private readonly builder: Builder<
54
- Context,
55
- Meta,
56
- ContextOverrides,
57
- InputIn,
58
- InputOut,
59
- OutputIn,
60
- OutputOut
61
- >,
62
- private readonly requestServices: EffectProcedureRequestServices<
63
- ResolverContext<
64
- Builder<
65
- Context,
66
- Meta,
67
- ContextOverrides,
68
- InputIn,
69
- InputOut,
70
- OutputIn,
71
- OutputOut
72
- >
73
- >,
74
- ProvidedServices,
75
- LayerError
76
- >,
30
+ private readonly builder: ProcedureBuilderSurface<ResolverContext<Context, Meta, ContextOverrides>>,
31
+ private readonly requestServices: EffectProcedureRequestServices<ResolverContext<Context, Meta, ContextOverrides>, ProvidedServices, LayerError>,
77
32
  private readonly runtime: RuntimeBridge<RuntimeRequirements>,
78
33
  private readonly subscriptionBuilder = builder,
79
34
  private readonly subscriptionOutput?: Schema.ConstraintDecoder<unknown>,
@@ -93,31 +48,12 @@ export class EffectProcedureBuilder<
93
48
  LayerError,
94
49
  RuntimeRequirements
95
50
  > {
96
- const next = this.builder.input(Schema.toStandardSchemaV1(schema) as never);
97
- const subscriptionNext = this.subscriptionBuilder.input(
98
- Schema.toStandardSchemaV1(schema) as never,
99
- );
51
+ const parser = Schema.toStandardSchemaV1(schema);
100
52
  return new EffectProcedureBuilder(
101
- next as unknown as Builder<
102
- Context,
103
- Meta,
104
- ContextOverrides,
105
- IntersectIfDefined<InputIn, SchemaValue["Encoded"]>,
106
- IntersectIfDefined<InputOut, SchemaValue["Type"]>,
107
- OutputIn,
108
- OutputOut
109
- >,
53
+ this.builder.input(parser),
110
54
  this.requestServices,
111
55
  this.runtime,
112
- subscriptionNext as unknown as Builder<
113
- Context,
114
- Meta,
115
- ContextOverrides,
116
- IntersectIfDefined<InputIn, SchemaValue["Encoded"]>,
117
- IntersectIfDefined<InputOut, SchemaValue["Type"]>,
118
- OutputIn,
119
- OutputOut
120
- >,
56
+ this.subscriptionBuilder.input(parser),
121
57
  this.subscriptionOutput,
122
58
  );
123
59
  }
@@ -136,28 +72,11 @@ export class EffectProcedureBuilder<
136
72
  LayerError,
137
73
  RuntimeRequirements
138
74
  > {
139
- const next = this.builder.output(Schema.toStandardSchemaV1(schema));
140
75
  return new EffectProcedureBuilder(
141
- next as unknown as Builder<
142
- Context,
143
- Meta,
144
- ContextOverrides,
145
- InputIn,
146
- InputOut,
147
- IntersectIfDefined<OutputIn, SchemaValue["Encoded"]>,
148
- IntersectIfDefined<OutputOut, SchemaValue["Type"]>
149
- >,
76
+ this.builder.output(Schema.toStandardSchemaV1(schema)),
150
77
  this.requestServices,
151
78
  this.runtime,
152
- this.subscriptionBuilder as unknown as Builder<
153
- Context,
154
- Meta,
155
- ContextOverrides,
156
- InputIn,
157
- InputOut,
158
- IntersectIfDefined<OutputIn, SchemaValue["Encoded"]>,
159
- IntersectIfDefined<OutputOut, SchemaValue["Type"]>
160
- >,
79
+ this.subscriptionBuilder,
161
80
  schema,
162
81
  );
163
82
  }
@@ -172,12 +91,9 @@ export class EffectProcedureBuilder<
172
91
  input: DefaultValue<InputIn, void>;
173
92
  output: DefaultValue<OutputOut, Output>;
174
93
  meta: Meta;
175
- }> {
176
- return this.dispatch("query", resolver) as TRPCQueryProcedure<{
177
- input: DefaultValue<InputIn, void>;
178
- output: DefaultValue<OutputOut, Output>;
179
- meta: Meta;
180
- }>;
94
+ }>;
95
+ query(resolver: EffectProcedureResolver<never, ProvidedServices | RuntimeRequirements, unknown>): unknown {
96
+ return this.builder.query(this.handler("query", resolver));
181
97
  }
182
98
 
183
99
  mutation<Output>(
@@ -190,12 +106,9 @@ export class EffectProcedureBuilder<
190
106
  input: DefaultValue<InputIn, void>;
191
107
  output: DefaultValue<OutputOut, Output>;
192
108
  meta: Meta;
193
- }> {
194
- return this.dispatch("mutation", resolver) as TRPCMutationProcedure<{
195
- input: DefaultValue<InputIn, void>;
196
- output: DefaultValue<OutputOut, Output>;
197
- meta: Meta;
198
- }>;
109
+ }>;
110
+ mutation(resolver: EffectProcedureResolver<never, ProvidedServices | RuntimeRequirements, unknown>): unknown {
111
+ return this.builder.mutation(this.handler("mutation", resolver));
199
112
  }
200
113
 
201
114
  subscription<Output>(
@@ -208,42 +121,20 @@ export class EffectProcedureBuilder<
208
121
  input: DefaultValue<InputIn, void>;
209
122
  output: AsyncIterable<DefaultValue<OutputOut, Output>, void, unknown>;
210
123
  meta: Meta;
211
- }> {
212
- const handler = makeSubscriptionHandler(
213
- this.runtime,
214
- resolver,
215
- this.requestServices,
216
- {
217
- captureStackTrace: captureStackTrace(),
218
- type: "subscription",
219
- },
220
- this.subscriptionOutput,
221
- ) as never;
124
+ }>;
125
+ subscription(resolver: EffectSubscriptionResolver<never, ProvidedServices | RuntimeRequirements, unknown>): unknown {
222
126
  return this.subscriptionBuilder.subscription(
223
- handler,
224
- ) as TRPCSubscriptionProcedure<{
225
- input: DefaultValue<InputIn, void>;
226
- output: AsyncIterable<DefaultValue<OutputOut, Output>, void, unknown>;
227
- meta: Meta;
228
- }>;
127
+ makeSubscriptionHandler(
128
+ this.runtime,
129
+ resolver,
130
+ this.requestServices,
131
+ { captureStackTrace: captureStackTrace(), type: "subscription" },
132
+ this.subscriptionOutput,
133
+ ),
134
+ );
229
135
  }
230
136
 
231
- private dispatch<Output>(
232
- type: "mutation" | "query",
233
- resolver: EffectProcedureResolver<
234
- DefaultValue<InputOut, undefined>,
235
- ProvidedServices | NoInfer<RuntimeRequirements>,
236
- Output
237
- >,
238
- ): unknown {
239
- const handler = makeProcedureHandler(
240
- this.runtime,
241
- resolver,
242
- this.requestServices,
243
- { captureStackTrace: captureStackTrace(), type },
244
- ) as never;
245
- return type === "query"
246
- ? this.builder.query(handler)
247
- : this.builder.mutation(handler);
137
+ private handler(type: "mutation" | "query", resolver: EffectProcedureResolver<never, ProvidedServices | RuntimeRequirements, unknown>) {
138
+ return makeProcedureHandler(this.runtime, resolver, this.requestServices, { captureStackTrace: captureStackTrace(), type });
248
139
  }
249
140
  }
@@ -0,0 +1,33 @@
1
+ import { StandardSchemaV1Error, type TRPCDefaultErrorShape, type TRPCError } from "@trpc/server";
2
+ import type { EncodedRejection } from "./client/rejection.ts";
3
+ import { type DeclaredRejection, RejectionError } from "./rejection.ts";
4
+
5
+ export interface RejectionData {
6
+ readonly rejection?: EncodedRejection;
7
+ }
8
+
9
+ export type RejectionErrorShape = TRPCDefaultErrorShape & { readonly data: RejectionData };
10
+
11
+ const inputRejection = ({ issues: [issue] }: StandardSchemaV1Error): EncodedRejection | undefined => {
12
+ if (issue === undefined) return undefined;
13
+ const field = (issue.path ?? []).map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
14
+ return field === ""
15
+ ? { _tag: "BadRequest", message: issue.message, invalidInput: true }
16
+ : { _tag: "BadRequest", message: issue.message, field, invalidInput: true };
17
+ };
18
+
19
+ const declared = ({ invalidInput: _reserved, ...rejection }: DeclaredRejection): EncodedRejection => rejection;
20
+
21
+ const rejectionFrom = (error: TRPCError): EncodedRejection | undefined => {
22
+ if (error instanceof RejectionError) return declared(error.rejection);
23
+ if (error.code === "BAD_REQUEST" && error.cause instanceof StandardSchemaV1Error) return inputRejection(error.cause);
24
+ return undefined;
25
+ };
26
+
27
+ export const withRejection = <Shape extends { readonly data: object }>(shape: Shape, error: TRPCError): Shape & { readonly data: RejectionData } => {
28
+ const rejection = rejectionFrom(error);
29
+ return rejection === undefined ? shape : { ...shape, data: { ...shape.data, rejection } };
30
+ };
31
+
32
+ export const rejectionFormatter = ({ shape, error }: { readonly shape: TRPCDefaultErrorShape; readonly error: TRPCError }): RejectionErrorShape =>
33
+ withRejection(shape, error);
@@ -0,0 +1,60 @@
1
+ import { type TRPC_ERROR_CODE_KEY, TRPCError } from "@trpc/server";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ const TAXONOMY_CODES = new Map<string, TRPC_ERROR_CODE_KEY>([
5
+ ["NotFound", "NOT_FOUND"],
6
+ ["Unauthorized", "UNAUTHORIZED"],
7
+ ["Forbidden", "FORBIDDEN"],
8
+ ["Conflict", "CONFLICT"],
9
+ ["PreconditionFailed", "PRECONDITION_FAILED"],
10
+ ["TooManyRequests", "TOO_MANY_REQUESTS"],
11
+ ["AuthUnavailable", "SERVICE_UNAVAILABLE"],
12
+ ]);
13
+
14
+ export const rejectionCode = (tag: string): TRPC_ERROR_CODE_KEY => TAXONOMY_CODES.get(tag) ?? "BAD_REQUEST";
15
+
16
+ const DeclaredRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
17
+
18
+ export type DeclaredRejection = typeof DeclaredRejection.Type;
19
+
20
+ interface InvalidInputIsReserved {
21
+ readonly "invalidInput is reserved for the mark on input that did not parse": never;
22
+ }
23
+
24
+ interface DeclaredEncoding {
25
+ readonly _tag: string;
26
+ readonly invalidInput?: InvalidInputIsReserved;
27
+ }
28
+
29
+ export class RejectionError extends TRPCError {
30
+ readonly rejection: DeclaredRejection;
31
+
32
+ constructor(rejection: DeclaredRejection, code: TRPC_ERROR_CODE_KEY = rejectionCode(rejection._tag)) {
33
+ super({ code, message: typeof rejection.message === "string" ? rejection.message : rejection._tag });
34
+ this.rejection = rejection;
35
+ }
36
+ }
37
+
38
+ export interface RejectWithOptions<Tag extends string> {
39
+ readonly code?: (tag: Tag) => TRPC_ERROR_CODE_KEY;
40
+ }
41
+
42
+ const encodeRejection = (schema: Schema.Constraint) => {
43
+ const encode = Schema.encodeUnknownEffect(schema);
44
+ const tagged = Schema.decodeUnknownEffect(DeclaredRejection);
45
+ return (rejection: unknown) => Effect.orDie(Effect.flatMap(encode(rejection), tagged));
46
+ };
47
+
48
+ export function rejectWith<S extends Schema.ConstraintCodec<{ readonly _tag: string }, DeclaredEncoding, unknown, unknown>>(
49
+ schema: S,
50
+ options?: RejectWithOptions<S["Type"]["_tag"]>,
51
+ ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Exclude<E, S["Type"]> | RejectionError, R | S["EncodingServices"]>;
52
+ export function rejectWith(schema: Schema.Constraint, options?: RejectWithOptions<string>) {
53
+ const is = Schema.is(schema);
54
+ const encode = encodeRejection(schema);
55
+ const code = options?.code ?? rejectionCode;
56
+ return <A, E, R>(self: Effect.Effect<A, E, R>) =>
57
+ Effect.catchIf(self, is, (rejection) =>
58
+ Effect.flatMap(encode(rejection), (encoded) => Effect.fail(new RejectionError(encoded, code(encoded._tag)))),
59
+ );
60
+ }
@@ -1,10 +1,6 @@
1
1
  import { type Layer as EffectLayer, Layer } from "effect";
2
2
 
3
- export interface EffectProcedureRequestServices<
4
- Context,
5
- Requirements,
6
- Error = never,
7
- > {
3
+ export interface EffectProcedureRequestServices<Context, Requirements, Error = never> {
8
4
  readonly layer: (context: Context) => EffectLayer.Layer<Requirements, Error>;
9
5
  }
10
6
 
@@ -21,23 +17,7 @@ export const extendRequestServices = <
21
17
  AdditionalError,
22
18
  AdditionalDependencies extends BaseRequirements = BaseRequirements,
23
19
  >(
24
- base: EffectProcedureRequestServices<
25
- BaseContext,
26
- BaseRequirements,
27
- BaseError
28
- >,
29
- additional: (
30
- context: Context,
31
- ) => EffectLayer.Layer<
32
- AdditionalRequirements,
33
- AdditionalError,
34
- AdditionalDependencies
35
- >,
36
- ): EffectProcedureRequestServices<
37
- Context,
38
- BaseRequirements | AdditionalRequirements,
39
- BaseError | AdditionalError
40
- > =>
41
- makeRequestServices((context: Context) =>
42
- Layer.provideMerge(additional(context), base.layer(context)),
43
- );
20
+ base: EffectProcedureRequestServices<BaseContext, BaseRequirements, BaseError>,
21
+ additional: (context: Context) => EffectLayer.Layer<AdditionalRequirements, AdditionalError, AdditionalDependencies>,
22
+ ): EffectProcedureRequestServices<Context, BaseRequirements | AdditionalRequirements, BaseError | AdditionalError> =>
23
+ makeRequestServices((context: Context) => Layer.provideMerge(additional(context), base.layer(context)));
@@ -1,7 +1,4 @@
1
1
  import { Context } from "effect";
2
2
 
3
3
  /** The transport cancellation signal for the current tRPC procedure. */
4
- export const RequestSignal = Context.Reference<AbortSignal | undefined>(
5
- "@shivaedev/effect-trpc/RequestSignal",
6
- { defaultValue: () => undefined },
7
- );
4
+ export const RequestSignal = Context.Reference<AbortSignal | undefined>("@shivaedev/effect-trpc/RequestSignal", { defaultValue: () => undefined });
@@ -0,0 +1,3 @@
1
+ import type { Layer } from "effect";
2
+
3
+ export type AnyTestLayer = Layer.Layer<any, any, never>;
@@ -1,11 +1,9 @@
1
1
  import { getTRPCErrorFromUnknown, type TRPCError } from "@trpc/server";
2
2
  import { type Context, Effect } from "effect";
3
- import type { EffectTRPCAdapter } from "../adapter.js";
3
+ import type { EffectTRPCAdapter } from "../adapter.ts";
4
4
 
5
5
  export type EffectCaller<Caller> = {
6
- readonly [Key in keyof Caller]: Caller[Key] extends (
7
- ...arguments_: infer Arguments
8
- ) => Promise<infer Result>
6
+ readonly [Key in keyof Caller]: Caller[Key] extends (...arguments_: infer Arguments) => Promise<infer Result>
9
7
  ? (...arguments_: Arguments) => Effect.Effect<Result, TRPCError>
10
8
  : Caller[Key] extends (...arguments_: infer Arguments) => infer Result
11
9
  ? (...arguments_: Arguments) => Result
@@ -14,14 +12,26 @@ export type EffectCaller<Caller> = {
14
12
  : Caller[Key];
15
13
  };
16
14
 
17
- export type EffectCallerFactory<Options, Caller> = EffectCaller<Caller> &
18
- ((options?: Options) => EffectCaller<Caller>);
15
+ export type EffectCallerFactory<Options, Caller> = EffectCaller<Caller> & ((options?: Options) => EffectCaller<Caller>);
19
16
 
20
- export const makeEffectCaller = <Caller extends object, Services>(
21
- adapter: Pick<EffectTRPCAdapter<never>, "runWithServices">,
17
+ type Adapter = Pick<EffectTRPCAdapter<never>, "runWithServices">;
18
+
19
+ const member = (node: unknown, segment: string): unknown =>
20
+ (typeof node === "object" && node !== null) || typeof node === "function" ? Reflect.get(node, segment) : undefined;
21
+
22
+ const invoke = (promiseCaller: object, path: ReadonlyArray<string>, argumentsList: ReadonlyArray<unknown>): Promise<unknown> => {
23
+ const leaf = path.reduce<unknown>(member, promiseCaller);
24
+ return typeof leaf === "function"
25
+ ? Promise.resolve(Reflect.apply(leaf, undefined, argumentsList))
26
+ : Promise.reject(new TypeError(`${path.join(".")} is not a procedure`));
27
+ };
28
+
29
+ export function makeEffectCaller<Caller extends object, Services>(
30
+ adapter: Adapter,
22
31
  promiseCaller: Caller,
23
32
  services: Context.Context<Services>,
24
- ): EffectCaller<Caller> => {
33
+ ): EffectCaller<Caller>;
34
+ export function makeEffectCaller(adapter: Adapter, promiseCaller: object, services: Context.Context<never>): unknown {
25
35
  const build = (path: ReadonlyArray<string>): unknown =>
26
36
  new Proxy(
27
37
  Object.assign(() => undefined, { path }),
@@ -29,16 +39,7 @@ export const makeEffectCaller = <Caller extends object, Services>(
29
39
  apply(_target, _this, argumentsList) {
30
40
  return Effect.tryPromise({
31
41
  catch: getTRPCErrorFromUnknown,
32
- try: () =>
33
- adapter.runWithServices(services, () => {
34
- let leaf: unknown = promiseCaller;
35
- for (const segment of path) {
36
- leaf = (leaf as Record<string, unknown>)[segment];
37
- }
38
- return (leaf as (...args: unknown[]) => Promise<unknown>)(
39
- ...argumentsList,
40
- );
41
- }),
42
+ try: () => adapter.runWithServices(services, () => invoke(promiseCaller, path, argumentsList)),
42
43
  });
43
44
  },
44
45
  get(_target, property) {
@@ -50,21 +51,17 @@ export const makeEffectCaller = <Caller extends object, Services>(
50
51
  },
51
52
  );
52
53
 
53
- return build([]) as EffectCaller<Caller>;
54
- };
54
+ return build([]);
55
+ }
55
56
 
56
- export const makeEffectCallerFactory = <
57
- Options,
58
- Caller extends object,
59
- Services,
60
- >(
61
- adapter: Pick<EffectTRPCAdapter<never>, "runWithServices">,
57
+ export function makeEffectCallerFactory<Options, Caller extends object, Services>(
58
+ adapter: Adapter,
62
59
  createCaller: (options?: Options) => Caller,
63
60
  services: Context.Context<Services>,
64
- ): EffectCallerFactory<Options, Caller> => {
61
+ ): EffectCallerFactory<Options, Caller>;
62
+ export function makeEffectCallerFactory(adapter: Adapter, createCaller: (options?: unknown) => object, services: Context.Context<never>): unknown {
65
63
  const defaultCaller = makeEffectCaller(adapter, createCaller(), services);
66
- const target = (options?: Options) =>
67
- makeEffectCaller(adapter, createCaller(options), services);
64
+ const target = (options?: unknown) => makeEffectCaller(adapter, createCaller(options), services);
68
65
 
69
66
  return new Proxy(target, {
70
67
  get(_target, property, receiver) {
@@ -76,5 +73,5 @@ export const makeEffectCallerFactory = <
76
73
  }
77
74
  return Reflect.get(defaultCaller, property);
78
75
  },
79
- }) as EffectCallerFactory<Options, Caller>;
80
- };
76
+ });
77
+ }
@@ -1,20 +1,17 @@
1
1
  import type { TestContext, Vitest } from "@effect/vitest";
2
2
  import type { EffectClock, EffectTestOptions } from "@shivaedev/effect-test";
3
3
  import type { Effect, Layer } from "effect";
4
- import type { EffectTRPCAdapter } from "../adapter.js";
5
- import type { EffectCallerFactory } from "./caller.js";
4
+ import type { EffectTRPCAdapter } from "../adapter.ts";
5
+ import type { AnyTestLayer } from "./any-test-layer.ts";
6
+ import type { EffectCallerFactory } from "./caller.ts";
6
7
 
7
- export type TrpcHarnessTest<Harness, Provided> = <
8
- A,
9
- Eff extends Effect.Effect<unknown, unknown, Provided>,
10
- >(
8
+ export type TrpcHarnessTest<Harness, Provided> = <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
11
9
  name: string,
12
10
  body: (harness: Harness, context: TestContext) => Generator<Eff, A, never>,
13
11
  options?: number | EffectTestOptions,
14
12
  ) => void;
15
13
 
16
- export interface TrpcHarnessTester<Harness, Provided>
17
- extends TrpcHarnessTest<Harness, Provided> {
14
+ export interface TrpcHarnessTester<Harness, Provided> extends TrpcHarnessTest<Harness, Provided> {
18
15
  readonly skip: TrpcHarnessTest<Harness, Provided>;
19
16
  readonly skipIf: (condition: unknown) => TrpcHarnessTest<Harness, Provided>;
20
17
  readonly runIf: (condition: unknown) => TrpcHarnessTest<Harness, Provided>;
@@ -23,25 +20,15 @@ export interface TrpcHarnessTester<Harness, Provided>
23
20
  cases: ReadonlyArray<Item>,
24
21
  ) => <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
25
22
  name: string,
26
- body: (
27
- item: Item,
28
- harness: Harness,
29
- context: TestContext,
30
- ) => Generator<Eff, A, never>,
23
+ body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
31
24
  options?: number | EffectTestOptions,
32
25
  ) => void;
33
26
  readonly fails: TrpcHarnessTest<Harness, Provided>;
34
27
  }
35
28
 
36
- export type TrpcTest<Options, Caller, Provided> = TrpcHarnessTest<
37
- EffectCallerFactory<Options, Caller>,
38
- Provided
39
- >;
29
+ export type TrpcTest<Options, Caller, Provided> = TrpcHarnessTest<EffectCallerFactory<Options, Caller>, Provided>;
40
30
 
41
- export type TrpcTester<Options, Caller, Provided> = TrpcHarnessTester<
42
- EffectCallerFactory<Options, Caller>,
43
- Provided
44
- >;
31
+ export type TrpcTester<Options, Caller, Provided> = TrpcHarnessTester<EffectCallerFactory<Options, Caller>, Provided>;
45
32
 
46
33
  export type TrpcIt<Options, Caller, Provided> = Vitest.Methods & {
47
34
  readonly effectTRPC: TrpcTester<Options, Caller, Provided>;
@@ -51,44 +38,25 @@ export type TrpcHarnessIt<Harness, Provided> = Vitest.Methods & {
51
38
  readonly effectTRPC: TrpcHarnessTester<Harness, Provided>;
52
39
  };
53
40
 
54
- export interface MakeTrpcItOptions<
55
- CreateCaller extends (...arguments_: never[]) => object,
56
- // biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
57
- TestLayer extends Layer.Layer<any, any, never>,
58
- > {
41
+ export interface MakeTrpcItOptions<CreateCaller extends (...arguments_: never[]) => object, TestLayer extends AnyTestLayer> {
59
42
  readonly adapter: Pick<EffectTRPCAdapter<never>, "runWithServices">;
60
- readonly around?: <A, E>(
61
- effect: Effect.Effect<A, E, Layer.Success<TestLayer>>,
62
- ) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>;
43
+ readonly around?: <A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>;
63
44
  readonly clock?: EffectClock;
64
45
  readonly createCaller: CreateCaller;
65
46
  readonly layer: TestLayer;
66
47
  }
67
48
 
68
- export interface MakeTrpcHarnessItOptions<
69
- CreateCaller extends (...arguments_: never[]) => object,
70
- // biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
71
- TestLayer extends Layer.Layer<any, any, never>,
72
- Harness,
73
- > extends MakeTrpcItOptions<CreateCaller, TestLayer> {
49
+ export interface MakeTrpcHarnessItOptions<CreateCaller extends (...arguments_: never[]) => object, TestLayer extends AnyTestLayer, Harness>
50
+ extends MakeTrpcItOptions<CreateCaller, TestLayer> {
74
51
  readonly makeHarness: (
75
- trpc: EffectCallerFactory<
76
- CallerOptions<CreateCaller>,
77
- CallerResult<CreateCaller>
78
- >,
52
+ trpc: EffectCallerFactory<CallerOptions<CreateCaller>, CallerResult<CreateCaller>>,
79
53
  context: TestContext,
80
54
  ) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
81
55
  }
82
56
 
83
- export type CallerOptions<CreateCaller> = CreateCaller extends (
84
- ...arguments_: infer Arguments
85
- ) => object
86
- ? Arguments[0]
87
- : never;
57
+ export type CallerOptions<CreateCaller> = CreateCaller extends (...arguments_: infer Arguments) => object ? Arguments[0] : never;
88
58
 
89
- export type CallerResult<CreateCaller> = CreateCaller extends (
90
- ...arguments_: infer _Arguments
91
- ) => infer Caller
59
+ export type CallerResult<CreateCaller> = CreateCaller extends (...arguments_: infer _Arguments) => infer Caller
92
60
  ? Caller extends object
93
61
  ? Caller
94
62
  : never