@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,743 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, Middleware, NextFunction } from '../types'
|
|
2
|
-
import crypto from 'node:crypto'
|
|
3
|
-
import { mkdir } from 'node:fs/promises'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
export interface CacheEntry {
|
|
7
|
-
key: string
|
|
8
|
-
response: {
|
|
9
|
-
status: number
|
|
10
|
-
statusText: string
|
|
11
|
-
headers: Record<string, string>
|
|
12
|
-
body: number[] // Store as number array for JSON serialization
|
|
13
|
-
contentType: string
|
|
14
|
-
}
|
|
15
|
-
createdAt: number
|
|
16
|
-
expiresAt: number
|
|
17
|
-
etag?: string
|
|
18
|
-
lastModified?: string
|
|
19
|
-
size: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ResponseCacheOptions {
|
|
23
|
-
enabled?: boolean
|
|
24
|
-
storage?: {
|
|
25
|
-
type: 'memory' | 'file' | 'hybrid'
|
|
26
|
-
directory?: string
|
|
27
|
-
maxSize?: number
|
|
28
|
-
maxEntries?: number
|
|
29
|
-
}
|
|
30
|
-
ttl?: {
|
|
31
|
-
default: number
|
|
32
|
-
routes?: Record<string, number>
|
|
33
|
-
methods?: Record<string, number>
|
|
34
|
-
}
|
|
35
|
-
keyGenerator?: (req: EnhancedRequest) => string
|
|
36
|
-
shouldCache?: (req: EnhancedRequest, res: Response) => boolean
|
|
37
|
-
varyHeaders?: string[]
|
|
38
|
-
compression?: {
|
|
39
|
-
enabled: boolean
|
|
40
|
-
threshold: number
|
|
41
|
-
algorithms: ('gzip' | 'deflate' | 'br')[]
|
|
42
|
-
}
|
|
43
|
-
invalidation?: {
|
|
44
|
-
patterns?: string[]
|
|
45
|
-
methods?: string[]
|
|
46
|
-
headers?: string[]
|
|
47
|
-
}
|
|
48
|
-
etag?: {
|
|
49
|
-
enabled: boolean
|
|
50
|
-
weak: boolean
|
|
51
|
-
}
|
|
52
|
-
staleWhileRevalidate?: {
|
|
53
|
-
enabled: boolean
|
|
54
|
-
maxAge: number
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface MemoryCacheEntry extends CacheEntry {
|
|
59
|
-
accessCount: number
|
|
60
|
-
lastAccessed: number
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class ResponseCache implements Middleware {
|
|
64
|
-
private options: Required<ResponseCacheOptions>
|
|
65
|
-
private memoryCache = new Map<string, MemoryCacheEntry>()
|
|
66
|
-
private cacheStats = {
|
|
67
|
-
hits: 0,
|
|
68
|
-
misses: 0,
|
|
69
|
-
sets: 0,
|
|
70
|
-
deletes: 0,
|
|
71
|
-
size: 0,
|
|
72
|
-
memoryUsage: 0,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private cleanupInterval?: ReturnType<typeof setInterval>
|
|
76
|
-
|
|
77
|
-
constructor(options: ResponseCacheOptions = {}) {
|
|
78
|
-
this.options = {
|
|
79
|
-
enabled: options.enabled ?? true,
|
|
80
|
-
storage: {
|
|
81
|
-
type: options.storage?.type ?? 'hybrid',
|
|
82
|
-
directory: options.storage?.directory ?? '.cache/responses',
|
|
83
|
-
maxSize: options.storage?.maxSize ?? 100 * 1024 * 1024, // 100MB
|
|
84
|
-
maxEntries: options.storage?.maxEntries ?? 10000,
|
|
85
|
-
...options.storage,
|
|
86
|
-
},
|
|
87
|
-
ttl: {
|
|
88
|
-
default: options.ttl?.default ?? 300000, // 5 minutes
|
|
89
|
-
routes: options.ttl?.routes ?? {},
|
|
90
|
-
methods: options.ttl?.methods ?? {
|
|
91
|
-
GET: 300000,
|
|
92
|
-
HEAD: 300000,
|
|
93
|
-
OPTIONS: 60000,
|
|
94
|
-
},
|
|
95
|
-
...options.ttl,
|
|
96
|
-
},
|
|
97
|
-
keyGenerator: options.keyGenerator ?? this.defaultKeyGenerator.bind(this),
|
|
98
|
-
shouldCache: options.shouldCache ?? this.defaultShouldCache.bind(this),
|
|
99
|
-
varyHeaders: options.varyHeaders ?? ['Accept', 'Accept-Encoding', 'Authorization'],
|
|
100
|
-
compression: {
|
|
101
|
-
enabled: options.compression?.enabled ?? true,
|
|
102
|
-
threshold: options.compression?.threshold ?? 1024,
|
|
103
|
-
algorithms: options.compression?.algorithms ?? ['gzip', 'deflate'],
|
|
104
|
-
...options.compression,
|
|
105
|
-
},
|
|
106
|
-
invalidation: {
|
|
107
|
-
patterns: options.invalidation?.patterns ?? [],
|
|
108
|
-
methods: options.invalidation?.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE'],
|
|
109
|
-
headers: options.invalidation?.headers ?? ['Cache-Control'],
|
|
110
|
-
...options.invalidation,
|
|
111
|
-
},
|
|
112
|
-
etag: {
|
|
113
|
-
enabled: options.etag?.enabled ?? true,
|
|
114
|
-
weak: options.etag?.weak ?? true,
|
|
115
|
-
...options.etag,
|
|
116
|
-
},
|
|
117
|
-
staleWhileRevalidate: {
|
|
118
|
-
enabled: options.staleWhileRevalidate?.enabled ?? false,
|
|
119
|
-
maxAge: options.staleWhileRevalidate?.maxAge ?? 60000,
|
|
120
|
-
...options.staleWhileRevalidate,
|
|
121
|
-
},
|
|
122
|
-
} as Required<ResponseCacheOptions>
|
|
123
|
-
|
|
124
|
-
this.initializeStorage()
|
|
125
|
-
this.startCleanupInterval()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private async initializeStorage(): Promise<void> {
|
|
129
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
130
|
-
try {
|
|
131
|
-
if (this.options.storage?.directory) {
|
|
132
|
-
await mkdir(this.options.storage.directory, { recursive: true })
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
console.warn(`[ResponseCache] Failed to create cache directory: ${error}`)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private startCleanupInterval(): void {
|
|
142
|
-
this.cleanupInterval = setInterval(() => {
|
|
143
|
-
void this.cleanup()
|
|
144
|
-
}, 60000) // Cleanup every minute
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
public async cleanup(): Promise<void> {
|
|
148
|
-
const now = Date.now()
|
|
149
|
-
|
|
150
|
-
// Clean memory cache - check expiration properly
|
|
151
|
-
const keysToDelete: string[] = []
|
|
152
|
-
for (const [key, entry] of this.memoryCache.entries()) {
|
|
153
|
-
if (entry.expiresAt <= now) {
|
|
154
|
-
keysToDelete.push(key)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Delete expired keys
|
|
159
|
-
for (const key of keysToDelete) {
|
|
160
|
-
this.memoryCache.delete(key)
|
|
161
|
-
this.cacheStats.deletes++
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Clean file cache if using file storage
|
|
165
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
166
|
-
await this.cleanupFileCache()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Update cache stats after cleanup
|
|
170
|
-
this.updateCacheStats()
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private async cleanupFileCache(): Promise<void> {
|
|
174
|
-
if (!this.options.storage.directory)
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const { readdir } = await import('node:fs/promises')
|
|
179
|
-
const files = await readdir(this.options.storage.directory)
|
|
180
|
-
|
|
181
|
-
for (const file of files) {
|
|
182
|
-
if (file.endsWith('.cache')) {
|
|
183
|
-
const filePath = join(this.options.storage.directory, file)
|
|
184
|
-
const bunFile = Bun.file(filePath)
|
|
185
|
-
|
|
186
|
-
if (await bunFile.exists()) {
|
|
187
|
-
try {
|
|
188
|
-
const content = await bunFile.text()
|
|
189
|
-
const entry: CacheEntry = JSON.parse(content)
|
|
190
|
-
|
|
191
|
-
if (entry.expiresAt < Date.now()) {
|
|
192
|
-
await bunFile.delete()
|
|
193
|
-
this.cacheStats.deletes++
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
// Invalid cache file, delete it
|
|
198
|
-
await bunFile.delete()
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
console.warn(`[ResponseCache] File cache cleanup failed: ${error}`)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private defaultKeyGenerator(req: EnhancedRequest): string {
|
|
210
|
-
const url = new URL(req.url)
|
|
211
|
-
const varyParts: string[] = []
|
|
212
|
-
|
|
213
|
-
// Include vary headers in key
|
|
214
|
-
for (const header of this.options.varyHeaders) {
|
|
215
|
-
const value = req.headers.get(header)
|
|
216
|
-
if (value) {
|
|
217
|
-
varyParts.push(`${header}:${value}`)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const keyData = [
|
|
222
|
-
req.method,
|
|
223
|
-
url.pathname,
|
|
224
|
-
url.search,
|
|
225
|
-
...varyParts,
|
|
226
|
-
].join('|')
|
|
227
|
-
|
|
228
|
-
return crypto.createHash('sha256').update(keyData).digest('hex')
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private defaultShouldCache(req: EnhancedRequest, res: Response): boolean {
|
|
232
|
-
// Don't cache if disabled
|
|
233
|
-
if (!this.options.enabled)
|
|
234
|
-
return false
|
|
235
|
-
|
|
236
|
-
// Don't cache non-GET/HEAD requests by default
|
|
237
|
-
if (!['GET', 'HEAD'].includes(req.method))
|
|
238
|
-
return false
|
|
239
|
-
|
|
240
|
-
// Don't cache responses with Cache-Control: no-cache or no-store
|
|
241
|
-
const cacheControl = res.headers.get('Cache-Control')
|
|
242
|
-
if (cacheControl?.includes('no-cache') || cacheControl?.includes('no-store')) {
|
|
243
|
-
return false
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Don't cache error responses (4xx, 5xx)
|
|
247
|
-
if (res.status >= 400)
|
|
248
|
-
return false
|
|
249
|
-
|
|
250
|
-
// Don't cache responses with Set-Cookie header
|
|
251
|
-
if (res.headers.get('Set-Cookie'))
|
|
252
|
-
return false
|
|
253
|
-
|
|
254
|
-
return true
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private getTTL(req: EnhancedRequest): number {
|
|
258
|
-
const method = req.method.toLowerCase()
|
|
259
|
-
const url = new URL(req.url)
|
|
260
|
-
const pathname = url.pathname
|
|
261
|
-
|
|
262
|
-
// Check for route-specific TTL
|
|
263
|
-
if (this.options.ttl?.routes) {
|
|
264
|
-
for (const [pattern, ttl] of Object.entries(this.options.ttl.routes)) {
|
|
265
|
-
if (pathname.includes(pattern)) {
|
|
266
|
-
return ttl
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Check for method-specific TTL
|
|
272
|
-
if (this.options.ttl?.methods?.[method]) {
|
|
273
|
-
return this.options.ttl.methods[method]
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Return default TTL
|
|
277
|
-
return this.options.ttl?.default || 300000 // 5 minutes default
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private generateETag(content: string | Uint8Array): string {
|
|
281
|
-
const hash = crypto.createHash('sha1')
|
|
282
|
-
hash.update(content)
|
|
283
|
-
const etag = hash.digest('hex').substring(0, 16)
|
|
284
|
-
return this.options.etag.weak ? `W/"${etag}"` : `"${etag}"`
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private async getFromMemoryCache(key: string): Promise<CacheEntry | null> {
|
|
288
|
-
const entry = this.memoryCache.get(key)
|
|
289
|
-
if (!entry)
|
|
290
|
-
return null
|
|
291
|
-
|
|
292
|
-
// Check if entry is expired
|
|
293
|
-
const now = Date.now()
|
|
294
|
-
if (entry.expiresAt <= now) {
|
|
295
|
-
this.memoryCache.delete(key)
|
|
296
|
-
this.cacheStats.deletes++
|
|
297
|
-
return null
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Update access time for LRU
|
|
301
|
-
entry.lastAccessed = Date.now()
|
|
302
|
-
entry.accessCount++
|
|
303
|
-
|
|
304
|
-
// Re-insert to update the entry in the map
|
|
305
|
-
this.memoryCache.set(key, entry)
|
|
306
|
-
return entry
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private async getFromFileCache(key: string): Promise<CacheEntry | null> {
|
|
310
|
-
if (!this.options.storage.directory)
|
|
311
|
-
return null
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
const filePath = join(this.options.storage.directory, `${key}.cache`)
|
|
315
|
-
const file = Bun.file(filePath)
|
|
316
|
-
|
|
317
|
-
if (!(await file.exists())) {
|
|
318
|
-
return null
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const data = await file.json() as CacheEntry
|
|
322
|
-
|
|
323
|
-
// Check if entry is expired
|
|
324
|
-
const now = Date.now()
|
|
325
|
-
if (data.expiresAt <= now) {
|
|
326
|
-
// Delete expired file
|
|
327
|
-
try {
|
|
328
|
-
const { unlink } = await import('node:fs/promises')
|
|
329
|
-
await unlink(filePath)
|
|
330
|
-
}
|
|
331
|
-
catch (deleteError) {
|
|
332
|
-
console.warn(`[ResponseCache] Error deleting expired file cache:`, deleteError)
|
|
333
|
-
}
|
|
334
|
-
return null
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return data
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
console.warn(`[ResponseCache] Error reading file cache for key ${key}:`, error)
|
|
341
|
-
return null
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private async setMemoryCache(key: string, entry: CacheEntry): Promise<void> {
|
|
346
|
-
// If key already exists, don't add as new entry
|
|
347
|
-
if (this.memoryCache.has(key)) {
|
|
348
|
-
return
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Check memory limits and evict if necessary before adding new entry
|
|
352
|
-
while (this.memoryCache.size >= (this.options.storage.maxEntries || 100)) {
|
|
353
|
-
this.evictLRU()
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const now = Date.now()
|
|
357
|
-
const memoryEntry: MemoryCacheEntry = {
|
|
358
|
-
...entry,
|
|
359
|
-
accessCount: 1,
|
|
360
|
-
lastAccessed: now,
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
this.memoryCache.set(key, memoryEntry)
|
|
364
|
-
this.updateCacheStats()
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private async setFileCache(key: string, entry: CacheEntry): Promise<void> {
|
|
368
|
-
if (!this.options.storage.directory)
|
|
369
|
-
return
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
const filePath = join(this.options.storage.directory, `${key}.cache`)
|
|
373
|
-
const content = JSON.stringify(entry)
|
|
374
|
-
await Bun.write(filePath, content)
|
|
375
|
-
}
|
|
376
|
-
catch (error) {
|
|
377
|
-
console.warn(`[ResponseCache] Failed to write file cache: ${error}`)
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
private evictLRU(): void {
|
|
382
|
-
if (this.memoryCache.size === 0)
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
// Find the entry with the oldest lastAccessed time
|
|
386
|
-
let oldestKey: string | null = null
|
|
387
|
-
let oldestTime = Number.MAX_SAFE_INTEGER
|
|
388
|
-
|
|
389
|
-
for (const [key, entry] of this.memoryCache.entries()) {
|
|
390
|
-
if (entry.lastAccessed < oldestTime) {
|
|
391
|
-
oldestTime = entry.lastAccessed
|
|
392
|
-
oldestKey = key
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (oldestKey) {
|
|
397
|
-
this.memoryCache.delete(oldestKey)
|
|
398
|
-
this.cacheStats.deletes++
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private updateCacheStats(): void {
|
|
403
|
-
this.cacheStats.size = this.memoryCache.size
|
|
404
|
-
this.cacheStats.memoryUsage = Array.from(this.memoryCache.values())
|
|
405
|
-
.reduce((total, entry) => total + entry.size, 0)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
private generateCacheKey(req: EnhancedRequest): string {
|
|
409
|
-
return this.options.keyGenerator(req)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
private async getCachedResponse(key: string): Promise<CacheEntry | null> {
|
|
413
|
-
// Try memory cache first
|
|
414
|
-
if (this.options.storage.type === 'memory' || this.options.storage.type === 'hybrid') {
|
|
415
|
-
const memoryEntry = await this.getFromMemoryCache(key)
|
|
416
|
-
if (memoryEntry) {
|
|
417
|
-
return memoryEntry
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Try file cache
|
|
422
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
423
|
-
const fileEntry = await this.getFromFileCache(key)
|
|
424
|
-
if (fileEntry) {
|
|
425
|
-
// Store in memory cache for faster access
|
|
426
|
-
if (this.options.storage.type === 'hybrid') {
|
|
427
|
-
await this.setMemoryCache(key, fileEntry)
|
|
428
|
-
}
|
|
429
|
-
return fileEntry
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return null
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
private async setCachedResponse(key: string, entry: CacheEntry): Promise<void> {
|
|
437
|
-
// Only increment sets if this is a new entry
|
|
438
|
-
const isNewEntry = !this.memoryCache.has(key)
|
|
439
|
-
|
|
440
|
-
// Store in memory cache
|
|
441
|
-
if (this.options.storage.type === 'memory' || this.options.storage.type === 'hybrid') {
|
|
442
|
-
await this.setMemoryCache(key, entry)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Store in file cache
|
|
446
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
447
|
-
await this.setFileCache(key, entry)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (isNewEntry) {
|
|
451
|
-
this.cacheStats.sets++
|
|
452
|
-
}
|
|
453
|
-
this.updateCacheStats()
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
private handleConditionalRequest(req: EnhancedRequest, entry: CacheEntry): Response | null {
|
|
457
|
-
const ifNoneMatch = req.headers.get('If-None-Match')
|
|
458
|
-
const ifModifiedSince = req.headers.get('If-Modified-Since')
|
|
459
|
-
|
|
460
|
-
// Check ETag
|
|
461
|
-
if (ifNoneMatch && entry.etag) {
|
|
462
|
-
if (ifNoneMatch === entry.etag || ifNoneMatch === '*') {
|
|
463
|
-
return new Response(null, { status: 304 })
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Check Last-Modified
|
|
468
|
-
if (ifModifiedSince && entry.lastModified) {
|
|
469
|
-
const modifiedSince = new Date(ifModifiedSince)
|
|
470
|
-
const lastModified = new Date(entry.lastModified)
|
|
471
|
-
|
|
472
|
-
if (lastModified <= modifiedSince) {
|
|
473
|
-
return new Response(null, { status: 304 })
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return null
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
481
|
-
const cacheKey = this.generateCacheKey(req)
|
|
482
|
-
|
|
483
|
-
// Check if this is an invalidating request
|
|
484
|
-
if (this.options.invalidation.methods?.includes(req.method)) {
|
|
485
|
-
const response = await next()
|
|
486
|
-
if (response) {
|
|
487
|
-
await this.invalidateCache(req)
|
|
488
|
-
response.headers.set('X-Cache', 'MISS')
|
|
489
|
-
return response
|
|
490
|
-
}
|
|
491
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Try to get cached response
|
|
495
|
-
let cachedEntry = await this.getCachedResponse(cacheKey)
|
|
496
|
-
|
|
497
|
-
// Check if cached entry is expired
|
|
498
|
-
if (cachedEntry && cachedEntry.expiresAt <= Date.now()) {
|
|
499
|
-
// Remove expired entry
|
|
500
|
-
await this.deleteCachedResponse(cacheKey)
|
|
501
|
-
cachedEntry = null
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (cachedEntry) {
|
|
505
|
-
// Track cache hit
|
|
506
|
-
this.cacheStats.hits++
|
|
507
|
-
|
|
508
|
-
// Handle conditional requests
|
|
509
|
-
const conditionalResponse = this.handleConditionalRequest(req, cachedEntry)
|
|
510
|
-
if (conditionalResponse) {
|
|
511
|
-
conditionalResponse.headers.set('X-Cache', 'HIT')
|
|
512
|
-
return conditionalResponse
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Return cached response
|
|
516
|
-
const response = this.createResponseFromEntry(cachedEntry)
|
|
517
|
-
if (response) {
|
|
518
|
-
response.headers.set('X-Cache', 'HIT')
|
|
519
|
-
return response
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Track cache miss
|
|
524
|
-
this.cacheStats.misses++
|
|
525
|
-
|
|
526
|
-
// Cache miss - get response from next middleware
|
|
527
|
-
const response = await next()
|
|
528
|
-
|
|
529
|
-
if (!response) {
|
|
530
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Set X-Cache header for miss
|
|
534
|
-
response.headers.set('X-Cache', 'MISS')
|
|
535
|
-
|
|
536
|
-
// Check if response should be cached
|
|
537
|
-
if (this.options.shouldCache(req, response)) {
|
|
538
|
-
try {
|
|
539
|
-
const { entry, response: updatedResponse } = await this.createCacheEntry(req, response)
|
|
540
|
-
await this.setCachedResponse(cacheKey, entry)
|
|
541
|
-
return updatedResponse
|
|
542
|
-
}
|
|
543
|
-
catch (error) {
|
|
544
|
-
console.warn(`[ResponseCache] Failed to cache response: ${error}`)
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return response
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
private async deleteCachedResponse(key: string): Promise<void> {
|
|
552
|
-
// Remove from memory cache
|
|
553
|
-
if (this.memoryCache.has(key)) {
|
|
554
|
-
this.memoryCache.delete(key)
|
|
555
|
-
this.cacheStats.deletes++
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Remove from file cache
|
|
559
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
560
|
-
try {
|
|
561
|
-
const filePath = join(this.options.storage.directory || '.cache/responses', `${key}.cache`)
|
|
562
|
-
const bunFile = Bun.file(filePath)
|
|
563
|
-
if (await bunFile.exists()) {
|
|
564
|
-
await bunFile.delete()
|
|
565
|
-
this.cacheStats.deletes++
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
catch (error) {
|
|
569
|
-
console.warn(`[ResponseCache] Failed to delete file cache: ${error}`)
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
this.updateCacheStats()
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
private async createCacheEntry(req: EnhancedRequest, res: Response): Promise<{ entry: CacheEntry, response: Response }> {
|
|
577
|
-
// Clone the response to avoid consuming the original stream
|
|
578
|
-
const clonedRes = res.clone()
|
|
579
|
-
const body = await clonedRes.arrayBuffer()
|
|
580
|
-
const bodyBytes = new Uint8Array(body)
|
|
581
|
-
const ttl = this.getTTL(req)
|
|
582
|
-
const now = Date.now()
|
|
583
|
-
|
|
584
|
-
const entry: CacheEntry = {
|
|
585
|
-
key: this.generateCacheKey(req),
|
|
586
|
-
response: {
|
|
587
|
-
status: res.status,
|
|
588
|
-
statusText: res.statusText,
|
|
589
|
-
headers: Object.fromEntries(res.headers.entries()),
|
|
590
|
-
body: Array.from(bodyBytes), // Convert to regular array for JSON serialization
|
|
591
|
-
contentType: res.headers.get('Content-Type') || 'text/plain',
|
|
592
|
-
},
|
|
593
|
-
createdAt: now,
|
|
594
|
-
expiresAt: now + ttl,
|
|
595
|
-
size: bodyBytes.length,
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Generate ETag if enabled
|
|
599
|
-
if (this.options.etag && this.options.etag.enabled) {
|
|
600
|
-
entry.etag = this.generateETag(bodyBytes)
|
|
601
|
-
if (entry.etag) {
|
|
602
|
-
entry.response.headers.ETag = entry.etag
|
|
603
|
-
res.headers.set('ETag', entry.etag)
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Set Last-Modified
|
|
608
|
-
entry.lastModified = new Date(now).toUTCString()
|
|
609
|
-
entry.response.headers['Last-Modified'] = entry.lastModified
|
|
610
|
-
res.headers.set('Last-Modified', entry.lastModified)
|
|
611
|
-
|
|
612
|
-
return { entry, response: res }
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
private createResponseFromEntry(entry: CacheEntry): Response {
|
|
616
|
-
const headers = new Headers(entry.response.headers)
|
|
617
|
-
|
|
618
|
-
// Add cache headers
|
|
619
|
-
headers.set('Age', Math.floor((Date.now() - entry.createdAt) / 1000).toString())
|
|
620
|
-
|
|
621
|
-
// Convert body back to Uint8Array if it's an array
|
|
622
|
-
const body = Array.isArray(entry.response.body)
|
|
623
|
-
? new Uint8Array(entry.response.body)
|
|
624
|
-
: entry.response.body
|
|
625
|
-
|
|
626
|
-
return new Response(body, {
|
|
627
|
-
status: entry.response.status,
|
|
628
|
-
statusText: entry.response.statusText,
|
|
629
|
-
headers,
|
|
630
|
-
})
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
private async backgroundRevalidate(cacheKey: string, req: EnhancedRequest, next: NextFunction): Promise<void> {
|
|
634
|
-
try {
|
|
635
|
-
const response = await next()
|
|
636
|
-
if (response && this.options.shouldCache(req, response)) {
|
|
637
|
-
const { entry } = await this.createCacheEntry(req, response)
|
|
638
|
-
await this.setCachedResponse(cacheKey, entry)
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
catch (error) {
|
|
642
|
-
console.warn(`[ResponseCache] Background revalidation failed: ${error}`)
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async invalidateCache(req?: EnhancedRequest): Promise<void> {
|
|
647
|
-
if (req) {
|
|
648
|
-
const patterns = this.options.invalidation.patterns
|
|
649
|
-
if (patterns && patterns.length > 0) {
|
|
650
|
-
// Invalidate specific patterns
|
|
651
|
-
const url = new URL(req.url)
|
|
652
|
-
|
|
653
|
-
for (const pattern of patterns) {
|
|
654
|
-
if (url.pathname.match(new RegExp(pattern))) {
|
|
655
|
-
await this.clearCache()
|
|
656
|
-
return
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
// No specific patterns, clear all cache for invalidating methods
|
|
662
|
-
await this.clearCache()
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
// Clear all cache
|
|
667
|
-
await this.clearCache()
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
async clearCache(): Promise<void> {
|
|
672
|
-
// Get current cache size before clearing
|
|
673
|
-
const currentSize = this.memoryCache.size
|
|
674
|
-
|
|
675
|
-
// Clear memory cache
|
|
676
|
-
this.memoryCache.clear()
|
|
677
|
-
this.cacheStats.deletes += currentSize
|
|
678
|
-
|
|
679
|
-
// Clear file cache if using file storage
|
|
680
|
-
if (this.options.storage.type === 'file' || this.options.storage.type === 'hybrid') {
|
|
681
|
-
try {
|
|
682
|
-
const { readdir, unlink } = await import('node:fs/promises')
|
|
683
|
-
const files = await readdir(this.options.storage.directory || '.cache/responses')
|
|
684
|
-
|
|
685
|
-
for (const file of files) {
|
|
686
|
-
if (file.endsWith('.cache')) {
|
|
687
|
-
const filePath = `${this.options.storage.directory || '.cache/responses'}/${file}`
|
|
688
|
-
await unlink(filePath)
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
console.warn(`[ResponseCache] Failed to clear file cache: ${error}`)
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
this.updateCacheStats()
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
getStats(): { hits: number, misses: number, sets: number, deletes: number, size: number, memoryUsage: number, hitRate: number } {
|
|
700
|
-
return {
|
|
701
|
-
hits: this.cacheStats.hits,
|
|
702
|
-
misses: this.cacheStats.misses,
|
|
703
|
-
sets: this.cacheStats.sets,
|
|
704
|
-
deletes: this.cacheStats.deletes,
|
|
705
|
-
size: this.cacheStats.size,
|
|
706
|
-
memoryUsage: this.cacheStats.memoryUsage,
|
|
707
|
-
hitRate: this.cacheStats.hits / (this.cacheStats.hits + this.cacheStats.misses) || 0,
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
destroy(): void {
|
|
712
|
-
if (this.cleanupInterval) {
|
|
713
|
-
clearInterval(this.cleanupInterval)
|
|
714
|
-
}
|
|
715
|
-
this.memoryCache.clear()
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Factory functions
|
|
720
|
-
export function responseCache(options?: ResponseCacheOptions): ResponseCache {
|
|
721
|
-
return new ResponseCache(options)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
export function memoryCache(options?: Omit<ResponseCacheOptions, 'storage'>): ResponseCache {
|
|
725
|
-
return new ResponseCache({
|
|
726
|
-
...options,
|
|
727
|
-
storage: { type: 'memory' },
|
|
728
|
-
})
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
export function fileCache(directory?: string, options?: Omit<ResponseCacheOptions, 'storage'>): ResponseCache {
|
|
732
|
-
return new ResponseCache({
|
|
733
|
-
...options,
|
|
734
|
-
storage: { type: 'file', directory },
|
|
735
|
-
})
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
export function hybridCache(options?: ResponseCacheOptions): ResponseCache {
|
|
739
|
-
return new ResponseCache({
|
|
740
|
-
...options,
|
|
741
|
-
storage: { type: 'hybrid' },
|
|
742
|
-
})
|
|
743
|
-
}
|