Thanks for your advise. Before publishing an issue, please check some components.
Before publishing an issue, please check whether the duplicated issue exists or not.
When you reporting a bug, then please write about those items:
- What version you're using
- If possible, give me an isolated way to reproduce the behavior.
- The behavior your expect to see, and the actual behavior.
I always welcome your suggestion. When you publishing a suggestion, then please write such items:
- A description of the problem you're trying to solve.
- An overview of the suggested solution.
- Examples of how the suggestion would work in various places.
- Code examples showing the expected behavior.
typia/
├── src/ # Source code
│ ├── executable/ # CLI commands and setup utilities
│ │ ├── typia.ts # Main CLI entry point
│ │ └── setup/ # Project setup and configuration
│ ├── factories/ # Metadata and AST node creation utilities
│ │ ├── MetadataFactory.ts # Core: Analyzes TS types → metadata
│ │ ├── ExpressionFactory.ts # Creates TypeScript expressions
│ │ ├── StatementFactory.ts # Creates TypeScript statements
│ │ └── internal/ # Internal factory utilities
│ ├── programmers/ # Code generation engines (the core logic!)
│ │ ├── IsProgrammer.ts # Generates is<T>() validation code
│ │ ├── AssertProgrammer.ts # Generates assert<T>() functions
│ │ ├── ValidateProgrammer.ts # Generates validate<T>() functions
│ │ ├── json/ # JSON serialization/parsing code gen
│ │ ├── llm/ # LLM function calling code gen
│ │ ├── protobuf/ # Protocol Buffers code gen
│ │ └── helpers/ # Common programming utilities
│ ├── transformers/ # AST transformation orchestrators
│ │ ├── CallExpressionTransformer.ts # Main: Routes typia.xxx() calls
│ │ ├── FileTransformer.ts # File-level transformations
│ │ └── features/ # Feature-specific transformers
│ │ ├── IsTransformer.ts # typia.is<T>() transformer
│ │ ├── AssertTransformer.ts # typia.assert<T>() transformer
│ │ ├── json/ # JSON feature transformers
│ │ ├── llm/ # LLM feature transformers
│ │ └── http/ # HTTP feature transformers
│ ├── schemas/ # Type definitions and metadata structures
│ │ ├── metadata/ # Core metadata type definitions
│ │ ├── json/ # JSON schema-related types
│ │ └── protobuf/ # Protocol Buffer schema types
│ ├── tags/ # Validation tag implementations (@tag decorations)
│ ├── typings/ # TypeScript utility types and interfaces
│ └── utils/ # General utility functions
├── test/ # Test files (⚠️ Large directory - 1000+ test files)
│ ├── src/features/ # Feature-specific tests (main test suite)
│ ├── schemas/ # Schema-related tests
│ └── structures/ # Test data structures
├── benchmark/ # Performance benchmarking code and results
├── website/ # Documentation website source
├── examples/ # Usage examples and demos
└── deploy/ # Deployment and publishing scripts
🎯 Key Directories for Newcomers:
- Start here:
src/transformers/features/- See how individual features work - Core logic:
src/programmers/- This is where the magic happens - Type analysis:
src/factories/MetadataFactory.ts- Understand how types become metadata - Examples:
test/src/features/- Real examples of how features should work
- Files: Use camelCase for regular files (e.g.,
jsonMetadata.ts) - Classes: Use PascalCase (e.g.,
TypeGuardError,MetadataFactory) - Interfaces: Prefix with 'I' (e.g.,
IValidation,IRandomGenerator) - Type aliases: Use PascalCase (e.g.,
Primitive,Resolved) - Functions: Use camelCase (e.g.,
stringify,validate) - Constants: Use UPPER_SNAKE_CASE for global constants
- Variables: Use camelCase
- Test files: Prefix with
test_(e.g.,test_stringify_object_recursive)
Before sending a pull request, please test your new code. Follow these steps carefully:
# Clean build - removes old lib/ directory and rebuilds everything
npm run buildTypia uses a custom test runner that discovers and executes functions starting with test_:
// test/src/features/test_is_string.ts
export function test_is_string(): void {
// Your test logic here
const input: unknown = "hello";
const result = typia.is<string>(input);
if (!result) throw new Error("String validation failed");
}# ⚠️ Note: The package.json uses a custom test system
# Run tests using the deploy script with test tag
npm run test
# Alternative: Run with ts-node directly
npx ts-node deploy --tag testIf tests fail, you have several debugging options:
Option A: VS Code Debugger (Recommended)
- Open VS Code in the project directory
- Set breakpoints in your code
- Press
F5or click "Start Debugging" - The debugger will stop at your breakpoints
Option B: Manual Debugging
// Add console.log statements in your test
export function test_debug_example(): void {
const input = "test";
console.log("Input:", input);
const result = typia.is<string>(input);
console.log("Result:", result);
if (!result) throw new Error("Test failed");
}Option C: Isolate Specific Tests Create a minimal test file to debug specific issues:
// debug_test.ts
import typia from "typia";
function debug() {
const validator = typia.is<string>;
console.log("Generated function:", validator.toString());
const result = validator("test");
console.log("Result:", result);
}
debug();Typia has different types of tests:
- Feature Tests (
test/src/features/): Test individual typia features - Schema Tests (
test/schemas/): Test schema generation and validation - Benchmark Tests (
benchmark/): Performance validation - Error Tests (
test-error/): Tests that should fail compilation
Run benchmarks to ensure your changes don't impact performance:
# Build and run benchmarks (takes several minutes)
cd benchmark
npm install
npm run build
npm run startType Guard Tests:
export function test_is_object(): void {
interface IPerson { name: string; age: number; }
const valid: unknown = { name: "John", age: 30 };
const invalid: unknown = { name: "John" }; // missing age
if (!typia.is<IPerson>(valid))
throw new Error("Valid object should pass");
if (typia.is<IPerson>(invalid))
throw new Error("Invalid object should not pass");
}JSON Tests:
export function test_json_stringify(): void {
interface IData { value: string; }
const input: IData = { value: "test" };
const result = typia.json.stringify<IData>(input);
const expected = JSON.stringify(input);
if (result !== expected)
throw new Error(`Expected ${expected}, got ${result}`);
}Error Handling Tests:
export function test_assert_error(): void {
try {
typia.assert<string>(123); // Should throw
throw new Error("Should have thrown an error");
} catch (error) {
if (!(error instanceof Error))
throw new Error("Should throw Error instance");
}
}When adding new features or fixing bugs, you should add corresponding tests. Here's how:
test/src/features/ # For typia feature tests (most common)
test/schemas/ # For schema-related tests
test-error/ # For tests that should fail compilation
test-esm/ # For ES module compatibility testsFollow the existing naming pattern:
test_[feature]_[specific_case].ts
Examples:
- test_is_string.ts # Basic string validation
- test_json_stringify_object.ts # JSON stringification of objects
- test_assert_recursive.ts # Assertion with recursive types
Test functions must follow these rules:
// ✅ Correct: exported function with test_ prefix
export function test_your_feature_name(): void {
// Test logic here
// Throw an Error if test fails
}
// ✅ Also correct: async test
export async function test_async_feature(): Promise<void> {
// Async test logic
}
// ❌ Incorrect: not exported
function test_private(): void { /* ... */ }
// ❌ Incorrect: wrong prefix
export function validate_something(): void { /* ... */ }// test/src/features/test_is_custom_object.ts
import typia from "typia";
import { RandomGenerator } from "typia/lib/utils/RandomGenerator";
export function test_is_custom_object(): void {
// Define the interface
interface IPerson {
name: string;
age: number;
email?: string; // optional property
hobbies: string[];
}
// Test with valid data
const validPerson: IPerson = {
name: RandomGenerator.string(),
age: RandomGenerator.number(),
email: RandomGenerator.email(),
hobbies: [RandomGenerator.string(), RandomGenerator.string()]
};
if (!typia.is<IPerson>(validPerson))
throw new Error("Valid person should pass validation");
// Test with invalid data - missing required property
const invalidPerson = {
name: RandomGenerator.string(),
// age is missing
hobbies: [RandomGenerator.string()]
};
if (typia.is<IPerson>(invalidPerson))
throw new Error("Invalid person should not pass validation");
// Test with wrong type
const wrongType = {
name: RandomGenerator.string(),
age: "not a number", // wrong type
hobbies: [RandomGenerator.string()]
};
if (typia.is<IPerson>(wrongType))
throw new Error("Person with wrong age type should not pass validation");
}Type Guards (typia.is<T>):
export function test_is_union_type(): void {
type StringOrNumber = string | number;
if (!typia.is<StringOrNumber>("hello"))
throw new Error("String should be valid");
if (!typia.is<StringOrNumber>(42))
throw new Error("Number should be valid");
if (typia.is<StringOrNumber>(true))
throw new Error("Boolean should not be valid");
}Assertions (typia.assert<T>):
export function test_assert_with_error(): void {
interface IUser { id: number; name: string; }
// Should not throw
const validUser = typia.assert<IUser>({ id: 1, name: "John" });
// Should throw
try {
typia.assert<IUser>({ id: "invalid", name: "John" });
throw new Error("Should have thrown assertion error");
} catch (error) {
if (!(error instanceof Error))
throw new Error("Should throw Error instance");
// Optionally check error message
if (!error.message.includes("id"))
throw new Error("Error should mention the invalid property");
}
}JSON Operations:
export function test_json_stringify_performance(): void {
interface ILargeObject {
items: Array<{ id: number; name: string; data: Record<string, any> }>;
}
const large: ILargeObject = {
items: Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
data: { value: i * 2, meta: `meta-${i}` }
}))
};
const start = Date.now();
const result = typia.json.stringify<ILargeObject>(large);
const end = Date.now();
const expected = JSON.stringify(large);
if (result !== expected)
throw new Error("JSON stringify result mismatch");
console.log(`Performance: ${end - start}ms for 1000 items`);
}Typia provides several utilities for testing:
import { RandomGenerator } from "typia/lib/utils/RandomGenerator";
// Generate random test data
const randomString = RandomGenerator.string();
const randomNumber = RandomGenerator.number();
const randomBoolean = RandomGenerator.boolean();
const randomArray = RandomGenerator.array(() => RandomGenerator.string());After creating your test, verify it works:
# Build first
npm run build
# Run all tests (includes your new test)
npm run test
# For faster iteration, create a minimal test runner:
# debug_runner.ts
import "./test/src/features/test_your_new_feature";- Clear naming: Test names should describe what they're testing
- Edge cases: Test boundary conditions, empty arrays, null values
- Error cases: Test that invalid inputs properly fail
- Performance: For core features, consider performance implications
- Documentation: Complex tests should have comments explaining the logic
Thanks for your contributing. Before sending a pull request to me, please check those components.
When you send a pull request, please include a description, of what your change intends to do, on the content. Title, make it clear and simple such below:
- Refactor features
- Fix #17
- Add tests for #28
As I've mentioned in the Contributing Code section, your PR should pass the test-automation module. If your PR includes new features that have not being handled in the ordinary test-automation module, then also update add the testing unit please.
If there're some specific reasons that could not pass the test-automation (not error but intended), then please update the ordinary test-automation module or write the reasons on your PR content and const me update the test-automation module.
Welcome to typia! This section will guide you through understanding the project architecture and making your first contribution. Typia's core magic lies in transforming TypeScript types into optimized runtime code using AST (Abstract Syntax Tree) transformations.
Before diving into code, understand what makes typia special:
// When you write this:
const result = typia.is<string>(input);
// Typia transforms it at compile-time to this optimized code:
const result = "string" === typeof input;This transformation happens through a sophisticated pipeline of TypeScript AST analysis and code generation.
Typia follows a clear transformation pipeline:
TypeScript Types → Metadata Analysis → Code Generation → AST Transformation → Optimized Runtime Code
src/
├── transformers/ # 🎯 AST transformation entry points
│ ├── CallExpressionTransformer.ts # Main transformer orchestrator
│ ├── FileTransformer.ts # File-level transformations
│ └── features/ # Feature-specific transformers
│ ├── IsTransformer.ts # Transforms typia.is<T>() calls
│ ├── AssertTransformer.ts # Transforms typia.assert<T>() calls
│ └── ValidateTransformer.ts # Transforms typia.validate<T>() calls
│
├── factories/ # 🏭 Metadata and AST node creation
│ ├── MetadataFactory.ts # Analyzes TypeScript types → metadata
│ ├── ExpressionFactory.ts # Creates TypeScript expressions
│ └── StatementFactory.ts # Creates TypeScript statements
│
├── programmers/ # 🔧 Code generation engines
│ ├── IsProgrammer.ts # Generates type guard functions
│ ├── AssertProgrammer.ts # Generates assertion functions
│ ├── ValidateProgrammer.ts # Generates validation functions
│ └── json/ # JSON-specific programmers
│
├── schemas/ # 📋 Type definitions and metadata structures
│ ├── metadata/ # Core metadata types
│ └── json/ # JSON schema types
│
└── utils/ # 🔨 Utility functions and helpers
// Input: TypeScript type like `{ name: string; age: number }`
// Output: Rich metadata describing the type structure
const metadata = MetadataFactory.analyze(checker, type);// Input: Metadata from step 1
// Output: TypeScript AST nodes for optimized validation code
const expression = IsProgrammer.write(metadata);// Input: Original typia.is<T>() call
// Output: Replaces call with generated validation code
CallExpressionTransformer.transform(node, generatedCode);- Create a Transformer (
src/transformers/features/YourFeatureTransformer.ts) - Create a Programmer (
src/programmers/YourFeatureProgrammer.ts) - Register in CallExpressionTransformer (add your transformer to the main transformer)
- Add Tests (
test/src/features/test_yourfeature.ts)
Let's say you want to add typia.isArray<T>() function:
- Create the Programmer (
src/programmers/IsArrayProgrammer.ts):
export namespace IsArrayProgrammer {
export const write = (metadata: Metadata): ts.Expression => {
return ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier("Array"),
"isArray"
),
undefined,
[ts.factory.createIdentifier("input")]
);
};
}- Create the Transformer (
src/transformers/features/IsArrayTransformer.ts):
export namespace IsArrayTransformer {
export const transform = (props: ITransformProps) =>
GenericTransformer.scalar({
...props,
method: "isArray",
write: (x) => IsArrayProgrammer.write(x.metadata),
});
}- Register in CallExpressionTransformer:
// Add to the transformer map
"isArray": IsArrayTransformer.transform,npm run dev # Watches for changes and rebuildsAdd console.log in programmers to see generated AST:
const expression = IsProgrammer.write(metadata);
console.log(ts.createPrinter().printNode(ts.EmitHint.Expression, expression, undefined));Create a simple test file and run the transformer:
// debug.ts
import typia from "typia";
const result = typia.is<string>("test"); // Your test caseThe project includes .vscode/launch.json. Press F5 to start debugging.
Metadata is the core data structure that represents TypeScript types:
interface Metadata {
any: boolean; // If type accepts any value
required: boolean; // If property is required
optional: boolean; // If property is optional
nullable: boolean; // If type can be null
functional: boolean; // If type is a function
atomics: MetadataAtomic[]; // Primitive types (string, number, etc.)
constants: MetadataConstant[]; // Literal values
templates: MetadataTemplate[]; // Template literal types
arrays: MetadataArrayType[]; // Array types
objects: MetadataObjectType[]; // Object types
// ... more properties
}AST (Abstract Syntax Tree) represents code structure. For example:
// Code: typia.is<string>(input)
// AST Structure:
CallExpression {
expression: PropertyAccessExpression {
expression: Identifier { text: "typia" },
name: Identifier { text: "is" }
},
typeArguments: [TypeReference { typeName: "string" }],
arguments: [Identifier { text: "input" }]
}- CallExpression: Function calls like
typia.is<T>() - TypeReference: Generic type parameters like
<string> - PropertyAccessExpression: Object property access like
typia.is - Identifier: Variable names and identifiers
All typia transformers follow this pattern:
export namespace YourTransformer {
export const transform = (props: ITransformProps) => {
// 1. Analyze the TypeScript type
const metadata = MetadataFactory.analyze(props.checker, props.type);
// 2. Generate optimized code
const expression = YourProgrammer.write(metadata);
// 3. Return the transformation result
return expression;
};
}JsonStringifyProgrammer.ts: Fast JSON serializationJsonParseProgrammer.ts: Type-safe JSON parsingJsonSchemaProgrammer.ts: JSON schema generation
LlmApplicationProgrammer.ts: LLM function calling schemasLlmParametersProgrammer.ts: Parameter schema generationLlmSchemaProgrammer.ts: Type-to-schema conversion
ProtobufEncodeProgrammer.ts: Encoding to Protocol BuffersProtobufDecodeProgrammer.ts: Decoding from Protocol BuffersProtobufMessageProgrammer.ts: .proto message generation
npm run buildnpm run testAdd tests in test/src/features/ following the pattern:
export function test_is_string(): void {
const validator = typia.is<string>;
if (!validator("hello")) throw new Error("String validation failed");
if (validator(123)) throw new Error("Number should not pass string validation");
}Typia includes extensive benchmarks. Run them to ensure your changes don't regress performance:
# Your changes should not significantly impact performance
npm run benchmark- Use strict type checking
- Prefer
constassertions - Use meaningful variable names
Use factory methods consistently:
// Good
ts.factory.createIdentifier("input")
// Not recommended
{ kind: ts.SyntaxKind.Identifier, text: "input" }Provide clear error messages for transformation failures:
if (!metadata.objects.length) {
throw new Error("Type must be an object for this transformation");
}TypeScript types can have circular references. Use metadata collection to handle them:
const collection = new MetadataCollection();
const metadata = MetadataFactory.analyze(checker, type, { collection });Generic type parameters need special handling:
// Check if type is a type parameter
if (type.flags & ts.TypeFlags.TypeParameter) {
// Handle type parameter case
}Maintain source map information for better error reporting:
const expression = ts.factory.createCallExpression(/* ... */);
ts.setSourceMapRange(expression, originalNode);- TypeScript Compiler API Handbook
- AST Explorer - Visualize TypeScript AST
Check the examples/ directory for practical usage examples.
I've referenced contribution guidance of the TypeScript.