@adhd/apigen-cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +890 -73
- package/README.md +371 -83
- package/index.js +96 -31
- package/index.mjs +12140 -4448
- package/lib/commands/generate-registry.d.ts +1 -1
- package/lib/commands/generate.d.ts +42 -1
- package/lib/commands/list-types.d.ts +11 -0
- package/lib/commands/run-registry.d.ts +1 -1
- package/lib/commands/run.d.ts +46 -1
- package/lib/commands/serve.d.ts +158 -0
- package/lib/import-source.d.ts +15 -4
- package/lib/logging.d.ts +1 -1
- package/lib/orchestrator.d.ts +76 -11
- package/lib/plugin-registry.d.ts +33 -0
- package/package.json +23 -9
- package/default-tsconfig.json +0 -10
- package/lib/pipeline.d.ts +0 -22
package/README.md
CHANGED
|
@@ -1,146 +1,434 @@
|
|
|
1
1
|
# @adhd/apigen-cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**MCP server, HTTP API (Fastify/Express), CLI, or JSON Schema**, with **zero changes to
|
|
5
|
-
the source**. Functions are the single source of truth; everything is derived.
|
|
3
|
+
**apigen: code-first, polyglot API generation.**
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Write ordinary functions in your language of choice. Get **type-safe, production-ready servers** in any protocol — MCP, HTTP (Fastify/Express), CLI, JSON Schema, Python Flask, or Python gRPC — with **zero framework boilerplate, zero annotations, and zero changes to your source**.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @adhd/apigen-cli --help
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What you can build
|
|
12
|
+
|
|
13
|
+
apigen turns your functions into running servers. Here's what that means in practice:
|
|
14
|
+
|
|
15
|
+
**A polyglot microservice from one command.** Mount TypeScript and Python files behind a single port — Fastify, Express, Flask, and gRPC all sharing one HTTP/1.1 + HTTP/2 endpoint, with per-namespace health and partial-availability isolation.
|
|
16
|
+
|
|
17
|
+
**An AI tool server from a single file.** Extract your business logic as an MCP server over stdio, SSE, or streaming HTTP — ready for any MCP host (Claude Desktop, Cursor, custom agents).
|
|
18
|
+
|
|
19
|
+
**Generated, deployable projects.** Emit a complete, runnable server project to disk, complete with `package.json`, `tsconfig.json`, and workspace-linked dependencies. The generated code is type-checked and imports only the dependencies your functions actually use.
|
|
20
|
+
|
|
21
|
+
**Cross-language type fidelity.** Types that normally break in JSON — `Date`, `bigint`, `Decimal`, `Uint8Array`, discriminated unions, nominal classes — survive the wire with exact precision, and they mean the *same bytes* whether the function runs in TypeScript or Python.
|
|
22
|
+
|
|
23
|
+
**Pluggable middleware via composition.** Add health endpoints, per-operation logging, auth, or rate-limiting with `--use` plugins — never by editing your business logic.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// This file. That's it. No decorators, no framework, no schema.
|
|
27
|
+
// apigen reads it and serves it as any protocol you ask for.
|
|
28
|
+
export async function greet(name: string): Promise<string> {
|
|
29
|
+
return `hello, ${name}`;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# → MCP server (AI agents consume your function as a tool)
|
|
35
|
+
npx @adhd/apigen-cli run --source greet.ts --type mcp
|
|
36
|
+
|
|
37
|
+
# → HTTP server on port 3000
|
|
38
|
+
npx @adhd/apigen-cli run --source greet.ts --type api-fastify --opt port=3000
|
|
39
|
+
|
|
40
|
+
# → CLI tool (interactive command from your function)
|
|
41
|
+
npx @adhd/apigen-cli run --source greet.ts --type cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The protocol is a deployment choice, never a rewrite.
|
|
10
45
|
|
|
11
46
|
---
|
|
12
47
|
|
|
13
|
-
## Install
|
|
48
|
+
## Install
|
|
14
49
|
|
|
15
|
-
|
|
50
|
+
### Via npm (published package)
|
|
16
51
|
|
|
17
52
|
```bash
|
|
18
|
-
|
|
53
|
+
npx @adhd/apigen-cli <command> # run without installing
|
|
54
|
+
npm install -g @adhd/apigen-cli # global install
|
|
55
|
+
apigen --help
|
|
56
|
+
```
|
|
19
57
|
|
|
20
|
-
|
|
21
|
-
alias apigen='npx tsx packages/apigen/cli/src/index.ts'
|
|
58
|
+
### From source (this monorepo)
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
```bash
|
|
24
61
|
npx nx build apigen-cli
|
|
25
|
-
|
|
62
|
+
alias apigen='node dist/entrypoint/apigen-cli/index.js'
|
|
26
63
|
```
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quickstart
|
|
31
68
|
|
|
32
|
-
|
|
33
|
-
used in the examples below.
|
|
69
|
+
### One function → one server → one curl
|
|
34
70
|
|
|
35
|
-
|
|
71
|
+
```bash
|
|
72
|
+
# Create your source
|
|
73
|
+
echo 'export async function greet(name: string): Promise<string> {
|
|
74
|
+
return `hello, ${name}`;
|
|
75
|
+
}' > hello.ts
|
|
76
|
+
|
|
77
|
+
# Serve it live
|
|
78
|
+
npx @adhd/apigen-cli run --source hello.ts --type api-fastify --opt port=8080
|
|
36
79
|
|
|
37
|
-
|
|
80
|
+
# Call it — arguments in {"data":{…}} envelope
|
|
81
|
+
curl -X POST http://localhost:8080/hello/greet \
|
|
82
|
+
-H 'content-type: application/json' \
|
|
83
|
+
-d '{"data":{"name":"ada"}}'
|
|
84
|
+
# → {"result":"hello, ada"}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### One source, every protocol
|
|
38
88
|
|
|
39
89
|
```bash
|
|
40
|
-
# MCP
|
|
41
|
-
apigen run --source
|
|
90
|
+
# MCP server (stdio — for AI agents)
|
|
91
|
+
npx @adhd/apigen-cli run --source hello.ts --type mcp
|
|
92
|
+
|
|
93
|
+
# MCP over SSE
|
|
94
|
+
npx @adhd/apigen-cli run --source hello.ts --type mcp --opt transport=sse --opt port=3000
|
|
42
95
|
|
|
43
|
-
#
|
|
44
|
-
apigen run --source
|
|
45
|
-
apigen run --source .../api.ts --type mcp --opt transport=streaming-http --opt port=3000
|
|
96
|
+
# HTTP (Express)
|
|
97
|
+
npx @adhd/apigen-cli run --source hello.ts --type api-express --opt port=3001
|
|
46
98
|
|
|
47
|
-
#
|
|
48
|
-
apigen run --source
|
|
49
|
-
|
|
99
|
+
# CLI tool (interactive)
|
|
100
|
+
npx @adhd/apigen-cli run --source hello.ts --type cli
|
|
101
|
+
|
|
102
|
+
# JSON Schema (generate to disk)
|
|
103
|
+
npx @adhd/apigen-cli generate --source hello.ts --type jsonschema --out-dir ./schema
|
|
50
104
|
```
|
|
51
105
|
|
|
52
|
-
|
|
53
|
-
`{"data":{…}}` (the request envelope), route is `/<namespace>/<fn>`:
|
|
106
|
+
### Multi-language, one port
|
|
54
107
|
|
|
55
108
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
# TypeScript + Python behind a single HTTP/gRPC front
|
|
110
|
+
npx @adhd/apigen-cli serve \
|
|
111
|
+
--source money.ts \
|
|
112
|
+
--source orders.py \
|
|
113
|
+
--port 8080
|
|
114
|
+
|
|
115
|
+
# Verify everything is healthy
|
|
116
|
+
curl http://localhost:8080/_meta/health
|
|
117
|
+
# → {"status":"ok","hosts":{"money":"ready","orders":"ready"}}
|
|
118
|
+
# A failed host degrades only its own namespace (503/UNAVAILABLE).
|
|
119
|
+
# The rest keep serving.
|
|
60
120
|
```
|
|
61
121
|
|
|
62
|
-
###
|
|
122
|
+
### With middleware
|
|
63
123
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
124
|
+
```bash
|
|
125
|
+
npx @adhd/apigen-cli run --source hello.ts --type api-fastify --use health --use logger
|
|
126
|
+
# health → GET /_meta/health (live health check)
|
|
127
|
+
# logger → per-operation structured logging to stderr
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## What you get: the generated server
|
|
133
|
+
|
|
134
|
+
When you `generate`, apigen emits a **real, runnable project**:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
./out/api/
|
|
138
|
+
├── package.json # only the deps your code actually uses
|
|
139
|
+
├── tsconfig.json # ready to compile
|
|
140
|
+
├── node_modules/ # linked (with --link-workspace) or via npm install
|
|
141
|
+
├── server.ts # the generated server entry point
|
|
142
|
+
└── routes.ts # generated route handlers
|
|
143
|
+
```
|
|
70
144
|
|
|
71
|
-
|
|
145
|
+
```bash
|
|
146
|
+
# Build output that runs standalone
|
|
147
|
+
npx @adhd/apigen-cli generate --source hello.ts --type api-fastify --out-dir ./out/api
|
|
148
|
+
cd ./out/api && npm install && npx tsx server.ts
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The generated server imports `dispatch`/`buildFnTable` from `@adhd/apigen-engine-runtime` — no inlined dispatch logic — so it behaves identically to `run`.
|
|
72
152
|
|
|
73
153
|
---
|
|
74
154
|
|
|
75
|
-
##
|
|
155
|
+
## Commands
|
|
156
|
+
|
|
157
|
+
### `apigen run` — Serve a source file as a live server
|
|
158
|
+
|
|
159
|
+
Start a live server from TypeScript or Python source. Functions are imported at runtime via `tsx` and handed to the selected plugin.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
apigen run --source <path> --type <plugin-id> [options]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
| Flag | Description |
|
|
166
|
+
|------|-------------|
|
|
167
|
+
| `--source <path>` | Source file (`.ts`, `.py`) |
|
|
168
|
+
| `--type <plugin-id>` | Output plugin — see [Plugins](#plugins) |
|
|
169
|
+
| `--export <mode>` | Export selection: `"default"` \| `"<name>"` \| omit for named |
|
|
170
|
+
| `--tsconfig <path>` | Explicit tsconfig.json |
|
|
171
|
+
| `--namespace <name>` | Route prefix (default: tsconfig folder name) |
|
|
172
|
+
| `--opt <key>=<value>` | Plugin option (repeatable) |
|
|
173
|
+
| `--use <plugin>` | Middleware plugin (repeatable) |
|
|
174
|
+
| `--config <path>` | Projection-override config file |
|
|
175
|
+
| `--v2` | Use v2 unified orchestrator |
|
|
176
|
+
|
|
177
|
+
The server handles `SIGINT`/`SIGTERM` gracefully.
|
|
178
|
+
|
|
179
|
+
### `apigen generate` — Generate server artifacts to disk
|
|
180
|
+
|
|
181
|
+
Extract TypeScript source and write generated files to an output directory. The output includes resolution scaffolding (`package.json`, `tsconfig.json`) so it runs standalone.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
apigen generate --source <path> --type <plugin-id> --out-dir <path> [options]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| Flag | Description |
|
|
188
|
+
|------|-------------|
|
|
189
|
+
| `--source <path>` | TypeScript source file |
|
|
190
|
+
| `--type <plugin-id>` | Output plugin |
|
|
191
|
+
| `--out-dir <path>` | Output directory |
|
|
192
|
+
| `--export <mode>` | Export selection |
|
|
193
|
+
| `--tsconfig <path>` | Explicit tsconfig.json |
|
|
194
|
+
| `--namespace <name>` | Package namespace |
|
|
195
|
+
| `--opt <key>=<value>` | Plugin option (repeatable) |
|
|
196
|
+
| `--use <plugin>` | Middleware plugin (repeatable) |
|
|
197
|
+
| `--config <path>` | Projection-override config file |
|
|
198
|
+
| `--link-workspace` | Emit workspace-linked `node_modules` (for monorepo dev before publish) |
|
|
199
|
+
| `--v2` | Use v2 unified orchestrator |
|
|
76
200
|
|
|
77
201
|
```bash
|
|
78
|
-
#
|
|
79
|
-
apigen generate --source
|
|
80
|
-
|
|
81
|
-
node /tmp/apigen-out/server.ts # or: npx tsx /tmp/apigen-out/server.ts
|
|
202
|
+
# Generate a Fastify HTTP server
|
|
203
|
+
apigen generate --source hello.ts --type api-fastify --out-dir ./out/http
|
|
204
|
+
cd ./out/http && npm install && npx tsx server.ts
|
|
82
205
|
|
|
83
|
-
|
|
84
|
-
apigen generate --source
|
|
85
|
-
|
|
206
|
+
# Generate an MCP server project with workspace linking
|
|
207
|
+
apigen generate --source hello.ts --type mcp --out-dir ./out/mcp --link-workspace
|
|
208
|
+
node ./out/mcp/server.ts
|
|
86
209
|
```
|
|
87
210
|
|
|
88
|
-
|
|
89
|
-
|
|
211
|
+
Per-surface dependencies are automatically patched into `package.json`: if your function uses `Decimal`, only then does `decimal.js` appear as a dependency.
|
|
212
|
+
|
|
213
|
+
### `apigen serve` — Multi-source, multi-language front server
|
|
214
|
+
|
|
215
|
+
Mount many source files across languages behind a single HTTP/1.1 + gRPC (HTTP/2) port. This is a small distributed system in one command: each source runs as a child process supervised with health probes and partial-availability isolation.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
apigen serve --source <path> [--source ...] --port <port> [--mount <ns>=<plugin> ...]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Flag | Description |
|
|
222
|
+
|------|-------------|
|
|
223
|
+
| `--source <path>` | Source to mount (repeatable; `.ts` → api-fastify, `.py` → py-flask) |
|
|
224
|
+
| `--port <port>` | Front TCP port (HTTP + gRPC multiplexed) |
|
|
225
|
+
| `--mount <ns>=<plugin>` | Pin namespace to specific plugin (repeatable; e.g. `ledger=py-grpc`) |
|
|
226
|
+
|
|
227
|
+
**Namespace by default** is the source filename stem. So `--source money.ts` creates namespace `money` served via `api-fastify`. Override with `--mount orders=api-express`.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Mix TypeScript and Python, four frameworks, one port
|
|
231
|
+
apigen serve \
|
|
232
|
+
--source money.ts \
|
|
233
|
+
--source orders.ts \
|
|
234
|
+
--source billing.py \
|
|
235
|
+
--source ledger.py \
|
|
236
|
+
--port 8080 \
|
|
237
|
+
--mount orders=api-express \
|
|
238
|
+
--mount ledger=py-grpc
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Architecture:**
|
|
242
|
+
- **Protocol demux** — A raw `net.Server` peeks the first 3 bytes. HTTP/2 prior-knowledge (h2c/gRPC) starts with `PRI`; everything else is HTTP/1.1. Both share one TCP port.
|
|
243
|
+
- **Spawn + supervise** — Each source starts as a child `apigen run` subprocess on a free loopback port. Children are tracked by PID.
|
|
244
|
+
- **Readiness probes** — HTTP hosts via `GET /_meta/health`, gRPC hosts via TCP connect. Both poll with 15s timeout.
|
|
245
|
+
- **Partial availability** — A dead child fails only its `/<ns>/*` routes (503 HTTP, UNAVAILABLE gRPC).
|
|
246
|
+
- **Orphan-free teardown** — `SIGINT`/`SIGTERM` sends `SIGTERM` then `SIGKILL` after 3s grace.
|
|
247
|
+
- **Python pre-provisioning** — The managed Python venv is set up before any child spawns, so first-time `pip install` doesn't eat the ready timeout.
|
|
248
|
+
|
|
249
|
+
### `apigen run-registry` — Multi-package live server
|
|
250
|
+
|
|
251
|
+
Discover packages by nx tag and wire them into one live server.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
apigen run-registry --packages-dir <path> --type <plugin-id> [options]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
All discovered packages are passed to `plugin.run()` in one invocation — one server, all namespaces.
|
|
258
|
+
|
|
259
|
+
### `apigen generate-registry` — Multi-package artifact generation
|
|
260
|
+
|
|
261
|
+
Discover packages by nx tag and generate artifacts for all in one pass.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
apigen generate-registry --packages-dir <path> --type <plugin-id> --out-dir <path> [options]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Plugins
|
|
270
|
+
|
|
271
|
+
| ID | Language | `run` | `generate` | What it builds |
|
|
272
|
+
|----|----------|-------|------------|----------------|
|
|
273
|
+
| `mcp` | TS | ✓ | ✓ | **MCP server** — model-context-protocol AI tool server. Transports: stdio, SSE, streaming-http |
|
|
274
|
+
| `jsonschema` | TS | | ✓ | **JSON Schema** — per-operation input/output schema files |
|
|
275
|
+
| `api-fastify` | TS | ✓ | ✓ | **Fastify HTTP server** — high-performance, plugin-based HTTP |
|
|
276
|
+
| `api-express` | TS | ✓ | ✓ | **Express HTTP server** — familiar Node.js HTTP framework |
|
|
277
|
+
| `cli` / `cli-output` | TS | ✓ | ✓ | **Interactive CLI tool** — your functions become command-line commands |
|
|
278
|
+
| `py-flask` | Py | ✓ | ✓ | **Python Flask server** — Python HTTP via the Flask framework |
|
|
279
|
+
| `py-grpc` | Py | ✓ | ✓ | **Python gRPC server** — HTTP/2 gRPC with protobuf wire format |
|
|
280
|
+
|
|
281
|
+
Non-TS plugins (`py-flask`, `py-grpc`) bypass TypeScript compilation — they spawn a Python subprocess. All plugins use the same canonical wire format, so a `Decimal` is the same JSON string from Fastify, Flask, or gRPC.
|
|
90
282
|
|
|
91
283
|
---
|
|
92
284
|
|
|
93
|
-
##
|
|
285
|
+
## Common flags
|
|
286
|
+
|
|
287
|
+
### `--opt <key>=<value>` — Plugin options
|
|
288
|
+
|
|
289
|
+
Passed through to the target plugin. Recognized keys:
|
|
290
|
+
|
|
291
|
+
| Key | Applies to | Default | Meaning |
|
|
292
|
+
|-----|------------|---------|---------|
|
|
293
|
+
| `transport` | `mcp` | `stdio` | `stdio` \| `sse` \| `streaming-http` |
|
|
294
|
+
| `port` | `mcp` (HTTP), `api-fastify`, `api-express`, `py-flask` | `3000` | Listen port |
|
|
295
|
+
| `host` | All HTTP transports | `127.0.0.1` | Bind address |
|
|
296
|
+
| `routePrefix` | `api-fastify`, `api-express` | `""` | Path prefix before `/<namespace>/<fn>` |
|
|
297
|
+
| `http.verb.<id>=GET/POST` | All HTTP plugins | (derived) | Override HTTP verb per operation |
|
|
298
|
+
|
|
299
|
+
MCP over `stdio` keeps **stdout** clean for JSON-RPC — all logs go to stderr.
|
|
94
300
|
|
|
95
|
-
|
|
301
|
+
### `--use <plugin>` — Middleware plugins
|
|
302
|
+
|
|
303
|
+
Compose cross-cutting behavior without editing your source code. Accepts:
|
|
304
|
+
|
|
305
|
+
- **Built-in slugs:** `health` — mounts `GET /_meta/health`; `logger` — per-operation structured logging
|
|
306
|
+
- **Package specifiers:** any npm package exporting a Plugin interface
|
|
307
|
+
- **Local paths:** filesystem path to a plugin module
|
|
96
308
|
|
|
97
309
|
```bash
|
|
98
|
-
apigen run
|
|
99
|
-
--tag api --type mcp
|
|
100
|
-
apigen generate-registry --packages-dir <dir> --tag api --type mcp --out-dir /tmp/reg-out
|
|
310
|
+
apigen run --source hello.ts --type api-fastify --use health --use logger
|
|
101
311
|
```
|
|
102
312
|
|
|
103
|
-
|
|
313
|
+
Multiple `--use` plugins compose in declaration order.
|
|
314
|
+
|
|
315
|
+
### `--export <mode>` — Export selection
|
|
316
|
+
|
|
317
|
+
Controls which exports become API operations:
|
|
318
|
+
|
|
319
|
+
| Value | Behavior |
|
|
320
|
+
|-------|----------|
|
|
321
|
+
| _(omit)_ | Named exports (`export function f`, `export const f = …`) |
|
|
322
|
+
| `default` | Default-exported function or object |
|
|
323
|
+
| `<name>` | A named object whose properties become operations |
|
|
324
|
+
|
|
325
|
+
> **v1 limitation:** `export { x as y }` aliases and anonymous defaults mis-name routes in v1 (named by declaration identifier). v2 names by exported symbol. Fixed by the `--v2` flag.
|
|
326
|
+
|
|
327
|
+
### `--v2` — Unified orchestrator
|
|
328
|
+
|
|
329
|
+
Activates the v2 detect→extract→merge→collision-check→generate/run pipeline. Uses a shared `ExtractionSession` (one ts-morph `Project` per tsconfig). Collision checks are a hard error (no silent last-writer-wins). v1 remains the default for backward compatibility.
|
|
330
|
+
|
|
331
|
+
### `--config <path>` — Projection-override file
|
|
332
|
+
|
|
333
|
+
JSON file for out-of-source HTTP verb and naming overrides (Tenet 1 — source is never modified). CLI `--opt` flags override file values.
|
|
334
|
+
|
|
335
|
+
### `--link-workspace` — Pre-publish bridge
|
|
336
|
+
|
|
337
|
+
When generating output that depends on `@adhd/apigen-*` packages not yet published, emits a workspace-linked `node_modules` so the output runs in place. For published consumers: omit this flag and run `npm install` instead.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Middleware system (plugins via `--use`)
|
|
342
|
+
|
|
343
|
+
apigen's architecture supports **layer and mount plugins** that compose at runtime:
|
|
344
|
+
|
|
345
|
+
- **Layer plugins** intercept the request/response cycle — validate, log, rate-limit, add auth context.
|
|
346
|
+
- **Mount plugins** add routes — health endpoints, metrics, status pages.
|
|
347
|
+
|
|
348
|
+
Built-in plugins:
|
|
349
|
+
|
|
350
|
+
| Plugin | Type | What it does |
|
|
351
|
+
|--------|------|-------------|
|
|
352
|
+
| `health` | Mount | Adds `GET /_meta/health` with aggregate status |
|
|
353
|
+
| `logger` | Layer | Per-operation pino logging to stderr |
|
|
354
|
+
|
|
355
|
+
Write your own by exporting a Plugin object (with `capabilities` field) from any npm package or local file.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Type safety and rich types
|
|
360
|
+
|
|
361
|
+
apigen infers JSON Schema from your TypeScript types. Types that plain JSON would mangle are carried through a canonical wire format:
|
|
362
|
+
|
|
363
|
+
| Type | Wire encoding | Survives? |
|
|
364
|
+
|------|--------------|-----------|
|
|
365
|
+
| `Date` | RFC3339 UTC string | ✓ |
|
|
366
|
+
| `bigint` / `int64` | Decimal string | ✓ (no precision loss past 2⁵³) |
|
|
367
|
+
| `Decimal` | Decimal string | ✓ (exact, never a binary float) |
|
|
368
|
+
| `Uint8Array` / bytes | Base64 | ✓ |
|
|
369
|
+
| Discriminated unions (`Dog \| Cat`) | Tagged variant | ✓ |
|
|
370
|
+
| Nominal classes | Reconstructed instance | ✓ |
|
|
371
|
+
| `readonly T[]` | Preserved element type | ✓ |
|
|
372
|
+
|
|
373
|
+
Cross-language fidelity: a `Decimal("123.456")` produces the same JSON string from TypeScript, Python Flask, and Python gRPC — verified by the conformance suite.
|
|
104
374
|
|
|
105
375
|
---
|
|
106
376
|
|
|
107
|
-
##
|
|
377
|
+
## Logging
|
|
108
378
|
|
|
109
|
-
|
|
110
|
-
- *(omit)* — named exports (`export function f`, `export const f = …`).
|
|
111
|
-
- `--export default` — a default-exported function/object.
|
|
112
|
-
- `--export <objName>` — a named object whose properties are the operations.
|
|
379
|
+
All logs go to **stderr** only — stdout is protocol-clean for MCP stdio transport.
|
|
113
380
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
381
|
+
| Flag | Env var | Values | Default |
|
|
382
|
+
|------|---------|--------|---------|
|
|
383
|
+
| `--log-level <level>` | `APIGEN_LOG_LEVEL` | `trace` \| `debug` \| `info` \| `warn` \| `error` \| `fatal` \| `silent` | `info` |
|
|
384
|
+
| `--log-format <format>` | `APIGEN_LOG_FORMAT` | `json` \| `pretty` | plain |
|
|
385
|
+
| `--log-file <path>` | `APIGEN_LOG_FILE` | file path | stderr |
|
|
118
386
|
|
|
119
|
-
|
|
120
|
-
- `--tsconfig <path>` — explicit tsconfig; otherwise the nearest one, else a builtin default.
|
|
121
|
-
- `--namespace <name>` — the package id / route prefix segment. Defaults to the tsconfig
|
|
122
|
-
folder name, falling back to the source file's folder.
|
|
387
|
+
---
|
|
123
388
|
|
|
124
|
-
|
|
125
|
-
- `--opt key=value` — repeatable; forwarded to the target plugin (see table above).
|
|
389
|
+
## Fail-fast guards
|
|
126
390
|
|
|
127
|
-
|
|
128
|
-
- `--log-level trace|debug|info|warn|error|fatal|silent` (env `APIGEN_LOG_LEVEL`)
|
|
129
|
-
- `--log-format json|pretty` (env `APIGEN_LOG_FORMAT`)
|
|
130
|
-
- `--log-file <path>` — write logs to a file instead of stderr (env `APIGEN_LOG_FILE`)
|
|
391
|
+
The CLI catches common mistakes early with actionable messages:
|
|
131
392
|
|
|
132
|
-
|
|
393
|
+
- **0 functions found:** `--source` points to generated output or a type-only file → `"0 functions found — looks like generated output or the wrong source file."`
|
|
394
|
+
- **Missing `decimal.js`:** Your function uses `Decimal` but the package isn't installed → `"function quote takes a Decimal; install \`decimal.js\`."`
|
|
395
|
+
- **Non-TS plugin bypass:** `--type py-flask` with `.py` source skips the TS pipeline (which would crash on a non-TS file)
|
|
133
396
|
|
|
134
397
|
---
|
|
135
398
|
|
|
136
399
|
## Nx integration
|
|
137
400
|
|
|
138
|
-
- Cache-aware generate target
|
|
139
|
-
- Scaffold a new output plugin
|
|
401
|
+
- **Cache-aware generate target:** `@adhd/apigen-generator-nx:generate` — run `apigen generate` as a cached Nx target. See [`packages/apigen/apigen-generator-nx`](../../packages/apigen/apigen-generator-nx).
|
|
402
|
+
- **Scaffold a new output plugin:** `nx g @adhd/apigen-generator-nx:plugin <name>` — generates a buildable plugin package implementing the `OutputPlugin` interface. Use this to add a new codegen target (e.g., `nestjs`, `hono`, `spring`) without forking apigen.
|
|
403
|
+
- **Scaffold a new host language:** `nx g @adhd/apigen-generator-nx:host <name>` — generates a host-language harness with a "red-by-construction" conformance manifest. Use this to add a new runtime (e.g., Rust, Go, Java) to the polyglot serve topology.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Architecture
|
|
408
|
+
|
|
409
|
+
apigen works in three stages:
|
|
410
|
+
|
|
411
|
+
1. **Extract** — Parse your source (TypeScript via ts-morph), infer JSON Schema for every parameter and return type, detect rich types (Date, Decimal, bigint, etc.).
|
|
412
|
+
2. **Compose** — Merge schemas across packages, run collision checks (no silent name shadowing).
|
|
413
|
+
3. **Project** — Hand the composed descriptor to the selected output plugin for live serving or code generation.
|
|
140
414
|
|
|
141
|
-
|
|
415
|
+
The v2 architecture (`docs/apigen/SPEC.md`) extends this to a polyglot detect → extract → merge → project pipeline with a canonical JSON descriptor as the neutral contract. Python plugins already ship; Rust/Go/Java host languages are designed in the SPEC.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Developing
|
|
142
420
|
|
|
143
421
|
```bash
|
|
144
|
-
npx nx build apigen-cli
|
|
145
|
-
npx nx test
|
|
422
|
+
npx nx build apigen-cli # Bundle (Vite)
|
|
423
|
+
npx nx test apigen-cli # Test (Vitest — unit + integration)
|
|
424
|
+
npx nx run apigen-cli:bench # Benchmarks (--expose-gc)
|
|
146
425
|
```
|
|
426
|
+
|
|
427
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Spec
|
|
432
|
+
|
|
433
|
+
- **v2 Canonical Spec:** [`docs/apigen/SPEC.md`](../../docs/apigen/SPEC.md) — architecture, canonical descriptor, polyglot topology
|
|
434
|
+
- **Guided Tour:** [`docs/apigen/DEMO.md`](../../docs/apigen/DEMO.md) — runnable walk-through from zero
|