Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | 287x 287x 2122x 2122x 1288x 1288x 2122x 834x 834x 834x 21x 21x 21x 532x 532x 532x 21x 287x 287x 2122x 2122x 2122x 2122x 2122x 2122x 1288x 834x 834x 156x 834x 21x 21x 834x 834x 33x 33x 33x 33x 33x 33x 32x 1x 1x 1x 1x | import type { InjectionTokenType } from '../../token/injection-token.mjs'
import { InjectableScope } from '../../enums/index.mjs'
/**
* Simple LRU cache for instance name generation.
* Uses a Map which maintains insertion order for efficient LRU eviction.
*/
class InstanceNameCache {
private readonly cache = new Map<string, string>()
private readonly maxSize: number
constructor(maxSize = 1000) {
this.maxSize = maxSize
}
get(key: string): string | undefined {
const value = this.cache.get(key)
if (value !== undefined) {
// Move to end (most recently used)
this.cache.delete(key)
this.cache.set(key, value)
}
return value
}
set(key: string, value: string): void {
Iif (this.cache.has(key)) {
this.cache.delete(key)
I} else if (this.cache.size >= this.maxSize) {
// Remove least recently used (first item)
const firstKey = this.cache.keys().next().value
if (firstKey !== undefined) {
this.cache.delete(firstKey)
}
}
this.cache.set(key, value)
}
clear(): void {
this.cache.clear()
}
}
/**
* Simple hash function for deterministic hashing of arguments
*/
function hashArgs(args: any): string {
const str = JSON.stringify(args, Object.keys(args || {}).sort())
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32-bit integer
}
return Math.abs(hash).toString(36)
}
/**
* Handles instance name generation with support for requestId and scope.
*
* Generates unique instance identifiers based on token, arguments, and scope.
* Request-scoped services MUST include requestId in their name for proper isolation.
*/
export class NameResolver {
private readonly instanceNameCache = new InstanceNameCache()
constructor(private readonly logger: Console | null = null) {}
/**
* Generates a unique instance name based on token, arguments, requestId, and scope.
*
* Name formats:
* - Singleton/Transient without args: `${tokenId}`
* - Singleton/Transient with args: `${tokenId}:${argsHash}`
* - Request without args: `${tokenId}:requestId=${requestId}`
* - Request with args: `${tokenId}:requestId=${requestId}:${argsHash}`
*
* @param token The injection token
* @param args Optional arguments
* @param requestId Optional request ID (required for request-scoped services)
* @param scope Optional scope (used to determine if requestId should be included)
* @returns The generated instance name
*/
generateInstanceName(
token: InjectionTokenType,
args?: any,
requestId?: string,
scope?: InjectableScope,
): string {
const tokenStr = token.toString()
const isRequest = scope === InjectableScope.Request
// For request-scoped services, requestId is required
Iif (isRequest && !requestId) {
throw new Error(
`[NameResolver] requestId is required for request-scoped services`,
)
}
// Build cache key
const cacheKey = `${tokenStr}:${scope}:${requestId || ''}:${args ? JSON.stringify(args) : ''}`
// Check cache first
const cached = this.instanceNameCache.get(cacheKey)
if (cached !== undefined) {
return cached
}
// Generate the instance name
let result = tokenStr
// Add requestId for request-scoped services
if (isRequest && requestId) {
result = `${result}:requestId=${requestId}`
}
// Add args hash if args are provided
if (args) {
const argsHash = hashArgs(args)
result = `${result}:${argsHash}`
}
// Cache the result
this.instanceNameCache.set(cacheKey, result)
return result
}
/**
* Upgrades an existing instance name to include requestId.
* Preserves any args hash that might already be in the name.
*
* Examples:
* - `TokenName` → `TokenName:requestId=req-123`
* - `TokenName:abc123` → `TokenName:requestId=req-123:abc123`
*
* @param existingName The existing instance name (without requestId)
* @param requestId The request ID to add
* @returns The upgraded instance name with requestId
*/
upgradeInstanceNameToRequest(
existingName: string,
requestId: string,
): string {
// Check if requestId is already in the name
Iif (existingName.includes(`:requestId=${requestId}`)) {
return existingName
}
// Find where to insert requestId
// Format: TokenName or TokenName:argsHash
// We want: TokenName:requestId=req-123 or TokenName:requestId=req-123:argsHash
// Check if there's an args hash (starts after first colon, but not requestId=)
const requestIdPattern = /:requestId=/
const hasRequestId = requestIdPattern.test(existingName)
Iif (hasRequestId) {
// Already has a requestId, don't upgrade
return existingName
}
// Find the token part (everything before first colon, or entire string if no colon)
const colonIndex = existingName.indexOf(':')
if (colonIndex === -1) {
// No colon, just token name: TokenName → TokenName:requestId=req-123
return `${existingName}:requestId=${requestId}`
}
// Has colon, means there's an args hash: TokenName:abc123 → TokenName:requestId=req-123:abc123
const tokenPart = existingName.substring(0, colonIndex)
const argsPart = existingName.substring(colonIndex + 1)
// Check if argsPart looks like an args hash (not requestId=)
Iif (argsPart.startsWith('requestId=')) {
// Already has requestId, return as is
return existingName
}
return `${tokenPart}:requestId=${requestId}:${argsPart}`
}
/**
* Formats a single argument value for instance name generation.
*/
formatArgValue(value: any): string {
if (typeof value === 'function') {
return `fn_${value.name}(${value.length})`
}
if (typeof value === 'symbol') {
return value.toString()
}
return JSON.stringify(value).slice(0, 40)
}
}
|