@rainbow-o23/n3 1.0.57 → 1.0.58-alpha.2

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
@@ -592,13 +592,17 @@ step set. Additionally, nested transactions are also supported, which means Tran
592
592
 
593
593
  #### Environment Parameters
594
594
 
595
- | Name | Type | Default Value | Comments |
596
- |-------------------------------------|--------|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
597
- | `endpoints.SYSTEM.ENDPOINT.url` | string | | Endpoint URL. |
598
- | `endpoints.SYSTEM.ENDPOINT.headers` | string | | Endpoint request headers, use global headers if this parameter doesn't present.<br>Format follows `name=value[;name=value[...]]`. |
599
- | `endpoints.SYSTEM.global.headers` | string | | Endpoint system global request headers.<br>Format follows `name=value[;name=value[...]]`. |
600
- | `endpoints.SYSTEM.ENDPOINT.timeout` | number | | Endpoint request timeout, in seconds, use global timeout if this parameter doesn't present. |
601
- | `endpoints.SYSTEM.global.timeout` | number | -1 | Endpoint system global timeout, in seconds, `-1` represents no timeout. |
595
+ | Name | Type | Default Value | Comments |
596
+ |---------------------------------------------------------|--------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
597
+ | `endpoints.SYSTEM.ENDPOINT.url` | string | | Endpoint URL. |
598
+ | `endpoints.SYSTEM.ENDPOINT.headers` | string | | Endpoint request headers, use global headers if this parameter doesn't present.<br>Format follows `name=value[;name=value[...]]`. |
599
+ | `endpoints.SYSTEM.global.headers` | string | | Endpoint system global request headers.<br>Format follows `name=value[;name=value[...]]`. |
600
+ | `endpoints.SYSTEM.ENDPOINT.headers.transparent` | string | | Endpoint request transparent-passed headers names, use global headers if this parameter doesn't present.<br>Format follows `name1[;name2[...]]`. |
601
+ | `endpoints.SYSTEM.global.headers.transparent` | string | | Endpoint system global request transparent-passed headers names.<br>Format follows `name1[;name2[...]]`. |
602
+ | `endpoints.SYSTEM.ENDPOINT.headers.transparent.omitted` | string | | Endpoint request headers names which omitted from transparent-passed headers, use global headers if this parameter doesn't present.<br>Format follows `name1[;name2[...]]`. |
603
+ | `endpoints.SYSTEM.global.headers.transparent.omitted` | string | | Endpoint system global request headers names which omitted from transparent-passed headers.<br>Format follows `name1[;name2[...]]`. |
604
+ | `endpoints.SYSTEM.ENDPOINT.timeout` | number | | Endpoint request timeout, in seconds, use global timeout if this parameter doesn't present. |
605
+ | `endpoints.SYSTEM.global.timeout` | number | -1 | Endpoint system global timeout, in seconds, `-1` represents no timeout. |
602
606
 
603
607
  `SYSTEM` represents endpoint system, `ENDPOINT` represents endpoint url. For example:
604
608
 
@@ -611,18 +615,60 @@ CFG_ENDPOINTS_ORDER_PAYMENT_URL=https://order.com/payment
611
615
 
612
616
  #### Constructor Parameters
613
617
 
614
- | Name | Type | Default Value | Comments |
615
- |----------------------|--------------------------------------------------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------|
616
- | endpointSystemCode | string | | Endpoint system code. |
617
- | endpointName | string | | Endpoint name. |
618
- | urlGenerate | ScriptFuncOrBody\<HttpGenerateUrl> | | Endpoint url generator, `$endpointUrl`. |
619
- | method | string | | Http method, default `post`. |
620
- | timeout | number | | Endpoint timeout, in seconds. |
621
- | headersGenerate | ScriptFuncOrBody\<HttpGenerateHeaders> | | Endpoint request headers generator. |
622
- | bodyUsed | boolean | | Send request with body or not, or automatically disregards the body when sending a `get` request when not specified. |
623
- | bodyGenerate | ScriptFuncOrBody\<HttpGenerateBody> | | Endpoint request body generator. |
624
- | responseGenerate | ScriptFuncOrBody\<HttpGenerateResponse> | | Endpoint response body generator, `$response`. |
625
- | responseErrorHandles | ScriptFuncOrBody\<HttpHandleError><br>or<br>{[key: HttpErrorCode]: ScriptFuncOrBody\<HttpHandleError>} | | Endpoint response error handlers. |
618
+ | Name | Type | Default Value | Comments |
619
+ |-------------------------------|--------------------------------------------------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------|
620
+ | endpointSystemCode | string | | Endpoint system code. |
621
+ | endpointName | string | | Endpoint name. |
622
+ | urlGenerate | ScriptFuncOrBody\<HttpGenerateUrl> | | Endpoint url generator, `$endpointUrl`. |
623
+ | method | string | | Http method, default `post`. |
624
+ | timeout | number | | Endpoint timeout, in seconds. |
625
+ | transparentHeaderNames | Array<string> | | Transparent-passed request headers names. |
626
+ | omittedTransparentHeaderNames | Array<string> | | Omitted names of transparently-passed request headers. |
627
+ | headersGenerate | ScriptFuncOrBody\<HttpGenerateHeaders> | | Endpoint request headers generator. |
628
+ | bodyUsed | boolean | | Send request with body or not, or automatically disregards the body when sending a `get` request when not specified. |
629
+ | bodyGenerate | ScriptFuncOrBody\<HttpGenerateBody> | | Endpoint request body generator. |
630
+ | responseGenerate | ScriptFuncOrBody\<HttpGenerateResponse> | | Endpoint response body generator, `$response`. |
631
+ | responseErrorHandles | ScriptFuncOrBody\<HttpHandleError><br>or<br>{[key: HttpErrorCode]: ScriptFuncOrBody\<HttpHandleError>} | | Endpoint response error handlers. |
632
+
633
+ - `transparentHeaderNames` and `omittedTransparentHeaderNames`:
634
+ Use `transparentHeaderNames` to specify the names of request headers whose values need to be transparently passed from the input
635
+ parameters to the downstream service. Separate the names with ';'. The names support using `.` for connection so that values from
636
+ multi-level objects can be directly retrieved. For example, `account.name` will retrieve the value of the `name` property from the
637
+ `account` property of the input object. When writing the values into the header values, the following rules apply:
638
+ - If the value is an array, use `, ` to connect the elements. `null` and empty strings will be filtered out.
639
+ - If the value is an object, use the object's keys to generate multiple headers. `null` and empty strings will be filtered out.
640
+ - For other values, convert them to strings. `null` and empty strings will be filtered out.
641
+ - Note that an empty string does not include blank strings, and no automatic trimming will be performed.
642
+
643
+ If the `transparentHeaderNames` at the step level is not defined, use the definition in
644
+ `endpoints.SYSTEM.ENDPOINT.headers.transparent`. If it is also not defined at the endpoint level, use the definition in
645
+ `endpoints.SYSTEM.global.headers.transparent`.
646
+
647
+ After obtaining the transparently passed request headers, check the definition of `omittedTransparentHeaderNames`. If it is defined,
648
+ remove the corresponding headers from the headers. `omittedTransparentHeaderNames` is case-insensitive. Similarly, if the
649
+ `omittedTransparentHeaderNames` at the step level is not defined, use the definition in
650
+ `endpoints.SYSTEM.ENDPOINT.headers.transparent.omitted`. If it is also not defined at the endpoint level, use the definition in
651
+ `endpoints.SYSTEM.global.headers.transparent.omitted`.
652
+
653
+ For example:
654
+ If the input data contains `{account: {name: 'John', token: '******'}}` and `transparentHeaderNames` is defined as `account`, then two
655
+ transparently passed headers will be obtained: `name=John` and `token=******`. At this time, if `omittedTransparentHeaderNames` is defined
656
+ as `name`, the headers that will be finally transparently passed to the downstream service are `token=******`, and `name` will be ignored.
657
+
658
+ ## Request headers
659
+
660
+ There are three ways to transmit request headers to downstream services. In order of priority from high to low, they are `headersGenerate`,
661
+ `headers.transparent`, and `headers`. If a header appears in a higher-priority method, the header with the same name generated by a
662
+ lower-priority method will be ignored. Note that the matching is case-sensitive.
663
+
664
+ Normally, if you need to transparently pass the request headers from the client to the downstream service, you should use `headers: true` in
665
+ the pipeline definition. Then you can directly use `transparentHeaderNames: headers` to obtain all the request headers, and then use
666
+ `omittedTransparentHeaderNames` for necessary filtering.
667
+
668
+ > It should be noted that since the fetch step initiates a new request to the downstream service, its request structure and data will be
669
+ > modified or reset according to requirements. Therefore, even if you need to transparently pass the request headers, some of the headers
670
+ > are still not applicable. So by default, the two headers `content-encoding` and `content-length` will be filtered out. No matter how the
671
+ > request headers are generated in the above process, these two headers are always automatically generated by `node-fetch`.
626
672
 
627
673
  ## Installation
628
674
 
package/index.cjs CHANGED
@@ -1281,6 +1281,8 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1281
1281
  _endpointTimeout;
1282
1282
  _urlGenerateSnippet;
1283
1283
  _urlGenerateFunc;
1284
+ _transparentHeaderNames;
1285
+ _omittedTransparentHeaderNames;
1284
1286
  _headersGenerateSnippet;
1285
1287
  _headersGenerateFunc;
1286
1288
  _bodyUsed;
@@ -1311,6 +1313,10 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1311
1313
  throw e;
1312
1314
  }
1313
1315
  });
1316
+ this._transparentHeaderNames = options.transparentHeaderNames
1317
+ ?? this.generateTransparentHeaderNames(config.getString(`endpoints.${endpointKey}.headers.transparent`), this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent`)));
1318
+ this._omittedTransparentHeaderNames = options.omittedTransparentHeaderNames
1319
+ ?? this.generateTransparentHeaderNames(config.getString(`endpoints.${endpointKey}.headers.transparent.omitted`), this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent.omitted`)));
1314
1320
  this._headersGenerateSnippet = options.headersGenerate;
1315
1321
  this._headersGenerateFunc = Utils.createAsyncFunction(this.getHeadersGenerateSnippet(), {
1316
1322
  createDefault: () => async (_$factor, _$request, _$helpers, _$) => (void 0),
@@ -1334,8 +1340,15 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1334
1340
  });
1335
1341
  this._responseGenerateSnippet = options.responseGenerate;
1336
1342
  this._responseGenerateFunc = Utils.createAsyncFunction(this.getResponseGenerateSnippet(), {
1337
- createDefault: () => async ($response, _$factor, _$request, _$helpers, _$) => {
1338
- return await $response.json();
1343
+ createDefault: () => async ($response, _$factor, _$request, $helpers, _$) => {
1344
+ const contentEncoding = $response.headers?.get('content-encoding');
1345
+ if (contentEncoding === 'zstd') {
1346
+ const buffer = await $response.buffer();
1347
+ return JSON.parse(await $helpers.$zstd(buffer));
1348
+ }
1349
+ else {
1350
+ return await $response.json();
1351
+ }
1339
1352
  },
1340
1353
  getVariableNames: () => this.getResponseGenerateVariableName(),
1341
1354
  error: (e) => {
@@ -1427,6 +1440,20 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1427
1440
  getUrlGenerateVariableName() {
1428
1441
  return ['$endpointUrl', '$factor', '$request', ...this.getHelpersVariableNames()];
1429
1442
  }
1443
+ getTransparentHeaderNames() {
1444
+ return this._transparentHeaderNames ?? [];
1445
+ }
1446
+ getOmittedTransparentHeaderNames() {
1447
+ return this._omittedTransparentHeaderNames ?? [];
1448
+ }
1449
+ generateTransparentHeaderNames(headerNames, base) {
1450
+ return [
1451
+ ...(base ?? []),
1452
+ ...new Set(`${headerNames || ''}`.split(';')
1453
+ .map(x => x.trim())
1454
+ .filter(x => x.length !== 0))
1455
+ ];
1456
+ }
1430
1457
  getHeadersGenerateSnippet() {
1431
1458
  return this._headersGenerateSnippet;
1432
1459
  }
@@ -1458,7 +1485,37 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1458
1485
  url = await this._urlGenerateFunc(this.getEndpointUrl(), data, request, $helpers, $helpers);
1459
1486
  const method = this.getEndpointMethod();
1460
1487
  const staticHeaders = this.getEndpointHeaders() ?? {};
1461
- const headers = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
1488
+ const transparentHeaders = (this.getTransparentHeaderNames() ?? []).reduce((headers, name) => {
1489
+ const value = Utils.getValue(data, name);
1490
+ if (value == null) {
1491
+ }
1492
+ else if (Array.isArray(value)) {
1493
+ const headerValue = value.filter(v => v != null && `${v}`.length !== 0).join(', ');
1494
+ if (headerValue.length !== 0) {
1495
+ headers[name] = headerValue;
1496
+ }
1497
+ }
1498
+ else if (typeof value === 'object') {
1499
+ Object.keys(value).forEach(key => {
1500
+ const headerValue = value[key];
1501
+ if (headerValue != null) {
1502
+ const s = `${headerValue}`;
1503
+ if (s.length !== 0) {
1504
+ headers[key] = s;
1505
+ }
1506
+ }
1507
+ });
1508
+ }
1509
+ else {
1510
+ const headerValue = `${value}`;
1511
+ if (headerValue.length !== 0) {
1512
+ headers[name] = headerValue;
1513
+ }
1514
+ }
1515
+ return headers;
1516
+ }, {});
1517
+ (this.getOmittedTransparentHeaderNames() ?? []).forEach(name => delete transparentHeaders[name]);
1518
+ const generatedHeaders = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
1462
1519
  let body;
1463
1520
  const bodyUsed = this.isBodyUsed();
1464
1521
  if (bodyUsed === true || (bodyUsed == null && method !== 'get')) {
@@ -1470,8 +1527,14 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1470
1527
  if (body != null && typeof body !== 'string') {
1471
1528
  body = JSON.stringify(body);
1472
1529
  }
1530
+ const headers = { ...staticHeaders, ...transparentHeaders, ...generatedHeaders };
1531
+ Object.keys(headers).filter(name => {
1532
+ return ['content-encoding', 'content-length'].includes(name.toLowerCase());
1533
+ }).forEach(name => {
1534
+ delete headers[name];
1535
+ });
1473
1536
  const response = await fetch(url, {
1474
- method, headers: { ...staticHeaders, ...headers }, body,
1537
+ method, headers, body,
1475
1538
  signal: this.needTimeout() ? (() => {
1476
1539
  const controller = new AbortController();
1477
1540
  setTimeout(() => controller.abort(), this.getEndpointTimeout());
package/index.js CHANGED
@@ -1279,6 +1279,8 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1279
1279
  _endpointTimeout;
1280
1280
  _urlGenerateSnippet;
1281
1281
  _urlGenerateFunc;
1282
+ _transparentHeaderNames;
1283
+ _omittedTransparentHeaderNames;
1282
1284
  _headersGenerateSnippet;
1283
1285
  _headersGenerateFunc;
1284
1286
  _bodyUsed;
@@ -1309,6 +1311,10 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1309
1311
  throw e;
1310
1312
  }
1311
1313
  });
1314
+ this._transparentHeaderNames = options.transparentHeaderNames
1315
+ ?? this.generateTransparentHeaderNames(config.getString(`endpoints.${endpointKey}.headers.transparent`), this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent`)));
1316
+ this._omittedTransparentHeaderNames = options.omittedTransparentHeaderNames
1317
+ ?? this.generateTransparentHeaderNames(config.getString(`endpoints.${endpointKey}.headers.transparent.omitted`), this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent.omitted`)));
1312
1318
  this._headersGenerateSnippet = options.headersGenerate;
1313
1319
  this._headersGenerateFunc = Utils.createAsyncFunction(this.getHeadersGenerateSnippet(), {
1314
1320
  createDefault: () => async (_$factor, _$request, _$helpers, _$) => (void 0),
@@ -1332,8 +1338,15 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1332
1338
  });
1333
1339
  this._responseGenerateSnippet = options.responseGenerate;
1334
1340
  this._responseGenerateFunc = Utils.createAsyncFunction(this.getResponseGenerateSnippet(), {
1335
- createDefault: () => async ($response, _$factor, _$request, _$helpers, _$) => {
1336
- return await $response.json();
1341
+ createDefault: () => async ($response, _$factor, _$request, $helpers, _$) => {
1342
+ const contentEncoding = $response.headers?.get('content-encoding');
1343
+ if (contentEncoding === 'zstd') {
1344
+ const buffer = await $response.buffer();
1345
+ return JSON.parse(await $helpers.$zstd(buffer));
1346
+ }
1347
+ else {
1348
+ return await $response.json();
1349
+ }
1337
1350
  },
1338
1351
  getVariableNames: () => this.getResponseGenerateVariableName(),
1339
1352
  error: (e) => {
@@ -1425,6 +1438,20 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1425
1438
  getUrlGenerateVariableName() {
1426
1439
  return ['$endpointUrl', '$factor', '$request', ...this.getHelpersVariableNames()];
1427
1440
  }
1441
+ getTransparentHeaderNames() {
1442
+ return this._transparentHeaderNames ?? [];
1443
+ }
1444
+ getOmittedTransparentHeaderNames() {
1445
+ return this._omittedTransparentHeaderNames ?? [];
1446
+ }
1447
+ generateTransparentHeaderNames(headerNames, base) {
1448
+ return [
1449
+ ...(base ?? []),
1450
+ ...new Set(`${headerNames || ''}`.split(';')
1451
+ .map(x => x.trim())
1452
+ .filter(x => x.length !== 0))
1453
+ ];
1454
+ }
1428
1455
  getHeadersGenerateSnippet() {
1429
1456
  return this._headersGenerateSnippet;
1430
1457
  }
@@ -1456,7 +1483,37 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1456
1483
  url = await this._urlGenerateFunc(this.getEndpointUrl(), data, request, $helpers, $helpers);
1457
1484
  const method = this.getEndpointMethod();
1458
1485
  const staticHeaders = this.getEndpointHeaders() ?? {};
1459
- const headers = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
1486
+ const transparentHeaders = (this.getTransparentHeaderNames() ?? []).reduce((headers, name) => {
1487
+ const value = Utils.getValue(data, name);
1488
+ if (value == null) {
1489
+ }
1490
+ else if (Array.isArray(value)) {
1491
+ const headerValue = value.filter(v => v != null && `${v}`.length !== 0).join(', ');
1492
+ if (headerValue.length !== 0) {
1493
+ headers[name] = headerValue;
1494
+ }
1495
+ }
1496
+ else if (typeof value === 'object') {
1497
+ Object.keys(value).forEach(key => {
1498
+ const headerValue = value[key];
1499
+ if (headerValue != null) {
1500
+ const s = `${headerValue}`;
1501
+ if (s.length !== 0) {
1502
+ headers[key] = s;
1503
+ }
1504
+ }
1505
+ });
1506
+ }
1507
+ else {
1508
+ const headerValue = `${value}`;
1509
+ if (headerValue.length !== 0) {
1510
+ headers[name] = headerValue;
1511
+ }
1512
+ }
1513
+ return headers;
1514
+ }, {});
1515
+ (this.getOmittedTransparentHeaderNames() ?? []).forEach(name => delete transparentHeaders[name]);
1516
+ const generatedHeaders = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
1460
1517
  let body;
1461
1518
  const bodyUsed = this.isBodyUsed();
1462
1519
  if (bodyUsed === true || (bodyUsed == null && method !== 'get')) {
@@ -1468,8 +1525,14 @@ class FetchPipelineStep extends AbstractFragmentaryPipelineStep {
1468
1525
  if (body != null && typeof body !== 'string') {
1469
1526
  body = JSON.stringify(body);
1470
1527
  }
1528
+ const headers = { ...staticHeaders, ...transparentHeaders, ...generatedHeaders };
1529
+ Object.keys(headers).filter(name => {
1530
+ return ['content-encoding', 'content-length'].includes(name.toLowerCase());
1531
+ }).forEach(name => {
1532
+ delete headers[name];
1533
+ });
1471
1534
  const response = await fetch(url, {
1472
- method, headers: { ...staticHeaders, ...headers }, body,
1535
+ method, headers, body,
1473
1536
  signal: this.needTimeout() ? (() => {
1474
1537
  const controller = new AbortController();
1475
1538
  setTimeout(() => controller.abort(), this.getEndpointTimeout());
@@ -7,6 +7,8 @@ export interface FetchPipelineStepOptions<In = PipelineStepPayload, Out = Pipeli
7
7
  urlGenerate?: ScriptFuncOrBody<HttpGenerateUrl<In, InFragment>>;
8
8
  method?: string;
9
9
  timeout?: number;
10
+ transparentHeaderNames?: Array<string>;
11
+ omittedTransparentHeaderNames?: Array<string>;
10
12
  headersGenerate?: ScriptFuncOrBody<HttpGenerateHeaders<In, InFragment>>;
11
13
  bodyUsed?: boolean;
12
14
  bodyGenerate?: ScriptFuncOrBody<HttpGenerateBody<In, InFragment, BodyData>>;
@@ -24,6 +26,8 @@ export declare class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineS
24
26
  private readonly _endpointTimeout;
25
27
  private readonly _urlGenerateSnippet;
26
28
  private readonly _urlGenerateFunc;
29
+ private readonly _transparentHeaderNames;
30
+ private readonly _omittedTransparentHeaderNames;
27
31
  private readonly _headersGenerateSnippet;
28
32
  private readonly _headersGenerateFunc;
29
33
  private readonly _bodyUsed;
@@ -44,6 +48,9 @@ export declare class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineS
44
48
  needTimeout(): boolean;
45
49
  getUrlGenerateSnippet(): ScriptFuncOrBody<HttpGenerateUrl<In, InFragment>>;
46
50
  protected getUrlGenerateVariableName(): Array<string>;
51
+ getTransparentHeaderNames(): Array<string>;
52
+ getOmittedTransparentHeaderNames(): Array<string>;
53
+ protected generateTransparentHeaderNames(headerNames?: string, base?: Array<string>): Array<string>;
47
54
  getHeadersGenerateSnippet(): ScriptFuncOrBody<HttpGenerateHeaders<In, InFragment>>;
48
55
  protected getHeadersGenerateVariableNames(): Array<string>;
49
56
  protected isBodyUsed(): boolean | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-o23/n3",
3
- "version": "1.0.57",
3
+ "version": "1.0.58-alpha.2",
4
4
  "description": "o23 pipelines",
5
5
  "main": "index.cjs",
6
6
  "module": "index.js",
@@ -21,7 +21,7 @@
21
21
  "url": "https://github.com/InsureMO/rainbow-o23/issues"
22
22
  },
23
23
  "dependencies": {
24
- "@rainbow-o23/n1": "1.0.57",
24
+ "@rainbow-o23/n1": "1.0.58-alpha.2",
25
25
  "node-fetch": "2.6.7",
26
26
  "typeorm": "^0.3.20",
27
27
  "typescript": "5.5.4"
@@ -30,6 +30,7 @@
30
30
  "@babel/core": "^7.23.9",
31
31
  "@babel/preset-env": "^7.23.9",
32
32
  "@babel/preset-typescript": "^7.23.3",
33
+ "@oneidentity/zstd-js": "^1.0.3",
33
34
  "@rollup/plugin-babel": "^6.0.4",
34
35
  "@rollup/plugin-eslint": "^9.0.3",
35
36
  "@types/better-sqlite3": "^7.6.6",
@@ -23,6 +23,8 @@ export interface FetchPipelineStepOptions<In = PipelineStepPayload, Out = Pipeli
23
23
  method?: string;
24
24
  /** on seconds */
25
25
  timeout?: number;
26
+ transparentHeaderNames?: Array<string>;
27
+ omittedTransparentHeaderNames?: Array<string>;
26
28
  headersGenerate?: ScriptFuncOrBody<HttpGenerateHeaders<In, InFragment>>;
27
29
  bodyUsed?: boolean;
28
30
  bodyGenerate?: ScriptFuncOrBody<HttpGenerateBody<In, InFragment, BodyData>>;
@@ -42,6 +44,8 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
42
44
  private readonly _endpointTimeout: number;
43
45
  private readonly _urlGenerateSnippet: ScriptFuncOrBody<HttpGenerateUrl<In, InFragment>>;
44
46
  private readonly _urlGenerateFunc: HttpGenerateUrl<In, InFragment>;
47
+ private readonly _transparentHeaderNames: Array<string>;
48
+ private readonly _omittedTransparentHeaderNames: Array<string>;
45
49
  private readonly _headersGenerateSnippet: ScriptFuncOrBody<HttpGenerateHeaders<In, InFragment>>;
46
50
  private readonly _headersGenerateFunc: HttpGenerateHeaders<In, InFragment>;
47
51
  private readonly _bodyUsed: boolean;
@@ -78,6 +82,14 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
78
82
  throw e;
79
83
  }
80
84
  });
85
+ this._transparentHeaderNames = options.transparentHeaderNames
86
+ ?? this.generateTransparentHeaderNames(
87
+ config.getString(`endpoints.${endpointKey}.headers.transparent`),
88
+ this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent`)));
89
+ this._omittedTransparentHeaderNames = options.omittedTransparentHeaderNames
90
+ ?? this.generateTransparentHeaderNames(
91
+ config.getString(`endpoints.${endpointKey}.headers.transparent.omitted`),
92
+ this.generateTransparentHeaderNames(config.getString(`endpoints.${this.getEndpointSystemCode()}.global.headers.transparent.omitted`)));
81
93
  this._headersGenerateSnippet = options.headersGenerate;
82
94
  this._headersGenerateFunc = Utils.createAsyncFunction(this.getHeadersGenerateSnippet(), {
83
95
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -104,8 +116,14 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
104
116
  this._responseGenerateSnippet = options.responseGenerate;
105
117
  this._responseGenerateFunc = Utils.createAsyncFunction(this.getResponseGenerateSnippet(), {
106
118
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
107
- createDefault: () => async ($response: Response, _$factor: InFragment, _$request: PipelineStepData<In>, _$helpers: PipelineStepHelpers, _$: PipelineStepHelpers) => {
108
- return await $response.json();
119
+ createDefault: () => async ($response: Response, _$factor: InFragment, _$request: PipelineStepData<In>, $helpers: PipelineStepHelpers, _$: PipelineStepHelpers) => {
120
+ const contentEncoding = $response.headers?.get('content-encoding');
121
+ if (contentEncoding === 'zstd') {
122
+ const buffer = await $response.buffer();
123
+ return JSON.parse(await $helpers.$zstd(buffer));
124
+ } else {
125
+ return await $response.json();
126
+ }
109
127
  },
110
128
  getVariableNames: () => this.getResponseGenerateVariableName(),
111
129
  error: (e: Error) => {
@@ -211,6 +229,23 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
211
229
  return ['$endpointUrl', '$factor', '$request', ...this.getHelpersVariableNames()];
212
230
  }
213
231
 
232
+ public getTransparentHeaderNames(): Array<string> {
233
+ return this._transparentHeaderNames ?? [];
234
+ }
235
+
236
+ public getOmittedTransparentHeaderNames(): Array<string> {
237
+ return this._omittedTransparentHeaderNames ?? [];
238
+ }
239
+
240
+ protected generateTransparentHeaderNames(headerNames?: string, base?: Array<string>): Array<string> {
241
+ return [
242
+ ...(base ?? []),
243
+ ...new Set(`${headerNames || ''}`.split(';')
244
+ .map(x => x.trim())
245
+ .filter(x => x.length !== 0))
246
+ ];
247
+ }
248
+
214
249
  public getHeadersGenerateSnippet(): ScriptFuncOrBody<HttpGenerateHeaders<In, InFragment>> {
215
250
  return this._headersGenerateSnippet;
216
251
  }
@@ -256,7 +291,36 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
256
291
  url = await this._urlGenerateFunc(this.getEndpointUrl(), data, request, $helpers, $helpers);
257
292
  const method = this.getEndpointMethod();
258
293
  const staticHeaders = this.getEndpointHeaders() ?? {};
259
- const headers = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
294
+ const transparentHeaders = (this.getTransparentHeaderNames() ?? []).reduce((headers, name) => {
295
+ const value = Utils.getValue(data, name);
296
+ if (value == null) {
297
+ // no value of given header name, ignored
298
+ } else if (Array.isArray(value)) {
299
+ const headerValue = value.filter(v => v != null && `${v}`.length !== 0).join(', ');
300
+ if (headerValue.length !== 0) {
301
+ headers[name] = headerValue;
302
+ }
303
+ } else if (typeof value === 'object') {
304
+ Object.keys(value).forEach(key => {
305
+ const headerValue = value[key];
306
+ if (headerValue != null) {
307
+ const s = `${headerValue}`;
308
+ if (s.length !== 0) {
309
+ headers[key] = s;
310
+ }
311
+ }
312
+ });
313
+ } else {
314
+ const headerValue = `${value}`;
315
+ if (headerValue.length !== 0) {
316
+ headers[name] = headerValue;
317
+ }
318
+ }
319
+ return headers;
320
+ }, {});
321
+ (this.getOmittedTransparentHeaderNames() ?? []).forEach(name => delete transparentHeaders[name]);
322
+
323
+ const generatedHeaders = await this._headersGenerateFunc(data, request, $helpers, $helpers) ?? {};
260
324
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
325
  let body: any;
262
326
  const bodyUsed = this.isBodyUsed();
@@ -268,8 +332,16 @@ export class FetchPipelineStep<In = PipelineStepPayload, Out = PipelineStepPaylo
268
332
  if (body != null && typeof body !== 'string') {
269
333
  body = JSON.stringify(body);
270
334
  }
335
+ const headers = {...staticHeaders, ...transparentHeaders, ...generatedHeaders};
336
+ // remove some headers, leave them to fetch to calculate automatically.
337
+ Object.keys(headers).filter(name => {
338
+ return ['content-encoding', 'content-length'].includes(name.toLowerCase());
339
+ }).forEach(name => {
340
+ delete headers[name];
341
+ });
342
+
271
343
  const response = await fetch(url, {
272
- method, headers: {...staticHeaders, ...headers}, body,
344
+ method, headers, body,
273
345
  signal: this.needTimeout() ? (() => {
274
346
  const controller = new AbortController();
275
347
  setTimeout(() => controller.abort(), this.getEndpointTimeout());
@@ -0,0 +1,65 @@
1
+ import {ZstdInit} from '@oneidentity/zstd-js';
2
+ import {ZSTDDecoder} from 'zstddec';
3
+
4
+ test('ZStandard compression test', async () => {
5
+ const data = JSON.stringify({a: new Array(10).fill('1234567890').join('')});
6
+ const inputArray = new Uint8Array(Buffer.from(data));
7
+
8
+ const {ZstdSimple, ZstdStream} = await ZstdInit();
9
+ // Create some sample data to compress
10
+ /*
11
+ * The required parameter is the data
12
+ * It must be a Uint8Array
13
+ * */
14
+ const compressedSimpleData: Uint8Array = ZstdSimple.compress(inputArray, 3);
15
+ {
16
+ // console.time('@oneidentity/zstd-js');
17
+ // for (let i = 0; i < 10000; i++) {
18
+ // const {ZstdSimple} = await ZstdInit();
19
+ // const decompressedArray = ZstdSimple.decompress(compressedSimpleData);
20
+ // new TextDecoder().decode(decompressedArray);
21
+ // }
22
+ // console.timeEnd('@oneidentity/zstd-js');
23
+
24
+ const {ZstdSimple} = await ZstdInit();
25
+ const decompressedArray = ZstdSimple.decompress(compressedSimpleData);
26
+ const decompressedStr = new TextDecoder().decode(decompressedArray);
27
+ console.log(decompressedStr);
28
+ console.log(decompressedStr.length);
29
+ console.log(JSON.parse(decompressedStr));
30
+ }
31
+ {
32
+ console.time('zstddec');
33
+ for (let i = 0; i < 10; i++) {
34
+ const decoder = new ZSTDDecoder();
35
+ await decoder.init();
36
+ const decompressedArray = decoder.decode(compressedSimpleData);
37
+ const removeTrailingZeros = (arr: Uint8Array): Uint8Array => {
38
+ let i = arr.length;
39
+ while (i > 0 && arr[i - 1] === 0) {
40
+ i--;
41
+ }
42
+ return arr.slice(0, i);
43
+ };
44
+ new TextDecoder().decode(removeTrailingZeros(decompressedArray));
45
+ }
46
+ console.timeEnd('zstddec');
47
+
48
+ const decoder = new ZSTDDecoder();
49
+ await decoder.init();
50
+ const decompressedArray = decoder.decode(compressedSimpleData);
51
+ const removeTrailingZeros = (arr: Uint8Array): Uint8Array => {
52
+ let i = arr.length;
53
+ while (i > 0 && arr[i - 1] === 0) {
54
+ i--;
55
+ }
56
+ return arr.slice(0, i);
57
+ };
58
+ const decompressedStr = new TextDecoder().decode(removeTrailingZeros(decompressedArray));
59
+ console.log(decompressedStr);
60
+ console.log(decompressedStr.length);
61
+ console.log(JSON.parse(decompressedStr));
62
+ }
63
+
64
+ // const compressedStreamData: Uint8Array = ZstdStream.compress(inputArray);
65
+ }, 30000);