@skyleague/therefore v7.15.1
∴ Therefore
Therefore is a schema-centric tool that deeply understands and works with data schemas. It enables you to:
Schema Definition & Conversion
- Define schemas in multiple formats (Zod, Therefore Schema Language, JSON Schema)
- Convert between different schema formats while preserving semantics
Code & Data Generation
- Generate code (TypeScript, JSON Schema, API clients)
- Create test data through schema-based arbitraries
Schema Composition
- Compose and transform schemas
- Reference and reuse schema components
graph LR
subgraph Input
A[Zod Schemas]
B[JSON Schema]
C[OpenAPI Spec]
D[Therefore Schemas]
E[GraphQL Schema]
end
T[Therefore]
subgraph Output
F[JSON Schema]
G[TypeScript Types]
H[API Clients]
I[Validation Code]
J[GraphQL Types]
K[Test Data]
end
A --> T
B --> T
C --> T
D --> T
E --> T
T --> F
T --> G
T --> H
T --> I
T --> J
T --> K
Core Concepts
Schema Understanding
Therefore deeply understands schemas and their relationships:
Type System
- Type hierarchies and relationships
- Cross-format compatibility
Validation & Composition
- Validation rules and constraints
- Schema composition patterns
Schema Operations
Therefore can perform various operations on schemas:
Format Handling
- Convert between Zod, JSON Schema, OpenAPI, and GraphQL
- Preserve validation rules and type information
- Handle complex type relationships
Schema Management
- Reference and combine schemas
- Handle circular dependencies
- Support modular schema design
Output Generation
- Generate TypeScript types and validation
- Create API clients from OpenAPI specs
- Build GraphQL schemas and resolvers
Testing Support
- Create arbitrary test data from schemas
- Support property-based testing
- Generate realistic mock data
Why Use Therefore?
The Problem
When building modern applications, you often need to:
Schema Management
- Work with multiple schema formats
- Convert between different formats
- Maintain schema semantics
Development Needs
- Generate consistent types
- Create validation code
- Build test data
Traditional approaches require manually maintaining multiple schema definitions or writing complex conversion code by hand.
The Solution
1. Schema Format Support
- Define schemas in your preferred format (Zod recommended)
- Import existing JSON Schema and OpenAPI specs
- Convert between formats while preserving semantics
- Keep all your schemas in sync
2. Schema Operations
- Convert between different schema formats
- Generate test data from your schemas
- Compose and transform schemas
- Reference and reuse schema components
3. Code Generation
- Generate TypeScript types from any supported format
- Create API clients from OpenAPI specs
- Build GraphQL schemas and resolvers
- Zero runtime overhead
When to Use
Use Therefore when you:
- Work with multiple schema formats (OpenAPI, JSON Schema)
- Want to generate type-safe API clients
- Need to convert between different schema formats
- Want consistent types and validation in TypeScript
Don't use Therefore if you:
- Only need simple TypeScript types
- Don't need schema conversion or code generation
- Don't work with multiple schema formats
Getting Started with Therefore
Choose Your Path
Therefore offers multiple ways to work with schemas. Here's how to choose the right approach:
1. Using Zod (Recommended)
// Define your schema with Zod
const user = z.object({
name: z.string(),
age: z.number()
})
// Therefore will generate types and more
Best for:
- New TypeScript projects
- Projects already using Zod
- When you need runtime validation
2. Using OpenAPI
// Define your OpenAPI spec
const api = $restclient({
openapi: '3.1.0',
info: {
title: 'User API',
version: '1.0.0'
},
paths: {
'/users': {
get: {
responses: {
'200': {
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/User' } }
}
}
}
}
}
}
}
}, {
validator: 'zod'
})
// Therefore will generate a type-safe client
Best for:
- Working with REST APIs
- When you have existing OpenAPI specs
- Building API clients
3. Using JSON Schema
// Import existing JSON Schema
const schema = $jsonschema({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
},
required: ['name']
})
// Therefore will generate TypeScript types
Best for:
- Projects with existing JSON Schema
- Cross-language schema sharing
- Legacy system integration
Quick Decision Guide
Choose based on your primary need:
TypeScript Development ↳ Use Zod approach
- Full TypeScript support
- Runtime validation
- Best developer experience
API Integration ↳ Use OpenAPI approach
- Type-safe API clients
- Request/response validation
- API documentation
Schema Conversion ↳ Use JSON Schema approach
- Cross-language compatibility
- Legacy system support
- Schema standardization
Not Sure? Start with the Zod approach - it's the most flexible and can be converted to other formats later if needed.
Installation
# Using npm
npm install @skyleague/therefore --save-dev
# Using yarn
yarn add -D @skyleague/therefore
# Using pnpm
pnpm add -D @skyleague/therefore
Therefore is a development tool with zero runtime dependencies - it generates code at build time.
Quick Start
Define Your Schema
Therefore looks for schema definitions in .schema.ts
files. You can:
- Define data structures with Zod
- Import existing JSON Schema files
- Use OpenAPI specifications
- Reference other schemas
Therefore will use these definitions to generate all the code you need.
Schema Definition
src/schemas/user.schema.ts
examples/docs/01-basic-zod.schema.ts
Generated Types
src/schemas/user.type.ts
examples/docs/01-basic-zod.type.ts
Generate Code
Run Therefore to process your schemas. It will:
- Find all
.schema.ts
files - Generate TypeScript types and validation
- Create JSON Schema files
- Build API clients if using OpenAPI
- Set up type guards and helpers
All generated code is type-safe and has zero runtime dependencies.
npx therefore -f src
Use Generated Code
Therefore creates everything you need to work with your data:
- Validate data at runtime with Zod
- Use generated TypeScript types
- Share JSON Schema with other services
- Make type-safe API calls
The generated code is ready to use with no additional setup needed.
import { User } from './user.type'
import { user } from './user.schema'
// Runtime validation
const data: unknown = JSON.parse(userInput)
if (user.safeParse(data).success) {
// data is typed as User
console.log(`Valid user: ${data.name}`)
}
// Type guard usage
if (User.is(data)) {
// data is typed as User here
console.log(data.name)
}
Schema Definition
Therefore provides a rich set of primitives for defining schemas that can be used for both code generation and schema conversion.
Primitive Types
String Type
// Create a string schema
$string()
// Length validation
$string().min(length: number) // Minimum string length
$string().max(length: number) // Maximum string length
$string().length(length: number) // Exact string length
$string().regex(pattern: RegExp|string) // Match regular expression
// Format validation
$string().email() // Email address (e.g. "user@example.com")
$string().uri() // URI/URL (e.g. "https://example.com")
$string().uuid() // UUID v4 (e.g. "123e4567-e89b-12d3-a456-426614174000")
$string().datetime() // ISO datetime (e.g. "2024-03-20T15:30:00Z")
$string().date() // ISO date (e.g. "2024-03-20")
$string().time() // ISO time (e.g. "15:30:00")
$string().duration() // ISO duration (e.g. "P1DT2H")
$string().ulid() // ULID (e.g. "01ARZ3NDEKTSV4RRFFQ69G5FAV")
$string().base64() // Base64 (e.g. "aGVsbG8=")
$string().hostname() // Hostname (e.g. "example.com")
$string().ipv4() // IPv4 (e.g. "192.168.1.1")
$string().ipv6() // IPv6 (e.g. "2001:db8::1")
Number Type
// Create a number schema
$number()
// Range validation
$number().min(value: number) // Minimum value (inclusive)
$number().max(value: number) // Maximum value (inclusive)
$number().exclusiveMin(value: number) // Minimum value (exclusive)
$number().exclusiveMax(value: number) // Maximum value (exclusive)
// Type validation
$number().integer() // Must be an integer
$number().multipleOf(base: number) // Must be multiple of base (e.g. 0.01 for cents)
Integer Type
// Create an integer schema
$integer()
// Range validation
$integer().min(value: number) // Minimum value (inclusive)
$integer().max(value: number) // Maximum value (inclusive)
$integer().exclusiveMin(value: number) // Minimum value (exclusive)
$integer().exclusiveMax(value: number) // Maximum value (exclusive)
$integer().multipleOf(base: number) // Must be multiple of base
Boolean Type
// Create a boolean schema
$boolean()
Null Type
// Create a null schema
$null() // Type that only accepts null
Const Type
// Create a const schema
$const(42) // Type that only accepts 42
$const('active') // Type that only accepts 'active'
$const(true) // Type that only accepts true
Unknown Type
// Create an unknown schema
$unknown() // Type that accepts any value
Container Types
Array Type
// Create an array schema
$array($string()) // Array of strings
$array($number()) // Array of numbers
$array($object({...})) // Array of objects
// Length validation
$array().min(length: number) // Minimum array length
$array().max(length: number) // Maximum array length
$array().length(length: number) // Exact array length
$array().unique() // All items must be unique
Tuple Type
// Create a tuple schema
$tuple([$string(), $number()]) // [string, number]
$tuple([$boolean(), $string(), $number()]) // [boolean, string, number]
Object Type
// Create an object schema
$object({
prop1: $string(),
prop2: $number(),
nested: $object({...})
})
// Schema modification
$object().strict() // Disallow additional properties
$object().extend({ newProp: $string() }) // Add new properties
$object().merge(otherObject) // Merge with another object schema
$object().keyof() // Get enum of property names
// Property selection
$object().pick('prop1', 'prop2') // Keep only specified properties
$object().omit('prop1', 'prop2') // Remove specified properties
$object().partial() // Make all properties optional
$object().partial('prop1', 'prop2') // Make specific properties optional
$object().required() // Make all properties required
$object().required('prop1', 'prop2') // Make specific properties required
Record Type
// Create a record schema
$record($string()) // Record with string values
$record($number()) // Record with number values
$record($object({...})) // Record with object values
Type Combinators
Enum Type
// Create an enum from array of strings
$enum(['draft', 'published']) // String literal union
$enum(['low', 'medium', 'high']) // Priority levels
// Create an enum from record (string values only)
$enum({
DRAFT: 'draft',
PUBLISHED: 'published',
ARCHIVED: 'archived'
})
Union Type
// Create a union schema
$union([schema1, schema2]) // Union of two schemas
$union([schema1, schema2, schema3]) // Union of multiple schemas
// Example: Common use cases
const stringOrNumber = $union([
$string(),
$number()
])
const status = $union([
$string().literal('loading'),
$object({
state: $string().literal('error'),
message: $string()
}),
$object({
state: $string().literal('success'),
data: $array($string())
})
])
Intersection Type
// Create an intersection schema
$intersection([schema1, schema2]) // Must satisfy both schemas
$intersection([schema1, schema2, schema3]) // Must satisfy all schemas
// Example: Common use cases
const withTimestamps = $intersection([
$object({
id: $string().uuid(),
name: $string()
}),
$object({
createdAt: $string().datetime(),
updatedAt: $string().datetime()
})
])
Type Modifiers
// Optional
schema.optional() // Makes schema optional (type | undefined)
$string().optional() // string | undefined
$number().optional() // number | undefined
// Nullable
schema.nullable() // Makes schema nullable (type | null)
$string().nullable() // string | null
$number().nullable() // number | null
// Default values
schema.default(value) // Provides default value if undefined
$string().default('') // string (defaults to '')
$number().default(0) // number (defaults to 0)
// Array conversion
schema.array() // Converts to array of schema
$string().array() // string[]
$number().array() // number[]
// Combinations
$string()
.nullable()
.optional() // string | null | undefined
$number()
.array()
.optional() // number[] | undefined
References
// Reference another schema
$ref(schema) // Reference to schema in same file
$moduleRef('./user', 'User') // Reference to schema in another file
// Example: Schema references
const address = $object({
street: $string(),
city: $string()
})
const user = $object({
name: $string(),
address: $ref(address),
team: $moduleRef('./team', 'Team')
})
Validation
Therefore provides powerful runtime validation capabilities through the .validator()
method. This allows you to generate type-safe validation code with zero runtime overhead.
Validation Strategies
Therefore supports two validation strategies:
1. Ajv (Default)
- High-performance JSON Schema validation
- Pre-compiled validation code
- Optimized for runtime performance
- Best for server-side validation with complex schemas
2. Zod
- Rich TypeScript-first validation
- Better developer experience
- More flexible validation rules
- Best for client-side validation
Using Validators
// Basic validation
const user = $object({
name: $string,
age: $number,
}).validator()
// With options
const user = $object({
name: $string,
age: $number,
}).validator({
type: 'ajv', // 'ajv' | 'zod'
compile: true, // Pre-compile validation code
formats: true, // Enable format validation
parse: true, // Generate parse functions
})
Generated Validation API
When using Ajv validation, Therefore generates a validator object with the following interface:
interface ValidatorType<T> {
// Validate data against the schema
validate: ValidateFunction<T>
// Access the underlying JSON Schema (getter)
get schema(): JsonSchema
// Access validation errors from last validation (getter)
get errors(): DefinedError[] | undefined
// Type guard for TypeScript type narrowing
is(o: unknown): o is T
// Optional: Parse with Either-style error handling (if parse: true)
parse?(o: unknown): { right: T } | { left: DefinedError[] }
}
!> When using Zod validation, Therefore only generates TypeScript types by default. You'll use Zod's native validation methods directly on the schema.
Validation Options
Ajv Options (Default)
{
type: 'ajv',
compile?: boolean, // Pre-compile validation code (default: true)
formats?: boolean, // Enable format validation (default: true)
parse?: boolean, // Generate parse functions (default: true)
coerce?: boolean, // Enable type coercion (default: false)
ajv?: AjvOptions, // Custom Ajv options
output?: {
jsonschema?: string // Custom JSON Schema output path
}
}
Zod Options
{
type: 'zod',
types?: boolean, // Generate TypeScript types (default: true)
output?: {
jsonschema?: string // Custom JSON Schema output path
}
}
Best Practices
Choose the Right Validator
- Use Ajv for high-performance server-side validation
- Use Zod for client-side validation and better DX
Pre-compilation
- Enable
compile: true
for Ajv in production - This generates standalone validation code
- Results in better runtime performance
- Enable
Error Handling
- Always check validation results
- Use
parse()
for better error handling - Use type guards for runtime safety
Type Safety
- Use the generated type guards
- TypeScript will narrow types automatically
- Validation ensures runtime type safety
Example: Complete Validation Flow
// Define schema with validation
export const user = $object({
id: $string().uuid(),
email: $string().email(),
age: $number().min(0),
}).validator()
// Using Ajv validation
function processUserAjv(data: unknown) {
// Type guard usage
if (user.is(data)) {
// data is typed as User here
console.log(data.email)
return
}
// Error handling
console.error('Invalid user:', user.errors)
}
// Using Zod validation
function processUserZod(data: unknown) {
const result = user.safeParse(data)
if (result.success) {
// data is typed as User here
console.log(result.data.email)
return
}
// Error handling with detailed issues
console.error('Invalid user:', result.error.issues)
}
Code Generation
Therefore can generate various types of code from your schemas:
- TypeScript Types: Generate TypeScript interfaces and types
- JSON Schema: Generate JSON Schema definitions
- API Clients: Generate type-safe API clients from OpenAPI specs
- GraphQL Types: Generate GraphQL type definitions
Example:
npx therefore -f src
This will generate:
- TypeScript types in
src/**/*.type.ts
- JSON Schema files in
schemas/
- API clients in
src/**/*.client.ts
Schema Conversion
Converting Between Schema Formats
Therefore enables seamless conversion between different schema formats while preserving all schema features:
Supported Conversions
- Zod ↔ JSON Schema
- OpenAPI ↔ TypeScript
- Therefore ↔ Zod
Preserved Features
- Validation rules
- Type information
- Documentation
- Custom formats
- Default values
- Optional fields
Zod to JSON Schema
examples/docs/05-zod-to-jsonschema.schema.ts
examples/docs/05-zod-to-jsonschema.schema.ts
Generated JSON Schema
examples/docs/schemas/product.schema.json
examples/docs/schemas/product.schema.json
Test Data Generation
Generating Test Data from Schemas
Convert schemas into arbitraries for testing:
import { arbitrary } from '@skyleague/therefore'
// Convert a schema into an arbitrary
const userArbitrary = arbitrary(user)
// Generate test data
const testUser = userArbitrary.sample()
Use Cases
- Property-based testing
- Mock data generation
- API testing
- Load testing
- Edge case validation
OpenAPI Client Generation
Generate fully type-safe API clients from OpenAPI/Swagger specifications with built-in validation and error handling.
Basic Usage
import { $restclient } from '@skyleague/therefore'
import type { OpenapiV3 } from '@skyleague/therefore'
const apiSpec: OpenapiV3 = {
openapi: '3.1.1',
info: {
title: 'Todo API',
version: '1.0.0',
},
...rest
}
export const client = $restclient(apiSpec, {
validator: 'zod',
client: 'got',
})
Configuration
The $restclient
primitive accepts these options:
Validation
validator?: 'zod' | 'ajv'
- Choose validation library (default: 'ajv')strict?: boolean
- Enable strict validation (default: false)formats?: boolean
- Validate string formats (default: true)compile?: boolean
- Pre-compile validators for better performance
Client Behavior
client?: 'got' | 'ky'
- HTTP client library (default: 'got')got
: Node.js focused clientky
: Browser focused client, uses fetch internally
useEither?: boolean
- Return Either type for responses (default: true)explicitContentNegotiation?: boolean
- Always include content-type (default: false)
Schema Handling
optionalNullable?: boolean
- Toggles whether optional fields will be allowed to be null. Some Rest APIs implicitly return null on optional fields. (default: false)allowIntersectionTypes?: boolean
- Support intersection types (default: false)preferOperationId?: boolean
- Use operationId for method names (default: true)
Advanced
transformOpenapi?: (spec: OpenapiV3) => OpenapiV3
- Transform spec before processingfilename?: string
- Custom output filename
Features
The generated client provides:
Type Safety
- Full TypeScript types for requests/responses
- Automatic path parameter handling
- Type-safe error handling
Validation
- Runtime request/response validation
- Support for Zod or Ajv validators
- Pre-compiled validators for performance
Authentication
- Built-in auth scheme support
- Automatic header injection
- Multiple auth methods per endpoint
Developer Experience
- Automatic content negotiation
- Error handling with type refinements
- Promise-based API
Client Config
src/api/todo.schema.ts
examples/docs/02-openapi-client.schema.ts
OpenAPI Spec
src/api/todo-api.yaml
Generated Client
src/api/todo.client.ts
examples/docs/02-openapi-client.client.ts
Generated Types
src/api/todo.zod.ts
examples/docs/02-openapi-client.zod.ts
GraphQL Support
!> GraphQL support is currently in an experimental phase. The API and functionality may undergo significant changes in future releases. While usable, we recommend carefully evaluating it for production use. We welcome feedback and contributions to help stabilize this feature.
Generate type-safe GraphQL schemas using Therefore's schema language:
- Define schemas with type safety
- Automatic resolver type generation
- Circular references support
- Schema composition and reuse
Schema Definition
src/schema/user.schema.ts
examples/docs/04-graphql.schema.ts
Generated Types
src/schema/user.type.ts
examples/docs/04-graphql.type.ts
Generated GraphQL Schema
src/schema/user.graphql
examples/docs/04-graphql.graphql
Development Guide
CLI Options
npx therefore [options]
Options:
-f, --files Globs to scan for schemas (required)
-i, --ignore-pattern Globs to exclude [default: ["**/*.d.ts","node_modules"]]
--clean Clean output directory before generation [default: true]
--migrate-to Migrate schemas to specified validator [choices: "zod"]
Security
The security considerations depend on your chosen validation strategy:
When Using Ajv (Default)
- Security considerations are a superset of
Ajv
- Therefore implements strict validation by default
- No additional properties allowed unless explicitly enabled
- All properties required unless marked optional
When Using Zod
- Security considerations are inherited from Zod
- Runtime type checking is performed by Zod's validation engine
- Strict type checking enabled by default
- Custom validation rules are executed in order defined
In both cases, Therefore itself does not perform validation - it generates the validation code that uses either Ajv or Zod. The security of your validation depends on: 1. The validation strategy you choose 2. The validation options you configure 3. How you handle validation errors in your code
Advanced Examples
Circular References
Therefore supports complex data types with circular references. A common example is the JSON data type, which can contain nested objects and arrays that reference themselves:
// Define a JSON type that can contain nested JSON values
export const json = $validator(
$union([
$string,
$null,
$boolean,
$number,
$record($ref(() => json)),
$array($ref(() => json))
])
)
The generated TypeScript types handle circular references correctly:
export type Json =
| string
| null
| boolean
| number
| { [k: string]: Json | undefined }
| Json[]
Enterprise Support
SkyLeague provides Enterprise Support for Therefore. Visit skyleague.io for:
- Custom integrations
- Implementation support
- Training and consulting
- Priority issue resolution
License
Copyright (c) 2025, SkyLeague Technologies B.V.. 'SkyLeague' and the astronaut logo are trademarks of SkyLeague Technologies, registered at Chamber of Commerce in The Netherlands under number 86650564.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 months ago
7 months ago
8 months ago
6 months ago
6 months ago
8 months ago
8 months ago
8 months ago
5 months ago
4 months ago
5 months ago
7 months ago
6 months ago
6 months ago
6 months ago
8 months ago
8 months ago
6 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
8 months ago
8 months ago
8 months ago
8 months ago
3 months ago
3 months ago
6 months ago
6 months ago
8 months ago
6 months ago
6 months ago
6 months ago
4 months ago
6 months ago
6 months ago
6 months ago
9 months ago
1 year ago
10 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
10 months ago
10 months ago
1 year ago
10 months ago
11 months ago
11 months ago
11 months ago
12 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago