@lad-tech/nsc-toolkit 1.20.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- ## [1.20.1](https://github.com/lad-tech/nsc-toolkit/compare/v1.20.0...v1.20.1) (2024-07-01)
1
+ # [1.22.0](https://github.com/lad-tech/nsc-toolkit/compare/v1.21.0...v1.22.0) (2024-08-14)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * Fix param name ([#108](https://github.com/lad-tech/nsc-toolkit/issues/108)) ([3b93529](https://github.com/lad-tech/nsc-toolkit/commit/3b9352967adae3694d3db92e1335c7ec4bfebcde))
6
+ * Add support pull consumer ([#110](https://github.com/lad-tech/nsc-toolkit/issues/110)) ([ed77ad7](https://github.com/lad-tech/nsc-toolkit/commit/ed77ad7fa0542c0e8d0b56e71b430a14672bafae))
package/dist/Client.js CHANGED
@@ -36,12 +36,29 @@ class Client extends Root_1.Root {
36
36
  message.ack = event.ack.bind(event);
37
37
  message.nak = event.nak.bind(event);
38
38
  }
39
- listener.emit(`${eventName}`, message);
39
+ listener.emit(eventName, message);
40
+ }
41
+ }
42
+ async startBatchWatch(fetcher, listener, eventName) {
43
+ while (true) {
44
+ const batch = [];
45
+ const events = fetcher.fetch();
46
+ for await (const event of events) {
47
+ let data;
48
+ try {
49
+ data = (0, nats_1.JSONCodec)().decode(event.data);
50
+ }
51
+ catch (error) {
52
+ data = (0, nats_1.StringCodec)().decode(event.data);
53
+ }
54
+ const message = { data };
55
+ message.ack = event.ack.bind(event);
56
+ message.nak = event.nak.bind(event);
57
+ batch.push(message);
58
+ }
59
+ listener.emit(eventName, batch);
40
60
  }
41
61
  }
42
- /**
43
- * Make listener for service events. Auto subscribe and unsubscribe to subject
44
- */
45
62
  getListener(serviceNameFrom, options) {
46
63
  if (!this.events) {
47
64
  throw new Error('The service does not generate events');
@@ -73,7 +90,12 @@ class Client extends Root_1.Root {
73
90
  subscription = this.broker.subscribe(`${this.serviceName}.${eventName}`, queue);
74
91
  }
75
92
  this.subscriptions.set(eventName, subscription);
76
- this.startWatch(subscription, listener, String(eventName));
93
+ if (StreamManager_1.StreamManager.isStreamFetcher(subscription) && StreamManager_1.StreamManager.isPullConsumerOptions(options)) {
94
+ this.startBatchWatch(subscription, listener, String(eventName));
95
+ }
96
+ else {
97
+ this.startWatch(subscription, listener, String(eventName));
98
+ }
77
99
  return method.call(target, eventName, handler);
78
100
  }
79
101
  catch (error) {
@@ -110,7 +132,7 @@ class Client extends Root_1.Root {
110
132
  }
111
133
  }
112
134
  async request(subject, data, { options, request, response }) {
113
- var _a, _b, _c, _d;
135
+ var _a, _b, _c, _d, _e;
114
136
  const tracer = opentelemetry.trace.getTracer('');
115
137
  const span = tracer.startSpan(subject, undefined, this.getContext(this.baggage));
116
138
  try {
@@ -119,7 +141,8 @@ class Client extends Root_1.Root {
119
141
  }
120
142
  const { spanId, traceId, traceFlags } = span.spanContext();
121
143
  const expired = this.getExpired((_b = this.baggage) === null || _b === void 0 ? void 0 : _b.expired, options === null || options === void 0 ? void 0 : options.timeout);
122
- const message = { payload: data, baggage: { expired, traceId, spanId, traceFlags } };
144
+ const requestId = (_c = this.baggage) === null || _c === void 0 ? void 0 : _c.requestId;
145
+ const message = { payload: data, baggage: { expired, traceId, spanId, traceFlags, requestId } };
123
146
  const timeout = expired - Date.now();
124
147
  if (timeout <= 0) {
125
148
  throw new Error('Timeout request service ' + subject);
@@ -141,9 +164,9 @@ class Client extends Root_1.Root {
141
164
  ? await this.makeHttpRequest(subject, message, options, timeout)
142
165
  : await this.makeBrokerRequest(subject, message, timeout);
143
166
  if (result.error) {
144
- throw new Error((_c = result.error.message) !== null && _c !== void 0 ? _c : result.error);
167
+ throw new Error((_d = result.error.message) !== null && _d !== void 0 ? _d : result.error);
145
168
  }
146
- if (((_d = options === null || options === void 0 ? void 0 : options.runTimeValidation) === null || _d === void 0 ? void 0 : _d.response) && response) {
169
+ if (((_e = options === null || options === void 0 ? void 0 : options.runTimeValidation) === null || _e === void 0 ? void 0 : _e.response) && response) {
147
170
  this.validate(result.payload, response);
148
171
  }
149
172
  if ((options === null || options === void 0 ? void 0 : options.cache) && !this.isStream(result.payload) && this.cache) {
@@ -181,7 +204,8 @@ class Client extends Root_1.Root {
181
204
  return (0, nats_1.JSONCodec)().decode(result.data);
182
205
  }
183
206
  catch (error) {
184
- return this.buildErrorMessage(error);
207
+ const errorMessage = new Error(`${error === null || error === void 0 ? void 0 : error.message}. Subject: ${subject} `);
208
+ return this.buildErrorMessage(errorMessage);
185
209
  }
186
210
  }
187
211
  async makeHttpRequest(subject, message, options, timeout) {
@@ -218,11 +242,13 @@ class Client extends Root_1.Root {
218
242
  resolve(JSON.parse(responseDataString));
219
243
  }
220
244
  catch (error) {
221
- resolve(this.buildErrorMessage(error));
245
+ const errorMessage = new Error(`${error === null || error === void 0 ? void 0 : error.message}. Subject: ${subject} `);
246
+ resolve(this.buildErrorMessage(errorMessage));
222
247
  }
223
248
  });
224
249
  request.on('error', error => {
225
- resolve(this.buildErrorMessage(error));
250
+ const errorMessage = new Error(`${error === null || error === void 0 ? void 0 : error.message}. Subject: ${subject} `);
251
+ resolve(this.buildErrorMessage(errorMessage));
226
252
  });
227
253
  if (this.isStream(message.payload)) {
228
254
  message.payload.pipe(request);
@@ -236,12 +262,23 @@ class Client extends Root_1.Root {
236
262
  if (!baggage) {
237
263
  return {};
238
264
  }
239
- return {
240
- 'nsc-expired': baggage.expired,
241
- 'nsc-trace-id': baggage.traceId,
242
- 'nsc-span-id': baggage.spanId,
243
- 'nsc-trace-flags': baggage.traceFlags,
244
- };
265
+ const headers = {};
266
+ if (baggage.expired) {
267
+ headers['nsc-expired'] = baggage.expired;
268
+ }
269
+ if (baggage.requestId) {
270
+ headers['x-request-id'] = baggage.requestId;
271
+ }
272
+ if (baggage.traceId) {
273
+ headers['nsc-trace-id'] = baggage.traceId;
274
+ }
275
+ if (baggage.spanId) {
276
+ headers['nsc-span-id'] = baggage.spanId;
277
+ }
278
+ if (baggage.traceFlags) {
279
+ headers['nsc-trace-flags'] = baggage.traceFlags;
280
+ }
281
+ return headers;
245
282
  }
246
283
  isJsMessage(message) {
247
284
  return !!message.ack && !!message.nak;
package/dist/Service.js CHANGED
@@ -192,7 +192,13 @@ class Service extends Root_1.Root {
192
192
  */
193
193
  getNextBaggage(span, baggage) {
194
194
  const { traceId, spanId, traceFlags } = span.spanContext();
195
- return { traceId, spanId, traceFlags, expired: baggage === null || baggage === void 0 ? void 0 : baggage.expired };
195
+ return { traceId, spanId, traceFlags, expired: baggage === null || baggage === void 0 ? void 0 : baggage.expired, requestId: baggage === null || baggage === void 0 ? void 0 : baggage.requestId };
196
+ }
197
+ /**
198
+ * Guard for determine whether baggage contains trace information
199
+ */
200
+ isBaggageContainTrace(params) {
201
+ return !!params && !!(params.traceId && params.spanId && params.traceFlags);
196
202
  }
197
203
  /**
198
204
  * If there is no baggage. For example, in HTTP Gateway
@@ -200,7 +206,7 @@ class Service extends Root_1.Root {
200
206
  getRootBaggage(subject, headers, ownTimeout) {
201
207
  const baggage = headers ? this.getBaggageFromHTTPHeader(headers) : undefined;
202
208
  const tracer = api_1.trace.getTracer('');
203
- const context = this.getContext(baggage);
209
+ const context = this.getContext(this.isBaggageContainTrace(baggage) ? baggage : undefined);
204
210
  const span = tracer.startSpan(subject, undefined, context);
205
211
  const newBaggage = this.getNextBaggage(span, baggage);
206
212
  this.rootSpans.set(newBaggage.traceId, span);
@@ -333,7 +339,7 @@ class Service extends Root_1.Root {
333
339
  async handled(payload, Method, baggage) {
334
340
  const subject = `${this.serviceName}.${Method.settings.action}`;
335
341
  const tracer = api_1.trace.getTracer('');
336
- const context = this.getContext(baggage);
342
+ const context = this.getContext(this.isBaggageContainTrace(baggage) ? baggage : undefined);
337
343
  const span = tracer.startSpan(subject, undefined, context);
338
344
  const logger = new toolbelt_1.Logs.Logger({
339
345
  location: `${this.serviceName}.${Method.settings.action}`,
@@ -579,15 +585,17 @@ class Service extends Root_1.Root {
579
585
  const traceId = headers['nsc-trace-id'];
580
586
  const spanId = headers['nsc-span-id'];
581
587
  const traceFlags = headers['nsc-trace-flags'] ? +headers['nsc-trace-flags'] : undefined;
588
+ const requestId = headers['x-request-id'] ? String(headers['x-request-id']) : undefined;
582
589
  if (traceId && spanId && traceFlags) {
583
590
  return {
584
591
  traceId,
585
592
  spanId,
586
593
  traceFlags,
587
594
  expired,
595
+ requestId,
588
596
  };
589
597
  }
590
- return undefined;
598
+ return { expired, requestId };
591
599
  }
592
600
  }
593
601
  exports.Service = Service;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamFetcher = void 0;
4
+ class StreamFetcher {
5
+ constructor(jsClient, streamName, consumerName, options) {
6
+ this.jsClient = jsClient;
7
+ this.streamName = streamName;
8
+ this.consumerName = consumerName;
9
+ this.options = options;
10
+ }
11
+ fetch(noWait, size, expires) {
12
+ return this.jsClient.fetch(this.streamName, this.consumerName, {
13
+ batch: size !== null && size !== void 0 ? size : this.options.batchSize,
14
+ expires: expires !== null && expires !== void 0 ? expires : this.options.batchTimeout,
15
+ no_wait: noWait !== null && noWait !== void 0 ? noWait : this.options.noWait,
16
+ });
17
+ }
18
+ }
19
+ exports.StreamFetcher = StreamFetcher;
20
+ //# sourceMappingURL=StreamFetcher.js.map
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StreamManager = void 0;
4
4
  const nats_1 = require("nats");
5
5
  const Root_1 = require("./Root");
6
+ const jsconsumeropts_1 = require("nats/lib/nats-base-client/jsconsumeropts");
7
+ const StreamFetcher_1 = require("./StreamFetcher");
6
8
  class StreamManager extends Root_1.Root {
7
9
  constructor(param) {
8
10
  super(param.broker, param.outputFormatter);
@@ -21,6 +23,12 @@ class StreamManager extends Root_1.Root {
21
23
  rollUps: true,
22
24
  };
23
25
  }
26
+ static isPullConsumerOptions(setting) {
27
+ return !!(setting === null || setting === void 0 ? void 0 : setting.batch);
28
+ }
29
+ static isStreamFetcher(consumer) {
30
+ return !!(consumer === null || consumer === void 0 ? void 0 : consumer.fetch);
31
+ }
24
32
  async createStreams() {
25
33
  if (!this.jsm) {
26
34
  this.jsm = await this.param.broker.jetstreamManager();
@@ -55,14 +63,29 @@ class StreamManager extends Root_1.Root {
55
63
  async createConsumer(serviceNameFrom, eventName, setting) {
56
64
  const consumerName = this.capitalizeFirstLetter(serviceNameFrom) + this.capitalizeFirstLetter(eventName);
57
65
  const prefix = this.param.options.prefix;
58
- const subjeсt = `${this.param.serviceName}.${prefix}.${eventName}`;
66
+ const subject = `${this.param.serviceName}.${prefix}.${eventName}`;
67
+ if (!this.jsm) {
68
+ this.jsm = await this.param.broker.jetstreamManager();
69
+ }
59
70
  const options = (0, nats_1.consumerOpts)();
71
+ const isPullConsumer = StreamManager.isPullConsumerOptions(setting);
60
72
  options
61
73
  .durable(consumerName)
62
74
  .manualAck()
63
75
  .ackExplicit()
64
- .maxAckPending((setting === null || setting === void 0 ? void 0 : setting.maxPending) || 10)
65
- .deliverTo((0, nats_1.createInbox)());
76
+ .filterSubject(subject)
77
+ .maxAckPending((setting === null || setting === void 0 ? void 0 : setting.maxPending) || 10);
78
+ if (!isPullConsumer) {
79
+ options.deliverTo((0, nats_1.createInbox)());
80
+ }
81
+ if (isPullConsumer) {
82
+ if (setting.maxPullRequestExpires) {
83
+ options.maxPullRequestExpires(setting.maxPullRequestExpires);
84
+ }
85
+ if (setting.maxPullRequestBatch) {
86
+ options.maxPullBatch(setting.maxPullRequestBatch);
87
+ }
88
+ }
66
89
  if (setting === null || setting === void 0 ? void 0 : setting.maxAckWaiting) {
67
90
  options.ackWait(setting.maxAckWaiting);
68
91
  }
@@ -77,7 +100,19 @@ class StreamManager extends Root_1.Root {
77
100
  options.deliverAll();
78
101
  }
79
102
  }
80
- return this.broker.jetstream().subscribe(subjeсt, options);
103
+ const streamName = await this.jsm.streams.find(subject);
104
+ if (!streamName) {
105
+ throw new Error(`Error creating consumer ${consumerName}. Stream for subject ${subject} not found`);
106
+ }
107
+ if ((0, jsconsumeropts_1.isConsumerOptsBuilder)(options)) {
108
+ await this.jsm.consumers.add(streamName, options.config);
109
+ }
110
+ return isPullConsumer
111
+ ? new StreamFetcher_1.StreamFetcher(this.broker.jetstream(), streamName, consumerName, {
112
+ batchSize: setting.maxPullRequestBatch,
113
+ batchTimeout: setting.maxPullRequestExpires,
114
+ })
115
+ : this.broker.jetstream().subscribe(subject, options);
81
116
  }
82
117
  getStreamName(eventName) {
83
118
  const serviceName = this.capitalizeFirstLetter(this.param.serviceName);
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { Readable } from 'node:stream';
3
- import { ClientParam, Emitter, GetListenerOptions, Listener, MethodSettings } from './interfaces';
3
+ import { ClientParam, Emitter, GetBatchListenerOptions, GetListenerOptions, Listener, ListenerBatch, MethodSettings } from './interfaces';
4
4
  import { Root } from './Root';
5
5
  type RequestData = Record<string, unknown> | Readable;
6
6
  export declare class Client<E extends Emitter = Emitter> extends Root {
@@ -13,10 +13,12 @@ export declare class Client<E extends Emitter = Emitter> extends Root {
13
13
  private REQUEST_HTTP_SETTINGS_TIMEOUT;
14
14
  constructor({ broker, events, loggerOutputFormatter, serviceName, baggage, cache, Ref }: ClientParam<E>);
15
15
  private startWatch;
16
+ private startBatchWatch;
16
17
  /**
17
18
  * Make listener for service events. Auto subscribe and unsubscribe to subject
18
19
  */
19
20
  getListener<A extends keyof E>(serviceNameFrom: string, options?: GetListenerOptions): Listener<E>;
21
+ getListener<A extends keyof E>(serviceNameFrom: string, options?: GetBatchListenerOptions): ListenerBatch<E>;
20
22
  private createCacheKey;
21
23
  private validate;
22
24
  protected request<R = any, P extends RequestData = RequestData>(subject: string, data: P, { options, request, response }: MethodSettings): Promise<R>;
@@ -37,6 +37,10 @@ export declare class Service<E extends Emitter = Emitter> extends Root {
37
37
  * Create Baggage from span. Expired one-on-one business logic call
38
38
  */
39
39
  private getNextBaggage;
40
+ /**
41
+ * Guard for determine whether baggage contains trace information
42
+ */
43
+ private isBaggageContainTrace;
40
44
  /**
41
45
  * If there is no baggage. For example, in HTTP Gateway
42
46
  */
@@ -45,6 +49,7 @@ export declare class Service<E extends Emitter = Emitter> extends Root {
45
49
  traceId: string;
46
50
  spanId: string;
47
51
  traceFlags: number;
52
+ requestId: string | undefined;
48
53
  };
49
54
  /**
50
55
  * End root baggage
@@ -0,0 +1,16 @@
1
+ import { JetStreamClient, JsMsg } from 'nats';
2
+ import { QueuedIterator } from 'nats/lib/nats-base-client/queued_iterator';
3
+ interface BatcherOptions {
4
+ batchSize?: number;
5
+ batchTimeout?: number;
6
+ noWait?: boolean;
7
+ }
8
+ export declare class StreamFetcher {
9
+ private jsClient;
10
+ private streamName;
11
+ private consumerName;
12
+ private options;
13
+ constructor(jsClient: JetStreamClient, streamName: string, consumerName: string, options: BatcherOptions);
14
+ fetch(noWait?: boolean, size?: number, expires?: number): QueuedIterator<JsMsg>;
15
+ }
16
+ export {};
@@ -1,6 +1,7 @@
1
- import { StreamManagerParam, GetListenerOptions } from '.';
2
- import { JetStreamSubscription } from 'nats';
1
+ import { StreamManagerParam, GetListenerOptions, GetBatchListenerOptions } from '.';
2
+ import { JetStreamSubscription, Subscription } from 'nats';
3
3
  import { Root } from './Root';
4
+ import { StreamFetcher } from './StreamFetcher';
4
5
  export declare class StreamManager extends Root {
5
6
  private param;
6
7
  private readonly STAR_WILDCARD;
@@ -10,8 +11,11 @@ export declare class StreamManager extends Root {
10
11
  private readonly defaultStreamOption;
11
12
  private jsm?;
12
13
  constructor(param: StreamManagerParam);
14
+ static isPullConsumerOptions(setting?: GetListenerOptions | GetBatchListenerOptions): setting is GetBatchListenerOptions;
15
+ static isStreamFetcher(consumer?: JetStreamSubscription | StreamFetcher | Subscription): consumer is StreamFetcher;
13
16
  createStreams(): Promise<void>;
14
17
  createConsumer(serviceNameFrom: string, eventName: string, setting?: GetListenerOptions): Promise<JetStreamSubscription>;
18
+ createConsumer(serviceNameFrom: string, eventName: string, setting?: GetBatchListenerOptions): Promise<StreamFetcher>;
15
19
  private getStreamName;
16
20
  private isNotFoundStreamError;
17
21
  private buildPrefixForStreamName;
@@ -30,6 +30,7 @@ export type Baggage = {
30
30
  traceId: string;
31
31
  spanId: string;
32
32
  traceFlags: number;
33
+ requestId?: string;
33
34
  expired?: number;
34
35
  };
35
36
  export type ExternalBaggage = {
@@ -37,6 +38,7 @@ export type ExternalBaggage = {
37
38
  'nsc-trace-id'?: string;
38
39
  'nsc-span-id'?: string;
39
40
  'nsc-trace-flags'?: number;
41
+ 'x-request-id'?: string;
40
42
  };
41
43
  export interface Message<M = any> {
42
44
  payload: M;
@@ -69,6 +71,11 @@ export interface GetListenerOptions {
69
71
  maxPending?: number;
70
72
  maxAckWaiting?: number;
71
73
  }
74
+ export interface GetBatchListenerOptions extends GetListenerOptions {
75
+ batch: true;
76
+ maxPullRequestBatch?: number;
77
+ maxPullRequestExpires?: number;
78
+ }
72
79
  export interface StreamManagerParam {
73
80
  serviceName: string;
74
81
  options: StreamOptions;
@@ -127,6 +134,10 @@ export interface Listener<E extends Emitter> {
127
134
  on<A extends keyof E>(action: A, handler: E[A]): void;
128
135
  off<A extends keyof E>(action: A, handler: E[A]): void;
129
136
  }
137
+ export interface ListenerBatch<E extends Emitter> {
138
+ on<A extends keyof E>(action: A, handler: (params: Array<Parameters<E[A]>[0]>) => void): void;
139
+ off<A extends keyof E>(action: A, handler: (params: Array<Parameters<E[A]>[0]>) => void): void;
140
+ }
130
141
  export interface HttpSettings {
131
142
  ip?: string;
132
143
  port?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lad-tech/nsc-toolkit",
3
- "version": "1.20.1",
3
+ "version": "1.22.0",
4
4
  "description": "Toolkit for create microservices around NATS",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",