azurajs 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +32 -0
  2. package/dist/IpResolver-BVgnGnpf.d.mts +5 -0
  3. package/dist/IpResolver-BVgnGnpf.d.ts +5 -0
  4. package/dist/SwaggerPlugin-C0UZTjaZ.d.ts +6 -0
  5. package/dist/SwaggerPlugin-wr9S4SRG.d.mts +6 -0
  6. package/dist/cookies/index.d.mts +7 -0
  7. package/dist/cookies/index.d.ts +7 -0
  8. package/dist/cookies/index.js +38 -0
  9. package/dist/cookies/index.js.map +1 -0
  10. package/dist/cookies/index.mjs +35 -0
  11. package/dist/cookies/index.mjs.map +1 -0
  12. package/dist/core/index.d.mts +18 -27
  13. package/dist/core/index.d.ts +18 -27
  14. package/dist/core/index.js +214 -14
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/core/index.mjs +214 -14
  17. package/dist/core/index.mjs.map +1 -1
  18. package/dist/cors/index.d.mts +7 -0
  19. package/dist/cors/index.d.ts +7 -0
  20. package/dist/cors/index.js +52 -0
  21. package/dist/cors/index.js.map +1 -0
  22. package/dist/cors/index.mjs +50 -0
  23. package/dist/cors/index.mjs.map +1 -0
  24. package/dist/decorators/index.d.mts +2 -0
  25. package/dist/decorators/index.d.ts +2 -0
  26. package/dist/decorators/index.js +25 -0
  27. package/dist/decorators/index.js.map +1 -1
  28. package/dist/decorators/index.mjs +24 -1
  29. package/dist/decorators/index.mjs.map +1 -1
  30. package/dist/decorators-B6l3CbxC.d.ts +13 -0
  31. package/dist/decorators-D5nY109r.d.mts +13 -0
  32. package/dist/http-error/index.d.mts +18 -0
  33. package/dist/http-error/index.d.ts +18 -0
  34. package/dist/http-error/index.js +81 -0
  35. package/dist/http-error/index.js.map +1 -0
  36. package/dist/http-error/index.mjs +79 -0
  37. package/dist/http-error/index.mjs.map +1 -0
  38. package/dist/index-j6QGMhZU.d.mts +30 -0
  39. package/dist/index-tpPZS_UK.d.ts +30 -0
  40. package/dist/index.d.mts +16 -5
  41. package/dist/index.d.ts +16 -5
  42. package/dist/index.js +1178 -14
  43. package/dist/index.js.map +1 -1
  44. package/dist/index.mjs +1176 -15
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/infra/index.d.mts +6 -0
  47. package/dist/infra/index.d.ts +6 -0
  48. package/dist/infra/index.js +162 -0
  49. package/dist/infra/index.js.map +1 -0
  50. package/dist/infra/index.mjs +159 -0
  51. package/dist/infra/index.mjs.map +1 -0
  52. package/dist/{Logger-iEQNVVSc.d.mts → logger/index.d.mts} +2 -1
  53. package/dist/{Logger-iEQNVVSc.d.ts → logger/index.d.ts} +2 -1
  54. package/dist/logger/index.js +123 -0
  55. package/dist/logger/index.js.map +1 -0
  56. package/dist/logger/index.mjs +120 -0
  57. package/dist/logger/index.mjs.map +1 -0
  58. package/dist/plugins/index.d.mts +8 -6
  59. package/dist/plugins/index.d.ts +8 -6
  60. package/dist/plugins/index.js +1101 -0
  61. package/dist/plugins/index.js.map +1 -1
  62. package/dist/plugins/index.mjs +1101 -1
  63. package/dist/plugins/index.mjs.map +1 -1
  64. package/dist/rate-limit/index.d.mts +7 -0
  65. package/dist/rate-limit/index.d.ts +7 -0
  66. package/dist/rate-limit/index.js +81 -0
  67. package/dist/rate-limit/index.js.map +1 -0
  68. package/dist/rate-limit/index.mjs +79 -0
  69. package/dist/rate-limit/index.mjs.map +1 -0
  70. package/dist/router/index.d.mts +4 -0
  71. package/dist/router/index.d.ts +4 -0
  72. package/dist/router/index.js +218 -0
  73. package/dist/router/index.js.map +1 -0
  74. package/dist/router/index.mjs +216 -0
  75. package/dist/router/index.mjs.map +1 -0
  76. package/dist/routes.type-DZO5VBW2.d.mts +58 -0
  77. package/dist/routes.type-DzHNkCag.d.ts +58 -0
  78. package/dist/swagger/index.d.mts +30 -0
  79. package/dist/swagger/index.d.ts +30 -0
  80. package/dist/swagger/index.js +1136 -0
  81. package/dist/swagger/index.js.map +1 -0
  82. package/dist/swagger/index.mjs +1126 -0
  83. package/dist/swagger/index.mjs.map +1 -0
  84. package/dist/swagger/swagger-ui-modern.html +894 -0
  85. package/dist/swagger.type-Bfn5nGR8.d.mts +42 -0
  86. package/dist/swagger.type-CfDbFCZC.d.ts +42 -0
  87. package/dist/types/index.d.mts +5 -58
  88. package/dist/types/index.d.ts +5 -58
  89. package/dist/utils/index.d.mts +7 -72
  90. package/dist/utils/index.d.ts +7 -72
  91. package/dist/validators/index.d.mts +48 -0
  92. package/dist/validators/index.d.ts +48 -0
  93. package/dist/validators/index.js +144 -0
  94. package/dist/validators/index.js.map +1 -0
  95. package/dist/validators/index.mjs +141 -0
  96. package/dist/validators/index.mjs.map +1 -0
  97. package/package.json +86 -2
  98. package/src/cookies/index.ts +1 -0
  99. package/src/core/index.ts +1 -0
  100. package/src/core/router.ts +26 -15
  101. package/src/core/server.ts +64 -14
  102. package/src/cors/index.ts +2 -0
  103. package/src/decorators/index.ts +2 -0
  104. package/src/http-error/index.ts +1 -0
  105. package/src/infra/index.ts +3 -0
  106. package/src/logger/index.ts +1 -0
  107. package/src/plugins/SwaggerPlugin.ts +45 -0
  108. package/src/plugins/index.ts +1 -0
  109. package/src/rate-limit/index.ts +2 -0
  110. package/src/router/index.ts +1 -0
  111. package/src/swagger/constants.ts +8 -0
  112. package/src/swagger/decorators.ts +35 -0
  113. package/src/swagger/index.ts +10 -0
  114. package/src/swagger/openapi-builder.ts +199 -0
  115. package/src/swagger/swagger-ui-html.ts +24 -0
  116. package/src/swagger/swagger-ui-modern.html +894 -0
  117. package/src/swagger/swagger-ui-template.ts +5 -0
  118. package/src/types/index.ts +7 -0
  119. package/src/types/swagger.type.ts +36 -0
  120. package/src/validators/index.ts +4 -0
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gerado por scripts/embed-swagger-ui.mjs a partir de swagger-ui-modern.html.
3
+ * Após editar o HTML, execute: node scripts/embed-swagger-ui.mjs
4
+ */
5
+ export const SWAGGER_UI_MODERN_HTML: string = "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\r\n <title>__PAGE_TITLE__</title>\r\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\r\n <link\r\n href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\"\r\n rel=\"stylesheet\" />\r\n <style>\r\n :root {\r\n --bg-body: #09090b;\r\n --bg-sidebar: #18181b;\r\n --bg-card: #18181b;\r\n --bg-card-hover: #27272a;\r\n --bg-input: #09090b;\r\n --border-color: #27272a;\r\n --primary-color: #6366f1;\r\n --primary-hover: #4f46e5;\r\n --text-primary: #f4f4f5;\r\n --text-secondary: #a1a1aa;\r\n --text-muted: #52525b;\r\n --success: #22c55e;\r\n --warning: #eab308;\r\n --danger: #ef4444;\r\n --info: #3b82f6;\r\n --font-sans: 'Inter', system-ui, -apple-system, sans-serif;\r\n --font-mono: 'JetBrains Mono', monospace;\r\n }\r\n\r\n * {\r\n margin: 0;\r\n padding: 0;\r\n box-sizing: border-box\r\n }\r\n\r\n body {\r\n font-family: var(--font-sans);\r\n background-color: var(--bg-body);\r\n color: var(--text-primary);\r\n line-height: 1.5;\r\n height: 100vh;\r\n overflow: hidden\r\n }\r\n\r\n ::-webkit-scrollbar {\r\n width: 6px;\r\n height: 6px\r\n }\r\n\r\n ::-webkit-scrollbar-track {\r\n background: transparent\r\n }\r\n\r\n ::-webkit-scrollbar-thumb {\r\n background: var(--border-color);\r\n border-radius: 3px\r\n }\r\n\r\n ::-webkit-scrollbar-thumb:hover {\r\n background: var(--text-muted)\r\n }\r\n\r\n .layout {\r\n display: flex;\r\n height: 100%\r\n }\r\n\r\n .sidebar {\r\n width: 300px;\r\n background: var(--bg-sidebar);\r\n border-right: 1px solid var(--border-color);\r\n display: flex;\r\n flex-direction: column;\r\n flex-shrink: 0\r\n }\r\n\r\n .sidebar-header {\r\n padding: 20px;\r\n border-bottom: 1px solid var(--border-color);\r\n background: var(--bg-sidebar)\r\n }\r\n\r\n .api-title {\r\n font-size: 1.25rem;\r\n font-weight: 700;\r\n color: var(--text-primary);\r\n margin-bottom: 4px\r\n }\r\n\r\n .api-version {\r\n font-size: 0.75rem;\r\n color: var(--primary-color);\r\n background: rgba(99, 102, 241, 0.1);\r\n padding: 2px 8px;\r\n border-radius: 12px;\r\n font-family: var(--font-mono)\r\n }\r\n\r\n .search-box {\r\n padding: 16px\r\n }\r\n\r\n .search-input {\r\n width: 100%;\r\n background: var(--bg-input);\r\n border: 1px solid var(--border-color);\r\n padding: 8px 12px;\r\n border-radius: 6px;\r\n color: var(--text-primary);\r\n font-size: 0.875rem;\r\n outline: none;\r\n transition: border-color 0.2s\r\n }\r\n\r\n .search-input:focus {\r\n border-color: var(--primary-color)\r\n }\r\n\r\n .nav-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n padding: 0 16px 20px\r\n }\r\n\r\n .nav-group-title {\r\n font-size: 0.75rem;\r\n text-transform: uppercase;\r\n color: var(--text-muted);\r\n font-weight: 600;\r\n margin: 24px 0 8px;\r\n letter-spacing: 0.05em\r\n }\r\n\r\n .nav-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n transition: background 0.2s;\r\n margin-bottom: 2px\r\n }\r\n\r\n .nav-item:hover {\r\n background: var(--bg-card-hover)\r\n }\r\n\r\n .nav-item.active {\r\n background: rgba(99, 102, 241, 0.1)\r\n }\r\n\r\n .method-tag {\r\n font-family: var(--font-mono);\r\n font-size: 0.65rem;\r\n font-weight: 700;\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n width: 45px;\r\n text-align: center\r\n }\r\n\r\n .get {\r\n color: var(--success);\r\n background: rgba(34, 197, 94, 0.1)\r\n }\r\n\r\n .post {\r\n color: var(--primary-color);\r\n background: rgba(99, 102, 241, 0.1)\r\n }\r\n\r\n .put {\r\n color: var(--warning);\r\n background: rgba(234, 179, 8, 0.1)\r\n }\r\n\r\n .delete {\r\n color: var(--danger);\r\n background: rgba(239, 68, 68, 0.1)\r\n }\r\n\r\n .patch {\r\n color: var(--info);\r\n background: rgba(59, 130, 246, 0.1)\r\n }\r\n\r\n .nav-path {\r\n font-size: 0.8rem;\r\n color: var(--text-secondary);\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n font-family: var(--font-mono)\r\n }\r\n\r\n .main-content {\r\n flex: 1;\r\n overflow-y: auto;\r\n padding: 40px;\r\n scroll-behavior: smooth\r\n }\r\n\r\n .content-width {\r\n max-width: 1000px;\r\n margin: 0 auto\r\n }\r\n\r\n .info-section {\r\n margin-bottom: 40px\r\n }\r\n\r\n .info-title {\r\n font-size: 2.5rem;\r\n font-weight: 800;\r\n margin-bottom: 16px;\r\n letter-spacing: -0.02em\r\n }\r\n\r\n .info-desc {\r\n color: var(--text-secondary);\r\n font-size: 1.1rem;\r\n max-width: 700px\r\n }\r\n\r\n .server-section {\r\n background: var(--bg-card);\r\n border: 1px solid var(--border-color);\r\n padding: 20px;\r\n border-radius: 8px;\r\n margin-bottom: 40px\r\n }\r\n\r\n .server-label {\r\n display: block;\r\n font-size: 0.875rem;\r\n color: var(--text-muted);\r\n margin-bottom: 8px\r\n }\r\n\r\n .server-select {\r\n width: 100%;\r\n background: var(--bg-input);\r\n border: 1px solid var(--border-color);\r\n color: var(--text-primary);\r\n padding: 10px;\r\n border-radius: 6px;\r\n font-family: var(--font-mono);\r\n font-size: 0.9rem\r\n }\r\n\r\n .endpoint-card {\r\n background: var(--bg-card);\r\n border: 1px solid var(--border-color);\r\n border-radius: 8px;\r\n margin-bottom: 24px;\r\n overflow: hidden;\r\n transition: border-color 0.2s\r\n }\r\n\r\n .endpoint-card:hover {\r\n border-color: var(--text-muted)\r\n }\r\n\r\n .card-header {\r\n padding: 16px 24px;\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n cursor: pointer;\r\n background: rgba(255, 255, 255, 0.02);\r\n user-select: none\r\n }\r\n\r\n .card-summary {\r\n flex: 1;\r\n font-weight: 500;\r\n font-size: 1rem\r\n }\r\n\r\n .card-path {\r\n font-family: var(--font-mono);\r\n font-size: 0.85rem;\r\n color: var(--text-muted)\r\n }\r\n\r\n .chevron {\r\n transition: transform 0.2s;\r\n color: var(--text-muted)\r\n }\r\n\r\n .card-header[aria-expanded=\"true\"] .chevron {\r\n transform: rotate(180deg)\r\n }\r\n\r\n .card-body {\r\n border-top: 1px solid var(--border-color);\r\n padding: 24px;\r\n display: none\r\n }\r\n\r\n .card-header[aria-expanded=\"true\"]+.card-body {\r\n display: block\r\n }\r\n\r\n .section-header {\r\n font-size: 0.75rem;\r\n text-transform: uppercase;\r\n color: var(--text-muted);\r\n font-weight: 700;\r\n margin: 24px 0 12px;\r\n letter-spacing: 0.05em\r\n }\r\n\r\n .section-header:first-child {\r\n margin-top: 0\r\n }\r\n\r\n .params-table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n font-size: 0.9rem;\r\n margin-bottom: 20px\r\n }\r\n\r\n .params-table th {\r\n text-align: left;\r\n color: var(--text-muted);\r\n padding: 8px 12px;\r\n font-weight: 500;\r\n border-bottom: 1px solid var(--border-color);\r\n font-size: 0.8rem\r\n }\r\n\r\n .params-table td {\r\n padding: 12px;\r\n border-bottom: 1px solid var(--border-color);\r\n color: var(--text-secondary);\r\n vertical-align: top\r\n }\r\n\r\n .params-table tr:last-child td {\r\n border-bottom: none\r\n }\r\n\r\n .param-name {\r\n font-family: var(--font-mono);\r\n color: var(--text-primary);\r\n font-weight: 600\r\n }\r\n\r\n .param-req {\r\n color: var(--danger);\r\n font-size: 0.7rem;\r\n margin-left: 4px\r\n }\r\n\r\n .param-meta {\r\n font-size: 0.8rem;\r\n color: var(--text-muted);\r\n margin-top: 4px;\r\n font-family: var(--font-mono)\r\n }\r\n\r\n .code-block {\r\n background: #000;\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n padding: 16px;\r\n font-family: var(--font-mono);\r\n font-size: 0.85rem;\r\n overflow-x: auto;\r\n color: #d4d4d8\r\n }\r\n\r\n .try-it-box {\r\n background: rgba(99, 102, 241, 0.05);\r\n border: 1px solid rgba(99, 102, 241, 0.2);\r\n border-radius: 8px;\r\n padding: 20px;\r\n margin-top: 32px\r\n }\r\n\r\n .form-grid {\r\n display: grid;\r\n grid-template-columns: 1fr;\r\n gap: 16px;\r\n margin-bottom: 20px\r\n }\r\n\r\n .form-group label {\r\n display: block;\r\n font-size: 0.8rem;\r\n font-weight: 600;\r\n margin-bottom: 6px;\r\n color: var(--text-secondary)\r\n }\r\n\r\n .form-group input {\r\n width: 100%;\r\n background: var(--bg-body);\r\n border: 1px solid var(--border-color);\r\n padding: 8px 12px;\r\n border-radius: 4px;\r\n color: var(--text-primary);\r\n font-family: var(--font-mono);\r\n font-size: 0.85rem\r\n }\r\n\r\n .form-group input:focus {\r\n border-color: var(--primary-color);\r\n outline: none\r\n }\r\n\r\n .body-editor {\r\n width: 100%;\r\n height: 200px;\r\n background: var(--bg-body);\r\n border: 1px solid var(--border-color);\r\n border-radius: 6px;\r\n padding: 12px;\r\n color: var(--text-primary);\r\n font-family: var(--font-mono);\r\n font-size: 0.85rem;\r\n resize: vertical;\r\n margin-bottom: 16px\r\n }\r\n\r\n .action-btn {\r\n background: var(--primary-color);\r\n color: white;\r\n border: none;\r\n padding: 10px 20px;\r\n border-radius: 6px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n font-size: 0.9rem;\r\n transition: background 0.2s;\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px\r\n }\r\n\r\n .action-btn:hover {\r\n background: var(--primary-hover)\r\n }\r\n\r\n .action-btn:disabled {\r\n opacity: 0.6;\r\n cursor: not-allowed\r\n }\r\n\r\n .response-area {\r\n margin-top: 20px;\r\n border-top: 1px solid var(--border-color);\r\n padding-top: 20px\r\n }\r\n\r\n .status-badge {\r\n display: inline-block;\r\n padding: 4px 8px;\r\n border-radius: 4px;\r\n font-size: 0.8rem;\r\n font-weight: 700;\r\n margin-bottom: 10px;\r\n font-family: var(--font-mono)\r\n }\r\n\r\n .status-2xx {\r\n background: rgba(34, 197, 94, 0.2);\r\n color: var(--success)\r\n }\r\n\r\n .status-4xx,\r\n .status-5xx {\r\n background: rgba(239, 68, 68, 0.2);\r\n color: var(--danger)\r\n }\r\n\r\n .loader {\r\n width: 16px;\r\n height: 16px;\r\n border: 2px solid #ffffff;\r\n border-bottom-color: transparent;\r\n border-radius: 50%;\r\n display: inline-block;\r\n animation: rotation 1s linear infinite\r\n }\r\n\r\n @keyframes rotation {\r\n 0% {\r\n transform: rotate(0deg)\r\n }\r\n\r\n 100% {\r\n transform: rotate(360deg)\r\n }\r\n }\r\n\r\n @media (max-width:768px) {\r\n .layout {\r\n flex-direction: column;\r\n overflow: auto\r\n }\r\n\r\n .sidebar {\r\n width: 100%;\r\n height: auto;\r\n border-right: none;\r\n border-bottom: 1px solid var(--border-color)\r\n }\r\n\r\n .main-content {\r\n padding: 20px;\r\n overflow: visible\r\n }\r\n\r\n .nav-list {\r\n max-height: 300px\r\n }\r\n }\r\n </style>\r\n</head>\r\n\r\n<body>\r\n <div class=\"layout\">\r\n <aside class=\"sidebar\">\r\n <div class=\"sidebar-header\">\r\n <div class=\"api-title\" id=\"apiTitle\">API Docs</div>\r\n <span class=\"api-version\" id=\"apiVersion\">v1.0.0</span>\r\n </div>\r\n <div class=\"search-box\">\r\n <input type=\"text\" class=\"search-input\" id=\"searchNav\" placeholder=\"Filter endpoints...\">\r\n </div>\r\n <div class=\"nav-list\" id=\"navList\"></div>\r\n </aside>\r\n <main class=\"main-content\">\r\n <div class=\"content-width\">\r\n <div class=\"info-section\">\r\n <h1 class=\"info-title\" id=\"docTitle\">API Reference</h1>\r\n <p class=\"info-desc\" id=\"docDesc\">Loading documentation...</p>\r\n </div>\r\n <div class=\"server-section\">\r\n <label class=\"server-label\">Base URL</label>\r\n <select class=\"server-select\" id=\"serverUrl\"></select>\r\n </div>\r\n <div id=\"endpointsContainer\"></div>\r\n </div>\r\n </main>\r\n </div>\r\n\r\n <script>\r\n const state = { spec: null, server: '' };\r\n function sanitizeId(str) { return String(str).replace(/[^a-zA-Z0-9_-]/g, '_'); }\r\n async function init() {\r\n try {\r\n const res = await fetch(\"___AZURA_SPEC_URL_PLACEHOLDER___\");\r\n if (!res.ok) throw new Error('Spec not found');\r\n state.spec = await res.json();\r\n renderHeader();\r\n renderServers();\r\n renderNavigation();\r\n renderEndpoints();\r\n setupSearch();\r\n attachGlobalListeners();\r\n } catch (err) {\r\n const descEl = document.getElementById('docDesc');\r\n descEl.textContent = 'Failed to load API definition. Check that the OpenAPI JSON endpoint is configured and reachable.';\r\n descEl.style.color = 'var(--danger)';\r\n }\r\n }\r\n\r\n function renderHeader() {\r\n const info = state.spec.info || {};\r\n document.title = `${info.title || 'API'} - Docs`;\r\n document.getElementById('apiTitle').textContent = info.title || 'API';\r\n document.getElementById('apiVersion').textContent = info.version ? `v${info.version}` : '';\r\n document.getElementById('docTitle').textContent = info.title || 'API Reference';\r\n document.getElementById('docDesc').textContent = info.description || 'No description provided.';\r\n }\r\n\r\n function renderServers() {\r\n const select = document.getElementById('serverUrl');\r\n const servers = state.spec.servers && state.spec.servers.length ? state.spec.servers : [{ url: window.location.origin, description: 'Current Origin' }];\r\n select.innerHTML = servers.map(s => `<option value=\"${s.url}\">${s.url}${s.description ? ` (${s.description})` : ''}</option>`).join('');\r\n state.server = servers[0].url;\r\n select.addEventListener('change', (e) => state.server = e.target.value);\r\n }\r\n\r\n function renderNavigation() {\r\n const nav = document.getElementById('navList');\r\n const tags = {};\r\n Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {\r\n Object.entries(methods).forEach(([method, op]) => {\r\n const tag = (op.tags && op.tags[0]) ? op.tags[0] : 'General';\r\n if (!tags[tag]) tags[tag] = [];\r\n tags[tag].push({ path, method, op });\r\n });\r\n });\r\n\r\n let html = '';\r\n Object.entries(tags).forEach(([tag, items]) => {\r\n html += `<div class=\"nav-group-title\">${tag}</div>`;\r\n items.forEach(item => {\r\n const label = item.op.summary || item.path;\r\n const filter = `${item.method} ${item.path} ${label}`.toLowerCase();\r\n const targetId = sanitizeId(`${item.method}-${item.path}`);\r\n html += `<div class=\"nav-item\" data-filter=\"${escapeHtmlAttr(filter)}\" data-target-id=\"${escapeHtmlAttr(targetId)}\">\r\n <span class=\"method-tag ${item.method.toLowerCase()}\">${item.method.toUpperCase()}</span>\r\n <span class=\"nav-path\">${escapeHtmlAttr(label)}</span>\r\n </div>`;\r\n });\r\n });\r\n\r\n nav.innerHTML = html;\r\n nav.querySelectorAll('.nav-item').forEach(el => {\r\n el.addEventListener('click', () => {\r\n const tid = el.dataset.targetId;\r\n scrollEndpoint(tid);\r\n document.querySelectorAll('.nav-item').forEach(i => i.classList.toggle('active', i === el));\r\n });\r\n });\r\n }\r\n\r\n function renderEndpoints() {\r\n const container = document.getElementById('endpointsContainer');\r\n container.innerHTML = '';\r\n Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {\r\n Object.entries(methods).forEach(([method, op]) => {\r\n container.appendChild(createEndpointCard(path, method, op));\r\n });\r\n });\r\n }\r\n\r\n function createEndpointCard(path, method, op) {\r\n const rawId = `${method}-${path}`;\r\n const id = sanitizeId(rawId);\r\n const card = document.createElement('div');\r\n card.className = 'endpoint-card';\r\n card.id = id;\r\n\r\n const descriptionHtml = op.description ? `<p style=\"margin-bottom:20px;color:var(--text-secondary)\">${escapeHtml(op.description)}</p>` : '';\r\n const paramsHtml = renderParameters(op.parameters);\r\n const requestBodyHtml = renderRequestBody(op.requestBody);\r\n const responsesHtml = renderResponses(op.responses);\r\n\r\n const inputsId = `inputs_${id}`;\r\n const bodyId = `body_${id}`;\r\n const tryButtonId = `btn_${id}`;\r\n const inputsHtmlForTry = (op.parameters || []).map(p => {\r\n const placeholder = p.schema?.type || '';\r\n const required = p.required ? ' <span style=\"color:var(--danger)\">*</span>' : '';\r\n return `<div class=\"form-group\">\r\n <label>${escapeHtml(p.name)} <span style=\"font-weight:400;color:var(--text-muted)\">(${escapeHtml(p.in)})</span>${required}</label>\r\n <input type=\"text\" data-name=\"${escapeHtmlAttr(p.name)}\" data-in=\"${escapeHtmlAttr(p.in)}\" placeholder=\"${escapeHtmlAttr(placeholder)}\">\r\n </div>`;\r\n }).join('');\r\n\r\n const bodyEditorHtml = op.requestBody ? `<div class=\"form-group\">\r\n <label>Request Body (JSON)</label>\r\n <textarea class=\"body-editor\" id=\"${bodyId}\">${getExampleBody(op.requestBody)}</textarea>\r\n </div>` : '';\r\n\r\n card.innerHTML = `\r\n <div class=\"card-header\" aria-expanded=\"false\">\r\n <span class=\"method-tag ${method.toLowerCase()}\">${method.toUpperCase()}</span>\r\n <span class=\"card-summary\">${escapeHtml(op.summary || path)}</span>\r\n <span class=\"card-path\">${escapeHtml(path)}</span>\r\n <svg class=\"chevron\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>\r\n </div>\r\n <div class=\"card-body\">\r\n ${descriptionHtml}\r\n ${paramsHtml}\r\n ${requestBodyHtml}\r\n <div class=\"try-it-box\">\r\n <div class=\"section-header\">Try It Out</div>\r\n <div class=\"form-grid\" id=\"${inputsId}\">\r\n ${inputsHtmlForTry}\r\n </div>\r\n ${bodyEditorHtml}\r\n <button class=\"action-btn\" id=\"${tryButtonId}\" data-method=\"${escapeHtmlAttr(method)}\" data-path=\"${escapeHtmlAttr(path)}\" data-card-id=\"${escapeHtmlAttr(id)}\">\r\n <span>Execute Request</span>\r\n </button>\r\n <div class=\"response-area\" style=\"display:none\"></div>\r\n </div>\r\n ${responsesHtml}\r\n </div>\r\n `;\r\n\r\n return card;\r\n }\r\n\r\n function renderParameters(params) {\r\n if (!params || params.length === 0) return '';\r\n return `\r\n <div class=\"section-header\">Parameters</div>\r\n <table class=\"params-table\">\r\n <thead><tr><th>Name</th><th>Description</th></tr></thead>\r\n <tbody>\r\n ${params.map(p => `\r\n <tr>\r\n <td>\r\n <div class=\"param-name\">${escapeHtml(p.name)}${p.required ? '<span class=\"param-req\">*</span>' : ''}</div>\r\n <div class=\"param-meta\">${escapeHtml(p.in)} &bull; ${escapeHtml(p.schema?.type || 'string')}</div>\r\n </td>\r\n <td>${escapeHtml(p.description || '-')}</td>\r\n </tr>\r\n `).join('')}\r\n </tbody>\r\n </table>\r\n `;\r\n }\r\n\r\n function renderRequestBody(body) {\r\n if (!body) return '';\r\n const contentType = Object.keys(body.content || {})[0] || 'application/json';\r\n const schema = body.content && body.content[contentType] ? body.content[contentType].schema : null;\r\n return `\r\n <div class=\"section-header\">Request Body <span style=\"font-weight:400;text-transform:none;color:var(--text-secondary)\">(${escapeHtml(contentType)})</span></div>\r\n <div class=\"code-block\">${escapeHtml(JSON.stringify(generateExample(schema), null, 2))}</div>\r\n `;\r\n }\r\n\r\n function renderResponses(responses) {\r\n if (!responses) return '';\r\n return `\r\n <div class=\"section-header\">Responses</div>\r\n ${Object.entries(responses).map(([status, res]) => `\r\n <div style=\"margin-bottom:16px\">\r\n <div style=\"font-weight:600;font-family:var(--font-mono);margin-bottom:4px\">\r\n <span style=\"color:${String(status).startsWith('2') ? 'var(--success)' : 'var(--danger)'}\">${escapeHtml(status)}</span>\r\n <span style=\"color:var(--text-secondary)\">${escapeHtml(res.description || '')}</span>\r\n </div>\r\n </div>\r\n `).join('')}\r\n `;\r\n }\r\n\r\n function getExampleBody(body) {\r\n if (!body || !body.content) return '{}';\r\n const contentType = Object.keys(body.content)[0];\r\n const schema = body.content[contentType].schema;\r\n try { return JSON.stringify(generateExample(schema), null, 2); } catch { return '{}'; }\r\n }\r\n\r\n function generateExample(schema) {\r\n if (!schema) return {};\r\n if (schema.example !== undefined) return schema.example;\r\n if (schema.$ref) {\r\n const ref = String(schema.$ref).replace(/^#\\/components\\/schemas\\//, '');\r\n const comp = state.spec.components && state.spec.components.schemas && state.spec.components.schemas[ref];\r\n if (comp) return generateExample(comp);\r\n return {};\r\n }\r\n if (schema.type === 'object' && schema.properties) {\r\n const obj = {};\r\n for (const [k, prop] of Object.entries(schema.properties)) {\r\n obj[k] = generateExample(prop);\r\n }\r\n return obj;\r\n }\r\n if (schema.type === 'array') return [generateExample(schema.items)];\r\n if (schema.type === 'string') return 'string';\r\n if (schema.type === 'integer' || schema.type === 'number') return 0;\r\n if (schema.type === 'boolean') return true;\r\n return null;\r\n }\r\n\r\n async function executeRequest(method, path, btn) {\r\n const cardId = btn.dataset.cardId;\r\n const card = document.getElementById(cardId);\r\n const inputs = card.querySelectorAll('input[data-name]');\r\n const bodyEditor = card.querySelector('.body-editor');\r\n const responseArea = card.querySelector('.response-area');\r\n const originalHtml = btn.innerHTML;\r\n btn.disabled = true;\r\n btn.innerHTML = '<span class=\"loader\"></span> Executing...';\r\n responseArea.style.display = 'none';\r\n try {\r\n let url = (state.server || '') + path;\r\n const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };\r\n const queryParams = new URLSearchParams();\r\n inputs.forEach(input => {\r\n const value = input.value.trim();\r\n if (!value) return;\r\n const type = input.dataset.in;\r\n const name = input.dataset.name;\r\n if (type === 'path') {\r\n url = url.replace(`{${name}}`, encodeURIComponent(value));\r\n } else if (type === 'query') {\r\n queryParams.append(name, value);\r\n } else if (type === 'header') {\r\n headers[name] = value;\r\n } else if (type === 'cookie') {\r\n // cookies handled as header for demo purposes\r\n headers['Cookie'] = (headers['Cookie'] ? headers['Cookie'] + '; ' : '') + `${name}=${value}`;\r\n }\r\n });\r\n const qs = queryParams.toString();\r\n if (qs) url += `?${qs}`;\r\n const options = { method: method.toUpperCase(), headers };\r\n if (bodyEditor && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {\r\n try {\r\n const parsed = JSON.parse(bodyEditor.value);\r\n options.body = JSON.stringify(parsed);\r\n } catch (e) {\r\n alert('Invalid JSON in Request Body');\r\n throw new Error('Invalid JSON');\r\n }\r\n }\r\n const start = performance.now();\r\n const res = await fetch(url, options);\r\n const duration = Math.round(performance.now() - start);\r\n let data;\r\n const ct = res.headers.get('content-type') || '';\r\n if (ct.includes('application/json')) data = await res.json(); else data = await res.text();\r\n const statusClass = res.ok ? 'status-2xx' : (String(res.status).startsWith('5') ? 'status-5xx' : 'status-4xx');\r\n responseArea.style.display = 'block';\r\n responseArea.innerHTML = `\r\n <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;\">\r\n <span class=\"status-badge ${statusClass}\">${res.status} ${escapeHtml(res.statusText)}</span>\r\n <span style=\"font-family:var(--font-mono);font-size:0.8rem;color:var(--text-muted)\">${duration}ms</span>\r\n </div>\r\n <div class=\"section-header\">Response Body</div>\r\n <div class=\"code-block\">${escapeHtml(typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data))}</div>\r\n <div class=\"section-header\">Response Headers</div>\r\n <div class=\"code-block\">${escapeHtml(Array.from(res.headers.entries()).map(([k, v]) => `${k}: ${v}`).join('\\\\n'))}</div>\r\n `;\r\n } catch (err) {\r\n responseArea.style.display = 'block';\r\n responseArea.innerHTML = `<div style=\"color:var(--danger);font-weight:600\">Error: ${escapeHtml(err.message || String(err))}</div>`;\r\n } finally {\r\n btn.disabled = false;\r\n btn.innerHTML = originalHtml;\r\n }\r\n }\r\n\r\n function toggleCard(header) {\r\n const isExpanded = header.getAttribute('aria-expanded') === 'true';\r\n header.setAttribute('aria-expanded', String(!isExpanded));\r\n }\r\n\r\n function scrollEndpoint(sanitizedId) {\r\n const el = document.getElementById(sanitizedId);\r\n if (!el) return;\r\n el.scrollIntoView({ behavior: 'smooth', block: 'start' });\r\n const header = el.querySelector('.card-header');\r\n if (header && header.getAttribute('aria-expanded') !== 'true') {\r\n header.setAttribute('aria-expanded', 'true');\r\n }\r\n }\r\n\r\n function setupSearch() {\r\n const input = document.getElementById('searchNav');\r\n input.addEventListener('input', (e) => {\r\n const val = e.target.value.toLowerCase();\r\n document.querySelectorAll('.nav-item').forEach(el => {\r\n const filter = el.dataset.filter || '';\r\n el.style.display = filter.includes(val) ? 'flex' : 'none';\r\n });\r\n });\r\n }\r\n\r\n function attachGlobalListeners() {\r\n document.querySelectorAll('.endpoint-card .card-header').forEach(header => {\r\n header.addEventListener('click', () => toggleCard(header));\r\n });\r\n document.querySelectorAll('.action-btn').forEach(btn => {\r\n btn.addEventListener('click', () => executeRequest(btn.dataset.method, btn.dataset.path, btn));\r\n });\r\n }\r\n\r\n function escapeHtml(str) {\r\n if (str === undefined || str === null) return '';\r\n return String(str)\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#39;');\r\n }\r\n function escapeHtmlAttr(str) {\r\n return escapeHtml(str).replace(/\"/g, '&quot;').replace(/'/g, '&#39;');\r\n }\r\n\r\n init();\r\n </script>\r\n</body>\r\n\r\n</html>";
@@ -53,3 +53,10 @@ export type {
53
53
  SSEOptions,
54
54
  MultipartOptions,
55
55
  } from "./plugins/plugin.type.js";
56
+
57
+ export type {
58
+ OpenApiInfo,
59
+ OpenApiServer,
60
+ BuildOpenApiOptions,
61
+ SwaggerPluginOptions,
62
+ } from "./swagger.type.js";
@@ -0,0 +1,36 @@
1
+ import type { RouteDocument } from "../core/router.js";
2
+
3
+ export interface OpenApiInfo {
4
+ title: string;
5
+ version: string;
6
+ description?: string;
7
+ contact?: { name?: string; url?: string; email?: string };
8
+ license?: { name: string; url?: string };
9
+ }
10
+
11
+ export interface OpenApiServer {
12
+ url: string;
13
+ description?: string;
14
+ }
15
+
16
+ export interface BuildOpenApiOptions {
17
+ /** Se omitido, usa título e versão genéricos. */
18
+ info?: OpenApiInfo;
19
+ servers?: OpenApiServer[];
20
+ /** Prefixo global (ex.: /api/v1) aplicado a todos os paths do documento */
21
+ pathPrefix?: string;
22
+ }
23
+
24
+ export interface SwaggerPluginOptions extends BuildOpenApiOptions {
25
+ /**
26
+ * Fonte de rotas — normalmente `() => app.getRouteDocuments()`.
27
+ * Avaliado em cada pedido ao JSON para refletir rotas novas.
28
+ */
29
+ getDocuments: () => RouteDocument[];
30
+ /** Caminho do OpenAPI JSON (default `/openapi.json`) */
31
+ specPath?: string;
32
+ /** Caminho da UI Swagger (default `/docs`) */
33
+ docsPath?: string;
34
+ /** Incluir barra de pesquisa e deep-linking */
35
+ deepLinking?: boolean;
36
+ }
@@ -0,0 +1,4 @@
1
+ export { DTOValidator } from "../utils/validators/DTOValidator.js";
2
+ export type { Schema, ValidationResult, ValidationError } from "../utils/validators/DTOValidator.js";
3
+ export { SchemaValidator } from "../utils/validators/SchemaValidator.js";
4
+ export type { ExternalSchema, SchemaValidationResult } from "../utils/validators/SchemaValidator.js";