feat: persist Dead Letter Queue to PostgreSQL (closes #221)#271
Open
whiteghost0001 wants to merge 1 commit into
Open
feat: persist Dead Letter Queue to PostgreSQL (closes #221)#271whiteghost0001 wants to merge 1 commit into
whiteghost0001 wants to merge 1 commit into
Conversation
## Problem
The Dead Letter Queue (DLQ) used by the indexer and webhook subsystems
was stored entirely in-memory (a plain array `dlqEntries` in
`src/routes/dlq.ts`). This meant every dead-lettered event was lost on
process restart, making the DLQ useless for production reliability and
incident investigation.
## What was done
### 1. Database migration — `migrations/1774715300000_dead-letter-queue.ts`
Created a new node-pg-migrate migration that adds the
`dead_letter_queue` table with the following schema:
- `id` TEXT PRIMARY KEY
- `topic` TEXT NOT NULL
- `payload` JSONB NOT NULL
- `error` TEXT NOT NULL
- `attempts` INTEGER NOT NULL DEFAULT 1
- `correlation_id` TEXT (nullable)
- `first_failed_at` TIMESTAMPTZ NOT NULL DEFAULT current_timestamp
- `last_failed_at` TIMESTAMPTZ NOT NULL DEFAULT current_timestamp
Indexes on `topic` and `first_failed_at` cover the two most common
query patterns (filter by topic, sort by age).
### 2. DLQ repository — `src/db/repositories/dlqRepository.ts`
New repository following the same pattern as `streamRepository.ts`:
uses `getPool()` and `query()` from `src/db/pool.ts`.
Exposed methods:
- `insert(entry)` — persists a new DLQ entry
- `findAll({ limit, offset, topic? })` — paginated list with optional
topic filter; returns `{ entries, total }`
- `findById(id)` — single entry lookup
- `update(id, patch)` — partial update for replay (reset attempts /
update lastFailedAt)
- `deleteById(id)` — acknowledge/remove one entry; returns boolean
- `deleteAll(topic?)` — bulk purge with optional topic filter; returns
count of deleted rows
### 3. Route update — `src/routes/dlq.ts`
- Removed the in-memory `dlqEntries` array, `getDlqEntries()`, and
`_resetDlq()` helpers.
- `enqueueDeadLetter()` is now `async` and calls
`dlqRepository.insert()`.
- All five route handlers (GET /, GET /:id, POST /:id/replay,
DELETE /:id, DELETE /) now delegate to the repository instead of
mutating the in-memory array.
- Audit events and structured logging are preserved unchanged.
## How it was done
- Followed the existing node-pg-migrate migration format (MigrationBuilder
API, timestamp-prefixed filename).
- Followed the existing repository pattern (getPool/query, row-to-type
mapper, parameterised queries throughout — no string interpolation of
user input).
- TypeScript compiles cleanly (`tsc --noEmit`); the only pre-existing
error is an unrelated syntax issue in `tests/webhooks/retry.rateLimit.test.ts`.
Closes Fluxora-Org#221
|
@whiteghost0001 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Dead Letter Queue (DLQ) used by the indexer and webhook subsystems was stored entirely in-memory (a plain array
dlqEntriesinsrc/routes/dlq.ts). This meant every dead-lettered event was lost on process restart, making the DLQ useless for production reliability and incident investigation.What was done
1. Database migration —
migrations/1774715300000_dead-letter-queue.tsCreated a new node-pg-migrate migration that adds thedead_letter_queuetable with the following schema:idTEXT PRIMARY KEYtopicTEXT NOT NULLpayloadJSONB NOT NULLerrorTEXT NOT NULLattemptsINTEGER NOT NULL DEFAULT 1correlation_idTEXT (nullable)first_failed_atTIMESTAMPTZ NOT NULL DEFAULT current_timestamplast_failed_atTIMESTAMPTZ NOT NULL DEFAULT current_timestampIndexes on
topicandfirst_failed_atcover the two most common query patterns (filter by topic, sort by age).2. DLQ repository —
src/db/repositories/dlqRepository.tsNew repository following the same pattern asstreamRepository.ts: usesgetPool()andquery()fromsrc/db/pool.ts.Exposed methods:
insert(entry)— persists a new DLQ entryfindAll({ limit, offset, topic? })— paginated list with optional topic filter; returns{ entries, total }findById(id)— single entry lookupupdate(id, patch)— partial update for replay (reset attempts / update lastFailedAt)deleteById(id)— acknowledge/remove one entry; returns booleandeleteAll(topic?)— bulk purge with optional topic filter; returns count of deleted rows3. Route update —
src/routes/dlq.tsdlqEntriesarray,getDlqEntries(), and_resetDlq()helpers.enqueueDeadLetter()is nowasyncand callsdlqRepository.insert().How it was done
tsc --noEmit); the only pre-existing error is an unrelated syntax issue intests/webhooks/retry.rateLimit.test.ts.Closes #221