chore: crush git history - reborn from consolidation on 2026-03-10
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* APOPHIS Plugin v1.0 — Fastify plugin entry point.
|
||||
* Thin wrapper: delegates all work to pure domain functions.
|
||||
* Fastify plugin API is accidental; APOPHIS logic is essential.
|
||||
*
|
||||
* Architecture: Orchestrator — imports focused builders from submodules.
|
||||
*/
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import { ScopeRegistry } from '../infrastructure/scope-registry.js'
|
||||
import { CleanupManager } from '../infrastructure/cleanup-manager.js'
|
||||
import { captureRoute } from '../domain/discovery.js'
|
||||
import { registerValidationHooks, storeRouteContract } from '../infrastructure/hook-validator.js'
|
||||
import { extractContract } from '../domain/contract.js'
|
||||
import { createExtensionRegistry } from '../extension/registry.js'
|
||||
import type { ApophisExtension } from '../extension/types.js'
|
||||
import { createPluginContractRegistry, BUILTIN_PLUGIN_CONTRACTS } from '../domain/plugin-contracts.js'
|
||||
import type { PluginContractRegistry } from '../domain/plugin-contracts.js'
|
||||
import { OutboundContractRegistry } from '../domain/outbound-contracts.js'
|
||||
import { createOutboundMockRuntime, type OutboundMockRuntime } from '../infrastructure/outbound-mock-runtime.js'
|
||||
import { validateProductionSafety, assertTestEnv } from '../infrastructure/production-safety.js'
|
||||
import {
|
||||
registerSwagger,
|
||||
buildSpec,
|
||||
buildScenario,
|
||||
buildCleanup,
|
||||
buildContract,
|
||||
buildStateful,
|
||||
buildCheck,
|
||||
} from './builders.js'
|
||||
import type { ApophisDecorations, ApophisOptions, OutboundCallRecord, OutboundContractSpec, TestConfig } from '../types.js'
|
||||
|
||||
export const apophisPlugin = async (fastify: FastifyInstance, opts: ApophisOptions): Promise<void> => {
|
||||
// Production safety: hard-fail if test-only options are present in production
|
||||
validateProductionSafety(opts)
|
||||
await registerSwagger(fastify, opts)
|
||||
// Initialize registries before route capture so onRoute can validate formulas
|
||||
// with any registered extension operation headers.
|
||||
const pluginContractRegistry = createPluginContractRegistry()
|
||||
const extensionRegistry = createExtensionRegistry()
|
||||
extensionRegistry.setPluginContractRegistry(pluginContractRegistry)
|
||||
// Initialize outbound contract registry
|
||||
const outboundContractRegistry = new OutboundContractRegistry()
|
||||
if (opts.outboundContracts) {
|
||||
outboundContractRegistry.registerAll(opts.outboundContracts)
|
||||
}
|
||||
// Track active outbound mock runtime for imperative E2E tests
|
||||
let activeMockRuntime: OutboundMockRuntime | undefined
|
||||
// Capture routes as they're registered via Fastify's onRoute hook
|
||||
fastify.addHook('onRoute', (routeOptions) => {
|
||||
const method = Array.isArray(routeOptions.method)
|
||||
? routeOptions.method.join(',')
|
||||
: routeOptions.method
|
||||
const schema = routeOptions.schema as Record<string, unknown> | undefined
|
||||
const prefix = (routeOptions as unknown as Record<string, unknown>).prefix as string | undefined
|
||||
const url = prefix && !routeOptions.url.startsWith(prefix)
|
||||
? `${prefix}${routeOptions.url}`
|
||||
: routeOptions.url
|
||||
captureRoute(fastify, {
|
||||
method,
|
||||
url,
|
||||
schema,
|
||||
prefix,
|
||||
})
|
||||
// Extract contract and attach to route config for runtime validation hooks
|
||||
const contract = extractContract(url, method, schema)
|
||||
if (contract.validateRuntime && (contract.requires.length > 0 || contract.ensures.length > 0)) {
|
||||
const config = routeOptions.config as Record<string, unknown> || {}
|
||||
config.apophisContract = contract
|
||||
routeOptions.config = config as typeof routeOptions.config
|
||||
// Store for hook validator lookup (Fastify doesn't expose routes after ready)
|
||||
const routeKey = `${contract.method} ${contract.path}`
|
||||
storeRouteContract(routeKey, contract, extensionRegistry.getExtensionHeaders())
|
||||
}
|
||||
})
|
||||
// Initialize scope registry with explicit config or empty
|
||||
const scope = new ScopeRegistry(opts.scopes ?? {})
|
||||
const cleanupManager = new CleanupManager(fastify, scope, opts.cleanup ?? false)
|
||||
// Register built-in plugin contracts
|
||||
for (const [name, spec] of Object.entries(BUILTIN_PLUGIN_CONTRACTS)) {
|
||||
pluginContractRegistry.register(name, spec)
|
||||
}
|
||||
// Register user-provided plugin contracts
|
||||
if (opts.pluginContracts) {
|
||||
for (const [name, spec] of Object.entries(opts.pluginContracts)) {
|
||||
pluginContractRegistry.register(name, spec)
|
||||
}
|
||||
}
|
||||
if (opts.extensions) {
|
||||
for (const ext of opts.extensions) {
|
||||
extensionRegistry.register(ext as ApophisExtension)
|
||||
}
|
||||
}
|
||||
const decorations: ApophisDecorations = {
|
||||
scope,
|
||||
contract: buildContract(fastify, scope, extensionRegistry, pluginContractRegistry, outboundContractRegistry),
|
||||
stateful: buildStateful(fastify, scope, cleanupManager, extensionRegistry, pluginContractRegistry, outboundContractRegistry),
|
||||
check: buildCheck(fastify, scope, extensionRegistry, pluginContractRegistry),
|
||||
scenario: buildScenario(fastify, scope, extensionRegistry),
|
||||
cleanup: buildCleanup(cleanupManager),
|
||||
spec: buildSpec(fastify),
|
||||
test: {
|
||||
registerPluginContracts: (name: string, spec: import('../types.js').PluginContractSpec) => {
|
||||
assertTestEnv('registerPluginContracts')
|
||||
pluginContractRegistry.register(name, spec)
|
||||
},
|
||||
registerOutboundContracts: (contracts: Record<string, OutboundContractSpec>) => {
|
||||
assertTestEnv('registerOutboundContracts')
|
||||
outboundContractRegistry.registerAll(contracts)
|
||||
},
|
||||
enableOutboundMocks: (mockOpts?: TestConfig['outboundMocks']) => {
|
||||
assertTestEnv('enableOutboundMocks')
|
||||
if (activeMockRuntime) {
|
||||
activeMockRuntime.restore()
|
||||
}
|
||||
// Disable case
|
||||
if (mockOpts === false) {
|
||||
activeMockRuntime = undefined
|
||||
return
|
||||
}
|
||||
const contracts = outboundContractRegistry.resolve(
|
||||
Array.from(outboundContractRegistry['contracts'].entries()).map(([name]) => name)
|
||||
)
|
||||
const mode = mockOpts?.mode ?? 'example'
|
||||
const unmatched = mockOpts?.unmatched ?? 'error'
|
||||
const seed = Math.floor(Math.random() * 0xFFFFFFFF)
|
||||
activeMockRuntime = createOutboundMockRuntime({
|
||||
contracts,
|
||||
mode,
|
||||
overrides: mockOpts?.overrides,
|
||||
unmatched,
|
||||
seed,
|
||||
})
|
||||
activeMockRuntime.install()
|
||||
},
|
||||
disableOutboundMocks: () => {
|
||||
assertTestEnv('disableOutboundMocks')
|
||||
if (activeMockRuntime) {
|
||||
activeMockRuntime.restore()
|
||||
activeMockRuntime = undefined
|
||||
}
|
||||
},
|
||||
getOutboundCalls: (name?: string): ReadonlyArray<OutboundCallRecord> => {
|
||||
assertTestEnv('getOutboundCalls')
|
||||
return activeMockRuntime?.getCalls(name) ?? []
|
||||
},
|
||||
},
|
||||
}
|
||||
fastify.decorate('apophis', decorations)
|
||||
// Runtime validation: never register hooks in production
|
||||
const isProd = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod'
|
||||
if (opts.runtime && opts.runtime !== 'off' && !isProd) {
|
||||
registerValidationHooks(fastify, { validateRuntime: true, runtimeLevel: opts.runtime })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user