@gravity-ui/gateway 4.6.0 → 4.7.1-alpha.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.
Files changed (95) hide show
  1. package/README.md +179 -3
  2. package/{build → dist/commonjs}/components/grpc.d.ts +5 -5
  3. package/{build → dist/commonjs}/components/grpc.js +109 -90
  4. package/{build → dist/commonjs}/components/mixed.d.ts +4 -4
  5. package/{build → dist/commonjs}/components/mixed.js +11 -12
  6. package/{build → dist/commonjs}/components/rest.d.ts +5 -5
  7. package/{build → dist/commonjs}/components/rest.js +42 -35
  8. package/{build → dist/commonjs}/constants.d.ts +2 -2
  9. package/{build → dist/commonjs}/constants.js +29 -19
  10. package/{build → dist/commonjs}/index.d.ts +9 -8
  11. package/{build → dist/commonjs}/index.js +35 -46
  12. package/{build → dist/commonjs}/models/common.d.ts +19 -8
  13. package/{build → dist/commonjs}/models/context.d.ts +0 -1
  14. package/dist/commonjs/package.json +3 -0
  15. package/dist/commonjs/utils/axios.d.ts +4 -0
  16. package/{build → dist/commonjs}/utils/axios.js +3 -4
  17. package/{build → dist/commonjs}/utils/common.d.ts +4 -3
  18. package/{build → dist/commonjs}/utils/common.js +19 -8
  19. package/{build → dist/commonjs}/utils/create-context-api.d.ts +2 -2
  20. package/{build → dist/commonjs}/utils/create-context-api.js +5 -6
  21. package/{build → dist/commonjs}/utils/grpc-reflection.js +15 -35
  22. package/{build → dist/commonjs}/utils/grpc.d.ts +1 -1
  23. package/{build → dist/commonjs}/utils/grpc.js +10 -11
  24. package/dist/commonjs/utils/overrideEndpoints/index.d.ts +2 -0
  25. package/dist/commonjs/utils/overrideEndpoints/index.js +4 -0
  26. package/{build → dist/commonjs}/utils/overrideEndpoints/overrideEndpoints.d.ts +1 -1
  27. package/{build → dist/commonjs}/utils/overrideEndpoints/overrideEndpoints.js +1 -2
  28. package/dist/commonjs/utils/package-root.d.ts +1 -0
  29. package/dist/commonjs/utils/package-root.js +44 -0
  30. package/{build → dist/commonjs}/utils/parse-error.d.ts +5 -5
  31. package/{build → dist/commonjs}/utils/parse-error.js +19 -19
  32. package/{build → dist/commonjs}/utils/proto-path-resolver.d.ts +1 -1
  33. package/{build → dist/commonjs}/utils/proto-path-resolver.js +24 -15
  34. package/{build → dist/commonjs}/utils/redact-sensitive-headers.d.ts +1 -2
  35. package/{build → dist/commonjs}/utils/redact-sensitive-headers.js +1 -2
  36. package/dist/commonjs/utils/source-dir.d.ts +1 -0
  37. package/dist/commonjs/utils/source-dir.js +41 -0
  38. package/{build → dist/commonjs}/utils/typed-api.d.ts +1 -1
  39. package/{build → dist/commonjs}/utils/typed-api.js +1 -2
  40. package/{build → dist/commonjs}/utils/validate.js +6 -10
  41. package/dist/esm/components/grpc.d.ts +24 -0
  42. package/dist/esm/components/grpc.js +691 -0
  43. package/dist/esm/components/mixed.d.ts +11 -0
  44. package/dist/esm/components/mixed.js +62 -0
  45. package/dist/esm/components/rest.d.ts +8 -0
  46. package/dist/esm/components/rest.js +357 -0
  47. package/dist/esm/constants.d.ts +53 -0
  48. package/dist/esm/constants.js +82 -0
  49. package/dist/esm/index.d.ts +13 -0
  50. package/dist/esm/index.js +274 -0
  51. package/dist/esm/models/common.d.ts +289 -0
  52. package/dist/esm/models/common.js +5 -0
  53. package/dist/esm/models/context.d.ts +22 -0
  54. package/dist/esm/models/context.js +1 -0
  55. package/dist/esm/models/error.d.ts +12 -0
  56. package/dist/esm/models/error.js +1 -0
  57. package/dist/esm/package.json +3 -0
  58. package/{build → dist/esm}/utils/axios.d.ts +1 -1
  59. package/dist/esm/utils/axios.js +24 -0
  60. package/dist/esm/utils/common.d.ts +16 -0
  61. package/dist/esm/utils/common.js +48 -0
  62. package/dist/esm/utils/create-context-api.d.ts +4 -0
  63. package/dist/esm/utils/create-context-api.js +38 -0
  64. package/dist/esm/utils/grpc-reflection.d.ts +28 -0
  65. package/dist/esm/utils/grpc-reflection.js +72 -0
  66. package/dist/esm/utils/grpc.d.ts +15 -0
  67. package/dist/esm/utils/grpc.js +72 -0
  68. package/dist/esm/utils/overrideEndpoints/index.d.ts +2 -0
  69. package/dist/esm/utils/overrideEndpoints/index.js +2 -0
  70. package/dist/esm/utils/overrideEndpoints/overrideEndpoints.d.ts +17 -0
  71. package/dist/esm/utils/overrideEndpoints/overrideEndpoints.js +96 -0
  72. package/dist/esm/utils/package-root.d.ts +1 -0
  73. package/dist/esm/utils/package-root.js +8 -0
  74. package/dist/esm/utils/parse-error.d.ts +30 -0
  75. package/dist/esm/utils/parse-error.js +214 -0
  76. package/dist/esm/utils/proto-path-resolver.d.ts +2 -0
  77. package/dist/esm/utils/proto-path-resolver.js +23 -0
  78. package/dist/esm/utils/redact-sensitive-headers.d.ts +3 -0
  79. package/dist/esm/utils/redact-sensitive-headers.js +12 -0
  80. package/dist/esm/utils/source-dir.d.ts +1 -0
  81. package/dist/esm/utils/source-dir.js +4 -0
  82. package/dist/esm/utils/typed-api.d.ts +2 -0
  83. package/dist/esm/utils/typed-api.js +3 -0
  84. package/dist/esm/utils/validate.d.ts +4 -0
  85. package/dist/esm/utils/validate.js +47 -0
  86. package/package.json +41 -16
  87. package/build/utils/overrideEndpoints/index.d.ts +0 -2
  88. package/build/utils/overrideEndpoints/index.js +0 -4
  89. /package/bin/{patch.js → patch.cjs} +0 -0
  90. /package/{build → dist/commonjs}/models/common.js +0 -0
  91. /package/{build → dist/commonjs}/models/context.js +0 -0
  92. /package/{build → dist/commonjs}/models/error.d.ts +0 -0
  93. /package/{build → dist/commonjs}/models/error.js +0 -0
  94. /package/{build → dist/commonjs}/utils/grpc-reflection.d.ts +0 -0
  95. /package/{build → dist/commonjs}/utils/validate.d.ts +0 -0
package/README.md CHANGED
@@ -7,7 +7,6 @@ A flexible and powerful Express controller for working with REST and gRPC APIs i
7
7
  - [Installation](#installation)
8
8
  - [Basic Usage](#basic-usage)
9
9
  - [Configuration](#configuration)
10
- - [Config Structure](#config-structure)
11
10
  - [Validation Schema](#validation-schema)
12
11
  - [Using the API in Node.js](#using-the-api-in-nodejs)
13
12
  - [Schema Scopes](#schema-scopes)
@@ -17,6 +16,8 @@ A flexible and powerful Express controller for working with REST and gRPC APIs i
17
16
  - [Error Handling](#error-handling)
18
17
  - [gRPC Reflection](#grpc-reflection-for-grpc-actions)
19
18
  - [Retryable Errors](#retryable-errors)
19
+ - [Request Cancellation](#request-cancellation)
20
+ - [Response Content Type Validation](#response-content-type-validation)
20
21
  - [Development](#development)
21
22
  - [Running Tests](#running-tests)
22
23
  - [Contributing](#contributing)
@@ -73,11 +74,14 @@ interface Stats {
73
74
  action: string;
74
75
  restStatus: number;
75
76
  grpcStatus?: number;
77
+ responseSize: number;
76
78
  requestId: string;
77
79
  requestTime: number;
78
80
  requestMethod: string;
79
81
  requestUrl: string;
80
82
  timestamp: number;
83
+ userId?: string;
84
+ traceId: string;
81
85
  }
82
86
 
83
87
  type SendStats = (
@@ -92,9 +96,18 @@ type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
92
96
 
93
97
  type ControllerType = 'rest' | 'grpc';
94
98
 
99
+ type ProxyHeadersFunctionExtra = {
100
+ service: string;
101
+ action: string;
102
+
103
+ protopath?: string;
104
+ protokey?: string;
105
+ };
106
+
95
107
  type ProxyHeadersFunction = (
96
108
  headers: IncomingHttpHeaders,
97
109
  type: ControllerType,
110
+ extra: ProxyHeadersFunctionExtra,
98
111
  ) => IncomingHttpHeaders;
99
112
  type ProxyHeaders = string[] | ProxyHeadersFunction;
100
113
  type ResponseContentType = AxiosResponse['headers']['Content-Type'];
@@ -172,7 +185,7 @@ interface GatewayConfig {
172
185
 
173
186
  // When passing a boolean value, it enables/disables debug headers in the response to the request.
174
187
  // For unary requests to gRPC backends, debug headers will include information from the trailing metadata returned by the backend.
175
- withDebugHeaders?: boolean;
188
+ withDebugHeaders?: boolean | ((req: Request, res: Response) => boolean);
176
189
 
177
190
  // Validation schema for parameters used when no schema is present in the action.
178
191
  // You can use DEFAULT_VALIDATION_SCHEMA from lib/constants.ts.
@@ -202,9 +215,63 @@ interface GatewayConfig {
202
215
 
203
216
  // Error constructor for handling errors
204
217
  ErrorConstructor: AppErrorConstructor;
218
+
219
+ // Axios interceptors configuration
220
+ axiosInterceptors?: AxiosInterceptorsConfig;
205
221
  }
206
222
  ```
207
223
 
224
+ ### `proxyHeaders`
225
+
226
+ `GatewayConfig.proxyHeaders` is an optional method that allows setting headers for requests at the entire `gateway` level:
227
+
228
+ ```javascript
229
+ const proxyHeaders = (headers, actionType, extra) => {
230
+ const normalizedHeaders = {...headers};
231
+ const {service, action, protopath, protokey} = extra;
232
+
233
+ if (actionType === 'rest' && service === 'mail') {
234
+ normalizedHeaders['x-mail-service-action'] = action;
235
+ }
236
+
237
+ return normalizedHeaders;
238
+ };
239
+
240
+ const {controller: gatewayController} = getGatewayControllers(
241
+ {root: Schema},
242
+ {...config, proxyHeaders},
243
+ );
244
+ ```
245
+
246
+ The `extra` parameter contains additional information about the request:
247
+
248
+ - `service`: The service name
249
+ - `action`: The action name
250
+ - `protopath`: The proto path (for gRPC actions)
251
+ - `protokey`: The proto key (for gRPC actions)
252
+
253
+ You can set headers for a specific action using `ApiServiceBaseActionConfig.proxyHeaders`:
254
+
255
+ ```javascript
256
+ const schema = {
257
+ userService: {
258
+ serviceName: 'users',
259
+ endpoints: {...},
260
+ actions: {
261
+ getProfile: {
262
+ path: () => '/profile',
263
+ method: 'GET',
264
+ proxyHeaders: (headers) => ({...headers, ['x-users-service-action']: 'get-profile'}),
265
+ },
266
+ },
267
+ },
268
+ };
269
+ ```
270
+
271
+ The `GatewayConfig.proxyHeaders` and `ApiServiceBaseActionConfig.proxyHeaders` are merged when the action is called. The strategy for merging headers is not guaranteed.
272
+
273
+ It is recommended to use `GatewayConfig.proxyHeaders` for assigning headers that are common to the entire application or a large number of actions. Otherwise, it is preferable to use `ApiServiceBaseActionConfig.proxyHeaders`.
274
+
208
275
  ### Validation Schema
209
276
 
210
277
  By default, for path params in REST actions, the following regexp is used: `/^((?!(\.\.|\?|#|\\|\/)).)*$/i`.
@@ -270,6 +337,8 @@ interface ApiActionConfig<Context, TRequestData> {
270
337
  timeout?: number;
271
338
  callback?: (response: TResponseData) => void;
272
339
  authArgs?: Record<string, unknown>;
340
+ userId?: string;
341
+ abortSignal?: AbortSignal;
273
342
  }
274
343
  ```
275
344
 
@@ -461,7 +530,7 @@ The **default** retry condition for REST-actions includes the following conditio
461
530
  - Network errors (detected by `axiosRetry.isNetworkError`)
462
531
  - Other retryable errors (detected by `axiosRetry.isRetryableError`)
463
532
 
464
- You can customize retry behavior for using the `axiosRetryCondition` config option:
533
+ You can customize retry behavior using the `axiosRetryCondition` config option:
465
534
 
466
535
  ```javascript
467
536
  const config = {
@@ -473,6 +542,27 @@ const config = {
473
542
  };
474
543
  ```
475
544
 
545
+ You can also set retry conditions at the action level:
546
+
547
+ ```javascript
548
+ const schema = {
549
+ userService: {
550
+ serviceName: 'users',
551
+ endpoints: {...},
552
+ actions: {
553
+ getProfile: {
554
+ path: () => '/profile',
555
+ method: 'GET',
556
+ axiosRetryCondition: (error) => {
557
+ // Custom logic for this specific action
558
+ return error.code === 'ECONNRESET';
559
+ },
560
+ },
561
+ },
562
+ },
563
+ };
564
+ ```
565
+
476
566
  #### gRPC-actions
477
567
 
478
568
  The **default** retry condition for gRPC-actions includes the certain gRPC status codes:
@@ -494,8 +584,94 @@ const config = {
494
584
  };
495
585
  ```
496
586
 
587
+ The library exports the `isRetryableGrpcError` function that you can use to check if a gRPC error is retryable according to the default conditions:
588
+
589
+ ```javascript
590
+ import {isRetryableGrpcError} from '@gravity-ui/gateway';
591
+
592
+ // Use in your custom retry condition
593
+ const customGrpcRetryCondition = (error) => {
594
+ return isRetryableGrpcError(error) || error.code === 'RESOURCE_EXHAUSTED';
595
+ };
596
+ ```
597
+
497
598
  For gRPC-requests that fail with `DEADLINE_EXCEEDED`, the service connection is recreated before retrying if config option `grpcRecreateService` is not set to `false`.
498
599
 
600
+ ### Request Cancellation
601
+
602
+ The gateway supports cancelling requests when the client disconnects. This is useful for long-running operations where you want to avoid unnecessary processing if the client is no longer waiting for the response.
603
+
604
+ This feature is enabled by default for exported controller. For API requests, you can pass an `AbortSignal` to cancel the request:
605
+
606
+ ```javascript
607
+ const abortController = new AbortController();
608
+
609
+ const result = await gatewayApi.serviceName.actionName({
610
+ authArgs: {token: 'auth-token'},
611
+ requestId: '123',
612
+ headers: {},
613
+ args: {param1: 'value1'},
614
+ ctx: context,
615
+ abortSignal: abortController.signal,
616
+ });
617
+ ```
618
+
619
+ You can also control this behavior at the action level using the `abortOnClientDisconnect` option:
620
+
621
+ ```javascript
622
+ const schema = {
623
+ userService: {
624
+ serviceName: 'users',
625
+ endpoints: {...},
626
+ actions: {
627
+ longRunningOperation: {
628
+ path: () => '/process',
629
+ method: 'POST',
630
+ abortOnClientDisconnect: true, // Enable cancellation for this action
631
+ },
632
+ },
633
+ },
634
+ };
635
+ ```
636
+
637
+ ### Response Content Type Validation
638
+
639
+ For REST actions, you can validate the content type of the response to ensure it matches your expectations. This is useful for ensuring that the API returns the expected format.
640
+
641
+ You can set the expected content type at the gateway level:
642
+
643
+ ```javascript
644
+ const config = {
645
+ // ...other config options
646
+ expectedResponseContentType: 'application/json',
647
+ };
648
+ ```
649
+
650
+ Or at the action level:
651
+
652
+ ```javascript
653
+ const schema = {
654
+ userService: {
655
+ serviceName: 'users',
656
+ endpoints: {...},
657
+ actions: {
658
+ getProfile: {
659
+ path: () => '/profile',
660
+ method: 'GET',
661
+ expectedResponseContentType: 'application/json',
662
+ },
663
+ getDocument: {
664
+ path: () => '/document',
665
+ method: 'GET',
666
+ expectedResponseContentType: ['application/pdf', 'application/octet-stream'],
667
+ },
668
+ },
669
+ },
670
+ };
671
+ ```
672
+
673
+ You can specify either a single content type or an array of acceptable content types. If the response content type doesn't match any of the expected types, an error will be thrown.
674
+
499
675
  ### gRPC Reflection for gRPC Actions
500
676
 
501
677
  Instead of using gRPC proto files, you can use gRPC reflection to determine the structure of services and methods.
@@ -1,9 +1,9 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
- import * as protobufjs from 'protobufjs';
2
+ import protobufjs from 'protobufjs';
3
3
  import type * as descriptor from 'protobufjs/ext/descriptor';
4
- import { ApiActionConfig, ApiServiceGrpcActionConfig, EndpointsConfig, GatewayApiOptions } from '../models/common';
5
- import { GatewayContext } from '../models/context';
6
- import { AppErrorConstructor } from '../models/error';
4
+ import { ApiActionConfig, ApiServiceGrpcActionConfig, EndpointsConfig, GatewayApiOptions } from '../models/common.js';
5
+ import { GatewayContext } from '../models/context.js';
6
+ import { AppErrorConstructor } from '../models/error.js';
7
7
  declare module 'protobufjs' {
8
8
  interface Root {
9
9
  toDescriptor(protoVersion: string): protobufjs.Message<descriptor.IFileDescriptorSet> & descriptor.IFileDescriptorSet;
@@ -20,5 +20,5 @@ export interface GrpcContext {
20
20
  }
21
21
  export declare function createRoot(includeGrpcPaths?: string[]): protobufjs.Root;
22
22
  export declare function getCredentialsMap(caCertificatePath?: string | null): CredentialsMap;
23
- export default function createGrpcAction<Context extends GatewayContext>({ root, credentials }: GrpcContext, endpoints: EndpointsConfig | undefined, config: ApiServiceGrpcActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor): (actionConfig: ApiActionConfig<Context, any, any>) => Promise<import("../models/common").GatewayActionClientStreamResponse<any> | import("../models/common").GatewayActionServerStreamResponse<any> | import("../models/common").GatewayActionDuplexStreamResponse<any> | import("../models/common").GatewayActionUnaryResponse<any>>;
23
+ export declare function createGrpcAction<Context extends GatewayContext>({ root, credentials }: GrpcContext, endpoints: EndpointsConfig | undefined, config: ApiServiceGrpcActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor): (actionConfig: ApiActionConfig<Context, any, any>) => Promise<import("../models/common.js").GatewayActionClientStreamResponse<any> | import("../models/common.js").GatewayActionServerStreamResponse<any> | import("../models/common.js").GatewayActionDuplexStreamResponse<any> | import("../models/common.js").GatewayActionUnaryResponse<any>>;
24
24
  export {};