Skip to main content

Schema Package (@schwab/schema)

Overview

The @schwab/schema package provides comprehensive TypeScript schema definitions and runtime validation using Zod for the entire Charles Schwab monorepo. It serves as the single source of truth for all data structures, API responses, UI component props, and transformation schemas, ensuring type safety and runtime validation across all applications.

Architecture

Package Configuration

Module Structure

Schema Package Exports
{
"imports": {
"#schemas/ui/*": "./src/ui/*.ts",
"#schemas/enums/*": "./src/enums/*.ts",
"#schemas/native-enums/*": "./src/native-enums/*.ts",
"#schemas/factories/*": "./src/factories/*.ts",
"#schemas/fetched/*": "./src/fetched/*.ts",
"#schemas/global/data-layer/*": "./src/global/data-layer/*.ts",
"#schemas/global/feature-flag/*": "./src/global/feature-flag/*.ts"
},
"exports": {
"./ui/*": "./src/ui/*.ts",
"./native-enums/*": "./src/native-enums/*.ts",
"./fetched/*": "./src/fetched/*.ts",
"./TResponse": "./src/fetch-types/TResponse.ts"
}
}

Core Schema Categories

1. Response Type Definitions

Standardized API Response Pattern:

Universal Response Schema
// src/fetch-types/TResponse.ts
import { z } from 'zod';

export const RRbusApiErrorSchema = z.object({
status: z.number(),
source: z.string(),
title: z.string(),
detail: z.string(),
});

export const RRbusApiErrorResponseSchema = z.object({
errors: z.array(RRbusApiErrorSchema),
});

const ResponseCreateSchema = z.object({
isOk: z.boolean(),
error: z.string().or(z.number()).or(RRbusApiErrorResponseSchema).nullable(),
cacheTags: z.array(z.string()).nullish(),
});

export type TResponse<T> = z.infer<typeof ResponseCreateSchema> & { data: T };

Usage Pattern:

// Standardized response across all API calls
const response: TResponse<UserData> = await fetchUserData(userId);

if (response.isOk) {
// TypeScript knows response.data is UserData
console.log(response.data.name);
} else {
// Handle error with type safety
console.error(response.error);
}

2. UI Component Schemas

Component Schema Structure:

Person Component Schema
// src/ui/Person/PersonSchema.ts
import { z } from 'zod';
import { DataLayerComponentSchema } from '#schemas/global/data-layer/DataLayerComponentSchema';
import { FeatureFlagSchema } from '#schemas/global/feature-flag/FeatureFlagSchema';
import { ERole } from '#schemas/native-enums/ERole';
import { MarqueeSchema } from '#schemas/ui/Marquee/MarqueeSchema';
import { LockupSchema } from '#schemas/ui/Lockup/LockupSchema';

export const PersonSchema = z.object({
analytics: FetchedApiMetadataAnalyticsSchema.nullish(),
body: z.string().nullish(),
children: z.custom<React.ReactNode>().optional(),
components: z.array(
z.discriminatedUnion('role', [
LockupSchema.pick({
role: true,
body: true,
heading: true,
headingStyle: true,
margin: true,
}),
MarqueeSchema.omit({ heading: true, subheading: true, headingLevel: true }),
]),
),
complianceCode: z.string().nullable(),
dataLayer: z.object({
[EDataLayer.Component]: DataLayerComponentSchema.optional(),
}).optional(),
role: z.literal(ERole.Person),
});

export type TPerson = z.infer<typeof PersonSchema>;

Component Categories:

CategoryExamplesDescription
Layout ComponentsMarquee, Container, Deck, MosaicPage layout and structure
Content ComponentsRichText, Heading, Image, VideoContent presentation
Interactive ComponentsButton, Cta, FormBuilder, ModalUser interaction elements
Navigation ComponentsMeganav, Nav, Menu, PaginationNavigation and menu systems
Data ComponentsTable, HighChart, SearchBarData visualization and input

3. Native Enums

Comprehensive Enum Definitions:

Core Enum Examples
// ERole.ts - Component role definitions
export enum ERole {
Person = 'person',
Story = 'story',
Topic = 'topic',
Collection = 'collection',
Marquee = 'marquee',
Button = 'button',
Image = 'image'
}

// EDrupalSource.ts - Content source identification
export enum EDrupalSource {
Education = 'education',
Marketing = 'marketing',
ClientCenter = 'client-center'
}

// EDataLayer.ts - Analytics data layer types
export enum EDataLayer {
Component = 'component',
Page = 'page',
Event = 'event'
}

Available Enum Categories:

Enum CategoryCountExamples
UI Enums15+ERole, EAlignment, EHeadingLevel
Data Enums10+EDrupalSource, EDataLayer, EChannel
Interaction Enums8+EActionType, EEvent, ENavMenu
Configuration Enums12+EPageMode, ECacheTags, EVercelWebhookEvents

4. Feature Flag Schemas

Feature Flag Type Definitions:

Feature Flag Schema
// src/global/feature-flag/FeatureFlagSchema.ts
import { z } from 'zod';

export const FeatureFlagSchema = z.object({
key: z.string(),
enabled: z.boolean(),
value: z.union([z.string(), z.number(), z.boolean(), z.object({})]).optional(),
context: z.object({
userId: z.string().optional(),
environment: z.string().optional(),
segment: z.string().optional(),
}).optional(),
});

export const FeatureFlagCollectionSchema = z.record(z.string(), FeatureFlagSchema);

export type TFeatureFlag = z.infer<typeof FeatureFlagSchema>;
export type TFeatureFlagCollection = z.infer<typeof FeatureFlagCollectionSchema>;

5. Data Layer Schemas

Analytics and Tracking Schemas:

Data Layer Schema
// src/global/data-layer/DataLayerComponentSchema.ts
export const DataLayerComponentSchema = z.object({
componentType: z.string(),
componentName: z.string().optional(),
componentId: z.string().optional(),
componentVersion: z.string().optional(),
content: z.object({
title: z.string().optional(),
category: z.string().optional(),
tags: z.array(z.string()).optional(),
}).optional(),
interaction: z.object({
type: z.string(),
target: z.string().optional(),
value: z.string().optional(),
}).optional(),
});

export type TDataLayerComponent = z.infer<typeof DataLayerComponentSchema>;

Factory Functions

1. Schema Factories

Dynamic Schema Creation:

Component Factory Functions
// src/factories/ui/createComponentSchema.ts
export function createComponentSchema<T extends z.ZodTypeAny>(
role: ERole,
baseSchema: T,
additionalProps?: z.ZodRawShape
) {
return baseSchema.extend({
role: z.literal(role),
...additionalProps,
});
}

// Usage
const CustomButtonSchema = createComponentSchema(
ERole.Button,
z.object({
text: z.string(),
variant: z.enum(['primary', 'secondary']),
}),
{
onClick: z.function().optional(),
}
);

2. Validation Factories

Validation Factory Pattern
// src/factories/validation/createValidator.ts
export function createValidator<T>(schema: z.ZodSchema<T>) {
return {
validate: (data: unknown): data is T => {
return schema.safeParse(data).success;
},

parse: (data: unknown): T => {
const result = schema.safeParse(data);
if (!result.success) {
throw new Error(`Validation failed: ${result.error.message}`);
}
return result.data;
},

safeparse: (data: unknown) => {
return schema.safeParse(data);
}
};
}

Advanced Schema Patterns

1. Discriminated Unions

Component Discrimination
// Polymorphic component schemas
export const ComponentSchema = z.discriminatedUnion('role', [
PersonSchema,
StorySchema,
TopicSchema,
CollectionSchema,
MarqueeSchema,
ButtonSchema,
ImageSchema,
]);

export type TComponent = z.infer<typeof ComponentSchema>;

// TypeScript automatically narrows types based on 'role' property
function renderComponent(component: TComponent) {
switch (component.role) {
case ERole.Person:
// TypeScript knows this is TPerson
return <PersonComponent {...component} />;
case ERole.Story:
// TypeScript knows this is TStory
return <StoryComponent {...component} />;
}
}

2. Conditional Schemas

Conditional Validation
// Dynamic schema based on component type
export const ConditionalComponentSchema = z.object({
role: z.nativeEnum(ERole),
data: z.any(),
}).superRefine((val, ctx) => {
switch (val.role) {
case ERole.Person:
const personResult = PersonSchema.safeParse(val.data);
if (!personResult.success) {
personResult.error.issues.forEach(issue =>
ctx.addIssue({ ...issue, path: ['data', ...issue.path] })
);
}
break;
// Additional cases...
}
});

3. Schema Composition

Schema Composition Patterns
// Base component schema
const BaseComponentSchema = z.object({
id: z.string().optional(),
className: z.string().optional(),
dataAttributes: z.record(z.string()).optional(),
});

// Extend base schema for specific components
export const InteractiveComponentSchema = BaseComponentSchema.extend({
onClick: z.function().optional(),
disabled: z.boolean().optional(),
ariaLabel: z.string().optional(),
});

// Compose multiple schemas
export const AccessibleInteractiveSchema = InteractiveComponentSchema
.merge(AccessibilitySchema)
.merge(AnalyticsSchema);

Runtime Validation Patterns

1. API Response Validation

API Response Validation
import { getDrupalAsset } from '@schwab/fetch/getDrupalAsset';
import { DrupalAssetSchema } from '@schwab/schema/ui/DrupalAsset/DrupalAssetSchema';

export async function getValidatedDrupalAsset(nodeId: string) {
const response = await getDrupalAsset(nodeId);

if (!response.isOk) {
throw new Error(`Failed to fetch asset: ${response.error}`);
}

// Runtime validation with Zod
const validatedData = DrupalAssetSchema.parse(response.data);

return validatedData;
}

2. Form Validation

Form Schema Validation
export const ContactFormSchema = z.object({
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
email: z.string().email('Invalid email format'),
phone: z.string().regex(/^\(\d{3}\) \d{3}-\d{4}$/, 'Invalid phone format'),
investmentAmount: z.number().min(1000, 'Minimum investment is $1,000'),
investmentGoals: z.array(z.enum(['retirement', 'education', 'general'])).min(1),
});

export type TContactForm = z.infer<typeof ContactFormSchema>;

// Usage in React Hook Form
const form = useForm<TContactForm>({
resolver: zodResolver(ContactFormSchema),
});

Performance Optimization

1. Lazy Schema Loading

Lazy Schema Imports
// Conditionally load complex schemas
export const getLazySchema = async (componentType: ERole) => {
switch (componentType) {
case ERole.HighChart:
const { HighChartSchema } = await import('./HighChart/HighChartSchema');
return HighChartSchema;
case ERole.FormBuilder:
const { FormBuilderSchema } = await import('./FormBuilder/FormBuilderSchema');
return FormBuilderSchema;
default:
return BaseComponentSchema;
}
};

2. Schema Caching

Schema Validation Caching
const schemaCache = new Map<string, z.ZodSchema>();

export function getCachedSchema(key: string, schemaFactory: () => z.ZodSchema) {
if (!schemaCache.has(key)) {
schemaCache.set(key, schemaFactory());
}
return schemaCache.get(key)!;
}

Testing Integration

1. Schema Testing Utilities

Schema Test Helpers
// src/test/schemaTestHelpers.ts
export function createSchemaTest<T>(
schema: z.ZodSchema<T>,
validExamples: unknown[],
invalidExamples: unknown[]
) {
return {
testValid: () => {
validExamples.forEach((example, index) => {
expect(() => schema.parse(example))
.not.toThrow(`Valid example ${index} should parse`);
});
},

testInvalid: () => {
invalidExamples.forEach((example, index) => {
expect(() => schema.parse(example))
.toThrow(`Invalid example ${index} should not parse`);
});
}
};
}

// Usage
const personSchemaTest = createSchemaTest(
PersonSchema,
[validPersonData1, validPersonData2],
[invalidPersonData1, invalidPersonData2]
);

2. Mock Data Schema Validation

Mock Data Validation
import mockPersonData from '@schwab/mock-data/person-all-components.json';
import { PersonSchema } from '@schwab/schema/ui/Person/PersonSchema';

describe('Mock Data Schema Validation', () => {
it('validates person mock data against schema', () => {
expect(() => PersonSchema.parse(mockPersonData)).not.toThrow();
});
});

Error Handling and Debugging

1. Enhanced Error Messages

Custom Error Handling
export function createDetailedZodError(error: z.ZodError, context?: string) {
const errorMessage = error.issues.map(issue => {
const path = issue.path.join('.');
return `${context ? `${context}: ` : ''}Field '${path}' ${issue.message}`;
}).join('\n');

return new Error(`Schema validation failed:\n${errorMessage}`);
}

// Usage
try {
PersonSchema.parse(invalidData);
} catch (error) {
if (error instanceof z.ZodError) {
throw createDetailedZodError(error, 'Person Component');
}
throw error;
}

2. Development-Time Validation

Development Validation
export function createDevValidator<T>(schema: z.ZodSchema<T>, componentName: string) {
return (data: unknown): T => {
if (process.env.NODE_ENV === 'development') {
const result = schema.safeParse(data);
if (!result.success) {
console.error(`${componentName} validation failed:`, result.error);
// Continue in development with warning
return data as T;
}
return result.data;
}

// Production: parse with errors
return schema.parse(data);
};
}

Migration and Versioning

1. Schema Evolution

Schema Migration Pattern
// Version 1 schema
export const PersonSchemaV1 = z.object({
name: z.string(),
email: z.string().email(),
});

// Version 2 schema (with migration)
export const PersonSchemaV2 = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string().email(),
phone: z.string().optional(),
});

// Migration function
export function migratePersonV1toV2(v1Data: z.infer<typeof PersonSchemaV1>) {
const [firstName, ...lastNameParts] = v1Data.name.split(' ');
return {
firstName,
lastName: lastNameParts.join(' '),
email: v1Data.email,
};
}

Future Enhancements

Planned Improvements

  1. Auto-generated Documentation: Schema-driven component documentation
  2. Runtime Performance: Optimized validation for production environments
  3. Schema Registry: Centralized schema versioning and management
  4. GraphQL Integration: Automatic GraphQL schema generation from Zod schemas
  5. Advanced Validation: Custom validation rules for business logic

Integration Roadmap

  • OpenAPI Integration: Generate OpenAPI specs from Zod schemas
  • Database Schemas: Sync database schemas with Zod definitions
  • Real-time Validation: WebSocket message validation
  • Multi-language Support: Schema generation for other languages

The Schema package provides the foundational type system and runtime validation layer that ensures data integrity, type safety, and consistent API contracts across the entire Charles Schwab monorepo. Its comprehensive coverage and Zod-based validation enable confident development with strong guarantees about data structure and validity.