Skip to content

farrow-schema Complete API Reference

Table of Contents

  1. Core Concepts
  2. Complete Export List
  3. Schema Type System
  4. Validator System
  5. Formatter System
  6. Helper Utilities
  7. Result Type
  8. Usage Patterns & Best Practices
  9. FAQ

Core Concepts

Design Philosophy

farrow-schema is a type-safe runtime validation and serialization system with core design principles:

  1. Types as Documentation - Schema definitions serve as both TypeScript types and runtime validation rules
  2. Declarative Modeling - Use declarative syntax to define complex data structures
  3. Three-in-One - Type definition, runtime validation, and metadata extraction unified in one system
  4. Recursion-Friendly - Native support for recursive and mutually referential complex types
  5. 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 Generation

Module Architecture

farrow-schema/
├── index.ts              # Main entry: type definition system
├── validator.ts          # Validator entry: runtime validation
└── formatter.ts          # Formatter entry: type metadata extraction

Complete 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:

typescript
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:

  1. Schema Classes - Type definitions

    • Basic types: String, Number, Boolean, Date, ID, Int, Float
    • Special types: Any, Unknown, Never, Json
    • Abstract classes: Schema, ObjectType, etc.
  2. Constructors - Type composition

    • List(Item) - List type
    • Optional(Item) - Optional type
    • Nullable(Item) - Nullable type
    • Record(Item) - Key-value pair type
    • Tuple(...Items) - Tuple type
    • Union(...Items) - Union type
    • Intersect(...Items) - Intersection type
    • Literal(value) - Literal type
    • Struct(descriptors) - Struct type
  3. Type Utilities - Type extraction and inspection

    • TypeOf<T> - Extract TypeScript type
    • getInstance(Ctor) - Get Schema instance
    • getSchemaCtor(Ctor) - Get Schema constructor
    • isSchemaCtor(input) - Check if Schema constructor
  4. Helper Functions - Schema operations

    • field(fieldInfo) - Define field metadata
    • pickObject(Ctor, keys) - Pick ObjectType fields
    • omitObject(Ctor, keys) - Omit ObjectType fields
    • pickStruct(Ctor, keys) - Pick StructType fields
    • omitStruct(Ctor, keys) - Omit StructType fields
    • partial(Ctor) - Make optional
    • required(Ctor) - Make required
    • keyof(Ctor) - Get field keys
  5. Result Type - Error handling

    • Result<T, E> - Result type
    • Ok(value) - Success result
    • Err(value) - Error result

Validator Entry (farrow-schema/validator)

Import Path: farrow-schema/validator

Core Responsibility: Provides runtime data validation functionality

Typical Usage:

typescript
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 object
    • Validator.validate(Ctor, input, options) - Validate data
    • Validator.impl(Ctor, impl) - Register validator implementation
    • Validator.get(Ctor) - Get validator implementation
  • createSchemaValidator(Ctor, options) - Create dedicated validator
  • ValidatorType<T> - Custom validator base class
  • RegExp(regexp) - Regular expression validator
  • SchemaErr(message, path) - Create validation error
  • Type Exports:
    • ValidationResult<T> - Validation result type
    • ValidationError - Validation error type
    • ValidatorOptions - 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:

typescript
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 object
    • Formatter.format(Ctor, context?) - Format Schema
    • Formatter.formatSchema(Ctor, ctx) - Context formatting
    • Formatter.impl(Ctor, impl) - Register formatter implementation
    • Formatter.get(Ctor) - Get formatter implementation
  • formatSchema(Ctor, context?) - Alias for Formatter.format
  • isNamedFormatType(formatType) - Check if named format type
  • Type Exports (15+ format types):
    • FormatType - Format type union
    • FormatTypes - Type dictionary
    • FormatContext - Format context
    • FormatScalarType, FormatObjectType, FormatStructType, FormatListType, etc.
    • FormatField, FormatFields - Field format info

Use Cases:

  1. API Documentation Generation - Auto-generate OpenAPI/Swagger docs
  2. Client Code Generation - Generate TypeScript/JavaScript SDK
  3. GraphQL Schema - Convert to GraphQL type definitions
  4. Cross-Language Type Sharing - Export as JSON Schema, Protocol Buffers, etc.
  5. Type Visualization - Generate type relationship diagrams

Module Selection Guide

NeedModuleTypical Scenario
Define data structuresfarrow-schemaDefine API input/output types, business models
Validate runtime datafarrow-schema/validatorAPI parameter validation, form validation
Extract type metadatafarrow-schema/formatterGenerate docs, code generation, cross-language type sharing
Full featuresImport allComplete type-safe system

Complete Usage Example:

typescript
// 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:

typescript
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 inference
  • displayName - Schema display name (for formatting)
  • namespace - Schema namespace (for formatting)
  • create() - Static factory method to create Schema-compliant values

Usage Example:

typescript
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

typescript
class Number extends Schema {
  __type!: number
}

Corresponding TypeScript Type: number

Example:

typescript
class Product extends ObjectType {
  price = Number
}

Int - Integer Type

typescript
class Int extends Schema {
  __type!: number
}

Corresponding TypeScript Type: numberValidation: Validates if integer, rounds down in non-strict mode

Example:

typescript
class Pagination extends ObjectType {
  page = Int
  limit = Int
}

Float - Float Type

typescript
class Float extends Schema {
  __type!: number
}

Corresponding TypeScript Type: numberValidation: Same as Number


String - String Type

typescript
class String extends Schema {
  __type!: string
}

Corresponding TypeScript Type: string


Boolean - Boolean Type

typescript
class Boolean extends Schema {
  __type!: boolean
}

Corresponding TypeScript Type: boolean


ID - Identifier Type

typescript
class ID extends Schema {
  __type!: string
}

Corresponding TypeScript Type: stringValidation: Cannot be empty string

Example:

typescript
class User extends ObjectType {
  id = ID
  name = String
}

Date - Date Type

typescript
class Date extends Schema {
  __type!: DateInstanceType
}

Corresponding TypeScript Type: DateValidation: Accepts Date instance, timestamp number, ISO date string

Example:

typescript
class Event extends ObjectType {
  title = String
  startDate = Date
}

Composite Type Schemas

List - List Type

Constructor:

typescript
const List = <T extends SchemaCtorInput>(Item: T): new () => ListType

Type:

typescript
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:

typescript
class Blog extends ObjectType {
  tags = List(String)              // string[]
  scores = List(Number)            // number[]
  nested = List(List(String))      // string[][]
}

Optional - Optional Type

Constructor:

typescript
const Optional = <T extends SchemaCtorInput>(Item: T): new () => OptionalType

Type:

typescript
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:

typescript
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 or undefined
  • Nullable(String) → Field must be provided, value can be null

Nullable - Nullable Type

Constructor:

typescript
const Nullable = <T extends SchemaCtorInput>(Item: T): new () => NullableType

Type:

typescript
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:

typescript
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 null

Record - Key-Value Pair Type

Constructor:

typescript
const Record = <T extends SchemaCtorInput>(Item: T): new () => RecordType

Type:

typescript
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:

typescript
class Config extends ObjectType {
  labels = Record(String)          // { [key: string]: string }
  counters = Record(Number)        // { [key: string]: number }
}

Tuple - Tuple Type

Constructor:

typescript
const Tuple = <T extends SchemaCtorInput[]>(...Items: T): new () => TupleType

Type:

typescript
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:

typescript
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:

typescript
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:

typescript
class User extends ObjectType {
  id = String
  name = String
  age = Number
}

type UserType = TypeOf<typeof User>
// { id: string, name: string, age: number }

Nested Objects:

typescript
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):

typescript
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:

typescript
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:

typescript
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:

typescript
abstract class StructType extends Schema {
  __type!: ShallowPrettier<TypeOfFieldDescriptors<this['descriptors']>>
  abstract descriptors: FieldDescriptors
}

Constructor:

typescript
const Struct = <T extends FieldDescriptors>(descriptors: T): new () => StructType

Description:

  • 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:

typescript
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):

typescript
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:

typescript
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:

typescript
// ❌ 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:

FeatureObjectTypeStruct
Recursive references✅ Supported❌ Not supported
Definition methodclass inheritancefunction call
Use caseComplex business models, tree structuresUnion type variants, dynamic construction
PerformanceSlightly slower (requires instantiation)Slightly faster
RecommendationGeneral recommendationSpecific scenarios

Union & Intersection Types

Union - Union Type

Constructor:

typescript
const Union = <T extends SchemaCtorInput[]>(...Items: T): new () => UnionType

Type:

typescript
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:

typescript
const Status = Union(
  Literal('draft'),
  Literal('published'),
  Literal('archived')
)

type StatusType = TypeOf<typeof Status>
// 'draft' | 'published' | 'archived'

Discriminated Union Types (Recommended):

typescript
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:

typescript
const Intersect = <T extends SchemaCtorInput[]>(...Items: T): new () => IntersectType

Type:

typescript
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:

typescript
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:

typescript
const Literal = <T extends Literals>(value: T): new () => LiteralType

Type:

typescript
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:

typescript
const Environment = Union(
  Literal('development'),
  Literal('staging'),
  Literal('production')
)

const APIResponse = Struct({
  status: Literal(200),
  message: Literal('OK'),
  data: Any
})

Predefined Literals:

typescript
const Null = Literal(null)
const Undefined = Literal(undefined)

Special Type Schemas

Any - Any Type

typescript
class Any extends Schema {
  __type!: any
}

Corresponding TypeScript Type: anyValidation: Accepts any value


Unknown - Unknown Type

typescript
class Unknown extends Schema {
  __type!: unknown
}

Corresponding TypeScript Type: unknownValidation: Accepts any value, but more type-safe


Never - Never Type

typescript
class Never extends Schema {
  __type!: never
}

Corresponding TypeScript Type: neverValidation: Should not validate this type, will throw error


Json - JSON Type

typescript
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:

typescript
const Strict = <T extends SchemaCtorInput>(Item: T): new () => StrictType

Type:

typescript
abstract class StrictType extends Schema {
  __type!: TypeOf<this['Item']>
  abstract Item: SchemaCtor
}

Description: Wrapped Schema enforces strict mode during validation

Example:

typescript
class User extends ObjectType {
  age = Strict(Number)  // Must be pure number, won't accept "25" string
}

NonStrict - Non-Strict Mode

Constructor:

typescript
const NonStrict = <T extends SchemaCtorInput>(Item: T): new () => NonStrictType

Type:

typescript
abstract class NonStrictType extends Schema {
  __type!: TypeOf<this['Item']>
  abstract Item: SchemaCtor
}

Description: Wrapped Schema enforces non-strict mode during validation

Example:

typescript
class User extends ObjectType {
  age = NonStrict(Number)  // Accepts 25 or "25"
}

ReadOnly - Read-Only Type

Constructor:

typescript
const ReadOnly = <T extends SchemaCtorInput>(Item: T): new () => ReadOnlyType

Type:

typescript
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:

typescript
const ReadOnlyDeep = <T extends SchemaCtorInput>(Item: T): new () => ReadOnlyDeepType

Type:

typescript
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:

typescript
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']
  : never

Description: Extract corresponding TypeScript type from Schema constructor or instance

Example:

typescript
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:

typescript
const getSchemaCtor = <T extends SchemaCtor>(Ctor: T): SchemaTypeOf<T>

Description: Convert primitive type constructors (Number, String, etc.) to corresponding Schema classes

Example:

typescript
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 itself

getInstance - Get Schema Instance

Function Signature:

typescript
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:

typescript
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)  // true

Internal Usage:

typescript
// 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:

typescript
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:

typescript
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:

typescript
const isSchemaCtor = (input: any): input is SchemaCtor

Description: 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:

typescript
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:

typescript
function validate<T extends SchemaCtor>(
  Ctor: T,
  input: unknown,
  options?: ValidatorOptions
): ValidationResult<TypeOf<T>>

Parameters:

  • Ctor - Schema constructor
  • input - Data to validate
  • options - Validation options
    • strict?: boolean - Strict mode (default false)

Returns: Validation result of type Result<T, ValidationError>

Example:

typescript
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:

typescript
function impl<T extends Schema>(
  Ctor: abstract new () => T,
  impl: ValidatorImpl<T>
): void

Parameters:

  • Ctor - Schema constructor
  • impl - Validator implementation, can be object or factory function

Description: Register validator implementation for Schema type (internally stored using WeakMap)

Example:

typescript
// 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:

typescript
function get<T extends SchemaCtor>(
  Ctor: T
): ValidatorMethods<SchemaTypeOf<T>> | undefined

Parameters:

  • Ctor - Schema constructor

Returns: Validator method object or undefined

Description: Get validator implementation corresponding to Schema (searches along prototype chain)


ValidationResult Type

Type Definition:

typescript
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:

typescript
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:

typescript
type ValidatorOptions = {
  strict?: boolean
}

Description:

  • strict: true - Strict mode (default), no type conversion
    • "25" won't convert to 25
    • "true" won't convert to true
  • strict: false - Lenient mode, attempts type conversion
    • "25"25
    • "true"true
    • "2024-01-01"Date

createSchemaValidator - Create Dedicated Validator

Function Signature:

typescript
function createSchemaValidator<S extends SchemaCtor>(
  SchemaCtor: S,
  options?: ValidatorOptions
): (input: unknown) => ValidationResult<TypeOf<S>>

Parameters:

  • SchemaCtor - Schema constructor
  • options - Validation options

Returns: Validation function bound with Schema and options

Example:

typescript
// 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:

typescript
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:

typescript
// 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:

typescript
const RegExp = (regexp: RegExp): new () => ValidatorType<string>

Parameters:

  • regexp - Regular expression

Returns: Regex validator constructor

Example:

typescript
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:

typescript
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:

typescript
function format<T extends SchemaCtor>(
  Ctor: T,
  context?: FormatContext
): {
  typeId: number
  types: FormatTypes
}

type FormatTypes = {
  [key: string]: FormatType
}

Parameters:

  • Ctor - Schema constructor
  • context - Format context (optional)

Returns:

  • typeId - Root type ID
  • types - Type dictionary, keys are type IDs (string form), values are format types

Example:

typescript
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:

typescript
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:

typescript
const formatSchema = Formatter.format

Description: Export alias for Formatter.format

Example:

typescript
import { formatSchema } from 'farrow-schema/formatter'

const result = formatSchema(User)

Formatter.formatSchema - Format Schema (Context Version)

Function Signature:

typescript
function formatSchema<T extends SchemaCtor>(
  Ctor: T,
  ctx: FormatContext
): number

Parameters:

  • Ctor - Schema constructor
  • ctx - Format context (required)

Returns: Type ID

Description:

  • Format Schema in given context
  • Uses context's addType and formatCache
  • Used to format nested types in custom formatters

Formatter.impl - Implement Custom Formatter

Function Signature:

typescript
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 constructor
  • impl - Formatter implementation

Description: Register formatter implementation for Schema type

Example:

typescript
// 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:

typescript
function get<T extends SchemaCtor>(
  Ctor: T
): FormatterMethods | undefined

Parameters:

  • 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:

typescript
type FormatContext = {
  addType: (type: FormatType) => number
  formatCache: WeakMap<Function, number>
}

Description:

  • addType - Add type to type dictionary, return assigned type ID
  • formatCache - Type cache, avoid repeatedly formatting same type

Use Case: Used in custom formatters


FormatType - Format Type (Union Type)

Type Definition:

typescript
type FormatType =
  | FormatScalarType
  | FormatObjectType
  | FormatUnionType
  | FormatStructType
  | FormatRecordType
  | FormatListType
  | FormatLiteralType
  | FormatNullableType
  | FormatOptionalType
  | FormatIntersectType
  | FormatTupleType
  | FormatStrictType
  | FormatNonStrictType
  | FormatReadOnlyType
  | FormatReadonlyDeepType

Description: Union of all format types, each type has type discriminant field


Format Type Details

FormatScalarType - Scalar Type Format

Type Definition:

typescript
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:

typescript
// 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:

typescript
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 displayName
  • fields - Object field dictionary
    • Key is field name
    • Value is field format info
  • namespace - Namespace (if set)

Example:

typescript
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:

typescript
type FormatStructType = {
  type: 'Struct'
  name?: string
  fields: FormatFields
  namespace?: string
}

Corresponding Schema: StructType

Description: Similar to FormatObjectType, but name is optional

Example:

typescript
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:

typescript
type FormatListType = {
  type: 'List'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: List

Field Description:

  • itemTypeId - List element type ID
  • $ref - Element type reference path

Example:

typescript
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:

typescript
type FormatNullableType = {
  type: 'Nullable'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: Nullable


FormatOptionalType - Optional Type Format

Type Definition:

typescript
type FormatOptionalType = {
  type: 'Optional'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: Optional


FormatLiteralType - Literal Type Format

Type Definition:

typescript
type FormatLiteralType = {
  type: 'Literal'
  value: Literals
}

type Literals = number | string | boolean | null | undefined

Corresponding Schema: Literal

Example:

typescript
const Status = Literal('active')

const { types } = Formatter.format(Status)
/*
types["0"] = {
  type: "Literal",
  value: "active"
}
*/

FormatUnionType - Union Type Format

Type Definition:

typescript
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 typeId and $ref

Example:

typescript
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:

typescript
type FormatIntersectType = {
  type: 'Intersect'
  name?: string
  itemTypes: { typeId: number; $ref: string }[]
  namespace?: string
}

Corresponding Schema: Intersect


FormatTupleType - Tuple Type Format

Type Definition:

typescript
type FormatTupleType = {
  type: 'Tuple'
  name?: string
  itemTypes: { typeId: number; $ref: string }[]
  namespace?: string
}

Corresponding Schema: Tuple

Example:

typescript
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:

typescript
type FormatRecordType = {
  type: 'Record'
  valueTypeId: number
  $ref: string
}

Corresponding Schema: Record


FormatStrictType - Strict Type Format

Type Definition:

typescript
type FormatStrictType = {
  type: 'Strict'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: Strict


FormatNonStrictType - Non-Strict Type Format

Type Definition:

typescript
type FormatNonStrictType = {
  type: 'NonStrict'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: NonStrict


FormatReadOnlyType - Read-Only Type Format

Type Definition:

typescript
type FormatReadOnlyType = {
  type: 'ReadOnly'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: ReadOnly


FormatReadonlyDeepType - Deep Read-Only Type Format

Type Definition:

typescript
type FormatReadonlyDeepType = {
  type: 'ReadOnlyDeep'
  itemTypeId: number
  $ref: string
}

Corresponding Schema: ReadOnlyDeep


isNamedFormatType - Check if Named Format Type

Function Signature:

typescript
const isNamedFormatType = (input: FormatType): input is NamedFormatType

type NamedFormatType =
  | FormatTupleType
  | FormatStructType
  | FormatUnionType
  | FormatIntersectType
  | FormatObjectType

Description: Type guard to check if format type can have a name

Example:

typescript
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

typescript
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:

typescript
const field = <T extends FieldInfo>(fieldInfo: T): T

type FieldInfo = {
  __type: SchemaCtor
  description?: string
  deprecated?: string
}

Parameters:

  • fieldInfo - Field info object
    • __type - Field Schema type
    • description - Field description (optional)
    • deprecated - Deprecation notice (optional)

Returns: Returns fieldInfo as-is (for type inference)

Example:

typescript
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:

typescript
const pickObject = <T extends ObjectType, Keys extends SchemaField<T, keyof T>[]>(
  Ctor: new () => T,
  keys: Keys
): PickObject<T, Keys>

Parameters:

  • Ctor - ObjectType constructor
  • keys - Array of field names to select

Returns: New ObjectType constructor with only selected fields

Example:

typescript
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:

typescript
const pickStruct = <T extends StructType, Keys extends (keyof T['descriptors'])[]>(
  Ctor: new () => T,
  keys: Keys
): new () => StructType

Parameters:

  • Ctor - StructType constructor
  • keys - Array of field names to select

Returns: New StructType constructor with only selected fields


pick - Generic Pick Function

Function Signature:

typescript
const pick: typeof pickObject & typeof pickStruct

Description: 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:

typescript
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:

typescript
const omitObject = <T extends ObjectType, Keys extends SchemaField<T, keyof T>[]>(
  Ctor: new () => T,
  keys: Keys
): OmitObject<T, Keys>

Parameters:

  • Ctor - ObjectType constructor
  • keys - Array of field names to exclude

Returns: New ObjectType constructor without excluded fields

Example:

typescript
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:

typescript
const omitStruct = <T extends StructType, Keys extends (keyof T['descriptors'])[]>(
  Ctor: new () => T,
  keys: Keys
): new () => StructType

omit - Generic Omit Function

Function Signature:

typescript
const omit: typeof omitObject & typeof omitStruct

Description: 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:

typescript
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:

typescript
const partialObject = <T extends ObjectType>(
  Ctor: new () => T
): PartialObjectType<T>

Parameters:

  • Ctor - ObjectType constructor

Returns: New ObjectType constructor with all fields optional

Example:

typescript
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:

typescript
const partialStruct = <T extends StructType>(
  Ctor: new () => T
): new () => PartialType

partial - Generic Make Optional Function

Function Signature:

typescript
const partial: typeof partialObject & typeof partialStruct

Example:

typescript
import { partial } from 'farrow-schema'

const PartialUser = partial(User)
const PartialUserStruct = partial(UserStruct)

required Series - Make Fields Required

requiredObject - Make ObjectType Required

Function Signature:

typescript
const requiredObject = <T extends ObjectType>(
  Ctor: new () => T
): RequiredObjectType<T>

Parameters:

  • Ctor - ObjectType constructor

Returns: New ObjectType constructor with all optional fields required

Example:

typescript
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:

typescript
const requiredStruct = <T extends StructType>(
  Ctor: new () => T
): new () => StructType

required - Generic Make Required Function

Function Signature:

typescript
const required: typeof requiredObject & typeof requiredStruct

Example:

typescript
import { required } from 'farrow-schema'

const RequiredUser = required(OptionalUser)

keyof Series - Get Field Keys

keyofObject - Get ObjectType Field Keys

Function Signature:

typescript
const keyofObject = <T extends ObjectType>(
  Ctor: new () => T
): (keyof TypeOf<T>)[]

Parameters:

  • Ctor - ObjectType constructor

Returns: Field name array

Example:

typescript
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:

typescript
const keyofStruct = <T extends StructType>(
  Ctor: new () => T
): (keyof T['descriptors'])[]

keyof - Generic Get Keys Function

Function Signature:

typescript
const keyof: typeof keyofObject & typeof keyofStruct

Example:

typescript
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:

typescript
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 value
  • Err - Error result, contains error value
  • kind - Discriminant field for type narrowing (recommended)
  • isOk / isErr - Boolean type guard properties (backward compatible)

Ok - Create Success Result

Function Signature:

typescript
const Ok = <T, E = string>(value: T): Result<T, E>

Parameters:

  • value - Success value

Returns: Ok result

Example:

typescript
import { Ok } from 'farrow-schema'

const result = Ok(42)
// { kind: 'Ok', value: 42, isOk: true, isErr: false }

Err - Create Error Result

Function Signature:

typescript
const Err = <E = string>(value: E): Err<E>

Parameters:

  • value - Error value

Returns: Err result

Example:

typescript
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):

typescript
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:

typescript
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:

typescript
type DateInstanceType = InstanceType<typeof Date>

Description: Alias for Date instance type, equivalent to Date


MarkReadOnlyDeep

Type Definition:

typescript
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 | bigint

Description: Deep recursive read-only type utility

Example:

typescript
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

typescript
// 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
})
typescript
class User extends ObjectType {
  id = ID

  email = field({
    __type: String,
    description: 'User email address',
    deprecated: 'Use contactEmail instead'
  })
}

2. Validation Patterns

typescript
// 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)
  // ...
})
typescript
// Recommended: Use kind field for narrowing
if (result.kind === 'Ok') {
  console.log(result.value)
} else {
  console.log(result.value.message)
}

3. Custom Validator Patterns

typescript
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

typescript
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

typescript
// 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

typescript
// 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

typescript
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 or undefined
  • Nullable(String) → Field must be provided, value can be null
typescript
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 missing

Q: How to extract nested types?

A: Use TypeOf combined with TypeScript's index access:

typescript
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 | undefined

Q: How to derive multiple variant types?

A: Use helper functions in chain composition:

typescript
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:

typescript
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.

This is a third-party Farrow documentation site | Built with ❤️ and TypeScript