@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,112 @@
1
+ import type { INodeProperties } from '@fsai-flow/workflow';
2
+ import { cronNodeOptions } from '@fsai-flow/workflow';
3
+
4
+ export const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
5
+
6
+ export const commonPollingParameters: INodeProperties[] = [
7
+ {
8
+ displayName: 'Poll Times',
9
+ name: 'pollTimes',
10
+ type: 'fixedCollection',
11
+ typeOptions: {
12
+ multipleValues: true,
13
+ multipleValueButtonText: 'Add Poll Time',
14
+ },
15
+ default: { item: [{ mode: 'everyMinute' }] },
16
+ description: 'Time at which polling should occur',
17
+ placeholder: 'Add Poll Time',
18
+ options: cronNodeOptions,
19
+ },
20
+ ];
21
+
22
+ export const commonCORSParameters: INodeProperties[] = [
23
+ {
24
+ displayName: 'Allowed Origins (CORS)',
25
+ name: 'allowedOrigins',
26
+ type: 'string',
27
+ default: '*',
28
+ description:
29
+ 'Comma-separated list of URLs allowed for cross-origin non-preflight requests. Use * (default) to allow all origins.',
30
+ },
31
+ ];
32
+
33
+ export const commonDeclarativeNodeOptionParameters: INodeProperties = {
34
+ displayName: 'Request Options',
35
+ name: 'requestOptions',
36
+ type: 'collection',
37
+ isNodeSetting: true,
38
+ placeholder: 'Add Option',
39
+ default: {},
40
+ options: [
41
+ {
42
+ displayName: 'Batching',
43
+ name: 'batching',
44
+ placeholder: 'Add Batching',
45
+ type: 'fixedCollection',
46
+ typeOptions: {
47
+ multipleValues: false,
48
+ },
49
+ default: {
50
+ batch: {},
51
+ },
52
+ options: [
53
+ {
54
+ displayName: 'Batching',
55
+ name: 'batch',
56
+ values: [
57
+ {
58
+ displayName: 'Items per Batch',
59
+ name: 'batchSize',
60
+ type: 'number',
61
+ typeOptions: {
62
+ minValue: -1,
63
+ },
64
+ default: 50,
65
+ description:
66
+ 'Input will be split in batches to throttle requests. -1 for disabled. 0 will be treated as 1.',
67
+ },
68
+ {
69
+ displayName: 'Batch Interval (ms)',
70
+ name: 'batchInterval',
71
+ type: 'number',
72
+ typeOptions: {
73
+ minValue: 0,
74
+ },
75
+ default: 1000,
76
+ description: 'Time (in milliseconds) between each batch of requests. 0 for disabled.',
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ {
83
+ displayName: 'Ignore SSL Issues (Insecure)',
84
+ name: 'allowUnauthorizedCerts',
85
+ type: 'boolean',
86
+ noDataExpression: true,
87
+ default: false,
88
+ description:
89
+ 'Whether to accept the response even if SSL certificate validation is not possible',
90
+ },
91
+ {
92
+ displayName: 'Proxy',
93
+ name: 'proxy',
94
+ type: 'string',
95
+ default: '',
96
+ placeholder: 'e.g. http://myproxy:3128',
97
+ description:
98
+ 'HTTP proxy to use. If authentication is required it can be defined as follow: http://username:password@myproxy:3128',
99
+ },
100
+ {
101
+ displayName: 'Timeout',
102
+ name: 'timeout',
103
+ type: 'number',
104
+ typeOptions: {
105
+ minValue: 1,
106
+ },
107
+ default: 10000,
108
+ description:
109
+ 'Time in ms to wait for the server to send response headers (and start the response body) before aborting the request',
110
+ },
111
+ ],
112
+ };
@@ -0,0 +1,31 @@
1
+ import glob from 'fast-glob';
2
+
3
+ import { DirectoryLoader } from './directory-loader';
4
+
5
+ /**
6
+ * Loader for source files of nodes and credentials located in a custom dir,
7
+ * e.g. `~/.n8n/custom`
8
+ */
9
+ export class CustomDirectoryLoader extends DirectoryLoader {
10
+ packageName = 'CUSTOM';
11
+
12
+ override async loadAll() {
13
+ const nodes = await glob('**/*.node.js', {
14
+ cwd: this.directory,
15
+ absolute: true,
16
+ });
17
+
18
+ for (const nodePath of nodes) {
19
+ this.loadNodeFromFile(nodePath);
20
+ }
21
+
22
+ const credentials = await glob('**/*.credentials.js', {
23
+ cwd: this.directory,
24
+ absolute: true,
25
+ });
26
+
27
+ for (const credentialPath of credentials) {
28
+ this.loadCredentialFromFile(credentialPath);
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,458 @@
1
+ import uniqBy from 'lodash/uniqBy';
2
+ import type {
3
+ CodexData,
4
+ DocumentationLink,
5
+ ICredentialType,
6
+ ICredentialTypeData,
7
+ INodeCredentialDescription,
8
+ INodePropertyOptions,
9
+ INodeType,
10
+ INodeTypeBaseDescription,
11
+ INodeTypeData,
12
+ INodeTypeDescription,
13
+ INodeTypeNameVersion,
14
+ IVersionedNodeType,
15
+ KnownNodesAndCredentials,
16
+ } from '@fsai-flow/workflow';
17
+ import { isSubNodeType } from '@fsai-flow/workflow';
18
+ import * as path from 'path';
19
+
20
+ import {
21
+ commonCORSParameters,
22
+ commonDeclarativeNodeOptionParameters,
23
+ commonPollingParameters,
24
+ CUSTOM_NODES_CATEGORY,
25
+ } from './constants';
26
+ import { loadClassInIsolation } from './load-class-in-isolation';
27
+
28
+ function toJSON(this: ICredentialType) {
29
+ return {
30
+ ...this,
31
+ authenticate:
32
+ typeof this.authenticate === 'function' ? {} : this.authenticate,
33
+ };
34
+ }
35
+
36
+ type Codex = {
37
+ categories: string[];
38
+ subcategories: { [subcategory: string]: string[] };
39
+ resources: {
40
+ primaryDocumentation: DocumentationLink[];
41
+ credentialDocumentation: DocumentationLink[];
42
+ };
43
+ alias: string[];
44
+ };
45
+
46
+ export type Types = {
47
+ nodes: INodeTypeBaseDescription[];
48
+ credentials: ICredentialType[];
49
+ };
50
+
51
+ /**
52
+ * Base class for loading n8n nodes and credentials from a directory.
53
+ * Handles the common functionality for resolving paths, loading classes, and managing node and credential types.
54
+ */
55
+ export abstract class DirectoryLoader {
56
+ isLazyLoaded = false;
57
+
58
+ // Another way of keeping track of the names and versions of a node. This
59
+ // seems to only be used by the installedPackages repository
60
+ loadedNodes: INodeTypeNameVersion[] = [];
61
+
62
+ // Stores the loaded descriptions and sourcepaths
63
+ nodeTypes: INodeTypeData = {};
64
+
65
+ credentialTypes: ICredentialTypeData = {};
66
+
67
+ // Stores the location and classnames of the nodes and credentials that are
68
+ // loaded; used to actually load the files in lazy-loading scenario.
69
+ known: KnownNodesAndCredentials = { nodes: {}, credentials: {} };
70
+
71
+ // Stores the different versions with their individual descriptions
72
+ types: Types = { nodes: [], credentials: [] };
73
+
74
+ readonly nodesByCredential: Record<string, string[]> = {};
75
+
76
+ constructor(
77
+ readonly directory: string,
78
+ protected excludeNodes: string[] = [],
79
+ protected includeNodes: string[] = [],
80
+ ) {}
81
+
82
+ abstract packageName: string;
83
+
84
+ abstract loadAll(): Promise<void>;
85
+
86
+ reset() {
87
+ this.loadedNodes = [];
88
+ this.nodeTypes = {};
89
+ this.credentialTypes = {};
90
+ this.known = { nodes: {}, credentials: {} };
91
+ this.types = { nodes: [], credentials: [] };
92
+ }
93
+
94
+ protected resolvePath(file: string) {
95
+ return path.resolve(this.directory, file);
96
+ }
97
+
98
+ private loadClass<T>(sourcePath: string) {
99
+ const filePath = this.resolvePath(sourcePath);
100
+ const [className] = path.parse(sourcePath).name.split('.');
101
+ try {
102
+ return loadClassInIsolation<T>(filePath, className);
103
+ } catch (error) {
104
+ throw error instanceof TypeError
105
+ ? new Error(
106
+ 'Class could not be found. Please check if the class is named correctly.',
107
+ )
108
+ : error;
109
+ }
110
+ }
111
+
112
+ /** Loads a nodes class from a file, fixes icons, and augments the codex */
113
+ loadNodeFromFile(filePath: string) {
114
+ const tempNode = this.loadClass<INodeType | IVersionedNodeType>(filePath);
115
+ this.addCodex(tempNode, filePath);
116
+
117
+ const nodeType = tempNode.description.name;
118
+
119
+ if (this.includeNodes.length && !this.includeNodes.includes(nodeType)) {
120
+ return;
121
+ }
122
+
123
+ if (this.excludeNodes.includes(nodeType)) {
124
+ return;
125
+ }
126
+
127
+ this.fixIconPaths(tempNode.description, filePath);
128
+
129
+ let nodeVersion = 1;
130
+ if ('nodeVersions' in tempNode) {
131
+ for (const versionNode of Object.values(tempNode.nodeVersions)) {
132
+ this.fixIconPaths(versionNode.description, filePath);
133
+ }
134
+
135
+ for (const version of Object.values(tempNode.nodeVersions)) {
136
+ this.addLoadOptionsMethods(version);
137
+ this.applySpecialNodeParameters(version);
138
+ }
139
+
140
+ const currentVersionNode = tempNode.nodeVersions[tempNode.currentVersion];
141
+ this.addCodex(currentVersionNode, filePath);
142
+ nodeVersion = tempNode.currentVersion;
143
+
144
+ if (currentVersionNode.hasOwnProperty('executeSingle')) {
145
+ throw new Error(
146
+ '"executeSingle" has been removed. Please update the code of this node to use "execute" instead.',
147
+ );
148
+ }
149
+ } else {
150
+ this.addLoadOptionsMethods(tempNode);
151
+ this.applySpecialNodeParameters(tempNode);
152
+
153
+ // Short renaming to avoid type issues
154
+ nodeVersion = Array.isArray(tempNode.description.version)
155
+ ? tempNode.description.version.slice(-1)[0]
156
+ : tempNode.description.version;
157
+ }
158
+
159
+ this.known.nodes[nodeType] = {
160
+ className: tempNode.constructor.name,
161
+ sourcePath: filePath,
162
+ };
163
+
164
+ this.nodeTypes[nodeType] = {
165
+ type: tempNode,
166
+ sourcePath: filePath,
167
+ };
168
+
169
+ this.loadedNodes.push({
170
+ name: nodeType,
171
+ version: nodeVersion,
172
+ });
173
+
174
+ this.getVersionedNodeTypeAll(tempNode).forEach(({ description }) => {
175
+ this.types.nodes.push(description);
176
+ });
177
+
178
+ for (const credential of this.getCredentialsForNode(tempNode)) {
179
+ if (!this.nodesByCredential[credential.name]) {
180
+ this.nodesByCredential[credential.name] = [];
181
+ }
182
+ this.nodesByCredential[credential.name].push(nodeType);
183
+ }
184
+ }
185
+
186
+ getNode(nodeType: string) {
187
+ const {
188
+ nodeTypes,
189
+ known: { nodes: knownNodes },
190
+ } = this;
191
+ if (!(nodeType in nodeTypes) && nodeType in knownNodes) {
192
+ const { sourcePath } = knownNodes[nodeType];
193
+ this.loadNodeFromFile(sourcePath);
194
+ }
195
+
196
+ if (nodeType in nodeTypes) {
197
+ return nodeTypes[nodeType];
198
+ }
199
+
200
+ throw new Error(`Unrecognized node type: ${nodeType}`);
201
+ }
202
+
203
+ /** Loads a credential class from a file, and fixes icons */
204
+ loadCredentialFromFile(filePath: string): void {
205
+ const tempCredential = this.loadClass<ICredentialType>(filePath);
206
+ // Add serializer method "toJSON" to the class so that authenticate method (if defined)
207
+ // gets mapped to the authenticate attribute before it is sent to the client.
208
+ // The authenticate property is used by the client to decide whether or not to
209
+ // include the credential type in the predefined credentials (HTTP node)
210
+ Object.assign(tempCredential, { toJSON });
211
+
212
+ this.fixIconPaths(tempCredential, filePath);
213
+
214
+ const credentialType = tempCredential.name;
215
+ this.known.credentials[credentialType] = {
216
+ className: tempCredential.constructor.name,
217
+ sourcePath: filePath,
218
+ extends: tempCredential.extends,
219
+ supportedNodes: this.nodesByCredential[credentialType],
220
+ };
221
+
222
+ this.credentialTypes[credentialType] = {
223
+ type: tempCredential,
224
+ sourcePath: filePath,
225
+ };
226
+
227
+ this.types.credentials.push(tempCredential);
228
+ }
229
+
230
+ getCredential(credentialType: string) {
231
+ const {
232
+ credentialTypes,
233
+ known: { credentials: knownCredentials },
234
+ } = this;
235
+ if (
236
+ !(credentialType in credentialTypes) &&
237
+ credentialType in knownCredentials
238
+ ) {
239
+ const { sourcePath } = knownCredentials[credentialType];
240
+ this.loadCredentialFromFile(sourcePath);
241
+ }
242
+
243
+ if (credentialType in credentialTypes) {
244
+ return credentialTypes[credentialType];
245
+ }
246
+
247
+ throw new Error(`Unrecognized credential type: ${credentialType}`);
248
+ }
249
+
250
+ /**
251
+ * Returns an array of credential descriptions that are supported by a node.
252
+ * For versioned nodes, combines and deduplicates credentials from all versions.
253
+ */
254
+ getCredentialsForNode(
255
+ object: IVersionedNodeType | INodeType,
256
+ ): INodeCredentialDescription[] {
257
+ if ('nodeVersions' in object) {
258
+ const credentials = Object.values(object.nodeVersions).flatMap(
259
+ ({ description }) => description.credentials ?? [],
260
+ );
261
+ return uniqBy(credentials, 'name');
262
+ }
263
+ return object.description.credentials ?? [];
264
+ }
265
+
266
+ /**
267
+ * Returns an array of all versions of a node type.
268
+ * For non-versioned nodes, returns an array with just that node.
269
+ * For versioned nodes, returns all available versions.
270
+ */
271
+ getVersionedNodeTypeAll(object: IVersionedNodeType | INodeType): INodeType[] {
272
+ if ('nodeVersions' in object) {
273
+ const nodeVersions = Object.values(object.nodeVersions).map((element) => {
274
+ element.description.name = object.description.name;
275
+ element.description.codex = object.description.codex;
276
+ return element;
277
+ });
278
+ return uniqBy(nodeVersions.reverse(), (node) => {
279
+ const { version } = node.description;
280
+ return Array.isArray(version) ? version.join(',') : version.toString();
281
+ });
282
+ }
283
+ return [object];
284
+ }
285
+
286
+ /**
287
+ * Retrieves `categories`, `subcategories` and alias (if defined)
288
+ * from the codex data for the node at the given file path.
289
+ */
290
+ private getCodex(filePath: string): CodexData {
291
+ const codexFilePath = this.resolvePath(`${filePath}on`); // .js to .json
292
+
293
+ const {
294
+ categories,
295
+ subcategories,
296
+ resources: { primaryDocumentation, credentialDocumentation },
297
+ alias,
298
+ } = module.require(codexFilePath) as Codex;
299
+
300
+ return {
301
+ ...(categories && { categories }),
302
+ ...(subcategories && { subcategories }),
303
+ ...(alias && { alias }),
304
+ resources: {
305
+ primaryDocumentation,
306
+ credentialDocumentation,
307
+ },
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Adds a node codex `categories` and `subcategories` (if defined)
313
+ * to a node description `codex` property.
314
+ */
315
+ private addCodex(node: INodeType | IVersionedNodeType, filePath: string) {
316
+ const isCustom = this.packageName === 'CUSTOM';
317
+ try {
318
+ let codex;
319
+
320
+ if (!isCustom) {
321
+ codex = node.description.codex;
322
+ }
323
+
324
+ if (codex === undefined) {
325
+ codex = this.getCodex(filePath);
326
+ }
327
+
328
+ if (isCustom) {
329
+ codex.categories = codex.categories
330
+ ? codex.categories.concat(CUSTOM_NODES_CATEGORY)
331
+ : [CUSTOM_NODES_CATEGORY];
332
+ }
333
+
334
+ node.description.codex = codex;
335
+ } catch {
336
+ console.debug(`No codex available for: ${node.description.name}`);
337
+
338
+ if (isCustom) {
339
+ node.description.codex = {
340
+ categories: [CUSTOM_NODES_CATEGORY],
341
+ };
342
+ }
343
+ }
344
+ }
345
+
346
+ private addLoadOptionsMethods(node: INodeType) {
347
+ if (node?.methods?.loadOptions) {
348
+ node.description.__loadOptionsMethods = Object.keys(
349
+ node.methods.loadOptions,
350
+ );
351
+ }
352
+ }
353
+
354
+ private applySpecialNodeParameters(nodeType: INodeType): void {
355
+ const { properties, polling, supportsCORS } = nodeType.description;
356
+ if (polling) {
357
+ properties.unshift(...commonPollingParameters);
358
+ }
359
+ if (nodeType.webhook && supportsCORS) {
360
+ const optionsProperty = properties.find(({ name }) => name === 'options');
361
+ if (optionsProperty)
362
+ optionsProperty.options = [
363
+ ...commonCORSParameters,
364
+ ...(optionsProperty.options as INodePropertyOptions[]),
365
+ ];
366
+ else properties.push(...commonCORSParameters);
367
+ }
368
+
369
+ DirectoryLoader.applyDeclarativeNodeOptionParameters(nodeType);
370
+ }
371
+
372
+ private getIconPath(icon: string, filePath: string) {
373
+ const iconPath = path.join(
374
+ path.dirname(filePath),
375
+ icon.replace('file:', ''),
376
+ );
377
+ return `icons/${this.packageName}/${iconPath}`;
378
+ }
379
+
380
+ private fixIconPaths(
381
+ obj: INodeTypeDescription | INodeTypeBaseDescription | ICredentialType,
382
+ filePath: string,
383
+ ) {
384
+ const { icon } = obj;
385
+ if (!icon) return;
386
+
387
+ if (typeof icon === 'string') {
388
+ if (icon.startsWith('file:')) {
389
+ obj.iconUrl = this.getIconPath(icon, filePath);
390
+ obj.icon = undefined;
391
+ }
392
+ } else if (
393
+ icon.light.startsWith('file:') &&
394
+ icon.dark.startsWith('file:')
395
+ ) {
396
+ obj.iconUrl = {
397
+ light: this.getIconPath(icon.light, filePath),
398
+ dark: this.getIconPath(icon.dark, filePath),
399
+ };
400
+ obj.icon = undefined;
401
+ }
402
+ }
403
+
404
+ /** Augments additional `Request Options` property on declarative node-type */
405
+ static applyDeclarativeNodeOptionParameters(nodeType: INodeType): void {
406
+ if (
407
+ !!nodeType.execute ||
408
+ !!nodeType.trigger ||
409
+ !!nodeType.webhook ||
410
+ !!nodeType.description.polling ||
411
+ isSubNodeType(nodeType.description)
412
+ ) {
413
+ return;
414
+ }
415
+
416
+ const parameters = nodeType.description.properties;
417
+ if (!parameters) {
418
+ return;
419
+ }
420
+
421
+ // Was originally under "options" instead of "requestOptions" so the chance
422
+ // that that existed was quite high. With this name the chance is actually
423
+ // very low that it already exists but lets leave it in anyway to be sure.
424
+ const existingRequestOptionsIndex = parameters.findIndex(
425
+ (parameter) => parameter.name === 'requestOptions',
426
+ );
427
+ if (existingRequestOptionsIndex !== -1) {
428
+ parameters[existingRequestOptionsIndex] = {
429
+ ...commonDeclarativeNodeOptionParameters,
430
+ options: [
431
+ ...(commonDeclarativeNodeOptionParameters.options ?? []),
432
+ ...(parameters[existingRequestOptionsIndex]?.options ?? []),
433
+ ],
434
+ };
435
+
436
+ const options = parameters[existingRequestOptionsIndex]?.options;
437
+
438
+ if (options) {
439
+ options.sort((a, b) => {
440
+ if ('displayName' in a && 'displayName' in b) {
441
+ if (a.displayName < b.displayName) {
442
+ return -1;
443
+ }
444
+ if (a.displayName > b.displayName) {
445
+ return 1;
446
+ }
447
+ }
448
+
449
+ return 0;
450
+ });
451
+ }
452
+ } else {
453
+ parameters.push(commonDeclarativeNodeOptionParameters);
454
+ }
455
+
456
+ return;
457
+ }
458
+ }
@@ -0,0 +1,5 @@
1
+ export { DirectoryLoader, type Types } from './directory-loader';
2
+ export { CustomDirectoryLoader } from './custom-directory-loader';
3
+ export { PackageDirectoryLoader } from './package-directory-loader';
4
+ export { LazyPackageDirectoryLoader } from './lazy-package-directory-loader';
5
+ export type { n8n } from './types';
@@ -0,0 +1,55 @@
1
+ import { PackageDirectoryLoader } from './package-directory-loader';
2
+
3
+ /**
4
+ * This loader extends PackageDirectoryLoader to load node and credentials lazily, if possible
5
+ */
6
+ export class LazyPackageDirectoryLoader extends PackageDirectoryLoader {
7
+ override async loadAll() {
8
+ try {
9
+ this.known.nodes = await this.readJSON('dist/known/nodes.json');
10
+ this.known.credentials = await this.readJSON('dist/known/credentials.json');
11
+
12
+ this.types.nodes = await this.readJSON('dist/types/nodes.json');
13
+ this.types.credentials = await this.readJSON('dist/types/credentials.json');
14
+
15
+ if (this.includeNodes.length) {
16
+ const allowedNodes: typeof this.known.nodes = {};
17
+ for (const nodeType of this.includeNodes) {
18
+ if (nodeType in this.known.nodes) {
19
+ allowedNodes[nodeType] = this.known.nodes[nodeType];
20
+ }
21
+ }
22
+ this.known.nodes = allowedNodes;
23
+
24
+ this.types.nodes = this.types.nodes.filter((nodeType) =>
25
+ this.includeNodes.includes(nodeType.name),
26
+ );
27
+ }
28
+
29
+ if (this.excludeNodes.length) {
30
+ for (const nodeType of this.excludeNodes) {
31
+ delete this.known.nodes[nodeType];
32
+ }
33
+
34
+ this.types.nodes = this.types.nodes.filter(
35
+ (nodeType) => !this.excludeNodes.includes(nodeType.name),
36
+ );
37
+ }
38
+
39
+ console.debug(
40
+ `Lazy-loading nodes and credentials from ${this.packageJson.name}`,
41
+ {
42
+ nodes: this.types.nodes?.length ?? 0,
43
+ credentials: this.types.credentials?.length ?? 0,
44
+ },
45
+ );
46
+
47
+ this.isLazyLoaded = true;
48
+
49
+ return; // We can load nodes and credentials lazily now
50
+ } catch {
51
+ console.debug("Can't enable lazy-loading");
52
+ await super.loadAll();
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,19 @@
1
+ import { createContext, Script } from 'vm';
2
+
3
+ import { inTest } from '../Constants';
4
+
5
+ const context = createContext({ require });
6
+ export const loadClassInIsolation = <T>(filePath: string, className: string) => {
7
+ if (process.platform === 'win32') {
8
+ filePath = filePath.replace(/\\/g, '/');
9
+ }
10
+
11
+ // Note: Skip the isolation because it breaks nock mocks in tests
12
+ if (inTest) {
13
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
14
+ return new (require(filePath)[className])() as T;
15
+ } else {
16
+ const script = new Script(`new (require('${filePath}').${className})()`);
17
+ return script.runInContext(context) as T;
18
+ }
19
+ };