Skip to content

feat(disputes): compress large responses (gzip/deflate)#544

Merged
greatest0fallt1me merged 1 commit into
Predictify-org:mainfrom
mohadon0:feature/disputes-52-compress
Jul 26, 2026
Merged

feat(disputes): compress large responses (gzip/deflate)#544
greatest0fallt1me merged 1 commit into
Predictify-org:mainfrom
mohadon0:feature/disputes-52-compress

Conversation

@mohadon0

Copy link
Copy Markdown
Contributor

Summary

Enables gzip and deflate response compression for the /api/markets/:id/disputes endpoint. Large responses (≥ 1 KiB) are transparently compressed using Node.js's built-in zlib module — no new runtime dependencies are introduced.


Changes

src/middleware/compression.ts (new)

A self-contained Express middleware module exporting:

Export Description
COMPRESSION_THRESHOLD 1024 bytes — minimum serialised response size before compression is applied
selectEncoding(acceptEncoding) Parses Accept-Encoding and returns "gzip", "deflate", or "identity". Prefers gzip.
compressResponse Express RequestHandler — intercepts res.json(), compresses large payloads, sets appropriate headers

Key behaviour:

  • Vary: Accept-Encoding is always set so HTTP caches (CDNs, proxies) store separate entries per encoding.
  • Payloads below COMPRESSION_THRESHOLD are forwarded to the original res.json() unchanged.
  • Compression errors fall back to identity (uncompressed) and log a warning — no 500s.
  • Status code is preserved through compression.

src/routes/disputes.ts (modified)

Added import { compressResponse } from "../middleware/compression" and registered it as router-level middleware:

disputesRouter.use(compressResponse);

This applies compression to all current and future handlers on the disputes router without touching individual route handlers.

tests/compression.test.ts (new)

625-line test file covering:

Suite Cases
selectEncoding unit gzip, deflate, gzip+deflate (prefers gzip), absent header, empty string, unsupported (br), case-insensitive
compressResponse unit next() called, Vary always set, res.json replaced only for compressed encodings, identity leaves original res.json
Integration — large payload gzip round-trip (gunzip + JSON.parse), deflate round-trip (inflate + JSON.parse), gzip preferred over deflate, Content-Encoding header, Content-Type preserved, Content-Length smaller than raw, status code preserved
Integration — small payload No Content-Encoding, Vary still set, raw JSON body intact
Integration — identity No compression without Accept-Encoding, identity explicit, unsupported (br)
Disputes route (createApp) gzip 201 round-trip, deflate 201 round-trip, small response uncompressed, absent header uncompressed, Vary always on disputes, status preserved, error 409 unaffected, 401 auth still works
COMPRESSION_THRESHOLD boundary Constant is 1024, payload at threshold IS compressed, payload below threshold is NOT

Design decisions

No new dependencies. The implementation uses zlib from the Node.js standard library. The popular compression npm package uses the same underlying primitives and would add a transitive dependency for no functional gain here.

Router-level middleware, not global. Compression is scoped to disputesRouter only — other routes are unaffected. A global app.use(compressResponse) could interfere with streaming responses (SSE, NDJSON export) and was intentionally avoided.

Synchronous zlib. gzipSync / deflateSync are used rather than the async streaming variants because res.json() is a synchronous call and the payloads involved are bounded JSON objects. For streaming endpoints the async pipeline approach would be appropriate.

Threshold at 1 KiB. The COMPRESSION_THRESHOLD (1024 bytes) is a named, exported constant so it can be adjusted or overridden in tests without magic numbers.


Files changed

src/middleware/compression.ts   (new — 188 lines)
src/routes/disputes.ts          (modified — +3 lines)
tests/compression.test.ts       (new — 625 lines)

How to verify

# Run the new compression tests
npx jest tests/compression.test.ts --no-coverage

# Run the existing disputes tests to confirm nothing regressed
npx jest tests/disputes.test.ts --no-coverage

Resolves #52.

- Add src/middleware/compression.ts with compressResponse middleware
  using Node.js built-in zlib (no new dependencies required)
- Support gzip and deflate encoding, selected via Accept-Encoding header
- Prefer gzip over deflate when both are advertised by the client
- Apply compression only to responses >= 1 KiB (COMPRESSION_THRESHOLD)
  to avoid CPU overhead on small payloads
- Always set Vary: Accept-Encoding so HTTP caches store separate entries
  per encoding
- Register compressResponse on the disputes router so all /api/markets/:id/disputes
  responses are eligible for compression
- Add tests/compression.test.ts with unit and integration coverage:
  - selectEncoding helper (all encoding combinations and edge cases)
  - compressResponse middleware unit tests (header, res.json replacement)
  - Integration: gzip/deflate compress large payloads, round-trip verified
  - Integration: small payloads (< 1 KiB) remain uncompressed
  - Integration: identity/absent Accept-Encoding bypasses compression
  - Integration: disputes route verified end-to-end for both paths
  - COMPRESSION_THRESHOLD boundary tests (at and below threshold)
@mohadon0
mohadon0 marked this pull request as draft July 26, 2026 08:25
@mohadon0
mohadon0 marked this pull request as ready for review July 26, 2026 08:25
@mohadon0 mohadon0 closed this Jul 26, 2026
@mohadon0 mohadon0 reopened this Jul 26, 2026
@greatest0fallt1me
greatest0fallt1me merged commit 76ccee3 into Predictify-org:main Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SBOM generation in CI for backend artifacts

2 participants