first commit

This commit is contained in:
2025-10-26 23:10:15 +08:00
commit 8f0345b7be
14961 changed files with 2356381 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
import type { ExtensionMap } from './extensions';
export declare function average(value: unknown[]): number;
export declare namespace average {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
returnType: string;
docURL: string;
};
}
export declare function toJsonString(value: unknown[]): string;
export declare namespace toJsonString {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
docURL: string;
returnType: string;
};
}
export declare function toInt(): undefined;
export declare function toFloat(): undefined;
export declare function toBoolean(): undefined;
export declare function toDateTime(): undefined;
export declare const arrayExtensions: ExtensionMap;
//# sourceMappingURL=array-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"array-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/array-extensions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,cAAc,CAAC;AAsH5D,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,UASvC;yBATe,OAAO;;;;;;;;;;;;AAiMvB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAE5C;yBAFe,YAAY;;;;;;;;;;;;AAI5B,wBAAgB,KAAK,cAEpB;AAED,wBAAgB,OAAO,cAEtB;AAED,wBAAgB,SAAS,cAExB;AAED,wBAAgB,UAAU,cAEzB;AAiVD,eAAO,MAAM,eAAe,EAAE,YA8B7B,CAAC"}

View File

@@ -0,0 +1,627 @@
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "lodash/isEqual", "lodash/uniqWith", "./object-extensions", "../errors/expression-extension.error", "../errors/expression.error", "../utils"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.arrayExtensions = void 0;
exports.average = average;
exports.toJsonString = toJsonString;
exports.toInt = toInt;
exports.toFloat = toFloat;
exports.toBoolean = toBoolean;
exports.toDateTime = toDateTime;
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const uniqWith_1 = __importDefault(require("lodash/uniqWith"));
const object_extensions_1 = require("./object-extensions");
const expression_extension_error_1 = require("../errors/expression-extension.error");
const expression_error_1 = require("../errors/expression.error");
const utils_1 = require("../utils");
function first(value) {
return value[0];
}
function isEmpty(value) {
return value.length === 0;
}
function isNotEmpty(value) {
return value.length > 0;
}
function last(value) {
return value[value.length - 1];
}
function pluck(value, extraArgs) {
if (!Array.isArray(extraArgs)) {
throw new expression_error_1.ExpressionError('arguments must be passed to pluck');
}
if (!extraArgs || extraArgs.length === 0) {
return value;
}
const plucked = value.reduce((pluckedFromObject, current) => {
if (current && typeof current === 'object') {
const p = [];
Object.keys(current).forEach((k) => {
extraArgs.forEach((field) => {
if (current && field === k) {
p.push(current[k]);
}
});
});
if (p.length > 0) {
pluckedFromObject.push(p.length === 1 ? p[0] : p);
}
}
return pluckedFromObject;
}, new Array());
return plucked;
}
function randomItem(value) {
const len = value === undefined ? 0 : value.length;
return len ? value[(0, utils_1.randomInt)(len)] : undefined;
}
function unique(value, extraArgs) {
const mapForEqualityCheck = (item) => {
if (extraArgs.length > 0 && item && typeof item === 'object') {
return extraArgs.reduce((acc, key) => {
acc[key] = item[key];
return acc;
}, {});
}
return item;
};
return (0, uniqWith_1.default)(value, (a, b) => (0, isEqual_1.default)(mapForEqualityCheck(a), mapForEqualityCheck(b)));
}
const ensureNumberArray = (arr, { fnName }) => {
if (arr.some((i) => typeof i !== 'number')) {
throw new expression_extension_error_1.ExpressionExtensionError(`${fnName}(): all array elements must be numbers`);
}
};
function sum(value) {
ensureNumberArray(value, { fnName: 'sum' });
return value.reduce((p, c) => {
if (typeof c === 'string') {
return p + parseFloat(c);
}
if (typeof c !== 'number') {
return NaN;
}
return p + c;
}, 0);
}
function min(value) {
ensureNumberArray(value, { fnName: 'min' });
return Math.min(...value.map((v) => {
if (typeof v === 'string') {
return parseFloat(v);
}
if (typeof v !== 'number') {
return NaN;
}
return v;
}));
}
function max(value) {
ensureNumberArray(value, { fnName: 'max' });
return Math.max(...value.map((v) => {
if (typeof v === 'string') {
return parseFloat(v);
}
if (typeof v !== 'number') {
return NaN;
}
return v;
}));
}
function average(value) {
ensureNumberArray(value, { fnName: 'average' });
// This would usually be NaN but I don't think users
// will expect that
if (value.length === 0) {
return 0;
}
return sum(value) / value.length;
}
function compact(value) {
return value
.filter((v) => {
if (v && typeof v === 'object' && Object.keys(v).length === 0)
return false;
return v !== null && v !== undefined && v !== 'nil' && v !== '';
})
.map((v) => {
if (typeof v === 'object' && v !== null) {
return (0, object_extensions_1.compact)(v);
}
return v;
});
}
function smartJoin(value, extraArgs) {
const [keyField, valueField] = extraArgs;
if (!keyField || !valueField || typeof keyField !== 'string' || typeof valueField !== 'string') {
throw new expression_extension_error_1.ExpressionExtensionError('smartJoin(): expected two string args, e.g. .smartJoin("name", "value")');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
return value.reduce((o, v) => {
if (typeof v === 'object' && v !== null && keyField in v && valueField in v) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
o[v[keyField]] = v[valueField];
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return o;
}, {});
}
function chunk(value, extraArgs) {
const [chunkSize] = extraArgs;
if (typeof chunkSize !== 'number' || chunkSize === 0) {
throw new expression_extension_error_1.ExpressionExtensionError('chunk(): expected non-zero numeric arg, e.g. .chunk(5)');
}
const chunks = [];
for (let i = 0; i < value.length; i += chunkSize) {
// I have no clue why eslint thinks 2 numbers could be anything but that but here we are
chunks.push(value.slice(i, i + chunkSize));
}
return chunks;
}
function renameKeys(value, extraArgs) {
if (extraArgs.length === 0 || extraArgs.length % 2 !== 0) {
throw new expression_extension_error_1.ExpressionExtensionError('renameKeys(): expected an even amount of args: from1, to1 [, from2, to2, ...]. e.g. .renameKeys("name", "title")');
}
return value.map((v) => {
if (typeof v !== 'object' || v === null) {
return v;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
const newObj = { ...v };
const chunkedArgs = chunk(extraArgs, [2]);
chunkedArgs.forEach(([from, to]) => {
if (from in newObj) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
newObj[to] = newObj[from];
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
delete newObj[from];
}
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return newObj;
});
}
function mergeObjects(value, extraArgs) {
const [other] = extraArgs;
if (!other) {
return value;
}
if (typeof other !== 'object') {
throw new expression_extension_error_1.ExpressionExtensionError('merge(): expected object arg');
}
const newObject = { ...value };
for (const [key, val] of Object.entries(other)) {
if (!(key in newObject)) {
newObject[key] = val;
}
}
return newObject;
}
function merge(value, extraArgs) {
const [others] = extraArgs;
if (others === undefined) {
// If there are no arguments passed, merge all objects within the array
const merged = value.reduce((combined, current) => {
if (current !== null && typeof current === 'object' && !Array.isArray(current)) {
combined = mergeObjects(combined, [current]);
}
return combined;
}, {});
return merged;
}
if (!Array.isArray(others)) {
throw new expression_extension_error_1.ExpressionExtensionError('merge(): expected array arg, e.g. .merge([{ id: 1, otherValue: 3 }])');
}
const listLength = value.length > others.length ? value.length : others.length;
let merged = {};
for (let i = 0; i < listLength; i++) {
if (value[i] !== undefined) {
if (typeof value[i] === 'object' && typeof others[i] === 'object') {
merged = Object.assign(merged, mergeObjects(value[i], [others[i]]));
}
}
}
return merged;
}
function union(value, extraArgs) {
const [others] = extraArgs;
if (!Array.isArray(others)) {
throw new expression_extension_error_1.ExpressionExtensionError('union(): expected array arg, e.g. .union([1, 2, 3, 4])');
}
const newArr = Array.from(value);
for (const v of others) {
if (newArr.findIndex((w) => (0, isEqual_1.default)(w, v)) === -1) {
newArr.push(v);
}
}
return unique(newArr, []);
}
function difference(value, extraArgs) {
const [others] = extraArgs;
if (!Array.isArray(others)) {
throw new expression_extension_error_1.ExpressionExtensionError('difference(): expected array arg, e.g. .difference([1, 2, 3, 4])');
}
const newArr = [];
for (const v of value) {
if (others.findIndex((w) => (0, isEqual_1.default)(w, v)) === -1) {
newArr.push(v);
}
}
return unique(newArr, []);
}
function intersection(value, extraArgs) {
const [others] = extraArgs;
if (!Array.isArray(others)) {
throw new expression_extension_error_1.ExpressionExtensionError('intersection(): expected array arg, e.g. .intersection([1, 2, 3, 4])');
}
const newArr = [];
for (const v of value) {
if (others.findIndex((w) => (0, isEqual_1.default)(w, v)) !== -1) {
newArr.push(v);
}
}
for (const v of others) {
if (value.findIndex((w) => (0, isEqual_1.default)(w, v)) !== -1) {
newArr.push(v);
}
}
return unique(newArr, []);
}
function append(value, extraArgs) {
return value.concat(extraArgs);
}
function toJsonString(value) {
return JSON.stringify(value);
}
function toInt() {
return undefined;
}
function toFloat() {
return undefined;
}
function toBoolean() {
return undefined;
}
function toDateTime() {
return undefined;
}
average.doc = {
name: 'average',
description: 'Returns the average of the numbers in the array. Throws an error if there are any non-numbers.',
examples: [{ example: '[12, 1, 5].average()', evaluated: '6' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-average',
};
compact.doc = {
name: 'compact',
description: 'Removes any empty values from the array. <code>null</code>, <code>""</code> and <code>undefined</code> count as empty.',
examples: [{ example: '[2, null, 1, ""].compact()', evaluated: '[2, 1]' }],
returnType: 'Array',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-compact',
};
isEmpty.doc = {
name: 'isEmpty',
description: 'Returns <code>true</code> if the array has no elements or is <code>null</code>',
examples: [
{ example: '[].isEmpty()', evaluated: 'true' },
{ example: "['quick', 'brown', 'fox'].isEmpty()", evaluated: 'false' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-isEmpty',
};
isNotEmpty.doc = {
name: 'isNotEmpty',
description: 'Returns <code>true</code> if the array has at least one element',
examples: [
{ example: "['quick', 'brown', 'fox'].isNotEmpty()", evaluated: 'true' },
{ example: '[].isNotEmpty()', evaluated: 'false' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-isNotEmpty',
};
first.doc = {
name: 'first',
description: 'Returns the first element of the array',
examples: [{ example: "['quick', 'brown', 'fox'].first()", evaluated: "'quick'" }],
returnType: 'any',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-first',
};
last.doc = {
name: 'last',
description: 'Returns the last element of the array',
examples: [{ example: "['quick', 'brown', 'fox'].last()", evaluated: "'fox'" }],
returnType: 'any',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-last',
};
max.doc = {
name: 'max',
description: 'Returns the largest number in the array. Throws an error if there are any non-numbers.',
examples: [{ example: '[1, 12, 5].max()', evaluated: '12' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-max',
};
min.doc = {
name: 'min',
description: 'Returns the smallest number in the array. Throws an error if there are any non-numbers.',
examples: [{ example: '[12, 1, 5].min()', evaluated: '1' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-min',
};
randomItem.doc = {
name: 'randomItem',
description: 'Returns a randomly-chosen element from the array',
examples: [
{ example: "['quick', 'brown', 'fox'].randomItem()", evaluated: "'brown'" },
{ example: "['quick', 'brown', 'fox'].randomItem()", evaluated: "'quick'" },
],
returnType: 'any',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-randomItem',
};
sum.doc = {
name: 'sum',
description: 'Returns the total of all the numbers in the array. Throws an error if there are any non-numbers.',
examples: [{ example: '[12, 1, 5].sum()', evaluated: '18' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-sum',
};
chunk.doc = {
name: 'chunk',
description: 'Splits the array into an array of sub-arrays, each with the given length',
examples: [{ example: '[1, 2, 3, 4, 5, 6].chunk(2)', evaluated: '[[1,2],[3,4],[5,6]]' }],
returnType: 'Array',
args: [
{
name: 'length',
optional: false,
description: 'The number of elements in each chunk',
type: 'number',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-chunk',
};
difference.doc = {
name: 'difference',
description: "Compares two arrays. Returns all elements in the base array that aren't present\nin <code>otherArray</code>.",
examples: [{ example: '[1, 2, 3].difference([2, 3])', evaluated: '[1]' }],
returnType: 'Array',
args: [
{
name: 'otherArray',
optional: false,
description: 'The array to compare to the base array',
type: 'Array',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-difference',
};
intersection.doc = {
name: 'intersection',
description: 'Compares two arrays. Returns all elements in the base array that are also present in the other array.',
examples: [{ example: '[1, 2].intersection([2, 3])', evaluated: '[2]' }],
returnType: 'Array',
args: [
{
name: 'otherArray',
optional: false,
description: 'The array to compare to the base array',
type: 'Array',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-intersection',
};
merge.doc = {
name: 'merge',
description: 'Merges two Object-arrays into one object by merging the key-value pairs of each element.',
examples: [
{
example: "[{ name: 'Nathan' }, { age: 42 }].merge([{ city: 'Berlin' }, { country: 'Germany' }])",
evaluated: "{ name: 'Nathan', age: 42, city: 'Berlin', country: 'Germany' }",
},
],
returnType: 'Object',
args: [
{
name: 'otherArray',
optional: false,
description: 'The array to merge into the base array',
type: 'Array',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-merge',
};
pluck.doc = {
name: 'pluck',
description: 'Returns an array containing the values of the given field(s) in each Object of the array. Ignores any array elements that arent Objects or dont have a key matching the field name(s) provided.',
examples: [
{
example: "[{ name: 'Nathan', age: 42 },{ name: 'Jan', city: 'Berlin' }].pluck('name')",
evaluated: '["Nathan", "Jan"]',
},
{
example: "[{ name: 'Nathan', age: 42 },{ name: 'Jan', city: 'Berlin' }].pluck('age')",
evaluated: '[42]',
},
],
returnType: 'Array',
args: [
{
name: 'fieldNames',
optional: false,
variadic: true,
description: 'The keys to retrieve the value of',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-pluck',
};
renameKeys.doc = {
name: 'renameKeys',
description: 'Changes all matching keys (field names) of any Objects in the array. Rename more than one key by\nadding extra arguments, i.e. <code>from1, to1, from2, to2, ...</code>.',
examples: [
{
example: "[{ name: 'bob' }, { name: 'meg' }].renameKeys('name', 'x')",
evaluated: "[{ x: 'bob' }, { x: 'meg' }]",
},
],
returnType: 'Array',
args: [
{
name: 'from',
optional: false,
description: 'The key to rename',
type: 'string',
},
{ name: 'to', optional: false, description: 'The new key name', type: 'string' },
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-renameKeys',
};
smartJoin.doc = {
name: 'smartJoin',
description: 'Creates a single Object from an array of Objects. Each Object in the array provides one field for the returned Object. Each Object in the array must contain a field with the key name and a field with the value.',
examples: [
{
example: "[{ field: 'age', value: 2 }, { field: 'city', value: 'Berlin' }].smartJoin('field', 'value')",
evaluated: "{ age: 2, city: 'Berlin' }",
},
],
returnType: 'Object',
args: [
{
name: 'keyField',
optional: false,
description: 'The field in each Object containing the key name',
type: 'string',
},
{
name: 'nameField',
optional: false,
description: 'The field in each Object containing the value',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-smartJoin',
};
union.doc = {
name: 'union',
description: 'Concatenates two arrays and then removes any duplicates',
examples: [{ example: '[1, 2].union([2, 3])', evaluated: '[1, 2, 3]' }],
returnType: 'Array',
args: [
{
name: 'otherArray',
optional: false,
description: 'The array to union with the base array',
type: 'Array',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-union',
};
unique.doc = {
name: 'unique',
description: 'Removes any duplicate elements from the array',
examples: [
{ example: "['quick', 'brown', 'quick'].unique()", evaluated: "['quick', 'brown']" },
{
example: "[{ name: 'Nathan', age: 42 }, { name: 'Nathan', age: 22 }].unique()",
evaluated: "[{ name: 'Nathan', age: 42 }, { name: 'Nathan', age: 22 }]",
},
{
example: "[{ name: 'Nathan', age: 42 }, { name: 'Nathan', age: 22 }].unique('name')",
evaluated: "[{ name: 'Nathan', age: 42 }]",
},
],
returnType: 'any',
aliases: ['removeDuplicates'],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-unique',
args: [
{
name: 'fieldNames',
optional: false,
variadic: true,
description: 'The object keys to check for equality',
type: 'any',
},
],
};
toJsonString.doc = {
name: 'toJsonString',
description: "Converts the array to a JSON string. The same as JavaScript's <code>JSON.stringify()</code>.",
examples: [
{
example: "['quick', 'brown', 'fox'].toJsonString()",
evaluated: '\'["quick","brown","fox"]\'',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-toJsonString',
returnType: 'string',
};
append.doc = {
name: 'append',
description: 'Adds new elements to the end of the array. Similar to <code>push()</code>, but returns the modified array. Consider using spread syntax instead (see examples).',
examples: [
{ example: "['forget', 'me'].append('not')", evaluated: "['forget', 'me', 'not']" },
{ example: '[9, 0, 2].append(1, 0)', evaluated: '[9, 0, 2, 1, 0]' },
{
example: '[...[9, 0, 2], 1, 0]',
evaluated: '[9, 0, 2, 1, 0]',
description: 'Consider using spread syntax instead',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-append',
returnType: 'Array',
args: [
{
name: 'elements',
optional: false,
variadic: true,
description: 'The elements to append, in order',
type: 'any',
},
],
};
const removeDuplicates = unique.bind({});
removeDuplicates.doc = { ...unique.doc, hidden: true };
exports.arrayExtensions = {
typeName: 'Array',
functions: {
removeDuplicates,
unique,
first,
last,
pluck,
randomItem,
sum,
min,
max,
average,
isNotEmpty,
isEmpty,
compact,
smartJoin,
chunk,
renameKeys,
merge,
union,
difference,
intersection,
append,
toJsonString,
toInt,
toFloat,
toBoolean,
toDateTime,
},
};
});
//# sourceMappingURL=array-extensions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
import type { ExtensionMap } from './extensions';
export declare function toBoolean(value: boolean): boolean;
export declare function toInt(value: boolean): 1 | 0;
export declare function toDateTime(): undefined;
export declare const booleanExtensions: ExtensionMap;
//# sourceMappingURL=boolean-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"boolean-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/boolean-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,WAEvC;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,SAEnC;AAED,wBAAgB,UAAU,cAEzB;AAmBD,eAAO,MAAM,iBAAiB,EAAE,YAS/B,CAAC"}

View File

@@ -0,0 +1,49 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.booleanExtensions = void 0;
exports.toBoolean = toBoolean;
exports.toInt = toInt;
exports.toDateTime = toDateTime;
function toBoolean(value) {
return value;
}
function toInt(value) {
return value ? 1 : 0;
}
function toDateTime() {
return undefined;
}
const toFloat = toInt;
const toNumber = toInt.bind({});
toNumber.doc = {
name: 'toNumber',
description: 'Converts <code>true</code> to <code>1</code> and <code>false</code> to <code>0</code>.',
examples: [
{ example: 'true.toNumber()', evaluated: '1' },
{ example: 'false.toNumber()', evaluated: '0' },
],
section: 'cast',
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/booleans/#boolean-toNumber',
};
exports.booleanExtensions = {
typeName: 'Boolean',
functions: {
toBoolean,
toInt,
toFloat,
toNumber,
toDateTime,
},
};
});
//# sourceMappingURL=boolean-extensions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"boolean-extensions.js","sourceRoot":"","sources":["../../../src/extensions/boolean-extensions.ts"],"names":[],"mappings":";;;;;;;;;;;;IAEA,8BAEC;IAED,sBAEC;IAED,gCAEC;IAVD,SAAgB,SAAS,CAAC,KAAc;QACvC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,SAAgB,KAAK,CAAC,KAAc;QACnC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,SAAgB,UAAU;QACzB,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC;IACtB,MAAM,QAAQ,GAAc,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE3C,QAAQ,CAAC,GAAG,GAAG;QACd,IAAI,EAAE,UAAU;QAChB,WAAW,EACV,wFAAwF;QACzF,QAAQ,EAAE;YACT,EAAE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,EAAE;YAC9C,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,GAAG,EAAE;SAC/C;QACD,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,QAAQ;QACpB,MAAM,EACL,2FAA2F;KAC5F,CAAC;IAEW,QAAA,iBAAiB,GAAiB;QAC9C,QAAQ,EAAE,SAAS;QACnB,SAAS,EAAE;YACV,SAAS;YACT,KAAK;YACL,OAAO;YACP,QAAQ;YACR,UAAU;SACV;KACD,CAAC"}

View File

@@ -0,0 +1,3 @@
import type { ExtensionMap } from './extensions';
export declare const dateExtensions: ExtensionMap;
//# sourceMappingURL=date-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"date-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/date-extensions.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA+kBjD,eAAO,MAAM,cAAc,EAAE,YAsB5B,CAAC"}

View File

@@ -0,0 +1,528 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "luxon", "./string-extensions", "./utils", "../errors/expression-extension.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.dateExtensions = void 0;
const luxon_1 = require("luxon");
const string_extensions_1 = require("./string-extensions");
const utils_1 = require("./utils");
const expression_extension_error_1 = require("../errors/expression-extension.error");
const durationUnits = [
'milliseconds',
'seconds',
'minutes',
'hours',
'days',
'weeks',
'months',
'quarters',
'years',
];
const dateParts = [
'day',
'week',
'month',
'year',
'hour',
'minute',
'second',
'millisecond',
'weekNumber',
'yearDayNumber',
'weekday',
];
const DURATION_MAP = {
day: 'days',
month: 'months',
year: 'years',
week: 'weeks',
hour: 'hours',
minute: 'minutes',
second: 'seconds',
millisecond: 'milliseconds',
ms: 'milliseconds',
sec: 'seconds',
secs: 'seconds',
hr: 'hours',
hrs: 'hours',
min: 'minutes',
mins: 'minutes',
};
const DATETIMEUNIT_MAP = {
days: 'day',
months: 'month',
years: 'year',
hours: 'hour',
minutes: 'minute',
seconds: 'second',
milliseconds: 'millisecond',
hrs: 'hour',
hr: 'hour',
mins: 'minute',
min: 'minute',
secs: 'second',
sec: 'second',
ms: 'millisecond',
};
function isDateTime(date) {
return date ? luxon_1.DateTime.isDateTime(date) : false;
}
function toDateTime(date) {
if (isDateTime(date))
return date;
if (typeof date === 'string') {
return (0, string_extensions_1.toDateTime)(date);
}
return luxon_1.DateTime.fromJSDate(date);
}
function generateDurationObject(durationValue, unit) {
const convertedUnit = DURATION_MAP[unit] || unit;
return { [`${convertedUnit}`]: durationValue };
}
function beginningOf(date, extraArgs) {
const [rawUnit = 'week'] = extraArgs;
const unit = DATETIMEUNIT_MAP[rawUnit] || rawUnit;
if (isDateTime(date))
return date.startOf(unit);
return luxon_1.DateTime.fromJSDate(date).startOf(unit).toJSDate();
}
function endOfMonth(date) {
if (isDateTime(date))
return date.endOf('month');
return luxon_1.DateTime.fromJSDate(date).endOf('month').toJSDate();
}
function extract(date, args) {
let [part = 'week'] = args;
if (part === 'yearDayNumber') {
date = isDateTime(date) ? date.toJSDate() : date;
const firstDayOfTheYear = new Date(date.getFullYear(), 0, 0);
const diff = date.getTime() -
firstDayOfTheYear.getTime() +
(firstDayOfTheYear.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000;
return Math.floor(diff / (1000 * 60 * 60 * 24));
}
if (part === 'week')
part = 'weekNumber';
const unit = DATETIMEUNIT_MAP[part] || part;
if (isDateTime(date))
return date.get(unit);
return luxon_1.DateTime.fromJSDate(date).get(unit);
}
function format(date, extraArgs) {
const [dateFormat, localeOpts = {}] = extraArgs;
if (isDateTime(date)) {
return date.toFormat(dateFormat, { ...localeOpts });
}
return luxon_1.DateTime.fromJSDate(date).toFormat(dateFormat, { ...localeOpts });
}
function isBetween(date, extraArgs) {
if (extraArgs.length !== 2) {
throw new expression_extension_error_1.ExpressionExtensionError('isBetween(): expected exactly two args');
}
const [first, second] = extraArgs;
const firstDate = (0, utils_1.convertToDateTime)(first);
const secondDate = (0, utils_1.convertToDateTime)(second);
if (!firstDate || !secondDate) {
return;
}
if (firstDate > secondDate) {
return secondDate < date && date < firstDate;
}
return secondDate > date && date > firstDate;
}
function isDst(date) {
if (isDateTime(date)) {
return date.isInDST;
}
return luxon_1.DateTime.fromJSDate(date).isInDST;
}
function isInLast(date, extraArgs) {
const [durationValue = 0, unit = 'minutes'] = extraArgs;
const dateInThePast = luxon_1.DateTime.now().minus(generateDurationObject(durationValue, unit));
let thisDate = date;
if (!isDateTime(thisDate)) {
thisDate = luxon_1.DateTime.fromJSDate(thisDate);
}
return dateInThePast <= thisDate && thisDate <= luxon_1.DateTime.now();
}
const WEEKEND_DAYS = [6, 7];
function isWeekend(date) {
const { weekday } = isDateTime(date) ? date : luxon_1.DateTime.fromJSDate(date);
return WEEKEND_DAYS.includes(weekday);
}
function minus(date, args) {
if (args.length === 1) {
const [arg] = args;
if (isDateTime(date))
return date.minus(arg);
return luxon_1.DateTime.fromJSDate(date).minus(arg).toJSDate();
}
const [durationValue = 0, unit = 'minutes'] = args;
const duration = generateDurationObject(durationValue, unit);
if (isDateTime(date))
return date.minus(duration);
return luxon_1.DateTime.fromJSDate(date).minus(duration).toJSDate();
}
function plus(date, args) {
if (args.length === 1) {
const [arg] = args;
if (isDateTime(date))
return date.plus(arg);
return luxon_1.DateTime.fromJSDate(date).plus(arg).toJSDate();
}
const [durationValue = 0, unit = 'minutes'] = args;
const duration = generateDurationObject(durationValue, unit);
if (isDateTime(date))
return date.plus(duration);
return luxon_1.DateTime.fromJSDate(date).plus(duration).toJSDate();
}
function diffTo(date, args) {
const [otherDate, unit = 'days'] = args;
let units = Array.isArray(unit) ? unit : [unit];
if (units.length === 0) {
units = ['days'];
}
const allowedUnitSet = new Set([...dateParts, ...durationUnits]);
const errorUnit = units.find((u) => !allowedUnitSet.has(u));
if (errorUnit) {
throw new expression_extension_error_1.ExpressionExtensionError(`Unsupported unit '${String(errorUnit)}'. Supported: ${durationUnits
.map((u) => `'${u}'`)
.join(', ')}.`);
}
const diffResult = date.diff(toDateTime(otherDate), units);
if (units.length > 1) {
return diffResult.toObject();
}
return diffResult.as(units[0]);
}
function diffToNow(date, args) {
const [unit] = args;
return diffTo(date, [luxon_1.DateTime.now(), unit]);
}
function toInt(date) {
if (isDateTime(date)) {
return date.toMillis();
}
return date.getTime();
}
const toFloat = toInt;
function toBoolean() {
return undefined;
}
// Only null/undefined return true, this is handled in ExpressionExtension.ts
function isEmpty() {
return false;
}
function isNotEmpty() {
return true;
}
endOfMonth.doc = {
name: 'endOfMonth',
returnType: 'DateTime',
hidden: true,
description: 'Transforms a date to the last possible moment that lies within the month.',
section: 'edit',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-endOfMonth',
};
isDst.doc = {
name: 'isDst',
returnType: 'boolean',
hidden: true,
description: 'Checks if a Date is within Daylight Savings Time.',
section: 'query',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isDst',
};
isWeekend.doc = {
name: 'isWeekend',
returnType: 'boolean',
hidden: true,
description: 'Checks if the Date falls on a Saturday or Sunday.',
section: 'query',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isWeekend',
};
beginningOf.doc = {
name: 'beginningOf',
description: 'Transform a Date to the start of the given time period. Default unit is `week`.',
section: 'edit',
hidden: true,
returnType: 'DateTime',
args: [{ name: 'unit?', type: 'DurationUnit' }],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-beginningOf',
};
extract.doc = {
name: 'extract',
description: 'Extracts a part of the date or time, e.g. the month, as a number. To extract textual names instead, see <code>format()</code>.',
examples: [
{ example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.extract('month')", evaluated: '3' },
{ example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.extract('hour')", evaluated: '18' },
],
section: 'query',
returnType: 'number',
args: [
{
name: 'unit',
optional: true,
description: 'The part of the date or time to return. One of: <code>year</code>, <code>month</code>, <code>week</code>, <code>day</code>, <code>hour</code>, <code>minute</code>, <code>second</code>',
default: '"week"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-extract',
};
format.doc = {
name: 'format',
description: 'Converts the DateTime to a string, using the format specified. <a target="_blank" href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens">Formatting guide</a>. For common formats, <code>toLocaleString()</code> may be easier.',
examples: [
{
example: "dt = '2024-04-30T18:49'.toDateTime()\ndt.format('dd/LL/yyyy')",
evaluated: "'30/04/2024'",
},
{
example: "dt = '2024-04-30T18:49'.toDateTime()\ndt.format('dd LLL yy')",
evaluated: "'30 Apr 24'",
},
{
example: "dt = '2024-04-30T18:49'.toDateTime()\ndt.setLocale('fr').format('dd LLL yyyy')",
evaluated: "'30 avr. 2024'",
},
{
example: "dt = '2024-04-30T18:49'.toDateTime()\ndt.format(\"HH 'hours and' mm 'minutes'\")",
evaluated: "'18 hours and 49 minutes'",
},
],
returnType: 'string',
section: 'format',
args: [
{
name: 'fmt',
description: 'The <a target="_blank" href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens">format</a> of the string to return ',
default: "'yyyy-MM-dd'",
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-format',
};
isBetween.doc = {
name: 'isBetween',
description: 'Returns <code>true</code> if the DateTime lies between the two moments specified',
examples: [
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.isBetween('2020-06-01', '2025-06-01')",
evaluated: 'true',
},
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.isBetween('2020', '2025')",
evaluated: 'true',
},
],
section: 'compare',
returnType: 'boolean',
args: [
{
name: 'date1',
description: 'The moment that the base DateTime must be after. Can be an ISO date string or a Luxon DateTime.',
type: 'string | DateTime',
},
{
name: 'date2',
description: 'The moment that the base DateTime must be before. Can be an ISO date string or a Luxon DateTime.',
type: 'string | DateTime',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isBetween',
};
isInLast.doc = {
name: 'isInLast',
hidden: true,
description: 'Checks if a Date is within a given time period. Default unit is `minute`.',
section: 'query',
returnType: 'boolean',
args: [
{ name: 'n', type: 'number' },
{ name: 'unit?', type: 'DurationUnit' },
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isInLast',
};
toDateTime.doc = {
name: 'toDateTime',
description: 'Converts a JavaScript Date to a Luxon DateTime. The DateTime contains the same information, but is easier to manipulate.',
examples: [
{
example: "jsDate = new Date('2024-03-30T18:49')\njsDate.toDateTime().plus(5, 'days')",
evaluated: '[DateTime: 2024-05-05T18:49:00.000Z]',
},
],
returnType: 'DateTime',
hidden: true,
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-toDateTime',
};
minus.doc = {
name: 'minus',
description: 'Subtracts a given period of time from the DateTime',
examples: [
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.minus(7, 'days')",
evaluated: '[DateTime: 2024-04-23T18:49:00.000Z]',
},
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.minus(4, 'years')",
evaluated: '[DateTime: 2020-04-30T18:49:00.000Z]',
},
],
section: 'edit',
returnType: 'DateTime',
args: [
{
name: 'n',
description: 'The number of units to subtract. Or use a Luxon <a target="_blank" href=”https://moment.github.io/luxon/api-docs/index.html#duration”>Duration</a> object to subtract multiple units at once.',
type: 'number | object',
},
{
name: 'unit',
optional: true,
description: 'The units of the number. One of: <code>years</code>, <code>months</code>, <code>weeks</code>, <code>days</code>, <code>hours</code>, <code>minutes</code>, <code>seconds</code>, <code>milliseconds</code>',
default: '"milliseconds"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-minus',
};
plus.doc = {
name: 'plus',
description: 'Adds a given period of time to the DateTime',
examples: [
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.plus(7, 'days')",
evaluated: '[DateTime: 2024-04-07T18:49:00.000Z]',
},
{
example: "dt = '2024-03-30T18:49'.toDateTime()\ndt.plus(4, 'years')",
evaluated: '[DateTime: 2028-03-30T18:49:00.000Z]',
},
],
section: 'edit',
returnType: 'DateTime',
args: [
{
name: 'n',
description: 'The number of units to add. Or use a Luxon <a target="_blank" href=”https://moment.github.io/luxon/api-docs/index.html#duration”>Duration</a> object to add multiple units at once.',
type: 'number | object',
},
{
name: 'unit',
optional: true,
description: 'The units of the number. One of: <code>years</code>, <code>months</code>, <code>weeks</code>, <code>days</code>, <code>hours</code>, <code>minutes</code>, <code>seconds</code>, <code>milliseconds</code>',
default: '"milliseconds"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-plus',
};
diffTo.doc = {
name: 'diffTo',
description: 'Returns the difference between two DateTimes, in the given unit(s)',
examples: [
{
example: "dt = '2025-01-01'.toDateTime()\ndt.diffTo('2024-03-30T18:49:07.234', 'days')",
evaluated: '276.21',
},
{
example: "dt1 = '2025-01-01T00:00:00.000'.toDateTime();\ndt2 = '2024-03-30T18:49:07.234'.toDateTime();\ndt1.diffTo(dt2, ['months', 'days'])",
evaluated: '{ months: 9, days: 1.21 }',
},
],
section: 'compare',
returnType: 'number | Record<DurationUnit, number>',
args: [
{
name: 'otherDateTime',
default: '$now',
description: 'The moment to subtract the base DateTime from. Can be an ISO date string or a Luxon DateTime.',
type: 'string | DateTime',
},
{
name: 'unit',
default: "'days'",
description: 'The unit, or array of units, to return the result in. Possible values: <code>years</code>, <code>months</code>, <code>weeks</code>, <code>days</code>, <code>hours</code>, <code>minutes</code>, <code>seconds</code>, <code>milliseconds</code>.',
type: 'string | string[]',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-diffTo',
};
diffToNow.doc = {
name: 'diffToNow',
description: 'Returns the difference between the current moment and the DateTime, in the given unit(s). For a textual representation, use <code>toRelative()</code> instead.',
examples: [
{
example: "dt = '2023-03-30T18:49:07.234'.toDateTime()\ndt.diffToNow('days')",
evaluated: '371.9',
},
{
example: "dt = '2023-03-30T18:49:07.234'.toDateTime()\ndt.diffToNow(['months', 'days'])",
evaluated: '{ months: 12, days: 5.9 }',
},
],
section: 'compare',
returnType: 'number | Record<DurationUnit, number>',
args: [
{
name: 'unit',
description: 'The unit, or array of units, to return the result in. Possible values: <code>years</code>, <code>months</code>, <code>weeks</code>, <code>days</code>, <code>hours</code>, <code>minutes</code>, <code>seconds</code>, <code>milliseconds</code>.',
default: "'days'",
type: 'string | string[]',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-diffToNow',
};
isEmpty.doc = {
name: 'isEmpty',
description: 'Returns <code>false</code> for all DateTimes. Returns <code>true</code> for <code>null</code>.',
examples: [
{ example: "dt = '2023-03-30T18:49:07.234'.toDateTime()\ndt.isEmpty()", evaluated: 'false' },
{ example: 'dt = null\ndt.isEmpty()', evaluated: 'true' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-isEmpty',
};
isNotEmpty.doc = {
name: 'isNotEmpty',
description: 'Returns <code>true</code> for all DateTimes. Returns <code>false</code> for <code>null</code>.',
examples: [
{ example: "dt = '2023-03-30T18:49:07.234'.toDateTime()\ndt.isNotEmpty()", evaluated: 'true' },
{ example: 'dt = null\ndt.isNotEmpty()', evaluated: 'false' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-isNotEmpty',
};
exports.dateExtensions = {
typeName: 'Date',
functions: {
beginningOf,
endOfMonth,
extract,
isBetween,
isDst,
isInLast,
isWeekend,
minus,
plus,
format,
toDateTime,
diffTo,
diffToNow,
toInt,
toFloat,
toBoolean,
isEmpty,
isNotEmpty,
},
};
});
//# sourceMappingURL=date-extensions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
import type { ExtensionMap } from './extensions';
export declare const EXTENSION_OBJECTS: ExtensionMap[];
export declare const hasExpressionExtension: (str: string) => boolean;
export declare const hasNativeMethod: (method: string) => boolean;
/**
* A function to inject an extender function call into the AST of an expression.
* This uses recast to do the transform.
*
* This function also polyfills optional chaining if using extended functions.
*
* ```ts
* 'a'.method('x') // becomes
* extend('a', 'method', ['x']);
*
* 'a'.first('x').second('y') // becomes
* extend(extend('a', 'first', ['x']), 'second', ['y']));
* ```
*/
export declare const extendTransform: (expression: string) => {
code: string;
} | undefined;
/**
* Extender function injected by expression extension plugin to allow calls to extensions.
*
* ```ts
* extend(input, "functionName", [...args]);
* ```
*/
export declare function extend(input: unknown, functionName: string, args: unknown[]): any;
export declare function extendOptional(input: unknown, functionName: string): Function | undefined;
export declare function extendSyntax(bracketedExpression: string, forceExtend?: boolean): string;
//# sourceMappingURL=expression-extension.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"expression-extension.d.ts","sourceRoot":"","sources":["../../../src/extensions/expression-extension.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkBjD,eAAO,MAAM,iBAAiB,EAAE,YAAY,EAO3C,CAAC;AA0BF,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,OAChB,CAAC;AAEtC,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,OAmBhD,CAAC;AAsBF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,KAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SA0TvE,CAAC;AAgEF;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAkC3E;AAED,wBAAgB,cAAc,CAC7B,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,GAElB,QAAQ,GAAG,SAAS,CAgBtB;AAID,wBAAgB,YAAY,CAAC,mBAAmB,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,MAAM,CAsDrF"}

View File

@@ -0,0 +1,491 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "esprima-next", "luxon", "recast", "recast/lib/util", "./array-extensions", "./boolean-extensions", "./date-extensions", "./expression-parser", "./number-extensions", "./object-extensions", "./string-extensions", "./utils", "../errors/expression-extension.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extendTransform = exports.hasNativeMethod = exports.hasExpressionExtension = exports.EXTENSION_OBJECTS = void 0;
exports.extend = extend;
exports.extendOptional = extendOptional;
exports.extendSyntax = extendSyntax;
const esprima_next_1 = require("esprima-next");
const luxon_1 = require("luxon");
const recast_1 = require("recast");
const util_1 = require("recast/lib/util");
const array_extensions_1 = require("./array-extensions");
const boolean_extensions_1 = require("./boolean-extensions");
const date_extensions_1 = require("./date-extensions");
const expression_parser_1 = require("./expression-parser");
const number_extensions_1 = require("./number-extensions");
const object_extensions_1 = require("./object-extensions");
const string_extensions_1 = require("./string-extensions");
const utils_1 = require("./utils");
const expression_extension_error_1 = require("../errors/expression-extension.error");
const EXPRESSION_EXTENDER = 'extend';
const EXPRESSION_EXTENDER_OPTIONAL = 'extendOptional';
function isEmpty(value) {
return value === null || value === undefined || !value;
}
function isNotEmpty(value) {
return !isEmpty(value);
}
exports.EXTENSION_OBJECTS = [
array_extensions_1.arrayExtensions,
date_extensions_1.dateExtensions,
number_extensions_1.numberExtensions,
object_extensions_1.objectExtensions,
string_extensions_1.stringExtensions,
boolean_extensions_1.booleanExtensions,
];
// eslint-disable-next-line @typescript-eslint/no-restricted-types
const genericExtensions = {
isEmpty,
isNotEmpty,
};
const EXPRESSION_EXTENSION_METHODS = Array.from(new Set([
...Object.keys(string_extensions_1.stringExtensions.functions),
...Object.keys(number_extensions_1.numberExtensions.functions),
...Object.keys(date_extensions_1.dateExtensions.functions),
...Object.keys(array_extensions_1.arrayExtensions.functions),
...Object.keys(object_extensions_1.objectExtensions.functions),
...Object.keys(boolean_extensions_1.booleanExtensions.functions),
...Object.keys(genericExtensions),
]));
const EXPRESSION_EXTENSION_REGEX = new RegExp(`(\\$if|\\.(${EXPRESSION_EXTENSION_METHODS.join('|')})\\s*(\\?\\.)?)\\s*\\(`);
const isExpressionExtension = (str) => EXPRESSION_EXTENSION_METHODS.some((m) => m === str);
const hasExpressionExtension = (str) => EXPRESSION_EXTENSION_REGEX.test(str);
exports.hasExpressionExtension = hasExpressionExtension;
const hasNativeMethod = (method) => {
if ((0, exports.hasExpressionExtension)(method)) {
return false;
}
const methods = method
.replace(/[^\w\s]/gi, ' ')
.split(' ')
.filter(Boolean); // DateTime.now().toLocaleString().format() => [DateTime,now,toLocaleString,format]
return methods.every((methodName) => {
return [String.prototype, Array.prototype, Number.prototype, Date.prototype].some((nativeType) => {
if (methodName in nativeType) {
return true;
}
return false;
});
});
};
exports.hasNativeMethod = hasNativeMethod;
// /**
// * recast's types aren't great and we need to use a lot of anys
// */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parseWithEsprimaNext(source, options) {
const ast = (0, esprima_next_1.parse)(source, {
loc: true,
locations: true,
comment: true,
range: (0, util_1.getOption)(options, 'range', false),
tolerant: (0, util_1.getOption)(options, 'tolerant', true),
tokens: true,
jsx: (0, util_1.getOption)(options, 'jsx', false),
sourceType: (0, util_1.getOption)(options, 'sourceType', 'module'),
});
return ast;
}
/**
* A function to inject an extender function call into the AST of an expression.
* This uses recast to do the transform.
*
* This function also polyfills optional chaining if using extended functions.
*
* ```ts
* 'a'.method('x') // becomes
* extend('a', 'method', ['x']);
*
* 'a'.first('x').second('y') // becomes
* extend(extend('a', 'first', ['x']), 'second', ['y']));
* ```
*/
const extendTransform = (expression) => {
try {
const ast = (0, recast_1.parse)(expression, { parser: { parse: parseWithEsprimaNext } });
let currentChain = 1;
// Polyfill optional chaining
(0, recast_1.visit)(ast, {
// eslint-disable-next-line complexity
visitChainExpression(path) {
this.traverse(path);
const chainNumber = currentChain;
currentChain += 1;
// This is to match behavior in our original expression evaluator (tmpl)
const globalIdentifier = recast_1.types.builders.identifier(typeof window !== 'object' ? 'global' : 'window');
// We want to define all of our commonly used identifiers and member
// expressions now so we don't have to create multiple instances
const undefinedIdentifier = recast_1.types.builders.identifier('undefined');
const cancelIdentifier = recast_1.types.builders.identifier(`chainCancelToken${chainNumber}`);
const valueIdentifier = recast_1.types.builders.identifier(`chainValue${chainNumber}`);
const cancelMemberExpression = recast_1.types.builders.memberExpression(globalIdentifier, cancelIdentifier);
const valueMemberExpression = recast_1.types.builders.memberExpression(globalIdentifier, valueIdentifier);
const patchedStack = [];
// This builds the cancel check. This lets us slide to the end of the expression
// if it's undefined/null at any of the optional points of the chain.
const buildCancelCheckWrapper = (node) => {
return recast_1.types.builders.conditionalExpression(recast_1.types.builders.binaryExpression('===', cancelMemberExpression, recast_1.types.builders.booleanLiteral(true)), undefinedIdentifier, node);
};
// This is just a quick small wrapper to create the assignment expression
// for the running value.
const buildValueAssignWrapper = (node) => {
return recast_1.types.builders.assignmentExpression('=', valueMemberExpression, node);
};
// This builds what actually does the comparison. It wraps the current
// chunk of the expression with a nullish coalescing operator that returns
// undefined if it's null or undefined. We do this because optional chains
// always return undefined if they fail part way, even if the value they
// fail on is null.
const buildOptionalWrapper = (node) => {
return recast_1.types.builders.binaryExpression('===', recast_1.types.builders.logicalExpression('??', buildValueAssignWrapper(node), undefinedIdentifier), undefinedIdentifier);
};
// Another small wrapper, but for assigning to the cancel token this time.
const buildCancelAssignWrapper = (node) => {
return recast_1.types.builders.assignmentExpression('=', cancelMemberExpression, node);
};
let currentNode = path.node.expression;
let currentPatch = null;
let patchTop = null;
let wrapNextTopInOptionalExtend = false;
// This patches the previous node to use our current one as it's left hand value.
// It takes `window.chainValue1.test1` and `window.chainValue1.test2` and turns it
// into `window.chainValue1.test2.test1`.
const updatePatch = (toPatch, node) => {
if (toPatch.type === 'MemberExpression' || toPatch.type === 'OptionalMemberExpression') {
toPatch.object = node;
}
else if (toPatch.type === 'CallExpression' ||
toPatch.type === 'OptionalCallExpression') {
toPatch.callee = node;
}
};
// This loop walks down an optional chain from the top. This will walk
// from right to left through an optional chain. We keep track of our current
// top of the chain (furthest right) and create a chain below it. This chain
// contains all of the (member and call) expressions that we need. These are
// patched versions that reference our current chain value. We then push this
// chain onto a stack when we hit an optional point in our chain.
while (true) {
// This should only ever be these types but you can optional chain on
// JSX nodes, which we don't support.
if (currentNode.type === 'MemberExpression' ||
currentNode.type === 'OptionalMemberExpression' ||
currentNode.type === 'CallExpression' ||
currentNode.type === 'OptionalCallExpression') {
let patchNode;
// Here we take the current node and extract the parts we actually care
// about.
// In the case of a member expression we take the property it's trying to
// access and make the object it's accessing be our chain value.
if (currentNode.type === 'MemberExpression' ||
currentNode.type === 'OptionalMemberExpression') {
patchNode = recast_1.types.builders.memberExpression(valueMemberExpression, currentNode.property);
// In the case of a call expression we take the arguments and make the
// callee our chain value.
}
else {
patchNode = recast_1.types.builders.callExpression(valueMemberExpression, currentNode.arguments);
}
// If we have a previous node we patch it here.
if (currentPatch) {
updatePatch(currentPatch, patchNode);
}
// If we have no top patch (first run, or just pushed onto the stack) we
// note it here.
if (!patchTop) {
patchTop = patchNode;
}
currentPatch = patchNode;
// This is an optional in our chain. In here we'll push the node onto the
// stack. We also do a polyfill if the top of the stack is function call
// that might be a extended function.
if (currentNode.optional) {
// Implement polyfill described below
if (wrapNextTopInOptionalExtend) {
wrapNextTopInOptionalExtend = false;
// This shouldn't ever happen
if (patchTop.type === 'MemberExpression' &&
patchTop.property.type === 'Identifier') {
patchTop = recast_1.types.builders.callExpression(recast_1.types.builders.identifier(EXPRESSION_EXTENDER_OPTIONAL), [patchTop.object, recast_1.types.builders.stringLiteral(patchTop.property.name)]);
}
}
patchedStack.push(patchTop);
patchTop = null;
currentPatch = null;
// Attempting to optional chain on an extended function. If we don't
// polyfill this most calls will always be undefined. Marking that the
// next part of the chain should be wrapped in our polyfill.
if ((currentNode.type === 'CallExpression' ||
currentNode.type === 'OptionalCallExpression') &&
(currentNode.callee.type === 'MemberExpression' ||
currentNode.callee.type === 'OptionalMemberExpression') &&
currentNode.callee.property.type === 'Identifier' &&
isExpressionExtension(currentNode.callee.property.name)) {
wrapNextTopInOptionalExtend = true;
}
}
// Finally we get the next point AST to walk down.
if (currentNode.type === 'MemberExpression' ||
currentNode.type === 'OptionalMemberExpression') {
currentNode = currentNode.object;
}
else {
currentNode = currentNode.callee;
}
}
else {
// We update the final patch to point to the first part of the optional chain
// which is probably an identifier for an object.
if (currentPatch) {
updatePatch(currentPatch, currentNode);
if (!patchTop) {
patchTop = currentPatch;
}
}
if (wrapNextTopInOptionalExtend) {
wrapNextTopInOptionalExtend = false;
// This shouldn't ever happen
if (patchTop?.type === 'MemberExpression' &&
patchTop.property.type === 'Identifier') {
patchTop = recast_1.types.builders.callExpression(recast_1.types.builders.identifier(EXPRESSION_EXTENDER_OPTIONAL), [patchTop.object, recast_1.types.builders.stringLiteral(patchTop.property.name)]);
}
}
// Push the first part of our chain to stack.
if (patchTop) {
patchedStack.push(patchTop);
}
else {
patchedStack.push(currentNode);
}
break;
}
}
// Since we're working from right to left we need to flip the stack
// for the correct order of operations
patchedStack.reverse();
// Walk the node stack and wrap all our expressions in cancel/assignment
// wrappers.
for (let i = 0; i < patchedStack.length; i++) {
let node = patchedStack[i];
// We don't wrap the last expression in an assignment wrapper because
// it's going to be returned anyway. We just wrap it in a cancel check
// wrapper.
if (i !== patchedStack.length - 1) {
node = buildCancelAssignWrapper(buildOptionalWrapper(node));
}
// Don't wrap the first part in a cancel wrapper because the cancel
// token will always be undefined.
if (i !== 0) {
node = buildCancelCheckWrapper(node);
}
// Replace the node in the stack with our wrapped one
patchedStack[i] = node;
}
// Put all our expressions in a sequence expression (also called a
// group operator). These will all be executed in order and the value
// of the final expression will be returned.
const sequenceNode = recast_1.types.builders.sequenceExpression(patchedStack);
path.replace(sequenceNode);
},
});
// Extended functions
(0, recast_1.visit)(ast, {
visitCallExpression(path) {
this.traverse(path);
if (path.node.callee.type === 'MemberExpression' &&
path.node.callee.property.type === 'Identifier' &&
isExpressionExtension(path.node.callee.property.name)) {
path.replace(recast_1.types.builders.callExpression(recast_1.types.builders.identifier(EXPRESSION_EXTENDER), [
path.node.callee.object,
recast_1.types.builders.stringLiteral(path.node.callee.property.name),
recast_1.types.builders.arrayExpression(path.node.arguments),
]));
}
else if (path.node.callee.type === 'Identifier' &&
path.node.callee.name === '$if' &&
path.node.arguments.every((v) => v.type !== 'SpreadElement')) {
if (path.node.arguments.length < 2) {
throw new expression_extension_error_1.ExpressionExtensionError('$if requires at least 2 parameters: test, value_if_true[, and value_if_false]');
}
const test = path.node.arguments[0];
const consequent = path.node.arguments[1];
const alternative = path.node.arguments[2] === undefined
? recast_1.types.builders.booleanLiteral(false)
: path.node.arguments[2];
path.replace(recast_1.types.builders.conditionalExpression(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
test,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
consequent,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
alternative));
}
},
});
return (0, recast_1.print)(ast);
}
catch (e) {
return;
}
};
exports.extendTransform = extendTransform;
function isDate(input) {
if (typeof input !== 'string' || !input.length) {
return false;
}
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(input)) {
return false;
}
const d = new Date(input);
return d instanceof Date && !isNaN(d.valueOf()) && d.toISOString() === input;
}
function findExtendedFunction(input, functionName) {
// eslint-disable-next-line @typescript-eslint/no-restricted-types
let foundFunction;
if (Array.isArray(input)) {
foundFunction = array_extensions_1.arrayExtensions.functions[functionName];
}
else if (isDate(input) && functionName !== 'toDate' && functionName !== 'toDateTime') {
// If it's a string date (from $json), convert it to a Date object,
// unless that function is `toDate`, since `toDate` does something
// very different on date objects
input = new Date(input);
foundFunction = date_extensions_1.dateExtensions.functions[functionName];
}
else if (typeof input === 'string') {
foundFunction = string_extensions_1.stringExtensions.functions[functionName];
}
else if (typeof input === 'number') {
foundFunction = number_extensions_1.numberExtensions.functions[functionName];
}
else if (input && (luxon_1.DateTime.isDateTime(input) || input instanceof Date)) {
foundFunction = date_extensions_1.dateExtensions.functions[functionName];
}
else if (input !== null && typeof input === 'object') {
foundFunction = object_extensions_1.objectExtensions.functions[functionName];
}
else if (typeof input === 'boolean') {
foundFunction = boolean_extensions_1.booleanExtensions.functions[functionName];
}
// Look for generic or builtin
if (!foundFunction) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const inputAny = input;
// This is likely a builtin we're implementing for another type
// (e.g. toLocaleString). We'll return that instead
if (inputAny && functionName && typeof inputAny[functionName] === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { type: 'native', function: inputAny[functionName] };
}
// Use a generic version if available
foundFunction = genericExtensions[functionName];
}
if (!foundFunction) {
return undefined;
}
return { type: 'extended', function: foundFunction };
}
/**
* Extender function injected by expression extension plugin to allow calls to extensions.
*
* ```ts
* extend(input, "functionName", [...args]);
* ```
*/
function extend(input, functionName, args) {
const foundFunction = findExtendedFunction(input, functionName);
// No type specific or generic function found. Check to see if
// any types have a function with that name. Then throw an error
// letting the user know the available types.
if (!foundFunction) {
(0, utils_1.checkIfValueDefinedOrThrow)(input, functionName);
const haveFunction = exports.EXTENSION_OBJECTS.filter((v) => functionName in v.functions);
if (!haveFunction.length) {
// This shouldn't really be possible but we should cover it anyway
throw new expression_extension_error_1.ExpressionExtensionError(`Unknown expression function: ${functionName}`);
}
if (haveFunction.length > 1) {
const lastType = `"${haveFunction.pop().typeName}"`;
const typeNames = `${haveFunction.map((v) => `"${v.typeName}"`).join(', ')}, and ${lastType}`;
throw new expression_extension_error_1.ExpressionExtensionError(`${functionName}() is only callable on types ${typeNames}`);
}
else {
throw new expression_extension_error_1.ExpressionExtensionError(`${functionName}() is only callable on type "${haveFunction[0].typeName}"`);
}
}
if (foundFunction.type === 'native') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return foundFunction.function.apply(input, args);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return foundFunction.function(input, args);
}
function extendOptional(input, functionName) {
const foundFunction = findExtendedFunction(input, functionName);
if (!foundFunction) {
return undefined;
}
if (foundFunction.type === 'native') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return foundFunction.function.bind(input);
}
return (...args) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return foundFunction.function(input, args);
};
}
const EXTENDED_SYNTAX_CACHE = {};
function extendSyntax(bracketedExpression, forceExtend = false) {
const chunks = (0, expression_parser_1.splitExpression)(bracketedExpression);
const codeChunks = chunks
.filter((c) => c.type === 'code')
.map((c) => c.text.replace(/("|').*?("|')/, '').trim());
if ((!codeChunks.some(exports.hasExpressionExtension) || (0, exports.hasNativeMethod)(bracketedExpression)) &&
!forceExtend) {
return bracketedExpression;
}
// If we've seen this expression before grab it from the cache
if (bracketedExpression in EXTENDED_SYNTAX_CACHE) {
return EXTENDED_SYNTAX_CACHE[bracketedExpression];
}
const extendedChunks = chunks.map((chunk) => {
if (chunk.type === 'code') {
let output = (0, exports.extendTransform)(chunk.text);
// esprima fails to parse bare objects (e.g. `{ data: something }`), we can
// work around this by wrapping it in an parentheses
if (!output?.code && chunk.text.trim()[0] === '{') {
output = (0, exports.extendTransform)(`(${chunk.text})`);
}
if (!output?.code) {
throw new expression_extension_error_1.ExpressionExtensionError('invalid syntax');
}
let text = output.code;
// We need to cut off any trailing semicolons. These cause issues
// with certain types of expression and cause the whole expression
// to fail.
if (text.trim().endsWith(';')) {
text = text.trim().slice(0, -1);
}
return {
...chunk,
text,
};
}
return chunk;
});
const expression = (0, expression_parser_1.joinExpression)(extendedChunks);
// Cache the expression so we don't have to do this transform again
EXTENDED_SYNTAX_CACHE[bracketedExpression] = expression;
return expression;
}
});
//# sourceMappingURL=expression-extension.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
export interface ExpressionText {
type: 'text';
text: string;
}
export interface ExpressionCode {
type: 'code';
text: string;
hasClosingBrackets: boolean;
}
export type ExpressionChunk = ExpressionCode | ExpressionText;
export declare const escapeCode: (text: string) => string;
export declare const splitExpression: (expression: string) => ExpressionChunk[];
export declare const joinExpression: (parts: ExpressionChunk[]) => string;
//# sourceMappingURL=expression-parser.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"expression-parser.d.ts","sourceRoot":"","sources":["../../../src/extensions/expression-parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAIb,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC;AAK9D,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAEzC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,KAAG,eAAe,EA4DnE,CAAC;AAOF,eAAO,MAAM,cAAc,GAAI,OAAO,eAAe,EAAE,KAAG,MASzD,CAAC"}

View File

@@ -0,0 +1,94 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinExpression = exports.splitExpression = exports.escapeCode = void 0;
const OPEN_BRACKET = /(?<escape>\\|)(?<brackets>\{\{)/;
const CLOSE_BRACKET = /(?<escape>\\|)(?<brackets>\}\})/;
const escapeCode = (text) => {
return text.replace('\\}}', '}}');
};
exports.escapeCode = escapeCode;
const splitExpression = (expression) => {
const chunks = [];
let searchingFor = 'open';
let activeRegex = OPEN_BRACKET;
let buffer = '';
let index = 0;
while (index < expression.length) {
const expr = expression.slice(index);
const res = activeRegex.exec(expr);
// No more brackets. If it's a closing bracket
// this is sort of valid so we accept it but mark
// that it has no closing bracket.
if (!res?.groups) {
buffer += expr;
if (searchingFor === 'open') {
chunks.push({
type: 'text',
text: buffer,
});
}
else {
chunks.push({
type: 'code',
text: (0, exports.escapeCode)(buffer),
hasClosingBrackets: false,
});
}
break;
}
if (res.groups.escape) {
buffer += expr.slice(0, res.index + 3);
index += res.index + 3;
}
else {
buffer += expr.slice(0, res.index);
if (searchingFor === 'open') {
chunks.push({
type: 'text',
text: buffer,
});
searchingFor = 'close';
activeRegex = CLOSE_BRACKET;
}
else {
chunks.push({
type: 'code',
text: (0, exports.escapeCode)(buffer),
hasClosingBrackets: true,
});
searchingFor = 'open';
activeRegex = OPEN_BRACKET;
}
index += res.index + 2;
buffer = '';
}
}
return chunks;
};
exports.splitExpression = splitExpression;
// Expressions only have closing brackets escaped
const escapeTmplExpression = (part) => {
return part.replace('}}', '\\}}');
};
const joinExpression = (parts) => {
return parts
.map((chunk) => {
if (chunk.type === 'code') {
return `{{${escapeTmplExpression(chunk.text)}${chunk.hasClosingBrackets ? '}}' : ''}`;
}
return chunk.text;
})
.join('');
};
exports.joinExpression = joinExpression;
});
//# sourceMappingURL=expression-parser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"expression-parser.js","sourceRoot":"","sources":["../../../src/extensions/expression-parser.ts"],"names":[],"mappings":";;;;;;;;;;;;IAgBA,MAAM,YAAY,GAAG,iCAAiC,CAAC;IACvD,MAAM,aAAa,GAAG,iCAAiC,CAAC;IAEjD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC;IAFW,QAAA,UAAU,cAErB;IAEK,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAqB,EAAE;QACxE,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,IAAI,YAAY,GAAqB,MAAM,CAAC;QAC5C,IAAI,WAAW,GAAG,YAAY,CAAC;QAE/B,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,OAAO,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,8CAA8C;YAC9C,iDAAiD;YACjD,kCAAkC;YAClC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC;gBAClB,MAAM,IAAI,IAAI,CAAC;gBACf,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;oBAC7B,MAAM,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,MAAM;qBACZ,CAAC,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACP,MAAM,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAA,kBAAU,EAAC,MAAM,CAAC;wBACxB,kBAAkB,EAAE,KAAK;qBACzB,CAAC,CAAC;gBACJ,CAAC;gBACD,MAAM;YACP,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACvB,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;gBACvC,KAAK,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;gBAEnC,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;oBAC7B,MAAM,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,MAAM;qBACZ,CAAC,CAAC;oBACH,YAAY,GAAG,OAAO,CAAC;oBACvB,WAAW,GAAG,aAAa,CAAC;gBAC7B,CAAC;qBAAM,CAAC;oBACP,MAAM,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAA,kBAAU,EAAC,MAAM,CAAC;wBACxB,kBAAkB,EAAE,IAAI;qBACxB,CAAC,CAAC;oBACH,YAAY,GAAG,MAAM,CAAC;oBACtB,WAAW,GAAG,YAAY,CAAC;gBAC5B,CAAC;gBAED,KAAK,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;gBACvB,MAAM,GAAG,EAAE,CAAC;YACb,CAAC;QACF,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC,CAAC;IA5DW,QAAA,eAAe,mBA4D1B;IAEF,iDAAiD;IACjD,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAE,EAAE;QAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC;IAEK,MAAM,cAAc,GAAG,CAAC,KAAwB,EAAU,EAAE;QAClE,OAAO,KAAK;aACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACd,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC3B,OAAO,KAAK,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACvF,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC;QACnB,CAAC,CAAC;aACD,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,CAAC,CAAC;IATW,QAAA,cAAc,kBASzB"}

View File

@@ -0,0 +1,28 @@
declare function ifEmpty<T, V>(value: V, defaultValue: T): T | (V & {});
declare namespace ifEmpty {
var doc: {
name: string;
description: string;
returnType: string;
args: {
name: string;
type: string;
}[];
docURL: string;
};
}
export declare const extendedFunctions: {
min: (...values: number[]) => number;
max: (...values: number[]) => number;
not: (value: unknown) => boolean;
average: (...args: number[]) => number;
numberList: (start: number, end: number) => number[];
zip: (keys: unknown[], values: unknown[]) => unknown;
$min: (...values: number[]) => number;
$max: (...values: number[]) => number;
$average: (...args: number[]) => number;
$not: (value: unknown) => boolean;
$ifEmpty: typeof ifEmpty;
};
export {};
//# sourceMappingURL=extended-functions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extended-functions.d.ts","sourceRoot":"","sources":["../../../src/extensions/extended-functions.ts"],"names":[],"mappings":"AA0CA,iBAAS,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,gBAgB/C;kBAhBQ,OAAO;;;;;;;;;;;;AA8BhB,eAAO,MAAM,iBAAiB;;;iBAlCV,OAAO,KAAG,OAAO;uBAJX,MAAM,EAAE;wBA3BP,MAAM,OAAO,MAAM,KAAG,MAAM,EAAE;gBAgBtC,OAAO,EAAE,UAAU,OAAO,EAAE,KAAG,OAAO;;;wBAW/B,MAAM,EAAE;kBAId,OAAO,KAAG,OAAO;;CA8CpC,CAAC"}

View File

@@ -0,0 +1,89 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./array-extensions", "../errors/expression-extension.error", "../errors/expression.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extendedFunctions = void 0;
const array_extensions_1 = require("./array-extensions");
const expression_extension_error_1 = require("../errors/expression-extension.error");
const expression_error_1 = require("../errors/expression.error");
const min = Math.min;
const max = Math.max;
const numberList = (start, end) => {
const size = Math.abs(start - end) + 1;
const arr = new Array(size);
let curr = start;
for (let i = 0; i < size; i++) {
if (start < end) {
arr[i] = curr++;
}
else {
arr[i] = curr--;
}
}
return arr;
};
const zip = (keys, values) => {
if (keys.length !== values.length) {
throw new expression_extension_error_1.ExpressionExtensionError('keys and values not of equal length');
}
return keys.reduce((p, c, i) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
p[c] = values[i];
return p;
}, {});
};
const average = (...args) => {
return (0, array_extensions_1.average)(args);
};
const not = (value) => {
return !value;
};
function ifEmpty(value, defaultValue) {
if (arguments.length !== 2) {
throw new expression_error_1.ExpressionError('expected two arguments (value, defaultValue) for this function');
}
if (value === undefined || value === null || value === '') {
return defaultValue;
}
if (typeof value === 'object') {
if (Array.isArray(value) && !value.length) {
return defaultValue;
}
if (!Object.keys(value).length) {
return defaultValue;
}
}
return value;
}
ifEmpty.doc = {
name: 'ifEmpty',
description: 'Returns the default value if the value is empty. Empty values are undefined, null, empty strings, arrays without elements and objects without keys.',
returnType: 'any',
args: [
{ name: 'value', type: 'any' },
{ name: 'defaultValue', type: 'any' },
],
docURL: 'https://docs.n8n.io/code/builtin/convenience',
};
exports.extendedFunctions = {
min,
max,
not,
average,
numberList,
zip,
$min: min,
$max: max,
$average: average,
$not: not,
$ifEmpty: ifEmpty,
};
});
//# sourceMappingURL=extended-functions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extended-functions.js","sourceRoot":"","sources":["../../../src/extensions/extended-functions.ts"],"names":[],"mappings":";;;;;;;;;;;;IAAA,yDAAyD;IACzD,qFAAgF;IAChF,iEAA6D;IAE7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IAErB,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,GAAW,EAAY,EAAE;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAS,IAAI,CAAC,CAAC;QAEpC,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;gBACjB,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACP,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;YACjB,CAAC;QACF,CAAC;QAED,OAAO,GAAG,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,IAAe,EAAE,MAAiB,EAAW,EAAE;QAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,IAAI,qDAAwB,CAAC,qCAAqC,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC9B,0GAA0G;YACzG,CAAS,CAAC,CAAQ,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACjC,OAAO,CAAC,CAAC;QACV,CAAC,EAAE,EAAE,CAAC,CAAC;IACR,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAG,IAAc,EAAE,EAAE;QACrC,OAAO,IAAA,0BAAQ,EAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,KAAc,EAAW,EAAE;QACvC,OAAO,CAAC,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,SAAS,OAAO,CAAO,KAAQ,EAAE,YAAe;QAC/C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,kCAAe,CAAC,gEAAgE,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC3D,OAAO,YAAY,CAAC;QACrB,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC3C,OAAO,YAAY,CAAC;YACrB,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;gBAChC,OAAO,YAAY,CAAC;YACrB,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,OAAO,CAAC,GAAG,GAAG;QACb,IAAI,EAAE,SAAS;QACf,WAAW,EACV,qJAAqJ;QACtJ,UAAU,EAAE,KAAK;QACjB,IAAI,EAAE;YACL,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE;YAC9B,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE;SACrC;QACD,MAAM,EAAE,8CAA8C;KACtD,CAAC;IAEW,QAAA,iBAAiB,GAAG;QAChC,GAAG;QACH,GAAG;QACH,GAAG;QACH,OAAO;QACP,UAAU;QACV,GAAG;QACH,IAAI,EAAE,GAAG;QACT,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,OAAO;KACjB,CAAC"}

View File

@@ -0,0 +1,42 @@
export interface ExtensionMap {
typeName: string;
functions: Record<string, Extension>;
}
export type Extension = Function & {
doc?: DocMetadata;
};
export type NativeDoc = {
typeName: string;
properties?: Record<string, {
doc?: DocMetadata;
}>;
functions: Record<string, {
doc?: DocMetadata;
}>;
};
export type DocMetadataArgument = {
name: string;
type?: string;
optional?: boolean;
variadic?: boolean;
description?: string;
default?: string;
args?: DocMetadataArgument[];
};
export type DocMetadataExample = {
example: string;
evaluated?: string;
description?: string;
};
export type DocMetadata = {
name: string;
returnType: string;
description?: string;
section?: string;
hidden?: boolean;
aliases?: string[];
args?: DocMetadataArgument[];
examples?: DocMetadataExample[];
docURL?: string;
};
//# sourceMappingURL=extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/extensions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC;AAGD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG;IAAE,GAAG,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAEzD,MAAM,MAAM,SAAS,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACnD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC7B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}

View File

@@ -0,0 +1,13 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
});
//# sourceMappingURL=extensions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../src/extensions/extensions.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,3 @@
export { extend, extendOptional, hasExpressionExtension, hasNativeMethod, extendTransform, EXTENSION_OBJECTS as ExpressionExtensions, } from './expression-extension';
export type { DocMetadata, NativeDoc, Extension, DocMetadataArgument, DocMetadataExample, } from './extensions';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,MAAM,EACN,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,iBAAiB,IAAI,oBAAoB,GACzC,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,cAAc,CAAC"}

21
node_modules/n8n-workflow/dist/cjs/extensions/index.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./expression-extension"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressionExtensions = exports.extendTransform = exports.hasNativeMethod = exports.hasExpressionExtension = exports.extendOptional = exports.extend = void 0;
var expression_extension_1 = require("./expression-extension");
Object.defineProperty(exports, "extend", { enumerable: true, get: function () { return expression_extension_1.extend; } });
Object.defineProperty(exports, "extendOptional", { enumerable: true, get: function () { return expression_extension_1.extendOptional; } });
Object.defineProperty(exports, "hasExpressionExtension", { enumerable: true, get: function () { return expression_extension_1.hasExpressionExtension; } });
Object.defineProperty(exports, "hasNativeMethod", { enumerable: true, get: function () { return expression_extension_1.hasNativeMethod; } });
Object.defineProperty(exports, "extendTransform", { enumerable: true, get: function () { return expression_extension_1.extendTransform; } });
Object.defineProperty(exports, "ExpressionExtensions", { enumerable: true, get: function () { return expression_extension_1.EXTENSION_OBJECTS; } });
});
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/index.ts"],"names":[],"mappings":";;;;;;;;;;;;IAAA,+DAOgC;IAN/B,8GAAA,MAAM,OAAA;IACN,sHAAA,cAAc,OAAA;IACd,8HAAA,sBAAsB,OAAA;IACtB,uHAAA,eAAe,OAAA;IACf,uHAAA,eAAe,OAAA;IACf,4HAAA,iBAAiB,OAAwB"}

View File

@@ -0,0 +1,27 @@
import { DateTime } from 'luxon';
import type { ExtensionMap } from './extensions';
type DateTimeFormat = 'ms' | 's' | 'us' | 'excel';
export declare function toDateTime(value: number, extraArgs: [DateTimeFormat]): DateTime;
export declare namespace toDateTime {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
section: string;
returnType: string;
args: {
name: string;
optional: boolean;
description: string;
default: string;
type: string;
}[];
docURL: string;
};
}
export declare const numberExtensions: ExtensionMap;
export {};
//# sourceMappingURL=number-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"number-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/number-extensions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA2DjD,KAAK,cAAc,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,OAAO,CAAC;AAClD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,cAAc,CAAC,YA4BpE;yBA5Be,UAAU;;;;;;;;;;;;;;;;;;;;AA4L1B,eAAO,MAAM,gBAAgB,EAAE,YAgB9B,CAAC"}

View File

@@ -0,0 +1,236 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "luxon", "../errors/expression-extension.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.numberExtensions = void 0;
exports.toDateTime = toDateTime;
// @vitest-environment jsdom
const luxon_1 = require("luxon");
const expression_extension_error_1 = require("../errors/expression-extension.error");
function format(value, extraArgs) {
const [locales = 'en-US', config = {}] = extraArgs;
return new Intl.NumberFormat(locales, config).format(value);
}
function isEven(value) {
if (!Number.isInteger(value)) {
throw new expression_extension_error_1.ExpressionExtensionError('isEven() is only callable on integers');
}
return value % 2 === 0;
}
function isOdd(value) {
if (!Number.isInteger(value)) {
throw new expression_extension_error_1.ExpressionExtensionError('isOdd() is only callable on integers');
}
return Math.abs(value) % 2 === 1;
}
function floor(value) {
return Math.floor(value);
}
function ceil(value) {
return Math.ceil(value);
}
function abs(value) {
return Math.abs(value);
}
function isInteger(value) {
return Number.isInteger(value);
}
function round(value, extraArgs) {
const [decimalPlaces = 0] = extraArgs;
return +value.toFixed(decimalPlaces);
}
function toBoolean(value) {
return value !== 0;
}
function toInt(value) {
return round(value, []);
}
function toFloat(value) {
return value;
}
function toDateTime(value, extraArgs) {
const [valueFormat = 'ms'] = extraArgs;
if (!['ms', 's', 'us', 'excel'].includes(valueFormat)) {
throw new expression_extension_error_1.ExpressionExtensionError(`Unsupported format '${String(valueFormat)}'. toDateTime() supports 'ms', 's', 'us' and 'excel'.`);
}
switch (valueFormat) {
// Excel format is days since 1900
// There is a bug where 1900 is incorrectly treated as a leap year
case 'excel': {
const DAYS_BETWEEN_1900_1970 = 25567;
const DAYS_LEAP_YEAR_BUG_ADJUST = 2;
const SECONDS_IN_DAY = 86_400;
return luxon_1.DateTime.fromSeconds((value - (DAYS_BETWEEN_1900_1970 + DAYS_LEAP_YEAR_BUG_ADJUST)) * SECONDS_IN_DAY);
}
case 's':
return luxon_1.DateTime.fromSeconds(value);
case 'us':
return luxon_1.DateTime.fromMillis(value / 1000);
case 'ms':
default:
return luxon_1.DateTime.fromMillis(value);
}
}
ceil.doc = {
name: 'ceil',
description: 'Rounds the number up to the next whole number',
examples: [{ example: '(1.234).ceil()', evaluated: '2' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-ceil',
};
floor.doc = {
name: 'floor',
description: 'Rounds the number down to the nearest whole number',
examples: [{ example: '(1.234).floor()', evaluated: '1' }],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-floor',
};
isEven.doc = {
name: 'isEven',
description: "Returns <code>true</code> if the number is even or <code>false</code> if not. Throws an error if the number isn't a whole number.",
examples: [
{ example: '(33).isEven()', evaluated: 'false' },
{ example: '(42).isEven()', evaluated: 'true' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isEven',
};
isOdd.doc = {
name: 'isOdd',
description: "Returns <code>true</code> if the number is odd or <code>false</code> if not. Throws an error if the number isn't a whole number.",
examples: [
{ example: '(33).isOdd()', evaluated: 'true' },
{ example: '(42).isOdd()', evaluated: 'false' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isOdd',
};
format.doc = {
name: 'format',
description: 'Returns a formatted string representing the number. Useful for formatting for a specific language or currency. The same as <a target="_blank" href=”https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat”><code>Intl.NumberFormat()</code></a>.',
examples: [
{ example: "(123456.789).format('de-DE')", evaluated: '123.456,789' },
{
example: "(123456.789).format('de-DE', {'style': 'currency', 'currency': 'EUR'})",
evaluated: '123.456,79 €',
},
],
returnType: 'string',
args: [
{
name: 'locale',
optional: true,
description: 'A <a target="_blank" href=”https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument”>locale tag</a> for formatting the number, e.g. <code>fr-FR</code>, <code>en-GB</code>, <code>pr-BR</code>',
default: '"en-US"',
type: 'string',
},
{
name: 'options',
optional: true,
description: 'Configuration options for number formatting. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat" target="_blank">More info</a>',
type: 'object',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-format',
};
round.doc = {
name: 'round',
description: 'Rounds the number to the nearest integer (or decimal place)',
examples: [
{ example: '(1.256).round()', evaluated: '1' },
{ example: '(1.256).round(1)', evaluated: '1.3' },
{ example: '(1.256).round(2)', evaluated: '1.26' },
],
returnType: 'number',
args: [
{
name: 'decimalPlaces',
optional: true,
description: 'The number of decimal places to round to',
default: '0',
type: 'number',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-round',
};
toBoolean.doc = {
name: 'toBoolean',
description: 'Returns <code>false</code> for <code>0</code> and <code>true</code> for any other number (including negative numbers).',
examples: [
{ example: '(12).toBoolean()', evaluated: 'true' },
{ example: '(0).toBoolean()', evaluated: 'false' },
{ example: '(-1.3).toBoolean()', evaluated: 'true' },
],
section: 'cast',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-toBoolean',
};
toDateTime.doc = {
name: 'toDateTime',
description: 'Converts a numerical timestamp into a <a target="_blank" href="https://moment.github.io/luxon/api-docs/">Luxon</a> DateTime. The format of the timestamp must be specified if it\'s not in milliseconds. Uses the timezone specified in workflow settings if available; otherwise, it defaults to the timezone set for the instance.',
examples: [
{ example: "(1708695471).toDateTime('s')", evaluated: '2024-02-23T14:37:51.000+01:00' },
{ example: "(1708695471000).toDateTime('ms')", evaluated: '2024-02-23T14:37:51.000+01:00' },
{ example: "(1708695471000000).toDateTime('us')", evaluated: '2024-02-23T14:37:51.000+01:00' },
{ example: "(45345).toDateTime('excel')", evaluated: '2024-02-23T01:00:00.000+01:00' },
],
section: 'cast',
returnType: 'DateTime',
args: [
{
name: 'format',
optional: true,
description: 'The type of timestamp to convert. Options are <code>ms</code> (for Unix timestamp in milliseconds), <code>s</code> (for Unix timestamp in seconds), <code>us</code> (for Unix timestamp in microseconds) or <code>excel</code> (for days since 1900).',
default: '"ms"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-toDateTime',
};
abs.doc = {
name: 'abs',
description: "Returns the number's absolute value, i.e. removes any minus sign",
examples: [
{ example: '(-1.7).abs()', evaluated: '1.7' },
{ example: '(1.7).abs()', evaluated: '1.7' },
],
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-abs',
};
isInteger.doc = {
name: 'isInteger',
description: 'Returns <code>true</code> if the number is a whole number',
examples: [
{ example: '(4).isInteger()', evaluated: 'true' },
{ example: '(4.12).isInteger()', evaluated: 'false' },
{ example: '(-4).isInteger()', evaluated: 'true' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isInteger',
};
exports.numberExtensions = {
typeName: 'Number',
functions: {
ceil,
floor,
format,
round,
abs,
isInteger,
isEven,
isOdd,
toBoolean,
toInt,
toFloat,
toDateTime,
},
};
});
//# sourceMappingURL=number-extensions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,46 @@
import type { ExtensionMap } from './extensions';
export declare function compact(value: object): object;
export declare namespace compact {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
returnType: string;
docURL: string;
};
}
export declare function urlEncode(value: object): string;
export declare namespace urlEncode {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
returnType: string;
docURL: string;
};
}
export declare function toJsonString(value: object): string;
export declare namespace toJsonString {
var doc: {
name: string;
description: string;
examples: {
example: string;
evaluated: string;
}[];
docURL: string;
returnType: string;
};
}
export declare function toInt(): undefined;
export declare function toFloat(): undefined;
export declare function toBoolean(): undefined;
export declare function toDateTime(): undefined;
export declare const objectExtensions: ExtensionMap;
//# sourceMappingURL=object-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"object-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/object-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAmEjD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB7C;yBAjBe,OAAO;;;;;;;;;;;;AAmBvB,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,UAEtC;yBAFe,SAAS;;;;;;;;;;;;AAIzB,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAEzC;yBAFe,YAAY;;;;;;;;;;;;AAI5B,wBAAgB,KAAK,cAEpB;AAED,wBAAgB,OAAO,cAEtB;AAED,wBAAgB,SAAS,cAExB;AAED,wBAAgB,UAAU,cAEzB;AA8LD,eAAO,MAAM,gBAAgB,EAAE,YAmB9B,CAAC"}

View File

@@ -0,0 +1,296 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../errors/expression-extension.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.objectExtensions = void 0;
exports.compact = compact;
exports.urlEncode = urlEncode;
exports.toJsonString = toJsonString;
exports.toInt = toInt;
exports.toFloat = toFloat;
exports.toBoolean = toBoolean;
exports.toDateTime = toDateTime;
const expression_extension_error_1 = require("../errors/expression-extension.error");
function isEmpty(value) {
return Object.keys(value).length === 0;
}
function isNotEmpty(value) {
return !isEmpty(value);
}
function keys(value) {
return Object.keys(value);
}
function values(value) {
return Object.values(value);
}
function hasField(value, extraArgs) {
const [name] = extraArgs;
return name in value;
}
function removeField(value, extraArgs) {
const [name] = extraArgs;
if (name in value) {
const newObject = { ...value };
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
delete newObject[name];
return newObject;
}
return value;
}
function removeFieldsContaining(value, extraArgs) {
const [match] = extraArgs;
if (typeof match !== 'string' || match === '') {
throw new expression_extension_error_1.ExpressionExtensionError('removeFieldsContaining(): expected non-empty string arg');
}
const newObject = { ...value };
for (const [key, val] of Object.entries(value)) {
if (typeof val === 'string' && val.includes(match)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
delete newObject[key];
}
}
return newObject;
}
function keepFieldsContaining(value, extraArgs) {
const [match] = extraArgs;
if (typeof match !== 'string' || match === '') {
throw new expression_extension_error_1.ExpressionExtensionError('argument of keepFieldsContaining must be a non-empty string');
}
const newObject = { ...value };
for (const [key, val] of Object.entries(value)) {
if (typeof val !== 'string' || (typeof val === 'string' && !val.includes(match))) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
delete newObject[key];
}
}
return newObject;
}
function compact(value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newObj = {};
for (const [key, val] of Object.entries(value)) {
if (val !== null && val !== undefined && val !== 'nil' && val !== '') {
if (typeof val === 'object') {
if (Object.keys(val).length === 0)
continue;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
newObj[key] = compact(val);
}
else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
newObj[key] = val;
}
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return newObj;
}
function urlEncode(value) {
return new URLSearchParams(value).toString();
}
function toJsonString(value) {
return JSON.stringify(value);
}
function toInt() {
return undefined;
}
function toFloat() {
return undefined;
}
function toBoolean() {
return undefined;
}
function toDateTime() {
return undefined;
}
isEmpty.doc = {
name: 'isEmpty',
description: 'Returns <code>true</code> if the Object has no keys (fields) set or is <code>null</code>',
examples: [
{ example: "({'name': 'Nathan'}).isEmpty()", evaluated: 'false' },
{ example: '({}).isEmpty()', evaluated: 'true' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-isEmpty',
};
isNotEmpty.doc = {
name: 'isNotEmpty',
description: 'Returns <code>true</code> if the Object has at least one key (field) set',
examples: [
{ example: "({'name': 'Nathan'}).isNotEmpty()", evaluated: 'true' },
{ example: '({}).isNotEmpty()', evaluated: 'false' },
],
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-isNotEmpty',
};
compact.doc = {
name: 'compact',
description: 'Removes all fields that have empty values, i.e. are <code>null</code>, <code>undefined</code>, <code>"nil"</code> or <code>""</code>',
examples: [{ example: "({ x: null, y: 2, z: '' }).compact()", evaluated: '{ y: 2 }' }],
returnType: 'Object',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-compact',
};
urlEncode.doc = {
name: 'urlEncode',
description: "Generates a URL parameter string from the Object's keys and values. Only top-level keys are supported.",
examples: [
{
example: "({ name: 'Mr Nathan', city: 'hanoi' }).urlEncode()",
evaluated: "'name=Mr+Nathan&city=hanoi'",
},
],
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-urlEncode',
};
hasField.doc = {
name: 'hasField',
description: 'Returns <code>true</code> if there is a field called <code>name</code>. Only checks top-level keys. Comparison is case-sensitive.',
examples: [
{ example: "({ name: 'Nathan', age: 42 }).hasField('name')", evaluated: 'true' },
{ example: "({ name: 'Nathan', age: 42 }).hasField('Name')", evaluated: 'false' },
{ example: "({ name: 'Nathan', age: 42 }).hasField('inventedField')", evaluated: 'false' },
],
returnType: 'boolean',
args: [
{
name: 'name',
optional: false,
description: 'The name of the key to search for',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-hasField',
};
removeField.doc = {
name: 'removeField',
description: "Removes a field from the Object. The same as JavaScript's <code>delete</code>.",
examples: [
{
example: "({ name: 'Nathan', city: 'hanoi' }).removeField('name')",
evaluated: "{ city: 'hanoi' }",
},
],
returnType: 'Object',
args: [
{
name: 'key',
optional: false,
description: 'The name of the field to remove',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-removeField',
};
removeFieldsContaining.doc = {
name: 'removeFieldsContaining',
description: "Removes keys (fields) whose values at least partly match the given <code>value</code>. Comparison is case-sensitive. Fields that aren't strings are always kept.",
examples: [
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).removeFieldsContaining('Nathan')",
evaluated: "{ city: 'hanoi', age: 42 }",
},
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).removeFieldsContaining('Han')",
evaluated: '{ age: 42 }',
},
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).removeFieldsContaining('nathan')",
evaluated: "{ name: 'Mr Nathan', city: 'hanoi', age: 42 }",
},
],
returnType: 'Object',
args: [
{
name: 'value',
optional: false,
description: 'The text that a value must contain in order to be removed',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-removeFieldsContaining',
};
keepFieldsContaining.doc = {
name: 'keepFieldsContaining',
description: "Removes any fields whose values don't at least partly match the given <code>value</code>. Comparison is case-sensitive. Fields that aren't strings will always be removed.",
examples: [
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).keepFieldsContaining('Nathan')",
evaluated: "{ name: 'Mr Nathan' }",
},
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).keepFieldsContaining('nathan')",
evaluated: '{}',
},
{
example: "({ name: 'Mr Nathan', city: 'hanoi', age: 42 }).keepFieldsContaining('han')",
evaluated: "{ name: 'Mr Nathan', city: 'hanoi' }",
},
],
returnType: 'Object',
args: [
{
name: 'value',
optional: false,
description: 'The text that a value must contain in order to be kept',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-keepFieldsContaining',
};
keys.doc = {
name: 'keys',
description: "Returns an array with all the field names (keys) the Object contains. The same as JavaScript's <code>Object.keys(obj)</code>.",
examples: [{ example: "({ name: 'Mr Nathan', age: 42 }).keys()", evaluated: "['name', 'age']" }],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-keys',
returnType: 'Array',
};
values.doc = {
name: 'values',
description: "Returns an array with all the values of the fields the Object contains. The same as JavaScript's <code>Object.values(obj)</code>.",
examples: [
{ example: "({ name: 'Mr Nathan', age: 42 }).values()", evaluated: "['Mr Nathan', 42]" },
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-values',
returnType: 'Array',
};
toJsonString.doc = {
name: 'toJsonString',
description: "Converts the Object to a JSON string. Similar to JavaScript's <code>JSON.stringify()</code>.",
examples: [
{
example: "({ name: 'Mr Nathan', age: 42 }).toJsonString()",
evaluated: '\'{"name":"Nathan","age":42}\'',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-toJsonString',
returnType: 'string',
};
exports.objectExtensions = {
typeName: 'Object',
functions: {
isEmpty,
isNotEmpty,
hasField,
removeField,
removeFieldsContaining,
keepFieldsContaining,
compact,
urlEncode,
keys,
values,
toJsonString,
toInt,
toFloat,
toBoolean,
toDateTime,
},
};
});
//# sourceMappingURL=object-extensions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
import { DateTime } from 'luxon';
import type { ExtensionMap } from './extensions';
export declare const SupportedHashAlgorithms: readonly ["md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha3"];
export declare function toJsonString(value: string): string;
export declare namespace toJsonString {
var doc: {
name: string;
description: string;
section: string;
returnType: string;
docURL: string;
examples: {
example: string;
evaluated: string;
}[];
};
}
export declare function toDateTime(value: string, extraArgs?: [string]): DateTime;
export declare namespace toDateTime {
var doc: {
name: string;
description: string;
section: string;
returnType: string;
docURL: string;
examples: {
example: string;
}[];
args: {
name: string;
optional: boolean;
description: string;
type: string;
}[];
};
}
export declare const stringExtensions: ExtensionMap;
//# sourceMappingURL=string-extensions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"string-extensions.d.ts","sourceRoot":"","sources":["../../../src/extensions/string-extensions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAKjC,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5D,eAAO,MAAM,uBAAuB,0EAQ1B,CAAC;AA6IX,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;yBAFe,YAAY;;;;;;;;;;;;;AA+D5B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,CAAC,MAAM,CAAQ,GAAG,QAAQ,CAoB9E;yBApBe,UAAU;;;;;;;;;;;;;;;;;;AAwoB1B,eAAO,MAAM,gBAAgB,EAAE,YAqC9B,CAAC"}

View File

@@ -0,0 +1,788 @@
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "js-base64", "jssha", "luxon", "md5", "title-case", "transliteration", "./number-extensions", "../errors/expression-extension.error", "../type-validation"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.stringExtensions = exports.SupportedHashAlgorithms = void 0;
exports.toJsonString = toJsonString;
exports.toDateTime = toDateTime;
const js_base64_1 = require("js-base64");
const jssha_1 = __importDefault(require("jssha"));
const luxon_1 = require("luxon");
const md5_1 = __importDefault(require("md5"));
const title_case_1 = require("title-case");
const transliteration_1 = require("transliteration");
const number_extensions_1 = require("./number-extensions");
const expression_extension_error_1 = require("../errors/expression-extension.error");
const type_validation_1 = require("../type-validation");
exports.SupportedHashAlgorithms = [
'md5',
'sha1',
'sha224',
'sha256',
'sha384',
'sha512',
'sha3',
];
// All symbols from https://www.xe.com/symbols/ as for 2022/11/09
const CURRENCY_REGEXP = /(\u004c\u0065\u006b|\u060b|\u0024|\u0192|\u20bc|\u0042\u0072|\u0042\u005a\u0024|\u0024\u0062|\u004b\u004d|\u0050|\u043b\u0432|\u0052\u0024|\u17db|\u00a5|\u20a1|\u006b\u006e|\u20b1|\u004b\u010d|\u006b\u0072|\u0052\u0044\u0024|\u00a3|\u20ac|\u00a2|\u0051|\u004c|\u0046\u0074|\u20b9|\u0052\u0070|\ufdfc|\u20aa|\u004a\u0024|\u20a9|\u20ad|\u0434\u0435\u043d|\u0052\u004d|\u20a8|\u20ae|\u004d\u0054|\u0043\u0024|\u20a6|\u0042\u002f\u002e|\u0047\u0073|\u0053\u002f\u002e|\u007a\u0142|\u006c\u0065\u0069|\u20bd|\u0414\u0438\u043d\u002e|\u0053|\u0052|\u0043\u0048\u0046|\u004e\u0054\u0024|\u0e3f|\u0054\u0054\u0024|\u20ba|\u20b4|\u0024\u0055|\u0042\u0073|\u20ab|\u005a\u0024)/gu;
/*
Extract the domain part from various inputs, including URLs, email addresses, and plain domains.
/^(?:(?:https?|ftp):\/\/)? // Match optional http, https, or ftp protocols
(?:mailto:)? // Match optional mailto:
(?:\/\/)? // Match optional double slashes
(?:www\.)? // Match optional www prefix
(?:[-\w]*\.)? // Match any optional subdomain
( // Capture the domain part
(?:(?:[-\w]+\.)+ // Match one or more subdomains
(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+) // Match top-level domain or Punycode encoded IDN(xn--80aswg.xn--p1ai)
|localhost // Match localhost
|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} // Match IPv4 addresses
)
)
(?::\d+)? // Match optional port number
(?:\/[^\s?]*)? // Match optional path
(?:\?[^\s#]*)? // Match optional query string
(?:#[^\s]*)?$/i; // Match optional hash fragment
*/
const DOMAIN_EXTRACT_REGEXP = /^(?:(?:https?|ftp):\/\/)?(?:mailto:)?(?:\/\/)?((?:www\.)?(?:(?:[-\w]+\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::\d+)?(?:\/[^\s?]*)?(?:\?[^\s#]*)?(?:#[^\s]*)?$/i;
/*
Matches domain names without the protocol or optional subdomains
/^(?:www\.)? // Match optional www prefix
( // Capture the domain part
(?:(?:[-\w]+\.)+ // Match one or more subdomains
(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+) // Match top-level domain or Punycode encoded IDN
|localhost // Match localhost
|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} // Match IPv4 addresses
)
)
(?::\d+)? // Match optional port number
(?:\/[^\s?]*)? // Match optional path
(?:\?[^\s#]*)? // Match optional query string
(?:#[^\s]*)?$/i; // Match optional fragment at the end of the string
*/
const DOMAIN_REGEXP = /^(?:www\.)?((?:(?:[-\w]+\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::\d+)?(?:\/[^\s?]*)?(?:\?[^\s#]*)?(?:#[^\s]*)?$/i;
/*
Matches email addresses
/(
( // Capture local part of the email address
([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*) // One or more characters not in the set, followed by
a period, followed by one or more characters not in the set
|(".+") // Or one or more characters inside quotes
)
)
@ // Match @ symbol
(?<domain>( // Capture the domain part of the email address
\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\] // Match IPv4 address inside brackets
|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}) // Or match domain with at least two subdomains and TLD
))/;
*/
const EMAIL_REGEXP = /(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@(?<domain>(\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
/*
Matches URLs with strict beginning and end of the string checks
/^(?:(?:https?|ftp):\/\/) // Match http, https, or ftp protocols at the start of the string
(?:www\.)? // Match optional www prefix
( // Capture the domain part
(?:(?:[-\w]+\.)+ // Match one or more subdomains
(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+) // Match top-level domain or Punycode encoded IDN
|localhost // Match localhost
|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} // Match IPv4 addresses
)
)
(?::\d+)? // Match optional port number
(?:\/[^\s?#]*)? // Match optional path
(?:\?[^\s#]*)? // Match optional query string
(?=([^\s]+#.*)?) // Positive lookahead for the fragment identifier
#?[^\s]*$/i; // Match optional fragment at the end of the string
*/
const URL_REGEXP_EXACT = /^(?:(?:https?|ftp):\/\/)(?:www\.)?((?:(?:[-\w]+\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::\d+)?(?:\/[^\s?#]*)?(?:\?[^\s#]*)?(?=([^\s]+#.*)?)#?[^\s]*$/i;
/*
Same as URL_REGEXP_EXACT but without the strict beginning and end of the string checks to allow for
matching URLs in the middle of a string
*/
const URL_REGEXP = /(?:(?:https?|ftp):\/\/)(?:www\.)?((?:(?:[-\w]+\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::\d+)?(?:\/[^\s?#]*)?(?:\?[^\s#]*)?(?=([^\s]+#.*)?)#?[^\s]*/i;
const CHAR_TEST_REGEXP = /\p{L}/u;
const PUNC_TEST_REGEXP = /[!?.]/;
function hash(value, extraArgs) {
const algorithm = extraArgs[0]?.toLowerCase() ?? 'md5';
switch (algorithm) {
case 'base64':
return (0, js_base64_1.toBase64)(value);
case 'md5':
return (0, md5_1.default)(value);
case 'sha1':
case 'sha224':
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha3':
const variant = {
sha1: 'SHA-1',
sha224: 'SHA-224',
sha256: 'SHA-256',
sha384: 'SHA-384',
sha512: 'SHA-512',
sha3: 'SHA3-512',
}[algorithm];
return new jssha_1.default(variant, 'TEXT').update(value).getHash('HEX');
default:
throw new expression_extension_error_1.ExpressionExtensionError(`Unknown algorithm ${algorithm}. Available algorithms are: ${exports.SupportedHashAlgorithms.join()}, and Base64.`);
}
}
function isEmpty(value) {
return value === '';
}
function isNotEmpty(value) {
return !isEmpty(value);
}
function length(value) {
return value.length;
}
function toJsonString(value) {
return JSON.stringify(value);
}
function removeMarkdown(value) {
let output = value;
try {
output = output.replace(/^([\s\t]*)([*\-+]|\d\.)\s+/gm, '$1');
output = output
// Header
.replace(/\n={2,}/g, '\n')
// Strikethrough
.replace(/~~/g, '')
// Fenced codeblocks
.replace(/`{3}.*\n/g, '');
output = output
// Remove HTML tags
.replace(/<[\w|\s|=|'|"|:|(|)|,|;|/|0-9|.|-]+[>|\\>]/g, '')
// Remove setext-style headers
.replace(/^[=-]{2,}\s*$/g, '')
// Remove footnotes?
.replace(/\[\^.+?\](: .*?$)?/g, '')
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
// Remove images
.replace(/!\[.*?\][[(].*?[\])]/g, '')
// Remove inline links
.replace(/\[(.*?)\][[(].*?[\])]/g, '$1')
// Remove Blockquotes
.replace(/>/g, '')
// Remove reference-style links?
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
// Remove atx-style headers
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
.replace(/([*_]{1,3})(\S.*?\S)\1/g, '$2')
.replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1')
.replace(/\n{2,}/g, '\n\n');
}
catch (e) {
return value;
}
return output;
}
function removeTags(value) {
return value.replace(/<[^>]*>?/gm, '');
}
function toDate(value) {
const date = new Date(Date.parse(value));
if (date.toString() === 'Invalid Date') {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to date');
}
// If time component is not specified, force 00:00h
if (!/:/.test(value)) {
date.setHours(0, 0, 0);
}
return date;
}
function toDateTime(value, extraArgs = ['']) {
try {
const [valueFormat] = extraArgs;
if (valueFormat) {
if (valueFormat === 'ms' ||
valueFormat === 's' ||
valueFormat === 'us' ||
valueFormat === 'excel') {
return (0, number_extensions_1.toDateTime)(Number(value), [valueFormat]);
}
return luxon_1.DateTime.fromFormat(value, valueFormat);
}
return (0, type_validation_1.tryToParseDateTime)(value);
}
catch (error) {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to Luxon DateTime');
}
}
function urlDecode(value, extraArgs) {
const [entireString = false] = extraArgs;
if (entireString) {
return decodeURI(value.toString());
}
return decodeURIComponent(value.toString());
}
function urlEncode(value, extraArgs) {
const [entireString = false] = extraArgs;
if (entireString) {
return encodeURI(value.toString());
}
return encodeURIComponent(value.toString());
}
function toInt(value, extraArgs) {
const [radix] = extraArgs;
const int = parseInt(value.replace(CURRENCY_REGEXP, ''), radix);
if (isNaN(int)) {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to integer');
}
return int;
}
function toFloat(value) {
if (value.includes(',')) {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to float, expected . as decimal separator');
}
const float = parseFloat(value.replace(CURRENCY_REGEXP, ''));
if (isNaN(float)) {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to float');
}
return float;
}
function toNumber(value) {
const num = Number(value.replace(CURRENCY_REGEXP, ''));
if (isNaN(num)) {
throw new expression_extension_error_1.ExpressionExtensionError('cannot convert to number');
}
return num;
}
function quote(value, extraArgs) {
const [quoteChar = '"'] = extraArgs;
return `${quoteChar}${value
.replace(/\\/g, '\\\\')
.replace(new RegExp(`\\${quoteChar}`, 'g'), `\\${quoteChar}`)}${quoteChar}`;
}
function isNumeric(value) {
if (value.includes(' '))
return false;
return !isNaN(value) && !isNaN(parseFloat(value));
}
function isUrl(value) {
return URL_REGEXP_EXACT.test(value);
}
function isDomain(value) {
return DOMAIN_REGEXP.test(value);
}
function isEmail(value) {
const result = EMAIL_REGEXP.test(value);
// email regex is loose so check manually for now
if (result && value.includes(' ')) {
return false;
}
return result;
}
function toTitleCase(value) {
return (0, title_case_1.titleCase)(value);
}
function replaceSpecialChars(value) {
return (0, transliteration_1.transliterate)(value, { unknown: '?' });
}
function toSentenceCase(value) {
let current = value.slice();
let buffer = '';
while (CHAR_TEST_REGEXP.test(current)) {
const charIndex = current.search(CHAR_TEST_REGEXP);
current =
current.slice(0, charIndex) +
current[charIndex].toLocaleUpperCase() +
current.slice(charIndex + 1).toLocaleLowerCase();
const puncIndex = current.search(PUNC_TEST_REGEXP);
if (puncIndex === -1) {
buffer += current;
current = '';
break;
}
buffer += current.slice(0, puncIndex + 1);
current = current.slice(puncIndex + 1);
}
return buffer;
}
function toSnakeCase(value) {
return value
.toLocaleLowerCase()
.replace(/[ \-]/g, '_')
.replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g, '');
}
function extractEmail(value) {
const matched = EMAIL_REGEXP.exec(value);
if (!matched) {
return undefined;
}
return matched[0];
}
function extractDomain(value) {
if (isEmail(value)) {
const matched = EMAIL_REGEXP.exec(value);
// This shouldn't happen
if (!matched) {
return undefined;
}
return matched.groups?.domain;
}
const domainMatch = value.match(DOMAIN_EXTRACT_REGEXP);
if (domainMatch) {
return domainMatch[1];
}
return undefined;
}
function extractUrl(value) {
const matched = URL_REGEXP.exec(value);
if (!matched) {
return undefined;
}
return matched[0];
}
function extractUrlPath(value) {
try {
const url = new URL(value);
return url.pathname;
}
catch (error) {
return undefined;
}
}
function parseJson(value) {
try {
return JSON.parse(value);
}
catch (error) {
if (value.includes("'")) {
throw new expression_extension_error_1.ExpressionExtensionError("Parsing failed. Check you're using double quotes");
}
throw new expression_extension_error_1.ExpressionExtensionError('Parsing failed');
}
}
function toBoolean(value) {
const normalized = value.toLowerCase();
const FALSY = new Set(['false', 'no', '0']);
return normalized.length > 0 && !FALSY.has(normalized);
}
function base64Encode(value) {
return (0, js_base64_1.toBase64)(value);
}
function base64Decode(value) {
return (0, js_base64_1.fromBase64)(value);
}
removeMarkdown.doc = {
name: 'removeMarkdown',
description: 'Removes any Markdown formatting from the string. Also removes HTML tags.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-removeMarkdown',
examples: [{ example: '"*bold*, [link]()".removeMarkdown()', evaluated: '"bold, link"' }],
};
removeTags.doc = {
name: 'removeTags',
description: 'Removes tags, such as HTML or XML, from the string.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-removeTags',
examples: [{ example: '"<b>bold</b>, <a>link</a>".removeTags()', evaluated: '"bold, link"' }],
};
toDate.doc = {
name: 'toDate',
description: 'Converts a string to a date.',
section: 'cast',
returnType: 'Date',
hidden: true,
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toDate',
};
toDateTime.doc = {
name: 'toDateTime',
description: 'Converts the string to a <a target="_blank" href="https://moment.github.io/luxon/api-docs/">Luxon</a> DateTime. Useful for further transformation. Supported formats for the string are ISO 8601, HTTP, RFC2822, SQL and Unix timestamp in milliseconds. To parse other formats, use <a target="_blank" href=”https://moment.github.io/luxon/api-docs/index.html#datetimefromformat”> <code>DateTime.fromFormat()</code></a>.',
section: 'cast',
returnType: 'DateTime',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toDateTime',
examples: [
{ example: '"2024-03-29T18:06:31.798+01:00".toDateTime()' },
{ example: '"Fri, 29 Mar 2024 18:08:01 +0100".toDateTime()' },
{ example: '"20240329".toDateTime()' },
{ example: '"1711732132990".toDateTime("ms")' },
{ example: '"31-01-2024".toDateTime("dd-MM-yyyy")' },
],
args: [
{
name: 'format',
optional: true,
description: 'The format of the date string. Options are <code>ms</code> (for Unix timestamp in milliseconds), <code>s</code> (for Unix timestamp in seconds), <code>us</code> (for Unix timestamp in microseconds) or <code>excel</code> (for days since 1900). Custom formats can be specified using <a href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens">Luxon tokens</a>.',
type: 'string',
},
],
};
toBoolean.doc = {
name: 'toBoolean',
description: 'Converts the string to a boolean value. <code>0</code>, <code>false</code> and <code>no</code> resolve to <code>false</code>, everything else to <code>true</code>. Case-insensitive.',
section: 'cast',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toBoolean',
examples: [
{ example: '"true".toBoolean()', evaluated: 'true' },
{ example: '"false".toBoolean()', evaluated: 'false' },
{ example: '"0".toBoolean()', evaluated: 'false' },
{ example: '"hello".toBoolean()', evaluated: 'true' },
],
};
toFloat.doc = {
name: 'toFloat',
description: 'Converts a string to a decimal number.',
section: 'cast',
returnType: 'number',
aliases: ['toDecimalNumber'],
hidden: true,
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toDecimalNumber',
};
toInt.doc = {
name: 'toInt',
description: 'Converts a string to an integer.',
section: 'cast',
returnType: 'number',
args: [{ name: 'radix?', type: 'number' }],
aliases: ['toWholeNumber'],
hidden: true,
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toInt',
};
toSentenceCase.doc = {
name: 'toSentenceCase',
description: 'Changes the capitalization of the string to sentence case. The first letter of each sentence is capitalized and all others are lowercased.',
examples: [{ example: '"quick! brown FOX".toSentenceCase()', evaluated: '"Quick! Brown fox"' }],
section: 'case',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toSentenceCase',
};
toSnakeCase.doc = {
name: 'toSnakeCase',
description: 'Changes the format of the string to snake case. Spaces and dashes are replaced by <code>_</code>, symbols are removed and all letters are lowercased.',
examples: [{ example: '"quick brown $FOX".toSnakeCase()', evaluated: '"quick_brown_fox"' }],
section: 'case',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toSnakeCase',
};
toTitleCase.doc = {
name: 'toTitleCase',
description: "Changes the capitalization of the string to title case. The first letter of each word is capitalized and the others left unchanged. Short prepositions and conjunctions aren't capitalized (e.g. 'a', 'the').",
examples: [{ example: '"quick a brown FOX".toTitleCase()', evaluated: '"Quick a Brown Fox"' }],
section: 'case',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toTitleCase',
};
urlEncode.doc = {
name: 'urlEncode',
description: 'Encodes the string so that it can be used in a URL. Spaces and special characters are replaced with codes of the form <code>%XX</code>.',
section: 'edit',
args: [
{
name: 'allChars',
optional: true,
description: 'Whether to encode characters that are part of the URI syntax (e.g. <code>=</code>, <code>?</code>)',
default: 'false',
type: 'boolean',
},
],
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-urlEncode',
examples: [
{ example: '"name=Nathan Automat".urlEncode()', evaluated: '"name%3DNathan%20Automat"' },
{ example: '"name=Nathan Automat".urlEncode(true)', evaluated: '"name=Nathan%20Automat"' },
],
};
urlDecode.doc = {
name: 'urlDecode',
description: 'Decodes a URL-encoded string. Replaces any character codes in the form of <code>%XX</code> with their corresponding characters.',
args: [
{
name: 'allChars',
optional: true,
description: 'Whether to decode characters that are part of the URI syntax (e.g. <code>=</code>, <code>?</code>)',
default: 'false',
type: 'boolean',
},
],
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-urlDecode',
examples: [
{ example: '"name%3DNathan%20Automat".urlDecode()', evaluated: '"name=Nathan Automat"' },
{ example: '"name%3DNathan%20Automat".urlDecode(true)', evaluated: '"name%3DNathan Automat"' },
],
};
replaceSpecialChars.doc = {
name: 'replaceSpecialChars',
description: 'Replaces special characters in the string with the closest ASCII character',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-replaceSpecialChars',
examples: [{ example: '"déjà".replaceSpecialChars()', evaluated: '"deja"' }],
};
length.doc = {
name: 'length',
section: 'query',
hidden: true,
description: 'Returns the character count of a string.',
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings',
};
isDomain.doc = {
name: 'isDomain',
description: 'Returns <code>true</code> if a string is a domain.',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isDomain',
examples: [
{ example: '"n8n.io".isDomain()', evaluated: 'true' },
{ example: '"http://n8n.io".isDomain()', evaluated: 'false' },
{ example: '"hello".isDomain()', evaluated: 'false' },
],
};
isEmail.doc = {
name: 'isEmail',
description: 'Returns <code>true</code> if the string is an email.',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isEmail',
examples: [
{ example: '"me@example.com".isEmail()', evaluated: 'true' },
{ example: '"It\'s me@example.com".isEmail()', evaluated: 'false' },
{ example: '"hello".isEmail()', evaluated: 'false' },
],
};
isNumeric.doc = {
name: 'isNumeric',
description: 'Returns <code>true</code> if the string represents a number.',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isNumeric',
examples: [
{ example: '"1.2234".isNumeric()', evaluated: 'true' },
{ example: '"hello".isNumeric()', evaluated: 'false' },
{ example: '"123E23".isNumeric()', evaluated: 'true' },
],
};
isUrl.doc = {
name: 'isUrl',
description: 'Returns <code>true</code> if a string is a valid URL',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isUrl',
examples: [
{ example: '"https://n8n.io".isUrl()', evaluated: 'true' },
{ example: '"n8n.io".isUrl()', evaluated: 'false' },
{ example: '"hello".isUrl()', evaluated: 'false' },
],
};
isEmpty.doc = {
name: 'isEmpty',
description: 'Returns <code>true</code> if the string has no characters or is <code>null</code>',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isEmpty',
examples: [
{ example: '"".isEmpty()', evaluated: 'true' },
{ example: '"hello".isEmpty()', evaluated: 'false' },
],
};
isNotEmpty.doc = {
name: 'isNotEmpty',
description: 'Returns <code>true</code> if the string has at least one character.',
section: 'validation',
returnType: 'boolean',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-isNotEmpty',
examples: [
{ example: '"hello".isNotEmpty()', evaluated: 'true' },
{ example: '"".isNotEmpty()', evaluated: 'false' },
],
};
toJsonString.doc = {
name: 'toJsonString',
description: 'Prepares the string to be inserted into a JSON object. Escapes any quotes and special characters (e.g. new lines), and wraps the string in quotes.The same as JavaScripts JSON.stringify().',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toJsonString',
examples: [
{
example: 'The "best" colours: red\nbrown.toJsonString()',
evaluated: '"The \\"best\\" colours: red\\nbrown"',
},
{ example: 'foo.toJsonString()', evaluated: '"foo"' },
],
};
extractEmail.doc = {
name: 'extractEmail',
description: 'Extracts the first email found in the string. Returns <code>undefined</code> if none is found.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractEmail',
examples: [
{ example: '"My email is me@example.com".extractEmail()', evaluated: "'me@example.com'" },
],
};
extractDomain.doc = {
name: 'extractDomain',
description: 'If the string is an email address or URL, returns its domain (or <code>undefined</code> if nothing found). If the string also contains other content, try using <code>extractEmail()</code> or <code>extractUrl()</code> first.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractDomain',
examples: [
{ example: '"me@example.com".extractDomain()', evaluated: "'example.com'" },
{ example: '"http://n8n.io/workflows".extractDomain()', evaluated: "'n8n.io'" },
{
example: '"It\'s me@example.com".extractEmail().extractDomain()',
evaluated: "'example.com'",
},
],
};
extractUrl.doc = {
name: 'extractUrl',
description: 'Extracts the first URL found in the string. Returns <code>undefined</code> if none is found. Only recognizes full URLs, e.g. those starting with <code>http</code>.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractUrl',
examples: [{ example: '"Check out http://n8n.io".extractUrl()', evaluated: "'http://n8n.io'" }],
};
extractUrlPath.doc = {
name: 'extractUrlPath',
description: 'Returns the part of a URL after the domain, or <code>undefined</code> if no URL found. If the string also contains other content, try using <code>extractUrl()</code> first.',
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractUrlPath',
examples: [
{ example: '"http://n8n.io/workflows".extractUrlPath()', evaluated: "'/workflows'" },
{
example: '"Check out http://n8n.io/workflows".extractUrl().extractUrlPath()',
evaluated: "'/workflows'",
},
],
};
hash.doc = {
name: 'hash',
description: 'Returns the string hashed with the given algorithm. Defaults to md5 if not specified.',
section: 'edit',
returnType: 'string',
args: [
{
name: 'algo',
optional: true,
description: 'The hashing algorithm to use. One of <code>md5</code>, <code>base64</code>, <code>sha1</code>, <code>sha224</code>, <code>sha256</code>, <code>sha384</code>, <code>sha512</code>, <code>sha3</code>, <code>ripemd160</code>\n ',
default: '"md5"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-hash',
examples: [{ example: '"hello".hash()', evaluated: "'5d41402abc4b2a76b9719d911017c592'" }],
};
quote.doc = {
name: 'quote',
description: 'Wraps a string in quotation marks, and escapes any quotation marks already in the string. Useful when constructing JSON, SQL, etc.',
section: 'edit',
returnType: 'string',
args: [
{
name: 'mark',
optional: true,
description: 'The type of quotation mark to use',
default: '"',
type: 'string',
},
],
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-quote',
examples: [{ example: '\'Nathan says "hi"\'.quote()', evaluated: '\'"Nathan says \\"hi\\""\'' }],
};
parseJson.doc = {
name: 'parseJson',
description: "Returns the JavaScript value or object represented by the string, or <code>undefined</code> if the string isn't valid JSON. Single-quoted JSON is not supported.",
section: 'cast',
returnType: 'any',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-parseJson',
examples: [
{ example: '\'{"name":"Nathan"}\'.parseJson()', evaluated: '\'{"name":"Nathan"}\'' },
{ example: "\"{'name':'Nathan'}\".parseJson()", evaluated: 'undefined' },
{ example: "'hello'.parseJson()", evaluated: 'undefined' },
],
};
base64Encode.doc = {
name: 'base64Encode',
description: 'Converts plain text to a base64-encoded string',
examples: [{ example: '"hello".base64Encode()', evaluated: '"aGVsbG8="' }],
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-base64Encode',
};
base64Decode.doc = {
name: 'base64Decode',
description: 'Converts a base64-encoded string to plain text',
examples: [{ example: '"aGVsbG8=".base64Decode()', evaluated: '"hello"' }],
section: 'edit',
returnType: 'string',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-base64Decode',
};
toNumber.doc = {
name: 'toNumber',
description: "Converts a string representing a number to a number. Errors if the string doesn't start with a valid number.",
section: 'cast',
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toNumber',
examples: [
{ example: '"123".toNumber()', evaluated: '123' },
{ example: '"1.23E10".toNumber()', evaluated: '12300000000' },
],
};
const toDecimalNumber = toFloat.bind({});
const toWholeNumber = toInt.bind({});
exports.stringExtensions = {
typeName: 'String',
functions: {
hash,
removeMarkdown,
removeTags,
toDate,
toDateTime,
toBoolean,
toDecimalNumber,
toNumber,
toFloat,
toInt,
toWholeNumber,
toSentenceCase,
toSnakeCase,
toTitleCase,
urlDecode,
urlEncode,
quote,
replaceSpecialChars,
length,
isDomain,
isEmail,
isNumeric,
isUrl,
isEmpty,
isNotEmpty,
toJsonString,
extractEmail,
extractDomain,
extractUrl,
extractUrlPath,
parseJson,
base64Encode,
base64Decode,
},
};
});
//# sourceMappingURL=string-extensions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
import { DateTime } from 'luxon';
export declare const convertToDateTime: (value: string | Date | DateTime) => DateTime | undefined;
export declare function checkIfValueDefinedOrThrow<T>(value: T, functionName: string): void;
//# sourceMappingURL=utils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/extensions/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAMjC,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,QAAQ,KAAG,QAAQ,GAAG,SAc9E,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAMlF"}

42
node_modules/n8n-workflow/dist/cjs/extensions/utils.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "luxon", "../errors/expression-extension.error"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToDateTime = void 0;
exports.checkIfValueDefinedOrThrow = checkIfValueDefinedOrThrow;
const luxon_1 = require("luxon");
const expression_extension_error_1 = require("../errors/expression-extension.error");
// Utility functions and type guards for expression extensions
const convertToDateTime = (value) => {
let converted;
if (typeof value === 'string') {
converted = luxon_1.DateTime.fromJSDate(new Date(value));
if (converted.invalidReason !== null) {
return;
}
}
else if (value instanceof Date) {
converted = luxon_1.DateTime.fromJSDate(value);
}
else if (luxon_1.DateTime.isDateTime(value)) {
converted = value;
}
return converted;
};
exports.convertToDateTime = convertToDateTime;
function checkIfValueDefinedOrThrow(value, functionName) {
if (value === undefined || value === null) {
throw new expression_extension_error_1.ExpressionExtensionError(`${functionName} can't be used on ${String(value)} value`, {
description: `To ignore this error, add a ? to the variable before this function, e.g. my_var?.${functionName}`,
});
}
}
});
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/extensions/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;IAsBA,gEAMC;IA5BD,iCAAiC;IAEjC,qFAAgF;IAEhF,8DAA8D;IAEvD,MAAM,iBAAiB,GAAG,CAAC,KAA+B,EAAwB,EAAE;QAC1F,IAAI,SAA+B,CAAC;QAEpC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,SAAS,GAAG,gBAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACjD,IAAI,SAAS,CAAC,aAAa,KAAK,IAAI,EAAE,CAAC;gBACtC,OAAO;YACR,CAAC;QACF,CAAC;aAAM,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;YAClC,SAAS,GAAG,gBAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;aAAM,IAAI,gBAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,SAAS,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC,CAAC;IAdW,QAAA,iBAAiB,qBAc5B;IAEF,SAAgB,0BAA0B,CAAI,KAAQ,EAAE,YAAoB;QAC3E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,qDAAwB,CAAC,GAAG,YAAY,qBAAqB,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE;gBAC7F,WAAW,EAAE,oFAAoF,YAAY,EAAE;aAC/G,CAAC,CAAC;QACJ,CAAC;IACF,CAAC"}