@hahnpro/flow-sdk 4.14.5 → 4.17.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.
@@ -15,10 +15,13 @@ export declare class FlowApplication {
15
15
  private elements;
16
16
  private logger;
17
17
  private outputStreamMap;
18
+ private outputQueueMetrics;
18
19
  private performanceMap;
19
20
  private properties;
20
21
  private _rpcClient;
21
22
  constructor(modules: ClassType<any>[], flow: Flow, logger?: Logger, amqpConnection?: AmqpConnection, skipApi?: boolean);
23
+ private setQueueMetrics;
24
+ private updateMetrics;
22
25
  subscribe: (streamId: string, observer: PartialObserver<FlowEvent>) => import("rxjs").Subscription;
23
26
  emit: (event: FlowEvent) => void;
24
27
  emitPartial: (completeEvent: FlowEvent, partialEvent: FlowEvent) => void;
@@ -14,7 +14,9 @@ const api_1 = require("./api");
14
14
  const utils_1 = require("./utils");
15
15
  const FlowLogger_1 = require("./FlowLogger");
16
16
  const RpcClient_1 = require("./RpcClient");
17
- const MAX_EVENT_SIZE_BYTES = 512 * 1024;
17
+ const MAX_EVENT_SIZE_BYTES = +process.env.MAX_EVENT_SIZE_BYTES || 512 * 1024;
18
+ const WARN_EVENT_PROCESSING_SEC = +process.env.WARN_EVENT_PROCESSING_SEC || 60;
19
+ const WARN_EVENT_QUEUE_SIZE = +process.env.WARN_EVENT_QUEUE_SIZE || 100;
18
20
  class FlowApplication {
19
21
  constructor(modules, flow, logger, amqpConnection, skipApi) {
20
22
  var _a, _b, _c;
@@ -22,7 +24,31 @@ class FlowApplication {
22
24
  this.declarations = {};
23
25
  this.elements = {};
24
26
  this.outputStreamMap = new Map();
27
+ this.outputQueueMetrics = new Map();
25
28
  this.performanceMap = new Map();
29
+ this.setQueueMetrics = (id) => {
30
+ const metrics = this.outputQueueMetrics.get(id) || { size: 0, lastAdd: 0, lastRemove: Date.now(), warnings: 0 };
31
+ const secsProcessing = Math.round((metrics.lastAdd - metrics.lastRemove) / 1000);
32
+ metrics.size++;
33
+ metrics.lastAdd = Date.now();
34
+ if (secsProcessing >= WARN_EVENT_PROCESSING_SEC * (metrics.warnings + 1)) {
35
+ this.logger.warn(`Input stream "${id}" has ${metrics.size} queued events and the last event has been processing for ${secsProcessing}s`);
36
+ metrics.warnings++;
37
+ }
38
+ else if (metrics.size % WARN_EVENT_QUEUE_SIZE === 0) {
39
+ this.logger.warn(`Input stream "${id}" has ${metrics.size} queued events`);
40
+ }
41
+ this.outputQueueMetrics.set(id, metrics);
42
+ };
43
+ this.updateMetrics = (id) => {
44
+ const metrics = this.outputQueueMetrics.get(id);
45
+ if (metrics) {
46
+ metrics.size = metrics.size > 0 ? metrics.size - 1 : 0;
47
+ metrics.lastRemove = Date.now();
48
+ metrics.warnings = 0;
49
+ this.outputQueueMetrics.set(id, metrics);
50
+ }
51
+ };
26
52
  this.subscribe = (streamId, observer) => this.getOutputStream(streamId).subscribe(observer);
27
53
  this.emit = (event) => {
28
54
  if (event) {
@@ -31,9 +57,6 @@ class FlowApplication {
31
57
  if (this.outputStreamMap.has(event.getStreamId())) {
32
58
  this.getOutputStream(event.getStreamId()).next(event);
33
59
  }
34
- else {
35
- throw new Error('Output Stream ' + event.getStreamId() + ' is not defined');
36
- }
37
60
  }
38
61
  catch (err) {
39
62
  this.logger.error(err);
@@ -42,13 +65,8 @@ class FlowApplication {
42
65
  };
43
66
  this.emitPartial = (completeEvent, partialEvent) => {
44
67
  try {
45
- if (completeEvent) {
46
- if (this.outputStreamMap.has(completeEvent.getStreamId())) {
47
- this.getOutputStream(completeEvent.getStreamId()).next(completeEvent);
48
- }
49
- else {
50
- throw new Error('Output Stream ' + completeEvent.getStreamId() + ' is not defined');
51
- }
68
+ if (completeEvent && this.outputStreamMap.has(completeEvent.getStreamId())) {
69
+ this.getOutputStream(completeEvent.getStreamId()).next(completeEvent);
52
70
  }
53
71
  if (partialEvent) {
54
72
  this.publishEvent(partialEvent);
@@ -196,7 +214,8 @@ class FlowApplication {
196
214
  if (!source || !target) {
197
215
  continue;
198
216
  }
199
- const streamId = `${source}.${sourceStream}`;
217
+ const sourceStreamId = `${source}.${sourceStream}`;
218
+ const targetStreamId = `${target}.${targetStream}`;
200
219
  const element = this.elements[target];
201
220
  if (!element || !element.constructor) {
202
221
  throw new Error(target + ' has not been initialized');
@@ -207,9 +226,9 @@ class FlowApplication {
207
226
  }
208
227
  const streamOptions = Reflect.getMetadata(`stream:options:${targetStream}`, element.constructor) || {};
209
228
  const concurrent = streamOptions.concurrent || 1;
210
- const outputStream = this.getOutputStream(streamId);
229
+ const outputStream = this.getOutputStream(sourceStreamId);
211
230
  outputStream
212
- .pipe((0, operators_1.mergeMap)(async (event) => {
231
+ .pipe((0, operators_1.tap)(() => this.setQueueMetrics(targetStreamId)), (0, operators_1.mergeMap)(async (event) => {
213
232
  this.performanceMap.set(event.getId(), perf_hooks_1.performance.eventLoopUtilization());
214
233
  try {
215
234
  await element[streamHandler](event);
@@ -224,12 +243,13 @@ class FlowApplication {
224
243
  }
225
244
  return event;
226
245
  }, concurrent), (0, operators_1.tap)((event) => {
246
+ this.updateMetrics(targetStreamId);
227
247
  let elu = this.performanceMap.get(event.getId());
228
248
  if (elu) {
229
249
  this.performanceMap.delete(event.getId());
230
250
  elu = perf_hooks_1.performance.eventLoopUtilization(elu);
231
251
  if (elu.utilization > 0.7 && elu.active > 1000) {
232
- this.logger.warn(`High event loop utilization detected for ${target}.${targetStream} with event ${event.getId()}! Handler has been active for ${Number(elu.active).toFixed(2)}ms with a utilization of ${Number(elu.utilization * 100).toFixed(2)}%. Consider refactoring or move tasks to a worker thread.`);
252
+ this.logger.warn(`High event loop utilization detected for ${targetStreamId} with event ${event.getId()}! Handler has been active for ${Number(elu.active).toFixed(2)}ms with a utilization of ${Number(elu.utilization * 100).toFixed(2)}%. Consider refactoring or move tasks to a worker thread.`);
233
253
  }
234
254
  }
235
255
  }))
@@ -9,6 +9,9 @@ export interface Endpoint {
9
9
  authToken: string;
10
10
  recipients?: string[];
11
11
  };
12
+ notificationCheck: number;
13
+ notificationCount: number;
14
+ notificationPause: number;
12
15
  readPermissions: string[];
13
16
  readWritePermissions: string[];
14
17
  }
@@ -3,6 +3,6 @@ import { DataService } from './data.service';
3
3
  import { Endpoint, EndpointLog } from './endpoint.interface';
4
4
  export declare class EndpointService extends DataService<Endpoint> {
5
5
  constructor(httpClient: HttpClient);
6
- sendNotification(endpointId: string, subject: string, message: string, group: string, eventLink?: string): Promise<void>;
6
+ sendNotification(endpointId: string, subject: string, message: string, group: string, eventLink?: string, assetLink?: string): Promise<void>;
7
7
  readLastLogByGroup(endpointId: string, group: string): Promise<EndpointLog>;
8
8
  }
@@ -6,8 +6,8 @@ class EndpointService extends data_service_1.DataService {
6
6
  constructor(httpClient) {
7
7
  super(httpClient, process.env.DEBUG_ENDPOINT_URL || 'api/notification/endpoints');
8
8
  }
9
- sendNotification(endpointId, subject, message, group, eventLink) {
10
- const body = Object.assign({ subject, message, group }, (eventLink && { eventLink }));
9
+ sendNotification(endpointId, subject, message, group, eventLink, assetLink) {
10
+ const body = Object.assign(Object.assign({ subject, message, group }, (eventLink && { eventLink })), (assetLink && { assetLink }));
11
11
  return this.httpClient.post(`${this.basePath}/${endpointId}`, body);
12
12
  }
13
13
  readLastLogByGroup(endpointId, group) {
@@ -101,6 +101,9 @@ export interface EndpointInit {
101
101
  authToken: string;
102
102
  recipients?: string[];
103
103
  };
104
+ notificationCheck?: number;
105
+ notificationCount?: number;
106
+ notificationPause?: number;
104
107
  readPermissions?: string[];
105
108
  readWritePermissions?: string[];
106
109
  }
@@ -58,6 +58,9 @@ class MockAPI {
58
58
  description: value.description,
59
59
  status: value.status,
60
60
  config: value.config,
61
+ notificationCheck: value.notificationCheck,
62
+ notificationCount: value.notificationCount,
63
+ notificationPause: value.notificationPause,
61
64
  readPermissions: [],
62
65
  readWritePermissions: [],
63
66
  }));
@@ -3,7 +3,7 @@ import { EndpointService } from '../endpoint.service';
3
3
  import { DataMockService } from './data.mock.service';
4
4
  export declare class EndpointMockService extends DataMockService<Endpoint> implements EndpointService {
5
5
  constructor(endpoints: Endpoint[]);
6
- sendNotification(endpointId: string, subject: string, message: string, group: string, eventLink?: string): Promise<void>;
6
+ sendNotification(endpointId: string, subject: string, message: string, group: string, eventLink?: string, assetLink?: string): Promise<void>;
7
7
  readLastLogByGroup(endpointId: string, group: string): Promise<{
8
8
  id: string;
9
9
  endpoint: string;
@@ -7,7 +7,7 @@ class EndpointMockService extends data_mock_service_1.DataMockService {
7
7
  super();
8
8
  this.data = endpoints;
9
9
  }
10
- sendNotification(endpointId, subject, message, group, eventLink) {
10
+ sendNotification(endpointId, subject, message, group, eventLink, assetLink) {
11
11
  return Promise.resolve();
12
12
  }
13
13
  readLastLogByGroup(endpointId, group) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hahnpro/flow-sdk",
3
- "version": "4.14.5",
3
+ "version": "4.17.0",
4
4
  "description": "SDK for building Flow Modules",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "amqp-connection-manager": "^3.7.0",
28
28
  "amqplib": "^0.8.0",
29
- "axios": "^0.21.4",
29
+ "axios": "^0.22.0",
30
30
  "class-transformer": "0.3.1",
31
31
  "class-validator": "~0.12.2",
32
32
  "class-validator-jsonschema": "^2.2.0",
@@ -38,19 +38,19 @@
38
38
  "p-queue": "^6.6.2",
39
39
  "python-shell": "^3.0.0",
40
40
  "reflect-metadata": "^0.1.13",
41
- "rxjs": "^7.3.0",
41
+ "rxjs": "^7.4.0",
42
42
  "string-interp": "^0.3.6",
43
43
  "uuid": "^8.3.2"
44
44
  },
45
45
  "devDependencies": {
46
- "@golevelup/nestjs-rabbitmq": "^1.17.1",
46
+ "@golevelup/nestjs-rabbitmq": "^1.18.0",
47
47
  "@types/amqp-connection-manager": "^2.0.12",
48
48
  "@types/amqplib": "^0.8.2",
49
49
  "@types/jest": "^27.0.2",
50
- "@types/lodash": "^4.14.174",
51
- "@types/node": "^14.17.19",
50
+ "@types/lodash": "^4.14.175",
51
+ "@types/node": "^14.17.21",
52
52
  "axios-mock-adapter": "^1.20.0",
53
- "jest": "^27.2.2",
53
+ "jest": "^27.2.4",
54
54
  "typescript": "^4.4.3"
55
55
  },
56
56
  "engines": {