@hahnpro/flow-sdk 9.6.5 → 2025.2.0-beta.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +904 -0
  2. package/package.json +7 -19
  3. package/src/index.d.ts +12 -0
  4. package/src/index.js +18 -0
  5. package/{dist → src/lib}/ContextManager.js +28 -0
  6. package/{dist → src/lib}/FlowApplication.js +38 -10
  7. package/{dist → src/lib}/FlowElement.js +15 -0
  8. package/{dist → src/lib}/FlowEvent.js +1 -0
  9. package/{dist → src/lib}/FlowLogger.js +16 -0
  10. package/{dist → src/lib}/FlowModule.js +1 -0
  11. package/{dist → src/lib}/RpcClient.js +4 -0
  12. package/{dist → src/lib}/amqp.js +1 -1
  13. package/{dist → src/lib}/extra-validators.js +2 -0
  14. package/{dist → src/lib}/nats.js +11 -5
  15. package/{dist → src/lib}/unit-utils.js +1 -0
  16. package/{dist → src/lib}/units.js +2 -0
  17. package/{dist → src/lib}/utils.js +44 -3
  18. package/LICENSE +0 -21
  19. package/{dist → src/lib}/ContextManager.d.ts +0 -0
  20. package/{dist → src/lib}/FlowApplication.d.ts +1 -1
  21. package/{dist → src/lib}/FlowElement.d.ts +1 -1
  22. package/{dist → src/lib}/FlowEvent.d.ts +0 -0
  23. package/{dist → src/lib}/FlowLogger.d.ts +0 -0
  24. package/{dist → src/lib}/FlowModule.d.ts +0 -0
  25. package/{dist → src/lib}/RpcClient.d.ts +0 -0
  26. package/{dist → src/lib}/TestModule.d.ts +0 -0
  27. package/{dist → src/lib}/TestModule.js +0 -0
  28. package/{dist → src/lib}/amqp.d.ts +0 -0
  29. package/{dist → src/lib}/extra-validators.d.ts +0 -0
  30. package/{dist → src/lib}/flow.interface.d.ts +0 -0
  31. package/{dist → src/lib}/flow.interface.js +0 -0
  32. package/{dist → src/lib}/index.d.ts +0 -0
  33. package/{dist → src/lib}/index.js +0 -0
  34. package/{dist → src/lib}/nats.d.ts +1 -1
  35. /package/{dist → src/lib}/rpc_server.py +0 -0
  36. /package/{dist → src/lib}/unit-decorators.d.ts +0 -0
  37. /package/{dist → src/lib}/unit-decorators.js +0 -0
  38. /package/{dist → src/lib}/unit-utils.d.ts +0 -0
  39. /package/{dist → src/lib}/units.d.ts +0 -0
  40. /package/{dist → src/lib}/utils.d.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hahnpro/flow-sdk",
3
- "version": "9.6.5",
3
+ "version": "2025.2.0-beta.2",
4
4
  "description": "SDK for building Flow Modules",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -11,30 +11,23 @@
11
11
  "type": "git",
12
12
  "url": "git@github.com:hahnprojects/flow.git"
13
13
  },
14
- "directories": {
15
- "lib": "lib",
16
- "test": "test"
17
- },
18
- "files": [
19
- "dist"
20
- ],
21
- "main": "./dist/index.js",
22
- "types": "./dist/index.d.ts",
14
+ "main": "./src/index.js",
15
+ "types": "./src/index.d.ts",
23
16
  "publishConfig": {
24
17
  "access": "public"
25
18
  },
26
19
  "dependencies": {
27
- "@hahnpro/hpc-api": "2025.5.1",
20
+ "@hahnpro/hpc-api": "2025.6.0",
28
21
  "@nats-io/jetstream": "3.2.0",
29
22
  "@nats-io/nats-core": "3.2.0",
30
23
  "@nats-io/transport-node": "3.2.0",
31
24
  "amqp-connection-manager": "4.1.15",
32
25
  "amqplib": "0.10.9",
33
26
  "class-transformer": "0.5.1",
34
- "class-validator": "~0.14.2",
27
+ "class-validator": "0.14.2",
35
28
  "cloudevents": "10.0.0",
36
29
  "lodash": "4.17.21",
37
- "object-sizeof": "~2.6.5",
30
+ "object-sizeof": "2.6.5",
38
31
  "python-shell": "5.0.0",
39
32
  "reflect-metadata": "0.2.2",
40
33
  "rxjs": "7.8.2",
@@ -59,10 +52,5 @@
59
52
  "engines": {
60
53
  "node": ">=v22"
61
54
  },
62
- "scripts": {
63
- "build": "../../node_modules/.bin/tsc -p tsconfig.lib.json",
64
- "build:nocomments": "../../node_modules/.bin/tsc -p tsconfig.nocomments.json",
65
- "postbuild": "../../node_modules/.bin/copyfiles -u 1 lib/*.py dist",
66
- "lint": "eslint '*/**/*.{js,ts}'"
67
- }
55
+ "type": "commonjs"
68
56
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from '@hahnpro/hpc-api';
2
+ export * from './lib/flow.interface';
3
+ export * from './lib/utils';
4
+ export * from './lib/FlowApplication';
5
+ export * from './lib/FlowElement';
6
+ export * from './lib/FlowEvent';
7
+ export * from './lib/FlowLogger';
8
+ export { FlowModule } from './lib/FlowModule';
9
+ export * from './lib/TestModule';
10
+ export * from './lib/unit-decorators';
11
+ export * from './lib/ContextManager';
12
+ export { IncompatableWith } from './lib/extra-validators';
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IncompatableWith = exports.FlowModule = void 0;
4
+ const tslib_1 = require("tslib");
5
+ tslib_1.__exportStar(require("@hahnpro/hpc-api"), exports);
6
+ tslib_1.__exportStar(require("./lib/flow.interface"), exports);
7
+ tslib_1.__exportStar(require("./lib/utils"), exports);
8
+ tslib_1.__exportStar(require("./lib/FlowApplication"), exports);
9
+ tslib_1.__exportStar(require("./lib/FlowElement"), exports);
10
+ tslib_1.__exportStar(require("./lib/FlowEvent"), exports);
11
+ tslib_1.__exportStar(require("./lib/FlowLogger"), exports);
12
+ var FlowModule_1 = require("./lib/FlowModule");
13
+ Object.defineProperty(exports, "FlowModule", { enumerable: true, get: function () { return FlowModule_1.FlowModule; } });
14
+ tslib_1.__exportStar(require("./lib/TestModule"), exports);
15
+ tslib_1.__exportStar(require("./lib/unit-decorators"), exports);
16
+ tslib_1.__exportStar(require("./lib/ContextManager"), exports);
17
+ var extra_validators_1 = require("./lib/extra-validators");
18
+ Object.defineProperty(exports, "IncompatableWith", { enumerable: true, get: function () { return extra_validators_1.IncompatableWith; } });
@@ -5,20 +5,42 @@ exports.flowInterpolate = flowInterpolate;
5
5
  const tslib_1 = require("tslib");
6
6
  const lodash_1 = require("lodash");
7
7
  const string_interp_1 = tslib_1.__importDefault(require("string-interp"));
8
+ /**
9
+ * Class representing a context manager for handling properties.
10
+ */
8
11
  class ContextManager {
12
+ /**
13
+ * Constructor of the ContextManager.
14
+ * @param {Logger} logger - The logger instance for logging messages.
15
+ * @param {Record<string, any>} [flowProperties={}] - Initial properties to set.
16
+ */
9
17
  constructor(logger, flowProperties = {}) {
10
18
  this.logger = logger;
11
19
  this.properties = { flow: flowProperties };
12
20
  }
21
+ /**
22
+ * Init or overwrite all properties.
23
+ * @param properties
24
+ */
13
25
  overwriteAllProperties(properties = {}) {
14
26
  this.properties = { flow: properties };
15
27
  }
16
28
  updateFlowProperties(properties = {}) {
17
29
  this.properties.flow = properties;
18
30
  }
31
+ /**
32
+ * Get a copy of the current properties.
33
+ * @returns {Record<string, any>} A copy of the properties.
34
+ */
19
35
  getProperties() {
20
36
  return { ...this.properties };
21
37
  }
38
+ /**
39
+ * Set a property.
40
+ * A property key starting with "flow." is reserved for the properties set by in UI and so it is not allowed to be set.
41
+ * @param {string} keyOrPath - The key or the path of the property.
42
+ * @param {any} value - The value of the property.
43
+ */
22
44
  set(keyOrPath, value) {
23
45
  if (keyOrPath.startsWith('flow.')) {
24
46
  this.logger.error(`Set property of "${keyOrPath}" is not allowed, because it starts with "flow.", so it is reserved for the properties set by in UI.`);
@@ -27,6 +49,11 @@ class ContextManager {
27
49
  (0, lodash_1.set)(this.properties, keyOrPath, value);
28
50
  }
29
51
  }
52
+ /**
53
+ * Get a property value by key.
54
+ * @param {string} keyOrPath - The key or the path of the property.
55
+ * @returns {any} The value of the property.
56
+ */
30
57
  get(keyOrPath) {
31
58
  return (0, lodash_1.get)(this.properties, keyOrPath, undefined);
32
59
  }
@@ -52,6 +79,7 @@ function flowInterpolate(value, properties) {
52
79
  return value;
53
80
  }
54
81
  else if (value != null && typeof value === 'string' && value.startsWith('${')) {
82
+ // get ${...} blocks and replace the ones that start with flow. in a new string
55
83
  const blockRegEx = /\$\{\s*(\S+)\s*}/g;
56
84
  let newValue = value;
57
85
  let m;
@@ -3,22 +3,22 @@ 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 perf_hooks_1 = require("perf_hooks");
6
7
  const hpc_api_1 = require("@hahnpro/hpc-api");
7
8
  const jetstream_1 = require("@nats-io/jetstream");
8
9
  const cloudevents_1 = require("cloudevents");
9
10
  const lodash_1 = require("lodash");
10
11
  const object_sizeof_1 = tslib_1.__importDefault(require("object-sizeof"));
11
- const perf_hooks_1 = require("perf_hooks");
12
12
  const rxjs_1 = require("rxjs");
13
13
  const operators_1 = require("rxjs/operators");
14
14
  const amqp_1 = require("./amqp");
15
+ const ContextManager_1 = require("./ContextManager");
15
16
  const flow_interface_1 = require("./flow.interface");
16
17
  const FlowLogger_1 = require("./FlowLogger");
18
+ const nats_1 = require("./nats");
17
19
  const RpcClient_1 = require("./RpcClient");
18
20
  const utils_1 = require("./utils");
19
- const nats_1 = require("./nats");
20
- const ContextManager_1 = require("./ContextManager");
21
- const MAX_EVENT_SIZE_BYTES = +process.env.MAX_EVENT_SIZE_BYTES || 512 * 1024;
21
+ const MAX_EVENT_SIZE_BYTES = +process.env.MAX_EVENT_SIZE_BYTES || 512 * 1024; // 512kb
22
22
  const WARN_EVENT_PROCESSING_SEC = +process.env.WARN_EVENT_PROCESSING_SEC || 60;
23
23
  const WARN_EVENT_QUEUE_SIZE = +process.env.WARN_EVENT_QUEUE_SIZE || 100;
24
24
  class FlowApplication {
@@ -88,6 +88,7 @@ class FlowApplication {
88
88
  }
89
89
  };
90
90
  this.emitPartial = (completeEvent, partialEvent) => {
91
+ // send complete event, log only partial event
91
92
  try {
92
93
  if (completeEvent && this.outputStreamMap.has(completeEvent.getStreamId())) {
93
94
  this.getOutputStream(completeEvent.getStreamId()).next(completeEvent);
@@ -167,9 +168,16 @@ class FlowApplication {
167
168
  }
168
169
  }
169
170
  else if (cloudEvent.subject.endsWith('.destroy')) {
171
+ // TODO war com.flowstudio.deployment.destroy in RabbitMq: wo wird das jetzt wieder gesendet?
170
172
  this.destroy();
171
173
  }
172
174
  };
175
+ /**
176
+ * Publish a flow event to the amqp flowlogs exchange.
177
+ * If the event size exceeds the limit it will be truncated
178
+ *
179
+ * TODO warum darf hier nicht false zurückgegeben werden? -> erzeugt loop
180
+ */
173
181
  this.publishNatsEventFlowlogs = async (event) => {
174
182
  if (!this._natsConnection || this._natsConnection.isClosed()) {
175
183
  return true;
@@ -190,6 +198,7 @@ class FlowApplication {
190
198
  }
191
199
  catch (err) {
192
200
  this.logger.error(err);
201
+ return false;
193
202
  }
194
203
  };
195
204
  if (baseLoggerOrConfig && !baseLoggerOrConfig.log) {
@@ -262,7 +271,7 @@ class FlowApplication {
262
271
  }
263
272
  catch (error) {
264
273
  this.logger.error('Message is not a valid CloudEvent and will be discarded');
265
- msg.ack();
274
+ msg.ack(); // Acknowledge the message to remove it from the queue
266
275
  continue;
267
276
  }
268
277
  await this.onMessage(event);
@@ -283,6 +292,7 @@ class FlowApplication {
283
292
  try {
284
293
  if (!this.skipApi && !(this._api instanceof hpc_api_1.MockAPI)) {
285
294
  const { owner } = this.context;
295
+ // only create real API if it should not be skipped and is not already a mock
286
296
  this._api = new hpc_api_1.API(this.apiClient, { activeOrg: owner?.id });
287
297
  }
288
298
  }
@@ -307,15 +317,16 @@ class FlowApplication {
307
317
  ...nats_1.defaultConsumerConfig,
308
318
  name: `flow-deployment-${this.context.deploymentId}`,
309
319
  filter_subject: `${nats_1.natsFlowsPrefixFlowDeployment}.${this.context.deploymentId}.*`,
310
- inactive_threshold: 10 * 60 * 1_000_000_000,
320
+ inactive_threshold: 10 * 60 * 1000000000, // 10 mins
311
321
  deliver_policy: jetstream_1.DeliverPolicy.New,
312
322
  };
313
323
  const consumer = await (0, nats_1.getOrCreateConsumer)(this.logger, this._natsConnection, nats_1.FLOWS_STREAM_NAME, consumerOptions.name, consumerOptions);
324
+ // Recreate consumers on reconnects: NO AWAIT, listen asynchronously
314
325
  const handleNatsStatus = async () => {
315
326
  try {
316
327
  this.logger.debug('ConsumerService: Reconnected to Nats and re-creating non-durable consumers');
317
328
  await (0, nats_1.getOrCreateConsumer)(this.logger, this._natsConnection, nats_1.FLOWS_STREAM_NAME, consumerOptions.name, consumerOptions);
318
- this.consumeNatsMessagesOfConsumer(consumer, { expires: 10 * 1_000_000_000 });
329
+ this.consumeNatsMessagesOfConsumer(consumer, { expires: 10 * 1000000000 /* 10 seconds */ });
319
330
  }
320
331
  catch (e) {
321
332
  this.logger.error('NATS Status-AsyncIterator is not available, cannot listen. Due to error:');
@@ -324,7 +335,8 @@ class FlowApplication {
324
335
  }
325
336
  };
326
337
  (0, nats_1.natsEventListener)(this._natsConnection, this.logger, handleNatsStatus);
327
- this.consumeNatsMessagesOfConsumer(consumer, { expires: 10 * 1_000_000_000 });
338
+ // NO AWAIT, listen for messages of the consumer asynchronously
339
+ this.consumeNatsMessagesOfConsumer(consumer, { expires: 10 * 1000000000 /* 10 seconds */ });
328
340
  }
329
341
  catch (e) {
330
342
  await logErrorAndExit(`Could not set up consumer for deployment messages exchanges: ${e}`);
@@ -334,7 +346,7 @@ class FlowApplication {
334
346
  json: true,
335
347
  setup: async (channel) => {
336
348
  try {
337
- await channel.assertExchange('flow', 'direct', { durable: true });
349
+ await channel.assertExchange('flow', 'direct', { durable: true }); // TODO wieso weshalb warum: wo wird das gebraucht?
338
350
  }
339
351
  catch (e) {
340
352
  await logErrorAndExit(`Could not assert exchanges: ${e}`);
@@ -364,7 +376,9 @@ class FlowApplication {
364
376
  const { id, name, properties, module, functionFqn } = element;
365
377
  try {
366
378
  const context = { ...this.context, id, name, logger: this.baseLogger, app: this };
367
- this.elements[id] = new this.declarations[`${module}.${functionFqn}`](context, this.contextManager.replaceAllPlaceholderProperties(properties));
379
+ this.elements[id] = new this.declarations[`${module}.${functionFqn}`](context,
380
+ // run recursively through all properties and interpolate them / replace them with their explicit value
381
+ this.contextManager.replaceAllPlaceholderProperties(properties));
368
382
  this.elements[id].setPropertiesWithPlaceholders((0, lodash_1.cloneDeep)(properties));
369
383
  }
370
384
  catch (err) {
@@ -430,6 +444,11 @@ class FlowApplication {
430
444
  this.initialized = true;
431
445
  this.logger.log('Flow Deployment is running');
432
446
  }
447
+ /**
448
+ * Calls onDestroy lifecycle method on all flow elements,
449
+ * closes amqp connection after allowing logs to be processed and published
450
+ * then exits process
451
+ */
433
452
  async destroy(exitCode = 0) {
434
453
  try {
435
454
  try {
@@ -443,10 +462,12 @@ class FlowApplication {
443
462
  catch (err) {
444
463
  this.logger.error(err);
445
464
  }
465
+ // allow time for logs to be processed
446
466
  await (0, utils_1.delay)(250);
447
467
  if (this.amqpConnection) {
448
468
  await this.amqpConnection.close();
449
469
  }
470
+ // Close all output streams
450
471
  for (const [id, stream] of this.outputStreamMap.entries()) {
451
472
  try {
452
473
  stream?.complete();
@@ -455,6 +476,7 @@ class FlowApplication {
455
476
  this.logger.error(`Error completing output stream ${id}: ${err.message}`);
456
477
  }
457
478
  }
479
+ // Nats: Delete consumer for flow deployment, stop message listening and close connection
458
480
  try {
459
481
  await this.natsMessageIterator?.close();
460
482
  await this._natsConnection?.drain();
@@ -475,11 +497,13 @@ class FlowApplication {
475
497
  catch (err) {
476
498
  this.logger.error(err);
477
499
  }
500
+ // remove process listeners
478
501
  process.removeAllListeners('SIGTERM');
479
502
  process.removeAllListeners('uncaughtException');
480
503
  process.removeAllListeners('unhandledRejection');
481
504
  }
482
505
  catch (err) {
506
+ /* eslint-disable-next-line no-console */
483
507
  console.error(err);
484
508
  }
485
509
  finally {
@@ -488,6 +512,10 @@ class FlowApplication {
488
512
  }
489
513
  }
490
514
  }
515
+ /**
516
+ * Returns rxjs subject for the specified stream id.
517
+ * A new subject will be created if one doesn't exist yet.
518
+ */
491
519
  getOutputStream(id) {
492
520
  const stream = this.outputStreamMap.get(id);
493
521
  if (!stream) {
@@ -40,9 +40,16 @@ class FlowElement {
40
40
  this.setProperties(properties);
41
41
  }
42
42
  }
43
+ /**
44
+ * Sets the placeholder properties for this flow element
45
+ * @param propertiesWithPlaceholders
46
+ */
43
47
  setPropertiesWithPlaceholders(propertiesWithPlaceholders) {
44
48
  this.propertiesWithPlaceholders = propertiesWithPlaceholders;
45
49
  }
50
+ /**
51
+ * Returns the placeholder properties for this flow element
52
+ */
46
53
  getPropertiesWithPlaceholders() {
47
54
  return this.propertiesWithPlaceholders;
48
55
  }
@@ -52,12 +59,18 @@ class FlowElement {
52
59
  get natsConnection() {
53
60
  return this.app?.natsConnection;
54
61
  }
62
+ /**
63
+ * Replace all placeholder properties with their explicit updated value and set them as the properties of the element
64
+ */
55
65
  replacePlaceholderAndSetProperties() {
56
66
  const placeholderProperties = this.propertiesWithPlaceholders;
57
67
  if (this.propertiesWithPlaceholders) {
58
68
  this.setProperties(this.app.getContextManager?.()?.replaceAllPlaceholderProperties(placeholderProperties));
59
69
  }
60
70
  }
71
+ /**
72
+ * @deprecated since version 4.8.0, will be removed in 5.0.0, use emitEvent(...) instead
73
+ */
61
74
  emitOutput(data = {}, outputId = 'default', time = new Date()) {
62
75
  return this.emitEvent(data, null, outputId, time);
63
76
  }
@@ -135,6 +148,7 @@ function InputStream(id = 'default', options) {
135
148
  if (!this.stopPropagateStream.has(id)) {
136
149
  this.stopPropagateStream.set(id, options?.stopPropagation ?? false);
137
150
  }
151
+ // add input stream to data to later determine if data should be propagated
138
152
  return method.call(this, new FlowEvent_1.FlowEvent({ id: event.getMetadata().elementId, ...event.getMetadata(), inputStreamId: id }, event.getData(), event.getType(), new Date(event.getTime())));
139
153
  };
140
154
  };
@@ -144,6 +158,7 @@ function FlowFunction(fqn) {
144
158
  if (!fqnRegExp.test(fqn)) {
145
159
  throw new Error(`Flow Function FQN (${fqn}) is not valid`);
146
160
  }
161
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
147
162
  return (target) => {
148
163
  Reflect.defineMetadata('element:functionFqn', fqn, target);
149
164
  target.prototype.functionFqn = fqn;
@@ -12,6 +12,7 @@ class FlowEvent {
12
12
  event.data = JSON.parse(event.data);
13
13
  }
14
14
  catch (err) {
15
+ /* ignore error */
15
16
  }
16
17
  }
17
18
  return event;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlowLogger = exports.STACK_TRACE = exports.defaultLogger = void 0;
4
4
  const FlowEvent_1 = require("./FlowEvent");
5
+ /* eslint-disable no-console */
5
6
  exports.defaultLogger = {
6
7
  debug: (msg, metadata) => console.debug(msg),
7
8
  error: (msg, metadata) => console.error(msg),
@@ -9,6 +10,7 @@ exports.defaultLogger = {
9
10
  warn: (msg, metadata) => console.warn(msg),
10
11
  verbose: (msg, metadata) => console.log(msg, metadata),
11
12
  };
13
+ /* eslint-enable no-console */
12
14
  var STACK_TRACE;
13
15
  (function (STACK_TRACE) {
14
16
  STACK_TRACE["FULL"] = "full";
@@ -16,6 +18,7 @@ var STACK_TRACE;
16
18
  })(STACK_TRACE || (exports.STACK_TRACE = STACK_TRACE = {}));
17
19
  class FlowLogger {
18
20
  static getStackTrace(stacktrace = STACK_TRACE.FULL) {
21
+ // get stacktrace without extra dependencies
19
22
  let stack;
20
23
  try {
21
24
  throw new Error('');
@@ -23,6 +26,7 @@ class FlowLogger {
23
26
  catch (error) {
24
27
  stack = error.stack || '';
25
28
  }
29
+ // cleanup stacktrace and remove calls within this file
26
30
  stack = stack
27
31
  .split('\n')
28
32
  .map((line) => line.trim())
@@ -45,6 +49,18 @@ class FlowLogger {
45
49
  this.warn = (message, options) => this.publish(message, 'warn', options);
46
50
  this.verbose = (message, options) => this.publish(message, 'verbose', options);
47
51
  }
52
+ /**
53
+ * Parses a message into a FlowLog object, including optional stack trace information.
54
+ *
55
+ * @details Requirements for the output format of messages:
56
+ * - Necessary for consistent logging and event publishing, because the OpenSearch index expects a specific structure: flat_object.
57
+ * - The current UI expects a `message` property to be present, so we ensure it is always set.
58
+ *
59
+ * @param {any} message - The message to be logged. Can be a string, an object with a `message` property, or any other type.
60
+ * @param {string} level - The log level (e.g., 'error', 'debug', 'warn', 'verbose').
61
+ * @param {LoggerOptions} options - Additional options for logging, such as whether to include a stack trace.
62
+ * @returns {FlowLog} - An object containing the parsed log message and optional stack trace.
63
+ */
48
64
  parseMessageToFlowLog(message, level, options) {
49
65
  let flowLogMessage;
50
66
  if (!message) {
@@ -6,6 +6,7 @@ function FlowModule(metadata) {
6
6
  if (!validateNameRegExp.test(metadata.name)) {
7
7
  throw new Error(`Flow Module name (${metadata.name}) is not valid. Name must be all lowercase and not contain any special characters except for hyphens. Can optionally start with a scope "@scopename/"`);
8
8
  }
9
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
9
10
  return (target) => {
10
11
  Reflect.defineMetadata('module:name', metadata.name, target);
11
12
  Reflect.defineMetadata('module:declarations', metadata.declarations, target);
@@ -37,12 +37,16 @@ class RpcClient {
37
37
  }
38
38
  else {
39
39
  const message = `received unexpected response correlationID: ${msg.properties.correlationId}`;
40
+ /* eslint-disable-next-line no-console */
40
41
  console.warn(message);
41
42
  }
42
43
  };
43
44
  this.callFunction = (routingKey, functionName, ...args) => {
45
+ // in case remote returns error add this to the trace
44
46
  const stack = new Error('test').stack;
45
47
  return new Promise((resolve, reject) => {
48
+ // save to correlationId-> resolve/reject map
49
+ // on return resolve or reject promise
46
50
  if (MAX_MSG_SIZE || WARN_MSG_SIZE) {
47
51
  const messageSize = (0, object_sizeof_1.default)(args);
48
52
  if (messageSize > MAX_MSG_SIZE) {
@@ -4,7 +4,7 @@ exports.createAmqpConnection = createAmqpConnection;
4
4
  const amqp_connection_manager_1 = require("amqp-connection-manager");
5
5
  function createAmqpConnection(config) {
6
6
  if (!config) {
7
- return;
7
+ return null;
8
8
  }
9
9
  const { protocol = process.env.RABBIT_PROTOCOL || 'amqp', hostname = process.env.RABBIT_HOST || 'localhost', port = +process.env.RABBIT_PORT || 5672, user = process.env.RABBIT_USER || 'guest', password = process.env.RABBIT_PASSWORD || 'guest', vhost = process.env.RABBIT_VHOST || '', } = config;
10
10
  const uri = `${protocol}://${user}:${password}@${hostname}:${port}${vhost ? '/' + vhost : ''}`;
@@ -27,6 +27,7 @@ let IsNotSiblingOfConstraint = class IsNotSiblingOfConstraint {
27
27
  IsNotSiblingOfConstraint = tslib_1.__decorate([
28
28
  (0, class_validator_1.ValidatorConstraint)({ async: false })
29
29
  ], IsNotSiblingOfConstraint);
30
+ // Create Decorator for the constraint that was just created
30
31
  function IsNotSiblingOf(props, validationOptions) {
31
32
  return (object, propertyName) => {
32
33
  (0, class_validator_1.registerDecorator)({
@@ -38,6 +39,7 @@ function IsNotSiblingOf(props, validationOptions) {
38
39
  });
39
40
  };
40
41
  }
42
+ // Helper function for determining if a prop should be validated
41
43
  function incompatibleSiblingsNotPresent(incompatibleSiblings) {
42
44
  return (o, v) => Boolean((0, class_validator_1.isDefined)(v) || incompatibleSiblings.every((prop) => !(0, class_validator_1.isDefined)(o[prop])));
43
45
  }
@@ -5,14 +5,15 @@ exports.getOrCreateConsumer = getOrCreateConsumer;
5
5
  exports.natsEventListener = natsEventListener;
6
6
  exports.publishNatsEvent = publishNatsEvent;
7
7
  exports.createNatsConnection = createNatsConnection;
8
+ const jetstream_1 = require("@nats-io/jetstream");
8
9
  const transport_node_1 = require("@nats-io/transport-node");
9
10
  const cloudevents_1 = require("cloudevents");
10
- const jetstream_1 = require("@nats-io/jetstream");
11
11
  const lodash_1 = require("lodash");
12
12
  exports.natsFlowsPrefixFlowDeployment = `fs.flowdeployment`;
13
+ // https://docs.nats.io/nats-concepts/jetstream/consumers#configuration
13
14
  exports.defaultConsumerConfig = {
14
15
  ack_policy: jetstream_1.AckPolicy.Explicit,
15
- ack_wait: 30_000_000_000,
16
+ ack_wait: 30000000000, // 30 seconds
16
17
  deliver_policy: jetstream_1.DeliverPolicy.All,
17
18
  max_ack_pending: 1000,
18
19
  max_deliver: -1,
@@ -59,6 +60,7 @@ async function natsEventListener(nc, logger, reconnectHandler) {
59
60
  return;
60
61
  }
61
62
  for await (const status of statusAsyncIterator) {
63
+ // Handle reconnect: event is triggered when the NATS client reconnected to the server
62
64
  if (status.type === 'reconnect') {
63
65
  reconnectHandler();
64
66
  }
@@ -67,7 +69,7 @@ async function natsEventListener(nc, logger, reconnectHandler) {
67
69
  async function publishNatsEvent(logger, nc, event, subject) {
68
70
  if (!nc || nc.isClosed()) {
69
71
  logger.error('NATS connection is not available, cannot publish event');
70
- return;
72
+ return null;
71
73
  }
72
74
  const cloudEvent = new cloudevents_1.CloudEvent({ datacontenttype: 'application/json', ...event });
73
75
  cloudEvent.validate();
@@ -79,19 +81,23 @@ async function publishNatsEvent(logger, nc, event, subject) {
79
81
  }
80
82
  else {
81
83
  logger.error(`Could not publish nats event, because jetstream is unavailable / undefined`);
84
+ return null;
82
85
  }
83
86
  }
84
87
  async function createNatsConnection(config) {
85
88
  const servers = config?.servers ?? process.env.NATS_SERVERS?.split(',') ?? [];
86
89
  const reconnect = config?.reconnect ?? (process.env.NATS_RECONNECT ?? 'true') === 'true';
90
+ // Default maxReconnectAttempts is 10
87
91
  let maxReconnectAttempts = config?.maxReconnectAttempts ?? parseInt(process.env.NATS_MAX_RECONNECT_ATTEMPTS ?? '-1', 10);
88
92
  if (isNaN(maxReconnectAttempts)) {
89
93
  maxReconnectAttempts = 10;
90
94
  }
95
+ // Default reconnectTimeWait is 2000ms
91
96
  let reconnectTimeWait = config?.reconnectTimeWait ?? parseInt(process.env.NATS_RECONNECT_TIME_WAIT ?? '2000', 10);
92
97
  if (isNaN(reconnectTimeWait)) {
93
98
  reconnectTimeWait = 2000;
94
99
  }
100
+ // Default timeout is 2000ms
95
101
  let timeout = config?.timeout ?? parseInt(process.env.NATS_TIMEOUT ?? '2000', 10);
96
102
  if (isNaN(timeout)) {
97
103
  timeout = 2000;
@@ -99,8 +105,8 @@ async function createNatsConnection(config) {
99
105
  const options = {
100
106
  servers,
101
107
  reconnect,
102
- maxReconnectAttempts,
103
- reconnectTimeWait,
108
+ maxReconnectAttempts, // <-- maxReconnectAttempts: -1 means infinite reconnect attempts
109
+ reconnectTimeWait, // <-- reconnectTimeWait: -1 means no wait time between reconnect attempts
104
110
  timeout,
105
111
  user: config?.user ?? process.env.NATS_USER,
106
112
  pass: config.pass ?? process.env.NATS_PASSWORD,
@@ -80,6 +80,7 @@ function verifyIndices(indices, unit) {
80
80
  .map((arr) => arr.filter((v) => v.index !== -1))
81
81
  .reduce((previousValue, currentValue) => {
82
82
  if (currentValue.length > 1) {
83
+ // copy existing arrays *length* times
83
84
  for (let j = 0; j < currentValue.length - 1; j++) {
84
85
  previousValue.forEach((value) => previousValue.push([...value]));
85
86
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.units = exports.dimensionToUnitMap = void 0;
4
4
  exports.dimensionToUnitMap = {
5
+ // SI dimensions
5
6
  T: 'time',
6
7
  L: 'length',
7
8
  M: 'mass',
@@ -9,6 +10,7 @@ exports.dimensionToUnitMap = {
9
10
  Θ: 'thermodynamicTemperature',
10
11
  N: 'amountOfSubstance',
11
12
  J: 'luminousIntensity',
13
+ // pseudo dimensions
12
14
  U: 'voltage',
13
15
  F: 'force',
14
16
  E: 'energy',