Skip to content

CLI Tool ​

@vafast/cli is the official Vafast command-line tool for syncing API type definitions from the server, enabling type safety across repositories.

Installation ​

bash
npm install -D @vafast/cli

Commands ​

vafast sync - Sync API Types ​

Fetches the API contract from the server and generates a TypeScript type definition file.

Basic Usage ​

bash
# basic usage
npx vafast sync --url http://localhost:3000

# specify the output file
npx vafast sync --url http://localhost:3000 --out src/types/api.ts

# specify the contract endpoint
npx vafast sync --url http://localhost:3000 --endpoint /api-spec

# strip a path prefix
npx vafast sync --url http://localhost:3000 \
  --endpoint /api/api-spec \
  --out src/types/api/blog.generated.ts \
  --strip-prefix /api

Options ​

OptionDescriptionDefault
--url <url>Server URL (required)-
--out <path>Output file pathsrc/api.generated.ts
--endpoint <path>Contract endpoint path/__contract__
--strip-prefix <prefix>Strip a path prefix-

Workflow ​

1. Server Setup ​

Expose the contract endpoint on your vafast server:

typescript
import { Server, defineRoute, defineRoutes, getApiSpec } from 'vafast'

const routes = defineRoutes([
  defineRoute({
    method: 'GET',
    path: '/users',
    handler: getUsersHandler
  }),
  defineRoute({
    method: 'POST',
    path: '/users',
    handler: createUserHandler
  }),
])

// add the contract endpoint
const allRoutes = [
  ...routes,
  defineRoute({
    method: 'GET',
    path: '/api-spec',
    handler: getApiSpec  // use it directly as the handler
  })
]

const server = new Server(allRoutes)
export default { fetch: server.fetch }

2. Client Sync ​

bash
npx vafast sync --url http://localhost:3000 --endpoint /api-spec

3. Use the Generated Types ​

typescript
import { createClient } from '@vafast/api-client'
import { createApiClient } from './api.generated'

// create the underlying client
const client = createClient({
  baseURL: 'http://localhost:3000',
  timeout: 30000
})

// create the type-safe API client
const api = createApiClient(client)

// type-safe calls (wrong paths are caught by TypeScript)
const { data, error } = await api.users.get({ page: 1 })

// ❌ TypeScript reports an error
// api.nonExistent.get()  // Error: Property 'nonExistent' does not exist

Example of Generated Types ​

The file generated by the CLI contains:

typescript
import type { ApiResponse, RequestConfig, Client, EdenClient } from '@vafast/api-client'
import { eden } from '@vafast/api-client'

/** API contract type */
export type Api = {
  users: {
    get: {
      query: { page?: number }
      return: any
    }
    post: {
      body: { name?: string }
      return: any
    }
  }
}

/** API client type alias */
export type ApiClientType = EdenClient<Api>

/**
 * Create a type-safe API client
 */
export function createApiClient(client: Client): EdenClient<Api> {
  return eden<Api>(client)
}

Automation ​

Configure scripts in package.json:

json
{
  "scripts": {
    "sync:auth": "vafast sync --url http://localhost:9003 --endpoint /auth/api/api-spec --out src/types/api/auth.generated.ts --strip-prefix /auth/api",
    "sync:blog": "vafast sync --url http://localhost:9002 --endpoint /blog/api/api-spec --out src/types/api/blog.generated.ts --strip-prefix /blog/api",
    "sync:types": "npm run sync:auth && npm run sync:blog",
    "dev": "vite",
    "build": "npm run sync:types && vite build"
  }
}

Multi-Service Example ​

typescript
// src/utils/apiClients.ts
import { createClient } from '@vafast/api-client'
import { createApiClient as createAuthClient } from '~/types/api/auth.generated'
import { createApiClient as createBlogClient } from '~/types/api/blog.generated'

// shared config
const AUTH_API = { baseURL: '/auth/api', timeout: 30000 }
const BLOG_API = { baseURL: '/blog/api', timeout: 30000 }

// create the clients
const authClient = createClient(AUTH_API)
const blogClient = createClient(BLOG_API).use(appIdMiddleware)

// export the type-safe APIs
export const auth = createAuthClient(authClient)
export const blog = createBlogClient(blogClient)

// usage
const { data, error } = await blog.posts.find.post({ current: 1, pageSize: 10 })

Notes ​

  1. Return types: if the backend doesn't define a response schema, the generated return type is any (gradual type safety). Adding response schemas on the backend gives you full type checking.

  2. The server must be running: when running sync, the server must be up and exposing the contract endpoint.

  3. Don't edit by hand: the generated file gets overwritten, so don't modify it manually.

  4. Type safety: the generated createApiClient returns EdenClient<Api>, and TypeScript catches wrong API paths.