spac
TypeScript DSL for authoring OpenAPI 3.1+ specs
spac is a TypeScript DSL for authoring OpenAPI 3.1+ specs. You write TypeScript — OpenAPI is the output.
import { Api, named } from '@spec-spac/spac'
import { Type } from '@sinclair/typebox'
const Pet = named('Pet', Type.Object({
id: Type.String(),
name: Type.String(),
}))
const api = new Api('3.1', 'Petstore')
api.group('/pets', g => {
g.get('/').response(Type.Array(Pet)).tag('pets')
g.post('/').body(Pet).response(Pet).tag('pets')
})
export default apiopenapi: 3.1.2
info:
title: Petstore
version: 1.0.0
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
paths:
/pets:
get:
tags:
- pets
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
post:
tags:
- pets
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: stringInstall
npm install @spec-spac/spac @sinclair/typeboxpnpm add @spec-spac/spac @sinclair/typeboxbun add @spec-spac/spac @sinclair/typeboxPackages
| Package | What it does |
|---|---|
@spec-spac/spac | Core library — define routes, groups, schemas, emit OpenAPI 3.1 JSON/YAML with source maps |
@spec-spac/from-openapi | CLI + library — reverse-generate spac TypeScript from an existing OpenAPI spec |
@spec-spac/from-openapi-biome | Biome formatter plugin for the code generator |
@spec-spac/from-openapi-prettier | Prettier formatter plugin for the code generator |
Why spac?
- TypeBox schemas directly — no wrapper DSL, full JSON Schema type inference
- Named schemas auto-hoist to
components.schemasas$ref - Group inheritance — tags and security cascade from groups to routes
- Macro system — reusable route/group/api transforms
- Source maps — trace emitted YAML back to TypeScript source lines
- Reverse generator — already have an OpenAPI spec? Generate spac code from it with
spac-from-openapi
Quick start
Create api.ts:
import { Api, named } from '@spec-spac/spac'
import { Type } from '@sinclair/typebox'
const User = named('User', Type.Object({
id: Type.String(),
name: Type.String(),
email: Type.String({ format: 'email' }),
}))
const api = new Api('3.1', 'My API', { version: '1.0.0' })
api.group('/users', g => {
g.tag('users')
g.get('/').response(Type.Array(User)).summary('List users')
g.post('/').body(User).response(User).summary('Create user')
})
console.log(JSON.stringify(api.emit(), null, 2))Run it:
npx tsx api.ts > openapi.jsonpnpm dlx tsx api.ts > openapi.jsonbunx tsx api.ts > openapi.jsonThat's it — openapi.json is a valid OpenAPI 3.1 document.
spac-from-openapi CLI
Already have an OpenAPI spec? Generate spac TypeScript from it:
npx spac-from-openapi petstore.json --out ./generatedpnpm dlx spac-from-openapi petstore.json --out ./generatedbunx spac-from-openapi petstore.json --out ./generatedUse --strip to clean up deeply nested paths:
spac-from-openapi cloudflare.json --out ./generated \
--strip '/accounts/{account_id}'See the full CLI reference for all options.