@forinda/kickjs-devtools 1.2.0 → 1.2.1

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.js CHANGED
@@ -1,17 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
3
 
10
4
  // src/index.ts
11
5
  import "reflect-metadata";
12
6
 
13
7
  // src/adapter.ts
14
- import { Router } from "express";
8
+ import { Router, static as serveStatic } from "express";
15
9
  import { dirname, join } from "path";
16
10
  import { fileURLToPath } from "url";
17
11
  import { existsSync, readFileSync } from "fs";
@@ -226,8 +220,7 @@ var DevToolsAdapter = class {
226
220
  }
227
221
  const publicDir = this.resolvePublicDir();
228
222
  if (publicDir) {
229
- const express = __require("express");
230
- router.use(express.static(publicDir));
223
+ router.use(serveStatic(publicDir));
231
224
  const indexHtml = readFileSync(join(publicDir, "index.html"), "utf-8");
232
225
  router.get("/", (_req, res) => {
233
226
  const html = indexHtml.replace("<body", `<body data-base="${this.basePath}"`);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/adapter.ts"],"sourcesContent":["import 'reflect-metadata'\n\nexport { DevToolsAdapter, type DevToolsOptions } from './adapter'\n","import type { Request, Response, NextFunction } from 'express'\nimport { Router } from 'express'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { randomBytes } from 'node:crypto'\nimport {\n type AppAdapter,\n type AdapterMiddleware,\n type Container,\n METADATA,\n ref,\n computed,\n reactive,\n watch,\n createLogger,\n type Ref,\n type ComputedRef,\n} from '@forinda/kickjs-core'\n\nconst log = createLogger('DevTools')\n\n/** Route metadata collected during mount */\ninterface RouteInfo {\n method: string\n path: string\n controller: string\n handler: string\n middleware: string[]\n}\n\n/** Per-route latency stats */\ninterface RouteStats {\n count: number\n totalMs: number\n minMs: number\n maxMs: number\n}\n\nexport interface DevToolsOptions {\n /** Base path for debug endpoints (default: '/_debug') */\n basePath?: string\n /** Only enable when this is true (default: process.env.NODE_ENV !== 'production') */\n enabled?: boolean\n /** Include environment variables (sanitized) at /_debug/config (default: false) */\n exposeConfig?: boolean\n /** Env var prefixes to expose (default: ['APP_', 'NODE_ENV']). Others are redacted. */\n configPrefixes?: string[]\n /** Callback when error rate exceeds threshold */\n onErrorRateExceeded?: (rate: number) => void\n /** Error rate threshold (default: 0.5 = 50%) */\n errorRateThreshold?: number\n /** Other adapters to discover stats from (e.g., WsAdapter) */\n adapters?: any[]\n\n /**\n * Secret token to guard DevTools access. When set, all requests must\n * include this token as `x-devtools-token` header or `?token=` query param.\n *\n * Auto-generated on startup if not provided. The token is logged to the console.\n * Set to `false` to disable the guard entirely (not recommended).\n *\n * @example\n * ```ts\n * new DevToolsAdapter({ secret: process.env.DEVTOOLS_SECRET })\n * ```\n */\n secret?: string | false\n}\n\n/**\n * DevToolsAdapter — Vue-style reactive introspection for KickJS applications.\n *\n * Exposes debug endpoints powered by reactive state (ref, computed, watch):\n * - `GET /_debug/routes` — all registered routes with middleware\n * - `GET /_debug/container` — DI registry with scopes and instantiation status\n * - `GET /_debug/metrics` — live request/error counts, error rate, uptime\n * - `GET /_debug/health` — deep health check with adapter status\n * - `GET /_debug/config` — sanitized environment variables (opt-in)\n * - `GET /_debug/state` — full reactive state snapshot\n *\n * @example\n * ```ts\n * import { DevToolsAdapter } from '@forinda/kickjs-devtools'\n *\n * bootstrap({\n * modules: [UserModule],\n * adapters: [\n * new DevToolsAdapter({\n * enabled: process.env.NODE_ENV !== 'production',\n * exposeConfig: true,\n * configPrefixes: ['APP_', 'DATABASE_'],\n * }),\n * ],\n * })\n * ```\n */\nexport class DevToolsAdapter implements AppAdapter {\n readonly name = 'DevToolsAdapter'\n\n private basePath: string\n private enabled: boolean\n private exposeConfig: boolean\n private configPrefixes: string[]\n private errorRateThreshold: number\n private secret: string | false\n\n // ── Reactive State ───────────────────────────────────────────────────\n /** Total requests received */\n readonly requestCount: Ref<number>\n /** Total responses with status >= 500 */\n readonly errorCount: Ref<number>\n /** Total responses with status >= 400 and < 500 */\n readonly clientErrorCount: Ref<number>\n /** Server start time */\n readonly startedAt: Ref<number>\n /** Computed error rate (server errors / total requests) */\n readonly errorRate: ComputedRef<number>\n /** Computed uptime in seconds */\n readonly uptimeSeconds: ComputedRef<number>\n /** Per-route latency tracking */\n readonly routeLatency: Record<string, RouteStats>\n\n // ── Internal State ───────────────────────────────────────────────────\n private routes: RouteInfo[] = []\n private container: Container | null = null\n private adapterStatuses: Record<string, string> = {}\n private stopErrorWatch: (() => void) | null = null\n private peerAdapters: any[] = []\n\n constructor(options: DevToolsOptions = {}) {\n this.basePath = options.basePath ?? '/_debug'\n this.enabled = options.enabled ?? process.env.NODE_ENV !== 'production'\n this.exposeConfig = options.exposeConfig ?? false\n this.configPrefixes = options.configPrefixes ?? ['APP_', 'NODE_ENV']\n this.errorRateThreshold = options.errorRateThreshold ?? 0.5\n this.peerAdapters = options.adapters ?? []\n\n // Secret token guard\n if (options.secret === false) {\n this.secret = false\n } else if (options.secret) {\n this.secret = options.secret\n } else {\n // Auto-generate a random token\n this.secret = randomBytes(16).toString('hex')\n }\n\n // Initialize reactive state\n this.requestCount = ref(0)\n this.errorCount = ref(0)\n this.clientErrorCount = ref(0)\n this.startedAt = ref(Date.now())\n this.routeLatency = reactive({})\n\n this.errorRate = computed(() =>\n this.requestCount.value > 0 ? this.errorCount.value / this.requestCount.value : 0,\n )\n\n this.uptimeSeconds = computed(() => Math.floor((Date.now() - this.startedAt.value) / 1000))\n\n // Watch error rate — log warnings when elevated\n if (options.onErrorRateExceeded) {\n const callback = options.onErrorRateExceeded\n const threshold = this.errorRateThreshold\n this.stopErrorWatch = watch(this.errorRate, (rate) => {\n if (rate > threshold) {\n callback(rate)\n }\n })\n } else {\n this.stopErrorWatch = watch(this.errorRate, (rate) => {\n if (rate > this.errorRateThreshold) {\n log.warn(`Error rate elevated: ${(rate * 100).toFixed(1)}%`)\n }\n })\n }\n }\n\n // ── Adapter Lifecycle ────────────────────────────────────────────────\n\n beforeMount(app: any, container: Container): void {\n if (!this.enabled) return\n\n this.container = container\n this.startedAt.value = Date.now()\n // Clear routes on rebuild/restart to prevent HMR duplication\n this.routes = []\n this.adapterStatuses[this.name] = 'running'\n\n const router = Router()\n\n // ── Access guard — require secret token ──────────────────────────\n if (this.secret !== false) {\n const token = this.secret\n router.use((req: Request, res: Response, next: NextFunction) => {\n const provided = req.headers['x-devtools-token'] ?? req.query?.token\n if (provided === token) return next()\n // Allow the dashboard HTML itself (it will include the token in API calls)\n if (req.path === '/' && req.method === 'GET' && !req.query?.token) {\n return next() // serve dashboard, it handles auth via token\n }\n // Serve static assets for the dashboard (js files)\n if (req.path.endsWith('.js') || req.path.endsWith('.css')) {\n return next()\n }\n res.status(403).json({ error: 'Forbidden — invalid or missing devtools token' })\n })\n }\n\n router.get('/routes', (_req: Request, res: Response) => {\n res.json({ routes: this.routes })\n })\n\n router.get('/container', (_req: Request, res: Response) => {\n const registrations = this.container?.getRegistrations() ?? []\n res.json({ registrations, count: registrations.length })\n })\n\n router.get('/metrics', (_req: Request, res: Response) => {\n res.json({\n requests: this.requestCount.value,\n serverErrors: this.errorCount.value,\n clientErrors: this.clientErrorCount.value,\n errorRate: this.errorRate.value,\n uptimeSeconds: this.uptimeSeconds.value,\n startedAt: new Date(this.startedAt.value).toISOString(),\n routeLatency: this.routeLatency,\n })\n })\n\n router.get('/health', (_req: Request, res: Response) => {\n const healthy = this.errorRate.value < this.errorRateThreshold\n const status = healthy ? 'healthy' : 'degraded'\n\n res.status(healthy ? 200 : 503).json({\n status,\n errorRate: this.errorRate.value,\n uptime: this.uptimeSeconds.value,\n adapters: this.adapterStatuses,\n })\n })\n\n router.get('/state', (_req: Request, res: Response) => {\n const wsAdapter = this.peerAdapters.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n res.json({\n reactive: {\n requestCount: this.requestCount.value,\n errorCount: this.errorCount.value,\n clientErrorCount: this.clientErrorCount.value,\n errorRate: this.errorRate.value,\n uptimeSeconds: this.uptimeSeconds.value,\n startedAt: new Date(this.startedAt.value).toISOString(),\n },\n routes: this.routes.length,\n container: this.container?.getRegistrations().length ?? 0,\n routeLatency: this.routeLatency,\n ...(wsAdapter ? { ws: wsAdapter.getStats() } : {}),\n })\n })\n\n router.get('/ws', (_req: Request, res: Response) => {\n const wsAdapter = this.peerAdapters.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n if (!wsAdapter) {\n res.json({ enabled: false, message: 'WsAdapter not found' })\n return\n }\n res.json({ enabled: true, ...wsAdapter.getStats() })\n })\n\n router.get('/queues', async (_req: Request, res: Response) => {\n const queueAdapter = this.peerAdapters.find(\n (a) => a.name === 'QueueAdapter' && typeof a.getQueueNames === 'function',\n )\n if (!queueAdapter) {\n res.json({ enabled: false, message: 'QueueAdapter not found' })\n return\n }\n try {\n const names: string[] = queueAdapter.getQueueNames?.() ?? []\n const queues: any[] = []\n for (const name of names) {\n const stats = await queueAdapter.getQueueStats?.(name)\n queues.push({ name, ...stats })\n }\n res.json({ enabled: true, queues })\n } catch {\n res.json({ enabled: true, queues: [], error: 'Failed to fetch queue stats' })\n }\n })\n\n if (this.exposeConfig) {\n router.get('/config', (_req: Request, res: Response) => {\n const config: Record<string, string> = {}\n for (const [key, value] of Object.entries(process.env)) {\n if (value === undefined) continue\n const allowed = this.configPrefixes.some((prefix) => key.startsWith(prefix))\n config[key] = allowed ? value : '[REDACTED]'\n }\n res.json({ config })\n })\n }\n\n // Dashboard UI — Vue + Tailwind from public/devtools directory\n const publicDir = this.resolvePublicDir()\n if (publicDir) {\n // Serve static assets (vue.global.min.js, tailwind-cdn.js)\n const express = require('express')\n router.use(express.static(publicDir))\n\n // Serve index.html with base path injected\n const indexHtml = readFileSync(join(publicDir, 'index.html'), 'utf-8')\n router.get('/', (_req: Request, res: Response) => {\n // Inject basePath as data attribute for the Vue app\n const html = indexHtml.replace('<body', `<body data-base=\"${this.basePath}\"`)\n res.type('html').send(html)\n })\n } else {\n router.get('/', (_req: Request, res: Response) => {\n res.type('html').send('<h1>DevTools: public directory not found</h1>')\n })\n }\n\n app.use(this.basePath, router)\n\n if (this.secret) {\n log.info(`DevTools mounted at ${this.basePath} [token: ${this.secret}]`)\n log.info(`Access: ${this.basePath}?token=${this.secret}`)\n } else {\n log.info(`DevTools mounted at ${this.basePath} [no guard]`)\n }\n }\n\n middleware(): AdapterMiddleware[] {\n if (!this.enabled) return []\n\n return [\n {\n handler: (req: Request, res: Response, next: NextFunction) => {\n const start = Date.now()\n this.requestCount.value++\n\n res.on('finish', () => {\n if (res.statusCode >= 500) this.errorCount.value++\n else if (res.statusCode >= 400) this.clientErrorCount.value++\n\n // Track per-route latency\n const routeKey = `${req.method} ${req.route?.path ?? req.path}`\n const elapsed = Date.now() - start\n\n if (!this.routeLatency[routeKey]) {\n this.routeLatency[routeKey] = {\n count: 0,\n totalMs: 0,\n minMs: Infinity,\n maxMs: 0,\n }\n }\n const stats = this.routeLatency[routeKey]\n stats.count++\n stats.totalMs += elapsed\n stats.minMs = Math.min(stats.minMs, elapsed)\n stats.maxMs = Math.max(stats.maxMs, elapsed)\n })\n\n next()\n },\n phase: 'beforeGlobal',\n },\n ]\n }\n\n onRouteMount(controllerClass: any, mountPath: string): void {\n if (!this.enabled) return\n\n const routes: Array<{ method: string; path: string; handlerName: string }> =\n Reflect.getMetadata(METADATA.ROUTES, controllerClass) ?? []\n\n const classMiddleware: any[] =\n Reflect.getMetadata(METADATA.CLASS_MIDDLEWARES, controllerClass) ?? []\n\n for (const route of routes) {\n const methodMiddleware: any[] =\n Reflect.getMetadata(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass.prototype,\n route.handlerName,\n ) ?? []\n\n this.routes.push({\n method: route.method.toUpperCase(),\n path: `${mountPath}${route.path === '/' ? '' : route.path}`,\n controller: controllerClass.name,\n handler: route.handlerName,\n middleware: [\n ...classMiddleware.map((m: any) => m.name || 'anonymous'),\n ...methodMiddleware.map((m: any) => m.name || 'anonymous'),\n ],\n })\n }\n }\n\n afterStart(_server: any, _container: Container): void {\n if (!this.enabled) return\n log.info(\n `DevTools ready — ${this.routes.length} routes tracked, ` +\n `${this.container?.getRegistrations().length ?? 0} DI bindings`,\n )\n }\n\n shutdown(): void {\n this.stopErrorWatch?.()\n this.adapterStatuses[this.name] = 'stopped'\n }\n\n /** Find the public/devtools directory relative to the built dist or source */\n private resolvePublicDir(): string | null {\n // Try relative to this file's location (works in dist/)\n const thisDir = dirname(fileURLToPath(import.meta.url))\n const candidates = [\n join(thisDir, '..', 'public', 'devtools'), // dist/ -> public/devtools\n join(thisDir, '..', '..', 'public', 'devtools'), // src/ -> public/devtools\n ]\n for (const dir of candidates) {\n if (existsSync(join(dir, 'index.html'))) return dir\n }\n return null\n }\n}\n"],"mappings":"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","names":["Router","dirname","join","fileURLToPath","existsSync","readFileSync","randomBytes","METADATA","ref","computed","reactive","watch","createLogger","log","createLogger","DevToolsAdapter","name","basePath","enabled","exposeConfig","configPrefixes","errorRateThreshold","secret","requestCount","errorCount","clientErrorCount","startedAt","errorRate","uptimeSeconds","routeLatency","routes","container","adapterStatuses","stopErrorWatch","peerAdapters","options","process","env","NODE_ENV","adapters","randomBytes","toString","ref","Date","now","reactive","computed","value","Math","floor","onErrorRateExceeded","callback","threshold","watch","rate","warn","toFixed","beforeMount","app","router","Router","token","use","req","res","next","provided","headers","query","path","method","endsWith","status","json","error","get","_req","registrations","getRegistrations","count","length","requests","serverErrors","clientErrors","toISOString","healthy","uptime","wsAdapter","find","a","getStats","ws","message","queueAdapter","getQueueNames","names","queues","stats","getQueueStats","push","config","key","Object","entries","undefined","allowed","some","prefix","startsWith","publicDir","resolvePublicDir","express","require","static","indexHtml","readFileSync","join","html","replace","type","send","info","middleware","handler","start","on","statusCode","routeKey","route","elapsed","totalMs","minMs","Infinity","maxMs","min","max","phase","onRouteMount","controllerClass","mountPath","Reflect","getMetadata","METADATA","ROUTES","classMiddleware","CLASS_MIDDLEWARES","methodMiddleware","METHOD_MIDDLEWARES","prototype","handlerName","toUpperCase","controller","map","m","afterStart","_server","_container","shutdown","thisDir","dirname","fileURLToPath","url","candidates","dir","existsSync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/adapter.ts"],"sourcesContent":["import 'reflect-metadata'\n\nexport { DevToolsAdapter, type DevToolsOptions } from './adapter'\n","import type { Request, Response, NextFunction } from 'express'\nimport { Router, static as serveStatic } from 'express'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { randomBytes } from 'node:crypto'\nimport {\n type AppAdapter,\n type AdapterMiddleware,\n type Container,\n METADATA,\n ref,\n computed,\n reactive,\n watch,\n createLogger,\n type Ref,\n type ComputedRef,\n} from '@forinda/kickjs-core'\n\nconst log = createLogger('DevTools')\n\n/** Route metadata collected during mount */\ninterface RouteInfo {\n method: string\n path: string\n controller: string\n handler: string\n middleware: string[]\n}\n\n/** Per-route latency stats */\ninterface RouteStats {\n count: number\n totalMs: number\n minMs: number\n maxMs: number\n}\n\nexport interface DevToolsOptions {\n /** Base path for debug endpoints (default: '/_debug') */\n basePath?: string\n /** Only enable when this is true (default: process.env.NODE_ENV !== 'production') */\n enabled?: boolean\n /** Include environment variables (sanitized) at /_debug/config (default: false) */\n exposeConfig?: boolean\n /** Env var prefixes to expose (default: ['APP_', 'NODE_ENV']). Others are redacted. */\n configPrefixes?: string[]\n /** Callback when error rate exceeds threshold */\n onErrorRateExceeded?: (rate: number) => void\n /** Error rate threshold (default: 0.5 = 50%) */\n errorRateThreshold?: number\n /** Other adapters to discover stats from (e.g., WsAdapter) */\n adapters?: any[]\n\n /**\n * Secret token to guard DevTools access. When set, all requests must\n * include this token as `x-devtools-token` header or `?token=` query param.\n *\n * Auto-generated on startup if not provided. The token is logged to the console.\n * Set to `false` to disable the guard entirely (not recommended).\n *\n * @example\n * ```ts\n * new DevToolsAdapter({ secret: process.env.DEVTOOLS_SECRET })\n * ```\n */\n secret?: string | false\n}\n\n/**\n * DevToolsAdapter — Vue-style reactive introspection for KickJS applications.\n *\n * Exposes debug endpoints powered by reactive state (ref, computed, watch):\n * - `GET /_debug/routes` — all registered routes with middleware\n * - `GET /_debug/container` — DI registry with scopes and instantiation status\n * - `GET /_debug/metrics` — live request/error counts, error rate, uptime\n * - `GET /_debug/health` — deep health check with adapter status\n * - `GET /_debug/config` — sanitized environment variables (opt-in)\n * - `GET /_debug/state` — full reactive state snapshot\n *\n * @example\n * ```ts\n * import { DevToolsAdapter } from '@forinda/kickjs-devtools'\n *\n * bootstrap({\n * modules: [UserModule],\n * adapters: [\n * new DevToolsAdapter({\n * enabled: process.env.NODE_ENV !== 'production',\n * exposeConfig: true,\n * configPrefixes: ['APP_', 'DATABASE_'],\n * }),\n * ],\n * })\n * ```\n */\nexport class DevToolsAdapter implements AppAdapter {\n readonly name = 'DevToolsAdapter'\n\n private basePath: string\n private enabled: boolean\n private exposeConfig: boolean\n private configPrefixes: string[]\n private errorRateThreshold: number\n private secret: string | false\n\n // ── Reactive State ───────────────────────────────────────────────────\n /** Total requests received */\n readonly requestCount: Ref<number>\n /** Total responses with status >= 500 */\n readonly errorCount: Ref<number>\n /** Total responses with status >= 400 and < 500 */\n readonly clientErrorCount: Ref<number>\n /** Server start time */\n readonly startedAt: Ref<number>\n /** Computed error rate (server errors / total requests) */\n readonly errorRate: ComputedRef<number>\n /** Computed uptime in seconds */\n readonly uptimeSeconds: ComputedRef<number>\n /** Per-route latency tracking */\n readonly routeLatency: Record<string, RouteStats>\n\n // ── Internal State ───────────────────────────────────────────────────\n private routes: RouteInfo[] = []\n private container: Container | null = null\n private adapterStatuses: Record<string, string> = {}\n private stopErrorWatch: (() => void) | null = null\n private peerAdapters: any[] = []\n\n constructor(options: DevToolsOptions = {}) {\n this.basePath = options.basePath ?? '/_debug'\n this.enabled = options.enabled ?? process.env.NODE_ENV !== 'production'\n this.exposeConfig = options.exposeConfig ?? false\n this.configPrefixes = options.configPrefixes ?? ['APP_', 'NODE_ENV']\n this.errorRateThreshold = options.errorRateThreshold ?? 0.5\n this.peerAdapters = options.adapters ?? []\n\n // Secret token guard\n if (options.secret === false) {\n this.secret = false\n } else if (options.secret) {\n this.secret = options.secret\n } else {\n // Auto-generate a random token\n this.secret = randomBytes(16).toString('hex')\n }\n\n // Initialize reactive state\n this.requestCount = ref(0)\n this.errorCount = ref(0)\n this.clientErrorCount = ref(0)\n this.startedAt = ref(Date.now())\n this.routeLatency = reactive({})\n\n this.errorRate = computed(() =>\n this.requestCount.value > 0 ? this.errorCount.value / this.requestCount.value : 0,\n )\n\n this.uptimeSeconds = computed(() => Math.floor((Date.now() - this.startedAt.value) / 1000))\n\n // Watch error rate — log warnings when elevated\n if (options.onErrorRateExceeded) {\n const callback = options.onErrorRateExceeded\n const threshold = this.errorRateThreshold\n this.stopErrorWatch = watch(this.errorRate, (rate) => {\n if (rate > threshold) {\n callback(rate)\n }\n })\n } else {\n this.stopErrorWatch = watch(this.errorRate, (rate) => {\n if (rate > this.errorRateThreshold) {\n log.warn(`Error rate elevated: ${(rate * 100).toFixed(1)}%`)\n }\n })\n }\n }\n\n // ── Adapter Lifecycle ────────────────────────────────────────────────\n\n beforeMount(app: any, container: Container): void {\n if (!this.enabled) return\n\n this.container = container\n this.startedAt.value = Date.now()\n // Clear routes on rebuild/restart to prevent HMR duplication\n this.routes = []\n this.adapterStatuses[this.name] = 'running'\n\n const router = Router()\n\n // ── Access guard — require secret token ──────────────────────────\n if (this.secret !== false) {\n const token = this.secret\n router.use((req: Request, res: Response, next: NextFunction) => {\n const provided = req.headers['x-devtools-token'] ?? req.query?.token\n if (provided === token) return next()\n // Allow the dashboard HTML itself (it will include the token in API calls)\n if (req.path === '/' && req.method === 'GET' && !req.query?.token) {\n return next() // serve dashboard, it handles auth via token\n }\n // Serve static assets for the dashboard (js files)\n if (req.path.endsWith('.js') || req.path.endsWith('.css')) {\n return next()\n }\n res.status(403).json({ error: 'Forbidden — invalid or missing devtools token' })\n })\n }\n\n router.get('/routes', (_req: Request, res: Response) => {\n res.json({ routes: this.routes })\n })\n\n router.get('/container', (_req: Request, res: Response) => {\n const registrations = this.container?.getRegistrations() ?? []\n res.json({ registrations, count: registrations.length })\n })\n\n router.get('/metrics', (_req: Request, res: Response) => {\n res.json({\n requests: this.requestCount.value,\n serverErrors: this.errorCount.value,\n clientErrors: this.clientErrorCount.value,\n errorRate: this.errorRate.value,\n uptimeSeconds: this.uptimeSeconds.value,\n startedAt: new Date(this.startedAt.value).toISOString(),\n routeLatency: this.routeLatency,\n })\n })\n\n router.get('/health', (_req: Request, res: Response) => {\n const healthy = this.errorRate.value < this.errorRateThreshold\n const status = healthy ? 'healthy' : 'degraded'\n\n res.status(healthy ? 200 : 503).json({\n status,\n errorRate: this.errorRate.value,\n uptime: this.uptimeSeconds.value,\n adapters: this.adapterStatuses,\n })\n })\n\n router.get('/state', (_req: Request, res: Response) => {\n const wsAdapter = this.peerAdapters.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n res.json({\n reactive: {\n requestCount: this.requestCount.value,\n errorCount: this.errorCount.value,\n clientErrorCount: this.clientErrorCount.value,\n errorRate: this.errorRate.value,\n uptimeSeconds: this.uptimeSeconds.value,\n startedAt: new Date(this.startedAt.value).toISOString(),\n },\n routes: this.routes.length,\n container: this.container?.getRegistrations().length ?? 0,\n routeLatency: this.routeLatency,\n ...(wsAdapter ? { ws: wsAdapter.getStats() } : {}),\n })\n })\n\n router.get('/ws', (_req: Request, res: Response) => {\n const wsAdapter = this.peerAdapters.find(\n (a) => a.name === 'WsAdapter' && typeof a.getStats === 'function',\n )\n if (!wsAdapter) {\n res.json({ enabled: false, message: 'WsAdapter not found' })\n return\n }\n res.json({ enabled: true, ...wsAdapter.getStats() })\n })\n\n router.get('/queues', async (_req: Request, res: Response) => {\n const queueAdapter = this.peerAdapters.find(\n (a) => a.name === 'QueueAdapter' && typeof a.getQueueNames === 'function',\n )\n if (!queueAdapter) {\n res.json({ enabled: false, message: 'QueueAdapter not found' })\n return\n }\n try {\n const names: string[] = queueAdapter.getQueueNames?.() ?? []\n const queues: any[] = []\n for (const name of names) {\n const stats = await queueAdapter.getQueueStats?.(name)\n queues.push({ name, ...stats })\n }\n res.json({ enabled: true, queues })\n } catch {\n res.json({ enabled: true, queues: [], error: 'Failed to fetch queue stats' })\n }\n })\n\n if (this.exposeConfig) {\n router.get('/config', (_req: Request, res: Response) => {\n const config: Record<string, string> = {}\n for (const [key, value] of Object.entries(process.env)) {\n if (value === undefined) continue\n const allowed = this.configPrefixes.some((prefix) => key.startsWith(prefix))\n config[key] = allowed ? value : '[REDACTED]'\n }\n res.json({ config })\n })\n }\n\n // Dashboard UI — Vue + Tailwind from public/devtools directory\n const publicDir = this.resolvePublicDir()\n if (publicDir) {\n // Serve static assets (vue.global.min.js, tailwind-cdn.js)\n router.use(serveStatic(publicDir))\n\n // Serve index.html with base path injected\n const indexHtml = readFileSync(join(publicDir, 'index.html'), 'utf-8')\n router.get('/', (_req: Request, res: Response) => {\n // Inject basePath as data attribute for the Vue app\n const html = indexHtml.replace('<body', `<body data-base=\"${this.basePath}\"`)\n res.type('html').send(html)\n })\n } else {\n router.get('/', (_req: Request, res: Response) => {\n res.type('html').send('<h1>DevTools: public directory not found</h1>')\n })\n }\n\n app.use(this.basePath, router)\n\n if (this.secret) {\n log.info(`DevTools mounted at ${this.basePath} [token: ${this.secret}]`)\n log.info(`Access: ${this.basePath}?token=${this.secret}`)\n } else {\n log.info(`DevTools mounted at ${this.basePath} [no guard]`)\n }\n }\n\n middleware(): AdapterMiddleware[] {\n if (!this.enabled) return []\n\n return [\n {\n handler: (req: Request, res: Response, next: NextFunction) => {\n const start = Date.now()\n this.requestCount.value++\n\n res.on('finish', () => {\n if (res.statusCode >= 500) this.errorCount.value++\n else if (res.statusCode >= 400) this.clientErrorCount.value++\n\n // Track per-route latency\n const routeKey = `${req.method} ${req.route?.path ?? req.path}`\n const elapsed = Date.now() - start\n\n if (!this.routeLatency[routeKey]) {\n this.routeLatency[routeKey] = {\n count: 0,\n totalMs: 0,\n minMs: Infinity,\n maxMs: 0,\n }\n }\n const stats = this.routeLatency[routeKey]\n stats.count++\n stats.totalMs += elapsed\n stats.minMs = Math.min(stats.minMs, elapsed)\n stats.maxMs = Math.max(stats.maxMs, elapsed)\n })\n\n next()\n },\n phase: 'beforeGlobal',\n },\n ]\n }\n\n onRouteMount(controllerClass: any, mountPath: string): void {\n if (!this.enabled) return\n\n const routes: Array<{ method: string; path: string; handlerName: string }> =\n Reflect.getMetadata(METADATA.ROUTES, controllerClass) ?? []\n\n const classMiddleware: any[] =\n Reflect.getMetadata(METADATA.CLASS_MIDDLEWARES, controllerClass) ?? []\n\n for (const route of routes) {\n const methodMiddleware: any[] =\n Reflect.getMetadata(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass.prototype,\n route.handlerName,\n ) ?? []\n\n this.routes.push({\n method: route.method.toUpperCase(),\n path: `${mountPath}${route.path === '/' ? '' : route.path}`,\n controller: controllerClass.name,\n handler: route.handlerName,\n middleware: [\n ...classMiddleware.map((m: any) => m.name || 'anonymous'),\n ...methodMiddleware.map((m: any) => m.name || 'anonymous'),\n ],\n })\n }\n }\n\n afterStart(_server: any, _container: Container): void {\n if (!this.enabled) return\n log.info(\n `DevTools ready — ${this.routes.length} routes tracked, ` +\n `${this.container?.getRegistrations().length ?? 0} DI bindings`,\n )\n }\n\n shutdown(): void {\n this.stopErrorWatch?.()\n this.adapterStatuses[this.name] = 'stopped'\n }\n\n /** Find the public/devtools directory relative to the built dist or source */\n private resolvePublicDir(): string | null {\n // Try relative to this file's location (works in dist/)\n const thisDir = dirname(fileURLToPath(import.meta.url))\n const candidates = [\n join(thisDir, '..', 'public', 'devtools'), // dist/ -> public/devtools\n join(thisDir, '..', '..', 'public', 'devtools'), // src/ -> public/devtools\n ]\n for (const dir of candidates) {\n if (existsSync(join(dir, 'index.html'))) return dir\n }\n return null\n }\n}\n"],"mappings":"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","names":["Router","static","serveStatic","dirname","join","fileURLToPath","existsSync","readFileSync","randomBytes","METADATA","ref","computed","reactive","watch","createLogger","log","createLogger","DevToolsAdapter","name","basePath","enabled","exposeConfig","configPrefixes","errorRateThreshold","secret","requestCount","errorCount","clientErrorCount","startedAt","errorRate","uptimeSeconds","routeLatency","routes","container","adapterStatuses","stopErrorWatch","peerAdapters","options","process","env","NODE_ENV","adapters","randomBytes","toString","ref","Date","now","reactive","computed","value","Math","floor","onErrorRateExceeded","callback","threshold","watch","rate","warn","toFixed","beforeMount","app","router","Router","token","use","req","res","next","provided","headers","query","path","method","endsWith","status","json","error","get","_req","registrations","getRegistrations","count","length","requests","serverErrors","clientErrors","toISOString","healthy","uptime","wsAdapter","find","a","getStats","ws","message","queueAdapter","getQueueNames","names","queues","stats","getQueueStats","push","config","key","Object","entries","undefined","allowed","some","prefix","startsWith","publicDir","resolvePublicDir","serveStatic","indexHtml","readFileSync","join","html","replace","type","send","info","middleware","handler","start","on","statusCode","routeKey","route","elapsed","totalMs","minMs","Infinity","maxMs","min","max","phase","onRouteMount","controllerClass","mountPath","Reflect","getMetadata","METADATA","ROUTES","classMiddleware","CLASS_MIDDLEWARES","methodMiddleware","METHOD_MIDDLEWARES","prototype","handlerName","toUpperCase","controller","map","m","afterStart","_server","_container","shutdown","thisDir","dirname","fileURLToPath","url","candidates","dir","existsSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-devtools",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Development introspection dashboard for KickJS — routes, DI container, metrics, health",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "reflect-metadata": "^0.2.2",
30
- "@forinda/kickjs-core": "1.2.0"
30
+ "@forinda/kickjs-core": "1.2.1"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "express": "^5.1.0"