Skip to content

Commit cf760e5

Browse files
committed
quick
1 parent dfdfc05 commit cf760e5

File tree

8 files changed

+335
-48
lines changed

8 files changed

+335
-48
lines changed

README.md

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
TypeScript DSL for authoring OpenAPI 3.1+ specs. Uses [TypeBox](https://github.com/sinclairzx81/typebox) for JSON Schema with full type inference — you write TypeScript, not YAML.
44

55
[![CI](https://github.com/rbbydotdev/spac/actions/workflows/ci.yml/badge.svg)](https://github.com/rbbydotdev/spac/actions/workflows/ci.yml)
6-
[![npm](https://img.shields.io/npm/v/spac.svg)](https://www.npmjs.com/package/spac)
6+
[![npm](https://img.shields.io/npm/v/@spec-spac/spac.svg)](https://www.npmjs.com/package/@spec-spac/spac)
77
[![TypeScript 5.7+](https://img.shields.io/badge/TypeScript-5.7+-3178c6.svg)](https://www.typescriptlang.org/)
88

99
**[Docs](https://rbbydotdev.github.io/spac/)** · **[Playground](https://rbbydotdev.github.io/spac/playground)** · **[Getting Started](https://rbbydotdev.github.io/spac/docs/tutorials/getting-started)** · **[API Reference](https://rbbydotdev.github.io/spac/docs/reference/api)**
@@ -28,32 +28,114 @@ const spec = api.emit() // valid OpenAPI 3.1 JSON
2828

2929
Named schemas automatically hoist to `components.schemas` as `$ref`s. Groups inherit tags and security. Macros let you compose reusable route/group patterns.
3030

31-
## Install
31+
## Quick start
32+
33+
### 1. Install
3234

3335
```sh
3436
npm install @spec-spac/spac @sinclair/typebox
3537
```
3638

39+
### 2. Define your API
40+
41+
Create `api.ts`:
42+
43+
```ts
44+
import { Api, named } from '@spec-spac/spac'
45+
import { Type } from '@sinclair/typebox'
46+
47+
const User = named('User', Type.Object({
48+
id: Type.String(),
49+
name: Type.String(),
50+
email: Type.String({ format: 'email' }),
51+
}))
52+
53+
const api = new Api('3.1', 'My API', { version: '1.0.0' })
54+
55+
api.group('/users', g => {
56+
g.tag('users')
57+
g.get('/').response(Type.Array(User)).summary('List users')
58+
g.get('/:id').params(Type.Object({ id: Type.String() })).response(User)
59+
g.post('/').body(User).response(User).summary('Create user')
60+
})
61+
62+
console.log(JSON.stringify(api.emit(), null, 2))
63+
```
64+
65+
### 3. Run it
66+
67+
```sh
68+
npx tsx api.ts > openapi.json
69+
```
70+
71+
That's it — `openapi.json` is a valid OpenAPI 3.1 document with `User` hoisted to `components.schemas`.
72+
3773
## Packages
3874

39-
| Package | Path | Description |
75+
| Package | Install | What it does |
4076
|---|---|---|
41-
| [`@spec-spac/spac`](packages/spac) | Core library | Define routes, groups, schemas, and emit OpenAPI 3.1 JSON/YAML with source maps |
42-
| [`@spec-spac/from-openapi`](packages/from-openapi) | Code generator | Parse an existing OpenAPI spec and emit idiomatic spac TypeScript |
43-
| [`@spec-spac/from-openapi-biome`](packages/from-openapi/plugins/biome) | Formatter | Biome formatter adapter for from-openapi |
44-
| [`@spec-spac/from-openapi-prettier`](packages/from-openapi/plugins/prettier) | Formatter | Prettier formatter adapter for from-openapi |
45-
| [`spac-playground`](packages/playground) | App | Split-pane CodeMirror viewer — SPAC source on the left, OpenAPI YAML on the right |
46-
| [`spac-vscode`](packages/spac-vscode) | Extension | VS Code extension for live OpenAPI preview |
77+
| [`@spec-spac/spac`](packages/spac) | `npm i @spec-spac/spac` | Core library — define routes, groups, schemas, emit OpenAPI 3.1 JSON/YAML with source maps |
78+
| [`@spec-spac/from-openapi`](packages/from-openapi) | `npm i -g @spec-spac/from-openapi` | CLI + library — reverse-generate spac TypeScript from an existing OpenAPI spec |
79+
| [`@spec-spac/from-openapi-biome`](packages/from-openapi/plugins/biome) | `npm i @spec-spac/from-openapi-biome` | Biome formatter plugin for the code generator |
80+
| [`@spec-spac/from-openapi-prettier`](packages/from-openapi/plugins/prettier) | `npm i @spec-spac/from-openapi-prettier` | Prettier formatter plugin for the code generator |
4781

48-
## Quick start
82+
## `spac-from-openapi` CLI
83+
84+
Already have an OpenAPI spec? Generate spac TypeScript from it:
85+
86+
```sh
87+
# Preview what will be generated (dry run)
88+
npx @spec-spac/from-openapi petstore.json
4989

50-
### Define routes and groups
90+
# Generate to a directory
91+
npx spac-from-openapi petstore.json --out ./generated
92+
93+
# Strip path prefixes for cleaner grouping
94+
npx spac-from-openapi cloudflare.json --out ./generated \
95+
--strip '/accounts/{account_id}' \
96+
--strip '/zones/{zone_id}'
97+
```
98+
99+
| Flag | Description |
100+
|------|-------------|
101+
| `--out <dir>` | Output directory (omit for dry-run) |
102+
| `--strip <prefix>` | Path prefix to strip before grouping (repeatable) |
103+
| `--name <name>` | Override API title |
104+
| `--spec-version <ver>` | Override OpenAPI version |
105+
| `--debug` | Enable source map support in generated code |
106+
107+
Output structure:
108+
109+
```
110+
generated/
111+
index.ts — Api setup, imports all groups
112+
shared/schemas.ts — Schemas used by 2+ groups
113+
<group>/index.ts — Routes for that group
114+
<group>/schemas.ts — Schemas local to that group
115+
```
116+
117+
## Core concepts
118+
119+
### Routes and chaining
120+
121+
Every HTTP method returns a builder — all configuration is chained:
51122

52123
```ts
53-
const api = new Api('3.1', 'My API')
124+
api.get('/users/:id')
125+
.params(Type.Object({ id: Type.String() }))
126+
.query(Type.Object({ fields: Type.Optional(Type.String()) }))
127+
.response(User)
128+
.error(404, ErrorSchema)
129+
.summary('Get a user by ID')
130+
.operationId('getUser')
131+
.tag('users')
132+
```
54133

55-
api.get('/health').response(Type.Object({ ok: Type.Boolean() }))
134+
### Groups
56135

136+
Groups collect routes under a shared prefix with inherited metadata:
137+
138+
```ts
57139
api.group('/users', g => {
58140
g.tag('users')
59141
g.security('bearer')
@@ -65,11 +147,10 @@ api.group('/users', g => {
65147

66148
### Named schemas
67149

150+
Wrap any TypeBox schema with `named()` to hoist it to `components.schemas`:
151+
68152
```ts
69-
const Pet = named('Pet', Type.Object({
70-
id: Type.String(),
71-
name: Type.String(),
72-
}))
153+
const Pet = named('Pet', Type.Object({ id: Type.String(), name: Type.String() }))
73154
// Any route referencing Pet emits { "$ref": "#/components/schemas/Pet" }
74155
```
75156

@@ -87,6 +168,8 @@ api.post('/items')
87168

88169
### Macros
89170

171+
Reusable transforms applied via `.use()`:
172+
90173
```ts
91174
import { macro } from '@spec-spac/spac'
92175

@@ -99,16 +182,9 @@ api.group('/admin', g => {
99182
})
100183
```
101184

102-
### Generate spac code from an existing OpenAPI spec
103-
104-
```sh
105-
npx spac-from-openapi spec.json --out ./generated
106-
npx spac-from-openapi spec.json --out ./generated --strip '/accounts/{account_id}'
107-
```
108-
109-
See [`@spec-spac/from-openapi` README](packages/from-openapi) for full CLI options.
185+
### Source maps
110186

111-
### Source mapping
187+
Map every line of emitted YAML back to the TypeScript that produced it:
112188

113189
```ts
114190
const api = new Api('3.1', 'Petstore', { version: '1.0.0', debug: true })
@@ -120,7 +196,7 @@ result.sourceMap // standard Source Map V3
120196
result.sourceTable // Map<jsonPath, SourceEntry>
121197
```
122198

123-
## Api reference
199+
## API reference
124200

125201
| Method | Description |
126202
|---|---|
@@ -140,14 +216,6 @@ result.sourceTable // Map<jsonPath, SourceEntry>
140216

141217
`.params()` `.query()` `.headers()` `.body()` `.response()` `.respond()` `.error()` `.summary()` `.description()` `.tag()` `.operationId()` `.deprecated()` `.security()` `.server()` `.extension()` `.use()`
142218

143-
## Architecture
144-
145-
```
146-
User code -> Api / GroupBuilder / RouteBuilder (AST) -> emitOpenApi() -> OpenAPI 3.1 JSON / YAML + Source Map V3
147-
```
148-
149-
The DSL builds an AST of `RouteNode` and `GroupNode` objects. `emitOpenApi()` walks the tree, flattens routes with inherited group metadata, resolves TypeBox schemas (hoisting named schemas to `components.schemas` as `$ref`s), and produces a spec-compliant OpenAPI 3.1 document.
150-
151219
## Contributing
152220

153221
See [INDEX.md](INDEX.md) for the full repo layout, build instructions, and the examples branch workflow.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"scripts": {
55
"format": "biome format --write packages/spac packages/from-openapi packages/examples packages/playground/scripts",
66
"format:check": "biome format packages/spac packages/from-openapi packages/examples packages/playground/scripts",
7-
"sync-examples": "bash scripts/sync-examples.sh"
7+
"sync-examples": "bash scripts/sync-examples.sh",
8+
"publish-pkg": "bash scripts/publish.sh"
89
},
910
"devDependencies": {
1011
"@biomejs/biome": "^2.4.7"

packages/from-openapi/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@spec-spac/from-openapi",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"type": "module",
55
"description": "Generate idiomatic spac TypeScript from existing OpenAPI specs",
66
"license": "MIT",

packages/spac/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@spec-spac/spac",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"type": "module",
55
"description": "TypeScript DSL for authoring OpenAPI 3.1+ specs with TypeBox schemas",
66
"license": "MIT",

packages/spac/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"rootDir": "src",
55
"outDir": "dist"
66
},
7-
"include": ["src"]
7+
"include": ["src"],
8+
"exclude": ["src/__tests__", "src/**/*.test.ts"]
89
}

packages/website/content/docs/index.mdx

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,97 @@ description: TypeScript DSL for authoring OpenAPI 3.1+ specs
88
```spac
99
import { Api, named } from '@spec-spac/spac'
1010
import { Type } from '@sinclair/typebox'
11-
1211
const Pet = named('Pet', Type.Object({
1312
id: Type.String(),
1413
name: Type.String(),
1514
}))
16-
1715
const api = new Api('3.1', 'Petstore')
1816
api.group('/pets', g => {
1917
g.get('/').response(Type.Array(Pet)).tag('pets')
2018
g.post('/').body(Pet).response(Pet).tag('pets')
2119
})
22-
2320
export default api
2421
```
2522

23+
## Install
24+
25+
```bash
26+
npm install @spec-spac/spac @sinclair/typebox
27+
```
28+
29+
## Packages
30+
31+
| Package | What it does |
32+
|---|---|
33+
| `@spec-spac/spac` | Core library — define routes, groups, schemas, emit OpenAPI 3.1 JSON/YAML with source maps |
34+
| `@spec-spac/from-openapi` | CLI + library — reverse-generate spac TypeScript from an existing OpenAPI spec |
35+
| `@spec-spac/from-openapi-biome` | Biome formatter plugin for the code generator |
36+
| `@spec-spac/from-openapi-prettier` | Prettier formatter plugin for the code generator |
37+
2638
## Why spac?
2739

2840
- **TypeBox schemas directly** — no wrapper DSL, full JSON Schema type inference
2941
- **Named schemas auto-hoist** to `components.schemas` as `$ref`
3042
- **Group inheritance** — tags and security cascade from groups to routes
3143
- **Macro system** — reusable route/group/api transforms
3244
- **Source maps** — trace emitted YAML back to TypeScript source lines
45+
- **Reverse generator** — already have an OpenAPI spec? Generate spac code from it with [`spac-from-openapi`](/docs/guides/from-openapi-cli)
3346

34-
## Documentation
47+
## Quick start
48+
49+
Create `api.ts`:
50+
51+
```ts
52+
import { Api, named } from '@spec-spac/spac'
53+
import { Type } from '@sinclair/typebox'
54+
55+
const User = named('User', Type.Object({
56+
id: Type.String(),
57+
name: Type.String(),
58+
email: Type.String({ format: 'email' }),
59+
}))
60+
61+
const api = new Api('3.1', 'My API', { version: '1.0.0' })
3562

36-
This documentation follows the [Diataxis](https://diataxis.fr/) framework:
63+
api.group('/users', g => {
64+
g.tag('users')
65+
g.get('/').response(Type.Array(User)).summary('List users')
66+
g.post('/').body(User).response(User).summary('Create user')
67+
})
68+
69+
console.log(JSON.stringify(api.emit(), null, 2))
70+
```
71+
72+
Run it:
73+
74+
```bash
75+
npx tsx api.ts > openapi.json
76+
```
77+
78+
That's it — `openapi.json` is a valid OpenAPI 3.1 document.
79+
80+
## `spac-from-openapi` CLI
81+
82+
Already have an OpenAPI spec? Generate spac TypeScript from it:
83+
84+
```bash
85+
npx spac-from-openapi petstore.json --out ./generated
86+
```
87+
88+
Use `--strip` to clean up deeply nested paths:
89+
90+
```bash
91+
npx spac-from-openapi cloudflare.json --out ./generated \
92+
--strip '/accounts/{account_id}'
93+
```
94+
95+
See the full [CLI reference](/docs/guides/from-openapi-cli) for all options.
96+
97+
## Documentation
3798

3899
<Cards>
39-
<Card title="Tutorials" href="/docs/tutorials/getting-started" description="Learning-oriented walkthroughs to get you started" />
40-
<Card title="Guides" href="/docs/guides/defining-routes" description="Task-oriented how-to guides for specific goals" />
41-
<Card title="Explanation" href="/docs/explanation/philosophy" description="Understanding-oriented discussion of concepts and design" />
42-
<Card title="Reference" href="/docs/reference/api" description="Technical API reference extracted from source" />
100+
<Card title="Getting Started" href="/docs/tutorials/getting-started" description="Build your first OpenAPI spec with spac in 5 minutes" />
101+
<Card title="Defining Routes" href="/docs/guides/defining-routes" description="Routes, groups, macros, and response helpers" />
102+
<Card title="from-openapi CLI" href="/docs/guides/from-openapi-cli" description="Generate spac code from existing OpenAPI specs" />
103+
<Card title="API Reference" href="/docs/reference/api" description="Full type reference for the Api class" />
43104
</Cards>

scripts/PUBLISHING.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Publishing to npm
2+
3+
## Quick start
4+
5+
```bash
6+
pnpm publish-pkg <package> <patch|minor|major>
7+
```
8+
9+
## Packages
10+
11+
| Shorthand | npm package |
12+
|-----------|-------------|
13+
| `spac` | `@spec-spac/spac` |
14+
| `from-openapi` | `@spec-spac/from-openapi` |
15+
| `biome` | `@spec-spac/from-openapi-biome` |
16+
| `prettier` | `@spec-spac/from-openapi-prettier` |
17+
| `all` | All four, in dependency order |
18+
19+
## Examples
20+
21+
```bash
22+
pnpm publish-pkg spac patch # 0.0.2 → 0.0.3
23+
pnpm publish-pkg spac minor # 0.1.0
24+
pnpm publish-pkg from-openapi patch
25+
pnpm publish-pkg all patch # bump + publish everything
26+
```
27+
28+
The script builds before publishing, so you don't need to run `build` separately.
29+
30+
## Auth
31+
32+
**Option 1 — npm login (interactive):**
33+
34+
```bash
35+
npm login
36+
```
37+
38+
**Option 2 — API token (non-interactive):**
39+
40+
Generate a token at https://www.npmjs.com/settings/~/tokens and set it:
41+
42+
```bash
43+
export NPM_TOKEN=npm_xxxxxxxxxxxx
44+
```
45+
46+
Add to your shell profile (`~/.zshrc`) to persist across sessions. The publish script picks it up automatically.
47+
48+
## After publishing
49+
50+
The script bumps `package.json` versions but doesn't commit. After publishing:
51+
52+
```bash
53+
git add -A && git commit -m "release: v0.1.0" && git push
54+
```

0 commit comments

Comments
 (0)