Skip to content

Fix elysia standard-mode compression via the documented mapResponse pattern - #1089

Open
MDA2AV wants to merge 2 commits into
mainfrom
fix/elysia-standard-compression
Open

Fix elysia standard-mode compression via the documented mapResponse pattern#1089
MDA2AV wants to merge 2 commits into
mainfrom
fix/elysia-standard-compression

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Description

Closes #1075 — the elysia entry hand-rolled compression negotiation in /json/:count while marked mode: standard, and its br branch shipped deflate bytes labeled content-encoding: br (latent: the branch compared the whole header to "br", so validation's gzip, br never reached it).

Why not a compression plugin

The plugin route (the issue's first preference) is a dead end on elysia 1.4 — verified by building, not just reading:

  • elysia-compress fails to compile: it imports mapResponse from elysia's root export, removed in elysia 1.2.6 (vermaysha/elysia-compress#149); the repo was archived Nov 2025. Its default 24 h compressed-response cache would also collide with the json-comp rule's "no pre-compressed caches" clause.
  • @labzzhq/compressor (fork) carries the identical broken import; the remaining plugins are 2+ years stale.

Elysia's own docs prescribe compression via the mapResponse lifecycle hook (essential/life-cycle) — that is the framework's built-in compression mechanism, so this PR implements the docs pattern extended with real negotiation, keeping the entry standard:

  • first acceptable encoding in the client's own order (br or gzip, q=0 honoured), node:zlib at default settings — a bare Accept-Encoding: br now returns genuine brotli
  • 1024-byte threshold, same default as the express/fastify middleware (Add express framework #1068/Add fastify framework #1069 precedent)
  • compression runs per request — no caches, no pre-compressed bodies
  • ready-made Responses/files pass through untouched, so static serving is byte-identical to before

Two Elysia warts the hook must handle (worth knowing for future TS entries)

  1. headers is absent from the hook context on the error path — an unguarded read turns every 404 into a 500.
  2. With any mapResponse registered, letting an Elysia custom-status object (status() returns, plugin 404s) fall through swallows the status into an empty 200; the hook maps code-carrying objects explicitly. Without this, GET /static/nonexistent.txt broke.

Verification

  • ./scripts/validate.sh elysia: 31 passed, 0 failed (json-comp: Content-Encoding: gzip, compressed schema, per-request negotiation; static: 20 sizes + 404 intact)
  • Bare-br check (the case validation doesn't exercise): curl -H 'Accept-Encoding: br' /json/50?m=3 decompresses with zlib.brotliDecompressSync to the full correct 50-item schema with correct totals

Thanks @nigrosimone for the report.

Repository owner deleted a comment from github-actions Bot Aug 7, 2026
@MDA2AV

MDA2AV commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f elysia --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ /benchmark --save cannot start: main has diverged and cannot be auto-merged into this branch. Please merge or rebase main manually, push, and re-run /benchmark --save.

…rn (#1075)

The entry hand-rolled Accept-Encoding negotiation in /json/:count with
Bun.gzipSync, and its br branch shipped Bun.deflateSync bytes labeled
"content-encoding: br" (latent because the branch compared the whole
header to "br", so "gzip, br" never reached it).

No compression plugin works on elysia 1.4: elysia-compress imports
mapResponse from elysia's root export, removed in 1.2.6
(vermaysha/elysia-compress#149, repo archived Nov 2025), and every fork
carries the same import. The framework's documented compression
mechanism is the mapResponse lifecycle hook, so this follows the
official docs example (elysiajs.com/essential/life-cycle) extended with
real per-request negotiation:

- picks the first acceptable encoding in the client's own order (br or
  gzip, q=0 honoured) via node:zlib at default settings — bare
  "Accept-Encoding: br" now returns genuine brotli
- 1024-byte threshold, matching the express/fastify middleware defaults
- no caches, no pre-compressed bodies; ready-made Responses and files
  pass through untouched (static serving identical to before)
- maps Elysia custom-status objects explicitly: with a mapResponse hook
  registered, letting a status() object fall through yields an empty
  200, which broke the static 404 check until handled

validate.sh elysia: 31 passed, 0 failed. Bare-br verified by
brotli-decompressing /json/50?m=3 to the full correct schema.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@MDA2AV
MDA2AV force-pushed the fix/elysia-standard-compression branch from 55aeaa9 to 694f87f Compare August 12, 2026 13:26
Repository owner deleted a comment from github-actions Bot Aug 12, 2026
@MDA2AV

MDA2AV commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

/benchmark -f elysia --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: elysia | Test: all tests

Test Conn RPS CPU Mem Δ RPS Δ Mem
baseline 512 1,425,861 6445.8% 1.7GiB -21.5% +21.4%
baseline 4096 1,439,469 6428.0% 1.6GiB -21.6% +6.7%
pipelined 512 91,526 2153.3% 1.4GiB -98.0% +41.1%
pipelined 4096 325,991 6717.9% 1.6GiB -93.5% +60.0%
limited-conn 512 1,149,028 6100.4% 1.6GiB -10.2% +45.5%
limited-conn 4096 1,273,447 6286.8% 1.6GiB -18.7% +6.7%
json 4096 904,900 6294.2% 1.7GiB +3.1% +13.3%
json-comp 512 296,545 5933.4% 1.8GiB -14.9% +5.9%
json-comp 4096 321,562 6150.6% 2.0GiB -13.7% +25.0%
json-comp 16384 329,788 6398.1% 2.5GiB -12.8% +25.0%
upload 32 1,462 4028.3% 2.7GiB -2.7% +3.8%
upload 256 1,668 6134.6% 3.3GiB +6.2% +10.0%
api-4 256 36,425 383.6% 296MiB -22.6% -7.2%
api-16 1024 124,590 1488.4% 1.3GiB +7.7% +8.3%
static 1024 86,268 6321.3% 3.3GiB -84.4% +50.0%
static 4096 85,903 6365.6% 3.4GiB -84.9% +54.5%
static 6800 86,966 6329.4% 3.5GiB -84.9% +169.2%
async-db 1024 246,796 5442.0% 4.8GiB +5.1% ~0%
Full log
[info] server ready

[run 1/3]
Running 5s test @ http://localhost:8080
  64 threads and 4096 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    49.83ms   14.58ms 135.90ms   69.77%
    Req/Sec     1.28k   158.34     6.18k    96.28%
  415966 requests in 5.10s, 24.72GB read
Requests/sec:  81548.24
Transfer/sec:      4.85GB
[info] CPU 6404.3% | Mem 3.4GiB

[run 2/3]
Running 5s test @ http://localhost:8080
  64 threads and 4096 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    48.76ms   15.24ms 157.50ms   71.04%
    Req/Sec     1.31k   308.57    11.04k    99.39%
  426113 requests in 5.11s, 25.32GB read
Requests/sec:  83367.83
Transfer/sec:      4.95GB
[info] CPU 6394.3% | Mem 3.5GiB

[run 3/3]
Running 5s test @ http://localhost:8080
  64 threads and 4096 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    47.35ms   13.57ms 131.69ms   69.85%
    Req/Sec     1.35k   229.16     9.57k    97.85%
  438124 requests in 5.10s, 26.04GB read
Requests/sec:  85903.40
Transfer/sec:      5.10GB
[info] CPU 6365.6% | Mem 3.4GiB

=== Best: 85903 req/s (CPU: 6365.6%, Mem: 3.4GiB) ===
[info] saved results/static/4096/elysia.json
httparena-bench-elysia
httparena-bench-elysia

==============================================
=== elysia / static / 6800c (tool=wrk) ===
==============================================
[info] waiting for server...
[info] server ready

[run 1/3]
Running 5s test @ http://localhost:8080
  64 threads and 6800 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    81.81ms   19.55ms 281.48ms   72.65%
    Req/Sec     1.29k   194.39     8.15k    98.40%
  418406 requests in 5.10s, 24.86GB read
Requests/sec:  82010.77
Transfer/sec:      4.87GB
[info] CPU 6395.5% | Mem 3.5GiB

[run 2/3]
Running 5s test @ http://localhost:8080
  64 threads and 6800 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    80.33ms   20.41ms 227.62ms   72.98%
    Req/Sec     1.31k   286.16    10.49k    99.39%
  426470 requests in 5.10s, 25.34GB read
Requests/sec:  83602.90
Transfer/sec:      4.97GB
[info] CPU 6412.9% | Mem 3.6GiB

[run 3/3]
Running 5s test @ http://localhost:8080
  64 threads and 6800 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    77.25ms   18.60ms 220.70ms   73.62%
    Req/Sec     1.37k   254.21    10.78k    98.99%
  443515 requests in 5.10s, 26.36GB read
Requests/sec:  86966.13
Transfer/sec:      5.17GB
[info] CPU 6329.4% | Mem 3.5GiB

=== Best: 86966 req/s (CPU: 6329.4%, Mem: 3.5GiB) ===
[info] saved results/static/6800/elysia.json
httparena-bench-elysia
httparena-bench-elysia
[info] skip: elysia does not subscribe to static-tls

==============================================
=== elysia / async-db / 1024c (tool=gcannon) ===
==============================================
[info] resetting postgres for a clean per-profile baseline
[info] starting postgres sidecar
httparena-postgres
[info] postgres ready (seeded)
[info] waiting for server...
[info] server ready

[run 1/3]
gcannon v0.5.3
  Target:    localhost:8080/
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  25
  Templates: 5
  Expected:  200
  Duration:  10s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   3.73ms   2.50ms   8.43ms   16.20ms   25.10ms

  2429482 requests in 10.00s, 2429484 responses
  Throughput: 242.90K req/s
  Bandwidth:  933.92MB/s
  Status codes: 2xx=2429484, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 2429480 / 2429484 responses (100.0%)
  Reconnects: 96926
  Per-template: 484414,485931,487312,486956,484867
  Per-template-ok: 484414,485931,487312,486956,484867
[info] CPU 5459.7% | Mem 5.4GiB

[run 2/3]
gcannon v0.5.3
  Target:    localhost:8080/
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  25
  Templates: 5
  Expected:  200
  Duration:  10s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   3.62ms   2.44ms   8.15ms   15.90ms   23.30ms

  2458247 requests in 10.00s, 2458252 responses
  Throughput: 245.77K req/s
  Bandwidth:  945.30MB/s
  Status codes: 2xx=2458252, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 2458247 / 2458252 responses (100.0%)
  Reconnects: 98374
  Per-template: 490047,491364,492865,493001,490970
  Per-template-ok: 490047,491364,492865,493001,490970
[info] CPU 5391.5% | Mem 4.9GiB

[run 3/3]
gcannon v0.5.3
  Target:    localhost:8080/
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  25
  Templates: 5
  Expected:  200
  Duration:  10s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   3.61ms   2.43ms   8.11ms   15.90ms   24.70ms

  2467965 requests in 10.00s, 2467969 responses
  Throughput: 246.74K req/s
  Bandwidth:  948.90MB/s
  Status codes: 2xx=2467969, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 2467964 / 2467969 responses (100.0%)
  Reconnects: 98740
  Per-template: 492215,493489,494596,494683,492981
  Per-template-ok: 492215,493489,494596,494683,492981
[info] CPU 5442.0% | Mem 4.8GiB

=== Best: 246796 req/s (CPU: 5442.0%, Mem: 4.8GiB) ===
[info] input BW: 16.48MB/s (avg template: 70 bytes)
[info] saved results/async-db/1024/elysia.json
httparena-bench-elysia
httparena-bench-elysia
[info] skip: elysia does not subscribe to crud
[info] skip: elysia does not subscribe to fortunes
[info] skip: elysia does not subscribe to baseline-h2
[info] skip: elysia does not subscribe to static-h2
[info] skip: elysia does not subscribe to baseline-h2c
[info] skip: elysia does not subscribe to json-h2c
[info] skip: elysia does not subscribe to baseline-h3
[info] skip: elysia does not subscribe to static-h3
[info] skip: elysia does not subscribe to gateway-64
[info] skip: elysia does not subscribe to gateway-h3
[info] skip: elysia does not subscribe to production-stack
[info] skip: elysia does not subscribe to unary-grpc
[info] skip: elysia does not subscribe to unary-grpc-tls
[info] skip: elysia does not subscribe to stream-grpc
[info] skip: elysia does not subscribe to stream-grpc-tls
[info] skip: elysia does not subscribe to echo-ws
[info] skip: elysia does not subscribe to echo-ws-pipeline
[info] skip: elysia does not subscribe to echo-ws-limited
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/results/elysia.json - 18 new, 18 total
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
[info] restoring loopback MTU to 65536

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.

elysia entry: hand-rolled compression in standard mode, and a mislabeled br branch

1 participant