@lucaapp/service-utils 1.28.0 → 1.29.0
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/lib/api/api.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ type ApiConstructorOptions = {
|
|
|
10
10
|
apiVersion?: string;
|
|
11
11
|
apiTitle?: string;
|
|
12
12
|
apiDescription?: string;
|
|
13
|
+
debug?: boolean;
|
|
13
14
|
};
|
|
14
15
|
export declare class Api {
|
|
15
16
|
router: Router;
|
|
@@ -19,6 +20,7 @@ export declare class Api {
|
|
|
19
20
|
apiTitle: string;
|
|
20
21
|
apiDescription: string;
|
|
21
22
|
private prefixPath;
|
|
23
|
+
debug: boolean;
|
|
22
24
|
constructor(options?: ApiConstructorOptions);
|
|
23
25
|
get<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TMiddlewares extends ReadonlyArray<Middleware<any, any, any, any, any, any>> | undefined = undefined>(path: string, summary: string, options: EndpointOptions<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>, handler: EndpointHandler<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>): void;
|
|
24
26
|
post<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TMiddlewares extends ReadonlyArray<Middleware<any, any, any, any, any, any>> | undefined = undefined>(path: string, summary: string, options: EndpointOptions<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>, handler: EndpointHandler<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>): void;
|
package/dist/lib/api/api.js
CHANGED
|
@@ -18,26 +18,27 @@ class Api {
|
|
|
18
18
|
this.apiVersion = options.apiVersion || '1.0.0';
|
|
19
19
|
this.apiTitle = options.apiTitle || 'API';
|
|
20
20
|
this.apiDescription = options.apiDescription || '';
|
|
21
|
+
this.debug = options.debug || false;
|
|
21
22
|
this.mountSwaggerMiddlewares();
|
|
22
23
|
}
|
|
23
24
|
get(path, summary, options, handler) {
|
|
24
|
-
(0, endpoint_1.mountEndpoint)(this.router, 'get', path, options, handler);
|
|
25
|
+
(0, endpoint_1.mountEndpoint)(this.router, 'get', path, options, handler, this.debug);
|
|
25
26
|
(0, endpoint_1.registerEndpoint)(this.registry, 'get', this.prefixPath, path, summary, options);
|
|
26
27
|
}
|
|
27
28
|
post(path, summary, options, handler) {
|
|
28
|
-
(0, endpoint_1.mountEndpoint)(this.router, 'post', path, options, handler);
|
|
29
|
+
(0, endpoint_1.mountEndpoint)(this.router, 'post', path, options, handler, this.debug);
|
|
29
30
|
(0, endpoint_1.registerEndpoint)(this.registry, 'post', this.prefixPath, path, summary, options);
|
|
30
31
|
}
|
|
31
32
|
patch(path, summary, options, handler) {
|
|
32
|
-
(0, endpoint_1.mountEndpoint)(this.router, 'patch', path, options, handler);
|
|
33
|
+
(0, endpoint_1.mountEndpoint)(this.router, 'patch', path, options, handler, this.debug);
|
|
33
34
|
(0, endpoint_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, path, summary, options);
|
|
34
35
|
}
|
|
35
36
|
put(path, summary, options, handler) {
|
|
36
|
-
(0, endpoint_1.mountEndpoint)(this.router, 'put', path, options, handler);
|
|
37
|
+
(0, endpoint_1.mountEndpoint)(this.router, 'put', path, options, handler, this.debug);
|
|
37
38
|
(0, endpoint_1.registerEndpoint)(this.registry, 'put', this.prefixPath, path, summary, options);
|
|
38
39
|
}
|
|
39
40
|
delete(path, summary, options, handler) {
|
|
40
|
-
(0, endpoint_1.mountEndpoint)(this.router, 'delete', path, options, handler);
|
|
41
|
+
(0, endpoint_1.mountEndpoint)(this.router, 'delete', path, options, handler, this.debug);
|
|
41
42
|
(0, endpoint_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, path, summary, options);
|
|
42
43
|
}
|
|
43
44
|
generateOpenAPISpec() {
|
|
@@ -3,6 +3,6 @@ import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
|
|
3
3
|
import { Middleware, EndpointResponseSchema } from './types/middleware';
|
|
4
4
|
import { EndpointOptions, EndpointHandler } from './types/endpoint';
|
|
5
5
|
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
6
|
-
export declare const mountEndpoint: <TResponseSchemas extends readonly EndpointResponseSchema[], TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TMiddlewares extends readonly Middleware<any, any, any, any, any, any>[] | undefined = undefined>(router: Router, method: HttpMethod, path: string, options: EndpointOptions<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>, handler: EndpointHandler<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares
|
|
6
|
+
export declare const mountEndpoint: <TResponseSchemas extends readonly EndpointResponseSchema[], TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TMiddlewares extends readonly Middleware<any, any, any, any, any, any>[] | undefined = undefined>(router: Router, method: HttpMethod, path: string, options: EndpointOptions<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>, handler: EndpointHandler<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TMiddlewares>, debug: boolean) => void;
|
|
7
7
|
export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>) => void;
|
|
8
8
|
export {};
|
package/dist/lib/api/endpoint.js
CHANGED
|
@@ -10,19 +10,46 @@ const boom_1 = require("@hapi/boom");
|
|
|
10
10
|
const buildResponseConfig = (responseOptions) => {
|
|
11
11
|
const responses = {};
|
|
12
12
|
responseOptions.forEach(response => {
|
|
13
|
-
responses[String(response.status)]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const isAlreadyPresent = !!responses[String(response.status)];
|
|
14
|
+
if (!isAlreadyPresent) {
|
|
15
|
+
responses[String(response.status)] = {
|
|
16
|
+
description: response.description,
|
|
17
|
+
...(!(response.schema instanceof zod_1.z.ZodVoid) && {
|
|
18
|
+
content: {
|
|
19
|
+
'application/json': {
|
|
20
|
+
schema: response.schema,
|
|
21
|
+
},
|
|
19
22
|
},
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const existingResponse = responses[response.status];
|
|
28
|
+
existingResponse.description =
|
|
29
|
+
existingResponse.description + ' | ' + response.description;
|
|
30
|
+
// No Content merge
|
|
31
|
+
if (!response.schema) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// No Content
|
|
35
|
+
if (!existingResponse.content ||
|
|
36
|
+
!existingResponse.content['application/json']) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
existingResponse.content['application/json'].schema =
|
|
40
|
+
existingResponse.content['application/json'].schema.or(response.schema);
|
|
23
41
|
});
|
|
24
42
|
return responses;
|
|
25
43
|
};
|
|
44
|
+
const isTypedError = (error) => {
|
|
45
|
+
if (!(error instanceof Error))
|
|
46
|
+
return false;
|
|
47
|
+
// can't access possibly existing type property without casting to any
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
if (typeof error.type !== 'string')
|
|
50
|
+
return false;
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
26
53
|
const validateAndSendResponse = async (expressResponse, response, validResponses) => {
|
|
27
54
|
(0, assert_1.default)(typeof response.status === 'number');
|
|
28
55
|
// get schema respective to status code
|
|
@@ -39,7 +66,7 @@ const validateAndSendResponse = async (expressResponse, response, validResponses
|
|
|
39
66
|
}
|
|
40
67
|
expressResponse.status(response.status).send(validatedResponseBody);
|
|
41
68
|
};
|
|
42
|
-
const handleMiddleware = async (middleware, context, request, response) => new Promise(async (resolve, reject) => {
|
|
69
|
+
const handleMiddleware = async (middleware, context, request, response, debug) => new Promise(async (resolve, reject) => {
|
|
43
70
|
try {
|
|
44
71
|
const handlerRequestBody = ((await middleware.options.schemas?.body?.parseAsync(request.body)) ||
|
|
45
72
|
undefined);
|
|
@@ -47,11 +74,16 @@ const handleMiddleware = async (middleware, context, request, response) => new P
|
|
|
47
74
|
const handlerRequestQuery = ((await middleware.options.schemas?.query?.parseAsync(request.query)) ||
|
|
48
75
|
undefined);
|
|
49
76
|
const handlerRequestHeaders = ((await middleware.options.schemas?.headers?.parseAsync(request.headers)) || undefined);
|
|
77
|
+
const { ip, baseUrl, route, method } = request;
|
|
50
78
|
const handlerRequest = {
|
|
51
79
|
body: handlerRequestBody,
|
|
52
80
|
params: handlerRequestParams,
|
|
53
81
|
query: handlerRequestQuery,
|
|
54
82
|
headers: handlerRequestHeaders,
|
|
83
|
+
ip,
|
|
84
|
+
baseUrl,
|
|
85
|
+
method,
|
|
86
|
+
path: route.path,
|
|
55
87
|
};
|
|
56
88
|
await middleware.handler(handlerRequest, async (middlewareResponse) => {
|
|
57
89
|
try {
|
|
@@ -83,10 +115,21 @@ const handleMiddleware = async (middleware, context, request, response) => new P
|
|
|
83
115
|
});
|
|
84
116
|
}
|
|
85
117
|
catch (error) {
|
|
118
|
+
if (isTypedError(error) &&
|
|
119
|
+
middleware.options.errors &&
|
|
120
|
+
middleware.options.errors[error.type]) {
|
|
121
|
+
const statusCode = middleware.options.errors[error.type];
|
|
122
|
+
return response.status(statusCode).send({
|
|
123
|
+
statusCode,
|
|
124
|
+
error: error.type,
|
|
125
|
+
message: error.message,
|
|
126
|
+
...(debug && { stack: error.stack }),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
86
129
|
reject(error);
|
|
87
130
|
}
|
|
88
131
|
});
|
|
89
|
-
const mountEndpoint = (router, method, path, options, handler) => {
|
|
132
|
+
const mountEndpoint = (router, method, path, options, handler, debug) => {
|
|
90
133
|
(0, assert_1.default)(options.responses.length > 0, 'You need to specify at least one response');
|
|
91
134
|
router[method](path, async (request, response, next) => {
|
|
92
135
|
try {
|
|
@@ -106,7 +149,7 @@ const mountEndpoint = (router, method, path, options, handler) => {
|
|
|
106
149
|
const ctx = {};
|
|
107
150
|
if (options.middlewares instanceof Array) {
|
|
108
151
|
for (const middleware of options.middlewares) {
|
|
109
|
-
const shouldContinue = await handleMiddleware(middleware, ctx, request, response);
|
|
152
|
+
const shouldContinue = await handleMiddleware(middleware, ctx, request, response, debug);
|
|
110
153
|
if (!shouldContinue)
|
|
111
154
|
return;
|
|
112
155
|
}
|
|
@@ -129,6 +172,17 @@ const mountEndpoint = (router, method, path, options, handler) => {
|
|
|
129
172
|
if (error instanceof zod_1.z.ZodError && !(0, boom_1.isBoom)(error)) {
|
|
130
173
|
return response.status(400).send({ issues: error.issues });
|
|
131
174
|
}
|
|
175
|
+
if (isTypedError(error) &&
|
|
176
|
+
options.errors &&
|
|
177
|
+
options.errors[error.type]) {
|
|
178
|
+
const statusCode = options.errors[error.type];
|
|
179
|
+
return response.status(statusCode).send({
|
|
180
|
+
statusCode,
|
|
181
|
+
error: error.type,
|
|
182
|
+
message: error.message,
|
|
183
|
+
...(debug && { stack: error.stack }),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
132
186
|
next(error);
|
|
133
187
|
}
|
|
134
188
|
});
|
|
@@ -142,6 +196,22 @@ options) => {
|
|
|
142
196
|
let querySchema = options.schemas?.query || undefined;
|
|
143
197
|
let headersSchema = options.schemas?.headers || undefined;
|
|
144
198
|
let responseSchemas = options.responses;
|
|
199
|
+
if (options.errors) {
|
|
200
|
+
for (const errorType of Object.keys(options.errors)) {
|
|
201
|
+
const statusCode = options.errors[errorType];
|
|
202
|
+
responseSchemas = responseSchemas.concat([
|
|
203
|
+
{
|
|
204
|
+
status: statusCode,
|
|
205
|
+
description: errorType,
|
|
206
|
+
schema: zod_1.z.object({
|
|
207
|
+
statusCode: zod_1.z.literal(statusCode),
|
|
208
|
+
error: zod_1.z.literal(errorType),
|
|
209
|
+
message: zod_1.z.string(),
|
|
210
|
+
}),
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
145
215
|
for (const middleware of options.middlewares || []) {
|
|
146
216
|
if (middleware.options.schemas?.body) {
|
|
147
217
|
bodySchema = bodySchema
|
|
@@ -166,6 +236,22 @@ options) => {
|
|
|
166
236
|
if (middleware.options.responses) {
|
|
167
237
|
responseSchemas = responseSchemas.concat(middleware.options.responses);
|
|
168
238
|
}
|
|
239
|
+
if (middleware.options.errors) {
|
|
240
|
+
for (const errorType of Object.keys(middleware.options.errors)) {
|
|
241
|
+
const statusCode = middleware.options.errors[errorType];
|
|
242
|
+
responseSchemas = responseSchemas.concat([
|
|
243
|
+
{
|
|
244
|
+
status: statusCode,
|
|
245
|
+
description: errorType,
|
|
246
|
+
schema: zod_1.z.object({
|
|
247
|
+
statusCode: zod_1.z.literal(statusCode),
|
|
248
|
+
error: zod_1.z.literal(errorType),
|
|
249
|
+
message: zod_1.z.string(),
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
169
255
|
}
|
|
170
256
|
const responseMap = buildResponseConfig(responseSchemas);
|
|
171
257
|
registry.registerPath({
|
|
@@ -10,6 +10,10 @@ export type MiddlewareHandler<TResponseSchema, TRequestBody = undefined, TReques
|
|
|
10
10
|
params: TRequestParams;
|
|
11
11
|
query: TRequestQuery;
|
|
12
12
|
headers: TRequestHeaders;
|
|
13
|
+
ip: string;
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
path: string;
|
|
16
|
+
method: string;
|
|
13
17
|
}, send: (response: ExtractZodOutput<ArrayToUnion<TResponseSchema>>) => void, next: (context: TContext) => void) => Promise<void>;
|
|
14
18
|
export type MiddlewareOptions<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TContext = undefined> = {
|
|
15
19
|
schemas?: {
|
|
@@ -20,6 +24,7 @@ export type MiddlewareOptions<TResponseSchemas extends ReadonlyArray<EndpointRes
|
|
|
20
24
|
context?: z.ZodSchema<TContext>;
|
|
21
25
|
};
|
|
22
26
|
responses: TResponseSchemas;
|
|
27
|
+
errors?: Record<string, number>;
|
|
23
28
|
};
|
|
24
29
|
export type Middleware<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBody = undefined, TRequestParams = undefined, TRequestQuery = undefined, TRequestHeaders = undefined, TContext = undefined> = {
|
|
25
30
|
options: MiddlewareOptions<TResponseSchemas, TRequestBody, TRequestParams, TRequestQuery, TRequestHeaders, TContext>;
|