Files
apophis-fastify/src/domain/discovery.ts
T

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)
}