Skip to content

Commit 016d2f6

Browse files
committed
Improve code splitting and architecture
1 parent b6ac53f commit 016d2f6

39 files changed

+1097
-1156
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.DS_Store
22
dist
33
node_modules
4+
coverage
45

56
packages/openapi-typescript/test/fixtures/cli-outputs/out
67

docs/.vitepress/en.ts

+10
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ export const en = defineConfig({
7878
{ text: "About", link: "/openapi-react-query/about" },
7979
],
8080
},
81+
{
82+
text: "openapi-adonis",
83+
items: [
84+
{ text: "Getting Started", link: "/openapi-adonis/" },
85+
{ text: "useQuery", link: "/openapi-react-query/use-query" },
86+
{ text: "useMutation", link: "/openapi-react-query/use-mutation" },
87+
{ text: "useSuspenseQuery", link: "/openapi-react-query/use-suspense-query" },
88+
{ text: "About", link: "/openapi-react-query/about" },
89+
],
90+
},
8191
],
8292
},
8393
search: {

docs/data/contributors.json

+1-1
Large diffs are not rendered by default.

docs/openapi-adonis/index.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: openapi-adonis
3+
---
4+
5+
# Introduction
6+
7+
openapi-adonis is an [Adonis.js] library to automatically generate OpenAPI schemas and documentation.
8+
9+
- ✅ Creates operations from the Adonis Router
10+
- ✅ Automatically adds your models to the schemas
11+
- ✅ Automatically adds your `@vinejs/vine` validators to the schemas
12+
- ✅ Extended type-inference using Typescript metadata
13+
- ✅ Extensible using Typescript decorators
14+
- ✅ Generates documentation for **Swagger, Scalar, Rapidoc**
15+
16+
The library is inspired by the popular library [@nestjs/swagger](https://www.npmjs.com/package/@nestjs/swagger) and is built on top of [openapi-decorators](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-decorators).
17+
18+
::: code-group
19+
20+
```tsx [app/controllers/users_controller.ts]
21+
import { apiOperation, apiResponse } from "openapi-adonis/decorators";
22+
import User from "#models/user";
23+
24+
class UsersController {
25+
@apiOperation({ summary: "List users" })
26+
@apiResponse({ type: [User] })
27+
async list() {
28+
return User.findManyBy({});
29+
}
30+
}
31+
```
32+
33+
```tsx [app/models/user.ts]
34+
import { apiProperty } from "openapi-adonis/decorators";
35+
36+
class User {
37+
@apiProperty()
38+
declare id: number;
39+
40+
@apiProperty()
41+
declare name: string;
42+
43+
@apiProperty({ required: false })
44+
declare mobile?: string;
45+
}
46+
```
47+
48+
```tsx [start/routes.ts]
49+
import router from "@adonisjs/core/services/router";
50+
import AdonisOpenAPI from "openapi-adonis";
51+
52+
const UsersController = () => import("#controllers/users_controller");
53+
54+
router.post("/users", [UsersController, "create"]);
55+
56+
const builder = AdonisOpenAPI.document().setTitle("OpenAPI Adonis Example");
57+
AdonisOpenAPI.setup("/docs", router, builder);
58+
```
59+
60+
:::
61+
62+
## Setup
63+
64+
Install this library by using `ace`:
65+
66+
```bash
67+
node ace add openapi-adonis
68+
```
69+
70+
## Basic usage

packages/openapi-adonis/biome.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
33
"extends": ["../../biome.json"],
44
"files": {
5-
"ignore": ["./example/"]
5+
"ignore": ["./example/", "./coverage/"]
66
},
77
"linter": {
88
"rules": {
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import User from '#models/user'
2-
import { apiBody, apiOperation, apiResponse, apiParam, apiQuery } from 'openapi-adonis/decorators'
1+
import User from "#models/user";
2+
import { listUserValidator } from "#validators/user";
3+
import { apiBody, apiOperation, apiParam, apiQuery, apiResponse } from "openapi-adonis/decorators";
4+
import { VineType, ModelType } from "openapi-adonis";
35

46
export default class UsersController {
5-
@apiOperation({ summary: 'Get users', tags: ['User'] })
6-
@apiResponse({ type: User })
7-
@apiParam({ name: 'id' })
8-
@apiQuery({ name: 'query' })
7+
@apiOperation({ summary: "Get users" })
8+
@apiResponse({ type: ModelType(User) })
9+
@apiParam({ name: "id" })
10+
@apiQuery({ name: "query" })
11+
@apiBody({ type: VineType(listUserValidator, "listUserValidator") })
912
async index(): Promise<User> {
10-
return new User()
13+
return new User();
1114
}
1215

13-
@apiOperation({ summary: 'Create new user', tags: ['User'] })
14-
@apiBody({ type: User })
16+
@apiOperation({ summary: "Create new user" })
17+
// @apiBody({ type: User })
1518
async create(): Promise<User> {
16-
return new User()
19+
return new User();
1720
}
1821
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import vine from "@vinejs/vine";
2+
3+
export const listUserValidator = vine.compile(
4+
vine.object({
5+
one: vine.string(),
6+
two: vine.number(),
7+
nested: vine.object({
8+
test: vine.number(),
9+
}),
10+
array: vine.array(vine.number()).maxLength(5),
11+
}),
12+
);

packages/openapi-adonis/example/start/routes.ts

+12-11
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@
77
|
88
*/
99

10-
import router from '@adonisjs/core/services/router'
11-
import AdonisOpenAPI from 'openapi-adonis'
10+
import router from "@adonisjs/core/services/router";
11+
import AdonisOpenAPI from "openapi-adonis";
1212

13-
const UsersController = () => import('#controllers/users_controller')
13+
const UsersController = () => import("#controllers/users_controller");
1414

15-
router.get('/', async () => {
15+
router.get("/", async () => {
1616
return {
17-
hello: 'world',
18-
}
19-
})
17+
hello: "world",
18+
};
19+
});
2020

21-
router.get('/users/:id', [UsersController, 'index'])
22-
router.post('/users', [UsersController, 'create'])
21+
// router.get("/users/:id", [UsersController, "index"]);
22+
// router.post("/users", [UsersController, "create"]);
23+
router.resource("users", UsersController).apiOnly();
2324

24-
const builder = AdonisOpenAPI.document().setTitle('OpenAPI Adonis Example')
25-
AdonisOpenAPI.setup('/docs', router, builder)
25+
const builder = AdonisOpenAPI.document().setTitle("OpenAPI Adonis Example");
26+
AdonisOpenAPI.setup("/docs", router, builder);

packages/openapi-adonis/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"format": "biome format . --write",
4646
"lint": "biome check .",
4747
"test": "vitest run",
48+
"coverage": "vitest run --coverage",
4849
"version": "pnpm run prepare && pnpm run build"
4950
},
5051
"dependencies": {
@@ -60,6 +61,7 @@
6061
"@adonisjs/http-server": "^7.2.3",
6162
"@types/lodash": "^4.17.7",
6263
"@types/node": "^22.1.0",
64+
"@vitest/coverage-v8": "^2.0.5",
6365
"tsup": "^8.2.4",
6466
"typescript": "^5.4.5",
6567
"unplugin-swc": "^1.5.1"

packages/openapi-adonis/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import type { Router } from "@adonisjs/http-server";
44
import { DocumentBuilder } from "openapi-decorators/builders";
55
import { generateScalarUI } from "openapi-decorators/ui";
66
import { loadRouter } from "./loaders/loadRouter";
7+
import { VineTypeResolver } from "./resolvers/vine-type.resolver";
8+
9+
export { VineType } from "./resolvers/vine-type.resolver";
10+
export { ModelType } from "openapi-decorators/resolvers";
711

812
// biome-ignore lint/complexity/noStaticOnlyClass: TODO: move out of class
913
export default class AdonisOpenAPI {
1014
public static document() {
11-
return new DocumentBuilder();
15+
return new DocumentBuilder().addResolver(VineTypeResolver);
1216
}
1317

1418
public static async load(builder: DocumentBuilder, router: Router): Promise<OpenAPIV3.Document> {

packages/openapi-adonis/src/loaders/loadController.ts

-57
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,33 @@
11
import type { OpenAPIV3 } from "openapi-types";
22
import { OperationBuilder, type DocumentBuilder } from "openapi-decorators/builders";
33
import type { AdonisRoute } from "../types";
4-
import { loadController } from "./loadController";
5-
import { getApiTags } from "openapi-decorators";
6-
import { loadApiTags } from "openapi-decorators/loaders";
4+
import { loadApiOperation, loadController } from "openapi-decorators/loaders";
75
import { normalizeRoutePattern } from "../utils/normalizeRoutePattern";
86

97
export async function loadRoute(document: DocumentBuilder, route: AdonisRoute) {
108
if (typeof route.handler !== "object" || !Array.isArray(route.handler.reference)) {
119
return;
1210
}
1311

14-
const importer = route.handler.reference[0] as Function;
12+
const importer = route.handler.reference[0] as () => Promise<{ default: any }>;
1513
const propertyKey = route.handler.reference[1] as string;
1614

1715
const target = (await importer().then((t: any) => t.default)) as any;
1816

19-
const operation = new OperationBuilder();
20-
21-
const apiTags = getApiTags(target);
22-
if (apiTags) {
23-
loadApiTags(operation, apiTags);
24-
}
25-
26-
loadController(document, operation, target.prototype, propertyKey);
27-
2817
for (const method of route.methods) {
2918
if (method === "HEAD") {
3019
continue;
3120
}
3221

33-
document.setOperation(
34-
method.toLowerCase() as OpenAPIV3.HttpMethods,
35-
normalizeRoutePattern(route.pattern),
36-
operation.build(),
37-
);
22+
const operation = new OperationBuilder();
23+
24+
loadApiOperation(operation, {
25+
method: method.toLowerCase() as `${OpenAPIV3.HttpMethods}`,
26+
pattern: normalizeRoutePattern(route.pattern),
27+
});
28+
29+
loadController(document, operation, target.prototype, propertyKey);
30+
31+
document.addOperation(operation);
3832
}
3933
}

0 commit comments

Comments
 (0)