Currently, I am developing an n8n node that essentially functions every time a specific event occurs.
To facilitate this process, I have created an abstract class which is invoked by the n8n environment. However, there seems to be a limitation in calling its methods as n8n utilizes Class.execute.call(thisArgs)
, thereby overriding the context of this
for the class instance.
Invocation of my class by n8n lib
The following code snippet has been extracted from the n8n source code:
import { createContext, Script } from 'vm'
import { AbstractNode } from './n8n'
const context = createContext({ require })
export const loadClassInIsolation = <T>(filePath: string, className: string) => {
const script = new Script(`new (require('${filePath}').${className})()`)
return script.runInContext(context) as T
}
async function run(): Promise<void> {
const myClass = loadClassInIsolation<AbstractNode<unknown>>(
'../dist/codex/node/Codex.node.js',
'Codex',
)
const thisArgs = {
prepareOutputData: (d: any): any => ({ ...d }),
}
console.log(await myClass.execute.call(thisArgs, thisArgs))
}
void run()
Overview of my abstract class
This is the class where I am encountering difficulties involving the use of this
:
import { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow'
export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> {
private _executeFunctions: IExecuteFunctions = null
set executeFunctions(value: IExecuteFunctions) {
this._executeFunctions = value
}
get executeFunctions(): IExecuteFunctions {
return this._executeFunctions
}
abstract run(t: TParams): Promise<INodeExecutionData>
async execute(): Promise<INodeExecutionData[][]> {
this.executeFunctions = this as unknown as IExecuteFunctions
// THIS LINE DOES NOT WORK
// ERROR: TypeError: this.run is not a function
await this.run({ prompts: ['hello', 'world'] } as TParams)
return this.executeFunctions.prepareOutputData([
{ json: { answer: 'Sample answer' } },
])
}
}
Dynamic instantiation of the class
This particular class implements the abstract run
method defined in the AbstractNode:
import { Logger } from '@nestjs/common'
import { FirefliesContext } from '@src/common'
import { AbstractNode } from '@src/n8n'
import { INodeExecutionData } from 'n8n-workflow'
type CodexParams = { prompts: string[] }
export class Codex extends AbstractNode<CodexParams> {
run({ prompts }: CodexParams): Promise<INodeExecutionData> {
console.log(`Prompts="${prompts.join(', ')}"`)
}
}
Attempted solutions
The issue arises due to the fact that the use of .call(thisArgs)
alters the context within the execute
function. One potential solution involves converting execute
into an arrow function but this would result in losing access to thisArgs
.
Hence, my inquiry is: Is there a feasible approach to retaining access to both the class instance this
and thisArgs
when utilizing .call()
? Having access to both entities would enable me to effectively call the implemented abstract method and utilize helper functions from thisArgs
.