@axiomify/core
v6.3.3
Published
Axiomify framework core — router, AJV-compiled Zod validation, hook manager, dispatcher, module system. The foundation @axiomify/* packages build on.
Maintainers
Readme
@axiomify/core
The framework-agnostic engine behind Axiomify. Router, AJV validator, hook manager, dispatcher, module system.
Install
npm install @axiomify/core zodQuick example
import { Axiomify } from '@axiomify/core';
import { NativeAdapter } from '@axiomify/native';
import { z } from 'zod';
const app = new Axiomify({ logger: console });
app.enableRequestId(); // opt-in X-Request-Id (off by default)
app.route({
method: 'POST',
path: '/users',
schema: {
body: z.object({ name: z.string(), email: z.string().email() }),
response: z.object({ id: z.string(), name: z.string() }),
},
handler: async (req, res) => {
res.status(201).send({ id: 'usr_1', name: req.body.name });
},
});
new NativeAdapter(app, { port: 3000 }).listen(() =>
console.log('Ready on 3000'),
);What's in this package
Axiomifyclass — app construction, route/hook/group/plugin registrationRouter— radix-trie router with named params, wildcards, HEAD auto-handlingValidationCompiler— AJV + transform-aware Zod integrationHookManager— microtask-free hook execution with fast pathsRequestDispatcher— per-request orchestrationADAPTER_LOCK_TOKEN— adapter authentication symbolAxiomifyLogger— injectable structured logger interface
Documentation
See docs/packages/core.md for the full API reference.
v5 migration notes
X-Request-Idis now opt-in: callapp.enableRequestId()explicitlyapp.lockRoutes(reason)→app.lockRoutes(ADAPTER_LOCK_TOKEN, reason)— adapters authenticate with the symbol from@axiomify/coreapp.serializeris a read-only getter; useapp.setSerializer(fn). The 4.x 5-arg positional form(data, message, statusCode, isError, req) => ...was removed in 5.0 — only the single-argument({ data, message, statusCode, isError, req }) => ...form is accepted, and async serializers throw at adapter constructionroute.meta→route.openapi(deprecated alias kept through 5.x, removed in 6.0). Field shape mirrors the OAS 3.1.0 Operation Object — see openapi docsRouteMetatype was renamed toOpenApiOperationin 5.0 (alias kept through 5.x, removed in 6.0); in 6.0 the alias is removedAppPlugintype alias removed (the 1-arg(app) => voidshape still works at runtime as anAppConfigurator)
Validation Execution Order
When a request is dispatched, validation and hooks are executed in the following strict order:
onRequesthooks: Run immediately after request initialization.- Route Matching & Extraction: Path, query, and headers are parsed and matched.
onPreHandlerhooks: Run before route-specific validation is performed.- Input Validation:
params,query,headers, andbodyare validated using Zod/AJV schemas in that order. If validation fails, aValidationErroris thrown and processed viaonErrorhooks. - Route Handler: The route-specific handler function is executed.
onPostHandlerhooks: Run after the route handler successfully completes.- Response Validation: If a response schema is defined, the outgoing payload is validated. In development, a mismatch throws a
ValidationError; in production, it is logged as an error and allowed to continue. onErrorhooks: Run if any error is thrown during dispatch.onClosehooks: Run after response headers are sent and connection/stream closes.
Request State Immutability API
To prevent privilege escalation and coordinate data safely across hooks, req.state is a wrapped immutable object implementing the following methods:
req.state.set(key, value): Stores a value under the given key. Throws an error if the key has already been set (write-once immutability).req.state.get(key): Retrieves the value associated with the key.- If the key is
'user', the stored object is recursively frozen viaObject.freezeto prevent downstream mutations. - Direct property access (e.g.,
req.state.key = valueandreq.state.key) is proxied to the write-once set/get APIs for backwards compatibility.
Core Configuration Options
When creating an Axiomify instance, you can configure these options:
strictSchema(boolean, default:false): Iftrue, throws a startup error when registering a route that has a typed handler but no schema definition. You can ignore this on specific routes by adding a@axiomify-ignore-schemacomment.routeConflict('throw' | 'warn', default:'throw'): Determines whether to throw an error or emit a warning when registering conflicting parameterized route paths (e.g./users/:idand/users/:userId).
Custom Fallback Handlers
Use app.setNotFoundHandler and app.setMethodNotAllowedHandler to customize responses for missing routes and mismatched methods:
app.setNotFoundHandler((req, res) => {
res.status(404).send({ error: 'NotFound' }, 'Resource not found');
});
app.setMethodNotAllowedHandler((req, res) => {
res.status(405).send({ error: 'MethodNotAllowed' }, 'Method not supported');
});Service Container Sealing
The dependency injection container is sealed after bootstrap (app.listen() or app.build()). Calling provide on the AppContext post-bootstrap throws an error. Use app.forceProvide(token, value) in tests to bypass the seal guard.
Global Error Sanitization
In production, all database or internal system errors are masked as a generic 500 server error to prevent sensitive data leaks. You can use the exported createErrorSanitizer helper to safely map database driver errors (Prisma, MongoDB, Sequelize, etc.) to API errors in an onError hook:
import { createErrorSanitizer } from '@axiomify/core';
const sanitizeError = createErrorSanitizer({ logger: app.logger });
app.addHook('onError', async (err, req, res) => {
const sanitized = sanitizeError(err);
if (sanitized) {
res.status(sanitized.statusCode).send(
{
error: sanitized.message,
data: sanitized.data,
},
sanitized.message,
);
}
});