@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.
- package/CHANGELOG.md +904 -0
- package/package.json +7 -19
- package/src/index.d.ts +12 -0
- package/src/index.js +18 -0
- package/{dist → src/lib}/ContextManager.js +28 -0
- package/{dist → src/lib}/FlowApplication.js +38 -10
- package/{dist → src/lib}/FlowElement.js +15 -0
- package/{dist → src/lib}/FlowEvent.js +1 -0
- package/{dist → src/lib}/FlowLogger.js +16 -0
- package/{dist → src/lib}/FlowModule.js +1 -0
- package/{dist → src/lib}/RpcClient.js +4 -0
- package/{dist → src/lib}/amqp.js +1 -1
- package/{dist → src/lib}/extra-validators.js +2 -0
- package/{dist → src/lib}/nats.js +11 -5
- package/{dist → src/lib}/unit-utils.js +1 -0
- package/{dist → src/lib}/units.js +2 -0
- package/{dist → src/lib}/utils.js +44 -3
- package/LICENSE +0 -21
- package/{dist → src/lib}/ContextManager.d.ts +0 -0
- package/{dist → src/lib}/FlowApplication.d.ts +1 -1
- package/{dist → src/lib}/FlowElement.d.ts +1 -1
- package/{dist → src/lib}/FlowEvent.d.ts +0 -0
- package/{dist → src/lib}/FlowLogger.d.ts +0 -0
- package/{dist → src/lib}/FlowModule.d.ts +0 -0
- package/{dist → src/lib}/RpcClient.d.ts +0 -0
- package/{dist → src/lib}/TestModule.d.ts +0 -0
- package/{dist → src/lib}/TestModule.js +0 -0
- package/{dist → src/lib}/amqp.d.ts +0 -0
- package/{dist → src/lib}/extra-validators.d.ts +0 -0
- package/{dist → src/lib}/flow.interface.d.ts +0 -0
- package/{dist → src/lib}/flow.interface.js +0 -0
- package/{dist → src/lib}/index.d.ts +0 -0
- package/{dist → src/lib}/index.js +0 -0
- package/{dist → src/lib}/nats.d.ts +1 -1
- /package/{dist → src/lib}/rpc_server.py +0 -0
- /package/{dist → src/lib}/unit-decorators.d.ts +0 -0
- /package/{dist → src/lib}/unit-decorators.js +0 -0
- /package/{dist → src/lib}/unit-utils.d.ts +0 -0
- /package/{dist → src/lib}/units.d.ts +0 -0
- /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": "
|
|
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
|
-
"
|
|
15
|
-
|
|
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.
|
|
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": "
|
|
27
|
+
"class-validator": "0.14.2",
|
|
35
28
|
"cloudevents": "10.0.0",
|
|
36
29
|
"lodash": "4.17.21",
|
|
37
|
-
"object-sizeof": "
|
|
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
|
-
"
|
|
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
|
|
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 *
|
|
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 *
|
|
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
|
-
|
|
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,
|
|
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;
|
|
@@ -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) {
|
package/{dist → src/lib}/amqp.js
RENAMED
|
@@ -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
|
}
|
package/{dist → src/lib}/nats.js
RENAMED
|
@@ -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:
|
|
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',
|