@fsai-flow/core 0.0.1
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/README.md +11 -0
- package/dist/README.md +11 -0
- package/dist/package.json +44 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +29 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/ActiveWebhooks.d.ts +59 -0
- package/dist/src/lib/ActiveWebhooks.js +184 -0
- package/dist/src/lib/ActiveWebhooks.js.map +1 -0
- package/dist/src/lib/ActiveWorkflows.d.ts +58 -0
- package/dist/src/lib/ActiveWorkflows.js +244 -0
- package/dist/src/lib/ActiveWorkflows.js.map +1 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.d.ts +26 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.js +179 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.js.map +1 -0
- package/dist/src/lib/BinaryDataManager/index.d.ts +21 -0
- package/dist/src/lib/BinaryDataManager/index.js +146 -0
- package/dist/src/lib/BinaryDataManager/index.js.map +1 -0
- package/dist/src/lib/ChangeCase.d.ts +9 -0
- package/dist/src/lib/ChangeCase.js +43 -0
- package/dist/src/lib/ChangeCase.js.map +1 -0
- package/dist/src/lib/Constants.d.ts +14 -0
- package/dist/src/lib/Constants.js +19 -0
- package/dist/src/lib/Constants.js.map +1 -0
- package/dist/src/lib/Credentials.d.ts +27 -0
- package/dist/src/lib/Credentials.js +89 -0
- package/dist/src/lib/Credentials.js.map +1 -0
- package/dist/src/lib/FileSystem.d.ts +26 -0
- package/dist/src/lib/FileSystem.js +179 -0
- package/dist/src/lib/FileSystem.js.map +1 -0
- package/dist/src/lib/InputConnectionDataLegacy.d.ts +2 -0
- package/dist/src/lib/InputConnectionDataLegacy.js +79 -0
- package/dist/src/lib/InputConnectionDataLegacy.js.map +1 -0
- package/dist/src/lib/Interfaces.d.ts +148 -0
- package/dist/src/lib/Interfaces.js +3 -0
- package/dist/src/lib/Interfaces.js.map +1 -0
- package/dist/src/lib/LoadNodeParameterOptions.d.ts +39 -0
- package/dist/src/lib/LoadNodeParameterOptions.js +150 -0
- package/dist/src/lib/LoadNodeParameterOptions.js.map +1 -0
- package/dist/src/lib/NodeExecuteFunctions.d.ts +226 -0
- package/dist/src/lib/NodeExecuteFunctions.js +2483 -0
- package/dist/src/lib/NodeExecuteFunctions.js.map +1 -0
- package/dist/src/lib/NodesLoader/constants.d.ts +5 -0
- package/dist/src/lib/NodesLoader/constants.js +106 -0
- package/dist/src/lib/NodesLoader/constants.js.map +1 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.d.ts +9 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.js +36 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/directory-loader.d.ts +66 -0
- package/dist/src/lib/NodesLoader/directory-loader.js +325 -0
- package/dist/src/lib/NodesLoader/directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/index.d.ts +5 -0
- package/dist/src/lib/NodesLoader/index.js +12 -0
- package/dist/src/lib/NodesLoader/index.js.map +1 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.d.ts +7 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js +52 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.d.ts +1 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.js +22 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.js.map +1 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.d.ts +17 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.js +100 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/types.d.ts +14 -0
- package/dist/src/lib/NodesLoader/types.js +3 -0
- package/dist/src/lib/NodesLoader/types.js.map +1 -0
- package/dist/src/lib/UserSettings.d.ts +80 -0
- package/dist/src/lib/UserSettings.js +261 -0
- package/dist/src/lib/UserSettings.js.map +1 -0
- package/dist/src/lib/WorkflowExecute.d.ts +53 -0
- package/dist/src/lib/WorkflowExecute.js +835 -0
- package/dist/src/lib/WorkflowExecute.js.map +1 -0
- package/dist/src/lib/index.d.ts +21 -0
- package/dist/src/lib/index.js +146 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/utils/crypto.d.ts +1 -0
- package/dist/src/utils/crypto.js +8 -0
- package/dist/src/utils/crypto.js.map +1 -0
- package/eslint.config.js +19 -0
- package/jest.config.ts +10 -0
- package/package.json +44 -0
- package/project.json +19 -0
- package/src/index.ts +27 -0
- package/src/lib/ActiveWebhooks.ts +245 -0
- package/src/lib/ActiveWorkflows.ts +304 -0
- package/src/lib/BinaryDataManager/FileSystem.ts +214 -0
- package/src/lib/BinaryDataManager/index.ts +187 -0
- package/src/lib/ChangeCase.ts +45 -0
- package/src/lib/Constants.ts +16 -0
- package/src/lib/Credentials.ts +108 -0
- package/src/lib/FileSystem.ts +214 -0
- package/src/lib/InputConnectionDataLegacy.ts +123 -0
- package/src/lib/Interfaces.ts +338 -0
- package/src/lib/LoadNodeParameterOptions.ts +235 -0
- package/src/lib/NodeExecuteFunctions.ts +3704 -0
- package/src/lib/NodesLoader/constants.ts +112 -0
- package/src/lib/NodesLoader/custom-directory-loader.ts +31 -0
- package/src/lib/NodesLoader/directory-loader.ts +458 -0
- package/src/lib/NodesLoader/index.ts +5 -0
- package/src/lib/NodesLoader/lazy-package-directory-loader.ts +55 -0
- package/src/lib/NodesLoader/load-class-in-isolation.ts +19 -0
- package/src/lib/NodesLoader/package-directory-loader.ts +107 -0
- package/src/lib/NodesLoader/types.ts +14 -0
- package/src/lib/UserSettings.ts +292 -0
- package/src/lib/WorkflowExecute.ts +1108 -0
- package/src/lib/index.ts +187 -0
- package/src/utils/crypto.ts +5 -0
- package/tests/Credentials.test.ts +88 -0
- package/tests/Helpers.ts +808 -0
- package/tests/WorkflowExecute.test.ts +1242 -0
- package/tsconfig.json +42 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +14 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* eslint-disable no-continue */
|
|
2
|
+
/* eslint-disable no-await-in-loop */
|
|
3
|
+
/* eslint-disable no-restricted-syntax */
|
|
4
|
+
import { CronJob } from 'cron';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
IGetExecutePollFunctions,
|
|
8
|
+
IGetExecuteTriggerFunctions,
|
|
9
|
+
INode,
|
|
10
|
+
IPollResponse,
|
|
11
|
+
ITriggerResponse,
|
|
12
|
+
IWorkflowExecuteAdditionalData,
|
|
13
|
+
LoggerProxy as Logger,
|
|
14
|
+
Workflow,
|
|
15
|
+
WorkflowActivateMode,
|
|
16
|
+
WorkflowExecuteMode,
|
|
17
|
+
} from '@fsai-flow/workflow';
|
|
18
|
+
|
|
19
|
+
import { secureRandomNumber } from '../utils/crypto';
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line import/no-cycle
|
|
22
|
+
import { ITriggerTime, IWorkflowData } from '../../src';
|
|
23
|
+
|
|
24
|
+
export class ActiveWorkflows {
|
|
25
|
+
private workflowData: {
|
|
26
|
+
[key: string]: IWorkflowData;
|
|
27
|
+
} = {};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns if the workflow is active
|
|
31
|
+
*
|
|
32
|
+
* @param {string} id The id of the workflow to check
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
* @memberof ActiveWorkflows
|
|
35
|
+
*/
|
|
36
|
+
isActive(id: string): boolean {
|
|
37
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
38
|
+
return this.workflowData.hasOwnProperty(id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the ids of the currently active workflows
|
|
43
|
+
*
|
|
44
|
+
* @returns {string[]}
|
|
45
|
+
* @memberof ActiveWorkflows
|
|
46
|
+
*/
|
|
47
|
+
allActiveWorkflows(): string[] {
|
|
48
|
+
return Object.keys(this.workflowData);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the Workflow data for the workflow with
|
|
53
|
+
* the given id if it is currently active
|
|
54
|
+
*
|
|
55
|
+
* @param {string} id
|
|
56
|
+
* @returns {(WorkflowData | undefined)}
|
|
57
|
+
* @memberof ActiveWorkflows
|
|
58
|
+
*/
|
|
59
|
+
get(id: string): IWorkflowData | undefined {
|
|
60
|
+
return this.workflowData[id];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Makes a workflow active
|
|
65
|
+
*
|
|
66
|
+
* @param {string} id The id of the workflow to activate
|
|
67
|
+
* @param {Workflow} workflow The workflow to activate
|
|
68
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData The additional data which is needed to run workflows
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
* @memberof ActiveWorkflows
|
|
71
|
+
*/
|
|
72
|
+
async add(
|
|
73
|
+
id: string,
|
|
74
|
+
workflow: Workflow,
|
|
75
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
76
|
+
mode: WorkflowExecuteMode,
|
|
77
|
+
activation: WorkflowActivateMode,
|
|
78
|
+
getTriggerFunctions: IGetExecuteTriggerFunctions,
|
|
79
|
+
getPollFunctions: IGetExecutePollFunctions,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
this.workflowData[id] = {};
|
|
82
|
+
const triggerNodes = workflow.getTriggerNodes();
|
|
83
|
+
|
|
84
|
+
let triggerResponse: ITriggerResponse | undefined;
|
|
85
|
+
this.workflowData[id].triggerResponses = [];
|
|
86
|
+
for (const triggerNode of triggerNodes) {
|
|
87
|
+
triggerResponse = await workflow.runTrigger(
|
|
88
|
+
triggerNode,
|
|
89
|
+
getTriggerFunctions,
|
|
90
|
+
additionalData,
|
|
91
|
+
mode,
|
|
92
|
+
activation,
|
|
93
|
+
);
|
|
94
|
+
if (triggerResponse !== undefined) {
|
|
95
|
+
// If a response was given save it
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
97
|
+
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const pollNodes = workflow.getPollNodes();
|
|
102
|
+
if (pollNodes.length) {
|
|
103
|
+
this.workflowData[id].pollResponses = [];
|
|
104
|
+
for (const pollNode of pollNodes) {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
106
|
+
this.workflowData[id].pollResponses!.push(
|
|
107
|
+
await this.activatePolling(
|
|
108
|
+
pollNode,
|
|
109
|
+
workflow,
|
|
110
|
+
additionalData,
|
|
111
|
+
getPollFunctions,
|
|
112
|
+
mode,
|
|
113
|
+
activation,
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Activates polling for the given node
|
|
122
|
+
*
|
|
123
|
+
* @param {INode} node
|
|
124
|
+
* @param {Workflow} workflow
|
|
125
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
126
|
+
* @param {IGetExecutePollFunctions} getPollFunctions
|
|
127
|
+
* @returns {Promise<IPollResponse>}
|
|
128
|
+
* @memberof ActiveWorkflows
|
|
129
|
+
*/
|
|
130
|
+
async activatePolling(
|
|
131
|
+
node: INode,
|
|
132
|
+
workflow: Workflow,
|
|
133
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
134
|
+
getPollFunctions: IGetExecutePollFunctions,
|
|
135
|
+
mode: WorkflowExecuteMode,
|
|
136
|
+
activation: WorkflowActivateMode,
|
|
137
|
+
): Promise<IPollResponse> {
|
|
138
|
+
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
|
139
|
+
|
|
140
|
+
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
|
141
|
+
item: ITriggerTime[];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Define the order the cron-time-parameter appear
|
|
145
|
+
const parameterOrder = [
|
|
146
|
+
'second', // 0 - 59
|
|
147
|
+
'minute', // 0 - 59
|
|
148
|
+
'hour', // 0 - 23
|
|
149
|
+
'dayOfMonth', // 1 - 31
|
|
150
|
+
'month', // 0 - 11(Jan - Dec)
|
|
151
|
+
'weekday', // 0 - 6(Sun - Sat)
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// Get all the trigger times
|
|
155
|
+
const cronTimes: string[] = [];
|
|
156
|
+
let cronTime: string[];
|
|
157
|
+
let parameterName: string;
|
|
158
|
+
if (pollTimes.item !== undefined) {
|
|
159
|
+
for (const item of pollTimes.item) {
|
|
160
|
+
cronTime = [];
|
|
161
|
+
if (item.mode === 'custom') {
|
|
162
|
+
cronTimes.push((item['cronExpression'] as string).trim());
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (item.mode === 'everyMinute') {
|
|
166
|
+
cronTimes.push(`${secureRandomNumber(1, 60).toString()} * * * * *`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (item.mode === 'everyX') {
|
|
170
|
+
if (item['unit'] === 'minutes') {
|
|
171
|
+
cronTimes.push(`${secureRandomNumber(1, 60).toString()} */${item['value']} * * * *`);
|
|
172
|
+
} else if (item['unit'] === 'hours') {
|
|
173
|
+
cronTimes.push(`${secureRandomNumber(1, 60).toString()} 0 */${item['value']} * * *`);
|
|
174
|
+
} else if (item['unit'] === 'seconds') {
|
|
175
|
+
cronTimes.push(`*/${item['value']} * * * * *`);
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (parameterName of parameterOrder) {
|
|
181
|
+
if (item[parameterName] !== undefined) {
|
|
182
|
+
// Value is set so use it
|
|
183
|
+
cronTime.push(item[parameterName] as string);
|
|
184
|
+
} else if (parameterName === 'second') {
|
|
185
|
+
// For seconds we use by default a random one to make sure to
|
|
186
|
+
// balance the load a little bit over time
|
|
187
|
+
cronTime.push(secureRandomNumber(1, 60).toString());
|
|
188
|
+
} else {
|
|
189
|
+
// For all others set "any"
|
|
190
|
+
cronTime.push('*');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
cronTimes.push(cronTime.join(' '));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The trigger function to execute when the cron-time got reached
|
|
199
|
+
const executeTrigger = async () => {
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
201
|
+
Logger.debug(`Polling trigger initiated for workflow "${workflow.name}"`, {
|
|
202
|
+
workflowName: workflow.name,
|
|
203
|
+
workflowId: workflow.id,
|
|
204
|
+
});
|
|
205
|
+
const pollResponse = await workflow.runPoll(node, pollFunctions);
|
|
206
|
+
|
|
207
|
+
if (pollResponse !== null) {
|
|
208
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
209
|
+
pollFunctions.__emit(pollResponse);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Execute the trigger directly to be able to know if it works
|
|
214
|
+
await executeTrigger();
|
|
215
|
+
|
|
216
|
+
const timezone = pollFunctions.getTimezone();
|
|
217
|
+
|
|
218
|
+
// Start the cron-jobs
|
|
219
|
+
const cronJobs: CronJob[] = [];
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
221
|
+
for (const cronTime of cronTimes) {
|
|
222
|
+
const cronTimeParts = cronTime.split(' ');
|
|
223
|
+
|
|
224
|
+
// Verificar se é polling em segundos
|
|
225
|
+
if (cronTimeParts.length === 6 && cronTimeParts[0].includes('/')) {
|
|
226
|
+
const secondsInterval = parseInt(cronTimeParts[0].replace('*/', ''), 10);
|
|
227
|
+
if (secondsInterval < 1) {
|
|
228
|
+
throw new Error('The polling interval is too short. It has to be at least 1 second!');
|
|
229
|
+
}
|
|
230
|
+
} else if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*') && !cronTimeParts[0].includes('/')) {
|
|
231
|
+
throw new Error('The polling interval is too short. It has to be at least a minute!');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Stop the cron-jobs
|
|
238
|
+
async function closeFunction() {
|
|
239
|
+
for (const cronJob of cronJobs) {
|
|
240
|
+
cronJob.stop();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
closeFunction,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Makes a workflow inactive
|
|
251
|
+
*
|
|
252
|
+
* @param {string} id The id of the workflow to deactivate
|
|
253
|
+
* @returns {Promise<void>}
|
|
254
|
+
* @memberof ActiveWorkflows
|
|
255
|
+
*/
|
|
256
|
+
async remove(id: string): Promise<void> {
|
|
257
|
+
if (!this.isActive(id)) {
|
|
258
|
+
// Workflow is currently not registered
|
|
259
|
+
throw new Error(
|
|
260
|
+
`The workflow with the id "${id}" is currently not active and can so not be removed`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const workflowData = this.workflowData[id];
|
|
265
|
+
|
|
266
|
+
if (workflowData.triggerResponses) {
|
|
267
|
+
for (const triggerResponse of workflowData.triggerResponses) {
|
|
268
|
+
if (triggerResponse.closeFunction) {
|
|
269
|
+
try {
|
|
270
|
+
await triggerResponse.closeFunction();
|
|
271
|
+
} catch (error: any) {
|
|
272
|
+
Logger.error(
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions
|
|
274
|
+
`There was a problem deactivating trigger of workflow "${id}": "${error.message}"`,
|
|
275
|
+
{
|
|
276
|
+
workflowId: id,
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (workflowData.pollResponses) {
|
|
285
|
+
for (const pollResponse of workflowData.pollResponses) {
|
|
286
|
+
if (pollResponse.closeFunction) {
|
|
287
|
+
try {
|
|
288
|
+
await pollResponse.closeFunction();
|
|
289
|
+
} catch (error:any) {
|
|
290
|
+
Logger.error(
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions
|
|
292
|
+
`There was a problem deactivating polling trigger of workflow "${id}": "${error.message}"`,
|
|
293
|
+
{
|
|
294
|
+
workflowId: id,
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
delete this.workflowData[id];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
|
|
5
|
+
import { IBinaryDataConfig, IBinaryDataManager } from '../Interfaces';
|
|
6
|
+
|
|
7
|
+
const PREFIX_METAFILE = 'binarymeta';
|
|
8
|
+
const PREFIX_PERSISTED_METAFILE = 'persistedmeta';
|
|
9
|
+
|
|
10
|
+
export class BinaryDataFileSystem implements IBinaryDataManager {
|
|
11
|
+
private storagePath: string;
|
|
12
|
+
|
|
13
|
+
private binaryDataTTL: number;
|
|
14
|
+
|
|
15
|
+
private persistedBinaryDataTTL: number;
|
|
16
|
+
|
|
17
|
+
constructor(config: IBinaryDataConfig) {
|
|
18
|
+
this.storagePath = config.localStoragePath;
|
|
19
|
+
this.binaryDataTTL = config.binaryDataTTL;
|
|
20
|
+
this.persistedBinaryDataTTL = config.persistedBinaryDataTTL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async init(startPurger = false): Promise<void> {
|
|
24
|
+
if (startPurger) {
|
|
25
|
+
setInterval(async () => {
|
|
26
|
+
await this.deleteMarkedFiles();
|
|
27
|
+
}, this.binaryDataTTL * 30000);
|
|
28
|
+
|
|
29
|
+
setInterval(async () => {
|
|
30
|
+
await this.deleteMarkedPersistedFiles();
|
|
31
|
+
}, this.persistedBinaryDataTTL * 30000);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return fs
|
|
35
|
+
.readdir(this.storagePath)
|
|
36
|
+
.catch(async () => fs.mkdir(this.storagePath, { recursive: true }))
|
|
37
|
+
.then(async () => fs.readdir(this.getBinaryDataMetaPath()))
|
|
38
|
+
.catch(async () => fs.mkdir(this.getBinaryDataMetaPath(), { recursive: true }))
|
|
39
|
+
.then(async () => fs.readdir(this.getBinaryDataPersistMetaPath()))
|
|
40
|
+
.catch(async () => fs.mkdir(this.getBinaryDataPersistMetaPath(), { recursive: true }))
|
|
41
|
+
.then(async () => this.deleteMarkedFiles())
|
|
42
|
+
.then(async () => this.deleteMarkedPersistedFiles())
|
|
43
|
+
.then(() => {});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async storeBinaryData(binaryBuffer: Buffer, executionId: string): Promise<string> {
|
|
47
|
+
const binaryDataId = this.generateFileName(executionId);
|
|
48
|
+
return this.addBinaryIdToPersistMeta(executionId, binaryDataId).then(async () =>
|
|
49
|
+
this.saveToLocalStorage(binaryBuffer, binaryDataId).then(() => binaryDataId),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async retrieveBinaryDataByIdentifier(identifier: string): Promise<Buffer> {
|
|
54
|
+
return this.retrieveFromLocalStorage(identifier);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async markDataForDeletionByExecutionId(executionId: string): Promise<void> {
|
|
58
|
+
const tt = new Date(new Date().getTime() + this.binaryDataTTL * 60000);
|
|
59
|
+
return fs.writeFile(
|
|
60
|
+
path.join(this.getBinaryDataMetaPath(), `${PREFIX_METAFILE}_${executionId}_${tt.valueOf()}`),
|
|
61
|
+
'',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async deleteMarkedFiles(): Promise<void> {
|
|
66
|
+
return this.deleteMarkedFilesByMeta(this.getBinaryDataMetaPath(), PREFIX_METAFILE);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async deleteMarkedPersistedFiles(): Promise<void> {
|
|
70
|
+
return this.deleteMarkedFilesByMeta(
|
|
71
|
+
this.getBinaryDataPersistMetaPath(),
|
|
72
|
+
PREFIX_PERSISTED_METAFILE,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async addBinaryIdToPersistMeta(executionId: string, identifier: string): Promise<void> {
|
|
77
|
+
const currentTime = new Date().getTime();
|
|
78
|
+
const timeAtNextHour = currentTime + 3600000 - (currentTime % 3600000);
|
|
79
|
+
const timeoutTime = timeAtNextHour + this.persistedBinaryDataTTL * 60000;
|
|
80
|
+
|
|
81
|
+
const filePath = path.join(
|
|
82
|
+
this.getBinaryDataPersistMetaPath(),
|
|
83
|
+
`${PREFIX_PERSISTED_METAFILE}_${executionId}_${timeoutTime}`,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return fs
|
|
87
|
+
.readFile(filePath)
|
|
88
|
+
.catch(async () => fs.writeFile(filePath, identifier))
|
|
89
|
+
.then(() => {});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async deleteMarkedFilesByMeta(metaPath: string, filePrefix: string): Promise<void> {
|
|
93
|
+
const currentTimeValue = new Date().valueOf();
|
|
94
|
+
const metaFileNames = await fs.readdir(metaPath);
|
|
95
|
+
|
|
96
|
+
const execsAdded: { [key: string]: number } = {};
|
|
97
|
+
|
|
98
|
+
const proms = metaFileNames.reduce(
|
|
99
|
+
(prev, curr) => {
|
|
100
|
+
const [prefix, executionId, ts] = curr.split('_');
|
|
101
|
+
|
|
102
|
+
if (prefix !== filePrefix) {
|
|
103
|
+
return prev;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const execTimestamp = parseInt(ts, 10);
|
|
107
|
+
|
|
108
|
+
if (execTimestamp < currentTimeValue) {
|
|
109
|
+
if (execsAdded[executionId]) {
|
|
110
|
+
// do not delete data, only meta file
|
|
111
|
+
prev.push(this.deleteMetaFileByPath(path.join(metaPath, curr)));
|
|
112
|
+
return prev;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
execsAdded[executionId] = 1;
|
|
116
|
+
prev.push(
|
|
117
|
+
this.deleteBinaryDataByExecutionId(executionId).then(async () =>
|
|
118
|
+
this.deleteMetaFileByPath(path.join(metaPath, curr)),
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return prev;
|
|
124
|
+
},
|
|
125
|
+
[Promise.resolve()],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return Promise.all(proms).then(() => {});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async duplicateBinaryDataByIdentifier(binaryDataId: string, prefix: string): Promise<string> {
|
|
132
|
+
const newBinaryDataId = this.generateFileName(prefix);
|
|
133
|
+
|
|
134
|
+
return fs
|
|
135
|
+
.copyFile(
|
|
136
|
+
path.join(this.storagePath, binaryDataId),
|
|
137
|
+
path.join(this.storagePath, newBinaryDataId),
|
|
138
|
+
)
|
|
139
|
+
.then(() => newBinaryDataId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async deleteBinaryDataByExecutionId(executionId: string): Promise<void> {
|
|
143
|
+
const regex = new RegExp(`${executionId}_*`);
|
|
144
|
+
const filenames = await fs.readdir(path.join(this.storagePath));
|
|
145
|
+
|
|
146
|
+
const proms = filenames.reduce(
|
|
147
|
+
(allProms, filename) => {
|
|
148
|
+
if (regex.test(filename)) {
|
|
149
|
+
allProms.push(fs.rm(path.join(this.storagePath, filename)));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return allProms;
|
|
153
|
+
},
|
|
154
|
+
[Promise.resolve()],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return Promise.all(proms).then(async () => Promise.resolve());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async deleteBinaryDataByIdentifier(identifier: string): Promise<void> {
|
|
161
|
+
return this.deleteFromLocalStorage(identifier);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async persistBinaryDataForExecutionId(executionId: string): Promise<void> {
|
|
165
|
+
return fs.readdir(this.getBinaryDataPersistMetaPath()).then(async (metafiles) => {
|
|
166
|
+
const proms = metafiles.reduce(
|
|
167
|
+
(prev, curr) => {
|
|
168
|
+
if (curr.startsWith(`${PREFIX_PERSISTED_METAFILE}_${executionId}_`)) {
|
|
169
|
+
prev.push(fs.rm(path.join(this.getBinaryDataPersistMetaPath(), curr)));
|
|
170
|
+
return prev;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return prev;
|
|
174
|
+
},
|
|
175
|
+
[Promise.resolve()],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return Promise.all(proms).then(() => {});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private generateFileName(prefix: string): string {
|
|
183
|
+
return `${prefix}_${uuid()}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private getBinaryDataMetaPath() {
|
|
187
|
+
return path.join(this.storagePath, 'meta');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private getBinaryDataPersistMetaPath() {
|
|
191
|
+
return path.join(this.storagePath, 'persistMeta');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async deleteMetaFileByPath(metafilePath: string): Promise<void> {
|
|
195
|
+
return fs.rm(metafilePath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async deleteFromLocalStorage(identifier: string) {
|
|
199
|
+
return fs.rm(path.join(this.storagePath, identifier));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async saveToLocalStorage(data: Buffer, identifier: string) {
|
|
203
|
+
await fs.writeFile(path.join(this.storagePath, identifier), data);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async retrieveFromLocalStorage(identifier: string): Promise<Buffer> {
|
|
207
|
+
const filePath = path.join(this.storagePath, identifier);
|
|
208
|
+
try {
|
|
209
|
+
return await fs.readFile(filePath);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
throw new Error(`Error finding file: ${filePath}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { IBinaryData, INodeExecutionData } from '@fsai-flow/workflow';
|
|
2
|
+
import { BINARY_ENCODING } from '../Constants';
|
|
3
|
+
import { IBinaryDataConfig, IBinaryDataManager } from '../Interfaces';
|
|
4
|
+
import { BinaryDataFileSystem } from './FileSystem';
|
|
5
|
+
|
|
6
|
+
export class BinaryDataManager {
|
|
7
|
+
private static instance: BinaryDataManager;
|
|
8
|
+
|
|
9
|
+
private managers: {
|
|
10
|
+
[key: string]: IBinaryDataManager;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
private binaryDataMode: string;
|
|
14
|
+
|
|
15
|
+
private availableModes: string[];
|
|
16
|
+
|
|
17
|
+
constructor(config: IBinaryDataConfig) {
|
|
18
|
+
this.binaryDataMode = config.mode;
|
|
19
|
+
this.availableModes = config.availableModes.split(',');
|
|
20
|
+
this.managers = {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async init(config: IBinaryDataConfig, mainManager = false): Promise<void> {
|
|
24
|
+
if (BinaryDataManager.instance) {
|
|
25
|
+
throw new Error('Binary Data Manager already initialized');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
BinaryDataManager.instance = new BinaryDataManager(config);
|
|
29
|
+
|
|
30
|
+
if (BinaryDataManager.instance.availableModes.includes('filesystem')) {
|
|
31
|
+
BinaryDataManager.instance.managers['filesystem'] = new BinaryDataFileSystem(config);
|
|
32
|
+
await BinaryDataManager.instance.managers['filesystem'].init(mainManager);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static getInstance(): BinaryDataManager {
|
|
39
|
+
if (!BinaryDataManager.instance) {
|
|
40
|
+
throw new Error('Binary Data Manager not initialized');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return BinaryDataManager.instance;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async storeBinaryData(
|
|
47
|
+
binaryData: IBinaryData,
|
|
48
|
+
binaryBuffer: Buffer,
|
|
49
|
+
executionId: string,
|
|
50
|
+
): Promise<IBinaryData> {
|
|
51
|
+
const retBinaryData = binaryData;
|
|
52
|
+
|
|
53
|
+
if (this.managers[this.binaryDataMode]) {
|
|
54
|
+
return this.managers[this.binaryDataMode]
|
|
55
|
+
.storeBinaryData(binaryBuffer, executionId)
|
|
56
|
+
.then((filename) => {
|
|
57
|
+
retBinaryData.id = this.generateBinaryId(filename);
|
|
58
|
+
return retBinaryData;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
retBinaryData.data = binaryBuffer.toString(BINARY_ENCODING);
|
|
63
|
+
return binaryData;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async retrieveBinaryData(binaryData: IBinaryData): Promise<Buffer> {
|
|
67
|
+
if (binaryData.id) {
|
|
68
|
+
return this.retrieveBinaryDataByIdentifier(binaryData.id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Buffer.from(binaryData.data, BINARY_ENCODING);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async retrieveBinaryDataByIdentifier(identifier: string): Promise<Buffer> {
|
|
75
|
+
const { mode, id } = this.splitBinaryModeFileId(identifier);
|
|
76
|
+
if (this.managers[mode]) {
|
|
77
|
+
return this.managers[mode].retrieveBinaryDataByIdentifier(id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error('Storage mode used to store binary data not available');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async markDataForDeletionByExecutionId(executionId: string): Promise<void> {
|
|
84
|
+
if (this.managers[this.binaryDataMode]) {
|
|
85
|
+
return this.managers[this.binaryDataMode].markDataForDeletionByExecutionId(executionId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Promise.resolve();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async persistBinaryDataForExecutionId(executionId: string): Promise<void> {
|
|
92
|
+
if (this.managers[this.binaryDataMode]) {
|
|
93
|
+
return this.managers[this.binaryDataMode].persistBinaryDataForExecutionId(executionId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async deleteBinaryDataByExecutionId(executionId: string): Promise<void> {
|
|
100
|
+
if (this.managers[this.binaryDataMode]) {
|
|
101
|
+
return this.managers[this.binaryDataMode].deleteBinaryDataByExecutionId(executionId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async duplicateBinaryData(
|
|
108
|
+
inputData: Array<INodeExecutionData[] | null> | unknown,
|
|
109
|
+
executionId: string,
|
|
110
|
+
): Promise<INodeExecutionData[][]> {
|
|
111
|
+
if (inputData && this.managers[this.binaryDataMode]) {
|
|
112
|
+
const returnInputData = (inputData as INodeExecutionData[][]).map(
|
|
113
|
+
async (executionDataArray) => {
|
|
114
|
+
if (executionDataArray) {
|
|
115
|
+
return Promise.all(
|
|
116
|
+
executionDataArray.map((executionData) => {
|
|
117
|
+
if (executionData.binary) {
|
|
118
|
+
return this.duplicateBinaryDataInExecData(executionData, executionId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return executionData;
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return executionDataArray;
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return Promise.all(returnInputData);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Promise.resolve(inputData as INodeExecutionData[][]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private generateBinaryId(filename: string) {
|
|
137
|
+
return `${this.binaryDataMode}:${filename}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private splitBinaryModeFileId(fileId: string): { mode: string; id: string } {
|
|
141
|
+
const [mode, id] = fileId.split(':');
|
|
142
|
+
return { mode, id };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async duplicateBinaryDataInExecData(
|
|
146
|
+
executionData: INodeExecutionData,
|
|
147
|
+
executionId: string,
|
|
148
|
+
): Promise<INodeExecutionData> {
|
|
149
|
+
const binaryManager = this.managers[this.binaryDataMode];
|
|
150
|
+
|
|
151
|
+
if (executionData.binary) {
|
|
152
|
+
const binaryDataKeys = Object.keys(executionData.binary);
|
|
153
|
+
const bdPromises = binaryDataKeys.map(async (key: string) => {
|
|
154
|
+
if (!executionData.binary) {
|
|
155
|
+
return { key, newId: undefined };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const binaryDataId = executionData.binary[key].id;
|
|
159
|
+
if (!binaryDataId) {
|
|
160
|
+
return { key, newId: undefined };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return binaryManager
|
|
164
|
+
?.duplicateBinaryDataByIdentifier(
|
|
165
|
+
this.splitBinaryModeFileId(binaryDataId).id,
|
|
166
|
+
executionId,
|
|
167
|
+
)
|
|
168
|
+
.then((filename) => ({
|
|
169
|
+
newId: this.generateBinaryId(filename),
|
|
170
|
+
key,
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return Promise.all(bdPromises).then((b) => {
|
|
175
|
+
return b.reduce((acc, curr) => {
|
|
176
|
+
if (acc.binary && curr) {
|
|
177
|
+
acc.binary[curr.key].id = curr.newId;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return acc;
|
|
181
|
+
}, executionData);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return executionData;
|
|
186
|
+
}
|
|
187
|
+
}
|