@stacksjs/bun-router 0.0.12 → 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 -48
- 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,702 +0,0 @@
|
|
|
1
|
-
import type { HTTPMethod, MatchResult, PatternMatchResult, Route } from '../types'
|
|
2
|
-
import type { CompiledRoute } from './route-trie'
|
|
3
|
-
import { RouteTrie } from './route-trie'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Route compilation options
|
|
7
|
-
*/
|
|
8
|
-
export interface RouteCompilerOptions {
|
|
9
|
-
enableCaching: boolean
|
|
10
|
-
enableMethodGrouping: boolean
|
|
11
|
-
enablePriorityOptimization: boolean
|
|
12
|
-
cacheSize: number
|
|
13
|
-
precompilePatterns: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Route matching statistics for performance monitoring
|
|
18
|
-
*/
|
|
19
|
-
export interface RouteMatchStats {
|
|
20
|
-
totalMatches: number
|
|
21
|
-
cacheHits: number
|
|
22
|
-
cacheMisses: number
|
|
23
|
-
averageMatchTime: number
|
|
24
|
-
trieDepth: number
|
|
25
|
-
methodDistribution: Record<string, number>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* High-performance route compiler with pre-compilation and optimization
|
|
30
|
-
*/
|
|
31
|
-
export class RouteCompiler {
|
|
32
|
-
private trie: RouteTrie
|
|
33
|
-
private matchCache: Map<string, MatchResult | null> = new Map()
|
|
34
|
-
private cacheKeys: string[] = [] // For LRU tracking
|
|
35
|
-
private stats: RouteMatchStats = {
|
|
36
|
-
totalMatches: 0,
|
|
37
|
-
cacheHits: 0,
|
|
38
|
-
cacheMisses: 0,
|
|
39
|
-
averageMatchTime: 0,
|
|
40
|
-
trieDepth: 0,
|
|
41
|
-
methodDistribution: {},
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private options: RouteCompilerOptions
|
|
45
|
-
|
|
46
|
-
constructor(options: Partial<RouteCompilerOptions> = {}) {
|
|
47
|
-
this.options = {
|
|
48
|
-
enableCaching: true,
|
|
49
|
-
enableMethodGrouping: true,
|
|
50
|
-
enablePriorityOptimization: true,
|
|
51
|
-
cacheSize: 1000,
|
|
52
|
-
precompilePatterns: true,
|
|
53
|
-
...options,
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.trie = new RouteTrie()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Add a route to the compiler
|
|
61
|
-
* @returns true if route was added, false if it's a duplicate
|
|
62
|
-
*/
|
|
63
|
-
addRoute(route: Route): boolean {
|
|
64
|
-
// Pre-compile the route pattern if enabled
|
|
65
|
-
if (this.options.precompilePatterns) {
|
|
66
|
-
this.precompileRoutePattern(route)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Check for exact duplicates before adding
|
|
70
|
-
const isDuplicate = this.checkForDuplicateRoute(route)
|
|
71
|
-
if (isDuplicate) {
|
|
72
|
-
return false
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Add to trie for fast matching
|
|
76
|
-
this.trie.addRoute(route)
|
|
77
|
-
|
|
78
|
-
// Clear cache when routes change
|
|
79
|
-
if (this.options.enableCaching) {
|
|
80
|
-
this.matchCache.clear()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Update method distribution stats
|
|
84
|
-
const method = route.method
|
|
85
|
-
this.stats.methodDistribution[method] = (this.stats.methodDistribution[method] || 0) + 1
|
|
86
|
-
|
|
87
|
-
return true
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Check if a route is an exact duplicate of an existing route
|
|
92
|
-
*/
|
|
93
|
-
private checkForDuplicateRoute(route: Route): boolean {
|
|
94
|
-
const routes = this.trie.getAllRoutes()
|
|
95
|
-
|
|
96
|
-
for (const existingRoute of routes) {
|
|
97
|
-
if (
|
|
98
|
-
existingRoute.route.path === route.path
|
|
99
|
-
&& existingRoute.route.method === route.method
|
|
100
|
-
) {
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return false
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Pre-compile route patterns for faster matching
|
|
110
|
-
*/
|
|
111
|
-
// Pattern cache for memory optimization
|
|
112
|
-
private patternCache: Map<string, { exec: (url: URL) => PatternMatchResult | null }> = new Map()
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Extract parameter names from a route path
|
|
116
|
-
*/
|
|
117
|
-
private extractParamNames(path: string): string[] {
|
|
118
|
-
const paramNames: string[] = []
|
|
119
|
-
const regex = /\{([^}:]+)(?::[^}]+)?\}/g
|
|
120
|
-
|
|
121
|
-
// Using a safe approach to avoid lint error with assignment in while condition
|
|
122
|
-
let tempMatch: RegExpExecArray | null
|
|
123
|
-
// eslint-disable-next-line no-cond-assign
|
|
124
|
-
while (tempMatch = regex.exec(path)) {
|
|
125
|
-
paramNames.push(tempMatch[1].replace('?', ''))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return paramNames
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Pre-compile route patterns for faster matching
|
|
133
|
-
*/
|
|
134
|
-
private precompileRoutePattern(route: Route): void {
|
|
135
|
-
if (!route.pattern && route.path.includes('{')) {
|
|
136
|
-
// Generate a cache key that includes constraints if present
|
|
137
|
-
let cacheKey = route.path
|
|
138
|
-
if (route.constraints && !Array.isArray(route.constraints)) {
|
|
139
|
-
// Sort constraint keys for consistent cache key generation
|
|
140
|
-
// Type assertion to handle the Record<string, string> case
|
|
141
|
-
const constraintsRecord = route.constraints as Record<string, string>
|
|
142
|
-
const constraintKeys = Object.keys(constraintsRecord).sort()
|
|
143
|
-
const constraintString = constraintKeys
|
|
144
|
-
.map(key => `${key}:${constraintsRecord[key]}`)
|
|
145
|
-
.join('|')
|
|
146
|
-
cacheKey = `${route.path}#${constraintString}`
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check if we already have this pattern cached
|
|
150
|
-
if (this.patternCache.has(cacheKey)) {
|
|
151
|
-
route.pattern = this.patternCache.get(cacheKey)!
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Convert Laravel-style parameters to URLPattern with constraints
|
|
156
|
-
const pattern = this.convertToURLPattern(
|
|
157
|
-
route.path,
|
|
158
|
-
route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
// Create a URL pattern adapter that matches the expected interface
|
|
162
|
-
const urlPatternAdapter = {
|
|
163
|
-
exec: (url: URL): PatternMatchResult | null => {
|
|
164
|
-
const pathname = url.pathname
|
|
165
|
-
const result = pattern.exec(pathname)
|
|
166
|
-
if (!result)
|
|
167
|
-
return null
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
pathname: {
|
|
171
|
-
groups: result.groups || {},
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Cache the compiled pattern
|
|
178
|
-
this.patternCache.set(cacheKey, urlPatternAdapter)
|
|
179
|
-
|
|
180
|
-
// Assign the pattern to the route
|
|
181
|
-
route.pattern = urlPatternAdapter
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Convert Laravel-style route to a simple regex pattern
|
|
187
|
-
* @param path The route path with Laravel-style parameters
|
|
188
|
-
* @param constraints Optional constraints for route parameters
|
|
189
|
-
*/
|
|
190
|
-
private convertToURLPattern(
|
|
191
|
-
path: string,
|
|
192
|
-
constraints?: Record<string, string>,
|
|
193
|
-
): { exec: (pathname: string) => { groups?: Record<string, string> } | null } {
|
|
194
|
-
// Convert {param} to named capture groups and {param:pattern} to constrained capture groups
|
|
195
|
-
let regexPattern = path.replace(/\{([^}:]+)(?::([^}]+))?\}/g, (_match, name, ..._args) => {
|
|
196
|
-
const pattern = _args[0]
|
|
197
|
-
// If there's a constraint for this parameter, use it instead of the default pattern
|
|
198
|
-
if (constraints && constraints[name]) {
|
|
199
|
-
return `(?<${name}>${constraints[name]})`
|
|
200
|
-
}
|
|
201
|
-
// If there's an inline pattern in the route path, use it
|
|
202
|
-
else if (pattern) {
|
|
203
|
-
return `(?<${name}>${pattern})`
|
|
204
|
-
}
|
|
205
|
-
// Default pattern for parameters without constraints
|
|
206
|
-
return `(?<${name}>[^/]+)`
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
// Escape forward slashes and add anchors
|
|
210
|
-
regexPattern = `^${regexPattern.replace(/\//g, '\\/')}$`
|
|
211
|
-
const regex = new RegExp(regexPattern)
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
exec: (pathname: string) => {
|
|
215
|
-
const match = pathname.match(regex)
|
|
216
|
-
if (!match)
|
|
217
|
-
return null
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
groups: match.groups || {},
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Match a request path and method to a route
|
|
228
|
-
*/
|
|
229
|
-
match(path: string, method: HTTPMethod): MatchResult | null {
|
|
230
|
-
const startTime = performance.now()
|
|
231
|
-
this.stats.totalMatches++
|
|
232
|
-
|
|
233
|
-
// Check cache first if enabled
|
|
234
|
-
if (this.options.enableCaching) {
|
|
235
|
-
const cacheKey = `${method}:${path}`
|
|
236
|
-
if (this.matchCache.has(cacheKey)) {
|
|
237
|
-
this.stats.cacheHits++
|
|
238
|
-
|
|
239
|
-
// Update LRU order - move this key to the end (most recently used)
|
|
240
|
-
this.updateCacheLRU(cacheKey)
|
|
241
|
-
|
|
242
|
-
const cached = this.matchCache.get(cacheKey)
|
|
243
|
-
this.updateMatchTime(startTime)
|
|
244
|
-
return cached || null
|
|
245
|
-
}
|
|
246
|
-
this.stats.cacheMisses++
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Use trie for fast matching
|
|
250
|
-
const trieMatch = this.trie.match(path, method)
|
|
251
|
-
let result: MatchResult | null = null
|
|
252
|
-
|
|
253
|
-
if (trieMatch) {
|
|
254
|
-
result = {
|
|
255
|
-
route: trieMatch.route,
|
|
256
|
-
params: trieMatch.params,
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Cache the result if enabled
|
|
261
|
-
if (this.options.enableCaching) {
|
|
262
|
-
const cacheKey = `${method}:${path}`
|
|
263
|
-
this.addToCache(cacheKey, result)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
this.updateMatchTime(startTime)
|
|
267
|
-
return result
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Add an entry to the cache with LRU eviction if needed
|
|
272
|
-
*/
|
|
273
|
-
private addToCache(key: string, value: MatchResult | null): void {
|
|
274
|
-
// If cache is full, evict least recently used item
|
|
275
|
-
if (this.matchCache.size >= this.options.cacheSize && !this.matchCache.has(key)) {
|
|
276
|
-
// Remove least recently used item (first in array)
|
|
277
|
-
if (this.cacheKeys.length > 0) {
|
|
278
|
-
const lruKey = this.cacheKeys.shift()!
|
|
279
|
-
this.matchCache.delete(lruKey)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Add new item to cache
|
|
284
|
-
this.matchCache.set(key, value)
|
|
285
|
-
|
|
286
|
-
// Update LRU tracking
|
|
287
|
-
this.updateCacheLRU(key)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Update LRU tracking for a cache key
|
|
292
|
-
*/
|
|
293
|
-
private updateCacheLRU(key: string): void {
|
|
294
|
-
// Remove key from current position if it exists
|
|
295
|
-
const index = this.cacheKeys.indexOf(key)
|
|
296
|
-
if (index !== -1) {
|
|
297
|
-
this.cacheKeys.splice(index, 1)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Add key to end of array (most recently used)
|
|
301
|
-
this.cacheKeys.push(key)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Update average match time statistics
|
|
306
|
-
*/
|
|
307
|
-
private updateMatchTime(startTime: number): void {
|
|
308
|
-
const matchTime = performance.now() - startTime
|
|
309
|
-
this.stats.averageMatchTime = (
|
|
310
|
-
(this.stats.averageMatchTime * (this.stats.totalMatches - 1) + matchTime)
|
|
311
|
-
/ this.stats.totalMatches
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Get all compiled routes sorted by priority
|
|
317
|
-
*/
|
|
318
|
-
getCompiledRoutes(): CompiledRoute[] {
|
|
319
|
-
const routes = this.trie.getAllRoutes()
|
|
320
|
-
|
|
321
|
-
if (this.options.enablePriorityOptimization) {
|
|
322
|
-
return routes.sort((a, b) => b.priority - a.priority)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return routes
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Get routes grouped by HTTP method
|
|
330
|
-
*/
|
|
331
|
-
getRoutesByMethod(): Map<HTTPMethod, CompiledRoute[]> {
|
|
332
|
-
const routesByMethod = new Map<HTTPMethod, CompiledRoute[]>()
|
|
333
|
-
|
|
334
|
-
for (const compiled of this.trie.getAllRoutes()) {
|
|
335
|
-
const method = compiled.route.method as HTTPMethod
|
|
336
|
-
if (!routesByMethod.has(method)) {
|
|
337
|
-
routesByMethod.set(method, [])
|
|
338
|
-
}
|
|
339
|
-
routesByMethod.get(method)!.push(compiled)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Sort each method's routes by priority
|
|
343
|
-
if (this.options.enablePriorityOptimization) {
|
|
344
|
-
for (const [method, routes] of routesByMethod) {
|
|
345
|
-
routes.sort((a, b) => b.priority - a.priority)
|
|
346
|
-
routesByMethod.set(method, routes)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return routesByMethod
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Warm up the cache with common routes
|
|
355
|
-
* @param commonPaths Array of paths and methods to pre-cache
|
|
356
|
-
* @param options Optional warming options
|
|
357
|
-
* @param options.force If true, enables caching temporarily even if disabled in compiler options
|
|
358
|
-
* @param options.precompile If true, performs a second match to ensure cache hits are recorded
|
|
359
|
-
* @returns Statistics about the warming operation
|
|
360
|
-
*/
|
|
361
|
-
warmCache(
|
|
362
|
-
commonPaths: Array<{ path: string, method: HTTPMethod }>,
|
|
363
|
-
options: { force?: boolean, precompile?: boolean } = {},
|
|
364
|
-
): { warmedPaths: number, cacheSize: number, hitRate: number } {
|
|
365
|
-
if (!this.options.enableCaching && !options.force) {
|
|
366
|
-
return { warmedPaths: 0, cacheSize: 0, hitRate: 0 }
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Enable caching temporarily if force is true
|
|
370
|
-
const originalCachingState = this.options.enableCaching
|
|
371
|
-
if (options.force && !this.options.enableCaching) {
|
|
372
|
-
this.options.enableCaching = true
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Clear existing cache entries for these paths to ensure fresh warming
|
|
376
|
-
if (options.precompile) {
|
|
377
|
-
for (const { path, method } of commonPaths) {
|
|
378
|
-
const cacheKey = `${method}:${path}`
|
|
379
|
-
this.matchCache.delete(cacheKey)
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Warm the cache by matching each path
|
|
384
|
-
const initialCacheSize = this.matchCache.size
|
|
385
|
-
const initialHits = this.stats.cacheHits
|
|
386
|
-
const initialTotal = this.stats.totalMatches
|
|
387
|
-
|
|
388
|
-
for (const { path, method } of commonPaths) {
|
|
389
|
-
// First match will cache the result
|
|
390
|
-
this.match(path, method)
|
|
391
|
-
|
|
392
|
-
// Second match should be a cache hit
|
|
393
|
-
if (options.precompile) {
|
|
394
|
-
this.match(path, method)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Calculate statistics
|
|
399
|
-
const newCacheEntries = this.matchCache.size - initialCacheSize
|
|
400
|
-
const newHits = this.stats.cacheHits - initialHits
|
|
401
|
-
const newTotal = this.stats.totalMatches - initialTotal
|
|
402
|
-
const hitRate = newTotal > 0 ? newHits / newTotal : 0
|
|
403
|
-
|
|
404
|
-
// Restore original caching state if needed
|
|
405
|
-
if (options.force && !originalCachingState) {
|
|
406
|
-
this.options.enableCaching = originalCachingState
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
warmedPaths: newCacheEntries,
|
|
411
|
-
cacheSize: this.matchCache.size,
|
|
412
|
-
hitRate,
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Clear all caches and reset statistics
|
|
418
|
-
*/
|
|
419
|
-
clear(): void {
|
|
420
|
-
this.trie.clear()
|
|
421
|
-
this.matchCache.clear()
|
|
422
|
-
this.cacheKeys = []
|
|
423
|
-
this.stats = {
|
|
424
|
-
totalMatches: 0,
|
|
425
|
-
cacheHits: 0,
|
|
426
|
-
cacheMisses: 0,
|
|
427
|
-
averageMatchTime: 0,
|
|
428
|
-
trieDepth: 0,
|
|
429
|
-
methodDistribution: {},
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Get performance statistics
|
|
435
|
-
*/
|
|
436
|
-
getStats(): RouteMatchStats {
|
|
437
|
-
const trieStats = this.trie.getStats()
|
|
438
|
-
return {
|
|
439
|
-
...this.stats,
|
|
440
|
-
trieDepth: trieStats.averageDepth,
|
|
441
|
-
methodDistribution: trieStats.methodDistribution,
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Get cache statistics
|
|
447
|
-
*/
|
|
448
|
-
getCacheStats(): {
|
|
449
|
-
size: number
|
|
450
|
-
maxSize: number
|
|
451
|
-
hitRate: number
|
|
452
|
-
enabled: boolean
|
|
453
|
-
} {
|
|
454
|
-
const hitRate = this.stats.totalMatches > 0
|
|
455
|
-
? this.stats.cacheHits / this.stats.totalMatches
|
|
456
|
-
: 0
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
size: this.matchCache.size,
|
|
460
|
-
maxSize: this.options.cacheSize,
|
|
461
|
-
hitRate,
|
|
462
|
-
enabled: this.options.enableCaching,
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Optimize route order based on usage patterns
|
|
468
|
-
*/
|
|
469
|
-
optimizeRouteOrder(_usageStats: Record<string, number>): void {
|
|
470
|
-
if (!this.options.enablePriorityOptimization)
|
|
471
|
-
return
|
|
472
|
-
|
|
473
|
-
// This would reorder routes based on actual usage patterns
|
|
474
|
-
// For now, we'll just clear the cache to force recompilation
|
|
475
|
-
this.matchCache.clear()
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Get route conflicts (overlapping patterns)
|
|
480
|
-
*/
|
|
481
|
-
getRouteConflicts(): Array<{
|
|
482
|
-
routes: CompiledRoute[]
|
|
483
|
-
conflictType: 'overlap' | 'duplicate' | 'ambiguous'
|
|
484
|
-
severity: 'low' | 'medium' | 'high'
|
|
485
|
-
}> {
|
|
486
|
-
const conflicts: Array<{
|
|
487
|
-
routes: CompiledRoute[]
|
|
488
|
-
conflictType: 'overlap' | 'duplicate' | 'ambiguous'
|
|
489
|
-
severity: 'low' | 'medium' | 'high'
|
|
490
|
-
}> = []
|
|
491
|
-
|
|
492
|
-
const routes = this.trie.getAllRoutes()
|
|
493
|
-
const routesByMethod = new Map<string, CompiledRoute[]>()
|
|
494
|
-
|
|
495
|
-
// Group routes by method
|
|
496
|
-
for (const route of routes) {
|
|
497
|
-
const method = route.route.method
|
|
498
|
-
if (!routesByMethod.has(method)) {
|
|
499
|
-
routesByMethod.set(method, [])
|
|
500
|
-
}
|
|
501
|
-
routesByMethod.get(method)!.push(route)
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Check for conflicts within each method
|
|
505
|
-
for (const [_method, methodRoutes] of routesByMethod) {
|
|
506
|
-
for (let i = 0; i < methodRoutes.length; i++) {
|
|
507
|
-
for (let j = i + 1; j < methodRoutes.length; j++) {
|
|
508
|
-
const route1 = methodRoutes[i]
|
|
509
|
-
const route2 = methodRoutes[j]
|
|
510
|
-
|
|
511
|
-
if (this.routesConflict(route1, route2)) {
|
|
512
|
-
const severity = this.getConflictSeverity(route1, route2)
|
|
513
|
-
const conflictType = this.getConflictType(route1, route2)
|
|
514
|
-
|
|
515
|
-
conflicts.push({
|
|
516
|
-
routes: [route1, route2],
|
|
517
|
-
conflictType,
|
|
518
|
-
severity,
|
|
519
|
-
})
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return conflicts
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Check if two routes conflict
|
|
530
|
-
*/
|
|
531
|
-
private routesConflict(route1: CompiledRoute, route2: CompiledRoute): boolean {
|
|
532
|
-
// Exact path match
|
|
533
|
-
if (route1.route.path === route2.route.path) {
|
|
534
|
-
return true
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// If routes have different methods, they don't conflict
|
|
538
|
-
if (route1.route.method !== route2.route.method) {
|
|
539
|
-
return false
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Check if patterns could match the same paths
|
|
543
|
-
return this.patternsOverlap(route1.segments, route2.segments)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Check if two route patterns overlap
|
|
548
|
-
*/
|
|
549
|
-
private patternsOverlap(segments1: any[], segments2: any[]): boolean {
|
|
550
|
-
// If segment lengths differ and neither has optional parameters or wildcards,
|
|
551
|
-
// they can't overlap
|
|
552
|
-
const hasOptionalOrWildcard1 = segments1.some(seg => seg.optional || seg.type === 'wildcard')
|
|
553
|
-
const hasOptionalOrWildcard2 = segments2.some(seg => seg.optional || seg.type === 'wildcard')
|
|
554
|
-
|
|
555
|
-
if (segments1.length !== segments2.length && !hasOptionalOrWildcard1 && !hasOptionalOrWildcard2) {
|
|
556
|
-
return false
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const maxLength = Math.max(segments1.length, segments2.length)
|
|
560
|
-
let potentialOverlap = true
|
|
561
|
-
|
|
562
|
-
for (let i = 0; i < maxLength; i++) {
|
|
563
|
-
const seg1 = i < segments1.length ? segments1[i] : null
|
|
564
|
-
const seg2 = i < segments2.length ? segments2[i] : null
|
|
565
|
-
|
|
566
|
-
// If one segment is missing and the other isn't optional, they can't overlap
|
|
567
|
-
if (!seg1 && seg2 && !seg2.optional) {
|
|
568
|
-
return false
|
|
569
|
-
}
|
|
570
|
-
if (!seg2 && seg1 && !seg1.optional) {
|
|
571
|
-
return false
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// If both segments are missing, continue
|
|
575
|
-
if (!seg1 || !seg2) {
|
|
576
|
-
continue
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Static segments must match exactly
|
|
580
|
-
if (seg1.type === 'static' && seg2.type === 'static') {
|
|
581
|
-
if (seg1.value !== seg2.value) {
|
|
582
|
-
return false
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Check for parameter constraints
|
|
586
|
-
else if (seg1.type === 'parameter' && seg2.type === 'parameter') {
|
|
587
|
-
// If both have patterns/constraints, check if they're mutually exclusive
|
|
588
|
-
if (seg1.pattern && seg2.pattern) {
|
|
589
|
-
// Simple check: if patterns are different, assume they might overlap
|
|
590
|
-
// A more sophisticated check would analyze the regex patterns
|
|
591
|
-
if (seg1.pattern.toString() === seg2.pattern.toString()) {
|
|
592
|
-
potentialOverlap = true
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// If parameter names are the same, they likely represent the same entity
|
|
597
|
-
// This is a heuristic that helps identify conflicts
|
|
598
|
-
if (seg1.paramName === seg2.paramName) {
|
|
599
|
-
potentialOverlap = true
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
// One static, one parameter
|
|
603
|
-
else if (
|
|
604
|
-
(seg1.type === 'static' && seg2.type === 'parameter')
|
|
605
|
-
|| (seg1.type === 'parameter' && seg2.type === 'static')
|
|
606
|
-
) {
|
|
607
|
-
const staticSeg = seg1.type === 'static' ? seg1 : seg2
|
|
608
|
-
const paramSeg = seg1.type === 'parameter' ? seg1 : seg2
|
|
609
|
-
|
|
610
|
-
// If parameter has a pattern, check if static value matches the pattern
|
|
611
|
-
if (paramSeg.pattern) {
|
|
612
|
-
if (!paramSeg.pattern.test(staticSeg.value)) {
|
|
613
|
-
return false
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
// Wildcard matches anything
|
|
618
|
-
else if (seg1.type === 'wildcard' || seg2.type === 'wildcard') {
|
|
619
|
-
return true
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return potentialOverlap
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Get conflict severity
|
|
628
|
-
*/
|
|
629
|
-
private getConflictSeverity(route1: CompiledRoute, route2: CompiledRoute): 'low' | 'medium' | 'high' {
|
|
630
|
-
if (route1.route.path === route2.route.path) {
|
|
631
|
-
return 'high' // Exact duplicate
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (route1.staticScore === route2.staticScore) {
|
|
635
|
-
return 'medium' // Same specificity
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return 'low' // Different specificity, priority will resolve
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Get conflict type
|
|
643
|
-
*/
|
|
644
|
-
private getConflictType(route1: CompiledRoute, route2: CompiledRoute): 'overlap' | 'duplicate' | 'ambiguous' {
|
|
645
|
-
if (route1.route.path === route2.route.path) {
|
|
646
|
-
return 'duplicate'
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Check for ambiguous parameter patterns
|
|
650
|
-
const hasAmbiguousParams = this.hasAmbiguousParameters(route1, route2)
|
|
651
|
-
if (hasAmbiguousParams) {
|
|
652
|
-
return 'ambiguous'
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Check for similar priority
|
|
656
|
-
if (Math.abs(route1.priority - route2.priority) < 10) {
|
|
657
|
-
return 'ambiguous'
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
return 'overlap'
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Check if routes have ambiguous parameters
|
|
665
|
-
*/
|
|
666
|
-
private hasAmbiguousParameters(route1: CompiledRoute, route2: CompiledRoute): boolean {
|
|
667
|
-
// If routes have different segment counts, they're less likely to be ambiguous
|
|
668
|
-
if (Math.abs(route1.segments.length - route2.segments.length) > 1) {
|
|
669
|
-
return false
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Count parameter segments in each route
|
|
673
|
-
const paramCount1 = route1.segments.filter(s => s.type === 'parameter').length
|
|
674
|
-
const paramCount2 = route2.segments.filter(s => s.type === 'parameter').length
|
|
675
|
-
|
|
676
|
-
// If both routes have parameters in the same positions, they might be ambiguous
|
|
677
|
-
if (paramCount1 > 0 && paramCount2 > 0) {
|
|
678
|
-
const minLength = Math.min(route1.segments.length, route2.segments.length)
|
|
679
|
-
let samePositionParams = 0
|
|
680
|
-
|
|
681
|
-
for (let i = 0; i < minLength; i++) {
|
|
682
|
-
if (route1.segments[i].type === 'parameter' && route2.segments[i].type === 'parameter') {
|
|
683
|
-
samePositionParams++
|
|
684
|
-
|
|
685
|
-
// If parameters have different constraints, they might still be distinct
|
|
686
|
-
const hasPattern1 = !!route1.segments[i].pattern
|
|
687
|
-
const hasPattern2 = !!route2.segments[i].pattern
|
|
688
|
-
|
|
689
|
-
if (hasPattern1 !== hasPattern2) {
|
|
690
|
-
return true // One has constraint, one doesn't = ambiguous
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// If more than half of parameters are in the same positions, likely ambiguous
|
|
696
|
-
return samePositionParams > 0
|
|
697
|
-
&& samePositionParams >= Math.min(paramCount1, paramCount2) / 2
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
return false
|
|
701
|
-
}
|
|
702
|
-
}
|