JSPM

@honorestjs/contract

0.1.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q32354F
  • License MIT

Contract-first API definitions for HonorestJS - Framework-agnostic contract definitions with full type safety

Package Exports

  • @honorestjs/contract

Readme

@honorestjs/contract

Framework-agnostic contract definitions for type-safe APIs

npm version License: MIT

@honorestjs/contract provides a powerful, type-safe way to define API contracts that can be shared between servers and clients. Built on top of Zod, it enables contract-first development with full TypeScript type inference and runtime validation.

Features

Contract-First Development - Define your API before implementation
🔒 Full Type Safety - Complete TypeScript type inference from contracts
Runtime Validation - Built-in validation using Zod schemas
🎯 Framework Agnostic - Use with any server or client framework
📝 OpenAPI Compatible - Generate OpenAPI specs from contracts
🚀 Zero Code Generation - Pure TypeScript, no build steps required

Installation

npm install @honorestjs/contract zod
# or
yarn add @honorestjs/contract zod
# or
pnpm add @honorestjs/contract zod
# or
bun add @honorestjs/contract zod

Quick Start

1. Define Your Contract

import { defineContract, endpoint } from '@honorestjs/contract'
import { z } from 'zod'

// Define schemas
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email()
})

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

// Define contract
export const UsersContract = defineContract({
  name: 'users',
  path: '/users',
  description: 'User management endpoints',
  endpoints: {
    getUser: endpoint({
      method: 'GET',
      path: '/:id',
      params: z.object({ id: z.string().uuid() }),
      output: UserSchema,
      description: 'Get a user by ID',
      errors: {
        404: z.object({ message: z.string() })
      }
    }),
    
    listUsers: endpoint({
      method: 'GET',
      path: '/',
      query: z.object({
        page: z.number().int().positive().default(1),
        limit: z.number().int().positive().max(100).default(20)
      }),
      output: z.object({
        users: z.array(UserSchema),
        total: z.number()
      })
    }),
    
    createUser: endpoint({
      method: 'POST',
      path: '/',
      body: CreateUserSchema,
      output: UserSchema,
      errors: {
        400: z.object({ message: z.string(), errors: z.array(z.any()) })
      }
    })
  }
})

2. Create an App Contract

import { defineApp } from '@honorestjs/contract'
import { UsersContract } from './contracts/users'
import { PostsContract } from './contracts/posts'

export const AppContract = defineApp({
  version: 1,
  prefix: '/api',
  contracts: {
    users: UsersContract,
    posts: PostsContract
  },
  metadata: {
    title: 'My API',
    version: '1.0.0',
    description: 'RESTful API for my application'
  }
})

3. Use Type Inference

import type { ContractInput, ContractOutput } from '@honorestjs/contract'

// Extract input types
type GetUserInput = ContractInput<typeof UsersContract, 'getUser'>
// { params: { id: string }, query: never, body: never, headers: never }

// Extract output types
type GetUserOutput = ContractOutput<typeof UsersContract, 'getUser'>
// { id: string, name: string, email: string }

// Use in your implementation
function getUser(input: GetUserInput['params']): Promise<GetUserOutput> {
  // Implementation
}

API Reference

Builders

endpoint(definition)

Creates an endpoint definition.

const getUserEndpoint = endpoint({
  method: 'GET',                              // HTTP method
  path: '/:id',                               // Endpoint path
  params: z.object({ id: z.string() }),      // Path parameters schema
  query: z.object({ include: z.string() }),  // Query parameters schema (optional)
  body: z.object({ ... }),                    // Request body schema (optional)
  headers: z.object({ ... }),                 // Headers schema (optional)
  output: z.object({ ... }),                  // Response schema
  errors: {                                   // Error responses (optional)
    404: z.object({ message: z.string() })
  },
  description: 'Get a user by ID',            // Description (optional)
  summary: 'Get user',                        // Summary (optional)
  tags: ['users'],                            // Tags (optional)
  deprecated: false                           // Deprecated flag (optional)
})

defineContract(definition)

Creates a contract (collection of endpoints).

const UsersContract = defineContract({
  name: 'users',                    // Contract name
  path: '/users',                   // Base path
  version: 1,                       // API version (optional)
  description: 'User endpoints',    // Description (optional)
  tags: ['users'],                  // Tags (optional)
  endpoints: {                      // Endpoint definitions
    getUser: getUserEndpoint,
    createUser: createUserEndpoint
  }
})

defineApp(definition)

Creates an app (collection of contracts).

const AppContract = defineApp({
  version: 1,                       // Global API version (optional)
  prefix: '/api',                   // Global prefix (optional)
  contracts: {                      // Contract definitions
    users: UsersContract,
    posts: PostsContract
  },
  metadata: {                       // App metadata (optional)
    title: 'My API',
    version: '1.0.0',
    description: 'API description',
    contact: { name: 'Support', email: 'support@example.com' },
    license: { name: 'MIT' }
  }
})

Type Utilities

ContractInput<TContract, TEndpoint>

Extracts input type from a contract endpoint.

type Input = ContractInput<typeof UsersContract, 'getUser'>
// { params: {...}, query: {...}, body: {...}, headers: {...} }

ContractOutput<TContract, TEndpoint>

Extracts output type from a contract endpoint.

type Output = ContractOutput<typeof UsersContract, 'getUser'>
// { id: string, name: string, email: string }

ContractErrors<TContract, TEndpoint>

Extracts error types from a contract endpoint.

type Errors = ContractErrors<typeof UsersContract, 'getUser'>
// { 404: { message: string } }

Validation

validate(schema, data)

Validates data against a Zod schema (async).

const result = await validate(UserSchema, userData)
if (result.success) {
  console.log(result.data) // Validated data
} else {
  console.error(result.error) // Validation error
}

validateEndpointInput(endpoint, input)

Validates all inputs for an endpoint.

const validation = await validateEndpointInput(
  UsersContract.endpoints.getUser,
  {
    params: { id: '123' },
    query: { include: 'posts' }
  }
)

if (!validation.isValid) {
  console.error(validation.errors)
}

validateEndpointOutput(endpoint, output)

Validates endpoint output.

const result = await validateEndpointOutput(
  UsersContract.endpoints.getUser,
  responseData
)

Integration

With HonorestJS Server

import { Controller } from 'honorestjs'
import { Contract } from 'honorestjs/contract'
import { UsersContract } from '@myapp/api-contract'

@Controller()
export class UsersController {
  @Contract(UsersContract.endpoints.getUser)
  async getUser(@Param('id') id: string) {
    // Implementation with automatic validation
  }
}

With @honorest/client

import { createClient } from '@honorest/client'
import { AppContract } from '@myapp/api-contract'

const api = createClient(AppContract, {
  baseUrl: 'http://localhost:3000'
})

// Fully type-safe
const user = await api.users.getUser({ params: { id: '123' }})

Best Practices

1. Organize Contracts by Domain

/contracts
  /users
    schemas.ts
    contract.ts
  /posts
    schemas.ts
    contract.ts
  index.ts  # Export AppContract

2. Reuse Schemas

// Define shared schemas
export const UserSchema = z.object({ ... })
export const CreateUserSchema = UserSchema.omit({ id: true })
export const UpdateUserSchema = CreateUserSchema.partial()

3. Document Everything

const endpoint = endpoint({
  // ...
  description: 'Detailed description of what this endpoint does',
  summary: 'Short summary',
  tags: ['users', 'admin'],
  examples: {
    request: { id: '123' },
    response: { id: '123', name: 'John' }
  }
})

4. Version Your Contracts

const UsersContractV1 = defineContract({
  name: 'users',
  version: 1,
  // ...
})

const UsersContractV2 = defineContract({
  name: 'users',
  version: 2,
  // ...
})

TypeScript Configuration

For best results, enable strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true
  }
}

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

MIT © HonorestJS