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