@veloxts/router 0.6.84 → 0.6.86

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @veloxts/router
2
2
 
3
+ ## 0.6.86
4
+
5
+ ### Patch Changes
6
+
7
+ - updated documentation
8
+ - Updated dependencies
9
+ - @veloxts/core@0.6.86
10
+ - @veloxts/validation@0.6.86
11
+
12
+ ## 0.6.85
13
+
14
+ ### Patch Changes
15
+
16
+ - implement missing features from original requirements
17
+ - Updated dependencies
18
+ - @veloxts/core@0.6.85
19
+ - @veloxts/validation@0.6.85
20
+
3
21
  ## 0.6.84
4
22
 
5
23
  ### Patch Changes
package/GUIDE.md CHANGED
@@ -134,19 +134,38 @@ await registerRestRoutes(app.server, {
134
134
  });
135
135
  ```
136
136
 
137
+ ### REST Route Options
138
+
139
+ | Option | Type | Default | Description |
140
+ |--------|------|---------|-------------|
141
+ | `prefix` | `string` | `'/api'` | URL prefix for all routes |
142
+ | `procedures` | `Record<string, ProcedureCollection>` | required | Procedure collections to register |
143
+ | `shortcuts` | `boolean` | `false` | Generate flat shortcut routes in addition to nested routes |
144
+ | `nestingWarnings` | `boolean` | `true` | Warn when nesting depth exceeds 3 levels |
145
+
146
+ When `shortcuts: true`, deeply nested resources also get flat access routes:
147
+ ```typescript
148
+ // With shortcuts enabled:
149
+ // GET /organizations/:orgId/projects/:projectId/tasks/:id (nested)
150
+ // GET /tasks/:id (flat shortcut)
151
+ ```
152
+
137
153
  ## tRPC Adapter
138
154
 
139
155
  ```typescript
140
- import { trpc, appRouter, registerTRPCPlugin } from '@veloxts/router';
156
+ import { trpc, appRouter, registerTRPCPlugin, type TRPCRouter } from '@veloxts/router';
141
157
 
142
158
  const t = trpc();
143
159
  const router = appRouter(t, [userProcedures]);
144
160
 
145
161
  await registerTRPCPlugin(app.server, { router, prefix: '/trpc' });
146
162
 
147
- export type AppRouter = typeof router;
163
+ // Use TRPCRouter for @trpc/react-query compatibility
164
+ export type AppRouter = TRPCRouter<typeof router>;
148
165
  ```
149
166
 
167
+ > **Note**: `AsTRPCRouter` is deprecated. Use `TRPCRouter` instead.
168
+
150
169
  ## OpenAPI Documentation
151
170
 
152
171
  Auto-generate OpenAPI 3.0.3 specifications from your procedure definitions and serve interactive Swagger UI documentation.
@@ -221,14 +240,17 @@ await registerDocs(app.server, {
221
240
  });
222
241
  ```
223
242
 
224
- ### CLI Command
243
+ ### CLI Commands
225
244
 
226
- Generate OpenAPI specs from the command line:
245
+ Generate and serve OpenAPI specs from the command line:
227
246
 
228
247
  ```bash
229
248
  # Basic generation
230
249
  velox openapi generate --output ./openapi.json
231
250
 
251
+ # YAML output (auto-detected from extension)
252
+ velox openapi generate -o ./docs/api.yaml
253
+
232
254
  # Full options
233
255
  velox openapi generate \
234
256
  --path ./src/procedures \
@@ -236,14 +258,36 @@ velox openapi generate \
236
258
  --title "My API" \
237
259
  --version "1.0.0" \
238
260
  --description "API documentation" \
239
- --server http://localhost:3030 \
240
- --server https://api.example.com \
241
- --prefix /api
261
+ --server "http://localhost:3030|Development" \
262
+ --server "https://api.example.com|Production" \
263
+ --prefix /api \
264
+ --recursive \
265
+ --pretty
242
266
 
243
267
  # Serve documentation locally
244
268
  velox openapi serve --port 8080
269
+
270
+ # Serve with hot-reload on file changes
271
+ velox openapi serve --watch -f ./docs/api.yaml
245
272
  ```
246
273
 
274
+ **Available Options:**
275
+
276
+ `velox openapi generate`:
277
+ - `-p, --path <path>` - Procedures directory (default: `./src/procedures`)
278
+ - `-o, --output <file>` - Output file path (default: `./openapi.json`)
279
+ - `-f, --format <format>` - Output format: `json` or `yaml` (auto-detected from extension)
280
+ - `-s, --server <url>` - Server URL (format: `url|description`, repeatable)
281
+ - `--prefix <prefix>` - API route prefix (default: `/api`)
282
+ - `-r, --recursive` - Scan subdirectories for procedures
283
+ - `-q, --quiet` - Suppress output except errors
284
+
285
+ `velox openapi serve`:
286
+ - `-f, --file <file>` - OpenAPI spec file (default: `./openapi.json`)
287
+ - `--port <port>` - Server port (default: `8080`)
288
+ - `--host <host>` - Host to bind (default: `localhost`)
289
+ - `-w, --watch` - Watch for file changes and hot-reload
290
+
247
291
  ### Security Schemes
248
292
 
249
293
  Guards are automatically mapped to OpenAPI security requirements:
package/dist/expose.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * @module serve
8
8
  */
9
9
  import { type VeloxApp } from '@veloxts/core';
10
- import { type AnyRouter } from './trpc/index.js';
10
+ import { type AnyRouter, type InferRouterFromCollections } from './trpc/index.js';
11
11
  import type { ProcedureCollection } from './types.js';
12
12
  /**
13
13
  * Options for serving procedures
@@ -38,38 +38,41 @@ export interface ServeOptions {
38
38
  * This is the recommended way to register your API.
39
39
  * Define your procedures once, serve them everywhere.
40
40
  *
41
+ * **IMPORTANT**: Use `as const` on the procedures array to preserve
42
+ * literal types for full type inference on the returned router.
43
+ *
41
44
  * @param app - VeloxTS application instance
42
- * @param procedures - Array of procedure collections to serve
45
+ * @param procedures - Array of procedure collections to serve (use `as const`)
43
46
  * @param options - Optional configuration for API and RPC prefixes
44
- * @returns The tRPC router for type exports
47
+ * @returns The typed tRPC router for type exports
45
48
  *
46
49
  * @example
47
50
  * ```typescript
48
51
  * // Both REST (/api) and tRPC (/trpc) with zero config
49
- * const router = await serve(app, [healthProcedures, userProcedures]);
50
- * export type AppRouter = typeof router;
52
+ * const router = await serve(app, [healthProcedures, userProcedures] as const);
53
+ * export type AppRouter = typeof router; // Fully typed!
51
54
  * ```
52
55
  *
53
56
  * @example
54
57
  * ```typescript
55
58
  * // REST only (external API)
56
- * await serve(app, [healthProcedures, userProcedures], { rpc: false });
59
+ * await serve(app, [healthProcedures, userProcedures] as const, { rpc: false });
57
60
  * ```
58
61
  *
59
62
  * @example
60
63
  * ```typescript
61
64
  * // tRPC only (internal app)
62
- * const router = await serve(app, [healthProcedures, userProcedures], { api: false });
65
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, { api: false });
63
66
  * export type AppRouter = typeof router;
64
67
  * ```
65
68
  *
66
69
  * @example
67
70
  * ```typescript
68
71
  * // Custom prefixes
69
- * const router = await serve(app, [healthProcedures, userProcedures], {
72
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, {
70
73
  * api: '/v1',
71
74
  * rpc: '/rpc',
72
75
  * });
73
76
  * ```
74
77
  */
75
- export declare function serve(app: VeloxApp, procedures: ProcedureCollection[], options?: ServeOptions): Promise<AnyRouter>;
78
+ export declare function serve<const T extends readonly ProcedureCollection[]>(app: VeloxApp, procedures: T, options?: ServeOptions): Promise<AnyRouter & InferRouterFromCollections<T>>;
package/dist/expose.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { fail } from '@veloxts/core';
10
10
  import { rest } from './rest/index.js';
11
- import { appRouter, registerTRPCPlugin, trpc } from './trpc/index.js';
11
+ import { appRouter, registerTRPCPlugin, trpc, } from './trpc/index.js';
12
12
  // ============================================================================
13
13
  // Main Function
14
14
  // ============================================================================
@@ -18,35 +18,38 @@ import { appRouter, registerTRPCPlugin, trpc } from './trpc/index.js';
18
18
  * This is the recommended way to register your API.
19
19
  * Define your procedures once, serve them everywhere.
20
20
  *
21
+ * **IMPORTANT**: Use `as const` on the procedures array to preserve
22
+ * literal types for full type inference on the returned router.
23
+ *
21
24
  * @param app - VeloxTS application instance
22
- * @param procedures - Array of procedure collections to serve
25
+ * @param procedures - Array of procedure collections to serve (use `as const`)
23
26
  * @param options - Optional configuration for API and RPC prefixes
24
- * @returns The tRPC router for type exports
27
+ * @returns The typed tRPC router for type exports
25
28
  *
26
29
  * @example
27
30
  * ```typescript
28
31
  * // Both REST (/api) and tRPC (/trpc) with zero config
29
- * const router = await serve(app, [healthProcedures, userProcedures]);
30
- * export type AppRouter = typeof router;
32
+ * const router = await serve(app, [healthProcedures, userProcedures] as const);
33
+ * export type AppRouter = typeof router; // Fully typed!
31
34
  * ```
32
35
  *
33
36
  * @example
34
37
  * ```typescript
35
38
  * // REST only (external API)
36
- * await serve(app, [healthProcedures, userProcedures], { rpc: false });
39
+ * await serve(app, [healthProcedures, userProcedures] as const, { rpc: false });
37
40
  * ```
38
41
  *
39
42
  * @example
40
43
  * ```typescript
41
44
  * // tRPC only (internal app)
42
- * const router = await serve(app, [healthProcedures, userProcedures], { api: false });
45
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, { api: false });
43
46
  * export type AppRouter = typeof router;
44
47
  * ```
45
48
  *
46
49
  * @example
47
50
  * ```typescript
48
51
  * // Custom prefixes
49
- * const router = await serve(app, [healthProcedures, userProcedures], {
52
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, {
50
53
  * api: '/v1',
51
54
  * rpc: '/rpc',
52
55
  * });
@@ -73,7 +76,8 @@ export async function serve(app, procedures, options = {}) {
73
76
  }
74
77
  // Register REST routes if enabled
75
78
  if (apiPrefix !== false) {
76
- app.routes(rest(procedures, { prefix: apiPrefix }));
79
+ // Need to spread to convert readonly tuple to mutable array for rest()
80
+ app.routes(rest([...procedures], { prefix: apiPrefix }));
77
81
  }
78
82
  return router;
79
83
  }
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@
39
39
  */
40
40
  /** Router package version */
41
41
  export declare const ROUTER_VERSION: string;
42
- export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, InferProcedureTypes, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
42
+ export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, InferProcedureTypes, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceChain, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
43
43
  export { PROCEDURE_METHOD_MAP, } from './types.js';
44
44
  export type { GuardErrorResponse, RouterErrorCode } from './errors.js';
45
45
  export { GuardError, isGuardError } from './errors.js';
@@ -51,10 +51,14 @@ export type { RouterResult } from './router-utils.js';
51
51
  export { createRouter, toRouter } from './router-utils.js';
52
52
  export type { NamingWarning, NamingWarningType, WarningConfig, WarningOption } from './warnings.js';
53
53
  export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
54
- export type { ExtractRoutesType, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
55
- export { buildNestedRestPath, buildRestPath, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
56
- export type { AnyRouter, InferAppRouter, TRPCInstance, TRPCPluginOptions, } from './trpc/index.js';
54
+ export type { ExtractRoutesType, GenerateRestRoutesOptions, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
55
+ export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
56
+ export type { AnyRouter,
57
+ /** @deprecated Use `TRPCRouter` instead */
58
+ AsTRPCRouter, CollectionsToRouterRecord, ExtractNamespace, ExtractProcedures, InferAppRouter, InferRouterFromCollections, MapProcedureRecordToTRPC, MapProcedureToTRPC, TRPCInstance, TRPCPluginOptions, TRPCRouter, } from './trpc/index.js';
57
59
  export { appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
60
+ export type { RpcOptions, RpcResult } from './rpc.js';
61
+ export { registerRpc, rpc } from './rpc.js';
58
62
  export type { DiscoveryOptions, DiscoveryResult, DiscoveryWarning } from './discovery/index.js';
59
63
  export { DiscoveryError, DiscoveryErrorCode, directoryNotFound, discoverProcedures, discoverProceduresVerbose, fileLoadError, invalidExport, invalidFileType, isDiscoveryError, noProceduresFound, permissionDenied, } from './discovery/index.js';
60
64
  export type { ServeOptions } from './expose.js';
@@ -101,5 +105,5 @@ export { APP_ROUTER, PROCEDURE_COLLECTIONS, REST_ADAPTER_CONFIG, ROUTER_CONFIG,
101
105
  * });
102
106
  * ```
103
107
  */
104
- export type { BuildParametersOptions, BuildParametersResult, GuardMappingOptions, JSONSchema, OpenAPIComponents, OpenAPIContact, OpenAPIEncoding, OpenAPIExample, OpenAPIExternalDocs, OpenAPIGeneratorOptions, OpenAPIHeader, OpenAPIHttpMethod, OpenAPIInfo, OpenAPILicense, OpenAPILink, OpenAPIMediaType, OpenAPIOAuthFlow, OpenAPIOAuthFlows, OpenAPIOperation, OpenAPIParameter, OpenAPIPathItem, OpenAPIRequestBody, OpenAPIResponse, OpenAPISecurityRequirement, OpenAPISecurityScheme, OpenAPIServer, OpenAPISpec, OpenAPITag, ParameterIn, QueryParamExtractionOptions, RouteInfo, SchemaConversionOptions, SecuritySchemeType, SwaggerUIConfig, SwaggerUIPluginOptions, } from './openapi/index.js';
105
- export { buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath, createSecurityRequirement, createStringSchema, createSwaggerUI, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes, generateOpenApiSpec, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, registerDocs, removeSchemaProperties, schemaHasProperties, swaggerUIPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
108
+ export type { BuildParametersOptions, BuildParametersResult, GuardMappingOptions, JSONSchema, OpenAPIComponents, OpenAPIContact, OpenAPIEncoding, OpenAPIExample, OpenAPIExternalDocs, OpenAPIGeneratorOptions, OpenAPIHeader, OpenAPIHttpMethod, OpenAPIInfo, OpenAPILicense, OpenAPILink, OpenAPIMediaType, OpenAPIOAuthFlow, OpenAPIOAuthFlows, OpenAPIOperation, OpenAPIParameter, OpenAPIPathItem, OpenAPIRequestBody, OpenAPIResponse, OpenAPISecurityRequirement, OpenAPISecurityScheme, OpenAPIServer, OpenAPISpec, OpenAPITag, ParameterIn, QueryParamExtractionOptions, RouteInfo, SchemaConversionOptions, SecuritySchemeType, SwaggerUIConfig, SwaggerUIHtmlOptions, SwaggerUIPluginOptions, } from './openapi/index.js';
109
+ export { buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath, createSecurityRequirement, createStringSchema, createSwaggerUI, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes, generateOpenApiSpec, generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, registerDocs, removeSchemaProperties, SWAGGER_UI_CDN, schemaHasProperties, swaggerUIPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
package/dist/index.js CHANGED
@@ -58,10 +58,11 @@ defineProcedures, executeProcedure, isCompiledProcedure, isProcedureCollection,
58
58
  export { createProcedure, typedProcedure } from './procedure/factory.js';
59
59
  export { createRouter, toRouter } from './router-utils.js';
60
60
  export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
61
- export { buildNestedRestPath, buildRestPath, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
61
+ export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
62
62
  export {
63
63
  // tRPC utilities
64
64
  appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
65
+ export { registerRpc, rpc } from './rpc.js';
65
66
  export { DiscoveryError, DiscoveryErrorCode, directoryNotFound, discoverProcedures, discoverProceduresVerbose, fileLoadError, invalidExport, invalidFileType, isDiscoveryError, noProceduresFound, permissionDenied, } from './discovery/index.js';
66
67
  export { serve } from './expose.js';
67
68
  // ============================================================================
@@ -97,6 +98,8 @@ createSecurityRequirement,
97
98
  // Schema converter
98
99
  createStringSchema,
99
100
  // Plugin
100
- createSwaggerUI, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes,
101
+ createSwaggerUI, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes,
101
102
  // Generator
102
- generateOpenApiSpec, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, registerDocs, removeSchemaProperties, schemaHasProperties, swaggerUIPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
103
+ generateOpenApiSpec,
104
+ // HTML Generator
105
+ generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, registerDocs, removeSchemaProperties, SWAGGER_UI_CDN, schemaHasProperties, swaggerUIPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Shared middleware chain execution utility
3
+ *
4
+ * Provides a single implementation for executing middleware chains,
5
+ * used by both the procedure builder and tRPC adapter.
6
+ *
7
+ * @module middleware/chain
8
+ */
9
+ import type { BaseContext } from '@veloxts/core';
10
+ import type { MiddlewareFunction } from '../types.js';
11
+ /**
12
+ * Result wrapper for middleware chain execution
13
+ */
14
+ export interface MiddlewareResult<TOutput> {
15
+ output: TOutput;
16
+ }
17
+ /**
18
+ * Execute a middleware chain with the given handler
19
+ *
20
+ * Builds the chain from end to start and executes it, allowing each
21
+ * middleware to extend the context before calling the next middleware.
22
+ *
23
+ * @param middlewares - Array of middleware functions to execute
24
+ * @param input - The input to pass to each middleware
25
+ * @param ctx - The context object (will be mutated by middleware)
26
+ * @param handler - The final handler to execute after all middleware
27
+ * @returns The output from the handler
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const result = await executeMiddlewareChain(
32
+ * procedure.middlewares,
33
+ * input,
34
+ * ctx,
35
+ * async () => handler({ input, ctx })
36
+ * );
37
+ * ```
38
+ */
39
+ export declare function executeMiddlewareChain<TInput, TOutput, TContext extends BaseContext>(middlewares: ReadonlyArray<MiddlewareFunction<TInput, TContext, TContext, TOutput>>, input: TInput, ctx: TContext, handler: () => Promise<TOutput>): Promise<TOutput>;
40
+ /**
41
+ * Create a precompiled middleware executor for a fixed middleware chain
42
+ *
43
+ * This is an optimization that builds the chain structure once during
44
+ * procedure compilation, creating a reusable function that can execute
45
+ * the chain without rebuilding closures on every request.
46
+ *
47
+ * @param middlewares - Array of middleware functions
48
+ * @param handler - The procedure handler (can return sync or async)
49
+ * @returns A function that executes the full chain
50
+ */
51
+ export declare function createMiddlewareExecutor<TInput, TOutput, TContext extends BaseContext>(middlewares: ReadonlyArray<MiddlewareFunction<TInput, TContext, TContext, TOutput>>, handler: (params: {
52
+ input: TInput;
53
+ ctx: TContext;
54
+ }) => TOutput | Promise<TOutput>): (input: TInput, ctx: TContext) => Promise<TOutput>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared middleware chain execution utility
3
+ *
4
+ * Provides a single implementation for executing middleware chains,
5
+ * used by both the procedure builder and tRPC adapter.
6
+ *
7
+ * @module middleware/chain
8
+ */
9
+ /**
10
+ * Execute a middleware chain with the given handler
11
+ *
12
+ * Builds the chain from end to start and executes it, allowing each
13
+ * middleware to extend the context before calling the next middleware.
14
+ *
15
+ * @param middlewares - Array of middleware functions to execute
16
+ * @param input - The input to pass to each middleware
17
+ * @param ctx - The context object (will be mutated by middleware)
18
+ * @param handler - The final handler to execute after all middleware
19
+ * @returns The output from the handler
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const result = await executeMiddlewareChain(
24
+ * procedure.middlewares,
25
+ * input,
26
+ * ctx,
27
+ * async () => handler({ input, ctx })
28
+ * );
29
+ * ```
30
+ */
31
+ export async function executeMiddlewareChain(middlewares, input, ctx, handler) {
32
+ // Build the chain from the end (handler) back to the start
33
+ let next = async () => {
34
+ const output = await handler();
35
+ return { output };
36
+ };
37
+ // Wrap each middleware from last to first
38
+ for (let i = middlewares.length - 1; i >= 0; i--) {
39
+ const middleware = middlewares[i];
40
+ const currentNext = next;
41
+ next = async () => {
42
+ return middleware({
43
+ input,
44
+ ctx,
45
+ next: async (opts) => {
46
+ // Allow middleware to extend context
47
+ if (opts?.ctx) {
48
+ Object.assign(ctx, opts.ctx);
49
+ }
50
+ return currentNext();
51
+ },
52
+ });
53
+ };
54
+ }
55
+ const result = await next();
56
+ return result.output;
57
+ }
58
+ /**
59
+ * Create a precompiled middleware executor for a fixed middleware chain
60
+ *
61
+ * This is an optimization that builds the chain structure once during
62
+ * procedure compilation, creating a reusable function that can execute
63
+ * the chain without rebuilding closures on every request.
64
+ *
65
+ * @param middlewares - Array of middleware functions
66
+ * @param handler - The procedure handler (can return sync or async)
67
+ * @returns A function that executes the full chain
68
+ */
69
+ export function createMiddlewareExecutor(middlewares, handler) {
70
+ // If no middlewares, just return a direct handler call
71
+ if (middlewares.length === 0) {
72
+ return async (input, ctx) => {
73
+ return handler({ input, ctx });
74
+ };
75
+ }
76
+ // Return an executor that uses the shared chain execution
77
+ return async (input, ctx) => {
78
+ return executeMiddlewareChain(middlewares, input, ctx, async () => handler({ input, ctx }));
79
+ };
80
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Middleware utilities
3
+ *
4
+ * @module middleware
5
+ */
6
+ export type { MiddlewareResult } from './chain.js';
7
+ export { createMiddlewareExecutor, executeMiddlewareChain } from './chain.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Middleware utilities
3
+ *
4
+ * @module middleware
5
+ */
6
+ export { createMiddlewareExecutor, executeMiddlewareChain } from './chain.js';
@@ -147,6 +147,16 @@ function generateOperation(route, namespace, options) {
147
147
  if (security.length > 0) {
148
148
  operation.security = security;
149
149
  }
150
+ // Add deprecation flag
151
+ if (procedure.deprecated) {
152
+ operation.deprecated = true;
153
+ // Add deprecation message as description suffix if present
154
+ if (procedure.deprecationMessage) {
155
+ operation.description = operation.description
156
+ ? `${operation.description}\n\n**Deprecated:** ${procedure.deprecationMessage}`
157
+ : `**Deprecated:** ${procedure.deprecationMessage}`;
158
+ }
159
+ }
150
160
  return operation;
151
161
  }
152
162
  // ============================================================================
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Swagger UI HTML Generator
3
+ *
4
+ * Generates standalone Swagger UI HTML pages for displaying OpenAPI specifications.
5
+ * This module is used by both the Fastify plugin and CLI serve command.
6
+ *
7
+ * @module @veloxts/router/openapi/html-generator
8
+ */
9
+ import type { SwaggerUIConfig } from './types.js';
10
+ /**
11
+ * Default Swagger UI configuration
12
+ */
13
+ export declare const DEFAULT_UI_CONFIG: SwaggerUIConfig;
14
+ /**
15
+ * Swagger UI CDN URLs
16
+ */
17
+ export declare const SWAGGER_UI_CDN: {
18
+ css: string;
19
+ bundle: string;
20
+ standalonePreset: string;
21
+ };
22
+ /**
23
+ * Options for generating Swagger UI HTML
24
+ */
25
+ export interface SwaggerUIHtmlOptions {
26
+ /**
27
+ * URL where the OpenAPI spec can be fetched
28
+ */
29
+ specUrl: string;
30
+ /**
31
+ * Page title
32
+ * @default 'API Documentation'
33
+ */
34
+ title?: string;
35
+ /**
36
+ * Custom favicon URL
37
+ */
38
+ favicon?: string;
39
+ /**
40
+ * Swagger UI configuration options
41
+ */
42
+ config?: Partial<SwaggerUIConfig>;
43
+ }
44
+ /**
45
+ * Generates a standalone Swagger UI HTML page
46
+ *
47
+ * @param options - HTML generation options
48
+ * @returns Complete HTML document as string
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const html = generateSwaggerUIHtml({
53
+ * specUrl: '/openapi.json',
54
+ * title: 'My API Documentation',
55
+ * config: { tryItOutEnabled: true },
56
+ * });
57
+ * ```
58
+ */
59
+ export declare function generateSwaggerUIHtml(options: SwaggerUIHtmlOptions): string;
60
+ /**
61
+ * Escapes HTML special characters to prevent XSS
62
+ *
63
+ * @param text - Text to escape
64
+ * @returns Escaped text
65
+ */
66
+ export declare function escapeHtml(text: string): string;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Swagger UI HTML Generator
3
+ *
4
+ * Generates standalone Swagger UI HTML pages for displaying OpenAPI specifications.
5
+ * This module is used by both the Fastify plugin and CLI serve command.
6
+ *
7
+ * @module @veloxts/router/openapi/html-generator
8
+ */
9
+ // ============================================================================
10
+ // Constants
11
+ // ============================================================================
12
+ /**
13
+ * Default Swagger UI configuration
14
+ */
15
+ export const DEFAULT_UI_CONFIG = {
16
+ deepLinking: true,
17
+ displayOperationId: false,
18
+ defaultModelsExpandDepth: 1,
19
+ defaultModelExpandDepth: 1,
20
+ docExpansion: 'list',
21
+ filter: false,
22
+ showExtensions: false,
23
+ tryItOutEnabled: true,
24
+ persistAuthorization: false,
25
+ };
26
+ /**
27
+ * Swagger UI CDN URLs
28
+ */
29
+ export const SWAGGER_UI_CDN = {
30
+ css: 'https://unpkg.com/swagger-ui-dist@5/swagger-ui.css',
31
+ bundle: 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js',
32
+ standalonePreset: 'https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js',
33
+ };
34
+ // ============================================================================
35
+ // HTML Generation
36
+ // ============================================================================
37
+ /**
38
+ * Generates a standalone Swagger UI HTML page
39
+ *
40
+ * @param options - HTML generation options
41
+ * @returns Complete HTML document as string
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const html = generateSwaggerUIHtml({
46
+ * specUrl: '/openapi.json',
47
+ * title: 'My API Documentation',
48
+ * config: { tryItOutEnabled: true },
49
+ * });
50
+ * ```
51
+ */
52
+ export function generateSwaggerUIHtml(options) {
53
+ const { specUrl, title = 'API Documentation', favicon, config = {} } = options;
54
+ // Merge config with defaults
55
+ const mergedConfig = {
56
+ ...DEFAULT_UI_CONFIG,
57
+ ...config,
58
+ };
59
+ const configJson = JSON.stringify({
60
+ url: specUrl,
61
+ dom_id: '#swagger-ui',
62
+ deepLinking: mergedConfig.deepLinking,
63
+ displayOperationId: mergedConfig.displayOperationId,
64
+ defaultModelsExpandDepth: mergedConfig.defaultModelsExpandDepth,
65
+ defaultModelExpandDepth: mergedConfig.defaultModelExpandDepth,
66
+ docExpansion: mergedConfig.docExpansion,
67
+ filter: mergedConfig.filter,
68
+ showExtensions: mergedConfig.showExtensions,
69
+ tryItOutEnabled: mergedConfig.tryItOutEnabled,
70
+ persistAuthorization: mergedConfig.persistAuthorization,
71
+ presets: ['SwaggerUIBundle.presets.apis', 'SwaggerUIStandalonePreset'],
72
+ plugins: ['SwaggerUIBundle.plugins.DownloadUrl'],
73
+ layout: 'StandaloneLayout',
74
+ });
75
+ const faviconTag = favicon ? `<link rel="icon" type="image/x-icon" href="${favicon}">` : '';
76
+ return `<!DOCTYPE html>
77
+ <html lang="en">
78
+ <head>
79
+ <meta charset="UTF-8">
80
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
81
+ <title>${escapeHtml(title)}</title>
82
+ ${faviconTag}
83
+ <link rel="stylesheet" href="${SWAGGER_UI_CDN.css}">
84
+ <style>
85
+ html { box-sizing: border-box; overflow-y: scroll; }
86
+ *, *:before, *:after { box-sizing: inherit; }
87
+ body { margin: 0; background: #fafafa; }
88
+ .swagger-ui .topbar { display: none; }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div id="swagger-ui"></div>
93
+ <script src="${SWAGGER_UI_CDN.bundle}"></script>
94
+ <script src="${SWAGGER_UI_CDN.standalonePreset}"></script>
95
+ <script>
96
+ window.onload = function() {
97
+ window.ui = SwaggerUIBundle(${configJson.replace(/"(SwaggerUIBundle\.presets\.apis|SwaggerUIStandalonePreset|SwaggerUIBundle\.plugins\.DownloadUrl)"/g, '$1')});
98
+ };
99
+ </script>
100
+ </body>
101
+ </html>`;
102
+ }
103
+ /**
104
+ * Escapes HTML special characters to prevent XSS
105
+ *
106
+ * @param text - Text to escape
107
+ * @returns Escaped text
108
+ */
109
+ export function escapeHtml(text) {
110
+ return text
111
+ .replace(/&/g, '&amp;')
112
+ .replace(/</g, '&lt;')
113
+ .replace(/>/g, '&gt;')
114
+ .replace(/"/g, '&quot;')
115
+ .replace(/'/g, '&#039;');
116
+ }