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,894 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
+ <title>__PAGE_TITLE__</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
12
+ rel="stylesheet" />
13
+ <style>
14
+ :root {
15
+ --bg-body: #09090b;
16
+ --bg-sidebar: #18181b;
17
+ --bg-card: #18181b;
18
+ --bg-card-hover: #27272a;
19
+ --bg-input: #09090b;
20
+ --border-color: #27272a;
21
+ --primary-color: #6366f1;
22
+ --primary-hover: #4f46e5;
23
+ --text-primary: #f4f4f5;
24
+ --text-secondary: #a1a1aa;
25
+ --text-muted: #52525b;
26
+ --success: #22c55e;
27
+ --warning: #eab308;
28
+ --danger: #ef4444;
29
+ --info: #3b82f6;
30
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
31
+ --font-mono: 'JetBrains Mono', monospace;
32
+ }
33
+
34
+ * {
35
+ margin: 0;
36
+ padding: 0;
37
+ box-sizing: border-box
38
+ }
39
+
40
+ body {
41
+ font-family: var(--font-sans);
42
+ background-color: var(--bg-body);
43
+ color: var(--text-primary);
44
+ line-height: 1.5;
45
+ height: 100vh;
46
+ overflow: hidden
47
+ }
48
+
49
+ ::-webkit-scrollbar {
50
+ width: 6px;
51
+ height: 6px
52
+ }
53
+
54
+ ::-webkit-scrollbar-track {
55
+ background: transparent
56
+ }
57
+
58
+ ::-webkit-scrollbar-thumb {
59
+ background: var(--border-color);
60
+ border-radius: 3px
61
+ }
62
+
63
+ ::-webkit-scrollbar-thumb:hover {
64
+ background: var(--text-muted)
65
+ }
66
+
67
+ .layout {
68
+ display: flex;
69
+ height: 100%
70
+ }
71
+
72
+ .sidebar {
73
+ width: 300px;
74
+ background: var(--bg-sidebar);
75
+ border-right: 1px solid var(--border-color);
76
+ display: flex;
77
+ flex-direction: column;
78
+ flex-shrink: 0
79
+ }
80
+
81
+ .sidebar-header {
82
+ padding: 20px;
83
+ border-bottom: 1px solid var(--border-color);
84
+ background: var(--bg-sidebar)
85
+ }
86
+
87
+ .api-title {
88
+ font-size: 1.25rem;
89
+ font-weight: 700;
90
+ color: var(--text-primary);
91
+ margin-bottom: 4px
92
+ }
93
+
94
+ .api-version {
95
+ font-size: 0.75rem;
96
+ color: var(--primary-color);
97
+ background: rgba(99, 102, 241, 0.1);
98
+ padding: 2px 8px;
99
+ border-radius: 12px;
100
+ font-family: var(--font-mono)
101
+ }
102
+
103
+ .search-box {
104
+ padding: 16px
105
+ }
106
+
107
+ .search-input {
108
+ width: 100%;
109
+ background: var(--bg-input);
110
+ border: 1px solid var(--border-color);
111
+ padding: 8px 12px;
112
+ border-radius: 6px;
113
+ color: var(--text-primary);
114
+ font-size: 0.875rem;
115
+ outline: none;
116
+ transition: border-color 0.2s
117
+ }
118
+
119
+ .search-input:focus {
120
+ border-color: var(--primary-color)
121
+ }
122
+
123
+ .nav-list {
124
+ flex: 1;
125
+ overflow-y: auto;
126
+ padding: 0 16px 20px
127
+ }
128
+
129
+ .nav-group-title {
130
+ font-size: 0.75rem;
131
+ text-transform: uppercase;
132
+ color: var(--text-muted);
133
+ font-weight: 600;
134
+ margin: 24px 0 8px;
135
+ letter-spacing: 0.05em
136
+ }
137
+
138
+ .nav-item {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 8px;
142
+ padding: 8px;
143
+ border-radius: 6px;
144
+ cursor: pointer;
145
+ transition: background 0.2s;
146
+ margin-bottom: 2px
147
+ }
148
+
149
+ .nav-item:hover {
150
+ background: var(--bg-card-hover)
151
+ }
152
+
153
+ .nav-item.active {
154
+ background: rgba(99, 102, 241, 0.1)
155
+ }
156
+
157
+ .method-tag {
158
+ font-family: var(--font-mono);
159
+ font-size: 0.65rem;
160
+ font-weight: 700;
161
+ padding: 2px 6px;
162
+ border-radius: 4px;
163
+ width: 45px;
164
+ text-align: center
165
+ }
166
+
167
+ .get {
168
+ color: var(--success);
169
+ background: rgba(34, 197, 94, 0.1)
170
+ }
171
+
172
+ .post {
173
+ color: var(--primary-color);
174
+ background: rgba(99, 102, 241, 0.1)
175
+ }
176
+
177
+ .put {
178
+ color: var(--warning);
179
+ background: rgba(234, 179, 8, 0.1)
180
+ }
181
+
182
+ .delete {
183
+ color: var(--danger);
184
+ background: rgba(239, 68, 68, 0.1)
185
+ }
186
+
187
+ .patch {
188
+ color: var(--info);
189
+ background: rgba(59, 130, 246, 0.1)
190
+ }
191
+
192
+ .nav-path {
193
+ font-size: 0.8rem;
194
+ color: var(--text-secondary);
195
+ white-space: nowrap;
196
+ overflow: hidden;
197
+ text-overflow: ellipsis;
198
+ font-family: var(--font-mono)
199
+ }
200
+
201
+ .main-content {
202
+ flex: 1;
203
+ overflow-y: auto;
204
+ padding: 40px;
205
+ scroll-behavior: smooth
206
+ }
207
+
208
+ .content-width {
209
+ max-width: 1000px;
210
+ margin: 0 auto
211
+ }
212
+
213
+ .info-section {
214
+ margin-bottom: 40px
215
+ }
216
+
217
+ .info-title {
218
+ font-size: 2.5rem;
219
+ font-weight: 800;
220
+ margin-bottom: 16px;
221
+ letter-spacing: -0.02em
222
+ }
223
+
224
+ .info-desc {
225
+ color: var(--text-secondary);
226
+ font-size: 1.1rem;
227
+ max-width: 700px
228
+ }
229
+
230
+ .server-section {
231
+ background: var(--bg-card);
232
+ border: 1px solid var(--border-color);
233
+ padding: 20px;
234
+ border-radius: 8px;
235
+ margin-bottom: 40px
236
+ }
237
+
238
+ .server-label {
239
+ display: block;
240
+ font-size: 0.875rem;
241
+ color: var(--text-muted);
242
+ margin-bottom: 8px
243
+ }
244
+
245
+ .server-select {
246
+ width: 100%;
247
+ background: var(--bg-input);
248
+ border: 1px solid var(--border-color);
249
+ color: var(--text-primary);
250
+ padding: 10px;
251
+ border-radius: 6px;
252
+ font-family: var(--font-mono);
253
+ font-size: 0.9rem
254
+ }
255
+
256
+ .endpoint-card {
257
+ background: var(--bg-card);
258
+ border: 1px solid var(--border-color);
259
+ border-radius: 8px;
260
+ margin-bottom: 24px;
261
+ overflow: hidden;
262
+ transition: border-color 0.2s
263
+ }
264
+
265
+ .endpoint-card:hover {
266
+ border-color: var(--text-muted)
267
+ }
268
+
269
+ .card-header {
270
+ padding: 16px 24px;
271
+ display: flex;
272
+ align-items: center;
273
+ gap: 16px;
274
+ cursor: pointer;
275
+ background: rgba(255, 255, 255, 0.02);
276
+ user-select: none
277
+ }
278
+
279
+ .card-summary {
280
+ flex: 1;
281
+ font-weight: 500;
282
+ font-size: 1rem
283
+ }
284
+
285
+ .card-path {
286
+ font-family: var(--font-mono);
287
+ font-size: 0.85rem;
288
+ color: var(--text-muted)
289
+ }
290
+
291
+ .chevron {
292
+ transition: transform 0.2s;
293
+ color: var(--text-muted)
294
+ }
295
+
296
+ .card-header[aria-expanded="true"] .chevron {
297
+ transform: rotate(180deg)
298
+ }
299
+
300
+ .card-body {
301
+ border-top: 1px solid var(--border-color);
302
+ padding: 24px;
303
+ display: none
304
+ }
305
+
306
+ .card-header[aria-expanded="true"]+.card-body {
307
+ display: block
308
+ }
309
+
310
+ .section-header {
311
+ font-size: 0.75rem;
312
+ text-transform: uppercase;
313
+ color: var(--text-muted);
314
+ font-weight: 700;
315
+ margin: 24px 0 12px;
316
+ letter-spacing: 0.05em
317
+ }
318
+
319
+ .section-header:first-child {
320
+ margin-top: 0
321
+ }
322
+
323
+ .params-table {
324
+ width: 100%;
325
+ border-collapse: collapse;
326
+ font-size: 0.9rem;
327
+ margin-bottom: 20px
328
+ }
329
+
330
+ .params-table th {
331
+ text-align: left;
332
+ color: var(--text-muted);
333
+ padding: 8px 12px;
334
+ font-weight: 500;
335
+ border-bottom: 1px solid var(--border-color);
336
+ font-size: 0.8rem
337
+ }
338
+
339
+ .params-table td {
340
+ padding: 12px;
341
+ border-bottom: 1px solid var(--border-color);
342
+ color: var(--text-secondary);
343
+ vertical-align: top
344
+ }
345
+
346
+ .params-table tr:last-child td {
347
+ border-bottom: none
348
+ }
349
+
350
+ .param-name {
351
+ font-family: var(--font-mono);
352
+ color: var(--text-primary);
353
+ font-weight: 600
354
+ }
355
+
356
+ .param-req {
357
+ color: var(--danger);
358
+ font-size: 0.7rem;
359
+ margin-left: 4px
360
+ }
361
+
362
+ .param-meta {
363
+ font-size: 0.8rem;
364
+ color: var(--text-muted);
365
+ margin-top: 4px;
366
+ font-family: var(--font-mono)
367
+ }
368
+
369
+ .code-block {
370
+ background: #000;
371
+ border: 1px solid var(--border-color);
372
+ border-radius: 6px;
373
+ padding: 16px;
374
+ font-family: var(--font-mono);
375
+ font-size: 0.85rem;
376
+ overflow-x: auto;
377
+ color: #d4d4d8
378
+ }
379
+
380
+ .try-it-box {
381
+ background: rgba(99, 102, 241, 0.05);
382
+ border: 1px solid rgba(99, 102, 241, 0.2);
383
+ border-radius: 8px;
384
+ padding: 20px;
385
+ margin-top: 32px
386
+ }
387
+
388
+ .form-grid {
389
+ display: grid;
390
+ grid-template-columns: 1fr;
391
+ gap: 16px;
392
+ margin-bottom: 20px
393
+ }
394
+
395
+ .form-group label {
396
+ display: block;
397
+ font-size: 0.8rem;
398
+ font-weight: 600;
399
+ margin-bottom: 6px;
400
+ color: var(--text-secondary)
401
+ }
402
+
403
+ .form-group input {
404
+ width: 100%;
405
+ background: var(--bg-body);
406
+ border: 1px solid var(--border-color);
407
+ padding: 8px 12px;
408
+ border-radius: 4px;
409
+ color: var(--text-primary);
410
+ font-family: var(--font-mono);
411
+ font-size: 0.85rem
412
+ }
413
+
414
+ .form-group input:focus {
415
+ border-color: var(--primary-color);
416
+ outline: none
417
+ }
418
+
419
+ .body-editor {
420
+ width: 100%;
421
+ height: 200px;
422
+ background: var(--bg-body);
423
+ border: 1px solid var(--border-color);
424
+ border-radius: 6px;
425
+ padding: 12px;
426
+ color: var(--text-primary);
427
+ font-family: var(--font-mono);
428
+ font-size: 0.85rem;
429
+ resize: vertical;
430
+ margin-bottom: 16px
431
+ }
432
+
433
+ .action-btn {
434
+ background: var(--primary-color);
435
+ color: white;
436
+ border: none;
437
+ padding: 10px 20px;
438
+ border-radius: 6px;
439
+ font-weight: 600;
440
+ cursor: pointer;
441
+ font-size: 0.9rem;
442
+ transition: background 0.2s;
443
+ display: inline-flex;
444
+ align-items: center;
445
+ gap: 8px
446
+ }
447
+
448
+ .action-btn:hover {
449
+ background: var(--primary-hover)
450
+ }
451
+
452
+ .action-btn:disabled {
453
+ opacity: 0.6;
454
+ cursor: not-allowed
455
+ }
456
+
457
+ .response-area {
458
+ margin-top: 20px;
459
+ border-top: 1px solid var(--border-color);
460
+ padding-top: 20px
461
+ }
462
+
463
+ .status-badge {
464
+ display: inline-block;
465
+ padding: 4px 8px;
466
+ border-radius: 4px;
467
+ font-size: 0.8rem;
468
+ font-weight: 700;
469
+ margin-bottom: 10px;
470
+ font-family: var(--font-mono)
471
+ }
472
+
473
+ .status-2xx {
474
+ background: rgba(34, 197, 94, 0.2);
475
+ color: var(--success)
476
+ }
477
+
478
+ .status-4xx,
479
+ .status-5xx {
480
+ background: rgba(239, 68, 68, 0.2);
481
+ color: var(--danger)
482
+ }
483
+
484
+ .loader {
485
+ width: 16px;
486
+ height: 16px;
487
+ border: 2px solid #ffffff;
488
+ border-bottom-color: transparent;
489
+ border-radius: 50%;
490
+ display: inline-block;
491
+ animation: rotation 1s linear infinite
492
+ }
493
+
494
+ @keyframes rotation {
495
+ 0% {
496
+ transform: rotate(0deg)
497
+ }
498
+
499
+ 100% {
500
+ transform: rotate(360deg)
501
+ }
502
+ }
503
+
504
+ @media (max-width:768px) {
505
+ .layout {
506
+ flex-direction: column;
507
+ overflow: auto
508
+ }
509
+
510
+ .sidebar {
511
+ width: 100%;
512
+ height: auto;
513
+ border-right: none;
514
+ border-bottom: 1px solid var(--border-color)
515
+ }
516
+
517
+ .main-content {
518
+ padding: 20px;
519
+ overflow: visible
520
+ }
521
+
522
+ .nav-list {
523
+ max-height: 300px
524
+ }
525
+ }
526
+ </style>
527
+ </head>
528
+
529
+ <body>
530
+ <div class="layout">
531
+ <aside class="sidebar">
532
+ <div class="sidebar-header">
533
+ <div class="api-title" id="apiTitle">API Docs</div>
534
+ <span class="api-version" id="apiVersion">v1.0.0</span>
535
+ </div>
536
+ <div class="search-box">
537
+ <input type="text" class="search-input" id="searchNav" placeholder="Filter endpoints...">
538
+ </div>
539
+ <div class="nav-list" id="navList"></div>
540
+ </aside>
541
+ <main class="main-content">
542
+ <div class="content-width">
543
+ <div class="info-section">
544
+ <h1 class="info-title" id="docTitle">API Reference</h1>
545
+ <p class="info-desc" id="docDesc">Loading documentation...</p>
546
+ </div>
547
+ <div class="server-section">
548
+ <label class="server-label">Base URL</label>
549
+ <select class="server-select" id="serverUrl"></select>
550
+ </div>
551
+ <div id="endpointsContainer"></div>
552
+ </div>
553
+ </main>
554
+ </div>
555
+
556
+ <script>
557
+ const state = { spec: null, server: '' };
558
+ function sanitizeId(str) { return String(str).replace(/[^a-zA-Z0-9_-]/g, '_'); }
559
+ async function init() {
560
+ try {
561
+ const res = await fetch("___AZURA_SPEC_URL_PLACEHOLDER___");
562
+ if (!res.ok) throw new Error('Spec not found');
563
+ state.spec = await res.json();
564
+ renderHeader();
565
+ renderServers();
566
+ renderNavigation();
567
+ renderEndpoints();
568
+ setupSearch();
569
+ attachGlobalListeners();
570
+ } catch (err) {
571
+ const descEl = document.getElementById('docDesc');
572
+ descEl.textContent = 'Failed to load API definition. Check that the OpenAPI JSON endpoint is configured and reachable.';
573
+ descEl.style.color = 'var(--danger)';
574
+ }
575
+ }
576
+
577
+ function renderHeader() {
578
+ const info = state.spec.info || {};
579
+ document.title = `${info.title || 'API'} - Docs`;
580
+ document.getElementById('apiTitle').textContent = info.title || 'API';
581
+ document.getElementById('apiVersion').textContent = info.version ? `v${info.version}` : '';
582
+ document.getElementById('docTitle').textContent = info.title || 'API Reference';
583
+ document.getElementById('docDesc').textContent = info.description || 'No description provided.';
584
+ }
585
+
586
+ function renderServers() {
587
+ const select = document.getElementById('serverUrl');
588
+ const servers = state.spec.servers && state.spec.servers.length ? state.spec.servers : [{ url: window.location.origin, description: 'Current Origin' }];
589
+ select.innerHTML = servers.map(s => `<option value="${s.url}">${s.url}${s.description ? ` (${s.description})` : ''}</option>`).join('');
590
+ state.server = servers[0].url;
591
+ select.addEventListener('change', (e) => state.server = e.target.value);
592
+ }
593
+
594
+ function renderNavigation() {
595
+ const nav = document.getElementById('navList');
596
+ const tags = {};
597
+ Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {
598
+ Object.entries(methods).forEach(([method, op]) => {
599
+ const tag = (op.tags && op.tags[0]) ? op.tags[0] : 'General';
600
+ if (!tags[tag]) tags[tag] = [];
601
+ tags[tag].push({ path, method, op });
602
+ });
603
+ });
604
+
605
+ let html = '';
606
+ Object.entries(tags).forEach(([tag, items]) => {
607
+ html += `<div class="nav-group-title">${tag}</div>`;
608
+ items.forEach(item => {
609
+ const label = item.op.summary || item.path;
610
+ const filter = `${item.method} ${item.path} ${label}`.toLowerCase();
611
+ const targetId = sanitizeId(`${item.method}-${item.path}`);
612
+ html += `<div class="nav-item" data-filter="${escapeHtmlAttr(filter)}" data-target-id="${escapeHtmlAttr(targetId)}">
613
+ <span class="method-tag ${item.method.toLowerCase()}">${item.method.toUpperCase()}</span>
614
+ <span class="nav-path">${escapeHtmlAttr(label)}</span>
615
+ </div>`;
616
+ });
617
+ });
618
+
619
+ nav.innerHTML = html;
620
+ nav.querySelectorAll('.nav-item').forEach(el => {
621
+ el.addEventListener('click', () => {
622
+ const tid = el.dataset.targetId;
623
+ scrollEndpoint(tid);
624
+ document.querySelectorAll('.nav-item').forEach(i => i.classList.toggle('active', i === el));
625
+ });
626
+ });
627
+ }
628
+
629
+ function renderEndpoints() {
630
+ const container = document.getElementById('endpointsContainer');
631
+ container.innerHTML = '';
632
+ Object.entries(state.spec.paths || {}).forEach(([path, methods]) => {
633
+ Object.entries(methods).forEach(([method, op]) => {
634
+ container.appendChild(createEndpointCard(path, method, op));
635
+ });
636
+ });
637
+ }
638
+
639
+ function createEndpointCard(path, method, op) {
640
+ const rawId = `${method}-${path}`;
641
+ const id = sanitizeId(rawId);
642
+ const card = document.createElement('div');
643
+ card.className = 'endpoint-card';
644
+ card.id = id;
645
+
646
+ const descriptionHtml = op.description ? `<p style="margin-bottom:20px;color:var(--text-secondary)">${escapeHtml(op.description)}</p>` : '';
647
+ const paramsHtml = renderParameters(op.parameters);
648
+ const requestBodyHtml = renderRequestBody(op.requestBody);
649
+ const responsesHtml = renderResponses(op.responses);
650
+
651
+ const inputsId = `inputs_${id}`;
652
+ const bodyId = `body_${id}`;
653
+ const tryButtonId = `btn_${id}`;
654
+ const inputsHtmlForTry = (op.parameters || []).map(p => {
655
+ const placeholder = p.schema?.type || '';
656
+ const required = p.required ? ' <span style="color:var(--danger)">*</span>' : '';
657
+ return `<div class="form-group">
658
+ <label>${escapeHtml(p.name)} <span style="font-weight:400;color:var(--text-muted)">(${escapeHtml(p.in)})</span>${required}</label>
659
+ <input type="text" data-name="${escapeHtmlAttr(p.name)}" data-in="${escapeHtmlAttr(p.in)}" placeholder="${escapeHtmlAttr(placeholder)}">
660
+ </div>`;
661
+ }).join('');
662
+
663
+ const bodyEditorHtml = op.requestBody ? `<div class="form-group">
664
+ <label>Request Body (JSON)</label>
665
+ <textarea class="body-editor" id="${bodyId}">${getExampleBody(op.requestBody)}</textarea>
666
+ </div>` : '';
667
+
668
+ card.innerHTML = `
669
+ <div class="card-header" aria-expanded="false">
670
+ <span class="method-tag ${method.toLowerCase()}">${method.toUpperCase()}</span>
671
+ <span class="card-summary">${escapeHtml(op.summary || path)}</span>
672
+ <span class="card-path">${escapeHtml(path)}</span>
673
+ <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>
674
+ </div>
675
+ <div class="card-body">
676
+ ${descriptionHtml}
677
+ ${paramsHtml}
678
+ ${requestBodyHtml}
679
+ <div class="try-it-box">
680
+ <div class="section-header">Try It Out</div>
681
+ <div class="form-grid" id="${inputsId}">
682
+ ${inputsHtmlForTry}
683
+ </div>
684
+ ${bodyEditorHtml}
685
+ <button class="action-btn" id="${tryButtonId}" data-method="${escapeHtmlAttr(method)}" data-path="${escapeHtmlAttr(path)}" data-card-id="${escapeHtmlAttr(id)}">
686
+ <span>Execute Request</span>
687
+ </button>
688
+ <div class="response-area" style="display:none"></div>
689
+ </div>
690
+ ${responsesHtml}
691
+ </div>
692
+ `;
693
+
694
+ return card;
695
+ }
696
+
697
+ function renderParameters(params) {
698
+ if (!params || params.length === 0) return '';
699
+ return `
700
+ <div class="section-header">Parameters</div>
701
+ <table class="params-table">
702
+ <thead><tr><th>Name</th><th>Description</th></tr></thead>
703
+ <tbody>
704
+ ${params.map(p => `
705
+ <tr>
706
+ <td>
707
+ <div class="param-name">${escapeHtml(p.name)}${p.required ? '<span class="param-req">*</span>' : ''}</div>
708
+ <div class="param-meta">${escapeHtml(p.in)} &bull; ${escapeHtml(p.schema?.type || 'string')}</div>
709
+ </td>
710
+ <td>${escapeHtml(p.description || '-')}</td>
711
+ </tr>
712
+ `).join('')}
713
+ </tbody>
714
+ </table>
715
+ `;
716
+ }
717
+
718
+ function renderRequestBody(body) {
719
+ if (!body) return '';
720
+ const contentType = Object.keys(body.content || {})[0] || 'application/json';
721
+ const schema = body.content && body.content[contentType] ? body.content[contentType].schema : null;
722
+ return `
723
+ <div class="section-header">Request Body <span style="font-weight:400;text-transform:none;color:var(--text-secondary)">(${escapeHtml(contentType)})</span></div>
724
+ <div class="code-block">${escapeHtml(JSON.stringify(generateExample(schema), null, 2))}</div>
725
+ `;
726
+ }
727
+
728
+ function renderResponses(responses) {
729
+ if (!responses) return '';
730
+ return `
731
+ <div class="section-header">Responses</div>
732
+ ${Object.entries(responses).map(([status, res]) => `
733
+ <div style="margin-bottom:16px">
734
+ <div style="font-weight:600;font-family:var(--font-mono);margin-bottom:4px">
735
+ <span style="color:${String(status).startsWith('2') ? 'var(--success)' : 'var(--danger)'}">${escapeHtml(status)}</span>
736
+ <span style="color:var(--text-secondary)">${escapeHtml(res.description || '')}</span>
737
+ </div>
738
+ </div>
739
+ `).join('')}
740
+ `;
741
+ }
742
+
743
+ function getExampleBody(body) {
744
+ if (!body || !body.content) return '{}';
745
+ const contentType = Object.keys(body.content)[0];
746
+ const schema = body.content[contentType].schema;
747
+ try { return JSON.stringify(generateExample(schema), null, 2); } catch { return '{}'; }
748
+ }
749
+
750
+ function generateExample(schema) {
751
+ if (!schema) return {};
752
+ if (schema.example !== undefined) return schema.example;
753
+ if (schema.$ref) {
754
+ const ref = String(schema.$ref).replace(/^#\/components\/schemas\//, '');
755
+ const comp = state.spec.components && state.spec.components.schemas && state.spec.components.schemas[ref];
756
+ if (comp) return generateExample(comp);
757
+ return {};
758
+ }
759
+ if (schema.type === 'object' && schema.properties) {
760
+ const obj = {};
761
+ for (const [k, prop] of Object.entries(schema.properties)) {
762
+ obj[k] = generateExample(prop);
763
+ }
764
+ return obj;
765
+ }
766
+ if (schema.type === 'array') return [generateExample(schema.items)];
767
+ if (schema.type === 'string') return 'string';
768
+ if (schema.type === 'integer' || schema.type === 'number') return 0;
769
+ if (schema.type === 'boolean') return true;
770
+ return null;
771
+ }
772
+
773
+ async function executeRequest(method, path, btn) {
774
+ const cardId = btn.dataset.cardId;
775
+ const card = document.getElementById(cardId);
776
+ const inputs = card.querySelectorAll('input[data-name]');
777
+ const bodyEditor = card.querySelector('.body-editor');
778
+ const responseArea = card.querySelector('.response-area');
779
+ const originalHtml = btn.innerHTML;
780
+ btn.disabled = true;
781
+ btn.innerHTML = '<span class="loader"></span> Executing...';
782
+ responseArea.style.display = 'none';
783
+ try {
784
+ let url = (state.server || '') + path;
785
+ const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
786
+ const queryParams = new URLSearchParams();
787
+ inputs.forEach(input => {
788
+ const value = input.value.trim();
789
+ if (!value) return;
790
+ const type = input.dataset.in;
791
+ const name = input.dataset.name;
792
+ if (type === 'path') {
793
+ url = url.replace(`{${name}}`, encodeURIComponent(value));
794
+ } else if (type === 'query') {
795
+ queryParams.append(name, value);
796
+ } else if (type === 'header') {
797
+ headers[name] = value;
798
+ } else if (type === 'cookie') {
799
+ // cookies handled as header for demo purposes
800
+ headers['Cookie'] = (headers['Cookie'] ? headers['Cookie'] + '; ' : '') + `${name}=${value}`;
801
+ }
802
+ });
803
+ const qs = queryParams.toString();
804
+ if (qs) url += `?${qs}`;
805
+ const options = { method: method.toUpperCase(), headers };
806
+ if (bodyEditor && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
807
+ try {
808
+ const parsed = JSON.parse(bodyEditor.value);
809
+ options.body = JSON.stringify(parsed);
810
+ } catch (e) {
811
+ alert('Invalid JSON in Request Body');
812
+ throw new Error('Invalid JSON');
813
+ }
814
+ }
815
+ const start = performance.now();
816
+ const res = await fetch(url, options);
817
+ const duration = Math.round(performance.now() - start);
818
+ let data;
819
+ const ct = res.headers.get('content-type') || '';
820
+ if (ct.includes('application/json')) data = await res.json(); else data = await res.text();
821
+ const statusClass = res.ok ? 'status-2xx' : (String(res.status).startsWith('5') ? 'status-5xx' : 'status-4xx');
822
+ responseArea.style.display = 'block';
823
+ responseArea.innerHTML = `
824
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
825
+ <span class="status-badge ${statusClass}">${res.status} ${escapeHtml(res.statusText)}</span>
826
+ <span style="font-family:var(--font-mono);font-size:0.8rem;color:var(--text-muted)">${duration}ms</span>
827
+ </div>
828
+ <div class="section-header">Response Body</div>
829
+ <div class="code-block">${escapeHtml(typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data))}</div>
830
+ <div class="section-header">Response Headers</div>
831
+ <div class="code-block">${escapeHtml(Array.from(res.headers.entries()).map(([k, v]) => `${k}: ${v}`).join('\\n'))}</div>
832
+ `;
833
+ } catch (err) {
834
+ responseArea.style.display = 'block';
835
+ responseArea.innerHTML = `<div style="color:var(--danger);font-weight:600">Error: ${escapeHtml(err.message || String(err))}</div>`;
836
+ } finally {
837
+ btn.disabled = false;
838
+ btn.innerHTML = originalHtml;
839
+ }
840
+ }
841
+
842
+ function toggleCard(header) {
843
+ const isExpanded = header.getAttribute('aria-expanded') === 'true';
844
+ header.setAttribute('aria-expanded', String(!isExpanded));
845
+ }
846
+
847
+ function scrollEndpoint(sanitizedId) {
848
+ const el = document.getElementById(sanitizedId);
849
+ if (!el) return;
850
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });
851
+ const header = el.querySelector('.card-header');
852
+ if (header && header.getAttribute('aria-expanded') !== 'true') {
853
+ header.setAttribute('aria-expanded', 'true');
854
+ }
855
+ }
856
+
857
+ function setupSearch() {
858
+ const input = document.getElementById('searchNav');
859
+ input.addEventListener('input', (e) => {
860
+ const val = e.target.value.toLowerCase();
861
+ document.querySelectorAll('.nav-item').forEach(el => {
862
+ const filter = el.dataset.filter || '';
863
+ el.style.display = filter.includes(val) ? 'flex' : 'none';
864
+ });
865
+ });
866
+ }
867
+
868
+ function attachGlobalListeners() {
869
+ document.querySelectorAll('.endpoint-card .card-header').forEach(header => {
870
+ header.addEventListener('click', () => toggleCard(header));
871
+ });
872
+ document.querySelectorAll('.action-btn').forEach(btn => {
873
+ btn.addEventListener('click', () => executeRequest(btn.dataset.method, btn.dataset.path, btn));
874
+ });
875
+ }
876
+
877
+ function escapeHtml(str) {
878
+ if (str === undefined || str === null) return '';
879
+ return String(str)
880
+ .replace(/&/g, '&amp;')
881
+ .replace(/</g, '&lt;')
882
+ .replace(/>/g, '&gt;')
883
+ .replace(/"/g, '&quot;')
884
+ .replace(/'/g, '&#39;');
885
+ }
886
+ function escapeHtmlAttr(str) {
887
+ return escapeHtml(str).replace(/"/g, '&quot;').replace(/'/g, '&#39;');
888
+ }
889
+
890
+ init();
891
+ </script>
892
+ </body>
893
+
894
+ </html>