3.4 KiB
MODIFIED Requirements
Requirement: Interceptor API
The package SHALL export bus.use(interceptor) registering an interceptor of shape <K extends keyof TMap>(envelope: EventEnvelope<TMap[K]>, next: Next<TMap, K>) => Promise<EmitResult>. Interceptor<TMap> and Next<TMap, K> are generic over the bus's event map. Next accepts an optional replacement envelope so an interceptor MAY rewrite the envelope before downstream interceptors and listeners observe it.
Scenario: Interceptor envelope is typed
- WHEN a consumer registers
bus.use<{login: {user: string}}>an interceptor on aEventBus<{login: {user: string}}> - THEN
envelope.payloadis typed{user: string}inside the interceptor body (notunknown)
Scenario: Interceptor can short-circuit
- WHEN an interceptor returns without calling
next() - THEN registered listeners do not fire AND
EmitResult.delivered === 0
Scenario: Interceptor throw aborts emit
- WHEN an interceptor throws
- THEN
emitAsyncrejects withEventsError("EVENTS.INTERCEPTOR.THREW")withcause === the thrown value
Scenario: Interceptor can replace envelope via next()
- WHEN an interceptor invokes
next(replacement)with a newEventEnvelope - THEN downstream interceptors and listeners receive the replacement envelope AND
replacement.payloadis delivered to listeners
Requirement: Structured errors
All thrown errors SHALL be EventsError extends SuaveplanError<EventsCode> with codes prefixed EVENTS.. Listener failures SHALL be collected into EmitResult.errors (synchronous failures) or EmitResult.deferredErrors (failures from listener-returned promises in sync emit), not propagated, unless an interceptor explicitly re-throws.
Scenario: Listener throw is captured
- WHEN a sync listener throws during
emit - THEN
emitreturnsEmitResultwitherrors[0].code === "EVENTS.LISTENER.THREW"ANDerrors[0].cause === the thrown valueANDdeliveredreflects successful synchronous deliveries only
Scenario: Async-listener rejection captured by sync emit
- WHEN a listener registered via
bus.on()returns aPromisethat rejects ANDbus.emit()is called - THEN the synchronous return of
emit()resolves withdeliveredcounting the dispatched listener (call did not throw) ANDEmitResult.deferredErrorsresolves to a list containing anEventsError("EVENTS.LISTENER.THREW")whosecauseis the rejection value
ADDED Requirements
Requirement: Sync emit bypasses interceptors with detection hook
Sync emit() SHALL NOT run async interceptors (interceptors are asynchronous and emit() is synchronous). The bus SHALL accept an optional onSyncEmitWithInterceptors callback in EventBusOptions that fires once per sync emit whenever interceptors are registered, allowing adapters to detect mixed usage.
Scenario: Sync emit with interceptors triggers warning hook
- WHEN
bus.emit("x", v)is called on a bus with at least one registered interceptor ANDonSyncEmitWithInterceptorsis provided - THEN
onSyncEmitWithInterceptors(count)is invoked with the registered interceptor count AND interceptors do not run AND listeners still fire in registration order
Scenario: Sync emit without interceptors does not invoke hook
- WHEN
bus.emit("x", v)is called and no interceptors are registered - THEN the hook is not invoked