@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.
- 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 +30 -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
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { type AnyProcedure, TRPCError } from "@trpc/server";
|
|
3
|
-
import type {
|
|
4
|
-
NodeHTTPHandlerOptions,
|
|
5
|
-
NodeHTTPRequest,
|
|
6
|
-
NodeHTTPResponse,
|
|
7
|
-
} from "@trpc/server/dist/adapters/node-http";
|
|
8
|
-
import cloneDeep from "lodash.clonedeep";
|
|
9
|
-
import type { ZodError, z } from "zod";
|
|
10
|
-
|
|
11
|
-
import { generateOpenApiDocument } from "../../generator";
|
|
12
|
-
import type {
|
|
13
|
-
OpenApiErrorResponse,
|
|
14
|
-
OpenApiMethod,
|
|
15
|
-
OpenApiResponse,
|
|
16
|
-
OpenApiRouter,
|
|
17
|
-
OpenApiSuccessResponse,
|
|
18
|
-
} from "../../types";
|
|
19
|
-
import { acceptsRequestBody } from "../../utils/method";
|
|
20
|
-
import { normalizePath } from "../../utils/path";
|
|
21
|
-
import { getInputOutputParsers } from "../../utils/procedure";
|
|
22
|
-
import {
|
|
23
|
-
instanceofZodTypeCoercible,
|
|
24
|
-
instanceofZodTypeLikeVoid,
|
|
25
|
-
instanceofZodTypeObject,
|
|
26
|
-
unwrapZodType,
|
|
27
|
-
zodSupportsCoerce,
|
|
28
|
-
} from "../../utils/zod";
|
|
29
|
-
import { TRPC_ERROR_CODE_HTTP_STATUS, getErrorFromUnknown } from "./errors";
|
|
30
|
-
import { getBody, getQuery } from "./input";
|
|
31
|
-
import { createProcedureCache } from "./procedures";
|
|
32
|
-
|
|
33
|
-
export type CreateOpenApiNodeHttpHandlerOptions<
|
|
34
|
-
TRouter extends OpenApiRouter,
|
|
35
|
-
TRequest extends NodeHTTPRequest,
|
|
36
|
-
TResponse extends NodeHTTPResponse,
|
|
37
|
-
> = Pick<
|
|
38
|
-
NodeHTTPHandlerOptions<TRouter, TRequest, TResponse>,
|
|
39
|
-
"router" | "createContext" | "responseMeta" | "onError" | "maxBodySize"
|
|
40
|
-
>;
|
|
41
|
-
|
|
42
|
-
export type OpenApiNextFunction = () => void;
|
|
43
|
-
|
|
44
|
-
export const createOpenApiNodeHttpHandler = <
|
|
45
|
-
TRouter extends OpenApiRouter,
|
|
46
|
-
TRequest extends NodeHTTPRequest,
|
|
47
|
-
TResponse extends NodeHTTPResponse,
|
|
48
|
-
>(
|
|
49
|
-
opts: CreateOpenApiNodeHttpHandlerOptions<TRouter, TRequest, TResponse>,
|
|
50
|
-
) => {
|
|
51
|
-
const router = cloneDeep(opts.router);
|
|
52
|
-
|
|
53
|
-
// Validate router
|
|
54
|
-
if (process.env.NODE_ENV !== "production") {
|
|
55
|
-
generateOpenApiDocument(router, { title: "", version: "", baseUrl: "" });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const { createContext, responseMeta, onError, maxBodySize } = opts;
|
|
59
|
-
const getProcedure = createProcedureCache(router);
|
|
60
|
-
|
|
61
|
-
return async (req: TRequest, res: TResponse, next?: OpenApiNextFunction) => {
|
|
62
|
-
const sendResponse = (
|
|
63
|
-
statusCode: number,
|
|
64
|
-
headers: Record<string, string>,
|
|
65
|
-
body: OpenApiResponse | undefined,
|
|
66
|
-
) => {
|
|
67
|
-
res.statusCode = statusCode;
|
|
68
|
-
res.setHeader("Content-Type", "application/json");
|
|
69
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
70
|
-
if (typeof value !== "undefined") {
|
|
71
|
-
res.setHeader(key, value);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
res.end(JSON.stringify(body));
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const method = req.method! as OpenApiMethod & "HEAD";
|
|
78
|
-
const reqUrl = req.url!;
|
|
79
|
-
const url = new URL(
|
|
80
|
-
reqUrl.startsWith("/") ? `http://127.0.0.1${reqUrl}` : reqUrl,
|
|
81
|
-
);
|
|
82
|
-
const path = normalizePath(url.pathname);
|
|
83
|
-
const { procedure, pathInput } = getProcedure(method, path) ?? {};
|
|
84
|
-
|
|
85
|
-
let input: any = undefined;
|
|
86
|
-
let ctx: any = undefined;
|
|
87
|
-
let data: any = undefined;
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
if (!procedure) {
|
|
91
|
-
if (next) {
|
|
92
|
-
return next();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Can be used for warmup
|
|
96
|
-
if (method === "HEAD") {
|
|
97
|
-
sendResponse(204, {}, undefined);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new TRPCError({
|
|
102
|
-
message: "Not found",
|
|
103
|
-
code: "NOT_FOUND",
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const useBody = acceptsRequestBody(method);
|
|
108
|
-
const schema = getInputOutputParsers(procedure.procedure)
|
|
109
|
-
.inputParser as z.ZodTypeAny;
|
|
110
|
-
const unwrappedSchema = unwrapZodType(schema, true);
|
|
111
|
-
|
|
112
|
-
// input should stay undefined if z.void()
|
|
113
|
-
if (!instanceofZodTypeLikeVoid(unwrappedSchema)) {
|
|
114
|
-
input = {
|
|
115
|
-
...(useBody ? await getBody(req, maxBodySize) : getQuery(req, url)),
|
|
116
|
-
...pathInput,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// if supported, coerce all string values to correct types
|
|
121
|
-
if (zodSupportsCoerce) {
|
|
122
|
-
if (instanceofZodTypeObject(unwrappedSchema)) {
|
|
123
|
-
Object.values(unwrappedSchema.shape).forEach((shapeSchema) => {
|
|
124
|
-
const unwrappedShapeSchema = unwrapZodType(shapeSchema, false);
|
|
125
|
-
if (instanceofZodTypeCoercible(unwrappedShapeSchema)) {
|
|
126
|
-
unwrappedShapeSchema._def.coerce = true;
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
ctx = await createContext?.({ req, res });
|
|
133
|
-
const caller = router.createCaller(ctx);
|
|
134
|
-
|
|
135
|
-
const segments = procedure.path.split(".");
|
|
136
|
-
const procedureFn = segments.reduce(
|
|
137
|
-
(acc, curr) => acc[curr],
|
|
138
|
-
caller as any,
|
|
139
|
-
) as AnyProcedure;
|
|
140
|
-
|
|
141
|
-
data = await procedureFn(input);
|
|
142
|
-
|
|
143
|
-
const meta = responseMeta?.({
|
|
144
|
-
type: procedure.type,
|
|
145
|
-
paths: [procedure.path],
|
|
146
|
-
ctx,
|
|
147
|
-
data: [data],
|
|
148
|
-
errors: [],
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const statusCode = meta?.status ?? 200;
|
|
152
|
-
const headers = meta?.headers ?? {};
|
|
153
|
-
const body: OpenApiSuccessResponse<typeof data> = data;
|
|
154
|
-
sendResponse(statusCode, headers, body);
|
|
155
|
-
} catch (cause) {
|
|
156
|
-
const error = getErrorFromUnknown(cause);
|
|
157
|
-
|
|
158
|
-
onError?.({
|
|
159
|
-
error,
|
|
160
|
-
type: procedure?.type ?? "unknown",
|
|
161
|
-
path: procedure?.path,
|
|
162
|
-
input,
|
|
163
|
-
ctx,
|
|
164
|
-
req,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const meta = responseMeta?.({
|
|
168
|
-
type: procedure?.type ?? "unknown",
|
|
169
|
-
paths: procedure?.path ? [procedure?.path] : undefined,
|
|
170
|
-
ctx,
|
|
171
|
-
data: [data],
|
|
172
|
-
errors: [error],
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const errorShape = router.getErrorShape({
|
|
176
|
-
error,
|
|
177
|
-
type: procedure?.type ?? "unknown",
|
|
178
|
-
path: procedure?.path,
|
|
179
|
-
input,
|
|
180
|
-
ctx,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const isInputValidationError =
|
|
184
|
-
error.code === "BAD_REQUEST" &&
|
|
185
|
-
error.cause instanceof Error &&
|
|
186
|
-
error.cause.name === "ZodError";
|
|
187
|
-
|
|
188
|
-
const statusCode =
|
|
189
|
-
meta?.status ?? TRPC_ERROR_CODE_HTTP_STATUS[error.code] ?? 500;
|
|
190
|
-
const headers = meta?.headers ?? {};
|
|
191
|
-
const body: OpenApiErrorResponse = {
|
|
192
|
-
message: isInputValidationError
|
|
193
|
-
? "Input validation failed"
|
|
194
|
-
: errorShape?.message ?? error.message ?? "An error occurred",
|
|
195
|
-
code: error.code,
|
|
196
|
-
issues: isInputValidationError
|
|
197
|
-
? (error.cause as ZodError).errors
|
|
198
|
-
: undefined,
|
|
199
|
-
};
|
|
200
|
-
sendResponse(statusCode, headers, body);
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { TRPCError } from "@trpc/server";
|
|
2
|
-
|
|
3
|
-
export const TRPC_ERROR_CODE_HTTP_STATUS: Record<TRPCError["code"], number> = {
|
|
4
|
-
PARSE_ERROR: 400,
|
|
5
|
-
BAD_REQUEST: 400,
|
|
6
|
-
NOT_FOUND: 404,
|
|
7
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
8
|
-
UNAUTHORIZED: 401,
|
|
9
|
-
FORBIDDEN: 403,
|
|
10
|
-
TIMEOUT: 408,
|
|
11
|
-
CONFLICT: 409,
|
|
12
|
-
CLIENT_CLOSED_REQUEST: 499,
|
|
13
|
-
PRECONDITION_FAILED: 412,
|
|
14
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
15
|
-
METHOD_NOT_SUPPORTED: 405,
|
|
16
|
-
TOO_MANY_REQUESTS: 429,
|
|
17
|
-
UNPROCESSABLE_CONTENT: 422,
|
|
18
|
-
NOT_IMPLEMENTED: 501,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export function getErrorFromUnknown(cause: unknown): TRPCError {
|
|
22
|
-
if (cause instanceof Error && cause.name === "TRPCError") {
|
|
23
|
-
return cause as TRPCError;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let errorCause: Error | undefined = undefined;
|
|
27
|
-
let stack: string | undefined = undefined;
|
|
28
|
-
|
|
29
|
-
if (cause instanceof Error) {
|
|
30
|
-
errorCause = cause;
|
|
31
|
-
stack = cause.stack;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const error = new TRPCError({
|
|
35
|
-
message: "Internal server error",
|
|
36
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
37
|
-
cause: errorCause,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (stack) {
|
|
41
|
-
error.stack = stack;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return error;
|
|
45
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
|
-
import { NodeHTTPRequest } from '@trpc/server/dist/adapters/node-http';
|
|
3
|
-
import parse from 'co-body';
|
|
4
|
-
|
|
5
|
-
export const getQuery = (req: NodeHTTPRequest, url: URL): Record<string, string> => {
|
|
6
|
-
const query: Record<string, string> = {};
|
|
7
|
-
|
|
8
|
-
if (!req.query) {
|
|
9
|
-
const parsedQs: Record<string, string[]> = {};
|
|
10
|
-
url.searchParams.forEach((value, key) => {
|
|
11
|
-
if (!parsedQs[key]) {
|
|
12
|
-
parsedQs[key] = [];
|
|
13
|
-
}
|
|
14
|
-
parsedQs[key]!.push(value);
|
|
15
|
-
});
|
|
16
|
-
req.query = parsedQs;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// normalize first value in array
|
|
20
|
-
Object.keys(req.query).forEach((key) => {
|
|
21
|
-
const value = req.query![key];
|
|
22
|
-
if (value) {
|
|
23
|
-
if (typeof value === 'string') {
|
|
24
|
-
query[key] = value;
|
|
25
|
-
} else if (Array.isArray(value)) {
|
|
26
|
-
if (typeof value[0] === 'string') {
|
|
27
|
-
query[key] = value[0];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return query;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const BODY_100_KB = 100000;
|
|
37
|
-
export const getBody = async (req: NodeHTTPRequest, maxBodySize = BODY_100_KB): Promise<any> => {
|
|
38
|
-
if ('body' in req) {
|
|
39
|
-
return req.body;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
req.body = undefined;
|
|
43
|
-
|
|
44
|
-
const contentType = req.headers['content-type'];
|
|
45
|
-
if (contentType === 'application/json' || contentType === 'application/x-www-form-urlencoded') {
|
|
46
|
-
try {
|
|
47
|
-
const { raw, parsed } = await parse(req, {
|
|
48
|
-
limit: maxBodySize,
|
|
49
|
-
strict: false,
|
|
50
|
-
returnRawBody: true,
|
|
51
|
-
});
|
|
52
|
-
req.body = raw ? parsed : undefined;
|
|
53
|
-
} catch (cause) {
|
|
54
|
-
if (cause instanceof Error && cause.name === 'PayloadTooLargeError') {
|
|
55
|
-
throw new TRPCError({
|
|
56
|
-
message: 'Request body too large',
|
|
57
|
-
code: 'PAYLOAD_TOO_LARGE',
|
|
58
|
-
cause: cause,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let errorCause: Error | undefined = undefined;
|
|
63
|
-
if (cause instanceof Error) {
|
|
64
|
-
errorCause = cause;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
throw new TRPCError({
|
|
68
|
-
message: 'Failed to parse request body',
|
|
69
|
-
code: 'PARSE_ERROR',
|
|
70
|
-
cause: errorCause,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return req.body;
|
|
76
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { OpenApiMethod, OpenApiProcedure, OpenApiRouter } from '../../types';
|
|
2
|
-
import { getPathRegExp, normalizePath } from '../../utils/path';
|
|
3
|
-
import { forEachOpenApiProcedure } from '../../utils/procedure';
|
|
4
|
-
|
|
5
|
-
export const createProcedureCache = (router: OpenApiRouter) => {
|
|
6
|
-
const procedureCache = new Map<
|
|
7
|
-
OpenApiMethod,
|
|
8
|
-
Map<
|
|
9
|
-
RegExp,
|
|
10
|
-
{
|
|
11
|
-
type: 'query' | 'mutation';
|
|
12
|
-
path: string;
|
|
13
|
-
procedure: OpenApiProcedure;
|
|
14
|
-
}
|
|
15
|
-
>
|
|
16
|
-
>();
|
|
17
|
-
|
|
18
|
-
const { queries, mutations } = router._def;
|
|
19
|
-
|
|
20
|
-
forEachOpenApiProcedure(queries, ({ path: queryPath, procedure, openapi }) => {
|
|
21
|
-
const { method } = openapi;
|
|
22
|
-
if (!procedureCache.has(method)) {
|
|
23
|
-
procedureCache.set(method, new Map());
|
|
24
|
-
}
|
|
25
|
-
const path = normalizePath(openapi.path);
|
|
26
|
-
const pathRegExp = getPathRegExp(path);
|
|
27
|
-
procedureCache.get(method)!.set(pathRegExp, {
|
|
28
|
-
type: 'query',
|
|
29
|
-
path: queryPath,
|
|
30
|
-
procedure,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
forEachOpenApiProcedure(mutations, ({ path: mutationPath, procedure, openapi }) => {
|
|
35
|
-
const { method } = openapi;
|
|
36
|
-
if (!procedureCache.has(method)) {
|
|
37
|
-
procedureCache.set(method, new Map());
|
|
38
|
-
}
|
|
39
|
-
const path = normalizePath(openapi.path);
|
|
40
|
-
const pathRegExp = getPathRegExp(path);
|
|
41
|
-
procedureCache.get(method)!.set(pathRegExp, {
|
|
42
|
-
type: 'mutation',
|
|
43
|
-
path: mutationPath,
|
|
44
|
-
procedure,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return (method: OpenApiMethod, path: string) => {
|
|
49
|
-
const procedureMethodCache = procedureCache.get(method);
|
|
50
|
-
if (!procedureMethodCache) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const procedureRegExp = Array.from(procedureMethodCache.keys()).find((re) => re.test(path));
|
|
55
|
-
if (!procedureRegExp) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const procedure = procedureMethodCache.get(procedureRegExp)!;
|
|
60
|
-
const pathInput = procedureRegExp.exec(path)?.groups ?? {};
|
|
61
|
-
|
|
62
|
-
return { procedure, pathInput };
|
|
63
|
-
};
|
|
64
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
|
|
3
|
-
import type { OpenApiRouter } from "../types";
|
|
4
|
-
import {
|
|
5
|
-
type CreateOpenApiNodeHttpHandlerOptions,
|
|
6
|
-
createOpenApiNodeHttpHandler,
|
|
7
|
-
} from "./node-http/core";
|
|
8
|
-
|
|
9
|
-
export type CreateOpenApiHttpHandlerOptions<TRouter extends OpenApiRouter> =
|
|
10
|
-
CreateOpenApiNodeHttpHandlerOptions<TRouter, IncomingMessage, ServerResponse>;
|
|
11
|
-
|
|
12
|
-
export const createOpenApiHttpHandler = <TRouter extends OpenApiRouter>(
|
|
13
|
-
opts: CreateOpenApiHttpHandlerOptions<TRouter>,
|
|
14
|
-
) => {
|
|
15
|
-
const openApiHttpHandler = createOpenApiNodeHttpHandler(opts);
|
|
16
|
-
return async (req: IncomingMessage, res: ServerResponse) => {
|
|
17
|
-
await openApiHttpHandler(req, res);
|
|
18
|
-
};
|
|
19
|
-
};
|
package/src/generator/index.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
-
|
|
3
|
-
import { OpenApiRouter } from '../types';
|
|
4
|
-
import { getOpenApiPathsObject } from './paths';
|
|
5
|
-
import { errorResponseObject } from './schema';
|
|
6
|
-
|
|
7
|
-
export const openApiVersion = '3.0.3';
|
|
8
|
-
|
|
9
|
-
export type GenerateOpenApiDocumentOptions = {
|
|
10
|
-
title: string;
|
|
11
|
-
description?: string;
|
|
12
|
-
version: string;
|
|
13
|
-
baseUrl: string;
|
|
14
|
-
docsUrl?: string;
|
|
15
|
-
tags?: string[];
|
|
16
|
-
securitySchemes?: OpenAPIV3.ComponentsObject['securitySchemes'];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const generateOpenApiDocument = (
|
|
20
|
-
appRouter: OpenApiRouter,
|
|
21
|
-
opts: GenerateOpenApiDocumentOptions,
|
|
22
|
-
): OpenAPIV3.Document => {
|
|
23
|
-
const securitySchemes = opts.securitySchemes || {
|
|
24
|
-
Authorization: {
|
|
25
|
-
type: 'http',
|
|
26
|
-
scheme: 'bearer',
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
openapi: openApiVersion,
|
|
31
|
-
info: {
|
|
32
|
-
title: opts.title,
|
|
33
|
-
description: opts.description,
|
|
34
|
-
version: opts.version,
|
|
35
|
-
},
|
|
36
|
-
servers: [
|
|
37
|
-
{
|
|
38
|
-
url: opts.baseUrl,
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
paths: getOpenApiPathsObject(appRouter, Object.keys(securitySchemes)),
|
|
42
|
-
components: {
|
|
43
|
-
securitySchemes,
|
|
44
|
-
responses: {
|
|
45
|
-
error: errorResponseObject,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
tags: opts.tags?.map((tag) => ({ name: tag })),
|
|
49
|
-
externalDocs: opts.docsUrl ? { url: opts.docsUrl } : undefined,
|
|
50
|
-
};
|
|
51
|
-
};
|
package/src/generator/paths.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { TRPCError } from "@trpc/server";
|
|
2
|
-
import { OpenAPIV3 } from "openapi-types";
|
|
3
|
-
|
|
4
|
-
import { OpenApiProcedureRecord, OpenApiRouter } from "../types";
|
|
5
|
-
import { acceptsRequestBody } from "../utils/method";
|
|
6
|
-
import { getPathParameters, normalizePath } from "../utils/path";
|
|
7
|
-
import {
|
|
8
|
-
forEachOpenApiProcedure,
|
|
9
|
-
getInputOutputParsers,
|
|
10
|
-
} from "../utils/procedure";
|
|
11
|
-
import {
|
|
12
|
-
getParameterObjects,
|
|
13
|
-
getRequestBodyObject,
|
|
14
|
-
getResponsesObject,
|
|
15
|
-
} from "./schema";
|
|
16
|
-
|
|
17
|
-
export const getOpenApiPathsObject = (
|
|
18
|
-
appRouter: OpenApiRouter,
|
|
19
|
-
securitySchemeNames: string[],
|
|
20
|
-
): OpenAPIV3.PathsObject => {
|
|
21
|
-
const pathsObject: OpenAPIV3.PathsObject = {};
|
|
22
|
-
const procedures = appRouter._def.procedures as OpenApiProcedureRecord;
|
|
23
|
-
|
|
24
|
-
forEachOpenApiProcedure(
|
|
25
|
-
procedures,
|
|
26
|
-
({ path: procedurePath, type, procedure, openapi }) => {
|
|
27
|
-
const procedureName = `${type}.${procedurePath}`;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
if (type === "subscription") {
|
|
31
|
-
throw new TRPCError({
|
|
32
|
-
message: "Subscriptions are not supported by OpenAPI v3",
|
|
33
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const { method, protect, summary, description, tags, headers } =
|
|
38
|
-
openapi;
|
|
39
|
-
|
|
40
|
-
const path = normalizePath(openapi.path);
|
|
41
|
-
const pathParameters = getPathParameters(path);
|
|
42
|
-
const headerParameters =
|
|
43
|
-
headers?.map((header) => ({ ...header, in: "header" })) || [];
|
|
44
|
-
|
|
45
|
-
const httpMethod = OpenAPIV3.HttpMethods[method];
|
|
46
|
-
if (!httpMethod) {
|
|
47
|
-
throw new TRPCError({
|
|
48
|
-
message: "Method must be GET, POST, PATCH, PUT or DELETE",
|
|
49
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (pathsObject[path]?.[httpMethod]) {
|
|
54
|
-
throw new TRPCError({
|
|
55
|
-
message: `Duplicate procedure defined for route ${method} ${path}`,
|
|
56
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const contentTypes = openapi.contentTypes || ["application/json"];
|
|
61
|
-
if (contentTypes.length === 0) {
|
|
62
|
-
throw new TRPCError({
|
|
63
|
-
message: "At least one content type must be specified",
|
|
64
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const { inputParser, outputParser } = getInputOutputParsers(procedure);
|
|
69
|
-
|
|
70
|
-
pathsObject[path] = {
|
|
71
|
-
...pathsObject[path],
|
|
72
|
-
[httpMethod]: {
|
|
73
|
-
operationId: procedurePath.replace(/\./g, "-"),
|
|
74
|
-
summary,
|
|
75
|
-
description,
|
|
76
|
-
tags: tags,
|
|
77
|
-
security: protect
|
|
78
|
-
? securitySchemeNames.map((name) => ({ [name]: [] }))
|
|
79
|
-
: undefined,
|
|
80
|
-
...(acceptsRequestBody(method)
|
|
81
|
-
? {
|
|
82
|
-
requestBody: getRequestBodyObject(
|
|
83
|
-
inputParser,
|
|
84
|
-
pathParameters,
|
|
85
|
-
contentTypes,
|
|
86
|
-
openapi.example?.request,
|
|
87
|
-
),
|
|
88
|
-
parameters: [
|
|
89
|
-
...headerParameters,
|
|
90
|
-
...(getParameterObjects(
|
|
91
|
-
inputParser,
|
|
92
|
-
pathParameters,
|
|
93
|
-
"path",
|
|
94
|
-
openapi.example?.request,
|
|
95
|
-
) || []),
|
|
96
|
-
],
|
|
97
|
-
}
|
|
98
|
-
: {
|
|
99
|
-
requestBody: undefined,
|
|
100
|
-
parameters: [
|
|
101
|
-
...headerParameters,
|
|
102
|
-
...(getParameterObjects(
|
|
103
|
-
inputParser,
|
|
104
|
-
pathParameters,
|
|
105
|
-
"all",
|
|
106
|
-
openapi.example?.request,
|
|
107
|
-
) || []),
|
|
108
|
-
],
|
|
109
|
-
}),
|
|
110
|
-
responses: getResponsesObject(
|
|
111
|
-
outputParser,
|
|
112
|
-
openapi.example?.response,
|
|
113
|
-
openapi.responseHeaders,
|
|
114
|
-
),
|
|
115
|
-
// ...(openapi.deprecated ? { deprecated: openapi.deprecated } : {}),
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
} catch (error: any) {
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
120
|
-
error.message = `[${procedureName}] - ${error.message}`;
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
return pathsObject;
|
|
127
|
-
};
|