mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
feat: Expression extension framework (#4372)
* ⚡ Introduce a framework for expression extension * 💡 Add some inline comments * ⚡ Introduce hash alias for encrypt * ⚡ Introduce a manual granular level approach to shadowing/overrideing extensions * 🔥 Cleanup comments * ⚡ Introduce a basic method of extension for native functions * ⚡ Add length to StringExtension * ⚡ Add number type to extension return types * ⚡ Temporarily introduce DateTime with extension * ⚡ Cleanup comments * ⚡ Organize imports * ♻️ Fix up some typings * ⚡ Fix typings * ♻️ Remove unnecessary resolve of expression * ⚡ Extensions Improvement * ♻️ Refactor EXPRESSION_EXTENSION_METHODS * ♻️ Refactor EXPRESSION_EXTENSION_METHODS * ♻️ Update extraArgs types * ♻️ Fix tests * ♻️ Fix bind type issue * ♻️ Fixing duration type issue * ♻️ Refactor to allow overrides on native methods * ♻️ Temporarily remove Date Extensions to pass tests * feat(dt-functions): introduce date expression extensions (#4045) * 🎉 Add Date Extensions into the mix * ✨ Introduce additional date extension methods * ✅ Add Date Expression Extension tests * 🔧 Add ability to debug tests * ♻️ Refactor extension for native types * 🔥 Move sayHi method to String Extension class * ♻️ Update scope when binding member methods * ✅ Add String Extension tests * feat(dt-functions): introduce array expression extensions (#4044) * ✨ Introduce Array Extensions * ✅ Add Array Expression tests * feat(dt-functions): introduce number expression extensions (#4046) * 🎉 Introduce Number Extensions * ⚡ Support more shared extensions * ⚡ Improve handling of name collision * ✅ Update tests * Fixed up tests * 🔥 Remove remove markdown * :recylce: Replace remove-markdown dependencies with implementation * ♻️ Replace remove-markdown dependencies with implementation * ✅ Update tests * ♻️ Fix scoping and cleanup * ♻️ Update comments and errors * ♻️ Fix linting errors * ➖ Remove unused dependencies * fix: expression extension not working with multiple extensions * refactor: change extension transform to be more efficient * test: update most test to work with new extend function * fix: update and fix type error in config * refactor: replace babel with recast * feat: add hashing functions to string extension * fix: removed export * test: add extension parser and transform tests * fix: vite tests breaking * refactor: remove commented out code * fix: parse dates passed from $json in extend function * refactor: review feedback changes for date extensions * refactor: review feedback changes for number extensions * fix: date extension beginningOf test * fix: broken build from merge * fix: another merge issue * refactor: address review feedback (remove ignores) * feat: new extension functions and tests * feat: non-dot notation functions * test: most of the other tests * fix: toSentenceCase for node versions below 16.6 * feat: add $if and $not expression extensions * Fix test to work on every timezone * lint: fix remaining lint issues Co-authored-by: Csaba Tuncsik <csaba@n8n.io> Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
104
packages/workflow/src/Extensions/ObjectExtensions.ts
Normal file
104
packages/workflow/src/Extensions/ObjectExtensions.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { ExpressionExtensionError } from '../ExpressionError';
|
||||
import type { ExtensionMap } from './Extensions';
|
||||
|
||||
export function merge(value: object, extraArgs: unknown[]): unknown {
|
||||
const [other] = extraArgs;
|
||||
if (typeof other !== 'object' || !other) {
|
||||
throw new ExpressionExtensionError('argument of merge must be an object');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const newObject: any = { ...value };
|
||||
for (const [key, val] of Object.entries(other)) {
|
||||
if (!(key in newObject)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
newObject[key] = val;
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
function isEmpty(value: object): boolean {
|
||||
return Object.keys(value).length === 0;
|
||||
}
|
||||
|
||||
function hasField(value: object, extraArgs: string[]): boolean {
|
||||
const [name] = extraArgs;
|
||||
return name in value;
|
||||
}
|
||||
|
||||
function removeField(value: object, extraArgs: string[]): object {
|
||||
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 as any)[name];
|
||||
return newObject;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function removeFieldsContaining(value: object, extraArgs: string[]): object {
|
||||
const [match] = extraArgs;
|
||||
if (typeof match !== 'string') {
|
||||
throw new ExpressionExtensionError('argument of removeFieldsContaining must be an string');
|
||||
}
|
||||
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 as any)[key];
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
function keepFieldsContaining(value: object, extraArgs: string[]): object {
|
||||
const [match] = extraArgs;
|
||||
if (typeof match !== 'string') {
|
||||
throw new ExpressionExtensionError('argument of keepFieldsContaining must be an string');
|
||||
}
|
||||
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 as any)[key];
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
export function compact(value: object): object {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const newObj: any = {};
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
if (val !== null && val !== undefined) {
|
||||
if (typeof val === 'object') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @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;
|
||||
}
|
||||
|
||||
export function urlEncode(value: object) {
|
||||
return new URLSearchParams(value as Record<string, string>).toString();
|
||||
}
|
||||
|
||||
export const objectExtensions: ExtensionMap = {
|
||||
typeName: 'Object',
|
||||
functions: {
|
||||
isEmpty,
|
||||
merge,
|
||||
hasField,
|
||||
removeField,
|
||||
removeFieldsContaining,
|
||||
keepFieldsContaining,
|
||||
compact,
|
||||
urlEncode,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user