Skip to content

fix: return content-type headers on non-bun runtimes#79

Merged
SaltyAom merged 25 commits into
elysiajs:mainfrom
cirex-web:fix/content-type-headers
May 10, 2026
Merged

fix: return content-type headers on non-bun runtimes#79
SaltyAom merged 25 commits into
elysiajs:mainfrom
cirex-web:fix/content-type-headers

Conversation

@cirex-web
Copy link
Copy Markdown
Contributor

@cirex-web cirex-web commented Apr 29, 2026

This PR fixes #62 (content-type headers not appearing on non-bun runtimes) by caching file Blobs (along with its content-type) instead of the Response object and using Elysia's own mapResponse handler to convert that to a Response. (this gives us the added benefit of range request headers (video files on Bun are now streamable again) and de-duplicated logic for converting files to http responses.)

While I was doing this, I also refactored the route mounting logic to consolidate file fetching and caching in one place. All routes are now added with mountRoute, which takes a urlPath and either a hardcoded file or an absolute file path. If it's the latter, every request on that url will call getFileResponse, where the file fetching, caching, and etag logic will happen.

Here's a quick demo of running the same Vite build on the current version of the plugin (left) vs. the new version (right). Note that JS fails to load on the left version because of the missing content type header. (If you're curious, the website is cmueats.com, which uses Elysia for its backend - I'm considering hosting the frontend on Elysia as well. It's currently on Vercel.)

Screen.Recording.2026-04-29.at.10.30.28.PM.mp4

This PR also fixes several other bugs

  • Previously, if alwaysStatic was true and the number of files exceeded staticLimit, no routes were added at all. Now, at most staticLimit routes will be added and the rest will be caught in the wildcard route.
  • Previously, if the response was cached by the LRUCache, further requests will have their e-tag or cache-related headers completely disregarded.
  • Setting indexHTML on Node previously had no effect.
  • In the alwaysStatic if clause, shouldIgnore(absolutePath) is checked, rather than the relative path.
  • Prevents file traversal attacks (eg. http://localhost:3001/..%2Fsrc/index.ts). (merged in from fix: resolve wildcard path against assetsDir before read #66)

Testing

The test suite now runs on both Bun (through bun test) and Node (through vitest). I added tests to cover the above bugs and combinations of alwaysStatic, bunFullstack, and indexHTML. With regards to staticLimit, html files will be mounted regardless of the limit iff bunFullstack is true. (this matches current implementation)

I also tested my changes on Windows, and everything seems fine.

The existing implementation fails 88 tests on Bun and 96 tests on Node, although these are a bit inflated due to the heavy use of parameterization on my new tests, lol.

Performance

Despite caching Blobs instead of Responses, there doesn't seem to be any performance degregation according to the newbench/index.ts script I wrote.

Before:
┌───┬───────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│   │ Task name     │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├───┼───────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0 │ route caching │ 124230 ± 0.46%   │ 112708 ± 4042.0  │ 8687 ± 0.12%           │ 8872 ± 323             │ 80496   │
└───┴───────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

After:
┌───┬───────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│   │ Task name     │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├───┼───────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0 │ route caching │ 101398 ± 0.55%   │ 94708 ± 3542.0   │ 10387 ± 0.08%          │ 10559 ± 398            │ 98622   │
└───┴───────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

Misc

  • I added a bun dev:node command for starting the example server with node instead of bun.
  • I also added a github workflow for running bun run test on every commit

This PR makes #65 redundant

Summary by CodeRabbit

  • New Features

    • Static assets now include ETag headers for efficient caching
    • HTTP 304 Not Modified responses reduce bandwidth on repeat requests
    • Enhanced multi-runtime compatibility with improved Node.js adapter support
  • Tests

    • Expanded coverage for static asset serving, caching behavior, and conditional requests

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR refactors the static file-serving plugin to fix missing Content-Type headers on Node/Deno runtimes by switching from raw fs.readFile() to Elysia's file() API, and introduces LRU-cached responses with ETag and conditional 304 support. It adds a comprehensive test suite, benchmarks, Node adapter support, and GitHub Actions CI.

Changes

Static File Serving Refactor with Caching & ETag Support

Layer / File(s) Summary
Type & Interface Foundations
src/types.ts, src/index.ts
Adds internal CachedFile interface storing Blob payload, file stats, and optional ETag; documents alwaysStatic behavior under bunFullstack.
Utility Layers
src/utils.ts
Refactors getFile() to always return Elysia's file() (not fs.readFile or Bun.file separately), enabling proper Content-Type propagation across runtimes; adds getFileStats() and alreadyCachedDownstream() for stat-based ETag validation; updates generateETag() to accept ElysiaFile and hash via Bun or fs.readFile; removes listHTMLFiles() helper.
Core Route & Response Logic
src/index.ts
Introduces LRUCache<string, CachedFile> for in-memory response caching; refactors route mounting into filesystem-driven iteration with staticLimit enforcement, HTML prioritization, and conditional wildcard route registration; implements getFileResponse() as centralized response builder with traversal protection, cached-response serving, ETag computation, and conditional 304 handling via alreadyCachedDownstream(); adds mountRoute() and getURLPath() helpers.
Example & Integration
example/index.ts, package.json
Wraps example app in async IIFE to avoid top-level await; conditionally applies Node adapter when not on Bun; adds dev scripts (dev:node, bench), adds @elysiajs/node, tinybench, tsx, vitest to devDependencies; updates test script to run bun run build && bun test && vitest run && npm run test:node.
Benchmarks
bench/index.ts, bench/index.d.ts
Creates tinybench harness that benchmarks route caching across four index.html URL variants with a 10-second time budget; logs results via console.table().
Testing
test/index.test.ts, test/utils.ts, test/utils.d.ts
Migrates from Bun's bun:test to Vitest; adds req() ambient type export; updates takodachi test utility to fall back to fs.readFile when Bun APIs unavailable; adds 400+ lines covering index.html routing variants, cache-hit/ETag/304 behavior, custom headers preservation, etag: false suppression, staticLimit monotonicity, Range requests (206), path traversal attacks, and PNG content-type validation.
Configuration & CI
.gitignore, tsconfig.json, tsconfig.dts.json, .github/workflows/test.yml, public/html/index.html
Adds cmueats-build to .gitignore; excludes bench from TypeScript .d.ts generation; adds GitHub Actions workflow for automated test runs on push/PR; normalizes HTML doctype to lowercase.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Server as Elysia Server<br/>(staticPlugin)
    participant FileCache as LRUCache<br/>(CachedFile)
    participant FileSystem as Filesystem
    participant ETag as ETag<br/>Generator

    Client->>Server: GET /public/style.css
    Server->>FileCache: Check if cached?
    alt Cache Hit
        FileCache-->>Server: CachedFile{Blob, Stats, etag}
        Note over Server: Extract etag from cache
        Server->>Server: Check If-None-Match header
        alt ETag Match (via alreadyCachedDownstream)
            Server-->>Client: 304 Not Modified
        else ETag Mismatch
            Server-->>Client: 200 + Content-Type + etag + cache-control
        end
    else Cache Miss
        FileCache-->>Server: null
        Server->>FileSystem: getFile(path)
        FileSystem-->>Server: ElysiaFile{path, value}
        Server->>FileSystem: getFileStats(path)
        FileSystem-->>Server: Stats{mtime, ...}
        Server->>ETag: generateETag(ElysiaFile)
        ETag-->>Server: etag string
        Server->>FileCache: Store CachedFile{Blob, Stats, etag}
        Server->>Server: Build response headers<br/>(Content-Type, etag, cache-control)
        Server-->>Client: 200 + headers + body
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Oh, so you thought static files just magically got their Content-Type headers~? ♡ How naïve~ 💔
Now they're cached, ETagged, and returning proper 304s across all runtimes...
Don't mess this up, dummy~ ☆

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately captures the main fix: returning content-type headers on non-Bun runtimes by using Elysia's file() instead of raw fs.readFile().
Linked Issues check ✅ Passed PR fully addresses issue #62 by caching file Blobs with MIME metadata via Elysia's file() and applying mapResponse, restoring Content-Type headers across all runtimes.
Out of Scope Changes check ✅ Passed All changes align with the scope: core fix (file-to-Blob caching), refactored routing via mountRoute, bug fixes (alwaysStatic/staticLimit handling, path traversal), expanded tests, and CI/build improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add coverage for cached static file responses to ensure ETag,
Cache-Control, 304 revalidation, custom headers, content-type,
and index.html behavior remain consistent after an ElysiaFile is
served from the LRU cache.

Update the cached response path so cached files run through the
same header and conditional request logic as uncached files.
@yuriiShmal
Copy link
Copy Markdown
Contributor

Note: both in the base and the edited version with more tests, it fails the always static with assets on an absolute path test for me:

137 | 
138 |     it('always static with assets on an absolute path', async () => {
139 |         const app = new Elysia().use(
140 |             staticPlugin({
141 |                 alwaysStatic: true,
142 |                 assets: join(expect.getState().testPath!, '../../public')
                                          ^
TypeError: expect.getState is not a function. (In 'expect.getState()', 'expect.getState' is undefined)

@cirex-web
Copy link
Copy Markdown
Contributor Author

you might need to run bun i (also testing should be bun run test not bun test)

@yuriiShmal yuriiShmal force-pushed the fix/content-type-headers branch from 17ef23f to b41bed8 Compare April 29, 2026 21:12
@yuriiShmal
Copy link
Copy Markdown
Contributor

yuriiShmal commented Apr 29, 2026

you might need to run bun i (also testing should be bun run test not bun test)

Yep, but the issue now is twofold.

  • There is a merge conflict.
  • MODULE_NOT_FOUND (even after running bun i). Currently the output looks like this:
Test Output
$ bun run test
$ bun run --bun vitest run && vitest run && bun run test:node

 RUN  v4.1.5 /home/yurii/Documents/Classes/17313/eric/elysia-static

 ✓ test/index.test.ts (37 tests) 101ms
   ✓ Static Plugin (37)
     ✓ should get root path 12ms
     ✓ should get nested path 3ms
     ✓ should get different path 2ms
     ✓ should handle prefix 2ms
     ✓ should handle empty prefix 3ms
     ✓ should supports multiple public 3ms
     ✓ ignore string pattern 2ms
     ✓ ignore regex pattern 2ms
     ✓ always static 4ms
     ✓ always static with assets on an absolute path 3ms
     ✓ exclude extension 4ms
     ✓ return custom headers 3ms
     ✓ call onError when using dynamic mode 3ms
     ✓ return etag header 3ms
     ✓ return no etag header when etag is false 2ms
     ✓ return Cache-Control header when maxAge is set 3ms
     ✓ return Cache-Control header when maxAge is not set 2ms
     ✓ skip Cache-Control header when maxAge is null 2ms
     ✓ set cache directive 2ms
     ✓ return not modified response (etag) 2ms
     ✓ return not modified response (time) 4ms
     ✓ return ok response when etag is false 2ms
     ✓ should 404 when navigate to folder 2ms
     ✓ serve index.html to default / 2ms
     ✓ does not serve index.html to default / when not indexHTML 2ms
     ✓ serves index.html to default / when alwaysStatic 2ms
     ✓ does not serve index.html to default / when alwaysStatic and not indexHTML 2ms
     ✓ accept detail 1ms
     ✓ should return necessary content-type headers 2ms
     ✓ preserves content-type on cached file responses 2ms
     ✓ preserves custom headers on cached file responses 2ms
     ✓ preserves etag and cache-control headers on cached file responses 2ms
     ✓ returns 304 for if-none-match after the file has been cached 2ms
     ✓ does not return 304 when cache-control no-cache is sent after file is cached 3ms
     ✓ returns 304 for if-none-match after cached alwaysStatic route response 2ms
     ✓ serves index.html from cache with content-type and cache headers 2ms
     ✓ returns 304 for cached index.html default route 2ms

 Test Files  1 passed (1)
      Tests  37 passed (37)
   Start at  17:14:05
   Duration  262ms (transform 50ms, setup 0ms, import 90ms, tests 101ms, environment 0ms)


 RUN  v4.1.5 /home/yurii/Documents/Classes/17313/eric/elysia-static

 ✓ test/index.test.ts (37 tests) 92ms
   ✓ Static Plugin (37)
     ✓ should get root path 29ms
     ✓ should get nested path 3ms
     ✓ should get different path 4ms
     ✓ should handle prefix 3ms
     ✓ should handle empty prefix 3ms
     ✓ should supports multiple public 2ms
     ✓ ignore string pattern 1ms
     ✓ ignore regex pattern 1ms
     ✓ always static 5ms
     ✓ always static with assets on an absolute path 5ms
     ✓ exclude extension 4ms
     ✓ return custom headers 2ms
     ✓ call onError when using dynamic mode 1ms
     ✓ return etag header 1ms
     ✓ return no etag header when etag is false 3ms
     ✓ return Cache-Control header when maxAge is set 1ms
     ✓ return Cache-Control header when maxAge is not set 1ms
     ✓ skip Cache-Control header when maxAge is null 1ms
     ✓ set cache directive 1ms
     ✓ return not modified response (etag) 1ms
     ✓ return not modified response (time) 1ms
     ✓ return ok response when etag is false 1ms
     ✓ should 404 when navigate to folder 1ms
     ✓ serve index.html to default / 1ms
     ✓ does not serve index.html to default / when not indexHTML 1ms
     ✓ serves index.html to default / when alwaysStatic 1ms
     ✓ does not serve index.html to default / when alwaysStatic and not indexHTML 1ms
     ✓ accept detail 0ms
     ✓ should return necessary content-type headers 1ms
     ✓ preserves content-type on cached file responses 1ms
     ✓ preserves custom headers on cached file responses 1ms
     ✓ preserves etag and cache-control headers on cached file responses 1ms
     ✓ returns 304 for if-none-match after the file has been cached 1ms
     ✓ does not return 304 when cache-control no-cache is sent after file is cached 3ms
     ✓ returns 304 for if-none-match after cached alwaysStatic route response 1ms
     ✓ serves index.html from cache with content-type and cache headers 1ms
     ✓ returns 304 for cached index.html default route 1ms

 Test Files  1 passed (1)
      Tests  37 passed (37)
   Start at  17:14:06
   Duration  347ms (transform 54ms, setup 0ms, import 165ms, tests 92ms, environment 0ms)

$ npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js

up to date, audited 3 packages in 481ms

found 0 vulnerabilities

up to date, audited 3 packages in 455ms

found 0 vulnerabilities
node:internal/modules/cjs/loader:645
      throw e;
      ^

Error: Cannot find module '/home/yurii/Documents/Classes/17313/eric/elysia-static/test/node/cjs/node_modules/@elysiajs/static/dist/index.js'
    at createEsmNotFoundErr (node:internal/modules/cjs/loader:1244:15)
    at finalizeEsmResolution (node:internal/modules/cjs/loader:1232:15)
    at resolveExports (node:internal/modules/cjs/loader:638:14)
    at Module._findPath (node:internal/modules/cjs/loader:711:31)
    at Module._resolveFilename (node:internal/modules/cjs/loader:1193:27)
    at Module._load (node:internal/modules/cjs/loader:1038:27)
    at Module.require (node:internal/modules/cjs/loader:1289:19)
    at require (node:internal/modules/helpers:182:18)
    at Object.<anonymous> (/home/yurii/Documents/Classes/17313/eric/elysia-static/test/node/cjs/index.js:5:26)
    at Module._compile (node:internal/modules/cjs/loader:1521:14) {
  code: 'MODULE_NOT_FOUND',
  path: '/home/yurii/Documents/Classes/17313/eric/elysia-static/test/node/cjs/node_modules/@elysiajs/static/package.json'
}

Node.js v20.19.5
error: script "test:node" exited with code 1
error: script "test" exited with code 1

Edit: Issues resolved!

cirex-web and others added 4 commits April 29, 2026 17:26
Replace the unresolved elysia/cookies subpath import with the
main Elysia Context type for the handler set object.
@yuriiShmal
Copy link
Copy Markdown
Contributor

Note: there was an issue with bun run build due to the cookie. Current testing requires that bun run build be run before bun run test is run.

yuriiShmal and others added 2 commits April 29, 2026 17:59
Ensure dist artifacts are generated before the test suite runs,
since the Node CJS/ESM tests import the package through its
compiled exports.
@cirex-web
Copy link
Copy Markdown
Contributor Author

due to the cookie.

?

@cirex-web
Copy link
Copy Markdown
Contributor Author

cirex-web commented Apr 29, 2026

@JoshuaNam do u want to add a CI check for bun run test that gets run on every commit to any PR or main?

@yuriiShmal
Copy link
Copy Markdown
Contributor

due to the cookie.

?

`bun run build` output before changes
yurii@yurii-ThinkPad-T14-Gen-5:~/Documents/Classes/17313/eric/elysia-static$ bun run build
$ bun build.ts
CLI Building entry: src/types.ts, src/index.ts, src/utils.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.5.1
CLI Target: node20
CLI Cleaning output folder
ESM Build start
CJS Build start
CJS dist/utils.js 4.98 KB
CJS dist/index.js 7.15 KB
CJS dist/types.js 721.00 B
CJS ⚡️ Build success in 28ms
ESM dist/types.mjs 0 B
ESM dist/index.mjs 5.33 KB
ESM dist/utils.mjs 3.71 KB
ESM ⚡️ Build success in 28ms
src/index.ts(21,30): error TS2307: Cannot find module 'elysia/cookies' or its corresponding type declarations.
  There are types at '/home/yurii/Documents/Classes/17313/eric/elysia-static/node_modules/elysia/dist/cookies.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.
ShellError: Failed with exit code 2
 exitCode: 2,
   stdout: "src/index.ts(21,30): error TS2307: Cannot find module 'elysia/cookies' or its corresponding type declarations.\n  There are types at '/home/yurii/Documents/Classes/17313/eric/elysia-static/node_modules/elysia/dist/cookies.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.\n",
   stderr: "",

      at ShellPromise (unknown:75:16)
      at BunShell (unknown:191:35)

Bun v1.3.12 (Linux x64)
error: script "build" exited with code 1

@cirex-web cirex-web marked this pull request as ready for review April 30, 2026 02:34
@cirex-web cirex-web marked this pull request as draft April 30, 2026 02:34
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 60: The package.json contains a "dev:node" npm script that invokes "tsx"
but "tsx" is not declared in devDependencies; either add "tsx" to
devDependencies (so the script can run in CI and fresh checkouts) or update the
"dev:node" script to use "bunx tsx" if you prefer Bun. Locate the "dev:node"
script in package.json and either add a devDependency entry for "tsx" with an
appropriate version, or change the script value from "tsx watch
example/index.ts" to "bunx tsx watch example/index.ts".

In `@src/index.ts`:
- Around line 92-113: finalizeFileResponse always recomputes the ETag by calling
generateETag(file), causing fs.readFile and hashing on every cache hit; modify
finalizeFileResponse to first check for a cached etag on the ElysiaFile instance
(e.g., file.etag) and only call generateETag(file) if that property is missing,
and when you compute an etag store it on file.etag so subsequent requests reuse
it; apply the same pattern to the other call site referenced (lines ~117-119) so
both paths avoid repeated disk reads and hashing.
- Around line 173-182: The current guard "if (files.length <= staticLimit) { ...
}" causes the static-file mounting loop in src/index.ts to be skipped entirely
when a large asset directory exceeds staticLimit, which prevents adding the
catch-all static route when alwaysStatic is true and skips bun HTML pre-bundling
(shouldBundleFileWithBun) for isBun && bunFullstack; update the logic so that
large directories still register the necessary catch-all/static route when
alwaysStatic is true and still handle pre-bundling of .html files when isBun &&
bunFullstack (e.g., remove or narrow the files.length <= staticLimit outer guard
and instead apply staticLimit per-file where appropriate, or add a separate code
path that ensures the catch-all route and .html pre-bundling run regardless of
files.length), referencing the loop variables files, staticLimit, alwaysStatic,
isBun, bunFullstack, and shouldBundleFileWithBun.
- Around line 197-203: The current loop returns new Elysia() when
fileExists(absoluteFilePath) is false, which discards all previously registered
routes; instead, change that return into a skip so the plugin continues
processing remaining files: when fileExists(absoluteFilePath) is false, log the
warning (if !silent) and continue the loop rather than returning a new Elysia()
(so remove/replace the return new Elysia() in the same block), ensuring the rest
of listFiles() processing and previously registered routes remain intact.
- Around line 252-260: The catch-all uses params['*'] joined to assets allowing
../ path traversal; fix getFileResponse invocation by resolving and validating
the target path before calling normalizePath: decode the param (fastDecodeURI or
raw), construct a candidate via path.resolve(assets, decoded), then ensure the
resolved path is inside the assets root (e.g., check that resolvedPath ===
assetsResolved or resolvedPath.startsWith(assetsResolved + path.sep) or use
path.relative to ensure it doesn't start with '..'); if validation fails, return
a 400/403 or a safe error instead of reading the file. Use the symbols assets,
params['*'], fastDecodeURI, path.resolve/path.relative, normalizePath and
getFileResponse in the changes.

In `@src/utils.ts`:
- Around line 204-217: generateETag currently rereads and hashes the file on
every call (in generateETag), negating file caching; fix by introducing an etag
cache (reuse existing LRUCache or a Map) keyed by
`${file.path}:${stat.mtimeMs}:${stat.size}` and return cached value when
present, otherwise compute the etag once (using file.value when available or
reading the file only on first compute), store it in the cache, and return it;
ensure cache key uses the file's mtimeMs and size so the entry auto-invalidates
when the file changes and update references to Crypto/Bun hashing logic in
generateETag to consult and populate this cache.

In `@test/index.test.ts`:
- Around line 536-554: The test "returns 304 for if-none-match after the file
has been cached" currently only checks status and body, which can miss
regressions where cache validators aren't preserved; update the assertion block
after computing second to also assert that second.headers.get('etag') strictly
equals the previously saved etag and that second.headers.get('cache-control')
(or the expected cache header name) is present and matches the expected value,
keeping the existing status/body checks; apply the same hardening to the other
similar tests (the ones asserting 304 at the later ranges) so each 304 response
verifies preserved ETag and Cache-Control headers using the same etag variable
and header checks referenced in this test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61e970ea-b3b2-4282-b81c-ff84368b5505

📥 Commits

Reviewing files that changed from the base of the PR and between 62aea0c and c60864b.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • .gitignore
  • example/index.ts
  • package.json
  • public/js/index.js
  • src/index.ts
  • src/types.ts
  • src/utils.ts
  • test/index.test.ts
  • test/utils.ts

Comment thread package.json Outdated
Comment thread src/index.ts Outdated
Comment thread src/index.ts Outdated
Comment thread src/index.ts Outdated
Comment thread src/index.ts Outdated
Comment thread src/utils.ts
Comment thread test/index.test.ts
@cirex-web cirex-web marked this pull request as ready for review May 7, 2026 22:38
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
bench/index.ts (2)

28-35: 💤 Low value

Leftover commented-out debug lines everywhere — did you just forget to clean up? How embarrassing~ (≖‿≖)

Lines 28, 30, and 35 all have commented-out console.log calls. They add noise and signal that this file is still in a "debugging" state rather than a committed benchmark harness♡

✨ Proposed fix
     for (const path of htmlPaths) {
-        // console.log(path)
         const res = await app.handle(req(path))
-        // await (await res.blob()).text()
+        await res.blob()
     }
 
 await bench.run()
-// console.log(bench.name)
 console.table(bench.table())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/index.ts` around lines 28 - 35, This file contains leftover
commented-out debug statements (commented console.log calls) around the
benchmark run—remove the noisy comments to clean up the committed code: delete
the commented lines near the call to app.handle(req) (the commented
"console.log(path)"), the commented await of res.blob().text(), and the trailing
commented "console.log(bench.name)" so bench.run(), app.handle(req), req, and
res remain as the only active code paths and the benchmark harness is tidy.

18-18: 💤 Low value

Debug console.log left in production benchmark output, how sloppy~ ( ̄ω ̄;)

console.log(app.routes) at line 18 dumps the full route table every time the benchmark runs, polluting the output. Not a great look for a harness that uses console.table for clean results~♡

✨ Proposed fix
-console.log(app.routes)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/index.ts` at line 18, Remove the stray debug print by deleting or
gating the console.log(app.routes) call so the route table is not printed during
normal benchmark runs; either remove the line entirely or wrap it in a
debug-only guard (e.g., check process.env.DEBUG or a --debug flag) before
calling console.log(app.routes) so production runs and console.table output
remain clean.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/index.ts`:
- Around line 29-31: The benchmark obtains a Response from app.handle(req(path))
but never consumes the response body (res is unused), so the benchmark skips
I/O/serialization cost; fix by draining the response inside the loop — e.g.,
await the body promise returned by res.blob() or res.text() (uncomment and await
the existing line), or use await res.arrayBuffer() for binary work — so the
route caching benchmark measures a full round-trip; update the code around the
app.handle(req(path)) call and the res variable to ensure the body is awaited
and any errors are propagated.
- Line 20: The benchmark callback currently awaits app.modules on every
iteration (await app.modules), which skews timing; move the await out of the
per-iteration callback by resolving app.modules once before registering/running
the benchmark (e.g., const modules = await app.modules) and then use the
resolved modules variable inside the benchmark function instead of awaiting
app.modules each iteration; update references in the benchmark callback to use
the resolved value so only the actual work is measured.

In `@bun-build/index.css`:
- Line 75: The font-family declaration in the `@font-face` block currently uses
quoted "Inter", which violates the stylelint font-family-name-quotes rule; fix
it by removing the quotes so the declaration reads font-family: Inter; (locate
the `@font-face` rule containing the font-family property and replace the quoted
"Inter" token with the unquoted Inter identifier).

In `@test/utils.d.ts`:
- Line 2: Replace the non-portable type by changing the exported declaration for
takodachi: currently "export declare const takodachi: string | NonSharedBuffer;"
— remove NonSharedBuffer and use a portable binary type such as Uint8Array so it
reads "string | Uint8Array"; update any related type usages of takodachi to
accept Uint8Array where applicable (references: the takodachi export in the
declaration file).

---

Nitpick comments:
In `@bench/index.ts`:
- Around line 28-35: This file contains leftover commented-out debug statements
(commented console.log calls) around the benchmark run—remove the noisy comments
to clean up the committed code: delete the commented lines near the call to
app.handle(req) (the commented "console.log(path)"), the commented await of
res.blob().text(), and the trailing commented "console.log(bench.name)" so
bench.run(), app.handle(req), req, and res remain as the only active code paths
and the benchmark harness is tidy.
- Line 18: Remove the stray debug print by deleting or gating the
console.log(app.routes) call so the route table is not printed during normal
benchmark runs; either remove the line entirely or wrap it in a debug-only guard
(e.g., check process.env.DEBUG or a --debug flag) before calling
console.log(app.routes) so production runs and console.table output remain
clean.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9210c505-e44b-47c3-8c5b-6cd62a65114d

📥 Commits

Reviewing files that changed from the base of the PR and between c60864b and a564720.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .gitignore
  • bench/index.d.ts
  • bench/index.ts
  • bun-build/index.css
  • bun-build/index.html
  • bun-build/src/client/frontend.d.ts
  • bun-build/src/client/frontend.tsx
  • example/index.ts
  • package.json
  • public/html/index.html
  • src/index.ts
  • src/utils.ts
  • test/index.test.ts
  • test/utils.d.ts
  • tsconfig.dts.json
  • tsconfig.json
✅ Files skipped from review due to trivial changes (5)
  • bench/index.d.ts
  • public/html/index.html
  • tsconfig.dts.json
  • .gitignore
  • bun-build/src/client/frontend.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • example/index.ts
  • package.json
  • src/index.ts
  • test/index.test.ts

Comment thread bench/index.ts Outdated
Comment thread bench/index.ts
Comment thread bun-build/index.css Outdated
Comment thread test/utils.d.ts Outdated
Co-authored-by: tiago <70700766+tiagozip@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/index.test.ts (1)

650-668: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

304 assertions are still too soft, you missed the sneaky part~ (¬‿¬)♡

These cases only assert 304 + empty body. They should also assert preserved etag and cache-control on the 304 response, otherwise header regressions can slip through.

Suggested hardening pattern (apply to all 3 tests)
         const second = await app.handle(request)

         expect(second.status).toBe(304)
         expect(second.body).toBe(null)
+        expect(second.headers.get('etag')).toBe(etag)
+        expect(second.headers.get('cache-control')).toBeTruthy()

Also applies to: 693-716, 740-758

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.test.ts` around lines 650 - 668, The 304 tests only check status
and empty body but must also assert response headers are preserved; update the
test "returns 304 for if-none-match after the file has been cached" (and the
similar tests around the other occurrences) to additionally assert that
second.headers.get('etag') equals the original etag and that
second.headers.get('cache-control') matches the cache-control from the first
response (or the expected value from staticPlugin()), ensuring the ETag and
Cache-Control headers are present and unchanged on the 304 response; locate the
assertions around variables first, etag, request, and second to add these header
checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/index.test.ts`:
- Around line 151-156: The test "always static with assets on an absolute path"
passes a relative string ('public/') to staticPlugin.assets, so it doesn't
exercise the absolute-path bug; update the test to supply a real absolute path
(e.g., use Node's path.resolve or path.join with __dirname or process.cwd())
when calling staticPlugin({ alwaysStatic: true, assets: ... }) and add the
necessary path import/require in the test file so the assets argument is
genuinely absolute.

---

Duplicate comments:
In `@test/index.test.ts`:
- Around line 650-668: The 304 tests only check status and empty body but must
also assert response headers are preserved; update the test "returns 304 for
if-none-match after the file has been cached" (and the similar tests around the
other occurrences) to additionally assert that second.headers.get('etag') equals
the original etag and that second.headers.get('cache-control') matches the
cache-control from the first response (or the expected value from
staticPlugin()), ensuring the ETag and Cache-Control headers are present and
unchanged on the 304 response; locate the assertions around variables first,
etag, request, and second to add these header checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af6d11cc-767f-4f5d-8f33-3bea199d8c5b

📥 Commits

Reviewing files that changed from the base of the PR and between a564720 and ebae90f.

📒 Files selected for processing (4)
  • bench/index.ts
  • example/index.ts
  • src/index.ts
  • test/index.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • bench/index.ts
  • example/index.ts
  • src/index.ts

Comment thread test/index.test.ts
@cirex-web cirex-web changed the title fix: return ElysiaFile instead of Response to get content-type headers fix: return content-type headers on non-bun runtimes May 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
example/index.ts (1)

5-21: ⚡ Quick win

Add explicit startup error handling, baka~ ( ̄▽ ̄)♡

If staticPlugin(...), .listen(...), or module init rejects, this path can fail without a controlled exit/log flow. Add a .catch on the IIFE so startup failures are deterministic.

Suggested patch
-;(async () => {
+;(async () => {
     const app = new Elysia(isBun ? {} : { adapter: node() })
         .use(
             await staticPlugin({
                 prefix: 'hi',
                 assets: 'public',
                 alwaysStatic: true,
                 bunFullstack: true,
                 decodeURI: false,
                 etag: false
                 // staticLimit: 1
             })
         )
         .listen(3005)
     await app.modules
     console.log(app.routes)
-})() // no top-level awaits allowed for cjs (error triggered by `bun dev:node`) (idk how to fix this)
+})().catch((error) => {
+    console.error('Failed to start example server:', error)
+    process.exitCode = 1
+})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/index.ts` around lines 5 - 21, The top-level async IIFE that
constructs Elysia, calls staticPlugin(...), .listen(3005) and awaits app.modules
can reject without handling; update the invocation of the IIFE (the anonymous
async function wrapping Elysia/staticPlugin/listen/await app.modules) to attach
a .catch handler that logs the error (including the error object) and exits the
process (or otherwise returns a non-zero status) so startup failures from
staticPlugin, .listen, or app.modules are reported and cause a deterministic
shutdown.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@example/index.ts`:
- Around line 5-21: The top-level async IIFE that constructs Elysia, calls
staticPlugin(...), .listen(3005) and awaits app.modules can reject without
handling; update the invocation of the IIFE (the anonymous async function
wrapping Elysia/staticPlugin/listen/await app.modules) to attach a .catch
handler that logs the error (including the error object) and exits the process
(or otherwise returns a non-zero status) so startup failures from staticPlugin,
.listen, or app.modules are reported and cause a deterministic shutdown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a08ea39-53da-4042-a1f8-86bb987cd557

📥 Commits

Reviewing files that changed from the base of the PR and between ebae90f and 3e543f8.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • public/kyuukurarin.mp4 is excluded by !**/*.mp4
📒 Files selected for processing (4)
  • example/index.ts
  • package.json
  • src/index.ts
  • test/index.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • package.json
  • src/index.ts
  • test/index.test.ts

@cirex-web
Copy link
Copy Markdown
Contributor Author

cirex-web commented May 10, 2026

Video streaming is still a bit jank on Node (waits for the entire file to load locally before playing from the start) and sometimes crashes the entire server if you reload the page enough times... (Without content-type headers, as it was previously, the video would just trigger a browser download, so I think this is a marginal improvement.) I think the issue is probably upstream since the only thing I'm returning is an entire blob chunk. I also tried alternative ways of returning the video as follows:

video streaming on Bun
____

file('test/kyuukurarin.mp4') [buffers entire video before playing]
() => file('test/kyuukurarin.mp4') [OK]
new Response(file('test/kyuukurarin.mp4').value) [starts streaming the thing, cannot scroll forwards or backwards]



on Node
____
file('test/kyuukurarin.mp4') [doesn't work]
() => file('test/kyuukurarin.mp4') [buffers entire video before playing]
new Response(file('test/kyuukurarin.mp4').value) [browser downloads the video onto local disk (okay if you put content-type mp4 it starts streaming it, cannot jump to specific part.)]

@cirex-web
Copy link
Copy Markdown
Contributor Author

@SaltyAom ok I think I'm done here lol, I think everything works just as it should. happy to make changes!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils.ts (1)

168-177: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the millisecond vs second precision mismatch in your If-Modified-Since comparison~♡

You're comparing fileStats.mtime.getTime() (milliseconds) against Date.parse(ifModifiedSince) (also milliseconds in JS, but HTTP-dates per RFC 7231 have only second precision). When a client echoes back a Last-Modified value like 1700000000000, and your file's mtime is 1700000000123, the check 123 <= 0 fails—so you keep sending the full file every. single. time. instead of that tasty little 304 Not Modified~♡

Confirmed: the plugin doesn't currently emit Last-Modified headers, so this bug rarely bites in practice. But the code path exists, it's tested, and it's still reachable via manually-crafted requests—plus future changes could add Last-Modified support. Just floor both sides to whole seconds like the fresh package does~♡

♻️ Suggested fix
     if (headers['if-modified-since']) {
-        const ifModifiedSince = headers['if-modified-since']
+        const ifModifiedSince = Date.parse(headers['if-modified-since'])
+        if (Number.isNaN(ifModifiedSince)) return false
 
+        // HTTP-date has 1-second resolution; truncate mtime to match
+        // so a round-tripped Last-Modified value compares equal.
+        const mtime = fileStats.mtime?.getTime()
+        if (mtime === undefined) return false
+        return Math.floor(mtime / 1000) <= Math.floor(ifModifiedSince / 1000)
-        try {
-            return (
-                fileStats.mtime !== undefined &&
-                fileStats.mtime.getTime() <= Date.parse(ifModifiedSince)
-            )
-        } catch {}
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils.ts` around lines 168 - 177, The If-Modified-Since check compares
millisecond-precision mtime (fileStats.mtime.getTime()) to the parsed header but
HTTP dates are second-precision; convert both sides to whole seconds before
comparing (e.g., divide or floor both fileStats.mtime.getTime() and
Date.parse(ifModifiedSince) by 1000) inside the existing try block that guards
headers['if-modified-since'], so replace the direct millisecond comparison in
that block with a seconds-based comparison using the same identifiers
(headers['if-modified-since'], ifModifiedSince, fileStats.mtime.getTime(),
Date.parse(ifModifiedSince)).
🧹 Nitpick comments (2)
.github/workflows/test.yml (2)

21-22: ⚡ Quick win

Ehhh~ No dependency caching? Your CI runs must be sooo slow~ (๑•́ ₃ •̀๑)

Every single CI run is gonna re-download all those packages from scratch~ How inefficient! ♡ Adding a simple cache step would speed things up significantly, but I guess you like waiting around~ (︶︹︺)

⚡ Proposed enhancement to cache dependencies

Add this step before the install step:

            - name: Cache dependencies
              uses: actions/cache@v3
              with:
                  path: ~/.bun/install/cache
                  key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}
                  restore-keys: |
                      ${{ runner.os }}-bun-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 21 - 22, Add a dependency caching
step before the "Install packages" step to avoid re-downloading packages on
every run: create a step named "Cache dependencies" that uses actions/cache@v3,
targets the Bun cache directory (e.g., ~/.bun/install/cache) and uses a key like
`${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}` with a restore-keys
fallback `${{ runner.os }}-bun-`, placing this step immediately before the step
that runs `bun install` (the "Install packages" step).

24-25: ⚡ Quick win

No test artifacts or coverage upload? That's gonna make debugging failures super fun~ (≖ ͜ʖ≖)

When tests fail in CI, you won't have any test reports or coverage data to look at~ You'll just have to stare at the console logs and guess what went wrong~ How thrilling! ♡ But maybeee you could save yourself some headaches by uploading test results and coverage~ Just a thought from someone smarter than you~ ( ˘ ³˘)♡

📊 Proposed enhancement to upload test artifacts

Add this step after the test step:

            - name: Upload test results
              if: always()
              uses: actions/upload-artifact@v3
              with:
                  name: test-results
                  path: |
                      coverage/
                      test-results/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 24 - 25, Add a new GitHub Actions
step immediately after the existing "Test" step (the step that runs "bun run
test") to always upload test artifacts; use the actions/upload-artifact@v3
action with a descriptive artifact name like "test-results" and include paths
such as coverage/ and test-results/ (so coverage and junit/other test reports
are preserved) and guard it with if: always() so artifacts upload even on
failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/test.yml:
- Around line 16-19: The workflow uses oven-sh/setup-bun with bun-version set to
"latest", which makes CI non-deterministic; update the action input for
bun-version in the GitHub Actions job (the uses: oven-sh/setup-bun@v1 block) to
pin a specific, stable Bun release (e.g., "v1.3.13") instead of "latest" so
builds are reproducible.

---

Outside diff comments:
In `@src/utils.ts`:
- Around line 168-177: The If-Modified-Since check compares
millisecond-precision mtime (fileStats.mtime.getTime()) to the parsed header but
HTTP dates are second-precision; convert both sides to whole seconds before
comparing (e.g., divide or floor both fileStats.mtime.getTime() and
Date.parse(ifModifiedSince) by 1000) inside the existing try block that guards
headers['if-modified-since'], so replace the direct millisecond comparison in
that block with a seconds-based comparison using the same identifiers
(headers['if-modified-since'], ifModifiedSince, fileStats.mtime.getTime(),
Date.parse(ifModifiedSince)).

---

Nitpick comments:
In @.github/workflows/test.yml:
- Around line 21-22: Add a dependency caching step before the "Install packages"
step to avoid re-downloading packages on every run: create a step named "Cache
dependencies" that uses actions/cache@v3, targets the Bun cache directory (e.g.,
~/.bun/install/cache) and uses a key like `${{ runner.os }}-bun-${{
hashFiles('bun.lockb') }}` with a restore-keys fallback `${{ runner.os }}-bun-`,
placing this step immediately before the step that runs `bun install` (the
"Install packages" step).
- Around line 24-25: Add a new GitHub Actions step immediately after the
existing "Test" step (the step that runs "bun run test") to always upload test
artifacts; use the actions/upload-artifact@v3 action with a descriptive artifact
name like "test-results" and include paths such as coverage/ and test-results/
(so coverage and junit/other test reports are preserved) and guard it with if:
always() so artifacts upload even on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca670436-84fe-461c-8a9e-b9b21e2fe25e

📥 Commits

Reviewing files that changed from the base of the PR and between 3e543f8 and c7f1d43.

⛔ Files ignored due to path filters (1)
  • public/MGICALCURE_LOVE_SHOT.mp3 is excluded by !**/*.mp3
📒 Files selected for processing (9)
  • .github/workflows/test.yml
  • bench/index.ts
  • package.json
  • src/index.ts
  • src/utils.ts
  • test/index.test.ts
  • test/utils.d.ts
  • tsconfig.dts.json
  • tsconfig.json
✅ Files skipped from review due to trivial changes (2)
  • test/utils.d.ts
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • bench/index.ts
  • package.json
  • test/index.test.ts
  • src/index.ts

Comment on lines +16 to +19
- name: Setup bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the latest stable version of Bun as of May 2026?

💡 Result:

The latest stable Bun version as of May 10, 2026 is Bun v1.3.13.[1][2] Evidence: Bun’s official release landing page and the GitHub “Releases” page show v1.3.13 as the most recent release, dated 2026-04-20 (after which no newer stable release is shown in the results retrieved). [3][4]

Citations:


Pin Bun to a specific version instead of latest for deterministic CI builds~

Using latest means your CI runs are non-deterministic and could break randomly when a new Bun version drops♡ How reckless~ (¬‿¬) That's gonna cause all kinds of fun debugging when tests suddenly fail for no reason at all, ne~

The current stable version as of May 2026 is v1.3.13 - pin to that instead of flying blind with latest~

🔧 Proposed fix to pin Bun version
             - name: Setup bun
               uses: oven-sh/setup-bun@v1
               with:
-                  bun-version: latest
+                  bun-version: 1.3.13
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Setup bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Setup bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.3.13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 16 - 19, The workflow uses
oven-sh/setup-bun with bun-version set to "latest", which makes CI
non-deterministic; update the action input for bun-version in the GitHub Actions
job (the uses: oven-sh/setup-bun@v1 block) to pin a specific, stable Bun release
(e.g., "v1.3.13") instead of "latest" so builds are reproducible.

@SaltyAom
Copy link
Copy Markdown
Member

ok

@SaltyAom SaltyAom merged commit 124f979 into elysiajs:main May 10, 2026
2 checks passed
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.

Missing Content-Type header when running on Node/Deno

4 participants