@hahnpro/flow-sdk 4.20.13 → 4.21.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.
@@ -1,10 +1,10 @@
1
1
  import 'reflect-metadata';
2
- import type { CloudEvent } from 'cloudevents';
2
+ import { CloudEvent } from 'cloudevents';
3
3
  import { PartialObserver } from 'rxjs';
4
4
  import { API } from '@hahnpro/hpc-api';
5
5
  import { AmqpConnection, Nack } from './amqp';
6
- import type { ClassType, Flow, FlowElementContext } from './flow.interface';
7
- import type { FlowEvent } from './FlowEvent';
6
+ import { ClassType, Flow, FlowElementContext } from './flow.interface';
7
+ import { FlowEvent } from './FlowEvent';
8
8
  import { Logger } from './FlowLogger';
9
9
  import { RpcClient } from './RpcClient';
10
10
  export declare class FlowApplication {
@@ -21,6 +21,7 @@ export declare class FlowApplication {
21
21
  private _rpcClient;
22
22
  constructor(modules: ClassType<any>[], flow: Flow, logger?: Logger, amqpConnection?: AmqpConnection, skipApi?: boolean);
23
23
  private init;
24
+ private publishLifecycleEvent;
24
25
  private setQueueMetrics;
25
26
  private updateMetrics;
26
27
  subscribe: (streamId: string, observer: PartialObserver<FlowEvent>) => import("rxjs").Subscription;
@@ -3,14 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlowApplication = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  require("reflect-metadata");
6
+ const cloudevents_1 = require("cloudevents");
6
7
  const object_sizeof_1 = tslib_1.__importDefault(require("object-sizeof"));
7
8
  const perf_hooks_1 = require("perf_hooks");
8
9
  const rxjs_1 = require("rxjs");
9
10
  const operators_1 = require("rxjs/operators");
10
- const uuid_1 = require("uuid");
11
+ const crypto_1 = require("crypto");
11
12
  const hpc_api_1 = require("@hahnpro/hpc-api");
12
13
  const amqp_1 = require("./amqp");
13
14
  const utils_1 = require("./utils");
15
+ const flow_interface_1 = require("./flow.interface");
14
16
  const FlowLogger_1 = require("./FlowLogger");
15
17
  const RpcClient_1 = require("./RpcClient");
16
18
  const MAX_EVENT_SIZE_BYTES = +process.env.MAX_EVENT_SIZE_BYTES || 512 * 1024;
@@ -24,6 +26,27 @@ class FlowApplication {
24
26
  this.outputStreamMap = new Map();
25
27
  this.outputQueueMetrics = new Map();
26
28
  this.performanceMap = new Map();
29
+ this.publishLifecycleEvent = (element, flowEventId, eventType, data = {}) => {
30
+ if (!this.amqpConnection) {
31
+ return;
32
+ }
33
+ try {
34
+ const { flowId, deploymentId, id: elementId, functionFqn, inputStreamId } = element.getMetadata();
35
+ const event = new cloudevents_1.CloudEvent({
36
+ source: `flows/${flowId}/deployments/${deploymentId}/elements/${elementId}`,
37
+ type: eventType,
38
+ data: Object.assign({ flowEventId,
39
+ functionFqn,
40
+ inputStreamId }, data),
41
+ time: new Date().toISOString(),
42
+ });
43
+ const message = event.toJSON();
44
+ return this.amqpConnection.publish('flow', 'lifecycle', message);
45
+ }
46
+ catch (err) {
47
+ this.logger.error(err);
48
+ }
49
+ };
27
50
  this.setQueueMetrics = (id) => {
28
51
  const metrics = this.outputQueueMetrics.get(id) || { size: 0, lastAdd: 0, lastRemove: Date.now(), warnings: 0 };
29
52
  const secsProcessing = Math.round((metrics.lastAdd - metrics.lastRemove) / 1000);
@@ -103,7 +126,7 @@ class FlowApplication {
103
126
  (_e = (_d = this.elements) === null || _d === void 0 ? void 0 : _d[element.id]) === null || _e === void 0 ? void 0 : _e.onPropertiesChanged(element.properties);
104
127
  }
105
128
  const statusEvent = {
106
- eventId: (0, uuid_1.v4)(),
129
+ eventId: (0, crypto_1.randomUUID)(),
107
130
  eventTime: new Date().toISOString(),
108
131
  eventType: 'com.hahnpro.event.health',
109
132
  contentType: 'application/json',
@@ -119,7 +142,7 @@ class FlowApplication {
119
142
  catch (err) {
120
143
  this.logger.error(err);
121
144
  const statusEvent = {
122
- eventId: (0, uuid_1.v4)(),
145
+ eventId: (0, crypto_1.randomUUID)(),
123
146
  eventTime: new Date().toISOString(),
124
147
  eventType: 'com.hahnpro.event.health',
125
148
  contentType: 'application/json',
@@ -203,6 +226,7 @@ class FlowApplication {
203
226
  try {
204
227
  await this.amqpConnection.managedChannel.assertExchange('deployment', 'direct', { durable: true });
205
228
  await this.amqpConnection.managedChannel.assertExchange('flowlogs', 'fanout', { durable: true });
229
+ await this.amqpConnection.managedChannel.assertExchange('flow', 'direct', { durable: true });
206
230
  }
207
231
  catch (e) {
208
232
  logErrorAndExit(`Could not assert exchanges: ${e}`);
@@ -269,11 +293,18 @@ class FlowApplication {
269
293
  const outputStream = this.getOutputStream(sourceStreamId);
270
294
  outputStream
271
295
  .pipe((0, operators_1.tap)(() => this.setQueueMetrics(targetStreamId)), (0, operators_1.mergeMap)(async (event) => {
272
- this.performanceMap.set(event.getId(), perf_hooks_1.performance.eventLoopUtilization());
296
+ const eventId = event.getId();
297
+ this.publishLifecycleEvent(element, eventId, flow_interface_1.LifecycleEvent.ACTIVATED);
298
+ this.performanceMap.set(eventId, perf_hooks_1.performance.eventLoopUtilization());
299
+ const start = perf_hooks_1.performance.now();
273
300
  try {
274
301
  await element[streamHandler](event);
302
+ const duration = Math.ceil(perf_hooks_1.performance.now() - start);
303
+ this.publishLifecycleEvent(element, eventId, flow_interface_1.LifecycleEvent.COMPLETED, { duration });
275
304
  }
276
305
  catch (err) {
306
+ const duration = Math.ceil(perf_hooks_1.performance.now() - start);
307
+ this.publishLifecycleEvent(element, eventId, flow_interface_1.LifecycleEvent.TERMINATED, { duration });
277
308
  try {
278
309
  element.handleApiError(err);
279
310
  }
@@ -288,8 +319,8 @@ class FlowApplication {
288
319
  if (elu) {
289
320
  this.performanceMap.delete(event.getId());
290
321
  elu = perf_hooks_1.performance.eventLoopUtilization(elu);
291
- if (elu.utilization > 0.7 && elu.active > 1000) {
292
- 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.`);
322
+ if (elu.utilization > 0.75 && elu.active > 2000) {
323
+ this.logger.warn(`High event loop utilization detected for ${targetStreamId} with event ${event.getId()}! Handler was 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.`);
293
324
  }
294
325
  }
295
326
  }))
@@ -22,6 +22,7 @@ export declare abstract class FlowElement<T = any> {
22
22
  onFlowPropertiesChanged?: (properties: Record<string, any>) => void;
23
23
  onContextChanged: (context: Partial<FlowContext>) => void;
24
24
  onPropertiesChanged: (properties: T) => void;
25
+ getMetadata: () => FlowElementContext;
25
26
  protected setProperties: (properties: T) => void;
26
27
  handleApiError: (error: any) => void;
27
28
  /**
@@ -22,6 +22,7 @@ class FlowElement {
22
22
  this.onPropertiesChanged = (properties) => {
23
23
  this.setProperties(properties);
24
24
  };
25
+ this.getMetadata = () => this.metadata;
25
26
  this.setProperties = (properties) => {
26
27
  if (this.propertiesClassType) {
27
28
  this.properties = this.validateProperties(this.propertiesClassType, properties, this.whitelist);
@@ -126,7 +127,7 @@ function InputStream(id = 'default', options) {
126
127
  if (!this.stopPropagateStream.has(id)) {
127
128
  this.stopPropagateStream.set(id, (_a = options === null || options === void 0 ? void 0 : options.stopPropagation) !== null && _a !== void 0 ? _a : false);
128
129
  }
129
- return method.call(this, new FlowEvent_1.FlowEvent(Object.assign(Object.assign({}, event.getMetadata()), { inputStreamId: id }), event.getData(), event.getType(), new Date(event.getTime())));
130
+ return method.call(this, new FlowEvent_1.FlowEvent(Object.assign(Object.assign({ id: event.getMetadata().elementId }, event.getMetadata()), { inputStreamId: id }), event.getData(), event.getType(), new Date(event.getTime())));
130
131
  };
131
132
  };
132
133
  }
@@ -8,7 +8,13 @@ export declare class FlowEvent {
8
8
  getDataContentType: () => string;
9
9
  getDataschema: () => string;
10
10
  getId: () => string;
11
- getMetadata: () => any;
11
+ getMetadata: () => {
12
+ deploymentId: string;
13
+ elementId: string;
14
+ flowId: string;
15
+ functionFqn: string;
16
+ inputStreamId: string;
17
+ };
12
18
  getSource: () => string;
13
19
  getStreamId: () => string;
14
20
  getSubject: () => string;
package/dist/RpcClient.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RpcClient = void 0;
4
- const uuid_1 = require("uuid");
4
+ const crypto_1 = require("crypto");
5
5
  class RpcClient {
6
6
  constructor(amqpConnection) {
7
7
  this.amqpConnection = amqpConnection;
@@ -40,7 +40,7 @@ class RpcClient {
40
40
  const stack = new Error('test').stack;
41
41
  return new Promise((resolve, reject) => {
42
42
  const call = { functionName, arguments: args };
43
- const correlationId = (0, uuid_1.v4)();
43
+ const correlationId = (0, crypto_1.randomUUID)();
44
44
  this.openRequests.set(correlationId, { resolve, reject, trace: RpcClient.formatTrace(stack) });
45
45
  this.channel
46
46
  .publish('rpc_direct_exchange', routingKey, call, { correlationId, replyTo: 'amq.rabbitmq.reply-to' })
@@ -38,4 +38,9 @@ export interface StreamOptions {
38
38
  concurrent?: number;
39
39
  }
40
40
  export declare type ClassType<T> = new (...args: any[]) => T;
41
+ export declare enum LifecycleEvent {
42
+ ACTIVATED = "com.hahnpro.flow_function.activated",
43
+ COMPLETED = "com.hahnpro.flow_function.completed",
44
+ TERMINATED = "com.hahnpro.flow_function.terminated"
45
+ }
41
46
  export {};
@@ -1,2 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LifecycleEvent = void 0;
4
+ var LifecycleEvent;
5
+ (function (LifecycleEvent) {
6
+ LifecycleEvent["ACTIVATED"] = "com.hahnpro.flow_function.activated";
7
+ LifecycleEvent["COMPLETED"] = "com.hahnpro.flow_function.completed";
8
+ LifecycleEvent["TERMINATED"] = "com.hahnpro.flow_function.terminated";
9
+ })(LifecycleEvent = exports.LifecycleEvent || (exports.LifecycleEvent = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hahnpro/flow-sdk",
3
- "version": "4.20.13",
3
+ "version": "4.21.0",
4
4
  "description": "SDK for building Flow Modules",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -24,9 +24,9 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@hahnpro/hpc-api": "3.0.0",
27
+ "@hahnpro/hpc-api": "3.1.0",
28
28
  "amqp-connection-manager": "^3.9.0",
29
- "amqplib": "^0.9.1",
29
+ "amqplib": "^0.10.0",
30
30
  "class-transformer": "0.5.1",
31
31
  "class-validator": "~0.13.2",
32
32
  "cloudevents": "^6.0.1",
@@ -35,20 +35,19 @@
35
35
  "python-shell": "^3.0.1",
36
36
  "reflect-metadata": "^0.1.13",
37
37
  "rxjs": "^7.5.5",
38
- "string-interp": "^0.3.6",
39
- "uuid": "^8.3.2"
38
+ "string-interp": "^0.3.6"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@golevelup/nestjs-rabbitmq": "^2.4.0",
43
42
  "@nestjs/common": "^8.4.6",
44
43
  "@types/amqp-connection-manager": "^2.0.12",
45
44
  "@types/amqplib": "^0.8.2",
46
- "@types/jest": "^27.5.1",
45
+ "@types/jest": "^28.1.1",
47
46
  "@types/lodash": "^4.14.182",
48
- "@types/node": "^16.11.38",
47
+ "@types/node": "^16.11.39",
49
48
  "class-validator-jsonschema": "^3.1.1",
50
- "jest": "^28.1.0",
51
- "typescript": "^4.7.2"
49
+ "jest": "^28.1.1",
50
+ "typescript": "^4.7.3"
52
51
  },
53
52
  "engines": {
54
53
  "node": "^14.13.1 || >=16.0.0"