@veloxts/router 0.6.56 → 0.6.58

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.
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Path Extractor
3
+ *
4
+ * Utilities for parsing and converting route paths between Fastify/Express format
5
+ * and OpenAPI format.
6
+ *
7
+ * @module @veloxts/router/openapi/path-extractor
8
+ */
9
+ import type { JSONSchema, OpenAPIParameter } from './types.js';
10
+ /**
11
+ * Converts a path from Express/Fastify format to OpenAPI format
12
+ *
13
+ * @param path - Path in Express/Fastify format (e.g., '/users/:id')
14
+ * @returns Path in OpenAPI format (e.g., '/users/{id}')
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * convertToOpenAPIPath('/users/:id')
19
+ * // '/users/{id}'
20
+ *
21
+ * convertToOpenAPIPath('/posts/:postId/comments/:id')
22
+ * // '/posts/{postId}/comments/{id}'
23
+ * ```
24
+ */
25
+ export declare function convertToOpenAPIPath(path: string): string;
26
+ /**
27
+ * Converts a path from OpenAPI format to Express/Fastify format
28
+ *
29
+ * @param path - Path in OpenAPI format (e.g., '/users/{id}')
30
+ * @returns Path in Express/Fastify format (e.g., '/users/:id')
31
+ */
32
+ export declare function convertFromOpenAPIPath(path: string): string;
33
+ /**
34
+ * Extracts parameter names from a path
35
+ *
36
+ * @param path - Path in Express/Fastify format
37
+ * @returns Array of parameter names
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * extractPathParamNames('/users/:id')
42
+ * // ['id']
43
+ *
44
+ * extractPathParamNames('/posts/:postId/comments/:id')
45
+ * // ['postId', 'id']
46
+ * ```
47
+ */
48
+ export declare function extractPathParamNames(path: string): string[];
49
+ /**
50
+ * Parses path parameters into OpenAPI Parameter objects
51
+ *
52
+ * @param path - Path in Express/Fastify format
53
+ * @param schemas - Optional schemas for parameters (keyed by param name)
54
+ * @returns Array of OpenAPI Parameter objects
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * parsePathParameters('/users/:id')
59
+ * // [{
60
+ * // name: 'id',
61
+ * // in: 'path',
62
+ * // required: true,
63
+ * // schema: { type: 'string' }
64
+ * // }]
65
+ * ```
66
+ */
67
+ export declare function parsePathParameters(path: string, schemas?: Record<string, JSONSchema>): OpenAPIParameter[];
68
+ /**
69
+ * Checks if a path has any parameters
70
+ *
71
+ * @param path - Path to check
72
+ * @returns True if path contains parameters
73
+ */
74
+ export declare function hasPathParameters(path: string): boolean;
75
+ /**
76
+ * Options for extracting query parameters
77
+ */
78
+ export interface QueryParamExtractionOptions {
79
+ /**
80
+ * Parameter names to exclude (e.g., path parameters)
81
+ */
82
+ exclude?: string[];
83
+ /**
84
+ * Default required state for parameters
85
+ * @default false
86
+ */
87
+ defaultRequired?: boolean;
88
+ }
89
+ /**
90
+ * Extracts query parameters from a JSON Schema
91
+ *
92
+ * Converts object schema properties to OpenAPI query parameters.
93
+ * Excludes properties that are path parameters.
94
+ *
95
+ * @param schema - JSON Schema representing input
96
+ * @param options - Extraction options
97
+ * @returns Array of OpenAPI Parameter objects
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const schema = {
102
+ * type: 'object',
103
+ * properties: {
104
+ * id: { type: 'string' },
105
+ * page: { type: 'integer' },
106
+ * limit: { type: 'integer' }
107
+ * },
108
+ * required: ['id']
109
+ * };
110
+ *
111
+ * extractQueryParameters(schema, { exclude: ['id'] })
112
+ * // [
113
+ * // { name: 'page', in: 'query', required: false, schema: { type: 'integer' } },
114
+ * // { name: 'limit', in: 'query', required: false, schema: { type: 'integer' } }
115
+ * // ]
116
+ * ```
117
+ */
118
+ export declare function extractQueryParameters(schema: JSONSchema | undefined, options?: QueryParamExtractionOptions): OpenAPIParameter[];
119
+ /**
120
+ * Options for building all parameters
121
+ */
122
+ export interface BuildParametersOptions {
123
+ /**
124
+ * Route path (for extracting path parameters)
125
+ */
126
+ path: string;
127
+ /**
128
+ * HTTP method
129
+ */
130
+ method: string;
131
+ /**
132
+ * Input schema (for query/body parameters)
133
+ */
134
+ inputSchema?: JSONSchema;
135
+ /**
136
+ * Custom schemas for path parameters
137
+ */
138
+ pathParamSchemas?: Record<string, JSONSchema>;
139
+ }
140
+ /**
141
+ * Result of building parameters
142
+ */
143
+ export interface BuildParametersResult {
144
+ /**
145
+ * Path parameters extracted from route
146
+ */
147
+ pathParams: OpenAPIParameter[];
148
+ /**
149
+ * Query parameters (for GET/DELETE)
150
+ */
151
+ queryParams: OpenAPIParameter[];
152
+ /**
153
+ * Names of path parameters (for excluding from body)
154
+ */
155
+ pathParamNames: string[];
156
+ }
157
+ /**
158
+ * Builds all parameter types for a route
159
+ *
160
+ * @param options - Build options
161
+ * @returns Path and query parameters
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const result = buildParameters({
166
+ * path: '/users/:id',
167
+ * method: 'GET',
168
+ * inputSchema: {
169
+ * type: 'object',
170
+ * properties: {
171
+ * id: { type: 'string' },
172
+ * include: { type: 'string' }
173
+ * }
174
+ * }
175
+ * });
176
+ *
177
+ * // result.pathParams = [{ name: 'id', in: 'path', ... }]
178
+ * // result.queryParams = [{ name: 'include', in: 'query', ... }]
179
+ * // result.pathParamNames = ['id']
180
+ * ```
181
+ */
182
+ export declare function buildParameters(options: BuildParametersOptions): BuildParametersResult;
183
+ /**
184
+ * Joins path segments, handling leading/trailing slashes
185
+ *
186
+ * @param segments - Path segments to join
187
+ * @returns Joined path
188
+ */
189
+ export declare function joinPaths(...segments: string[]): string;
190
+ /**
191
+ * Normalizes a path by removing duplicate slashes and ensuring leading slash
192
+ *
193
+ * @param path - Path to normalize
194
+ * @returns Normalized path
195
+ */
196
+ export declare function normalizePath(path: string): string;
197
+ /**
198
+ * Extracts the resource name from a path
199
+ *
200
+ * @param path - Path to extract from
201
+ * @returns Resource name (last non-parameter segment)
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * extractResourceFromPath('/api/users/:id')
206
+ * // 'users'
207
+ *
208
+ * extractResourceFromPath('/api/posts/:postId/comments/:id')
209
+ * // 'comments'
210
+ * ```
211
+ */
212
+ export declare function extractResourceFromPath(path: string): string | undefined;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Path Extractor
3
+ *
4
+ * Utilities for parsing and converting route paths between Fastify/Express format
5
+ * and OpenAPI format.
6
+ *
7
+ * @module @veloxts/router/openapi/path-extractor
8
+ */
9
+ // ============================================================================
10
+ // Path Conversion
11
+ // ============================================================================
12
+ /**
13
+ * Regex pattern for path parameters in Express/Fastify format
14
+ * Matches :paramName
15
+ */
16
+ const PATH_PARAM_REGEX = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
17
+ /**
18
+ * Converts a path from Express/Fastify format to OpenAPI format
19
+ *
20
+ * @param path - Path in Express/Fastify format (e.g., '/users/:id')
21
+ * @returns Path in OpenAPI format (e.g., '/users/{id}')
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * convertToOpenAPIPath('/users/:id')
26
+ * // '/users/{id}'
27
+ *
28
+ * convertToOpenAPIPath('/posts/:postId/comments/:id')
29
+ * // '/posts/{postId}/comments/{id}'
30
+ * ```
31
+ */
32
+ export function convertToOpenAPIPath(path) {
33
+ return path.replace(PATH_PARAM_REGEX, '{$1}');
34
+ }
35
+ /**
36
+ * Converts a path from OpenAPI format to Express/Fastify format
37
+ *
38
+ * @param path - Path in OpenAPI format (e.g., '/users/{id}')
39
+ * @returns Path in Express/Fastify format (e.g., '/users/:id')
40
+ */
41
+ export function convertFromOpenAPIPath(path) {
42
+ return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, ':$1');
43
+ }
44
+ // ============================================================================
45
+ // Path Parameter Extraction
46
+ // ============================================================================
47
+ /**
48
+ * Extracts parameter names from a path
49
+ *
50
+ * @param path - Path in Express/Fastify format
51
+ * @returns Array of parameter names
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * extractPathParamNames('/users/:id')
56
+ * // ['id']
57
+ *
58
+ * extractPathParamNames('/posts/:postId/comments/:id')
59
+ * // ['postId', 'id']
60
+ * ```
61
+ */
62
+ export function extractPathParamNames(path) {
63
+ const matches = [...path.matchAll(PATH_PARAM_REGEX)];
64
+ return matches.map((match) => match[1]);
65
+ }
66
+ /**
67
+ * Parses path parameters into OpenAPI Parameter objects
68
+ *
69
+ * @param path - Path in Express/Fastify format
70
+ * @param schemas - Optional schemas for parameters (keyed by param name)
71
+ * @returns Array of OpenAPI Parameter objects
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * parsePathParameters('/users/:id')
76
+ * // [{
77
+ * // name: 'id',
78
+ * // in: 'path',
79
+ * // required: true,
80
+ * // schema: { type: 'string' }
81
+ * // }]
82
+ * ```
83
+ */
84
+ export function parsePathParameters(path, schemas) {
85
+ const paramNames = extractPathParamNames(path);
86
+ return paramNames.map((name) => ({
87
+ name,
88
+ in: 'path',
89
+ required: true,
90
+ schema: schemas?.[name] ?? { type: 'string' },
91
+ }));
92
+ }
93
+ /**
94
+ * Checks if a path has any parameters
95
+ *
96
+ * @param path - Path to check
97
+ * @returns True if path contains parameters
98
+ */
99
+ export function hasPathParameters(path) {
100
+ return PATH_PARAM_REGEX.test(path);
101
+ }
102
+ /**
103
+ * Extracts query parameters from a JSON Schema
104
+ *
105
+ * Converts object schema properties to OpenAPI query parameters.
106
+ * Excludes properties that are path parameters.
107
+ *
108
+ * @param schema - JSON Schema representing input
109
+ * @param options - Extraction options
110
+ * @returns Array of OpenAPI Parameter objects
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const schema = {
115
+ * type: 'object',
116
+ * properties: {
117
+ * id: { type: 'string' },
118
+ * page: { type: 'integer' },
119
+ * limit: { type: 'integer' }
120
+ * },
121
+ * required: ['id']
122
+ * };
123
+ *
124
+ * extractQueryParameters(schema, { exclude: ['id'] })
125
+ * // [
126
+ * // { name: 'page', in: 'query', required: false, schema: { type: 'integer' } },
127
+ * // { name: 'limit', in: 'query', required: false, schema: { type: 'integer' } }
128
+ * // ]
129
+ * ```
130
+ */
131
+ export function extractQueryParameters(schema, options = {}) {
132
+ if (!schema || schema.type !== 'object' || !schema.properties) {
133
+ return [];
134
+ }
135
+ const { exclude = [], defaultRequired = false } = options;
136
+ const properties = schema.properties;
137
+ const required = new Set(schema.required ?? []);
138
+ const params = [];
139
+ for (const [name, propSchema] of Object.entries(properties)) {
140
+ // Skip excluded properties (usually path parameters)
141
+ if (exclude.includes(name)) {
142
+ continue;
143
+ }
144
+ params.push({
145
+ name,
146
+ in: 'query',
147
+ required: required.has(name) || defaultRequired,
148
+ description: propSchema.description,
149
+ schema: propSchema,
150
+ });
151
+ }
152
+ return params;
153
+ }
154
+ /**
155
+ * Builds all parameter types for a route
156
+ *
157
+ * @param options - Build options
158
+ * @returns Path and query parameters
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const result = buildParameters({
163
+ * path: '/users/:id',
164
+ * method: 'GET',
165
+ * inputSchema: {
166
+ * type: 'object',
167
+ * properties: {
168
+ * id: { type: 'string' },
169
+ * include: { type: 'string' }
170
+ * }
171
+ * }
172
+ * });
173
+ *
174
+ * // result.pathParams = [{ name: 'id', in: 'path', ... }]
175
+ * // result.queryParams = [{ name: 'include', in: 'query', ... }]
176
+ * // result.pathParamNames = ['id']
177
+ * ```
178
+ */
179
+ export function buildParameters(options) {
180
+ const { path, method, inputSchema, pathParamSchemas } = options;
181
+ // Extract path parameters
182
+ const pathParamNames = extractPathParamNames(path);
183
+ const pathParams = parsePathParameters(path, pathParamSchemas);
184
+ // Extract query parameters for GET and DELETE
185
+ let queryParams = [];
186
+ if (method === 'GET' || method === 'DELETE') {
187
+ queryParams = extractQueryParameters(inputSchema, {
188
+ exclude: pathParamNames,
189
+ });
190
+ }
191
+ return {
192
+ pathParams,
193
+ queryParams,
194
+ pathParamNames,
195
+ };
196
+ }
197
+ // ============================================================================
198
+ // Path Utilities
199
+ // ============================================================================
200
+ /**
201
+ * Joins path segments, handling leading/trailing slashes
202
+ *
203
+ * @param segments - Path segments to join
204
+ * @returns Joined path
205
+ */
206
+ export function joinPaths(...segments) {
207
+ return ('/' +
208
+ segments
209
+ .map((s) => s.replace(/^\/+|\/+$/g, ''))
210
+ .filter(Boolean)
211
+ .join('/'));
212
+ }
213
+ /**
214
+ * Normalizes a path by removing duplicate slashes and ensuring leading slash
215
+ *
216
+ * @param path - Path to normalize
217
+ * @returns Normalized path
218
+ */
219
+ export function normalizePath(path) {
220
+ // Remove duplicate slashes
221
+ let normalized = path.replace(/\/+/g, '/');
222
+ // Ensure leading slash
223
+ if (!normalized.startsWith('/')) {
224
+ normalized = `/${normalized}`;
225
+ }
226
+ // Remove trailing slash (unless it's just '/')
227
+ if (normalized.length > 1 && normalized.endsWith('/')) {
228
+ normalized = normalized.slice(0, -1);
229
+ }
230
+ return normalized;
231
+ }
232
+ /**
233
+ * Extracts the resource name from a path
234
+ *
235
+ * @param path - Path to extract from
236
+ * @returns Resource name (last non-parameter segment)
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * extractResourceFromPath('/api/users/:id')
241
+ * // 'users'
242
+ *
243
+ * extractResourceFromPath('/api/posts/:postId/comments/:id')
244
+ * // 'comments'
245
+ * ```
246
+ */
247
+ export function extractResourceFromPath(path) {
248
+ const segments = path.split('/').filter(Boolean);
249
+ // Find last non-parameter segment
250
+ for (let i = segments.length - 1; i >= 0; i--) {
251
+ if (!segments[i].startsWith(':') && !segments[i].startsWith('{')) {
252
+ return segments[i];
253
+ }
254
+ }
255
+ return undefined;
256
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Swagger UI Fastify Plugin
3
+ *
4
+ * Serves Swagger UI documentation for VeloxTS APIs.
5
+ *
6
+ * @module @veloxts/router/openapi/plugin
7
+ */
8
+ import type { FastifyPluginAsync } from 'fastify';
9
+ import type { OpenAPISpec, SwaggerUIPluginOptions } from './types.js';
10
+ /**
11
+ * Swagger UI Fastify plugin
12
+ *
13
+ * Registers routes for serving Swagger UI and the OpenAPI specification.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { swaggerUIPlugin } from '@veloxts/router';
18
+ *
19
+ * app.register(swaggerUIPlugin, {
20
+ * routePrefix: '/docs',
21
+ * collections: [userProcedures, postProcedures],
22
+ * openapi: {
23
+ * info: {
24
+ * title: 'My API',
25
+ * version: '1.0.0',
26
+ * description: 'A VeloxTS-powered API',
27
+ * },
28
+ * servers: [{ url: 'http://localhost:3030' }],
29
+ * },
30
+ * });
31
+ * ```
32
+ */
33
+ export declare const swaggerUIPlugin: FastifyPluginAsync<SwaggerUIPluginOptions>;
34
+ /**
35
+ * Creates a Swagger UI plugin with pre-configured options
36
+ *
37
+ * @param options - Plugin options
38
+ * @returns Configured plugin
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { createSwaggerUI } from '@veloxts/router';
43
+ *
44
+ * const docs = createSwaggerUI({
45
+ * collections: [userProcedures],
46
+ * openapi: {
47
+ * info: { title: 'My API', version: '1.0.0' },
48
+ * },
49
+ * });
50
+ *
51
+ * app.register(docs);
52
+ * ```
53
+ */
54
+ export declare function createSwaggerUI(options: SwaggerUIPluginOptions): FastifyPluginAsync<SwaggerUIPluginOptions>;
55
+ /**
56
+ * Registers multiple procedure collections with Swagger UI
57
+ *
58
+ * Convenience function that sets up both REST routes and documentation.
59
+ *
60
+ * @param fastify - Fastify instance
61
+ * @param options - Documentation options
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { registerDocs } from '@veloxts/router';
66
+ *
67
+ * await registerDocs(app, {
68
+ * collections: [userProcedures, postProcedures],
69
+ * openapi: {
70
+ * info: { title: 'My API', version: '1.0.0' },
71
+ * },
72
+ * });
73
+ * ```
74
+ */
75
+ export declare function registerDocs(fastify: {
76
+ register: (plugin: FastifyPluginAsync<SwaggerUIPluginOptions>, options: SwaggerUIPluginOptions) => Promise<void>;
77
+ }, options: SwaggerUIPluginOptions): Promise<void>;
78
+ /**
79
+ * Gets the generated OpenAPI specification without registering routes
80
+ *
81
+ * Useful for testing or exporting the spec programmatically.
82
+ *
83
+ * @param options - Plugin options
84
+ * @returns Generated OpenAPI specification
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { getOpenApiSpec } from '@veloxts/router';
89
+ * import fs from 'fs';
90
+ *
91
+ * const spec = getOpenApiSpec({
92
+ * collections: [userProcedures],
93
+ * openapi: {
94
+ * info: { title: 'My API', version: '1.0.0' },
95
+ * },
96
+ * });
97
+ *
98
+ * fs.writeFileSync('openapi.json', JSON.stringify(spec, null, 2));
99
+ * ```
100
+ */
101
+ export declare function getOpenApiSpec(options: SwaggerUIPluginOptions): OpenAPISpec;