spac

TypeScript DSL for authoring OpenAPI 3.1+ specs. You write TypeScript, OpenAPI is the output.

$npm install @spec-spac/spac @sinclair/typebox

Merge-friendly by design

Each team owns a TypeScript module. Diffs are line-level — not indentation battles in a 10k-line YAML.

api.group('/pets', g => g.get('/').response(Pet))

Compile-time version safety

Api<'3.1'> in function signatures. assertVersion() catches team drift before CI even runs.

function registerPets(api: Api<'3.1'>) {}

Share macros, not copy-paste

Auth, audit headers, x- extensions — publish once, import everywhere.

import { authenticated } from '@co/macros'
Multi-team composition
index.ts
// Each team owns its own module — monorepo or multi-repo.
import { Api } from '@spec-spac/spac'
import { registerPets } from '@co/api-pets'
import { registerOrders } from '@co/api-orders'
import { registerUsers } from '@co/api-users'

const api = new Api('3.1', 'Company API', { versionPolicy: 'strict' })

registerPets(api)    // @co/api-pets
registerOrders(api)  // @co/api-orders
registerUsers(api)   // @co/api-users

api.versionAudit().compatible  // ← fails CI on drift
api.emit()                     // ← plain OpenAPI 3.1

Each team declares the OpenAPI version they authored against with assertVersion(). Drift is caught at compile time, audit time, or CI — not after deploy. Why spac →