@lucaapp/service-utils 5.26.0 → 5.27.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.
@@ -28,6 +28,7 @@ export declare class Api {
28
28
  apiTitle: string;
29
29
  apiDescription: string;
30
30
  private prefixPath;
31
+ private rootPrefixPath;
31
32
  debug: boolean;
32
33
  tags: string[];
33
34
  constructor(options?: ApiConstructorOptions);
@@ -10,6 +10,7 @@ const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
10
10
  const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi");
11
11
  const endpoint_1 = require("./endpoint");
12
12
  const openapi_1 = require("./openapi");
13
+ const security_1 = require("./security");
13
14
  const body_parser_1 = __importDefault(require("body-parser"));
14
15
  const rawBodySaver = function (req, res, buf, encoding) {
15
16
  if (buf && buf.length) {
@@ -22,6 +23,7 @@ class Api {
22
23
  this.router = options.router || (0, express_1.Router)();
23
24
  this.registry = options.registry || new zod_to_openapi_1.OpenAPIRegistry();
24
25
  this.prefixPath = options.prefixPath || '';
26
+ this.rootPrefixPath = this.prefixPath;
25
27
  this.openApiVersion = options.openApiVersion || '3.0.0';
26
28
  this.apiVersion = options.apiVersion || '1.0.0';
27
29
  this.apiTitle = options.apiTitle || 'API';
@@ -31,33 +33,35 @@ class Api {
31
33
  this.router.use(body_parser_1.default.json({ verify: rawBodySaver, limit: '2mb' }));
32
34
  }
33
35
  child(options) {
34
- return new Api({
36
+ const child = new Api({
35
37
  prefixPath: this.prefixPath + (options.prefixPath || ''),
36
38
  debug: this.debug,
37
39
  tags: [...this.tags, ...(options.tags || [])],
38
40
  registry: this.registry,
39
41
  router: this.router,
40
42
  });
43
+ child.rootPrefixPath = this.rootPrefixPath;
44
+ return child;
41
45
  }
42
46
  get(path, summary, options, handler) {
43
47
  (0, endpoint_1.mountEndpoint)(this.router, 'get', path, options, handler, this.debug);
44
- (0, openapi_1.registerEndpoint)(this.registry, 'get', this.prefixPath, path, summary, options, this.tags, this.debug);
48
+ (0, openapi_1.registerEndpoint)(this.registry, 'get', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
45
49
  }
46
50
  post(path, summary, options, handler) {
47
51
  (0, endpoint_1.mountEndpoint)(this.router, 'post', path, options, handler, this.debug);
48
- (0, openapi_1.registerEndpoint)(this.registry, 'post', this.prefixPath, path, summary, options, this.tags, this.debug);
52
+ (0, openapi_1.registerEndpoint)(this.registry, 'post', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
49
53
  }
50
54
  patch(path, summary, options, handler) {
51
55
  (0, endpoint_1.mountEndpoint)(this.router, 'patch', path, options, handler, this.debug);
52
- (0, openapi_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, path, summary, options, this.tags, this.debug);
56
+ (0, openapi_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
53
57
  }
54
58
  put(path, summary, options, handler) {
55
59
  (0, endpoint_1.mountEndpoint)(this.router, 'put', path, options, handler, this.debug);
56
- (0, openapi_1.registerEndpoint)(this.registry, 'put', this.prefixPath, path, summary, options, this.tags, this.debug);
60
+ (0, openapi_1.registerEndpoint)(this.registry, 'put', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
57
61
  }
58
62
  delete(path, summary, options, handler) {
59
63
  (0, endpoint_1.mountEndpoint)(this.router, 'delete', path, options, handler, this.debug);
60
- (0, openapi_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, path, summary, options, this.tags, this.debug);
64
+ (0, openapi_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
61
65
  }
62
66
  generateOpenAPISpec() {
63
67
  let generator;
@@ -74,9 +78,11 @@ class Api {
74
78
  title: this.apiTitle,
75
79
  description: this.apiDescription,
76
80
  },
81
+ servers: [{ url: '/' }],
77
82
  });
78
83
  (0, openapi_1.forcePathParamsRequired)(spec);
79
84
  (0, openapi_1.hoistCombinatorRequiredFields)(spec);
85
+ spec.components = { ...spec.components, securitySchemes: security_1.SECURITY_SCHEMES };
80
86
  return spec;
81
87
  }
82
88
  mountSwaggerMiddlewares() {
@@ -3,5 +3,6 @@ export * from './api';
3
3
  export * from './endpoint';
4
4
  export * from './middleware';
5
5
  export * from './response';
6
+ export type { SecurityScheme } from './security';
6
7
  export * from './send';
7
8
  export { hoistCombinatorRequiredFields } from './openapi';
@@ -25,5 +25,5 @@ export declare const hoistCombinatorRequiredFields: (spec: {
25
25
  schemas?: Record<string, unknown>;
26
26
  };
27
27
  }) => void;
28
- export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
28
+ export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, rootPrefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
29
29
  export {};
@@ -4,6 +4,21 @@ exports.registerEndpoint = exports.hoistCombinatorRequiredFields = exports.force
4
4
  const zod_1 = require("zod");
5
5
  const http_1 = require("./types/http");
6
6
  const mapExpressParamsToOpenAPIParams = (path) => path.replaceAll(/\:([^\:\/]+)/g, '{$1}');
7
+ const deriveOperationId = (registry, method, path) => {
8
+ const operationId = method +
9
+ path
10
+ .replaceAll(':', 'by-')
11
+ .split(/[^A-Za-z0-9]+/)
12
+ .filter(Boolean)
13
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
14
+ .join('');
15
+ const isTaken = registry.definitions.some(definition => definition.type === 'route' &&
16
+ definition.route.operationId === operationId);
17
+ if (isTaken) {
18
+ throw new Error(`Duplicate operationId ${operationId} for ${method.toUpperCase()} ${path}, set options.operationId`);
19
+ }
20
+ return operationId;
21
+ };
7
22
  const buildResponseConfig = (responseOptions) => {
8
23
  const responses = {};
9
24
  responseOptions.forEach(response => {
@@ -210,7 +225,7 @@ const hoistCombinatorRequiredFields = (spec) => {
210
225
  visit(schemas);
211
226
  };
212
227
  exports.hoistCombinatorRequiredFields = hoistCombinatorRequiredFields;
213
- const registerEndpoint = (registry, method, prefixPath, path, summary,
228
+ const registerEndpoint = (registry, method, prefixPath, rootPrefixPath, path, summary,
214
229
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
230
  options, tags, debug) => {
216
231
  let bodySchema = options.schemas?.body || undefined;
@@ -218,6 +233,7 @@ options, tags, debug) => {
218
233
  let querySchema = options.schemas?.query || undefined;
219
234
  let headersSchema = options.schemas?.headers || undefined;
220
235
  let responseSchemas = options.responses;
236
+ const securitySchemes = new Set();
221
237
  if (options.errors) {
222
238
  for (const errorType of Object.keys(options.errors)) {
223
239
  const statusCode = options.errors[errorType];
@@ -278,6 +294,9 @@ options, tags, debug) => {
278
294
  });
279
295
  }
280
296
  }
297
+ for (const scheme of middleware.options.security || []) {
298
+ securitySchemes.add(scheme);
299
+ }
281
300
  }
282
301
  // Add ZodError response
283
302
  responseSchemas.push({
@@ -334,7 +353,11 @@ options, tags, debug) => {
334
353
  headers: headersSchema ? [headersSchema] : [],
335
354
  },
336
355
  responses: responseMap,
337
- ...(options.operationId && { operationId: options.operationId }),
356
+ operationId: options.operationId ||
357
+ deriveOperationId(registry, method, prefixPath.slice(rootPrefixPath.length) + path),
358
+ ...(securitySchemes.size > 0 && {
359
+ security: [...securitySchemes].map(scheme => ({ [scheme]: [] })),
360
+ }),
338
361
  });
339
362
  };
340
363
  exports.registerEndpoint = registerEndpoint;
@@ -0,0 +1,3 @@
1
+ import type { SecuritySchemeObject } from 'openapi3-ts/oas30';
2
+ export type SecurityScheme = 'jwt_operator' | 'jwt_support' | 'jwt_operator_device' | 'jwt_consumer' | 'api_key';
3
+ export declare const SECURITY_SCHEMES: Record<SecurityScheme, SecuritySchemeObject>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SECURITY_SCHEMES = void 0;
4
+ const X_AUTH_HEADER = {
5
+ type: 'apiKey',
6
+ in: 'header',
7
+ name: 'X-Auth',
8
+ };
9
+ exports.SECURITY_SCHEMES = {
10
+ jwt_operator: X_AUTH_HEADER,
11
+ jwt_support: X_AUTH_HEADER,
12
+ jwt_operator_device: X_AUTH_HEADER,
13
+ jwt_consumer: X_AUTH_HEADER,
14
+ api_key: { type: 'apiKey', in: 'header', name: 'X-Api-Key' },
15
+ };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { SecurityScheme } from '../security';
2
3
  import { ArrayToUnion, ExtractZodOutput, ZodSchemaOuput } from './utils';
3
4
  export type EndpointResponseSchema = {
4
5
  status: number;
@@ -45,6 +46,7 @@ export type MiddlewareOptions<TResponseSchemas extends ReadonlyArray<EndpointRes
45
46
  returnRawBody?: boolean;
46
47
  };
47
48
  errors?: Record<string, number>;
49
+ security?: ReadonlyArray<SecurityScheme>;
48
50
  };
49
51
  export type Middleware<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TContextSchema> = {
50
52
  options: MiddlewareOptions<TResponseSchemas, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TContextSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucaapp/service-utils",
3
- "version": "5.26.0",
3
+ "version": "5.27.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -88,7 +88,7 @@
88
88
  "tar": "7.5.21",
89
89
  "body-parser": "1.20.6",
90
90
  "lodash": "^4.18.1",
91
- "qs": "6.15.2",
91
+ "qs": "6.16.0",
92
92
  "dottie": "2.0.7",
93
93
  "fast-xml-parser": "^5.7.1",
94
94
  "yaml": "^2.8.3",