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 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | 268x 268x 526x 526x 526x 526x 526x 526x 526x 134x 134x 144x 13x 13x 144x 340x 340x 340x 340x 340x 340x 340x 27x 313x 313x 493x 313x 340x 343x 496x 343x 496x 526x 526x 526x 526x 526x 526x 526x 526x 153x 526x 526x 526x 526x 526x 526x 526x 496x 496x 526x 526x 526x | import type { IHolderStorage } from '../holder/holder-storage.interface.mjs'
import type { InstanceHolder } from '../holder/instance-holder.mjs'
import type { LifecycleEventBus } from '../lifecycle/lifecycle-event-bus.mjs'
import { InstanceStatus } from '../holder/instance-holder.mjs'
export interface ClearAllOptions {
/** Whether to wait for all services to settle before starting (default: true) */
waitForSettlement?: boolean
}
export interface InvalidationOptions {
/** Whether to emit events after invalidation (default: true) */
emitEvents?: boolean
/** Custom event emitter function */
onInvalidated?: (instanceName: string) => Promise<void>
/** Whether to cascade invalidation to dependents (default: false - events handle it) */
cascade?: boolean
}
/**
* Manages graceful service cleanup with event-based invalidation.
*
* Uses event subscriptions instead of manual dependent finding.
* When a service is created, it subscribes to destroy events of its dependencies.
* When a dependency is destroyed, the event automatically invalidates dependents.
*/
export class ServiceInvalidator {
constructor(
private readonly eventBus: LifecycleEventBus | null,
private readonly logger: Console | null = null,
) {}
/**
* Invalidates a service using a specific storage.
* Event-based invalidation means dependents are automatically invalidated
* via destroy event subscriptions - no need to manually find dependents.
*
* @param service The instance name to invalidate
* @param storage The storage to use for this invalidation
* @param options Additional options for invalidation behavior
*/
async invalidateWithStorage(
service: string,
storage: IHolderStorage,
options: InvalidationOptions = {},
): Promise<void> {
const { emitEvents = true, onInvalidated } = options
this.logger?.log(
`[ServiceInvalidator] Starting invalidation process for ${service}`,
)
const result = storage.get(service)
Iif (result === null) {
return
}
const [, holder] = result
Eif (holder) {
await this.invalidateHolderWithStorage(
service,
holder,
storage,
emitEvents,
onInvalidated,
)
}
}
/**
* Sets up destroy event subscriptions for a service's dependencies.
* Called when a service is successfully instantiated.
*
* @param serviceName The name of the service
* @param dependencies The set of dependency names
* @param storage The storage to use for invalidation
* @param holder The holder for the service (to add unsubscribe to destroy listeners)
*/
setupDependencySubscriptions(
serviceName: string,
dependencies: Set<string>,
storage: IHolderStorage,
holder: InstanceHolder,
): void {
Iif (!this.eventBus) {
return
}
for (const dependencyName of dependencies) {
// Subscribe to the dependency's destroy event
const unsubscribe = this.eventBus.on(dependencyName, 'destroy', () => {
this.logger?.log(
`[ServiceInvalidator] Dependency ${dependencyName} destroyed, invalidating ${serviceName}`,
)
// Automatically invalidate this service when dependency is destroyed
this.invalidateWithStorage(serviceName, storage).catch((error) => {
this.logger?.error(
`[ServiceInvalidator] Error invalidating ${serviceName} after dependency ${dependencyName} destroyed:`,
error,
)
})
})
// Store unsubscribe function in the service's destroy listeners
// so it's cleaned up when the service is destroyed
holder.destroyListeners.push(unsubscribe)
}
}
/**
* Gracefully clears all services in a specific storage.
* This allows clearing request-scoped services using a RequestStorage.
*/
async clearAllWithStorage(
storage: IHolderStorage,
options: ClearAllOptions = {},
): Promise<void> {
const { waitForSettlement = true } = options
this.logger?.log(
'[ServiceInvalidator] Starting graceful clearing of all services',
)
// Wait for all services to settle if requested
Eif (waitForSettlement) {
this.logger?.log(
'[ServiceInvalidator] Waiting for all services to settle...',
)
await this.readyWithStorage(storage)
}
// Get all service names that need to be cleared
const allServiceNames = storage.getAllNames()
if (allServiceNames.length === 0) {
this.logger?.log('[ServiceInvalidator] No services to clear')
} else {
this.logger?.log(
`[ServiceInvalidator] Found ${allServiceNames.length} services to clear: ${allServiceNames.join(', ')}`,
)
// Clear services - events will handle dependent invalidation
const clearPromises = allServiceNames.map((serviceName) =>
this.invalidateWithStorage(serviceName, storage),
)
await Promise.all(clearPromises)
}
this.logger?.log('[ServiceInvalidator] Graceful clearing completed')
}
/**
* Waits for all services in a specific storage to settle.
*/
async readyWithStorage(storage: IHolderStorage): Promise<void> {
const holders: InstanceHolder<any>[] = []
storage.forEach((_: string, holder: InstanceHolder) => holders.push(holder))
await Promise.all(
holders.map((holder) => this.waitForHolderToSettle(holder)),
)
}
// ============================================================================
// INTERNAL INVALIDATION HELPERS
// ============================================================================
/**
* Invalidates a single holder using a specific storage.
*/
private async invalidateHolderWithStorage(
key: string,
holder: InstanceHolder<any>,
storage: IHolderStorage,
emitEvents: boolean,
onInvalidated?: (instanceName: string) => Promise<void>,
): Promise<void> {
await this.invalidateHolderByStatus(holder, {
context: key,
onDestroy: () =>
this.destroyHolderWithStorage(
key,
holder,
storage,
emitEvents,
onInvalidated,
),
})
}
/**
* Common invalidation logic for holders based on their status.
*/
private async invalidateHolderByStatus(
holder: InstanceHolder<any>,
options: {
context: string
onDestroy: () => Promise<void>
},
): Promise<void> {
switch (holder.status) {
case InstanceStatus.Destroying:
await holder.destroyPromise
break
case InstanceStatus.Creating:
// Wait for creation to complete before destroying
await holder.creationPromise
await options.onDestroy()
break
default:
await options.onDestroy()
break
}
}
/**
* Destroys a holder using a specific storage.
*/
private async destroyHolderWithStorage(
key: string,
holder: InstanceHolder<any>,
storage: IHolderStorage,
emitEvents: boolean,
onInvalidated?: (instanceName: string) => Promise<void>,
): Promise<void> {
holder.status = InstanceStatus.Destroying
this.logger?.log(
`[ServiceInvalidator] Invalidating ${key} and notifying listeners`,
)
holder.destroyPromise = Promise.all(
holder.destroyListeners.map((listener) => listener()),
).then(async () => {
holder.destroyListeners = []
holder.deps.clear()
storage.delete(key)
// Emit events if enabled and event bus exists
Eif (emitEvents && this.eventBus) {
await this.emitInstanceEvent(key, 'destroy')
}
// Call custom callback if provided
Iif (onInvalidated) {
await onInvalidated(key)
}
})
await holder.destroyPromise
}
/**
* Waits for a holder to settle (either created, destroyed, or error state).
*/
private async waitForHolderToSettle(
holder: InstanceHolder<any>,
): Promise<void> {
switch (holder.status) {
case InstanceStatus.Creating:
await holder.creationPromise
break
case InstanceStatus.Destroying:
await holder.destroyPromise
break
// Already settled states
case InstanceStatus.Created:
case InstanceStatus.Error:
break
}
}
/**
* Emits events to listeners for instance lifecycle events.
*/
private emitInstanceEvent(
name: string,
event: 'create' | 'destroy' = 'create',
) {
Iif (!this.eventBus) {
return Promise.resolve()
}
this.logger?.log(
`[ServiceInvalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`,
)
return this.eventBus.emit(name, event)
}
}
|