Custom node method invocation

Describe the issue/error/question

Currently n8n calls custom node functions using nodeType.execute.call(thisArgs) which works fine, except if this is necessary in custom node class as Function.prototype.call() overrides this context.

Example

Abstract class:

import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType }
from 'n8n-workflow'

export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> {
  abstract run(t: TParams): Promise<INodeExecutionData>

  async execute(): Promise<INodeExecutionData[][]> {
    const executeFunctions = this as unknown as IExecuteFunctions

    // THIS LINE DOES NOT WORK
    // ERROR: TypeError: this.run is not a function
    // It's no possible to access any instance method from class (any call with "this")
    // Because "this" was override by .call(thisArgs)
    await this.run({ prompts: ['hello', 'world'] } as TParams)

    return this.executeFunctions.prepareOutputData([
      { json: { answer: 'Sample answer' } },
    ])
  }
}

Node class

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(', ')}"`)
  }
}

Suggestion

I’d like to suggest to change every call using: .call(thisArgs) to .call(thisArgs, thisArgs) that way thisArgs overrides this and is passed as function argument as well.

So what about the overriding context thing? That can be fixed by changing execute to arrow function instead which keeps instance scope, the change would be:

import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType }
from 'n8n-workflow'

export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> {
  abstract run(t: TParams): Promise<INodeExecutionData>

  execute = async (...args: any[]): Promise<INodeExecutionData[][]> => {
    const executeFunctions = args as unknown as IExecuteFunctions

    // NOW IT WORKS as "this" is instance "this"
    await this.run({ prompts: ['hello', 'world'] } as TParams)

    return this.executeFunctions.prepareOutputData([
      { json: { answer: 'Sample answer' } },
    ])
  }
}

I can open the PR, but first I’d like to bring this for discussion.

Information on your n8n setup

  • n8n version: 0.213.0
  • Database you’re using (default: SQLite): Postgres
  • Running n8n with the execution process [own(default), main]: main
  • Running n8n via [Docker, npm, n8n.cloud, desktop app]: Docker

Hi @ndeitch, thank you so much for sharing your thoughts on this. I don’t have a strong opinion on this, but maybe our node builders here on the forum @marcus and @Jon have?

I opened a PR 2 weeks ago, but no one reviewed it.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.