@hahnpro/flow-sdk 4.20.11 → 4.21.0-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/dist/FlowApplication.d.ts +5 -4
- package/dist/FlowApplication.js +50 -9
- package/dist/FlowElement.d.ts +1 -0
- package/dist/FlowElement.js +2 -1
- package/dist/FlowEvent.d.ts +7 -1
- package/dist/FlowLogger.d.ts +1 -1
- package/dist/FlowLogger.js +1 -2
- package/dist/RpcClient.js +2 -2
- package/dist/amqp.d.ts +2 -2
- package/dist/flow.interface.d.ts +5 -0
- package/dist/flow.interface.js +7 -0
- package/package.json +11 -12
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import
|
|
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
|
|
7
|
-
import
|
|
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;
|
|
@@ -32,7 +33,7 @@ export declare class FlowApplication {
|
|
|
32
33
|
* Publish a flow event to the amqp flowlogs exchange.
|
|
33
34
|
* If the event size exceeds the limit it will be truncated
|
|
34
35
|
*/
|
|
35
|
-
publishEvent: (event: FlowEvent) =>
|
|
36
|
+
publishEvent: (event: FlowEvent) => void;
|
|
36
37
|
rpcClient(): Promise<RpcClient>;
|
|
37
38
|
/**
|
|
38
39
|
* Calls onDestroy lifecycle method on all flow elements,
|
package/dist/FlowApplication.js
CHANGED
|
@@ -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
|
|
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,24 +126,34 @@ 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,
|
|
129
|
+
eventId: (0, crypto_1.randomUUID)(),
|
|
107
130
|
eventTime: new Date().toISOString(),
|
|
108
131
|
eventType: 'com.hahnpro.event.health',
|
|
109
132
|
contentType: 'application/json',
|
|
110
133
|
data: { deploymentId: this.context.deploymentId, status: 'updated' },
|
|
111
134
|
};
|
|
112
|
-
|
|
135
|
+
try {
|
|
136
|
+
(_f = this.amqpConnection) === null || _f === void 0 ? void 0 : _f.publish('deployment', 'health', statusEvent);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
this.logger.error(err);
|
|
140
|
+
}
|
|
113
141
|
}
|
|
114
142
|
catch (err) {
|
|
115
143
|
this.logger.error(err);
|
|
116
144
|
const statusEvent = {
|
|
117
|
-
eventId: (0,
|
|
145
|
+
eventId: (0, crypto_1.randomUUID)(),
|
|
118
146
|
eventTime: new Date().toISOString(),
|
|
119
147
|
eventType: 'com.hahnpro.event.health',
|
|
120
148
|
contentType: 'application/json',
|
|
121
149
|
data: { deploymentId: this.context.deploymentId, status: 'updating failed' },
|
|
122
150
|
};
|
|
123
|
-
|
|
151
|
+
try {
|
|
152
|
+
(_g = this.amqpConnection) === null || _g === void 0 ? void 0 : _g.publish('deployment', 'health', statusEvent);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
this.logger.error(e);
|
|
156
|
+
}
|
|
124
157
|
}
|
|
125
158
|
}
|
|
126
159
|
else if (event.type === 'com.flowstudio.deployment.message') {
|
|
@@ -193,6 +226,7 @@ class FlowApplication {
|
|
|
193
226
|
try {
|
|
194
227
|
await this.amqpConnection.managedChannel.assertExchange('deployment', 'direct', { durable: true });
|
|
195
228
|
await this.amqpConnection.managedChannel.assertExchange('flowlogs', 'fanout', { durable: true });
|
|
229
|
+
await this.amqpConnection.managedChannel.assertExchange('flow', 'direct', { durable: true });
|
|
196
230
|
}
|
|
197
231
|
catch (e) {
|
|
198
232
|
logErrorAndExit(`Could not assert exchanges: ${e}`);
|
|
@@ -203,7 +237,7 @@ class FlowApplication {
|
|
|
203
237
|
exchange: 'deployment',
|
|
204
238
|
routingKey: this.context.deploymentId,
|
|
205
239
|
queueOptions: { durable: false, exclusive: true },
|
|
206
|
-
});
|
|
240
|
+
}, 'FlowApplication.onMessage');
|
|
207
241
|
}
|
|
208
242
|
catch (err) {
|
|
209
243
|
logErrorAndExit(`Could not subscribe to deployment exchange: ${err}`);
|
|
@@ -259,11 +293,18 @@ class FlowApplication {
|
|
|
259
293
|
const outputStream = this.getOutputStream(sourceStreamId);
|
|
260
294
|
outputStream
|
|
261
295
|
.pipe((0, operators_1.tap)(() => this.setQueueMetrics(targetStreamId)), (0, operators_1.mergeMap)(async (event) => {
|
|
262
|
-
|
|
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();
|
|
263
300
|
try {
|
|
264
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 });
|
|
265
304
|
}
|
|
266
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 });
|
|
267
308
|
try {
|
|
268
309
|
element.handleApiError(err);
|
|
269
310
|
}
|
|
@@ -278,8 +319,8 @@ class FlowApplication {
|
|
|
278
319
|
if (elu) {
|
|
279
320
|
this.performanceMap.delete(event.getId());
|
|
280
321
|
elu = perf_hooks_1.performance.eventLoopUtilization(elu);
|
|
281
|
-
if (elu.utilization > 0.
|
|
282
|
-
this.logger.warn(`High event loop utilization detected for ${targetStreamId} with event ${event.getId()}! Handler
|
|
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.`);
|
|
283
324
|
}
|
|
284
325
|
}
|
|
285
326
|
}))
|
package/dist/FlowElement.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/FlowElement.js
CHANGED
|
@@ -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
|
}
|
package/dist/FlowEvent.d.ts
CHANGED
|
@@ -8,7 +8,13 @@ export declare class FlowEvent {
|
|
|
8
8
|
getDataContentType: () => string;
|
|
9
9
|
getDataschema: () => string;
|
|
10
10
|
getId: () => string;
|
|
11
|
-
getMetadata: () =>
|
|
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/FlowLogger.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class FlowLogger implements Logger {
|
|
|
16
16
|
private readonly logger;
|
|
17
17
|
private readonly publishEvent?;
|
|
18
18
|
private static getStackTrace;
|
|
19
|
-
constructor(metadata: FlowElementContext, logger?: Logger, publishEvent?: (event: FlowEvent) =>
|
|
19
|
+
constructor(metadata: FlowElementContext, logger?: Logger, publishEvent?: (event: FlowEvent) => void);
|
|
20
20
|
debug: (message: any, options?: LoggerOptions) => void;
|
|
21
21
|
error: (message: any, options?: LoggerOptions) => void;
|
|
22
22
|
log: (message: any, options?: LoggerOptions) => void;
|
package/dist/FlowLogger.js
CHANGED
|
@@ -35,10 +35,9 @@ class FlowLogger {
|
|
|
35
35
|
return stack.splice(1).join('\n');
|
|
36
36
|
}
|
|
37
37
|
publish(message, level, options) {
|
|
38
|
-
var _a;
|
|
39
38
|
if (this.publishEvent) {
|
|
40
39
|
const event = new FlowEvent_1.FlowEvent(this.metadata, message, `flow.log.${level}`);
|
|
41
|
-
|
|
40
|
+
this.publishEvent(event);
|
|
42
41
|
}
|
|
43
42
|
switch (level) {
|
|
44
43
|
case 'debug':
|
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
|
|
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,
|
|
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' })
|
package/dist/amqp.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export interface AmqpConnection {
|
|
|
5
5
|
connection: Connection;
|
|
6
6
|
managedChannel: ChannelWrapper;
|
|
7
7
|
managedConnection: AmqpConnectionManager;
|
|
8
|
-
createSubscriber<T>(handler: (msg: T | undefined, rawMessage?: ConsumeMessage) => Promise<any | undefined>, msgOptions: MessageHandlerOptions): Promise<void>;
|
|
9
|
-
publish(exchange: string, routingKey: string, message: any, options?: Options.Publish):
|
|
8
|
+
createSubscriber<T>(handler: (msg: T | undefined, rawMessage?: ConsumeMessage) => Promise<any | undefined | void>, msgOptions: MessageHandlerOptions, originalHandlerName: string): Promise<void>;
|
|
9
|
+
publish(exchange: string, routingKey: string, message: any, options?: Options.Publish): void;
|
|
10
10
|
}
|
|
11
11
|
export declare class Nack {
|
|
12
12
|
private readonly _requeue;
|
package/dist/flow.interface.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/flow.interface.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "4.21.0-2",
|
|
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": "
|
|
27
|
+
"@hahnpro/hpc-api": "3.0.0",
|
|
28
28
|
"amqp-connection-manager": "^3.9.0",
|
|
29
|
-
"amqplib": "^0.9.
|
|
29
|
+
"amqplib": "^0.9.1",
|
|
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
|
-
"@golevelup/nestjs-rabbitmq": "^2.
|
|
43
|
-
"@nestjs/common": "^8.4.
|
|
41
|
+
"@golevelup/nestjs-rabbitmq": "^2.4.0",
|
|
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.
|
|
45
|
+
"@types/jest": "^27.5.1",
|
|
47
46
|
"@types/lodash": "^4.14.182",
|
|
48
|
-
"@types/node": "^16.11.
|
|
49
|
-
"class-validator-jsonschema": "^3.1.
|
|
50
|
-
"jest": "^
|
|
51
|
-
"typescript": "^4.
|
|
47
|
+
"@types/node": "^16.11.38",
|
|
48
|
+
"class-validator-jsonschema": "^3.1.1",
|
|
49
|
+
"jest": "^28.1.0",
|
|
50
|
+
"typescript": "^4.7.2"
|
|
52
51
|
},
|
|
53
52
|
"engines": {
|
|
54
53
|
"node": "^14.13.1 || >=16.0.0"
|