azurajs 3.0.3 → 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 (113) 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 +17 -26
  13. package/dist/core/index.d.ts +17 -26
  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 +15 -4
  41. package/dist/index.d.ts +15 -4
  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/plugins/index.d.mts +8 -6
  53. package/dist/plugins/index.d.ts +8 -6
  54. package/dist/plugins/index.js +1101 -0
  55. package/dist/plugins/index.js.map +1 -1
  56. package/dist/plugins/index.mjs +1101 -1
  57. package/dist/plugins/index.mjs.map +1 -1
  58. package/dist/rate-limit/index.d.mts +7 -0
  59. package/dist/rate-limit/index.d.ts +7 -0
  60. package/dist/rate-limit/index.js +81 -0
  61. package/dist/rate-limit/index.js.map +1 -0
  62. package/dist/rate-limit/index.mjs +79 -0
  63. package/dist/rate-limit/index.mjs.map +1 -0
  64. package/dist/router/index.d.mts +4 -0
  65. package/dist/router/index.d.ts +4 -0
  66. package/dist/router/index.js +218 -0
  67. package/dist/router/index.js.map +1 -0
  68. package/dist/router/index.mjs +216 -0
  69. package/dist/router/index.mjs.map +1 -0
  70. package/dist/routes.type-DZO5VBW2.d.mts +58 -0
  71. package/dist/routes.type-DzHNkCag.d.ts +58 -0
  72. package/dist/swagger/index.d.mts +30 -0
  73. package/dist/swagger/index.d.ts +30 -0
  74. package/dist/swagger/index.js +1136 -0
  75. package/dist/swagger/index.js.map +1 -0
  76. package/dist/swagger/index.mjs +1126 -0
  77. package/dist/swagger/index.mjs.map +1 -0
  78. package/dist/swagger/swagger-ui-modern.html +894 -0
  79. package/dist/swagger.type-Bfn5nGR8.d.mts +42 -0
  80. package/dist/swagger.type-CfDbFCZC.d.ts +42 -0
  81. package/dist/types/index.d.mts +5 -58
  82. package/dist/types/index.d.ts +5 -58
  83. package/dist/utils/index.d.mts +6 -71
  84. package/dist/utils/index.d.ts +6 -71
  85. package/dist/validators/index.d.mts +48 -0
  86. package/dist/validators/index.d.ts +48 -0
  87. package/dist/validators/index.js +144 -0
  88. package/dist/validators/index.js.map +1 -0
  89. package/dist/validators/index.mjs +141 -0
  90. package/dist/validators/index.mjs.map +1 -0
  91. package/package.json +77 -2
  92. package/src/cookies/index.ts +1 -0
  93. package/src/core/index.ts +1 -0
  94. package/src/core/router.ts +26 -15
  95. package/src/core/server.ts +64 -14
  96. package/src/cors/index.ts +2 -0
  97. package/src/decorators/index.ts +2 -0
  98. package/src/http-error/index.ts +1 -0
  99. package/src/infra/index.ts +3 -0
  100. package/src/plugins/SwaggerPlugin.ts +45 -0
  101. package/src/plugins/index.ts +1 -0
  102. package/src/rate-limit/index.ts +2 -0
  103. package/src/router/index.ts +1 -0
  104. package/src/swagger/constants.ts +8 -0
  105. package/src/swagger/decorators.ts +35 -0
  106. package/src/swagger/index.ts +10 -0
  107. package/src/swagger/openapi-builder.ts +199 -0
  108. package/src/swagger/swagger-ui-html.ts +24 -0
  109. package/src/swagger/swagger-ui-modern.html +894 -0
  110. package/src/swagger/swagger-ui-template.ts +5 -0
  111. package/src/types/index.ts +7 -0
  112. package/src/types/swagger.type.ts +36 -0
  113. package/src/validators/index.ts +4 -0
package/dist/index.mjs CHANGED
@@ -185,31 +185,37 @@ var Router = class {
185
185
  return path;
186
186
  }
187
187
  getAllRoutes() {
188
+ return this.getRouteDocuments().map(({ method, path }) => ({ method, path }));
189
+ }
190
+ /**
191
+ * Todas as rotas com metadados (ex.: OpenAPI) associados ao handler.
192
+ */
193
+ getRouteDocuments() {
188
194
  const routes = [];
189
- for (const [key] of this.staticRoutes) {
195
+ for (const [key, stored] of this.staticRoutes) {
190
196
  const [method, path] = key.split(":", 2);
191
- routes.push({ method, path });
197
+ routes.push({ method, path, meta: stored.meta });
192
198
  }
193
- this.collectRoutes(this.root, "", routes);
199
+ this.collectRoutesWithMeta(this.root, "", routes);
194
200
  return routes;
195
201
  }
196
- collectRoutes(node, prefix, routes) {
197
- for (const [method] of node.handlers) {
198
- routes.push({ method, path: prefix || "/" });
202
+ collectRoutesWithMeta(node, prefix, routes) {
203
+ for (const [method, stored] of node.handlers) {
204
+ routes.push({ method, path: prefix || "/", meta: stored.meta });
199
205
  }
200
206
  for (const [seg, child] of node.children) {
201
- this.collectRoutes(child, `${prefix}/${seg}`, routes);
207
+ this.collectRoutesWithMeta(child, `${prefix}/${seg}`, routes);
202
208
  }
203
209
  if (node.paramChild) {
204
- this.collectRoutes(
210
+ this.collectRoutesWithMeta(
205
211
  node.paramChild,
206
212
  `${prefix}/:${node.paramChild.paramName}`,
207
213
  routes
208
214
  );
209
215
  }
210
216
  if (node.wildcardHandler) {
211
- for (const [method] of node.wildcardHandler) {
212
- routes.push({ method, path: `${prefix}/*` });
217
+ for (const [method, stored] of node.wildcardHandler) {
218
+ routes.push({ method, path: `${prefix}/*`, meta: stored.meta });
213
219
  }
214
220
  }
215
221
  }
@@ -640,7 +646,174 @@ function clearCookieHeader(name, options = {}) {
640
646
  });
641
647
  }
642
648
 
649
+ // src/swagger/constants.ts
650
+ var API_TAGS_KEY = "azura:api-tags";
651
+ var METHOD_TAGS_KEY = "azura:swagger-method-tags";
652
+ var API_OPERATION_KEY = "azura:swagger-operation";
653
+
654
+ // src/swagger/openapi-builder.ts
655
+ function resolveInfo(options) {
656
+ return options.info ?? {
657
+ title: "AzuraJS API",
658
+ version: "1.0.0",
659
+ description: "Generated OpenAPI specification"
660
+ };
661
+ }
662
+ function toOpenApiPath(path) {
663
+ if (path === "/") return "/";
664
+ const mapped = path.split("/").map((seg) => {
665
+ if (seg === "*") return "{wildcard}";
666
+ return seg.startsWith(":") ? `{${seg.slice(1)}}` : seg;
667
+ }).join("/").replace(/\/{2,}/g, "/");
668
+ if (mapped.endsWith("/*")) {
669
+ return mapped.slice(0, -2) + "/{wildcard}";
670
+ }
671
+ return mapped;
672
+ }
673
+ function pathParametersFromTemplate(path) {
674
+ const re = /\{([^}]+)\}/g;
675
+ const out = [];
676
+ let m;
677
+ const seen = /* @__PURE__ */ new Set();
678
+ while ((m = re.exec(path)) !== null) {
679
+ const name = m[1];
680
+ if (seen.has(name)) continue;
681
+ seen.add(name);
682
+ out.push({
683
+ name,
684
+ in: "path",
685
+ required: true,
686
+ schema: { type: "string" }
687
+ });
688
+ }
689
+ return out;
690
+ }
691
+ function defaultResponses(meta) {
692
+ if (meta?.responses && Object.keys(meta.responses).length > 0) {
693
+ const out = {};
694
+ for (const [code, r] of Object.entries(meta.responses)) {
695
+ out[String(code)] = {
696
+ description: r.description,
697
+ ...r.schema ? { content: { "application/json": { schema: r.schema } } } : {}
698
+ };
699
+ }
700
+ return out;
701
+ }
702
+ return {
703
+ "200": {
704
+ description: "Successful response",
705
+ content: {
706
+ "application/json": {
707
+ schema: { type: "object", additionalProperties: true }
708
+ }
709
+ }
710
+ }
711
+ };
712
+ }
713
+ function mergeParameters(meta, pathParams) {
714
+ const fromMeta = meta?.parameters ?? [];
715
+ const names = new Set(pathParams.map((p) => p.name));
716
+ const rest = fromMeta.filter((p) => !(p.in === "path" && names.has(p.name)));
717
+ const mapped = fromMeta.filter((p) => p.in === "path").map((p) => ({
718
+ name: p.name,
719
+ in: "path",
720
+ required: p.required ?? true,
721
+ description: p.description,
722
+ schema: p.schema ?? { type: p.type ?? "string" }
723
+ }));
724
+ const pathMerged = [...pathParams];
725
+ for (const m of mapped) {
726
+ if (!pathMerged.find((x) => x.name === m.name)) pathMerged.push(m);
727
+ }
728
+ return [...pathMerged, ...rest.map((p) => mapParameter(p))];
729
+ }
730
+ function mapParameter(p) {
731
+ const base = {
732
+ name: p.name,
733
+ in: p.in,
734
+ required: p.required ?? (p.in === "path" ? true : false),
735
+ description: p.description
736
+ };
737
+ if (p.in === "body") {
738
+ return {
739
+ ...base,
740
+ content: {
741
+ "application/json": {
742
+ schema: p.schema ?? { type: "object" }
743
+ }
744
+ }
745
+ };
746
+ }
747
+ return {
748
+ ...base,
749
+ schema: p.schema ?? { type: p.type ?? "string" }
750
+ };
751
+ }
752
+ function operationIdFor(method, oPath) {
753
+ const slug = oPath.replace(/[/{}\-*]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
754
+ return `${method.toLowerCase()}_${slug || "root"}`;
755
+ }
756
+ function operationForMethod(method, oPath, meta) {
757
+ const op = {
758
+ operationId: operationIdFor(method, oPath),
759
+ summary: meta?.summary ?? `${method} ${oPath}`,
760
+ ...meta?.description ? { description: meta.description } : {},
761
+ ...meta?.deprecated ? { deprecated: true } : {},
762
+ ...meta?.tags?.length ? { tags: meta.tags } : {},
763
+ ...meta?.security?.length ? { security: meta.security } : {}
764
+ };
765
+ if (["POST", "PUT", "PATCH"].includes(method) && !meta?.parameters?.some((x) => x.in === "body")) {
766
+ op.requestBody = {
767
+ content: {
768
+ "application/json": {
769
+ schema: { type: "object", additionalProperties: true }
770
+ }
771
+ },
772
+ required: false
773
+ };
774
+ }
775
+ op.responses = defaultResponses(meta);
776
+ return op;
777
+ }
778
+ function buildOpenApiDocument(routes, options) {
779
+ const info = resolveInfo(options);
780
+ const prefix = options.pathPrefix ?? "";
781
+ const paths = {};
782
+ const seen = /* @__PURE__ */ new Set();
783
+ for (const r of routes) {
784
+ const full = prefix + r.path;
785
+ const oPath = toOpenApiPath(full);
786
+ const key = `${r.method}:${oPath}`;
787
+ if (seen.has(key)) continue;
788
+ seen.add(key);
789
+ const pathParams = pathParametersFromTemplate(oPath);
790
+ const op = operationForMethod(r.method, oPath, r.meta);
791
+ const parameters = mergeParameters(r.meta, pathParams);
792
+ if (parameters.length) op.parameters = parameters;
793
+ const methodLc = r.method.toLowerCase();
794
+ if (!paths[oPath]) paths[oPath] = {};
795
+ paths[oPath][methodLc] = op;
796
+ }
797
+ return {
798
+ openapi: "3.0.3",
799
+ info: {
800
+ title: info.title,
801
+ version: info.version,
802
+ ...info.description ? { description: info.description } : {},
803
+ ...info.contact ? { contact: info.contact } : {},
804
+ ...info.license ? { license: info.license } : {}
805
+ },
806
+ ...options.servers?.length ? { servers: options.servers } : {},
807
+ paths
808
+ };
809
+ }
810
+
643
811
  // src/core/server.ts
812
+ function isRouteDocumentMeta(o) {
813
+ if (!o || typeof o !== "object" || Array.isArray(o)) return false;
814
+ const x = o;
815
+ return "summary" in x || "description" in x || "tags" in x || "deprecated" in x || "parameters" in x || "responses" in x || "security" in x || "produces" in x || "consumes" in x;
816
+ }
644
817
  var CONTROLLER_META_KEY = "azura:controller";
645
818
  var ROUTES_META_KEY = "azura:routes";
646
819
  var PARAMS_META_KEY = "azura:params";
@@ -729,9 +902,19 @@ var AzuraServer = class {
729
902
  return this;
730
903
  }
731
904
  route(method, path, handlers) {
732
- const routeHandler = handlers[handlers.length - 1];
733
- const middlewares = handlers.slice(0, -1);
734
- this.router.add(method, path, routeHandler, middlewares);
905
+ let meta;
906
+ let list = handlers;
907
+ const last = list[list.length - 1];
908
+ if (list.length >= 2 && isRouteDocumentMeta(last)) {
909
+ meta = last;
910
+ list = list.slice(0, -1);
911
+ }
912
+ if (list.length === 0) {
913
+ throw new Error(`Route ${method} ${path} requires a handler function`);
914
+ }
915
+ const routeHandler = list[list.length - 1];
916
+ const middlewares = list.slice(0, -1);
917
+ this.router.add(method, path, routeHandler, middlewares, meta);
735
918
  return this;
736
919
  }
737
920
  register(...controllers) {
@@ -744,6 +927,7 @@ var AzuraServer = class {
744
927
  const instance = new Controller2();
745
928
  const prefix = Reflect.getMetadata?.(CONTROLLER_META_KEY, Controller2) ?? "";
746
929
  const controllerMiddlewares = Reflect.getMetadata?.("azura:middlewares", Controller2) ?? [];
930
+ const controllerTags = Reflect.getMetadata?.(API_TAGS_KEY, Controller2) ?? [];
747
931
  const prototype = Controller2.prototype;
748
932
  const propertyNames = Object.getOwnPropertyNames(prototype).filter(
749
933
  (p) => p !== "constructor"
@@ -759,6 +943,14 @@ var AzuraServer = class {
759
943
  const paramsMeta = Reflect.getMetadata?.(PARAMS_META_KEY, prototype, propertyKey) ?? [];
760
944
  const routeMiddlewares = Reflect.getMetadata?.("azura:middlewares", prototype, propertyKey) ?? [];
761
945
  const allMiddlewares = [...controllerMiddlewares, ...routeMiddlewares];
946
+ const methodTags = Reflect.getMetadata?.(METHOD_TAGS_KEY, prototype, propertyKey) ?? [];
947
+ const opExtra = Reflect.getMetadata?.(API_OPERATION_KEY, prototype, propertyKey) ?? {};
948
+ const tagList = [...controllerTags, ...methodTags, ...routeMeta.meta?.tags ?? []];
949
+ const mergedMeta = (() => {
950
+ const m = { ...routeMeta.meta, ...opExtra };
951
+ if (tagList.length) m.tags = tagList;
952
+ return Object.keys(m).length ? m : void 0;
953
+ })();
762
954
  const handler = async (req, res) => {
763
955
  const args = this.resolveParams(paramsMeta, req, res);
764
956
  const result = await instance[propertyKey](...args);
@@ -770,7 +962,7 @@ var AzuraServer = class {
770
962
  }
771
963
  }
772
964
  };
773
- this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, routeMeta.meta);
965
+ this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, mergedMeta);
774
966
  }
775
967
  }
776
968
  resolveParams(paramsMeta, req, res) {
@@ -1124,6 +1316,14 @@ var AzuraServer = class {
1124
1316
  getRoutes() {
1125
1317
  return this.router.getAllRoutes();
1126
1318
  }
1319
+ /** Rotas com metadados OpenAPI (para Swagger ou export manual). */
1320
+ getRouteDocuments() {
1321
+ return this.router.getRouteDocuments();
1322
+ }
1323
+ /** Gera documento OpenAPI 3.0 a partir das rotas registadas. */
1324
+ getOpenApiDocument(options = {}) {
1325
+ return buildOpenApiDocument(this.getRouteDocuments(), options);
1326
+ }
1127
1327
  };
1128
1328
 
1129
1329
  // src/decorators/Route.ts
@@ -1223,6 +1423,24 @@ function Meta(key, value) {
1223
1423
  };
1224
1424
  }
1225
1425
 
1426
+ // src/swagger/decorators.ts
1427
+ function ApiTags(...tags) {
1428
+ return function(target, propertyKey, _descriptor) {
1429
+ if (propertyKey === void 0) {
1430
+ Reflect.defineMetadata(API_TAGS_KEY, tags, target);
1431
+ } else {
1432
+ Reflect.defineMetadata(METHOD_TAGS_KEY, tags, target, String(propertyKey));
1433
+ }
1434
+ };
1435
+ }
1436
+ function ApiOperation(meta) {
1437
+ return function(target, propertyKey, _descriptor) {
1438
+ const key = String(propertyKey);
1439
+ const cur = Reflect.getMetadata?.(API_OPERATION_KEY, target, key) ?? {};
1440
+ Reflect.defineMetadata(API_OPERATION_KEY, { ...cur, ...meta }, target, key);
1441
+ };
1442
+ }
1443
+
1226
1444
  // src/plugins/CORSPlugin.ts
1227
1445
  var DEFAULT_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
1228
1446
  var DEFAULT_HEADERS = ["Content-Type", "Authorization", "Accept", "X-Requested-With"];
@@ -2256,6 +2474,949 @@ function bufferIndexOf(buf, search, offset) {
2256
2474
  return -1;
2257
2475
  }
2258
2476
 
2477
+ // src/swagger/swagger-ui-template.ts
2478
+ var SWAGGER_UI_MODERN_HTML = `<!DOCTYPE html>\r
2479
+ <html lang="en">\r
2480
+ \r
2481
+ <head>\r
2482
+ <meta charset="UTF-8" />\r
2483
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />\r
2484
+ <title>__PAGE_TITLE__</title>\r
2485
+ <link rel="preconnect" href="https://fonts.googleapis.com" />\r
2486
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />\r
2487
+ <link\r
2488
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"\r
2489
+ rel="stylesheet" />\r
2490
+ <style>\r
2491
+ :root {\r
2492
+ --bg-body: #09090b;\r
2493
+ --bg-sidebar: #18181b;\r
2494
+ --bg-card: #18181b;\r
2495
+ --bg-card-hover: #27272a;\r
2496
+ --bg-input: #09090b;\r
2497
+ --border-color: #27272a;\r
2498
+ --primary-color: #6366f1;\r
2499
+ --primary-hover: #4f46e5;\r
2500
+ --text-primary: #f4f4f5;\r
2501
+ --text-secondary: #a1a1aa;\r
2502
+ --text-muted: #52525b;\r
2503
+ --success: #22c55e;\r
2504
+ --warning: #eab308;\r
2505
+ --danger: #ef4444;\r
2506
+ --info: #3b82f6;\r
2507
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;\r
2508
+ --font-mono: 'JetBrains Mono', monospace;\r
2509
+ }\r
2510
+ \r
2511
+ * {\r
2512
+ margin: 0;\r
2513
+ padding: 0;\r
2514
+ box-sizing: border-box\r
2515
+ }\r
2516
+ \r
2517
+ body {\r
2518
+ font-family: var(--font-sans);\r
2519
+ background-color: var(--bg-body);\r
2520
+ color: var(--text-primary);\r
2521
+ line-height: 1.5;\r
2522
+ height: 100vh;\r
2523
+ overflow: hidden\r
2524
+ }\r
2525
+ \r
2526
+ ::-webkit-scrollbar {\r
2527
+ width: 6px;\r
2528
+ height: 6px\r
2529
+ }\r
2530
+ \r
2531
+ ::-webkit-scrollbar-track {\r
2532
+ background: transparent\r
2533
+ }\r
2534
+ \r
2535
+ ::-webkit-scrollbar-thumb {\r
2536
+ background: var(--border-color);\r
2537
+ border-radius: 3px\r
2538
+ }\r
2539
+ \r
2540
+ ::-webkit-scrollbar-thumb:hover {\r
2541
+ background: var(--text-muted)\r
2542
+ }\r
2543
+ \r
2544
+ .layout {\r
2545
+ display: flex;\r
2546
+ height: 100%\r
2547
+ }\r
2548
+ \r
2549
+ .sidebar {\r
2550
+ width: 300px;\r
2551
+ background: var(--bg-sidebar);\r
2552
+ border-right: 1px solid var(--border-color);\r
2553
+ display: flex;\r
2554
+ flex-direction: column;\r
2555
+ flex-shrink: 0\r
2556
+ }\r
2557
+ \r
2558
+ .sidebar-header {\r
2559
+ padding: 20px;\r
2560
+ border-bottom: 1px solid var(--border-color);\r
2561
+ background: var(--bg-sidebar)\r
2562
+ }\r
2563
+ \r
2564
+ .api-title {\r
2565
+ font-size: 1.25rem;\r
2566
+ font-weight: 700;\r
2567
+ color: var(--text-primary);\r
2568
+ margin-bottom: 4px\r
2569
+ }\r
2570
+ \r
2571
+ .api-version {\r
2572
+ font-size: 0.75rem;\r
2573
+ color: var(--primary-color);\r
2574
+ background: rgba(99, 102, 241, 0.1);\r
2575
+ padding: 2px 8px;\r
2576
+ border-radius: 12px;\r
2577
+ font-family: var(--font-mono)\r
2578
+ }\r
2579
+ \r
2580
+ .search-box {\r
2581
+ padding: 16px\r
2582
+ }\r
2583
+ \r
2584
+ .search-input {\r
2585
+ width: 100%;\r
2586
+ background: var(--bg-input);\r
2587
+ border: 1px solid var(--border-color);\r
2588
+ padding: 8px 12px;\r
2589
+ border-radius: 6px;\r
2590
+ color: var(--text-primary);\r
2591
+ font-size: 0.875rem;\r
2592
+ outline: none;\r
2593
+ transition: border-color 0.2s\r
2594
+ }\r
2595
+ \r
2596
+ .search-input:focus {\r
2597
+ border-color: var(--primary-color)\r
2598
+ }\r
2599
+ \r
2600
+ .nav-list {\r
2601
+ flex: 1;\r
2602
+ overflow-y: auto;\r
2603
+ padding: 0 16px 20px\r
2604
+ }\r
2605
+ \r
2606
+ .nav-group-title {\r
2607
+ font-size: 0.75rem;\r
2608
+ text-transform: uppercase;\r
2609
+ color: var(--text-muted);\r
2610
+ font-weight: 600;\r
2611
+ margin: 24px 0 8px;\r
2612
+ letter-spacing: 0.05em\r
2613
+ }\r
2614
+ \r
2615
+ .nav-item {\r
2616
+ display: flex;\r
2617
+ align-items: center;\r
2618
+ gap: 8px;\r
2619
+ padding: 8px;\r
2620
+ border-radius: 6px;\r
2621
+ cursor: pointer;\r
2622
+ transition: background 0.2s;\r
2623
+ margin-bottom: 2px\r
2624
+ }\r
2625
+ \r
2626
+ .nav-item:hover {\r
2627
+ background: var(--bg-card-hover)\r
2628
+ }\r
2629
+ \r
2630
+ .nav-item.active {\r
2631
+ background: rgba(99, 102, 241, 0.1)\r
2632
+ }\r
2633
+ \r
2634
+ .method-tag {\r
2635
+ font-family: var(--font-mono);\r
2636
+ font-size: 0.65rem;\r
2637
+ font-weight: 700;\r
2638
+ padding: 2px 6px;\r
2639
+ border-radius: 4px;\r
2640
+ width: 45px;\r
2641
+ text-align: center\r
2642
+ }\r
2643
+ \r
2644
+ .get {\r
2645
+ color: var(--success);\r
2646
+ background: rgba(34, 197, 94, 0.1)\r
2647
+ }\r
2648
+ \r
2649
+ .post {\r
2650
+ color: var(--primary-color);\r
2651
+ background: rgba(99, 102, 241, 0.1)\r
2652
+ }\r
2653
+ \r
2654
+ .put {\r
2655
+ color: var(--warning);\r
2656
+ background: rgba(234, 179, 8, 0.1)\r
2657
+ }\r
2658
+ \r
2659
+ .delete {\r
2660
+ color: var(--danger);\r
2661
+ background: rgba(239, 68, 68, 0.1)\r
2662
+ }\r
2663
+ \r
2664
+ .patch {\r
2665
+ color: var(--info);\r
2666
+ background: rgba(59, 130, 246, 0.1)\r
2667
+ }\r
2668
+ \r
2669
+ .nav-path {\r
2670
+ font-size: 0.8rem;\r
2671
+ color: var(--text-secondary);\r
2672
+ white-space: nowrap;\r
2673
+ overflow: hidden;\r
2674
+ text-overflow: ellipsis;\r
2675
+ font-family: var(--font-mono)\r
2676
+ }\r
2677
+ \r
2678
+ .main-content {\r
2679
+ flex: 1;\r
2680
+ overflow-y: auto;\r
2681
+ padding: 40px;\r
2682
+ scroll-behavior: smooth\r
2683
+ }\r
2684
+ \r
2685
+ .content-width {\r
2686
+ max-width: 1000px;\r
2687
+ margin: 0 auto\r
2688
+ }\r
2689
+ \r
2690
+ .info-section {\r
2691
+ margin-bottom: 40px\r
2692
+ }\r
2693
+ \r
2694
+ .info-title {\r
2695
+ font-size: 2.5rem;\r
2696
+ font-weight: 800;\r
2697
+ margin-bottom: 16px;\r
2698
+ letter-spacing: -0.02em\r
2699
+ }\r
2700
+ \r
2701
+ .info-desc {\r
2702
+ color: var(--text-secondary);\r
2703
+ font-size: 1.1rem;\r
2704
+ max-width: 700px\r
2705
+ }\r
2706
+ \r
2707
+ .server-section {\r
2708
+ background: var(--bg-card);\r
2709
+ border: 1px solid var(--border-color);\r
2710
+ padding: 20px;\r
2711
+ border-radius: 8px;\r
2712
+ margin-bottom: 40px\r
2713
+ }\r
2714
+ \r
2715
+ .server-label {\r
2716
+ display: block;\r
2717
+ font-size: 0.875rem;\r
2718
+ color: var(--text-muted);\r
2719
+ margin-bottom: 8px\r
2720
+ }\r
2721
+ \r
2722
+ .server-select {\r
2723
+ width: 100%;\r
2724
+ background: var(--bg-input);\r
2725
+ border: 1px solid var(--border-color);\r
2726
+ color: var(--text-primary);\r
2727
+ padding: 10px;\r
2728
+ border-radius: 6px;\r
2729
+ font-family: var(--font-mono);\r
2730
+ font-size: 0.9rem\r
2731
+ }\r
2732
+ \r
2733
+ .endpoint-card {\r
2734
+ background: var(--bg-card);\r
2735
+ border: 1px solid var(--border-color);\r
2736
+ border-radius: 8px;\r
2737
+ margin-bottom: 24px;\r
2738
+ overflow: hidden;\r
2739
+ transition: border-color 0.2s\r
2740
+ }\r
2741
+ \r
2742
+ .endpoint-card:hover {\r
2743
+ border-color: var(--text-muted)\r
2744
+ }\r
2745
+ \r
2746
+ .card-header {\r
2747
+ padding: 16px 24px;\r
2748
+ display: flex;\r
2749
+ align-items: center;\r
2750
+ gap: 16px;\r
2751
+ cursor: pointer;\r
2752
+ background: rgba(255, 255, 255, 0.02);\r
2753
+ user-select: none\r
2754
+ }\r
2755
+ \r
2756
+ .card-summary {\r
2757
+ flex: 1;\r
2758
+ font-weight: 500;\r
2759
+ font-size: 1rem\r
2760
+ }\r
2761
+ \r
2762
+ .card-path {\r
2763
+ font-family: var(--font-mono);\r
2764
+ font-size: 0.85rem;\r
2765
+ color: var(--text-muted)\r
2766
+ }\r
2767
+ \r
2768
+ .chevron {\r
2769
+ transition: transform 0.2s;\r
2770
+ color: var(--text-muted)\r
2771
+ }\r
2772
+ \r
2773
+ .card-header[aria-expanded="true"] .chevron {\r
2774
+ transform: rotate(180deg)\r
2775
+ }\r
2776
+ \r
2777
+ .card-body {\r
2778
+ border-top: 1px solid var(--border-color);\r
2779
+ padding: 24px;\r
2780
+ display: none\r
2781
+ }\r
2782
+ \r
2783
+ .card-header[aria-expanded="true"]+.card-body {\r
2784
+ display: block\r
2785
+ }\r
2786
+ \r
2787
+ .section-header {\r
2788
+ font-size: 0.75rem;\r
2789
+ text-transform: uppercase;\r
2790
+ color: var(--text-muted);\r
2791
+ font-weight: 700;\r
2792
+ margin: 24px 0 12px;\r
2793
+ letter-spacing: 0.05em\r
2794
+ }\r
2795
+ \r
2796
+ .section-header:first-child {\r
2797
+ margin-top: 0\r
2798
+ }\r
2799
+ \r
2800
+ .params-table {\r
2801
+ width: 100%;\r
2802
+ border-collapse: collapse;\r
2803
+ font-size: 0.9rem;\r
2804
+ margin-bottom: 20px\r
2805
+ }\r
2806
+ \r
2807
+ .params-table th {\r
2808
+ text-align: left;\r
2809
+ color: var(--text-muted);\r
2810
+ padding: 8px 12px;\r
2811
+ font-weight: 500;\r
2812
+ border-bottom: 1px solid var(--border-color);\r
2813
+ font-size: 0.8rem\r
2814
+ }\r
2815
+ \r
2816
+ .params-table td {\r
2817
+ padding: 12px;\r
2818
+ border-bottom: 1px solid var(--border-color);\r
2819
+ color: var(--text-secondary);\r
2820
+ vertical-align: top\r
2821
+ }\r
2822
+ \r
2823
+ .params-table tr:last-child td {\r
2824
+ border-bottom: none\r
2825
+ }\r
2826
+ \r
2827
+ .param-name {\r
2828
+ font-family: var(--font-mono);\r
2829
+ color: var(--text-primary);\r
2830
+ font-weight: 600\r
2831
+ }\r
2832
+ \r
2833
+ .param-req {\r
2834
+ color: var(--danger);\r
2835
+ font-size: 0.7rem;\r
2836
+ margin-left: 4px\r
2837
+ }\r
2838
+ \r
2839
+ .param-meta {\r
2840
+ font-size: 0.8rem;\r
2841
+ color: var(--text-muted);\r
2842
+ margin-top: 4px;\r
2843
+ font-family: var(--font-mono)\r
2844
+ }\r
2845
+ \r
2846
+ .code-block {\r
2847
+ background: #000;\r
2848
+ border: 1px solid var(--border-color);\r
2849
+ border-radius: 6px;\r
2850
+ padding: 16px;\r
2851
+ font-family: var(--font-mono);\r
2852
+ font-size: 0.85rem;\r
2853
+ overflow-x: auto;\r
2854
+ color: #d4d4d8\r
2855
+ }\r
2856
+ \r
2857
+ .try-it-box {\r
2858
+ background: rgba(99, 102, 241, 0.05);\r
2859
+ border: 1px solid rgba(99, 102, 241, 0.2);\r
2860
+ border-radius: 8px;\r
2861
+ padding: 20px;\r
2862
+ margin-top: 32px\r
2863
+ }\r
2864
+ \r
2865
+ .form-grid {\r
2866
+ display: grid;\r
2867
+ grid-template-columns: 1fr;\r
2868
+ gap: 16px;\r
2869
+ margin-bottom: 20px\r
2870
+ }\r
2871
+ \r
2872
+ .form-group label {\r
2873
+ display: block;\r
2874
+ font-size: 0.8rem;\r
2875
+ font-weight: 600;\r
2876
+ margin-bottom: 6px;\r
2877
+ color: var(--text-secondary)\r
2878
+ }\r
2879
+ \r
2880
+ .form-group input {\r
2881
+ width: 100%;\r
2882
+ background: var(--bg-body);\r
2883
+ border: 1px solid var(--border-color);\r
2884
+ padding: 8px 12px;\r
2885
+ border-radius: 4px;\r
2886
+ color: var(--text-primary);\r
2887
+ font-family: var(--font-mono);\r
2888
+ font-size: 0.85rem\r
2889
+ }\r
2890
+ \r
2891
+ .form-group input:focus {\r
2892
+ border-color: var(--primary-color);\r
2893
+ outline: none\r
2894
+ }\r
2895
+ \r
2896
+ .body-editor {\r
2897
+ width: 100%;\r
2898
+ height: 200px;\r
2899
+ background: var(--bg-body);\r
2900
+ border: 1px solid var(--border-color);\r
2901
+ border-radius: 6px;\r
2902
+ padding: 12px;\r
2903
+ color: var(--text-primary);\r
2904
+ font-family: var(--font-mono);\r
2905
+ font-size: 0.85rem;\r
2906
+ resize: vertical;\r
2907
+ margin-bottom: 16px\r
2908
+ }\r
2909
+ \r
2910
+ .action-btn {\r
2911
+ background: var(--primary-color);\r
2912
+ color: white;\r
2913
+ border: none;\r
2914
+ padding: 10px 20px;\r
2915
+ border-radius: 6px;\r
2916
+ font-weight: 600;\r
2917
+ cursor: pointer;\r
2918
+ font-size: 0.9rem;\r
2919
+ transition: background 0.2s;\r
2920
+ display: inline-flex;\r
2921
+ align-items: center;\r
2922
+ gap: 8px\r
2923
+ }\r
2924
+ \r
2925
+ .action-btn:hover {\r
2926
+ background: var(--primary-hover)\r
2927
+ }\r
2928
+ \r
2929
+ .action-btn:disabled {\r
2930
+ opacity: 0.6;\r
2931
+ cursor: not-allowed\r
2932
+ }\r
2933
+ \r
2934
+ .response-area {\r
2935
+ margin-top: 20px;\r
2936
+ border-top: 1px solid var(--border-color);\r
2937
+ padding-top: 20px\r
2938
+ }\r
2939
+ \r
2940
+ .status-badge {\r
2941
+ display: inline-block;\r
2942
+ padding: 4px 8px;\r
2943
+ border-radius: 4px;\r
2944
+ font-size: 0.8rem;\r
2945
+ font-weight: 700;\r
2946
+ margin-bottom: 10px;\r
2947
+ font-family: var(--font-mono)\r
2948
+ }\r
2949
+ \r
2950
+ .status-2xx {\r
2951
+ background: rgba(34, 197, 94, 0.2);\r
2952
+ color: var(--success)\r
2953
+ }\r
2954
+ \r
2955
+ .status-4xx,\r
2956
+ .status-5xx {\r
2957
+ background: rgba(239, 68, 68, 0.2);\r
2958
+ color: var(--danger)\r
2959
+ }\r
2960
+ \r
2961
+ .loader {\r
2962
+ width: 16px;\r
2963
+ height: 16px;\r
2964
+ border: 2px solid #ffffff;\r
2965
+ border-bottom-color: transparent;\r
2966
+ border-radius: 50%;\r
2967
+ display: inline-block;\r
2968
+ animation: rotation 1s linear infinite\r
2969
+ }\r
2970
+ \r
2971
+ @keyframes rotation {\r
2972
+ 0% {\r
2973
+ transform: rotate(0deg)\r
2974
+ }\r
2975
+ \r
2976
+ 100% {\r
2977
+ transform: rotate(360deg)\r
2978
+ }\r
2979
+ }\r
2980
+ \r
2981
+ @media (max-width:768px) {\r
2982
+ .layout {\r
2983
+ flex-direction: column;\r
2984
+ overflow: auto\r
2985
+ }\r
2986
+ \r
2987
+ .sidebar {\r
2988
+ width: 100%;\r
2989
+ height: auto;\r
2990
+ border-right: none;\r
2991
+ border-bottom: 1px solid var(--border-color)\r
2992
+ }\r
2993
+ \r
2994
+ .main-content {\r
2995
+ padding: 20px;\r
2996
+ overflow: visible\r
2997
+ }\r
2998
+ \r
2999
+ .nav-list {\r
3000
+ max-height: 300px\r
3001
+ }\r
3002
+ }\r
3003
+ </style>\r
3004
+ </head>\r
3005
+ \r
3006
+ <body>\r
3007
+ <div class="layout">\r
3008
+ <aside class="sidebar">\r
3009
+ <div class="sidebar-header">\r
3010
+ <div class="api-title" id="apiTitle">API Docs</div>\r
3011
+ <span class="api-version" id="apiVersion">v1.0.0</span>\r
3012
+ </div>\r
3013
+ <div class="search-box">\r
3014
+ <input type="text" class="search-input" id="searchNav" placeholder="Filter endpoints...">\r
3015
+ </div>\r
3016
+ <div class="nav-list" id="navList"></div>\r
3017
+ </aside>\r
3018
+ <main class="main-content">\r
3019
+ <div class="content-width">\r
3020
+ <div class="info-section">\r
3021
+ <h1 class="info-title" id="docTitle">API Reference</h1>\r
3022
+ <p class="info-desc" id="docDesc">Loading documentation...</p>\r
3023
+ </div>\r
3024
+ <div class="server-section">\r
3025
+ <label class="server-label">Base URL</label>\r
3026
+ <select class="server-select" id="serverUrl"></select>\r
3027
+ </div>\r
3028
+ <div id="endpointsContainer"></div>\r
3029
+ </div>\r
3030
+ </main>\r
3031
+ </div>\r
3032
+ \r
3033
+ <script>\r
3034
+ const state = { spec: null, server: '' };\r
3035
+ function sanitizeId(str) { return String(str).replace(/[^a-zA-Z0-9_-]/g, '_'); }\r
3036
+ async function init() {\r
3037
+ try {\r
3038
+ const res = await fetch("___AZURA_SPEC_URL_PLACEHOLDER___");\r
3039
+ if (!res.ok) throw new Error('Spec not found');\r
3040
+ state.spec = await res.json();\r
3041
+ renderHeader();\r
3042
+ renderServers();\r
3043
+ renderNavigation();\r
3044
+ renderEndpoints();\r
3045
+ setupSearch();\r
3046
+ attachGlobalListeners();\r
3047
+ } catch (err) {\r
3048
+ const descEl = document.getElementById('docDesc');\r
3049
+ descEl.textContent = 'Failed to load API definition. Check that the OpenAPI JSON endpoint is configured and reachable.';\r
3050
+ descEl.style.color = 'var(--danger)';\r
3051
+ }\r
3052
+ }\r
3053
+ \r
3054
+ function renderHeader() {\r
3055
+ const info = state.spec.info || {};\r
3056
+ document.title = \`\${info.title || 'API'} - Docs\`;\r
3057
+ document.getElementById('apiTitle').textContent = info.title || 'API';\r
3058
+ document.getElementById('apiVersion').textContent = info.version ? \`v\${info.version}\` : '';\r
3059
+ document.getElementById('docTitle').textContent = info.title || 'API Reference';\r
3060
+ document.getElementById('docDesc').textContent = info.description || 'No description provided.';\r
3061
+ }\r
3062
+ \r
3063
+ function renderServers() {\r
3064
+ const select = document.getElementById('serverUrl');\r
3065
+ const servers = state.spec.servers && state.spec.servers.length ? state.spec.servers : [{ url: window.location.origin, description: 'Current Origin' }];\r
3066
+ select.innerHTML = servers.map(s => \`<option value="\${s.url}">\${s.url}\${s.description ? \` (\${s.description})\` : ''}</option>\`).join('');\r
3067
+ state.server = servers[0].url;\r
3068
+ select.addEventListener('change', (e) => state.server = e.target.value);\r
3069
+ }\r
3070
+ \r
3071
+ function renderNavigation() {\r
3072
+ const nav = document.getElementById('navList');\r
3073
+ const tags = {};\r
3074
+ Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {\r
3075
+ Object.entries(methods).forEach(([method, op]) => {\r
3076
+ const tag = (op.tags && op.tags[0]) ? op.tags[0] : 'General';\r
3077
+ if (!tags[tag]) tags[tag] = [];\r
3078
+ tags[tag].push({ path, method, op });\r
3079
+ });\r
3080
+ });\r
3081
+ \r
3082
+ let html = '';\r
3083
+ Object.entries(tags).forEach(([tag, items]) => {\r
3084
+ html += \`<div class="nav-group-title">\${tag}</div>\`;\r
3085
+ items.forEach(item => {\r
3086
+ const label = item.op.summary || item.path;\r
3087
+ const filter = \`\${item.method} \${item.path} \${label}\`.toLowerCase();\r
3088
+ const targetId = sanitizeId(\`\${item.method}-\${item.path}\`);\r
3089
+ html += \`<div class="nav-item" data-filter="\${escapeHtmlAttr(filter)}" data-target-id="\${escapeHtmlAttr(targetId)}">\r
3090
+ <span class="method-tag \${item.method.toLowerCase()}">\${item.method.toUpperCase()}</span>\r
3091
+ <span class="nav-path">\${escapeHtmlAttr(label)}</span>\r
3092
+ </div>\`;\r
3093
+ });\r
3094
+ });\r
3095
+ \r
3096
+ nav.innerHTML = html;\r
3097
+ nav.querySelectorAll('.nav-item').forEach(el => {\r
3098
+ el.addEventListener('click', () => {\r
3099
+ const tid = el.dataset.targetId;\r
3100
+ scrollEndpoint(tid);\r
3101
+ document.querySelectorAll('.nav-item').forEach(i => i.classList.toggle('active', i === el));\r
3102
+ });\r
3103
+ });\r
3104
+ }\r
3105
+ \r
3106
+ function renderEndpoints() {\r
3107
+ const container = document.getElementById('endpointsContainer');\r
3108
+ container.innerHTML = '';\r
3109
+ Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {\r
3110
+ Object.entries(methods).forEach(([method, op]) => {\r
3111
+ container.appendChild(createEndpointCard(path, method, op));\r
3112
+ });\r
3113
+ });\r
3114
+ }\r
3115
+ \r
3116
+ function createEndpointCard(path, method, op) {\r
3117
+ const rawId = \`\${method}-\${path}\`;\r
3118
+ const id = sanitizeId(rawId);\r
3119
+ const card = document.createElement('div');\r
3120
+ card.className = 'endpoint-card';\r
3121
+ card.id = id;\r
3122
+ \r
3123
+ const descriptionHtml = op.description ? \`<p style="margin-bottom:20px;color:var(--text-secondary)">\${escapeHtml(op.description)}</p>\` : '';\r
3124
+ const paramsHtml = renderParameters(op.parameters);\r
3125
+ const requestBodyHtml = renderRequestBody(op.requestBody);\r
3126
+ const responsesHtml = renderResponses(op.responses);\r
3127
+ \r
3128
+ const inputsId = \`inputs_\${id}\`;\r
3129
+ const bodyId = \`body_\${id}\`;\r
3130
+ const tryButtonId = \`btn_\${id}\`;\r
3131
+ const inputsHtmlForTry = (op.parameters || []).map(p => {\r
3132
+ const placeholder = p.schema?.type || '';\r
3133
+ const required = p.required ? ' <span style="color:var(--danger)">*</span>' : '';\r
3134
+ return \`<div class="form-group">\r
3135
+ <label>\${escapeHtml(p.name)} <span style="font-weight:400;color:var(--text-muted)">(\${escapeHtml(p.in)})</span>\${required}</label>\r
3136
+ <input type="text" data-name="\${escapeHtmlAttr(p.name)}" data-in="\${escapeHtmlAttr(p.in)}" placeholder="\${escapeHtmlAttr(placeholder)}">\r
3137
+ </div>\`;\r
3138
+ }).join('');\r
3139
+ \r
3140
+ const bodyEditorHtml = op.requestBody ? \`<div class="form-group">\r
3141
+ <label>Request Body (JSON)</label>\r
3142
+ <textarea class="body-editor" id="\${bodyId}">\${getExampleBody(op.requestBody)}</textarea>\r
3143
+ </div>\` : '';\r
3144
+ \r
3145
+ card.innerHTML = \`\r
3146
+ <div class="card-header" aria-expanded="false">\r
3147
+ <span class="method-tag \${method.toLowerCase()}">\${method.toUpperCase()}</span>\r
3148
+ <span class="card-summary">\${escapeHtml(op.summary || path)}</span>\r
3149
+ <span class="card-path">\${escapeHtml(path)}</span>\r
3150
+ <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
3151
+ </div>\r
3152
+ <div class="card-body">\r
3153
+ \${descriptionHtml}\r
3154
+ \${paramsHtml}\r
3155
+ \${requestBodyHtml}\r
3156
+ <div class="try-it-box">\r
3157
+ <div class="section-header">Try It Out</div>\r
3158
+ <div class="form-grid" id="\${inputsId}">\r
3159
+ \${inputsHtmlForTry}\r
3160
+ </div>\r
3161
+ \${bodyEditorHtml}\r
3162
+ <button class="action-btn" id="\${tryButtonId}" data-method="\${escapeHtmlAttr(method)}" data-path="\${escapeHtmlAttr(path)}" data-card-id="\${escapeHtmlAttr(id)}">\r
3163
+ <span>Execute Request</span>\r
3164
+ </button>\r
3165
+ <div class="response-area" style="display:none"></div>\r
3166
+ </div>\r
3167
+ \${responsesHtml}\r
3168
+ </div>\r
3169
+ \`;\r
3170
+ \r
3171
+ return card;\r
3172
+ }\r
3173
+ \r
3174
+ function renderParameters(params) {\r
3175
+ if (!params || params.length === 0) return '';\r
3176
+ return \`\r
3177
+ <div class="section-header">Parameters</div>\r
3178
+ <table class="params-table">\r
3179
+ <thead><tr><th>Name</th><th>Description</th></tr></thead>\r
3180
+ <tbody>\r
3181
+ \${params.map(p => \`\r
3182
+ <tr>\r
3183
+ <td>\r
3184
+ <div class="param-name">\${escapeHtml(p.name)}\${p.required ? '<span class="param-req">*</span>' : ''}</div>\r
3185
+ <div class="param-meta">\${escapeHtml(p.in)} &bull; \${escapeHtml(p.schema?.type || 'string')}</div>\r
3186
+ </td>\r
3187
+ <td>\${escapeHtml(p.description || '-')}</td>\r
3188
+ </tr>\r
3189
+ \`).join('')}\r
3190
+ </tbody>\r
3191
+ </table>\r
3192
+ \`;\r
3193
+ }\r
3194
+ \r
3195
+ function renderRequestBody(body) {\r
3196
+ if (!body) return '';\r
3197
+ const contentType = Object.keys(body.content || {})[0] || 'application/json';\r
3198
+ const schema = body.content && body.content[contentType] ? body.content[contentType].schema : null;\r
3199
+ return \`\r
3200
+ <div class="section-header">Request Body <span style="font-weight:400;text-transform:none;color:var(--text-secondary)">(\${escapeHtml(contentType)})</span></div>\r
3201
+ <div class="code-block">\${escapeHtml(JSON.stringify(generateExample(schema), null, 2))}</div>\r
3202
+ \`;\r
3203
+ }\r
3204
+ \r
3205
+ function renderResponses(responses) {\r
3206
+ if (!responses) return '';\r
3207
+ return \`\r
3208
+ <div class="section-header">Responses</div>\r
3209
+ \${Object.entries(responses).map(([status, res]) => \`\r
3210
+ <div style="margin-bottom:16px">\r
3211
+ <div style="font-weight:600;font-family:var(--font-mono);margin-bottom:4px">\r
3212
+ <span style="color:\${String(status).startsWith('2') ? 'var(--success)' : 'var(--danger)'}">\${escapeHtml(status)}</span>\r
3213
+ <span style="color:var(--text-secondary)">\${escapeHtml(res.description || '')}</span>\r
3214
+ </div>\r
3215
+ </div>\r
3216
+ \`).join('')}\r
3217
+ \`;\r
3218
+ }\r
3219
+ \r
3220
+ function getExampleBody(body) {\r
3221
+ if (!body || !body.content) return '{}';\r
3222
+ const contentType = Object.keys(body.content)[0];\r
3223
+ const schema = body.content[contentType].schema;\r
3224
+ try { return JSON.stringify(generateExample(schema), null, 2); } catch { return '{}'; }\r
3225
+ }\r
3226
+ \r
3227
+ function generateExample(schema) {\r
3228
+ if (!schema) return {};\r
3229
+ if (schema.example !== undefined) return schema.example;\r
3230
+ if (schema.$ref) {\r
3231
+ const ref = String(schema.$ref).replace(/^#\\/components\\/schemas\\//, '');\r
3232
+ const comp = state.spec.components && state.spec.components.schemas && state.spec.components.schemas[ref];\r
3233
+ if (comp) return generateExample(comp);\r
3234
+ return {};\r
3235
+ }\r
3236
+ if (schema.type === 'object' && schema.properties) {\r
3237
+ const obj = {};\r
3238
+ for (const [k, prop] of Object.entries(schema.properties)) {\r
3239
+ obj[k] = generateExample(prop);\r
3240
+ }\r
3241
+ return obj;\r
3242
+ }\r
3243
+ if (schema.type === 'array') return [generateExample(schema.items)];\r
3244
+ if (schema.type === 'string') return 'string';\r
3245
+ if (schema.type === 'integer' || schema.type === 'number') return 0;\r
3246
+ if (schema.type === 'boolean') return true;\r
3247
+ return null;\r
3248
+ }\r
3249
+ \r
3250
+ async function executeRequest(method, path, btn) {\r
3251
+ const cardId = btn.dataset.cardId;\r
3252
+ const card = document.getElementById(cardId);\r
3253
+ const inputs = card.querySelectorAll('input[data-name]');\r
3254
+ const bodyEditor = card.querySelector('.body-editor');\r
3255
+ const responseArea = card.querySelector('.response-area');\r
3256
+ const originalHtml = btn.innerHTML;\r
3257
+ btn.disabled = true;\r
3258
+ btn.innerHTML = '<span class="loader"></span> Executing...';\r
3259
+ responseArea.style.display = 'none';\r
3260
+ try {\r
3261
+ let url = (state.server || '') + path;\r
3262
+ const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };\r
3263
+ const queryParams = new URLSearchParams();\r
3264
+ inputs.forEach(input => {\r
3265
+ const value = input.value.trim();\r
3266
+ if (!value) return;\r
3267
+ const type = input.dataset.in;\r
3268
+ const name = input.dataset.name;\r
3269
+ if (type === 'path') {\r
3270
+ url = url.replace(\`{\${name}}\`, encodeURIComponent(value));\r
3271
+ } else if (type === 'query') {\r
3272
+ queryParams.append(name, value);\r
3273
+ } else if (type === 'header') {\r
3274
+ headers[name] = value;\r
3275
+ } else if (type === 'cookie') {\r
3276
+ // cookies handled as header for demo purposes\r
3277
+ headers['Cookie'] = (headers['Cookie'] ? headers['Cookie'] + '; ' : '') + \`\${name}=\${value}\`;\r
3278
+ }\r
3279
+ });\r
3280
+ const qs = queryParams.toString();\r
3281
+ if (qs) url += \`?\${qs}\`;\r
3282
+ const options = { method: method.toUpperCase(), headers };\r
3283
+ if (bodyEditor && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {\r
3284
+ try {\r
3285
+ const parsed = JSON.parse(bodyEditor.value);\r
3286
+ options.body = JSON.stringify(parsed);\r
3287
+ } catch (e) {\r
3288
+ alert('Invalid JSON in Request Body');\r
3289
+ throw new Error('Invalid JSON');\r
3290
+ }\r
3291
+ }\r
3292
+ const start = performance.now();\r
3293
+ const res = await fetch(url, options);\r
3294
+ const duration = Math.round(performance.now() - start);\r
3295
+ let data;\r
3296
+ const ct = res.headers.get('content-type') || '';\r
3297
+ if (ct.includes('application/json')) data = await res.json(); else data = await res.text();\r
3298
+ const statusClass = res.ok ? 'status-2xx' : (String(res.status).startsWith('5') ? 'status-5xx' : 'status-4xx');\r
3299
+ responseArea.style.display = 'block';\r
3300
+ responseArea.innerHTML = \`\r
3301
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">\r
3302
+ <span class="status-badge \${statusClass}">\${res.status} \${escapeHtml(res.statusText)}</span>\r
3303
+ <span style="font-family:var(--font-mono);font-size:0.8rem;color:var(--text-muted)">\${duration}ms</span>\r
3304
+ </div>\r
3305
+ <div class="section-header">Response Body</div>\r
3306
+ <div class="code-block">\${escapeHtml(typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data))}</div>\r
3307
+ <div class="section-header">Response Headers</div>\r
3308
+ <div class="code-block">\${escapeHtml(Array.from(res.headers.entries()).map(([k, v]) => \`\${k}: \${v}\`).join('\\\\n'))}</div>\r
3309
+ \`;\r
3310
+ } catch (err) {\r
3311
+ responseArea.style.display = 'block';\r
3312
+ responseArea.innerHTML = \`<div style="color:var(--danger);font-weight:600">Error: \${escapeHtml(err.message || String(err))}</div>\`;\r
3313
+ } finally {\r
3314
+ btn.disabled = false;\r
3315
+ btn.innerHTML = originalHtml;\r
3316
+ }\r
3317
+ }\r
3318
+ \r
3319
+ function toggleCard(header) {\r
3320
+ const isExpanded = header.getAttribute('aria-expanded') === 'true';\r
3321
+ header.setAttribute('aria-expanded', String(!isExpanded));\r
3322
+ }\r
3323
+ \r
3324
+ function scrollEndpoint(sanitizedId) {\r
3325
+ const el = document.getElementById(sanitizedId);\r
3326
+ if (!el) return;\r
3327
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });\r
3328
+ const header = el.querySelector('.card-header');\r
3329
+ if (header && header.getAttribute('aria-expanded') !== 'true') {\r
3330
+ header.setAttribute('aria-expanded', 'true');\r
3331
+ }\r
3332
+ }\r
3333
+ \r
3334
+ function setupSearch() {\r
3335
+ const input = document.getElementById('searchNav');\r
3336
+ input.addEventListener('input', (e) => {\r
3337
+ const val = e.target.value.toLowerCase();\r
3338
+ document.querySelectorAll('.nav-item').forEach(el => {\r
3339
+ const filter = el.dataset.filter || '';\r
3340
+ el.style.display = filter.includes(val) ? 'flex' : 'none';\r
3341
+ });\r
3342
+ });\r
3343
+ }\r
3344
+ \r
3345
+ function attachGlobalListeners() {\r
3346
+ document.querySelectorAll('.endpoint-card .card-header').forEach(header => {\r
3347
+ header.addEventListener('click', () => toggleCard(header));\r
3348
+ });\r
3349
+ document.querySelectorAll('.action-btn').forEach(btn => {\r
3350
+ btn.addEventListener('click', () => executeRequest(btn.dataset.method, btn.dataset.path, btn));\r
3351
+ });\r
3352
+ }\r
3353
+ \r
3354
+ function escapeHtml(str) {\r
3355
+ if (str === undefined || str === null) return '';\r
3356
+ return String(str)\r
3357
+ .replace(/&/g, '&amp;')\r
3358
+ .replace(/</g, '&lt;')\r
3359
+ .replace(/>/g, '&gt;')\r
3360
+ .replace(/"/g, '&quot;')\r
3361
+ .replace(/'/g, '&#39;');\r
3362
+ }\r
3363
+ function escapeHtmlAttr(str) {\r
3364
+ return escapeHtml(str).replace(/"/g, '&quot;').replace(/'/g, '&#39;');\r
3365
+ }\r
3366
+ \r
3367
+ init();\r
3368
+ </script>\r
3369
+ </body>\r
3370
+ \r
3371
+ </html>`;
3372
+
3373
+ // src/swagger/swagger-ui-html.ts
3374
+ function renderSwaggerUiPage(options) {
3375
+ const { title, specUrl } = options;
3376
+ return SWAGGER_UI_MODERN_HTML.replaceAll("__PAGE_TITLE__", escapeHtml(title)).replaceAll(
3377
+ '"___AZURA_SPEC_URL_PLACEHOLDER___"',
3378
+ JSON.stringify(specUrl)
3379
+ );
3380
+ }
3381
+ function escapeHtml(s) {
3382
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3383
+ }
3384
+
3385
+ // src/plugins/SwaggerPlugin.ts
3386
+ function SwaggerPlugin(options) {
3387
+ const {
3388
+ getDocuments,
3389
+ specPath = "/openapi.json",
3390
+ docsPath = "/docs",
3391
+ deepLinking = true,
3392
+ servers,
3393
+ pathPrefix,
3394
+ info
3395
+ } = options;
3396
+ const title = info?.title ?? "API";
3397
+ return async (ctx, next) => {
3398
+ const { req, res } = ctx;
3399
+ const path = req.pathname ?? "";
3400
+ if (path === specPath || path === `${specPath}/`) {
3401
+ const doc = buildOpenApiDocument(getDocuments(), {
3402
+ info: info ?? { title, version: "1.0.0" },
3403
+ servers,
3404
+ pathPrefix
3405
+ });
3406
+ res.json(doc);
3407
+ return;
3408
+ }
3409
+ if (path === docsPath || path === `${docsPath}/`) {
3410
+ const html = renderSwaggerUiPage({
3411
+ title,
3412
+ specUrl: specPath});
3413
+ res.html(html);
3414
+ return;
3415
+ }
3416
+ next();
3417
+ };
3418
+ }
3419
+
2259
3420
  // src/utils/validators/DTOValidator.ts
2260
3421
  var DTOValidator = class {
2261
3422
  static validate(data, schema) {
@@ -2534,6 +3695,6 @@ function LoggingMiddleware() {
2534
3695
  };
2535
3696
  }
2536
3697
 
2537
- export { AzuraServer, Body, CORSPlugin, CircuitBreakerPlugin, CompressionPlugin, ConfigModule, Controller, Cookies, DTOValidator, Delete, ETagPlugin, Get, Head, Headers, HealthCheckPlugin, HelmetPlugin, HttpError, HttpStatus, HttpStatusText, Ip, JWTPlugin, Logger, LoggingMiddleware, Meta, MultipartPlugin, NextFunc, Options, Param, Patch, Post, ProxyPlugin, Put, Query, RateLimitPlugin, Req, RequestIdPlugin, Res, Router, SSEManager, SSEPlugin, SchemaValidator, Session, SessionPlugin, StaticPlugin, TimeoutPlugin, UseMiddleware, applyDecorators, clearCookieHeader, parseBody, parseCookies, parseQueryString, parseUrl, resolveIp, serializeCookie, signJWT, verifyJWT };
3698
+ export { ApiOperation, ApiTags, AzuraServer, Body, CORSPlugin, CircuitBreakerPlugin, CompressionPlugin, ConfigModule, Controller, Cookies, DTOValidator, Delete, ETagPlugin, Get, Head, Headers, HealthCheckPlugin, HelmetPlugin, HttpError, HttpStatus, HttpStatusText, Ip, JWTPlugin, Logger, LoggingMiddleware, Meta, MultipartPlugin, NextFunc, Options, Param, Patch, Post, ProxyPlugin, Put, Query, RateLimitPlugin, Req, RequestIdPlugin, Res, Router, SSEManager, SSEPlugin, SchemaValidator, Session, SessionPlugin, StaticPlugin, SwaggerPlugin, TimeoutPlugin, UseMiddleware, applyDecorators, clearCookieHeader, parseBody, parseCookies, parseQueryString, parseUrl, resolveIp, serializeCookie, signJWT, verifyJWT };
2538
3699
  //# sourceMappingURL=index.mjs.map
2539
3700
  //# sourceMappingURL=index.mjs.map