@stacksjs/bun-router 0.0.5 → 0.0.6
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/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/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 -1
- package/dist/cli.js +1 -1
- 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 +2 -0
- package/dist/index.js +319 -205
- 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 +245 -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 +113 -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 +276 -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 +1776 -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 +3 -2
- 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 +678 -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 +219 -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 +264 -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 +612 -0
- package/src/response/response-factory.ts +588 -0
- package/src/router/api-routes.ts +243 -0
- package/src/router/file-based-routing.ts +670 -0
- package/src/router/file-streaming.ts +372 -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 +185 -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 +212 -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 +1508 -0
- package/src/router/server.ts +406 -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 +2049 -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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { WebSocketConfig } from '../types'
|
|
2
|
+
import type { Router } from './router'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket extension for Router class
|
|
6
|
+
*/
|
|
7
|
+
export function registerWebSocketHandling(RouterClass: typeof Router): void {
|
|
8
|
+
Object.defineProperties(RouterClass.prototype, {
|
|
9
|
+
/**
|
|
10
|
+
* Configure WebSocket handling
|
|
11
|
+
*/
|
|
12
|
+
websocket: {
|
|
13
|
+
value(config: WebSocketConfig): Router {
|
|
14
|
+
this.wsConfig = config
|
|
15
|
+
return this
|
|
16
|
+
},
|
|
17
|
+
writable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Publish a message to a WebSocket topic
|
|
23
|
+
*/
|
|
24
|
+
publish: {
|
|
25
|
+
value(topic: string, data: string | ArrayBuffer | Uint8Array, compress = false): number {
|
|
26
|
+
if (!this.serverInstance) {
|
|
27
|
+
throw new Error('Server not started, cannot publish to WebSocket topics')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return this.serverInstance.publish(topic, data, compress)
|
|
31
|
+
},
|
|
32
|
+
writable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the number of subscribers for a WebSocket topic
|
|
38
|
+
*/
|
|
39
|
+
subscriberCount: {
|
|
40
|
+
value(topic: string): number {
|
|
41
|
+
if (!this.serverInstance) {
|
|
42
|
+
throw new Error('Server not started, cannot get subscriber count')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return this.serverInstance.subscriberCount(topic)
|
|
46
|
+
},
|
|
47
|
+
writable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Upgrade an HTTP request to a WebSocket connection
|
|
53
|
+
*/
|
|
54
|
+
upgrade: {
|
|
55
|
+
value(request: Request, options?: { headers?: Record<string, string>, data?: any }): boolean {
|
|
56
|
+
if (!this.serverInstance) {
|
|
57
|
+
throw new Error('Server not started, cannot upgrade to WebSocket')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!this.wsConfig) {
|
|
61
|
+
throw new Error('WebSocket configuration not set, use router.websocket() to configure')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.serverInstance.upgrade(request, options)
|
|
65
|
+
},
|
|
66
|
+
writable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set a timeout for a request
|
|
72
|
+
*/
|
|
73
|
+
timeout: {
|
|
74
|
+
value(request: Request, seconds: number): void {
|
|
75
|
+
if (!this.serverInstance) {
|
|
76
|
+
throw new Error('Server not started, cannot set timeout')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.serverInstance.timeout(request, seconds)
|
|
80
|
+
},
|
|
81
|
+
writable: true,
|
|
82
|
+
configurable: true,
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the IP address of a request
|
|
87
|
+
*/
|
|
88
|
+
requestIP: {
|
|
89
|
+
value(request: Request): { address: string, port: number } | null {
|
|
90
|
+
if (!this.serverInstance) {
|
|
91
|
+
throw new Error('Server not started, cannot get request IP')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this.serverInstance.requestIP(request)
|
|
95
|
+
},
|
|
96
|
+
writable: true,
|
|
97
|
+
configurable: true,
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import type { EnhancedRequest } from '../types'
|
|
2
|
+
import { LRUCache } from '../cache/lru-cache'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Route caching configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface RouteCacheConfig {
|
|
8
|
+
ttl?: number // Time to live in milliseconds
|
|
9
|
+
maxSize?: number // Maximum number of cached responses
|
|
10
|
+
tags?: string[] // Cache tags for invalidation
|
|
11
|
+
varyBy?: string[] // Headers to vary cache by
|
|
12
|
+
excludeQuery?: string[] // Query parameters to exclude from cache key
|
|
13
|
+
condition?: (req: EnhancedRequest) => boolean // Condition to determine if response should be cached
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cached response metadata
|
|
18
|
+
*/
|
|
19
|
+
export interface CachedResponse {
|
|
20
|
+
response: Response
|
|
21
|
+
headers: Record<string, string>
|
|
22
|
+
status: number
|
|
23
|
+
body: string | Uint8Array
|
|
24
|
+
timestamp: number
|
|
25
|
+
ttl?: number
|
|
26
|
+
tags: string[]
|
|
27
|
+
etag?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cache key generator for route responses
|
|
32
|
+
*/
|
|
33
|
+
export class RouteCacheKeyGenerator {
|
|
34
|
+
/**
|
|
35
|
+
* Generate cache key from request
|
|
36
|
+
*/
|
|
37
|
+
static generateKey(req: EnhancedRequest, config: RouteCacheConfig): string {
|
|
38
|
+
const url = new URL(req.url)
|
|
39
|
+
const method = req.method
|
|
40
|
+
const path = url.pathname
|
|
41
|
+
|
|
42
|
+
// Base key components
|
|
43
|
+
const keyParts = [method, path]
|
|
44
|
+
|
|
45
|
+
// Add query parameters (excluding specified ones)
|
|
46
|
+
const queryParams = new URLSearchParams(url.search)
|
|
47
|
+
const excludeQuery = config.excludeQuery || []
|
|
48
|
+
|
|
49
|
+
const relevantQuery: string[] = []
|
|
50
|
+
for (const [key, value] of queryParams.entries()) {
|
|
51
|
+
if (!excludeQuery.includes(key)) {
|
|
52
|
+
relevantQuery.push(`${key}=${value}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (relevantQuery.length > 0) {
|
|
57
|
+
keyParts.push(relevantQuery.sort().join('&'))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add vary headers
|
|
61
|
+
if (config.varyBy) {
|
|
62
|
+
const varyValues: string[] = []
|
|
63
|
+
for (const header of config.varyBy) {
|
|
64
|
+
const value = req.headers.get(header)
|
|
65
|
+
if (value) {
|
|
66
|
+
varyValues.push(`${header}:${value}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (varyValues.length > 0) {
|
|
70
|
+
keyParts.push(varyValues.join('|'))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return keyParts.join('::')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Route cache manager with tag-based invalidation
|
|
80
|
+
*/
|
|
81
|
+
export class RouteCacheManager {
|
|
82
|
+
private cache: LRUCache<CachedResponse>
|
|
83
|
+
private tagIndex: Map<string, Set<string>> = new Map() // tag -> cache keys
|
|
84
|
+
private keyTags: Map<string, Set<string>> = new Map() // cache key -> tags
|
|
85
|
+
|
|
86
|
+
constructor(maxSize: number = 1000) {
|
|
87
|
+
this.cache = new LRUCache<CachedResponse>({ maxSize })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get cached response
|
|
92
|
+
*/
|
|
93
|
+
async get(key: string): Promise<CachedResponse | null> {
|
|
94
|
+
const cached = this.cache.get(key)
|
|
95
|
+
if (!cached) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check TTL
|
|
100
|
+
if (cached.ttl && Date.now() - cached.timestamp > cached.ttl) {
|
|
101
|
+
this.delete(key)
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return cached
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Set cached response
|
|
110
|
+
*/
|
|
111
|
+
async set(key: string, response: CachedResponse): Promise<void> {
|
|
112
|
+
// Store in cache
|
|
113
|
+
this.cache.set(key, response)
|
|
114
|
+
|
|
115
|
+
// Update tag indexes
|
|
116
|
+
this.keyTags.set(key, new Set(response.tags))
|
|
117
|
+
|
|
118
|
+
for (const tag of response.tags) {
|
|
119
|
+
if (!this.tagIndex.has(tag)) {
|
|
120
|
+
this.tagIndex.set(tag, new Set())
|
|
121
|
+
}
|
|
122
|
+
this.tagIndex.get(tag)!.add(key)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Delete cached response
|
|
128
|
+
*/
|
|
129
|
+
delete(key: string): boolean {
|
|
130
|
+
const tags = this.keyTags.get(key)
|
|
131
|
+
if (tags) {
|
|
132
|
+
// Remove from tag indexes
|
|
133
|
+
for (const tag of tags) {
|
|
134
|
+
const tagKeys = this.tagIndex.get(tag)
|
|
135
|
+
if (tagKeys) {
|
|
136
|
+
tagKeys.delete(key)
|
|
137
|
+
if (tagKeys.size === 0) {
|
|
138
|
+
this.tagIndex.delete(tag)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.keyTags.delete(key)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this.cache.delete(key)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Invalidate cache by tags
|
|
150
|
+
*/
|
|
151
|
+
invalidateByTags(tags: string[]): number {
|
|
152
|
+
const keysToDelete = new Set<string>()
|
|
153
|
+
|
|
154
|
+
for (const tag of tags) {
|
|
155
|
+
const tagKeys = this.tagIndex.get(tag)
|
|
156
|
+
if (tagKeys) {
|
|
157
|
+
for (const key of tagKeys) {
|
|
158
|
+
keysToDelete.add(key)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const key of keysToDelete) {
|
|
164
|
+
this.delete(key)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return keysToDelete.size
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear all cache
|
|
172
|
+
*/
|
|
173
|
+
clear(): void {
|
|
174
|
+
this.cache.clear()
|
|
175
|
+
this.tagIndex.clear()
|
|
176
|
+
this.keyTags.clear()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get cache statistics
|
|
181
|
+
*/
|
|
182
|
+
getStats(): ReturnType<LRUCache<CachedResponse>['getStats']> & { totalTags: number, totalKeyTagMappings: number } {
|
|
183
|
+
return {
|
|
184
|
+
...this.cache.getStats(),
|
|
185
|
+
totalTags: this.tagIndex.size,
|
|
186
|
+
totalKeyTagMappings: this.keyTags.size,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Global route cache manager instance
|
|
193
|
+
*/
|
|
194
|
+
export const routeCacheManager: RouteCacheManager = new RouteCacheManager()
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Create route caching middleware
|
|
198
|
+
*/
|
|
199
|
+
export function createRouteCacheMiddleware(config: RouteCacheConfig) {
|
|
200
|
+
return async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
201
|
+
// Check if caching condition is met
|
|
202
|
+
if (config.condition && !config.condition(req)) {
|
|
203
|
+
return await next()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Only cache GET and HEAD requests by default
|
|
207
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
208
|
+
return await next()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Generate cache key
|
|
212
|
+
const cacheKey = RouteCacheKeyGenerator.generateKey(req, config)
|
|
213
|
+
|
|
214
|
+
// Try to get from cache
|
|
215
|
+
const cached = await routeCacheManager.get(cacheKey)
|
|
216
|
+
if (cached) {
|
|
217
|
+
// Return cached response
|
|
218
|
+
const headers = new Headers(cached.headers)
|
|
219
|
+
|
|
220
|
+
// Add cache headers
|
|
221
|
+
headers.set('X-Cache', 'HIT')
|
|
222
|
+
headers.set('X-Cache-Key', cacheKey)
|
|
223
|
+
|
|
224
|
+
if (cached.etag) {
|
|
225
|
+
headers.set('ETag', cached.etag)
|
|
226
|
+
|
|
227
|
+
// Check if client has matching ETag
|
|
228
|
+
const clientETag = req.headers.get('If-None-Match')
|
|
229
|
+
if (clientETag === cached.etag) {
|
|
230
|
+
return new Response(null, { status: 304, headers })
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return new Response(cached.body as any, {
|
|
235
|
+
status: cached.status,
|
|
236
|
+
headers,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Execute route handler
|
|
241
|
+
const response = await next()
|
|
242
|
+
|
|
243
|
+
// Only cache successful responses
|
|
244
|
+
if (response.status >= 200 && response.status < 300) {
|
|
245
|
+
try {
|
|
246
|
+
// Clone response to cache it
|
|
247
|
+
const responseClone = response.clone()
|
|
248
|
+
const body = await responseClone.arrayBuffer()
|
|
249
|
+
|
|
250
|
+
// Generate ETag
|
|
251
|
+
const etag = await generateETag(body)
|
|
252
|
+
|
|
253
|
+
// Prepare headers for caching
|
|
254
|
+
const headers: Record<string, string> = {}
|
|
255
|
+
responseClone.headers.forEach((value, key) => {
|
|
256
|
+
headers[key] = value
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// Create cached response
|
|
260
|
+
const cachedResponse: CachedResponse = {
|
|
261
|
+
response: responseClone as Response,
|
|
262
|
+
headers,
|
|
263
|
+
status: response.status,
|
|
264
|
+
body: new Uint8Array(body),
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
ttl: config.ttl,
|
|
267
|
+
tags: config.tags || [],
|
|
268
|
+
etag,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Store in cache
|
|
272
|
+
await routeCacheManager.set(cacheKey, cachedResponse)
|
|
273
|
+
|
|
274
|
+
// Add cache headers to original response
|
|
275
|
+
const originalHeaders = new Headers(response.headers)
|
|
276
|
+
originalHeaders.set('X-Cache', 'MISS')
|
|
277
|
+
originalHeaders.set('X-Cache-Key', cacheKey)
|
|
278
|
+
originalHeaders.set('ETag', etag)
|
|
279
|
+
|
|
280
|
+
return new Response(response.body, {
|
|
281
|
+
status: response.status,
|
|
282
|
+
headers: originalHeaders,
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
console.error('Error caching response:', error)
|
|
287
|
+
// Return original response if caching fails
|
|
288
|
+
return response
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return response
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate ETag from response body
|
|
298
|
+
*/
|
|
299
|
+
async function generateETag(body: ArrayBuffer): Promise<string> {
|
|
300
|
+
const hash = await crypto.subtle.digest('SHA-256', body)
|
|
301
|
+
const hashArray = Array.from(new Uint8Array(hash))
|
|
302
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
303
|
+
return `"${hashHex.substring(0, 16)}"` // Use first 16 chars for ETag
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Factory functions for common cache configurations
|
|
308
|
+
*/
|
|
309
|
+
export const RouteCacheFactory = {
|
|
310
|
+
/**
|
|
311
|
+
* Short-term cache (5 minutes)
|
|
312
|
+
*/
|
|
313
|
+
short: (tags: string[] = []): RouteCacheConfig => ({
|
|
314
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
315
|
+
maxSize: 500,
|
|
316
|
+
tags,
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Medium-term cache (1 hour)
|
|
321
|
+
*/
|
|
322
|
+
medium: (tags: string[] = []): RouteCacheConfig => ({
|
|
323
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
324
|
+
maxSize: 1000,
|
|
325
|
+
tags,
|
|
326
|
+
}),
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Long-term cache (24 hours)
|
|
330
|
+
*/
|
|
331
|
+
long: (tags: string[] = []): RouteCacheConfig => ({
|
|
332
|
+
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
|
333
|
+
maxSize: 2000,
|
|
334
|
+
tags,
|
|
335
|
+
}),
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* API response cache with common settings
|
|
339
|
+
*/
|
|
340
|
+
api: (tags: string[] = []): RouteCacheConfig => ({
|
|
341
|
+
ttl: 15 * 60 * 1000, // 15 minutes
|
|
342
|
+
maxSize: 1000,
|
|
343
|
+
tags,
|
|
344
|
+
varyBy: ['Authorization', 'Accept', 'Accept-Language'],
|
|
345
|
+
excludeQuery: ['_t', 'timestamp', 'cache_bust'],
|
|
346
|
+
}),
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Static content cache
|
|
350
|
+
*/
|
|
351
|
+
static: (tags: string[] = []): RouteCacheConfig => ({
|
|
352
|
+
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
353
|
+
maxSize: 5000,
|
|
354
|
+
tags,
|
|
355
|
+
varyBy: ['Accept-Encoding'],
|
|
356
|
+
}),
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* User-specific cache
|
|
360
|
+
*/
|
|
361
|
+
user: (tags: string[] = []): RouteCacheConfig => ({
|
|
362
|
+
ttl: 30 * 60 * 1000, // 30 minutes
|
|
363
|
+
maxSize: 2000,
|
|
364
|
+
tags,
|
|
365
|
+
varyBy: ['Authorization'],
|
|
366
|
+
condition: req => !!req.headers.get('Authorization'),
|
|
367
|
+
}),
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Cache invalidation utilities
|
|
372
|
+
*/
|
|
373
|
+
export const CacheInvalidation = {
|
|
374
|
+
/**
|
|
375
|
+
* Invalidate by tags
|
|
376
|
+
*/
|
|
377
|
+
byTags: (tags: string[]): number => {
|
|
378
|
+
return routeCacheManager.invalidateByTags(tags)
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Invalidate all user-related cache
|
|
383
|
+
*/
|
|
384
|
+
forUser: (userId: string): number => {
|
|
385
|
+
return routeCacheManager.invalidateByTags([`user:${userId}`])
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Invalidate model-related cache
|
|
390
|
+
*/
|
|
391
|
+
forModel: (modelName: string, id?: string): number => {
|
|
392
|
+
const tags = id ? [`${modelName}:${id}`, modelName] : [modelName]
|
|
393
|
+
return routeCacheManager.invalidateByTags(tags)
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Clear all cache
|
|
398
|
+
*/
|
|
399
|
+
all: (): void => {
|
|
400
|
+
routeCacheManager.clear()
|
|
401
|
+
},
|
|
402
|
+
}
|