farrow-schema Complete API Reference
Table of Contents
- Core Concepts
- Complete Export List
- Schema Type System
- Validator System
- Formatter System
- Helper Utilities
- Result Type
- Usage Patterns & Best Practices
- FAQ
Core Concepts
Design Philosophy
farrow-schema is a type-safe runtime validation and serialization system with core design principles:
- Types as Documentation - Schema definitions serve as both TypeScript types and runtime validation rules
- Declarative Modeling - Use declarative syntax to define complex data structures
- Three-in-One - Type definition, runtime validation, and metadata extraction unified in one system
- Recursion-Friendly - Native support for recursive and mutually referential complex types
- Composability - Schemas can be freely composed, transformed, and derived
Three Core Systems
Schema (Type Definition) → TypeScript Types
Validator (Validation) → Runtime Data Validation
Formatter (Formatting) → API Documentation GenerationModule Architecture
farrow-schema/
├── index.ts # Main entry: type definition system
├── validator.ts # Validator entry: runtime validation
└── formatter.ts # Formatter entry: type metadata extractionComplete Export List
Main Entry (farrow-schema)
Import Path: farrow-schema
Core Responsibility: Provides type definition system, type utilities, helper functions, and Result type
Typical Usage:
import {
// Schema base class
Schema, ObjectType, StructType,
// Basic types
String, Number, Boolean, Date, ID, Int, Float,
// Composite type constructors
List, Optional, Nullable, Record, Tuple,
// Union & Intersection
Union, Intersect, Literal,
// Structured type constructors
Struct,
// Modifiers
Strict, NonStrict, ReadOnly, ReadOnlyDeep,
// Special types
Any, Unknown, Never, Json,
// Type utilities
TypeOf, getInstance, getSchemaCtor,
// Result type
Result, Ok, Err,
// Helper functions
field, pickObject, omitObject, pickStruct, omitStruct, partial, required, keyof
} from 'farrow-schema'Core Export Categories:
Schema Classes - Type definitions
- Basic types:
String,Number,Boolean,Date,ID,Int,Float - Special types:
Any,Unknown,Never,Json - Abstract classes:
Schema,ObjectType, etc.
- Basic types:
Constructors - Type composition
List(Item)- List typeOptional(Item)- Optional typeNullable(Item)- Nullable typeRecord(Item)- Key-value pair typeTuple(...Items)- Tuple typeUnion(...Items)- Union typeIntersect(...Items)- Intersection typeLiteral(value)- Literal typeStruct(descriptors)- Struct type
Type Utilities - Type extraction and inspection
TypeOf<T>- Extract TypeScript typegetInstance(Ctor)- Get Schema instancegetSchemaCtor(Ctor)- Get Schema constructorisSchemaCtor(input)- Check if Schema constructor
Helper Functions - Schema operations
field(fieldInfo)- Define field metadatapickObject(Ctor, keys)- Pick ObjectType fieldsomitObject(Ctor, keys)- Omit ObjectType fieldspickStruct(Ctor, keys)- Pick StructType fieldsomitStruct(Ctor, keys)- Omit StructType fieldspartial(Ctor)- Make optionalrequired(Ctor)- Make requiredkeyof(Ctor)- Get field keys
Result Type - Error handling
Result<T, E>- Result typeOk(value)- Success resultErr(value)- Error result
Validator Entry (farrow-schema/validator)
Import Path: farrow-schema/validator
Core Responsibility: Provides runtime data validation functionality
Typical Usage:
import { Validator, createSchemaValidator, ValidatorType, RegExp } from 'farrow-schema/validator'
import { ObjectType, String, Number } from 'farrow-schema'
class User extends ObjectType {
name = String
age = Number
}
// Method 1: Direct validation
const result = Validator.validate(User, { name: 'Alice', age: 25 })
// Method 2: Create dedicated validator
const validateUser = createSchemaValidator(User)
const result2 = validateUser({ name: 'Bob', age: 30 })
// Method 3: Custom validator
class Email extends ValidatorType<string> {
validate(input: unknown) {
const strResult = Validator.validate(String, input)
if (strResult.kind === 'Err') return strResult
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strResult.value)) {
return this.Err('Invalid email format')
}
return this.Ok(strResult.value)
}
}Core Exports:
Validator- Validator objectValidator.validate(Ctor, input, options)- Validate dataValidator.impl(Ctor, impl)- Register validator implementationValidator.get(Ctor)- Get validator implementation
createSchemaValidator(Ctor, options)- Create dedicated validatorValidatorType<T>- Custom validator base classRegExp(regexp)- Regular expression validatorSchemaErr(message, path)- Create validation error- Type Exports:
ValidationResult<T>- Validation result typeValidationError- Validation error typeValidatorOptions- Validation options
Formatter Entry (farrow-schema/formatter)
Import Path: farrow-schema/formatter
Core Responsibility: Provides Schema metadata extraction and serialization for documentation generation, code generation, and cross-language type sharing
Typical Usage:
import { Formatter, formatSchema, isNamedFormatType } from 'farrow-schema/formatter'
import { ObjectType, String, Number, List } from 'farrow-schema'
class User extends ObjectType {
id = String
name = String
age = Number
tags = List(String)
}
// Method 1: Format Schema
const { typeId, types } = Formatter.format(User)
// Method 2: Use alias
const result = formatSchema(User)
// Iterate type information
for (const [id, formatType] of Object.entries(types)) {
if (isNamedFormatType(formatType)) {
console.log(`Type ${id}: ${formatType.name}`)
}
}
// Generate docs, client code, etc.
function generateAPIDoc(types: FormatTypes) {
// Generate OpenAPI, GraphQL Schema, etc. from types
}Core Exports:
Formatter- Formatter objectFormatter.format(Ctor, context?)- Format SchemaFormatter.formatSchema(Ctor, ctx)- Context formattingFormatter.impl(Ctor, impl)- Register formatter implementationFormatter.get(Ctor)- Get formatter implementation
formatSchema(Ctor, context?)- Alias forFormatter.formatisNamedFormatType(formatType)- Check if named format type- Type Exports (15+ format types):
FormatType- Format type unionFormatTypes- Type dictionaryFormatContext- Format contextFormatScalarType,FormatObjectType,FormatStructType,FormatListType, etc.FormatField,FormatFields- Field format info
Use Cases:
- API Documentation Generation - Auto-generate OpenAPI/Swagger docs
- Client Code Generation - Generate TypeScript/JavaScript SDK
- GraphQL Schema - Convert to GraphQL type definitions
- Cross-Language Type Sharing - Export as JSON Schema, Protocol Buffers, etc.
- Type Visualization - Generate type relationship diagrams
Module Selection Guide
| Need | Module | Typical Scenario |
|---|---|---|
| Define data structures | farrow-schema | Define API input/output types, business models |
| Validate runtime data | farrow-schema/validator | API parameter validation, form validation |
| Extract type metadata | farrow-schema/formatter | Generate docs, code generation, cross-language type sharing |
| Full features | Import all | Complete type-safe system |
Complete Usage Example:
// 1. Define Schema (farrow-schema)
import { ObjectType, String, Number, List } from 'farrow-schema'
class User extends ObjectType {
name = String
age = Number
tags = List(String)
}
// 2. Validate data (farrow-schema/validator)
import { Validator } from 'farrow-schema/validator'
const result = Validator.validate(User, {
name: 'Alice',
age: 25,
tags: ['developer', 'typescript']
})
if (result.kind === 'Ok') {
console.log('Valid user:', result.value)
}
// 3. Extract metadata (farrow-schema/formatter)
import { Formatter } from 'farrow-schema/formatter'
const { typeId, types } = Formatter.format(User)
// Use for API docs, client SDK generation, etc.Schema Type System
Base Abstract Classes
Schema
Base class for all Schemas.
Type Signature:
abstract class Schema {
abstract __type: unknown
static displayName?: string
static namespace?: string
static create<T extends SchemaCtor>(this: T, value: TypeOf<T>): TypeOf<T>
}Description:
__type- Phantom property for TypeScript type inferencedisplayName- Schema display name (for formatting)namespace- Schema namespace (for formatting)create()- Static factory method to create Schema-compliant values
Usage Example:
class User extends ObjectType {
name = String
age = Number
}
User.displayName = 'UserSchema'
User.namespace = 'com.example'
const user = User.create({ name: 'Alice', age: 25 })Basic Type Schemas
Number - Number Type
class Number extends Schema {
__type!: number
}Corresponding TypeScript Type: number
Example:
class Product extends ObjectType {
price = Number
}Int - Integer Type
class Int extends Schema {
__type!: number
}Corresponding TypeScript Type: numberValidation: Validates if integer, rounds down in non-strict mode
Example:
class Pagination extends ObjectType {
page = Int
limit = Int
}Float - Float Type
class Float extends Schema {
__type!: number
}Corresponding TypeScript Type: numberValidation: Same as Number
String - String Type
class String extends Schema {
__type!: string
}Corresponding TypeScript Type: string
Boolean - Boolean Type
class Boolean extends Schema {
__type!: boolean
}Corresponding TypeScript Type: boolean
ID - Identifier Type
class ID extends Schema {
__type!: string
}Corresponding TypeScript Type: stringValidation: Cannot be empty string
Example:
class User extends ObjectType {
id = ID
name = String
}Date - Date Type
class Date extends Schema {
__type!: DateInstanceType
}Corresponding TypeScript Type: DateValidation: Accepts Date instance, timestamp number, ISO date string
Example:
class Event extends ObjectType {
title = String
startDate = Date
}Composite Type Schemas
List - List Type
Constructor:
const List = <T extends SchemaCtorInput>(Item: T): new () => ListTypeType:
abstract class ListType extends Schema {
__type!: TypeOf<this['Item']>[]
abstract Item: SchemaCtor
}Parameters:
Item- Schema type for list elements
Returns: List Schema constructor with element type TypeOf<Item>[]
Example:
class Blog extends ObjectType {
tags = List(String) // string[]
scores = List(Number) // number[]
nested = List(List(String)) // string[][]
}Optional - Optional Type
Constructor:
const Optional = <T extends SchemaCtorInput>(Item: T): new () => OptionalTypeType:
abstract class OptionalType extends Schema {
__type!: TypeOf<this['Item']> | undefined
abstract Item: SchemaCtor
}Parameters:
Item- Schema type for optional value
Returns: Optional Schema constructor with type TypeOf<Item> | undefined
Example:
class User extends ObjectType {
name = String // Required
bio = Optional(String) // string | undefined
}
// Generated TypeScript type
type UserType = {
name: string
bio?: string // Automatically mapped to optional property
}Difference from Nullable:
Optional(String)→ Field can be omitted orundefinedNullable(String)→ Field must be provided, value can benull
Nullable - Nullable Type
Constructor:
const Nullable = <T extends SchemaCtorInput>(Item: T): new () => NullableTypeType:
abstract class NullableType extends Schema {
__type!: TypeOf<this['Item']> | null
abstract Item: SchemaCtor
}Parameters:
Item- Schema type for nullable value
Returns: Nullable Schema constructor with type TypeOf<Item> | null
Example:
class Article extends ObjectType {
title = String
content = Nullable(String) // string | null
publishedAt = Nullable(Date) // Date | null
}
// Usage
const data1 = { title: "Title" } // ❌ content field missing
const data2 = { title: "Title", content: null } // ✅ Explicitly provide nullRecord - Key-Value Pair Type
Constructor:
const Record = <T extends SchemaCtorInput>(Item: T): new () => RecordTypeType:
abstract class RecordType extends Schema {
__type!: { [key: string]: TypeOf<this['Item']> }
abstract Item: SchemaCtor
}Parameters:
Item- Schema type for values
Returns: Record Schema constructor with type { [key: string]: TypeOf<Item> }
Example:
class Config extends ObjectType {
labels = Record(String) // { [key: string]: string }
counters = Record(Number) // { [key: string]: number }
}Tuple - Tuple Type
Constructor:
const Tuple = <T extends SchemaCtorInput[]>(...Items: T): new () => TupleTypeType:
abstract class TupleType extends Schema {
__type!: TypeOfTuple<this['Items']>
abstract Items: SchemaCtor[]
}
type TypeOfTuple<T> = T extends []
? []
: T extends [SchemaCtor, ...infer Rest]
? [TypeOf<T[0]>, ...TypeOfTuple<Rest>]
: []Parameters:
...Items- Array of Schema types for tuple elements
Returns: Tuple Schema constructor
Example:
class Geometry extends ObjectType {
point = Tuple(Number, Number) // [number, number]
rgb = Tuple(Number, Number, Number) // [number, number, number]
mixed = Tuple(String, Number, Boolean) // [string, number, boolean]
}Structured Type Schemas
ObjectType - Object Type
Type:
abstract class ObjectType extends Schema {
__type!: {
[key in keyof this as SchemaField<this, key>]: TypeOfField<this[key]>
}
}Description:
- Define complex nested object structures
- Supports recursive references - Core advantage of ObjectType
- Fields can be Schema constructors, FieldInfo, or nested FieldDescriptors
- Automatically extracts all fields except
__type
Basic Usage:
class User extends ObjectType {
id = String
name = String
age = Number
}
type UserType = TypeOf<typeof User>
// { id: string, name: string, age: number }Nested Objects:
class User extends ObjectType {
id = String
name = String
profile = {
bio: String,
avatar: Optional(String),
social: {
twitter: Optional(String),
github: Optional(String)
}
}
}
type UserType = TypeOf<typeof User>
// {
// id: string
// name: string
// profile: {
// bio: string
// avatar?: string
// social: {
// twitter?: string
// github?: string
// }
// }
// }Recursive References (Self-Referencing):
class Comment extends ObjectType {
id = String
content = String
replies = List(Comment) // ✅ Self-reference supported
parent = Optional(Comment) // ✅ Optional self-reference supported
}
type CommentType = TypeOf<typeof Comment>
// {
// id: string
// content: string
// replies: CommentType[] // Recursive type
// parent?: CommentType // Recursive optional type
// }Mutual References:
class Post extends ObjectType {
title = String
author = User // Reference User
}
class User extends ObjectType {
name = String
posts = List(Post) // Reference Post, forming circular reference
}Field Metadata:
import { field } from 'farrow-schema'
class User extends ObjectType {
id = ID
name = field({
__type: String,
description: 'User full name',
deprecated: 'Use fullName instead'
})
age = Number
}StructType - Struct Type
Type:
abstract class StructType extends Schema {
__type!: ShallowPrettier<TypeOfFieldDescriptors<this['descriptors']>>
abstract descriptors: FieldDescriptors
}Constructor:
const Struct = <T extends FieldDescriptors>(descriptors: T): new () => StructTypeDescription:
- Quick definition of static structures
- Does not support recursive references - Core limitation of Struct
- Suitable for quick definition of variant structures in union types
- Suitable for runtime dynamic Schema construction
Basic Usage:
const UserStruct = Struct({
id: String,
name: String,
age: Number
})
type UserType = TypeOf<typeof UserStruct>
// { id: string, name: string, age: number }Use in Union Types (Recommended):
const APIResult = Union(
Struct({
success: Literal(true),
data: List(User)
}),
Struct({
success: Literal(false),
error: String
})
)
type APIResultType = TypeOf<typeof APIResult>
// { success: true, data: User[] } | { success: false, error: string }Runtime Dynamic Construction:
function createValidator(fields: string[]) {
const descriptors: FieldDescriptors = {}
fields.forEach(field => {
descriptors[field] = String
})
return Struct(descriptors)
}
const DynamicSchema = createValidator(['name', 'email', 'phone'])❌ Does Not Support Recursive References:
// ❌ Error: Recursive references not supported
const BadComment = Struct({
id: String,
content: String,
replies: List(BadComment) // ❌ ReferenceError: Cannot access 'BadComment' before initialization
})
// ✅ Correct: Use ObjectType
class GoodComment extends ObjectType {
id = String
content = String
replies = List(GoodComment) // ✅ Recursive references supported
}ObjectType vs Struct Selection Guide:
| Feature | ObjectType | Struct |
|---|---|---|
| Recursive references | ✅ Supported | ❌ Not supported |
| Definition method | class inheritance | function call |
| Use case | Complex business models, tree structures | Union type variants, dynamic construction |
| Performance | Slightly slower (requires instantiation) | Slightly faster |
| Recommendation | General recommendation | Specific scenarios |
Union & Intersection Types
Union - Union Type
Constructor:
const Union = <T extends SchemaCtorInput[]>(...Items: T): new () => UnionTypeType:
abstract class UnionType extends Schema {
__type!: TypeOf<this['Items'][number]>
abstract Items: SchemaCtor[]
}Parameters:
...Items- Array of Schema members for union type
Returns: Union type Schema constructor
Enum Values:
const Status = Union(
Literal('draft'),
Literal('published'),
Literal('archived')
)
type StatusType = TypeOf<typeof Status>
// 'draft' | 'published' | 'archived'Discriminated Union Types (Recommended):
const Payment = Union(
Struct({
type: Literal('credit_card'),
cardNumber: String,
cvv: String
}),
Struct({
type: Literal('paypal'),
email: String
})
)
type PaymentType = TypeOf<typeof Payment>
// { type: 'credit_card', cardNumber: string, cvv: string }
// | { type: 'paypal', email: string }
// TypeScript automatic type narrowing
function processPayment(payment: PaymentType) {
switch (payment.type) {
case 'credit_card':
// payment.cardNumber and payment.cvv available here
console.log(payment.cardNumber)
break
case 'paypal':
// payment.email available here
console.log(payment.email)
break
}
}Intersect - Intersection Type
Constructor:
const Intersect = <T extends SchemaCtorInput[]>(...Items: T): new () => IntersectTypeType:
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never
abstract class IntersectType extends Schema {
__type!: UnionToIntersection<TypeOf<this['Items'][number]>>
abstract Items: SchemaCtor[]
}Parameters:
...Items- Array of Schema members for intersection type
Returns: Intersection type Schema constructor
Example:
class BaseUser extends ObjectType {
id = String
name = String
}
class ContactInfo extends ObjectType {
email = String
phone = String
}
// Merge multiple types
const FullUser = Intersect(BaseUser, ContactInfo)
type FullUserType = TypeOf<typeof FullUser>
// { id: string, name: string, email: string, phone: string }Literal - Literal Type
Constructor:
const Literal = <T extends Literals>(value: T): new () => LiteralTypeType:
type Literals = number | string | boolean | null | undefined
abstract class LiteralType extends Schema {
__type!: this['value']
abstract value: Literals
}Parameters:
value- Literal value
Returns: Literal type Schema constructor
Example:
const Environment = Union(
Literal('development'),
Literal('staging'),
Literal('production')
)
const APIResponse = Struct({
status: Literal(200),
message: Literal('OK'),
data: Any
})Predefined Literals:
const Null = Literal(null)
const Undefined = Literal(undefined)Special Type Schemas
Any - Any Type
class Any extends Schema {
__type!: any
}Corresponding TypeScript Type: anyValidation: Accepts any value
Unknown - Unknown Type
class Unknown extends Schema {
__type!: unknown
}Corresponding TypeScript Type: unknownValidation: Accepts any value, but more type-safe
Never - Never Type
class Never extends Schema {
__type!: never
}Corresponding TypeScript Type: neverValidation: Should not validate this type, will throw error
Json - JSON Type
type JsonType =
| number
| string
| boolean
| null
| undefined
| JsonType[]
| { toJSON(): string }
| { [key: string]: JsonType }
class Json extends Schema {
__type!: JsonType
}Corresponding TypeScript Type: JsonType (recursive JSON type) Validation: Validates if value is valid JSON
Modifier Types
Strict - Strict Mode
Constructor:
const Strict = <T extends SchemaCtorInput>(Item: T): new () => StrictTypeType:
abstract class StrictType extends Schema {
__type!: TypeOf<this['Item']>
abstract Item: SchemaCtor
}Description: Wrapped Schema enforces strict mode during validation
Example:
class User extends ObjectType {
age = Strict(Number) // Must be pure number, won't accept "25" string
}NonStrict - Non-Strict Mode
Constructor:
const NonStrict = <T extends SchemaCtorInput>(Item: T): new () => NonStrictTypeType:
abstract class NonStrictType extends Schema {
__type!: TypeOf<this['Item']>
abstract Item: SchemaCtor
}Description: Wrapped Schema enforces non-strict mode during validation
Example:
class User extends ObjectType {
age = NonStrict(Number) // Accepts 25 or "25"
}ReadOnly - Read-Only Type
Constructor:
const ReadOnly = <T extends SchemaCtorInput>(Item: T): new () => ReadOnlyTypeType:
abstract class ReadOnlyType extends Schema {
__type!: Readonly<TypeOf<this['Item']>>
abstract Item: SchemaCtor
}Description: Shallow read-only, only top-level properties read-only
ReadOnlyDeep - Deep Read-Only Type
Constructor:
const ReadOnlyDeep = <T extends SchemaCtorInput>(Item: T): new () => ReadOnlyDeepTypeType:
abstract class ReadOnlyDeepType extends Schema {
__type!: MarkReadOnlyDeep<TypeOf<this['Item']>>
abstract Item: SchemaCtor
}Description: Deep recursive read-only, all nested properties read-only
Type Utility Functions
TypeOf - Extract TypeScript Type
Type Signature:
type TypeOf<T extends SchemaCtor | Schema> =
T extends DateConstructor ? DateInstanceType
: T extends Primitives ? ReturnType<T>
: T extends new () => { __type: infer U } ? U
: T extends Schema ? T['__type']
: neverDescription: Extract corresponding TypeScript type from Schema constructor or instance
Example:
class User extends ObjectType {
name = String
age = Number
tags = List(String)
}
type UserType = TypeOf<typeof User>
// { name: string, age: number, tags: string[] }
// Can also extract nested types
type UserName = UserType['name'] // string
type UserTags = UserType['tags'] // string[]getSchemaCtor - Get Schema Constructor
Function Signature:
const getSchemaCtor = <T extends SchemaCtor>(Ctor: T): SchemaTypeOf<T>Description: Convert primitive type constructors (Number, String, etc.) to corresponding Schema classes
Example:
const StringSchema = getSchemaCtor(String) // Returns farrow-schema's String class
const NumberSchema = getSchemaCtor(Number) // Returns farrow-schema's Number class
const UserSchema = getSchemaCtor(User) // Returns User class itselfgetInstance - Get Schema Instance
Function Signature:
const getInstance = <T extends SchemaCtor>(Ctor: T): InstanceTypeOf<T>Core Purpose: Convert Schema constructor to runtime instance, extracting field type information. Uses singleton pattern (WeakMap caching), each constructor instantiated only once.
Why Needed:
- Schema class field definitions (e.g.,
name = String) only exist on instances - Constructor itself cannot access this field information
- Validators and formatters need to iterate fields for processing
Example:
class User extends ObjectType {
name = String
age = Number
tags = List(String)
}
// ❌ Constructor cannot access fields
console.log(User.name) // undefined
// ✅ Instance can access field definitions
const instance = getInstance(User)
console.log(instance.name) // String Schema constructor
console.log(instance.age) // Number Schema constructor
console.log(instance.tags) // List(String) Schema constructor
// Caching mechanism: Multiple calls return same instance
const instance2 = getInstance(User)
console.log(instance === instance2) // trueInternal Usage:
// In validator
Validator.impl(ObjectType, (schema) => {
// schema = getInstance(User)
// Can access schema.name, schema.age, etc. fields
return {
validate: (input) => {
for (const key in schema) {
// Iterate fields for validation
}
}
}
})
// In formatter
Formatter.impl(ObjectType, (schema) => {
// schema = getInstance(User)
// Extract fields to generate type metadata
return {
format(ctx) {
for (const [key, field] of Object.entries(schema)) {
// Format each field
}
}
}
})toSchemaCtor - Convert to Schema Constructor
Function Signature:
const toSchemaCtor = <T extends SchemaCtorInput>(Item: T): ToSchemaCtor<T>Description:
- If input is Schema constructor, return directly
- If input is FieldDescriptors object, auto-wrap as Struct
Example:
const StringCtor = toSchemaCtor(String)
// Returns String Schema
const StructCtor = toSchemaCtor({
name: String,
age: Number
})
// Returns Struct({ name: String, age: Number })isSchemaCtor - Check if Schema Constructor
Function Signature:
const isSchemaCtor = (input: any): input is SchemaCtorDescription: Type guard to check if input is Schema constructor
Validator System
Import Path: farrow-schema/validator
Validator Object
Core object of validation system, provides validation functionality.
Type:
const Validator = {
impl<T extends Schema>(
Ctor: abstract new () => T,
impl: ValidatorImpl<T>
): void
get<T extends SchemaCtor>(
Ctor: T
): ValidatorMethods<SchemaTypeOf<T>> | undefined
validate<T extends SchemaCtor>(
Ctor: T,
input: unknown,
options?: ValidatorOptions
): ValidationResult<TypeOf<T>>
}Validator.validate - Validate Data
Function Signature:
function validate<T extends SchemaCtor>(
Ctor: T,
input: unknown,
options?: ValidatorOptions
): ValidationResult<TypeOf<T>>Parameters:
Ctor- Schema constructorinput- Data to validateoptions- Validation optionsstrict?: boolean- Strict mode (defaultfalse)
Returns: Validation result of type Result<T, ValidationError>
Example:
import { Validator } from 'farrow-schema/validator'
class User extends ObjectType {
name = String
age = Number
}
// Basic validation
const result = Validator.validate(User, {
name: "Zhang San",
age: 25
})
if (result.kind === 'Ok') {
console.log("Validation success:", result.value)
// result.value type is { name: string, age: number }
} else {
console.log("Validation failed:", result.value.message)
console.log("Error location:", result.value.path?.join('.'))
}
// Strict mode (default)
const strictResult = Validator.validate(User, {
name: "Li Si",
age: "30" // ❌ String will fail in strict mode
})
// Lenient mode
const lenientResult = Validator.validate(User, {
name: "Wang Wu",
age: "35" // ✅ String will be converted to number
}, { strict: false })Validator.impl - Implement Custom Validator
Function Signature:
function impl<T extends Schema>(
Ctor: abstract new () => T,
impl: ValidatorImpl<T>
): voidParameters:
Ctor- Schema constructorimpl- Validator implementation, can be object or factory function
Description: Register validator implementation for Schema type (internally stored using WeakMap)
Example:
// Object form
Validator.impl(String, {
validate: (input) => {
if (typeof input === 'string') {
return Ok(input)
}
return SchemaErr(`${input} is not a string`)
}
})
// Factory function form (access schema instance)
Validator.impl(LiteralType, (schema) => ({
validate: (input) => {
if (input === schema.value) {
return Ok(input)
}
return SchemaErr(`${input} is not literal ${schema.value}`)
}
}))Validator.get - Get Validator Implementation
Function Signature:
function get<T extends SchemaCtor>(
Ctor: T
): ValidatorMethods<SchemaTypeOf<T>> | undefinedParameters:
Ctor- Schema constructor
Returns: Validator method object or undefined
Description: Get validator implementation corresponding to Schema (searches along prototype chain)
ValidationResult Type
Type Definition:
type ValidationResult<T = any> = Result<T, ValidationError>
type ValidationError = {
path?: (string | number)[]
message: string
}Description:
path- Error path, array form, e.g.,['user', 'profile', 'age']message- Error message
Example:
const result = Validator.validate(User, invalidData)
if (result.kind === 'Err') {
console.log(result.value.message) // "25 is not a string"
console.log(result.value.path) // ["name"]
}ValidatorOptions Type
Type Definition:
type ValidatorOptions = {
strict?: boolean
}Description:
strict: true- Strict mode (default), no type conversion"25"won't convert to25"true"won't convert totrue
strict: false- Lenient mode, attempts type conversion"25"→25"true"→true"2024-01-01"→Date
createSchemaValidator - Create Dedicated Validator
Function Signature:
function createSchemaValidator<S extends SchemaCtor>(
SchemaCtor: S,
options?: ValidatorOptions
): (input: unknown) => ValidationResult<TypeOf<S>>Parameters:
SchemaCtor- Schema constructoroptions- Validation options
Returns: Validation function bound with Schema and options
Example:
// Create dedicated validator
const validateUser = createSchemaValidator(User)
// Use in API
app.post('/users', (req, res) => {
const result = validateUser(req.body)
if (result.kind === 'Err') {
return res.status(400).json({
error: result.value.message,
path: result.value.path
})
}
// result.value is type-checked
const user = await createUser(result.value)
res.json(user)
})ValidatorType - Custom Validator Base Class
Type Definition:
abstract class ValidatorType<T = unknown> extends Schema {
__type!: T
abstract validate(input: unknown): ValidationResult<T>
Ok(value: T): ValidationResult<T>
Err(...args: Parameters<typeof SchemaErr>): ValidationResult<T>
}Description: Abstract base class for creating custom validators
Example:
// Email validator
class EmailType extends ValidatorType<string> {
validate(input: unknown) {
const result = Validator.validate(String, input)
if (result.kind === 'Err') return result
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(result.value)) {
return this.Err('Invalid email format')
}
return this.Ok(result.value)
}
}
// Usage
class User extends ObjectType {
name = String
email = EmailType // Use custom validator directly
}
// Parameterized validator
const StringLength = (min: number, max: number) => {
return class StringLength extends ValidatorType<string> {
validate(input: unknown) {
const result = Validator.validate(String, input)
if (result.kind === 'Err') return result
if (result.value.length < min || result.value.length > max) {
return this.Err(`Length must be between ${min}-${max} characters`)
}
return this.Ok(result.value)
}
}
}
class Article extends ObjectType {
title = StringLength(5, 100)
}RegExp - Regular Expression Validator
Function Signature:
const RegExp = (regexp: RegExp): new () => ValidatorType<string>Parameters:
regexp- Regular expression
Returns: Regex validator constructor
Example:
import { RegExp } from 'farrow-schema/validator'
class User extends ObjectType {
username = RegExp(/^[a-zA-Z0-9_]{3,16}$/)
phone = RegExp(/^1[3-9]\d{9}$/)
}Formatter System
Import Path: farrow-schema/formatter
Formatting system converts Schemas to structured type metadata, commonly used for:
- Generating API documentation
- Generating client code
- Type information serialization
- GraphQL Schema generation
Formatter Object
Core object of formatting system.
Type:
const Formatter = {
impl<T extends Schema>(
Ctor: abstract new () => T,
impl: FormatterImpl<T>
): void
get<T extends SchemaCtor>(
Ctor: T
): FormatterMethods | undefined
formatSchema<T extends SchemaCtor>(
Ctor: T,
ctx: FormatContext
): number
format<T extends SchemaCtor>(
Ctor: T,
context?: FormatContext
): { typeId: number, types: FormatTypes }
}Formatter.format - Format Schema
Function Signature:
function format<T extends SchemaCtor>(
Ctor: T,
context?: FormatContext
): {
typeId: number
types: FormatTypes
}
type FormatTypes = {
[key: string]: FormatType
}Parameters:
Ctor- Schema constructorcontext- Format context (optional)
Returns:
typeId- Root type IDtypes- Type dictionary, keys are type IDs (string form), values are format types
Example:
import { Formatter } from 'farrow-schema/formatter'
class User extends ObjectType {
id = ID
name = String
age = Number
tags = List(String)
}
const { typeId, types } = Formatter.format(User)
console.log(typeId) // 0
console.log(types)
/*
{
"0": {
type: "Object",
name: "User",
fields: {
id: { typeId: 1, $ref: "#/types/1", description: undefined },
name: { typeId: 2, $ref: "#/types/2", description: undefined },
age: { typeId: 3, $ref: "#/types/3", description: undefined },
tags: { typeId: 4, $ref: "#/types/4", description: undefined }
}
},
"1": { type: "Scalar", valueType: "string", valueName: "ID" },
"2": { type: "Scalar", valueType: "string", valueName: "String" },
"3": { type: "Scalar", valueType: "number", valueName: "Number" },
"4": { type: "List", itemTypeId: 2, $ref: "#/types/2" }
}
*/Complex Example:
class Article extends ObjectType {
title = String
author = User // Nested ObjectType
tags = List(String)
status = Union(
Literal('draft'),
Literal('published')
)
}
const { typeId, types } = Formatter.format(Article)
/*
types contains:
- Article ObjectType (typeId: 0)
- String Scalar (typeId: 1)
- User ObjectType (typeId: 2)
- ID Scalar (typeId: 3)
- Number Scalar (typeId: 4)
- List<String> (typeId: 5)
- Union (typeId: 6)
- Literal('draft') (typeId: 7)
- Literal('published') (typeId: 8)
*/formatSchema - Alias for format
Function Signature:
const formatSchema = Formatter.formatDescription: Export alias for Formatter.format
Example:
import { formatSchema } from 'farrow-schema/formatter'
const result = formatSchema(User)Formatter.formatSchema - Format Schema (Context Version)
Function Signature:
function formatSchema<T extends SchemaCtor>(
Ctor: T,
ctx: FormatContext
): numberParameters:
Ctor- Schema constructorctx- Format context (required)
Returns: Type ID
Description:
- Format Schema in given context
- Uses context's
addTypeandformatCache - Used to format nested types in custom formatters
Formatter.impl - Implement Custom Formatter
Function Signature:
function impl<T extends Schema>(
Ctor: abstract new () => T,
impl: FormatterImpl<T>
): void
type FormatterImpl<T extends Schema = Schema> =
| FormatterMethods
| ((schema: T) => FormatterMethods)
type FormatterMethods = {
format(context: FormatContext): number
}Parameters:
Ctor- Schema constructorimpl- Formatter implementation
Description: Register formatter implementation for Schema type
Example:
// Object form
Formatter.impl(String, {
format(ctx) {
return ctx.addType({
type: 'Scalar',
valueType: 'string',
valueName: 'String',
})
}
})
// Factory function form (access schema instance)
Formatter.impl(LiteralType, (schema) => ({
format(ctx) {
return ctx.addType({
type: 'Literal',
value: schema.value,
})
}
}))
// Nested type formatting
Formatter.impl(ListType, (schema) => ({
format(ctx) {
// First format element type
const itemTypeId = Formatter.formatSchema(schema.Item, ctx)
// Then add List type
return ctx.addType({
type: 'List',
itemTypeId: itemTypeId,
$ref: `#/types/${itemTypeId}`,
})
}
}))Formatter.get - Get Formatter Implementation
Function Signature:
function get<T extends SchemaCtor>(
Ctor: T
): FormatterMethods | undefinedParameters:
Ctor- Schema constructor
Returns: Formatter method object or undefined
Description: Get formatter implementation corresponding to Schema (searches along prototype chain)
FormatContext - Format Context
Type Definition:
type FormatContext = {
addType: (type: FormatType) => number
formatCache: WeakMap<Function, number>
}Description:
addType- Add type to type dictionary, return assigned type IDformatCache- Type cache, avoid repeatedly formatting same type
Use Case: Used in custom formatters
FormatType - Format Type (Union Type)
Type Definition:
type FormatType =
| FormatScalarType
| FormatObjectType
| FormatUnionType
| FormatStructType
| FormatRecordType
| FormatListType
| FormatLiteralType
| FormatNullableType
| FormatOptionalType
| FormatIntersectType
| FormatTupleType
| FormatStrictType
| FormatNonStrictType
| FormatReadOnlyType
| FormatReadonlyDeepTypeDescription: Union of all format types, each type has type discriminant field
Format Type Details
FormatScalarType - Scalar Type Format
Type Definition:
type FormatScalarType = {
type: 'Scalar'
valueType: string // TypeScript type string
valueName: string // Schema name
}Corresponding Schema: String, Number, Int, Float, Boolean, ID, Date, Any, Unknown, Never, Json
Example:
// String Schema
{ type: 'Scalar', valueType: 'string', valueName: 'String' }
// Number Schema
{ type: 'Scalar', valueType: 'number', valueName: 'Number' }
// ID Schema
{ type: 'Scalar', valueType: 'string', valueName: 'ID' }FormatObjectType - Object Type Format
Type Definition:
type FormatObjectType = {
type: 'Object'
name: string
fields: FormatFields
namespace?: string
}
type FormatFields = {
[key: string]: FormatField
}
type FormatField = {
typeId: number
$ref: string
description?: string
deprecated?: string
}Corresponding Schema: ObjectType
Field Description:
name- ObjectType class name or displayNamefields- Object field dictionary- Key is field name
- Value is field format info
namespace- Namespace (if set)
Example:
class User extends ObjectType {
id = ID
name = field({
__type: String,
description: 'User name'
})
}
User.displayName = 'UserModel'
User.namespace = 'com.example'
const { types } = Formatter.format(User)
/*
types["0"] = {
type: "Object",
name: "UserModel",
namespace: "com.example",
fields: {
id: {
typeId: 1,
$ref: "#/types/1",
description: undefined,
deprecated: undefined
},
name: {
typeId: 2,
$ref: "#/types/2",
description: "User name",
deprecated: undefined
}
}
}
*/FormatStructType - Struct Type Format
Type Definition:
type FormatStructType = {
type: 'Struct'
name?: string
fields: FormatFields
namespace?: string
}Corresponding Schema: StructType
Description: Similar to FormatObjectType, but name is optional
Example:
const UserStruct = Struct({
id: ID,
name: String
})
const { types } = Formatter.format(UserStruct)
/*
types["0"] = {
type: "Struct",
name: undefined, // Struct has no name by default
fields: { ... }
}
*/FormatListType - List Type Format
Type Definition:
type FormatListType = {
type: 'List'
itemTypeId: number
$ref: string
}Corresponding Schema: List
Field Description:
itemTypeId- List element type ID$ref- Element type reference path
Example:
class Blog extends ObjectType {
tags = List(String)
}
const { types } = Formatter.format(Blog)
/*
types["2"] = {
type: "List",
itemTypeId: 1, // String's typeId
$ref: "#/types/1"
}
*/FormatNullableType - Nullable Type Format
Type Definition:
type FormatNullableType = {
type: 'Nullable'
itemTypeId: number
$ref: string
}Corresponding Schema: Nullable
FormatOptionalType - Optional Type Format
Type Definition:
type FormatOptionalType = {
type: 'Optional'
itemTypeId: number
$ref: string
}Corresponding Schema: Optional
FormatLiteralType - Literal Type Format
Type Definition:
type FormatLiteralType = {
type: 'Literal'
value: Literals
}
type Literals = number | string | boolean | null | undefinedCorresponding Schema: Literal
Example:
const Status = Literal('active')
const { types } = Formatter.format(Status)
/*
types["0"] = {
type: "Literal",
value: "active"
}
*/FormatUnionType - Union Type Format
Type Definition:
type FormatUnionType = {
type: 'Union'
name?: string
itemTypes: { typeId: number; $ref: string }[]
namespace?: string
}Corresponding Schema: Union
Field Description:
itemTypes- Union type member array- Each member contains
typeIdand$ref
- Each member contains
Example:
const Status = Union(
Literal('draft'),
Literal('published')
)
const { types } = Formatter.format(Status)
/*
types["0"] = {
type: "Union",
name: undefined,
itemTypes: [
{ typeId: 1, $ref: "#/types/1" }, // Literal('draft')
{ typeId: 2, $ref: "#/types/2" } // Literal('published')
]
}
*/FormatIntersectType - Intersection Type Format
Type Definition:
type FormatIntersectType = {
type: 'Intersect'
name?: string
itemTypes: { typeId: number; $ref: string }[]
namespace?: string
}Corresponding Schema: Intersect
FormatTupleType - Tuple Type Format
Type Definition:
type FormatTupleType = {
type: 'Tuple'
name?: string
itemTypes: { typeId: number; $ref: string }[]
namespace?: string
}Corresponding Schema: Tuple
Example:
const Point = Tuple(Number, Number)
const { types } = Formatter.format(Point)
/*
types["0"] = {
type: "Tuple",
name: undefined,
itemTypes: [
{ typeId: 1, $ref: "#/types/1" }, // Number
{ typeId: 1, $ref: "#/types/1" } // Number (same typeId, cached)
]
}
*/FormatRecordType - Record Type Format
Type Definition:
type FormatRecordType = {
type: 'Record'
valueTypeId: number
$ref: string
}Corresponding Schema: Record
FormatStrictType - Strict Type Format
Type Definition:
type FormatStrictType = {
type: 'Strict'
itemTypeId: number
$ref: string
}Corresponding Schema: Strict
FormatNonStrictType - Non-Strict Type Format
Type Definition:
type FormatNonStrictType = {
type: 'NonStrict'
itemTypeId: number
$ref: string
}Corresponding Schema: NonStrict
FormatReadOnlyType - Read-Only Type Format
Type Definition:
type FormatReadOnlyType = {
type: 'ReadOnly'
itemTypeId: number
$ref: string
}Corresponding Schema: ReadOnly
FormatReadonlyDeepType - Deep Read-Only Type Format
Type Definition:
type FormatReadonlyDeepType = {
type: 'ReadOnlyDeep'
itemTypeId: number
$ref: string
}Corresponding Schema: ReadOnlyDeep
isNamedFormatType - Check if Named Format Type
Function Signature:
const isNamedFormatType = (input: FormatType): input is NamedFormatType
type NamedFormatType =
| FormatTupleType
| FormatStructType
| FormatUnionType
| FormatIntersectType
| FormatObjectTypeDescription: Type guard to check if format type can have a name
Example:
import { isNamedFormatType } from 'farrow-schema/formatter'
const { types } = Formatter.format(User)
for (const [id, formatType] of Object.entries(types)) {
if (isNamedFormatType(formatType)) {
console.log(`Type ${id} name: ${formatType.name}`)
}
}Formatter Practical Example
Generate API Documentation
import { Formatter } from 'farrow-schema/formatter'
class User extends ObjectType {
id = ID
name = String
email = String
}
class CreateUserInput extends ObjectType {
name = String
email = String
}
const APIEndpoint = Struct({
path: Literal('/api/users'),
method: Literal('POST'),
input: CreateUserInput,
output: User
})
const { typeId, types } = Formatter.format(APIEndpoint)
// Iterate types to generate docs
function generateDocs(types: FormatTypes) {
for (const [id, formatType] of Object.entries(types)) {
switch (formatType.type) {
case 'Object':
console.log(`Object: ${formatType.name}`)
for (const [fieldName, field] of Object.entries(formatType.fields)) {
const fieldType = types[field.typeId]
console.log(` ${fieldName}: ${getTypeName(fieldType)}`)
if (field.description) {
console.log(` Description: ${field.description}`)
}
}
break
case 'Literal':
console.log(`Literal: ${formatType.value}`)
break
// ... other types
}
}
}
function getTypeName(formatType: FormatType): string {
switch (formatType.type) {
case 'Scalar':
return formatType.valueName
case 'Object':
return formatType.name
case 'List':
return `List<${formatType.itemTypeId}>`
// ... other types
default:
return 'Unknown'
}
}Helper Utilities
Import Path: farrow-schema (automatically exported from main entry)
Helper module provides utility functions for operating on Schemas.
field - Field Definition Helper Function
Function Signature:
const field = <T extends FieldInfo>(fieldInfo: T): T
type FieldInfo = {
__type: SchemaCtor
description?: string
deprecated?: string
}Parameters:
fieldInfo- Field info object__type- Field Schema typedescription- Field description (optional)deprecated- Deprecation notice (optional)
Returns: Returns fieldInfo as-is (for type inference)
Example:
import { field } from 'farrow-schema'
class User extends ObjectType {
id = ID
name = field({
__type: String,
description: 'User full name'
})
email = field({
__type: String,
description: 'User email',
deprecated: 'Use contactEmail instead'
})
}
// Formatting will include description and deprecated info
const { types } = Formatter.format(User)pick Series - Select Fields
pickObject - Pick ObjectType Fields
Function Signature:
const pickObject = <T extends ObjectType, Keys extends SchemaField<T, keyof T>[]>(
Ctor: new () => T,
keys: Keys
): PickObject<T, Keys>Parameters:
Ctor- ObjectType constructorkeys- Array of field names to select
Returns: New ObjectType constructor with only selected fields
Example:
import { pickObject } from 'farrow-schema'
class FullUser extends ObjectType {
id = String
name = String
email = String
password = String
createdAt = Date
}
// Select public fields
const PublicUser = pickObject(FullUser, ['id', 'name'])
type PublicUserType = TypeOf<typeof PublicUser>
// { id: string, name: string }pickStruct - Pick Struct Fields
Function Signature:
const pickStruct = <T extends StructType, Keys extends (keyof T['descriptors'])[]>(
Ctor: new () => T,
keys: Keys
): new () => StructTypeParameters:
Ctor- StructType constructorkeys- Array of field names to select
Returns: New StructType constructor with only selected fields
pick - Generic Pick Function
Function Signature:
const pick: typeof pickObject & typeof pickStructDescription: Automatically determines if ObjectType or StructType and calls corresponding function
⚠️ Type Inference Limitation: Due to TypeScript limitations, the generic pick function cannot provide accurate type hints and autocomplete when entering the second parameter (field name array).
Recommended Approach:
- For ObjectType, use
pickObject - For StructType, use
pickStruct
This way you get complete type inference and IDE intelligent hints.
Example:
import { pick, pickObject, pickStruct } from 'farrow-schema'
// ❌ Not recommended: Second parameter lacks type hints
const PublicUser = pick(FullUser, ['id', 'name'])
// ✅ Recommended: Use dedicated function, get field name autocomplete
const PublicUser = pickObject(FullUser, ['id', 'name']) // IDE will suggest available fields
const PublicUserStruct = pickStruct(FullUserStruct, ['id', 'name'])omit Series - Exclude Fields
omitObject - Omit ObjectType Fields
Function Signature:
const omitObject = <T extends ObjectType, Keys extends SchemaField<T, keyof T>[]>(
Ctor: new () => T,
keys: Keys
): OmitObject<T, Keys>Parameters:
Ctor- ObjectType constructorkeys- Array of field names to exclude
Returns: New ObjectType constructor without excluded fields
Example:
import { omitObject } from 'farrow-schema'
class FullUser extends ObjectType {
id = String
name = String
email = String
password = String
createdAt = Date
}
// Exclude sensitive fields
const SafeUser = omitObject(FullUser, ['password'])
type SafeUserType = TypeOf<typeof SafeUser>
// { id: string, name: string, email: string, createdAt: Date }
// Exclude system fields
const UserInput = omitObject(FullUser, ['id', 'createdAt'])
type UserInputType = TypeOf<typeof UserInput>
// { name: string, email: string, password: string }omitStruct - Omit Struct Fields
Function Signature:
const omitStruct = <T extends StructType, Keys extends (keyof T['descriptors'])[]>(
Ctor: new () => T,
keys: Keys
): new () => StructTypeomit - Generic Omit Function
Function Signature:
const omit: typeof omitObject & typeof omitStructDescription: Automatically determines if ObjectType or StructType and calls corresponding function
⚠️ Type Inference Limitation: Due to TypeScript limitations, the generic omit function cannot provide accurate type hints and autocomplete when entering the second parameter (field name array).
Recommended Approach:
- For ObjectType, use
omitObject - For StructType, use
omitStruct
This way you get complete type inference and IDE intelligent hints.
Example:
import { omit, omitObject, omitStruct } from 'farrow-schema'
// ❌ Not recommended: Second parameter lacks type hints
const SafeUser = omit(FullUser, ['password'])
// ✅ Recommended: Use dedicated function, get field name autocomplete
const SafeUser = omitObject(FullUser, ['password']) // IDE will suggest available fields
const SafeUserStruct = omitStruct(FullUserStruct, ['password'])partial Series - Make Fields Optional
partialObject - Make ObjectType Optional
Function Signature:
const partialObject = <T extends ObjectType>(
Ctor: new () => T
): PartialObjectType<T>Parameters:
Ctor- ObjectType constructor
Returns: New ObjectType constructor with all fields optional
Example:
import { partialObject } from 'farrow-schema'
class User extends ObjectType {
id = String
name = String
email = String
}
const PartialUser = partialObject(User)
type PartialUserType = TypeOf<typeof PartialUser>
// { id?: string, name?: string, email?: string }partialStruct - Make Struct Optional
Function Signature:
const partialStruct = <T extends StructType>(
Ctor: new () => T
): new () => PartialTypepartial - Generic Make Optional Function
Function Signature:
const partial: typeof partialObject & typeof partialStructExample:
import { partial } from 'farrow-schema'
const PartialUser = partial(User)
const PartialUserStruct = partial(UserStruct)required Series - Make Fields Required
requiredObject - Make ObjectType Required
Function Signature:
const requiredObject = <T extends ObjectType>(
Ctor: new () => T
): RequiredObjectType<T>Parameters:
Ctor- ObjectType constructor
Returns: New ObjectType constructor with all optional fields required
Example:
import { requiredObject } from 'farrow-schema'
class OptionalUser extends ObjectType {
id = Optional(String)
name = Optional(String)
email = Optional(String)
}
const RequiredUser = requiredObject(OptionalUser)
type RequiredUserType = TypeOf<typeof RequiredUser>
// { id: string, name: string, email: string }requiredStruct - Make Struct Required
Function Signature:
const requiredStruct = <T extends StructType>(
Ctor: new () => T
): new () => StructTyperequired - Generic Make Required Function
Function Signature:
const required: typeof requiredObject & typeof requiredStructExample:
import { required } from 'farrow-schema'
const RequiredUser = required(OptionalUser)keyof Series - Get Field Keys
keyofObject - Get ObjectType Field Keys
Function Signature:
const keyofObject = <T extends ObjectType>(
Ctor: new () => T
): (keyof TypeOf<T>)[]Parameters:
Ctor- ObjectType constructor
Returns: Field name array
Example:
import { keyofObject } from 'farrow-schema'
class User extends ObjectType {
id = String
name = String
email = String
}
const keys = keyofObject(User)
// ['id', 'name', 'email']keyofStruct - Get Struct Field Keys
Function Signature:
const keyofStruct = <T extends StructType>(
Ctor: new () => T
): (keyof T['descriptors'])[]keyof - Generic Get Keys Function
Function Signature:
const keyof: typeof keyofObject & typeof keyofStructExample:
import { keyof } from 'farrow-schema'
const userKeys = keyof(User)
const structKeys = keyof(UserStruct)Result Type
Import Path: farrow-schema (automatically exported from main entry)
Result type provides functional error handling pattern.
Result Type Definition
Type:
type Result<T = any, E = string> = Ok<T> | Err<E>
type Ok<T = any> = {
kind: 'Ok'
value: T
isOk: true
isErr: false
}
type Err<E = any> = {
kind: 'Err'
value: E
isOk: false
isErr: true
}Description:
Ok- Success result, contains success valueErr- Error result, contains error valuekind- Discriminant field for type narrowing (recommended)isOk/isErr- Boolean type guard properties (backward compatible)
Ok - Create Success Result
Function Signature:
const Ok = <T, E = string>(value: T): Result<T, E>Parameters:
value- Success value
Returns: Ok result
Example:
import { Ok } from 'farrow-schema'
const result = Ok(42)
// { kind: 'Ok', value: 42, isOk: true, isErr: false }Err - Create Error Result
Function Signature:
const Err = <E = string>(value: E): Err<E>Parameters:
value- Error value
Returns: Err result
Example:
import { Err } from 'farrow-schema'
const result = Err('Something went wrong')
// { kind: 'Err', value: 'Something went wrong', isOk: false, isErr: true }Result Usage Patterns
Type Narrowing (Recommended):
import { Result, Ok, Err } from 'farrow-schema'
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return Err('Division by zero')
}
return Ok(a / b)
}
const result = divide(10, 2)
// Recommended: Use kind field for type narrowing
if (result.kind === 'Ok') {
console.log(result.value) // Type is number
} else {
console.log(result.value) // Type is string
}
// Can also use isOk/isErr (backward compatible)
if (result.isOk) {
console.log(result.value) // Type is number
}Use in Business Logic:
import { Validator } from 'farrow-schema/validator'
import { Result, Ok, Err } from 'farrow-schema'
function processUser(data: unknown): Result<UserType, string> {
const validationResult = Validator.validate(User, data)
if (validationResult.kind === 'Err') {
return Err(`Data validation failed: ${validationResult.value.message}`)
}
const user = validationResult.value
if (user.age < 18) {
return Err('User age must be greater than 18')
}
return Ok(user)
}
const result = processUser(rawData)
if (result.kind === 'Ok') {
// Use user
console.log(result.value)
} else {
// Handle error
console.log(result.value)
}Type Utilities
Import Path: farrow-schema (automatically exported from main entry)
DateInstanceType
Type Definition:
type DateInstanceType = InstanceType<typeof Date>Description: Alias for Date instance type, equivalent to Date
MarkReadOnlyDeep
Type Definition:
type MarkReadOnlyDeep<T> = T extends Basic | ((...args: any[]) => unknown)
? T
: T extends ReadonlyMap<infer KeyType, infer ValueType>
? ReadOnlyMapDeep<KeyType, ValueType>
: T extends ReadonlySet<infer ItemType>
? ReadOnlySetDeep<ItemType>
: T extends {}
? ReadOnlyObjectDeep<T>
: unknown
type Basic = null | undefined | string | number | boolean | symbol | bigintDescription: Deep recursive read-only type utility
Example:
type User = {
name: string
profile: {
bio: string
tags: string[]
}
}
type ReadOnlyUser = MarkReadOnlyDeep<User>
/*
{
readonly name: string
readonly profile: {
readonly bio: string
readonly tags: readonly string[]
}
}
*/Usage Patterns & Best Practices
1. Schema Definition Patterns
✅ Recommended: Use ObjectType for Business Models
// Recommended: Clear business models
class User extends ObjectType {
id = ID
name = String
email = String
profile = {
bio: String,
avatar: Optional(String)
}
}
// Not recommended: Use Struct (no recursive support)
const User = Struct({
id: ID,
name: String
})✅ Recommended: Use Field Metadata
class User extends ObjectType {
id = ID
email = field({
__type: String,
description: 'User email address',
deprecated: 'Use contactEmail instead'
})
}2. Validation Patterns
✅ Recommended: Create Dedicated Validators
// Recommended: Create validator ahead of time, reuse
const validateUser = createSchemaValidator(User)
app.post('/users', (req, res) => {
const result = validateUser(req.body)
if (result.kind === 'Err') {
return res.status(400).json(result.value)
}
// ...
})
// Not recommended: Call Validator.validate every time
app.post('/users', (req, res) => {
const result = Validator.validate(User, req.body)
// ...
})✅ Recommended: Use Result Type for Error Handling
// Recommended: Use kind field for narrowing
if (result.kind === 'Ok') {
console.log(result.value)
} else {
console.log(result.value.message)
}3. Custom Validator Patterns
✅ Recommended: Inherit ValidatorType
class EmailType extends ValidatorType<string> {
validate(input: unknown) {
const result = Validator.validate(String, input)
if (result.kind === 'Err') return result
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(result.value)) {
return this.Err('Invalid email format')
}
return this.Ok(result.value)
}
}
// Usage
class User extends ObjectType {
email = EmailType
}4. Type Transformation Patterns
✅ Recommended: Use Helper Functions for Type Derivation
class User extends ObjectType {
id = ID
name = String
email = String
password = String
createdAt = Date
}
// Derive public type
const PublicUser = omitObject(User, ['password'])
// Derive create input type
const CreateUserInput = omitObject(User, ['id', 'createdAt'])
// Derive update input type
const UpdateUserInput = partialObject(
omitObject(User, ['id', 'createdAt'])
)5. Recursive Type Patterns
✅ Recommended: Use ObjectType (Supports Recursion)
// Tree structure
class TreeNode extends ObjectType {
value = String
children = List(TreeNode) // ✅ Recursive reference
}
// Comment system
class Comment extends ObjectType {
id = ID
content = String
author = User
replies = List(Comment) // ✅ Recursive reference
parent = Optional(Comment) // ✅ Optional recursive reference
}6. Union Type Patterns
✅ Recommended: Use Discriminated Union Types
// Recommended: Use type field as discriminator
const APIResponse = Union(
Struct({
type: Literal('success'),
data: User
}),
Struct({
type: Literal('error'),
message: String,
code: Number
})
)
// TypeScript automatic type narrowing
function handle(response: TypeOf<typeof APIResponse>) {
switch (response.type) {
case 'success':
console.log(response.data) // ✅ Type correct
break
case 'error':
console.log(response.message) // ✅ Type correct
break
}
}7. Formatting Patterns
✅ Recommended: Use Formatter to Generate API Documentation
import { Formatter, isNamedFormatType } from 'farrow-schema/formatter'
const { typeId, types } = Formatter.format(User)
// Generate OpenAPI Schema
function toOpenAPISchema(types: FormatTypes, rootId: number) {
const rootType = types[rootId]
if (rootType.type === 'Object') {
const properties: any = {}
for (const [name, field] of Object.entries(rootType.fields)) {
properties[name] = convertField(types[field.typeId])
}
return { type: 'object', properties }
}
// ... other type conversions
}FAQ
Q: What's the difference between Optional and Nullable?
A:
Optional(String)→ Field can be omitted orundefinedNullable(String)→ Field must be provided, value can benull
class User extends ObjectType {
bio = Optional(String) // Can be omitted
deletedAt = Nullable(Date) // Must be provided, can be null
}
// ✅ Valid
{ bio: undefined }
{ deletedAt: null }
// ❌ Invalid
{ bio: null } // bio doesn't accept null
{ } // deletedAt field missingQ: How to extract nested types?
A: Use TypeOf combined with TypeScript's index access:
class User extends ObjectType {
profile = {
bio: String,
social: {
twitter: Optional(String),
github: Optional(String)
}
}
}
type UserType = TypeOf<typeof User>
type ProfileType = UserType['profile']
type SocialType = UserType['profile']['social']
type TwitterType = UserType['profile']['social']['twitter'] // string | undefinedQ: How to derive multiple variant types?
A: Use helper functions in chain composition:
class FullUser extends ObjectType {
id = ID
name = String
email = String
password = String
createdAt = Date
updatedAt = Date
}
// Public user info
const PublicUser = omitObject(FullUser, ['password'])
// Create input (no ID and timestamps)
const CreateUserInput = omitObject(FullUser, ['id', 'createdAt', 'updatedAt', 'password'])
// Update input (partial fields, no ID and timestamps)
const UpdateUserInput = partialObject(
omitObject(FullUser, ['id', 'createdAt', 'updatedAt', 'password'])
)
// Login input (only email and password)
const LoginInput = pickObject(FullUser, ['email', 'password'])Q: How to use formatter output?
A: Formatter output can be used for multiple scenarios:
const { typeId, types } = Formatter.format(User)
// 1. Generate API documentation
function generateDocs(types: FormatTypes) {
for (const [id, formatType] of Object.entries(types)) {
if (formatType.type === 'Object') {
console.log(`## ${formatType.name}`)
for (const [name, field] of Object.entries(formatType.fields)) {
console.log(`- ${name}: ${field.description || ''}`)
}
}
}
}
// 2. Generate client SDK
function generateClient(types: FormatTypes, rootId: number) {
// Generate TypeScript interfaces, API call functions, etc. based on types
}
// 3. Convert to other formats (JSON Schema, GraphQL Schema, etc.)
function toJSONSchema(types: FormatTypes, rootId: number) {
// ...
}Summary
farrow-schema provides a powerful and flexible type system, allowing you to:
- Types as Documentation - Schema definitions serve as both TypeScript types and runtime validation rules
- Declarative Modeling - Use declarative syntax to define complex data structures
- Three-in-One - Type definition, runtime validation, and metadata extraction unified in one system
- Recursion-Friendly - Native support for recursive and mutually referential complex types
- Composability - Schemas can be freely composed, transformed, and derived
- Error Handling - Functional Result type for elegant exception handling
Through these features, farrow-schema helps you build type-safe, maintainable applications.
