express-file-cluster 0.2.1 → 0.2.3

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.
package/dist/index.cjs CHANGED
@@ -1,21 +1,23 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkQB3X6LKZcjs = require('./chunk-QB3X6LKZ.cjs');
3
+ var _chunkUUF2OYRWcjs = require('./chunk-UUF2OYRW.cjs');
4
4
 
5
5
 
6
6
 
7
7
 
8
- var _chunkUDE6S53Ccjs = require('./chunk-UDE6S53C.cjs');
8
+ var _chunkXH7O25BLcjs = require('./chunk-XH7O25BL.cjs');
9
9
 
10
10
 
11
11
  var _chunkMQNVNOH5cjs = require('./chunk-MQNVNOH5.cjs');
12
12
 
13
13
  // src/index.ts
14
+ var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
14
15
  var _express = require('express'); var _express2 = _interopRequireDefault(_express);
15
16
  var _cors = require('cors'); var _cors2 = _interopRequireDefault(_cors);
16
17
  var _cookieparser = require('cookie-parser'); var _cookieparser2 = _interopRequireDefault(_cookieparser);
17
18
  var _cluster = require('cluster'); var _cluster2 = _interopRequireDefault(_cluster);
18
19
  var _os = require('os'); var _os2 = _interopRequireDefault(_os);
20
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
19
21
 
20
22
  // src/router/mount.ts
21
23
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
@@ -25,9 +27,12 @@ function asyncWrap(handler) {
25
27
  };
26
28
  }
27
29
  async function mountRoutes(app, routes) {
30
+ const mounted = [];
28
31
  for (const route of routes) {
29
32
  const mod = await Promise.resolve().then(() => _interopRequireWildcard(require(route.filePath)));
30
33
  const routeMiddlewares = Array.isArray(mod["middlewares"]) ? mod["middlewares"] : [];
34
+ const raw = mod["meta"];
35
+ const meta = raw != null && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
31
36
  const implemented = [];
32
37
  const unimplemented = [];
33
38
  for (const method of HTTP_METHODS) {
@@ -52,7 +57,9 @@ async function mountRoutes(app, routes) {
52
57
  }
53
58
  });
54
59
  }
60
+ mounted.push({ ...route, methods: implemented, ...meta !== void 0 && { meta } });
55
61
  }
62
+ return mounted;
56
63
  }
57
64
 
58
65
  // src/cluster/index.ts
@@ -96,9 +103,7 @@ async function loadMongoose() {
96
103
  mongoose = await Promise.resolve().then(() => _interopRequireWildcard(require("mongoose")));
97
104
  return mongoose;
98
105
  } catch (e) {
99
- throw new Error(
100
- "[EFC] mongoose is not installed. Run: npm install mongoose"
101
- );
106
+ throw new Error("[EFC] mongoose is not installed. Run: npm install mongoose");
102
107
  }
103
108
  }
104
109
  async function connectMongo(url) {
@@ -220,8 +225,8 @@ var db = new Proxy({}, {
220
225
  });
221
226
 
222
227
  // src/tasks/scanner.ts
223
- var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
224
- var _path = require('path'); var _path2 = _interopRequireDefault(_path);
228
+
229
+
225
230
  async function scanTasks(tasksDir) {
226
231
  if (!_fs2.default.existsSync(tasksDir)) return;
227
232
  const files = _fs2.default.readdirSync(tasksDir).filter((f) => /\.(ts|js|mts|mjs)$/.test(f));
@@ -235,7 +240,7 @@ async function scanTasks(tasksDir) {
235
240
  console.warn(`[EFC] Task file ${file} has no valid default export \u2014 skipping`);
236
241
  continue;
237
242
  }
238
- _chunkUDE6S53Ccjs.registerTask.call(void 0, taskName, { ...def, filePath });
243
+ _chunkXH7O25BLcjs.registerTask.call(void 0, taskName, { ...def, filePath });
239
244
  console.log(`[EFC] Registered task: ${taskName}`);
240
245
  } catch (err) {
241
246
  console.warn(`[EFC] Failed to load task ${file}:`, err);
@@ -290,7 +295,7 @@ async function initBullMQ(opts) {
290
295
  const worker = new bullmq.Worker(
291
296
  "efc",
292
297
  async (job) => {
293
- const def = _chunkUDE6S53Ccjs.taskRegistry.get(job.name);
298
+ const def = _chunkXH7O25BLcjs.taskRegistry.get(job.name);
294
299
  if (!def) {
295
300
  throw new Error(`[EFC] No task registered for name: ${job.name}`);
296
301
  }
@@ -308,10 +313,12 @@ async function initBullMQ(opts) {
308
313
  worker.on("completed", (job) => {
309
314
  console.log(`[EFC] Task ${job.name} completed (id=${job.id})`);
310
315
  });
311
- _chunkUDE6S53Ccjs.setEnqueueImpl.call(void 0, async (name, payload) => {
312
- const def = _chunkUDE6S53Ccjs.taskRegistry.get(name);
316
+ _chunkXH7O25BLcjs.setEnqueueImpl.call(void 0, async (name, payload) => {
317
+ const def = _chunkXH7O25BLcjs.taskRegistry.get(name);
313
318
  if (!def) {
314
- throw new Error(`[EFC] Cannot enqueue unknown task: "${name}". Is the task file in tasksDir?`);
319
+ throw new Error(
320
+ `[EFC] Cannot enqueue unknown task: "${name}". Is the task file in tasksDir?`
321
+ );
315
322
  }
316
323
  await queue.add(name, payload, {
317
324
  attempts: _nullishCoalesce(def.options.retries, () => ( 3)),
@@ -337,6 +344,568 @@ function parseRedisUrl(url) {
337
344
  }
338
345
  }
339
346
 
347
+ // src/dashboard.ts
348
+ var HTTP_STATUS = {
349
+ 200: "OK",
350
+ 201: "Created",
351
+ 202: "Accepted",
352
+ 204: "No Content",
353
+ 400: "Bad Request",
354
+ 401: "Unauthorized",
355
+ 403: "Forbidden",
356
+ 404: "Not Found",
357
+ 405: "Method Not Allowed",
358
+ 409: "Conflict",
359
+ 422: "Unprocessable Entity",
360
+ 500: "Internal Server Error",
361
+ 503: "Service Unavailable"
362
+ };
363
+ function generateDashboard(routes, basePath, port, projectName, projectVersion) {
364
+ const safeRoutes = JSON.stringify(
365
+ routes.map((r) => ({ urlPath: r.urlPath, methods: r.methods, params: r.params, meta: _nullishCoalesce(r.meta, () => ( null)) }))
366
+ ).replace(/<\/script>/gi, "<\\/script>");
367
+ const safeHttpStatus = JSON.stringify(HTTP_STATUS);
368
+ return `<!DOCTYPE html>
369
+ <html lang="en">
370
+ <head>
371
+ <meta charset="UTF-8" />
372
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
373
+ <title>${projectName} \u2014 API Reference</title>
374
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
375
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
376
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
377
+ <style>
378
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
379
+ :root {
380
+ --bg: #080b0f;
381
+ --bg-card: #0d1117;
382
+ --bg-raised: #111820;
383
+ --border: #1e2832;
384
+ --border-lit: #263040;
385
+ --text: #e2e8f0;
386
+ --text-dim: #64748b;
387
+ --text-muted: #334155;
388
+ --accent: #F0A030;
389
+ --accent-dim: rgba(240,160,48,.12);
390
+ --accent-glow: rgba(240,160,48,.22);
391
+ --purple: #a78bfa;
392
+ --blue: #60a5fa;
393
+ --orange: #fb923c;
394
+ --green: #7AAE8E;
395
+ --radius: 10px;
396
+ --radius-lg: 16px;
397
+ --font-mono: 'JetBrains Mono', monospace;
398
+ --font-sans: 'Inter', sans-serif;
399
+ }
400
+ html { scroll-behavior: smooth; }
401
+ body {
402
+ background: var(--bg); color: var(--text);
403
+ font-family: var(--font-sans); font-size: 16px;
404
+ line-height: 1.6; -webkit-font-smoothing: antialiased;
405
+ position: relative;
406
+ }
407
+ body::before {
408
+ content: ''; position: fixed; inset: 0; z-index: 0;
409
+ background-image:
410
+ linear-gradient(var(--border) 1px, transparent 1px),
411
+ linear-gradient(90deg, var(--border) 1px, transparent 1px);
412
+ background-size: 60px 60px; opacity: .28; pointer-events: none;
413
+ }
414
+ section, footer, .page-hero, .docs-layout { position: relative; z-index: 1; }
415
+ a { text-decoration: none; color: inherit; }
416
+ em { font-style: italic; }
417
+ code {
418
+ font-family: var(--font-mono); font-size: .85em;
419
+ background: var(--bg-raised); border: 1px solid var(--border);
420
+ border-radius: 4px; padding: 1px 6px; color: var(--accent);
421
+ }
422
+ .container { max-width: 1140px; margin: 0 auto; padding: 0 24px; }
423
+ ::-webkit-scrollbar { width: 6px; }
424
+ ::-webkit-scrollbar-track { background: var(--bg); }
425
+ ::-webkit-scrollbar-thumb { background: var(--border-lit); border-radius: 3px; }
426
+
427
+ /* \u2500\u2500 PAGE HERO \u2500\u2500 */
428
+ .page-hero {
429
+ padding: 48px 0 56px; border-bottom: 1px solid var(--border);
430
+ }
431
+ .page-hero .hero-glow {
432
+ position: absolute; top: -40px; left: 10%; width: 500px; height: 350px;
433
+ background: radial-gradient(ellipse, rgba(240,160,48,.1) 0%, transparent 68%);
434
+ pointer-events: none;
435
+ }
436
+ .page-title {
437
+ font-size: clamp(2rem, 5vw, 3.4rem); font-weight: 800;
438
+ line-height: 1.1; letter-spacing: -.02em; margin-bottom: 24px;
439
+ display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
440
+ }
441
+ .project-version {
442
+ font-family: var(--font-mono); font-size: clamp(.85rem, 2vw, 1.1rem);
443
+ font-weight: 500; color: var(--accent);
444
+ background: var(--accent-dim); border: 1px solid rgba(240,160,48,.3);
445
+ border-radius: 6px; padding: 2px 10px; letter-spacing: .5px;
446
+ }
447
+ .hero-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 40px; }
448
+ .base-url-pill {
449
+ display: flex; align-items: center; gap: 8px;
450
+ background: var(--bg-card); border: 1px solid var(--border);
451
+ border-radius: 7px; padding: 8px 14px;
452
+ }
453
+ .base-url-label { font-size: .72rem; font-family: var(--font-mono);
454
+ color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
455
+ .base-url-val { font-family: var(--font-mono); font-size: .8rem; color: var(--accent); }
456
+ .count-pill {
457
+ font-family: var(--font-mono); font-size: .72rem; color: var(--text-dim);
458
+ background: var(--bg-card); border: 1px solid var(--border);
459
+ border-radius: 7px; padding: 8px 14px;
460
+ }
461
+
462
+ /* \u2500\u2500 TOOLBAR \u2500\u2500 */
463
+ .toolbar {
464
+ display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
465
+ }
466
+ .method-filters { display: flex; gap: 4px; flex-wrap: wrap; }
467
+ .filter-btn {
468
+ background: var(--bg-card); border: 1px solid var(--border);
469
+ color: var(--text-dim); font-family: var(--font-mono);
470
+ font-size: .72rem; font-weight: 600; letter-spacing: .5px;
471
+ padding: 6px 12px; border-radius: 6px; cursor: pointer;
472
+ transition: all .15s;
473
+ }
474
+ .filter-btn:hover { border-color: var(--border-lit); color: var(--text); }
475
+ .filter-btn.active { background: var(--accent); color: #0d0d1a; border-color: var(--accent); }
476
+ .filter-btn[data-method="GET"].active { background: rgba(96,165,250,.18); color: #60a5fa; border-color: rgba(96,165,250,.4); }
477
+ .filter-btn[data-method="POST"].active { background: rgba(122,174,142,.18); color: #7AAE8E; border-color: rgba(122,174,142,.4); }
478
+ .filter-btn[data-method="PUT"].active { background: rgba(240,160,48,.18); color: #F0A030; border-color: rgba(240,160,48,.4); }
479
+ .filter-btn[data-method="PATCH"].active { background: rgba(251,191,36,.18); color: #fbbf24; border-color: rgba(251,191,36,.4); }
480
+ .filter-btn[data-method="DELETE"].active { background: rgba(248,113,113,.18); color: #f87171; border-color: rgba(248,113,113,.4); }
481
+ .filter-btn[data-method="HEAD"].active { background: rgba(167,139,250,.18); color: #a78bfa; border-color: rgba(167,139,250,.4); }
482
+ .search-wrap { margin-left: auto; }
483
+ .search-input {
484
+ background: var(--bg-card); border: 1px solid var(--border);
485
+ color: var(--text); font-family: var(--font-sans); font-size: .85rem;
486
+ padding: 7px 14px; border-radius: 7px; width: 240px;
487
+ outline: none; transition: border-color .2s;
488
+ }
489
+ .search-input::placeholder { color: var(--text-muted); }
490
+ .search-input:focus { border-color: var(--border-lit); }
491
+
492
+ /* \u2500\u2500 DOCS LAYOUT \u2500\u2500 */
493
+ .docs-layout {
494
+ display: grid; grid-template-columns: 220px 1fr;
495
+ gap: 48px; padding: 52px 0 80px; max-width: 1140px;
496
+ margin: 0 auto; padding-left: 24px; padding-right: 24px;
497
+ }
498
+
499
+ /* \u2500\u2500 SIDEBAR \u2500\u2500 */
500
+ .docs-sidebar { position: sticky; top: 24px; height: fit-content; }
501
+ .sidebar-section { margin-bottom: 28px; }
502
+ .sidebar-label {
503
+ font-family: var(--font-mono); font-size: .68rem; font-weight: 600;
504
+ color: var(--text-muted); letter-spacing: 1.2px; text-transform: uppercase;
505
+ margin-bottom: 8px; padding: 0 4px;
506
+ }
507
+ .sidebar-link {
508
+ display: flex; align-items: center; gap: 8px;
509
+ padding: 7px 10px; border-radius: 6px;
510
+ border-left: 2px solid transparent;
511
+ font-size: .82rem; color: var(--text-dim); cursor: pointer;
512
+ transition: background .15s, color .15s, border-color .15s;
513
+ text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
514
+ }
515
+ .sidebar-link:hover { background: var(--bg-raised); color: var(--text); }
516
+ .sidebar-link.active { color: var(--accent); border-left-color: var(--accent); background: var(--accent-dim); }
517
+ .sidebar-method-dot {
518
+ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
519
+ }
520
+ .sidebar-path { font-family: var(--font-mono); font-size: .75rem; }
521
+
522
+ /* \u2500\u2500 ENDPOINT CARDS \u2500\u2500 */
523
+ .endpoint-card {
524
+ background: var(--bg-card); border: 1px solid var(--border);
525
+ border-radius: var(--radius-lg); margin-bottom: 20px;
526
+ overflow: hidden; transition: border-color .2s;
527
+ animation: fadeUp .45s ease both;
528
+ }
529
+ .endpoint-card:hover { border-color: var(--border-lit); }
530
+ @keyframes fadeUp {
531
+ from { opacity: 0; transform: translateY(16px); }
532
+ to { opacity: 1; transform: translateY(0); }
533
+ }
534
+ @media (prefers-reduced-motion: reduce) {
535
+ .endpoint-card { animation: none; }
536
+ }
537
+ .endpoint-header {
538
+ display: flex; align-items: center; gap: 14px;
539
+ padding: 20px 24px 18px; border-bottom: 1px solid var(--border);
540
+ flex-wrap: wrap;
541
+ }
542
+ .method-badges { display: flex; gap: 6px; flex-wrap: wrap; }
543
+ .method-badge {
544
+ font-family: var(--font-mono); font-size: .7rem; font-weight: 600;
545
+ letter-spacing: .8px; padding: 4px 10px; border-radius: 5px;
546
+ border: 1px solid; text-transform: uppercase;
547
+ }
548
+ .endpoint-path {
549
+ font-family: var(--font-mono); font-size: .9rem; color: var(--text);
550
+ background: none; border: none; padding: 0;
551
+ word-break: break-all;
552
+ }
553
+ .endpoint-body { padding: 18px 24px 24px; }
554
+ .endpoint-desc { font-size: .9rem; color: var(--text-dim); line-height: 1.7; margin-bottom: 20px; }
555
+
556
+ /* \u2500\u2500 CODE BLOCKS \u2500\u2500 */
557
+ .example-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
558
+ .code-block-wrap {
559
+ background: var(--bg-raised); border: 1px solid var(--border);
560
+ border-radius: var(--radius); overflow: hidden;
561
+ }
562
+ .code-bar {
563
+ display: flex; align-items: center; gap: 6px;
564
+ padding: 9px 14px; border-bottom: 1px solid var(--border);
565
+ background: var(--bg-card);
566
+ }
567
+ .dot { width: 11px; height: 11px; border-radius: 50%; }
568
+ .dot-r { background: #ff5f57; }
569
+ .dot-y { background: #febc2e; }
570
+ .dot-g { background: #28c840; }
571
+ .code-label { font-family: var(--font-mono); font-size: .68rem; color: var(--text-dim); margin-left: 4px; }
572
+ pre.code-pre {
573
+ font-family: var(--font-mono); font-size: .78rem; line-height: 1.7;
574
+ padding: 16px 18px; overflow-x: auto; tab-size: 2; white-space: pre;
575
+ }
576
+ .c-dim { color: var(--text-muted); }
577
+ .c-green { color: #7AAE8E; }
578
+ .c-blue { color: #60a5fa; }
579
+ .c-orange { color: #fb923c; }
580
+ .c-purple { color: #a78bfa; }
581
+ .c-accent { color: var(--accent); }
582
+ .status-2xx { color: #7AAE8E; }
583
+ .status-4xx { color: #f87171; }
584
+ .status-5xx { color: #fb923c; }
585
+
586
+ /* \u2500\u2500 EMPTY STATE \u2500\u2500 */
587
+ .empty-state {
588
+ text-align: center; padding: 80px 24px;
589
+ color: var(--text-muted); font-size: .9rem;
590
+ }
591
+
592
+ /* \u2500\u2500 RESPONSIVE \u2500\u2500 */
593
+ @media (max-width: 900px) {
594
+ .docs-layout { grid-template-columns: 1fr; }
595
+ .docs-sidebar { position: static; }
596
+ .example-grid { grid-template-columns: 1fr; }
597
+ .search-wrap { margin-left: 0; width: 100%; }
598
+ .search-input { width: 100%; }
599
+ }
600
+ @media (max-width: 600px) {
601
+ .page-hero { padding: 32px 0 32px; }
602
+ .page-title { font-size: 2rem; }
603
+ }
604
+ </style>
605
+ </head>
606
+ <body>
607
+
608
+ <div class="page-hero">
609
+ <div class="hero-glow"></div>
610
+ <div class="container">
611
+ <h1 class="page-title">${projectName}${projectVersion ? `<span class="project-version">v${projectVersion}</span>` : ""}</h1>
612
+ <div class="hero-meta">
613
+ <div class="base-url-pill">
614
+ <span class="base-url-label">Base</span>
615
+ <span class="base-url-val">http://localhost:${port}${basePath}</span>
616
+ </div>
617
+ <div class="count-pill" id="countPill"></div>
618
+ </div>
619
+ <div class="toolbar">
620
+ <div class="method-filters" id="methodFilters">
621
+ <button class="filter-btn active" data-method="ALL">ALL</button>
622
+ </div>
623
+ <div class="search-wrap">
624
+ <input type="text" id="searchInput" class="search-input" placeholder="Search endpoints\u2026" />
625
+ </div>
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ <div class="docs-layout">
631
+ <aside class="docs-sidebar" id="sidebar"></aside>
632
+ <main id="endpoints"></main>
633
+ </div>
634
+
635
+ <script>
636
+ const ROUTES = ${safeRoutes};
637
+ const BASE_PATH = ${JSON.stringify(basePath)};
638
+ const HTTP_STATUS = ${safeHttpStatus};
639
+
640
+ const METHOD_STYLE = {
641
+ GET: { bg: 'rgba(96,165,250,.14)', color: '#60a5fa', border: 'rgba(96,165,250,.35)' },
642
+ POST: { bg: 'rgba(122,174,142,.14)', color: '#7AAE8E', border: 'rgba(122,174,142,.35)' },
643
+ PUT: { bg: 'rgba(240,160,48,.14)', color: '#F0A030', border: 'rgba(240,160,48,.35)' },
644
+ PATCH: { bg: 'rgba(251,191,36,.14)', color: '#fbbf24', border: 'rgba(251,191,36,.35)' },
645
+ DELETE: { bg: 'rgba(248,113,113,.14)', color: '#f87171', border: 'rgba(248,113,113,.35)' },
646
+ HEAD: { bg: 'rgba(167,139,250,.14)', color: '#a78bfa', border: 'rgba(167,139,250,.35)' },
647
+ OPTIONS: { bg: 'rgba(148,163,184,.14)', color: '#94a3b8', border: 'rgba(148,163,184,.35)' },
648
+ };
649
+
650
+ function esc(s) {
651
+ return String(s)
652
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;')
653
+ .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
654
+ }
655
+
656
+ function methodBadge(m) {
657
+ const s = METHOD_STYLE[m] || METHOD_STYLE.GET;
658
+ return '<span class="method-badge" style="background:' + s.bg + ';color:' + s.color + ';border-color:' + s.border + '">' + m + '</span>';
659
+ }
660
+
661
+ function joinPath(base, p) {
662
+ return (base.replace(/\\/+$/, '') + p) || '/';
663
+ }
664
+
665
+ function sidebarDot(method) {
666
+ const s = METHOD_STYLE[method] || METHOD_STYLE.GET;
667
+ return '<span class="sidebar-method-dot" style="background:' + s.color + '"></span>';
668
+ }
669
+
670
+ function statusClass(code) {
671
+ if (code >= 200 && code < 300) return 'status-2xx';
672
+ if (code >= 400 && code < 500) return 'status-4xx';
673
+ if (code >= 500) return 'status-5xx';
674
+ return '';
675
+ }
676
+
677
+ function renderSchema(val, indent) {
678
+ const pad = ' '.repeat(indent || 0);
679
+ if (val === null) return '<span class="c-dim">null</span>';
680
+ if (typeof val === 'string') {
681
+ const isDate = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(val);
682
+ return isDate ? '<span class="c-green">Date</span>' : '<span class="c-purple">String</span>';
683
+ }
684
+ if (typeof val === 'number') return '<span class="c-orange">Number</span>';
685
+ if (typeof val === 'boolean') return '<span class="c-purple">Boolean</span>';
686
+ if (Array.isArray(val)) {
687
+ if (!val.length) return '[]';
688
+ return '[\\n' + pad + ' ' + renderSchema(val[0], (indent || 0) + 1) + '\\n' + pad + ']';
689
+ }
690
+ if (typeof val === 'object') {
691
+ const keys = Object.keys(val);
692
+ if (!keys.length) return '{}';
693
+ const inner = keys.map(function(k) {
694
+ return pad + ' <span class="c-blue">&quot;' + esc(k) + '&quot;</span>: ' + renderSchema(val[k], (indent || 0) + 1);
695
+ }).join(',\\n');
696
+ return '{\\n' + inner + '\\n' + pad + '}';
697
+ }
698
+ return esc(String(val));
699
+ }
700
+
701
+ function codeBlock(label, content) {
702
+ return '<div class="code-block-wrap">' +
703
+ '<div class="code-bar">' +
704
+ '<span class="dot dot-r"></span>' +
705
+ '<span class="dot dot-y"></span>' +
706
+ '<span class="dot dot-g"></span>' +
707
+ '<span class="code-label">' + esc(label) + '</span>' +
708
+ '</div>' +
709
+ '<pre class="code-pre">' + content + '</pre>' +
710
+ '</div>';
711
+ }
712
+
713
+ function buildRequestContent(route, example) {
714
+ const req = example || {};
715
+ const method = route.methods[0] || 'GET';
716
+ let path = joinPath(BASE_PATH, route.urlPath);
717
+ if (req.params) {
718
+ for (const [k, v] of Object.entries(req.params)) {
719
+ path = path.replace(':' + k, String(v));
720
+ }
721
+ }
722
+ if (req.query && Object.keys(req.query).length) {
723
+ path += '?' + new URLSearchParams(req.query).toString();
724
+ }
725
+ let lines = '<span class="c-accent">' + esc(method) + '</span> <span class="c-blue">' + esc(path) + '</span>';
726
+ const headers = { ...(req.headers || {}) };
727
+ if (req.body !== undefined && !headers['Content-Type']) {
728
+ headers['Content-Type'] = 'application/json';
729
+ }
730
+ if (Object.keys(headers).length) {
731
+ lines += '\\n' + Object.entries(headers)
732
+ .map(([k, v]) => '<span class="c-dim">' + esc(k) + ':</span> ' + esc(String(v)))
733
+ .join('\\n');
734
+ }
735
+ if (req.body !== undefined) {
736
+ lines += '\\n\\n' + renderSchema(req.body, 0);
737
+ }
738
+ return lines;
739
+ }
740
+
741
+ function buildResponseContent(example) {
742
+ const resp = example || {};
743
+ const code = resp.status || 200;
744
+ const text = HTTP_STATUS[code] || 'OK';
745
+ const cls = statusClass(code);
746
+ let lines = 'HTTP/1.1 <span class="' + cls + '">' + code + ' ' + esc(text) + '</span>';
747
+ if (resp.body !== undefined) {
748
+ lines += '\\n<span class="c-dim">Content-Type:</span> application/json\\n\\n';
749
+ lines += renderSchema(resp.body, 0);
750
+ }
751
+ return lines;
752
+ }
753
+
754
+ function renderCard(route, index) {
755
+ const id = 'ep-' + index;
756
+ const fullPath = joinPath(BASE_PATH, route.urlPath);
757
+ const badges = route.methods.map(methodBadge).join('');
758
+ const meta = route.meta;
759
+ const desc = meta && meta.description
760
+ ? '<p class="endpoint-desc">' + esc(meta.description) + '</p>'
761
+ : '';
762
+ let examples = '';
763
+ if (meta) {
764
+ const reqHtml = meta.request !== undefined
765
+ ? codeBlock('request', buildRequestContent(route, meta.request))
766
+ : '';
767
+ const respHtml = meta.response !== undefined
768
+ ? codeBlock('response', buildResponseContent(meta.response))
769
+ : '';
770
+ if (reqHtml || respHtml) {
771
+ examples = '<div class="example-grid">' + reqHtml + respHtml + '</div>';
772
+ }
773
+ }
774
+ const delay = (index * 0.06).toFixed(2) + 's';
775
+ return '<div class="endpoint-card" id="' + id + '" data-methods="' + route.methods.join(',') + '" data-path="' + esc(fullPath) + '" style="animation-delay:' + delay + '">' +
776
+ '<div class="endpoint-header">' +
777
+ '<div class="method-badges">' + badges + '</div>' +
778
+ '<code class="endpoint-path">' + esc(fullPath) + '</code>' +
779
+ '</div>' +
780
+ '<div class="endpoint-body">' + desc + examples + '</div>' +
781
+ '</div>';
782
+ }
783
+
784
+ function buildMethodFilters() {
785
+ const allMethods = [...new Set(ROUTES.flatMap(r => r.methods))].sort();
786
+ const filtersEl = document.getElementById('methodFilters');
787
+ allMethods.forEach(m => {
788
+ const btn = document.createElement('button');
789
+ btn.className = 'filter-btn';
790
+ btn.dataset.method = m;
791
+ btn.textContent = m;
792
+ filtersEl.appendChild(btn);
793
+ });
794
+ }
795
+
796
+ function buildSidebar(routes) {
797
+ const sidebar = document.getElementById('sidebar');
798
+ if (!routes.length) { sidebar.innerHTML = ''; return; }
799
+
800
+ const label = document.createElement('div');
801
+ label.className = 'sidebar-label';
802
+ label.textContent = 'Endpoints';
803
+ sidebar.appendChild(label);
804
+
805
+ routes.forEach((route, i) => {
806
+ const link = document.createElement('div');
807
+ link.className = 'sidebar-link';
808
+ link.dataset.target = 'ep-' + ROUTES.indexOf(route);
809
+ link.innerHTML = sidebarDot(route.methods[0] || 'GET') +
810
+ '<span class="sidebar-path">' + esc(joinPath(BASE_PATH, route.urlPath)) + '</span>';
811
+ link.addEventListener('click', () => {
812
+ const target = document.getElementById(link.dataset.target);
813
+ if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
814
+ });
815
+ sidebar.appendChild(link);
816
+ });
817
+ }
818
+
819
+ let activeFilter = 'ALL';
820
+ let searchQuery = '';
821
+
822
+ function applyFilters() {
823
+ const cards = document.querySelectorAll('.endpoint-card');
824
+ let visible = 0;
825
+ cards.forEach(card => {
826
+ const methods = card.dataset.methods.split(',');
827
+ const path = card.dataset.path || '';
828
+ const matchMethod = activeFilter === 'ALL' || methods.includes(activeFilter);
829
+ const matchSearch = !searchQuery || path.toLowerCase().includes(searchQuery);
830
+ const show = matchMethod && matchSearch;
831
+ card.style.display = show ? '' : 'none';
832
+ if (show) visible++;
833
+ });
834
+ document.getElementById('empty-state').style.display = visible ? 'none' : 'block';
835
+
836
+ // Sync sidebar visibility
837
+ document.querySelectorAll('.sidebar-link').forEach(link => {
838
+ const targetId = link.dataset.target;
839
+ const targetCard = document.getElementById(targetId);
840
+ link.style.display = targetCard && targetCard.style.display !== 'none' ? '' : 'none';
841
+ });
842
+ }
843
+
844
+ function init() {
845
+ const count = ROUTES.length;
846
+ document.getElementById('countPill').textContent = count + ' endpoint' + (count !== 1 ? 's' : '');
847
+
848
+ buildMethodFilters();
849
+
850
+ const container = document.getElementById('endpoints');
851
+ container.innerHTML = ROUTES.map((r, i) => renderCard(r, i)).join('');
852
+
853
+ // Add empty state after cards
854
+ const emptyEl = document.createElement('div');
855
+ emptyEl.id = 'empty-state';
856
+ emptyEl.className = 'empty-state';
857
+ emptyEl.style.display = 'none';
858
+ emptyEl.innerHTML = '<p>No endpoints match your filter.</p>';
859
+ container.after(emptyEl);
860
+
861
+ buildSidebar(ROUTES);
862
+
863
+ // Method filter clicks
864
+ document.getElementById('methodFilters').addEventListener('click', e => {
865
+ const btn = e.target.closest('.filter-btn');
866
+ if (!btn) return;
867
+ document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
868
+ btn.classList.add('active');
869
+ activeFilter = btn.dataset.method;
870
+ applyFilters();
871
+ });
872
+
873
+ // Search
874
+ document.getElementById('searchInput').addEventListener('input', e => {
875
+ searchQuery = e.target.value.trim().toLowerCase();
876
+ applyFilters();
877
+ });
878
+
879
+ // Scroll spy for sidebar
880
+ const cardEls = document.querySelectorAll('.endpoint-card');
881
+ const sidebarLinks = document.querySelectorAll('.sidebar-link');
882
+ const observer = new IntersectionObserver(entries => {
883
+ entries.forEach(entry => {
884
+ if (entry.isIntersecting) {
885
+ const id = entry.target.id;
886
+ sidebarLinks.forEach(l => l.classList.toggle('active', l.dataset.target === id));
887
+ }
888
+ });
889
+ }, { rootMargin: '-80px 0px -60% 0px' });
890
+ cardEls.forEach(el => observer.observe(el));
891
+
892
+ }
893
+
894
+ try {
895
+ init();
896
+ } catch (e) {
897
+ document.body.insertAdjacentHTML('afterbegin',
898
+ '<div style="position:fixed;top:60px;left:0;right:0;z-index:9999;background:#7f1d1d;color:#fca5a5;padding:16px 24px;font-family:monospace;font-size:13px;border-bottom:1px solid #ef4444">' +
899
+ '<strong>Dashboard JS error:</strong> ' + (e && e.message ? e.message : String(e)) +
900
+ (e && e.stack ? '<pre style="margin-top:8px;white-space:pre-wrap">' + e.stack + '</pre>' : '') +
901
+ '</div>'
902
+ );
903
+ }
904
+ </script>
905
+ </body>
906
+ </html>`;
907
+ }
908
+
340
909
  // src/errors.ts
341
910
  var HttpError = class _HttpError extends Error {
342
911
 
@@ -383,12 +952,32 @@ async function ignite(config) {
383
952
  const {
384
953
  port: _port,
385
954
  workers,
386
- apiDir,
387
955
  globalMiddlewares = [],
388
956
  onWorkerReady,
389
957
  onWorkerCrash,
390
958
  onError
391
959
  } = config;
960
+ function resolveConventionDir(name) {
961
+ const candidates = [
962
+ ...process.argv[1] ? [_path2.default.join(_path2.default.dirname(process.argv[1]), name)] : [],
963
+ _path2.default.join(process.cwd(), "src", name),
964
+ _path2.default.join(process.cwd(), name),
965
+ _path2.default.join(process.cwd(), "dist", name)
966
+ ];
967
+ return _nullishCoalesce(candidates.find((c) => _fs2.default.existsSync(c)), () => ( candidates[0]));
968
+ }
969
+ const apiDir = resolveConventionDir("api");
970
+ const tasksDir = resolveConventionDir("tasks");
971
+ const basePath = _nullishCoalesce(config.basePath, () => ( "/v1/api"));
972
+ function readProjectMeta() {
973
+ try {
974
+ const raw = _fs2.default.readFileSync(_path2.default.join(process.cwd(), "package.json"), "utf8");
975
+ const pkg = JSON.parse(raw);
976
+ return { name: _nullishCoalesce(pkg.name, () => ( "API")), version: _nullishCoalesce(pkg.version, () => ( "")) };
977
+ } catch (e5) {
978
+ return { name: "API", version: "" };
979
+ }
980
+ }
392
981
  const envPort = process.env["PORT"] != null ? Number(process.env["PORT"]) : NaN;
393
982
  const port = _port != null && !Number.isNaN(_port) ? _port : !Number.isNaN(envPort) ? envPort : 3e3;
394
983
  const databaseUrl = _nullishCoalesce(config.databaseUrl, () => ( process.env["DATABASE_URL"]));
@@ -432,15 +1021,15 @@ async function ignite(config) {
432
1021
  }
433
1022
  if (jwtSecret) {
434
1023
  const cookieDomain = process.env["COOKIE_DOMAIN"];
435
- _chunkQB3X6LKZcjs.configureAuth.call(void 0, {
1024
+ _chunkUUF2OYRWcjs.configureAuth.call(void 0, {
436
1025
  secret: jwtSecret,
437
1026
  strategy: authStrategy,
438
1027
  expiresIn: _nullishCoalesce(process.env["JWT_EXPIRES_IN"], () => ( "7d")),
439
1028
  ...cookieDomain !== void 0 && { cookieDomain }
440
1029
  });
441
1030
  }
442
- if (config.tasksDir) {
443
- await scanTasks(config.tasksDir);
1031
+ if (tasksDir) {
1032
+ await scanTasks(tasksDir);
444
1033
  }
445
1034
  if (config.tasks) {
446
1035
  if (config.tasks.backend === "bullmq") {
@@ -451,7 +1040,16 @@ async function ignite(config) {
451
1040
  }
452
1041
  }
453
1042
  const routes = _chunkMQNVNOH5cjs.scanDir.call(void 0, apiDir);
454
- await mountRoutes(app, routes);
1043
+ const apiRouter = _express2.default.Router();
1044
+ const mounted = await mountRoutes(apiRouter, routes);
1045
+ app.use(basePath, apiRouter);
1046
+ if (config.dashboard && process.env["NODE_ENV"] === "development") {
1047
+ app.get("/", (_req, res) => {
1048
+ const { name, version } = readProjectMeta();
1049
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1050
+ res.send(generateDashboard(mounted, basePath, port, name, version));
1051
+ });
1052
+ }
455
1053
  if (onError) {
456
1054
  app.use(onError);
457
1055
  } else {