@fsai-flow/workflow 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/.eslintrc.json +33 -0
- package/README.md +11 -0
- package/dist/README.md +11 -0
- package/dist/package.json +42 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +33 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/Constants.d.ts +68 -0
- package/dist/src/lib/Constants.js +106 -0
- package/dist/src/lib/Constants.js.map +1 -0
- package/dist/src/lib/DeferredPromise.d.ts +6 -0
- package/dist/src/lib/DeferredPromise.js +11 -0
- package/dist/src/lib/DeferredPromise.js.map +1 -0
- package/dist/src/lib/Expression.d.ts +65 -0
- package/dist/src/lib/Expression.js +215 -0
- package/dist/src/lib/Expression.js.map +1 -0
- package/dist/src/lib/Interfaces.d.ts +1569 -0
- package/dist/src/lib/Interfaces.js +44 -0
- package/dist/src/lib/Interfaces.js.map +1 -0
- package/dist/src/lib/LoggerProxy.d.ts +9 -0
- package/dist/src/lib/LoggerProxy.js +40 -0
- package/dist/src/lib/LoggerProxy.js.map +1 -0
- package/dist/src/lib/MetadataUtils.d.ts +4 -0
- package/dist/src/lib/MetadataUtils.js +27 -0
- package/dist/src/lib/MetadataUtils.js.map +1 -0
- package/dist/src/lib/NodeErrors.d.ts +82 -0
- package/dist/src/lib/NodeErrors.js +289 -0
- package/dist/src/lib/NodeErrors.js.map +1 -0
- package/dist/src/lib/NodeHelpers.d.ts +198 -0
- package/dist/src/lib/NodeHelpers.js +1348 -0
- package/dist/src/lib/NodeHelpers.js.map +1 -0
- package/dist/src/lib/ObservableObject.d.ts +5 -0
- package/dist/src/lib/ObservableObject.js +61 -0
- package/dist/src/lib/ObservableObject.js.map +1 -0
- package/dist/src/lib/RoutingNode.d.ts +18 -0
- package/dist/src/lib/RoutingNode.js +508 -0
- package/dist/src/lib/RoutingNode.js.map +1 -0
- package/dist/src/lib/TelemetryHelpers.d.ts +3 -0
- package/dist/src/lib/TelemetryHelpers.js +69 -0
- package/dist/src/lib/TelemetryHelpers.js.map +1 -0
- package/dist/src/lib/TypeValidation.d.ts +21 -0
- package/dist/src/lib/TypeValidation.js +385 -0
- package/dist/src/lib/TypeValidation.js.map +1 -0
- package/dist/src/lib/VersionedNodeType.d.ts +9 -0
- package/dist/src/lib/VersionedNodeType.js +26 -0
- package/dist/src/lib/VersionedNodeType.js.map +1 -0
- package/dist/src/lib/Workflow.d.ts +248 -0
- package/dist/src/lib/Workflow.js +901 -0
- package/dist/src/lib/Workflow.js.map +1 -0
- package/dist/src/lib/WorkflowDataProxy.d.ts +87 -0
- package/dist/src/lib/WorkflowDataProxy.js +556 -0
- package/dist/src/lib/WorkflowDataProxy.js.map +1 -0
- package/dist/src/lib/WorkflowErrors.d.ts +9 -0
- package/dist/src/lib/WorkflowErrors.js +18 -0
- package/dist/src/lib/WorkflowErrors.js.map +1 -0
- package/dist/src/lib/WorkflowHooks.d.ts +11 -0
- package/dist/src/lib/WorkflowHooks.js +34 -0
- package/dist/src/lib/WorkflowHooks.js.map +1 -0
- package/dist/src/lib/errors/base/base.error.d.ts +30 -0
- package/dist/src/lib/errors/base/base.error.js +45 -0
- package/dist/src/lib/errors/base/base.error.js.map +1 -0
- package/dist/src/lib/errors/base/operational.error.d.ts +15 -0
- package/dist/src/lib/errors/base/operational.error.js +19 -0
- package/dist/src/lib/errors/base/operational.error.js.map +1 -0
- package/dist/src/lib/errors/error.types.d.ts +11 -0
- package/dist/src/lib/errors/error.types.js +3 -0
- package/dist/src/lib/errors/error.types.js.map +1 -0
- package/dist/src/lib/errors/index.d.ts +1 -0
- package/dist/src/lib/errors/index.js +6 -0
- package/dist/src/lib/errors/index.js.map +1 -0
- package/dist/src/lib/result.d.ts +19 -0
- package/dist/src/lib/result.js +36 -0
- package/dist/src/lib/result.js.map +1 -0
- package/dist/src/lib/utils.d.ts +50 -0
- package/dist/src/lib/utils.js +110 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/eslint.config.js +19 -0
- package/jest.config.ts +10 -0
- package/package.json +40 -0
- package/project.json +19 -0
- package/src/index.ts +33 -0
- package/src/lib/Constants.ts +124 -0
- package/src/lib/DeferredPromise.ts +14 -0
- package/src/lib/Expression.ts +375 -0
- package/src/lib/Interfaces.ts +2229 -0
- package/src/lib/LoggerProxy.ts +43 -0
- package/src/lib/MetadataUtils.ts +34 -0
- package/src/lib/NodeErrors.ts +332 -0
- package/src/lib/NodeHelpers.ts +1666 -0
- package/src/lib/ObservableObject.ts +77 -0
- package/src/lib/RoutingNode.ts +862 -0
- package/src/lib/TelemetryHelpers.ts +86 -0
- package/src/lib/TypeValidation.ts +431 -0
- package/src/lib/VersionedNodeType.ts +30 -0
- package/src/lib/Workflow.ts +1266 -0
- package/src/lib/WorkflowDataProxy.ts +708 -0
- package/src/lib/WorkflowErrors.ts +18 -0
- package/src/lib/WorkflowHooks.ts +51 -0
- package/src/lib/errors/base/base.error.ts +68 -0
- package/src/lib/errors/base/operational.error.ts +21 -0
- package/src/lib/errors/error.types.ts +14 -0
- package/src/lib/errors/index.ts +1 -0
- package/src/lib/result.ts +34 -0
- package/src/lib/utils.ts +132 -0
- package/tests/Helpers.ts +667 -0
- package/tests/NodeHelpers.test.ts +3053 -0
- package/tests/ObservableObject.test.ts +171 -0
- package/tests/RoutingNode.test.ts +1680 -0
- package/tests/Workflow.test.ts +1284 -0
- package/tests/WorkflowDataProxy.test.ts +199 -0
- package/tsconfig.json +27 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.spec.json +14 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IConnection,
|
|
3
|
+
INode,
|
|
4
|
+
INodeNameIndex,
|
|
5
|
+
INodesGraph,
|
|
6
|
+
INodeGraphItem,
|
|
7
|
+
INodesGraphResult,
|
|
8
|
+
IWorkflowBase,
|
|
9
|
+
INodeTypes,
|
|
10
|
+
} from '..';
|
|
11
|
+
|
|
12
|
+
import { getInstance as getLoggerInstance } from './LoggerProxy';
|
|
13
|
+
|
|
14
|
+
export function getNodeTypeForName(workflow: IWorkflowBase, nodeName: string): INode | undefined {
|
|
15
|
+
return workflow.nodes.find((node) => node.name === nodeName);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function generateNodesGraph(
|
|
19
|
+
workflow: IWorkflowBase,
|
|
20
|
+
nodeTypes: INodeTypes,
|
|
21
|
+
): INodesGraphResult {
|
|
22
|
+
const nodesGraph: INodesGraph = {
|
|
23
|
+
node_types: [],
|
|
24
|
+
node_connections: [],
|
|
25
|
+
nodes: {},
|
|
26
|
+
};
|
|
27
|
+
const nodeNameAndIndex: INodeNameIndex = {};
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
workflow.nodes.forEach((node: INode, index: number) => {
|
|
31
|
+
nodesGraph.node_types.push(node.type);
|
|
32
|
+
const nodeItem: INodeGraphItem = {
|
|
33
|
+
type: node.type,
|
|
34
|
+
position: node.position,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (node.type === 'n8n-nodes-base.httpRequest') {
|
|
38
|
+
try {
|
|
39
|
+
nodeItem.domain = new URL(node.parameters['url'] as string).hostname;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
nodeItem.domain = node.parameters['url'] as string;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
const nodeType = nodeTypes.getByNameAndVersion(node.type);
|
|
45
|
+
|
|
46
|
+
nodeType?.description.properties.forEach((property) => {
|
|
47
|
+
if (
|
|
48
|
+
property.name === 'operation' ||
|
|
49
|
+
property.name === 'resource' ||
|
|
50
|
+
property.name === 'mode'
|
|
51
|
+
) {
|
|
52
|
+
nodeItem[property.name] = property.default ? property.default.toString() : undefined;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
nodeItem.operation = node.parameters['operation']?.toString() ?? nodeItem.operation;
|
|
57
|
+
nodeItem.resource = node.parameters['resource']?.toString() ?? nodeItem.resource;
|
|
58
|
+
nodeItem.mode = node.parameters['mode']?.toString() ?? nodeItem.mode;
|
|
59
|
+
}
|
|
60
|
+
nodesGraph.nodes[`${index}`] = nodeItem;
|
|
61
|
+
nodeNameAndIndex[node.name] = index.toString();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const getGraphConnectionItem = (startNode: string, connectionItem: IConnection) => {
|
|
65
|
+
return { start: nodeNameAndIndex[startNode], end: nodeNameAndIndex[connectionItem.node] };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
Object.keys(workflow.connections).forEach((nodeName) => {
|
|
69
|
+
const connections = workflow.connections[nodeName];
|
|
70
|
+
Object.keys(connections).forEach((connection) => {
|
|
71
|
+
connections[connection].forEach((element) => {
|
|
72
|
+
element.forEach((element2) => {
|
|
73
|
+
nodesGraph.node_connections.push(getGraphConnectionItem(nodeName, element2));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
} catch (e) {
|
|
79
|
+
const logger = getLoggerInstance();
|
|
80
|
+
logger.warn(`Failed to generate nodes graph for workflowId: ${workflow.id as string | number}`);
|
|
81
|
+
logger.warn((e as Error).message);
|
|
82
|
+
logger.warn((e as Error).stack ?? '');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { nodeGraph: nodesGraph, nameIndices: nodeNameAndIndex };
|
|
86
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import isObject from 'lodash/isObject';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
FieldType,
|
|
6
|
+
FormFieldsParameter,
|
|
7
|
+
INodePropertyOptions,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
} from './Interfaces';
|
|
10
|
+
|
|
11
|
+
import { jsonParse } from './utils';
|
|
12
|
+
|
|
13
|
+
export const tryToParseNumber = (value: unknown): number => {
|
|
14
|
+
const isValidNumber = !isNaN(Number(value));
|
|
15
|
+
|
|
16
|
+
if (!isValidNumber) {
|
|
17
|
+
throw new Error('Failed to parse value to number');
|
|
18
|
+
}
|
|
19
|
+
return Number(value);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const tryToParseString = (value: unknown): string => {
|
|
23
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
24
|
+
if (typeof value === 'undefined') return '';
|
|
25
|
+
if (
|
|
26
|
+
typeof value === 'string' ||
|
|
27
|
+
typeof value === 'bigint' ||
|
|
28
|
+
typeof value === 'boolean' ||
|
|
29
|
+
typeof value === 'number'
|
|
30
|
+
) {
|
|
31
|
+
return value.toString();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return String(value);
|
|
35
|
+
};
|
|
36
|
+
export const tryToParseAlphanumericString = (value: unknown): string => {
|
|
37
|
+
const parsed = tryToParseString(value);
|
|
38
|
+
const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
39
|
+
if (!regex.test(parsed)) {
|
|
40
|
+
throw new Error('Value is not a valid alphanumeric string');
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
};
|
|
44
|
+
export const tryToParseBoolean = (value: unknown): value is boolean => {
|
|
45
|
+
if (typeof value === 'boolean') {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof value === 'string' && ['true', 'false'].includes(value.toLowerCase())) {
|
|
50
|
+
return value.toLowerCase() === 'true';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If value is not a empty string, try to parse it to a number
|
|
54
|
+
if (!(typeof value === 'string' && value.trim() === '')) {
|
|
55
|
+
const num = Number(value);
|
|
56
|
+
if (num === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
} else if (num === 1) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error('Failed to parse value as boolean');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const tryToParseDateTime = (value: unknown): DateTime => {
|
|
67
|
+
if (value instanceof DateTime && value.isValid) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (value instanceof Date) {
|
|
72
|
+
const fromJSDate = DateTime.fromJSDate(value);
|
|
73
|
+
if (fromJSDate.isValid) {
|
|
74
|
+
return fromJSDate;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const dateString = String(value).trim();
|
|
79
|
+
|
|
80
|
+
// Rely on luxon to parse different date formats
|
|
81
|
+
const isoDate = DateTime.fromISO(dateString, { setZone: true });
|
|
82
|
+
if (isoDate.isValid) {
|
|
83
|
+
return isoDate;
|
|
84
|
+
}
|
|
85
|
+
const httpDate = DateTime.fromHTTP(dateString, { setZone: true });
|
|
86
|
+
if (httpDate.isValid) {
|
|
87
|
+
return httpDate;
|
|
88
|
+
}
|
|
89
|
+
const rfc2822Date = DateTime.fromRFC2822(dateString, { setZone: true });
|
|
90
|
+
if (rfc2822Date.isValid) {
|
|
91
|
+
return rfc2822Date;
|
|
92
|
+
}
|
|
93
|
+
const sqlDate = DateTime.fromSQL(dateString, { setZone: true });
|
|
94
|
+
if (sqlDate.isValid) {
|
|
95
|
+
return sqlDate;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const parsedDateTime = DateTime.fromMillis(Date.parse(dateString));
|
|
99
|
+
if (parsedDateTime.isValid) {
|
|
100
|
+
return parsedDateTime;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error('Value is not a valid date');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const tryToParseTime = (value: unknown): string => {
|
|
107
|
+
const isTimeInput = /^\d{2}:\d{2}(:\d{2})?((\-|\+)\d{4})?((\-|\+)\d{1,2}(:\d{2})?)?$/s.test(
|
|
108
|
+
String(value),
|
|
109
|
+
);
|
|
110
|
+
if (!isTimeInput) {
|
|
111
|
+
throw new Error('Value is not a valid time');
|
|
112
|
+
}
|
|
113
|
+
return String(value);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const tryToParseArray = (value: unknown): unknown[] => {
|
|
117
|
+
try {
|
|
118
|
+
if (typeof value === 'object' && Array.isArray(value)) {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let parsed: unknown[];
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(String(value)) as unknown[];
|
|
125
|
+
} catch (e) {
|
|
126
|
+
parsed = JSON.parse(String(value).replace(/'/g, '"')) as unknown[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!Array.isArray(parsed)) {
|
|
130
|
+
throw new Error('Value is not a valid array');
|
|
131
|
+
}
|
|
132
|
+
return parsed;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
throw new Error('Value is not a valid array');
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const tryToParseObject = (value: unknown): object => {
|
|
139
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const o = jsonParse<object>(String(value), { acceptJSObject: true });
|
|
144
|
+
|
|
145
|
+
if (typeof o !== 'object' || Array.isArray(o)) {
|
|
146
|
+
throw new Error('Value is not a valid object');
|
|
147
|
+
}
|
|
148
|
+
return o;
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw new Error('Value is not a valid object');
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const ALLOWED_FORM_FIELDS_KEYS = [
|
|
155
|
+
'fieldLabel',
|
|
156
|
+
'fieldType',
|
|
157
|
+
'placeholder',
|
|
158
|
+
'fieldOptions',
|
|
159
|
+
'multiselect',
|
|
160
|
+
'multipleFiles',
|
|
161
|
+
'acceptFileTypes',
|
|
162
|
+
'formatDate',
|
|
163
|
+
'requiredField',
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const ALLOWED_FIELD_TYPES = [
|
|
167
|
+
'date',
|
|
168
|
+
'dropdown',
|
|
169
|
+
'email',
|
|
170
|
+
'file',
|
|
171
|
+
'number',
|
|
172
|
+
'password',
|
|
173
|
+
'text',
|
|
174
|
+
'textarea',
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
export const tryToParseJsonToFormFields = (value: unknown): FormFieldsParameter => {
|
|
178
|
+
const fields: FormFieldsParameter = [];
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const rawFields = jsonParse<Array<{ [key: string]: unknown }>>(value as string, {
|
|
182
|
+
acceptJSObject: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
for (const [index, field] of rawFields.entries()) {
|
|
186
|
+
for (const key of Object.keys(field)) {
|
|
187
|
+
if (!ALLOWED_FORM_FIELDS_KEYS.includes(key)) {
|
|
188
|
+
throw new Error(`Key '${key}' in field ${index} is not valid for form fields`);
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
key !== 'fieldOptions' &&
|
|
192
|
+
!['string', 'number', 'boolean'].includes(typeof field[key])
|
|
193
|
+
) {
|
|
194
|
+
field[key] = String(field[key]);
|
|
195
|
+
} else if (typeof field[key] === 'string') {
|
|
196
|
+
field[key] = field[key].replace(/</g, '<').replace(/>/g, '>');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (key === 'fieldType' && !ALLOWED_FIELD_TYPES.includes(field[key] as string)) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Field type '${field[key] as string}' in field ${index} is not valid for form fields`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (key === 'fieldOptions') {
|
|
206
|
+
if (Array.isArray(field[key])) {
|
|
207
|
+
field[key] = { values: field[key] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (
|
|
211
|
+
typeof field[key] !== 'object' ||
|
|
212
|
+
!(field[key] as { [key: string]: unknown })['values']
|
|
213
|
+
) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Field dropdown in field ${index} does has no 'values' property that contain an array of options`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const [optionIndex, option] of (
|
|
220
|
+
(field[key] as { [key: string]: unknown })['values'] as Array<{
|
|
221
|
+
[key: string]: { option: string };
|
|
222
|
+
}>
|
|
223
|
+
).entries()) {
|
|
224
|
+
if (Object.keys(option).length !== 1 || typeof option['option'] !== 'string') {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Field dropdown in field ${index} has an invalid option ${optionIndex}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fields.push(field as FormFieldsParameter[number]);
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error instanceof Error) throw error;
|
|
237
|
+
|
|
238
|
+
throw new Error('Value is not valid JSON');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return fields;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const getValueDescription = <T>(value: T): string => {
|
|
245
|
+
if (typeof value === 'object') {
|
|
246
|
+
if (value === null) return "'null'";
|
|
247
|
+
if (Array.isArray(value)) return 'array';
|
|
248
|
+
return 'object';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return `'${String(value)}'`;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const tryToParseUrl = (value: unknown): string => {
|
|
255
|
+
if (typeof value === 'string' && !value.includes('://')) {
|
|
256
|
+
value = `http://${value}`;
|
|
257
|
+
}
|
|
258
|
+
const urlPattern = /^(https?|ftp|file):\/\/\S+|www\.\S+/;
|
|
259
|
+
if (!urlPattern.test(String(value))) {
|
|
260
|
+
throw new Error(`The value "${String(value)}" is not a valid url.`);
|
|
261
|
+
}
|
|
262
|
+
return String(value);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const tryToParseJwt = (value: unknown): string => {
|
|
266
|
+
const error = new Error(`The value "${String(value)}" is not a valid JWT token.`);
|
|
267
|
+
|
|
268
|
+
if (!value) throw error;
|
|
269
|
+
|
|
270
|
+
const jwtPattern = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]*$/;
|
|
271
|
+
|
|
272
|
+
if (!jwtPattern.test(String(value))) throw error;
|
|
273
|
+
|
|
274
|
+
return String(value);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
type ValidateFieldTypeOptions = Partial<{
|
|
278
|
+
valueOptions: INodePropertyOptions[];
|
|
279
|
+
strict: boolean;
|
|
280
|
+
parseStrings: boolean;
|
|
281
|
+
}>;
|
|
282
|
+
|
|
283
|
+
// Validates field against the schema and tries to parse it to the correct type
|
|
284
|
+
export function validateFieldType<K extends FieldType>(
|
|
285
|
+
fieldName: string,
|
|
286
|
+
value: unknown,
|
|
287
|
+
type: K,
|
|
288
|
+
options?: ValidateFieldTypeOptions,
|
|
289
|
+
): ValidationResult<K>;
|
|
290
|
+
// eslint-disable-next-line complexity
|
|
291
|
+
export function validateFieldType(
|
|
292
|
+
fieldName: string,
|
|
293
|
+
value: unknown,
|
|
294
|
+
type: FieldType,
|
|
295
|
+
options: ValidateFieldTypeOptions = {},
|
|
296
|
+
): ValidationResult {
|
|
297
|
+
if (value === null || value === undefined) return { valid: true };
|
|
298
|
+
const strict = options.strict ?? false;
|
|
299
|
+
const valueOptions = options.valueOptions ?? [];
|
|
300
|
+
const parseStrings = options.parseStrings ?? false;
|
|
301
|
+
|
|
302
|
+
const defaultErrorMessage = `'${fieldName}' expects a ${type} but we got ${getValueDescription(value)}`;
|
|
303
|
+
switch (type.toLowerCase()) {
|
|
304
|
+
case 'string': {
|
|
305
|
+
if (!parseStrings) return { valid: true, newValue: value };
|
|
306
|
+
try {
|
|
307
|
+
if (strict && typeof value !== 'string') {
|
|
308
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
309
|
+
}
|
|
310
|
+
return { valid: true, newValue: tryToParseString(value) };
|
|
311
|
+
} catch (e) {
|
|
312
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
case 'string-alphanumeric': {
|
|
316
|
+
try {
|
|
317
|
+
return { valid: true, newValue: tryToParseAlphanumericString(value) };
|
|
318
|
+
} catch (e) {
|
|
319
|
+
return {
|
|
320
|
+
valid: false,
|
|
321
|
+
errorMessage:
|
|
322
|
+
'Value is not a valid alphanumeric string, only letters, numbers and underscore allowed',
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
case 'number': {
|
|
327
|
+
try {
|
|
328
|
+
if (strict && typeof value !== 'number') {
|
|
329
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
330
|
+
}
|
|
331
|
+
return { valid: true, newValue: tryToParseNumber(value) };
|
|
332
|
+
} catch (e) {
|
|
333
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
case 'boolean': {
|
|
337
|
+
try {
|
|
338
|
+
if (strict && typeof value !== 'boolean') {
|
|
339
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
340
|
+
}
|
|
341
|
+
return { valid: true, newValue: tryToParseBoolean(value) };
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
case 'datetime': {
|
|
347
|
+
try {
|
|
348
|
+
return { valid: true, newValue: tryToParseDateTime(value) };
|
|
349
|
+
} catch (e) {
|
|
350
|
+
const luxonDocsURL =
|
|
351
|
+
'https://moment.github.io/luxon/api-docs/index.html#datetimefromformat';
|
|
352
|
+
const errorMessage = `${defaultErrorMessage} <br/><br/> Consider using <a href="${luxonDocsURL}" target="_blank"><code>DateTime.fromFormat</code></a> to work with custom date formats.`;
|
|
353
|
+
return { valid: false, errorMessage };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
case 'time': {
|
|
357
|
+
try {
|
|
358
|
+
return { valid: true, newValue: tryToParseTime(value) };
|
|
359
|
+
} catch (e) {
|
|
360
|
+
return {
|
|
361
|
+
valid: false,
|
|
362
|
+
errorMessage: `'${fieldName}' expects time (hh:mm:(:ss)) but we got ${getValueDescription(value)}.`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
case 'object': {
|
|
367
|
+
try {
|
|
368
|
+
if (strict && !isObject(value)) {
|
|
369
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
370
|
+
}
|
|
371
|
+
return { valid: true, newValue: tryToParseObject(value) };
|
|
372
|
+
} catch (e) {
|
|
373
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
case 'array': {
|
|
377
|
+
if (strict && !Array.isArray(value)) {
|
|
378
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
return { valid: true, newValue: tryToParseArray(value) };
|
|
382
|
+
} catch (e) {
|
|
383
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
case 'options': {
|
|
387
|
+
const validOptions = valueOptions.map((option) => option.value).join(', ');
|
|
388
|
+
const isValidOption = valueOptions.some((option) => option.value === value);
|
|
389
|
+
|
|
390
|
+
if (!isValidOption) {
|
|
391
|
+
return {
|
|
392
|
+
valid: false,
|
|
393
|
+
errorMessage: `'${fieldName}' expects one of the following values: [${validOptions}] but we got ${getValueDescription(
|
|
394
|
+
value,
|
|
395
|
+
)}`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return { valid: true, newValue: value };
|
|
399
|
+
}
|
|
400
|
+
case 'url': {
|
|
401
|
+
try {
|
|
402
|
+
return { valid: true, newValue: tryToParseUrl(value) };
|
|
403
|
+
} catch (e) {
|
|
404
|
+
return { valid: false, errorMessage: defaultErrorMessage };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
case 'jwt': {
|
|
408
|
+
try {
|
|
409
|
+
return { valid: true, newValue: tryToParseJwt(value) };
|
|
410
|
+
} catch (e) {
|
|
411
|
+
return {
|
|
412
|
+
valid: false,
|
|
413
|
+
errorMessage: 'Value is not a valid JWT token',
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
case 'form-fields': {
|
|
418
|
+
try {
|
|
419
|
+
return { valid: true, newValue: tryToParseJsonToFormFields(value) };
|
|
420
|
+
} catch (e) {
|
|
421
|
+
return {
|
|
422
|
+
valid: false,
|
|
423
|
+
errorMessage: (e as Error).message,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
default: {
|
|
428
|
+
return { valid: true, newValue: value };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { INodeTypeBaseDescription, IVersionedNodeType, INodeType } from './Interfaces';
|
|
2
|
+
|
|
3
|
+
export class VersionedNodeType implements IVersionedNodeType {
|
|
4
|
+
currentVersion: number;
|
|
5
|
+
|
|
6
|
+
nodeVersions: IVersionedNodeType['nodeVersions'];
|
|
7
|
+
|
|
8
|
+
description: INodeTypeBaseDescription;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
nodeVersions: IVersionedNodeType['nodeVersions'],
|
|
12
|
+
description: INodeTypeBaseDescription,
|
|
13
|
+
) {
|
|
14
|
+
this.nodeVersions = nodeVersions;
|
|
15
|
+
this.currentVersion = description.defaultVersion ?? this.getLatestVersion();
|
|
16
|
+
this.description = description;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getLatestVersion() {
|
|
20
|
+
return Math.max(...Object.keys(this.nodeVersions).map(Number));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getNodeType(version?: number): INodeType {
|
|
24
|
+
if (version) {
|
|
25
|
+
return this.nodeVersions[version];
|
|
26
|
+
} else {
|
|
27
|
+
return this.nodeVersions[this.currentVersion];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|