How Instatic Uses TypeBox for Boundary Validation: A Complete Guide
Instatic validates every untrusted input crossing system boundaries by passing it through TypeBox schemas using a "validate-then-trust" workflow with helpers like parseValue, safeParseValue, and compiled validators for hot paths.
In the CoreBunch/Instatic codebase, TypeBox boundary validation serves as the single source of truth for all data integrity. Whether handling HTTP requests, persisted JSON, or plugin manifests, the architecture enforces strict runtime checks that eliminate parallel TypeScript interfaces and ensure type safety across the entire stack.
The "Validate-Then-Trust" Architecture
Instatic treats every external datum as a potential threat until proven otherwise. The validation workflow follows three strict steps: define a schema using @sinclair/typebox, parse the incoming value with specialized helpers, and cache compiled validators for performance-critical paths. This pattern appears consistently across HTTP handlers, storage layers, and plugin systems according to the repository's architectural tests in src/__tests__/architecture/boundary-validation.test.ts.
Core Validation Helpers in typeboxHelpers.ts
The primary validation API lives in src/core/utils/typeboxHelpers.ts. This module exports the Type builder, Static type extractor, and four critical boundary functions that handle different failure modes.
Hard Boundaries with parseValue
For critical boundaries where invalid data represents an unrecoverable error, Instatic uses parseValue(schema, value). This function performs strict parsing and throws immediately on validation failure, making it ideal for HTTP request bodies and configuration files.
import { Type, parseValue } from '@core/utils/typeboxHelpers';
const CreateUserSchema = Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 })
});
// Throws if validation fails
const user = parseValue(CreateUserSchema, rawBody);
Soft Boundaries with safeParseValue
When callers need to handle validation failures gracefully, safeParseValue(schema, value) returns a discriminated union: { ok: true; value } | { ok: false; errors }. This pattern appears in user preferences and optional metadata parsing where partial failure is acceptable.
Fallback Handling and Array Filtering
The withFallback(schema, fallback) helper annotates schemas with default values for tolerant parsers, while filterArray(itemSchema, values) keeps only entries that satisfy a schema—useful for cleaning imported data without failing the entire operation. The module also exports formatValueErrors for human-readable error messages.
Performance Optimization with Compiled Validators
To avoid the overhead of Value.Check on every call, src/core/utils/typeboxCompiler.ts implements a caching layer. Functions like compiled(schema), compiledCheck, compiledSafeParseValue, and compiledDecode compile a TypeBox validator once per schema object and reuse it across subsequent validations.
import { compiledCheck, compiledDecode } from '@core/utils/typeboxCompiler';
import { DataRowSchema } from '@/modules/data/rowSchema';
if (!compiledCheck(DataRowSchema, rawRow)) {
throw new Error('Invalid data row');
}
const row = compiledDecode(DataRowSchema, rawRow);
Boundary-Specific Implementation Patterns
Different system boundaries require specialized validation strategies. Instatic centralizes these patterns in dedicated modules to ensure consistent TypeBox boundary validation across the application.
HTTP Request Bodies in server/http.ts
Server-side handlers validate incoming payloads using readValidatedBody in server/http.ts. This utility extracts the raw body, enforces size limits, runs safeParseValue against the provided schema, and returns either the typed value or null—triggering a badRequest response on failure.
import { readValidatedBody, badRequest } from '../../http';
const body = await readValidatedBody(req, CreateUserSchema);
if (!body) return badRequest('Invalid request body');
// body is now typed as { email: string; password: string }
Client-Side API Calls in apiClient.ts
The client HTTP layer (src/core/http/apiClient.ts) automatically validates responses through apiRequest and apiBlobRequest. These helpers include credentials, execute the fetch, and validate the JSON payload against a TypeBox schema, throwing a unified ApiError on any mismatch.
import { apiRequest } from '@core/http';
import { Type } from '@core/utils/typeboxHelpers';
const PostsResponseSchema = Type.Object({
rows: Type.Array(Type.Object({
id: Type.String(),
title: Type.String()
}))
});
const data = await apiRequest('/admin/api/cms/posts', {
schema: PostsResponseSchema
});
Persisted JSON and Plugin Manifests
For data stored in localStorage, database JSON columns, or plugin manifests, src/core/utils/jsonValidate.ts provides safeParseJson, parseJsonWithFallback, and parseJsonResponse. The persistence layer (src/core/persistence/validate.ts) and plugin system (src/core/plugins/manifest.ts) reuse the same TypeBox schemas, ensuring validation consistency across storage boundaries.
import { parseJsonWithFallback } from '@core/utils/jsonValidate';
import { EditorPreferencesSchema } from '@/modules/editor/preferences';
const prefs = parseJsonWithFallback(
localStorage.getItem('editorPrefs') ?? '',
EditorPreferencesSchema,
DEFAULT_PREFERENCES
);
Source of Truth: Schemas Over Interfaces
Instatic enforces a strict architectural rule: the TypeBox schema is the authoritative definition. Developers derive TypeScript types using Static<typeof Schema> (re-exported in typeboxHelpers.ts), eliminating duplicate interface declarations. Architectural tests verify that every boundary validation follows this pattern, ensuring no untyped data enters the system.
Summary
- TypeBox boundary validation in Instatic follows a "validate-then-trust" principle for all external data crossing system boundaries.
src/core/utils/typeboxHelpers.tsprovidesparseValuefor hard boundaries andsafeParseValuefor soft boundaries, pluswithFallbackandfilterArrayfor tolerant parsing.src/core/utils/typeboxCompiler.tscaches compiled validators viacompiledCheckandcompiledDecodeto optimize hot-path performance.- Server-side request validation uses
readValidatedBodyinserver/http.ts, while client calls useapiRequestfromsrc/core/http/apiClient.ts. - Persisted JSON and plugin manifests validate through
src/core/utils/jsonValidate.ts, sharing schemas withsrc/core/persistence/validate.tsandsrc/core/plugins/manifest.ts. - All TypeScript types derive from schemas via
Static<typeof Schema>, with architectural tests insrc/__tests__/architecture/boundary-validation.test.tsenforcing compliance.
Frequently Asked Questions
What is the difference between parseValue and safeParseValue in Instatic?
parseValue performs strict validation and throws immediately on failure, making it suitable for hard boundaries like HTTP request bodies where invalid data is an unrecoverable error. safeParseValue returns a discriminated union { ok: true; value } | { ok: false; errors } that allows the caller to handle validation failures gracefully, ideal for soft boundaries such as user preferences or optional configuration.
How does Instatic optimize TypeBox validation performance for high-frequency operations?
Instatic caches compiled validators in src/core/utils/typeboxCompiler.ts using functions like compiled, compiledCheck, compiledSafeParseValue, and compiledDecode. These utilities compile the TypeBox schema once per unique schema object and reuse the validator across subsequent calls, avoiding the runtime overhead of repeated Value.Check operations.
Where does Instatic validate HTTP request bodies and API responses?
Server-side request bodies are validated in server/http.ts using the readValidatedBody helper, which returns null for invalid payloads and enforces size limits. Client-side responses are validated automatically by apiRequest and apiBlobRequest in src/core/http/apiClient.ts, which throw ApiError when response data fails schema validation.
How does Instatic ensure TypeBox schemas remain the single source of truth?
The codebase derives all TypeScript types from TypeBox schemas using Static<typeof Schema>, explicitly avoiding parallel interface declarations. Architectural tests in src/__tests__/architecture/boundary-validation.test.ts enforce this rule, ensuring every boundary validation references a TypeBox schema rather than a standalone type definition.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →