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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →