Skip to content

Schema Type System

farrow-schema provides a complete type system that allows you to precisely describe data structures.

🧱 Basic Types

Scalar Types

typescript
import { String, Number, Boolean, Date, ID } from 'farrow-schema'

class Profile extends ObjectType {
  id = ID           // string (non-empty)
  name = String     // string
  age = Number      // number
  active = Boolean  // boolean
  createdAt = Date  // Date
}

Numeric Types

typescript
class Product extends ObjectType {
  price = Number      // Any number
  quantity = Int      // Integer (checked during validation)
  rating = Float      // Float
}

Optional and Nullable

typescript
import { Optional, Nullable } from 'farrow-schema'

class Article extends ObjectType {
  title = String
  bio = Optional(String)      // string | undefined (field can be omitted)
  content = Nullable(String)  // string | null (field must be provided)
}

// Usage examples
const data1 = { title: 'Hello' }                     // ✅ bio can be omitted
const data2 = { title: 'Hello', bio: undefined }     // ✅
const data3 = { title: 'Hello', content: null }      // ✅ content explicitly null

Optional vs Nullable:

  • Optional(T)T | undefined, field can be omitted
  • Nullable(T)T | null, field must exist but value can be null

Composite Types

List Types

typescript
import { List } from 'farrow-schema'

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

Record Types

typescript
import { Record } from 'farrow-schema'

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

Tuple Types

typescript
import { Tuple } from 'farrow-schema'

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]
}

Object Types

Basic Objects

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

type UserType = TypeOf<typeof User>
// {
//   id: string
//   name: string
//   email: string
// }

Nested Objects

Use object literals to define nested structures:

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

Core advantage of ObjectType: supports self-references and mutual references

typescript
// Self-reference - tree structure
class Comment extends ObjectType {
  id = String
  content = String
  replies = List(Comment)        // ✅ Supports self-reference
  parent = Optional(Comment)     // ✅ Optional self-reference
}

// Mutual references
class Post extends ObjectType {
  title = String
  author = User                  // Reference User
}

class User extends ObjectType {
  name = String
  posts = List(Post)             // Reference Post, forming circular reference
}

Struct - Quick Structure Definition

Applicable scenarios:

  • Define variants in union types
  • Dynamically build Schema at runtime
  • Quickly define simple structures

Limitation: Does not support recursive references

typescript
import { Struct } from 'farrow-schema'

// Basic usage
const UserStruct = Struct({
  id: String,
  name: String,
  age: Number
})

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

Use in union types (recommended scenario):

typescript
import { Union, Literal } from 'farrow-schema'

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 }

ObjectType vs Struct:

FeatureObjectTypeStruct
Definition methodClass inheritanceFunction call
Recursive references✅ Supported❌ Not supported
Applicable scenariosComplex business models, tree structuresUnion type variants, dynamic building
RecommendationGeneral recommendationSpecific scenarios

Union and Literal Types

Literal Types

typescript
import { Literal } from 'farrow-schema'

// Single literal
const StatusActive = Literal('active')
const HttpOK = Literal(200)

Union Types

typescript
import { Union } from 'farrow-schema'

// Enumeration values
const Status = Union(
  Literal('draft'),
  Literal('published'),
  Literal('archived')
)

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

Use type field as discriminator:

typescript
const Payment = Union(
  Struct({
    type: Literal('credit_card'),
    cardNumber: String,
    cvv: String
  }),
  Struct({
    type: Literal('paypal'),
    email: String
  }),
  Struct({
    type: Literal('crypto'),
    wallet: String,
    network: String
  })
)

type PaymentType = TypeOf<typeof Payment>

// TypeScript automatic type narrowing
function processPayment(payment: PaymentType) {
  switch (payment.type) {
    case 'credit_card':
      console.log(payment.cardNumber) // ✅ Type safe
      break
    case 'paypal':
      console.log(payment.email)      // ✅ Type safe
      break
    case 'crypto':
      console.log(payment.wallet)     // ✅ Type safe
      break
  }
}

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