@fsai-flow/core 0.0.3 → 0.0.5
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/README.md +31 -0
- package/dist/package.json +54 -0
- package/package.json +2 -2
- package/src/lib/ActiveWebhooks.ts +1 -1
- package/src/lib/ActiveWorkflows.ts +1 -1
- package/src/lib/LoadNodeParameterOptions.ts +1 -1
- package/src/lib/NodeExecuteFunctions.ts +1 -1
- package/src/lib/RedisLeaderElectionManager.ts +82 -0
- package/src/lib/UserSettings.ts +1 -1
- package/src/lib/WorkflowExecute.ts +1 -5
- package/tsconfig.json +0 -1
- package/tsconfig.base.json +0 -28
package/dist/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @fsai-flow/core
|
|
2
|
+
|
|
3
|
+
This library is a dependency for [FSAI-Flow](https://github.com/your-org/flowx) that contains base classes and types used throughout the application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
This project is published to the npm registry. To use it in your project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @fsai-flow/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Building
|
|
14
|
+
|
|
15
|
+
Run `nx build core` to build the library.
|
|
16
|
+
|
|
17
|
+
## Running unit tests
|
|
18
|
+
|
|
19
|
+
Run `nx test core` to execute the unit tests via [Jest](https://jestjs.io).
|
|
20
|
+
|
|
21
|
+
## Contributing
|
|
22
|
+
|
|
23
|
+
To contribute to this project:
|
|
24
|
+
|
|
25
|
+
1. Clone this repository
|
|
26
|
+
2. Make your changes
|
|
27
|
+
3. Open a Pull Request
|
|
28
|
+
|
|
29
|
+
## Publishing
|
|
30
|
+
|
|
31
|
+
Maintainers can publish a new version to the npm package registry.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fsai-flow/core",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@fsai-flow/workflow": "0.0.3",
|
|
6
|
+
"client-oauth2": "^4.3.3",
|
|
7
|
+
"cron": "~3.3.0",
|
|
8
|
+
"crypto-js": "~4.2.0",
|
|
9
|
+
"file-type": "^16.0.0",
|
|
10
|
+
"form-data": "4.0.4",
|
|
11
|
+
"https-proxy-agent": "^7.0.6",
|
|
12
|
+
"ioredis": "^5.3.2",
|
|
13
|
+
"lodash": "^4.17.21",
|
|
14
|
+
"mime-types": "^2.1.27",
|
|
15
|
+
"oauth-1.0a": "^2.2.6",
|
|
16
|
+
"p-cancelable": "^2.0.0",
|
|
17
|
+
"qs": "^6.10.1",
|
|
18
|
+
"request": "^2.88.2",
|
|
19
|
+
"request-promise-native": "^1.0.9",
|
|
20
|
+
"simple-oauth2": "^5.1.0",
|
|
21
|
+
"tslib": "^2.3.0",
|
|
22
|
+
"uuid": "^11.0.3",
|
|
23
|
+
"fast-glob": "3.2.12"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/crypto-js": "^4.0.1",
|
|
27
|
+
"@types/express": "^5.0.0",
|
|
28
|
+
"@types/lodash": "^4.17.13",
|
|
29
|
+
"@types/lodash.get": "^4.4.9",
|
|
30
|
+
"@types/mime-types": "^2.1.0",
|
|
31
|
+
"@types/node": "^22.10.2",
|
|
32
|
+
"@types/qs": "^6.9.17",
|
|
33
|
+
"@types/request": "^2.48.12",
|
|
34
|
+
"@types/request-promise-native": "^1.0.15",
|
|
35
|
+
"@types/simple-oauth2": "^5.0.7",
|
|
36
|
+
"@types/uuid": "^10.0.0",
|
|
37
|
+
"axios": "^1.7.9",
|
|
38
|
+
"ioredis": "^5.3.2",
|
|
39
|
+
"jsonc-eslint-parser": "^2.4.0",
|
|
40
|
+
"typescript": "~5.7.2"
|
|
41
|
+
},
|
|
42
|
+
"type": "commonjs",
|
|
43
|
+
"main": "dist/src/index",
|
|
44
|
+
"types": "dist/src/index.d.ts",
|
|
45
|
+
"overrides": {
|
|
46
|
+
"request": {
|
|
47
|
+
"form-data": "2.5.4",
|
|
48
|
+
"tough-cookie": "4.1.3"
|
|
49
|
+
},
|
|
50
|
+
"request-promise-native": {
|
|
51
|
+
"tough-cookie": "4.1.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@ import { RedisLeaderElectionManager } from './RedisLeaderElectionManager';
|
|
|
23
23
|
import type Redis from 'ioredis';
|
|
24
24
|
|
|
25
25
|
// eslint-disable-next-line import/no-cycle
|
|
26
|
-
import { ITriggerTime, IWorkflowData } from '
|
|
26
|
+
import { ITriggerTime, IWorkflowData } from '..';
|
|
27
27
|
import { RedisOptions } from 'ioredis';
|
|
28
28
|
|
|
29
29
|
interface IPollingWorkflow {
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from '@fsai-flow/workflow';
|
|
26
26
|
|
|
27
27
|
// eslint-disable-next-line import/no-cycle
|
|
28
|
-
import { NodeExecuteFunctions } from '
|
|
28
|
+
import { NodeExecuteFunctions } from '..';
|
|
29
29
|
|
|
30
30
|
const TEMP_NODE_NAME = 'Temp-Node';
|
|
31
31
|
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
|
@@ -38,11 +38,76 @@ export class RedisLeaderElectionManager {
|
|
|
38
38
|
Logger.info(`Redis connected for leader election: ${this.lockKey}`);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
this.redis.on('ready', () => {
|
|
42
|
+
Logger.info(`Redis ready for leader election: ${this.lockKey}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
41
45
|
this.redis.on('error', (error: any) => {
|
|
42
46
|
Logger.error(`Redis connection error for leader election: ${error.message}`);
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Wait for Redis connection to be ready
|
|
52
|
+
*/
|
|
53
|
+
private async waitForRedisConnection(): Promise<void> {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
if (this.redis.status === 'ready') {
|
|
56
|
+
Logger.debug(`Redis already ready for ${this.lockKey}`);
|
|
57
|
+
resolve();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let isSettled = false;
|
|
62
|
+
let timeoutId: NodeJS.Timeout | null = null;
|
|
63
|
+
|
|
64
|
+
// Cleanup function to prevent memory leaks
|
|
65
|
+
const cleanup = () => {
|
|
66
|
+
if (isSettled) return; // Already cleaned up
|
|
67
|
+
isSettled = true;
|
|
68
|
+
|
|
69
|
+
// Clear timeout safely
|
|
70
|
+
if (timeoutId !== null) {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
timeoutId = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Remove event listeners safely
|
|
76
|
+
try {
|
|
77
|
+
this.redis.off('ready', onReady);
|
|
78
|
+
this.redis.off('error', onError);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// Ignore cleanup errors - Redis connection might be destroyed
|
|
81
|
+
Logger.debug(`Cleanup warning for ${this.lockKey}: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const onReady = () => {
|
|
86
|
+
cleanup();
|
|
87
|
+
Logger.debug(`Redis connection established for ${this.lockKey}`);
|
|
88
|
+
resolve();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const onError = (error: Error) => {
|
|
92
|
+
cleanup();
|
|
93
|
+
Logger.error(`Redis connection failed for ${this.lockKey}: ${error.message}`);
|
|
94
|
+
reject(error);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onTimeout = () => {
|
|
98
|
+
cleanup();
|
|
99
|
+
reject(new Error(`Redis connection timeout after 10 seconds for ${this.lockKey}`));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Set up timeout
|
|
103
|
+
timeoutId = setTimeout(onTimeout, 10000); // 10 second timeout
|
|
104
|
+
|
|
105
|
+
// Set up event listeners
|
|
106
|
+
this.redis.once('ready', onReady);
|
|
107
|
+
this.redis.once('error', onError);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
46
111
|
/**
|
|
47
112
|
* Start the leader election process
|
|
48
113
|
*/
|
|
@@ -50,6 +115,11 @@ export class RedisLeaderElectionManager {
|
|
|
50
115
|
Logger.info(`🚀 Starting Redis leader election for ${this.nodeId} on key ${this.lockKey}`);
|
|
51
116
|
Logger.info(`🔧 Leader election config: TTL=${this.lockTTL}ms, RenewalInterval=${this.renewalInterval}ms`);
|
|
52
117
|
|
|
118
|
+
// Wait for Redis connection to be ready
|
|
119
|
+
Logger.info(`⏳ ${this.nodeId} waiting for Redis connection...`);
|
|
120
|
+
await this.waitForRedisConnection();
|
|
121
|
+
Logger.info(`✅ Redis connection ready for ${this.nodeId}`);
|
|
122
|
+
|
|
53
123
|
// Try to acquire leadership immediately
|
|
54
124
|
Logger.info(`🎯 ${this.nodeId} making initial leadership attempt...`);
|
|
55
125
|
await this.tryAcquireLeadership();
|
|
@@ -98,6 +168,12 @@ export class RedisLeaderElectionManager {
|
|
|
98
168
|
*/
|
|
99
169
|
private async tryAcquireLeadership(): Promise<boolean> {
|
|
100
170
|
try {
|
|
171
|
+
// Check if Redis connection is ready before attempting operations
|
|
172
|
+
if (this.redis.status !== 'ready') {
|
|
173
|
+
Logger.warn(`Redis not ready for ${this.nodeId}, current status: ${this.redis.status}`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
101
177
|
Logger.debug(`Node ${this.nodeId} attempting to acquire leadership for ${this.lockKey}`);
|
|
102
178
|
|
|
103
179
|
// Use SET with NX (not exists) and PX (expire in milliseconds)
|
|
@@ -160,6 +236,12 @@ export class RedisLeaderElectionManager {
|
|
|
160
236
|
*/
|
|
161
237
|
private async renewLock(): Promise<boolean> {
|
|
162
238
|
try {
|
|
239
|
+
// Check if Redis connection is ready before attempting operations
|
|
240
|
+
if (this.redis.status !== 'ready') {
|
|
241
|
+
Logger.warn(`Redis not ready for ${this.nodeId} renewal, current status: ${this.redis.status}`);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
163
245
|
Logger.debug(`🔄 Node ${this.nodeId} attempting to renew leadership for ${this.lockKey}`);
|
|
164
246
|
|
|
165
247
|
// Use Lua script to atomically check ownership and renew
|
package/src/lib/UserSettings.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
WorkflowOperationError,
|
|
24
24
|
} from '@fsai-flow/workflow';
|
|
25
25
|
import { get } from 'lodash';
|
|
26
|
-
import { NodeExecuteFunctions } from '
|
|
26
|
+
import { NodeExecuteFunctions } from '..';
|
|
27
27
|
|
|
28
28
|
export class WorkflowExecute {
|
|
29
29
|
runExecutionData: IRunExecutionData;
|
|
@@ -283,10 +283,6 @@ export class WorkflowExecute {
|
|
|
283
283
|
): void {
|
|
284
284
|
let stillDataMissing = false;
|
|
285
285
|
|
|
286
|
-
if (connectionData.node === '__proto__' || connectionData.node === 'constructor' || connectionData.node === 'prototype') {
|
|
287
|
-
throw new Error('Prototype pollution detected');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
286
|
// Check if node has multiple inputs as then we have to wait for all input data
|
|
291
287
|
// to be present before we can add it to the node-execution-stack
|
|
292
288
|
if (workflow.connectionsByDestinationNode[connectionData.node]['main'].length > 1) {
|
package/tsconfig.json
CHANGED
package/tsconfig.base.json
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compileOnSave": false,
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"sourceMap": true,
|
|
6
|
-
"declaration": false,
|
|
7
|
-
"moduleResolution": "node",
|
|
8
|
-
"emitDecoratorMetadata": true,
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"importHelpers": true,
|
|
11
|
-
"target": "es2015",
|
|
12
|
-
"module": "esnext",
|
|
13
|
-
"lib": ["es2020", "dom"],
|
|
14
|
-
"skipLibCheck": true,
|
|
15
|
-
"skipDefaultLibCheck": true,
|
|
16
|
-
"baseUrl": ".",
|
|
17
|
-
"paths": {
|
|
18
|
-
"@fsai-flow/core": ["libs/core/src/index.ts"],
|
|
19
|
-
"@fsai-flow/nodes-base": ["libs/nodes-base/src/index.ts"],
|
|
20
|
-
"@fsai-flow/nodes-langchain": ["libs/nodes-langchain/src/index.ts"],
|
|
21
|
-
"@fsai-flow/workflow": ["libs/workflow/src/index.ts"],
|
|
22
|
-
"@fsai-flow/design-system": ["apps/design-system/src/main.ts"],
|
|
23
|
-
"@fsai-flow/editor-ui": ["apps/editor-ui/src/index.ts"],
|
|
24
|
-
"@fsai-flow/cli": ["apps/cli/src/index.ts"],
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"exclude": ["node_modules", "tmp"]
|
|
28
|
-
}
|