Data Validation in the TypeScript SDK for CommonGrants: A Complete Guide

The CommonGrants TypeScript SDK implements a dual-layer validation strategy using Zod schemas for runtime type safety and compile-time type inference, ensuring all API requests and responses strictly conform to the Common Grants Protocol specification.

The Common Grants Protocol provides a standardized API specification for grant opportunities, and its official TypeScript SDK delivers robust data validation to prevent runtime errors. This article examines how the SDK handles data validation TypeScript SDK CommonGrants implementations across compile-time and runtime layers, referencing the actual source code in the hhs/simpler-grants-protocol repository.

How the TypeScript SDK Validates Data: Architecture Overview

The validation architecture relies on three interconnected layers that work together to ensure data integrity. This design provides static type safety during development and runtime assurance when processing API responses.

Compile-Time Validation via Zod Type Inference

The SDK generates TypeScript types automatically from Zod schemas using z.infer. This approach ensures that TypeScript definitions in lib/ts-sdk/src/types.ts remain synchronized with the runtime validation rules defined in the schema files. When developers use SDK methods, they receive full IntelliSense and compile-time checking against the exact shapes defined in the Common Grants Protocol.

Runtime Validation with Zod Schemas

Every data structure in the SDK—whether request parameters, response bodies, or custom fields—has a corresponding Zod schema in lib/ts-sdk/src/schemas/zod/models.ts. These schemas enforce constraints such as string length, URL format, enum values, and required fields. When the SDK receives data from the API, it calls .parse() on the appropriate schema, which either returns a typed object or throws a ZodError if validation fails.

Key Validation Components and Source Files

Schema Definitions in models.ts

The file lib/ts-sdk/src/schemas/zod/models.ts serves as the single source of truth for all data structures. It defines schemas such as OpportunityBaseSchema, PaginatedBodyParamsSchema, and OppSearchRequestSchema. These definitions include field types, optional markers, and validation constraints that apply to both requests and responses.

Type Inference in types.ts

Located at lib/ts-sdk/src/types.ts, this file exports TypeScript interfaces derived from the Zod schemas. For example, the OppSearchRequest type is inferred directly from its Zod counterpart, ensuring that TypeScript definitions automatically update when schemas change. This file typically references lines 67-78 for core request type definitions.

Response Parsing in client.ts

The lib/ts-sdk/src/client/client.ts file contains the parseItem helper function (lines 295-304) that validates API responses. When the SDK receives data, this helper calls schema.parse(item) for each response entry. If parsing succeeds, the method returns a typed value; if it fails, the SDK captures the ZodError and propagates it for error handling.

Custom Field Validation in with-custom-fields.ts

For applications requiring domain-specific fields, lib/ts-sdk/src/extensions/with-custom-fields.ts (lines 14-30) provides the withCustomFields extension. This utility accepts optional Zod schemas via the valueSchema property for each custom field. When validating, the SDK checks custom field values against these schemas, ensuring that extended opportunity data meets application-specific constraints.

JSON Schema Cross-Validation in Test Utilities

To maintain fidelity with the official Common Grants Protocol specification, the SDK includes lib/ts-sdk/__tests__/utils/ajv-validator.ts. This file implements an AJV validator that cross-checks Zod schemas against upstream JSON-Schema definitions during the fuzz-testing suite. This ensures that runtime validation rules remain compatible with the protocol's canonical specification.

Practical Validation Examples

Validating Request Bodies Before API Calls

Before sending data to the API, you can validate request objects using the exported Zod schemas:

import { z } from "zod";
import { OppSearchRequest } from "@common-grants/sdk/types";
import { client } from "@common-grants/sdk";

// Build a request (could be user-provided)
const request: Partial<OppSearchRequest> = {
  search: "climate",
  filters: {
    status: { in: ["open", "forecasted"] },
  },
};

// Validate using the generated Zod schema (exported from models.ts)
const requestSchema = z.object({
  search: z.string().optional(),
  filters: z.object({
    status: z
      .object({ in: z.array(z.string()) })
      .optional(),
  }).optional(),
  sorting: z.any().optional(),
  pagination: z.any().optional(),
});

const parsed = requestSchema.parse(request);   // throws ZodError if invalid

// Safe call – the SDK will re-validate the response automatically
const result = await client.searchOpportunities(parsed);

Source: request type definition – types.ts line 67-78

Automatic Response Validation

The SDK automatically validates API responses using the parseItem helper in the client:

import { client } from "@common-grants/sdk";

async function listOpenOpportunities() {
  // The client internally parses each item with OpportunityBaseSchema
  const response = await client.listOpportunities({
    filters: { status: { in: ["open"] } },
  });

  // `response.items` is now typed as OpportunityBase[]
  response.items.forEach((opp) => {
    console.log(opp.id, opp.title, opp.status.value);
    // If any field didn't match the schema, the call would have thrown earlier.
  });
}

Underlying validation: client.ts uses schema.parse(item) for each response entry – see the parseItem helper in client.ts line 295-304.

Validating Custom Field Values

For opportunities with custom fields, use the with-custom-fields extension to validate domain-specific constraints:

import { withCustomFields } from "@common-grants/sdk/extensions";

// Define a custom field with a Zod schema that only accepts positive integers
const myCustomField = {
  key: "priority",
  value: 5,
  // Optional Zod schema for the value
  valueSchema: z.number().int().positive(),
};

// Extend a base opportunity with the custom field
const opp = await client.getOpportunity("123e4567-e89b-12d3-a456-426614174000");

// Validate custom fields
withCustomFields(opp.customFields).validate(); // throws if `priority` is not a positive int

Source: custom-field handling – with-custom-fields.ts line 14-30

Summary

  • Dual-layer validation strategy: The SDK combines Zod schemas for runtime parsing with z.infer for compile-time TypeScript types, ensuring end-to-end type safety.
  • Single source of truth: All data structures are defined in lib/ts-sdk/src/schemas/zod/models.ts, from which both runtime validators and TypeScript interfaces are derived.
  • Automatic response validation: The parseItem helper in lib/ts-sdk/src/client/client.ts validates every API response entry against Zod schemas, throwing descriptive errors for mismatches.
  • Extensible custom field support: The with-custom-fields extension allows developers to attach Zod schemas to custom opportunity fields, enabling domain-specific validation logic.
  • Specification fidelity: AJV-based cross-validation in lib/ts-sdk/__tests__/utils/ajv-validator.ts ensures Zod schemas remain compatible with the official Common Grants Protocol JSON Schema definitions.

Frequently Asked Questions

What validation library does the CommonGrants TypeScript SDK use?

The SDK uses Zod as its primary validation library. All schemas are defined in lib/ts-sdk/src/schemas/zod/models.ts and related files, providing a single source of truth for data shapes and constraints. The SDK leverages Zod's type inference capabilities to generate TypeScript definitions automatically, ensuring that static types and runtime validators never diverge.

How does the SDK handle validation errors at runtime?

When parsing fails, Zod throws a ZodError containing detailed issue information. The SDK catches these errors in lib/ts-sdk/src/client/client.ts through the parseItem helper (lines 295-304), which calls schema.parse(item) for each response entry. If validation fails, the error propagates to the caller, where CLI utilities transform the technical Zod error into user-friendly validation messages indicating exactly which fields failed validation and why.

Can I add custom validation rules for opportunity fields?

Yes. The SDK supports custom field validation through the with-custom-fields extension located in lib/ts-sdk/src/extensions/with-custom-fields.ts (lines 14-30). When defining a custom field, you can provide an optional valueSchema property containing a Zod schema. The extension's validate() method then checks custom field values against these schemas, allowing you to enforce domain-specific constraints such as numeric ranges, specific string patterns, or complex object structures beyond the base protocol specification.

How does the SDK ensure TypeScript types match the API specification?

The SDK maintains specification fidelity through two mechanisms. First, TypeScript types in lib/ts-sdk/src/types.ts are derived directly from Zod schemas using z.infer, ensuring they automatically update when schemas change. Second, the test suite includes lib/ts-sdk/__tests__/utils/ajv-validator.ts, which implements an AJV validator to cross-check Zod schemas against upstream JSON-Schema definitions during fuzz testing. This ensures the SDK's validation logic remains faithful to the official Common Grants Protocol specification.

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 →