n8n-nodes-base
Version:
Base nodes of n8n
493 lines • 17.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeTrailingSlash = exports.sanitizeDataPathKey = exports.keysToLowercase = exports.fuzzyCompare = exports.compareItems = exports.flattenKeys = exports.shuffleArray = void 0;
exports.chunk = chunk;
exports.flatten = flatten;
exports.updateDisplayOptions = updateDisplayOptions;
exports.processJsonInput = processJsonInput;
exports.wrapData = wrapData;
exports.formatPrivateKey = formatPrivateKey;
exports.getResolvables = getResolvables;
exports.flattenObject = flattenObject;
exports.capitalize = capitalize;
exports.generatePairedItemData = generatePairedItemData;
exports.preparePairedItemDataArray = preparePairedItemDataArray;
exports.escapeHtml = escapeHtml;
exports.sortItemKeysByPriorityList = sortItemKeysByPriorityList;
exports.createUtmCampaignLink = createUtmCampaignLink;
exports.addExecutionHints = addExecutionHints;
const get_1 = __importDefault(require("lodash/get"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const isNull_1 = __importDefault(require("lodash/isNull"));
const isObject_1 = __importDefault(require("lodash/isObject"));
const merge_1 = __importDefault(require("lodash/merge"));
const reduce_1 = __importDefault(require("lodash/reduce"));
const n8n_workflow_1 = require("n8n-workflow");
/**
* Creates an array of elements split into groups the length of `size`.
* If `array` can't be split evenly, the final chunk will be the remaining
* elements.
*
* @param {Array} array The array to process.
* @param {number} [size=1] The length of each chunk
* @example
*
* chunk(['a', 'b', 'c', 'd'], 2)
* // => [['a', 'b'], ['c', 'd']]
*
* chunk(['a', 'b', 'c', 'd'], 3)
* // => [['a', 'b', 'c'], ['d']]
*/
function chunk(array, size = 1) {
const length = array === null ? 0 : array.length;
if (!length || size < 1) {
return [];
}
let index = 0;
let resIndex = 0;
const result = new Array(Math.ceil(length / size));
while (index < length) {
result[resIndex++] = array.slice(index, (index += size));
}
return result;
}
/**
* Shuffles an array in place using the Fisher-Yates shuffle algorithm
* @param {Array} array The array to shuffle.
*/
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = (0, n8n_workflow_1.randomInt)(i + 1);
[array[i], array[j]] = [array[j], array[i]];
}
};
exports.shuffleArray = shuffleArray;
/**
* Flattens an object with deep data
* @param {IDataObject} data The object to flatten
* @param {string[]} prefix The prefix to add to each key in the returned flat object
*/
const flattenKeys = (obj, prefix = []) => {
return !(0, isObject_1.default)(obj)
? { [prefix.join('.')]: obj }
: (0, reduce_1.default)(obj, (cum, next, key) => (0, merge_1.default)(cum, (0, exports.flattenKeys)(next, [...prefix, key])), {});
};
exports.flattenKeys = flattenKeys;
/**
* Takes a multidimensional array and converts it to a one-dimensional array.
*
* @param {Array} nestedArray The array to be flattened.
* @example
*
* flatten([['a', 'b'], ['c', 'd']])
* // => ['a', 'b', 'c', 'd']
*
*/
function flatten(nestedArray) {
const result = [];
(function loop(array) {
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
loop(array[i]);
}
else {
result.push(array[i]);
}
}
})(nestedArray);
//TODO: check logic in MicrosoftSql.node.ts
return result;
}
/**
* Compares the values of specified keys in two objects.
*
* @param {T} obj1 - The first object to compare.
* @param {T} obj2 - The second object to compare.
* @param {string[]} keys - An array of keys to compare.
* @param {boolean} disableDotNotation - Whether to use dot notation to access nested properties.
* @returns {boolean} - Whether the values of the specified keys are equal in both objects.
*/
const compareItems = (obj1, obj2, keys, disableDotNotation = false) => {
let result = true;
for (const key of keys) {
if (!disableDotNotation) {
if (!(0, isEqual_1.default)((0, get_1.default)(obj1.json, key), (0, get_1.default)(obj2.json, key))) {
result = false;
break;
}
}
else {
if (!(0, isEqual_1.default)(obj1.json[key], obj2.json[key])) {
result = false;
break;
}
}
}
return result;
};
exports.compareItems = compareItems;
function updateDisplayOptions(displayOptions, properties) {
return properties.map((nodeProperty) => {
return {
...nodeProperty,
displayOptions: (0, merge_1.default)({}, nodeProperty.displayOptions, displayOptions),
};
});
}
function processJsonInput(jsonData, inputName) {
let values;
const input = inputName ? `'${inputName}' ` : '';
if (typeof jsonData === 'string') {
try {
values = (0, n8n_workflow_1.jsonParse)(jsonData);
}
catch (error) {
throw new n8n_workflow_1.ApplicationError(`Input ${input} must contain a valid JSON`, { level: 'warning' });
}
}
else if (typeof jsonData === 'object') {
values = jsonData;
}
else {
throw new n8n_workflow_1.ApplicationError(`Input ${input} must contain a valid JSON`, { level: 'warning' });
}
return values;
}
function isFalsy(value) {
if ((0, isNull_1.default)(value))
return true;
if (typeof value === 'string' && value === '')
return true;
if (Array.isArray(value) && value.length === 0)
return true;
return false;
}
const parseStringAndCompareToObject = (str, arr) => {
try {
const parsedArray = (0, n8n_workflow_1.jsonParse)(str);
return (0, isEqual_1.default)(parsedArray, arr);
}
catch (error) {
return false;
}
};
const fuzzyCompare = (useFuzzyCompare, compareVersion = 1) => {
if (!useFuzzyCompare) {
//Fuzzy compare is false we do strict comparison
return (item1, item2) => (0, isEqual_1.default)(item1, item2);
}
return (item1, item2) => {
//Both types are the same, so we do strict comparison
if (!(0, isNull_1.default)(item1) && !(0, isNull_1.default)(item2) && typeof item1 === typeof item2) {
return (0, isEqual_1.default)(item1, item2);
}
if (compareVersion >= 2) {
//Null, 0 and "0" treated as equal
if ((0, isNull_1.default)(item1) && ((0, isNull_1.default)(item2) || item2 === 0 || item2 === '0')) {
return true;
}
if ((0, isNull_1.default)(item2) && ((0, isNull_1.default)(item1) || item1 === 0 || item1 === '0')) {
return true;
}
}
//Null, empty strings, empty arrays all treated as the same
if (isFalsy(item1) && isFalsy(item2))
return true;
//When a field is missing in one branch and isFalsy() in another, treat them as matching
if (isFalsy(item1) && item2 === undefined)
return true;
if (item1 === undefined && isFalsy(item2))
return true;
//Compare numbers and strings representing that number
if (typeof item1 === 'number' && typeof item2 === 'string') {
return item1.toString() === item2;
}
if (typeof item1 === 'string' && typeof item2 === 'number') {
return item1 === item2.toString();
}
//Compare objects/arrays and their stringified version
if (!(0, isNull_1.default)(item1) && typeof item1 === 'object' && typeof item2 === 'string') {
return parseStringAndCompareToObject(item2, item1);
}
if (!(0, isNull_1.default)(item2) && typeof item1 === 'string' && typeof item2 === 'object') {
return parseStringAndCompareToObject(item1, item2);
}
//Compare booleans and strings representing the boolean (’true’, ‘True’, ‘TRUE’)
if (typeof item1 === 'boolean' && typeof item2 === 'string') {
if (item1 === true && item2.toLocaleLowerCase() === 'true')
return true;
if (item1 === false && item2.toLocaleLowerCase() === 'false')
return true;
}
if (typeof item2 === 'boolean' && typeof item1 === 'string') {
if (item2 === true && item1.toLocaleLowerCase() === 'true')
return true;
if (item2 === false && item1.toLocaleLowerCase() === 'false')
return true;
}
//Compare booleans and the numbers/string 0 and 1
if (typeof item1 === 'boolean' && typeof item2 === 'number') {
if (item1 === true && item2 === 1)
return true;
if (item1 === false && item2 === 0)
return true;
}
if (typeof item2 === 'boolean' && typeof item1 === 'number') {
if (item2 === true && item1 === 1)
return true;
if (item2 === false && item1 === 0)
return true;
}
if (typeof item1 === 'boolean' && typeof item2 === 'string') {
if (item1 === true && item2 === '1')
return true;
if (item1 === false && item2 === '0')
return true;
}
if (typeof item2 === 'boolean' && typeof item1 === 'string') {
if (item2 === true && item1 === '1')
return true;
if (item2 === false && item1 === '0')
return true;
}
return (0, isEqual_1.default)(item1, item2);
};
};
exports.fuzzyCompare = fuzzyCompare;
function wrapData(data) {
if (!Array.isArray(data)) {
return [{ json: data }];
}
return data.map((item) => ({
json: item,
}));
}
const keysToLowercase = (headers) => {
if (typeof headers !== 'object' || Array.isArray(headers) || headers === null)
return headers;
return Object.entries(headers).reduce((acc, [key, value]) => {
acc[key.toLowerCase()] = value;
return acc;
}, {});
};
exports.keysToLowercase = keysToLowercase;
/**
* Formats a private key by removing unnecessary whitespace and adding line breaks.
* @param privateKey - The private key to format.
* @returns The formatted private key.
*/
function formatPrivateKey(privateKey, keyIsPublic = false) {
let regex = /(PRIVATE KEY|CERTIFICATE)/;
if (keyIsPublic) {
regex = /(PUBLIC KEY)/;
}
if (!privateKey || /\n/.test(privateKey)) {
return privateKey;
}
let formattedPrivateKey = '';
const parts = privateKey.split('-----').filter((item) => item !== '');
parts.forEach((part) => {
if (regex.test(part)) {
formattedPrivateKey += `-----${part}-----`;
}
else {
const passRegex = /Proc-Type|DEK-Info/;
if (passRegex.test(part)) {
part = part.replace(/:\s+/g, ':');
formattedPrivateKey += part.replace(/\\n/g, '\n').replace(/\s+/g, '\n');
}
else {
formattedPrivateKey += part.replace(/\\n/g, '\n').replace(/\s+/g, '\n');
}
}
});
return formattedPrivateKey;
}
/**
* @TECH_DEBT Explore replacing with handlebars
*/
function getResolvables(expression) {
if (!expression)
return [];
const resolvables = [];
const resolvableRegex = /({{[\s\S]*?}})/g;
let match;
while ((match = resolvableRegex.exec(expression)) !== null) {
if (match[1]) {
resolvables.push(match[1]);
}
}
return resolvables;
}
/**
* Flattens an object with deep data
*
* @param {IDataObject} data The object to flatten
*/
function flattenObject(data) {
const returnData = {};
for (const key1 of Object.keys(data)) {
if (data[key1] !== null && typeof data[key1] === 'object') {
if (data[key1] instanceof Date) {
returnData[key1] = data[key1]?.toString();
continue;
}
const flatObject = flattenObject(data[key1]);
for (const key2 in flatObject) {
if (flatObject[key2] === undefined) {
continue;
}
returnData[`${key1}.${key2}`] = flatObject[key2];
}
}
else {
returnData[key1] = data[key1];
}
}
return returnData;
}
/**
* Capitalizes the first letter of a string
*
* @param {string} string The string to capitalize
*/
function capitalize(str) {
if (!str)
return str;
const chars = str.split('');
chars[0] = chars[0].toUpperCase();
return chars.join('');
}
function generatePairedItemData(length) {
return Array.from({ length }, (_, item) => ({
item,
}));
}
/**
* Output Paired Item Data Array
*
* @param {number | IPairedItemData | IPairedItemData[] | undefined} pairedItem
*/
function preparePairedItemDataArray(pairedItem) {
if (pairedItem === undefined)
return [];
if (typeof pairedItem === 'number')
return [{ item: pairedItem }];
if (Array.isArray(pairedItem))
return pairedItem;
return [pairedItem];
}
const sanitizeDataPathKey = (item, key) => {
if (item[key] !== undefined) {
return key;
}
if ((key.startsWith("['") && key.endsWith("']")) ||
(key.startsWith('["') && key.endsWith('"]'))) {
key = key.slice(2, -2);
if (item[key] !== undefined) {
return key;
}
}
return key;
};
exports.sanitizeDataPathKey = sanitizeDataPathKey;
/**
* Escape HTML
*
* @param {string} text The text to escape
*/
function escapeHtml(text) {
if (!text)
return '';
return text.replace(/&|<|>|'|"/g, (match) => {
switch (match) {
case '&':
return '&';
case '<':
return '<';
case '>':
return '>';
case ''':
return "'";
case '"':
return '"';
default:
return match;
}
});
}
/**
* Sorts each item json's keys by a priority list
*
* @param {INodeExecutionData[]} data The array of items which keys will be sorted
* @param {string[]} priorityList The priority list, keys of item.json will be sorted in this order first then alphabetically
*/
function sortItemKeysByPriorityList(data, priorityList) {
return data.map((item) => {
const itemKeys = Object.keys(item.json);
const updatedKeysOrder = itemKeys.sort((a, b) => {
const indexA = priorityList.indexOf(a);
const indexB = priorityList.indexOf(b);
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
}
else if (indexA !== -1) {
return -1;
}
else if (indexB !== -1) {
return 1;
}
return a.localeCompare(b);
});
const updatedItem = {};
for (const key of updatedKeysOrder) {
updatedItem[key] = item.json[key];
}
item.json = updatedItem;
return item;
});
}
function createUtmCampaignLink(nodeType, instanceId) {
return `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(nodeType)}${instanceId ? '_' + instanceId : ''}`;
}
const removeTrailingSlash = (url) => {
if (url.endsWith('/')) {
return url.slice(0, -1);
}
return url;
};
exports.removeTrailingSlash = removeTrailingSlash;
function addExecutionHints(context, node, items, operation, executeOnce) {
if ((node.type === n8n_workflow_1.POSTGRES_NODE_TYPE || node.type === n8n_workflow_1.MYSQL_NODE_TYPE) &&
operation === 'select' &&
items.length > 1 &&
!executeOnce) {
context.addExecutionHints({
message: `This node ran ${items.length} times, once for each input item. To run for the first item only, enable 'execute once' in the node settings`,
location: 'outputPane',
});
}
if (node.type === n8n_workflow_1.POSTGRES_NODE_TYPE &&
operation === 'executeQuery' &&
items.length > 1 &&
context.getNodeParameter('options.queryBatching', 0, 'single') === 'single' &&
context.getNodeParameter('query', 0, '').toLowerCase().startsWith('insert')) {
context.addExecutionHints({
message: "Inserts were batched for performance. If you need to preserve item matching, consider changing 'Query batching' to 'Independent' in the options.",
location: 'outputPane',
});
}
if (node.type === n8n_workflow_1.MYSQL_NODE_TYPE &&
operation === 'executeQuery' &&
context.getNodeParameter('options.queryBatching', 0, 'single') === 'single' &&
context.getNodeParameter('query', 0, '').toLowerCase().startsWith('insert')) {
context.addExecutionHints({
message: "Inserts were batched for performance. If you need to preserve item matching, consider changing 'Query batching' to 'Independent' in the options.",
location: 'outputPane',
});
}
}
//# sourceMappingURL=utilities.js.map