How Freebuff Handles Strict Typing with TypeScript: A Layered Type Safety Architecture

Freebuff enforces rigorous type safety through a layered strategy combining TypeScript's strict compiler settings in tsconfig.base.json, project references for cross-package validation, and Zod runtime schemas using z.strictObject to prevent data drift.

Freebuff implements a "strict-by-default" typing model that guards against errors from development through production. By layering static analysis with runtime validation, the open-source AI agent framework ensures that type contracts remain intact across its monorepo architecture. This article examines the specific implementation details in the CodebuffAI/freebuff repository that enable comprehensive TypeScript strict typing.

Compile-Time Strictness via tsconfig.base.json

Freebuff centralizes its type enforcement in the root tsconfig.base.json file, ensuring all packages inherit identical compiler safeguards.

Strict Compiler Flags

The base configuration enables strict: true, which automatically activates strictNullChecks, noImplicitAny, noImplicitReturns, and strictFunctionTypes. Additional safeguards include forceConsistentCasingInFileNames to prevent cross-platform import errors. All project sub-configs extend this base file, meaning every package—whether common, agents, or sdk—compiles under identical strict rules.

// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitReturns": true,
    "forceConsistentCasingInFileNames": true
  }
}

Cross-Package Type Safety with Project References

The top-level tsconfig.json extends the base configuration and establishes a project reference graph that maintains type boundaries across package boundaries.

Path Aliases and References

The configuration defines path aliases (@codebuff/*) that enable TypeScript to type-check imports across package boundaries. The references section treats each package as a separate compilation unit while preserving type safety across the monorepo. This prevents leaky abstractions where one package might inadvertently bypass another's type constraints.

// tsconfig.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "paths": {
      "@codebuff/*": ["packages/*/src"]
    }
  },
  "references": [
    { "path": "./packages/common" },
    { "path": "./packages/sdk" }
  ]
}

Runtime Validation with Zod Schemas

Static typing alone cannot validate external data. Freebuff supplements compile-time checks with Zod schemas that enforce strict object shapes at runtime.

Strict Object Validation

In common/src/types/mcp.ts, critical configuration objects use z.strictObject rather than z.object. This rejects unknown keys at runtime, preventing drift between TypeScript types and actual data received from external services or environment variables.

// common/src/types/mcp.ts
import { z } from 'zod/v4';

export const mcpConfigSchema = z.strictObject({
  type: z.enum(['stdio', 'http']),
  command: z.string(),
  args: z.array(z.string())
});

// Runtime validation rejects extra properties
const parsed = mcpConfigSchema.parse({
  type: 'stdio',
  command: 'node',
  args: ['app.js'],
  unexpected: true  // Throws ZodError
});

Utility Functions for Schema Debugging

Freebuff includes helper utilities to bridge the gap between Zod schemas and developer tooling.

Schema Serialization

The schemaToJsonStr function in common/src/util/zod-schema.ts converts Zod schemas into clean JSON representations. It strips internal $schema meta-properties to produce focused, shareable type documentation that matches the runtime contract exactly.

// common/src/util/zod-schema.ts
export function schemaToJsonStr(schema: z.ZodTypeAny): string {
  const json = zodToJsonSchema(schema);
  delete (json as Record<string, unknown>).$schema;
  return JSON.stringify(json, null, 2);
}

SDK-Level Type Contracts

The Freebuff SDK exposes only strictly typed interfaces derived from Zod schemas, ensuring consumers receive IntelliSense while maintaining runtime validation.

Typed Public APIs

In sdk/src/tools/read-files.ts and related entry points, the SDK re-exports inferred types alongside their validation schemas. This creates "defensive programming" boundaries where callers get compile-time autocomplete, but the underlying implementation still validates input using the Zod parser.

// sdk/src/index.ts
export { mcpConfigSchema as MCPConfigSchema } from '@codebuff/common/src/types/mcp';
export type MCPConfig = z.infer<typeof MCPConfigSchema>;

// Consumer usage gets both IntelliSense and runtime safety
import { MCPConfig } from '@codebuff/sdk';

const cfg: MCPConfig = { type: 'http', url: 'https://example.com' };

Summary

  • Strict inheritance: tsconfig.base.json enforces strict: true across all packages, preventing any types and unchecked nulls.
  • Monorepo safety: Project references and @codebuff/* path aliases maintain type boundaries between packages.
  • Runtime enforcement: z.strictObject in common/src/types/mcp.ts rejects unknown keys, preventing configuration drift.
  • Developer tooling: The schemaToJsonStr utility converts schemas to clean JSON for debugging and documentation.
  • Dual validation: SDK functions expose Zod-derived types that provide both compile-time IntelliSense and runtime parsing guarantees.

Frequently Asked Questions

What specific TypeScript strict flags does Freebuff enable?

Freebuff enables strict: true in tsconfig.base.json, which activates strictNullChecks, noImplicitAny, noImplicitReturns, and strictFunctionTypes. It also explicitly sets forceConsistentCasingInFileNames to ensure import paths match file system casing across operating systems.

How does Freebuff prevent runtime data from violating TypeScript types?

Freebuff uses Zod schemas with z.strictObject (as seen in common/src/types/mcp.ts) to parse external data. Unlike standard z.object, strictObject throws errors when unexpected properties exist, ensuring that runtime data conforms exactly to the TypeScript interface definitions.

Why does Freebuff use project references instead of a single tsconfig?

Project references allow TypeScript to treat each package (common, sdk, agents) as a separate compilation unit while maintaining type safety across boundaries. This structure enforces that changes in one package correctly propagate type errors to dependent packages without requiring complete rebuilds of the entire monorepo.

Where does the SDK expose typed interfaces derived from Zod schemas?

The SDK exposes these interfaces in entry points like sdk/src/index.ts and specific tool files such as sdk/src/tools/read-files.ts. These files export both the Zod schema objects and their inferred TypeScript types, allowing consumers to use z.infer<typeof Schema> for compile-time checking while the schema handles runtime validation.

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 →