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.
- package/README.md +435 -0
- package/dist/builder/define-controller.d.ts +115 -0
- package/dist/builder/define-controller.d.ts.map +1 -0
- package/dist/builder/define-controller.js +80 -0
- package/dist/builder/define-controller.js.map +1 -0
- package/dist/builder/define-endpoint.d.ts +157 -0
- package/dist/builder/define-endpoint.d.ts.map +1 -0
- package/dist/builder/define-endpoint.js +103 -0
- package/dist/builder/define-endpoint.js.map +1 -0
- package/dist/builder/define-route.d.ts +191 -0
- package/dist/builder/define-route.d.ts.map +1 -0
- package/dist/builder/define-route.js +124 -0
- package/dist/builder/define-route.js.map +1 -0
- package/dist/builder/index.d.ts +5 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +7 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/openapi-builder.d.ts +120 -0
- package/dist/builder/openapi-builder.d.ts.map +1 -0
- package/dist/builder/openapi-builder.js +349 -0
- package/dist/builder/openapi-builder.js.map +1 -0
- package/dist/builder/path-params.d.ts +129 -0
- package/dist/builder/path-params.d.ts.map +1 -0
- package/dist/builder/path-params.js +85 -0
- package/dist/builder/path-params.js.map +1 -0
- package/dist/builder/types.d.ts +149 -0
- package/dist/builder/types.d.ts.map +1 -0
- package/dist/builder/types.js +6 -0
- package/dist/builder/types.js.map +1 -0
- package/dist/config/defaults.d.ts +10 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +28 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/define-config.d.ts +50 -0
- package/dist/config/define-config.d.ts.map +1 -0
- package/dist/config/define-config.js +80 -0
- package/dist/config/define-config.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +103 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/decorators/api.d.ts +35 -0
- package/dist/decorators/api.d.ts.map +1 -0
- package/dist/decorators/api.js +34 -0
- package/dist/decorators/api.js.map +1 -0
- package/dist/decorators/controller.d.ts +35 -0
- package/dist/decorators/controller.d.ts.map +1 -0
- package/dist/decorators/controller.js +34 -0
- package/dist/decorators/controller.js.map +1 -0
- package/dist/decorators/endpoint.d.ts +93 -0
- package/dist/decorators/endpoint.d.ts.map +1 -0
- package/dist/decorators/endpoint.js +108 -0
- package/dist/decorators/endpoint.js.map +1 -0
- package/dist/decorators/index.d.ts +5 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +6 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/route.d.ts +93 -0
- package/dist/decorators/route.d.ts.map +1 -0
- package/dist/decorators/route.js +108 -0
- package/dist/decorators/route.js.map +1 -0
- package/dist/errors/base.d.ts +8 -0
- package/dist/errors/base.d.ts.map +1 -0
- package/dist/errors/base.js +13 -0
- package/dist/errors/base.js.map +1 -0
- package/dist/errors/configuration.d.ts +22 -0
- package/dist/errors/configuration.d.ts.map +1 -0
- package/dist/errors/configuration.js +33 -0
- package/dist/errors/configuration.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/validation.d.ts +46 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +52 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata/index.d.ts +2 -0
- package/dist/metadata/index.d.ts.map +1 -0
- package/dist/metadata/index.js +2 -0
- package/dist/metadata/index.js.map +1 -0
- package/dist/metadata/storage.d.ts +50 -0
- package/dist/metadata/storage.d.ts.map +1 -0
- package/dist/metadata/storage.js +100 -0
- package/dist/metadata/storage.js.map +1 -0
- package/dist/metadata/types.d.ts +142 -0
- package/dist/metadata/types.d.ts.map +1 -0
- package/dist/metadata/types.js +2 -0
- package/dist/metadata/types.js.map +1 -0
- package/dist/response/helpers.d.ts +132 -0
- package/dist/response/helpers.d.ts.map +1 -0
- package/dist/response/helpers.js +197 -0
- package/dist/response/helpers.js.map +1 -0
- package/dist/response/index.d.ts +4 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +4 -0
- package/dist/response/index.js.map +1 -0
- package/dist/response/types.d.ts +59 -0
- package/dist/response/types.d.ts.map +1 -0
- package/dist/response/types.js +26 -0
- package/dist/response/types.js.map +1 -0
- package/dist/runtime/adapter-types.d.ts +119 -0
- package/dist/runtime/adapter-types.d.ts.map +1 -0
- package/dist/runtime/adapter-types.js +2 -0
- package/dist/runtime/adapter-types.js.map +1 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/response-helpers.d.ts +138 -0
- package/dist/runtime/response-helpers.d.ts.map +1 -0
- package/dist/runtime/response-helpers.js +105 -0
- package/dist/runtime/response-helpers.js.map +1 -0
- package/dist/runtime/route-utils.d.ts +22 -0
- package/dist/runtime/route-utils.d.ts.map +1 -0
- package/dist/runtime/route-utils.js +47 -0
- package/dist/runtime/route-utils.js.map +1 -0
- package/dist/runtime/types.d.ts +125 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/validation/index.d.ts +3 -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/dist/validation/types.d.ts +55 -0
- package/dist/validation/types.d.ts.map +1 -0
- package/dist/validation/types.js +2 -0
- package/dist/validation/types.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe endpoint builder with automatic type inference.
|
|
3
|
+
*
|
|
4
|
+
* Unlike decorators, this function-based API provides full TypeScript
|
|
5
|
+
* type inference for body, query, and params without manual annotations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const createUser = defineEndpoint({
|
|
10
|
+
* route: 'POST /api/users',
|
|
11
|
+
* body: CreateUserRequest,
|
|
12
|
+
* responses: { 201: { schema: User } },
|
|
13
|
+
* }, async ({ body }) => {
|
|
14
|
+
* // body is automatically typed as Static<typeof CreateUserRequest>
|
|
15
|
+
* return created(User, await db.users.create(body))
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { TSchema, Static } from '@sinclair/typebox';
|
|
20
|
+
import type { AnyResponse } from '../response/types.js';
|
|
21
|
+
import type { HttpMethod, AuthLevel, ResponseDefinition } from '../metadata/types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Route string format: "METHOD /path"
|
|
24
|
+
*/
|
|
25
|
+
type RouteString = `${'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'} /${string}`;
|
|
26
|
+
/**
|
|
27
|
+
* Response configuration for endpoints.
|
|
28
|
+
*/
|
|
29
|
+
type ResponsesConfig = Record<number, TSchema | null | {
|
|
30
|
+
schema: TSchema | null;
|
|
31
|
+
description?: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for defineEndpoint.
|
|
35
|
+
*/
|
|
36
|
+
export interface EndpointConfig<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
37
|
+
/** Route in format "METHOD /path" (e.g., "POST /api/users") */
|
|
38
|
+
route: RouteString;
|
|
39
|
+
/** Short summary for OpenAPI docs */
|
|
40
|
+
summary?: string;
|
|
41
|
+
/** Detailed description for OpenAPI docs */
|
|
42
|
+
description?: string;
|
|
43
|
+
/** Unique operation ID for OpenAPI */
|
|
44
|
+
operationId?: string;
|
|
45
|
+
/** Mark endpoint as deprecated */
|
|
46
|
+
deprecated?: boolean;
|
|
47
|
+
/** Authentication requirement */
|
|
48
|
+
auth?: AuthLevel;
|
|
49
|
+
/** Request body schema */
|
|
50
|
+
body?: TBody;
|
|
51
|
+
/** Query parameters schema */
|
|
52
|
+
query?: TQuery;
|
|
53
|
+
/** Path parameters schema */
|
|
54
|
+
params?: TParams;
|
|
55
|
+
/** Response definitions by status code */
|
|
56
|
+
responses: ResponsesConfig;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Context object passed to endpoint handlers.
|
|
60
|
+
* Types are automatically inferred from the endpoint configuration.
|
|
61
|
+
*/
|
|
62
|
+
export interface HandlerContext<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
63
|
+
/** Validated request body (typed from body schema) */
|
|
64
|
+
body: TBody extends TSchema ? Static<TBody> : undefined;
|
|
65
|
+
/** Validated query parameters (typed from query schema) */
|
|
66
|
+
query: TQuery extends TSchema ? Static<TQuery> : undefined;
|
|
67
|
+
/** Validated path parameters (typed from params schema) */
|
|
68
|
+
params: TParams extends TSchema ? Static<TParams> : undefined;
|
|
69
|
+
/** Authenticated user (available when auth is 'required' or 'optional') */
|
|
70
|
+
user: unknown;
|
|
71
|
+
/** Raw framework request object (FastifyRequest, Hono Context, etc.) */
|
|
72
|
+
raw: unknown;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Handler function type with inferred parameter types.
|
|
76
|
+
*/
|
|
77
|
+
export type EndpointHandler<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> = (ctx: HandlerContext<TBody, TQuery, TParams>) => Promise<AnyResponse> | AnyResponse;
|
|
78
|
+
/**
|
|
79
|
+
* Endpoint definition returned by defineEndpoint.
|
|
80
|
+
* Contains configuration and handler for registration with adapters.
|
|
81
|
+
*/
|
|
82
|
+
export interface EndpointDefinition<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
83
|
+
/** Type discriminator */
|
|
84
|
+
readonly __type: 'endpoint';
|
|
85
|
+
/** Parsed HTTP method */
|
|
86
|
+
readonly method: HttpMethod;
|
|
87
|
+
/** Parsed path */
|
|
88
|
+
readonly path: string;
|
|
89
|
+
/** Original configuration */
|
|
90
|
+
readonly config: EndpointConfig<TBody, TQuery, TParams>;
|
|
91
|
+
/** Normalized response definitions */
|
|
92
|
+
readonly responses: Record<number, ResponseDefinition>;
|
|
93
|
+
/** Handler function */
|
|
94
|
+
readonly handler: EndpointHandler<TBody, TQuery, TParams>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Define a type-safe API endpoint with automatic type inference.
|
|
98
|
+
*
|
|
99
|
+
* This function-based approach provides full TypeScript type inference
|
|
100
|
+
* for request body, query parameters, and path parameters without
|
|
101
|
+
* requiring manual `Static<typeof Schema>` annotations.
|
|
102
|
+
*
|
|
103
|
+
* @param config - Endpoint configuration including route, schemas, and responses
|
|
104
|
+
* @param handler - Async function that handles the request
|
|
105
|
+
* @returns EndpointDefinition for registration with framework adapters
|
|
106
|
+
*
|
|
107
|
+
* @example Basic GET endpoint
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const getUser = defineEndpoint({
|
|
110
|
+
* route: 'GET /api/users/:id',
|
|
111
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
112
|
+
* responses: {
|
|
113
|
+
* 200: { schema: User },
|
|
114
|
+
* 404: null,
|
|
115
|
+
* },
|
|
116
|
+
* }, async ({ params }) => {
|
|
117
|
+
* // params.id is typed as string
|
|
118
|
+
* const user = await findUser(params.id)
|
|
119
|
+
* return user ? ok(User, user) : apiError.notFound()
|
|
120
|
+
* })
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @example POST with body and auth
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const createUser = defineEndpoint({
|
|
126
|
+
* route: 'POST /api/users',
|
|
127
|
+
* auth: 'required',
|
|
128
|
+
* body: CreateUserRequest,
|
|
129
|
+
* responses: { 201: { schema: User } },
|
|
130
|
+
* }, async ({ body, user }) => {
|
|
131
|
+
* // body is typed as { name: string; email: string }
|
|
132
|
+
* return created(User, await db.users.create(body))
|
|
133
|
+
* })
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example GET with query parameters
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const listUsers = defineEndpoint({
|
|
139
|
+
* route: 'GET /api/users',
|
|
140
|
+
* query: Type.Object({
|
|
141
|
+
* page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
|
|
142
|
+
* limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
143
|
+
* }),
|
|
144
|
+
* responses: { 200: { schema: UserList } },
|
|
145
|
+
* }, async ({ query }) => {
|
|
146
|
+
* // query.page and query.limit are typed
|
|
147
|
+
* return ok(UserList, await db.users.list(query))
|
|
148
|
+
* })
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare function defineEndpoint<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined>(config: EndpointConfig<TBody, TQuery, TParams>, handler: EndpointHandler<TBody, TQuery, TParams>): EndpointDefinition<TBody, TQuery, TParams>;
|
|
152
|
+
/**
|
|
153
|
+
* Type guard to check if a value is an EndpointDefinition.
|
|
154
|
+
*/
|
|
155
|
+
export declare function isEndpointDefinition(value: unknown): value is EndpointDefinition;
|
|
156
|
+
export {};
|
|
157
|
+
//# sourceMappingURL=define-endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-endpoint.d.ts","sourceRoot":"","sources":["../../src/builder/define-endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a route string into method and path.
|
|
3
|
+
*/
|
|
4
|
+
function parseRoute(route) {
|
|
5
|
+
const spaceIndex = route.indexOf(' ');
|
|
6
|
+
const method = route.slice(0, spaceIndex).toLowerCase();
|
|
7
|
+
const path = route.slice(spaceIndex + 1);
|
|
8
|
+
return [method, path];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalize response definitions to consistent format.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeResponses(responses) {
|
|
14
|
+
const normalized = {};
|
|
15
|
+
for (const [status, value] of Object.entries(responses)) {
|
|
16
|
+
if (value === null) {
|
|
17
|
+
normalized[Number(status)] = { schema: null };
|
|
18
|
+
}
|
|
19
|
+
else if (typeof value === 'object' && 'schema' in value) {
|
|
20
|
+
normalized[Number(status)] = value;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
normalized[Number(status)] = { schema: value };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Define a type-safe API endpoint with automatic type inference.
|
|
30
|
+
*
|
|
31
|
+
* This function-based approach provides full TypeScript type inference
|
|
32
|
+
* for request body, query parameters, and path parameters without
|
|
33
|
+
* requiring manual `Static<typeof Schema>` annotations.
|
|
34
|
+
*
|
|
35
|
+
* @param config - Endpoint configuration including route, schemas, and responses
|
|
36
|
+
* @param handler - Async function that handles the request
|
|
37
|
+
* @returns EndpointDefinition for registration with framework adapters
|
|
38
|
+
*
|
|
39
|
+
* @example Basic GET endpoint
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const getUser = defineEndpoint({
|
|
42
|
+
* route: 'GET /api/users/:id',
|
|
43
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
44
|
+
* responses: {
|
|
45
|
+
* 200: { schema: User },
|
|
46
|
+
* 404: null,
|
|
47
|
+
* },
|
|
48
|
+
* }, async ({ params }) => {
|
|
49
|
+
* // params.id is typed as string
|
|
50
|
+
* const user = await findUser(params.id)
|
|
51
|
+
* return user ? ok(User, user) : apiError.notFound()
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example POST with body and auth
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const createUser = defineEndpoint({
|
|
58
|
+
* route: 'POST /api/users',
|
|
59
|
+
* auth: 'required',
|
|
60
|
+
* body: CreateUserRequest,
|
|
61
|
+
* responses: { 201: { schema: User } },
|
|
62
|
+
* }, async ({ body, user }) => {
|
|
63
|
+
* // body is typed as { name: string; email: string }
|
|
64
|
+
* return created(User, await db.users.create(body))
|
|
65
|
+
* })
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example GET with query parameters
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const listUsers = defineEndpoint({
|
|
71
|
+
* route: 'GET /api/users',
|
|
72
|
+
* query: Type.Object({
|
|
73
|
+
* page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
|
|
74
|
+
* limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
75
|
+
* }),
|
|
76
|
+
* responses: { 200: { schema: UserList } },
|
|
77
|
+
* }, async ({ query }) => {
|
|
78
|
+
* // query.page and query.limit are typed
|
|
79
|
+
* return ok(UserList, await db.users.list(query))
|
|
80
|
+
* })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function defineEndpoint(config, handler) {
|
|
84
|
+
const [method, path] = parseRoute(config.route);
|
|
85
|
+
return {
|
|
86
|
+
__type: 'endpoint',
|
|
87
|
+
method,
|
|
88
|
+
path,
|
|
89
|
+
config,
|
|
90
|
+
responses: normalizeResponses(config.responses),
|
|
91
|
+
handler,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Type guard to check if a value is an EndpointDefinition.
|
|
96
|
+
*/
|
|
97
|
+
export function isEndpointDefinition(value) {
|
|
98
|
+
return (typeof value === 'object'
|
|
99
|
+
&& value !== null
|
|
100
|
+
&& '__type' in value
|
|
101
|
+
&& value.__type === 'endpoint');
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=define-endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-endpoint.js","sourceRoot":"","sources":["../../src/builder/define-endpoint.ts"],"names":[],"mappings":"AAmHA;;GAEG;AACH,SAAS,UAAU,CAAC,KAAkB;IACpC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,WAAW,EAAgB,CAAA;IACrE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;IACxC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACvB,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,SAA0B;IACpD,MAAM,UAAU,GAAuC,EAAE,CAAA;IAEzD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAC/C,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC1D,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAA2B,CAAA;QAC1D,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAgB,EAAE,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,UAAU,cAAc,CAK5B,MAA8C,EAC9C,OAAgD;IAEhD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAE/C,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,MAAM;QACN,IAAI;QACJ,MAAM;QACN,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC;QAC/C,OAAO;KACR,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;WACtB,KAAK,KAAK,IAAI;WACd,QAAQ,IAAI,KAAK;WAChB,KAA6B,CAAC,MAAM,KAAK,UAAU,CACxD,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe route builder with automatic type inference.
|
|
3
|
+
*
|
|
4
|
+
* Unlike decorators, this function-based API provides full TypeScript
|
|
5
|
+
* type inference for body, query, and params without manual annotations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const createUser = defineRoute({
|
|
10
|
+
* route: 'POST /api/users',
|
|
11
|
+
* body: CreateUserRequest,
|
|
12
|
+
* responses: { 201: { schema: User } },
|
|
13
|
+
* }, async ({ body }) => {
|
|
14
|
+
* // body is automatically typed as Static<typeof CreateUserRequest>
|
|
15
|
+
* return created(User, await db.users.create(body))
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { TSchema, Static } from '@sinclair/typebox';
|
|
20
|
+
import type { AnyResponse } from '../response/types.js';
|
|
21
|
+
import type { HttpMethod, AuthLevel, ResponseDefinition } from '../metadata/types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Route string format: "METHOD /path"
|
|
24
|
+
*/
|
|
25
|
+
type RouteString = `${'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'} /${string}`;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the path portion from a route string.
|
|
28
|
+
* @example ExtractPathFromRoute<'GET /users/:id'> = '/users/:id'
|
|
29
|
+
*/
|
|
30
|
+
type ExtractPathFromRoute<T extends string> = T extends `${string} ${infer Path}` ? Path : string;
|
|
31
|
+
/**
|
|
32
|
+
* Response configuration for routes.
|
|
33
|
+
*/
|
|
34
|
+
type ResponsesConfig = Record<number, TSchema | null | {
|
|
35
|
+
schema: TSchema | null;
|
|
36
|
+
description?: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Configuration for defineRoute.
|
|
40
|
+
*
|
|
41
|
+
* @typeParam TRoute - The route string type (e.g., 'GET /users/:id') for path param inference
|
|
42
|
+
*/
|
|
43
|
+
export interface RouteConfig<TRoute extends RouteString = RouteString, TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
44
|
+
/** Route in format "METHOD /path" (e.g., "POST /api/users") */
|
|
45
|
+
route: TRoute;
|
|
46
|
+
/** Short summary for OpenAPI docs */
|
|
47
|
+
summary?: string;
|
|
48
|
+
/** Detailed description for OpenAPI docs */
|
|
49
|
+
description?: string;
|
|
50
|
+
/** Unique operation ID for OpenAPI */
|
|
51
|
+
operationId?: string;
|
|
52
|
+
/** Mark route as deprecated */
|
|
53
|
+
deprecated?: boolean;
|
|
54
|
+
/** Authentication requirement */
|
|
55
|
+
auth?: AuthLevel;
|
|
56
|
+
/** Request body schema */
|
|
57
|
+
body?: TBody;
|
|
58
|
+
/** Query parameters schema */
|
|
59
|
+
query?: TQuery;
|
|
60
|
+
/** Path parameters schema */
|
|
61
|
+
params?: TParams;
|
|
62
|
+
/** Response definitions by status code */
|
|
63
|
+
responses: ResponsesConfig;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Context object passed to route handlers.
|
|
67
|
+
* Types are automatically inferred from the route configuration.
|
|
68
|
+
*/
|
|
69
|
+
export interface HandlerContext<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
70
|
+
/** Validated request body (typed from body schema) */
|
|
71
|
+
body: TBody extends TSchema ? Static<TBody> : undefined;
|
|
72
|
+
/** Validated query parameters (typed from query schema) */
|
|
73
|
+
query: TQuery extends TSchema ? Static<TQuery> : undefined;
|
|
74
|
+
/** Validated path parameters (typed from params schema) */
|
|
75
|
+
params: TParams extends TSchema ? Static<TParams> : undefined;
|
|
76
|
+
/** Authenticated user (available when auth is 'required' or 'optional') */
|
|
77
|
+
user: unknown;
|
|
78
|
+
/** Raw framework request object (FastifyRequest, Hono Context, etc.) */
|
|
79
|
+
raw: unknown;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handler function type with inferred parameter types.
|
|
83
|
+
*/
|
|
84
|
+
export type RouteHandler<TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> = (ctx: HandlerContext<TBody, TQuery, TParams>) => Promise<AnyResponse> | AnyResponse;
|
|
85
|
+
/**
|
|
86
|
+
* Route definition returned by defineRoute.
|
|
87
|
+
* Contains configuration and optionally a handler for registration with adapters.
|
|
88
|
+
*
|
|
89
|
+
* When used without a handler, the route is a "contract" that can be shared
|
|
90
|
+
* with clients for type-safe API calls.
|
|
91
|
+
*
|
|
92
|
+
* @typeParam TPath - The route path as a literal string type (e.g., '/users/:id')
|
|
93
|
+
* Used for path parameter inference when TParams is not explicitly provided.
|
|
94
|
+
*/
|
|
95
|
+
export interface RouteDefinition<TPath extends string = string, TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined> {
|
|
96
|
+
/** Type discriminator */
|
|
97
|
+
readonly __type: 'route';
|
|
98
|
+
/** Parsed HTTP method */
|
|
99
|
+
readonly method: HttpMethod;
|
|
100
|
+
/** Parsed path (typed as literal for param inference) */
|
|
101
|
+
readonly path: TPath;
|
|
102
|
+
/** Original configuration */
|
|
103
|
+
readonly config: RouteConfig<RouteString, TBody, TQuery, TParams>;
|
|
104
|
+
/** Normalized response definitions */
|
|
105
|
+
readonly responses: Record<number, ResponseDefinition>;
|
|
106
|
+
/** Handler function (optional - omitted for contract-only definitions) */
|
|
107
|
+
readonly handler?: RouteHandler<TBody, TQuery, TParams>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Define a type-safe API route with automatic type inference.
|
|
111
|
+
*
|
|
112
|
+
* This function-based approach provides full TypeScript type inference
|
|
113
|
+
* for request body, query parameters, and path parameters without
|
|
114
|
+
* requiring manual `Static<typeof Schema>` annotations.
|
|
115
|
+
*
|
|
116
|
+
* @param config - Route configuration including path, schemas, and responses
|
|
117
|
+
* @param handler - Optional async function that handles the request.
|
|
118
|
+
* When omitted, creates a "contract-only" definition that can be shared with clients.
|
|
119
|
+
* @returns RouteDefinition for registration with framework adapters or client generation
|
|
120
|
+
*
|
|
121
|
+
* @example Basic GET route with handler (server-side)
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const getUser = defineRoute({
|
|
124
|
+
* route: 'GET /api/users/:id',
|
|
125
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
126
|
+
* responses: {
|
|
127
|
+
* 200: { schema: User },
|
|
128
|
+
* 404: null,
|
|
129
|
+
* },
|
|
130
|
+
* }, async ({ params }) => {
|
|
131
|
+
* // params.id is typed as string
|
|
132
|
+
* const user = await findUser(params.id)
|
|
133
|
+
* return user ? ok(User, user) : apiError.notFound()
|
|
134
|
+
* })
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @example Contract-only route (for client generation)
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // In shared contracts file - can be imported by both server and client
|
|
140
|
+
* export const getUserContract = defineRoute({
|
|
141
|
+
* route: 'GET /api/users/:id',
|
|
142
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
143
|
+
* responses: {
|
|
144
|
+
* 200: { schema: User },
|
|
145
|
+
* 404: { schema: ApiError },
|
|
146
|
+
* },
|
|
147
|
+
* })
|
|
148
|
+
*
|
|
149
|
+
* // On server - attach handler
|
|
150
|
+
* const getUser = defineRoute(getUserContract.config, async ({ params }) => { ... })
|
|
151
|
+
*
|
|
152
|
+
* // On client - use contract for type-safe calls
|
|
153
|
+
* const client = createClient({ controllers: { users: usersContracts }, ... })
|
|
154
|
+
* const result = await client.users.getUser({ params: { id: '123' } })
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @example POST with body and auth
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const createUser = defineRoute({
|
|
160
|
+
* route: 'POST /api/users',
|
|
161
|
+
* auth: 'required',
|
|
162
|
+
* body: CreateUserRequest,
|
|
163
|
+
* responses: { 201: { schema: User } },
|
|
164
|
+
* }, async ({ body, user }) => {
|
|
165
|
+
* // body is typed as { name: string; email: string }
|
|
166
|
+
* return created(User, await db.users.create(body))
|
|
167
|
+
* })
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @example GET with query parameters
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const listUsers = defineRoute({
|
|
173
|
+
* route: 'GET /api/users',
|
|
174
|
+
* query: Type.Object({
|
|
175
|
+
* page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
|
|
176
|
+
* limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
177
|
+
* }),
|
|
178
|
+
* responses: { 200: { schema: UserList } },
|
|
179
|
+
* }, async ({ query }) => {
|
|
180
|
+
* // query.page and query.limit are typed
|
|
181
|
+
* return ok(UserList, await db.users.list(query))
|
|
182
|
+
* })
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export declare function defineRoute<TRoute extends RouteString, TBody extends TSchema | undefined = undefined, TQuery extends TSchema | undefined = undefined, TParams extends TSchema | undefined = undefined>(config: RouteConfig<TRoute, TBody, TQuery, TParams>, handler?: RouteHandler<TBody, TQuery, TParams>): RouteDefinition<ExtractPathFromRoute<TRoute>, TBody, TQuery, TParams>;
|
|
186
|
+
/**
|
|
187
|
+
* Type guard to check if a value is a RouteDefinition.
|
|
188
|
+
*/
|
|
189
|
+
export declare function isRouteDefinition(value: unknown): value is RouteDefinition;
|
|
190
|
+
export {};
|
|
191
|
+
//# sourceMappingURL=define-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-route.d.ts","sourceRoot":"","sources":["../../src/builder/define-route.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a route string into method and path.
|
|
3
|
+
*/
|
|
4
|
+
function parseRoute(route) {
|
|
5
|
+
const spaceIndex = route.indexOf(' ');
|
|
6
|
+
const method = route.slice(0, spaceIndex).toLowerCase();
|
|
7
|
+
const path = route.slice(spaceIndex + 1);
|
|
8
|
+
return [method, path];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalize response definitions to consistent format.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeResponses(responses) {
|
|
14
|
+
const normalized = {};
|
|
15
|
+
for (const [status, value] of Object.entries(responses)) {
|
|
16
|
+
if (value === null) {
|
|
17
|
+
normalized[Number(status)] = { schema: null };
|
|
18
|
+
}
|
|
19
|
+
else if (typeof value === 'object' && 'schema' in value) {
|
|
20
|
+
normalized[Number(status)] = value;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
normalized[Number(status)] = { schema: value };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Define a type-safe API route with automatic type inference.
|
|
30
|
+
*
|
|
31
|
+
* This function-based approach provides full TypeScript type inference
|
|
32
|
+
* for request body, query parameters, and path parameters without
|
|
33
|
+
* requiring manual `Static<typeof Schema>` annotations.
|
|
34
|
+
*
|
|
35
|
+
* @param config - Route configuration including path, schemas, and responses
|
|
36
|
+
* @param handler - Optional async function that handles the request.
|
|
37
|
+
* When omitted, creates a "contract-only" definition that can be shared with clients.
|
|
38
|
+
* @returns RouteDefinition for registration with framework adapters or client generation
|
|
39
|
+
*
|
|
40
|
+
* @example Basic GET route with handler (server-side)
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const getUser = defineRoute({
|
|
43
|
+
* route: 'GET /api/users/:id',
|
|
44
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
45
|
+
* responses: {
|
|
46
|
+
* 200: { schema: User },
|
|
47
|
+
* 404: null,
|
|
48
|
+
* },
|
|
49
|
+
* }, async ({ params }) => {
|
|
50
|
+
* // params.id is typed as string
|
|
51
|
+
* const user = await findUser(params.id)
|
|
52
|
+
* return user ? ok(User, user) : apiError.notFound()
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example Contract-only route (for client generation)
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // In shared contracts file - can be imported by both server and client
|
|
59
|
+
* export const getUserContract = defineRoute({
|
|
60
|
+
* route: 'GET /api/users/:id',
|
|
61
|
+
* params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
|
|
62
|
+
* responses: {
|
|
63
|
+
* 200: { schema: User },
|
|
64
|
+
* 404: { schema: ApiError },
|
|
65
|
+
* },
|
|
66
|
+
* })
|
|
67
|
+
*
|
|
68
|
+
* // On server - attach handler
|
|
69
|
+
* const getUser = defineRoute(getUserContract.config, async ({ params }) => { ... })
|
|
70
|
+
*
|
|
71
|
+
* // On client - use contract for type-safe calls
|
|
72
|
+
* const client = createClient({ controllers: { users: usersContracts }, ... })
|
|
73
|
+
* const result = await client.users.getUser({ params: { id: '123' } })
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example POST with body and auth
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const createUser = defineRoute({
|
|
79
|
+
* route: 'POST /api/users',
|
|
80
|
+
* auth: 'required',
|
|
81
|
+
* body: CreateUserRequest,
|
|
82
|
+
* responses: { 201: { schema: User } },
|
|
83
|
+
* }, async ({ body, user }) => {
|
|
84
|
+
* // body is typed as { name: string; email: string }
|
|
85
|
+
* return created(User, await db.users.create(body))
|
|
86
|
+
* })
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @example GET with query parameters
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const listUsers = defineRoute({
|
|
92
|
+
* route: 'GET /api/users',
|
|
93
|
+
* query: Type.Object({
|
|
94
|
+
* page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),
|
|
95
|
+
* limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
96
|
+
* }),
|
|
97
|
+
* responses: { 200: { schema: UserList } },
|
|
98
|
+
* }, async ({ query }) => {
|
|
99
|
+
* // query.page and query.limit are typed
|
|
100
|
+
* return ok(UserList, await db.users.list(query))
|
|
101
|
+
* })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function defineRoute(config, handler) {
|
|
105
|
+
const [method, path] = parseRoute(config.route);
|
|
106
|
+
return {
|
|
107
|
+
__type: 'route',
|
|
108
|
+
method,
|
|
109
|
+
path: path,
|
|
110
|
+
config,
|
|
111
|
+
responses: normalizeResponses(config.responses),
|
|
112
|
+
handler,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Type guard to check if a value is a RouteDefinition.
|
|
117
|
+
*/
|
|
118
|
+
export function isRouteDefinition(value) {
|
|
119
|
+
return (typeof value === 'object'
|
|
120
|
+
&& value !== null
|
|
121
|
+
&& '__type' in value
|
|
122
|
+
&& value.__type === 'route');
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=define-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-route.js","sourceRoot":"","sources":["../../src/builder/define-route.ts"],"names":[],"mappings":"AAmIA;;GAEG;AACH,SAAS,UAAU,CAAC,KAAkB;IACpC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,WAAW,EAAgB,CAAA;IACrE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;IACxC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACvB,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,SAA0B;IACpD,MAAM,UAAU,GAAuC,EAAE,CAAA;IAEzD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAC/C,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC1D,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAA2B,CAAA;QAC1D,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAgB,EAAE,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,UAAU,WAAW,CAMzB,MAAmD,EACnD,OAA8C;IAE9C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAE/C,OAAO;QACL,MAAM,EAAE,OAAO;QACf,MAAM;QACN,IAAI,EAAE,IAAoC;QAC1C,MAAM;QACN,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC;QAC/C,OAAO;KACR,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;WACtB,KAAK,KAAK,IAAI;WACd,QAAQ,IAAI,KAAK;WAChB,KAA6B,CAAC,MAAM,KAAK,OAAO,CACrD,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { OpenApiVersion, OpenApiDocument, OpenApiInfo, OpenApiServer, OpenApiTag, OpenApiPathItem, OpenApiOperation, OpenApiParameter, OpenApiRequestBody, OpenApiResponse, OpenApiMediaType, OpenApiHeader, OpenApiSchema, OpenApiSecurityScheme, OpenApiOAuthFlows, OpenApiOAuthFlow, } from './types.js';
|
|
2
|
+
export { defineRoute, isRouteDefinition, type RouteConfig, type HandlerContext, type RouteHandler, type RouteDefinition, } from './define-route.js';
|
|
3
|
+
export { defineController, isControllerDefinition, getControllerRoutes, type ControllerConfig, type ControllerDefinition, type RouteRecord, type AnyRouteDefinition, } from './define-controller.js';
|
|
4
|
+
export { extractParamNames, createParamsSchema, getParamsSchema, type ExtractRouteParams, type HasPathParams, type ParamsFromPath, type InferParams, } from './path-params.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builder/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAA;AAG/B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Route builder
|
|
2
|
+
export { defineRoute, isRouteDefinition, } from './define-route.js';
|
|
3
|
+
// Controller builder
|
|
4
|
+
export { defineController, isControllerDefinition, getControllerRoutes, } from './define-controller.js';
|
|
5
|
+
// Path parameter utilities
|
|
6
|
+
export { extractParamNames, createParamsSchema, getParamsSchema, } from './path-params.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/builder/index.ts"],"names":[],"mappings":"AAmBA,gBAAgB;AAChB,OAAO,EACL,WAAW,EACX,iBAAiB,GAKlB,MAAM,mBAAmB,CAAA;AAE1B,qBAAqB;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,GAKpB,MAAM,wBAAwB,CAAA;AAE/B,2BAA2B;AAC3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAKhB,MAAM,kBAAkB,CAAA"}
|