@stacksjs/bun-router 0.0.14 → 0.0.15
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 +261 -584
- package/bin/cli.ts +9 -0
- package/dist/auth.d.ts +134 -0
- package/dist/cache/lru-cache.d.ts +139 -0
- package/dist/cache/middleware-memoization.d.ts +151 -0
- package/dist/cache/route-cache-warmer.d.ts +161 -0
- package/dist/cache/sqlite-cache.d.ts +209 -0
- package/dist/cache/streaming-cache.d.ts +132 -0
- package/dist/chunk-j0e7z7hd.js +18053 -0
- package/dist/cli/colors.d.ts +15 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/middleware.d.ts +31 -0
- package/dist/cli/openapi.d.ts +17 -0
- package/dist/cli/router.d.ts +15 -0
- package/dist/cli/routes.d.ts +29 -0
- package/dist/cli/utils.d.ts +74 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3360 -0
- package/dist/config.d.ts +7 -0
- package/dist/container/container.d.ts +273 -0
- package/dist/container/contextual-binding.d.ts +240 -0
- package/dist/container/decorators.d.ts +141 -0
- package/dist/container/service-provider.d.ts +285 -0
- package/dist/development/hot-reload.d.ts +187 -0
- package/dist/development/index.d.ts +167 -0
- package/dist/development/performance-profiler.d.ts +217 -0
- package/dist/development/route-debugger.d.ts +154 -0
- package/dist/development/route-inspector.d.ts +211 -0
- package/dist/development/typescript-utilities.d.ts +209 -0
- package/dist/docs.d.ts +11 -0
- package/dist/errors/circuit-breaker.d.ts +195 -0
- package/dist/errors/error-handler.d.ts +88 -0
- package/dist/errors/error-reporting.d.ts +144 -0
- package/dist/errors/exceptions.d.ts +184 -0
- package/dist/errors/graceful-degradation.d.ts +154 -0
- package/dist/errors/index.d.ts +9 -0
- package/dist/errors/router-errors.d.ts +110 -0
- package/dist/file-serving/static-files.d.ts +143 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13968 -0
- package/dist/middleware/auth.d.ts +63 -0
- package/dist/middleware/content_security_policy.d.ts +53 -0
- package/dist/middleware/cors.d.ts +5 -0
- package/dist/middleware/csrf.d.ts +7 -0
- package/dist/middleware/ddos_protection.d.ts +39 -0
- package/dist/middleware/file_security.d.ts +26 -0
- package/dist/middleware/file_upload.d.ts +38 -0
- package/dist/middleware/helmet.d.ts +54 -0
- package/dist/middleware/index.d.ts +44 -0
- package/dist/middleware/input_validation.d.ts +45 -0
- package/dist/middleware/json_body.d.ts +4 -0
- package/dist/middleware/performance_alerting.d.ts +87 -0
- package/dist/middleware/performance_dashboard.d.ts +87 -0
- package/dist/middleware/performance_monitor.d.ts +209 -0
- package/dist/middleware/pipeline.d.ts +131 -0
- package/dist/middleware/rate_limit.d.ts +36 -0
- package/dist/middleware/request_id.d.ts +4 -0
- package/dist/middleware/request_signing.d.ts +153 -0
- package/dist/middleware/request_tracer.d.ts +72 -0
- package/dist/middleware/response_cache.d.ts +97 -0
- package/dist/middleware/security.d.ts +69 -0
- package/dist/middleware/security_suite.d.ts +46 -0
- package/dist/middleware/session.d.ts +23 -0
- package/dist/model-binding/index.d.ts +2 -0
- package/dist/model-binding/model-middleware.d.ts +118 -0
- package/dist/model-binding/model-registry.d.ts +164 -0
- package/dist/model-binding.d.ts +185 -0
- package/dist/model-resolver-factory.d.ts +31 -0
- package/dist/observability/correlation.d.ts +187 -0
- package/dist/observability/health-checks.d.ts +186 -0
- package/dist/observability/index.d.ts +60 -0
- package/dist/observability/integration.d.ts +147 -0
- package/dist/observability/metrics.d.ts +184 -0
- package/dist/observability/tracing.d.ts +187 -0
- package/dist/optimization/bun-utilities.d.ts +224 -0
- package/dist/query-builder-integration.d.ts +36 -0
- package/dist/request/context.d.ts +34 -0
- package/dist/request/enhanced-request.d.ts +213 -0
- package/dist/request/macros.d.ts +300 -0
- package/dist/response/macros.d.ts +256 -0
- package/dist/response/response-factory.d.ts +133 -0
- package/dist/router/api-routes.d.ts +5 -0
- package/dist/router/file-based-routing.d.ts +118 -0
- package/dist/router/file-streaming.d.ts +5 -0
- package/dist/router/fluent-router.d.ts +315 -0
- package/dist/router/fluent-routing.d.ts +271 -0
- package/dist/router/group-organization.d.ts +5 -0
- package/dist/router/handler-resolver.d.ts +24 -0
- package/dist/router/http-methods.d.ts +10 -0
- package/dist/router/index.d.ts +114 -0
- package/dist/router/middleware-groups.d.ts +94 -0
- package/dist/router/middleware-integration.d.ts +112 -0
- package/dist/router/middleware.d.ts +5 -0
- package/dist/router/model-binding.d.ts +5 -0
- package/dist/router/optimized-route-matching.d.ts +6 -0
- package/dist/router/route-building.d.ts +5 -0
- package/dist/router/route-compiler.d.ts +153 -0
- package/dist/router/route-matching.d.ts +5 -0
- package/dist/router/route-trie.d.ts +114 -0
- package/dist/router/router.d.ts +317 -0
- package/dist/router/server.d.ts +5 -0
- package/dist/router/validation-integration.d.ts +170 -0
- package/dist/router/view-rendering.d.ts +5 -0
- package/dist/router/websocket.d.ts +5 -0
- package/dist/routing/route-caching.d.ts +129 -0
- package/dist/routing/route-throttling.d.ts +150 -0
- package/dist/routing/subdomain-routing.d.ts +206 -0
- package/dist/session/database-store.d.ts +51 -0
- package/dist/session/file-store.d.ts +22 -0
- package/dist/session/index.d.ts +106 -0
- package/dist/session/memory-store.d.ts +21 -0
- package/dist/session/redis-store.d.ts +31 -0
- package/dist/streaming/index.d.ts +3 -0
- package/dist/streaming/sse-handler.d.ts +138 -0
- package/dist/streaming/stream-handler.d.ts +114 -0
- package/dist/testing/auth-testing.d.ts +156 -0
- package/dist/testing/file-upload-testing.d.ts +190 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/middleware-testing.d.ts +138 -0
- package/dist/testing/model-binding-testing.d.ts +186 -0
- package/dist/testing/performance-testing.d.ts +235 -0
- package/dist/testing/test-client.d.ts +117 -0
- package/dist/testing/test-request.d.ts +85 -0
- package/dist/testing/test-response.d.ts +90 -0
- package/dist/testing/types.d.ts +207 -0
- package/dist/testing/websocket-testing.d.ts +227 -0
- package/dist/types/controller-types.d.ts +208 -0
- package/dist/types/core.d.ts +543 -0
- package/dist/types/middleware-types.d.ts +227 -0
- package/dist/types/request-response-augmentation.d.ts +261 -0
- package/dist/types/route-inference.d.ts +168 -0
- package/dist/types.d.ts +1794 -0
- package/dist/url.d.ts +56 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/query-preservation.d.ts +48 -0
- package/dist/utils.d.ts +69 -0
- package/dist/validation/validator.d.ts +140 -0
- package/dist/websocket/clustering.d.ts +185 -0
- package/package.json +45 -44
- package/src/auth.ts +469 -0
- package/src/cache/lru-cache.ts +457 -0
- package/src/cache/middleware-memoization.ts +531 -0
- package/src/cache/route-cache-warmer.ts +486 -0
- package/src/cache/sqlite-cache.ts +783 -0
- package/src/cache/streaming-cache.ts +572 -0
- package/src/cli/colors.ts +29 -0
- package/src/cli/index.ts +287 -0
- package/src/cli/middleware.ts +291 -0
- package/src/cli/openapi.ts +407 -0
- package/src/cli/router.ts +188 -0
- package/src/cli/routes.ts +265 -0
- package/src/cli/utils.ts +531 -0
- package/src/cli.ts +5 -0
- package/src/config.ts +322 -0
- package/src/container/container.ts +740 -0
- package/src/container/contextual-binding.ts +603 -0
- package/src/container/decorators.ts +359 -0
- package/src/container/service-provider.ts +596 -0
- package/src/development/hot-reload.ts +673 -0
- package/src/development/index.ts +499 -0
- package/src/development/performance-profiler.ts +717 -0
- package/src/development/route-debugger.ts +527 -0
- package/src/development/route-inspector.ts +749 -0
- package/src/development/typescript-utilities.ts +682 -0
- package/src/docs.ts +397 -0
- package/src/errors/circuit-breaker.ts +732 -0
- package/src/errors/error-handler.ts +569 -0
- package/src/errors/error-reporting.ts +672 -0
- package/src/errors/exceptions.ts +536 -0
- package/src/errors/graceful-degradation.ts +621 -0
- package/src/errors/index.ts +21 -0
- package/src/errors/router-errors.ts +632 -0
- package/src/file-serving/static-files.ts +581 -0
- package/src/index.ts +14 -0
- package/src/middleware/auth.ts +220 -0
- package/src/middleware/content_security_policy.ts +215 -0
- package/src/middleware/cors.ts +74 -0
- package/src/middleware/csrf.ts +108 -0
- package/src/middleware/ddos_protection.ts +255 -0
- package/src/middleware/file_security.ts +191 -0
- package/src/middleware/file_upload.ts +275 -0
- package/src/middleware/helmet.ts +268 -0
- package/src/middleware/index.ts +117 -0
- package/src/middleware/input_validation.ts +449 -0
- package/src/middleware/json_body.ts +37 -0
- package/src/middleware/performance_alerting.ts +538 -0
- package/src/middleware/performance_dashboard.ts +661 -0
- package/src/middleware/performance_monitor.ts +943 -0
- package/src/middleware/pipeline.ts +489 -0
- package/src/middleware/rate_limit.ts +245 -0
- package/src/middleware/request_id.ts +36 -0
- package/src/middleware/request_signing.ts +636 -0
- package/src/middleware/request_tracer.ts +636 -0
- package/src/middleware/response_cache.ts +743 -0
- package/src/middleware/security.ts +482 -0
- package/src/middleware/security_suite.ts +257 -0
- package/src/middleware/session.ts +91 -0
- package/src/model-binding/index.ts +18 -0
- package/src/model-binding/model-middleware.ts +425 -0
- package/src/model-binding/model-registry.ts +550 -0
- package/src/model-binding.ts +370 -0
- package/src/model-resolver-factory.ts +106 -0
- package/src/observability/correlation.ts +691 -0
- package/src/observability/health-checks.ts +729 -0
- package/src/observability/index.ts +184 -0
- package/src/observability/integration.ts +548 -0
- package/src/observability/metrics.ts +753 -0
- package/src/observability/tracing.ts +638 -0
- package/src/optimization/bun-utilities.ts +778 -0
- package/src/query-builder-integration.ts +137 -0
- package/src/request/context.ts +62 -0
- package/src/request/enhanced-request.ts +857 -0
- package/src/request/macros.ts +688 -0
- package/src/response/macros.ts +665 -0
- package/src/response/response-factory.ts +596 -0
- package/src/router/api-routes.ts +243 -0
- package/src/router/file-based-routing.ts +690 -0
- package/src/router/file-streaming.ts +383 -0
- package/src/router/fluent-router.ts +927 -0
- package/src/router/fluent-routing.ts +797 -0
- package/src/router/group-organization.ts +213 -0
- package/src/router/handler-resolver.ts +291 -0
- package/src/router/http-methods.ts +377 -0
- package/src/router/index.ts +187 -0
- package/src/router/middleware-groups.ts +222 -0
- package/src/router/middleware-integration.ts +399 -0
- package/src/router/middleware.ts +231 -0
- package/src/router/model-binding.ts +215 -0
- package/src/router/optimized-route-matching.ts +253 -0
- package/src/router/route-building.ts +221 -0
- package/src/router/route-compiler.ts +702 -0
- package/src/router/route-matching.ts +349 -0
- package/src/router/route-trie.ts +464 -0
- package/src/router/router.ts +1604 -0
- package/src/router/server.ts +462 -0
- package/src/router/validation-integration.ts +443 -0
- package/src/router/view-rendering.ts +233 -0
- package/src/router/websocket.ts +100 -0
- package/src/routing/route-caching.ts +402 -0
- package/src/routing/route-throttling.ts +469 -0
- package/src/routing/subdomain-routing.ts +492 -0
- package/src/session/database-store.ts +109 -0
- package/src/session/file-store.ts +148 -0
- package/src/session/index.ts +244 -0
- package/src/session/memory-store.ts +88 -0
- package/src/session/redis-store.ts +93 -0
- package/src/streaming/index.ts +17 -0
- package/src/streaming/sse-handler.ts +482 -0
- package/src/streaming/stream-handler.ts +552 -0
- package/src/testing/auth-testing.ts +446 -0
- package/src/testing/file-upload-testing.ts +543 -0
- package/src/testing/index.ts +10 -0
- package/src/testing/middleware-testing.ts +322 -0
- package/src/testing/model-binding-testing.ts +645 -0
- package/src/testing/performance-testing.ts +738 -0
- package/src/testing/test-client.ts +310 -0
- package/src/testing/test-request.ts +315 -0
- package/src/testing/test-response.ts +332 -0
- package/src/testing/types.ts +224 -0
- package/src/testing/websocket-testing.ts +590 -0
- package/src/types/controller-types.ts +385 -0
- package/src/types/core.ts +683 -0
- package/src/types/middleware-types.ts +419 -0
- package/src/types/request-response-augmentation.ts +489 -0
- package/src/types/route-inference.ts +357 -0
- package/src/types.ts +2067 -0
- package/src/url.ts +126 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/query-preservation.ts +201 -0
- package/src/utils.ts +327 -0
- package/src/validation/validator.ts +685 -0
- package/src/websocket/clustering.ts +762 -0
- package/CHANGELOG.md +0 -770
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { chalk } from './colors'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for the openapi command
|
|
7
|
+
*/
|
|
8
|
+
export interface OpenAPIOptions {
|
|
9
|
+
output?: string
|
|
10
|
+
title?: string
|
|
11
|
+
version?: string
|
|
12
|
+
description?: string
|
|
13
|
+
basePath?: string
|
|
14
|
+
servers?: string
|
|
15
|
+
tags?: boolean
|
|
16
|
+
security?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate OpenAPI specification for API routes
|
|
21
|
+
*/
|
|
22
|
+
export async function generateOpenAPISpec(options: OpenAPIOptions): Promise<void> {
|
|
23
|
+
try {
|
|
24
|
+
// Try to load router instance from the application
|
|
25
|
+
const routesFile = `${process.cwd()}/routes/index.ts`
|
|
26
|
+
const { router } = await import(routesFile)
|
|
27
|
+
|
|
28
|
+
if (!router) {
|
|
29
|
+
console.error(chalk.red(`Could not find router instance in ${routesFile}.`))
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get routes from router
|
|
34
|
+
const routes = (router as any).routes || []
|
|
35
|
+
|
|
36
|
+
if (routes.length === 0) {
|
|
37
|
+
console.log(chalk.yellow('No routes defined. Creating empty OpenAPI specification.'))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Filter API routes - typically these start with /api or have type 'api'
|
|
41
|
+
const apiRoutes = routes.filter((route: any) => {
|
|
42
|
+
return route.type === 'api' || route.path.startsWith('/api')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Define base structure for OpenAPI spec
|
|
46
|
+
const openapi: any = {
|
|
47
|
+
openapi: '3.0.3',
|
|
48
|
+
info: {
|
|
49
|
+
title: options.title || 'API Documentation',
|
|
50
|
+
version: options.version || '1.0.0',
|
|
51
|
+
description: options.description || 'Generated API documentation',
|
|
52
|
+
},
|
|
53
|
+
servers: generateServers(options),
|
|
54
|
+
paths: {},
|
|
55
|
+
components: {
|
|
56
|
+
schemas: {},
|
|
57
|
+
securitySchemes: {},
|
|
58
|
+
},
|
|
59
|
+
tags: [],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Collect unique tags if enabled
|
|
63
|
+
const uniqueTags = new Set<string>()
|
|
64
|
+
|
|
65
|
+
// Process routes and add them to the paths
|
|
66
|
+
for (const route of apiRoutes) {
|
|
67
|
+
const pathItem: any = openapi.paths[route.path] || {}
|
|
68
|
+
|
|
69
|
+
// Extract tag from route path or route metadata
|
|
70
|
+
let tag = ''
|
|
71
|
+
if (options.tags !== false) {
|
|
72
|
+
tag = route.tag || extractTagFromPath(route.path)
|
|
73
|
+
uniqueTags.add(tag)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Convert route to OpenAPI operation
|
|
77
|
+
const operation: any = {
|
|
78
|
+
summary: route.summary || `${route.method.toUpperCase()} ${route.path}`,
|
|
79
|
+
description: route.description || '',
|
|
80
|
+
operationId: route.name || generateOperationId(route.method, route.path),
|
|
81
|
+
parameters: generateParameters(route),
|
|
82
|
+
responses: generateResponses(route),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add tag if available
|
|
86
|
+
if (tag) {
|
|
87
|
+
operation.tags = [tag]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add request body for applicable methods
|
|
91
|
+
if (['post', 'put', 'patch'].includes(route.method.toLowerCase())) {
|
|
92
|
+
operation.requestBody = generateRequestBody(route)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Add security if enabled and available in route
|
|
96
|
+
if (options.security !== false && route.middleware) {
|
|
97
|
+
const securityItems = extractSecurityFromMiddleware(route.middleware)
|
|
98
|
+
if (securityItems.length > 0) {
|
|
99
|
+
operation.security = securityItems
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add operation to path
|
|
104
|
+
pathItem[route.method.toLowerCase()] = operation
|
|
105
|
+
openapi.paths[route.path] = pathItem
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add tags to OpenAPI spec if enabled
|
|
109
|
+
if (options.tags !== false) {
|
|
110
|
+
openapi.tags = Array.from(uniqueTags).map(tag => ({
|
|
111
|
+
name: tag,
|
|
112
|
+
description: `Operations related to ${tag}`,
|
|
113
|
+
}))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Write the OpenAPI spec to the output file
|
|
117
|
+
const outputPath = options.output || 'openapi.json'
|
|
118
|
+
await fs.writeFile(outputPath, JSON.stringify(openapi, null, 2))
|
|
119
|
+
console.log(chalk.green(`✨ OpenAPI specification generated at ${outputPath}`))
|
|
120
|
+
console.log(chalk.blue(`Import this file into Postman, Swagger UI, or other API tools to explore your API.`))
|
|
121
|
+
}
|
|
122
|
+
catch (error: any) {
|
|
123
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
124
|
+
console.error(chalk.red(`Routes file not found at ${process.cwd()}/routes/index.ts`))
|
|
125
|
+
console.error(chalk.yellow('Make sure your routes are defined and exported as "router" in routes/index.ts'))
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.error(chalk.red(`Error generating OpenAPI specification: ${error.message}`))
|
|
129
|
+
}
|
|
130
|
+
throw error
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generate server objects for OpenAPI spec
|
|
136
|
+
*/
|
|
137
|
+
function generateServers(options: OpenAPIOptions): any[] {
|
|
138
|
+
const servers = []
|
|
139
|
+
|
|
140
|
+
// Add servers from options
|
|
141
|
+
if (options.servers) {
|
|
142
|
+
const serverUrls = options.servers.split(',').map((url: string) => url.trim())
|
|
143
|
+
for (const url of serverUrls) {
|
|
144
|
+
servers.push({
|
|
145
|
+
url,
|
|
146
|
+
description: `Server ${servers.length + 1}`,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If no servers provided, add default
|
|
152
|
+
if (servers.length === 0) {
|
|
153
|
+
servers.push({
|
|
154
|
+
url: options.basePath || '/',
|
|
155
|
+
description: 'Default server',
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return servers
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extract tag from route path
|
|
164
|
+
*/
|
|
165
|
+
function extractTagFromPath(path: string): string {
|
|
166
|
+
// Extract first meaningful segment from path
|
|
167
|
+
const segments = path.split('/').filter(Boolean)
|
|
168
|
+
if (segments.length > 0) {
|
|
169
|
+
// If first segment is 'api', use second segment
|
|
170
|
+
if (segments[0].toLowerCase() === 'api' && segments.length > 1) {
|
|
171
|
+
return segments[1]
|
|
172
|
+
}
|
|
173
|
+
return segments[0]
|
|
174
|
+
}
|
|
175
|
+
return 'default'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate a camelCase operation ID from method and path
|
|
180
|
+
*/
|
|
181
|
+
function generateOperationId(method: string, path: string): string {
|
|
182
|
+
const pathWithoutParams = path.replace(/\{([^}]+)\}/g, '_$1')
|
|
183
|
+
const segments = pathWithoutParams.split('/').filter(Boolean)
|
|
184
|
+
|
|
185
|
+
if (segments.length === 0) {
|
|
186
|
+
return method.toLowerCase()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let operationId = method.toLowerCase()
|
|
190
|
+
|
|
191
|
+
for (const segment of segments) {
|
|
192
|
+
operationId += segment.charAt(0).toUpperCase() + segment.slice(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return operationId
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Generate OpenAPI parameters from route path
|
|
200
|
+
*/
|
|
201
|
+
function generateParameters(route: any): any[] {
|
|
202
|
+
const parameters = []
|
|
203
|
+
|
|
204
|
+
// Path parameters
|
|
205
|
+
const pathParams = route.path.match(/\{([^}]+)\}/g)
|
|
206
|
+
if (pathParams) {
|
|
207
|
+
for (const param of pathParams) {
|
|
208
|
+
const paramName = param.slice(1, -1)
|
|
209
|
+
parameters.push({
|
|
210
|
+
name: paramName,
|
|
211
|
+
in: 'path',
|
|
212
|
+
required: true,
|
|
213
|
+
schema: {
|
|
214
|
+
type: 'string',
|
|
215
|
+
},
|
|
216
|
+
description: `Path parameter: ${paramName}`,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add query parameters if defined in the route
|
|
222
|
+
if (route.params && route.params.query) {
|
|
223
|
+
for (const [name, schema] of Object.entries(route.params.query)) {
|
|
224
|
+
parameters.push({
|
|
225
|
+
name,
|
|
226
|
+
in: 'query',
|
|
227
|
+
schema: mapSchemaType(schema as any),
|
|
228
|
+
required: (schema as any).required === true,
|
|
229
|
+
description: (schema as any).description || `Query parameter: ${name}`,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return parameters
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Map schema type to OpenAPI schema
|
|
239
|
+
*/
|
|
240
|
+
function mapSchemaType(schema: any): any {
|
|
241
|
+
if (!schema || typeof schema !== 'object') {
|
|
242
|
+
return { type: 'string' }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const openApiSchema: any = {}
|
|
246
|
+
|
|
247
|
+
if (schema.type) {
|
|
248
|
+
openApiSchema.type = schema.type
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (schema.format) {
|
|
252
|
+
openApiSchema.format = schema.format
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (schema.enum) {
|
|
256
|
+
openApiSchema.enum = schema.enum
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (schema.default !== undefined) {
|
|
260
|
+
openApiSchema.default = schema.default
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return openApiSchema
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Generate responses object for OpenAPI
|
|
268
|
+
*/
|
|
269
|
+
function generateResponses(route: any): any {
|
|
270
|
+
const responses: any = {
|
|
271
|
+
200: {
|
|
272
|
+
description: 'Successful response',
|
|
273
|
+
content: {
|
|
274
|
+
'application/json': {
|
|
275
|
+
schema: {},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Add common error responses
|
|
282
|
+
responses['400'] = {
|
|
283
|
+
description: 'Bad request',
|
|
284
|
+
content: {
|
|
285
|
+
'application/json': {
|
|
286
|
+
schema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
properties: {
|
|
289
|
+
error: { type: 'string' },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
responses['401'] = {
|
|
297
|
+
description: 'Unauthorized',
|
|
298
|
+
content: {
|
|
299
|
+
'application/json': {
|
|
300
|
+
schema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
error: { type: 'string' },
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
responses['404'] = {
|
|
311
|
+
description: 'Not found',
|
|
312
|
+
content: {
|
|
313
|
+
'application/json': {
|
|
314
|
+
schema: {
|
|
315
|
+
type: 'object',
|
|
316
|
+
properties: {
|
|
317
|
+
error: { type: 'string' },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
responses['500'] = {
|
|
325
|
+
description: 'Server error',
|
|
326
|
+
content: {
|
|
327
|
+
'application/json': {
|
|
328
|
+
schema: {
|
|
329
|
+
type: 'object',
|
|
330
|
+
properties: {
|
|
331
|
+
error: { type: 'string' },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Add custom responses from route if available
|
|
339
|
+
if (route.responses) {
|
|
340
|
+
for (const [status, response] of Object.entries(route.responses)) {
|
|
341
|
+
responses[status] = {
|
|
342
|
+
description: (response as any).description || `Response with status ${status}`,
|
|
343
|
+
content: {
|
|
344
|
+
'application/json': {
|
|
345
|
+
schema: (response as any).schema || {},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return responses
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Generate request body object for OpenAPI
|
|
357
|
+
*/
|
|
358
|
+
function generateRequestBody(route: any): any {
|
|
359
|
+
// Default empty request body
|
|
360
|
+
const requestBody: any = {
|
|
361
|
+
required: true,
|
|
362
|
+
content: {
|
|
363
|
+
'application/json': {
|
|
364
|
+
schema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Add schema from route if available
|
|
372
|
+
if (route.params && route.params.body) {
|
|
373
|
+
requestBody.content['application/json'].schema = route.params.body
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return requestBody
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Extract security requirements from middleware
|
|
381
|
+
*/
|
|
382
|
+
function extractSecurityFromMiddleware(middleware: any[]): any[] {
|
|
383
|
+
const securityItems = []
|
|
384
|
+
|
|
385
|
+
// Check for common auth middleware like jwt, oauth, apiKey, etc.
|
|
386
|
+
for (const mw of middleware) {
|
|
387
|
+
const name = typeof mw === 'string' ? mw : mw.name
|
|
388
|
+
|
|
389
|
+
if (name && name.toLowerCase().includes('auth')) {
|
|
390
|
+
// Add security scheme based on middleware type
|
|
391
|
+
if (name.toLowerCase().includes('jwt')) {
|
|
392
|
+
securityItems.push({ BearerAuth: [] })
|
|
393
|
+
}
|
|
394
|
+
else if (name.toLowerCase().includes('apikey')) {
|
|
395
|
+
securityItems.push({ ApiKeyAuth: [] })
|
|
396
|
+
}
|
|
397
|
+
else if (name.toLowerCase().includes('basic')) {
|
|
398
|
+
securityItems.push({ BasicAuth: [] })
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
securityItems.push({ [name]: [] })
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return securityItems
|
|
407
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import { chalk } from './colors'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for the router:types command
|
|
8
|
+
*/
|
|
9
|
+
export interface GenerateRouterTypesOptions {
|
|
10
|
+
output?: string
|
|
11
|
+
watch?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate TypeScript types for router extensions
|
|
16
|
+
*/
|
|
17
|
+
export async function generateRouterTypes(outputPath: string): Promise<void> {
|
|
18
|
+
try {
|
|
19
|
+
// Try to load router instance from the application
|
|
20
|
+
const routesFile = `${process.cwd()}/routes/index.ts`
|
|
21
|
+
const { router } = await import(routesFile)
|
|
22
|
+
|
|
23
|
+
if (!router) {
|
|
24
|
+
console.error(chalk.red(`Could not find router instance in ${routesFile}.`))
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get router methods excluding internal properties
|
|
29
|
+
const routerMethods = Object.getOwnPropertyNames(Object.getPrototypeOf(router))
|
|
30
|
+
.filter(method =>
|
|
31
|
+
!method.startsWith('_')
|
|
32
|
+
&& method !== 'constructor'
|
|
33
|
+
&& typeof (router as any)[method] === 'function',
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// Get custom properties that were added to router
|
|
37
|
+
const extendedProperties = Object.keys(router)
|
|
38
|
+
.filter(prop =>
|
|
39
|
+
!['routes', 'middleware', 'hooks', 'config'].includes(prop)
|
|
40
|
+
&& typeof prop === 'string'
|
|
41
|
+
&& !prop.startsWith('_'),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// Generate type definition
|
|
45
|
+
const typeContent = `/**
|
|
46
|
+
* This file is auto-generated.
|
|
47
|
+
* DO NOT EDIT THIS FILE DIRECTLY.
|
|
48
|
+
* To update, run 'bun router router:types'
|
|
49
|
+
*/
|
|
50
|
+
import type { Router, RouteHandler, MiddlewareHandler, RouteDefinition } from '../src/types'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extended Router interface with all available methods and properties
|
|
54
|
+
*/
|
|
55
|
+
export interface ExtendedRouter extends Router {
|
|
56
|
+
${routerMethods.map((method) => {
|
|
57
|
+
// Provide basic type definitions for common router methods
|
|
58
|
+
if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(method)) {
|
|
59
|
+
return `${method}(path: string, handler: RouteHandler, options?: any): Router`
|
|
60
|
+
}
|
|
61
|
+
else if (method === 'use') {
|
|
62
|
+
return `${method}(middleware: MiddlewareHandler | MiddlewareHandler[]): Router`
|
|
63
|
+
}
|
|
64
|
+
else if (method === 'group') {
|
|
65
|
+
return `${method}(prefix: string, callback: (router: Router) => void): Router`
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return `${method}: Function`
|
|
69
|
+
}
|
|
70
|
+
}).join('\n ')}
|
|
71
|
+
|
|
72
|
+
${extendedProperties.map((prop) => {
|
|
73
|
+
const propType = typeof (router as any)[prop]
|
|
74
|
+
return `${prop}: ${propType}`
|
|
75
|
+
}).join('\n ')}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Type for extending the router with custom methods
|
|
80
|
+
*/
|
|
81
|
+
export type RouterExtension = {
|
|
82
|
+
[key: string]: Function | any
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Helper type for correctly typing router extensions
|
|
87
|
+
*
|
|
88
|
+
* Example usage:
|
|
89
|
+
* \`\`\`typescript
|
|
90
|
+
* import { Router, ExtendRouter } from './router-types'
|
|
91
|
+
*
|
|
92
|
+
* // Define extension type
|
|
93
|
+
* interface MyCustomRouter extends ExtendRouter<{
|
|
94
|
+
* customMethod(param: string): void
|
|
95
|
+
* }> {}
|
|
96
|
+
*
|
|
97
|
+
* // Create and extend the router
|
|
98
|
+
* const router = new Router() as MyCustomRouter
|
|
99
|
+
* router.customMethod = (param: string) => {
|
|
100
|
+
* console.log(param)
|
|
101
|
+
* }
|
|
102
|
+
* \`\`\`
|
|
103
|
+
*/
|
|
104
|
+
export type ExtendRouter<T extends RouterExtension> = Router & T
|
|
105
|
+
`
|
|
106
|
+
|
|
107
|
+
// Write to file
|
|
108
|
+
await fs.writeFile(outputPath, typeContent)
|
|
109
|
+
console.log(chalk.green(`✨ Router types generated at ${outputPath}`))
|
|
110
|
+
}
|
|
111
|
+
catch (error: any) {
|
|
112
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
113
|
+
console.error(chalk.red(`Routes file not found at ${process.cwd()}/routes/index.ts`))
|
|
114
|
+
console.error(chalk.yellow('Make sure your routes are defined and exported as "router" in routes/index.ts'))
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error(chalk.red(`Error generating router types: ${error.message}`))
|
|
118
|
+
}
|
|
119
|
+
throw error
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Watch for changes in router files and regenerate types
|
|
125
|
+
*/
|
|
126
|
+
export async function watchRouterFiles(outputPath: string): Promise<void> {
|
|
127
|
+
const routerFiles = [
|
|
128
|
+
path.join(process.cwd(), 'routes'),
|
|
129
|
+
path.join(process.cwd(), 'src'),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
// Check if at least one directory exists
|
|
133
|
+
let validPath = false
|
|
134
|
+
for (const dirPath of routerFiles) {
|
|
135
|
+
try {
|
|
136
|
+
await fs.access(dirPath)
|
|
137
|
+
validPath = true
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Continue checking other paths
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!validPath) {
|
|
146
|
+
console.error(chalk.red(`No valid source directories found to watch.`))
|
|
147
|
+
process.exit(1)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Initial generation
|
|
151
|
+
await generateRouterTypes(outputPath)
|
|
152
|
+
console.log(chalk.green(`✨ Router types generated at ${outputPath}`))
|
|
153
|
+
console.log(chalk.blue(`Watching for changes in router files...`))
|
|
154
|
+
|
|
155
|
+
// Setup file watchers for all paths
|
|
156
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
157
|
+
|
|
158
|
+
for (const dirPath of routerFiles) {
|
|
159
|
+
try {
|
|
160
|
+
const watcher = fs.watch(dirPath, { recursive: true })
|
|
161
|
+
|
|
162
|
+
// Using IIFE to create separate async context for each watcher
|
|
163
|
+
;(async () => {
|
|
164
|
+
for await (const _event of watcher) {
|
|
165
|
+
// Debounce to avoid multiple regenerations when multiple files change
|
|
166
|
+
if (timeoutId) {
|
|
167
|
+
clearTimeout(timeoutId)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
timeoutId = setTimeout(async () => {
|
|
171
|
+
console.log(chalk.yellow(`Changes detected in router files. Regenerating types...`))
|
|
172
|
+
try {
|
|
173
|
+
await generateRouterTypes(outputPath)
|
|
174
|
+
console.log(chalk.green(`✨ Router types regenerated at ${outputPath}`))
|
|
175
|
+
}
|
|
176
|
+
catch (error: any) {
|
|
177
|
+
console.error(chalk.red(`Error regenerating types: ${error.message}`))
|
|
178
|
+
}
|
|
179
|
+
timeoutId = null
|
|
180
|
+
}, 500)
|
|
181
|
+
}
|
|
182
|
+
})()
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Skip if this path doesn't exist
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|