effect-orpc 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/index.js +806 -476
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/contract.ts +491 -0
- package/src/effect-builder.ts +349 -619
- package/src/effect-enhance-router.ts +20 -21
- package/src/effect-procedure.ts +274 -263
- package/src/effect-runtime.ts +134 -0
- package/src/eoc.ts +499 -0
- package/src/extension/compose-surfaces.ts +15 -0
- package/src/extension/create-node-proxy.ts +270 -0
- package/src/extension/state.ts +108 -0
- package/src/index.ts +20 -3
- package/src/tagged-error.ts +24 -4
- package/src/tests/contract.test.ts +346 -0
- package/src/tests/effect-builder.proxy.test.ts +253 -0
- package/src/tests/effect-error-map.test.ts +22 -3
- package/src/tests/parity-shared.ts +32 -0
- package/src/tests/parity.contract-builder-variants.test.ts +192 -0
- package/src/tests/parity.contract-builder.test.ts +222 -0
- package/src/tests/parity.effect-builder.test.ts +210 -0
- package/src/tests/parity.effect-procedure.test.ts +124 -0
- package/src/tests/parity.implementer-variants.test.ts +249 -0
- package/src/tests/parity.implementer.test.ts +280 -0
- package/src/tests/shared.ts +2 -0
- package/src/types/effect-builder-surface.ts +441 -0
- package/src/types/effect-procedure-surface.ts +243 -0
- package/src/types/index.ts +22 -26
- package/src/types/variants.ts +100 -16
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Middleware, MiddlewareOutputFn, Procedure } from "@orpc/server";
|
|
2
|
+
import { describe, expectTypeOf, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { makeEffectORPC } from "../index";
|
|
5
|
+
import type { CurrentContext, InitialContext } from "./parity-shared";
|
|
6
|
+
import { runtime } from "./parity-shared";
|
|
7
|
+
import {
|
|
8
|
+
type AssertExtends,
|
|
9
|
+
baseErrorMap,
|
|
10
|
+
type BaseMeta,
|
|
11
|
+
inputSchema,
|
|
12
|
+
outputSchema,
|
|
13
|
+
} from "./shared";
|
|
14
|
+
|
|
15
|
+
const procedure = makeEffectORPC(runtime)
|
|
16
|
+
.$context<InitialContext>()
|
|
17
|
+
.$meta({ mode: "dev" } as BaseMeta)
|
|
18
|
+
.$input(inputSchema)
|
|
19
|
+
.errors(baseErrorMap)
|
|
20
|
+
.use(({ next }) => next({ context: { auth: true as boolean } }))
|
|
21
|
+
.output(outputSchema)
|
|
22
|
+
.effect(function* () {
|
|
23
|
+
return { output: 456 };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("parity: @orpc/server procedure-decorated.test-d.ts", () => {
|
|
27
|
+
it("is a procedure", () => {
|
|
28
|
+
expectTypeOf<
|
|
29
|
+
AssertExtends<
|
|
30
|
+
typeof procedure,
|
|
31
|
+
Procedure<
|
|
32
|
+
InitialContext & Record<never, never>,
|
|
33
|
+
CurrentContext,
|
|
34
|
+
typeof inputSchema,
|
|
35
|
+
typeof outputSchema,
|
|
36
|
+
typeof baseErrorMap,
|
|
37
|
+
BaseMeta
|
|
38
|
+
>
|
|
39
|
+
>
|
|
40
|
+
>().toEqualTypeOf<true>();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it(".errors / .meta / .route", () => {
|
|
44
|
+
expectTypeOf(
|
|
45
|
+
procedure.errors({
|
|
46
|
+
BAD_GATEWAY: { message: "BAD_GATEWAY" },
|
|
47
|
+
OVERRIDE: { message: "OVERRIDE" },
|
|
48
|
+
}),
|
|
49
|
+
).toBeObject();
|
|
50
|
+
|
|
51
|
+
expectTypeOf(procedure.meta({ mode: "dev", log: true })).toBeObject();
|
|
52
|
+
expectTypeOf(
|
|
53
|
+
procedure.route({ method: "POST", path: "/v2/users", tags: ["tag"] }),
|
|
54
|
+
).toBeObject();
|
|
55
|
+
|
|
56
|
+
// @ts-expect-error - invalid meta
|
|
57
|
+
procedure.meta({ log: "INVALID" });
|
|
58
|
+
// @ts-expect-error - invalid method
|
|
59
|
+
procedure.route({ method: "INVALID" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe(".use", () => {
|
|
63
|
+
it("without map input", () => {
|
|
64
|
+
expectTypeOf(
|
|
65
|
+
procedure.use(({ next }, input, output) => {
|
|
66
|
+
expectTypeOf(input).toEqualTypeOf<{ input: string }>();
|
|
67
|
+
expectTypeOf(output).toBeFunction();
|
|
68
|
+
return next({ context: { extra: true } });
|
|
69
|
+
}),
|
|
70
|
+
).toBeObject();
|
|
71
|
+
|
|
72
|
+
procedure.use(
|
|
73
|
+
// @ts-expect-error - invalid TInContext
|
|
74
|
+
{} as Middleware<{ auth: "invalid" }, any, any, any, any, any>,
|
|
75
|
+
);
|
|
76
|
+
// @ts-expect-error - input is not match
|
|
77
|
+
procedure.use(({ next }, _input: "invalid") => next({}));
|
|
78
|
+
procedure.use(
|
|
79
|
+
// @ts-expect-error - output is not match
|
|
80
|
+
({ next }, _input, _output: MiddlewareOutputFn<"invalid">) => next({}),
|
|
81
|
+
);
|
|
82
|
+
// @ts-expect-error - conflict context
|
|
83
|
+
procedure.use(({ next }) => next({ context: { db: undefined } }));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("with map input", () => {
|
|
87
|
+
expectTypeOf(
|
|
88
|
+
procedure.use(
|
|
89
|
+
({ next }, input: { mapped: string }, output) => {
|
|
90
|
+
expectTypeOf(input).toEqualTypeOf<{ mapped: string }>();
|
|
91
|
+
expectTypeOf(output).toBeFunction();
|
|
92
|
+
return next({ context: { extra: true } });
|
|
93
|
+
},
|
|
94
|
+
(input) => ({ mapped: input.input }),
|
|
95
|
+
),
|
|
96
|
+
).toBeObject();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it(".callable / .actionable", () => {
|
|
101
|
+
expectTypeOf(
|
|
102
|
+
procedure.callable({
|
|
103
|
+
context: async (_clientContext: { batch?: boolean }) => ({
|
|
104
|
+
db: "postgres",
|
|
105
|
+
}),
|
|
106
|
+
}),
|
|
107
|
+
).toBeFunction();
|
|
108
|
+
|
|
109
|
+
expectTypeOf(
|
|
110
|
+
procedure.actionable({
|
|
111
|
+
context: async (_clientContext: { batch?: boolean }) => ({
|
|
112
|
+
db: "postgres",
|
|
113
|
+
}),
|
|
114
|
+
}),
|
|
115
|
+
).toBeFunction();
|
|
116
|
+
|
|
117
|
+
procedure.actionable({
|
|
118
|
+
// @ts-expect-error - all clientContext must be optional
|
|
119
|
+
context: async (_clientContext: { batch: boolean }) => ({
|
|
120
|
+
db: "postgres",
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { Client, ORPCError } from "@orpc/client";
|
|
2
|
+
import type { ErrorFromErrorMap } from "@orpc/contract";
|
|
3
|
+
import type {
|
|
4
|
+
ActionableClient,
|
|
5
|
+
ActionableError,
|
|
6
|
+
ImplementedProcedure,
|
|
7
|
+
Middleware,
|
|
8
|
+
MiddlewareOutputFn,
|
|
9
|
+
ProcedureImplementer,
|
|
10
|
+
} from "@orpc/server";
|
|
11
|
+
import { describe, expectTypeOf, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { implementEffect } from "../index";
|
|
14
|
+
import type { InitialContext } from "./parity-shared";
|
|
15
|
+
import { runtime, typedContract } from "./parity-shared";
|
|
16
|
+
import {
|
|
17
|
+
type AssertExtends,
|
|
18
|
+
baseErrorMap,
|
|
19
|
+
type BaseMeta,
|
|
20
|
+
inputSchema,
|
|
21
|
+
outputSchema,
|
|
22
|
+
} from "./shared";
|
|
23
|
+
|
|
24
|
+
const implementer = implementEffect(typedContract, runtime)
|
|
25
|
+
.$context<InitialContext>()
|
|
26
|
+
.use(({ next }) => next({ context: { auth: true as boolean } }));
|
|
27
|
+
|
|
28
|
+
describe("parity: @orpc/server implementer-procedure.test-d.ts", () => {
|
|
29
|
+
const builder = implementer.ping;
|
|
30
|
+
|
|
31
|
+
it("backward compatibility", () => {
|
|
32
|
+
expectTypeOf<keyof typeof builder>().toEqualTypeOf<
|
|
33
|
+
"~orpc" | "handler" | "use" | "effect"
|
|
34
|
+
>();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("is a contract procedure", () => {
|
|
38
|
+
expectTypeOf(builder["~orpc"]).toBeObject();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("extends the matching upstream implementer interface", () => {
|
|
42
|
+
expectTypeOf<
|
|
43
|
+
AssertExtends<
|
|
44
|
+
typeof builder,
|
|
45
|
+
ProcedureImplementer<
|
|
46
|
+
InitialContext & Record<never, never>,
|
|
47
|
+
InitialContext & { auth: boolean },
|
|
48
|
+
typeof inputSchema,
|
|
49
|
+
typeof outputSchema,
|
|
50
|
+
typeof baseErrorMap,
|
|
51
|
+
BaseMeta
|
|
52
|
+
>
|
|
53
|
+
>
|
|
54
|
+
>().toEqualTypeOf<true>();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe(".use", () => {
|
|
58
|
+
it("without map input", () => {
|
|
59
|
+
expectTypeOf(
|
|
60
|
+
builder.use(
|
|
61
|
+
(
|
|
62
|
+
{ context, next, path, procedure, errors, signal },
|
|
63
|
+
input,
|
|
64
|
+
output,
|
|
65
|
+
) => {
|
|
66
|
+
expectTypeOf(input).toEqualTypeOf<{ input: string }>();
|
|
67
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
68
|
+
InitialContext & { auth: boolean }
|
|
69
|
+
>();
|
|
70
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
71
|
+
expectTypeOf(procedure).toBeObject();
|
|
72
|
+
expectTypeOf(output).toBeFunction();
|
|
73
|
+
expectTypeOf<
|
|
74
|
+
AssertExtends<
|
|
75
|
+
ReturnType<typeof errors.BASE>,
|
|
76
|
+
ORPCError<"BASE", { output: number }>
|
|
77
|
+
>
|
|
78
|
+
>().toEqualTypeOf<true>();
|
|
79
|
+
expectTypeOf<
|
|
80
|
+
AssertExtends<
|
|
81
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
82
|
+
ORPCError<"OVERRIDE", any>
|
|
83
|
+
>
|
|
84
|
+
>().toEqualTypeOf<true>();
|
|
85
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
86
|
+
|
|
87
|
+
return next({ context: { extra: true } });
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
).toBeObject();
|
|
91
|
+
|
|
92
|
+
builder.use(
|
|
93
|
+
// @ts-expect-error - invalid TInContext
|
|
94
|
+
{} as Middleware<{ auth: "invalid" }, any, any, any, any, any>,
|
|
95
|
+
);
|
|
96
|
+
// @ts-expect-error - input is not match
|
|
97
|
+
builder.use(({ next }, _input: "invalid") => next({}));
|
|
98
|
+
// @ts-expect-error - output is not match
|
|
99
|
+
builder.use(({ next }, _input, _output: MiddlewareOutputFn<"invalid">) =>
|
|
100
|
+
next({}),
|
|
101
|
+
);
|
|
102
|
+
// @ts-expect-error - conflict context
|
|
103
|
+
builder.use(({ next }) => next({ context: { db: undefined } }));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("with map input", () => {
|
|
107
|
+
expectTypeOf(
|
|
108
|
+
builder.use(
|
|
109
|
+
(
|
|
110
|
+
{ context, next, path, procedure, errors, signal },
|
|
111
|
+
input: { mapped: boolean },
|
|
112
|
+
output,
|
|
113
|
+
) => {
|
|
114
|
+
expectTypeOf(input).toEqualTypeOf<{ mapped: boolean }>();
|
|
115
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
116
|
+
InitialContext & { auth: boolean }
|
|
117
|
+
>();
|
|
118
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
119
|
+
expectTypeOf(procedure).toBeObject();
|
|
120
|
+
expectTypeOf(output).toBeFunction();
|
|
121
|
+
expectTypeOf<
|
|
122
|
+
AssertExtends<
|
|
123
|
+
ReturnType<typeof errors.BASE>,
|
|
124
|
+
ORPCError<"BASE", { output: number }>
|
|
125
|
+
>
|
|
126
|
+
>().toEqualTypeOf<true>();
|
|
127
|
+
expectTypeOf<
|
|
128
|
+
AssertExtends<
|
|
129
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
130
|
+
ORPCError<"OVERRIDE", any>
|
|
131
|
+
>
|
|
132
|
+
>().toEqualTypeOf<true>();
|
|
133
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
134
|
+
|
|
135
|
+
return next({ context: { extra: true } });
|
|
136
|
+
},
|
|
137
|
+
(input) => {
|
|
138
|
+
expectTypeOf(input).toEqualTypeOf<{ input: string }>();
|
|
139
|
+
return { mapped: true };
|
|
140
|
+
},
|
|
141
|
+
),
|
|
142
|
+
).toBeObject();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("with TInContext", () => {
|
|
146
|
+
const mid = {} as Middleware<
|
|
147
|
+
{ cacheable?: boolean } & Record<never, never>,
|
|
148
|
+
Record<never, never>,
|
|
149
|
+
unknown,
|
|
150
|
+
any,
|
|
151
|
+
any,
|
|
152
|
+
BaseMeta
|
|
153
|
+
>;
|
|
154
|
+
|
|
155
|
+
expectTypeOf(builder.use(mid)).toBeObject();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it(".handler returns an implemented procedure", () => {
|
|
160
|
+
const handled = builder.handler(
|
|
161
|
+
({ input, context, procedure, path, signal, errors }) => {
|
|
162
|
+
expectTypeOf(input).toEqualTypeOf<{ input: string }>();
|
|
163
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
164
|
+
InitialContext & { auth: boolean }
|
|
165
|
+
>();
|
|
166
|
+
expectTypeOf(procedure).toBeObject();
|
|
167
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
168
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
169
|
+
expectTypeOf<
|
|
170
|
+
AssertExtends<
|
|
171
|
+
ReturnType<typeof errors.BASE>,
|
|
172
|
+
ORPCError<"BASE", { output: number }>
|
|
173
|
+
>
|
|
174
|
+
>().toEqualTypeOf<true>();
|
|
175
|
+
expectTypeOf<
|
|
176
|
+
AssertExtends<
|
|
177
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
178
|
+
ORPCError<"OVERRIDE", any>
|
|
179
|
+
>
|
|
180
|
+
>().toEqualTypeOf<true>();
|
|
181
|
+
|
|
182
|
+
return { output: 456 };
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expectTypeOf<
|
|
187
|
+
AssertExtends<
|
|
188
|
+
typeof handled,
|
|
189
|
+
ImplementedProcedure<
|
|
190
|
+
InitialContext & Record<never, never>,
|
|
191
|
+
InitialContext & { auth: boolean },
|
|
192
|
+
typeof inputSchema,
|
|
193
|
+
typeof outputSchema,
|
|
194
|
+
typeof baseErrorMap,
|
|
195
|
+
BaseMeta
|
|
196
|
+
>
|
|
197
|
+
>
|
|
198
|
+
>().toEqualTypeOf<true>();
|
|
199
|
+
|
|
200
|
+
expectTypeOf(handled.use).toBeFunction();
|
|
201
|
+
expectTypeOf(handled.callable).toBeFunction();
|
|
202
|
+
expectTypeOf(handled.actionable).toBeFunction();
|
|
203
|
+
const callable = handled.callable({
|
|
204
|
+
context: async (_clientContext: { batch?: boolean }) => ({
|
|
205
|
+
db: "postgres",
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expectTypeOf<
|
|
210
|
+
AssertExtends<
|
|
211
|
+
typeof callable,
|
|
212
|
+
Client<
|
|
213
|
+
{ batch?: boolean },
|
|
214
|
+
{ input: number },
|
|
215
|
+
{ output: string },
|
|
216
|
+
ErrorFromErrorMap<typeof baseErrorMap>
|
|
217
|
+
>
|
|
218
|
+
>
|
|
219
|
+
>().toEqualTypeOf<true>();
|
|
220
|
+
|
|
221
|
+
const actionable = handled.actionable({
|
|
222
|
+
context: async (_clientContext: { batch?: boolean }) => ({
|
|
223
|
+
db: "postgres",
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expectTypeOf<
|
|
228
|
+
AssertExtends<
|
|
229
|
+
typeof actionable,
|
|
230
|
+
ActionableClient<
|
|
231
|
+
{ input: number },
|
|
232
|
+
{ output: string },
|
|
233
|
+
ActionableError<ErrorFromErrorMap<typeof baseErrorMap>>
|
|
234
|
+
>
|
|
235
|
+
>
|
|
236
|
+
>().toEqualTypeOf<true>();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("parity: @orpc/server implementer-variants.test-d.ts", () => {
|
|
241
|
+
it("router-level .use returns an internal implementer", () => {
|
|
242
|
+
const applied = implementer.nested.use(({ next }) =>
|
|
243
|
+
next({ context: { extra: true } }),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expectTypeOf(applied).toBeObject();
|
|
247
|
+
expectTypeOf(applied.ping.handler).toBeFunction();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { ORPCError } from "@orpc/client";
|
|
2
|
+
import type { Meta, Schema } from "@orpc/contract";
|
|
3
|
+
import type { Middleware, Router } from "@orpc/server";
|
|
4
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
EffectImplementer,
|
|
8
|
+
EffectImplementerInternal,
|
|
9
|
+
EffectProcedureImplementer,
|
|
10
|
+
} from "../index";
|
|
11
|
+
import { implementEffect } from "../index";
|
|
12
|
+
import type { CurrentContext, InitialContext } from "./parity-shared";
|
|
13
|
+
import { runtime, typedContract } from "./parity-shared";
|
|
14
|
+
import type { AssertExtends } from "./shared";
|
|
15
|
+
import {
|
|
16
|
+
baseErrorMap,
|
|
17
|
+
type BaseMeta,
|
|
18
|
+
inputSchema,
|
|
19
|
+
outputSchema,
|
|
20
|
+
} from "./shared";
|
|
21
|
+
|
|
22
|
+
const rootImplementer = implementEffect(typedContract, runtime);
|
|
23
|
+
const implementer = rootImplementer
|
|
24
|
+
.$context<InitialContext>()
|
|
25
|
+
.use(({ next }) => next({ context: { auth: true as boolean } }));
|
|
26
|
+
|
|
27
|
+
describe("parity: @orpc/server implementer.test-d.ts", () => {
|
|
28
|
+
describe("root level", () => {
|
|
29
|
+
it(".$context", () => {
|
|
30
|
+
expectTypeOf(rootImplementer.$context<{ anything: string }>()).toExtend<
|
|
31
|
+
EffectImplementer<
|
|
32
|
+
typeof typedContract,
|
|
33
|
+
{ anything: string } & Record<never, never>,
|
|
34
|
+
{ anything: string },
|
|
35
|
+
never,
|
|
36
|
+
never
|
|
37
|
+
>
|
|
38
|
+
>();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it(".$config", () => {
|
|
42
|
+
expectTypeOf(
|
|
43
|
+
rootImplementer.$config({
|
|
44
|
+
initialInputValidationIndex: Number.NEGATIVE_INFINITY,
|
|
45
|
+
}),
|
|
46
|
+
).toExtend<
|
|
47
|
+
EffectImplementer<
|
|
48
|
+
typeof typedContract,
|
|
49
|
+
Record<never, never>,
|
|
50
|
+
Record<never, never>,
|
|
51
|
+
never,
|
|
52
|
+
never
|
|
53
|
+
>
|
|
54
|
+
>();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("router level", () => {
|
|
59
|
+
it(".middleware", () => {
|
|
60
|
+
expectTypeOf(
|
|
61
|
+
rootImplementer
|
|
62
|
+
.$context<InitialContext>()
|
|
63
|
+
.nested.middleware(
|
|
64
|
+
(
|
|
65
|
+
{ context, next, path, procedure, errors, signal },
|
|
66
|
+
input,
|
|
67
|
+
output,
|
|
68
|
+
) => {
|
|
69
|
+
expectTypeOf(context).toExtend<InitialContext>();
|
|
70
|
+
expectTypeOf(input).toEqualTypeOf<unknown>();
|
|
71
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
72
|
+
expectTypeOf(procedure).toBeObject();
|
|
73
|
+
expectTypeOf(output).toBeFunction();
|
|
74
|
+
expectTypeOf(errors).toBeObject();
|
|
75
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
76
|
+
|
|
77
|
+
return next({ context: { extra: true } });
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
).toBeObject();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it(".use", () => {
|
|
84
|
+
expectTypeOf(
|
|
85
|
+
implementer.nested.use(
|
|
86
|
+
(
|
|
87
|
+
{ context, next, path, procedure, errors, signal },
|
|
88
|
+
input,
|
|
89
|
+
output,
|
|
90
|
+
) => {
|
|
91
|
+
expectTypeOf(context).toEqualTypeOf<CurrentContext>();
|
|
92
|
+
expectTypeOf(input).toEqualTypeOf<unknown>();
|
|
93
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
94
|
+
expectTypeOf(procedure).toBeObject();
|
|
95
|
+
expectTypeOf(output).toBeFunction();
|
|
96
|
+
expectTypeOf(errors).toBeObject();
|
|
97
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
98
|
+
|
|
99
|
+
return next({ context: { extra: true } });
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
).toBeObject();
|
|
103
|
+
|
|
104
|
+
const mid = {} as Middleware<
|
|
105
|
+
{ cacheable?: boolean } & Record<never, never>,
|
|
106
|
+
Record<never, never>,
|
|
107
|
+
unknown,
|
|
108
|
+
unknown,
|
|
109
|
+
any,
|
|
110
|
+
BaseMeta
|
|
111
|
+
>;
|
|
112
|
+
|
|
113
|
+
expectTypeOf(implementer.use(mid)).toExtend<
|
|
114
|
+
EffectImplementerInternal<
|
|
115
|
+
typeof typedContract,
|
|
116
|
+
InitialContext & { cacheable?: boolean },
|
|
117
|
+
Omit<CurrentContext, never> & Record<never, never>,
|
|
118
|
+
never,
|
|
119
|
+
never
|
|
120
|
+
>
|
|
121
|
+
>();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it(".router / .lazy", () => {
|
|
125
|
+
const implementedRouter = {
|
|
126
|
+
ping: implementer.ping.handler(({ input }) => ({
|
|
127
|
+
output: Number(input.input),
|
|
128
|
+
})),
|
|
129
|
+
pong: implementer.pong.handler(() => undefined),
|
|
130
|
+
nested: {
|
|
131
|
+
ping: implementer.nested.ping.handler(({ input }) => ({
|
|
132
|
+
output: Number(input.input),
|
|
133
|
+
})),
|
|
134
|
+
pong: implementer.nested.pong.handler(() => undefined),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const router = implementer.router(implementedRouter);
|
|
138
|
+
|
|
139
|
+
expectTypeOf<
|
|
140
|
+
AssertExtends<
|
|
141
|
+
typeof router,
|
|
142
|
+
Router<typeof typedContract, CurrentContext>
|
|
143
|
+
>
|
|
144
|
+
>().toEqualTypeOf<true>();
|
|
145
|
+
|
|
146
|
+
expectTypeOf(
|
|
147
|
+
implementer.lazy(async () => ({ default: implementedRouter })),
|
|
148
|
+
).toBeObject();
|
|
149
|
+
|
|
150
|
+
// @ts-expect-error - meta def is not match
|
|
151
|
+
implementer.router({ ping: {} });
|
|
152
|
+
|
|
153
|
+
// @ts-expect-error - missing implementation
|
|
154
|
+
implementer.router({ ping: implementedRouter.ping });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("each procedure is a ProcedureImplementer", () => {
|
|
159
|
+
type ExpectedPing = EffectProcedureImplementer<
|
|
160
|
+
InitialContext & Record<never, never>,
|
|
161
|
+
CurrentContext,
|
|
162
|
+
typeof inputSchema,
|
|
163
|
+
typeof outputSchema,
|
|
164
|
+
typeof baseErrorMap,
|
|
165
|
+
BaseMeta,
|
|
166
|
+
never,
|
|
167
|
+
never
|
|
168
|
+
>;
|
|
169
|
+
|
|
170
|
+
type ExpectedPong = EffectProcedureImplementer<
|
|
171
|
+
InitialContext & Record<never, never>,
|
|
172
|
+
CurrentContext,
|
|
173
|
+
Schema<unknown, unknown>,
|
|
174
|
+
Schema<unknown, unknown>,
|
|
175
|
+
Record<never, never>,
|
|
176
|
+
Meta,
|
|
177
|
+
never,
|
|
178
|
+
never
|
|
179
|
+
>;
|
|
180
|
+
|
|
181
|
+
expectTypeOf(implementer.ping).toExtend<ExpectedPing>();
|
|
182
|
+
expectTypeOf(implementer.nested.ping).toExtend<ExpectedPing>();
|
|
183
|
+
expectTypeOf(implementer.pong).toExtend<ExpectedPong>();
|
|
184
|
+
expectTypeOf(implementer.nested.pong).toExtend<ExpectedPong>();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("procedure .use preserves the leaf error map", () => {
|
|
188
|
+
implementer.ping.use(({ errors, next }) => {
|
|
189
|
+
expectTypeOf<
|
|
190
|
+
AssertExtends<
|
|
191
|
+
ReturnType<typeof errors.BASE>,
|
|
192
|
+
ORPCError<"BASE", { output: number }>
|
|
193
|
+
>
|
|
194
|
+
>().toEqualTypeOf<true>();
|
|
195
|
+
expectTypeOf<
|
|
196
|
+
AssertExtends<
|
|
197
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
198
|
+
ORPCError<"OVERRIDE", any>
|
|
199
|
+
>
|
|
200
|
+
>().toEqualTypeOf<true>();
|
|
201
|
+
// @ts-expect-error - leaf middleware errors should not widen to arbitrary keys
|
|
202
|
+
expect(errors.MISSING).toBeUndefined();
|
|
203
|
+
|
|
204
|
+
return next({});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
implementer.ping.use(
|
|
208
|
+
({ errors, next }) => {
|
|
209
|
+
expectTypeOf<
|
|
210
|
+
AssertExtends<
|
|
211
|
+
ReturnType<typeof errors.BASE>,
|
|
212
|
+
ORPCError<"BASE", { output: number }>
|
|
213
|
+
>
|
|
214
|
+
>().toEqualTypeOf<true>();
|
|
215
|
+
expectTypeOf<
|
|
216
|
+
AssertExtends<
|
|
217
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
218
|
+
ORPCError<"OVERRIDE", any>
|
|
219
|
+
>
|
|
220
|
+
>().toEqualTypeOf<true>();
|
|
221
|
+
// @ts-expect-error - mapped input overload should keep the same leaf error map
|
|
222
|
+
expect(errors.MISSING).toBeUndefined();
|
|
223
|
+
|
|
224
|
+
return next({});
|
|
225
|
+
},
|
|
226
|
+
() => ({ mapped: true }),
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("procedure .handler preserves upstream handler option typing", () => {
|
|
231
|
+
implementer.ping.handler(
|
|
232
|
+
({ context, input, path, procedure, errors, signal }) => {
|
|
233
|
+
expectTypeOf(context).toExtend<CurrentContext>();
|
|
234
|
+
expectTypeOf(input).toEqualTypeOf<{ input: string }>();
|
|
235
|
+
expectTypeOf(path).toExtend<readonly string[]>();
|
|
236
|
+
expectTypeOf(procedure).toBeObject();
|
|
237
|
+
expectTypeOf(signal).toExtend<AbortSignal | undefined>();
|
|
238
|
+
expectTypeOf<
|
|
239
|
+
AssertExtends<
|
|
240
|
+
ReturnType<typeof errors.BASE>,
|
|
241
|
+
ORPCError<"BASE", { output: number }>
|
|
242
|
+
>
|
|
243
|
+
>().toEqualTypeOf<true>();
|
|
244
|
+
expectTypeOf<
|
|
245
|
+
AssertExtends<
|
|
246
|
+
ReturnType<typeof errors.OVERRIDE>,
|
|
247
|
+
ORPCError<"OVERRIDE", any>
|
|
248
|
+
>
|
|
249
|
+
>().toEqualTypeOf<true>();
|
|
250
|
+
// @ts-expect-error - handler errors should not widen beyond the leaf error map
|
|
251
|
+
expect(errors.MISSING).toBeUndefined();
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
output: Number(input.input),
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
implementer.ping.handler(
|
|
260
|
+
// @ts-expect-error - handler output must match the contract output shape
|
|
261
|
+
() => ({ output: "wrong" }),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
implementer.ping.handler(
|
|
265
|
+
// @ts-expect-error - handler input should be the parsed contract input
|
|
266
|
+
({ input }: { input: { invalid: true } }) => ({
|
|
267
|
+
output: Number(input.invalid),
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("procedure leaves do not expose .middleware", () => {
|
|
273
|
+
expect(
|
|
274
|
+
(implementer.ping as { middleware?: unknown }).middleware,
|
|
275
|
+
).toBeUndefined();
|
|
276
|
+
// @ts-expect-error - procedure implementer leaves should not expose middleware
|
|
277
|
+
expect(implementer.ping.middleware).toBeUndefined();
|
|
278
|
+
expectTypeOf(implementer.nested).toHaveProperty("middleware");
|
|
279
|
+
});
|
|
280
|
+
});
|