@kontract/adonis 0.1.0
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 +507 -0
- package/dist/adapters/controller-registrar.d.ts +313 -0
- package/dist/adapters/controller-registrar.d.ts.map +1 -0
- package/dist/adapters/controller-registrar.js +324 -0
- package/dist/adapters/controller-registrar.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/route-registrar.d.ts +53 -0
- package/dist/adapters/route-registrar.d.ts.map +1 -0
- package/dist/adapters/route-registrar.js +139 -0
- package/dist/adapters/route-registrar.js.map +1 -0
- package/dist/adapters/router.d.ts +37 -0
- package/dist/adapters/router.d.ts.map +1 -0
- package/dist/adapters/router.js +129 -0
- package/dist/adapters/router.js.map +1 -0
- package/dist/builder/index.d.ts +2 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +3 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/openapi-builder.d.ts +100 -0
- package/dist/builder/openapi-builder.d.ts.map +1 -0
- package/dist/builder/openapi-builder.js +388 -0
- package/dist/builder/openapi-builder.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +2 -0
- package/dist/serializers/index.js.map +1 -0
- package/dist/serializers/lucid.d.ts +100 -0
- package/dist/serializers/lucid.d.ts.map +1 -0
- package/dist/serializers/lucid.js +114 -0
- package/dist/serializers/lucid.js.map +1 -0
- package/dist/validation/ajv.d.ts +42 -0
- package/dist/validation/ajv.d.ts.map +1 -0
- package/dist/validation/ajv.js +170 -0
- package/dist/validation/ajv.js.map +1 -0
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +3 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
# @kontract/adonis
|
|
2
|
+
|
|
3
|
+
AdonisJS adapter for [kontract](../kontract). Provides route registration, AJV validation, Lucid ORM serializers, and OpenAPI spec generation for AdonisJS v6.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic route registration** - Register decorated endpoints with AdonisJS router
|
|
8
|
+
- **AJV validation** - TypeBox schema validation with type coercion and format support
|
|
9
|
+
- **Lucid serializers** - Automatic serialization of Lucid models and paginators
|
|
10
|
+
- **OpenAPI generation** - Build OpenAPI 3.0/3.1 specs from decorators
|
|
11
|
+
- **Full TypeBox support** - Validate requests against TypeBox schemas
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @kontract/adonis @sinclair/typebox ajv ajv-formats
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Note:** This package depends on `kontract` (the core library), which will be installed automatically. You can import from either package - the adapter re-exports all commonly used items from the core.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Define Your Controller
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// app/controllers/books_controller.ts
|
|
27
|
+
import { Api, Endpoint, ok, apiError } from '@kontract/adonis'
|
|
28
|
+
import { Type, Static } from '@sinclair/typebox'
|
|
29
|
+
import Book from '#models/book'
|
|
30
|
+
|
|
31
|
+
const BookSchema = Type.Object({
|
|
32
|
+
id: Type.String({ format: 'uuid' }),
|
|
33
|
+
title: Type.String(),
|
|
34
|
+
author: Type.String(),
|
|
35
|
+
}, { $id: 'Book' })
|
|
36
|
+
|
|
37
|
+
const CreateBookRequest = Type.Object({
|
|
38
|
+
title: Type.String({ minLength: 1 }),
|
|
39
|
+
author: Type.String({ minLength: 1 }),
|
|
40
|
+
}, { $id: 'CreateBookRequest' })
|
|
41
|
+
|
|
42
|
+
@Api({ tag: 'Books', description: 'Book management' })
|
|
43
|
+
export default class BooksController {
|
|
44
|
+
@Endpoint('GET /api/v1/books', {
|
|
45
|
+
summary: 'List all books',
|
|
46
|
+
responses: {
|
|
47
|
+
200: { schema: Type.Array(BookSchema) },
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
async index() {
|
|
51
|
+
const books = await Book.all()
|
|
52
|
+
return ok(Type.Array(BookSchema), books.map(b => b.toResponse()))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Endpoint('POST /api/v1/books', {
|
|
56
|
+
summary: 'Create a book',
|
|
57
|
+
auth: 'required',
|
|
58
|
+
body: CreateBookRequest,
|
|
59
|
+
responses: {
|
|
60
|
+
201: { schema: BookSchema },
|
|
61
|
+
422: null,
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
async store(
|
|
65
|
+
ctx: HttpContext,
|
|
66
|
+
body: Static<typeof CreateBookRequest>
|
|
67
|
+
) {
|
|
68
|
+
const book = await Book.create(body)
|
|
69
|
+
return ok(BookSchema, book.toResponse())
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Register Routes
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// start/routes.ts
|
|
78
|
+
import router from '@adonisjs/core/services/router'
|
|
79
|
+
import { registerDecoratorRoutes, validate } from '@kontract/adonis'
|
|
80
|
+
|
|
81
|
+
// Import controllers to trigger decorator registration
|
|
82
|
+
import '#controllers/books_controller'
|
|
83
|
+
import '#controllers/users_controller'
|
|
84
|
+
|
|
85
|
+
// Register all decorated routes
|
|
86
|
+
registerDecoratorRoutes(router, { validate })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Generate OpenAPI Spec
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// commands/generate_openapi.ts
|
|
93
|
+
import { OpenApiBuilder } from '@kontract/adonis'
|
|
94
|
+
|
|
95
|
+
// Import controllers
|
|
96
|
+
import '#controllers/books_controller'
|
|
97
|
+
import '#controllers/users_controller'
|
|
98
|
+
|
|
99
|
+
const builder = new OpenApiBuilder({
|
|
100
|
+
title: 'My API',
|
|
101
|
+
description: 'API documentation',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
servers: [
|
|
104
|
+
{ url: 'http://localhost:3333', description: 'Development' },
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const spec = builder.build()
|
|
109
|
+
console.log(JSON.stringify(spec, null, 2))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Runtime Validation
|
|
113
|
+
|
|
114
|
+
The packages support runtime validation for both **requests** and **responses**.
|
|
115
|
+
|
|
116
|
+
### Request Validation (Automatic)
|
|
117
|
+
|
|
118
|
+
Request validation happens automatically when you use `registerDecoratorRoutes()`. The route registrar validates `body`, `query`, and `params` against the TypeBox schemas defined in your `@Endpoint` decorators.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
@Endpoint('POST /api/v1/books', {
|
|
122
|
+
body: CreateBookRequest, // Validated at runtime
|
|
123
|
+
query: PaginationQuery, // Validated at runtime
|
|
124
|
+
params: BookIdParams, // Validated at runtime
|
|
125
|
+
responses: { 201: BookSchema },
|
|
126
|
+
})
|
|
127
|
+
async store(ctx, body, query, params) {
|
|
128
|
+
// body, query, params are already validated and typed
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If validation fails, an `AjvValidationError` is thrown with status 422.
|
|
133
|
+
|
|
134
|
+
### Response Validation (Optional)
|
|
135
|
+
|
|
136
|
+
Response validation catches contract violations during development. Enable it by calling `defineConfig()` at application startup:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// start/kernel.ts or providers/app_provider.ts
|
|
140
|
+
import { defineConfig } from 'kontract'
|
|
141
|
+
import { createAjvValidator } from '@kontract/adonis'
|
|
142
|
+
|
|
143
|
+
const validator = createAjvValidator()
|
|
144
|
+
|
|
145
|
+
defineConfig({
|
|
146
|
+
openapi: {
|
|
147
|
+
info: { title: 'My API', version: '1.0.0' },
|
|
148
|
+
},
|
|
149
|
+
runtime: {
|
|
150
|
+
validateResponses: process.env.NODE_ENV !== 'production',
|
|
151
|
+
},
|
|
152
|
+
validator: (schema, data) => validator.validate(schema, data),
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
When enabled, response helpers like `ok()`, `created()`, etc. will validate the response data against the schema and throw `ResponseValidationError` if it doesn't match.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// This will throw in development if user doesn't match UserSchema
|
|
160
|
+
return ok(UserSchema, user)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Route Registration
|
|
164
|
+
|
|
165
|
+
### registerDecoratorRoutes(router, options)
|
|
166
|
+
|
|
167
|
+
Registers all routes defined via `@Endpoint` decorators with the AdonisJS router.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import router from '@adonisjs/core/services/router'
|
|
171
|
+
import { registerDecoratorRoutes, validate } from '@kontract/adonis'
|
|
172
|
+
|
|
173
|
+
registerDecoratorRoutes(router, {
|
|
174
|
+
validate, // AJV validation function
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The registrar:
|
|
179
|
+
- Creates routes for each `@Endpoint` decorator
|
|
180
|
+
- Validates request body, query, and params against TypeBox schemas
|
|
181
|
+
- Handles authentication based on `auth` option
|
|
182
|
+
- Calls the controller method with validated data
|
|
183
|
+
- Processes API responses (status codes, JSON, binary)
|
|
184
|
+
|
|
185
|
+
### Controller Method Signature
|
|
186
|
+
|
|
187
|
+
Controller methods receive validated data as separate parameters:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
async store(
|
|
191
|
+
ctx: HttpContext, // AdonisJS context
|
|
192
|
+
body: BodyType, // Validated request body
|
|
193
|
+
query: QueryType, // Validated query parameters
|
|
194
|
+
params: ParamsType // Validated path parameters
|
|
195
|
+
) {
|
|
196
|
+
// body, query, params are already validated
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Validation
|
|
201
|
+
|
|
202
|
+
### validate(schema, data)
|
|
203
|
+
|
|
204
|
+
Validates data against a TypeBox schema. Throws `AjvValidationError` on failure.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { validate } from '@kontract/adonis'
|
|
208
|
+
import { Type } from '@sinclair/typebox'
|
|
209
|
+
|
|
210
|
+
const schema = Type.Object({
|
|
211
|
+
email: Type.String({ format: 'email' }),
|
|
212
|
+
age: Type.Integer({ minimum: 0 }),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const data = validate(schema, { email: 'user@example.com', age: 25 })
|
|
217
|
+
// data is typed and validated
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error instanceof AjvValidationError) {
|
|
220
|
+
console.log(error.errors)
|
|
221
|
+
// [{ field: 'email', message: 'must match format "email"' }]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### createAjvValidator(options?)
|
|
227
|
+
|
|
228
|
+
Create a customized AJV validator instance.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { createAjvValidator } from '@kontract/adonis'
|
|
232
|
+
|
|
233
|
+
const validator = createAjvValidator({
|
|
234
|
+
coerceTypes: true, // Convert strings to numbers, etc.
|
|
235
|
+
removeAdditional: true, // Strip unknown properties
|
|
236
|
+
useDefaults: true, // Apply default values
|
|
237
|
+
formats: {
|
|
238
|
+
'custom-format': (value) => /^[A-Z]+$/.test(value),
|
|
239
|
+
},
|
|
240
|
+
ajvOptions: {
|
|
241
|
+
// Additional AJV options
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Validate (returns errors array)
|
|
246
|
+
const errors = validator.validate(schema, data)
|
|
247
|
+
|
|
248
|
+
// Validate or throw
|
|
249
|
+
validator.validateOrThrow(schema, data)
|
|
250
|
+
|
|
251
|
+
// Pre-compile for performance
|
|
252
|
+
const compiled = validator.compile(schema)
|
|
253
|
+
compiled.validate(data)
|
|
254
|
+
compiled.validateOrThrow(data)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### AjvValidationError
|
|
258
|
+
|
|
259
|
+
Error thrown when validation fails.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { AjvValidationError } from '@kontract/adonis'
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
validate(schema, data)
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error instanceof AjvValidationError) {
|
|
268
|
+
error.status // 422
|
|
269
|
+
error.code // 'E_VALIDATION_ERROR'
|
|
270
|
+
error.errors // [{ field: 'email', message: '...' }]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Serializers
|
|
276
|
+
|
|
277
|
+
Built-in serializers for Lucid models and paginators.
|
|
278
|
+
|
|
279
|
+
### Type Guards
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import {
|
|
283
|
+
isLucidModel, // Has serialize() method
|
|
284
|
+
isTypedModel, // Has toResponse() method
|
|
285
|
+
isPaginator, // Lucid paginator object
|
|
286
|
+
hasSerialize, // Generic serialize check
|
|
287
|
+
} from '@kontract/adonis'
|
|
288
|
+
|
|
289
|
+
if (isTypedModel(data)) {
|
|
290
|
+
return data.toResponse()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (isLucidModel(data)) {
|
|
294
|
+
return data.serialize()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (isPaginator(data)) {
|
|
298
|
+
// { data: [...], meta: { total, perPage, currentPage, ... } }
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Serializer Registry
|
|
303
|
+
|
|
304
|
+
Serializers are ordered by priority (higher = checked first):
|
|
305
|
+
|
|
306
|
+
| Serializer | Priority | Checks |
|
|
307
|
+
|------------|----------|--------|
|
|
308
|
+
| `paginatorSerializer` | 150 | `isPaginator()` |
|
|
309
|
+
| `typedModelSerializer` | 100 | `isTypedModel()` |
|
|
310
|
+
| `lucidModelSerializer` | 50 | `isLucidModel()` |
|
|
311
|
+
| `serializableSerializer` | 25 | `hasSerialize()` |
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { lucidSerializers } from '@kontract/adonis'
|
|
315
|
+
|
|
316
|
+
// All serializers in priority order
|
|
317
|
+
lucidSerializers
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## OpenAPI Builder
|
|
321
|
+
|
|
322
|
+
### OpenApiBuilder
|
|
323
|
+
|
|
324
|
+
Generates OpenAPI specifications from decorated controllers.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { OpenApiBuilder } from '@kontract/adonis'
|
|
328
|
+
|
|
329
|
+
const builder = new OpenApiBuilder({
|
|
330
|
+
title: 'My API',
|
|
331
|
+
description: 'API documentation',
|
|
332
|
+
version: '1.0.0',
|
|
333
|
+
servers: [
|
|
334
|
+
{ url: 'https://api.example.com', description: 'Production' },
|
|
335
|
+
{ url: 'http://localhost:3333', description: 'Development' },
|
|
336
|
+
],
|
|
337
|
+
openapiVersion: '3.1.0', // or '3.0.3'
|
|
338
|
+
securityScheme: {
|
|
339
|
+
name: 'BearerAuth',
|
|
340
|
+
type: 'http',
|
|
341
|
+
scheme: 'bearer',
|
|
342
|
+
bearerFormat: 'JWT',
|
|
343
|
+
description: 'JWT access token',
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
const spec = builder.build()
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### OpenApiBuilderOptions
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
interface OpenApiBuilderOptions {
|
|
354
|
+
title: string
|
|
355
|
+
description: string
|
|
356
|
+
version: string
|
|
357
|
+
servers: Array<{ url: string; description: string }>
|
|
358
|
+
openapiVersion?: '3.0.3' | '3.1.0' // default: '3.1.0'
|
|
359
|
+
securityScheme?: {
|
|
360
|
+
name: string
|
|
361
|
+
type: 'http' | 'apiKey' | 'oauth2'
|
|
362
|
+
scheme?: string
|
|
363
|
+
bearerFormat?: string
|
|
364
|
+
description?: string
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Generated Features
|
|
370
|
+
|
|
371
|
+
The builder automatically:
|
|
372
|
+
|
|
373
|
+
- **Collects tags** from `@Api` decorators
|
|
374
|
+
- **Converts paths** (`:id` to `{id}`)
|
|
375
|
+
- **Generates operationIds** from method names
|
|
376
|
+
- **Adds security** for `auth: 'required'` endpoints
|
|
377
|
+
- **Adds 401 response** for authenticated endpoints
|
|
378
|
+
- **Registers schemas** in components
|
|
379
|
+
- **Handles file uploads** as multipart/form-data
|
|
380
|
+
|
|
381
|
+
### OperationId Generation
|
|
382
|
+
|
|
383
|
+
For standard CRUD method names, operationIds are auto-generated:
|
|
384
|
+
|
|
385
|
+
| Method | Path | Generated operationId |
|
|
386
|
+
|--------|------|----------------------|
|
|
387
|
+
| `index` | GET /users | `listUsers` |
|
|
388
|
+
| `show` | GET /users/:id | `getUser` |
|
|
389
|
+
| `store` | POST /users | `createUser` |
|
|
390
|
+
| `update` | PUT /users/:id | `updateUser` |
|
|
391
|
+
| `destroy` | DELETE /users/:id | `deleteUser` |
|
|
392
|
+
|
|
393
|
+
Custom method names use the method name as operationId.
|
|
394
|
+
|
|
395
|
+
## Re-exports
|
|
396
|
+
|
|
397
|
+
For convenience, the adapter re-exports common items from the core package:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import {
|
|
401
|
+
// Decorators
|
|
402
|
+
Api,
|
|
403
|
+
Endpoint,
|
|
404
|
+
|
|
405
|
+
// Response helpers
|
|
406
|
+
ok,
|
|
407
|
+
created,
|
|
408
|
+
accepted,
|
|
409
|
+
noContent,
|
|
410
|
+
badRequest,
|
|
411
|
+
unauthorized,
|
|
412
|
+
forbidden,
|
|
413
|
+
notFound,
|
|
414
|
+
conflict,
|
|
415
|
+
unprocessableEntity,
|
|
416
|
+
tooManyRequests,
|
|
417
|
+
internalServerError,
|
|
418
|
+
serviceUnavailable,
|
|
419
|
+
binary,
|
|
420
|
+
apiError,
|
|
421
|
+
|
|
422
|
+
// Configuration
|
|
423
|
+
defineConfig,
|
|
424
|
+
getConfig,
|
|
425
|
+
|
|
426
|
+
// Types
|
|
427
|
+
type ApiOptions,
|
|
428
|
+
type EndpointOptions,
|
|
429
|
+
type ApiResponse,
|
|
430
|
+
type BinaryResponse,
|
|
431
|
+
type EndpointMetadata,
|
|
432
|
+
type ApiMetadata,
|
|
433
|
+
} from '@kontract/adonis'
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Utilities
|
|
437
|
+
|
|
438
|
+
### stripNestedIds(schema)
|
|
439
|
+
|
|
440
|
+
Removes `$id` from nested schemas to prevent AJV conflicts when the same schema is used multiple times.
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { stripNestedIds } from '@kontract/adonis'
|
|
444
|
+
|
|
445
|
+
const cleanedSchema = stripNestedIds(schema)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### getDefaultValidator()
|
|
449
|
+
|
|
450
|
+
Get or create the singleton AJV validator instance.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { getDefaultValidator } from '@kontract/adonis'
|
|
454
|
+
|
|
455
|
+
const validator = getDefaultValidator()
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### resetDefaultValidator()
|
|
459
|
+
|
|
460
|
+
Reset the default validator (useful for testing).
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { resetDefaultValidator } from '@kontract/adonis'
|
|
464
|
+
|
|
465
|
+
beforeEach(() => {
|
|
466
|
+
resetDefaultValidator()
|
|
467
|
+
})
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Integration Example
|
|
471
|
+
|
|
472
|
+
Complete example with AdonisJS:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// start/routes.ts
|
|
476
|
+
import router from '@adonisjs/core/services/router'
|
|
477
|
+
import { registerDecoratorRoutes, validate, OpenApiBuilder } from '@kontract/adonis'
|
|
478
|
+
|
|
479
|
+
// Import all controllers
|
|
480
|
+
const controllers = import.meta.glob('../app/controllers/**/*.ts', { eager: true })
|
|
481
|
+
|
|
482
|
+
// Register decorator-based routes
|
|
483
|
+
registerDecoratorRoutes(router, { validate })
|
|
484
|
+
|
|
485
|
+
// Serve OpenAPI spec
|
|
486
|
+
router.get('/docs/json', async ({ response }) => {
|
|
487
|
+
const builder = new OpenApiBuilder({
|
|
488
|
+
title: 'My API',
|
|
489
|
+
description: 'API documentation',
|
|
490
|
+
version: '1.0.0',
|
|
491
|
+
servers: [{ url: 'http://localhost:3333', description: 'Development' }],
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
return response.json(builder.build())
|
|
495
|
+
})
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Peer Dependencies
|
|
499
|
+
|
|
500
|
+
- `@adonisjs/core` ^6.0.0
|
|
501
|
+
- `@sinclair/typebox` >=0.32.0
|
|
502
|
+
- `ajv` ^8.0.0 (optional, required for validation)
|
|
503
|
+
- `@adonisjs/lucid` ^21.0.0 (optional, required for Lucid serializers)
|
|
504
|
+
|
|
505
|
+
## License
|
|
506
|
+
|
|
507
|
+
MIT
|