chore: crush git history - reborn from consolidation on 2026-03-10
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { inferCategory } from './category.js'
|
||||
import { inferContractsFromRouteSchema } from './schema-to-contract.js'
|
||||
// Reuse empty arrays to avoid allocation
|
||||
import type { HttpMethod, OutboundBinding, RouteContract, ValidatedFormula } from '../types.js'
|
||||
const EMPTY_REQUIRES: ValidatedFormula[] = []
|
||||
const EMPTY_ENSURES: ValidatedFormula[] = []
|
||||
const EMPTY_INVARIANTS: ValidatedFormula[] = []
|
||||
// Two-level cache: WeakMap<schema, Map<"METHOD path", RouteContract>>
|
||||
// Preserves automatic GC of schema objects while correctly caching per-route contracts
|
||||
const contractCache = new WeakMap<Record<string, unknown>, Map<string, RouteContract>>()
|
||||
export const extractContract = (
|
||||
path: string,
|
||||
method: string,
|
||||
schema: Record<string, unknown> | undefined
|
||||
): RouteContract => {
|
||||
const s = schema ?? {}
|
||||
// Fast path: two-level cache lookup (guard against null — WeakMap rejects null keys)
|
||||
if (schema != null) {
|
||||
let routeMap = contractCache.get(schema)
|
||||
if (routeMap === undefined) {
|
||||
routeMap = new Map()
|
||||
contractCache.set(schema, routeMap)
|
||||
}
|
||||
const key = `${method.toUpperCase()} ${path}`
|
||||
const cached = routeMap.get(key)
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
const override = typeof s['x-category'] === 'string' ? s['x-category'] : undefined
|
||||
const category = inferCategory(path, method, override)
|
||||
// APOPHIS annotations may live on the top-level schema OR nested inside
|
||||
// response.statusCode (e.g. schema.response[200]['x-ensures']).
|
||||
// We merge both levels so contracts are never silently dropped.
|
||||
const responseSchema = (s.response ?? {}) as Record<string, Record<string, unknown>>
|
||||
const firstStatus = Object.values(responseSchema)[0] ?? {}
|
||||
const topRequires = s['x-requires']
|
||||
const nestedRequires = firstStatus['x-requires']
|
||||
const requires = Array.isArray(topRequires) && topRequires.length > 0
|
||||
? (topRequires as string[])
|
||||
: Array.isArray(nestedRequires) && nestedRequires.length > 0
|
||||
? (nestedRequires as string[])
|
||||
: EMPTY_REQUIRES
|
||||
const topEnsures = s['x-ensures']
|
||||
const nestedEnsures = firstStatus['x-ensures']
|
||||
const explicitEnsures = Array.isArray(topEnsures) && topEnsures.length > 0
|
||||
? (topEnsures as string[])
|
||||
: Array.isArray(nestedEnsures) && nestedEnsures.length > 0
|
||||
? (nestedEnsures as string[])
|
||||
: []
|
||||
// Infer contracts from JSON Schema constraints (required, minimum, maximum, pattern, etc.)
|
||||
// These supplement explicit x-ensures — never replace them.
|
||||
const inferred = inferContractsFromRouteSchema(s)
|
||||
const inferredSet = new Set(inferred)
|
||||
const explicitSet = new Set(explicitEnsures)
|
||||
// Deduplicate: don't add inferred formulas that the user already wrote explicitly
|
||||
const additionalInferred = inferred.filter(f => !explicitSet.has(f))
|
||||
// Merge: explicit first, then inferred
|
||||
const ensures = explicitEnsures.length > 0 || additionalInferred.length > 0
|
||||
? [...explicitEnsures, ...additionalInferred]
|
||||
: EMPTY_ENSURES
|
||||
const validateRuntime =
|
||||
(s['x-validate-runtime'] !== false) &&
|
||||
(firstStatus['x-validate-runtime'] !== false)
|
||||
// Extract timeout from schema annotation
|
||||
const timeoutValue = s['x-timeout'] ?? firstStatus['x-timeout']
|
||||
const timeout = typeof timeoutValue === 'number' && timeoutValue > 0
|
||||
? timeoutValue
|
||||
: undefined
|
||||
// Parse x-outbound annotation
|
||||
const outboundRaw = s['x-outbound']
|
||||
const outbound: OutboundBinding[] | undefined = Array.isArray(outboundRaw)
|
||||
? (outboundRaw as OutboundBinding[])
|
||||
: undefined
|
||||
// Parse x-variants annotation
|
||||
const variantsRaw = s['x-variants']
|
||||
const variants = Array.isArray(variantsRaw)
|
||||
? variantsRaw.map((v: unknown) => {
|
||||
if (typeof v === 'string') {
|
||||
return { name: v }
|
||||
}
|
||||
if (v !== null && typeof v === 'object') {
|
||||
const vo = v as Record<string, unknown>
|
||||
return {
|
||||
name: String(vo.name || 'unnamed'),
|
||||
headers: vo.headers as Record<string, string> | undefined,
|
||||
when: vo.when as string | undefined,
|
||||
}
|
||||
}
|
||||
return { name: String(v) }
|
||||
})
|
||||
: undefined
|
||||
const contract: RouteContract = {
|
||||
path,
|
||||
method: method.toUpperCase() as HttpMethod,
|
||||
category,
|
||||
requires: requires as ValidatedFormula[],
|
||||
ensures: ensures as ValidatedFormula[],
|
||||
invariants: EMPTY_INVARIANTS,
|
||||
regexPatterns: {},
|
||||
validateRuntime,
|
||||
schema: s,
|
||||
timeout,
|
||||
outbound,
|
||||
variants,
|
||||
}
|
||||
if (schema !== undefined) {
|
||||
const key = `${method.toUpperCase()} ${path}`
|
||||
let routeMap = contractCache.get(schema)
|
||||
if (routeMap === undefined) {
|
||||
routeMap = new Map()
|
||||
contractCache.set(schema, routeMap)
|
||||
}
|
||||
routeMap.set(key, contract)
|
||||
}
|
||||
return contract
|
||||
}
|
||||
Reference in New Issue
Block a user