@gravity-ui/gateway 4.3.0 → 4.4.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/README.md CHANGED
@@ -1,16 +1,35 @@
1
1
  # @gravity-ui/gateway · [![npm package](https://img.shields.io/npm/v/@gravity-ui/gateway)](https://www.npmjs.com/package/@gravity-ui/gateway) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/gateway/.github/workflows/ci.yml?label=CI&logo=github)](https://github.com/gravity-ui/gateway/actions/workflows/ci.yml?query=branch:main)
2
2
 
3
- Express controller for working with REST/GRPC APIs.
4
-
5
- ## Install
3
+ A flexible and powerful Express controller for working with REST and gRPC APIs in Node.js applications.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Basic Usage](#basic-usage)
9
+ - [Configuration](#configuration)
10
+ - [Config Structure](#config-structure)
11
+ - [Validation Schema](#validation-schema)
12
+ - [Using the API in Node.js](#using-the-api-in-nodejs)
13
+ - [Schema Scopes](#schema-scopes)
14
+ - [Connecting Specific Actions](#connecting-specific-actions)
15
+ - [Overriding Endpoints](#overriding-endpoints)
16
+ - [Authentication](#authentication)
17
+ - [Error Handling](#error-handling)
18
+ - [gRPC Reflection](#grpc-reflection-for-grpc-actions)
19
+ - [Retryable Errors](#retryable-errors)
20
+ - [Development](#development)
21
+ - [Running Tests](#running-tests)
22
+ - [Contributing](#contributing)
23
+
24
+ ## Installation
6
25
 
7
26
  ```shell
8
- npm install --save-dev @gravity-ui/gateway
27
+ npm install --save @gravity-ui/gateway
9
28
  ```
10
29
 
11
- ## Usage
30
+ ## Basic Usage
12
31
 
13
- First of all, you need to create a controller where you will import Gateway and Schema, and then return the initialized gateway controller:
32
+ First, create a controller by importing Gateway and your API schemas:
14
33
 
15
34
  ```javascript
16
35
  import {getGatewayControllers} from '@gravity-ui/gateway';
@@ -27,7 +46,7 @@ const {controller: gatewayController} = getGatewayControllers({root: Schema}, co
27
46
  export default gatewayController;
28
47
  ```
29
48
 
30
- Next, the controller described above should be connected to a route of the following format (the project should use [expresskit](https://github.com/gravity-ui/expresskit)):
49
+ Then, connect the controller to your Express routes (using [expresskit](https://github.com/gravity-ui/expresskit)):
31
50
 
32
51
  ```javascript
33
52
  {
@@ -37,11 +56,12 @@ Next, the controller described above should be connected to a route of the follo
37
56
 
38
57
  The `prefix` can be any prefix for API endpoints (for example, `/gateway/:service/:action`).
39
58
 
40
- ### Config Structure
59
+ ## Configuration
41
60
 
42
61
  ```typescript
43
62
  import {AxiosRequestConfig} from 'axios';
44
63
  import {IncomingHttpHeaders} from 'http';
64
+ import {IAxiosRetryConfig} from 'axios-retry';
45
65
 
46
66
  interface OnUnknownActionData {
47
67
  service?: string;
@@ -67,6 +87,11 @@ type SendStats = (
67
87
  meta: {debugHeaders: Headers},
68
88
  ) => void;
69
89
 
90
+ type GrpcRetryCondition = (error: ServiceError) => boolean;
91
+ type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
92
+
93
+ type ControllerType = 'rest' | 'grpc';
94
+
70
95
  type ProxyHeadersFunction = (
71
96
  headers: IncomingHttpHeaders,
72
97
  type: ControllerType,
@@ -74,19 +99,48 @@ type ProxyHeadersFunction = (
74
99
  type ProxyHeaders = string[] | ProxyHeadersFunction;
75
100
  type ResponseContentType = AxiosResponse['headers']['Content-Type'];
76
101
 
102
+ type GetAuthHeadersParams<AuthArgs = Record<string, unknown>> = {
103
+ actionType: ControllerType;
104
+ serviceName: string;
105
+ requestHeaders: Headers;
106
+ authArgs: AuthArgs | undefined;
107
+ };
108
+
109
+ interface AppErrorArgs {
110
+ code?: string | number;
111
+ details?: object;
112
+ debug?: object;
113
+ }
114
+
115
+ interface AppErrorWrapArgs extends AppErrorArgs {
116
+ message?: string;
117
+ }
118
+
119
+ interface AppErrorConstructor {
120
+ new (message?: string, args?: AppErrorArgs): Error;
121
+
122
+ wrap: (error: Error, args?: AppErrorWrapArgs) => Error;
123
+ }
124
+
77
125
  interface GatewayConfig {
78
- // Gateway Installation (external/internal/...). If the configuration is not provided, it is determined from process.env.APP_INSTALLATION.
126
+ // Gateway Installation (external/internal/...). If not provided, determined from process.env.APP_INSTALLATION.
79
127
  installation?: string;
80
- // Gateway Environment (production/testing/...). If the configuration is not provided, it is determined from process.env.APP_ENV.
128
+
129
+ // Gateway Environment (production/testing/...). If not provided, determined from process.env.APP_ENV.
81
130
  env?: string;
131
+
82
132
  // Additional gRPC client options.
83
133
  grpcOptions?: object;
134
+
84
135
  // Additional Axios client options.
85
136
  axiosConfig?: AxiosRequestConfig;
86
- // List of actions that need to be connected from the schema. By default, all actions are connected.
137
+
138
+ // List of actions to connect from the schema. By default, all actions are connected.
87
139
  actions?: string[];
140
+
88
141
  // Called when an unknown service or action is provided.
89
142
  onUnknownAction?: (req: Request, res: Response, data: OnUnknownActionData) => any;
143
+
90
144
  // Called before the request is executed.
91
145
  onBeforeAction?: (
92
146
  req: Request,
@@ -96,42 +150,90 @@ interface GatewayConfig {
96
150
  action: string,
97
151
  config?: ApiServiceActionConfig,
98
152
  ) => any;
153
+
99
154
  // Called upon successful completion of the request.
100
155
  onRequestSuccess?: (req: Request, res: Response, data: any) => any;
156
+
101
157
  // Called in case of unsuccessful request execution.
102
158
  onRequestFailed?: (req: Request, res: Response, error: any) => any;
159
+
103
160
  // List of paths to the necessary proto files for the gateway.
104
161
  includeProtoRoots?: string[];
162
+
105
163
  // Configuration of the path to the certificate in gRPC.
106
164
  // Set to null to use system certificates by default.
107
165
  caCertificatePath?: string | null;
166
+
108
167
  // Telemetry sending configuration.
109
168
  sendStats?: SendStats;
169
+
110
170
  // Configuration of headers sent to the API.
111
171
  proxyHeaders?: ProxyHeaders;
172
+
112
173
  // When passing a boolean value, it enables/disables debug headers in the response to the request.
113
174
  // For unary requests to gRPC backends, debug headers will include information from the trailing metadata returned by the backend.
114
175
  withDebugHeaders?: boolean;
115
- // Validation schema for parameters used when no schema is present in the action. Documentation: https://ajv.js.org/json-schema.html#json-data-type
176
+
177
+ // Validation schema for parameters used when no schema is present in the action.
116
178
  // You can use DEFAULT_VALIDATION_SCHEMA from lib/constants.ts.
117
179
  validationSchema?: object;
180
+
118
181
  // Enables encoding of REST path arguments (default is true).
119
182
  encodePathArgs?: boolean;
183
+
120
184
  // Configuration for automatic connection re-establishment upon connection error through L3 load balancer (default is true).
121
185
  grpcRecreateService?: boolean;
122
- // Enable verification of response contentType header. Actual only for REST actions. This value can be set / redefined the in action confg.
186
+
187
+ // Customize retry behavior for grpc requests
188
+ grpcRetryCondition?: GrpcRetryCondition;
189
+
190
+ // Customize retry behavior for rest (axios) requests
191
+ axiosRetryCondition?: AxiosRetryCondition;
192
+
193
+ // Enable verification of response contentType header. Actual only for REST actions.
194
+ // This value can be set/redefined in the action config.
123
195
  expectedResponseContentType?: ResponseContentType | ResponseContentType[];
196
+
197
+ // Function to get authentication arguments for API requests
198
+ getAuthArgs: (req: Request, res: Response) => Record<string, unknown> | undefined;
199
+
200
+ // Function to get authentication headers for API requests
201
+ getAuthHeaders: (params: GetAuthHeadersParams) => Record<string, string> | undefined;
202
+
203
+ // Error constructor for handling errors
204
+ ErrorConstructor: AppErrorConstructor;
124
205
  }
125
206
  ```
126
207
 
127
- #### validationSchema
208
+ ### Validation Schema
209
+
210
+ By default, for path params in REST actions, the following regexp is used: `/^((?!(\.\.|\?|#|\\|\/)).)*$/i`.
211
+ If the parameter value does not pass validation, the `GATEWAY_INVALID_PARAM_VALUE` error is returned.
128
212
 
129
- By default, for path params in rest actions used the following regexp: `/^((?!(\.\.|\?|#|\\|\/)).)*$/i`.
130
- If the parameter value does not pass validation, the `GATEWAY_INVALID_PARAM_VALUE` value is returned.
213
+ You can use the `DEFAULT_VALIDATION_SCHEMA` from `lib/constants.ts` as a starting point:
131
214
 
132
- ### Usage in Node.js
215
+ ```javascript
216
+ export const DEFAULT_VALIDATION_SCHEMA = {
217
+ additionalProperties: {
218
+ oneOf: [
219
+ {
220
+ type: 'number',
221
+ },
222
+ {
223
+ type: 'string',
224
+ pattern: '^((?!(\\.\\.|\\?|#|\\\\|\\/)).)*$',
225
+ },
226
+ {
227
+ type: 'object',
228
+ },
229
+ ],
230
+ },
231
+ };
232
+ ```
233
+
234
+ ### Using the API in Node.js
133
235
 
134
- Upon gateway initialization, in addition to exporting the controller, it also exports an `api` object, which represents the core for executing requests to the backend.
236
+ In addition to the Express controller, the gateway also exports an `api` object for making direct requests to backend services:
135
237
 
136
238
  ```javascript
137
239
  import {getGatewayControllers} from '@gravity-ui/gateway';
@@ -146,15 +248,18 @@ const config = {
146
248
  };
147
249
 
148
250
  const {api: gatewayApi} = getGatewayControllers({root: Schema}, config);
149
- ```
150
-
151
- Subsequently, in the code, you can use it as follows:
152
251
 
153
- ```javascript
154
- gatewayApi[service][action](actionConfig);
252
+ // Use the API to make requests
253
+ const result = await gatewayApi.serviceName.actionName({
254
+ authArgs: {token: 'auth-token'},
255
+ requestId: '123',
256
+ headers: {},
257
+ args: {param1: 'value1'},
258
+ ctx: context,
259
+ });
155
260
  ```
156
261
 
157
- `actionConfig` has the following structure:
262
+ The `actionConfig` parameter has the following structure:
158
263
 
159
264
  ```typescript
160
265
  interface ApiActionConfig<Context, TRequestData> {
@@ -171,50 +276,49 @@ interface ApiActionConfig<Context, TRequestData> {
171
276
  ### Schema Scopes
172
277
 
173
278
  Each schema belongs to its own namespace. Service and action names between schemas are completely independent and can coincide. Each scope has an independent gRPC context, which eliminates naming conflicts between schemas in proto files.
174
- The scope name is the key in the first parameter of the object containing the schemas.
279
+
280
+ The scope name is the key in the first parameter of the object containing the schemas:
175
281
 
176
282
  ```javascript
177
283
  const schemasByScopes = {scope1: schema1, scope2: schema2};
178
284
  ```
179
285
 
180
- Example with two scope namespaces: `root` and `anotherScope`.
286
+ Example with two scope namespaces: `root` and `anotherScope`:
181
287
 
182
288
  ```javascript
183
289
  import {getGatewayControllers} from '@gravity-ui/gateway';
184
290
 
185
291
  const {
186
- controller, // Controller
187
- api, // API (for Node.js environment)
188
- } = getGatewayControllers({ root: rootSchema, anotherScope: anotherSchema}, config);
292
+ controller, // Controller
293
+ api, // API (for Node.js environment)
294
+ } = getGatewayControllers({root: rootSchema, anotherScope: anotherSchema}, config);
189
295
 
190
- // API calls are made by specifying the scope.
191
- const resultFromRoot = api.rootSchema.<root-service>.<root-action>(params);
192
- const resultFromAnother = api.anotherSchema.<another-service>.<another-action>(params);
296
+ // API calls are made by specifying the scope
297
+ const resultFromRoot = api.root.rootService.rootAction(params);
298
+ const resultFromAnother = api.anotherScope.anotherService.anotherAction(params);
193
299
  ```
194
300
 
195
- There is a special scope called root. Its methods can be invoked without explicitly specifying the scope.
301
+ There is a special scope called `root`. Its methods can be invoked without explicitly specifying the scope:
196
302
 
197
303
  ```javascript
198
- const resultFromRoot = api.rootSchema.<root-service>.<root-action>(params);
304
+ const resultFromRoot = api.root.rootService.rootAction(params);
199
305
  // Same result
200
- const sameResultFromRoot = api.<root-service>.<root-action>(params);
306
+ const sameResultFromRoot = api.rootService.rootAction(params);
201
307
  ```
202
308
 
203
- The controller for the expresskit will also expect the `:scope` parameter.
309
+ The controller for expresskit will also expect the `:scope` parameter. If the scope parameter is not specified, the default scope is assumed to be `root`.
204
310
 
205
311
  ```javascript
206
312
  {
207
- 'POST /<prefix>/:scope/:service/:action': {...}
313
+ 'POST /<prefix>/:scope/:service/:action': gatewayController
208
314
  }
209
315
  ```
210
316
 
211
- If the scope parameter is not specified, the default scope is assumed to be `root`.
212
-
213
- ### Connecting a Specific Set of Actions
317
+ ### Connecting Specific Actions
214
318
 
215
- When initializing the `gateway`, there is an option to explicitly specify the actions that need to be connected from the schemas. To do this, provide a list of available client-side actions in the `actions` field in the config. If `actions` are not provided, all actions from the schemas are connected by default.
319
+ You can explicitly specify which actions to connect from the schemas using the `actions` field in the config. If actions are not provided, all actions from the schemas are connected by default.
216
320
 
217
- ```typescript
321
+ ```javascript
218
322
  import {getGatewayControllers} from '@gravity-ui/gateway';
219
323
  import rootSchema from '<schemas package>';
220
324
  import localSchema from '../shared/schemas';
@@ -223,49 +327,221 @@ const config = {
223
327
  installation: 'external',
224
328
  env: 'production',
225
329
  includeProtoRoots: ['...'],
226
- actions: ['local.*', 'root.serviceA.*', 'root.serviceB.get'], // List of actions to be connected from the schemas. By default, all actions are connected.
330
+ actions: [
331
+ 'local.*', // All actions from the 'local' scope
332
+ 'root.serviceA.*', // All actions from 'serviceA' in the 'root' scope
333
+ 'root.serviceB.getUser', // Only the 'getUser' action from 'serviceB' in the 'root' scope
334
+ ],
227
335
  };
228
336
 
229
337
  const {api: gatewayApi} = getGatewayControllers({root: rootSchema, local: localSchema}, config);
230
338
  ```
231
339
 
232
- The following combinations are available for specifying connected actions:
340
+ Available patterns for specifying actions:
341
+
342
+ - `<scope>.*` - all actions from the specified scope
343
+ - `<scope>.<service>.*` - all actions from the specified service
344
+ - `<scope>.<service>.<action>` - only the specified action
345
+
346
+ **Note:** This configuration only affects client-side access. All actions remain accessible on the server side.
347
+
348
+ ### Overriding Endpoints
349
+
350
+ You can override specific endpoints using the `GATEWAY_ENDPOINTS_OVERRIDES` environment variable. This is useful for testing environments.
351
+
352
+ Example format:
353
+
354
+ ```javascript
355
+ GATEWAY_ENDPOINTS_OVERRIDES = JSON.stringify({
356
+ serviceName: {
357
+ endpoint: 'https://example.com',
358
+ },
359
+ 'example.exampleService': {
360
+ endpoint: 'https://overrided.example.com',
361
+ },
362
+ });
363
+ ```
364
+
365
+ ### Authentication
366
+
367
+ The gateway supports set up authentication through the `getAuthArgs` and `getAuthHeaders` config options:
368
+
369
+ ```javascript
370
+ const config = {
371
+ // ...other config options
372
+
373
+ // Get authentication arguments for request
374
+ getAuthArgs: (req, res) => ({
375
+ token: req.authorization.token,
376
+ }),
377
+
378
+ // Generate authentication headers for backend requests
379
+ getAuthHeaders: (params) => {
380
+ if (!params?.token) return undefined;
381
+
382
+ return {
383
+ Authorization: `Bearer ${params.token}`,
384
+ };
385
+ },
386
+ };
387
+ ```
388
+
389
+ You can also define service-specific authentication by adding a `getAuthHeaders` function to individual actions:
390
+
391
+ ```javascript
392
+ const schema = {
393
+ userService: {
394
+ serviceName: 'users',
395
+ endpoints: {...},
396
+ actions: {
397
+ getProfile: {
398
+ path: () => '/profile',
399
+ method: 'GET',
400
+ getAuthHeaders: (params) => ({
401
+ 'X-Special-Auth': params.token,
402
+ }),
403
+ },
404
+ },
405
+ },
406
+ };
407
+ ```
408
+
409
+ ### Error Handling
410
+
411
+ The gateway provides several ways to handle errors:
412
+
413
+ 1. **Error constructor** through the `ErrorConstructor` (reqiured field) config option:
414
+
415
+ ```javascript
416
+ class CustomError extends Error {
417
+ constructor(message, options = {}) {
418
+ super(message);
419
+ this.name = 'CustomError';
420
+ this.code = options.code || 'UNKNOWN_ERROR';
421
+ this.status = options.status || 500;
422
+ this.details = options.details;
423
+ }
424
+
425
+ static wrap(error) {
426
+ if (error instanceof CustomError) return error;
427
+ return new CustomError(error.message, {
428
+ code: error.code || 'INTERNAL_ERROR',
429
+ status: error.status || 500,
430
+ });
431
+ }
432
+ }
433
+
434
+ const config = {
435
+ // ...other config options
436
+ ErrorConstructor: CustomError,
437
+ };
438
+ ```
439
+
440
+ 2. **Custom request error handling** through the `onRequestFailed` config option:
441
+
442
+ ```javascript
443
+ const config = {
444
+ // ...other config options
445
+ onRequestFailed: (req, res, error) => {
446
+ console.error('Request failed:', error);
447
+ return res.status(error.status || 500).json({
448
+ error: error.message,
449
+ code: error.code,
450
+ });
451
+ },
452
+ };
453
+ ```
454
+
455
+ ### Retryable Errors
456
+
457
+ #### REST-actions
458
+
459
+ The **default** retry condition for REST-actions includes the following conditions:
460
+
461
+ - Network errors (detected by `axiosRetry.isNetworkError`)
462
+ - Other retryable errors (detected by `axiosRetry.isRetryableError`)
463
+
464
+ You can customize retry behavior for using the `axiosRetryCondition` config option:
465
+
466
+ ```javascript
467
+ const config = {
468
+ // ...other config options
469
+ axiosRetryCondition: (error) => {
470
+ // Custom logic to determine if the request should be retried
471
+ return error.code === 'TIMEOUT';
472
+ },
473
+ };
474
+ ```
475
+
476
+ #### gRPC-actions
477
+
478
+ The **default** retry condition for gRPC-actions includes the certain gRPC status codes:
233
479
 
234
- - `<scope>.*` - all actions from the scope scope are connected (for example, `local.*`)
235
- - `<scope>.<service>.*` - all actions from the service service are connected (for example, `root.serviceA.*`)
236
- - `<scope>.<service>.action` - only the specified action is connected (for example, `root.serviceB.get`)
480
+ - `UNAVAILABLE`
481
+ - `CANCELLED`
482
+ - `ABORTED`
483
+ - `UNKNOWN`
237
484
 
238
- **Important.** The actions configuration only affects the list of actions that will be accessible from the client (e.g., via the `sdk`). All actions from the schemas will continue to be accessible on Node.js.
485
+ You can customize retry behavior using the `grpcRetryCondition` config option:
239
486
 
240
- ### GATEWAY_ENDPOINTS_OVERRIDES
487
+ ```javascript
488
+ const config = {
489
+ // ...other config options
490
+ grpcRetryCondition: (error) => {
491
+ // Custom logic to determine if the request should be retried
492
+ return error.code === 'RESOURCE_EXHAUSTED';
493
+ },
494
+ };
495
+ ```
241
496
 
242
- Through the `GATEWAY_ENDPOINTS_OVERRIDES` environment variable, you can override specific endpoints. This can be useful for testing environments. A simple example: `{"serviceName":{"endpoint":"https://example.com"}}`. You can find a more detailed format in the OverrideParams interface and test examples.
497
+ For gRPC-requests that fail with `DEADLINE_EXCEEDED`, the service connection is recreated before retrying if config option `grpcRecreateService` is not set to `false`.
243
498
 
244
499
  ### gRPC Reflection for gRPC Actions
245
500
 
246
- Instead of using gRPC proto files, a gRPC action can determine the structure of the service and the required method through reflection.
501
+ Instead of using gRPC proto files, you can use gRPC reflection to determine the structure of services and methods.
502
+
503
+ **Prerequisites:**
247
504
 
248
- **Enabling Reflection**
505
+ 1. Install the `grpc-reflection-js` package:
249
506
 
250
- To use reflection, you need to:
507
+ ```shell
508
+ npm install --save grpc-reflection-js
509
+ ```
251
510
 
252
- - Install the `grpc-reflection-js` package as a peer dependency.
253
- - Apply patches to the `protobufjs` library. You can do this in the following ways:
511
+ 2. Apply patches to the `protobufjs` library:
512
+ - Add `npx gateway-reflection-patch` to your project's `postinstall` script. This assumes that protobufjs is located in the root of node_modules.
513
+ - Copy the patch from the library's patches folder to your project root, install [patch-package](https://www.npmjs.com/package/patch-package), and add the patch-package command to the `postinstall` script. In this case, you need to keep an eye on updates to the patches in the gateway when updating it.
254
514
 
255
- a) Add `npx gateway-reflection-patch` to the `postinstall` script in your project and execute it. This assumes that protobufjs is located in the root of node_modules.
515
+ If you encounter a "cannot run in wd [...]" error during Docker build, you can add unsafe-perm = true to your .npmrc file.
256
516
 
257
- b) Copy the patch from the library's patches folder to your project's root, install [patch-package](https://www.npmjs.com/package/patch-package), and add the `patch-package` command to the `postinstall` script. In this case, you need to keep an eye on updates to the patches in the gateway when updating it.
517
+ 3. Configure your action to use reflection:
258
518
 
259
- If you encounter a "cannot run in wd [...]" error during Docker build, you can add unsafe-perm = true to your .npmrc file as described here.
519
+ ```javascript
520
+ import {GrpcReflection} from '@gravity-ui/gateway';
260
521
 
261
- - In the `action` configuration, replace the `protoPath` option with the `reflection` option and set its value to the appropriate `GrpcReflection` enum value. For reflection to work, the endpoint must support it.
522
+ const schema = {
523
+ userService: {
524
+ serviceName: 'users',
525
+ endpoints: {...},
526
+ actions: {
527
+ getUser: {
528
+ protoKey: 'users.v1.UserService',
529
+ action: 'GetUser',
530
+ reflection: GrpcReflection.OnFirstRequest,
531
+ // Optional: refresh reflection cache every 3600 seconds (1 hour)
532
+ reflectionRefreshSec: 3600,
533
+ },
534
+ },
535
+ },
536
+ };
537
+ ```
262
538
 
263
- Possible values for `GrpcReflection`, affecting the caching of reflection results:
539
+ **Reflection Options:**
264
540
 
265
- - `OnFirstRequest` - Perform reflection on the first action request. Use cached reflections.
266
- - `OnEveryRequest` - Perform reflection before every action request. Do not use cached reflections.
541
+ - `GrpcReflection.OnFirstRequest` - Perform reflection on the first request. Use cached reflections.
542
+ - `GrpcReflection.OnEveryRequest` - Perform reflection before every request. Do not use cached reflections.
267
543
 
268
- For the `OnFirstRequest` options you can specify the reflectionRefreshSec parameter, which indicates how often in seconds the reflection cache can be updated in the background. Cache updates happen asynchronously and don't block the current request. The initial reflection request with an empty cache might introduce some delay in the request.
544
+ For the `OnFirstRequest` options you can specify the `reflectionRefreshSec` parameter, which indicates how often in seconds the reflection cache can be updated in the background. Cache updates happen asynchronously and don't block the current request. The initial reflection request with an empty cache might introduce some delay in the request.
269
545
 
270
546
  **Particularities**
271
547
 
@@ -282,3 +558,23 @@ For development, you need to apply the patch locally using the command `npx patc
282
558
  **ChannelCredentials Type Mismatch Error**
283
559
 
284
560
  This error can occur due to duplicate installations of the `@grpc/grpc-js` library. It's recommended to ensure that all versions of this library are aligned and consistent to avoid this issue.
561
+
562
+ ## Development
563
+
564
+ ### Running Tests
565
+
566
+ ```shell
567
+ # Run unit tests
568
+ npm test
569
+
570
+ # Run integration tests
571
+ npm run test-integration
572
+ ```
573
+
574
+ ### Contributing
575
+
576
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
577
+
578
+ ## License
579
+
580
+ MIT
@@ -600,13 +600,17 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
600
600
  (0, grpc_1.isRecreateServiceError)(error);
601
601
  const shouldRetry = error &&
602
602
  retries &&
603
- ((_b = (_a = options.grpcRetryCondition) === null || _a === void 0 ? void 0 : _a.call(options, error)) !== null && _b !== void 0 ? _b : (0, grpc_1.isRetryableError)(error));
603
+ ((_b = (_a = options.grpcRetryCondition) === null || _a === void 0 ? void 0 : _a.call(options, error)) !== null && _b !== void 0 ? _b : (0, grpc_1.isRetryableGrpcError)(error));
604
604
  if (shouldRecreateService) {
605
605
  ctx.log(`Service client for ${config.protoKey} is going to be re-created`);
606
606
  recreateService(service, timeout * 1.5, ctx, args);
607
607
  }
608
608
  if (shouldRetry) {
609
- ctx.logError(`Request failed, retrying ${retries--} more times`);
609
+ (0, common_2.handleError)(ErrorConstructor, error, ctx, `Request failed, retrying ${retries--} more times`, {
610
+ serviceName,
611
+ actionName,
612
+ debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
613
+ });
610
614
  // Update pointer to re-created client in local service variable
611
615
  try {
612
616
  service = await getService(args);
package/build/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { ApiWithRoot, GatewayConfig, GatewayRequest, GatewayResponse, SchemasByS
2
2
  import { GatewayContext } from './models/context';
3
3
  export * from './utils/typed-api';
4
4
  export * from './utils/grpc-reflection';
5
- export { isRetryableError as isRetryableGrpcError } from './utils/grpc';
5
+ export { isRetryableGrpcError } from './utils/grpc';
6
6
  export * from './models/common';
7
7
  export * from './models/context';
8
8
  export * from './models/error';
package/build/index.js CHANGED
@@ -40,7 +40,7 @@ const overrideEndpoints_1 = __importDefault(require("./utils/overrideEndpoints")
40
40
  __exportStar(require("./utils/typed-api"), exports);
41
41
  __exportStar(require("./utils/grpc-reflection"), exports);
42
42
  var grpc_2 = require("./utils/grpc");
43
- Object.defineProperty(exports, "isRetryableGrpcError", { enumerable: true, get: function () { return grpc_2.isRetryableError; } });
43
+ Object.defineProperty(exports, "isRetryableGrpcError", { enumerable: true, get: function () { return grpc_2.isRetryableGrpcError; } });
44
44
  __exportStar(require("./models/common"), exports);
45
45
  __exportStar(require("./models/context"), exports);
46
46
  __exportStar(require("./models/error"), exports);
@@ -1,5 +1,5 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
2
  import * as protobufjs from 'protobufjs';
3
3
  export declare function decodeAnyMessageRecursively(root: protobufjs.Root, message?: unknown, decodeAnyMessageProtoLoaderOptions?: protobufjs.IConversionOptions): unknown;
4
- export declare function isRetryableError(error?: grpc.ServiceError): boolean;
4
+ export declare function isRetryableGrpcError(error?: grpc.ServiceError): boolean;
5
5
  export declare function isRecreateServiceError(error?: grpc.ServiceError): boolean;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /* eslint-disable camelcase */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.isRecreateServiceError = exports.isRetryableError = exports.decodeAnyMessageRecursively = void 0;
4
+ exports.isRecreateServiceError = exports.isRetryableGrpcError = exports.decodeAnyMessageRecursively = void 0;
5
5
  const constants_1 = require("../constants");
6
6
  function isEncodedMessage(message) {
7
7
  return Boolean(message.type_url && message.value);
@@ -40,13 +40,13 @@ function decodeAnyMessageRecursively(root, message, decodeAnyMessageProtoLoaderO
40
40
  }
41
41
  }
42
42
  exports.decodeAnyMessageRecursively = decodeAnyMessageRecursively;
43
- function isRetryableError(error) {
43
+ function isRetryableGrpcError(error) {
44
44
  if (!error) {
45
45
  return false;
46
46
  }
47
47
  return constants_1.RETRYABLE_STATUS_CODES.includes(error.code);
48
48
  }
49
- exports.isRetryableError = isRetryableError;
49
+ exports.isRetryableGrpcError = isRetryableGrpcError;
50
50
  function isRecreateServiceError(error) {
51
51
  if (!error) {
52
52
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/gateway",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -42,8 +42,8 @@
42
42
  "@grpc/grpc-js": "^1.9.9",
43
43
  "@grpc/proto-loader": "^0.7.8",
44
44
  "ajv": "^8.12.0",
45
- "axios": "^1.3.5",
46
- "axios-retry": "^3.4.0",
45
+ "axios": "^1.8.3",
46
+ "axios-retry": "^3.9.1",
47
47
  "lodash": "^4.17.21",
48
48
  "object-sizeof": "^2.6.5",
49
49
  "protobufjs": "^7.2.5",