@shivaedev/effect-trpc 0.3.4 → 0.4.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.
- package/CHANGELOG.md +21 -1
- package/README.md +76 -0
- package/dist/adapter.d.ts +6 -5
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +2 -1
- package/dist/adapter.js.map +1 -1
- package/dist/client/rejection.d.ts +10 -0
- package/dist/client/rejection.d.ts.map +1 -0
- package/dist/client/rejection.js +9 -0
- package/dist/client/rejection.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/context-bridge.d.ts.map +1 -1
- package/dist/internal/context-bridge.js +1 -2
- package/dist/internal/context-bridge.js.map +1 -1
- package/dist/internal/loose.d.ts +4 -0
- package/dist/internal/loose.d.ts.map +1 -0
- package/dist/internal/loose.js +7 -0
- package/dist/internal/loose.js.map +1 -0
- package/dist/internal/procedure-handler.d.ts +5 -12
- package/dist/internal/procedure-handler.d.ts.map +1 -1
- package/dist/internal/procedure-handler.js +3 -4
- package/dist/internal/procedure-handler.js.map +1 -1
- package/dist/internal/procedure-types.d.ts +17 -4
- package/dist/internal/procedure-types.d.ts.map +1 -1
- package/dist/internal/runtime.d.ts +4 -4
- package/dist/internal/runtime.d.ts.map +1 -1
- package/dist/internal/runtime.js +5 -13
- package/dist/internal/runtime.js.map +1 -1
- package/dist/internal/subscription-handler.d.ts +5 -12
- package/dist/internal/subscription-handler.d.ts.map +1 -1
- package/dist/internal/subscription-handler.js +5 -6
- package/dist/internal/subscription-handler.js.map +1 -1
- package/dist/procedure.d.ts +6 -8
- package/dist/procedure.d.ts.map +1 -1
- package/dist/procedure.js +8 -17
- package/dist/procedure.js.map +1 -1
- package/dist/rejection-formatter.d.ts +18 -0
- package/dist/rejection-formatter.d.ts.map +1 -0
- package/dist/rejection-formatter.js +21 -0
- package/dist/rejection-formatter.js.map +1 -0
- package/dist/rejection.d.ts +15 -0
- package/dist/rejection.d.ts.map +1 -0
- package/dist/rejection.js +31 -0
- package/dist/rejection.js.map +1 -0
- package/dist/request-services.d.ts.map +1 -1
- package/dist/request-services.js.map +1 -1
- package/dist/request-signal.d.ts.map +1 -1
- package/dist/request-signal.js.map +1 -1
- package/dist/testing/any-test-layer.d.ts +3 -0
- package/dist/testing/any-test-layer.d.ts.map +1 -0
- package/dist/testing/any-test-layer.js +2 -0
- package/dist/testing/any-test-layer.js.map +1 -0
- package/dist/testing/caller.d.ts +5 -3
- package/dist/testing/caller.d.ts.map +1 -1
- package/dist/testing/caller.js +12 -11
- package/dist/testing/caller.js.map +1 -1
- package/dist/testing/types.d.ts +5 -4
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/vitest.d.ts +4 -3
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js +13 -14
- package/dist/testing/vitest.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/adapter.ts +24 -76
- package/src/client/rejection.ts +16 -0
- package/src/client.ts +1 -0
- package/src/errors.ts +8 -19
- package/src/index.ts +9 -6
- package/src/internal/context-bridge.ts +3 -10
- package/src/internal/loose.ts +14 -0
- package/src/internal/procedure-handler.ts +11 -34
- package/src/internal/procedure-types.ts +37 -50
- package/src/internal/runtime.ts +14 -49
- package/src/internal/subscription-handler.ts +15 -47
- package/src/procedure.ts +33 -142
- package/src/rejection-formatter.ts +29 -0
- package/src/rejection.ts +47 -0
- package/src/request-services.ts +5 -25
- package/src/request-signal.ts +1 -4
- package/src/testing/any-test-layer.ts +3 -0
- package/src/testing/caller.ts +29 -32
- package/src/testing/types.ts +15 -47
- package/src/testing/vitest.ts +19 -51
- package/src/testing.ts +3 -3
- 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.
|
|
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.
|
|
16
|
-
import type { RuntimeBridge } from "./internal/runtime.
|
|
17
|
-
import { captureStackTrace } from "./internal/stack-trace.
|
|
18
|
-
import { makeSubscriptionHandler } from "./internal/subscription-handler.
|
|
19
|
-
import type { EffectProcedureRequestServices } from "./request-services.
|
|
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:
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
232
|
-
|
|
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,29 @@
|
|
|
1
|
+
import { StandardSchemaV1Error, type TRPCDefaultErrorShape, type TRPCError } from "@trpc/server";
|
|
2
|
+
import type { EncodedRejection } from "./client/rejection.ts";
|
|
3
|
+
import { 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 === "" ? { _tag: "BadRequest", message: issue.message } : { _tag: "BadRequest", message: issue.message, field };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const rejectionFrom = (error: TRPCError): EncodedRejection | undefined => {
|
|
18
|
+
if (error instanceof RejectionError) return error.rejection;
|
|
19
|
+
if (error.code === "BAD_REQUEST" && error.cause instanceof StandardSchemaV1Error) return inputRejection(error.cause);
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const withRejection = <Shape extends { readonly data: object }>(shape: Shape, error: TRPCError): Shape & { readonly data: RejectionData } => {
|
|
24
|
+
const rejection = rejectionFrom(error);
|
|
25
|
+
return rejection === undefined ? shape : { ...shape, data: { ...shape.data, rejection } };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const rejectionFormatter = ({ shape, error }: { readonly shape: TRPCDefaultErrorShape; readonly error: TRPCError }): RejectionErrorShape =>
|
|
29
|
+
withRejection(shape, error);
|
package/src/rejection.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type TRPC_ERROR_CODE_KEY, TRPCError } from "@trpc/server";
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import { EncodedRejection } from "./client/rejection.ts";
|
|
4
|
+
|
|
5
|
+
const TAXONOMY_CODES = new Map<string, TRPC_ERROR_CODE_KEY>([
|
|
6
|
+
["NotFound", "NOT_FOUND"],
|
|
7
|
+
["Unauthorized", "UNAUTHORIZED"],
|
|
8
|
+
["Forbidden", "FORBIDDEN"],
|
|
9
|
+
["Conflict", "CONFLICT"],
|
|
10
|
+
["PreconditionFailed", "PRECONDITION_FAILED"],
|
|
11
|
+
["AuthUnavailable", "SERVICE_UNAVAILABLE"],
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const rejectionCode = (tag: string): TRPC_ERROR_CODE_KEY => TAXONOMY_CODES.get(tag) ?? "BAD_REQUEST";
|
|
15
|
+
|
|
16
|
+
export class RejectionError extends TRPCError {
|
|
17
|
+
readonly rejection: EncodedRejection;
|
|
18
|
+
|
|
19
|
+
constructor(rejection: EncodedRejection, code: TRPC_ERROR_CODE_KEY = rejectionCode(rejection._tag)) {
|
|
20
|
+
super({ code, message: typeof rejection.message === "string" ? rejection.message : rejection._tag });
|
|
21
|
+
this.rejection = rejection;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RejectWithOptions<Tag extends string> {
|
|
26
|
+
readonly code?: (tag: Tag) => TRPC_ERROR_CODE_KEY;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const encodeRejection = (schema: Schema.Constraint) => {
|
|
30
|
+
const encode = Schema.encodeUnknownEffect(schema);
|
|
31
|
+
const tagged = Schema.decodeUnknownEffect(EncodedRejection);
|
|
32
|
+
return (rejection: unknown) => Effect.orDie(Effect.flatMap(encode(rejection), tagged));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function rejectWith<S extends Schema.ConstraintDecoder<{ readonly _tag: string }, unknown>>(
|
|
36
|
+
schema: S,
|
|
37
|
+
options?: RejectWithOptions<S["Type"]["_tag"]>,
|
|
38
|
+
): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Exclude<E, S["Type"]> | RejectionError, R | S["EncodingServices"]>;
|
|
39
|
+
export function rejectWith(schema: Schema.Constraint, options?: RejectWithOptions<string>) {
|
|
40
|
+
const is = Schema.is(schema);
|
|
41
|
+
const encode = encodeRejection(schema);
|
|
42
|
+
const code = options?.code ?? rejectionCode;
|
|
43
|
+
return <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
44
|
+
Effect.catchIf(self, is, (rejection) =>
|
|
45
|
+
Effect.flatMap(encode(rejection), (encoded) => Effect.fail(new RejectionError(encoded, code(encoded._tag)))),
|
|
46
|
+
);
|
|
47
|
+
}
|
package/src/request-services.ts
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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)));
|
package/src/request-signal.ts
CHANGED
|
@@ -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 });
|
package/src/testing/caller.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
21
|
-
|
|
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([])
|
|
54
|
-
}
|
|
54
|
+
return build([]);
|
|
55
|
+
}
|
|
55
56
|
|
|
56
|
-
export
|
|
57
|
-
|
|
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?:
|
|
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
|
-
})
|
|
80
|
-
}
|
|
76
|
+
});
|
|
77
|
+
}
|
package/src/testing/types.ts
CHANGED
|
@@ -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.
|
|
5
|
-
import type {
|
|
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
|
-
|
|
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
|
package/src/testing/vitest.ts
CHANGED
|
@@ -1,79 +1,47 @@
|
|
|
1
1
|
import { it as effectIt, type Vitest } from "@effect/vitest";
|
|
2
2
|
import { makeEffectIt } from "@shivaedev/effect-test";
|
|
3
3
|
import { Effect, type Layer } from "effect";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
CallerResult,
|
|
8
|
-
MakeTrpcHarnessItOptions,
|
|
9
|
-
MakeTrpcItOptions,
|
|
10
|
-
TrpcHarnessIt,
|
|
11
|
-
TrpcIt,
|
|
12
|
-
} from "./types.js";
|
|
4
|
+
import type { AnyTestLayer } from "./any-test-layer.ts";
|
|
5
|
+
import { makeEffectCallerFactory } from "./caller.ts";
|
|
6
|
+
import type { CallerOptions, CallerResult, MakeTrpcHarnessItOptions, MakeTrpcItOptions, TrpcHarnessIt, TrpcIt } from "./types.ts";
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readonly effectTRPC: Tester;
|
|
18
|
-
} =>
|
|
19
|
-
new Proxy(effectIt, {
|
|
8
|
+
function withEffectTRPC<Tester>(tester: Tester): Vitest.Methods & { readonly effectTRPC: Tester };
|
|
9
|
+
function withEffectTRPC(tester: unknown): unknown {
|
|
10
|
+
return new Proxy(effectIt, {
|
|
20
11
|
get(target, property, receiver) {
|
|
21
12
|
if (property === "effectTRPC") {
|
|
22
13
|
return tester;
|
|
23
14
|
}
|
|
24
15
|
return Reflect.get(target, property, receiver);
|
|
25
16
|
},
|
|
26
|
-
})
|
|
17
|
+
});
|
|
18
|
+
}
|
|
27
19
|
|
|
28
|
-
export
|
|
29
|
-
CreateCaller extends (...arguments_: never[]) => object,
|
|
30
|
-
// biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
|
|
31
|
-
TestLayer extends Layer.Layer<any, any, never>,
|
|
32
|
-
Harness,
|
|
33
|
-
>(
|
|
20
|
+
export function makeTrpcHarnessIt<CreateCaller extends (...arguments_: never[]) => object, TestLayer extends AnyTestLayer, Harness>(
|
|
34
21
|
options: MakeTrpcHarnessItOptions<CreateCaller, TestLayer, Harness>,
|
|
35
|
-
): TrpcHarnessIt<Harness, Layer.Success<TestLayer
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
): TrpcHarnessIt<Harness, Layer.Success<TestLayer>>;
|
|
23
|
+
export function makeTrpcHarnessIt<TestLayer extends AnyTestLayer, Harness>(
|
|
24
|
+
options: MakeTrpcHarnessItOptions<(options?: unknown) => object, TestLayer, Harness>,
|
|
25
|
+
): unknown {
|
|
40
26
|
const { effectApp } = makeEffectIt({
|
|
41
27
|
around: options.around,
|
|
42
28
|
clock: options.clock,
|
|
43
29
|
layer: options.layer,
|
|
44
30
|
makeHarness: (context) =>
|
|
45
31
|
Effect.gen(function* () {
|
|
46
|
-
const services = yield* Effect.context<
|
|
47
|
-
const trpc = makeEffectCallerFactory(
|
|
48
|
-
options.adapter,
|
|
49
|
-
// CreateCaller is a rest-never function type. The option and
|
|
50
|
-
// result aliases are the factory TypeScript cannot recover here.
|
|
51
|
-
options.createCaller as unknown as (options?: Options) => Caller,
|
|
52
|
-
services,
|
|
53
|
-
);
|
|
32
|
+
const services = yield* Effect.context<Layer.Success<TestLayer>>();
|
|
33
|
+
const trpc = makeEffectCallerFactory(options.adapter, options.createCaller, services);
|
|
54
34
|
return yield* options.makeHarness(trpc, context);
|
|
55
35
|
}),
|
|
56
36
|
});
|
|
57
37
|
|
|
58
38
|
return withEffectTRPC(effectApp);
|
|
59
|
-
}
|
|
39
|
+
}
|
|
60
40
|
|
|
61
|
-
export const makeTrpcIt = <
|
|
62
|
-
CreateCaller extends (...arguments_: never[]) => object,
|
|
63
|
-
// biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
|
|
64
|
-
TestLayer extends Layer.Layer<any, any, never>,
|
|
65
|
-
>(
|
|
41
|
+
export const makeTrpcIt = <CreateCaller extends (...arguments_: never[]) => object, TestLayer extends AnyTestLayer>(
|
|
66
42
|
options: MakeTrpcItOptions<CreateCaller, TestLayer>,
|
|
67
|
-
): TrpcIt<
|
|
68
|
-
CallerOptions<CreateCaller>,
|
|
69
|
-
CallerResult<CreateCaller>,
|
|
70
|
-
Layer.Success<TestLayer>
|
|
71
|
-
> =>
|
|
43
|
+
): TrpcIt<CallerOptions<CreateCaller>, CallerResult<CreateCaller>, Layer.Success<TestLayer>> =>
|
|
72
44
|
makeTrpcHarnessIt({
|
|
73
45
|
...options,
|
|
74
46
|
makeHarness: (trpc) => Effect.succeed(trpc),
|
|
75
|
-
})
|
|
76
|
-
CallerOptions<CreateCaller>,
|
|
77
|
-
CallerResult<CreateCaller>,
|
|
78
|
-
Layer.Success<TestLayer>
|
|
79
|
-
>;
|
|
47
|
+
});
|