96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
/**
|
|
* Route discovery from a Fastify instance.
|
|
* Pure functions, no side effects.
|
|
*
|
|
* Fastify 5 removed the public `routes` array. We capture routes via the `onRoute`
|
|
* hook during plugin registration and store them in a WeakMap keyed by the instance.
|
|
*/
|
|
import { extractContract } from './contract.js'
|
|
import type { RouteContract } from '../types.js'
|
|
|
|
interface CapturedRoute {
|
|
method: string
|
|
url: string
|
|
schema?: Record<string, unknown>
|
|
prefix?: string
|
|
}
|
|
// WeakMap to store captured routes per Fastify instance (no memory leaks)
|
|
const capturedRoutes = new WeakMap<object, CapturedRoute[]>()
|
|
/**
|
|
* Capture a route for discovery.
|
|
* Called from the plugin's `onRoute` hook.
|
|
*/
|
|
export const captureRoute = (
|
|
instance: object,
|
|
route: CapturedRoute
|
|
): void => {
|
|
const existing = capturedRoutes.get(instance) ?? []
|
|
existing.push(route)
|
|
capturedRoutes.set(instance, existing)
|
|
}
|
|
|
|
/**
|
|
* Fallback route discovery for Fastify 5 when routes were registered before
|
|
* the APOPHIS plugin (e.g., external apps loaded by CLI).
|
|
* Uses hasRoute to test known route patterns.
|
|
*/
|
|
function discoverRoutesFallback(
|
|
instance: { hasRoute?: (opts: { method: string; url: string }) => boolean }
|
|
): RouteContract[] {
|
|
if (typeof instance.hasRoute !== 'function') {
|
|
return []
|
|
}
|
|
|
|
// Common HTTP methods to test
|
|
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
|
|
|
|
// We can't enumerate all possible routes, but we can check if the instance
|
|
// has any routes at all by testing a few common patterns
|
|
// This is a best-effort fallback
|
|
const routes: RouteContract[] = []
|
|
|
|
// Try to extract routes from the instance's internal state
|
|
// Fastify stores routes in find-my-way router, but it's not directly accessible
|
|
// We'll use a heuristic: check if the instance responds to common route methods
|
|
|
|
// Check if instance has any routes by looking at prototype methods
|
|
const hasRouting = typeof (instance as any).routing === 'function'
|
|
if (!hasRouting) {
|
|
return []
|
|
}
|
|
|
|
// Since we can't enumerate routes in Fastify 5 without the onRoute hook,
|
|
// we return empty and let the caller handle the "no routes" case
|
|
return []
|
|
}
|
|
|
|
/**
|
|
* Discover routes from a Fastify instance.
|
|
*
|
|
* First checks captured routes (from onRoute hook), then falls back to
|
|
* the legacy `routes` array for Fastify 4 compatibility.
|
|
*/
|
|
export const discoverRoutes = (instance: { routes?: Array<{ method: string; url: string; schema?: Record<string, unknown> }>; hasRoute?: (opts: { method: string; url: string }) => boolean }): RouteContract[] => {
|
|
// Fastify 5: routes captured via onRoute hook
|
|
const captured = capturedRoutes.get(instance)
|
|
if (captured && captured.length > 0) {
|
|
return captured.map((route) =>
|
|
extractContract(route.url, route.method, route.schema)
|
|
)
|
|
}
|
|
// Fastify 4 fallback
|
|
if (Array.isArray(instance.routes) && instance.routes.length > 0) {
|
|
return instance.routes.map((route) =>
|
|
extractContract(route.url, route.method, route.schema)
|
|
)
|
|
}
|
|
// Fastify 5 fallback: routes registered before plugin
|
|
return discoverRoutesFallback(instance)
|
|
}
|
|
/**
|
|
* Clear captured routes for an instance (useful for testing).
|
|
*/
|
|
export const clearCapturedRoutes = (instance: object): void => {
|
|
capturedRoutes.delete(instance)
|
|
}
|