@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.
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 +29 -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,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
- };
@@ -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
- };
@@ -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
- };