@lad-tech/nsc-toolkit 0.5.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.
Files changed (54) hide show
  1. package/.eslintrc.js +105 -0
  2. package/.github/workflows/publish-package-to-npmjs.yml +18 -0
  3. package/.prettierrc.json +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +118 -0
  6. package/dist/Client.js +198 -0
  7. package/dist/Client.js.map +1 -0
  8. package/dist/Method.js +7 -0
  9. package/dist/Method.js.map +1 -0
  10. package/dist/Root.js +66 -0
  11. package/dist/Root.js.map +1 -0
  12. package/dist/Service.js +387 -0
  13. package/dist/Service.js.map +1 -0
  14. package/dist/index.js +22 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/injector.js +39 -0
  17. package/dist/injector.js.map +1 -0
  18. package/dist/interfaces.js +4 -0
  19. package/dist/interfaces.js.map +1 -0
  20. package/dist/types/Client.d.ts +25 -0
  21. package/dist/types/Method.d.ts +6 -0
  22. package/dist/types/Root.d.ts +26 -0
  23. package/dist/types/Service.d.ts +78 -0
  24. package/dist/types/index.d.ts +5 -0
  25. package/dist/types/injector.d.ts +11 -0
  26. package/dist/types/interfaces.d.ts +72 -0
  27. package/examples/HttpGate/index.ts +61 -0
  28. package/examples/HttpGate/package-lock.json +835 -0
  29. package/examples/HttpGate/package.json +15 -0
  30. package/examples/LogicService/index.ts +16 -0
  31. package/examples/LogicService/interfaces.ts +10 -0
  32. package/examples/LogicService/methods/WeirdSum.ts +20 -0
  33. package/examples/LogicService/service.json +31 -0
  34. package/examples/LogicService/service.ts +15 -0
  35. package/examples/MathService/Untitled-1.json +62 -0
  36. package/examples/MathService/index.ts +22 -0
  37. package/examples/MathService/interfaces.ts +26 -0
  38. package/examples/MathService/methods/Fibonacci.ts +29 -0
  39. package/examples/MathService/methods/Sum.ts +16 -0
  40. package/examples/MathService/methods/SumStream.ts +18 -0
  41. package/examples/MathService/service.json +64 -0
  42. package/examples/MathService/service.ts +18 -0
  43. package/examples/SimpleCache.ts +21 -0
  44. package/examples/misc/trace_1.png +0 -0
  45. package/examples/misc/trace_2.png +0 -0
  46. package/package.json +41 -0
  47. package/src/Client.ts +237 -0
  48. package/src/Method.ts +7 -0
  49. package/src/Root.ts +69 -0
  50. package/src/Service.ts +419 -0
  51. package/src/index.ts +5 -0
  52. package/src/injector.ts +43 -0
  53. package/src/interfaces.ts +89 -0
  54. package/tsconfig.json +19 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "gate",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "fastify": "^4.3.0"
14
+ }
15
+ }
@@ -0,0 +1,16 @@
1
+ import { Client } from '../../src/Client';
2
+ import { NatsConnection } from 'nats';
3
+ import { WeirdSumRequest, WeirdSumResponse } from './interfaces';
4
+ import { Baggage, CacheSettings } from '../../src/interfaces';
5
+ import { name, methods } from './service.json';
6
+ export * from './interfaces';
7
+
8
+ export default class ServiceMathClient extends Client {
9
+ constructor(nats: NatsConnection, baggage?: Baggage, cacheSettings?: CacheSettings) {
10
+ super(nats, name, baggage, cacheSettings);
11
+ }
12
+
13
+ public async weirdSum(payload: WeirdSumRequest) {
14
+ return this.request<WeirdSumResponse>(`${name}.${methods.WeirdSum.action}`, payload, methods.WeirdSum);
15
+ }
16
+ }
@@ -0,0 +1,10 @@
1
+ // SERVICE
2
+
3
+ export type WeirdSumRequest = {
4
+ a: number;
5
+ b: number;
6
+ };
7
+
8
+ export type WeirdSumResponse = {
9
+ result: number;
10
+ };
@@ -0,0 +1,20 @@
1
+ import { WeirdSumRequest, WeirdSumResponse } from '../interfaces';
2
+ import { related, service } from '../../../src/injector';
3
+ import { methods } from '../service.json';
4
+
5
+ import Math from '../../MathService';
6
+ import { BaseMethod } from '../../../src/Method';
7
+
8
+ @related
9
+ export class WeirdSum extends BaseMethod {
10
+ static settings = methods.WeirdSum;
11
+ @service(Math) private math: Math;
12
+
13
+ public async handler(request: WeirdSumRequest): Promise<WeirdSumResponse> {
14
+ const sum = await this.math.sum(request);
15
+ const fibonacci = await this.math.fibonacci({ length: sum.result });
16
+ this.logger.info('Some info');
17
+ const result = await this.math.sumStream(fibonacci);
18
+ return result;
19
+ }
20
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "Logic",
3
+ "description": "busines logic BFF",
4
+ "methods": {
5
+ "WeirdSum": {
6
+ "action": "weirdsum",
7
+ "description": "Weird sum logic. Gets two numbers and adds them. The result is a length Fibonacci sequence. Add the sequence and get the result",
8
+ "options": {
9
+ "cache": 5,
10
+ "runTimeValidation": {
11
+ "request": true
12
+ }
13
+ },
14
+ "request": {
15
+ "type": "object",
16
+ "properties": {
17
+ "a": { "type": "number" },
18
+ "b": {"type": "number" }
19
+ },
20
+ "required": ["a", "b"]
21
+ },
22
+ "response": {
23
+ "type": "object",
24
+ "properties": {
25
+ "result": { "type": "number" }
26
+ },
27
+ "required": ["result"]
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ import { Service } from '../../src/Service';
2
+ import { name } from './service.json';
3
+ import { connect } from 'nats';
4
+
5
+ import { WeirdSum } from './methods/WeirdSum';
6
+
7
+ (async () => {
8
+ const brokerConnection = await connect({ servers: ['localhost:4222'] });
9
+ new Service({
10
+ name,
11
+ brokerConnection,
12
+ methods: [WeirdSum],
13
+ events: [],
14
+ }).start();
15
+ })();
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "name": {
6
+ "type": "string"
7
+ },
8
+ "description": {
9
+ "type": "string"
10
+ },
11
+ "methods": {
12
+ "type": "object",
13
+ "additionalProperties": {
14
+ "type": "object",
15
+ "properties": {
16
+ "action": { "type": "string" },
17
+ "description": { "type": "string" },
18
+ "options": { "$ref": "#/$defs/options" },
19
+ "request": { "type": "object" },
20
+ "response": { "type": "object" }
21
+ },
22
+ "required": [ "action", "description", "options" ]
23
+ }
24
+ },
25
+ "events": {
26
+ "type": "object",
27
+ "additionalProperties": {
28
+ "type": "object",
29
+ "properties": {
30
+ "name": { "type": "string" },
31
+ "description": { "type": "string" },
32
+ "message": { "type": "object" }
33
+ },
34
+ "required": [ "action", "description", "options", "message" ]
35
+ }
36
+ }
37
+ },
38
+ "required": [ "name", "description", "methods" ],
39
+
40
+ "$defs": {
41
+ "options": {
42
+ "type": "object",
43
+ "properties": {
44
+ "useStream": {
45
+ "type": "object",
46
+ "properties": {
47
+ "request": { "type": "boolean" },
48
+ "response": { "type": "boolean" }
49
+ }
50
+ },
51
+ "cache": { "type": "number" },
52
+ "runTimeValidation": {
53
+ "type": "object",
54
+ "properties": {
55
+ "request": { "type": "boolean" },
56
+ "response": { "type": "boolean" }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,22 @@
1
+ import { Client } from '../../src/Client';
2
+ import { NatsConnection } from 'nats';
3
+ import { SumRequest, SumResponse, EmitterMath, SumStreamResponse, FibonacciRequest } from './interfaces';
4
+ import { Baggage, CacheSettings } from '../../src/interfaces';
5
+ import { name, methods } from './service.json';
6
+ import { Readable } from 'stream';
7
+ export * from './interfaces';
8
+
9
+ export default class ServiceMathClient extends Client<EmitterMath> {
10
+ constructor(nats: NatsConnection, baggage?: Baggage, cacheSettings?: CacheSettings) {
11
+ super(nats, name, baggage, cacheSettings);
12
+ }
13
+ public async sum(payload: SumRequest) {
14
+ return this.request<SumResponse>(`${name}.${methods.Sum.action}`, payload, methods.Sum);
15
+ }
16
+ public async sumStream(payload: Readable) {
17
+ return this.request<SumStreamResponse>(`${name}.${methods.SumStream.action}`, payload, methods.SumStream);
18
+ }
19
+ public async fibonacci(payload: FibonacciRequest) {
20
+ return this.request<Readable>(`${name}.${methods.Fibonacci.action}`, payload, methods.Fibonacci);
21
+ }
22
+ }
@@ -0,0 +1,26 @@
1
+ // SERVICE
2
+
3
+ interface ElapsedEvent {
4
+ elapsed: number;
5
+ }
6
+
7
+ export type EmitterMath = {
8
+ elapsed: (params: ElapsedEvent) => void;
9
+ };
10
+
11
+ export type SumRequest = {
12
+ a: number;
13
+ b: number;
14
+ };
15
+
16
+ export interface SumResponse {
17
+ result: number;
18
+ }
19
+
20
+ export type FibonacciRequest = {
21
+ length: number;
22
+ };
23
+
24
+ export type SumStreamResponse = {
25
+ result: number;
26
+ };
@@ -0,0 +1,29 @@
1
+ import { FibonacciRequest } from '../interfaces';
2
+ import { related } from '../../../src/injector';
3
+ import { methods } from '../service.json';
4
+ import { Readable } from 'stream';
5
+ import { setTimeout } from 'timers/promises';
6
+ import { BaseMethod } from '../../../src/Method';
7
+ import { Method } from '../../../src/interfaces';
8
+
9
+ @related
10
+ export class Fibonacci extends BaseMethod {
11
+ static settings = methods.Fibonacci;
12
+ private firstNumber = 0;
13
+ private secondNumber = 1;
14
+ public async handler(request: FibonacciRequest): Promise<Readable> {
15
+ const { length } = request;
16
+ return Readable.from(this.getFibonacciSequence(length));
17
+ }
18
+ private async *getFibonacciSequence(length: number) {
19
+ let limit = length;
20
+ do {
21
+ await setTimeout(100);
22
+ const nextNumber = this.firstNumber + this.secondNumber;
23
+ this.firstNumber = this.secondNumber;
24
+ this.secondNumber = nextNumber;
25
+ yield nextNumber;
26
+ limit--;
27
+ } while (limit > 0);
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ import { SumRequest, SumResponse } from '../interfaces';
2
+ import { related } from '../../../src/injector';
3
+ import { methods } from '../service.json';
4
+ import { BaseMethod } from '../../../src/Method';
5
+
6
+ @related
7
+ export class Sum extends BaseMethod {
8
+ static settings = methods.Sum;
9
+ public async handler(request: SumRequest): Promise<SumResponse> {
10
+ const { a, b } = request;
11
+ return { result: this.sum(a, b) };
12
+ }
13
+ private sum(a: number, b: number) {
14
+ return a + b;
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ import { SumStreamResponse } from '../interfaces';
2
+ import { related } from '../../../src/injector';
3
+ import { methods } from '../service.json';
4
+ import { Readable } from 'stream';
5
+ import { BaseMethod } from '../../../src/Method';
6
+
7
+ @related
8
+ export class SumStream extends BaseMethod {
9
+ static settings = methods.SumStream;
10
+ private result = 0;
11
+ public async handler(request: Readable): Promise<SumStreamResponse> {
12
+ for await (const chunk of request) {
13
+ const sequenceNumber = +Buffer.from(chunk).toString();
14
+ this.result += sequenceNumber;
15
+ }
16
+ return { result: this.result };
17
+ }
18
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "Math",
3
+ "description": "Mathematics service",
4
+ "methods": {
5
+ "Sum": {
6
+ "action": "sum",
7
+ "description": "Addition of two numbers",
8
+ "options": {},
9
+ "request": {
10
+ "type": "object",
11
+ "properties": {
12
+ "a": { "type": "number" },
13
+ "b": {"type": "number" }
14
+ },
15
+ "required": ["a", "b"]
16
+ },
17
+ "response": {
18
+ "type": "object",
19
+ "properties": {
20
+ "result": { "type": "number" }
21
+ },
22
+ "required": ["result"]
23
+ }
24
+ },
25
+ "Fibonacci": {
26
+ "action": "fibonacci",
27
+ "description": "Return Fibonacci sequence given length",
28
+ "options": {
29
+ "useStream": {
30
+ "response": true
31
+ }
32
+ },
33
+ "request": {
34
+ "type": "object",
35
+ "properties": {
36
+ "length": { "type": "number" }
37
+ },
38
+ "required": ["length"]
39
+ },
40
+ "response": {
41
+ "type": "number"
42
+ }
43
+ },
44
+ "SumStream": {
45
+ "action": "sumstream",
46
+ "description": "Adding all the numbers of the stream",
47
+ "options": {
48
+ "useStream": {
49
+ "request": true
50
+ }
51
+ },
52
+ "request": {
53
+ "type": "number"
54
+ },
55
+ "response": {
56
+ "type": "object",
57
+ "properties": {
58
+ "result": { "type": "number" }
59
+ },
60
+ "required": ["result"]
61
+ }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ import { Service } from '../../src/Service';
2
+ import { EmitterMath } from './interfaces';
3
+ import { name } from './service.json';
4
+ import { connect } from 'nats';
5
+
6
+ import { Sum } from './methods/Sum';
7
+ import { SumStream } from './methods/SumStream';
8
+ import { Fibonacci } from './methods/Fibonacci';
9
+
10
+ (async () => {
11
+ const brokerConnection = await connect({ servers: ['localhost:4222'] });
12
+ new Service<EmitterMath>({
13
+ name,
14
+ brokerConnection,
15
+ methods: [Sum, SumStream, Fibonacci],
16
+ events: ['elapsed'],
17
+ }).start();
18
+ })();
@@ -0,0 +1,21 @@
1
+ export class SimpleCache {
2
+ private map = new Map<string, string>();
3
+
4
+ public async get(key: string) {
5
+ return this.map.get(key);
6
+ }
7
+
8
+ public async delete(key: string) {
9
+ this.map.delete(key);
10
+ return;
11
+ }
12
+
13
+ public async set(key: string, data: string, expired?: number) {
14
+ this.map.set(key, data);
15
+ if (expired) {
16
+ setTimeout(() => {
17
+ this.map.delete(key);
18
+ }, expired * 60 * 1000);
19
+ }
20
+ }
21
+ }
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@lad-tech/nsc-toolkit",
3
+ "version": "0.5.2",
4
+ "description": "Toolkit for create microservices around NATS",
5
+ "main": "dist/index.js",
6
+ "types": "./dist/types/index.d.ts",
7
+ "scripts": {
8
+ "build": "rm -rf ./dist && tsc",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "keywords": [],
12
+ "author": "DevHive crew",
13
+ "license": "MIT",
14
+ "devDependencies": {
15
+ "@types/node": "^17.0.42",
16
+ "@typescript-eslint/eslint-plugin": "^5.43.0",
17
+ "eslint": "^7.32.0",
18
+ "eslint-config-airbnb-base": "^15.0.0",
19
+ "eslint-config-prettier": "^8.5.0",
20
+ "eslint-plugin-import": "^2.25.4",
21
+ "eslint-plugin-jest": "^26.1.3",
22
+ "eslint-plugin-node": "^11.1.0",
23
+ "eslint-plugin-prettier": "^4.0.0",
24
+ "prettier": "^2.6.1",
25
+ "prettier-eslint": "^13.0.0",
26
+ "ts-jest": "^27.1.4",
27
+ "typescript": "^4.6.3"
28
+ },
29
+ "dependencies": {
30
+ "@lad-tech/toolbelt": "^1.1.1",
31
+ "@opentelemetry/api": "^1.1.0",
32
+ "@opentelemetry/exporter-jaeger": "^1.3.1",
33
+ "@opentelemetry/resources": "^1.3.1",
34
+ "@opentelemetry/sdk-trace-base": "^1.3.1",
35
+ "@opentelemetry/semantic-conventions": "^1.3.1",
36
+ "ajv": "^8.11.0",
37
+ "nats": "^2.6.1",
38
+ "reflect-metadata": "^0.1.13"
39
+ }
40
+
41
+ }
package/src/Client.ts ADDED
@@ -0,0 +1,237 @@
1
+ import { Root } from './Root';
2
+ import { NatsConnection, Subscription, JSONCodec } from 'nats';
3
+ import {
4
+ Baggage,
5
+ Message,
6
+ Emitter,
7
+ Listener,
8
+ HttpSettings,
9
+ ExternalBaggage,
10
+ CacheSettings,
11
+ MethodOptions,
12
+ MethodSettings,
13
+ } from './interfaces';
14
+ import * as opentelemetry from '@opentelemetry/api';
15
+ import { EventEmitter, Readable } from 'stream';
16
+ import * as http from 'http';
17
+ import { createHash } from 'crypto';
18
+ import { setTimeout } from 'timers/promises';
19
+ import Ajv from 'ajv';
20
+
21
+ export class Client<E extends Emitter = {}> extends Root {
22
+ private subscriptions = new Map<keyof E, Subscription>();
23
+ private REQUEST_HTTP_SETTINGS_TIMEOUT = 1000; // ms
24
+
25
+ constructor(
26
+ natsConnection: NatsConnection,
27
+ private serviceName: string,
28
+ private baggage?: Baggage,
29
+ private cache?: CacheSettings,
30
+ ) {
31
+ super(natsConnection);
32
+ this.logger.setLocation(serviceName);
33
+ }
34
+
35
+ /**
36
+ * Make listener for service events. Auto subscribe and unsubscribe to subject
37
+ */
38
+ public getListener<A extends keyof E>(eventNames: Array<A>, queue?: string): Listener<E> {
39
+ const listener = new EventEmitter();
40
+ eventNames.forEach(async eventName => {
41
+ const subscription = this.brocker.subscribe(`${this.serviceName}.${eventName as string}`, { queue });
42
+ this.subscriptions.set(eventName, subscription);
43
+ for await (const event of subscription) {
44
+ const data = JSONCodec<unknown>().decode(event.data);
45
+ listener.emit(`${eventName as string}`, data);
46
+ }
47
+ });
48
+ return new Proxy(listener, {
49
+ get: (target, prop, receiver) => {
50
+ const method = Reflect.get(target, prop, receiver);
51
+ if (prop === 'off') {
52
+ return (eventName: A, listener: (params: unknown) => void) => {
53
+ this.logger.info('Unsubscribe', eventName);
54
+ const subscription = this.subscriptions.get(eventName);
55
+ subscription?.unsubscribe();
56
+ this.subscriptions.delete(eventName);
57
+ return method.call(target, eventName, listener);
58
+ };
59
+ }
60
+ return method;
61
+ },
62
+ }) as Listener<E>;
63
+ }
64
+
65
+ private createCacheKey(subject: string, data: Record<string, unknown>) {
66
+ const dataHash = createHash('sha1').update(JSON.stringify(data)).digest('hex');
67
+ return `${this.CACHE_SERVICE_KEY}:${subject}:${dataHash}`;
68
+ }
69
+
70
+ private validate(data: any, schema: Record<string, unknown>) {
71
+ const requestValidator = new Ajv().compile(schema);
72
+ const valid = requestValidator(data);
73
+ if (!valid) {
74
+ throw new Error(JSON.stringify(requestValidator.errors));
75
+ }
76
+ }
77
+
78
+ protected async request<R = any>(
79
+ subject: string,
80
+ data: Record<string, unknown> | Readable,
81
+ { options, request, response }: MethodSettings,
82
+ ): Promise<R> {
83
+ const tracer = opentelemetry.trace.getTracer('');
84
+ const span = tracer.startSpan(subject, undefined, this.getContext(this.baggage));
85
+ try {
86
+ if (options?.runTimeValidation?.request && request) {
87
+ this.validate(data, request);
88
+ }
89
+
90
+ const { spanId, traceId, traceFlags } = span.spanContext();
91
+ const expired = this.getExpired(this.baggage?.expired, options?.timeout);
92
+ const message: Message = { payload: data, baggage: { expired, traceId, spanId, traceFlags } };
93
+ const timeout = expired - Date.now();
94
+
95
+ if (timeout <= 0) {
96
+ throw new Error('Timeout request service ' + subject);
97
+ }
98
+
99
+ let key = '';
100
+
101
+ if (options?.cache && !this.isStream(data) && this.cache) {
102
+ try {
103
+ key = this.createCacheKey(subject, data);
104
+ const result = await Promise.race<any>([this.cache.service.get(key), setTimeout(this.cache.timeout, null)]);
105
+ if (result) {
106
+ return JSON.parse(result);
107
+ }
108
+ } catch (error) {
109
+ this.logger.warn('get cache: ', error);
110
+ }
111
+ }
112
+
113
+ const result = options?.useStream
114
+ ? await this.makeHttpRequest(subject, message, options, timeout)
115
+ : await this.makeBrokerRequest(subject, message, timeout);
116
+
117
+ if (result.error) {
118
+ throw new Error(result.error.message);
119
+ }
120
+
121
+ if (options?.runTimeValidation?.response && response) {
122
+ this.validate(result.payload, response);
123
+ }
124
+
125
+ if (options?.cache && !this.isStream(result.payload) && this.cache) {
126
+ this.cache.service.set(key, JSON.stringify(result.payload), options.cache);
127
+ }
128
+
129
+ return result.payload;
130
+ } catch (error) {
131
+ span.setAttribute('error', true);
132
+ span.setAttribute('error.kind', error);
133
+ this.logger.error(error);
134
+ throw error;
135
+ } finally {
136
+ span.end();
137
+ }
138
+ }
139
+
140
+ private async getHTTPSettingsFromRemoteService() {
141
+ const subject = `${this.serviceName}.${this.SERVICE_SUBJECT_FOR_GET_HTTP_SETTINGS}`;
142
+ const result = await this.brocker.request(subject, Buffer.from(JSON.stringify('')), {
143
+ timeout: this.REQUEST_HTTP_SETTINGS_TIMEOUT,
144
+ });
145
+ const { ip, port } = JSONCodec<HttpSettings>().decode(result.data);
146
+ if (!ip || !port) {
147
+ throw new Error(`Remote service ${this.serviceName} did not return http settings`);
148
+ }
149
+ return { ip, port };
150
+ }
151
+
152
+ private isStream(data: unknown | Readable): data is Readable {
153
+ return data instanceof Readable;
154
+ }
155
+
156
+ private async makeBrokerRequest(subject: string, message: Message, timeout: number) {
157
+ try {
158
+ const result = await this.brocker.request(subject, Buffer.from(JSON.stringify(message)), { timeout });
159
+ return JSONCodec<Message<any>>().decode(result.data);
160
+ } catch (error) {
161
+ return this.buildErrorMessage(error);
162
+ }
163
+ }
164
+
165
+ private async makeHttpRequest(
166
+ subject: string,
167
+ message: Message | Message<Readable>,
168
+ options: MethodOptions,
169
+ timeout: number,
170
+ ): Promise<Message<any>> {
171
+ return new Promise(async resolve => {
172
+ const { ip, port } = await this.getHTTPSettingsFromRemoteService();
173
+ const serviceActionPath = subject.split('.');
174
+ const headersFromBaggage = this.convertBaggaggeToExternalHeader(message.baggage);
175
+
176
+ const headers = {
177
+ 'Content-Type': this.isStream(message.payload) ? 'application/octet-stream' : 'application/json',
178
+ ...headersFromBaggage,
179
+ };
180
+ if (!this.isStream(message.payload)) {
181
+ headers['Content-Length'] = Buffer.byteLength(JSON.stringify(message.payload));
182
+ }
183
+ const request = http.request(
184
+ {
185
+ host: ip,
186
+ port,
187
+ path: `/${serviceActionPath.join('/')}`,
188
+ method: 'POST',
189
+ headers,
190
+ timeout,
191
+ },
192
+ async response => {
193
+ if (options?.useStream?.response) {
194
+ resolve({ payload: response });
195
+ return;
196
+ }
197
+
198
+ const data: Buffer[] = [];
199
+ for await (const chunk of response) {
200
+ data.push(chunk);
201
+ }
202
+
203
+ const responseDataString = Buffer.concat(data).toString();
204
+ try {
205
+ resolve(JSON.parse(responseDataString));
206
+ } catch (error) {
207
+ resolve(this.buildErrorMessage(error));
208
+ }
209
+ },
210
+ );
211
+
212
+ request.on('error', error => {
213
+ resolve(this.buildErrorMessage(error));
214
+ });
215
+
216
+ if (this.isStream(message.payload)) {
217
+ message.payload.pipe(request);
218
+ return;
219
+ }
220
+
221
+ request.write(JSON.stringify(message.payload));
222
+ request.end();
223
+ });
224
+ }
225
+
226
+ private convertBaggaggeToExternalHeader(baggage?: Baggage): ExternalBaggage {
227
+ if (!baggage) {
228
+ return {};
229
+ }
230
+ return {
231
+ 'nsc-expired': baggage.expired,
232
+ 'nsc-trace-id': baggage.traceId,
233
+ 'nsc-span-id': baggage.spanId,
234
+ 'nsc-trace-flags': baggage.traceFlags,
235
+ };
236
+ }
237
+ }