ts-openapi-express 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jacob Shirley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ts-openapi-express
2
+
3
+ For detailed documentation, examples, and API reference, see the [main README](../../README.md).
@@ -0,0 +1,21 @@
1
+ import { error as openapiValidatorErrors } from 'express-openapi-validator';
2
+ export type OpenapiValidatorError = (typeof openapiValidatorErrors)[keyof typeof openapiValidatorErrors];
3
+ declare const isExpressOpenAPIValidatorError: (err: any) => err is Error & {
4
+ status: number;
5
+ };
6
+ declare class HttpError extends Error {
7
+ readonly status: number;
8
+ constructor(message: string, status: number);
9
+ type(): string;
10
+ }
11
+ declare class UnauthorizedError extends HttpError {
12
+ constructor();
13
+ }
14
+ declare class RequestBodyTooLargeError extends HttpError {
15
+ constructor(maxSizeBytes: number);
16
+ }
17
+ declare class InvalidJsonError extends HttpError {
18
+ readonly jsonString: string;
19
+ constructor(jsonString: string);
20
+ }
21
+ export { isExpressOpenAPIValidatorError, HttpError, RequestBodyTooLargeError, InvalidJsonError, UnauthorizedError, };
package/dist/errors.js ADDED
@@ -0,0 +1,38 @@
1
+ import { error as openapiValidatorErrors } from 'express-openapi-validator';
2
+ const isExpressOpenAPIValidatorError = (err) => {
3
+ for (const key in openapiValidatorErrors) {
4
+ if (err instanceof
5
+ openapiValidatorErrors[key]) {
6
+ return true;
7
+ }
8
+ }
9
+ return false;
10
+ };
11
+ class HttpError extends Error {
12
+ status;
13
+ constructor(message, status) {
14
+ super(message);
15
+ this.status = status;
16
+ }
17
+ type() {
18
+ return this.constructor.name;
19
+ }
20
+ }
21
+ class UnauthorizedError extends HttpError {
22
+ constructor() {
23
+ super('Unauthorized', 401);
24
+ }
25
+ }
26
+ class RequestBodyTooLargeError extends HttpError {
27
+ constructor(maxSizeBytes) {
28
+ super('Request body too large. Max size: ' + maxSizeBytes + ' bytes', 413);
29
+ }
30
+ }
31
+ class InvalidJsonError extends HttpError {
32
+ jsonString;
33
+ constructor(jsonString) {
34
+ super(`Invalid JSON: ${jsonString}`, 400);
35
+ this.jsonString = jsonString;
36
+ }
37
+ }
38
+ export { isExpressOpenAPIValidatorError, HttpError, RequestBodyTooLargeError, InvalidJsonError, UnauthorizedError, };
@@ -0,0 +1,5 @@
1
+ export * from './openapiRoutes.js';
2
+ export * from './openapiExpress.js';
3
+ export * from './types.js';
4
+ export * from './errors.js';
5
+ export { redirect } from './utils.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './openapiRoutes.js';
2
+ export * from './openapiExpress.js';
3
+ export * from './types.js';
4
+ export * from './errors.js';
5
+ export { redirect } from './utils.js';
@@ -0,0 +1,8 @@
1
+ declare function resolveLocal<T>(object: object, ref: string): T;
2
+ declare function resolvePointer<T>(object: T, options?: {
3
+ resolveLocalRefs?: boolean;
4
+ }, parent?: any, currentPath?: string, element?: string): T;
5
+ declare function resolveFile<T = any>(path: string, options?: {
6
+ resolveLocalRefs?: boolean;
7
+ }, element?: string): T;
8
+ export { resolveLocal, resolveFile, resolvePointer };
@@ -0,0 +1,39 @@
1
+ import { parseDocument } from 'yaml';
2
+ import { get } from 'lodash-es';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ function resolveLocal(object, ref) {
6
+ const [p, element] = ref.split('#');
7
+ return get(object, element.split('/').filter(Boolean));
8
+ }
9
+ function resolvePointer(object, options, parent = object, currentPath, element) {
10
+ const resolveLocal = options?.resolveLocalRefs ?? true;
11
+ const data = element
12
+ ? get(object, element.split('/').filter(Boolean))
13
+ : object;
14
+ for (const key in data) {
15
+ const value = data[key];
16
+ if (typeof value === 'object') {
17
+ data[key] = resolvePointer(value, options, parent, currentPath, '');
18
+ continue;
19
+ }
20
+ if (key !== '$ref')
21
+ continue;
22
+ const [p, element] = (value + '').split('#');
23
+ if (p === '' && !resolveLocal)
24
+ continue;
25
+ return !currentPath
26
+ ? get(parent, element.split('/').filter(Boolean))
27
+ : resolveFile(path.join(p ? path.dirname(currentPath) : currentPath, p), options, element);
28
+ }
29
+ return data;
30
+ }
31
+ function resolveFile(path, options, element = '') {
32
+ const src = fs.readFileSync(path, 'utf-8');
33
+ const object = path.endsWith('.yaml')
34
+ ? parseDocument(src).toJSON()
35
+ : JSON.parse(src);
36
+ const result = resolvePointer(object, options, object, path, element);
37
+ return result;
38
+ }
39
+ export { resolveLocal, resolveFile, resolvePointer };
@@ -0,0 +1,5 @@
1
+ import { ExpressMiddleware } from '../types.js';
2
+ declare function json(options?: {
3
+ limit?: number;
4
+ }): ExpressMiddleware;
5
+ export { json };
@@ -0,0 +1,28 @@
1
+ import { InvalidJsonError, RequestBodyTooLargeError } from '../errors.js';
2
+ const DEFAULT_LIMIT_BYTES = 10 * 1024 * 1024;
3
+ function json(options) {
4
+ const limit = options?.limit ?? DEFAULT_LIMIT_BYTES;
5
+ return async function json(req, res, next) {
6
+ const contentType = req.headers['content-type'];
7
+ if (contentType?.trim()?.includes('application/json')) {
8
+ let body = '';
9
+ for await (const chunk of req) {
10
+ body += chunk.toString();
11
+ if (body.length > limit) {
12
+ next(new RequestBodyTooLargeError(limit));
13
+ return;
14
+ }
15
+ }
16
+ req.bodyAsString = body;
17
+ try {
18
+ req.body = JSON.parse(body);
19
+ }
20
+ catch (e) {
21
+ next(new InvalidJsonError(body));
22
+ return;
23
+ }
24
+ }
25
+ next();
26
+ };
27
+ }
28
+ export { json };
@@ -0,0 +1,11 @@
1
+ import type { OpenapiSpec } from '../openapi-types/index.js';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ declare function makeQueryWritable(req: Request, res: Response, next: NextFunction): void;
4
+ declare function requestResponseValidatorMiddleware(options: {
5
+ spec: OpenapiSpec;
6
+ validation?: {
7
+ requests?: boolean;
8
+ responses?: boolean;
9
+ } | false;
10
+ }): (import("express-openapi-validator/dist/framework/types.js").OpenApiRequestHandler[] | typeof makeQueryWritable)[];
11
+ export { requestResponseValidatorMiddleware };
@@ -0,0 +1,40 @@
1
+ import { middleware as openapiExpressValidatorMiddleware } from 'express-openapi-validator';
2
+ // TODO: remove this when express-openapi-validator supports Express 5.x. See: https://github.com/cdimascio/express-openapi-validator/issues/969
3
+ // This is caused by a breaking change in Express 5.x, where query is immutable by default
4
+ function makeQueryWritable(req, res, next) {
5
+ if (req.query)
6
+ Object.defineProperty(req, 'query', {
7
+ writable: true,
8
+ value: { ...req.query },
9
+ });
10
+ next();
11
+ }
12
+ function makeQueryReadOnly(req, res, next) {
13
+ if (req.query)
14
+ Object.defineProperty(req, 'query', {
15
+ writable: false,
16
+ value: { ...req.query },
17
+ });
18
+ next();
19
+ }
20
+ function requestResponseValidatorMiddleware(options) {
21
+ const { spec, validation } = options;
22
+ const middleware = openapiExpressValidatorMiddleware({
23
+ apiSpec: spec,
24
+ validateApiSpec: true,
25
+ ignoreUndocumented: true,
26
+ validateSecurity: false,
27
+ validateRequests: validation === false
28
+ ? false
29
+ : validation?.requests === false
30
+ ? false
31
+ : true,
32
+ validateResponses: validation === false
33
+ ? false
34
+ : validation?.responses === false
35
+ ? false
36
+ : true,
37
+ });
38
+ return [makeQueryWritable, middleware, makeQueryReadOnly];
39
+ }
40
+ export { requestResponseValidatorMiddleware };
@@ -0,0 +1,77 @@
1
+ interface IncomingHttpHeaders {
2
+ accept?: string | undefined;
3
+ 'accept-language'?: string | undefined;
4
+ 'accept-patch'?: string | undefined;
5
+ 'accept-ranges'?: string | undefined;
6
+ 'access-control-allow-credentials'?: string | undefined;
7
+ 'access-control-allow-headers'?: string | undefined;
8
+ 'access-control-allow-methods'?: string | undefined;
9
+ 'access-control-allow-origin'?: string | undefined;
10
+ 'access-control-expose-headers'?: string | undefined;
11
+ 'access-control-max-age'?: string | undefined;
12
+ 'access-control-request-headers'?: string | undefined;
13
+ 'access-control-request-method'?: string | undefined;
14
+ age?: string | undefined;
15
+ allow?: string | undefined;
16
+ 'alt-svc'?: string | undefined;
17
+ authorization?: string | undefined;
18
+ 'cache-control'?: string | undefined;
19
+ connection?: string | undefined;
20
+ 'content-disposition'?: string | undefined;
21
+ 'content-encoding'?: string | undefined;
22
+ 'content-language'?: string | undefined;
23
+ 'content-length'?: string | undefined;
24
+ 'content-location'?: string | undefined;
25
+ 'content-range'?: string | undefined;
26
+ 'content-type'?: string | undefined;
27
+ cookie?: string | undefined;
28
+ date?: string | undefined;
29
+ etag?: string | undefined;
30
+ expect?: string | undefined;
31
+ expires?: string | undefined;
32
+ forwarded?: string | undefined;
33
+ from?: string | undefined;
34
+ host?: string | undefined;
35
+ 'if-match'?: string | undefined;
36
+ 'if-modified-since'?: string | undefined;
37
+ 'if-none-match'?: string | undefined;
38
+ 'if-unmodified-since'?: string | undefined;
39
+ 'last-modified'?: string | undefined;
40
+ location?: string | undefined;
41
+ origin?: string | undefined;
42
+ pragma?: string | undefined;
43
+ 'proxy-authenticate'?: string | undefined;
44
+ 'proxy-authorization'?: string | undefined;
45
+ 'public-key-pins'?: string | undefined;
46
+ range?: string | undefined;
47
+ referer?: string | undefined;
48
+ 'retry-after'?: string | undefined;
49
+ 'sec-websocket-accept'?: string | undefined;
50
+ 'sec-websocket-extensions'?: string | undefined;
51
+ 'sec-websocket-key'?: string | undefined;
52
+ 'sec-websocket-protocol'?: string | undefined;
53
+ 'sec-websocket-version'?: string | undefined;
54
+ 'set-cookie'?: string[] | undefined;
55
+ 'strict-transport-security'?: string | undefined;
56
+ tk?: string | undefined;
57
+ trailer?: string | undefined;
58
+ 'transfer-encoding'?: string | undefined;
59
+ upgrade?: string | undefined;
60
+ 'user-agent'?: string | undefined;
61
+ vary?: string | undefined;
62
+ via?: string | undefined;
63
+ warning?: string | undefined;
64
+ 'www-authenticate'?: string | undefined;
65
+ }
66
+ type StandardRequestHeaders = {
67
+ [K in keyof IncomingHttpHeaders as string extends K ? never : number extends K ? never : K]: IncomingHttpHeaders[K];
68
+ };
69
+ type StandardRequestHeader = Lowercase<keyof StandardRequestHeaders>;
70
+ type StandardResponseHeader = Lowercase<'Accept-Ranges' | 'Age' | 'Allow' | 'Cache-Control' | 'Content-Disposition' | 'Content-Encoding' | 'Content-Language' | 'Content-Length' | 'Content-Location' | 'Content-MD5' | 'Content-Range' | 'Content-Security-Policy' | 'Content-Type' | 'Date' | 'ETag' | 'Expires' | 'Last-Modified' | 'Link' | 'Location' | 'P3P' | 'Pragma' | 'Proxy-Authenticate' | 'Refresh' | 'Retry-After' | 'Server' | 'Set-Cookie' | 'Strict-Transport-Security' | 'Trailer' | 'Transfer-Encoding' | 'Upgrade' | 'User-Agent' | 'Vary' | 'Via' | 'Warning' | 'WWW-Authenticate' | 'X-Content-Type-Options' | 'X-Frame-Options' | 'X-Powered-By' | 'X-XSS-Protection'>;
71
+ type StandardHeaderMap = {
72
+ [x in StandardResponseHeader]?: string;
73
+ };
74
+ type HeadersMap = {
75
+ [x in `x-${string}` | `X-${string}` | StandardRequestHeader]?: string;
76
+ };
77
+ export type { StandardRequestHeader, StandardResponseHeader, StandardHeaderMap, HeadersMap, };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './http.js';
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './http.js';
@@ -0,0 +1,59 @@
1
+ import { OpenAPIV3 } from 'openapi-types';
2
+ type LowercaseKeys<T> = {
3
+ [K in keyof T as K extends string ? Lowercase<K> : K]: T[K];
4
+ };
5
+ type HttpMethod = 'post' | 'get' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'trace' | 'connect';
6
+ type OpenapiSpec = OpenAPIV3.Document;
7
+ type GetQueryParameters<T> = T extends {
8
+ parameters: {
9
+ query?: any;
10
+ };
11
+ } ? T['parameters']['query'] : never;
12
+ type GetPathParameters<T> = T extends {
13
+ parameters: {
14
+ path: any;
15
+ };
16
+ } ? T['parameters']['path'] : never;
17
+ type GetRequestHeaders<T> = T extends {
18
+ parameters: {
19
+ header?: any;
20
+ };
21
+ } ? [T['parameters']['header']] extends [never | undefined] ? never : LowercaseKeys<T['parameters']['header']> : never;
22
+ type GetRequestBody<T> = T extends {
23
+ requestBody?: {
24
+ content?: any;
25
+ };
26
+ } ? undefined extends T['requestBody'] ? NonNullable<T['requestBody']>['content'][keyof NonNullable<T['requestBody']>['content']] | undefined : NonNullable<T['requestBody']>['content'][keyof NonNullable<T['requestBody']>['content']] : never;
27
+ type GetStatusCodes<T> = T extends {
28
+ responses: {
29
+ [x: number]: any;
30
+ };
31
+ } ? Extract<keyof T['responses'], number> : never;
32
+ type GetResponseBody<T, Code extends number = number> = T extends {
33
+ responses: {
34
+ [x in Code]: {
35
+ content?: {
36
+ [key: string]: any;
37
+ };
38
+ };
39
+ };
40
+ } ? T['responses'][Code]['content'][keyof T['responses'][Code]['content']] : never;
41
+ type GetJsonBody<T, Code extends number = number> = T extends {
42
+ responses: {
43
+ [x in Code]: {
44
+ content?: {
45
+ 'application/json': any;
46
+ };
47
+ };
48
+ };
49
+ } ? Extract<T['responses'][Code extends never ? Extract<keyof T['responses'], number> : Code]['content'], object>['application/json'] : never;
50
+ type GetResponseHeaders<T, Code extends number = GetStatusCodes<T>> = T extends {
51
+ responses: {
52
+ [code in Code]: {
53
+ headers: {
54
+ [headerName: string]: any;
55
+ };
56
+ };
57
+ };
58
+ } ? T['responses'][Code]['headers'] : never;
59
+ export type { HttpMethod, OpenapiSpec, GetJsonBody, GetStatusCodes, GetPathParameters, GetRequestBody, GetQueryParameters, GetRequestHeaders, GetResponseHeaders, GetResponseBody, };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { Application } from 'express';
2
+ import { ExpressMiddleware, OpenapiApplication, Routes } from './types.js';
3
+ declare function openapiExpress<Spec>(options: {
4
+ specPath: string;
5
+ routes: Routes<Spec>;
6
+ middleware?: ExpressMiddleware[];
7
+ validateRequest?: boolean;
8
+ validateResponse?: boolean;
9
+ decodeJsonBody?: boolean;
10
+ app?: Application;
11
+ disableXPoweredBy?: boolean;
12
+ }): OpenapiApplication<Spec>;
13
+ export { openapiExpress };
@@ -0,0 +1,37 @@
1
+ import express from 'express';
2
+ import { json } from './middleware/json.js';
3
+ import { requestResponseValidatorMiddleware } from './middleware/validation.js';
4
+ import { resolveFile } from './jsonPointer.js';
5
+ import { openapiRoutes } from './openapiRoutes.js';
6
+ function openapiExpress(options) {
7
+ const { specPath, routes, decodeJsonBody = true, disableXPoweredBy = true, validateRequest = true, validateResponse = true, middleware = [], } = options;
8
+ const spec = resolveFile(specPath, { resolveLocalRefs: false });
9
+ const app = options.app || express();
10
+ if (disableXPoweredBy)
11
+ app.disable('x-powered-by');
12
+ app.use('/openapi.json', (req, res) => {
13
+ res.json(spec);
14
+ });
15
+ if (decodeJsonBody) {
16
+ app.use(json());
17
+ }
18
+ if (validateRequest || validateResponse)
19
+ app.use(...requestResponseValidatorMiddleware({
20
+ spec,
21
+ validation: {
22
+ requests: validateRequest,
23
+ responses: validateResponse,
24
+ },
25
+ }));
26
+ for (const m of middleware) {
27
+ app.use(m);
28
+ }
29
+ openapiRoutes(routes, app);
30
+ return Object.assign(app, {
31
+ version: spec.info.version,
32
+ openApiPath: specPath,
33
+ spec,
34
+ routes: routes,
35
+ });
36
+ }
37
+ export { openapiExpress };
@@ -0,0 +1,4 @@
1
+ import { Routes } from './types.js';
2
+ import { Application } from 'express';
3
+ declare function openapiRoutes<TPaths>(routes: Routes<TPaths>, app?: Application): Application;
4
+ export { openapiRoutes };
@@ -0,0 +1,91 @@
1
+ import { openapiPathToExpressPath } from './utils.js';
2
+ import express from 'express';
3
+ import { Readable } from 'stream';
4
+ function assertHttpMethod(method) {
5
+ if (![
6
+ 'get',
7
+ 'post',
8
+ 'put',
9
+ 'patch',
10
+ 'delete',
11
+ 'trace',
12
+ 'options',
13
+ 'connect',
14
+ ].includes(method)) {
15
+ throw new Error(`Invalid HTTP method: ${method}`);
16
+ }
17
+ }
18
+ function routeResponse(route) {
19
+ return async (request, res) => {
20
+ const result = await route.handler(request);
21
+ const responseCodes = Object.keys(result).map(Number);
22
+ if (responseCodes.length === 0) {
23
+ throw new Error('No response code defined');
24
+ }
25
+ if (responseCodes.length > 1) {
26
+ throw new Error('Multiple response codes defined');
27
+ }
28
+ const responseCode = responseCodes[0];
29
+ if (!result[responseCode]) {
30
+ throw new Error(`No response defined for status code ${responseCode}`);
31
+ }
32
+ for (const header in result[responseCode].headers) {
33
+ res.set(String(header), String(result[responseCode].headers[header]));
34
+ }
35
+ res.status(responseCode);
36
+ const body = 'body' in result[responseCode]
37
+ ? result[responseCode].body
38
+ : undefined;
39
+ if (body === undefined) {
40
+ res.end();
41
+ }
42
+ else if (body instanceof Readable) {
43
+ body.pipe(res);
44
+ }
45
+ else if (body instanceof ReadableStream) {
46
+ const reader = body.getReader();
47
+ const read = async () => {
48
+ const { done, value } = await reader.read();
49
+ if (done) {
50
+ res.end();
51
+ return;
52
+ }
53
+ res.write(value.toString());
54
+ await read();
55
+ };
56
+ await read();
57
+ }
58
+ else if (typeof body === 'string') {
59
+ res.send(body);
60
+ }
61
+ else if (typeof body === 'object' &&
62
+ body !== null &&
63
+ (Symbol.asyncIterator in body || Symbol.iterator in body)) {
64
+ if (Array.isArray(body)) {
65
+ res.json(body);
66
+ return;
67
+ }
68
+ for await (const chunk of body) {
69
+ res.write(chunk.toString());
70
+ }
71
+ res.end();
72
+ }
73
+ else {
74
+ res.json(typeof body === 'object' && body && 'toJSON' in body
75
+ ? body.toJSON()
76
+ : body);
77
+ }
78
+ };
79
+ }
80
+ function openapiRoutes(routes, app = express()) {
81
+ for (const path of Object.keys(routes)) {
82
+ for (const method in routes[path]) {
83
+ assertHttpMethod(method);
84
+ const route = routes[path][method];
85
+ const expressPath = openapiPathToExpressPath(String(path));
86
+ app[method](expressPath, routeResponse(route));
87
+ }
88
+ }
89
+ return app;
90
+ }
91
+ export { openapiRoutes };
@@ -0,0 +1,59 @@
1
+ import type { Application, NextFunction, Request as ExpressRequest, Response as ExpressResponse } from 'express';
2
+ import { GetRequestBody, GetRequestHeaders, GetResponseBody, GetPathParameters, GetQueryParameters, GetResponseHeaders, GetStatusCodes, HttpMethod, OpenapiSpec, StandardHeaderMap, StandardRequestHeader } from './openapi-types/index.js';
3
+ import { Readable } from 'stream';
4
+ type OpenapiRequest<T> = Omit<ExpressRequest, 'query' | 'params' | 'body' | 'header' | 'headers'> & {
5
+ query: GetQueryParameters<T>;
6
+ params: GetPathParameters<T>;
7
+ body: GetRequestBody<T>;
8
+ header: <const THeader extends keyof GetRequestHeaders<T>>(header: StandardRequestHeader | THeader) => GetRequestHeaders<T>[THeader];
9
+ headers: StandardHeaderMap & GetRequestHeaders<T>;
10
+ };
11
+ type OpenapiResponse<T, Code extends GetStatusCodes<T> = GetStatusCodes<T>> = {
12
+ [code in NoInfer<Code>]?: {
13
+ headers: GetResponseHeaders<T, code> & StandardHeaderMap;
14
+ } & (GetResponseBody<T, code> extends [undefined] ? {} : {
15
+ body: GetResponseBody<T, code> | {
16
+ toJSON(): GetResponseBody<T, code>;
17
+ } | Readable | ReadableStream<{
18
+ toString(): string;
19
+ }> | Iterable<{
20
+ toString(): string;
21
+ }> | AsyncIterable<{
22
+ toString(): string;
23
+ }>;
24
+ });
25
+ };
26
+ type HandlerResult<Schema> = Promise<OpenapiResponse<Schema>> | OpenapiResponse<Schema>;
27
+ type Route<Schema> = {
28
+ handler: (request: OpenapiRequest<Schema>) => HandlerResult<Schema>;
29
+ middleware?: ((request: OpenapiRequest<Schema>) => HandlerResult<Schema>)[];
30
+ };
31
+ type Routes<Paths> = {
32
+ [Path in keyof Paths]: {
33
+ [Method in Extract<keyof Paths[Path], HttpMethod> as [
34
+ undefined
35
+ ] extends [Paths[Path][Method]] ? never : Method]: Route<Paths[Path][Method]>;
36
+ };
37
+ };
38
+ type OpenapiApplication<T> = Application & {
39
+ version: string;
40
+ openApiPath: string;
41
+ routes: Routes<T>;
42
+ spec: OpenapiSpec;
43
+ };
44
+ type ExpressMiddleware = (request: ExpressRequest, response: ExpressResponse, next: NextFunction) => void;
45
+ type ExpressMiddlewareWithError = (error: any, request: ExpressRequest, response: ExpressResponse, next: NextFunction) => void;
46
+ type ErrorResponse = {
47
+ message: string;
48
+ detail: string;
49
+ path: string;
50
+ errorCode: string;
51
+ };
52
+ declare global {
53
+ namespace Express {
54
+ interface Request {
55
+ bodyAsString: string;
56
+ }
57
+ }
58
+ }
59
+ export type { OpenapiApplication, OpenapiRequest, OpenapiResponse, OpenapiSpec, Route, Routes, HandlerResult, ExpressMiddleware, ExpressMiddlewareWithError, ErrorResponse, ExpressRequest, ExpressResponse, };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { OpenapiRequest } from './types.js';
2
+ declare const validateRequest: <T>(req: any) => req is OpenapiRequest<T>;
3
+ declare const openapiPathToExpressPath: (openapiPath: string) => string;
4
+ declare const redirect: (url: string) => {
5
+ 302: {
6
+ headers: {};
7
+ body: string;
8
+ };
9
+ };
10
+ export { validateRequest, openapiPathToExpressPath, redirect };
package/dist/utils.js ADDED
@@ -0,0 +1,16 @@
1
+ const validateRequest = (req) => {
2
+ //TODO: validate the request
3
+ return true;
4
+ };
5
+ const openapiPathToExpressPath = (openapiPath) => {
6
+ return openapiPath.replace(/\{/gm, ':').replace(/\}/gm, '');
7
+ };
8
+ const redirect = (url) => {
9
+ return {
10
+ 302: {
11
+ headers: {},
12
+ body: `<head><meta http-equiv="Refresh" content="0; URL=${url}" /></head>`,
13
+ },
14
+ };
15
+ };
16
+ export { validateRequest, openapiPathToExpressPath, redirect };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "ts-openapi-express",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "",
6
+ "main": "dist/index.js",
7
+ "directories": {
8
+ "test": "test"
9
+ },
10
+ "keywords": [
11
+ "openapi",
12
+ "express",
13
+ "typescript",
14
+ "rest-api",
15
+ "api",
16
+ "validation",
17
+ "type-safe",
18
+ "schema-validation",
19
+ "openapi-typescript",
20
+ "request-validation",
21
+ "response-validation",
22
+ "nodejs"
23
+ ],
24
+ "author": "",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "express": "^5.0.0",
28
+ "express-openapi-validator": "^5.3.9",
29
+ "lodash-es": "^4.17.23",
30
+ "yaml": "^2.8.1"
31
+ },
32
+ "devDependencies": {
33
+ "@types/express": "^5.0.0",
34
+ "@types/lodash-es": "^4.17.12",
35
+ "@types/node": "^22.0.0",
36
+ "@types/supertest": "^6.0.2",
37
+ "@types/swagger-ui-express": "^4.1.6",
38
+ "openapi-types": "^12.1.0",
39
+ "openapi-typescript": "^7.4.1",
40
+ "supertest": "^7.0.0",
41
+ "typescript": "^5.0.4",
42
+ "vitest": "^4.0.18"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md"
47
+ ],
48
+ "scripts": {
49
+ "test": "pnpm test:unit",
50
+ "test:unit": "pnpm test:compile:spec && vitest run",
51
+ "test:compile:spec": "openapi-typescript test/unit/openapi.test.yaml --output test/unit/test-schema.ts",
52
+ "compile": "tsc -p tsconfig.prod.json",
53
+ "watch": "tsc -p tsconfig.prod.json --watch"
54
+ }
55
+ }