@dokploy/trpc-openapi 0.0.2 → 0.0.3
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/dist/generator/paths.d.ts.map +1 -1
- package/dist/generator/paths.js +2 -2
- package/dist/generator/paths.js.map +1 -1
- package/dist/generator/schema.d.ts.map +1 -1
- package/dist/generator/schema.js +3 -13
- package/dist/generator/schema.js.map +1 -1
- package/dist/types.d.ts +12 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/procedure.d.ts +4 -3
- package/dist/utils/procedure.d.ts.map +1 -1
- package/dist/utils/procedure.js +29 -7
- package/dist/utils/procedure.js.map +1 -1
- package/package.json +14 -9
- package/assets/trpc-openapi-graph.png +0 -0
- package/assets/trpc-openapi-readme.png +0 -0
- package/assets/trpc-openapi.svg +0 -4
- package/examples/with-express/README.md +0 -11
- package/examples/with-express/package.json +0 -28
- package/examples/with-express/src/database.ts +0 -67
- package/examples/with-express/src/index.ts +0 -27
- package/examples/with-express/src/openapi.ts +0 -13
- package/examples/with-express/src/router.ts +0 -424
- package/examples/with-express/tsconfig.json +0 -102
- package/examples/with-interop/README.md +0 -10
- package/examples/with-interop/package.json +0 -13
- package/examples/with-interop/src/index.ts +0 -17
- package/examples/with-interop/tsconfig.json +0 -103
- package/examples/with-nextjs/.eslintrc.json +0 -3
- package/examples/with-nextjs/README.md +0 -12
- package/examples/with-nextjs/next-env.d.ts +0 -5
- package/examples/with-nextjs/next.config.js +0 -6
- package/examples/with-nextjs/package.json +0 -33
- package/examples/with-nextjs/public/favicon.ico +0 -0
- package/examples/with-nextjs/src/pages/_app.tsx +0 -7
- package/examples/with-nextjs/src/pages/api/[...trpc].ts +0 -18
- package/examples/with-nextjs/src/pages/api/openapi.json.ts +0 -10
- package/examples/with-nextjs/src/pages/api/trpc/[...trpc].ts +0 -9
- package/examples/with-nextjs/src/pages/index.tsx +0 -12
- package/examples/with-nextjs/src/server/database.ts +0 -67
- package/examples/with-nextjs/src/server/openapi.ts +0 -13
- package/examples/with-nextjs/src/server/router.ts +0 -425
- package/examples/with-nextjs/tsconfig.json +0 -24
- package/jest.config.ts +0 -12
- package/pnpm-workspace.yaml +0 -7
- package/src/adapters/express.ts +0 -20
- package/src/adapters/index.ts +0 -3
- package/src/adapters/next.ts +0 -64
- package/src/adapters/node-http/core.ts +0 -203
- package/src/adapters/node-http/errors.ts +0 -45
- package/src/adapters/node-http/input.ts +0 -76
- package/src/adapters/node-http/procedures.ts +0 -64
- package/src/adapters/standalone.ts +0 -19
- package/src/generator/index.ts +0 -51
- package/src/generator/paths.ts +0 -127
- package/src/generator/schema.ts +0 -238
- package/src/index.ts +0 -42
- package/src/types.ts +0 -79
- package/src/utils/method.ts +0 -8
- package/src/utils/path.ts +0 -12
- package/src/utils/procedure.ts +0 -45
- package/src/utils/zod.ts +0 -115
- package/test/adapters/express.test.ts +0 -150
- package/test/adapters/next.test.ts +0 -162
- package/test/adapters/standalone.test.ts +0 -1335
- package/test/generator.test.ts +0 -2897
- package/tsconfig.build.json +0 -4
- package/tsconfig.eslint.json +0 -5
- package/tsconfig.json +0 -19
package/src/generator/schema.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { TRPCError } from "@trpc/server";
|
|
3
|
-
import { OpenAPIV3 } from "openapi-types";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import zodToJsonSchema from "zod-to-json-schema";
|
|
6
|
-
|
|
7
|
-
import { OpenApiContentType } from "../types";
|
|
8
|
-
import {
|
|
9
|
-
instanceofZodType,
|
|
10
|
-
instanceofZodTypeCoercible,
|
|
11
|
-
instanceofZodTypeLikeString,
|
|
12
|
-
instanceofZodTypeLikeVoid,
|
|
13
|
-
instanceofZodTypeObject,
|
|
14
|
-
instanceofZodTypeOptional,
|
|
15
|
-
unwrapZodType,
|
|
16
|
-
zodSupportsCoerce,
|
|
17
|
-
} from "../utils/zod";
|
|
18
|
-
|
|
19
|
-
const zodSchemaToOpenApiSchemaObject = (
|
|
20
|
-
zodSchema: z.ZodType,
|
|
21
|
-
): OpenAPIV3.SchemaObject => {
|
|
22
|
-
// FIXME: https://github.com/StefanTerdell/zod-to-json-schema/issues/35
|
|
23
|
-
return zodToJsonSchema(zodSchema, {
|
|
24
|
-
target: "openApi3",
|
|
25
|
-
$refStrategy: "none",
|
|
26
|
-
}) as any;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const getParameterObjects = (
|
|
30
|
-
currentSchema: unknown,
|
|
31
|
-
pathParameters: string[],
|
|
32
|
-
inType: "all" | "path" | "query",
|
|
33
|
-
example: Record<string, any> | undefined,
|
|
34
|
-
): OpenAPIV3.ParameterObject[] | undefined => {
|
|
35
|
-
const schema = currentSchema ?? z.void();
|
|
36
|
-
const isRequired = !schema.isOptional();
|
|
37
|
-
const unwrappedSchema = unwrapZodType(schema, true);
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
pathParameters.length === 0 &&
|
|
41
|
-
instanceofZodTypeLikeVoid(unwrappedSchema)
|
|
42
|
-
) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!instanceofZodTypeObject(unwrappedSchema)) {
|
|
47
|
-
throw new TRPCError({
|
|
48
|
-
message: "Input parser must be a ZodObject",
|
|
49
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const shape = unwrappedSchema.shape;
|
|
54
|
-
const shapeKeys = Object.keys(shape);
|
|
55
|
-
|
|
56
|
-
for (const pathParameter of pathParameters) {
|
|
57
|
-
if (!shapeKeys.includes(pathParameter)) {
|
|
58
|
-
throw new TRPCError({
|
|
59
|
-
message: `Input parser expects key from path: "${pathParameter}"`,
|
|
60
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return shapeKeys
|
|
66
|
-
.filter((shapeKey) => {
|
|
67
|
-
const isPathParameter = pathParameters.includes(shapeKey);
|
|
68
|
-
if (inType === "path") {
|
|
69
|
-
return isPathParameter;
|
|
70
|
-
} else if (inType === "query") {
|
|
71
|
-
return !isPathParameter;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
})
|
|
75
|
-
.map((shapeKey) => {
|
|
76
|
-
let shapeSchema = shape[shapeKey]!;
|
|
77
|
-
const isShapeRequired = !shapeSchema.isOptional();
|
|
78
|
-
const isPathParameter = pathParameters.includes(shapeKey);
|
|
79
|
-
|
|
80
|
-
if (!instanceofZodTypeLikeString(shapeSchema)) {
|
|
81
|
-
if (zodSupportsCoerce) {
|
|
82
|
-
if (!instanceofZodTypeCoercible(shapeSchema)) {
|
|
83
|
-
throw new TRPCError({
|
|
84
|
-
message: `Input parser key: "${shapeKey}" must be ZodString, ZodNumber, ZodBoolean, ZodBigInt or ZodDate`,
|
|
85
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
throw new TRPCError({
|
|
90
|
-
message: `Input parser key: "${shapeKey}" must be ZodString`,
|
|
91
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (instanceofZodTypeOptional(shapeSchema)) {
|
|
97
|
-
if (isPathParameter) {
|
|
98
|
-
throw new TRPCError({
|
|
99
|
-
message: `Path parameter: "${shapeKey}" must not be optional`,
|
|
100
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
shapeSchema = shapeSchema.unwrap();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const { description, ...openApiSchemaObject } =
|
|
107
|
-
zodSchemaToOpenApiSchemaObject(shapeSchema);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
name: shapeKey,
|
|
111
|
-
in: isPathParameter ? "path" : "query",
|
|
112
|
-
required: isPathParameter || (isRequired && isShapeRequired),
|
|
113
|
-
schema: openApiSchemaObject,
|
|
114
|
-
description: description,
|
|
115
|
-
example: example?.[shapeKey],
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export const getRequestBodyObject = (
|
|
121
|
-
currentSchema: unknown,
|
|
122
|
-
pathParameters: string[],
|
|
123
|
-
contentTypes: OpenApiContentType[],
|
|
124
|
-
example: Record<string, any> | undefined,
|
|
125
|
-
): OpenAPIV3.RequestBodyObject | undefined => {
|
|
126
|
-
// if (!instanceofZodType(schema)) {
|
|
127
|
-
// throw new TRPCError({
|
|
128
|
-
// message: "Input parser expects a Zod validator",
|
|
129
|
-
// code: "INTERNAL_SERVER_ERROR",
|
|
130
|
-
// });
|
|
131
|
-
// }
|
|
132
|
-
|
|
133
|
-
const schema = currentSchema ?? z.void();
|
|
134
|
-
|
|
135
|
-
const isRequired = !schema.isOptional();
|
|
136
|
-
const unwrappedSchema = unwrapZodType(schema, true);
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
pathParameters.length === 0 &&
|
|
140
|
-
instanceofZodTypeLikeVoid(unwrappedSchema)
|
|
141
|
-
) {
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!instanceofZodTypeObject(unwrappedSchema)) {
|
|
146
|
-
throw new TRPCError({
|
|
147
|
-
message: "Input parser must be a ZodObject",
|
|
148
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// remove path parameters
|
|
153
|
-
const mask: Record<string, true> = {};
|
|
154
|
-
const dedupedExample = example && { ...example };
|
|
155
|
-
pathParameters.forEach((pathParameter) => {
|
|
156
|
-
mask[pathParameter] = true;
|
|
157
|
-
if (dedupedExample) {
|
|
158
|
-
delete dedupedExample[pathParameter];
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
const dedupedSchema = unwrappedSchema.omit(mask);
|
|
162
|
-
|
|
163
|
-
// if all keys are path parameters
|
|
164
|
-
if (
|
|
165
|
-
pathParameters.length > 0 &&
|
|
166
|
-
Object.keys(dedupedSchema.shape).length === 0
|
|
167
|
-
) {
|
|
168
|
-
return undefined;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const openApiSchemaObject = zodSchemaToOpenApiSchemaObject(dedupedSchema);
|
|
172
|
-
const content: OpenAPIV3.RequestBodyObject["content"] = {};
|
|
173
|
-
for (const contentType of contentTypes) {
|
|
174
|
-
content[contentType] = {
|
|
175
|
-
schema: openApiSchemaObject,
|
|
176
|
-
example: dedupedExample,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
required: isRequired,
|
|
182
|
-
content,
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
export const errorResponseObject: OpenAPIV3.ResponseObject = {
|
|
186
|
-
description: "Error response",
|
|
187
|
-
content: {
|
|
188
|
-
"application/json": {
|
|
189
|
-
schema: zodSchemaToOpenApiSchemaObject(
|
|
190
|
-
z.object({
|
|
191
|
-
message: z.string(),
|
|
192
|
-
code: z.string(),
|
|
193
|
-
issues: z.array(z.object({ message: z.string() })).optional(),
|
|
194
|
-
}),
|
|
195
|
-
),
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
export const getResponsesObject = (
|
|
201
|
-
schema: unknown,
|
|
202
|
-
example: Record<string, any> | undefined,
|
|
203
|
-
headers:
|
|
204
|
-
| Record<string, OpenAPIV3.HeaderObject | OpenAPIV3.ReferenceObject>
|
|
205
|
-
| undefined,
|
|
206
|
-
): OpenAPIV3.ResponsesObject => {
|
|
207
|
-
// if (!instanceofZodType(schema)) {
|
|
208
|
-
// throw new TRPCError({
|
|
209
|
-
// message: 'Output parser expects a Zod validator',
|
|
210
|
-
// code: 'INTERNAL_SERVER_ERROR',
|
|
211
|
-
// });
|
|
212
|
-
// }
|
|
213
|
-
|
|
214
|
-
console.log(schema);
|
|
215
|
-
|
|
216
|
-
const successResponseObject: OpenAPIV3.ResponseObject = {
|
|
217
|
-
description: "Successful response",
|
|
218
|
-
headers: headers,
|
|
219
|
-
content: {
|
|
220
|
-
"application/json": {
|
|
221
|
-
...(schema && {
|
|
222
|
-
schema: zodSchemaToOpenApiSchemaObject(schema),
|
|
223
|
-
}),
|
|
224
|
-
// schema: zodSchemaToOpenApiSchemaObject(schema),
|
|
225
|
-
example,
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// console.log(zodSchemaToOpenApiSchemaObject(schema));
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
200: successResponseObject,
|
|
234
|
-
default: {
|
|
235
|
-
$ref: "#/components/responses/error",
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CreateOpenApiExpressMiddlewareOptions,
|
|
3
|
-
CreateOpenApiHttpHandlerOptions,
|
|
4
|
-
CreateOpenApiNextHandlerOptions,
|
|
5
|
-
createOpenApiExpressMiddleware,
|
|
6
|
-
createOpenApiHttpHandler,
|
|
7
|
-
createOpenApiNextHandler,
|
|
8
|
-
} from "./adapters";
|
|
9
|
-
import {
|
|
10
|
-
GenerateOpenApiDocumentOptions,
|
|
11
|
-
generateOpenApiDocument,
|
|
12
|
-
openApiVersion,
|
|
13
|
-
} from "./generator";
|
|
14
|
-
import {
|
|
15
|
-
OpenApiErrorResponse,
|
|
16
|
-
OpenApiMeta,
|
|
17
|
-
OpenApiMethod,
|
|
18
|
-
OpenApiResponse,
|
|
19
|
-
OpenApiRouter,
|
|
20
|
-
OpenApiSuccessResponse,
|
|
21
|
-
} from "./types";
|
|
22
|
-
import { ZodTypeLikeString, ZodTypeLikeVoid } from "./utils/zod";
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
CreateOpenApiExpressMiddlewareOptions,
|
|
26
|
-
CreateOpenApiHttpHandlerOptions,
|
|
27
|
-
CreateOpenApiNextHandlerOptions,
|
|
28
|
-
createOpenApiExpressMiddleware,
|
|
29
|
-
createOpenApiHttpHandler,
|
|
30
|
-
createOpenApiNextHandler,
|
|
31
|
-
openApiVersion,
|
|
32
|
-
generateOpenApiDocument,
|
|
33
|
-
GenerateOpenApiDocumentOptions,
|
|
34
|
-
OpenApiRouter,
|
|
35
|
-
OpenApiMeta,
|
|
36
|
-
OpenApiMethod,
|
|
37
|
-
OpenApiResponse,
|
|
38
|
-
OpenApiSuccessResponse,
|
|
39
|
-
OpenApiErrorResponse,
|
|
40
|
-
ZodTypeLikeString,
|
|
41
|
-
ZodTypeLikeVoid,
|
|
42
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { Procedure, ProcedureParams, Router } from '@trpc/server';
|
|
2
|
-
import type { RootConfig } from '@trpc/server/dist/core/internals/config';
|
|
3
|
-
import { TRPC_ERROR_CODE_KEY } from '@trpc/server/rpc';
|
|
4
|
-
import type { RouterDef } from '@trpc/server/src/core/router';
|
|
5
|
-
import { OpenAPIV3 } from 'openapi-types';
|
|
6
|
-
import { ZodIssue } from 'zod';
|
|
7
|
-
|
|
8
|
-
export type OpenApiMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
9
|
-
|
|
10
|
-
type TRPCMeta = Record<string, unknown>;
|
|
11
|
-
|
|
12
|
-
export type OpenApiContentType =
|
|
13
|
-
| 'application/json'
|
|
14
|
-
| 'application/x-www-form-urlencoded'
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
16
|
-
| (string & {});
|
|
17
|
-
|
|
18
|
-
export type OpenApiMeta<TMeta = TRPCMeta> = TMeta & {
|
|
19
|
-
openapi?: {
|
|
20
|
-
enabled?: boolean;
|
|
21
|
-
method: OpenApiMethod;
|
|
22
|
-
path: `/${string}`;
|
|
23
|
-
summary?: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
protect?: boolean;
|
|
26
|
-
tags?: string[];
|
|
27
|
-
headers?: (OpenAPIV3.ParameterBaseObject & { name: string; in?: 'header' })[];
|
|
28
|
-
contentTypes?: OpenApiContentType[];
|
|
29
|
-
deprecated?: boolean;
|
|
30
|
-
example?: {
|
|
31
|
-
request?: Record<string, any>;
|
|
32
|
-
response?: Record<string, any>;
|
|
33
|
-
};
|
|
34
|
-
responseHeaders?: Record<string, OpenAPIV3.HeaderObject | OpenAPIV3.ReferenceObject>;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type OpenApiProcedure<TMeta = TRPCMeta> = Procedure<
|
|
39
|
-
'query' | 'mutation',
|
|
40
|
-
ProcedureParams<
|
|
41
|
-
RootConfig<{
|
|
42
|
-
transformer: any;
|
|
43
|
-
errorShape: any;
|
|
44
|
-
ctx: any;
|
|
45
|
-
meta: OpenApiMeta<TMeta>;
|
|
46
|
-
}>,
|
|
47
|
-
any,
|
|
48
|
-
any,
|
|
49
|
-
any,
|
|
50
|
-
any,
|
|
51
|
-
any,
|
|
52
|
-
OpenApiMeta<TMeta>
|
|
53
|
-
>
|
|
54
|
-
>;
|
|
55
|
-
|
|
56
|
-
export type OpenApiProcedureRecord<TMeta = TRPCMeta> = Record<string, OpenApiProcedure<TMeta>>;
|
|
57
|
-
|
|
58
|
-
export type OpenApiRouter<TMeta = TRPCMeta> = Router<
|
|
59
|
-
RouterDef<
|
|
60
|
-
RootConfig<{
|
|
61
|
-
transformer: any;
|
|
62
|
-
errorShape: any;
|
|
63
|
-
ctx: any;
|
|
64
|
-
meta: OpenApiMeta<TMeta>;
|
|
65
|
-
}>,
|
|
66
|
-
any,
|
|
67
|
-
any
|
|
68
|
-
>
|
|
69
|
-
>;
|
|
70
|
-
|
|
71
|
-
export type OpenApiSuccessResponse<D = any> = D;
|
|
72
|
-
|
|
73
|
-
export type OpenApiErrorResponse = {
|
|
74
|
-
message: string;
|
|
75
|
-
code: TRPC_ERROR_CODE_KEY;
|
|
76
|
-
issues?: ZodIssue[];
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export type OpenApiResponse<D = any> = OpenApiSuccessResponse<D> | OpenApiErrorResponse;
|
package/src/utils/method.ts
DELETED
package/src/utils/path.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const normalizePath = (path: string) => {
|
|
2
|
-
return `/${path.replace(/^\/|\/$/g, '')}`;
|
|
3
|
-
};
|
|
4
|
-
|
|
5
|
-
export const getPathParameters = (path: string) => {
|
|
6
|
-
return Array.from(path.matchAll(/\{(.+?)\}/g)).map(([_, key]) => key!);
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const getPathRegExp = (path: string) => {
|
|
10
|
-
const groupedExp = path.replace(/\{(.+?)\}/g, (_, key: string) => `(?<${key}>[^/]+)`);
|
|
11
|
-
return new RegExp(`^${groupedExp}$`, 'i');
|
|
12
|
-
};
|
package/src/utils/procedure.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-unresolved
|
|
2
|
-
import { ProcedureType } from '@trpc/server';
|
|
3
|
-
import { AnyZodObject, z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { OpenApiMeta, OpenApiProcedure, OpenApiProcedureRecord } from '../types';
|
|
6
|
-
|
|
7
|
-
const mergeInputs = (inputParsers: AnyZodObject[]): AnyZodObject => {
|
|
8
|
-
return inputParsers.reduce((acc, inputParser) => {
|
|
9
|
-
return acc.merge(inputParser);
|
|
10
|
-
}, z.object({}));
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// `inputParser` & `outputParser` are private so this is a hack to access it
|
|
14
|
-
export const getInputOutputParsers = (procedure: OpenApiProcedure) => {
|
|
15
|
-
const { inputs, output } = procedure._def;
|
|
16
|
-
return {
|
|
17
|
-
inputParser: inputs.length >= 2 ? mergeInputs(inputs as AnyZodObject[]) : inputs[0],
|
|
18
|
-
outputParser: output,
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const getProcedureType = (procedure: OpenApiProcedure): ProcedureType => {
|
|
23
|
-
if (procedure._def.query) return 'query';
|
|
24
|
-
if (procedure._def.mutation) return 'mutation';
|
|
25
|
-
if (procedure._def.subscription) return 'subscription';
|
|
26
|
-
throw new Error('Unknown procedure type');
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const forEachOpenApiProcedure = (
|
|
30
|
-
procedureRecord: OpenApiProcedureRecord,
|
|
31
|
-
callback: (values: {
|
|
32
|
-
path: string;
|
|
33
|
-
type: ProcedureType;
|
|
34
|
-
procedure: OpenApiProcedure;
|
|
35
|
-
openapi: NonNullable<OpenApiMeta['openapi']>;
|
|
36
|
-
}) => void,
|
|
37
|
-
) => {
|
|
38
|
-
for (const [path, procedure] of Object.entries(procedureRecord)) {
|
|
39
|
-
const { openapi } = procedure._def.meta ?? {};
|
|
40
|
-
if (openapi && openapi.enabled !== false) {
|
|
41
|
-
const type = getProcedureType(procedure);
|
|
42
|
-
callback({ path, type, procedure, openapi });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
};
|
package/src/utils/zod.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
export const instanceofZodType = (type: any): type is z.ZodTypeAny => {
|
|
4
|
-
return !!type?._def?.typeName;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const instanceofZodTypeKind = <Z extends z.ZodFirstPartyTypeKind>(
|
|
8
|
-
type: z.ZodTypeAny,
|
|
9
|
-
zodTypeKind: Z,
|
|
10
|
-
): type is InstanceType<typeof z[Z]> => {
|
|
11
|
-
return type?._def?.typeName === zodTypeKind;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const instanceofZodTypeOptional = (
|
|
15
|
-
type: z.ZodTypeAny,
|
|
16
|
-
): type is z.ZodOptional<z.ZodTypeAny> => {
|
|
17
|
-
return instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodOptional);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const instanceofZodTypeObject = (type: z.ZodTypeAny): type is z.ZodObject<z.ZodRawShape> => {
|
|
21
|
-
return instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodObject);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type ZodTypeLikeVoid = z.ZodVoid | z.ZodUndefined | z.ZodNever;
|
|
25
|
-
|
|
26
|
-
export const instanceofZodTypeLikeVoid = (type: z.ZodTypeAny): type is ZodTypeLikeVoid => {
|
|
27
|
-
return (
|
|
28
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodVoid) ||
|
|
29
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodUndefined) ||
|
|
30
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodNever)
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const unwrapZodType = (type: z.ZodTypeAny, unwrapPreprocess: boolean): z.ZodTypeAny => {
|
|
35
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodOptional)) {
|
|
36
|
-
return unwrapZodType(type.unwrap(), unwrapPreprocess);
|
|
37
|
-
}
|
|
38
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodDefault)) {
|
|
39
|
-
return unwrapZodType(type.removeDefault(), unwrapPreprocess);
|
|
40
|
-
}
|
|
41
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodLazy)) {
|
|
42
|
-
return unwrapZodType(type._def.getter(), unwrapPreprocess);
|
|
43
|
-
}
|
|
44
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodEffects)) {
|
|
45
|
-
if (type._def.effect.type === 'refinement') {
|
|
46
|
-
return unwrapZodType(type._def.schema, unwrapPreprocess);
|
|
47
|
-
}
|
|
48
|
-
if (type._def.effect.type === 'transform') {
|
|
49
|
-
return unwrapZodType(type._def.schema, unwrapPreprocess);
|
|
50
|
-
}
|
|
51
|
-
if (unwrapPreprocess && type._def.effect.type === 'preprocess') {
|
|
52
|
-
return unwrapZodType(type._def.schema, unwrapPreprocess);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return type;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type NativeEnumType = {
|
|
59
|
-
[k: string]: string | number;
|
|
60
|
-
[nu: number]: string;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export type ZodTypeLikeString =
|
|
64
|
-
| z.ZodString
|
|
65
|
-
| z.ZodOptional<ZodTypeLikeString>
|
|
66
|
-
| z.ZodDefault<ZodTypeLikeString>
|
|
67
|
-
| z.ZodEffects<ZodTypeLikeString, unknown, unknown>
|
|
68
|
-
| z.ZodUnion<[ZodTypeLikeString, ...ZodTypeLikeString[]]>
|
|
69
|
-
| z.ZodIntersection<ZodTypeLikeString, ZodTypeLikeString>
|
|
70
|
-
| z.ZodLazy<ZodTypeLikeString>
|
|
71
|
-
| z.ZodLiteral<string>
|
|
72
|
-
| z.ZodEnum<[string, ...string[]]>
|
|
73
|
-
| z.ZodNativeEnum<NativeEnumType>;
|
|
74
|
-
|
|
75
|
-
export const instanceofZodTypeLikeString = (_type: z.ZodTypeAny): _type is ZodTypeLikeString => {
|
|
76
|
-
const type = unwrapZodType(_type, false);
|
|
77
|
-
|
|
78
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodEffects)) {
|
|
79
|
-
if (type._def.effect.type === 'preprocess') {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodUnion)) {
|
|
84
|
-
return !type._def.options.some((option) => !instanceofZodTypeLikeString(option));
|
|
85
|
-
}
|
|
86
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodIntersection)) {
|
|
87
|
-
return (
|
|
88
|
-
instanceofZodTypeLikeString(type._def.left) && instanceofZodTypeLikeString(type._def.right)
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodLiteral)) {
|
|
92
|
-
return typeof type._def.value === 'string';
|
|
93
|
-
}
|
|
94
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodEnum)) {
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodNativeEnum)) {
|
|
98
|
-
return !Object.values(type._def.values).some((value) => typeof value === 'number');
|
|
99
|
-
}
|
|
100
|
-
return instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodString);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const zodSupportsCoerce = 'coerce' in z;
|
|
104
|
-
|
|
105
|
-
export type ZodTypeCoercible = z.ZodNumber | z.ZodBoolean | z.ZodBigInt | z.ZodDate;
|
|
106
|
-
|
|
107
|
-
export const instanceofZodTypeCoercible = (_type: z.ZodTypeAny): _type is ZodTypeCoercible => {
|
|
108
|
-
const type = unwrapZodType(_type, false);
|
|
109
|
-
return (
|
|
110
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodNumber) ||
|
|
111
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodBoolean) ||
|
|
112
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodBigInt) ||
|
|
113
|
-
instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodDate)
|
|
114
|
-
);
|
|
115
|
-
};
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
|
-
import { initTRPC } from '@trpc/server';
|
|
3
|
-
import express from 'express';
|
|
4
|
-
import fetch from 'node-fetch';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
CreateOpenApiExpressMiddlewareOptions,
|
|
9
|
-
OpenApiMeta,
|
|
10
|
-
OpenApiRouter,
|
|
11
|
-
createOpenApiExpressMiddleware,
|
|
12
|
-
} from '../../src';
|
|
13
|
-
|
|
14
|
-
const createContextMock = jest.fn();
|
|
15
|
-
const responseMetaMock = jest.fn();
|
|
16
|
-
const onErrorMock = jest.fn();
|
|
17
|
-
|
|
18
|
-
const clearMocks = () => {
|
|
19
|
-
createContextMock.mockClear();
|
|
20
|
-
responseMetaMock.mockClear();
|
|
21
|
-
onErrorMock.mockClear();
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const createExpressServerWithRouter = <TRouter extends OpenApiRouter>(
|
|
25
|
-
handlerOpts: CreateOpenApiExpressMiddlewareOptions<TRouter>,
|
|
26
|
-
serverOpts?: { basePath?: `/${string}` },
|
|
27
|
-
) => {
|
|
28
|
-
const openApiExpressMiddleware = createOpenApiExpressMiddleware({
|
|
29
|
-
router: handlerOpts.router,
|
|
30
|
-
createContext: handlerOpts.createContext ?? createContextMock,
|
|
31
|
-
responseMeta: handlerOpts.responseMeta ?? responseMetaMock,
|
|
32
|
-
onError: handlerOpts.onError ?? onErrorMock,
|
|
33
|
-
maxBodySize: handlerOpts.maxBodySize,
|
|
34
|
-
} as any);
|
|
35
|
-
|
|
36
|
-
const app = express();
|
|
37
|
-
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
39
|
-
app.use(serverOpts?.basePath ?? '/', openApiExpressMiddleware);
|
|
40
|
-
|
|
41
|
-
const server = app.listen(0);
|
|
42
|
-
const port = (server.address() as any).port as number;
|
|
43
|
-
const url = `http://localhost:${port}`;
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
url,
|
|
47
|
-
close: () => server.close(),
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const t = initTRPC.meta<OpenApiMeta>().context<any>().create();
|
|
52
|
-
|
|
53
|
-
describe('express adapter', () => {
|
|
54
|
-
afterEach(() => {
|
|
55
|
-
clearMocks();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('with valid routes', async () => {
|
|
59
|
-
const appRouter = t.router({
|
|
60
|
-
sayHelloQuery: t.procedure
|
|
61
|
-
.meta({ openapi: { method: 'GET', path: '/say-hello' } })
|
|
62
|
-
.input(z.object({ name: z.string() }))
|
|
63
|
-
.output(z.object({ greeting: z.string() }))
|
|
64
|
-
.query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
|
|
65
|
-
sayHelloMutation: t.procedure
|
|
66
|
-
.meta({ openapi: { method: 'POST', path: '/say-hello' } })
|
|
67
|
-
.input(z.object({ name: z.string() }))
|
|
68
|
-
.output(z.object({ greeting: z.string() }))
|
|
69
|
-
.mutation(({ input }) => ({ greeting: `Hello ${input.name}!` })),
|
|
70
|
-
sayHelloSlash: t.procedure
|
|
71
|
-
.meta({ openapi: { method: 'GET', path: '/say/hello' } })
|
|
72
|
-
.input(z.object({ name: z.string() }))
|
|
73
|
-
.output(z.object({ greeting: z.string() }))
|
|
74
|
-
.query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const { url, close } = createExpressServerWithRouter({
|
|
78
|
-
router: appRouter,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
const res = await fetch(`${url}/say-hello?name=James`, { method: 'GET' });
|
|
83
|
-
const body = await res.json();
|
|
84
|
-
|
|
85
|
-
expect(res.status).toBe(200);
|
|
86
|
-
expect(body).toEqual({ greeting: 'Hello James!' });
|
|
87
|
-
expect(createContextMock).toHaveBeenCalledTimes(1);
|
|
88
|
-
expect(responseMetaMock).toHaveBeenCalledTimes(1);
|
|
89
|
-
expect(onErrorMock).toHaveBeenCalledTimes(0);
|
|
90
|
-
|
|
91
|
-
clearMocks();
|
|
92
|
-
}
|
|
93
|
-
{
|
|
94
|
-
const res = await fetch(`${url}/say-hello`, {
|
|
95
|
-
method: 'POST',
|
|
96
|
-
headers: { 'Content-Type': 'application/json' },
|
|
97
|
-
body: JSON.stringify({ name: 'James' }),
|
|
98
|
-
});
|
|
99
|
-
const body = await res.json();
|
|
100
|
-
|
|
101
|
-
expect(res.status).toBe(200);
|
|
102
|
-
expect(body).toEqual({ greeting: 'Hello James!' });
|
|
103
|
-
expect(createContextMock).toHaveBeenCalledTimes(1);
|
|
104
|
-
expect(responseMetaMock).toHaveBeenCalledTimes(1);
|
|
105
|
-
expect(onErrorMock).toHaveBeenCalledTimes(0);
|
|
106
|
-
|
|
107
|
-
clearMocks();
|
|
108
|
-
}
|
|
109
|
-
{
|
|
110
|
-
const res = await fetch(`${url}/say/hello?name=James`, { method: 'GET' });
|
|
111
|
-
const body = await res.json();
|
|
112
|
-
|
|
113
|
-
expect(res.status).toBe(200);
|
|
114
|
-
expect(body).toEqual({ greeting: 'Hello James!' });
|
|
115
|
-
expect(createContextMock).toHaveBeenCalledTimes(1);
|
|
116
|
-
expect(responseMetaMock).toHaveBeenCalledTimes(1);
|
|
117
|
-
expect(onErrorMock).toHaveBeenCalledTimes(0);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
close();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('with basePath', async () => {
|
|
124
|
-
const appRouter = t.router({
|
|
125
|
-
echo: t.procedure
|
|
126
|
-
.meta({ openapi: { method: 'GET', path: '/echo' } })
|
|
127
|
-
.input(z.object({ payload: z.string() }))
|
|
128
|
-
.output(z.object({ payload: z.string(), context: z.undefined() }))
|
|
129
|
-
.query(({ input }) => ({ payload: input.payload })),
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const { url, close } = createExpressServerWithRouter(
|
|
133
|
-
{ router: appRouter },
|
|
134
|
-
{ basePath: '/open-api' },
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const res = await fetch(`${url}/open-api/echo?payload=jlalmes`, { method: 'GET' });
|
|
138
|
-
const body = await res.json();
|
|
139
|
-
|
|
140
|
-
expect(res.status).toBe(200);
|
|
141
|
-
expect(body).toEqual({
|
|
142
|
-
payload: 'jlalmes',
|
|
143
|
-
});
|
|
144
|
-
expect(createContextMock).toHaveBeenCalledTimes(1);
|
|
145
|
-
expect(responseMetaMock).toHaveBeenCalledTimes(1);
|
|
146
|
-
expect(onErrorMock).toHaveBeenCalledTimes(0);
|
|
147
|
-
|
|
148
|
-
close();
|
|
149
|
-
});
|
|
150
|
-
});
|