@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.
@@ -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
- constructor(modules: ClassType<any>[], flow: Flow, logger?: Logger, amqpConnection?: AmqpConnection, skipApi?: boolean);
23
- private init;
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;
@@ -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
- this.init(flow, modules, logger, skipApi);
212
+ if (!explicitInit) {
213
+ this.init();
214
+ }
209
215
  }
210
- async init(flow, modules, logger, skipApi) {
211
- this.context = Object.assign({}, flow.context);
212
- this.properties = flow.properties || {};
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() {
@@ -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: any, inputEvent: FlowEvent, outputId?: string, time?: Date): FlowEvent;
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;
@@ -133,7 +133,7 @@ function InputStream(id = 'default', options) {
133
133
  }
134
134
  exports.InputStream = InputStream;
135
135
  function FlowFunction(fqn) {
136
- const fqnRegExp = new RegExp('^([a-zA-Z][a-zA-Z0-9]*[.-])*[a-zA-Z][a-zA-Z0-9]*$');
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
  }
@@ -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.emitOutput(event.getData());
10
+ return this.emitEvent({}, event);
11
11
  }
12
12
  };
13
13
  tslib_1.__decorate([
@@ -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
- remoteProcedures = {}
16
+ remote_procedures = {}
17
+ flow_logs_exchange: AbstractRobustExchange
16
18
 
17
19
 
18
20
  def RemoteProcedure(func):
19
- global remoteProcedures
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
- remoteProcedures[func.__name__] = func
27
+ remote_procedures[func.__name__] = func
26
28
 
27
29
  return function_wrapper
28
30
 
29
31
 
30
- loop = asyncio.get_event_loop()
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(sendReply(exchange, reply1, message), loop=loop)
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
- # call function
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
- else:
54
- reply = {
55
- "type": "error",
56
- "message": request["functionName"] + " is not a function",
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
- async def sendReply(exchange: Exchange, reply, originalMessage: Message):
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=originalMessage.correlation_id
68
+ correlation_id=original_message.correlation_id
66
69
  ),
67
- routing_key=originalMessage.reply_to,
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.21.1",
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.2.0",
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.1",
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.5",
37
+ "rxjs": "^7.5.6",
38
38
  "string-interp": "^0.3.6"
39
39
  },
40
40
  "devDependencies": {
41
- "@golevelup/nestjs-rabbitmq": "^2.4.0",
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.1",
45
+ "@types/jest": "^28.1.6",
46
46
  "@types/lodash": "^4.14.182",
47
- "@types/node": "^16.11.40",
47
+ "@types/node": "^16.11.45",
48
48
  "class-validator-jsonschema": "^3.1.1",
49
- "jest": "^28.1.1",
50
- "typescript": "^4.7.3"
49
+ "jest": "^28.1.3",
50
+ "typescript": "^4.7.4"
51
51
  },
52
52
  "engines": {
53
53
  "node": "^14.13.1 || >=16.0.0"