@sdk-it/generic 0.46.0 → 0.46.2
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/README.md +94 -163
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
# @sdk-it/generic
|
|
2
2
|
|
|
3
|
-
<p align="center">
|
|
3
|
+
<p align="center">Analyze TypeScript routes and produce an OpenAPI document</p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@sdk-it/generic` extracts routes, validation schemas, and responses from a
|
|
6
|
+
TypeScript project. Framework packages provide the runtime middleware and
|
|
7
|
+
response analyzer.
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Framework integrations
|
|
8
10
|
|
|
9
|
-
- [
|
|
11
|
+
- [Hono](../hono/README.md)
|
|
10
12
|
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
15
|
+
For a Hono project:
|
|
16
|
+
|
|
13
17
|
```bash
|
|
14
|
-
npm install @sdk-it/
|
|
18
|
+
npm install @sdk-it/hono hono zod
|
|
19
|
+
npm install --save-dev @sdk-it/core @sdk-it/generic typescript@^6.0.3
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
Consider the following example:
|
|
22
|
+
## Analyze a Hono project
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
Define routes with `validate` and an `@openapi` JSDoc tag:
|
|
22
25
|
|
|
23
26
|
```typescript
|
|
24
|
-
|
|
27
|
+
// src/app.ts
|
|
28
|
+
import { Hono } from 'hono';
|
|
29
|
+
import { z } from 'zod';
|
|
25
30
|
|
|
26
|
-
import { validate } from '@sdk-it/
|
|
31
|
+
import { validate } from '@sdk-it/hono/runtime';
|
|
27
32
|
|
|
28
|
-
const app =
|
|
33
|
+
export const app = new Hono();
|
|
29
34
|
|
|
30
35
|
/**
|
|
31
36
|
* @openapi getAuthor
|
|
@@ -35,161 +40,109 @@ app.get(
|
|
|
35
40
|
'/authors/:id',
|
|
36
41
|
validate((payload) => ({
|
|
37
42
|
id: {
|
|
38
|
-
select: payload.
|
|
43
|
+
select: payload.params.id,
|
|
39
44
|
against: z.string(),
|
|
40
45
|
},
|
|
41
46
|
})),
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
return
|
|
47
|
+
(c) => {
|
|
48
|
+
const { id } = c.var.input;
|
|
49
|
+
return c.json({ id, name: 'John Doe' });
|
|
45
50
|
},
|
|
46
51
|
);
|
|
47
52
|
```
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
Analyze the TypeScript project and write the resulting OpenAPI document:
|
|
50
55
|
|
|
51
56
|
```typescript
|
|
52
|
-
|
|
57
|
+
// openapi.ts
|
|
58
|
+
import { writeFile } from 'node:fs/promises';
|
|
53
59
|
|
|
54
|
-
import { analyze
|
|
55
|
-
import {
|
|
60
|
+
import { analyze } from '@sdk-it/generic';
|
|
61
|
+
import { responseAnalyzer } from '@sdk-it/hono';
|
|
56
62
|
|
|
57
|
-
const { paths, components } = await analyze('
|
|
63
|
+
const { paths, components, tags } = await analyze('./tsconfig.json', {
|
|
58
64
|
responseAnalyzer,
|
|
59
65
|
});
|
|
60
66
|
|
|
61
67
|
const spec = {
|
|
68
|
+
openapi: '3.1.0',
|
|
62
69
|
info: {
|
|
63
70
|
title: 'My API',
|
|
64
71
|
version: '1.0.0',
|
|
65
72
|
},
|
|
66
73
|
paths,
|
|
67
74
|
components,
|
|
75
|
+
tags: tags.map((name) => ({ name })),
|
|
68
76
|
};
|
|
69
77
|
|
|
70
|
-
await writeFile(
|
|
71
|
-
join(process.cwd(), 'openapi.json'),
|
|
72
|
-
JSON.stringify(spec, null, 2),
|
|
73
|
-
);
|
|
78
|
+
await writeFile('openapi.json', JSON.stringify(spec, null, 2));
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
> See [typescript](../typescript/README.md) to create fully functional SDKs from the generated OpenAPI specification.
|
|
81
|
+
Run the script with Node.js 24 or newer:
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Use file name as tag**
|
|
83
|
+
```bash
|
|
84
|
+
node openapi.ts
|
|
85
|
+
```
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
See [`@sdk-it/typescript`](../typescript/README.md) to generate a client from
|
|
88
|
+
the OpenAPI document.
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
apps/
|
|
89
|
-
backend/
|
|
90
|
-
tsconfig.app.json
|
|
91
|
-
src/
|
|
92
|
-
routes/
|
|
93
|
-
authors.ts
|
|
94
|
-
```
|
|
90
|
+
## Customize operations
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
`onOperation` receives every derived operation. This example uses the route
|
|
93
|
+
file name as its tag:
|
|
97
94
|
|
|
98
95
|
```typescript
|
|
99
96
|
import { basename } from 'node:path';
|
|
100
|
-
import { camelcase } from 'stringcase';
|
|
101
97
|
|
|
102
|
-
import { analyze
|
|
98
|
+
import { analyze } from '@sdk-it/generic';
|
|
99
|
+
import { responseAnalyzer } from '@sdk-it/hono';
|
|
103
100
|
|
|
104
|
-
const { paths, components } = await analyze(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
[
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
tags: [fileName],
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
};
|
|
101
|
+
const { paths, components } = await analyze(
|
|
102
|
+
'./apps/backend/tsconfig.app.json',
|
|
103
|
+
{
|
|
104
|
+
responseAnalyzer,
|
|
105
|
+
onOperation(sourceFile, _method, _path, operation) {
|
|
106
|
+
operation.tags = [basename(sourceFile, '.ts')];
|
|
107
|
+
return {};
|
|
108
|
+
},
|
|
116
109
|
},
|
|
117
|
-
|
|
110
|
+
);
|
|
118
111
|
```
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
## Customize type mappings
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
Use `typesMap` for TypeScript types without a direct OpenAPI representation.
|
|
116
|
+
For example, map Prisma's `Decimal` to a string so it keeps its precision on
|
|
117
|
+
the wire:
|
|
125
118
|
|
|
126
119
|
```typescript
|
|
127
|
-
import { writeFile } from 'node:fs/promises';
|
|
128
|
-
import { join } from 'node:path';
|
|
129
|
-
import { cwd } from 'node:process';
|
|
130
|
-
|
|
131
120
|
import { defaultTypesMap } from '@sdk-it/core';
|
|
132
121
|
import { analyze } from '@sdk-it/generic';
|
|
133
122
|
import { responseAnalyzer } from '@sdk-it/hono';
|
|
134
123
|
|
|
135
|
-
|
|
136
|
-
const customTypeMappings = {
|
|
137
|
-
Decimal: 'string',
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const { paths, components } = await analyze('path/to/tsconfig.json', {
|
|
124
|
+
const { paths, components } = await analyze('./tsconfig.json', {
|
|
141
125
|
responseAnalyzer,
|
|
142
126
|
typesMap: {
|
|
143
127
|
...defaultTypesMap,
|
|
144
128
|
Decimal: 'string',
|
|
145
129
|
},
|
|
146
130
|
});
|
|
147
|
-
|
|
148
|
-
const spec = {
|
|
149
|
-
openapi: '3.1.0',
|
|
150
|
-
info: {
|
|
151
|
-
title: 'My API',
|
|
152
|
-
version: '1.0.0',
|
|
153
|
-
},
|
|
154
|
-
paths,
|
|
155
|
-
components,
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
await writeFile('openapi.json', JSON.stringify(spec, null, 2));
|
|
159
131
|
```
|
|
160
132
|
|
|
161
|
-
|
|
133
|
+
## Reference external schemas
|
|
162
134
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
By default, the analyzer only sees schemas defined inline in the validate middleware.
|
|
166
|
-
|
|
167
|
-
For instance the following route handler is perfectly valid and will be analyzed correctly.
|
|
135
|
+
The analyzer can evaluate inline Zod schemas directly:
|
|
168
136
|
|
|
169
137
|
```typescript
|
|
170
|
-
|
|
171
|
-
* @openapi getAuthor
|
|
172
|
-
* @tags authors
|
|
173
|
-
*/
|
|
174
|
-
app.get(
|
|
175
|
-
'/authors/:id',
|
|
176
|
-
validate((payload) => ({
|
|
177
|
-
id: {
|
|
178
|
-
select: payload.param.id,
|
|
179
|
-
against: z.string(),
|
|
180
|
-
},
|
|
181
|
-
})),
|
|
182
|
-
async (req, res) => {
|
|
183
|
-
const { id } = req.input;
|
|
184
|
-
return res.json({ id, name: 'John Doe' });
|
|
185
|
-
},
|
|
186
|
-
);
|
|
138
|
+
against: z.string().min(2).max(100);
|
|
187
139
|
```
|
|
188
140
|
|
|
189
|
-
|
|
141
|
+
When a validator references a schema from another file, use a namespace import
|
|
142
|
+
in the route:
|
|
190
143
|
|
|
191
|
-
```
|
|
192
|
-
//
|
|
144
|
+
```typescript
|
|
145
|
+
// src/schemas.ts
|
|
193
146
|
import { z } from 'zod';
|
|
194
147
|
|
|
195
148
|
export const authorSchema = z.object({
|
|
@@ -198,88 +151,62 @@ export const authorSchema = z.object({
|
|
|
198
151
|
});
|
|
199
152
|
```
|
|
200
153
|
|
|
201
|
-
```
|
|
154
|
+
```typescript
|
|
155
|
+
// src/app.ts
|
|
156
|
+
import { Hono } from 'hono';
|
|
202
157
|
import crypto from 'node:crypto';
|
|
203
158
|
import { z } from 'zod';
|
|
204
159
|
|
|
205
|
-
import { validate } from '@sdk-it/
|
|
160
|
+
import { validate } from '@sdk-it/hono/runtime';
|
|
206
161
|
|
|
207
|
-
import
|
|
162
|
+
import * as schemas from './schemas.ts';
|
|
163
|
+
|
|
164
|
+
const app = new Hono();
|
|
208
165
|
|
|
209
|
-
/**
|
|
210
|
-
* @openapi createBook
|
|
211
|
-
* @tags books
|
|
212
|
-
*/
|
|
213
166
|
app.post(
|
|
214
167
|
'/books',
|
|
215
|
-
validate((payload) => ({
|
|
168
|
+
validate('application/json', (payload) => ({
|
|
216
169
|
title: {
|
|
217
170
|
select: payload.body.title,
|
|
218
171
|
against: z.string().min(2).max(100),
|
|
219
172
|
},
|
|
220
173
|
author: {
|
|
221
174
|
select: payload.body.author,
|
|
222
|
-
against: authorSchema,
|
|
175
|
+
against: schemas.authorSchema,
|
|
223
176
|
},
|
|
224
177
|
})),
|
|
225
|
-
|
|
226
|
-
const { title, author } =
|
|
227
|
-
return
|
|
178
|
+
(c) => {
|
|
179
|
+
const { title, author } = c.var.input;
|
|
180
|
+
return c.json({ id: crypto.randomUUID(), title, author }, 201);
|
|
228
181
|
},
|
|
229
182
|
);
|
|
230
183
|
```
|
|
231
184
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
The analyzer's `imports` option lets you include additional files in the analysis.
|
|
185
|
+
Then inject that namespace when analyzing the project:
|
|
235
186
|
|
|
236
|
-
```
|
|
237
|
-
import {
|
|
187
|
+
```typescript
|
|
188
|
+
import { fileURLToPath } from 'node:url';
|
|
238
189
|
|
|
239
190
|
import { analyze } from '@sdk-it/generic';
|
|
191
|
+
import { responseAnalyzer } from '@sdk-it/hono';
|
|
240
192
|
|
|
241
|
-
const { paths, components } = await analyze('
|
|
193
|
+
const { paths, components } = await analyze('./tsconfig.json', {
|
|
242
194
|
responseAnalyzer,
|
|
243
195
|
imports: [
|
|
244
196
|
{
|
|
245
197
|
import: 'schemas',
|
|
246
|
-
from:
|
|
198
|
+
from: fileURLToPath(new URL('./src/schemas.ts', import.meta.url)),
|
|
247
199
|
},
|
|
248
200
|
],
|
|
249
201
|
});
|
|
250
202
|
```
|
|
251
203
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
import * as schemas from './schemas';
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* @openapi createBook
|
|
259
|
-
* @tags books
|
|
260
|
-
*/
|
|
261
|
-
app.post(
|
|
262
|
-
'/books',
|
|
263
|
-
validate((payload) => ({
|
|
264
|
-
title: {
|
|
265
|
-
select: payload.body.title,
|
|
266
|
-
against: z.string().min(2).max(100),
|
|
267
|
-
},
|
|
268
|
-
author: {
|
|
269
|
-
select: payload.body.author,
|
|
270
|
-
against: schemas.authorSchema,
|
|
271
|
-
},
|
|
272
|
-
})),
|
|
273
|
-
async (req, res) => {
|
|
274
|
-
const { title, author } = req.input;
|
|
275
|
-
return res.json({ id: crypto.randomUUID(), title, author });
|
|
276
|
-
},
|
|
277
|
-
);
|
|
278
|
-
```
|
|
204
|
+
The injected file must be loadable by the Node.js process running the
|
|
205
|
+
analyzer.
|
|
279
206
|
|
|
280
|
-
|
|
207
|
+
## Hide an operation
|
|
281
208
|
|
|
282
|
-
|
|
209
|
+
Add `@access private` to exclude a route from the generated OpenAPI document:
|
|
283
210
|
|
|
284
211
|
```typescript
|
|
285
212
|
/**
|
|
@@ -287,10 +214,14 @@ Control endpoint visibility with the `@access` tag in JSDoc comments. For now, o
|
|
|
287
214
|
* @tags authors
|
|
288
215
|
* @access private
|
|
289
216
|
*/
|
|
290
|
-
app.get(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
217
|
+
app.get(
|
|
218
|
+
'/authors/:id',
|
|
219
|
+
validate((payload) => ({
|
|
220
|
+
id: {
|
|
221
|
+
select: payload.params.id,
|
|
222
|
+
against: z.string(),
|
|
223
|
+
},
|
|
224
|
+
})),
|
|
225
|
+
(c) => c.json({ id: c.var.input.id, name: 'John Doe' }),
|
|
226
|
+
);
|
|
294
227
|
```
|
|
295
|
-
|
|
296
|
-
In this example, the `getAuthor` operation will be hidden from the generated OpenAPI specification.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdk-it/generic",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"!**/*.test.*"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@sdk-it/core": "0.46.
|
|
25
|
+
"@sdk-it/core": "0.46.2",
|
|
26
26
|
"stringcase": "^4.3.1",
|
|
27
27
|
"debug": "^4.4.0",
|
|
28
28
|
"openapi3-ts": "^4.4.0"
|