@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,45 @@
1
+ export class ChangeCase {
2
+ private static transformInput(input: string): string[] {
3
+ return input
4
+ .split(/[^a-zA-Z0-9]+/) // Split by non-alphanumeric characters
5
+ .filter((word) => word.length > 0) // Remove empty segments
6
+ .map((word) => word[0].toUpperCase() + word.slice(1).toLowerCase()); // Capitalize first letter and lowercase the rest
7
+ }
8
+
9
+ static toCamelCase(str: string): string {
10
+ const words = ChangeCase.transformInput(str);
11
+ return words[0].toLowerCase() + words.slice(1).join('');
12
+ }
13
+
14
+ static toPascalCase(str: string): string {
15
+ const words = str
16
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // insert space before capital letters in camelCase
17
+ .split(/[^a-zA-Z0-9]+|[\s]+/) // split by non-alphanumerics and added spaces
18
+ .filter(word => word.length > 0)
19
+ .map(word => word[0].toUpperCase() + word.slice(1).toLowerCase());
20
+
21
+ return words.join('');
22
+ }
23
+
24
+ static toSnakeCase(str: string): string {
25
+ return str
26
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
27
+ .replace(/[\W_]+/g, '_')
28
+ .toLowerCase();
29
+ }
30
+
31
+ static toCapitalCase(str: string): string {
32
+ return str.replace(/\b\w/g, (chr) => chr.toUpperCase());
33
+ }
34
+
35
+ static toParamCase(str: string): string {
36
+ return str
37
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
38
+ .replace(/[\W_]+/g, '-')
39
+ .toLowerCase();
40
+ }
41
+
42
+ static noCase(str: string): string {
43
+ return str.replace(/[^\w\s]/g, '').toLowerCase();
44
+ }
45
+ }
@@ -0,0 +1,16 @@
1
+ const { NODE_ENV } = process.env;
2
+ export const inProduction = NODE_ENV === 'production';
3
+ export const inDevelopment = !NODE_ENV || NODE_ENV === 'development';
4
+ export const inTest = NODE_ENV === 'test';
5
+
6
+ export const BINARY_ENCODING = 'base64';
7
+ export const CUSTOM_EXTENSION_ENV = 'N8N_CUSTOM_EXTENSIONS';
8
+ export const ENCRYPTION_KEY_ENV_OVERWRITE = 'FSAI_FLOW_ENCRYPTION_KEY';
9
+ export const EXTENSIONS_SUBDIRECTORY = 'custom';
10
+ export const USER_FOLDER_ENV_OVERWRITE = 'N8N_USER_FOLDER';
11
+ export const USER_SETTINGS_FILE_NAME = 'config';
12
+ export const USER_SETTINGS_SUBFOLDER = '.n8n';
13
+ export const PLACEHOLDER_EMPTY_EXECUTION_ID = '__UNKOWN__';
14
+ export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';
15
+ export const TUNNEL_SUBDOMAIN_ENV = 'N8N_TUNNEL_SUBDOMAIN';
16
+ export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
@@ -0,0 +1,108 @@
1
+ import {
2
+ CredentialInformation,
3
+ ICredentialDataDecryptedObject,
4
+ ICredentials,
5
+ ICredentialsEncrypted,
6
+ } from '@fsai-flow/workflow';
7
+
8
+ import { AES, enc } from 'crypto-js';
9
+
10
+ export class Credentials extends ICredentials {
11
+ /**
12
+ * Returns if the given nodeType has access to data
13
+ */
14
+ hasNodeAccess(nodeType: string): boolean {
15
+ // eslint-disable-next-line no-restricted-syntax
16
+ for (const accessData of this.nodesAccess) {
17
+ if (accessData.nodeType === nodeType) {
18
+ return true;
19
+ }
20
+ }
21
+
22
+ return false;
23
+ }
24
+
25
+ /**
26
+ * Sets new credential object
27
+ */
28
+ setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void {
29
+ this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
30
+ }
31
+
32
+ /**
33
+ * Sets new credentials for given key
34
+ */
35
+ setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void {
36
+ let fullData;
37
+ try {
38
+ fullData = this.getData(encryptionKey);
39
+ } catch (e) {
40
+ fullData = {};
41
+ }
42
+
43
+ fullData[key] = data;
44
+
45
+ return this.setData(fullData, encryptionKey);
46
+ }
47
+
48
+ /**
49
+ * Returns the decrypted credential object
50
+ */
51
+ getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
52
+ if (nodeType && !this.hasNodeAccess(nodeType)) {
53
+ throw new Error(
54
+ `The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`,
55
+ );
56
+ }
57
+
58
+ if (this.data === undefined) {
59
+ throw new Error('No data is set so nothing can be returned.');
60
+ }
61
+
62
+ const decryptedData = AES.decrypt(this.data, encryptionKey);
63
+
64
+ try {
65
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
66
+ return JSON.parse(decryptedData.toString(enc.Utf8));
67
+ } catch (e) {
68
+ throw new Error(
69
+ 'Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
70
+ );
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Returns the decrypted credentials for given key
76
+ */
77
+ getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation {
78
+ const fullData = this.getData(encryptionKey, nodeType);
79
+
80
+ if (fullData === null) {
81
+ throw new Error(`No data was set.`);
82
+ }
83
+
84
+ // eslint-disable-next-line no-prototype-builtins
85
+ if (!fullData.hasOwnProperty(key)) {
86
+ throw new Error(`No data for key "${key}" exists.`);
87
+ }
88
+
89
+ return fullData[key];
90
+ }
91
+
92
+ /**
93
+ * Returns the encrypted credentials to be saved
94
+ */
95
+ getDataToSave(): ICredentialsEncrypted {
96
+ if (this.data === undefined) {
97
+ throw new Error(`No credentials were set to save.`);
98
+ }
99
+
100
+ return {
101
+ id: this.id,
102
+ name: this.name,
103
+ type: this.type,
104
+ data: this.data,
105
+ nodesAccess: this.nodesAccess,
106
+ };
107
+ }
108
+ }
@@ -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,123 @@
1
+ import {
2
+ INode,
3
+ INodeType,
4
+ IExecuteFunctions,
5
+ ISupplyDataFunctions,
6
+ INodeInputConfiguration,
7
+ INodeTypeDescription,
8
+ Workflow,
9
+ SupplyData,
10
+ NodeConnectionType,
11
+ NodeOperationError,
12
+ IWebhookFunctions,
13
+ } from '@fsai-flow/workflow';
14
+
15
+ export async function getInputConnectionDataLegacy(
16
+ this: ISupplyDataFunctions | IWebhookFunctions,
17
+ workflow: Workflow,
18
+ connectionType: NodeConnectionType,
19
+ itemIndex: number,
20
+ nodeTypeData: INodeType
21
+ ): Promise<unknown> {
22
+ const parentNode = this.getNode();
23
+
24
+ const nodeInputs = getNodeInputs(workflow, parentNode, nodeTypeData.description).map(
25
+ (input) => (typeof input === 'string' ? { type: input } : input),
26
+ ) as INodeInputConfiguration[];
27
+ const inputConfiguration = nodeInputs.find((input) => input.type === connectionType);
28
+ if (inputConfiguration === undefined) {
29
+ throw new NodeOperationError(
30
+ parentNode,
31
+ `Node does not have input of type`,
32
+ {
33
+ description: `Node ${parentNode.name} does not have input of type ${connectionType}`,
34
+ }
35
+ );
36
+ }
37
+
38
+ const connectedNodes = getConnectedNodes(workflow, parentNode, connectionType);
39
+ if (connectedNodes.length === 0) {
40
+ if (inputConfiguration.required) {
41
+ throw new NodeOperationError(
42
+ parentNode,
43
+ `A ${inputConfiguration?.displayName ?? connectionType} sub-node must be connected and enabled`,
44
+ );
45
+ }
46
+ return inputConfiguration.maxConnections === 1 ? undefined : [];
47
+ }
48
+
49
+ if (
50
+ inputConfiguration.maxConnections !== undefined &&
51
+ connectedNodes.length > inputConfiguration.maxConnections
52
+ ) {
53
+ throw new NodeOperationError(
54
+ parentNode,
55
+ `Only ${inputConfiguration.maxConnections} ${connectionType} sub-nodes are/is allowed to be connected`,
56
+ );
57
+ }
58
+
59
+ const nodes: SupplyData[] = [];
60
+ for (const connectedNode of connectedNodes) {
61
+ const connectedNodeType = workflow.nodeTypes.getByNameAndVersion(
62
+ connectedNode.type,
63
+ connectedNode.typeVersion,
64
+ );
65
+
66
+ if (!connectedNodeType) {
67
+ continue;
68
+ }
69
+
70
+ if (connectedNodeType && !connectedNodeType.supplyData) {
71
+ throw new NodeOperationError(connectedNode, 'Node does not have a `supplyData` method defined', {
72
+ itemIndex,
73
+ });
74
+ } else {
75
+ try {
76
+ const supplyData = await connectedNodeType.supplyData?.call(this, itemIndex);
77
+ if (supplyData) {
78
+ nodes.push(supplyData);
79
+ }
80
+ } catch (error: any) {
81
+ throw new NodeOperationError(connectedNode, `Error in sub-node ${connectedNode.name}`, {
82
+ itemIndex,
83
+ description: error?.message ?? 'Unknown error',
84
+ });
85
+ }
86
+ }
87
+ }
88
+
89
+ return inputConfiguration.maxConnections === 1
90
+ ? (nodes || [])[0]?.response
91
+ : nodes.map((node) => node.response);
92
+ }
93
+
94
+ function getNodeInputs(
95
+ workflow: Workflow,
96
+ node: INode,
97
+ nodeTypeData: INodeTypeDescription,
98
+ ): Array<NodeConnectionType | INodeInputConfiguration> {
99
+ if (Array.isArray(nodeTypeData?.inputs)) {
100
+ return nodeTypeData.inputs as Array<NodeConnectionType | INodeInputConfiguration>;
101
+ }
102
+
103
+ // Calculate the outputs dynamically
104
+ try {
105
+ return (workflow.expression.getSimpleParameterValue(
106
+ node,
107
+ nodeTypeData.inputs,
108
+ 'internal',
109
+ {},
110
+ ) || []) as NodeConnectionType[];
111
+ } catch (e) {
112
+ console.warn('Could not calculate inputs dynamically for node: ', node.name);
113
+ return [];
114
+ }
115
+ }
116
+
117
+ function getConnectedNodes(workflow: Workflow, node: INode, connectionType: NodeConnectionType) {
118
+ return workflow
119
+ .getParentNodes(node.name, connectionType, 1)
120
+ .map((nodeName) => workflow.getNode(nodeName))
121
+ .filter((node) => !!node)
122
+ .filter((node) => node.disabled !== true);
123
+ }