@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.
Files changed (113) hide show
  1. package/README.md +11 -0
  2. package/dist/README.md +11 -0
  3. package/dist/package.json +44 -0
  4. package/dist/src/index.d.ts +15 -0
  5. package/dist/src/index.js +29 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/lib/ActiveWebhooks.d.ts +59 -0
  8. package/dist/src/lib/ActiveWebhooks.js +184 -0
  9. package/dist/src/lib/ActiveWebhooks.js.map +1 -0
  10. package/dist/src/lib/ActiveWorkflows.d.ts +58 -0
  11. package/dist/src/lib/ActiveWorkflows.js +244 -0
  12. package/dist/src/lib/ActiveWorkflows.js.map +1 -0
  13. package/dist/src/lib/BinaryDataManager/FileSystem.d.ts +26 -0
  14. package/dist/src/lib/BinaryDataManager/FileSystem.js +179 -0
  15. package/dist/src/lib/BinaryDataManager/FileSystem.js.map +1 -0
  16. package/dist/src/lib/BinaryDataManager/index.d.ts +21 -0
  17. package/dist/src/lib/BinaryDataManager/index.js +146 -0
  18. package/dist/src/lib/BinaryDataManager/index.js.map +1 -0
  19. package/dist/src/lib/ChangeCase.d.ts +9 -0
  20. package/dist/src/lib/ChangeCase.js +43 -0
  21. package/dist/src/lib/ChangeCase.js.map +1 -0
  22. package/dist/src/lib/Constants.d.ts +14 -0
  23. package/dist/src/lib/Constants.js +19 -0
  24. package/dist/src/lib/Constants.js.map +1 -0
  25. package/dist/src/lib/Credentials.d.ts +27 -0
  26. package/dist/src/lib/Credentials.js +89 -0
  27. package/dist/src/lib/Credentials.js.map +1 -0
  28. package/dist/src/lib/FileSystem.d.ts +26 -0
  29. package/dist/src/lib/FileSystem.js +179 -0
  30. package/dist/src/lib/FileSystem.js.map +1 -0
  31. package/dist/src/lib/InputConnectionDataLegacy.d.ts +2 -0
  32. package/dist/src/lib/InputConnectionDataLegacy.js +79 -0
  33. package/dist/src/lib/InputConnectionDataLegacy.js.map +1 -0
  34. package/dist/src/lib/Interfaces.d.ts +148 -0
  35. package/dist/src/lib/Interfaces.js +3 -0
  36. package/dist/src/lib/Interfaces.js.map +1 -0
  37. package/dist/src/lib/LoadNodeParameterOptions.d.ts +39 -0
  38. package/dist/src/lib/LoadNodeParameterOptions.js +150 -0
  39. package/dist/src/lib/LoadNodeParameterOptions.js.map +1 -0
  40. package/dist/src/lib/NodeExecuteFunctions.d.ts +226 -0
  41. package/dist/src/lib/NodeExecuteFunctions.js +2483 -0
  42. package/dist/src/lib/NodeExecuteFunctions.js.map +1 -0
  43. package/dist/src/lib/NodesLoader/constants.d.ts +5 -0
  44. package/dist/src/lib/NodesLoader/constants.js +106 -0
  45. package/dist/src/lib/NodesLoader/constants.js.map +1 -0
  46. package/dist/src/lib/NodesLoader/custom-directory-loader.d.ts +9 -0
  47. package/dist/src/lib/NodesLoader/custom-directory-loader.js +36 -0
  48. package/dist/src/lib/NodesLoader/custom-directory-loader.js.map +1 -0
  49. package/dist/src/lib/NodesLoader/directory-loader.d.ts +66 -0
  50. package/dist/src/lib/NodesLoader/directory-loader.js +325 -0
  51. package/dist/src/lib/NodesLoader/directory-loader.js.map +1 -0
  52. package/dist/src/lib/NodesLoader/index.d.ts +5 -0
  53. package/dist/src/lib/NodesLoader/index.js +12 -0
  54. package/dist/src/lib/NodesLoader/index.js.map +1 -0
  55. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.d.ts +7 -0
  56. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js +52 -0
  57. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js.map +1 -0
  58. package/dist/src/lib/NodesLoader/load-class-in-isolation.d.ts +1 -0
  59. package/dist/src/lib/NodesLoader/load-class-in-isolation.js +22 -0
  60. package/dist/src/lib/NodesLoader/load-class-in-isolation.js.map +1 -0
  61. package/dist/src/lib/NodesLoader/package-directory-loader.d.ts +17 -0
  62. package/dist/src/lib/NodesLoader/package-directory-loader.js +100 -0
  63. package/dist/src/lib/NodesLoader/package-directory-loader.js.map +1 -0
  64. package/dist/src/lib/NodesLoader/types.d.ts +14 -0
  65. package/dist/src/lib/NodesLoader/types.js +3 -0
  66. package/dist/src/lib/NodesLoader/types.js.map +1 -0
  67. package/dist/src/lib/UserSettings.d.ts +80 -0
  68. package/dist/src/lib/UserSettings.js +261 -0
  69. package/dist/src/lib/UserSettings.js.map +1 -0
  70. package/dist/src/lib/WorkflowExecute.d.ts +53 -0
  71. package/dist/src/lib/WorkflowExecute.js +835 -0
  72. package/dist/src/lib/WorkflowExecute.js.map +1 -0
  73. package/dist/src/lib/index.d.ts +21 -0
  74. package/dist/src/lib/index.js +146 -0
  75. package/dist/src/lib/index.js.map +1 -0
  76. package/dist/src/utils/crypto.d.ts +1 -0
  77. package/dist/src/utils/crypto.js +8 -0
  78. package/dist/src/utils/crypto.js.map +1 -0
  79. package/eslint.config.js +19 -0
  80. package/jest.config.ts +10 -0
  81. package/package.json +44 -0
  82. package/project.json +19 -0
  83. package/src/index.ts +27 -0
  84. package/src/lib/ActiveWebhooks.ts +245 -0
  85. package/src/lib/ActiveWorkflows.ts +304 -0
  86. package/src/lib/BinaryDataManager/FileSystem.ts +214 -0
  87. package/src/lib/BinaryDataManager/index.ts +187 -0
  88. package/src/lib/ChangeCase.ts +45 -0
  89. package/src/lib/Constants.ts +16 -0
  90. package/src/lib/Credentials.ts +108 -0
  91. package/src/lib/FileSystem.ts +214 -0
  92. package/src/lib/InputConnectionDataLegacy.ts +123 -0
  93. package/src/lib/Interfaces.ts +338 -0
  94. package/src/lib/LoadNodeParameterOptions.ts +235 -0
  95. package/src/lib/NodeExecuteFunctions.ts +3704 -0
  96. package/src/lib/NodesLoader/constants.ts +112 -0
  97. package/src/lib/NodesLoader/custom-directory-loader.ts +31 -0
  98. package/src/lib/NodesLoader/directory-loader.ts +458 -0
  99. package/src/lib/NodesLoader/index.ts +5 -0
  100. package/src/lib/NodesLoader/lazy-package-directory-loader.ts +55 -0
  101. package/src/lib/NodesLoader/load-class-in-isolation.ts +19 -0
  102. package/src/lib/NodesLoader/package-directory-loader.ts +107 -0
  103. package/src/lib/NodesLoader/types.ts +14 -0
  104. package/src/lib/UserSettings.ts +292 -0
  105. package/src/lib/WorkflowExecute.ts +1108 -0
  106. package/src/lib/index.ts +187 -0
  107. package/src/utils/crypto.ts +5 -0
  108. package/tests/Credentials.test.ts +88 -0
  109. package/tests/Helpers.ts +808 -0
  110. package/tests/WorkflowExecute.test.ts +1242 -0
  111. package/tsconfig.json +42 -0
  112. package/tsconfig.lib.json +10 -0
  113. 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
+ }