@hahnpro/flow-sdk 9.4.1 → 9.6.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.js +36 -22
- package/dist/FlowLogger.d.ts +18 -0
- package/dist/FlowLogger.js +48 -16
- package/dist/nats.js +1 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.js +14 -0
- package/package.json +1 -1
package/dist/FlowApplication.js
CHANGED
|
@@ -32,9 +32,6 @@ class FlowApplication {
|
|
|
32
32
|
this.outputQueueMetrics = new Map();
|
|
33
33
|
this.performanceMap = new Map();
|
|
34
34
|
this.publishLifecycleEvent = async (element, flowEventId, eventType, data = {}) => {
|
|
35
|
-
if (!this.amqpChannel) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
35
|
try {
|
|
39
36
|
const { flowId, deploymentId, id: elementId, functionFqn, inputStreamId } = element.getMetadata();
|
|
40
37
|
const natsEvent = {
|
|
@@ -47,7 +44,7 @@ class FlowApplication {
|
|
|
47
44
|
...data,
|
|
48
45
|
},
|
|
49
46
|
};
|
|
50
|
-
await (0, nats_1.publishNatsEvent)(this.logger, this.
|
|
47
|
+
await (0, nats_1.publishNatsEvent)(this.logger, this._natsConnection, natsEvent, `${nats_1.natsFlowsPrefixFlowDeployment}.flowlifecycle.${deploymentId}`);
|
|
51
48
|
}
|
|
52
49
|
catch (err) {
|
|
53
50
|
this.logger.error(err);
|
|
@@ -141,7 +138,7 @@ class FlowApplication {
|
|
|
141
138
|
status: 'updated',
|
|
142
139
|
},
|
|
143
140
|
};
|
|
144
|
-
await (0, nats_1.publishNatsEvent)(this.logger, this.
|
|
141
|
+
await (0, nats_1.publishNatsEvent)(this.logger, this._natsConnection, natsEvent);
|
|
145
142
|
}
|
|
146
143
|
catch (err) {
|
|
147
144
|
this.logger.error(err);
|
|
@@ -154,7 +151,7 @@ class FlowApplication {
|
|
|
154
151
|
status: 'updating failed',
|
|
155
152
|
},
|
|
156
153
|
};
|
|
157
|
-
await (0, nats_1.publishNatsEvent)(this.logger, this.
|
|
154
|
+
await (0, nats_1.publishNatsEvent)(this.logger, this._natsConnection, natsEvent);
|
|
158
155
|
}
|
|
159
156
|
}
|
|
160
157
|
else if (cloudEvent.subject.endsWith('.message')) {
|
|
@@ -174,8 +171,8 @@ class FlowApplication {
|
|
|
174
171
|
}
|
|
175
172
|
};
|
|
176
173
|
this.publishNatsEventFlowlogs = async (event) => {
|
|
177
|
-
if (!this.
|
|
178
|
-
return;
|
|
174
|
+
if (!this._natsConnection || this._natsConnection.isClosed()) {
|
|
175
|
+
return true;
|
|
179
176
|
}
|
|
180
177
|
try {
|
|
181
178
|
const formatedEvent = event.format();
|
|
@@ -188,7 +185,7 @@ class FlowApplication {
|
|
|
188
185
|
subject: `${this.context.deploymentId}`,
|
|
189
186
|
data: formatedEvent,
|
|
190
187
|
};
|
|
191
|
-
await (0, nats_1.publishNatsEvent)(this.logger, this.
|
|
188
|
+
await (0, nats_1.publishNatsEvent)(this.logger, this._natsConnection, natsEvent);
|
|
192
189
|
return true;
|
|
193
190
|
}
|
|
194
191
|
catch (err) {
|
|
@@ -315,6 +312,7 @@ class FlowApplication {
|
|
|
315
312
|
name: `flow-deployment-${this.context.deploymentId}`,
|
|
316
313
|
filter_subject: `${nats_1.natsFlowsPrefixFlowDeployment}.${this.context.deploymentId}.*`,
|
|
317
314
|
inactive_threshold: 10 * 60 * 1_000_000_000,
|
|
315
|
+
deliver_policy: jetstream_1.DeliverPolicy.New,
|
|
318
316
|
};
|
|
319
317
|
const consumer = await (0, nats_1.getOrCreateConsumer)(this.logger, this._natsConnection, nats_1.FLOWS_STREAM_NAME, consumerOptions.name, consumerOptions);
|
|
320
318
|
const handleNatsStatus = async () => {
|
|
@@ -453,21 +451,37 @@ class FlowApplication {
|
|
|
453
451
|
if (this.amqpConnection) {
|
|
454
452
|
await this.amqpConnection.close();
|
|
455
453
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
454
|
+
for (const [id, stream] of this.outputStreamMap.entries()) {
|
|
455
|
+
try {
|
|
456
|
+
stream?.complete();
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
this.logger.error(`Error completing output stream ${id}: ${err.message}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
await this.natsMessageIterator?.close();
|
|
464
|
+
await this._natsConnection?.drain();
|
|
465
|
+
await this._natsConnection?.close();
|
|
466
|
+
if (this._natsConnection && !this._natsConnection.isClosed()) {
|
|
467
|
+
await (0, jetstream_1.jetstreamManager)(this._natsConnection).then((jsm) => {
|
|
468
|
+
jsm.consumers
|
|
469
|
+
.delete(nats_1.FLOWS_STREAM_NAME, `flow-deployment-${this.context?.deploymentId}`)
|
|
470
|
+
.then(() => {
|
|
471
|
+
this.logger.debug(`Deleted consumer for flow deployment ${this.context?.deploymentId}`);
|
|
472
|
+
})
|
|
473
|
+
.catch((err) => {
|
|
474
|
+
this.logger.error(`Could not delete consumer for flow deployment ${this.context?.deploymentId}: ${err.message}`);
|
|
475
|
+
});
|
|
465
476
|
});
|
|
466
|
-
}
|
|
467
|
-
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
this.logger.error(err);
|
|
468
481
|
}
|
|
469
|
-
|
|
470
|
-
|
|
482
|
+
process.removeAllListeners('SIGTERM');
|
|
483
|
+
process.removeAllListeners('uncaughtException');
|
|
484
|
+
process.removeAllListeners('unhandledRejection');
|
|
471
485
|
}
|
|
472
486
|
catch (err) {
|
|
473
487
|
console.error(err);
|
package/dist/FlowLogger.d.ts
CHANGED
|
@@ -8,8 +8,13 @@ export interface Logger {
|
|
|
8
8
|
verbose(message: any, metadata?: any): void;
|
|
9
9
|
}
|
|
10
10
|
export declare const defaultLogger: Logger;
|
|
11
|
+
export declare enum STACK_TRACE {
|
|
12
|
+
FULL = "full",
|
|
13
|
+
ONLY_LOG_CALL = "only-log-call"
|
|
14
|
+
}
|
|
11
15
|
export interface LoggerOptions {
|
|
12
16
|
truncate: boolean;
|
|
17
|
+
stackTrace?: STACK_TRACE;
|
|
13
18
|
}
|
|
14
19
|
export declare class FlowLogger implements Logger {
|
|
15
20
|
private readonly metadata;
|
|
@@ -22,5 +27,18 @@ export declare class FlowLogger implements Logger {
|
|
|
22
27
|
log: (message: any, options?: LoggerOptions) => void;
|
|
23
28
|
warn: (message: any, options?: LoggerOptions) => void;
|
|
24
29
|
verbose: (message: any, options?: LoggerOptions) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Parses a message into a FlowLog object, including optional stack trace information.
|
|
32
|
+
*
|
|
33
|
+
* @details Requirements for the output format of messages:
|
|
34
|
+
* - Necessary for consistent logging and event publishing, because the OpenSearch index expects a specific structure: flat_object.
|
|
35
|
+
* - The current UI expects a `message` property to be present, so we ensure it is always set.
|
|
36
|
+
*
|
|
37
|
+
* @param {any} message - The message to be logged. Can be a string, an object with a `message` property, or any other type.
|
|
38
|
+
* @param {string} level - The log level (e.g., 'error', 'debug', 'warn', 'verbose').
|
|
39
|
+
* @param {LoggerOptions} options - Additional options for logging, such as whether to include a stack trace.
|
|
40
|
+
* @returns {FlowLog} - An object containing the parsed log message and optional stack trace.
|
|
41
|
+
*/
|
|
42
|
+
private parseMessageToFlowLog;
|
|
25
43
|
private publish;
|
|
26
44
|
}
|
package/dist/FlowLogger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FlowLogger = exports.defaultLogger = void 0;
|
|
3
|
+
exports.FlowLogger = exports.STACK_TRACE = exports.defaultLogger = void 0;
|
|
4
4
|
const FlowEvent_1 = require("./FlowEvent");
|
|
5
5
|
exports.defaultLogger = {
|
|
6
6
|
debug: (msg, metadata) => console.debug(msg),
|
|
@@ -9,8 +9,13 @@ exports.defaultLogger = {
|
|
|
9
9
|
warn: (msg, metadata) => console.warn(msg),
|
|
10
10
|
verbose: (msg, metadata) => console.log(msg, metadata),
|
|
11
11
|
};
|
|
12
|
+
var STACK_TRACE;
|
|
13
|
+
(function (STACK_TRACE) {
|
|
14
|
+
STACK_TRACE["FULL"] = "full";
|
|
15
|
+
STACK_TRACE["ONLY_LOG_CALL"] = "only-log-call";
|
|
16
|
+
})(STACK_TRACE || (exports.STACK_TRACE = STACK_TRACE = {}));
|
|
12
17
|
class FlowLogger {
|
|
13
|
-
static getStackTrace() {
|
|
18
|
+
static getStackTrace(stacktrace = STACK_TRACE.FULL) {
|
|
14
19
|
let stack;
|
|
15
20
|
try {
|
|
16
21
|
throw new Error('');
|
|
@@ -21,8 +26,14 @@ class FlowLogger {
|
|
|
21
26
|
stack = stack
|
|
22
27
|
.split('\n')
|
|
23
28
|
.map((line) => line.trim())
|
|
24
|
-
.filter((value) => !value.includes('Logger'));
|
|
25
|
-
|
|
29
|
+
.filter((value) => value.includes('at ') && !value.includes('Logger'));
|
|
30
|
+
if (stacktrace === STACK_TRACE.ONLY_LOG_CALL && stack.length > 0) {
|
|
31
|
+
stack = stack[0];
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
stack = stack.splice(1).join('\n');
|
|
35
|
+
}
|
|
36
|
+
return stack;
|
|
26
37
|
}
|
|
27
38
|
constructor(metadata, logger = exports.defaultLogger, publishEvent) {
|
|
28
39
|
this.metadata = metadata;
|
|
@@ -34,28 +45,49 @@ class FlowLogger {
|
|
|
34
45
|
this.warn = (message, options) => this.publish(message, 'warn', options);
|
|
35
46
|
this.verbose = (message, options) => this.publish(message, 'verbose', options);
|
|
36
47
|
}
|
|
48
|
+
parseMessageToFlowLog(message, level, options) {
|
|
49
|
+
let flowLogMessage;
|
|
50
|
+
if (!message) {
|
|
51
|
+
flowLogMessage = 'No message provided!';
|
|
52
|
+
}
|
|
53
|
+
else if (typeof message.message === 'string') {
|
|
54
|
+
flowLogMessage = message.message;
|
|
55
|
+
}
|
|
56
|
+
else if (typeof message === 'string') {
|
|
57
|
+
flowLogMessage = message;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
try {
|
|
61
|
+
flowLogMessage = JSON.stringify(message.message ?? message);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
flowLogMessage = 'Error: Could not stringify the message.';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const flowLog = { message: flowLogMessage };
|
|
68
|
+
if (['error', 'debug', 'warn', 'verbose'].includes(level) || options?.stackTrace) {
|
|
69
|
+
flowLog.stackTrace = FlowLogger.getStackTrace(options?.stackTrace ?? STACK_TRACE.ONLY_LOG_CALL);
|
|
70
|
+
}
|
|
71
|
+
return flowLog;
|
|
72
|
+
}
|
|
37
73
|
publish(message, level, options) {
|
|
74
|
+
const flowLogData = this.parseMessageToFlowLog(message, level, options);
|
|
38
75
|
if (this.publishEvent) {
|
|
39
|
-
const
|
|
40
|
-
? message
|
|
41
|
-
: {
|
|
42
|
-
...message,
|
|
43
|
-
message: typeof message === 'string' ? message : JSON.stringify(message),
|
|
44
|
-
};
|
|
45
|
-
const event = new FlowEvent_1.FlowEvent(this.metadata, data, `flow.log.${level}`);
|
|
76
|
+
const event = new FlowEvent_1.FlowEvent(this.metadata, flowLogData, `flow.log.${level}`);
|
|
46
77
|
this.publishEvent(event);
|
|
47
78
|
}
|
|
79
|
+
const messageWithStackTrace = flowLogData.stackTrace ? `${flowLogData.message}\n${flowLogData.stackTrace}` : flowLogData.message;
|
|
48
80
|
switch (level) {
|
|
49
81
|
case 'debug':
|
|
50
|
-
return this.logger.debug(
|
|
82
|
+
return this.logger.debug(messageWithStackTrace, { ...this.metadata, ...options });
|
|
51
83
|
case 'error':
|
|
52
|
-
return this.logger.error(
|
|
84
|
+
return this.logger.error(messageWithStackTrace, { ...this.metadata, ...options });
|
|
53
85
|
case 'warn':
|
|
54
|
-
return this.logger.warn(
|
|
86
|
+
return this.logger.warn(messageWithStackTrace, { ...this.metadata, ...options });
|
|
55
87
|
case 'verbose':
|
|
56
|
-
return this.logger.verbose(
|
|
88
|
+
return this.logger.verbose(messageWithStackTrace, { ...this.metadata, ...options });
|
|
57
89
|
default:
|
|
58
|
-
this.logger.log(
|
|
90
|
+
this.logger.log(messageWithStackTrace, { ...this.metadata, ...options });
|
|
59
91
|
}
|
|
60
92
|
}
|
|
61
93
|
}
|
package/dist/nats.js
CHANGED
|
@@ -66,6 +66,7 @@ async function natsEventListener(nc, logger, reconnectHandler) {
|
|
|
66
66
|
}
|
|
67
67
|
async function publishNatsEvent(logger, nc, event, subject) {
|
|
68
68
|
if (!nc || nc.isClosed()) {
|
|
69
|
+
logger.error('NATS connection is not available, cannot publish event');
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
const cloudEvent = new cloudevents_1.CloudEvent({ datacontenttype: 'application/json', ...event });
|
package/dist/utils.d.ts
CHANGED
|
@@ -3,6 +3,45 @@ export declare function fillTemplate(value: any, ...templateVariables: any): any
|
|
|
3
3
|
export declare function getCircularReplacer(): (key: any, value: any) => any;
|
|
4
4
|
export declare function toArray(value?: string | string[]): string[];
|
|
5
5
|
export declare function delay(ms: number): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a promise that resolves after a specified delay, with support for cancellation via an AbortSignal.
|
|
8
|
+
*
|
|
9
|
+
* @param {number} ms - The delay duration in milliseconds.
|
|
10
|
+
* @param {Object} [options] - Optional configuration.
|
|
11
|
+
* @param {AbortSignal} [options.signal] - An AbortSignal to allow cancellation of the delay.
|
|
12
|
+
*
|
|
13
|
+
* @returns {Promise<void>} A promise that resolves after the specified delay or rejects if aborted.
|
|
14
|
+
*
|
|
15
|
+
* @throws {Error} If the AbortSignal is already aborted or gets aborted during the delay, the promise rejects with an "AbortError".
|
|
16
|
+
*
|
|
17
|
+
* @details Usage:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* @FlowFunction('test.task.LongRunningTask')
|
|
20
|
+
* class LongRunningTask extends FlowTask<Properties> {
|
|
21
|
+
* private readonly abortController = new AbortController();
|
|
22
|
+
*
|
|
23
|
+
* constructor(...) {...}
|
|
24
|
+
*
|
|
25
|
+
* @InputStream()
|
|
26
|
+
* public async loveMeLongTime(event) {
|
|
27
|
+
* try {
|
|
28
|
+
* await delayWithAbort(this.properties.delay, { signal: this.abortController.signal });
|
|
29
|
+
* return this.emitEvent({ foo: 'bar' }, null);
|
|
30
|
+
* } catch (err) {
|
|
31
|
+
* if (err.message === 'AbortError') {
|
|
32
|
+
* return; // Task was aborted
|
|
33
|
+
* }
|
|
34
|
+
* throw err;
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* public onDestroy = () => { this.abortController.abort(); };
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function delayWithAbort(ms: number, options?: {
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
}): Promise<void>;
|
|
6
45
|
export declare function deleteFiles(dir: string, ...filenames: string[]): Promise<void>;
|
|
7
46
|
export declare function handleApiError(error: any, logger: FlowLogger): void;
|
|
8
47
|
export declare function runPyScript(scriptPath: string, data: any): Promise<any>;
|
package/dist/utils.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.fillTemplate = fillTemplate;
|
|
|
4
4
|
exports.getCircularReplacer = getCircularReplacer;
|
|
5
5
|
exports.toArray = toArray;
|
|
6
6
|
exports.delay = delay;
|
|
7
|
+
exports.delayWithAbort = delayWithAbort;
|
|
7
8
|
exports.deleteFiles = deleteFiles;
|
|
8
9
|
exports.handleApiError = handleApiError;
|
|
9
10
|
exports.runPyScript = runPyScript;
|
|
@@ -63,6 +64,19 @@ function toArray(value = []) {
|
|
|
63
64
|
function delay(ms) {
|
|
64
65
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
66
|
}
|
|
67
|
+
function delayWithAbort(ms, options) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
if (options?.signal?.aborted) {
|
|
70
|
+
reject(new Error('AbortError'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const timeout = setTimeout(() => resolve(), ms);
|
|
74
|
+
options?.signal?.addEventListener('abort', () => {
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
reject(new Error('AbortError'));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
66
80
|
async function deleteFiles(dir, ...filenames) {
|
|
67
81
|
for (const filename of filenames) {
|
|
68
82
|
await fs_1.promises.unlink((0, path_1.join)(dir, filename)).catch((err) => {
|