@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
package/src/url.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Named-route URL generation
|
|
3
|
-
*
|
|
4
|
-
* A module-level registry of `name → path` populated by both `Router` and
|
|
5
|
-
* `FluentRouter` whenever a route is given a name. `url(name, params)`
|
|
6
|
-
* resolves a registered name into a URL with path parameters substituted
|
|
7
|
-
* and unmatched params appended as a query string.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import process from 'node:process'
|
|
11
|
-
|
|
12
|
-
const namedRouteRegistry: Map<string, string> = new Map()
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Register a named-route path. Called by Router/FluentRouter when a route
|
|
16
|
-
* gets a `.name()`.
|
|
17
|
-
*/
|
|
18
|
-
export function registerNamedRoute(name: string, path: string): void {
|
|
19
|
-
namedRouteRegistry.set(name, path)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Look up a registered named-route path without parameter substitution.
|
|
24
|
-
* Mostly useful for tests and tooling.
|
|
25
|
-
*/
|
|
26
|
-
export function getNamedRoutePath(name: string): string | undefined {
|
|
27
|
-
return namedRouteRegistry.get(name)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns a snapshot of every registered named-route. The map itself is not
|
|
32
|
-
* exposed so callers can't mutate the registry.
|
|
33
|
-
*/
|
|
34
|
-
export function getNamedRoutes(): ReadonlyMap<string, string> {
|
|
35
|
-
return new Map(namedRouteRegistry)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Clear the registry. Provided for tests; production code should not need it.
|
|
40
|
-
*/
|
|
41
|
-
export function clearNamedRoutes(): void {
|
|
42
|
-
namedRouteRegistry.clear()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface UrlOptions {
|
|
46
|
-
/**
|
|
47
|
-
* Return a fully-qualified URL using `process.env.APP_URL` (falling back to
|
|
48
|
-
* `http://localhost`) instead of a path. Defaults to `false`.
|
|
49
|
-
*/
|
|
50
|
-
absolute?: boolean
|
|
51
|
-
/**
|
|
52
|
-
* Override the base used for `absolute: true`. Wins over `APP_URL`.
|
|
53
|
-
*/
|
|
54
|
-
base?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Generate a URL for a named route.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```ts
|
|
62
|
-
* router.get('/users/{id}', handler).name('users.show')
|
|
63
|
-
*
|
|
64
|
-
* url('users.show', { id: 42 })
|
|
65
|
-
* // → '/users/42'
|
|
66
|
-
*
|
|
67
|
-
* url('users.show', { id: 42, tab: 'profile' })
|
|
68
|
-
* // → '/users/42?tab=profile'
|
|
69
|
-
*
|
|
70
|
-
* url('users.show', { id: 42 }, { absolute: true })
|
|
71
|
-
* // → 'https://app.example/users/42' (when APP_URL=https://app.example)
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export function url(
|
|
75
|
-
name: string,
|
|
76
|
-
params: Record<string, string | number | boolean> = {},
|
|
77
|
-
options: UrlOptions = {},
|
|
78
|
-
): string {
|
|
79
|
-
const path = namedRouteRegistry.get(name)
|
|
80
|
-
if (!path) {
|
|
81
|
-
const known = [...namedRouteRegistry.keys()].sort().join(', ') || '(none registered)'
|
|
82
|
-
throw new Error(`Named route '${name}' is not defined. Known routes: ${known}`)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const consumed = new Set<string>()
|
|
86
|
-
let resolved = path
|
|
87
|
-
|
|
88
|
-
for (const [key, value] of Object.entries(params)) {
|
|
89
|
-
const required = `{${key}}`
|
|
90
|
-
const optional = `{${key}?}`
|
|
91
|
-
const encoded = encodeURIComponent(String(value))
|
|
92
|
-
|
|
93
|
-
if (resolved.includes(required)) {
|
|
94
|
-
resolved = resolved.replaceAll(required, encoded)
|
|
95
|
-
consumed.add(key)
|
|
96
|
-
}
|
|
97
|
-
else if (resolved.includes(optional)) {
|
|
98
|
-
resolved = resolved.replaceAll(optional, encoded)
|
|
99
|
-
consumed.add(key)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Strip any unfilled optional placeholders (e.g. `/posts/{slug?}` → `/posts/`)
|
|
104
|
-
resolved = resolved.replace(/\/\{[^}]+\?\}/g, '')
|
|
105
|
-
|
|
106
|
-
// Throw on unfilled required placeholders so the caller sees the bug.
|
|
107
|
-
const missing = resolved.match(/\{([^}?]+)\}/g)
|
|
108
|
-
if (missing && missing.length > 0)
|
|
109
|
-
throw new Error(`Missing required params for route '${name}': ${missing.join(', ')}`)
|
|
110
|
-
|
|
111
|
-
// Append leftover params as query string.
|
|
112
|
-
const leftover = Object.entries(params).filter(([k]) => !consumed.has(k))
|
|
113
|
-
if (leftover.length > 0) {
|
|
114
|
-
const qs = new URLSearchParams()
|
|
115
|
-
for (const [k, v] of leftover) qs.set(k, String(v))
|
|
116
|
-
resolved += `?${qs.toString()}`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (options.absolute) {
|
|
120
|
-
const base = (options.base ?? process.env.APP_URL ?? 'http://localhost').replace(/\/$/, '')
|
|
121
|
-
const prefixed = base.startsWith('http') ? base : `https://${base}`
|
|
122
|
-
return `${prefixed}${resolved.startsWith('/') ? resolved : `/${resolved}`}`
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return resolved
|
|
126
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './query-preservation'
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query Parameter Preservation
|
|
3
|
-
*
|
|
4
|
-
* Automatically preserves specified query parameters across navigation.
|
|
5
|
-
* This is useful for maintaining state like siteId, theme, locale, etc.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface QueryPreservationConfig {
|
|
9
|
-
/**
|
|
10
|
-
* Whether query preservation is enabled
|
|
11
|
-
* Default: true when preserve array is non-empty
|
|
12
|
-
*/
|
|
13
|
-
enabled?: boolean
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Query parameters to preserve across navigation
|
|
17
|
-
* Example: ['siteId', 'theme', 'locale']
|
|
18
|
-
*/
|
|
19
|
-
preserve?: string[]
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Query parameters to never preserve (takes precedence over preserve)
|
|
23
|
-
* Example: ['_t', 'utm_source']
|
|
24
|
-
*/
|
|
25
|
-
exclude?: string[]
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* URL patterns where preservation should apply
|
|
29
|
-
* Default: all internal links (same origin)
|
|
30
|
-
* Example: ['/dashboard/*', '/admin/*']
|
|
31
|
-
*/
|
|
32
|
-
routes?: string[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Default configuration
|
|
37
|
-
*/
|
|
38
|
-
export const defaultQueryPreservationConfig: QueryPreservationConfig = {
|
|
39
|
-
enabled: true,
|
|
40
|
-
preserve: [],
|
|
41
|
-
exclude: ['_t', '_cache', 'callback'],
|
|
42
|
-
routes: [],
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* eslint-disable prefer-const */
|
|
46
|
-
/**
|
|
47
|
-
* Generate the client-side script for query preservation
|
|
48
|
-
* This script intercepts link clicks and preserves specified query params
|
|
49
|
-
*/
|
|
50
|
-
export function generateQueryPreservationScript(config: QueryPreservationConfig): string {
|
|
51
|
-
const { preserve = [], exclude = [], routes = [] } = config
|
|
52
|
-
|
|
53
|
-
if (!preserve.length) {
|
|
54
|
-
return ''
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Minified but readable client-side script
|
|
58
|
-
return `<script data-query-preservation>
|
|
59
|
-
(function(){
|
|
60
|
-
var preserve = ${JSON.stringify(preserve)};
|
|
61
|
-
var exclude = ${JSON.stringify(exclude)};
|
|
62
|
-
var routes = ${JSON.stringify(routes)};
|
|
63
|
-
|
|
64
|
-
function shouldPreserve(href) {
|
|
65
|
-
// Only preserve for same-origin links
|
|
66
|
-
try {
|
|
67
|
-
var url = new URL(href, window.location.origin);
|
|
68
|
-
if (url.origin !== window.location.origin) return false;
|
|
69
|
-
// Check route patterns if specified
|
|
70
|
-
if (routes.length) {
|
|
71
|
-
var path = url.pathname;
|
|
72
|
-
return routes.some(function(pattern) {
|
|
73
|
-
if (pattern.endsWith('*')) {
|
|
74
|
-
return path.startsWith(pattern.slice(0, -1));
|
|
75
|
-
}
|
|
76
|
-
// eslint-disable-next-line prefer-template
|
|
77
|
-
return path === pattern || path.startsWith(pattern + '/');
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
catch (e) { return false; }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function getPreservedParams() {
|
|
86
|
-
var current = new URLSearchParams(window.location.search);
|
|
87
|
-
var preserved = {};
|
|
88
|
-
preserve.forEach(function(key) {
|
|
89
|
-
if (current.has(key) && exclude.indexOf(key) === -1) {
|
|
90
|
-
preserved[key] = current.get(key);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
return preserved;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function enhanceUrl(href) {
|
|
97
|
-
var url = new URL(href, window.location.origin);
|
|
98
|
-
var preserved = getPreservedParams();
|
|
99
|
-
Object.keys(preserved).forEach(function(key) {
|
|
100
|
-
if (!url.searchParams.has(key)) {
|
|
101
|
-
url.searchParams.set(key, preserved[key]);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
return url.toString();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Intercept all link clicks
|
|
108
|
-
document.addEventListener('click', function(e) {
|
|
109
|
-
var link = e.target.closest('a[href]');
|
|
110
|
-
if (!link) return;
|
|
111
|
-
|
|
112
|
-
var href = link.getAttribute('href');
|
|
113
|
-
if (!href || href.startsWith('#') || href.startsWith('javascript:') ||
|
|
114
|
-
href.startsWith('mailto:') || href.startsWith('tel:')) return;
|
|
115
|
-
|
|
116
|
-
if (!shouldPreserve(href)) return;
|
|
117
|
-
|
|
118
|
-
// Check if link already has all preserved params
|
|
119
|
-
var enhanced = enhanceUrl(href);
|
|
120
|
-
if (enhanced !== new URL(href, window.location.origin).toString()) {
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
window.location.href = enhanced;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Expose API for programmatic navigation
|
|
127
|
-
window.__preserveQuery = {
|
|
128
|
-
enhance: enhanceUrl,
|
|
129
|
-
getPreserved: getPreservedParams,
|
|
130
|
-
navigate: function(href) {
|
|
131
|
-
window.location.href = enhanceUrl(href);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
})();
|
|
135
|
-
</script>`
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Inject the query preservation script into HTML content
|
|
140
|
-
* Inserts before </head> or </body> if </head> not found
|
|
141
|
-
*/
|
|
142
|
-
export function injectQueryPreservationScript(
|
|
143
|
-
html: string,
|
|
144
|
-
config: QueryPreservationConfig
|
|
145
|
-
): string {
|
|
146
|
-
if (!config.enabled || !config.preserve?.length) {
|
|
147
|
-
return html
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const script = generateQueryPreservationScript(config)
|
|
151
|
-
if (!script) {
|
|
152
|
-
return html
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Try to inject before </head> for early execution
|
|
156
|
-
if (html.includes('</head>')) {
|
|
157
|
-
return html.replace('</head>', `${script}\n</head>`)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Fallback: inject before </body>
|
|
161
|
-
if (html.includes('</body>')) {
|
|
162
|
-
return html.replace('</body>', `${script}\n</body>`)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Last resort: append to end
|
|
166
|
-
return html + script
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Build a URL with preserved query parameters
|
|
171
|
-
* Server-side utility for generating links
|
|
172
|
-
*/
|
|
173
|
-
export function buildUrlWithPreservedParams(
|
|
174
|
-
path: string,
|
|
175
|
-
currentUrl: string | URL,
|
|
176
|
-
config: QueryPreservationConfig,
|
|
177
|
-
additionalParams?: Record<string, string>
|
|
178
|
-
): string {
|
|
179
|
-
const current = typeof currentUrl === 'string' ? new URL(currentUrl) : currentUrl
|
|
180
|
-
const target = new URL(path, current.origin)
|
|
181
|
-
|
|
182
|
-
// Preserve specified params from current URL
|
|
183
|
-
if (config.preserve) {
|
|
184
|
-
for (const key of config.preserve) {
|
|
185
|
-
if (current.searchParams.has(key) && !config.exclude?.includes(key)) {
|
|
186
|
-
if (!target.searchParams.has(key)) {
|
|
187
|
-
target.searchParams.set(key, current.searchParams.get(key)!)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Add any additional params
|
|
194
|
-
if (additionalParams) {
|
|
195
|
-
for (const [key, value] of Object.entries(additionalParams)) {
|
|
196
|
-
target.searchParams.set(key, value)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return target.pathname + target.search
|
|
201
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import type { ActionHandler, ActionHandlerClass, RouteHandler } from './types'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
// Re-export query preservation utilities
|
|
5
|
-
export * from './utils/query-preservation'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Normalizes a path by ensuring it starts with a forward slash and has no trailing slash
|
|
9
|
-
* @param path The path to normalize
|
|
10
|
-
*/
|
|
11
|
-
export function normalizePath(path: string): string {
|
|
12
|
-
// First replace double (or more) slashes with a single slash
|
|
13
|
-
let normalizedPath = path.replace(/\/+/g, '/')
|
|
14
|
-
|
|
15
|
-
// Then ensure there's a leading slash and no trailing slash (unless it's just /)
|
|
16
|
-
if (!normalizedPath.startsWith('/')) {
|
|
17
|
-
normalizedPath = `/${normalizedPath}`
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
21
|
-
normalizedPath = normalizedPath.slice(0, -1)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return normalizedPath
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Converts a string path to an action path for dynamic imports
|
|
29
|
-
* @param path The path to convert (e.g., 'Actions/Home/IndexAction')
|
|
30
|
-
*/
|
|
31
|
-
export function toActionPath(path: string): string {
|
|
32
|
-
return path.replace(/\//g, '_').toLowerCase()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if a handler is a class constructor implementing ActionHandlerClass
|
|
37
|
-
* @param handler The handler to check
|
|
38
|
-
*/
|
|
39
|
-
export function isActionClass(handler: ActionHandler): handler is new () => ActionHandlerClass {
|
|
40
|
-
if (typeof handler !== 'function' && typeof handler !== 'object') {
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check if it's a class constructor
|
|
45
|
-
if (typeof handler === 'function' && handler.prototype) {
|
|
46
|
-
const prototype = handler.prototype as unknown as { handle?: unknown }
|
|
47
|
-
if (typeof prototype.handle === 'function') {
|
|
48
|
-
return true
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check if it's an instance with a handle method
|
|
53
|
-
if (typeof handler === 'object' && handler !== null && 'handle' in handler && typeof (handler as any).handle === 'function') {
|
|
54
|
-
return true
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return false
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Checks if a handler is a route handler function
|
|
62
|
-
* @param handler The handler to check
|
|
63
|
-
*/
|
|
64
|
-
export function isRouteHandler(handler: ActionHandler): handler is RouteHandler {
|
|
65
|
-
return typeof handler === 'function' && !isActionClass(handler)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Extracts named parameters from a path pattern
|
|
70
|
-
* @param pattern The path pattern (e.g., '/users/{id}/posts/{postId}')
|
|
71
|
-
*/
|
|
72
|
-
export function extractParamNames(pattern: string): string[] {
|
|
73
|
-
const matches = pattern.match(/\{([^}]+)\}/g)
|
|
74
|
-
return matches ? matches.map(m => m.slice(1, -1).replace('?', '')) : []
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Creates a regex pattern from a path pattern
|
|
79
|
-
* @param pattern The path pattern
|
|
80
|
-
*/
|
|
81
|
-
export function createPathRegex(pattern: string): RegExp {
|
|
82
|
-
const regexPattern = pattern.replace(/\{([^}]+)\}/g, '([^/]+)')
|
|
83
|
-
return new RegExp(`^${regexPattern}$`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Matches a path against a pattern and extracts parameters
|
|
88
|
-
* @param pattern The path pattern (e.g., '/users/{id}')
|
|
89
|
-
* @param path The actual path (e.g., '/users/123')
|
|
90
|
-
* @param params Output parameter object that will contain extracted parameters
|
|
91
|
-
* @param constraints Optional constraints for route parameters
|
|
92
|
-
* @returns Boolean indicating whether the path matches the pattern
|
|
93
|
-
*/
|
|
94
|
-
export function matchPath(
|
|
95
|
-
pattern: string,
|
|
96
|
-
path: string,
|
|
97
|
-
params: Record<string, string>,
|
|
98
|
-
constraints?: Record<string, string>,
|
|
99
|
-
): boolean {
|
|
100
|
-
// Both paths should be normalized
|
|
101
|
-
const normalizedPattern = normalizePath(pattern)
|
|
102
|
-
const normalizedPath = normalizePath(path)
|
|
103
|
-
|
|
104
|
-
// Special case for wildcard patterns
|
|
105
|
-
if (pattern.endsWith('/*')) {
|
|
106
|
-
const basePattern = pattern.slice(0, -2)
|
|
107
|
-
if (path.startsWith(basePattern)) {
|
|
108
|
-
return true
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Handle patterns with parameters
|
|
113
|
-
const patternSegments = normalizedPattern.split('/').filter(Boolean)
|
|
114
|
-
const pathSegments = normalizedPath.split('/').filter(Boolean)
|
|
115
|
-
|
|
116
|
-
// Quick check - if segments don't match (accounting for optional params) then no match
|
|
117
|
-
// For mandatory parameters, the pattern and path must have same number of segments
|
|
118
|
-
const optionalParamCount = (normalizedPattern.match(/\{[^}]+\?\}/g) || []).length
|
|
119
|
-
|
|
120
|
-
if (pathSegments.length < patternSegments.length - optionalParamCount
|
|
121
|
-
|| pathSegments.length > patternSegments.length) {
|
|
122
|
-
return false
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Match each segment
|
|
126
|
-
for (let i = 0; i < patternSegments.length; i++) {
|
|
127
|
-
const patternSegment = patternSegments[i]
|
|
128
|
-
const pathSegment = pathSegments[i]
|
|
129
|
-
|
|
130
|
-
// If we've run out of path segments and this is not an optional parameter
|
|
131
|
-
if (pathSegment === undefined) {
|
|
132
|
-
if (patternSegment.match(/\{[^}]+\?\}/)) {
|
|
133
|
-
// This is an optional parameter and we have no path segment for it
|
|
134
|
-
continue
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// Required parameter or static segment with no matching path segment
|
|
138
|
-
return false
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check if this segment is a parameter
|
|
143
|
-
const paramMatch = patternSegment.match(/\{([^}:]+)(?::[^}]+)?(\?)?\}/)
|
|
144
|
-
if (paramMatch) {
|
|
145
|
-
// This is a parameter segment, extract param name
|
|
146
|
-
const paramName = paramMatch[1].replace('?', '')
|
|
147
|
-
// Store the parameter value
|
|
148
|
-
params[paramName] = pathSegment
|
|
149
|
-
|
|
150
|
-
// Check constraints if they exist
|
|
151
|
-
if (constraints && constraints[paramName]) {
|
|
152
|
-
try {
|
|
153
|
-
const regex = new RegExp(`^${constraints[paramName]}$`)
|
|
154
|
-
if (!regex.test(pathSegment)) {
|
|
155
|
-
return false
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
// If regex is invalid, treat as no constraint
|
|
160
|
-
console.warn(`Invalid constraint regex for parameter ${paramName}: ${constraints[paramName]}`)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
else if (patternSegment !== pathSegment) {
|
|
165
|
-
// Static segment doesn't match
|
|
166
|
-
return false
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return true
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Joins path segments ensuring proper formatting
|
|
175
|
-
* @param segments Path segments to join
|
|
176
|
-
*/
|
|
177
|
-
export function joinPaths(...segments: string[]): string {
|
|
178
|
-
return normalizePath(segments.join('/'))
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Validates a route path format
|
|
183
|
-
* @param path The path to validate
|
|
184
|
-
*/
|
|
185
|
-
export function validatePath(path: string): boolean {
|
|
186
|
-
// Path must start with a slash
|
|
187
|
-
if (!path.startsWith('/')) {
|
|
188
|
-
return false
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Check for balanced curly braces
|
|
192
|
-
const stack: string[] = []
|
|
193
|
-
for (const char of path) {
|
|
194
|
-
if (char === '{') {
|
|
195
|
-
stack.push(char)
|
|
196
|
-
}
|
|
197
|
-
else if (char === '}') {
|
|
198
|
-
if (stack.length === 0 || stack.pop() !== '{') {
|
|
199
|
-
return false
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return stack.length === 0
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Check if a file exists at the given path
|
|
209
|
-
* @param path The file path to check
|
|
210
|
-
*/
|
|
211
|
-
export async function fileExists(path: string): Promise<boolean> {
|
|
212
|
-
try {
|
|
213
|
-
const file = Bun.file(path)
|
|
214
|
-
return await file.exists()
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Resolve a view name to an actual file path
|
|
223
|
-
* @param viewName The view name (without extension)
|
|
224
|
-
* @param viewsPath The base path for views
|
|
225
|
-
* @param extensions File extensions to try
|
|
226
|
-
*/
|
|
227
|
-
export async function resolveViewPath(
|
|
228
|
-
viewName: string,
|
|
229
|
-
viewsPath: string,
|
|
230
|
-
extensions: string[],
|
|
231
|
-
): Promise<string | null> {
|
|
232
|
-
// If viewName already contains an extension, try it directly
|
|
233
|
-
if (extensions.some(ext => viewName.endsWith(ext))) {
|
|
234
|
-
const fullPath = normalizePath(join(viewsPath, viewName))
|
|
235
|
-
if (await fileExists(fullPath)) {
|
|
236
|
-
return fullPath
|
|
237
|
-
}
|
|
238
|
-
return null
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Try each extension
|
|
242
|
-
for (const ext of extensions) {
|
|
243
|
-
const fullPath = normalizePath(join(viewsPath, `${viewName}${ext}`))
|
|
244
|
-
if (await fileExists(fullPath)) {
|
|
245
|
-
return fullPath
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return null
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Basic HTML template processing - replaces {{ varName }} with the corresponding value
|
|
254
|
-
* @param template The HTML template string
|
|
255
|
-
* @param data The data to inject into the template
|
|
256
|
-
*/
|
|
257
|
-
export function processHtmlTemplate(template: string, data: Record<string, any>): string {
|
|
258
|
-
// Process general variables {{ varName }}
|
|
259
|
-
let result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
260
|
-
const trimmedKey = key.trim()
|
|
261
|
-
const value = getNestedValue(data, trimmedKey)
|
|
262
|
-
return value !== undefined ? String(value) : match
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
// Process conditionals {{#if condition}} content {{/if}}
|
|
266
|
-
result = processConditionals(result, data)
|
|
267
|
-
|
|
268
|
-
// Process loops {{#each items}} content {{/each}}
|
|
269
|
-
result = processLoops(result, data)
|
|
270
|
-
|
|
271
|
-
return result
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Process conditional statements in templates
|
|
276
|
-
*/
|
|
277
|
-
function processConditionals(template: string, data: Record<string, any>): string {
|
|
278
|
-
// Match {{#if condition}} content {{/if}} or {{#if condition}} content {{else}} alternative {{/if}}
|
|
279
|
-
return template.replace(
|
|
280
|
-
/\{\{#if ([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
|
|
281
|
-
(_match, condition, content, alternative = '') => {
|
|
282
|
-
const conditionTrimmed = condition.trim()
|
|
283
|
-
const value = getNestedValue(data, conditionTrimmed)
|
|
284
|
-
// Explicitly check for falsy values (false, undefined, null, 0, "")
|
|
285
|
-
return value ? content : alternative
|
|
286
|
-
},
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Process loop statements in templates
|
|
292
|
-
*/
|
|
293
|
-
function processLoops(template: string, data: Record<string, any>): string {
|
|
294
|
-
// Match {{#each items}} content {{/each}}
|
|
295
|
-
return template.replace(
|
|
296
|
-
/\{\{#each ([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
|
|
297
|
-
(_match, arrayKey, content) => {
|
|
298
|
-
const array = getNestedValue(data, arrayKey.trim())
|
|
299
|
-
if (!Array.isArray(array))
|
|
300
|
-
return ''
|
|
301
|
-
|
|
302
|
-
return array.map((item) => {
|
|
303
|
-
// Replace {{@index}} with the current index
|
|
304
|
-
// Replace {{this}} with the current item
|
|
305
|
-
// Replace {{this.property}} with the current item's property
|
|
306
|
-
return content
|
|
307
|
-
.replace(/\{\{@index\}\}/g, String(array.indexOf(item)))
|
|
308
|
-
.replace(/\{\{this\}\}/g, String(item))
|
|
309
|
-
.replace(/\{\{this\.([^}]+)\}\}/g, (_matchStr: string, prop: string) => {
|
|
310
|
-
const value = item[prop.trim()]
|
|
311
|
-
return value !== undefined ? String(value) : ''
|
|
312
|
-
})
|
|
313
|
-
}).join('')
|
|
314
|
-
},
|
|
315
|
-
)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Get a nested value from an object using dot notation
|
|
320
|
-
* @param obj The object to extract value from
|
|
321
|
-
* @param path The path to the value (e.g., 'user.profile.name')
|
|
322
|
-
*/
|
|
323
|
-
function getNestedValue(obj: Record<string, any>, path: string): any {
|
|
324
|
-
return path.split('.').reduce((prev, curr) => {
|
|
325
|
-
return prev && prev[curr] !== undefined ? prev[curr] : undefined
|
|
326
|
-
}, obj)
|
|
327
|
-
}
|