@dokploy/trpc-openapi 0.0.2 → 0.0.4

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 (68) hide show
  1. package/dist/generator/paths.d.ts.map +1 -1
  2. package/dist/generator/paths.js +2 -2
  3. package/dist/generator/paths.js.map +1 -1
  4. package/dist/generator/schema.d.ts.map +1 -1
  5. package/dist/generator/schema.js +3 -13
  6. package/dist/generator/schema.js.map +1 -1
  7. package/dist/types.d.ts +12 -10
  8. package/dist/types.d.ts.map +1 -1
  9. package/dist/utils/procedure.d.ts +4 -3
  10. package/dist/utils/procedure.d.ts.map +1 -1
  11. package/dist/utils/procedure.js +30 -7
  12. package/dist/utils/procedure.js.map +1 -1
  13. package/package.json +14 -9
  14. package/assets/trpc-openapi-graph.png +0 -0
  15. package/assets/trpc-openapi-readme.png +0 -0
  16. package/assets/trpc-openapi.svg +0 -4
  17. package/examples/with-express/README.md +0 -11
  18. package/examples/with-express/package.json +0 -28
  19. package/examples/with-express/src/database.ts +0 -67
  20. package/examples/with-express/src/index.ts +0 -27
  21. package/examples/with-express/src/openapi.ts +0 -13
  22. package/examples/with-express/src/router.ts +0 -424
  23. package/examples/with-express/tsconfig.json +0 -102
  24. package/examples/with-interop/README.md +0 -10
  25. package/examples/with-interop/package.json +0 -13
  26. package/examples/with-interop/src/index.ts +0 -17
  27. package/examples/with-interop/tsconfig.json +0 -103
  28. package/examples/with-nextjs/.eslintrc.json +0 -3
  29. package/examples/with-nextjs/README.md +0 -12
  30. package/examples/with-nextjs/next-env.d.ts +0 -5
  31. package/examples/with-nextjs/next.config.js +0 -6
  32. package/examples/with-nextjs/package.json +0 -33
  33. package/examples/with-nextjs/public/favicon.ico +0 -0
  34. package/examples/with-nextjs/src/pages/_app.tsx +0 -7
  35. package/examples/with-nextjs/src/pages/api/[...trpc].ts +0 -18
  36. package/examples/with-nextjs/src/pages/api/openapi.json.ts +0 -10
  37. package/examples/with-nextjs/src/pages/api/trpc/[...trpc].ts +0 -9
  38. package/examples/with-nextjs/src/pages/index.tsx +0 -12
  39. package/examples/with-nextjs/src/server/database.ts +0 -67
  40. package/examples/with-nextjs/src/server/openapi.ts +0 -13
  41. package/examples/with-nextjs/src/server/router.ts +0 -425
  42. package/examples/with-nextjs/tsconfig.json +0 -24
  43. package/jest.config.ts +0 -12
  44. package/pnpm-workspace.yaml +0 -7
  45. package/src/adapters/express.ts +0 -20
  46. package/src/adapters/index.ts +0 -3
  47. package/src/adapters/next.ts +0 -64
  48. package/src/adapters/node-http/core.ts +0 -203
  49. package/src/adapters/node-http/errors.ts +0 -45
  50. package/src/adapters/node-http/input.ts +0 -76
  51. package/src/adapters/node-http/procedures.ts +0 -64
  52. package/src/adapters/standalone.ts +0 -19
  53. package/src/generator/index.ts +0 -51
  54. package/src/generator/paths.ts +0 -127
  55. package/src/generator/schema.ts +0 -238
  56. package/src/index.ts +0 -42
  57. package/src/types.ts +0 -79
  58. package/src/utils/method.ts +0 -8
  59. package/src/utils/path.ts +0 -12
  60. package/src/utils/procedure.ts +0 -45
  61. package/src/utils/zod.ts +0 -115
  62. package/test/adapters/express.test.ts +0 -150
  63. package/test/adapters/next.test.ts +0 -162
  64. package/test/adapters/standalone.test.ts +0 -1335
  65. package/test/generator.test.ts +0 -2897
  66. package/tsconfig.build.json +0 -4
  67. package/tsconfig.eslint.json +0 -5
  68. package/tsconfig.json +0 -19
@@ -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;
@@ -1,8 +0,0 @@
1
- import { OpenApiMethod } from '../types';
2
-
3
- export const acceptsRequestBody = (method: OpenApiMethod) => {
4
- if (method === 'GET' || method === 'DELETE') {
5
- return false;
6
- }
7
- return true;
8
- };
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
- };
@@ -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
- });