@hahnpro/flow-sdk 4.21.1 → 4.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/dist/FlowApplication.d.ts +7 -3
- package/dist/FlowApplication.js +28 -19
- package/dist/FlowElement.d.ts +1 -1
- package/dist/FlowElement.js +1 -1
- package/dist/TestModule.js +1 -1
- package/dist/rpc_server.py +35 -21
- package/dist/utils.js +2 -1
- package/package.json +9 -9
|
@@ -8,19 +8,23 @@ import { FlowEvent } from './FlowEvent';
|
|
|
8
8
|
import { Logger } from './FlowLogger';
|
|
9
9
|
import { RpcClient } from './RpcClient';
|
|
10
10
|
export declare class FlowApplication {
|
|
11
|
+
private modules;
|
|
12
|
+
private flow;
|
|
11
13
|
private amqpConnection?;
|
|
14
|
+
private skipApi;
|
|
12
15
|
api: API;
|
|
13
16
|
private context;
|
|
14
17
|
private declarations;
|
|
15
18
|
private elements;
|
|
16
|
-
private logger;
|
|
19
|
+
private readonly logger;
|
|
17
20
|
private outputStreamMap;
|
|
18
21
|
private outputQueueMetrics;
|
|
19
22
|
private performanceMap;
|
|
20
23
|
private properties;
|
|
21
24
|
private _rpcClient;
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
private initialized;
|
|
26
|
+
constructor(modules: ClassType<any>[], flow: Flow, logger?: Logger, amqpConnection?: AmqpConnection, skipApi?: boolean, explicitInit?: boolean);
|
|
27
|
+
init(): Promise<void>;
|
|
24
28
|
private publishLifecycleEvent;
|
|
25
29
|
private setQueueMetrics;
|
|
26
30
|
private updateMetrics;
|
package/dist/FlowApplication.js
CHANGED
|
@@ -19,13 +19,17 @@ const MAX_EVENT_SIZE_BYTES = +process.env.MAX_EVENT_SIZE_BYTES || 512 * 1024;
|
|
|
19
19
|
const WARN_EVENT_PROCESSING_SEC = +process.env.WARN_EVENT_PROCESSING_SEC || 60;
|
|
20
20
|
const WARN_EVENT_QUEUE_SIZE = +process.env.WARN_EVENT_QUEUE_SIZE || 100;
|
|
21
21
|
class FlowApplication {
|
|
22
|
-
constructor(modules, flow, logger, amqpConnection, skipApi = false) {
|
|
22
|
+
constructor(modules, flow, logger, amqpConnection, skipApi = false, explicitInit = false) {
|
|
23
|
+
this.modules = modules;
|
|
24
|
+
this.flow = flow;
|
|
23
25
|
this.amqpConnection = amqpConnection;
|
|
26
|
+
this.skipApi = skipApi;
|
|
24
27
|
this.declarations = {};
|
|
25
28
|
this.elements = {};
|
|
26
29
|
this.outputStreamMap = new Map();
|
|
27
30
|
this.outputQueueMetrics = new Map();
|
|
28
31
|
this.performanceMap = new Map();
|
|
32
|
+
this.initialized = false;
|
|
29
33
|
this.publishLifecycleEvent = (element, flowEventId, eventType, data = {}) => {
|
|
30
34
|
if (!this.amqpConnection) {
|
|
31
35
|
return;
|
|
@@ -205,22 +209,26 @@ class FlowApplication {
|
|
|
205
209
|
this.logger.log('Flow Deployment is terminating');
|
|
206
210
|
this.destroy(0);
|
|
207
211
|
});
|
|
208
|
-
|
|
212
|
+
if (!explicitInit) {
|
|
213
|
+
this.init();
|
|
214
|
+
}
|
|
209
215
|
}
|
|
210
|
-
async init(
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
async init() {
|
|
217
|
+
if (this.initialized)
|
|
218
|
+
return;
|
|
219
|
+
this.context = Object.assign({}, this.flow.context);
|
|
220
|
+
this.properties = this.flow.properties || {};
|
|
213
221
|
try {
|
|
214
|
-
if (!skipApi) {
|
|
222
|
+
if (!this.skipApi) {
|
|
215
223
|
this.api = new hpc_api_1.API();
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
catch (err) {
|
|
219
227
|
this.logger.error((err === null || err === void 0 ? void 0 : err.message) || err);
|
|
220
228
|
}
|
|
221
|
-
const logErrorAndExit = (err) => {
|
|
229
|
+
const logErrorAndExit = async (err) => {
|
|
222
230
|
this.logger.error(new Error(err));
|
|
223
|
-
this.destroy(1);
|
|
231
|
+
await this.destroy(1);
|
|
224
232
|
};
|
|
225
233
|
if (this.amqpConnection) {
|
|
226
234
|
try {
|
|
@@ -229,7 +237,7 @@ class FlowApplication {
|
|
|
229
237
|
await this.amqpConnection.managedChannel.assertExchange('flow', 'direct', { durable: true });
|
|
230
238
|
}
|
|
231
239
|
catch (e) {
|
|
232
|
-
logErrorAndExit(`Could not assert exchanges: ${e}`);
|
|
240
|
+
await logErrorAndExit(`Could not assert exchanges: ${e}`);
|
|
233
241
|
return;
|
|
234
242
|
}
|
|
235
243
|
try {
|
|
@@ -240,38 +248,38 @@ class FlowApplication {
|
|
|
240
248
|
}, 'FlowApplication.onMessage');
|
|
241
249
|
}
|
|
242
250
|
catch (err) {
|
|
243
|
-
logErrorAndExit(`Could not subscribe to deployment exchange: ${err}`);
|
|
251
|
+
await logErrorAndExit(`Could not subscribe to deployment exchange: ${err}`);
|
|
244
252
|
return;
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
|
-
for (const module of modules) {
|
|
255
|
+
for (const module of this.modules) {
|
|
248
256
|
const moduleName = Reflect.getMetadata('module:name', module);
|
|
249
257
|
const moduleDeclarations = Reflect.getMetadata('module:declarations', module);
|
|
250
258
|
if (!moduleName || !moduleDeclarations || !Array.isArray(moduleDeclarations)) {
|
|
251
|
-
logErrorAndExit(`FlowModule (${module.name}) metadata is missing or invalid`);
|
|
259
|
+
await logErrorAndExit(`FlowModule (${module.name}) metadata is missing or invalid`);
|
|
252
260
|
return;
|
|
253
261
|
}
|
|
254
262
|
for (const declaration of moduleDeclarations) {
|
|
255
263
|
const functionFqn = Reflect.getMetadata('element:functionFqn', declaration);
|
|
256
264
|
if (!functionFqn) {
|
|
257
|
-
logErrorAndExit(`FlowFunction (${declaration.name}) metadata is missing or invalid`);
|
|
265
|
+
await logErrorAndExit(`FlowFunction (${declaration.name}) metadata is missing or invalid`);
|
|
258
266
|
return;
|
|
259
267
|
}
|
|
260
268
|
this.declarations[`${moduleName}.${functionFqn}`] = declaration;
|
|
261
269
|
}
|
|
262
270
|
}
|
|
263
|
-
for (const element of flow.elements) {
|
|
271
|
+
for (const element of this.flow.elements) {
|
|
264
272
|
const { id, name, properties, module, functionFqn } = element;
|
|
265
273
|
try {
|
|
266
|
-
const context = Object.assign(Object.assign({}, this.context), { id, name, logger, app: this });
|
|
274
|
+
const context = Object.assign(Object.assign({}, this.context), { id, name, logger: this.logger, app: this });
|
|
267
275
|
this.elements[id] = new this.declarations[`${module}.${functionFqn}`](context, properties);
|
|
268
276
|
}
|
|
269
277
|
catch (err) {
|
|
270
|
-
logErrorAndExit(`Could not create FlowElement for ${module}.${functionFqn}`);
|
|
278
|
+
await logErrorAndExit(`Could not create FlowElement for ${module}.${functionFqn}`);
|
|
271
279
|
return;
|
|
272
280
|
}
|
|
273
281
|
}
|
|
274
|
-
for (const connection of flow.connections) {
|
|
282
|
+
for (const connection of this.flow.connections) {
|
|
275
283
|
const { source, target, sourceStream = 'default', targetStream = 'default' } = connection;
|
|
276
284
|
if (!source || !target) {
|
|
277
285
|
continue;
|
|
@@ -280,12 +288,12 @@ class FlowApplication {
|
|
|
280
288
|
const targetStreamId = `${target}.${targetStream}`;
|
|
281
289
|
const element = this.elements[target];
|
|
282
290
|
if (!element || !element.constructor) {
|
|
283
|
-
logErrorAndExit(`${target} has not been initialized`);
|
|
291
|
+
await logErrorAndExit(`${target} has not been initialized`);
|
|
284
292
|
return;
|
|
285
293
|
}
|
|
286
294
|
const streamHandler = Reflect.getMetadata(`stream:${targetStream}`, element.constructor);
|
|
287
295
|
if (!streamHandler || !element[streamHandler]) {
|
|
288
|
-
logErrorAndExit(`${target} does not implement a handler for ${targetStream}`);
|
|
296
|
+
await logErrorAndExit(`${target} does not implement a handler for ${targetStream}`);
|
|
289
297
|
return;
|
|
290
298
|
}
|
|
291
299
|
const streamOptions = Reflect.getMetadata(`stream:options:${targetStream}`, element.constructor) || {};
|
|
@@ -326,6 +334,7 @@ class FlowApplication {
|
|
|
326
334
|
}))
|
|
327
335
|
.subscribe();
|
|
328
336
|
}
|
|
337
|
+
this.initialized = true;
|
|
329
338
|
this.logger.log('Flow Deployment is running');
|
|
330
339
|
}
|
|
331
340
|
getProperties() {
|
package/dist/FlowElement.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare abstract class FlowElement<T = any> {
|
|
|
29
29
|
* @deprecated since version 4.8.0, will be removed in 5.0.0, use emitEvent(...) instead
|
|
30
30
|
*/
|
|
31
31
|
protected emitOutput(data?: any, outputId?: string, time?: Date): FlowEvent;
|
|
32
|
-
protected emitEvent(data:
|
|
32
|
+
protected emitEvent(data: Record<string, any>, inputEvent: FlowEvent, outputId?: string, time?: Date): FlowEvent;
|
|
33
33
|
protected validateProperties<P>(classType: ClassType<P>, properties?: any, whitelist?: boolean): P;
|
|
34
34
|
protected logValidationErrors(error: any, parent?: string): void;
|
|
35
35
|
protected validateEventData<E>(classType: ClassType<E>, event: FlowEvent, whitelist?: boolean): E;
|
package/dist/FlowElement.js
CHANGED
|
@@ -133,7 +133,7 @@ function InputStream(id = 'default', options) {
|
|
|
133
133
|
}
|
|
134
134
|
exports.InputStream = InputStream;
|
|
135
135
|
function FlowFunction(fqn) {
|
|
136
|
-
const fqnRegExp =
|
|
136
|
+
const fqnRegExp = /^([a-zA-Z][a-zA-Z\d]*[.-])*[a-zA-Z][a-zA-Z\d]*$/;
|
|
137
137
|
if (!fqnRegExp.test(fqn)) {
|
|
138
138
|
throw new Error(`Flow Function FQN (${fqn}) is not valid`);
|
|
139
139
|
}
|
package/dist/TestModule.js
CHANGED
|
@@ -7,7 +7,7 @@ const FlowEvent_1 = require("./FlowEvent");
|
|
|
7
7
|
const FlowModule_1 = require("./FlowModule");
|
|
8
8
|
let TestTrigger = class TestTrigger extends FlowElement_1.FlowTask {
|
|
9
9
|
async onDefault(event) {
|
|
10
|
-
return this.
|
|
10
|
+
return this.emitEvent({}, event);
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
tslib_1.__decorate([
|
package/dist/rpc_server.py
CHANGED
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
from asyncio import Future
|
|
4
4
|
from functools import partial, wraps
|
|
5
5
|
from aio_pika import IncomingMessage, Exchange, Message, connect_robust, ExchangeType
|
|
6
|
+
from aio_pika.abc import AbstractRobustExchange
|
|
6
7
|
import os
|
|
7
8
|
|
|
8
9
|
user = os.getenv("RABBIT_USER", "guest")
|
|
@@ -12,22 +13,24 @@ port = os.getenv("RABBIT_PORT", "5672")
|
|
|
12
13
|
vhost = os.getenv("RABBIT_VHOST", "")
|
|
13
14
|
routingKey = os.getenv("RPC_ROUTING_KEY", "rpc")
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
remote_procedures = {}
|
|
17
|
+
flow_logs_exchange: AbstractRobustExchange
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def RemoteProcedure(func):
|
|
19
|
-
global
|
|
21
|
+
global remote_procedures
|
|
20
22
|
|
|
21
23
|
@wraps(func)
|
|
22
24
|
def function_wrapper(*args, **kwargs):
|
|
23
25
|
return func(*args, **kwargs)
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
remote_procedures[func.__name__] = func
|
|
26
28
|
|
|
27
29
|
return function_wrapper
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
loop = asyncio.
|
|
32
|
+
loop = asyncio.new_event_loop()
|
|
33
|
+
asyncio.set_event_loop(loop)
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
async def on_message(exchange: Exchange, message: IncomingMessage):
|
|
@@ -39,36 +42,38 @@ async def on_message(exchange: Exchange, message: IncomingMessage):
|
|
|
39
42
|
# print(traceback.format_list(traceback.extract_stack(err)))
|
|
40
43
|
reply1 = {"type": "error", "message": str(err), "stack": "failed"}
|
|
41
44
|
|
|
42
|
-
asyncio.ensure_future(
|
|
45
|
+
asyncio.ensure_future(send_reply(exchange, reply1, message), loop=loop)
|
|
43
46
|
|
|
44
|
-
with message.process():
|
|
45
|
-
request = json.loads(message.body.decode())
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
if remoteProcedures.keys().__contains__(request["functionName"]):
|
|
49
|
-
func = remoteProcedures.get(request["functionName"])
|
|
50
|
-
future = loop.run_in_executor(None, func, *request["arguments"])
|
|
51
|
-
future.add_done_callback(callback)
|
|
48
|
+
request = json.loads(message.body.decode())
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
await sendReply(exchange, reply, originalMessage=message)
|
|
50
|
+
# call function
|
|
51
|
+
if remote_procedures.keys().__contains__(request["functionName"]):
|
|
52
|
+
func = remote_procedures.get(request["functionName"])
|
|
53
|
+
future = loop.run_in_executor(None, func, *request["arguments"])
|
|
54
|
+
future.add_done_callback(callback)
|
|
59
55
|
|
|
56
|
+
else:
|
|
57
|
+
reply = {
|
|
58
|
+
"type": "error",
|
|
59
|
+
"message": request["functionName"] + " is not a function",
|
|
60
|
+
}
|
|
61
|
+
await send_reply(exchange, reply, original_message=message)
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
|
|
64
|
+
async def send_reply(exchange: Exchange, reply, original_message: Message):
|
|
62
65
|
await exchange.publish(
|
|
63
66
|
Message(
|
|
64
67
|
body=json.dumps(reply).encode("utf-8"),
|
|
65
|
-
correlation_id=
|
|
68
|
+
correlation_id=original_message.correlation_id
|
|
66
69
|
),
|
|
67
|
-
routing_key=
|
|
70
|
+
routing_key=original_message.reply_to,
|
|
68
71
|
)
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
async def main(loop, routing_key):
|
|
75
|
+
global flow_logs_exchange
|
|
76
|
+
|
|
72
77
|
url = "amqp://%s:%s@%s:%s/%s" % (user, password, host, port, vhost)
|
|
73
78
|
connection = await connect_robust(
|
|
74
79
|
url, loop=loop
|
|
@@ -77,6 +82,7 @@ async def main(loop, routing_key):
|
|
|
77
82
|
channel = await connection.channel()
|
|
78
83
|
|
|
79
84
|
dest_exchange = await channel.declare_exchange(name="rpc_direct_exchange", type=ExchangeType.DIRECT)
|
|
85
|
+
flow_logs_exchange = await channel.declare_exchange(name='flowlogs', type=ExchangeType.FANOUT, durable=True)
|
|
80
86
|
|
|
81
87
|
queue = await channel.declare_queue("", exclusive=True)
|
|
82
88
|
|
|
@@ -90,3 +96,11 @@ async def main(loop, routing_key):
|
|
|
90
96
|
def start_consumer(routing_key=routingKey):
|
|
91
97
|
loop.create_task(main(loop, routing_key))
|
|
92
98
|
loop.run_forever()
|
|
99
|
+
|
|
100
|
+
def log(message):
|
|
101
|
+
global flow_logs_exchange
|
|
102
|
+
|
|
103
|
+
if flow_logs_exchange is not None:
|
|
104
|
+
flow_logs_exchange.publish(Message(body=json.dumps(message).encode("utf-8")), "")
|
|
105
|
+
else:
|
|
106
|
+
print("Connection not established. Call start_consumer first.")
|
package/dist/utils.js
CHANGED
|
@@ -61,7 +61,8 @@ function delay(ms) {
|
|
|
61
61
|
exports.delay = delay;
|
|
62
62
|
async function deleteFiles(dir, ...filenames) {
|
|
63
63
|
for (const filename of filenames) {
|
|
64
|
-
await fs_1.promises.unlink((0, path_1.join)(dir, filename)).catch((err) => {
|
|
64
|
+
await fs_1.promises.unlink((0, path_1.join)(dir, filename)).catch((err) => {
|
|
65
|
+
});
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
exports.deleteFiles = deleteFiles;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hahnpro/flow-sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.22.0",
|
|
4
4
|
"description": "SDK for building Flow Modules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -24,30 +24,30 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@hahnpro/hpc-api": "3.
|
|
27
|
+
"@hahnpro/hpc-api": "3.3.0",
|
|
28
28
|
"amqp-connection-manager": "^3.9.0",
|
|
29
29
|
"amqplib": "^0.10.0",
|
|
30
30
|
"class-transformer": "0.5.1",
|
|
31
31
|
"class-validator": "~0.13.2",
|
|
32
|
-
"cloudevents": "^6.0.
|
|
32
|
+
"cloudevents": "^6.0.2",
|
|
33
33
|
"lodash": "^4.17.21",
|
|
34
34
|
"object-sizeof": "^1.6.3",
|
|
35
35
|
"python-shell": "^3.0.1",
|
|
36
36
|
"reflect-metadata": "^0.1.13",
|
|
37
|
-
"rxjs": "^7.5.
|
|
37
|
+
"rxjs": "^7.5.6",
|
|
38
38
|
"string-interp": "^0.3.6"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@golevelup/nestjs-rabbitmq": "^
|
|
41
|
+
"@golevelup/nestjs-rabbitmq": "^3.0.0",
|
|
42
42
|
"@nestjs/common": "^8.4.7",
|
|
43
43
|
"@types/amqp-connection-manager": "^2.0.12",
|
|
44
44
|
"@types/amqplib": "^0.8.2",
|
|
45
|
-
"@types/jest": "^28.1.
|
|
45
|
+
"@types/jest": "^28.1.6",
|
|
46
46
|
"@types/lodash": "^4.14.182",
|
|
47
|
-
"@types/node": "^16.11.
|
|
47
|
+
"@types/node": "^16.11.45",
|
|
48
48
|
"class-validator-jsonschema": "^3.1.1",
|
|
49
|
-
"jest": "^28.1.
|
|
50
|
-
"typescript": "^4.7.
|
|
49
|
+
"jest": "^28.1.3",
|
|
50
|
+
"typescript": "^4.7.4"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": "^14.13.1 || >=16.0.0"
|