From 4366645aa0fd49cef3e17b40d342713b7dd183e6 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 10:56:46 +0530 Subject: [PATCH 1/6] fix: use endpoint templates instead of paths in metrics labels Prevents high-cardinality metric labels in Prometheus by mapping request paths to endpoint templates (e.g., /docs/:slug, /apis/:slug). API routes keep their exact path (/api/page, /api/search). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chronicle/src/server/plugins/telemetry.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/chronicle/src/server/plugins/telemetry.ts b/packages/chronicle/src/server/plugins/telemetry.ts index 7f31d5fd..42303738 100644 --- a/packages/chronicle/src/server/plugins/telemetry.ts +++ b/packages/chronicle/src/server/plugins/telemetry.ts @@ -13,6 +13,24 @@ declare module 'nitro/types' { } } +const ENDPOINT_MAP: [string, string | null][] = [ + ['/api/', null], + ['/_content/', '/_content/:path'], + ['/apis/', '/apis/:slug'], + ['/assets/', '/assets/:file'], +] + +const STATIC_ROUTES = new Set(['/llms.txt', '/robots.txt', '/sitemap.xml', '/og']) + +function toEndpoint(pathname: string): string { + if (pathname === '/') return '/'; + for (const [prefix, template] of ENDPOINT_MAP) { + if (pathname.startsWith(prefix)) return template ?? pathname; + } + if (STATIC_ROUTES.has(pathname)) return pathname; + return '/docs/:slug'; +} + export default definePlugin((nitroApp) => { const config = loadConfig() if (!config.telemetry?.enabled) return @@ -42,7 +60,7 @@ export default definePlugin((nitroApp) => { }) nitroApp.hooks.hook('chronicle:ssr-rendered', (route, status, durationMs) => { - ssrRenderDuration.record(durationMs, { route, status }) + ssrRenderDuration.record(durationMs, { route: toEndpoint(route), status }) }) nitroApp.hooks.hook('request', (event) => { @@ -54,8 +72,8 @@ export default definePlugin((nitroApp) => { if (start === undefined) return const duration = performance.now() - start const method = event.req.method - const route = new URL(event.req.url).pathname - requestCounter.add(1, { method, route, status: res.status }) - requestDuration.record(duration, { method, route, status: res.status }) + const endpoint = toEndpoint(new URL(event.req.url).pathname) + requestCounter.add(1, { method, endpoint, status: res.status }) + requestDuration.record(duration, { method, endpoint, status: res.status }) }) }) From 6d2e63837b6532c623a900f726a32e3cdf46ef30 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 10:58:15 +0530 Subject: [PATCH 2/6] refactor: extract ROUTES constants for endpoint templates Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chronicle/src/server/plugins/telemetry.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/chronicle/src/server/plugins/telemetry.ts b/packages/chronicle/src/server/plugins/telemetry.ts index 42303738..0f0472a7 100644 --- a/packages/chronicle/src/server/plugins/telemetry.ts +++ b/packages/chronicle/src/server/plugins/telemetry.ts @@ -13,22 +13,31 @@ declare module 'nitro/types' { } } +const ROUTES = { + ROOT: '/', + DOCS: '/docs/:slug', + API: '/api/:action', + APIS: '/apis/:slug', + ASSETS: '/assets/:file', + CONTENT: '/_content/:path', +} as const + const ENDPOINT_MAP: [string, string | null][] = [ ['/api/', null], - ['/_content/', '/_content/:path'], - ['/apis/', '/apis/:slug'], - ['/assets/', '/assets/:file'], + ['/_content/', ROUTES.CONTENT], + ['/apis/', ROUTES.APIS], + ['/assets/', ROUTES.ASSETS], ] const STATIC_ROUTES = new Set(['/llms.txt', '/robots.txt', '/sitemap.xml', '/og']) function toEndpoint(pathname: string): string { - if (pathname === '/') return '/'; + if (pathname === '/') return ROUTES.ROOT; for (const [prefix, template] of ENDPOINT_MAP) { if (pathname.startsWith(prefix)) return template ?? pathname; } if (STATIC_ROUTES.has(pathname)) return pathname; - return '/docs/:slug'; + return ROUTES.DOCS; } export default definePlugin((nitroApp) => { From dc170aa453d00e0cd1adee2c09b7507f2e0094e7 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 10:59:13 +0530 Subject: [PATCH 3/6] refactor: rename API/APIS routes to API_INTERNAL/API_PROXY Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/plugins/telemetry.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/chronicle/src/server/plugins/telemetry.ts b/packages/chronicle/src/server/plugins/telemetry.ts index 0f0472a7..7d1332b7 100644 --- a/packages/chronicle/src/server/plugins/telemetry.ts +++ b/packages/chronicle/src/server/plugins/telemetry.ts @@ -16,8 +16,8 @@ declare module 'nitro/types' { const ROUTES = { ROOT: '/', DOCS: '/docs/:slug', - API: '/api/:action', - APIS: '/apis/:slug', + API_INTERNAL: '/api/:action', + API_PROXY: '/apis/:slug', ASSETS: '/assets/:file', CONTENT: '/_content/:path', } as const @@ -25,7 +25,7 @@ const ROUTES = { const ENDPOINT_MAP: [string, string | null][] = [ ['/api/', null], ['/_content/', ROUTES.CONTENT], - ['/apis/', ROUTES.APIS], + ['/apis/', ROUTES.API_PROXY], ['/assets/', ROUTES.ASSETS], ] From e4d72ee4cec91a6f1e827d85b6b4e1b3a0ec386a Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 10:59:34 +0530 Subject: [PATCH 4/6] fix: rename API_PROXY to API_REFERENCE for /apis/ route Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/plugins/telemetry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/chronicle/src/server/plugins/telemetry.ts b/packages/chronicle/src/server/plugins/telemetry.ts index 7d1332b7..b3ed17fe 100644 --- a/packages/chronicle/src/server/plugins/telemetry.ts +++ b/packages/chronicle/src/server/plugins/telemetry.ts @@ -17,7 +17,7 @@ const ROUTES = { ROOT: '/', DOCS: '/docs/:slug', API_INTERNAL: '/api/:action', - API_PROXY: '/apis/:slug', + API_REFERENCE: '/apis/:slug', ASSETS: '/assets/:file', CONTENT: '/_content/:path', } as const @@ -25,7 +25,7 @@ const ROUTES = { const ENDPOINT_MAP: [string, string | null][] = [ ['/api/', null], ['/_content/', ROUTES.CONTENT], - ['/apis/', ROUTES.API_PROXY], + ['/apis/', ROUTES.API_REFERENCE], ['/assets/', ROUTES.ASSETS], ] From afc03cf030e7cefdd3da6f2fc7031e2830b708f6 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 11:00:38 +0530 Subject: [PATCH 5/6] test: add toEndpoint tests for metric route templates Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/server/plugins/telemetry.test.ts | 47 +++++++++++++++++++ .../chronicle/src/server/plugins/telemetry.ts | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 packages/chronicle/src/server/plugins/telemetry.test.ts diff --git a/packages/chronicle/src/server/plugins/telemetry.test.ts b/packages/chronicle/src/server/plugins/telemetry.test.ts new file mode 100644 index 00000000..003634d5 --- /dev/null +++ b/packages/chronicle/src/server/plugins/telemetry.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from 'bun:test' +import { toEndpoint } from './telemetry' + +describe('toEndpoint', () => { + test('root path', () => { + expect(toEndpoint('/')).toBe('/') + }) + + test('docs pages map to /docs/:slug', () => { + expect(toEndpoint('/docs/intro')).toBe('/docs/:slug') + expect(toEndpoint('/docs/guides/installation')).toBe('/docs/:slug') + expect(toEndpoint('/developer/gettingstarted/auth')).toBe('/docs/:slug') + }) + + test('api internal routes keep exact path', () => { + expect(toEndpoint('/api/page')).toBe('/api/page') + expect(toEndpoint('/api/search')).toBe('/api/search') + expect(toEndpoint('/api/specs')).toBe('/api/specs') + expect(toEndpoint('/api/health')).toBe('/api/health') + }) + + test('api reference pages map to /apis/:slug', () => { + expect(toEndpoint('/apis/petstore/listPets')).toBe('/apis/:slug') + expect(toEndpoint('/apis/frontier/getUser')).toBe('/apis/:slug') + }) + + test('assets map to /assets/:file', () => { + expect(toEndpoint('/assets/chunk-abc123.js')).toBe('/assets/:file') + expect(toEndpoint('/assets/style-xyz.css')).toBe('/assets/:file') + }) + + test('content paths map to /_content/:path', () => { + expect(toEndpoint('/_content/docs/intro.mdx')).toBe('/_content/:path') + }) + + test('static routes keep exact path', () => { + expect(toEndpoint('/llms.txt')).toBe('/llms.txt') + expect(toEndpoint('/robots.txt')).toBe('/robots.txt') + expect(toEndpoint('/sitemap.xml')).toBe('/sitemap.xml') + expect(toEndpoint('/og')).toBe('/og') + }) + + test('versioned docs map to /docs/:slug', () => { + expect(toEndpoint('/v1/docs/intro')).toBe('/docs/:slug') + expect(toEndpoint('/v2/guides/setup')).toBe('/docs/:slug') + }) +}) diff --git a/packages/chronicle/src/server/plugins/telemetry.ts b/packages/chronicle/src/server/plugins/telemetry.ts index b3ed17fe..450ef4d8 100644 --- a/packages/chronicle/src/server/plugins/telemetry.ts +++ b/packages/chronicle/src/server/plugins/telemetry.ts @@ -31,7 +31,7 @@ const ENDPOINT_MAP: [string, string | null][] = [ const STATIC_ROUTES = new Set(['/llms.txt', '/robots.txt', '/sitemap.xml', '/og']) -function toEndpoint(pathname: string): string { +export function toEndpoint(pathname: string): string { if (pathname === '/') return ROUTES.ROOT; for (const [prefix, template] of ENDPOINT_MAP) { if (pathname.startsWith(prefix)) return template ?? pathname; From 55b894911a5cfb967e073584d0e280d6ba40b62a Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 3 Jun 2026 12:08:16 +0530 Subject: [PATCH 6/6] fix(ci): use in-process Nitro runner for Windows dev smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit node-process runner uses fork() + IPC which is flaky on Windows CI runners — child process sometimes never sends ready signal, causing health check to hang until job timeout. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62df9dee..2ef821ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,8 @@ jobs: - name: Smoke test dev server shell: bash + env: + NITRO_DEV_RUNNER: ${{ matrix.os == 'windows-latest' && 'self' || '' }} run: | ./packages/chronicle/bin/chronicle.js dev --config examples/basic/chronicle.yaml --port 3001 & DEV_PID=$!