@adhd/apigen-core-client 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 +2 -5
- package/README.md +259 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
## 0.1.1 (2026-07-23)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
3
|
+
This was a version bump only for apigen-core-client to align it with other projects, there were no code changes.
|
|
7
4
|
|
|
8
5
|
## [Unreleased]
|
|
9
6
|
|
package/README.md
CHANGED
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
# @adhd/apigen-core-client
|
|
2
2
|
|
|
3
|
-
> The core apigen engine — TypeScript source → JSON Schema extraction, composition,
|
|
3
|
+
> The core apigen engine — TypeScript source → JSON Schema extraction, composition, the plugin contract, and the programmatic API for running live servers without code generation.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
`@adhd/apigen-core-client` reads TypeScript source files, derives JSON Schemas for each exported function's parameters and return type, and composes them with middleware-contributed envelope fields. It defines the **v1 `OutputPlugin`** contract for code generation and the **v2 `Plugin`** capability interface (target / layer / mount / envelope) for the full plugin lifecycle. Pure TypeScript — designed for Node and the browser, though ts-morph's type resolution is Node-only. Runtime dependencies: ts-morph, ts-json-schema-generator, pino, typescript, decimal.js, and @adhd/apigen-base-logical.
|
|
5
|
+
`@adhd/apigen-core-client` reads TypeScript source files, derives JSON Schemas for each exported function's parameters and return type, composes them with middleware-contributed envelope fields, and provides the `OutputPlugin` contract that every plugin implements. It's the library you import when you want to **use apigen programmatically** — start MCP servers, Fastify HTTP services, or CLI tools directly from your Node.js code, without the CLI.
|
|
8
6
|
|
|
9
7
|
Part of [apigen](../README.md). See the [apigen spec](../../../docs/apigen/SPEC.md) for the full architecture.
|
|
10
8
|
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Two paths: CLI or library
|
|
12
|
+
|
|
13
|
+
apigen serves two audiences through the same engine. Which one are you?
|
|
14
|
+
|
|
15
|
+
| You want to… | Use the CLI | Use this library |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Start a server with one command, no code | `npx @adhd/apigen-cli run --source ./api.ts --type mcp --opt transport=sse` | — |
|
|
18
|
+
| Generate a deployable project to disk | `npx @adhd/apigen-cli generate --source ./api.ts --type api-fastify --out-dir ./out` | — |
|
|
19
|
+
| Embed a live server in your own Node.js process | — | `mcpPlugin.run({…})` from `@adhd/apigen-plugin-mcp` |
|
|
20
|
+
| Build a custom deploy pipeline with lifecycles | — | `extract()` → `composeSchemas()` → `plugin.run({ signal })` |
|
|
21
|
+
| Create a custom output plugin | Combined CLI + library | `OutputPlugin` / `Plugin` interface |
|
|
22
|
+
| Serve a multi-language polyglot front | `npx @adhd/apigen-cli serve --source ./api.ts --source ./api.py --port 8080` | — (this is a CLI-only orchestration) |
|
|
23
|
+
|
|
24
|
+
**The CLI is a thin wrapper** — every `apigen run --source ./api.ts --type mcp` call internally does exactly what you'd write in five lines of library code:
|
|
25
|
+
|
|
26
|
+
| CLI command | Library equivalent |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `apigen run --source ./api.ts --type mcp` | `mcpPlugin.run({ packages: [{ id, schemas, importPath: './api.ts', fns: mod }] })` |
|
|
29
|
+
| `apigen run --source ./api.ts --type api-fastify --use health --use logger` | `apiFastifyPlugin.run({ packages: [...], options: { usePlugins: [healthPlugin, loggerPlugin] } })` |
|
|
30
|
+
| `apigen run --source ./api.ts --type mcp --opt transport=sse --opt port=3100` | `mcpPlugin.run({ packages: [...], options: { transport: 'sse', port: 3100 } })` |
|
|
31
|
+
| `apigen generate --source ./api.ts --type jsonschema --out-dir ./schema` | `jsonschemaPlugin.generate({ packages: [...], outputDir: './schema' })` |
|
|
32
|
+
|
|
33
|
+
**The rest of this document focuses on the library path.** See the [CLI README](../../../entrypoint/apigen-cli/README.md) for CLI usage, or the [How-To: Running Servers](./docs/how-to/running-servers.md) for a step-by-step walkthrough.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
11
37
|
## Install
|
|
12
38
|
|
|
13
39
|
```bash
|
|
14
40
|
npm install @adhd/apigen-core-client
|
|
41
|
+
# Plus the plugins you want to run:
|
|
42
|
+
npm install @adhd/apigen-plugin-mcp @adhd/apigen-plugin-api-fastify @adhd/apigen-plugin-openapi
|
|
15
43
|
```
|
|
16
44
|
|
|
45
|
+
All packages are published to npm with `access: public`.
|
|
46
|
+
|
|
17
47
|
## Quickstart
|
|
18
48
|
|
|
19
49
|
Extract canonical operation descriptors from a TypeScript file, then compose them with middleware envelope fields:
|
|
@@ -84,6 +114,229 @@ sourcesForPlugin({ language: 'ts' }, ['src/api.ts', 'src/api.py', 'README.md']);
|
|
|
84
114
|
// => ['src/api.ts']
|
|
85
115
|
```
|
|
86
116
|
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Run Servers Programmatically
|
|
120
|
+
|
|
121
|
+
The core use case for the library path: **extract operations from your TypeScript source, then start a live server — no code generation, no CLI.**
|
|
122
|
+
|
|
123
|
+
Every plugin with a `run()` method receives a `RunInput` with three things: the composed schemas (for routing and validation), the live function references (your actual module imports), and an `AbortSignal` for graceful shutdown. The `run()` starts the server and returns a `Promise<void>` that resolves when the signal fires.
|
|
124
|
+
|
|
125
|
+
### Start an MCP server (SSE transport)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { extract, composeSchemas } from '@adhd/apigen-core-client';
|
|
129
|
+
import { mcpPlugin } from '@adhd/apigen-plugin-mcp';
|
|
130
|
+
|
|
131
|
+
// A function — no framework imports, no decorators, no annotations
|
|
132
|
+
// (this lives in, say, services/api.ts)
|
|
133
|
+
export async function greet(name: string): Promise<string> {
|
|
134
|
+
return `hello, ${name}!`;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { extract, composeSchemas } from '@adhd/apigen-core-client';
|
|
140
|
+
import { mcpPlugin } from '@adhd/apigen-plugin-mcp';
|
|
141
|
+
|
|
142
|
+
async function startMcp(sourceFile: string) {
|
|
143
|
+
// 1. Extract operations from your TypeScript source
|
|
144
|
+
const ops = await extract({ sourceFile, namespace: 'demo' });
|
|
145
|
+
|
|
146
|
+
// 2. Build generated schemas (one per exported function)
|
|
147
|
+
const generated = {
|
|
148
|
+
metadata: { namespace: 'demo', phase: '' },
|
|
149
|
+
schemas: Object.fromEntries(
|
|
150
|
+
ops.filter(o => o.kind === 'action').map(op => [
|
|
151
|
+
op.path[op.path.length - 1].raw,
|
|
152
|
+
{ input: op.input, output: op.output, hasCtx: op.hasCtx, 'x-apigen-safe': op.safe },
|
|
153
|
+
])
|
|
154
|
+
),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 3. Compose with middleware envelope (empty for this example)
|
|
158
|
+
const schemas = composeSchemas(generated, []);
|
|
159
|
+
|
|
160
|
+
// 4. Import the source to get live function references
|
|
161
|
+
const mod = await import(sourceFile);
|
|
162
|
+
|
|
163
|
+
// 5. Start the MCP server — no codegen, live dispatch
|
|
164
|
+
const abort = new AbortController();
|
|
165
|
+
process.on('SIGINT', () => abort.abort());
|
|
166
|
+
|
|
167
|
+
await mcpPlugin.run!({
|
|
168
|
+
packages: [{
|
|
169
|
+
id: 'demo',
|
|
170
|
+
schemas,
|
|
171
|
+
importPath: sourceFile,
|
|
172
|
+
fns: mod, // your actual exported functions
|
|
173
|
+
createClient: async () => ({}),
|
|
174
|
+
}],
|
|
175
|
+
outputDir: '',
|
|
176
|
+
options: {
|
|
177
|
+
transport: 'sse', // stdio | sse | streaming-http
|
|
178
|
+
port: 3100,
|
|
179
|
+
host: '0.0.0.0',
|
|
180
|
+
// Override tool descriptions per function (optional):
|
|
181
|
+
// toolDescriptions: { greet: 'Say hello to a user' },
|
|
182
|
+
},
|
|
183
|
+
signal: abort.signal,
|
|
184
|
+
operations: ops, // needed for --use mount plugins
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The MCP server is now live at `http://localhost:3100/sse` with `POST /messages?sessionId=...`. Every exported function in your source is an MCP tool — callable from Claude Desktop, Cursor, or any MCP host. **Equivalent CLI command:** `apigen run --source <file> --type mcp --opt transport=sse --opt port=3100`
|
|
190
|
+
|
|
191
|
+
### Start a Fastify HTTP server (with OpenAPI docs)
|
|
192
|
+
|
|
193
|
+
Add the OpenAPI plugin as a `--use` mount plugin — it contributes a `GET /_meta/openapi` endpoint that serves a live OpenAPI 3.1 document derived from your operations at request time.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { extract, composeSchemas } from '@adhd/apigen-core-client';
|
|
197
|
+
import { apiFastifyPlugin } from '@adhd/apigen-plugin-api-fastify';
|
|
198
|
+
import { openapiPlugin } from '@adhd/apigen-plugin-openapi';
|
|
199
|
+
import { healthPlugin } from '@adhd/apigen-plugin-health';
|
|
200
|
+
import { loggerPlugin } from '@adhd/apigen-plugin-logger';
|
|
201
|
+
|
|
202
|
+
async function startFastify(sourceFile: string) {
|
|
203
|
+
const ops = await extract({ sourceFile, namespace: 'api' });
|
|
204
|
+
const generated = {
|
|
205
|
+
metadata: { namespace: 'api', phase: '' },
|
|
206
|
+
schemas: Object.fromEntries(
|
|
207
|
+
ops.filter(o => o.kind === 'action').map(op => [
|
|
208
|
+
op.path[op.path.length - 1].raw,
|
|
209
|
+
{ input: op.input, output: op.output, hasCtx: op.hasCtx, 'x-apigen-safe': op.safe },
|
|
210
|
+
])
|
|
211
|
+
),
|
|
212
|
+
};
|
|
213
|
+
const schemas = composeSchemas(generated, []);
|
|
214
|
+
const mod = await import(sourceFile);
|
|
215
|
+
|
|
216
|
+
const abort = new AbortController();
|
|
217
|
+
process.on('SIGINT', () => abort.abort());
|
|
218
|
+
|
|
219
|
+
await apiFastifyPlugin.run!({
|
|
220
|
+
packages: [{
|
|
221
|
+
id: 'api',
|
|
222
|
+
schemas,
|
|
223
|
+
importPath: sourceFile,
|
|
224
|
+
fns: mod,
|
|
225
|
+
createClient: async () => ({}),
|
|
226
|
+
}],
|
|
227
|
+
outputDir: '',
|
|
228
|
+
options: {
|
|
229
|
+
port: 3200,
|
|
230
|
+
host: '0.0.0.0',
|
|
231
|
+
routePrefix: '',
|
|
232
|
+
// Mount plugins contributed as HTTP routes:
|
|
233
|
+
usePlugins: [openapiPlugin, healthPlugin, loggerPlugin],
|
|
234
|
+
// Per-plugin options:
|
|
235
|
+
// useOptions: { openapi: { title: 'My API', version: '1.0.0' } },
|
|
236
|
+
},
|
|
237
|
+
signal: abort.signal,
|
|
238
|
+
operations: ops,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
After startup:
|
|
244
|
+
- `GET /api/greet?name=ada` — safe operations are GET (query params)
|
|
245
|
+
- `POST /api/greet` — unsafe operations use `{"data":{…}}` body
|
|
246
|
+
- `GET /_meta/health` — contributed by `healthPlugin`
|
|
247
|
+
- `GET /_meta/openapi` — contributed by `openapiPlugin`, serves a live OpenAPI 3.1 spec
|
|
248
|
+
|
|
249
|
+
**Equivalent CLI command:** `apigen run --source <file> --type api-fastify --opt port=3200 --use openapi --use health --use logger`
|
|
250
|
+
|
|
251
|
+
### Run both servers side by side
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
async function startAll(sourceFile: string) {
|
|
255
|
+
const ops = await extract({ sourceFile, namespace: 'svc' });
|
|
256
|
+
const generated = {
|
|
257
|
+
metadata: { namespace: 'svc', phase: '' },
|
|
258
|
+
schemas: Object.fromEntries(
|
|
259
|
+
ops.filter(o => o.kind === 'action').map(op => [
|
|
260
|
+
op.path[op.path.length - 1].raw,
|
|
261
|
+
{ input: op.input, output: op.output, hasCtx: op.hasCtx, 'x-apigen-safe': op.safe },
|
|
262
|
+
])
|
|
263
|
+
),
|
|
264
|
+
};
|
|
265
|
+
const schemas = composeSchemas(generated, []);
|
|
266
|
+
const mod = await import(sourceFile);
|
|
267
|
+
const pkg = [{
|
|
268
|
+
id: 'svc',
|
|
269
|
+
schemas,
|
|
270
|
+
importPath: sourceFile,
|
|
271
|
+
fns: mod,
|
|
272
|
+
createClient: async () => ({}),
|
|
273
|
+
}];
|
|
274
|
+
|
|
275
|
+
const abort = new AbortController();
|
|
276
|
+
process.on('SIGINT', () => abort.abort());
|
|
277
|
+
|
|
278
|
+
await Promise.all([
|
|
279
|
+
mcpPlugin.run!({ packages: pkg, outputDir: '', options: { transport: 'sse', port: 3100 }, signal: abort.signal, operations: ops }),
|
|
280
|
+
apiFastifyPlugin.run!({ packages: pkg, outputDir: '', options: { port: 3200, usePlugins: [openapiPlugin, healthPlugin] }, signal: abort.signal, operations: ops }),
|
|
281
|
+
]);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
One source file, two servers, both live from the same import.
|
|
286
|
+
|
|
287
|
+
### Integrate with your deploy system
|
|
288
|
+
|
|
289
|
+
Because `run()` returns a `Promise<void>` that resolves on `signal.abort`, you can wrap it in any lifecycle model — containers, process managers, custom mesh registrations.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
export async function deployService(config: {
|
|
293
|
+
source: string;
|
|
294
|
+
port: number;
|
|
295
|
+
type: 'mcp' | 'api-fastify';
|
|
296
|
+
}) {
|
|
297
|
+
const ops = await extract({ sourceFile: config.source, namespace: 'deploy' });
|
|
298
|
+
/* build schemas, import module — same pattern as above */
|
|
299
|
+
|
|
300
|
+
const plugin = config.type === 'mcp' ? mcpPlugin : apiFastifyPlugin;
|
|
301
|
+
const opts = config.type === 'mcp'
|
|
302
|
+
? { transport: 'sse', port: config.port }
|
|
303
|
+
: { port: config.port, usePlugins: [openapiPlugin, healthPlugin] };
|
|
304
|
+
|
|
305
|
+
const abort = new AbortController();
|
|
306
|
+
// Your deployer hook: register with service mesh, health probe, etc.
|
|
307
|
+
const server = plugin.run!({ packages, outputDir: '', options: opts, signal: abort.signal });
|
|
308
|
+
return { abort, running: server }; // call abort.abort() to stop gracefully
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Options reference for `run()`
|
|
313
|
+
|
|
314
|
+
The `options` object in `RunInput` accepts plugin-specific keys. Here are the common ones:
|
|
315
|
+
|
|
316
|
+
| Key | Plugin(s) | Type | Default | Description |
|
|
317
|
+
|-----|-----------|------|---------|-------------|
|
|
318
|
+
| `transport` | `mcp` | `'stdio' \| 'sse' \| 'streaming-http'` | `'stdio'` | MCP transport protocol |
|
|
319
|
+
| `port` | `mcp` (HTTP), `api-fastify` | `number` | `3000` | Listen port |
|
|
320
|
+
| `host` | All | `string` | `'127.0.0.1'` | Bind address |
|
|
321
|
+
| `routePrefix` | `api-fastify` | `string` | `''` | Path prefix before `/<ns>/<fn>` |
|
|
322
|
+
| `usePlugins` | All `run()` targets | `Plugin[]` | `[]` | Layer/mount/envelope plugins to compose |
|
|
323
|
+
| `toolDescriptions` | `mcp` | `Record<string, string>` | `{}` | Per-tool description overrides |
|
|
324
|
+
|
|
325
|
+
### Plugin cheat sheet
|
|
326
|
+
|
|
327
|
+
| Plugin | Package | `run()` | `generate()` | What it builds |
|
|
328
|
+
|--------|---------|---------|--------------|----------------|
|
|
329
|
+
| `mcpPlugin` | `@adhd/apigen-plugin-mcp` | ✓ | ✓ | MCP server (stdio/SSE/streaming-http) |
|
|
330
|
+
| `apiFastifyPlugin` | `@adhd/apigen-plugin-api-fastify` | ✓ | ✓ | Fastify HTTP server |
|
|
331
|
+
| `apiExpressPlugin` | `@adhd/apigen-plugin-api-express` | ✓ | ✓ | Express HTTP server |
|
|
332
|
+
| `cliPlugin` | `@adhd/apigen-plugin-cli-output` | ✓ | ✓ | Commander CLI tool |
|
|
333
|
+
| `jsonschemaPlugin` | `@adhd/apigen-plugin-jsonschema` | — | ✓ | JSON Schema files |
|
|
334
|
+
| `openapiPlugin` | `@adhd/apigen-plugin-openapi` | — | — | Mount: `GET /_meta/openapi` |
|
|
335
|
+
| `healthPlugin` | `@adhd/apigen-plugin-health` | — | — | Mount: `GET /_meta/health` |
|
|
336
|
+
| `loggerPlugin` | `@adhd/apigen-plugin-logger` | — | — | Layer: per-operation logging |
|
|
337
|
+
|
|
338
|
+
> See the [How-To: Running Servers](./docs/how-to/running-servers.md) for a complete walkthrough with all three transports and deployment patterns.
|
|
339
|
+
|
|
87
340
|
## Features
|
|
88
341
|
|
|
89
342
|
### v2 Symbol-Based Extraction
|
|
@@ -223,6 +476,8 @@ Recognized extensions: `.ts/.tsx/.mts/.cts` → `ts`, `.py` → `py`, `.rs` →
|
|
|
223
476
|
|
|
224
477
|
## How-To Guides
|
|
225
478
|
|
|
479
|
+
- [Writing Source Files for apigen](./docs/how-to/writing-source-files.md) — How to structure your TypeScript source: naming, exports, types, and what makes a good API surface
|
|
480
|
+
- [Running Servers Programmatically](./docs/how-to/running-servers.md) — Start MCP, Fastify, or Express servers from your own Node.js process
|
|
226
481
|
- [End-to-End Extraction Pipeline](./docs/how-to/extraction-pipeline.md) — From source file to composed schemas
|
|
227
482
|
- [Building apigen Plugins](./docs/how-to/building-plugins.md) — v1 OutputPlugin and v2 Plugin development
|
|
228
483
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/apigen-core-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"ts-morph": "^23.0.0",
|
|
6
6
|
"ts-json-schema-generator": "^2.3.0",
|
|
7
7
|
"pino": "10.3.1",
|
|
8
8
|
"typescript": "^6.0.3",
|
|
9
|
-
"@adhd/apigen-base-logical": "^0.0.
|
|
9
|
+
"@adhd/apigen-base-logical": "^0.0.2"
|
|
10
10
|
},
|
|
11
11
|
"main": "./index.js",
|
|
12
12
|
"module": "./index.mjs",
|