snap-on-openapi 1.0.5 → 1.0.7

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/OpenApi.d.ts CHANGED
@@ -49,10 +49,12 @@ export declare class OpenApi<TRouteTypes extends string, TErrorCodes extends str
49
49
  processRootRoute(originalReq: Request): Promise<{
50
50
  status: number;
51
51
  body: unknown;
52
+ headers: Record<string, string>;
52
53
  }>;
53
54
  protected handleError(e: unknown, req: Request): {
54
55
  status: number;
55
56
  body: z.TypeOf<import("./index.js").OpenApiErrorConfigMap<TErrorCodes>[TErrorCodes]["responseValidator"]>;
57
+ headers: {};
56
58
  };
57
59
  protected static getBuilder(): ConfigBuilder<SampleRouteType, ErrorCode, DefaultErrorMap, DefaultRouteParamsMap, DefaultRouteContextMap, DefaultRouteMap, DefaultConfig>;
58
60
  }
package/dist/OpenApi.js CHANGED
@@ -235,12 +235,17 @@ export class OpenApi {
235
235
  }
236
236
  });
237
237
  }
238
- const validated = route.validators.response.safeParse(response);
238
+ const finalResponse = route.validators.responseHeaders ? response : { body: response, headers: {} };
239
+ const finalResponseValidator = z.object({
240
+ body: route.validators.response,
241
+ headers: route.validators.responseHeaders?.strict() ?? z.object({}),
242
+ });
243
+ const validated = finalResponseValidator.safeParse(finalResponse);
239
244
  if (!validated.success) {
240
245
  throw new ValidationError(validated.error, ValidationLocation.Response);
241
246
  }
242
247
  this.logger.info('Response: 200', validated.data);
243
- return { status: 200, body: validated.data };
248
+ return { status: 200, body: validated.data.body, headers: validated.data.headers };
244
249
  }
245
250
  catch (e) {
246
251
  return this.handleError(e, originalReq);
@@ -256,12 +261,12 @@ export class OpenApi {
256
261
  throw new Error("Error response haven't passed validation");
257
262
  }
258
263
  this.logger.info(`Response: '${status}'`, response.body);
259
- return { status: Number(status), body: response.body };
264
+ return { status: Number(status), body: response.body, headers: {} };
260
265
  }
261
266
  catch (e) {
262
267
  this.logger.error('Error during error handling', e);
263
268
  const status = this.config.errors[this.config.defaultError.code].status;
264
- return { status: Number(status), body: this.config.defaultError.body };
269
+ return { status: Number(status), body: this.config.defaultError.body, headers: {} };
265
270
  }
266
271
  }
267
272
  static getBuilder() {
@@ -1,4 +1,5 @@
1
1
  import { ExpressApp } from './types/ExpressApp.js';
2
+ import { ExpressRequest } from './types/ExpressRequest.js';
2
3
  import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
3
4
  import { OpenApi } from '../../OpenApi.js';
4
5
  import { AnyConfig } from '../../types/config/AnyConfig.js';
@@ -8,6 +9,7 @@ export declare class ExpressWrapper<TRouteTypes extends string, TErrorCodes exte
8
9
  protected developmentUtils: DevelopmentUtils;
9
10
  protected schemaRoute: RoutePath;
10
11
  constructor(openApi: OpenApi<TRouteTypes, TErrorCodes, TConfig>);
12
+ requestBodyToString(req: ExpressRequest): Promise<string | undefined>;
11
13
  createStoplightRoute(route: RoutePath, expressApp: ExpressApp): void;
12
14
  createSwaggerRoute(route: RoutePath, expressApp: ExpressApp): void;
13
15
  createSchemaRoute(route: RoutePath, expressApp: ExpressApp): void;
@@ -8,6 +8,25 @@ export class ExpressWrapper {
8
8
  this.service = openApi;
9
9
  this.developmentUtils = new DevelopmentUtils();
10
10
  }
11
+ async requestBodyToString(req) {
12
+ if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
13
+ return undefined;
14
+ }
15
+ return new Promise((resolve, reject) => {
16
+ const chunks = [];
17
+ req.on('data', (chunk) => {
18
+ chunks.push(chunk);
19
+ });
20
+ req.on('end', () => {
21
+ const bodyBuffer = Buffer.concat(chunks);
22
+ const bodyString = bodyBuffer.toString();
23
+ resolve(bodyString);
24
+ });
25
+ req.on('error', (error) => {
26
+ reject(error);
27
+ });
28
+ });
29
+ }
11
30
  createStoplightRoute(route, expressApp) {
12
31
  const handler = async (req, res) => {
13
32
  const body = this.developmentUtils.getStoplightHtml(this.schemaRoute);
@@ -50,13 +69,19 @@ export class ExpressWrapper {
50
69
  host: req.host,
51
70
  pathname: req.originalUrl,
52
71
  });
72
+ const body = await this.requestBodyToString(req);
53
73
  const openApiRequest = new Request(url, {
54
- body: req.body,
55
74
  headers: headers,
56
75
  method: req.method,
76
+ body: body,
57
77
  });
58
78
  const result = await this.service.processRootRoute(openApiRequest);
59
- res.status(result.status).header('Content-Type', 'application/json').json(result.body);
79
+ res.status(result.status);
80
+ res.header('Content-Type', 'application/json');
81
+ for (const header of Object.entries(result.headers)) {
82
+ res.header(header[0], header[1]);
83
+ }
84
+ res.json(result.body);
60
85
  };
61
86
  const regex = new RegExp(`${route}.*`);
62
87
  expressApp.get(regex, handler);
@@ -5,4 +5,7 @@ export interface ExpressRequest {
5
5
  protocol: string;
6
6
  originalUrl: string;
7
7
  host: string;
8
+ on(event: 'data', listener: (chunk: Buffer) => void): this;
9
+ on(event: 'end', listener: () => void): this;
10
+ on(event: 'error', listener: (error: Error) => void): this;
8
11
  }
@@ -6,5 +6,5 @@ import { RouteExtraProps } from '../../types/config/RouteExtraProps.js';
6
6
  export declare class RoutingFactory<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
7
7
  protected map: TConfig;
8
8
  constructor(map: TConfig);
9
- createRoute<TType extends TRouteTypes, TMethod extends Method, TResponseValidator extends ZodFirstPartySchemaTypes, TQueryValidator extends ZodObject<ZodRawShape> | undefined = undefined, TPathValidator extends ZodObject<ZodRawShape> | undefined = undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined = undefined>(params: Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TMethod> & (RouteExtraProps<TConfig['routes'][TType]['extraProps']>)): Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator>;
9
+ createRoute<TType extends TRouteTypes, TMethod extends Method, TResponseValidator extends ZodFirstPartySchemaTypes, TQueryValidator extends ZodObject<ZodRawShape> | undefined = undefined, TPathValidator extends ZodObject<ZodRawShape> | undefined = undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined = undefined, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined = undefined>(params: Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator, TMethod> & (RouteExtraProps<TConfig['routes'][TType]['extraProps']>)): Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator>;
10
10
  }
@@ -14,6 +14,7 @@ export class RoutingFactory {
14
14
  query: params.validators.query,
15
15
  path: params.validators.path,
16
16
  response: params.validators.response,
17
+ responseHeaders: params.validators.responseHeaders,
17
18
  body: params.validators.body,
18
19
  },
19
20
  handler: params.handler,
@@ -53,6 +53,7 @@ export class TanstackStartWrapper {
53
53
  const response = await this.service.processRootRoute(ctx.request);
54
54
  const res = new Response(JSON.stringify(response.body), {
55
55
  status: response.status,
56
+ headers: response.headers,
56
57
  });
57
58
  return res;
58
59
  };
@@ -1,3 +1,3 @@
1
1
  import { ZodObject, ZodRawShape, ZodFirstPartySchemaTypes } from 'zod';
2
2
  import { Route } from './Route.js';
3
- export type AnyRoute<TRouteType extends string> = Route<TRouteType, any, ZodFirstPartySchemaTypes, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined>;
3
+ export type AnyRoute<TRouteType extends string> = Route<TRouteType, any, ZodFirstPartySchemaTypes, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined>;
@@ -1,7 +1,13 @@
1
1
  import { z, ZodFirstPartySchemaTypes, ZodObject, ZodRawShape } from 'zod';
2
2
  import { Method } from '../enums/Methods.js';
3
3
  import { RoutePath } from './RoutePath.js';
4
- export interface Route<TType extends string, TContext extends object, TResponseValidator extends ZodFirstPartySchemaTypes, TPathValidator extends ZodObject<ZodRawShape> | undefined, TQueryValidator extends ZodObject<ZodRawShape> | undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined, TMethod extends Method = Method> {
4
+ type BodyHandlerResponse<T extends ZodFirstPartySchemaTypes> = Promise<z.infer<T>>;
5
+ type FullHandlerRespnse<T extends ZodFirstPartySchemaTypes, THeaders extends ZodObject<ZodRawShape>> = Promise<{
6
+ body: z.infer<T>;
7
+ headers: z.infer<THeaders>;
8
+ }>;
9
+ type HandlerResponse<T extends ZodFirstPartySchemaTypes, TH extends ZodObject<ZodRawShape> | undefined> = TH extends undefined ? BodyHandlerResponse<T> : FullHandlerRespnse<T, Exclude<TH, undefined>>;
10
+ export interface Route<TType extends string, TContext extends object, TResponseValidator extends ZodFirstPartySchemaTypes, TPathValidator extends ZodObject<ZodRawShape> | undefined, TQueryValidator extends ZodObject<ZodRawShape> | undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined, TMethod extends Method = Method> {
5
11
  type: TType;
6
12
  method: TMethod;
7
13
  path: RoutePath;
@@ -11,6 +17,7 @@ export interface Route<TType extends string, TContext extends object, TResponseV
11
17
  path?: TPathValidator;
12
18
  body?: TMethod extends 'GET' ? never : TBodyValidator;
13
19
  response: TResponseValidator;
20
+ responseHeaders?: TResponseHeadersValidator;
14
21
  };
15
22
  handler: (context: {
16
23
  params: {
@@ -18,5 +25,6 @@ export interface Route<TType extends string, TContext extends object, TResponseV
18
25
  query: TQueryValidator extends ZodObject<ZodRawShape> ? z.infer<TQueryValidator> : object;
19
26
  path: TPathValidator extends ZodObject<ZodRawShape> ? z.infer<TPathValidator> : object;
20
27
  };
21
- } & TContext) => Promise<z.infer<TResponseValidator>>;
28
+ } & TContext) => HandlerResponse<TResponseValidator, TResponseHeadersValidator>;
22
29
  }
30
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { Logger } from '../../services/Logger/Logger.js';
2
2
  import { LogLevel } from '../../services/Logger/types/LogLevel.js';
3
+ import { AnyRoute } from '../AnyRoute.js';
3
4
  import { RoutePath } from '../RoutePath.js';
4
5
  import { ErrorConfigMap } from './ErrorConfigMap.js';
5
6
  import { ErrorResponse } from './ErrorResponse.js';
@@ -18,4 +19,9 @@ export type Config<TRouteTypes extends string, TErrorCodes extends string, TErro
18
19
  servers?: Server[];
19
20
  logLevel?: LogLevel;
20
21
  handleError?: (e: unknown, req: Request) => ErrorResponse<TErrorCodes, TErrorConfigMap>;
22
+ middleware?: <T extends TRouteTypes>(route: AnyRoute<T>, ctx: Awaited<ReturnType<TRouteContextMap[T]>>) => Promise<{
23
+ body?: unknown;
24
+ status?: number;
25
+ headers?: Record<string, string>;
26
+ }>;
21
27
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "snap-on-openapi",
3
3
  "author": "Alex Sarychev",
4
- "version": "1.0.5",
4
+ "version": "1.0.7",
5
5
  "description": "Swiftly build type-checked OpenAPI applications with Zod and TypeScript",
6
6
  "type": "module",
7
7
  "license": "ISC",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "rm -rf dist && tsc && tsc-alias && cp -R ./assets ./dist && cp ./README.md ./dist",
19
- "check": "npm run test:typecheck && npm run lint && npm run test",
19
+ "check": "npm run test:typecheck && npm run lint",
20
20
  "test": "vitest --run",
21
21
  "test:gha": "npx vitest --run --coverage --reporter=verbose",
22
22
  "test:typecheck": "tsc -p tsconfig.test.json",