---
name: n8n-node-builder
description: >
  بناء وتصميم custom nodes لـ n8n من الصفر بشكل احترافي وكامل.
  استخدم هذه الـ skill في أي وقت يطلب فيه المستخدم:
  - إنشاء custom node في n8n
  - بناء integration مخصص لـ n8n
  - تصميم credentials لـ n8n node
  - نشر أو اختبار n8n custom node
  - فهم بنية n8n nodes أو كتابة TypeScript لـ n8n
  - تحويل API إلى n8n node
  - بناء declarative أو programmatic n8n nodes
  سواء كان المستخدم مبتدئاً أو محترفاً، هذه الـ skill تغطي كل خطوة من إعداد البيئة حتى النشر على npm.
---

# 🔧 N8N Custom Node Builder — الدليل الشامل الكامل

---

## هيكل المشروع

```
my-n8n-node/                          ← اسم الـ package (يبدأ بـ n8n-nodes-)
├── package.json                       ← تسجيل الـ nodes + metadata
├── tsconfig.json                      ← إعدادات TypeScript
├── eslint.config.mjs
├── nodes/
│   └── MyNode/
│       ├── MyNode.node.ts             ← منطق الـ node الرئيسي
│       └── mynode.svg                 ← أيقونة الـ node
├── credentials/
│   └── MyNodeApi.credentials.ts       ← تعريف الـ authentication
├── icons/                             ← light + dark versions
│   ├── mynode-light.svg
│   └── mynode-dark.svg
└── dist/                              ← الكود المترجم (لا تعدله يدوياً)
```

---

## الخطوة 1: إعداد البيئة

### المتطلبات
- **Node.js v18+** (يُفضل v22+، استخدم `nvm` لإدارة الإصدارات)
- **npm أو pnpm**
- **Git**
- **VS Code** (مُوصى به)

### بدء مشروع جديد

```bash
# الطريقة السريعة — CLI رسمي يبني كل شيء تلقائياً
npm create @n8n/node

# أو clone من الـ starter repository
git clone https://github.com/n8n-io/n8n-nodes-starter my-node
cd my-node
npm install
```

### أوامر التطوير

```bash
npm run build        # compile TypeScript → dist/
npm run dev          # build + watch + تشغيل n8n على localhost:5678
npm run lint         # فحص الكود
npm run lint:fix     # إصلاح تلقائي
```

### ربط الـ node بـ n8n يدوياً

```bash
# 1. بناء وإنشاء link
npm run build
npm link

# 2. ربطه في مجلد n8n
mkdir -p ~/.n8n/custom
cd ~/.n8n/custom
npm init -y
npm link n8n-nodes-mynode    # الاسم كما في package.json

# 3. تشغيل n8n
n8n start
```

### ربط عبر Docker

```bash
docker run -d --name n8n-dev -p 5680:5678 \
  -e N8N_COMMUNITY_PACKAGES_ENABLED=true \
  -v ~/.n8n/custom/node_modules/n8n-nodes-mynode:/home/node/.n8n/custom/node_modules/n8n-nodes-mynode \
  n8nio/n8n

# بعد كل تعديل:
npm run build && docker restart n8n-dev
```

---

## الخطوة 2: package.json

```json
{
  "name": "n8n-nodes-myservice",
  "version": "0.1.0",
  "description": "n8n node for MyService API",
  "license": "MIT",
  "main": "index.js",
  "scripts": {
    "build": "n8n-node build",
    "dev": "n8n-node dev",
    "lint": "n8n-node lint",
    "lint:fix": "n8n-node lint --fix"
  },
  "n8n": {
    "n8nNodesApiVersion": 1,
    "credentials": [
      "dist/credentials/MyServiceApi.credentials.js"
    ],
    "nodes": [
      "dist/nodes/MyNode/MyNode.node.js"
    ]
  },
  "devDependencies": {
    "@n8n/node-cli": "*"
  },
  "keywords": ["n8n-community-node-package"]
}
```

---

## الخطوة 3: بناء الـ Node — الهيكل الأساسي

```typescript
// nodes/MyNode/MyNode.node.ts
import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeOperationError,
} from 'n8n-workflow';

export class MyNode implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My Node',
    name: 'myNode',                    // camelCase — يُستخدم داخلياً
    icon: { light: 'file:mynode-dark.svg', dark: 'file:mynode-light.svg' },
    group: ['transform'],              // ['trigger'] | ['input'] | ['output'] | ['transform']
    version: 1,
    description: 'وصف مختصر لما يفعله الـ node',
    defaults: { name: 'My Node' },
    inputs: ['main'],
    outputs: ['main'],
    usableAsTool: true,                // يسمح باستخدامه كـ tool في AI workflows
    credentials: [
      { name: 'myServiceApi', required: true }
    ],
    requestDefaults: {
      baseURL: 'https://api.myservice.com',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    },
    properties: [
      // هنا تُعرِّف كل الـ UI fields — انظر قسم Properties
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    for (let i = 0; i < items.length; i++) {
      try {
        const param = this.getNodeParameter('paramName', i) as string;
        const credentials = await this.getCredentials('myServiceApi');

        const response = await this.helpers.httpRequest({
          method: 'GET',
          url: `/data`,
          headers: { Authorization: `Bearer ${credentials.apiKey}` },
          qs: { query: param },
        });

        returnData.push({ json: response, pairedItem: { item: i } });
      } catch (error) {
        if (this.continueOnFail()) {
          returnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
          continue;
        }
        throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
      }
    }

    return [returnData];
  }
}
```

---

## الخطوة 4: بناء الـ Credentials

```typescript
// credentials/MyServiceApi.credentials.ts
import {
  IAuthenticateGeneric,
  ICredentialTestRequest,
  ICredentialType,
  INodeProperties,
  Icon,
} from 'n8n-workflow';

export class MyServiceApi implements ICredentialType {
  name = 'myServiceApi';
  displayName = 'My Service API';
  documentationUrl = 'https://docs.myservice.com/auth';
  icon: Icon = { light: 'file:myservice-dark.svg', dark: 'file:myservice-light.svg' };

  properties: INodeProperties[] = [
    {
      displayName: 'API Key',
      name: 'apiKey',
      type: 'string',
      typeOptions: { password: true },    // يخفي الـ key في الواجهة
      default: '',
      required: true,
    },
  ];

  authenticate: IAuthenticateGeneric = {
    type: 'generic',
    properties: {
      headers: { Authorization: '=Bearer {{$credentials.apiKey}}' },
    },
  };

  test: ICredentialTestRequest = {
    request: {
      baseURL: 'https://api.myservice.com',
      url: '/me',
      method: 'GET',
    },
  };
}
```

---

## الخطوة 5: Node Styles — Declarative vs Programmatic

### متى تستخدم أي أسلوب؟

| | Declarative | Programmatic |
|---|---|---|
| **الكود** | أقل، routing config | أكثر، execute() كاملة |
| **المرونة** | HTTP APIs فقط | لا حدود |
| **n8n توصي** | ✅ الافتراضي | للحالات المعقدة |
| **Pagination** | مدمج تلقائياً | يدوي |

### Declarative Style — مثال كامل

```typescript
import { INodeType, INodeTypeDescription } from 'n8n-workflow';

export class GithubIssues implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'GitHub Issues',
    name: 'githubIssues',
    icon: 'file:github.svg',
    group: ['transform'],
    version: 1,
    description: 'Manage GitHub Issues',
    defaults: { name: 'GitHub Issues' },
    inputs: ['main'],
    outputs: ['main'],
    usableAsTool: true,
    credentials: [{ name: 'githubApi', required: true }],
    requestDefaults: {
      baseURL: 'https://api.github.com',
      headers: {
        Accept: 'application/vnd.github.v3+json',
        'Content-Type': 'application/json',
      },
    },
    properties: [
      {
        displayName: 'Resource',
        name: 'resource',
        type: 'options',
        noDataExpression: true,
        options: [
          { name: 'Issue', value: 'issue' },
          { name: 'Comment', value: 'comment' },
        ],
        default: 'issue',
      },
      {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        noDataExpression: true,
        displayOptions: { show: { resource: ['issue'] } },
        options: [
          {
            name: 'Get',
            value: 'get',
            action: 'Get an issue',
            routing: {
              request: {
                method: 'GET',
                url: '=/repos/{{$parameter.owner}}/{{$parameter.repo}}/issues/{{$parameter.issueNumber}}',
              },
            },
          },
          {
            name: 'Create',
            value: 'create',
            action: 'Create an issue',
            routing: {
              request: {
                method: 'POST',
                url: '=/repos/{{$parameter.owner}}/{{$parameter.repo}}/issues',
                body: {
                  title: '={{$parameter.title}}',
                  body: '={{$parameter.body}}',
                },
              },
            },
          },
          {
            name: 'Get All',
            value: 'getAll',
            action: 'Get all issues',
            routing: {
              request: {
                method: 'GET',
                url: '=/repos/{{$parameter.owner}}/{{$parameter.repo}}/issues',
              },
            },
          },
        ],
        default: 'get',
      },
      {
        displayName: 'Owner',
        name: 'owner',
        type: 'string',
        default: '',
        required: true,
        displayOptions: { show: { resource: ['issue'] } },
      },
      {
        displayName: 'Repository',
        name: 'repo',
        type: 'string',
        default: '',
        required: true,
        displayOptions: { show: { resource: ['issue'] } },
      },
      {
        displayName: 'Issue Number',
        name: 'issueNumber',
        type: 'number',
        default: 1,
        displayOptions: { show: { resource: ['issue'], operation: ['get'] } },
      },
      {
        displayName: 'Title',
        name: 'title',
        type: 'string',
        default: '',
        displayOptions: { show: { resource: ['issue'], operation: ['create'] } },
      },
    ],
  };
  // لا توجد execute() في الـ declarative style
}
```

### Programmatic Style — مثال كامل

```typescript
import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeApiError,
  NodeOperationError,
} from 'n8n-workflow';

export class CryptoPrice implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Crypto Price',
    name: 'cryptoPrice',
    icon: 'fa:coins',
    group: ['transform'],
    version: 1,
    description: 'Get cryptocurrency prices from Binance',
    defaults: { name: 'Crypto Price' },
    inputs: ['main'],
    outputs: ['main'],
    usableAsTool: true,
    properties: [
      {
        displayName: 'Symbol',
        name: 'symbol',
        type: 'string',
        default: 'BTCUSDT',
        required: true,
        description: 'Trading pair symbol e.g. BTCUSDT, ETHUSDT',
      },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    for (let i = 0; i < items.length; i++) {
      try {
        const symbol = this.getNodeParameter('symbol', i) as string;

        const response = await this.helpers.httpRequest({
          method: 'GET',
          url: 'https://api.binance.com/api/v3/ticker/price',
          qs: { symbol: symbol.toUpperCase() },
        });

        returnData.push({
          json: {
            symbol: response.symbol,
            price: parseFloat(response.price),
            timestamp: new Date().toISOString(),
          },
          pairedItem: { item: i },
        });

      } catch (error: any) {
        if (this.continueOnFail()) {
          returnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
          continue;
        }
        if (error.statusCode === 400) {
          throw new NodeOperationError(this.getNode(), `Invalid symbol`, { itemIndex: i });
        }
        throw new NodeApiError(this.getNode(), error);
      }
    }

    return [returnData];
  }
}
```

### Trigger Node

```typescript
import { ITriggerFunctions, ITriggerResponse, INodeType, INodeTypeDescription } from 'n8n-workflow';

export class MyTrigger implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My Trigger',
    name: 'myTrigger',
    group: ['trigger'],
    version: 1,
    description: 'Triggers workflow on interval',
    defaults: { name: 'My Trigger' },
    inputs: [],
    outputs: ['main'],
    properties: [],
  };

  async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
    const interval = setInterval(async () => {
      const data = { timestamp: new Date().toISOString() };
      this.emit([[{ json: data }]]);
    }, 60000);

    return {
      closeFunction: async () => { clearInterval(interval); },
    };
  }
}
```

### Multiple Outputs

```typescript
// outputs في الـ description: outputs: ['main', 'main']
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
  const successItems: INodeExecutionData[] = [];
  const failureItems: INodeExecutionData[] = [];

  for (let i = 0; i < this.getInputData().length; i++) {
    const result = await processItem();
    if (result.success) successItems.push({ json: result.data });
    else failureItems.push({ json: result.error });
  }

  return [successItems, failureItems];  // منفذان
}
```

---

## الخطوة 6: Properties API — كل الأنواع

### الهيكل الأساسي لكل property

```typescript
{
  displayName: 'Field Label',        // ما يراه المستخدم
  name: 'fieldName',                 // camelCase — اسم داخلي
  type: 'string',                    // نوع الـ field
  default: '',                       // القيمة الافتراضية (مطلوبة دائماً)
  required: true,
  description: 'وصف الحقل',
  placeholder: 'مثال على القيمة',
  displayOptions: {
    show: { otherField: ['value1'] },
    hide: { otherField: ['value2'] },
  },
  noDataExpression: true,            // يمنع expressions (للـ resource/operation)
}
```

### string
```typescript
{ displayName: 'Message', name: 'message', type: 'string', default: '',
  typeOptions: { rows: 4, password: true } }    // rows=textarea, password=hidden
```

### number
```typescript
{ displayName: 'Limit', name: 'limit', type: 'number', default: 10,
  typeOptions: { minValue: 1, maxValue: 100 } }
```

### boolean
```typescript
{ displayName: 'Include Metadata', name: 'includeMetadata', type: 'boolean', default: false }
```

### options (dropdown)
```typescript
{
  displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true,
  options: [
    { name: 'Get', value: 'get', action: 'Get a record', description: 'Retrieve by ID' },
    { name: 'Create', value: 'create', action: 'Create a record' },
    { name: 'Delete', value: 'delete', action: 'Delete a record' },
  ],
  default: 'get',
}
```

### multiOptions
```typescript
{
  displayName: 'Fields', name: 'fields', type: 'multiOptions',
  options: [
    { name: 'Name', value: 'name' },
    { name: 'Email', value: 'email' },
    { name: 'Phone', value: 'phone' },
  ],
  default: ['name', 'email'],
}
```

### collection (خيارات إضافية اختيارية)
```typescript
{
  displayName: 'Additional Fields', name: 'additionalFields',
  type: 'collection', placeholder: 'Add Field', default: {},
  options: [
    { displayName: 'Page Size', name: 'pageSize', type: 'number', default: 20 },
    {
      displayName: 'Sort By', name: 'sortBy', type: 'options',
      options: [{ name: 'Date', value: 'created_at' }, { name: 'Name', value: 'name' }],
      default: 'created_at',
    },
  ],
}
// في الكود:
const additionalFields = this.getNodeParameter('additionalFields', i) as { pageSize?: number; sortBy?: string };
const pageSize = additionalFields.pageSize ?? 20;
```

### fixedCollection (بيانات منظمة)
```typescript
{
  displayName: 'Headers', name: 'headers', type: 'fixedCollection',
  typeOptions: { multipleValues: true },
  default: {}, placeholder: 'Add Header',
  options: [{
    name: 'header', displayName: 'Header',
    values: [
      { displayName: 'Name', name: 'name', type: 'string', default: '' },
      { displayName: 'Value', name: 'value', type: 'string', default: '' },
    ],
  }],
}
// في الكود:
const { header = [] } = this.getNodeParameter('headers', i) as { header?: Array<{ name: string; value: string }> };
```

### json
```typescript
{ displayName: 'Body', name: 'body', type: 'json', default: '{}' }
// في الكود:
const body = JSON.parse(this.getNodeParameter('body', i) as string);
```

### resourceLocator (بحث ديناميكي)
```typescript
{
  displayName: 'Project', name: 'project', type: 'resourceLocator',
  default: { mode: 'list', value: '' }, required: true,
  modes: [
    {
      displayName: 'From List', name: 'list', type: 'list',
      typeOptions: { searchListMethod: 'getProjects', searchable: true },
    },
    { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'project-id' },
  ],
}
```

### displayOptions — التحكم في الظهور
```typescript
displayOptions: {
  show: { operation: ['create', 'update'], resource: ['issue'] },
}
displayOptions: {
  hide: { status: ['inactive'] },
}
```

### قراءة القيم في الكود
```typescript
const name    = this.getNodeParameter('name', i) as string;
const limit   = this.getNodeParameter('limit', i) as number;
const active  = this.getNodeParameter('active', i) as boolean;
const op      = this.getNodeParameter('operation', i) as string;
const fields  = this.getNodeParameter('fields', i) as string[];
const opts    = this.getNodeParameter('additionalFields', i) as { pageSize?: number };
const { header = [] } = this.getNodeParameter('headers', i) as { header?: any[] };
```

---

## الخطوة 7: Credentials — كل أنواع الـ Authentication

### API Key في الـ Header
```typescript
authenticate: IAuthenticateGeneric = {
  type: 'generic',
  properties: { headers: { 'X-API-Key': '={{$credentials.apiKey}}' } },
};
```

### API Key في الـ Query String
```typescript
authenticate: IAuthenticateGeneric = {
  type: 'generic',
  properties: { qs: { api_key: '={{$credentials.apiKey}}' } },
};
```

### Bearer Token
```typescript
authenticate: IAuthenticateGeneric = {
  type: 'generic',
  properties: { headers: { Authorization: '=Bearer {{$credentials.token}}' } },
};
```

### Basic Auth
```typescript
export class MyBasicAuth implements ICredentialType {
  name = 'myBasicAuth';
  displayName = 'My Service Basic Auth';
  properties: INodeProperties[] = [
    { displayName: 'Username', name: 'username', type: 'string', default: '', required: true },
    { displayName: 'Password', name: 'password', type: 'string', typeOptions: { password: true }, default: '', required: true },
  ];
  authenticate: IAuthenticateGeneric = {
    type: 'generic',
    properties: { auth: { username: '={{$credentials.username}}', password: '={{$credentials.password}}' } },
  };
}
```

### OAuth2
```typescript
export class MyOAuth2 implements ICredentialType {
  name = 'myOAuth2';
  displayName = 'My Service OAuth2';
  extends = ['oAuth2Api'];
  properties: INodeProperties[] = [
    { displayName: 'Grant Type', name: 'grantType', type: 'hidden', default: 'authorizationCode' },
    { displayName: 'Authorization URL', name: 'authUrl', type: 'hidden', default: 'https://app.myservice.com/oauth/authorize' },
    { displayName: 'Access Token URL', name: 'accessTokenUrl', type: 'hidden', default: 'https://api.myservice.com/oauth/token' },
    { displayName: 'Scope', name: 'scope', type: 'hidden', default: 'read write' },
    { displayName: 'Authentication', name: 'authentication', type: 'hidden', default: 'header' },
  ];
}
```

### Multiple Auth Methods في نفس الـ Node
```typescript
// في credentials[] بالـ node description:
credentials: [
  { name: 'myApi', required: true, displayOptions: { show: { authentication: ['token'] } } },
  { name: 'myOAuth2Api', required: true, displayOptions: { show: { authentication: ['oauth2'] } } },
],
// property للاختيار:
{ displayName: 'Authentication', name: 'authentication', type: 'options', noDataExpression: true,
  options: [{ name: 'Access Token', value: 'token' }, { name: 'OAuth2', value: 'oauth2' }],
  default: 'token' }
```

### قراءة الـ credentials في الكود
```typescript
const credentials = await this.getCredentials('myServiceApi');
const apiKey = credentials.apiKey as string;
const baseUrl = credentials.domain as string;
```

### Custom Credential Test
```typescript
test: ICredentialTestRequest = {
  request: {
    baseURL: '={{$credentials.domain.replace(/\\/+$/, "")}}',
    url: '/api/v1/me',
    method: 'GET',
    headers: { Authorization: '=Bearer {{$credentials.apiKey}}' },
  },
  rules: [
    {
      type: 'responseSuccessBody',
      properties: { key: 'status', value: 'active', message: 'Account is not active' },
    },
  ],
};
```

---

## الخطوة 8: Advanced Patterns

### HTTP Helpers
```typescript
// httpRequest — الموصى به
const response = await this.helpers.httpRequest({
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
  url: 'https://api.example.com/endpoint',
  headers: { 'Content-Type': 'application/json' },
  qs: { page: 1, limit: 20 },
  body: { name: 'value' },
  returnFullResponse: true,       // إرجاع statusCode + headers أيضاً
  ignoreHttpStatusErrors: true,   // لا ترمي error على 4xx/5xx
});
const { statusCode, body } = response;
```

### Pagination
```typescript
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
  const returnData: INodeExecutionData[] = [];
  const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
  const limit = returnAll ? Infinity : (this.getNodeParameter('limit', 0) as number);

  let page = 1;
  let hasMore = true;

  while (hasMore && returnData.length < limit) {
    const response = await this.helpers.httpRequest({
      method: 'GET',
      url: 'https://api.example.com/items',
      qs: { page, per_page: 100 },
    });

    const items = response.data as any[];
    returnData.push(...items.map(item => ({ json: item })));
    hasMore = response.meta.has_more && items.length > 0;
    page++;
  }

  return [returnData.slice(0, limit)];
}
```

### Error Handling الاحترافي
```typescript
try {
  const result = await this.helpers.httpRequest({ ... });
  returnData.push({ json: result, pairedItem: { item: i } });
} catch (error: any) {
  if (this.continueOnFail()) {
    returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
    continue;
  }
  if (error.statusCode === 401) {
    throw new NodeApiError(this.getNode(), error, {
      message: 'Invalid API credentials',
      description: 'Please check your API key in the credentials section',
    });
  }
  if (error.statusCode === 404) {
    throw new NodeOperationError(this.getNode(), 'Resource not found', { itemIndex: i });
  }
  if (error.statusCode === 429) {
    throw new NodeApiError(this.getNode(), error, { message: 'Rate limit exceeded' });
  }
  throw new NodeOperationError(this.getNode(), error.message, { itemIndex: i });
}
```

### Dynamic List Method (لـ resourceLocator)
```typescript
methods = {
  listSearch: {
    async getProjects(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
      const credentials = await this.getCredentials('myApi');
      const projects = await this.helpers.httpRequest({
        method: 'GET',
        url: 'https://api.example.com/projects',
        headers: { Authorization: `Bearer ${credentials.token}` },
      });
      return {
        results: projects
          .filter((p: any) => !filter || p.name.includes(filter))
          .map((p: any) => ({ name: p.name, value: p.id, url: `https://app.example.com/projects/${p.id}` })),
      };
    },
  },
};
```

### Batch Processing
```typescript
const BATCH_SIZE = 10;
for (let start = 0; start < items.length; start += BATCH_SIZE) {
  const batch = items.slice(start, start + BATCH_SIZE);
  const results = await Promise.all(
    batch.map((_, idx) => {
      const id = this.getNodeParameter('id', start + idx) as string;
      return this.helpers.httpRequest({ method: 'GET', url: `/items/${id}` });
    })
  );
  results.forEach(r => returnData.push({ json: r }));
  if (start + BATCH_SIZE < items.length) {
    await new Promise(resolve => setTimeout(resolve, 500)); // rate limit delay
  }
}
```

### Caching
```typescript
export class MyNode implements INodeType {
  private static cache = new Map<string, { data: any; timestamp: number }>();
  private static CACHE_TTL = 5 * 60 * 1000; // 5 دقائق

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const cacheKey = 'my-data';
    const cached = MyNode.cache.get(cacheKey);
    const now = Date.now();
    let data: any;
    if (cached && (now - cached.timestamp) < MyNode.CACHE_TTL) {
      data = cached.data;
    } else {
      data = await this.helpers.httpRequest({ method: 'GET', url: '...' });
      MyNode.cache.set(cacheKey, { data, timestamp: now });
    }
    return [[{ json: data }]];
  }
}
```

### Binary Data (الملفات)
```typescript
// قراءة ملف من الـ input وإرساله
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);

const formData = new FormData();
formData.append('file', new Blob([buffer]), binaryData.fileName);
const response = await this.helpers.httpRequest({ method: 'POST', url: '/upload', body: formData });

// حفظ ملف كـ output
const outBinary = await this.helpers.prepareBinaryData(buffer, 'output.pdf', 'application/pdf');
returnData.push({ json: {}, binary: { data: outBinary } });
```

### Webhook Node
```typescript
import { IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';

export class MyWebhook implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My Webhook', name: 'myWebhook',
    group: ['trigger'], version: 1,
    inputs: [], outputs: ['main'],
    webhooks: [{
      name: 'default', httpMethod: 'POST',
      responseMode: 'onReceived', path: 'my-webhook',
    }],
    properties: [],
  };

  async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
    const body = this.getBodyData();
    if (!body.event) return { noWebhookResponse: true };
    return { workflowData: [[{ json: { body, headers: this.getHeaderData() } }]] };
  }
}
```

---

## الخطوة 9: النشر على npm

```bash
npm run build
npm run lint
# حدّث README باستخدام README_TEMPLATE.md كقالب
npm publish
```

للتقديم للتحقق الرسمي على n8n Cloud: https://creators.n8n.io/nodes

---

## ✅ Checklist قبل النشر

- [ ] اسم الـ package يبدأ بـ `n8n-nodes-`
- [ ] الـ `n8n.nodes` و `n8n.credentials` محدّثان في `package.json`
- [ ] `keywords: ["n8n-community-node-package"]` موجودة
- [ ] الأيقونة بنسختين light + dark
- [ ] `usableAsTool: true` موجودة في كل node
- [ ] `typeOptions: { password: true }` على حقول الـ secrets
- [ ] `test` property موجودة في كل credential
- [ ] `requestDefaults` محدّد للـ baseURL
- [ ] error handling مع `continueOnFail()` مطبّق في كل loop
- [ ] `pairedItem: { item: i }` على كل output item
- [ ] `npm run lint` بدون أخطاء
- [ ] `npm run build` ينتهي بنجاح
- [ ] اختبار الـ node في workflow حقيقي
- [ ] ترخيص MIT في `LICENSE.md`

---

## مصادر مهمة

- [n8n Docs — Creating Nodes](https://docs.n8n.io/integrations/creating-nodes/)
- [n8n Starter Repository](https://github.com/n8n-io/n8n-nodes-starter)
- [n8n Built-in Nodes Source Code](https://github.com/n8n-io/n8n/tree/master/packages/nodes-base/nodes)
- [n8n Community Forum](https://community.n8n.io/)
- [n8n Creator Portal](https://creators.n8n.io/nodes)
