kontract 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.
Files changed (138) hide show
  1. package/README.md +435 -0
  2. package/dist/builder/define-controller.d.ts +115 -0
  3. package/dist/builder/define-controller.d.ts.map +1 -0
  4. package/dist/builder/define-controller.js +80 -0
  5. package/dist/builder/define-controller.js.map +1 -0
  6. package/dist/builder/define-endpoint.d.ts +157 -0
  7. package/dist/builder/define-endpoint.d.ts.map +1 -0
  8. package/dist/builder/define-endpoint.js +103 -0
  9. package/dist/builder/define-endpoint.js.map +1 -0
  10. package/dist/builder/define-route.d.ts +191 -0
  11. package/dist/builder/define-route.d.ts.map +1 -0
  12. package/dist/builder/define-route.js +124 -0
  13. package/dist/builder/define-route.js.map +1 -0
  14. package/dist/builder/index.d.ts +5 -0
  15. package/dist/builder/index.d.ts.map +1 -0
  16. package/dist/builder/index.js +7 -0
  17. package/dist/builder/index.js.map +1 -0
  18. package/dist/builder/openapi-builder.d.ts +120 -0
  19. package/dist/builder/openapi-builder.d.ts.map +1 -0
  20. package/dist/builder/openapi-builder.js +349 -0
  21. package/dist/builder/openapi-builder.js.map +1 -0
  22. package/dist/builder/path-params.d.ts +129 -0
  23. package/dist/builder/path-params.d.ts.map +1 -0
  24. package/dist/builder/path-params.js +85 -0
  25. package/dist/builder/path-params.js.map +1 -0
  26. package/dist/builder/types.d.ts +149 -0
  27. package/dist/builder/types.d.ts.map +1 -0
  28. package/dist/builder/types.js +6 -0
  29. package/dist/builder/types.js.map +1 -0
  30. package/dist/config/defaults.d.ts +10 -0
  31. package/dist/config/defaults.d.ts.map +1 -0
  32. package/dist/config/defaults.js +28 -0
  33. package/dist/config/defaults.js.map +1 -0
  34. package/dist/config/define-config.d.ts +50 -0
  35. package/dist/config/define-config.d.ts.map +1 -0
  36. package/dist/config/define-config.js +80 -0
  37. package/dist/config/define-config.js.map +1 -0
  38. package/dist/config/index.d.ts +4 -0
  39. package/dist/config/index.d.ts.map +1 -0
  40. package/dist/config/index.js +5 -0
  41. package/dist/config/index.js.map +1 -0
  42. package/dist/config/types.d.ts +103 -0
  43. package/dist/config/types.d.ts.map +1 -0
  44. package/dist/config/types.js +2 -0
  45. package/dist/config/types.js.map +1 -0
  46. package/dist/decorators/api.d.ts +35 -0
  47. package/dist/decorators/api.d.ts.map +1 -0
  48. package/dist/decorators/api.js +34 -0
  49. package/dist/decorators/api.js.map +1 -0
  50. package/dist/decorators/controller.d.ts +35 -0
  51. package/dist/decorators/controller.d.ts.map +1 -0
  52. package/dist/decorators/controller.js +34 -0
  53. package/dist/decorators/controller.js.map +1 -0
  54. package/dist/decorators/endpoint.d.ts +93 -0
  55. package/dist/decorators/endpoint.d.ts.map +1 -0
  56. package/dist/decorators/endpoint.js +108 -0
  57. package/dist/decorators/endpoint.js.map +1 -0
  58. package/dist/decorators/index.d.ts +5 -0
  59. package/dist/decorators/index.d.ts.map +1 -0
  60. package/dist/decorators/index.js +6 -0
  61. package/dist/decorators/index.js.map +1 -0
  62. package/dist/decorators/route.d.ts +93 -0
  63. package/dist/decorators/route.d.ts.map +1 -0
  64. package/dist/decorators/route.js +108 -0
  65. package/dist/decorators/route.js.map +1 -0
  66. package/dist/errors/base.d.ts +8 -0
  67. package/dist/errors/base.d.ts.map +1 -0
  68. package/dist/errors/base.js +13 -0
  69. package/dist/errors/base.js.map +1 -0
  70. package/dist/errors/configuration.d.ts +22 -0
  71. package/dist/errors/configuration.d.ts.map +1 -0
  72. package/dist/errors/configuration.js +33 -0
  73. package/dist/errors/configuration.js.map +1 -0
  74. package/dist/errors/index.d.ts +4 -0
  75. package/dist/errors/index.d.ts.map +1 -0
  76. package/dist/errors/index.js +4 -0
  77. package/dist/errors/index.js.map +1 -0
  78. package/dist/errors/validation.d.ts +46 -0
  79. package/dist/errors/validation.d.ts.map +1 -0
  80. package/dist/errors/validation.js +52 -0
  81. package/dist/errors/validation.js.map +1 -0
  82. package/dist/index.d.ts +48 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +44 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/metadata/index.d.ts +2 -0
  87. package/dist/metadata/index.d.ts.map +1 -0
  88. package/dist/metadata/index.js +2 -0
  89. package/dist/metadata/index.js.map +1 -0
  90. package/dist/metadata/storage.d.ts +50 -0
  91. package/dist/metadata/storage.d.ts.map +1 -0
  92. package/dist/metadata/storage.js +100 -0
  93. package/dist/metadata/storage.js.map +1 -0
  94. package/dist/metadata/types.d.ts +142 -0
  95. package/dist/metadata/types.d.ts.map +1 -0
  96. package/dist/metadata/types.js +2 -0
  97. package/dist/metadata/types.js.map +1 -0
  98. package/dist/response/helpers.d.ts +132 -0
  99. package/dist/response/helpers.d.ts.map +1 -0
  100. package/dist/response/helpers.js +197 -0
  101. package/dist/response/helpers.js.map +1 -0
  102. package/dist/response/index.d.ts +4 -0
  103. package/dist/response/index.d.ts.map +1 -0
  104. package/dist/response/index.js +4 -0
  105. package/dist/response/index.js.map +1 -0
  106. package/dist/response/types.d.ts +59 -0
  107. package/dist/response/types.d.ts.map +1 -0
  108. package/dist/response/types.js +26 -0
  109. package/dist/response/types.js.map +1 -0
  110. package/dist/runtime/adapter-types.d.ts +119 -0
  111. package/dist/runtime/adapter-types.d.ts.map +1 -0
  112. package/dist/runtime/adapter-types.js +2 -0
  113. package/dist/runtime/adapter-types.js.map +1 -0
  114. package/dist/runtime/index.d.ts +12 -0
  115. package/dist/runtime/index.d.ts.map +1 -0
  116. package/dist/runtime/index.js +10 -0
  117. package/dist/runtime/index.js.map +1 -0
  118. package/dist/runtime/response-helpers.d.ts +138 -0
  119. package/dist/runtime/response-helpers.d.ts.map +1 -0
  120. package/dist/runtime/response-helpers.js +105 -0
  121. package/dist/runtime/response-helpers.js.map +1 -0
  122. package/dist/runtime/route-utils.d.ts +22 -0
  123. package/dist/runtime/route-utils.d.ts.map +1 -0
  124. package/dist/runtime/route-utils.js +47 -0
  125. package/dist/runtime/route-utils.js.map +1 -0
  126. package/dist/runtime/types.d.ts +125 -0
  127. package/dist/runtime/types.d.ts.map +1 -0
  128. package/dist/runtime/types.js +2 -0
  129. package/dist/runtime/types.js.map +1 -0
  130. package/dist/validation/index.d.ts +3 -0
  131. package/dist/validation/index.d.ts.map +1 -0
  132. package/dist/validation/index.js +3 -0
  133. package/dist/validation/index.js.map +1 -0
  134. package/dist/validation/types.d.ts +55 -0
  135. package/dist/validation/types.d.ts.map +1 -0
  136. package/dist/validation/types.js +2 -0
  137. package/dist/validation/types.js.map +1 -0
  138. package/package.json +93 -0
package/README.md ADDED
@@ -0,0 +1,435 @@
1
+ # kontract
2
+
3
+ Framework-agnostic OpenAPI decorator system with TypeBox schema support.
4
+
5
+ ## Features
6
+
7
+ - **Unified `@Endpoint` decorator** - Define route, auth, schemas, and responses in one place
8
+ - **Type-safe response helpers** - `ok()`, `created()`, `notFound()` with compile-time validation
9
+ - **Framework-agnostic** - Core library has no framework dependencies
10
+ - **OpenAPI 3.1.0 & 3.0.3** - Generate specs for either version
11
+ - **TypeBox integration** - Native support for TypeBox schemas
12
+
13
+ ## Installation
14
+
15
+ **For AdonisJS projects**, use the adapter package instead (it includes this package):
16
+ ```bash
17
+ npm install @kontract/adonis @sinclair/typebox ajv ajv-formats
18
+ ```
19
+
20
+ **For other frameworks** or custom integrations:
21
+ ```bash
22
+ npm install kontract @sinclair/typebox
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { Api, Endpoint, ok, apiError } from 'kontract'
29
+ import { Type, Static } from '@sinclair/typebox'
30
+
31
+ // 1. Define your schemas
32
+ const User = Type.Object({
33
+ id: Type.Number(),
34
+ name: Type.String(),
35
+ email: Type.String({ format: 'email' }),
36
+ }, { $id: 'User' })
37
+
38
+ type UserType = Static<typeof User>
39
+
40
+ // 2. Decorate your controllers
41
+ @Api({ tag: 'Users', description: 'User management endpoints' })
42
+ class UsersController {
43
+ @Endpoint('GET /api/v1/users/:id', {
44
+ summary: 'Get a user by ID',
45
+ params: Type.Object({ id: Type.String() }),
46
+ responses: {
47
+ 200: { schema: User, description: 'The user' },
48
+ 404: null,
49
+ },
50
+ })
51
+ async show(ctx: unknown, body: unknown, query: unknown, params: { id: string }) {
52
+ const user = await findUser(params.id)
53
+ if (!user) {
54
+ return apiError.notFound('User not found')
55
+ }
56
+ return ok(User, user)
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Decorators
62
+
63
+ ### `@Api(options)`
64
+
65
+ Class decorator for controllers. Groups endpoints under an OpenAPI tag.
66
+
67
+ ```typescript
68
+ interface ApiOptions {
69
+ tag: string // OpenAPI tag for grouping endpoints
70
+ description?: string // Description of the API group
71
+ prefix?: string // Optional path prefix for all endpoints
72
+ }
73
+ ```
74
+
75
+ **Example:**
76
+
77
+ ```typescript
78
+ @Api({
79
+ tag: 'Books',
80
+ description: 'Book management endpoints',
81
+ prefix: '/api/v1'
82
+ })
83
+ class BooksController { }
84
+ ```
85
+
86
+ ### `@Endpoint(route, options)`
87
+
88
+ Method decorator for endpoints. Defines the route, validation schemas, and OpenAPI documentation.
89
+
90
+ ```typescript
91
+ interface EndpointOptions {
92
+ summary?: string // Short summary for OpenAPI docs
93
+ description?: string // Detailed description
94
+ operationId?: string // Unique operation ID (auto-generated if not provided)
95
+ deprecated?: boolean // Mark endpoint as deprecated
96
+ auth?: 'required' | 'optional' | 'none' // Authentication requirement
97
+ body?: TSchema // Request body schema (TypeBox)
98
+ query?: TSchema // Query parameters schema
99
+ params?: TSchema // Path parameters schema
100
+ file?: FileUploadConfig // File upload configuration
101
+ responses: Record<number, TSchema | null | { schema: TSchema | null; description?: string }>
102
+ middleware?: unknown[] // Framework-specific middleware
103
+ }
104
+ ```
105
+
106
+ **Examples:**
107
+
108
+ ```typescript
109
+ // GET with path parameters
110
+ @Endpoint('GET /api/v1/books/:id', {
111
+ summary: 'Get a book by ID',
112
+ params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
113
+ responses: {
114
+ 200: { schema: Book, description: 'The book' },
115
+ 404: null,
116
+ },
117
+ })
118
+ async show() { }
119
+
120
+ // POST with body and authentication
121
+ @Endpoint('POST /api/v1/books', {
122
+ summary: 'Create a new book',
123
+ auth: 'required',
124
+ body: CreateBookRequest,
125
+ responses: {
126
+ 201: { schema: Book, description: 'Book created' },
127
+ 422: { schema: ValidationError },
128
+ },
129
+ })
130
+ async store() { }
131
+
132
+ // GET with query parameters
133
+ @Endpoint('GET /api/v1/books', {
134
+ summary: 'List books',
135
+ query: Type.Object({
136
+ page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
137
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 20 })),
138
+ search: Type.Optional(Type.String()),
139
+ }),
140
+ responses: {
141
+ 200: { schema: BookListResponse },
142
+ },
143
+ })
144
+ async index() { }
145
+
146
+ // DELETE with no response body
147
+ @Endpoint('DELETE /api/v1/books/:id', {
148
+ summary: 'Delete a book',
149
+ auth: 'required',
150
+ params: Type.Object({ id: Type.String() }),
151
+ responses: {
152
+ 204: null,
153
+ 404: null,
154
+ },
155
+ })
156
+ async destroy() { }
157
+
158
+ // File upload
159
+ @Endpoint('POST /api/v1/books/:id/cover', {
160
+ summary: 'Upload book cover',
161
+ auth: 'required',
162
+ file: { fieldName: 'cover', multiple: false },
163
+ responses: {
164
+ 200: { schema: Book },
165
+ },
166
+ })
167
+ async uploadCover() { }
168
+ ```
169
+
170
+ ## Response Helpers
171
+
172
+ Response helpers create typed API responses. They return a structured object with `status` and `data` properties.
173
+
174
+ ### Success Responses
175
+
176
+ ```typescript
177
+ import { ok, created, accepted, noContent } from 'kontract'
178
+
179
+ // 200 OK
180
+ return ok(UserSchema, { id: 1, name: 'John' })
181
+
182
+ // 201 Created
183
+ return created(UserSchema, { id: 1, name: 'John' })
184
+
185
+ // 202 Accepted
186
+ return accepted(JobSchema, { jobId: 'abc123' })
187
+
188
+ // 204 No Content
189
+ return noContent()
190
+ ```
191
+
192
+ ### Error Responses
193
+
194
+ For full control over error response data:
195
+
196
+ ```typescript
197
+ import {
198
+ badRequest,
199
+ unauthorized,
200
+ forbidden,
201
+ notFound,
202
+ conflict,
203
+ unprocessableEntity,
204
+ tooManyRequests,
205
+ internalServerError,
206
+ badGateway,
207
+ serviceUnavailable
208
+ } from 'kontract'
209
+
210
+ // All error helpers follow the same pattern: (schema, data)
211
+ return notFound(ErrorSchema, { message: 'Book not found' })
212
+ return unauthorized(ErrorSchema, { message: 'Invalid token' })
213
+ ```
214
+
215
+ ### Unified `apiError` Helper
216
+
217
+ For common error patterns with sensible defaults. Uses a standard `ApiErrorBody` structure:
218
+
219
+ ```typescript
220
+ import { apiError } from 'kontract'
221
+
222
+ // Use defaults
223
+ return apiError.notFound() // "Resource not found"
224
+ return apiError.unauthorized() // "Authentication required"
225
+ return apiError.forbidden() // "Access denied"
226
+
227
+ // Override message
228
+ return apiError.notFound('Book not found')
229
+ return apiError.serviceUnavailable('External API is down')
230
+
231
+ // Validation errors with field details
232
+ return apiError.validation([
233
+ { field: 'email', message: 'Invalid email format' },
234
+ { field: 'age', message: 'Must be a positive number' },
235
+ ])
236
+ ```
237
+
238
+ Available methods:
239
+ | Method | Status | Default Message |
240
+ |--------|--------|-----------------|
241
+ | `apiError.badRequest(msg?)` | 400 | "Bad request" |
242
+ | `apiError.unauthorized(msg?)` | 401 | "Authentication required" |
243
+ | `apiError.forbidden(msg?)` | 403 | "Access denied" |
244
+ | `apiError.notFound(msg?)` | 404 | "Resource not found" |
245
+ | `apiError.conflict(msg?)` | 409 | "Resource conflict" |
246
+ | `apiError.validation(errors)` | 422 | "Validation failed" |
247
+ | `apiError.rateLimited(msg?)` | 429 | "Too many requests" |
248
+ | `apiError.internal(msg?)` | 500 | "Internal server error" |
249
+ | `apiError.serviceUnavailable(msg?)` | 503 | "Service unavailable" |
250
+ | `apiError.externalApi(msg?)` | 502 | "External API error" |
251
+
252
+ ### Binary Responses
253
+
254
+ For file downloads:
255
+
256
+ ```typescript
257
+ import { binary } from 'kontract'
258
+
259
+ // Return a file download
260
+ return binary(200, 'application/pdf', pdfBuffer, 'report.pdf')
261
+ return binary(200, 'image/png', imageBuffer)
262
+ ```
263
+
264
+ ## Metadata Access
265
+
266
+ Access registered decorator metadata programmatically:
267
+
268
+ ```typescript
269
+ import {
270
+ getRegisteredControllers,
271
+ getApiMetadata,
272
+ getEndpointMetadata,
273
+ getControllerMetadata,
274
+ getAllControllerMetadata,
275
+ clearRegistry,
276
+ } from 'kontract'
277
+
278
+ // Get all registered controllers
279
+ const controllers = getRegisteredControllers()
280
+
281
+ // Get @Api metadata for a specific controller
282
+ const apiMeta = getApiMetadata(UsersController)
283
+ // { tag: 'Users', description: 'User management' }
284
+
285
+ // Get all @Endpoint metadata for a controller
286
+ const endpoints = getEndpointMetadata(UsersController)
287
+ // EndpointMetadata[]
288
+
289
+ // Get combined metadata
290
+ const metadata = getControllerMetadata(UsersController)
291
+ // { controller, api: ApiMetadata, endpoints: EndpointMetadata[] }
292
+
293
+ // Get all metadata at once
294
+ const allMetadata = getAllControllerMetadata()
295
+ // Array of { controller, api, endpoints }
296
+
297
+ // Clear registry (useful for testing)
298
+ clearRegistry()
299
+ ```
300
+
301
+ ## Error Classes
302
+
303
+ ### RequestValidationError
304
+
305
+ Thrown when request validation fails:
306
+
307
+ ```typescript
308
+ import { RequestValidationError } from 'kontract'
309
+
310
+ try {
311
+ validate(schema, data)
312
+ } catch (error) {
313
+ if (error instanceof RequestValidationError) {
314
+ error.status // 422
315
+ error.code // 'E_VALIDATION_ERROR'
316
+ error.errors // [{ field: 'email', message: '...' }]
317
+ error.source // 'body' | 'query' | 'params'
318
+ error.schema // The TypeBox schema that failed
319
+ error.data // The data that was validated
320
+
321
+ // Get response-ready format
322
+ const response = error.toResponse()
323
+ // { status: 422, code: 'E_VALIDATION_ERROR', message: 'Validation failed', errors: [...] }
324
+ }
325
+ }
326
+ ```
327
+
328
+ ### ResponseValidationError
329
+
330
+ Thrown when response validation fails (development mode only):
331
+
332
+ ```typescript
333
+ import { ResponseValidationError } from 'kontract'
334
+ ```
335
+
336
+ ### Configuration Errors
337
+
338
+ ```typescript
339
+ import {
340
+ ConfigurationError, // General configuration issue
341
+ AdapterNotFoundError, // Framework adapter not found
342
+ SerializerNotFoundError // Serializer not found for data type
343
+ } from 'kontract'
344
+ ```
345
+
346
+ ## Types
347
+
348
+ ### Response Types
349
+
350
+ ```typescript
351
+ import type {
352
+ ApiResponse, // { status: number, data: T }
353
+ BinaryResponse, // { status: number, binary: true, contentType: string, data: Buffer, filename?: string }
354
+ AnyResponse, // ApiResponse | BinaryResponse
355
+ ApiErrorBody, // { status: number, code: string, message: string, errors?: [...] }
356
+ ErrorCode, // Error code string type
357
+ } from 'kontract'
358
+
359
+ import { ErrorCodes, isBinaryResponse } from 'kontract'
360
+
361
+ // Check if response is binary
362
+ if (isBinaryResponse(result)) {
363
+ // result.contentType, result.data, result.filename
364
+ }
365
+ ```
366
+
367
+ ### Metadata Types
368
+
369
+ ```typescript
370
+ import type {
371
+ HttpMethod, // 'get' | 'post' | 'put' | 'patch' | 'delete'
372
+ AuthLevel, // 'required' | 'optional' | 'none'
373
+ RouteString, // 'GET /path' format
374
+ ResponseDefinition, // { schema: TSchema | null, description?: string }
375
+ FileUploadConfig, // { fieldName: string, multiple?: boolean }
376
+ ApiMetadata, // @Api decorator metadata
377
+ EndpointMetadata, // @Endpoint decorator metadata
378
+ ControllerMetadata, // Combined controller metadata
379
+ } from 'kontract'
380
+ ```
381
+
382
+ ### OpenAPI Types
383
+
384
+ ```typescript
385
+ import type {
386
+ OpenApiVersion, // '3.0.3' | '3.1.0'
387
+ OpenApiDocument, // Full OpenAPI specification document
388
+ OpenApiPathItem, // Path item object
389
+ OpenApiOperation, // Operation object
390
+ OpenApiParameter, // Parameter object
391
+ OpenApiRequestBody, // Request body object
392
+ OpenApiResponse, // Response object
393
+ OpenApiMediaType, // Media type object
394
+ OpenApiSchema, // Schema object
395
+ OpenApiSecurityScheme, // Security scheme object
396
+ } from 'kontract'
397
+ ```
398
+
399
+ ### Validation Types
400
+
401
+ ```typescript
402
+ import type {
403
+ Validator, // Validator interface
404
+ CompiledValidator, // Pre-compiled validator for performance
405
+ ValidatorOptions, // Validator configuration options
406
+ ValidationErrorDetail, // { field: string, message: string }
407
+ } from 'kontract'
408
+ ```
409
+
410
+ ### Runtime/Adapter Types
411
+
412
+ ```typescript
413
+ import type {
414
+ RequestContext, // Generic request context
415
+ AuthUser, // Authenticated user type
416
+ AuthResult, // Authentication result
417
+ RouteHandler, // Route handler function
418
+ RouterAdapter, // Router adapter interface
419
+ AuthAdapter, // Authentication adapter interface
420
+ ContainerAdapter, // DI container adapter interface
421
+ ResponseAdapter, // Response adapter interface
422
+ LoggerAdapter, // Logger adapter interface
423
+ FrameworkAdapters, // All adapters combined
424
+ } from 'kontract'
425
+ ```
426
+
427
+ ## Framework Adapters
428
+
429
+ This is the core library. For framework-specific implementations, see:
430
+
431
+ - **AdonisJS**: [`@kontract/adonis`](../kontract-adonis)
432
+
433
+ ## License
434
+
435
+ MIT
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Controller builder for grouping routes under an OpenAPI tag.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * const usersController = defineController({
7
+ * tag: 'Users',
8
+ * description: 'User management routes',
9
+ * }, {
10
+ * getUser,
11
+ * createUser,
12
+ * updateUser,
13
+ * deleteUser,
14
+ * })
15
+ * ```
16
+ */
17
+ import type { TSchema } from '@sinclair/typebox';
18
+ import type { RouteDefinition } from './define-route.js';
19
+ /**
20
+ * Configuration for defineController.
21
+ */
22
+ export interface ControllerConfig {
23
+ /** OpenAPI tag for grouping routes */
24
+ tag: string;
25
+ /** Description of the controller/API group */
26
+ description?: string;
27
+ /** Optional path prefix for all routes */
28
+ prefix?: string;
29
+ }
30
+ /**
31
+ * Any route definition regardless of its generic parameters.
32
+ * Used in controller definitions to allow mixed route types.
33
+ */
34
+ export type AnyRouteDefinition = RouteDefinition<string, TSchema | undefined, TSchema | undefined, TSchema | undefined>;
35
+ /**
36
+ * A record of route definitions.
37
+ */
38
+ export type RouteRecord = Record<string, AnyRouteDefinition>;
39
+ /**
40
+ * Controller definition returned by defineController.
41
+ */
42
+ export interface ControllerDefinition<T extends RouteRecord = RouteRecord> {
43
+ /** Type discriminator */
44
+ readonly __type: 'controller';
45
+ /** Controller configuration (tag, description, prefix) */
46
+ readonly config: ControllerConfig;
47
+ /** Map of route names to definitions */
48
+ readonly routes: T;
49
+ }
50
+ /**
51
+ * Define a controller that groups routes under an OpenAPI tag.
52
+ *
53
+ * Controllers provide organizational structure for your API:
54
+ * - Group related routes together
55
+ * - Apply a common tag for OpenAPI documentation
56
+ * - Optionally apply a path prefix to all routes
57
+ *
58
+ * @param config - Controller configuration
59
+ * @param routes - Record of route definitions
60
+ * @returns ControllerDefinition for registration with framework adapters
61
+ *
62
+ * @example Basic controller
63
+ * ```typescript
64
+ * import { defineController } from 'kontract'
65
+ * import { getUser, createUser, updateUser, deleteUser } from './user-routes.js'
66
+ *
67
+ * export const usersController = defineController({
68
+ * tag: 'Users',
69
+ * description: 'User management routes',
70
+ * }, {
71
+ * getUser,
72
+ * createUser,
73
+ * updateUser,
74
+ * deleteUser,
75
+ * })
76
+ * ```
77
+ *
78
+ * @example With path prefix
79
+ * ```typescript
80
+ * export const adminController = defineController({
81
+ * tag: 'Admin',
82
+ * description: 'Administrative routes',
83
+ * prefix: '/admin',
84
+ * }, {
85
+ * listUsers: defineRoute({ route: 'GET /users', ... }, ...),
86
+ * // Actual path will be /admin/users
87
+ * })
88
+ * ```
89
+ *
90
+ * @example Registration with adapters
91
+ * ```typescript
92
+ * // Fastify
93
+ * import { registerController } from '@kontract/fastify'
94
+ * registerController(app, usersController, options)
95
+ *
96
+ * // Hono
97
+ * import { registerController } from '@kontract/hono'
98
+ * registerController(app, usersController, options)
99
+ * ```
100
+ */
101
+ export declare function defineController<T extends RouteRecord>(config: ControllerConfig, routes: T): ControllerDefinition<T>;
102
+ /**
103
+ * Type guard to check if a value is a ControllerDefinition.
104
+ */
105
+ export declare function isControllerDefinition(value: unknown): value is ControllerDefinition;
106
+ /**
107
+ * Get all routes from a controller with their full paths.
108
+ * Applies the controller's prefix if configured.
109
+ */
110
+ export declare function getControllerRoutes(controller: ControllerDefinition): Array<{
111
+ name: string;
112
+ route: AnyRouteDefinition;
113
+ fullPath: string;
114
+ }>;
115
+ //# sourceMappingURL=define-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-controller.d.ts","sourceRoot":"","sources":["../../src/builder/define-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC,CAAA;AAEvH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;AAE5D;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACvE,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;IACjC,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EACpD,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,CAAC,GACR,oBAAoB,CAAC,CAAC,CAAC,CAMzB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAOpF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,oBAAoB,GAC/B,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAQtE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Define a controller that groups routes under an OpenAPI tag.
3
+ *
4
+ * Controllers provide organizational structure for your API:
5
+ * - Group related routes together
6
+ * - Apply a common tag for OpenAPI documentation
7
+ * - Optionally apply a path prefix to all routes
8
+ *
9
+ * @param config - Controller configuration
10
+ * @param routes - Record of route definitions
11
+ * @returns ControllerDefinition for registration with framework adapters
12
+ *
13
+ * @example Basic controller
14
+ * ```typescript
15
+ * import { defineController } from 'kontract'
16
+ * import { getUser, createUser, updateUser, deleteUser } from './user-routes.js'
17
+ *
18
+ * export const usersController = defineController({
19
+ * tag: 'Users',
20
+ * description: 'User management routes',
21
+ * }, {
22
+ * getUser,
23
+ * createUser,
24
+ * updateUser,
25
+ * deleteUser,
26
+ * })
27
+ * ```
28
+ *
29
+ * @example With path prefix
30
+ * ```typescript
31
+ * export const adminController = defineController({
32
+ * tag: 'Admin',
33
+ * description: 'Administrative routes',
34
+ * prefix: '/admin',
35
+ * }, {
36
+ * listUsers: defineRoute({ route: 'GET /users', ... }, ...),
37
+ * // Actual path will be /admin/users
38
+ * })
39
+ * ```
40
+ *
41
+ * @example Registration with adapters
42
+ * ```typescript
43
+ * // Fastify
44
+ * import { registerController } from '@kontract/fastify'
45
+ * registerController(app, usersController, options)
46
+ *
47
+ * // Hono
48
+ * import { registerController } from '@kontract/hono'
49
+ * registerController(app, usersController, options)
50
+ * ```
51
+ */
52
+ export function defineController(config, routes) {
53
+ return {
54
+ __type: 'controller',
55
+ config,
56
+ routes,
57
+ };
58
+ }
59
+ /**
60
+ * Type guard to check if a value is a ControllerDefinition.
61
+ */
62
+ export function isControllerDefinition(value) {
63
+ return (typeof value === 'object'
64
+ && value !== null
65
+ && '__type' in value
66
+ && value.__type === 'controller');
67
+ }
68
+ /**
69
+ * Get all routes from a controller with their full paths.
70
+ * Applies the controller's prefix if configured.
71
+ */
72
+ export function getControllerRoutes(controller) {
73
+ const prefix = controller.config.prefix ?? '';
74
+ return Object.entries(controller.routes).map(([name, route]) => ({
75
+ name,
76
+ route,
77
+ fullPath: prefix + route.path,
78
+ }));
79
+ }
80
+ //# sourceMappingURL=define-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-controller.js","sourceRoot":"","sources":["../../src/builder/define-controller.ts"],"names":[],"mappings":"AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAwB,EACxB,MAAS;IAET,OAAO;QACL,MAAM,EAAE,YAAY;QACpB,MAAM;QACN,MAAM;KACP,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;WACtB,KAAK,KAAK,IAAI;WACd,QAAQ,IAAI,KAAK;WAChB,KAA6B,CAAC,MAAM,KAAK,YAAY,CAC1D,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,UAAgC;IAEhC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAA;IAE7C,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI;QACJ,KAAK;QACL,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,IAAI;KAC9B,CAAC,CAAC,CAAA;AACL,CAAC"}