@stacksjs/bun-router 0.0.13 → 0.0.14
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 +770 -0
- package/README.md +584 -261
- package/package.json +43 -47
- package/bin/cli.ts +0 -9
- package/dist/auth.d.ts +0 -134
- package/dist/cache/lru-cache.d.ts +0 -139
- package/dist/cache/middleware-memoization.d.ts +0 -151
- package/dist/cache/route-cache-warmer.d.ts +0 -161
- package/dist/cache/sqlite-cache.d.ts +0 -209
- package/dist/cache/streaming-cache.d.ts +0 -132
- package/dist/chunk-1mvtv6jw.js +0 -13012
- package/dist/cli/colors.d.ts +0 -15
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/middleware.d.ts +0 -31
- package/dist/cli/openapi.d.ts +0 -17
- package/dist/cli/router.d.ts +0 -15
- package/dist/cli/routes.d.ts +0 -29
- package/dist/cli/utils.d.ts +0 -74
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -3195
- package/dist/config.d.ts +0 -7
- package/dist/container/container.d.ts +0 -273
- package/dist/container/contextual-binding.d.ts +0 -240
- package/dist/container/decorators.d.ts +0 -141
- package/dist/container/service-provider.d.ts +0 -285
- package/dist/development/hot-reload.d.ts +0 -187
- package/dist/development/index.d.ts +0 -167
- package/dist/development/performance-profiler.d.ts +0 -217
- package/dist/development/route-debugger.d.ts +0 -154
- package/dist/development/route-inspector.d.ts +0 -211
- package/dist/development/typescript-utilities.d.ts +0 -209
- package/dist/docs.d.ts +0 -11
- package/dist/errors/circuit-breaker.d.ts +0 -195
- package/dist/errors/error-handler.d.ts +0 -88
- package/dist/errors/error-reporting.d.ts +0 -144
- package/dist/errors/exceptions.d.ts +0 -184
- package/dist/errors/graceful-degradation.d.ts +0 -154
- package/dist/errors/index.d.ts +0 -9
- package/dist/errors/router-errors.d.ts +0 -110
- package/dist/file-serving/static-files.d.ts +0 -143
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -13933
- package/dist/middleware/auth.d.ts +0 -63
- package/dist/middleware/content_security_policy.d.ts +0 -53
- package/dist/middleware/cors.d.ts +0 -5
- package/dist/middleware/csrf.d.ts +0 -7
- package/dist/middleware/ddos_protection.d.ts +0 -39
- package/dist/middleware/file_security.d.ts +0 -26
- package/dist/middleware/file_upload.d.ts +0 -38
- package/dist/middleware/helmet.d.ts +0 -54
- package/dist/middleware/index.d.ts +0 -44
- package/dist/middleware/input_validation.d.ts +0 -45
- package/dist/middleware/json_body.d.ts +0 -4
- package/dist/middleware/performance_alerting.d.ts +0 -87
- package/dist/middleware/performance_dashboard.d.ts +0 -87
- package/dist/middleware/performance_monitor.d.ts +0 -209
- package/dist/middleware/pipeline.d.ts +0 -131
- package/dist/middleware/rate_limit.d.ts +0 -36
- package/dist/middleware/request_id.d.ts +0 -4
- package/dist/middleware/request_signing.d.ts +0 -153
- package/dist/middleware/request_tracer.d.ts +0 -72
- package/dist/middleware/response_cache.d.ts +0 -97
- package/dist/middleware/security.d.ts +0 -69
- package/dist/middleware/security_suite.d.ts +0 -46
- package/dist/middleware/session.d.ts +0 -23
- package/dist/model-binding/index.d.ts +0 -2
- package/dist/model-binding/model-middleware.d.ts +0 -118
- package/dist/model-binding/model-registry.d.ts +0 -164
- package/dist/model-binding.d.ts +0 -185
- package/dist/model-resolver-factory.d.ts +0 -31
- package/dist/observability/correlation.d.ts +0 -187
- package/dist/observability/health-checks.d.ts +0 -186
- package/dist/observability/index.d.ts +0 -60
- package/dist/observability/integration.d.ts +0 -147
- package/dist/observability/metrics.d.ts +0 -184
- package/dist/observability/tracing.d.ts +0 -187
- package/dist/optimization/bun-utilities.d.ts +0 -224
- package/dist/query-builder-integration.d.ts +0 -36
- package/dist/request/context.d.ts +0 -34
- package/dist/request/enhanced-request.d.ts +0 -213
- package/dist/request/macros.d.ts +0 -300
- package/dist/response/macros.d.ts +0 -245
- package/dist/response/response-factory.d.ts +0 -133
- package/dist/router/api-routes.d.ts +0 -5
- package/dist/router/file-based-routing.d.ts +0 -118
- package/dist/router/file-streaming.d.ts +0 -5
- package/dist/router/fluent-router.d.ts +0 -315
- package/dist/router/fluent-routing.d.ts +0 -271
- package/dist/router/group-organization.d.ts +0 -5
- package/dist/router/handler-resolver.d.ts +0 -24
- package/dist/router/http-methods.d.ts +0 -10
- package/dist/router/index.d.ts +0 -114
- package/dist/router/middleware-groups.d.ts +0 -94
- package/dist/router/middleware-integration.d.ts +0 -112
- package/dist/router/middleware.d.ts +0 -5
- package/dist/router/model-binding.d.ts +0 -5
- package/dist/router/optimized-route-matching.d.ts +0 -6
- package/dist/router/route-building.d.ts +0 -5
- package/dist/router/route-compiler.d.ts +0 -153
- package/dist/router/route-matching.d.ts +0 -5
- package/dist/router/route-trie.d.ts +0 -114
- package/dist/router/router.d.ts +0 -294
- package/dist/router/server.d.ts +0 -5
- package/dist/router/validation-integration.d.ts +0 -170
- package/dist/router/view-rendering.d.ts +0 -5
- package/dist/router/websocket.d.ts +0 -5
- package/dist/routing/route-caching.d.ts +0 -129
- package/dist/routing/route-throttling.d.ts +0 -150
- package/dist/routing/subdomain-routing.d.ts +0 -206
- package/dist/session/database-store.d.ts +0 -51
- package/dist/session/file-store.d.ts +0 -22
- package/dist/session/index.d.ts +0 -106
- package/dist/session/memory-store.d.ts +0 -21
- package/dist/session/redis-store.d.ts +0 -31
- package/dist/streaming/index.d.ts +0 -3
- package/dist/streaming/sse-handler.d.ts +0 -138
- package/dist/streaming/stream-handler.d.ts +0 -114
- package/dist/testing/auth-testing.d.ts +0 -156
- package/dist/testing/file-upload-testing.d.ts +0 -190
- package/dist/testing/index.d.ts +0 -10
- package/dist/testing/middleware-testing.d.ts +0 -138
- package/dist/testing/model-binding-testing.d.ts +0 -186
- package/dist/testing/performance-testing.d.ts +0 -235
- package/dist/testing/test-client.d.ts +0 -117
- package/dist/testing/test-request.d.ts +0 -85
- package/dist/testing/test-response.d.ts +0 -90
- package/dist/testing/types.d.ts +0 -207
- package/dist/testing/websocket-testing.d.ts +0 -227
- package/dist/types/controller-types.d.ts +0 -208
- package/dist/types/core.d.ts +0 -543
- package/dist/types/middleware-types.d.ts +0 -227
- package/dist/types/request-response-augmentation.d.ts +0 -261
- package/dist/types/route-inference.d.ts +0 -168
- package/dist/types.d.ts +0 -1782
- package/dist/url.d.ts +0 -56
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/query-preservation.d.ts +0 -48
- package/dist/utils.d.ts +0 -69
- package/dist/validation/validator.d.ts +0 -140
- package/dist/websocket/clustering.d.ts +0 -185
- package/src/auth.ts +0 -469
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -740
- package/src/container/contextual-binding.ts +0 -603
- package/src/container/decorators.ts +0 -359
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -732
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -621
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -581
- package/src/index.ts +0 -14
- package/src/middleware/auth.ts +0 -219
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -74
- package/src/middleware/csrf.ts +0 -108
- package/src/middleware/ddos_protection.ts +0 -255
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -264
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -449
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -538
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -943
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -245
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -636
- package/src/middleware/request_tracer.ts +0 -636
- package/src/middleware/response_cache.ts +0 -743
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -62
- package/src/request/enhanced-request.ts +0 -857
- package/src/request/macros.ts +0 -688
- package/src/response/macros.ts +0 -612
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -670
- package/src/router/file-streaming.ts +0 -372
- package/src/router/fluent-router.ts +0 -927
- package/src/router/fluent-routing.ts +0 -797
- package/src/router/group-organization.ts +0 -213
- package/src/router/handler-resolver.ts +0 -291
- package/src/router/http-methods.ts +0 -377
- package/src/router/index.ts +0 -187
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -231
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -253
- package/src/router/route-building.ts +0 -212
- package/src/router/route-compiler.ts +0 -702
- package/src/router/route-matching.ts +0 -349
- package/src/router/route-trie.ts +0 -464
- package/src/router/router.ts +0 -1581
- package/src/router/server.ts +0 -462
- package/src/router/validation-integration.ts +0 -443
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -446
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -419
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -357
- package/src/types.ts +0 -2055
- package/src/url.ts +0 -126
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -327
- package/src/validation/validator.ts +0 -685
- package/src/websocket/clustering.ts +0 -762
|
@@ -1,670 +0,0 @@
|
|
|
1
|
-
import type { ActionHandler, EnhancedRequest, QueryPreservationConfig } from '../types'
|
|
2
|
-
import type { Router } from './router'
|
|
3
|
-
import { join, relative, resolve, basename, dirname } from 'node:path'
|
|
4
|
-
import { readdirSync, statSync, existsSync } from 'node:fs'
|
|
5
|
-
import { injectQueryPreservationScript } from '../utils/query-preservation'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* File-based routing configuration
|
|
9
|
-
*/
|
|
10
|
-
export interface FileBasedRoutingConfig {
|
|
11
|
-
/**
|
|
12
|
-
* Whether automatic file-based routing is enabled
|
|
13
|
-
* Default: true (auto-enabled when views directory is detected)
|
|
14
|
-
*/
|
|
15
|
-
enabled?: boolean
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Directory containing view files for automatic routing
|
|
19
|
-
* Default: auto-detected from 'src/views', 'views', 'resources/views'
|
|
20
|
-
*/
|
|
21
|
-
viewsPath?: string
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* File extensions to treat as routable pages
|
|
25
|
-
* Default: ['.stx', '.html']
|
|
26
|
-
*/
|
|
27
|
-
extensions?: string[]
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Files/directories to exclude from routing
|
|
31
|
-
* Default: ['_', 'components', 'layouts', 'partials', 'scripts', 'styles']
|
|
32
|
-
*/
|
|
33
|
-
exclude?: string[]
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Directory containing component files for STX rendering
|
|
37
|
-
* Default: join(viewsPath, 'components')
|
|
38
|
-
*/
|
|
39
|
-
componentsDir?: string
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Directory containing layout files for STX rendering
|
|
43
|
-
* Default: join(viewsPath, 'layouts')
|
|
44
|
-
*/
|
|
45
|
-
layoutsDir?: string
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Directory containing partial files for STX rendering
|
|
49
|
-
* Default: join(viewsPath, 'partials')
|
|
50
|
-
*/
|
|
51
|
-
partialsDir?: string
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Custom render function for view files
|
|
55
|
-
* If not provided, attempts to use STX renderer or serves raw content
|
|
56
|
-
*/
|
|
57
|
-
render?: (filePath: string, data: Record<string, unknown>, request: EnhancedRequest) => Promise<Response>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface DiscoveredRoute {
|
|
61
|
-
filePath: string
|
|
62
|
-
routePath: string
|
|
63
|
-
params: string[]
|
|
64
|
-
isIndex: boolean
|
|
65
|
-
isDynamic: boolean
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Default directories to scan for views (in order of priority)
|
|
70
|
-
*/
|
|
71
|
-
const DEFAULT_VIEW_DIRECTORIES: string[] = [
|
|
72
|
-
'src/views',
|
|
73
|
-
'views',
|
|
74
|
-
'resources/views',
|
|
75
|
-
'app/views',
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Default exclusion patterns for non-routable files/directories
|
|
80
|
-
*/
|
|
81
|
-
const DEFAULT_EXCLUDES: string[] = [
|
|
82
|
-
'_', // Underscore-prefixed files are private
|
|
83
|
-
'components', // Component partials
|
|
84
|
-
'layouts', // Layout templates
|
|
85
|
-
'partials', // Partial templates
|
|
86
|
-
'scripts', // Script files
|
|
87
|
-
'styles', // Style files
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Default routable file extensions
|
|
92
|
-
*/
|
|
93
|
-
const DEFAULT_EXTENSIONS: string[] = ['.stx', '.html']
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Auto-detect the views directory from common conventions
|
|
97
|
-
*/
|
|
98
|
-
function detectViewsDirectory(cwd: string = process.cwd()): string | null {
|
|
99
|
-
for (const dir of DEFAULT_VIEW_DIRECTORIES) {
|
|
100
|
-
const fullPath = resolve(cwd, dir)
|
|
101
|
-
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
102
|
-
return fullPath
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return null
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Check if a directory contains any routable files
|
|
110
|
-
*/
|
|
111
|
-
function hasRoutableFiles(dir: string, extensions: string[], exclude: string[]): boolean {
|
|
112
|
-
if (!existsSync(dir)) return false
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const entries = readdirSync(dir, { withFileTypes: true })
|
|
116
|
-
for (const entry of entries) {
|
|
117
|
-
if (exclude.some(pattern => entry.name.startsWith(pattern) || entry.name === pattern)) {
|
|
118
|
-
continue
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
122
|
-
return true
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (entry.isDirectory()) {
|
|
126
|
-
const subDir = join(dir, entry.name)
|
|
127
|
-
if (hasRoutableFiles(subDir, extensions, exclude)) {
|
|
128
|
-
return true
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// Directory not readable
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return false
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Converts a file path to a route path
|
|
142
|
-
* Examples:
|
|
143
|
-
* views/index.stx -> /
|
|
144
|
-
* views/about.stx -> /about
|
|
145
|
-
* views/dashboard/index.stx -> /dashboard
|
|
146
|
-
* views/dashboard/errors.stx -> /dashboard/errors
|
|
147
|
-
* views/users/[id].stx -> /users/{id}
|
|
148
|
-
* views/posts/[...slug].stx -> /posts/{slug}*
|
|
149
|
-
*/
|
|
150
|
-
function filePathToRoutePath(filePath: string, viewsDir: string, extensions: string[]): { routePath: string; params: string[] } {
|
|
151
|
-
// Get relative path from views directory
|
|
152
|
-
let routePath = relative(viewsDir, filePath)
|
|
153
|
-
|
|
154
|
-
// Remove file extension
|
|
155
|
-
for (const ext of extensions) {
|
|
156
|
-
if (routePath.endsWith(ext)) {
|
|
157
|
-
routePath = routePath.slice(0, -ext.length)
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Convert directory separators to forward slashes
|
|
163
|
-
routePath = routePath.replace(/\\/g, '/')
|
|
164
|
-
|
|
165
|
-
// Handle index files
|
|
166
|
-
if (routePath === 'index' || routePath.endsWith('/index')) {
|
|
167
|
-
routePath = routePath.replace(/\/?index$/, '')
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Extract dynamic parameters
|
|
171
|
-
const params: string[] = []
|
|
172
|
-
|
|
173
|
-
// Convert [...param] to {param}* (catch-all) syntax
|
|
174
|
-
routePath = routePath.replace(/\[\.\.\.([^\]]+)\]/g, (_match, param) => {
|
|
175
|
-
params.push(param)
|
|
176
|
-
return `{${param}}*`
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
// Convert [param] to {param} syntax
|
|
180
|
-
routePath = routePath.replace(/\[([^\]]+)\]/g, (_match, param) => {
|
|
181
|
-
params.push(param)
|
|
182
|
-
return `{${param}}`
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
// Ensure leading slash
|
|
186
|
-
if (!routePath.startsWith('/')) {
|
|
187
|
-
routePath = `/${routePath}`
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Handle root path
|
|
191
|
-
if (routePath === '/') {
|
|
192
|
-
return { routePath: '/', params }
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { routePath, params }
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Recursively discovers all routable files in a directory
|
|
200
|
-
*/
|
|
201
|
-
function discoverRoutes(
|
|
202
|
-
dir: string,
|
|
203
|
-
viewsDir: string,
|
|
204
|
-
extensions: string[],
|
|
205
|
-
exclude: string[],
|
|
206
|
-
): DiscoveredRoute[] {
|
|
207
|
-
const routes: DiscoveredRoute[] = []
|
|
208
|
-
|
|
209
|
-
if (!existsSync(dir)) {
|
|
210
|
-
return routes
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const entries = readdirSync(dir, { withFileTypes: true })
|
|
214
|
-
|
|
215
|
-
for (const entry of entries) {
|
|
216
|
-
const fullPath = join(dir, entry.name)
|
|
217
|
-
|
|
218
|
-
// Skip excluded files/directories
|
|
219
|
-
if (exclude.some(pattern => entry.name.startsWith(pattern) || entry.name === pattern)) {
|
|
220
|
-
continue
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (entry.isDirectory()) {
|
|
224
|
-
// Recursively discover routes in subdirectories
|
|
225
|
-
routes.push(...discoverRoutes(fullPath, viewsDir, extensions, exclude))
|
|
226
|
-
}
|
|
227
|
-
else if (entry.isFile()) {
|
|
228
|
-
// Check if file has a routable extension
|
|
229
|
-
const hasRoutableExtension = extensions.some(ext => entry.name.endsWith(ext))
|
|
230
|
-
|
|
231
|
-
if (hasRoutableExtension) {
|
|
232
|
-
const { routePath, params } = filePathToRoutePath(fullPath, viewsDir, extensions)
|
|
233
|
-
|
|
234
|
-
routes.push({
|
|
235
|
-
filePath: fullPath,
|
|
236
|
-
routePath,
|
|
237
|
-
params,
|
|
238
|
-
isIndex: entry.name.startsWith('index.'),
|
|
239
|
-
isDynamic: params.length > 0,
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Sort routes: static routes before dynamic, more specific before less specific
|
|
246
|
-
routes.sort((a, b) => {
|
|
247
|
-
// Static routes come first
|
|
248
|
-
if (a.isDynamic !== b.isDynamic) {
|
|
249
|
-
return a.isDynamic ? 1 : -1
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// More segments = more specific
|
|
253
|
-
const aSegments = a.routePath.split('/').length
|
|
254
|
-
const bSegments = b.routePath.split('/').length
|
|
255
|
-
if (aSegments !== bSegments) {
|
|
256
|
-
return bSegments - aSegments
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Alphabetical for consistency
|
|
260
|
-
return a.routePath.localeCompare(b.routePath)
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
// Check for duplicate routes and warn
|
|
264
|
-
const seen = new Map<string, DiscoveredRoute>()
|
|
265
|
-
for (const route of routes) {
|
|
266
|
-
const existing = seen.get(route.routePath)
|
|
267
|
-
if (existing) {
|
|
268
|
-
console.warn(
|
|
269
|
-
`[bun-router] Warning: Duplicate route "${route.routePath}" found:\n` +
|
|
270
|
-
` - ${existing.filePath}\n` +
|
|
271
|
-
` - ${route.filePath}\n` +
|
|
272
|
-
` Using: ${existing.filePath}`
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
seen.set(route.routePath, route)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return Array.from(seen.values())
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Check for pre-built HTML version of a view file
|
|
285
|
-
* Looks in dist/views/ for production builds
|
|
286
|
-
*/
|
|
287
|
-
function findPrebuiltView(stxFilePath: string, viewsDir: string): string | null {
|
|
288
|
-
// Get relative path from views directory
|
|
289
|
-
const relativePath = relative(viewsDir, stxFilePath)
|
|
290
|
-
|
|
291
|
-
// Check common pre-built locations
|
|
292
|
-
const prebuiltLocations = [
|
|
293
|
-
resolve(process.cwd(), 'dist/views', relativePath.replace(/\.stx$/, '.html')),
|
|
294
|
-
resolve(process.cwd(), 'views', relativePath.replace(/\.stx$/, '.html')),
|
|
295
|
-
resolve('/var/task/views', relativePath.replace(/\.stx$/, '.html')), // Lambda
|
|
296
|
-
]
|
|
297
|
-
|
|
298
|
-
for (const location of prebuiltLocations) {
|
|
299
|
-
if (existsSync(location)) {
|
|
300
|
-
return location
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return null
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Render an STX file using the @stacksjs/stx library
|
|
309
|
-
*/
|
|
310
|
-
async function renderStxFile(
|
|
311
|
-
filePath: string,
|
|
312
|
-
viewsDir: string,
|
|
313
|
-
data: Record<string, unknown>,
|
|
314
|
-
routingConfig?: FileBasedRoutingConfig,
|
|
315
|
-
): Promise<string> {
|
|
316
|
-
// Dynamic import to avoid build-time resolution
|
|
317
|
-
const stxModule = '@stacksjs/stx'
|
|
318
|
-
const stx = await import(/* @vite-ignore */ stxModule)
|
|
319
|
-
|
|
320
|
-
if (!stx.processDirectives || !stx.extractVariables) {
|
|
321
|
-
throw new Error('STX library not properly loaded. Install with: bun add @stacksjs/stx')
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const content = await Bun.file(filePath).text()
|
|
325
|
-
|
|
326
|
-
// Extract script content and template
|
|
327
|
-
const scriptMatch = content.match(/<script\s+server\s*>([\s\S]*?)<\/script>/i)
|
|
328
|
-
const scriptContent = scriptMatch ? scriptMatch[1] : ''
|
|
329
|
-
let templateContent = scriptMatch
|
|
330
|
-
? content.replace(/<script\s+server\s*>[\s\S]*?<\/script>/i, '')
|
|
331
|
-
: content
|
|
332
|
-
|
|
333
|
-
// Replace <script client> with regular <script>
|
|
334
|
-
templateContent = templateContent.replace(/<script\s+client\s*>/gi, '<script>')
|
|
335
|
-
|
|
336
|
-
// Build context with data
|
|
337
|
-
const context: Record<string, unknown> = {
|
|
338
|
-
__filename: filePath,
|
|
339
|
-
__dirname: dirname(filePath),
|
|
340
|
-
props: data,
|
|
341
|
-
...data,
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Extract variables from server script
|
|
345
|
-
if (scriptContent) {
|
|
346
|
-
await stx.extractVariables(scriptContent, context, filePath)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Resolve componentsDir: user config > viewsDir/components fallback
|
|
350
|
-
const resolvedComponentsDir = routingConfig?.componentsDir
|
|
351
|
-
? resolve(routingConfig.componentsDir)
|
|
352
|
-
: join(viewsDir, 'components')
|
|
353
|
-
|
|
354
|
-
const resolvedLayoutsDir = routingConfig?.layoutsDir
|
|
355
|
-
? resolve(routingConfig.layoutsDir)
|
|
356
|
-
: join(viewsDir, 'layouts')
|
|
357
|
-
|
|
358
|
-
const resolvedPartialsDir = routingConfig?.partialsDir
|
|
359
|
-
? resolve(routingConfig.partialsDir)
|
|
360
|
-
: join(viewsDir, 'partials')
|
|
361
|
-
|
|
362
|
-
// Configure STX with proper paths
|
|
363
|
-
const config = {
|
|
364
|
-
...stx.defaultConfig,
|
|
365
|
-
componentsDir: resolvedComponentsDir,
|
|
366
|
-
layoutsDir: resolvedLayoutsDir,
|
|
367
|
-
partialsDir: resolvedPartialsDir,
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return stx.processDirectives(templateContent, context, filePath, config, new Set())
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Creates a handler for rendering a view file
|
|
375
|
-
*/
|
|
376
|
-
function createViewHandler(
|
|
377
|
-
filePath: string,
|
|
378
|
-
viewsDir: string,
|
|
379
|
-
config: FileBasedRoutingConfig,
|
|
380
|
-
queryPreservationConfig?: QueryPreservationConfig,
|
|
381
|
-
): ActionHandler {
|
|
382
|
-
return async (req: EnhancedRequest): Promise<Response> => {
|
|
383
|
-
const url = new URL(req.url)
|
|
384
|
-
|
|
385
|
-
// Build request data for template context
|
|
386
|
-
const data = {
|
|
387
|
-
params: req.params || {},
|
|
388
|
-
query: Object.fromEntries(url.searchParams),
|
|
389
|
-
url: req.url,
|
|
390
|
-
path: url.pathname,
|
|
391
|
-
method: req.method,
|
|
392
|
-
headers: Object.fromEntries(req.headers.entries()),
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Apply query preservation script to HTML if configured
|
|
397
|
-
*/
|
|
398
|
-
function applyQueryPreservation(html: string): string {
|
|
399
|
-
if (queryPreservationConfig?.enabled !== false && queryPreservationConfig?.preserve?.length) {
|
|
400
|
-
return injectQueryPreservationScript(html, queryPreservationConfig)
|
|
401
|
-
}
|
|
402
|
-
return html
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
// 1. Check for pre-built HTML (production optimization)
|
|
407
|
-
const prebuiltPath = findPrebuiltView(filePath, viewsDir)
|
|
408
|
-
if (prebuiltPath) {
|
|
409
|
-
let html = await Bun.file(prebuiltPath).text()
|
|
410
|
-
html = applyQueryPreservation(html)
|
|
411
|
-
return new Response(html, {
|
|
412
|
-
status: 200,
|
|
413
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
414
|
-
})
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// 2. Render STX file
|
|
418
|
-
if (filePath.endsWith('.stx')) {
|
|
419
|
-
let html = await renderStxFile(filePath, viewsDir, data, config)
|
|
420
|
-
html = applyQueryPreservation(html)
|
|
421
|
-
return new Response(html, {
|
|
422
|
-
status: 200,
|
|
423
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
424
|
-
})
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// 3. Serve raw HTML
|
|
428
|
-
let content = await Bun.file(filePath).text()
|
|
429
|
-
content = applyQueryPreservation(content)
|
|
430
|
-
return new Response(content, {
|
|
431
|
-
status: 200,
|
|
432
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
catch (error) {
|
|
436
|
-
console.error(`[bun-router] Error rendering ${filePath}:`, error)
|
|
437
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Registers file-based routing extension on the Router class
|
|
444
|
-
*/
|
|
445
|
-
export function registerFileBasedRouting(RouterClass: typeof Router): void {
|
|
446
|
-
Object.defineProperties(RouterClass.prototype, {
|
|
447
|
-
/**
|
|
448
|
-
* Internal: detected views directory
|
|
449
|
-
*/
|
|
450
|
-
_viewsDir: {
|
|
451
|
-
value: null as string | null,
|
|
452
|
-
writable: true,
|
|
453
|
-
configurable: true,
|
|
454
|
-
},
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Internal: file-based routing config
|
|
458
|
-
*/
|
|
459
|
-
_fileRoutingConfig: {
|
|
460
|
-
value: null as FileBasedRoutingConfig | null,
|
|
461
|
-
writable: true,
|
|
462
|
-
configurable: true,
|
|
463
|
-
},
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Internal: discovered file-based routes
|
|
467
|
-
*/
|
|
468
|
-
_fileBasedRoutes: {
|
|
469
|
-
value: [] as DiscoveredRoute[],
|
|
470
|
-
writable: true,
|
|
471
|
-
configurable: true,
|
|
472
|
-
},
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Internal: whether file routes have been initialized
|
|
476
|
-
*/
|
|
477
|
-
_fileRoutesInitialized: {
|
|
478
|
-
value: false,
|
|
479
|
-
writable: true,
|
|
480
|
-
configurable: true,
|
|
481
|
-
},
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Initialize automatic file-based routing
|
|
485
|
-
* Called automatically when serve() is invoked
|
|
486
|
-
*/
|
|
487
|
-
_initFileRoutes: {
|
|
488
|
-
async value(): Promise<void> {
|
|
489
|
-
if (this._fileRoutesInitialized) return
|
|
490
|
-
|
|
491
|
-
// Auto-detect views directory if not configured
|
|
492
|
-
const viewsDir = this._viewsDir || detectViewsDirectory()
|
|
493
|
-
if (!viewsDir) {
|
|
494
|
-
this._fileRoutesInitialized = true
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const config = this._fileRoutingConfig || {}
|
|
499
|
-
const extensions = config.extensions || DEFAULT_EXTENSIONS
|
|
500
|
-
const exclude = config.exclude || DEFAULT_EXCLUDES
|
|
501
|
-
|
|
502
|
-
// Check if there are any routable files
|
|
503
|
-
if (!hasRoutableFiles(viewsDir, extensions, exclude)) {
|
|
504
|
-
this._fileRoutesInitialized = true
|
|
505
|
-
return
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Discover and register routes
|
|
509
|
-
const routes = discoverRoutes(viewsDir, viewsDir, extensions, exclude)
|
|
510
|
-
|
|
511
|
-
if (this.config.verbose) {
|
|
512
|
-
console.log(`[bun-router] Auto-discovered ${routes.length} file-based routes from ${viewsDir}:`)
|
|
513
|
-
for (const route of routes) {
|
|
514
|
-
console.log(` ${route.routePath} -> ${relative(viewsDir, route.filePath)}`)
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Store viewsDir for use in handlers
|
|
519
|
-
this._viewsDir = viewsDir
|
|
520
|
-
|
|
521
|
-
// Register each discovered route
|
|
522
|
-
for (const route of routes) {
|
|
523
|
-
const handler = createViewHandler(route.filePath, viewsDir, config, this.config.queryPreservation)
|
|
524
|
-
|
|
525
|
-
// Only register if no explicit route exists for this path
|
|
526
|
-
const existingRoute = this.routes.find(
|
|
527
|
-
(r: { path: string; method: string }) => r.path === route.routePath && r.method === 'GET'
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
if (!existingRoute) {
|
|
531
|
-
this.get(route.routePath, handler, 'web')
|
|
532
|
-
this._fileBasedRoutes.push(route)
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
this._fileRoutesInitialized = true
|
|
537
|
-
},
|
|
538
|
-
writable: true,
|
|
539
|
-
configurable: true,
|
|
540
|
-
},
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Configure file-based routing
|
|
544
|
-
* Call this before serve() to customize behavior
|
|
545
|
-
*/
|
|
546
|
-
views: {
|
|
547
|
-
value(config: FileBasedRoutingConfig | string): Router {
|
|
548
|
-
if (typeof config === 'string') {
|
|
549
|
-
// Just a path
|
|
550
|
-
this._viewsDir = resolve(config)
|
|
551
|
-
this._fileRoutingConfig = {}
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
// Full config
|
|
555
|
-
if (config.viewsPath) {
|
|
556
|
-
this._viewsDir = resolve(config.viewsPath)
|
|
557
|
-
}
|
|
558
|
-
this._fileRoutingConfig = config
|
|
559
|
-
}
|
|
560
|
-
return this
|
|
561
|
-
},
|
|
562
|
-
writable: true,
|
|
563
|
-
configurable: true,
|
|
564
|
-
},
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Explicitly disable file-based routing
|
|
568
|
-
*/
|
|
569
|
-
disableFileRouting: {
|
|
570
|
-
value(): Router {
|
|
571
|
-
this._fileRoutingConfig = { enabled: false }
|
|
572
|
-
return this
|
|
573
|
-
},
|
|
574
|
-
writable: true,
|
|
575
|
-
configurable: true,
|
|
576
|
-
},
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* Get list of discovered file-based routes
|
|
580
|
-
*/
|
|
581
|
-
getFileRoutes: {
|
|
582
|
-
value(): DiscoveredRoute[] {
|
|
583
|
-
return this._fileBasedRoutes || []
|
|
584
|
-
},
|
|
585
|
-
writable: true,
|
|
586
|
-
configurable: true,
|
|
587
|
-
},
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Legacy method for manual file routes loading
|
|
591
|
-
* Kept for backwards compatibility
|
|
592
|
-
*/
|
|
593
|
-
fileRoutes: {
|
|
594
|
-
async value(viewsPath?: string, options?: Omit<FileBasedRoutingConfig, 'viewsPath'>): Promise<Router> {
|
|
595
|
-
if (viewsPath) {
|
|
596
|
-
this._viewsDir = resolve(viewsPath)
|
|
597
|
-
}
|
|
598
|
-
if (options) {
|
|
599
|
-
this._fileRoutingConfig = { ...this._fileRoutingConfig, ...options }
|
|
600
|
-
}
|
|
601
|
-
// Force re-initialization
|
|
602
|
-
this._fileRoutesInitialized = false
|
|
603
|
-
await this._initFileRoutes()
|
|
604
|
-
return this
|
|
605
|
-
},
|
|
606
|
-
writable: true,
|
|
607
|
-
configurable: true,
|
|
608
|
-
},
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Legacy method alias
|
|
612
|
-
*/
|
|
613
|
-
loadFileRoutes: {
|
|
614
|
-
async value(config: FileBasedRoutingConfig = {}): Promise<Router> {
|
|
615
|
-
return this.fileRoutes(config.viewsPath, config)
|
|
616
|
-
},
|
|
617
|
-
writable: true,
|
|
618
|
-
configurable: true,
|
|
619
|
-
},
|
|
620
|
-
})
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Type augmentation for Router
|
|
624
|
-
declare module './router' {
|
|
625
|
-
interface Router {
|
|
626
|
-
_viewsDir?: string | null
|
|
627
|
-
_fileRoutingConfig?: FileBasedRoutingConfig | null
|
|
628
|
-
_fileBasedRoutes?: DiscoveredRoute[]
|
|
629
|
-
_fileRoutesInitialized?: boolean
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Internal: Initialize file-based routing (called by serve())
|
|
633
|
-
*/
|
|
634
|
-
_initFileRoutes(): Promise<void>
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Configure file-based routing
|
|
638
|
-
* @param config - Path to views directory or full configuration object
|
|
639
|
-
* @example
|
|
640
|
-
* router.views('src/views')
|
|
641
|
-
* router.views({ viewsPath: 'src/views', extensions: ['.stx'] })
|
|
642
|
-
*/
|
|
643
|
-
views(config: FileBasedRoutingConfig | string): Router
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* Disable automatic file-based routing
|
|
647
|
-
*/
|
|
648
|
-
disableFileRouting(): Router
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Get the list of discovered file-based routes
|
|
652
|
-
*/
|
|
653
|
-
getFileRoutes(): DiscoveredRoute[]
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Manually trigger file-based route discovery
|
|
657
|
-
* @param viewsPath - Optional path to views directory
|
|
658
|
-
* @param options - Additional configuration options
|
|
659
|
-
*/
|
|
660
|
-
fileRoutes(viewsPath?: string, options?: Omit<FileBasedRoutingConfig, 'viewsPath'>): Promise<Router>
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Legacy: Load file-based routes with configuration
|
|
664
|
-
*/
|
|
665
|
-
loadFileRoutes(config?: FileBasedRoutingConfig): Promise<Router>
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
export type { DiscoveredRoute }
|
|
670
|
-
export { detectViewsDirectory, discoverRoutes, DEFAULT_VIEW_DIRECTORIES, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS }
|