@rainbow-o23/n3 1.0.56 → 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
@@ -492,9 +492,16 @@ Array<TypeOrmEntityToLoad>;
492
492
 
493
493
  ##### Environment Parameters
494
494
 
495
- | Name | Type | Default Value | Comments |
496
- |-------------------------|--------|---------------|-------------|
497
- | `typeorm.DB.fetch.size` | number | 20 | Fetch size. |
495
+ | Name | Type | Default Value | Comments |
496
+ |-----------------------------------|---------|---------------|----------------------------------------|
497
+ | `typeorm.DB.fetch.size` | number | 20 | Fetch size. |
498
+ | `typeorm.DB.stream.pause.enabled` | boolean | false | Pause and resume result stream or not. |
499
+
500
+ ##### Constructor Parameters
501
+
502
+ | Name | Type | Default Value | Comments |
503
+ |--------------------|---------|---------------|----------------------------------------|
504
+ | pauseStreamEnabled | boolean | | Pause and resume result stream or not. |
498
505
 
499
506
  ##### Request and Response
500
507
 
@@ -585,13 +592,17 @@ step set. Additionally, nested transactions are also supported, which means Tran
585
592
 
586
593
  #### Environment Parameters
587
594
 
588
- | Name | Type | Default Value | Comments |
589
- |-------------------------------------|--------|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
590
- | `endpoints.SYSTEM.ENDPOINT.url` | string | | Endpoint URL. |
591
- | `endpoints.SYSTEM.ENDPOINT.headers` | string | | Endpoint request headers, use global headers if this parameter doesn't present.<br>Format follows `name=value[;name=value[...]]`. |
592
- | `endpoints.SYSTEM.global.headers` | string | | Endpoint system global request headers.<br>Format follows `name=value[;name=value[...]]`. |
593
- | `endpoints.SYSTEM.ENDPOINT.timeout` | number | | Endpoint request timeout, in seconds, use global timeout if this parameter doesn't present. |
594
- | `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. |
595
606
 
596
607
  `SYSTEM` represents endpoint system, `ENDPOINT` represents endpoint url. For example:
597
608
 
@@ -604,18 +615,60 @@ CFG_ENDPOINTS_ORDER_PAYMENT_URL=https://order.com/payment
604
615
 
605
616
  #### Constructor Parameters
606
617
 
607
- | Name | Type | Default Value | Comments |
608
- |----------------------|--------------------------------------------------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------|
609
- | endpointSystemCode | string | | Endpoint system code. |
610
- | endpointName | string | | Endpoint name. |
611
- | urlGenerate | ScriptFuncOrBody\<HttpGenerateUrl> | | Endpoint url generator, `$endpointUrl`. |
612
- | method | string | | Http method, default `post`. |
613
- | timeout | number | | Endpoint timeout, in seconds. |
614
- | headersGenerate | ScriptFuncOrBody\<HttpGenerateHeaders> | | Endpoint request headers generator. |
615
- | bodyUsed | boolean | | Send request with body or not, or automatically disregards the body when sending a `get` request when not specified. |
616
- | bodyGenerate | ScriptFuncOrBody\<HttpGenerateBody> | | Endpoint request body generator. |
617
- | responseGenerate | ScriptFuncOrBody\<HttpGenerateResponse> | | Endpoint response body generator, `$response`. |
618
- | 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`.
619
672
 
620
673
  ## Installation
621
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());
@@ -2223,6 +2286,7 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2223
2286
  _streamToSnippet;
2224
2287
  _streamToFunc;
2225
2288
  _stepBuilders;
2289
+ _pauseStreamEnabled;
2226
2290
  constructor(options) {
2227
2291
  super(options);
2228
2292
  const config = this.getConfig();
@@ -2237,6 +2301,7 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2237
2301
  }
2238
2302
  });
2239
2303
  this._stepBuilders = options.steps;
2304
+ this._pauseStreamEnabled = options.pauseStreamEnabled ?? config.getBoolean(`typeorm.${this.getDataSourceName()}.stream.pause.enabled`, false);
2240
2305
  }
2241
2306
  getFetchSize() {
2242
2307
  return this._fetchSize;
@@ -2250,6 +2315,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2250
2315
  getStepBuilders() {
2251
2316
  return this._stepBuilders ?? [];
2252
2317
  }
2318
+ isPauseStreamEnabled() {
2319
+ return this._pauseStreamEnabled;
2320
+ }
2253
2321
  async doPerform(basis, request) {
2254
2322
  const { sql, params } = this.getSql(basis, basis?.params);
2255
2323
  return await this.autoTrans(async (runner) => {
@@ -2312,7 +2380,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2312
2380
  reject(e);
2313
2381
  });
2314
2382
  readable.on('data', async (data) => {
2315
- readable.pause();
2383
+ if (this.isPauseStreamEnabled()) {
2384
+ readable.pause();
2385
+ }
2316
2386
  rows.push(data);
2317
2387
  await pipe({
2318
2388
  resolve, reject: async (e) => {
@@ -2320,7 +2390,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2320
2390
  reject(e);
2321
2391
  }, end: false
2322
2392
  });
2323
- readable.resume();
2393
+ if (this.isPauseStreamEnabled()) {
2394
+ readable.resume();
2395
+ }
2324
2396
  });
2325
2397
  };
2326
2398
  return new Promise((resolve, reject) => read({ resolve, reject }));
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());
@@ -2221,6 +2284,7 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2221
2284
  _streamToSnippet;
2222
2285
  _streamToFunc;
2223
2286
  _stepBuilders;
2287
+ _pauseStreamEnabled;
2224
2288
  constructor(options) {
2225
2289
  super(options);
2226
2290
  const config = this.getConfig();
@@ -2235,6 +2299,7 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2235
2299
  }
2236
2300
  });
2237
2301
  this._stepBuilders = options.steps;
2302
+ this._pauseStreamEnabled = options.pauseStreamEnabled ?? config.getBoolean(`typeorm.${this.getDataSourceName()}.stream.pause.enabled`, false);
2238
2303
  }
2239
2304
  getFetchSize() {
2240
2305
  return this._fetchSize;
@@ -2248,6 +2313,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2248
2313
  getStepBuilders() {
2249
2314
  return this._stepBuilders ?? [];
2250
2315
  }
2316
+ isPauseStreamEnabled() {
2317
+ return this._pauseStreamEnabled;
2318
+ }
2251
2319
  async doPerform(basis, request) {
2252
2320
  const { sql, params } = this.getSql(basis, basis?.params);
2253
2321
  return await this.autoTrans(async (runner) => {
@@ -2310,7 +2378,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2310
2378
  reject(e);
2311
2379
  });
2312
2380
  readable.on('data', async (data) => {
2313
- readable.pause();
2381
+ if (this.isPauseStreamEnabled()) {
2382
+ readable.pause();
2383
+ }
2314
2384
  rows.push(data);
2315
2385
  await pipe({
2316
2386
  resolve, reject: async (e) => {
@@ -2318,7 +2388,9 @@ class TypeOrmLoadManyBySQLUseCursorPipelineStep extends AbstractTypeOrmBySQLPipe
2318
2388
  reject(e);
2319
2389
  }, end: false
2320
2390
  });
2321
- readable.resume();
2391
+ if (this.isPauseStreamEnabled()) {
2392
+ readable.resume();
2393
+ }
2322
2394
  });
2323
2395
  };
2324
2396
  return new Promise((resolve, reject) => read({ resolve, reject }));
@@ -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;
@@ -8,17 +8,20 @@ export interface TypeOrmLoadManyBySQLUseCursorPipelineStepOptions<In = PipelineS
8
8
  fetchSize?: number;
9
9
  streamTo?: ScriptFuncOrBody<StreamToFunc<In, Item>>;
10
10
  steps?: Array<PipelineStepBuilder>;
11
+ pauseStreamEnabled?: boolean;
11
12
  }
12
13
  export declare class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = Undefinable<TypeOrmLoadBasis>, OutFragment = Out, Item = any> extends AbstractTypeOrmBySQLPipelineStep<In, Out, Undefinable<TypeOrmLoadBasis>, OutFragment> {
13
14
  private readonly _fetchSize;
14
15
  private readonly _streamToSnippet;
15
16
  private readonly _streamToFunc;
16
17
  private readonly _stepBuilders;
18
+ private readonly _pauseStreamEnabled;
17
19
  constructor(options: TypeOrmLoadManyBySQLUseCursorPipelineStepOptions<In, Out, InFragment, OutFragment>);
18
20
  protected getFetchSize(): number;
19
21
  getStreamToSnippet(): ScriptFuncOrBody<StreamToFunc<In, Item>>;
20
22
  protected generateVariableNames(): Array<string>;
21
23
  protected getStepBuilders(): Array<PipelineStepBuilder>;
24
+ protected isPauseStreamEnabled(): boolean;
22
25
  protected doPerform(basis: Undefinable<TypeOrmLoadBasis>, request: PipelineStepData<In>): Promise<Undefinable<OutFragment>>;
23
26
  protected getFetchDataVariableName(): string;
24
27
  protected getRequestVariableName(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-o23/n3",
3
- "version": "1.0.56",
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.56",
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());
@@ -22,6 +22,7 @@ export interface TypeOrmLoadManyBySQLUseCursorPipelineStepOptions<In = PipelineS
22
22
  fetchSize?: number;
23
23
  streamTo?: ScriptFuncOrBody<StreamToFunc<In, Item>>;
24
24
  steps?: Array<PipelineStepBuilder>;
25
+ pauseStreamEnabled?: boolean;
25
26
  }
26
27
 
27
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -31,6 +32,7 @@ export class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload,
31
32
  private readonly _streamToSnippet: ScriptFuncOrBody<StreamToFunc<In, Item>>;
32
33
  private readonly _streamToFunc: StreamToFunc<In, Item>;
33
34
  private readonly _stepBuilders: Array<PipelineStepBuilder>;
35
+ private readonly _pauseStreamEnabled: boolean;
34
36
 
35
37
  public constructor(options: TypeOrmLoadManyBySQLUseCursorPipelineStepOptions<In, Out, InFragment, OutFragment>) {
36
38
  super(options);
@@ -47,6 +49,10 @@ export class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload,
47
49
  }
48
50
  });
49
51
  this._stepBuilders = options.steps;
52
+ // for unknown reason, in some environment if the pause and resume functions of readable invoked,
53
+ // only the first row are returns and end event invoked immediately
54
+ // so default disable it.
55
+ this._pauseStreamEnabled = options.pauseStreamEnabled ?? config.getBoolean(`typeorm.${this.getDataSourceName()}.stream.pause.enabled`, false);
50
56
  }
51
57
 
52
58
  protected getFetchSize(): number {
@@ -65,6 +71,10 @@ export class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload,
65
71
  return this._stepBuilders ?? [];
66
72
  }
67
73
 
74
+ protected isPauseStreamEnabled(): boolean {
75
+ return this._pauseStreamEnabled;
76
+ }
77
+
68
78
  protected async doPerform(basis: Undefinable<TypeOrmLoadBasis>, request: PipelineStepData<In>): Promise<Undefinable<OutFragment>> {
69
79
  const {sql, params} = this.getSql(basis, basis?.params);
70
80
  return await this.autoTrans<OutFragment>(async (runner) => {
@@ -136,7 +146,9 @@ export class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload,
136
146
  reject(e);
137
147
  });
138
148
  readable.on('data', async (data) => {
139
- readable.pause();
149
+ if (this.isPauseStreamEnabled()) {
150
+ readable.pause();
151
+ }
140
152
  rows.push(data);
141
153
  await pipe({
142
154
  resolve, reject: async (e: Error) => {
@@ -144,7 +156,9 @@ export class TypeOrmLoadManyBySQLUseCursorPipelineStep<In = PipelineStepPayload,
144
156
  reject(e);
145
157
  }, end: false
146
158
  });
147
- readable.resume();
159
+ if (this.isPauseStreamEnabled()) {
160
+ readable.resume();
161
+ }
148
162
  });
149
163
  };
150
164
  return new Promise<OutFragment>((resolve, reject) => read({resolve, reject}));
@@ -9,7 +9,8 @@ describe('TypeORM Cursor Suite', () => {
9
9
  process.env.CFG_TYPEORM_TEST_HOST = 'localhost';
10
10
  process.env.CFG_TYPEORM_TEST_USERNAME = 'o23';
11
11
  process.env.CFG_TYPEORM_TEST_PASSWORD = 'o23';
12
- const type = 'pg' + 'sql';
12
+ // process.env.CFG_TYPEORM_TEST_STREAM_PAUSE_ENABLED = 'true'
13
+ const type = 'my' + 'sql';
13
14
  if (type === 'mysql') {
14
15
  process.env.CFG_TYPEORM_TEST_TYPE = 'mysql';
15
16
  process.env.CFG_TYPEORM_TEST_PORT = '3306';
@@ -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);