{
  "title": "قوانین رسمی جنریتور SDK",
  "slug": "team/platform/api/sdk-generator-rules",
  "url": "/docs/team/platform/api/sdk-generator-rules",
  "frontmatter": {},
  "sections": [
    {
      "level": 1,
      "heading": "قوانین رسمی جنریتور SDK",
      "content": "> این سند **تنها منبع اصلی (Single Source of Truth)** برای پیاده‌سازی و توسعه جنریتور SDK در پروژه `cli/` است.\n> تمام تغییرات آینده در منطق جنریتور **باید** با قوانین این سند انطباق داشته باشند.\n> نقض هر یک از قوانین اجباری این سند موجب رد PR در CI می‌شود.\n\n---"
    },
    {
      "level": 2,
      "heading": "۱. مقدمه",
      "content": "جنریتور SDK پلتفرم NONS یک ابزار دو مرحله‌ای است که از فایل `openapi.yaml` هر سرویس، کد TypeScript آماده مصرف تولید می‌کند. این جنریتور در دایرکتوری `cli/` پیاده‌سازی شده و به عنوان باینری `nons` کامپایل می‌شود.\n\nدر طول یک Audit جامع تولیدی (2026-07-03)، چندین نقص بنیادی کشف شد که منجر به تولید SDK **غیرقابل کامپایل** می‌شد:\n\n- **۴ تابع `getAuth()` تکراری** در یک فایل\n- **۴ تابع `submit()` تکراری** در یک فایل\n- **کلمه کلیدی رزرو شده** `protected` به عنوان نام تابع\n- **بدنه درخواست‌ها** به دلیل نادیده گرفتن `form-urlencoded` همیشه `null`\n\nاین سند هم ریشه این مشکلات را مستند می‌کند و هم قوانین الزامی برای جلوگیری از تکرار آن‌ها را تعریف می‌نماید.\n\n---"
    },
    {
      "level": 2,
      "heading": "۲. معماری پایپلاین",
      "content": "```\nservice/docs/openapi.yaml\n              │\n              │  nons registry build --source <path>\n              ▼\n cli/internal/registry/builder.go\n              │  (OpenAPI → Manifest)\n              ▼\n.nons/registry/<service>/manifest.json\n              │\n              │  nons generate\n              ▼\n cli/internal/generator/typescript/typescript.go\n              │  (Manifest → TypeScript)\n              ▼\n.nons/generated/\n  ├── types/<service>.ts          ← TypeScript interfaces\n  ├── api-client/<service>.ts     ← HTTP client functions\n  └── hooks/use<Service>.ts       ← React hooks\n```"
    },
    {
      "level": 3,
      "heading": "مسئولیت هر مرحله",
      "content": "| مرحله | فایل | ورودی | خروجی |\n|-------|------|-------|-------|\n| Registry Builder | `registry/builder.go` | `openapi.yaml` | `manifest.json` |\n| TS Generator | `generator/typescript/typescript.go` | `manifest.json` | `*.ts` |\n| Manifest Schema | `manifest/manifest.go` | — | تعریف ساختار داده |\n\n---"
    },
    {
      "level": 2,
      "heading": "۳. قوانین اجباری",
      "content": ""
    },
    {
      "level": 3,
      "heading": "قانون SDK-001 — نام‌گذاری تابع از `operationId` (بحرانی)",
      "content": "**شناسه:** SDK-001  \n**شدت:** بحرانی — نقض این قانون SDK را غیرقابل کامپایل می‌کند  \n**فایل مرتبط:** `cli/internal/generator/typescript/typescript.go`"
    },
    {
      "level": 4,
      "heading": "❌ پیاده‌سازی اشتباه (قبلی)",
      "content": "```go\n// BUG: استفاده از آخرین بخش token به عنوان نام تابع\nfunc getFunctionName(token string, service string) string {\n    parts := strings.Split(token, \".\")\n    action := parts[len(parts)-1]   // auth.login.get → \"get\" → \"getAuth\"\n    ...                              // auth.register.get → \"get\" → \"getAuth\" تکراری!\n}\n```\n\nنتیجه اشتباه:\n\n```typescript\nexport async function getAuth(): Promise<FlowResponseJSON> { /* /v1/auth/login */ }\nexport async function getAuth(): Promise<FlowResponseJSON> { /* /v1/auth/register */ }  // ❌ تکراری\nexport async function getAuth(): Promise<any>              { /* /v1/auth/settings */ }  // ❌ تکراری\n```"
    },
    {
      "level": 4,
      "heading": "✅ پیاده‌سازی صحیح",
      "content": "نام تابع **باید** از فیلد `OperationID` که در manifest ذخیره شده، مشتق شود:\n\n```go\n// CORRECT: استفاده از operationId یکتا\nfunc getFunctionName(op manifest.Operation) string {\n    if op.OperationID != \"\" {\n        return lowerCamelCase(op.OperationID)\n    }\n    return sanitizeFunctionName(op.Token)\n}\n```\n\nنتیجه صحیح:\n\n```typescript\nexport async function getLoginFlow(): Promise<FlowResponseJSON> { }    // ✅ یکتا\nexport async function getRegisterFlow(): Promise<FlowResponseJSON> { } // ✅ یکتا\nexport async function getSettingsFlow(): Promise<any> { }              // ✅ یکتا\n```\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-002 — بلاک‌لیست کلمات کلیدی TypeScript (بحرانی)",
      "content": "**شناسه:** SDK-002  \n**شدت:** بحرانی — نام تابع رزرو شده منجر به SyntaxError می‌شود"
    },
    {
      "level": 4,
      "heading": "❌ خروجی اشتباه (قبلی)",
      "content": "```typescript\nexport async function protected(): Promise<any> { }  // ❌ SyntaxError\n```"
    },
    {
      "level": 4,
      "heading": "✅ پیاده‌سازی صحیح",
      "content": "```go\nvar tsReservedKeywords = map[string]bool{\n    \"break\": true, \"case\": true, \"catch\": true, \"class\": true, \"const\": true,\n    \"continue\": true, \"delete\": true, \"do\": true, \"else\": true, \"export\": true,\n    \"extends\": true, \"finally\": true, \"for\": true, \"function\": true, \"if\": true,\n    \"import\": true, \"in\": true, \"instanceof\": true, \"let\": true, \"new\": true,\n    \"null\": true, \"return\": true, \"static\": true, \"super\": true, \"switch\": true,\n    \"this\": true, \"throw\": true, \"try\": true, \"typeof\": true, \"var\": true,\n    \"void\": true, \"while\": true, \"yield\": true,\n    // TypeScript\n    \"abstract\": true, \"any\": true, \"async\": true, \"await\": true, \"enum\": true,\n    \"implements\": true, \"interface\": true, \"module\": true, \"namespace\": true,\n    \"package\": true, \"private\": true, \"protected\": true, \"public\": true,\n    \"readonly\": true, \"type\": true, \"undefined\": true,\n}\n\nfunc safeFunctionName(name string) string {\n    if tsReservedKeywords[name] {\n        return name + \"Op\"\n    }\n    return name\n}\n```\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-003 — حفظ `operationId` در Manifest (بحرانی)",
      "content": "**شناسه:** SDK-003  \n**شدت:** بحرانی — پیش‌نیاز قانون SDK-001"
    },
    {
      "level": 4,
      "heading": "❌ ساختار ناقص (قبلی)",
      "content": "```go\ntype Operation struct {\n    Method   string  `json:\"method\"`\n    Path     string  `json:\"path\"`\n    // ← OperationID وجود ندارد!\n}\n```"
    },
    {
      "level": 4,
      "heading": "✅ ساختار صحیح",
      "content": "```go\ntype Operation struct {\n    Method      string       `json:\"method\"`\n    Path        string       `json:\"path\"`\n    OperationID string       `json:\"operation_id\"`  // ← اضافه شد\n    Request     *string      `json:\"request\"`\n    Response    *string      `json:\"response\"`\n    Auth        bool         `json:\"auth\"`\n    Timeout     int          `json:\"timeout\"`\n    Retry       int          `json:\"retry\"`\n    Cache       *CacheConfig `json:\"cache,omitempty\"`\n}\n```\n\nو در builder:\n\n```go\noperations[token] = manifest.Operation{\n    Method:      method,\n    Path:        pathStr,\n    OperationID: op.OperationID,  // ← از OpenAPI کپی می‌شود\n    ...\n}\n```\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-004 — پشتیبانی از `application/x-www-form-urlencoded` (بالا)",
      "content": "**شناسه:** SDK-004  \n**شدت:** بالا — عدم رعایت منجر به ارسال body خالی و خطای ۴۰۰ در runtime"
    },
    {
      "level": 4,
      "heading": "❌ پیاده‌سازی ناقص (قبلی)",
      "content": "```go\n// فقط application/json خوانده می‌شود — form-urlencoded نادیده گرفته می‌شود\ncontent := op.RequestBody.Value.Content.Get(\"application/json\")\n```"
    },
    {
      "level": 4,
      "heading": "✅ پیاده‌سازی صحیح (با fallback)",
      "content": "```go\nvar reqSchemaName *string\nvar reqContentType string\n\nif op.RequestBody != nil && op.RequestBody.Value != nil {\n    content := op.RequestBody.Value.Content.Get(\"application/json\")\n    if content != nil && content.Schema != nil && content.Schema.Ref != \"\" {\n        name := refToName(content.Schema.Ref)\n        reqSchemaName = &name\n        reqContentType = \"application/json\"\n    } else {\n        content = op.RequestBody.Value.Content.Get(\"application/x-www-form-urlencoded\")\n        if content != nil && content.Schema != nil && content.Schema.Ref != \"\" {\n            name := refToName(content.Schema.Ref)\n            reqSchemaName = &name\n            reqContentType = \"application/x-www-form-urlencoded\"\n        }\n    }\n}\n```\n\nو در generator، بر اساس `ContentType`:\n\n```typescript\n// form-urlencoded:\nconst formData = new URLSearchParams(Object.entries(data).map(([k, v]) => [k, String(v)]));\nbody: formData.toString()  +  'Content-Type': 'application/x-www-form-urlencoded'\n\n// json:\nbody: JSON.stringify(data)  +  'Content-Type': 'application/json'\n```\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-005 — Quote کردن نام‌های Property غیر-Identifier (متوسط)",
      "content": "**شناسه:** SDK-005  \n**شدت:** متوسط — منجر به SyntaxError در TypeScript"
    },
    {
      "level": 4,
      "heading": "❌ خروجی اشتباه",
      "content": "```typescript\nexport interface RegisterSubmitRequest {\n    traits.email: string;  // ❌ SyntaxError — dot در نام property\n}\n```"
    },
    {
      "level": 4,
      "heading": "✅ خروجی صحیح",
      "content": "```typescript\nexport interface RegisterSubmitRequest {\n    'traits.email': string;  // ✅ با single quote\n}\n```"
    },
    {
      "level": 4,
      "heading": "قانون تشخیص",
      "content": "یک property key نیاز به quote دارد اگر:\n- شامل `.` یا `-` یا فضای خالی باشد\n- با عدد شروع شود\n- کلمه کلیدی JavaScript باشد\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-006 — جلوگیری از Variable Shadowing در Hooks (متوسط)",
      "content": "**شناسه:** SDK-006  \n**شدت:** متوسط — منجر به خطای runtime که در کامپایل مشخص نمی‌شود"
    },
    {
      "level": 4,
      "heading": "❌ خروجی اشتباه",
      "content": "```typescript\nexport function useError() {\n    const [error, setError] = useState<any>(null);  // state var \"error\"\n    const res = await error();   // ❌ فراخوانی state variable، نه API function\n```"
    },
    {
      "level": 4,
      "heading": "✅ خروجی صحیح",
      "content": "```typescript\nexport function useError() {\n    const [fetchError, setFetchError] = useState<any>(null);  // renamed\n    const res = await error();   // ✅ صحیح\n```\n\n**قانون:** اگر نام state variable با نام یک API function import شده یکسان باشد، پیشوند `fetch` اضافه شود.\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-007 — کنترل `useEffect` auto-execute (پایین)",
      "content": "**شناسه:** SDK-007  \n**شدت:** پایین — منجر به side effect غیرمنتظره\n\n**قانون:** فقط endpoint‌های GET که side-effect مخرب ندارند می‌توانند auto-execute داشته باشند. عملیات‌هایی که token آن‌ها شامل `logout`، `revoke`، `delete`، `remove` است نباید auto-execute داشته باشند.\n\n---"
    },
    {
      "level": 3,
      "heading": "قانون SDK-008 — `operationId` اجباری و یکتا در OpenAPI (بحرانی)",
      "content": "**شناسه:** SDK-008  \n**شدت:** بحرانی — builder باید این را enforce کند\n\n```go\n// builder.go — اعتبارسنجی یکتایی operationId\nseen := make(map[string]string)\nfor token, op := range operations {\n    if op.OperationID == \"\" {\n        return nil, fmt.Errorf(\"operation %s (at %s %s) missing operationId in OpenAPI\",\n            token, op.Method, op.Path)\n    }\n    if existing, found := seen[op.OperationID]; found {\n        return nil, fmt.Errorf(\"duplicate operationId %q: tokens %s and %s\",\n            op.OperationID, existing, token)\n    }\n    seen[op.OperationID] = token\n}\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "۴. چک‌لیست CI",
      "content": "```bash"
    },
    {
      "level": 1,
      "heading": "۱. ساخت CLI",
      "content": "cd cli && go build ./... && go test -race ./internal/..."
    },
    {
      "level": 1,
      "heading": "۲. تولید SDK از OpenAPI سرویس",
      "content": "nons registry build --source ../nons-api/services/auth-service/docs/openapi.yaml auth\nnons generate"
    },
    {
      "level": 1,
      "heading": "۳. بررسی duplicate exports",
      "content": "grep -o \"export async function [a-zA-Z]*\" .nons/generated/api-client/*.ts \\\n    | sort | uniq -d | grep . && echo \"FAIL: duplicate exports\" || echo \"OK\""
    },
    {
      "level": 1,
      "heading": "۴. بررسی reserved keywords",
      "content": "grep -E \"function (protected|private|public|class)\\b\" .nons/generated/api-client/*.ts \\\n    && echo \"FAIL: reserved keyword\" || echo \"OK\""
    },
    {
      "level": 1,
      "heading": "۵. بررسی property با dot بدون quote",
      "content": "grep -E \"^\\s+[a-z]+\\.[a-z]+:\" .nons/generated/types/*.ts \\\n    && echo \"FAIL: unquoted dot-key\" || echo \"OK\""
    },
    {
      "level": 1,
      "heading": "۶. TypeScript compile",
      "content": "npx tsc --noEmit --strict --esModuleInterop .nons/generated/**/*.ts && echo \"OK\" || echo \"FAIL\"\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "۵. وضعیت اصلاحات",
      "content": "| شناسه | توضیح | وضعیت |\n|--------|--------|--------|\n| SDK-001 | نام تابع از token آخر | ✅ رفع شد v1.1.0 |\n| SDK-002 | کلمه کلیدی رزرو شده | ✅ رفع شد v1.1.0 |\n| SDK-003 | `operationId` در manifest نیست | ✅ رفع شد v1.1.0 |\n| SDK-004 | form-urlencoded body نادیده گرفته می‌شود | ✅ رفع شد v1.1.0 |\n| SDK-005 | نام property با dot | ✅ رفع شد v1.1.0 |\n| SDK-006 | Variable shadowing در hooks | ✅ رفع شد v1.1.0 |\n| SDK-007 | Auto-execute روی عملیات مخرب | ✅ رفع شد v1.1.0 |\n| SDK-008 | یکتایی operationId enforce نمی‌شود | ✅ رفع شد v1.1.0 |\n\n---"
    },
    {
      "level": 2,
      "heading": "۶. مستندات مرتبط",
      "content": "- [OpenAPI Guidelines](./openapi-guidelines.md) — قوانین نوشتن `openapi.yaml` در سرویس‌های Go\n- [API Design Guidelines](./api-design-guidelines.md) — اصول طراحی REST API"
    }
  ]
}