Files
probo/contrib/claude/n8n.md
Bryan Frimin ab52dc0a34 Clean agent rules
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-19 11:42:50 +02:00

6.7 KiB

n8n Node (packages/n8n-node)

Community node package @probo/n8n-nodes-probo exposing the Probo API as n8n operations. One Probo node with many resources; each resource maps to a set of GraphQL operations against the Console or Connect API.

Directory structure

packages/n8n-node/
  credentials/ProboApi.credentials.ts   # API key credential (Bearer token)
  nodes/Probo/
    Probo.node.ts                       # Node class — resource picker, dispatch
    Probo.node.json                     # n8n codex metadata
    GenericFunctions.ts                 # GraphQL request helpers, pagination
    actions/
      index.ts                          # Resource registry, dispatch, field aggregators
      <resource>/
        index.ts                        # Operation dropdown + spread descriptions + re-exports
        create.operation.ts             # One file per operation
        get.operation.ts
        getAll.operation.ts
        update.operation.ts
        delete.operation.ts
        ...

Resource registration

Two places must be updated when adding a resource:

1. actions/index.ts — import the module and add it to the resources map:

import * as myresource from './myresource';

export const resources: Record<string, ResourceModule> = {
    // ... existing resources ...
    myresource: myresource as ResourceModule,
};

2. Probo.node.ts — add a Resource dropdown entry in the properties array:

{
    name: 'My Resource',
    value: 'myresource',
    description: 'Manage my resources',
},

The value must match the key in resources and the displayOptions.show.resource in every operation file.

Per-resource file pattern

<resource>/index.ts

  1. Import each *.operation.ts as a namespace.
  2. Export description — the operation dropdown (gated with displayOptions.show.resource) plus all spread operation descriptions.
  3. Re-export each operation module with a name matching its operation value.
import * as createOp from './create.operation';
import * as getOp from './get.operation';
import * as getAllOp from './getAll.operation';

export const description: INodeProperties[] = [
    {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
            show: {
                resource: ['myresource'],
            },
        },
        options: [
            {
                name: 'Create',
                value: 'create',
                description: 'Create a new resource',
                action: 'Create a resource',
            },
            // ... more operations ...
        ],
        default: 'create',
    },
    ...createOp.description,
    ...getOp.description,
    ...getAllOp.description,
];

export {
    createOp as create,
    getOp as get,
    getAllOp as getAll,
};

Export names (create, get, getAll, etc.) must match the operation value strings — getExecuteFunction uses them as keys.

<resource>/<verb>.operation.ts

Each file exports description (field definitions) and execute (the handler):

export const description: INodeProperties[] = [
    {
        displayName: 'Organization ID',
        name: 'organizationId',
        type: 'string',
        displayOptions: {
            show: {
                resource: ['myresource'],
                operation: ['create'],
            },
        },
        default: '',
        required: true,
    },
    // ... more fields ...
    {
        displayName: 'Additional Fields',
        name: 'additionalFields',
        type: 'collection',
        placeholder: 'Add Field',
        default: {},
        displayOptions: {
            show: {
                resource: ['myresource'],
                operation: ['create'],
            },
        },
        options: [
            // optional field definitions
        ],
    },
];

export async function execute(
    this: IExecuteFunctions,
    itemIndex: number,
): Promise<INodeExecutionData> {
    const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
    const name = this.getNodeParameter('name', itemIndex) as string;

    const query = `
        mutation CreateMyResource($input: CreateMyResourceInput!) {
            createMyResource(input: $input) {
                myResourceEdge {
                    node {
                        id
                        name
                    }
                }
            }
        }
    `;

    const responseData = await proboApiRequest.call(this, query, {
        input: { organizationId, name },
    });

    return {
        json: responseData,
        pairedItem: { item: itemIndex },
    };
}

GraphQL helpers

All helpers live in GenericFunctions.ts.

Helper API endpoint Use case
proboApiRequest /api/console/v1/graphql Single mutations and queries
proboConnectApiRequest /api/connect/v1/graphql Organization/user operations (IAM)
proboApiRequestAllItems Console API Cursor-paginated list queries
proboConnectApiRequestAllItems Connect API Cursor-paginated list queries (IAM)
proboApiMultipartRequest Console API File upload mutations (multipart/form-data)

Pagination (proboApiRequestAllItems)

Caller supplies a getConnection function that navigates from the raw GraphQL response to the Relay connection object (must have edges and pageInfo):

const items = await proboApiRequestAllItems.call(
    this,
    query,
    { organizationId },
    (response) => {
        const data = response?.data as IDataObject | undefined;
        const node = data?.node as IDataObject | undefined;
        return node?.myResources as IDataObject | undefined;
    },
    returnAll,
    limit,
);

Internal page size is 100. When returnAll is false, stops at limit.

Update pattern

For nullable fields, empty string means "clear the value":

if (additionalFields.description !== undefined) {
    input.description = additionalFields.description === '' ? null : additionalFields.description;
}

Adding a new resource — checklist

  1. Directory — create nodes/Probo/actions/<resource>/ with index.ts and one *.operation.ts per operation
  2. Operations — each file exports description (fields gated with displayOptions) and execute (reads params, calls GraphQL, returns { json, pairedItem })
  3. Index<resource>/index.ts defines the operation dropdown, spreads all descriptions, re-exports ops with matching value names
  4. Register — import and add to resources map in actions/index.ts
  5. Node — add Resource dropdown entry in Probo.node.ts properties
  6. Verifynpx n8n-node lint must pass