Async processing
Two durable mechanisms and one that does not survive a crash.
| Mechanism | Process | Survives process crash |
|---|---|---|
Prisma outbox, then EventEmitter2 | Dispatcher only on the worker | Yes. The row commits with the business write |
| Bull queue | Worker processors | Yes. Redis |
EventEmitter2.emit inside the API | Listeners loaded in that process | No |
Do not replace an outbox enqueue with an in-process emit for order side effects. A listener registered only on the worker will not see an API emit, and the emit is not transactional.
Dispatcher
| Behavior | Value read |
|---|---|
| Poll | Every 1 second |
| Claim | Batch 100, FOR UPDATE SKIP LOCKED |
| Dispatch concurrency | 10 |
| Success | Status PROCESSED |
| Listener throw | Attempts increment, stay PENDING until 10, then DEAD. lastError stored |
| Backoff | min(60_000, 2^attempts * 1000) ms |
Stale PROCESSING | Older than 5 minutes returns to PENDING without incrementing attempts |
| Purge | PROCESSED older than 30 days, batches of 5000, max 20 batches. Cron 03:00 |
ProcessedEvent purge | Older than 90 days, same batching |
emitAsync waits for listeners. If any listener rejects, the promise rejects and the event stays retryable, so all listeners run again. A listener that already succeeded and is not idempotent will double-apply. IdempotentEventHandlerService exists and is opt-in. It locks the order row when orderId is provided and records ProcessedEvent. Which listeners call it was not fully enumerated.
DEAD rows are not automatically returned to PENDING. The replay procedure is unknown.
In-process events that are not outbox types
| Name | Producer | Listener found |
|---|---|---|
channel.created | ChannelService | channel location auto-bind |
channel.updated | ChannelService | webhook health |
audit.log | audit interceptor, WooCommerce webhook health | AuditLogListener |
product.content.changed | product event publisher | ProductEmbeddingListener |
product.channel-allocation.changed | product events | ProductStockSyncListener |
shipment.status.changed | shipment services | fulfillment, NDR, return shipment |
return.status.changed | return command | none |
return.stock.restored | return inventory | none |
return.status.changed and return.stock.restored are emitted. No @OnEvent listener was found. Nothing retries them.