@hahnpro/flow-sdk 2025.2.0-beta.1 → 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/package.json +5 -4
- package/src/{index.ts → index.d.ts} +0 -3
- package/src/index.js +18 -0
- package/src/lib/ContextManager.d.ts +40 -0
- package/src/lib/ContextManager.js +105 -0
- package/src/lib/FlowApplication.d.ts +85 -0
- package/src/lib/FlowApplication.js +528 -0
- package/src/lib/FlowElement.d.ts +67 -0
- package/src/lib/FlowElement.js +178 -0
- package/src/lib/FlowEvent.d.ts +25 -0
- package/src/lib/FlowEvent.js +72 -0
- package/src/lib/FlowLogger.d.ts +44 -0
- package/src/lib/FlowLogger.js +110 -0
- package/src/lib/FlowModule.d.ts +7 -0
- package/src/lib/FlowModule.js +14 -0
- package/src/lib/RpcClient.d.ts +13 -0
- package/src/lib/RpcClient.js +88 -0
- package/src/lib/TestModule.d.ts +2 -0
- package/src/lib/TestModule.js +27 -0
- package/src/lib/amqp.d.ts +14 -0
- package/src/lib/amqp.js +12 -0
- package/src/lib/extra-validators.d.ts +1 -0
- package/src/lib/extra-validators.js +53 -0
- package/src/lib/flow.interface.d.ts +48 -0
- package/src/lib/flow.interface.js +9 -0
- package/src/lib/{index.ts → index.d.ts} +0 -3
- package/src/lib/index.js +18 -0
- package/src/lib/nats.d.ts +12 -0
- package/src/lib/nats.js +115 -0
- package/src/lib/unit-decorators.d.ts +39 -0
- package/src/lib/unit-decorators.js +156 -0
- package/src/lib/unit-utils.d.ts +8 -0
- package/src/lib/unit-utils.js +144 -0
- package/src/lib/units.d.ts +31 -0
- package/src/lib/units.js +572 -0
- package/src/lib/utils.d.ts +51 -0
- package/src/lib/utils.js +178 -0
- package/jest.config.ts +0 -10
- package/project.json +0 -41
- package/src/lib/ContextManager.ts +0 -111
- package/src/lib/FlowApplication.ts +0 -659
- package/src/lib/FlowElement.ts +0 -220
- package/src/lib/FlowEvent.ts +0 -73
- package/src/lib/FlowLogger.ts +0 -131
- package/src/lib/FlowModule.ts +0 -18
- package/src/lib/RpcClient.ts +0 -99
- package/src/lib/TestModule.ts +0 -14
- package/src/lib/__pycache__/rpc_server.cpython-310.pyc +0 -0
- package/src/lib/amqp.ts +0 -32
- package/src/lib/extra-validators.ts +0 -62
- package/src/lib/flow.interface.ts +0 -56
- package/src/lib/nats.ts +0 -140
- package/src/lib/unit-decorators.ts +0 -156
- package/src/lib/unit-utils.ts +0 -163
- package/src/lib/units.ts +0 -587
- package/src/lib/utils.ts +0 -176
- package/test/context-manager-purpose.spec.ts +0 -248
- package/test/context-manager.spec.ts +0 -55
- package/test/context.spec.ts +0 -180
- package/test/event.spec.ts +0 -155
- package/test/extra-validators.spec.ts +0 -84
- package/test/flow-logger.spec.ts +0 -104
- package/test/flow.spec.ts +0 -508
- package/test/input-stream.decorator.spec.ts +0 -379
- package/test/long-rpc.test.py +0 -14
- package/test/long-running-rpc.spec.ts +0 -60
- package/test/message.spec.ts +0 -57
- package/test/mocks/logger.mock.ts +0 -7
- package/test/mocks/nats-connection.mock.ts +0 -135
- package/test/mocks/nats-prepare.reals-nats.ts +0 -15
- package/test/rpc.spec.ts +0 -198
- package/test/rpc.test.py +0 -45
- package/test/rx.spec.ts +0 -92
- package/test/unit-decorator.spec.ts +0 -57
- package/test/utils.spec.ts +0 -210
- package/test/validation.spec.ts +0 -174
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -22
- package/tsconfig.spec.json +0 -8
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fillTemplate = fillTemplate;
|
|
4
|
+
exports.getCircularReplacer = getCircularReplacer;
|
|
5
|
+
exports.toArray = toArray;
|
|
6
|
+
exports.delay = delay;
|
|
7
|
+
exports.delayWithAbort = delayWithAbort;
|
|
8
|
+
exports.deleteFiles = deleteFiles;
|
|
9
|
+
exports.handleApiError = handleApiError;
|
|
10
|
+
exports.runPyScript = runPyScript;
|
|
11
|
+
exports.truncate = truncate;
|
|
12
|
+
const tslib_1 = require("tslib");
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
const util_1 = require("util");
|
|
16
|
+
const axios_1 = require("axios");
|
|
17
|
+
const isPlainObject_1 = tslib_1.__importDefault(require("lodash/isPlainObject"));
|
|
18
|
+
const python_shell_1 = require("python-shell");
|
|
19
|
+
const string_interp_1 = tslib_1.__importDefault(require("string-interp"));
|
|
20
|
+
function fillTemplate(value, ...templateVariables) {
|
|
21
|
+
if ((0, isPlainObject_1.default)(value)) {
|
|
22
|
+
for (const key of Object.keys(value)) {
|
|
23
|
+
value[key] = fillTemplate(value[key], ...templateVariables);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
else if (Array.isArray(value) && value.length > 0) {
|
|
28
|
+
value.forEach(function (v, index) {
|
|
29
|
+
this[index] = fillTemplate(v, ...templateVariables);
|
|
30
|
+
}, value);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
else if (value != null && typeof value === 'string' && value.includes('${')) {
|
|
34
|
+
for (const variables of templateVariables) {
|
|
35
|
+
try {
|
|
36
|
+
const result = (0, string_interp_1.default)(value, variables || {});
|
|
37
|
+
if (result) {
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function getCircularReplacer() {
|
|
51
|
+
const seen = new WeakSet();
|
|
52
|
+
return (key, value) => {
|
|
53
|
+
if (typeof value === 'object' && value !== null) {
|
|
54
|
+
if (seen.has(value)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
seen.add(value);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function toArray(value = []) {
|
|
63
|
+
return Array.isArray(value) ? value : value.split(',').map((v) => v.trim());
|
|
64
|
+
}
|
|
65
|
+
function delay(ms) {
|
|
66
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates a promise that resolves after a specified delay, with support for cancellation via an AbortSignal.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} ms - The delay duration in milliseconds.
|
|
72
|
+
* @param {Object} [options] - Optional configuration.
|
|
73
|
+
* @param {AbortSignal} [options.signal] - An AbortSignal to allow cancellation of the delay.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<void>} A promise that resolves after the specified delay or rejects if aborted.
|
|
76
|
+
*
|
|
77
|
+
* @throws {Error} If the AbortSignal is already aborted or gets aborted during the delay, the promise rejects with an "AbortError".
|
|
78
|
+
*
|
|
79
|
+
* @details Usage:
|
|
80
|
+
* ```typescript
|
|
81
|
+
* @FlowFunction('test.task.LongRunningTask')
|
|
82
|
+
* class LongRunningTask extends FlowTask<Properties> {
|
|
83
|
+
* private readonly abortController = new AbortController();
|
|
84
|
+
*
|
|
85
|
+
* constructor(...) {...}
|
|
86
|
+
*
|
|
87
|
+
* @InputStream()
|
|
88
|
+
* public async loveMeLongTime(event) {
|
|
89
|
+
* try {
|
|
90
|
+
* await delayWithAbort(this.properties.delay, { signal: this.abortController.signal });
|
|
91
|
+
* return this.emitEvent({ foo: 'bar' }, null);
|
|
92
|
+
* } catch (err) {
|
|
93
|
+
* if (err.message === 'AbortError') {
|
|
94
|
+
* return; // Task was aborted
|
|
95
|
+
* }
|
|
96
|
+
* throw err;
|
|
97
|
+
* }
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* public onDestroy = () => { this.abortController.abort(); };
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
function delayWithAbort(ms, options) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
if (options?.signal?.aborted) {
|
|
107
|
+
reject(new Error('AbortError'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const timeout = setTimeout(() => resolve(), ms);
|
|
111
|
+
options?.signal?.addEventListener('abort', () => {
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
reject(new Error('AbortError'));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function deleteFiles(dir, ...filenames) {
|
|
118
|
+
for (const filename of filenames) {
|
|
119
|
+
await fs_1.promises.unlink((0, path_1.join)(dir, filename)).catch((err) => {
|
|
120
|
+
/*ignore*/
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function handleApiError(error, logger) {
|
|
125
|
+
if ((0, axios_1.isAxiosError)(error)) {
|
|
126
|
+
const status = error.response?.status ?? error.code ?? '';
|
|
127
|
+
const statusText = error.response?.statusText ?? '';
|
|
128
|
+
const url = error.config?.url ?? '[Unknown URL]';
|
|
129
|
+
const method = error.config?.method?.toUpperCase() ?? '';
|
|
130
|
+
let errorText = error.response?.data ?? '';
|
|
131
|
+
if (typeof errorText !== 'string') {
|
|
132
|
+
try {
|
|
133
|
+
errorText = JSON.stringify(errorText, null, 2);
|
|
134
|
+
}
|
|
135
|
+
catch (_) {
|
|
136
|
+
errorText = '[Unserializable error body]';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
logger.error(`${status} ${statusText}: ${method} request to ${url} failed\n${errorText}`);
|
|
140
|
+
if (error.stack) {
|
|
141
|
+
logger.error(error.stack);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
logger.error(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function runPyScript(scriptPath, data) {
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
let pyData;
|
|
151
|
+
const pyshell = new python_shell_1.PythonShell(scriptPath, { mode: 'text', pythonOptions: ['-u'] });
|
|
152
|
+
pyshell.send(JSON.stringify(data));
|
|
153
|
+
pyshell.on('message', (message) => {
|
|
154
|
+
try {
|
|
155
|
+
pyData = JSON.parse(message);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
pyData = message;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
pyshell.end((err, code, signal) => {
|
|
162
|
+
if (err) {
|
|
163
|
+
return reject(err);
|
|
164
|
+
}
|
|
165
|
+
return resolve(pyData);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Truncates an object or string to the specified max length and depth
|
|
171
|
+
*/
|
|
172
|
+
function truncate(msg, depth = 4, maxStringLength = 1000) {
|
|
173
|
+
let truncated = (0, util_1.inspect)(msg, { depth, maxStringLength });
|
|
174
|
+
if (truncated.startsWith("'") && truncated.endsWith("'")) {
|
|
175
|
+
truncated = truncated.substring(1, truncated.length - 1);
|
|
176
|
+
}
|
|
177
|
+
return truncated;
|
|
178
|
+
}
|
package/jest.config.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
displayName: 'flow-sdk',
|
|
3
|
-
preset: '../../jest.preset.js',
|
|
4
|
-
testEnvironment: 'node',
|
|
5
|
-
transform: {
|
|
6
|
-
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
7
|
-
},
|
|
8
|
-
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
9
|
-
coverageDirectory: '../../coverage/lib/flow-sdk',
|
|
10
|
-
};
|
package/project.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "flow-sdk",
|
|
3
|
-
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"root": "lib/flow-sdk",
|
|
5
|
-
"sourceRoot": "lib/flow-sdk/src",
|
|
6
|
-
"projectType": "library",
|
|
7
|
-
"release": {
|
|
8
|
-
"version": {
|
|
9
|
-
"preserveLocalDependencyProtocols": false,
|
|
10
|
-
"manifestRootsToUpdate": ["dist/{projectRoot}"]
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"targets": {
|
|
14
|
-
"build": {
|
|
15
|
-
"executor": "@nx/js:tsc",
|
|
16
|
-
"outputs": ["{options.outputPath}"],
|
|
17
|
-
"options": {
|
|
18
|
-
"outputPath": "dist/lib/flow-sdk",
|
|
19
|
-
"main": "lib/flow-sdk/src/index.ts",
|
|
20
|
-
"tsConfig": "lib/flow-sdk/tsconfig.lib.json",
|
|
21
|
-
"assets": ["lib/flow-sdk/*.md", "lib/flow-sdk/src/lib/*.py"]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"nx-release-publish": {
|
|
25
|
-
"options": {
|
|
26
|
-
"packageRoot": "dist/{projectRoot}"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"lint": {
|
|
30
|
-
"executor": "@nx/eslint:lint"
|
|
31
|
-
},
|
|
32
|
-
"test": {
|
|
33
|
-
"executor": "@nx/jest:jest",
|
|
34
|
-
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
35
|
-
"options": {
|
|
36
|
-
"jestConfig": "lib/flow-sdk/jest.config.ts"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"tags": []
|
|
41
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { cloneDeep, get, isPlainObject, set } from 'lodash';
|
|
2
|
-
import interp from 'string-interp';
|
|
3
|
-
|
|
4
|
-
import { Logger } from './FlowLogger';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Class representing a context manager for handling properties.
|
|
8
|
-
*/
|
|
9
|
-
export class ContextManager {
|
|
10
|
-
private properties: Record<string, any>;
|
|
11
|
-
|
|
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
|
-
*/
|
|
17
|
-
constructor(
|
|
18
|
-
protected logger: Logger,
|
|
19
|
-
flowProperties: Record<string, any> = {},
|
|
20
|
-
) {
|
|
21
|
-
this.properties = { flow: flowProperties };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Init or overwrite all properties.
|
|
26
|
-
* @param properties
|
|
27
|
-
*/
|
|
28
|
-
public overwriteAllProperties(properties: Record<string, any> = {}): void {
|
|
29
|
-
this.properties = { flow: properties };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public updateFlowProperties(properties: Record<string, any> = {}): void {
|
|
33
|
-
this.properties.flow = properties;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get a copy of the current properties.
|
|
38
|
-
* @returns {Record<string, any>} A copy of the properties.
|
|
39
|
-
*/
|
|
40
|
-
public getProperties(): Record<string, any> {
|
|
41
|
-
return { ...this.properties };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Set a property.
|
|
46
|
-
* A property key starting with "flow." is reserved for the properties set by in UI and so it is not allowed to be set.
|
|
47
|
-
* @param {string} keyOrPath - The key or the path of the property.
|
|
48
|
-
* @param {any} value - The value of the property.
|
|
49
|
-
*/
|
|
50
|
-
public set(keyOrPath: string, value: any): void {
|
|
51
|
-
if (keyOrPath.startsWith('flow.')) {
|
|
52
|
-
this.logger.error(
|
|
53
|
-
`Set property of "${keyOrPath}" is not allowed, because it starts with "flow.", so it is reserved for the properties set by in UI.`,
|
|
54
|
-
);
|
|
55
|
-
} else {
|
|
56
|
-
set(this.properties, keyOrPath, value);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get a property value by key.
|
|
62
|
-
* @param {string} keyOrPath - The key or the path of the property.
|
|
63
|
-
* @returns {any} The value of the property.
|
|
64
|
-
*/
|
|
65
|
-
public get(keyOrPath: string): any {
|
|
66
|
-
return get(this.properties, keyOrPath, undefined);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public replaceAllPlaceholderProperties(properties: any) {
|
|
70
|
-
return flowInterpolate(cloneDeep(properties), this.properties);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function flowInterpolate(value: any, properties: Record<string, any>): any {
|
|
75
|
-
if (!properties) {
|
|
76
|
-
return value;
|
|
77
|
-
}
|
|
78
|
-
if (isPlainObject(value)) {
|
|
79
|
-
for (const key of Object.keys(value)) {
|
|
80
|
-
value[key] = flowInterpolate(value[key], properties);
|
|
81
|
-
}
|
|
82
|
-
return value;
|
|
83
|
-
} else if (Array.isArray(value) && value.length > 0) {
|
|
84
|
-
value.forEach(function (v, index) {
|
|
85
|
-
this[index] = flowInterpolate(v, properties);
|
|
86
|
-
}, value);
|
|
87
|
-
return value;
|
|
88
|
-
} else if (value != null && typeof value === 'string' && value.startsWith('${')) {
|
|
89
|
-
// get ${...} blocks and replace the ones that start with flow. in a new string
|
|
90
|
-
const blockRegEx = /\$\{\s*(\S+)\s*}/g;
|
|
91
|
-
let newValue = value;
|
|
92
|
-
let m: RegExpExecArray;
|
|
93
|
-
do {
|
|
94
|
-
m = blockRegEx.exec(value);
|
|
95
|
-
if (m?.[1].startsWith('flow.')) {
|
|
96
|
-
newValue = newValue.replace(m[0], interpolate(m[0], { flow: properties.flow }));
|
|
97
|
-
}
|
|
98
|
-
} while (m);
|
|
99
|
-
return newValue;
|
|
100
|
-
} else {
|
|
101
|
-
return value;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function interpolate(text: string, templateVariables: Record<string, any>): string {
|
|
106
|
-
try {
|
|
107
|
-
return interp(text, templateVariables) ?? text;
|
|
108
|
-
} catch (err) {
|
|
109
|
-
return text;
|
|
110
|
-
}
|
|
111
|
-
}
|