Skip to content

patch 1.4.19#1610

Merged
SaltyAom merged 18 commits intomainfrom
next
Dec 13, 2025
Merged

patch 1.4.19#1610
SaltyAom merged 18 commits intomainfrom
next

Conversation

@SaltyAom
Copy link
Copy Markdown
Member

@SaltyAom SaltyAom commented Dec 9, 2025

Improvement

Bug fix:

  • #1591, #1590 merge set.headers without duplicating Response

Summary by CodeRabbit

  • Bug Fixes

    • Rejects invalid cookie signatures with rotated secrets; fixes header duplication and restores default error handling in Cloudflare Workers; safer error and scheduling fallbacks.
  • Improvements

    • Faster lazy compilation for fetch, improved after-response scheduling (including aot:false), better nested FormData coercion, and refined type/group inference behavior.
  • Documentation

    • Added CHANGELOG entries for v1.4.18 and v1.4.19.
  • Tests

    • Expanded FormData, context, Cloudflare, and type-level test coverage.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 9, 2025

Warning

Rate limit exceeded

@SaltyAom has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2bdd6 and 3334797.

📒 Files selected for processing (5)
  • .github/workflows/publish.yml (2 hunks)
  • src/adapter/bun/index.ts (3 hunks)
  • src/compose.ts (8 hunks)
  • src/index.ts (11 hunks)
  • src/sucrose.ts (2 hunks)

Walkthrough

Adds CHANGELOG v1.4.19; exposes runtime hasSetImmediate and replaces direct setImmediate calls with a Promise fallback; schedules afterResponse hooks via the runtime-aware scheduler; fixes multi-secret cookie verification; tightens schema UnwrapSchema typing; adjusts group path typing and compile caching; refactors sucrose context detection; wires composeErrorHandler into Cloudflare adapter; updates examples and tests.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added v1.4.19 entry listing security, improvements, and bug fixes (setImmediate fallback, afterResponse scheduling, nested FormData coercion, mergeDeep seen WeakSet, Cloudflare path/handler fixes, cookie signature handling).
Example
example/a.ts
Replaced cookie-based demo and manual request handling with a minimal Bun-based Elysia() app exposing / and /json routes and using Bun.serve.
Runtime scheduling & utils
src/compose.ts, src/utils.ts
Added export const hasSetImmediate = typeof setImmediate === 'function'; replaced direct setImmediate calls with a runtime-aware scheduler (use setImmediate if available, otherwise Promise.resolve().then).
Dynamic handler hooks
src/dynamic-handle.ts
Extracted hooks from handler store and schedule afterResponse execution asynchronously using the runtime-aware scheduler (uses hasSetImmediate guard).
Cookies
src/cookies.ts
Multi-secret cookie verification now initializes decoded as false and sets it true only on a successful unsign; throws InvalidCookieSignature if no secret succeeds.
Cloudflare adapter
src/adapter/cloudflare-worker/index.ts, test/cloudflare/src/index.ts
Wired composeErrorHandler into beforeCompile; changed listen semantics to not implicitly call app.compile(); test updated to mount a sub-app via .use(sub).
Schema typings
src/schema.ts
Imported UnwrapSchema and updated ElysiaTypeCheck<T> methods (clean, parse, and safeParse success data) to use UnwrapSchema<T>.
Routing / compile caching
src/index.ts
Added local _compiled cache to reuse precompiled handler and adjusted group overload typing to treat empty Prefix specially (Prefix extends '' ? BasePath : JoinPath<...>).
Sucrose / context inference
src/sucrose.ts
Replaced heuristic with isContextPassToFunction scanner (captureFunction/exactParameter loop), trimmed code extraction, added try/catch and debug logging to detect context passed into function parameters (affects inference flags).
Adapter — Bun formatting
src/adapter/bun/index.ts
Formatting refactor: expanded ternary expressions to multi-line if blocks in message validation branches; no semantic changes.
Tests — FormData
test/type-system/formdata.test.ts
Large rewrite consolidating endpoints and expanding nested FormData coverage (JSON-stringified nested fields, files, validation error scenarios); updated expected payload shapes.
Tests — Dynamic hooks
test/core/dynamic.test.ts
Replaced previous query-array tests with one verifying local afterResponse is called when aot: false.
Tests — Sucrose
test/sucrose/sucrose.test.ts
Added test ensuring context-pass-to-function with sub-context sets all inference flags.
Tests — Types utils & group empty prefix
test/types/utils.ts, test/types/index.ts
Added type-level tests: getSchemaValidator/safeParse type assertions and tests for grouping with an empty prefix and prefix-type override.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant App as Elysia App
  participant Handler
  participant Scheduler as setImmediate?/Promise
  participant After as afterResponse hooks
  participant Adapter as Cloudflare Adapter

  Client->>App: HTTP request
  App->>Handler: invoke route handler (uses compiled/_compiled cache if present)
  Handler-->>App: Response
  App->>Scheduler: schedule afterResponse (checks hasSetImmediate)
  Scheduler->>After: invoke afterResponse hooks asynchronously
  After-->>App: side-effects (cookies, logs, etc.)
  App-->>Client: final Response
  Note over Adapter: Cloudflare adapter now wires composeErrorHandler before compile/listen
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • src/sucrose.ts — correctness and safety of regexes/loop, and removal/noise from debug logging.
    • src/compose.ts & src/dynamic-handle.ts — microtask vs macrotask ordering, error propagation, and afterResponse timing implications.
    • src/cookies.ts — multi-secret verification correctness and edge cases.
    • src/index.ts — compile caching correctness and behavior in precompiled/Cloudflare scenarios.
    • test/type-system/formdata.test.ts — breadth and correctness of new nested FormData cases.

Possibly related PRs

  • 1.4.6 patch #1411 — touches mergeDeep behavior and seen-WeakSet changes related to merging logic referenced in the changelog.
  • Elysia 1.4: Weirs #1398 — modifies cookie parsing/verification logic; closely related to the multi-secret cookie verification fix.
  • 1.4.5 patch #1403 — addresses cookie validation/coercion and scheduling interactions; related to cookie and compose changes.

"I hopped through code with twitchy paws,
moved hooks so timing keeps its laws.
Forms nested neat, types trimmed fine,
cached routes hum — a tidy line.
I nibble bugs and leave a carrot sign. 🥕🐇"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'patch 1.4.19' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes made in the changeset. Replace with a more descriptive title that highlights the main change, such as 'Fix nested form data handling and set.headers duplication' or 'v1.4.19: Support nested form data and fix header merging'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Dec 9, 2025

Open in StackBlitz

npm i https://pkg.pr.new/elysiajs/elysia@1610

commit: 9210318

Copy link
Copy Markdown
Contributor

@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: 2

🧹 Nitpick comments (1)
CHANGELOG.md (1)

2-2: Minor: Add missing colon after "Improvement" for consistency.

Line 2 lacks a colon after Improvement, while line 6 has Bug fix: with a colon. For consistency with established formatting, add the colon.

-Improvement
+Improvement:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a480edb and 3143445.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)

CHANGELOG.md Outdated
# 1.4.19 - 9 Dec 2025
Improvement
- [#1588](https://github.com/elysiajs/elysia/pull/1588), [#1587](https://github.com/elysiajs/elysia/pull/1587) add seen weakset during mergeDeep
- [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1138) nested form data
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix broken reference link on line 4.

The issue reference displays #1139 but the URL points to #1138. Update the URL to match the displayed issue number.

-- [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1138) nested form data
+- [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1139) nested form data
📝 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
- [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1138) nested form data
- [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1139) nested form data
🤖 Prompt for AI Agents
In CHANGELOG.md around line 4, the displayed issue reference shows #1139 but its
URL points to .../issues/1138; update the third link's URL to .../issues/1139 so
the href matches the displayed issue number (change the 1138 in the URL to
1139).

Copy link
Copy Markdown
Contributor

@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: 2

Caution

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

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

813-833: Bug: encoded loose path is never actually registered (duplicate add of loosePath)

In the non‑AOT dynamic router branch:

const loosePath = getLoosePath(path)
this.router.dynamic.add(method, loosePath, { ... })

const encoded = encodePath(loosePath)
if (loosePath !== encoded)
  this.router.dynamic.add(method, loosePath, { ... })

the second add still uses loosePath instead of encoded, so:

  • The encoded loose path is never registered.
  • The same path/method pair is added twice, which is redundant and could mask intent.

This should mirror the earlier path handling and actually register encoded:

- const encoded = encodePath(loosePath)
- if (loosePath !== encoded)
-   this.router.dynamic.add(method, loosePath, {
+ const encoded = encodePath(loosePath)
+ if (loosePath !== encoded)
+   this.router.dynamic.add(method, encoded, {
      validator,
      hooks,
      content: localHook?.type as string,
      handle,
      route: path
    })
🧹 Nitpick comments (1)
src/sucrose.ts (1)

547-583: Escape context before embedding it in regex to avoid syntax errors and static-analysis noise

isContextPassToFunction currently builds regexes directly from the context string:

const captureFunction = new RegExp(`\\w\\((?:.*?)?${context}(?:.*?)?\\)`, 'gs')
const exactParameter = new RegExp(`${context}(,|\\))`, 'gs')

Because context is derived from the parameter string, it can sometimes include characters like {, }, . or whitespace (e.g. rest-destructuring forms), which makes these patterns syntactically invalid and forces you into the try/catch path. It also triggers the static “regexp-from-variable” warning.

You can keep the current matching strategy but escape context once before use:

- const captureFunction = new RegExp(
-   `\\w\\((?:.*?)?${context}(?:.*?)?\\)`,
-   'gs'
- )
- const exactParameter = new RegExp(`${context}(,|\\))`, 'gs')
+ const escaped = context.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ const captureFunction = new RegExp(
+   `\\w\\((?:.*?)?${escaped}(?:.*?)?\\)`,
+   'gs'
+ )
+ const exactParameter = new RegExp(`${escaped}(,|\\))`, 'gs')

This avoids regex syntax errors, reduces reliance on the catch-all error handler, and should satisfy the static-analysis warning without changing semantics.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c95af80 and 620d96b.

📒 Files selected for processing (8)
  • CHANGELOG.md (1 hunks)
  • example/a.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/schema.ts (2 hunks)
  • src/sucrose.ts (2 hunks)
  • test/core/dynamic.test.ts (1 hunks)
  • test/sucrose/sucrose.test.ts (1 hunks)
  • test/types/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • example/a.ts
🧰 Additional context used
🧬 Code graph analysis (5)
test/types/utils.ts (1)
src/schema.ts (1)
  • getSchemaValidator (380-1041)
src/schema.ts (2)
src/index.ts (1)
  • UnwrapSchema (8223-8223)
src/types.ts (1)
  • UnwrapSchema (457-495)
test/core/dynamic.test.ts (2)
src/index.ts (2)
  • Elysia (185-8135)
  • Elysia (8137-8137)
test/utils.ts (1)
  • req (1-2)
src/sucrose.ts (1)
src/universal/request.ts (1)
  • body (81-144)
test/sucrose/sucrose.test.ts (1)
src/sucrose.ts (1)
  • sucrose (652-766)
🪛 ast-grep (0.40.0)
src/sucrose.ts

[warning] 557-557: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${context}(,|\\)), 'gs')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (4)
src/index.ts (1)

584-601: Form-data coercion gate looks correct and avoids touching non-TypeBox schemas

The new additionalCoerce logic that first resolveSchema(cloned.body, models, modules) and then requires Kind in resolved && (hasType('File', resolved) || hasType('Files', resolved)) before switching to coerceFormData() is a good guardrail: it keeps form-data coercion limited to actual TypeBox file schemas and falls back to coercePrimitiveRoot() otherwise. This aligns with the intent to support nested form data without impacting standard/custom schemas.

Also applies to: 652-665

test/types/utils.ts (1)

1-17: Type-level test correctly validates safeParse().data inference

The new test directly asserts that getSchemaValidator(schema).safeParse(...).data is { id: number; name: string } on success, which matches the intended UnwrapSchema behavior for object schemas. This is a good, minimal guard for future typing changes.

test/sucrose/sucrose.test.ts (1)

311-330: New sucrose test usefully extends context-inference coverage

The handle context pass to function with sub context case validates that passing context as the single handler parameter—and only accessing a nested property—still flips all inference flags to true. This matches the new isContextPassToFunction logic and strengthens regression coverage around context-based inference.

src/schema.ts (1)

24-35: UnwrapSchema integration and resolveSchema helper correctly align with runtime behavior

  • Importing UnwrapSchema and updating ElysiaTypeCheck<T> so Clean, parse, and the success branch of safeParse all return UnwrapSchema<T> matches how TypeBox and StandardSchemaV1Like schemas are decoded at runtime. For TypeBox schemas, UnwrapSchema extracts the static output type via TImport['static']; for StandardSchemaV1Like, it uses Schema['~standard']['types']['output'].
  • The new resolveSchema helper (string → module $defs → models map) centralizes schema resolution and is used from other modules to determine coercion strategy without duplicating lookup logic.
  • The replaceSchemaTypeFromManyOptions call for root object schemas without additionalProperties set (wrapping them into an explicit t.Object(..., { additionalProperties: false })) is a targeted normalization that keeps response/body cleaning strict by default while honoring explicit settings.
  • The type-level test in test/types/utils.ts confirms that getSchemaValidator(t.Object({...})).safeParse().data is correctly typed as the unwrapped static type, validating the end-to-end behavior.

Comment on lines +697 to +716
it('call local afterResponse on aot: false', async () => {
let called = false

const app = new Elysia({ aot: false })
.use(IdsModel)
.get('/', ({ query }) => query, {
name: 'ids'
})

const data = await app
.handle(req('/?names=rapi&names=anis'))
.then((x) => x.json())

expect(data).toEqual({
names: ['rapi', 'anis']
})
})
.guard(
{
afterResponse: () => {
called = true
}
},
(app) => app.get('/test', () => 'afterResponse')
)
.get('/', () => 'hi')

const value = await app.handle(req('/test')).then((x) => x.text())
await Bun.sleep(6.7)

expect(value).toBe('afterResponse')
expect(called).toBeTrue()
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Bun.sleep milliseconds unit documentation

💡 Result:

Bun.sleep expects milliseconds (number) — or a Date to sleep until. Example: await Bun.sleep(1000) sleeps ~1000 ms. Bun.sleepSync also uses milliseconds. [1][2][3]

Sources:
[1] Bun API — Bun.sleep.
[2] Bun runtime/utils documentation — Bun.sleep.
[3] Bun v0.5.8 release notes (sleepSync uses milliseconds).


🏁 Script executed:

cd test/core && wc -l dynamic.test.ts

Repository: elysiajs/elysia

Length of output: 78


🏁 Script executed:

sed -n '697,750p' test/core/dynamic.test.ts

Repository: elysiajs/elysia

Length of output: 1346


Make afterResponse timing in this test more robust and remove dead code

The new call local afterResponse on aot: false test correctly exercises a local afterResponse hook in dynamic mode, but it relies on await Bun.sleep(6.7) (6.7 milliseconds) as a timing buffer before asserting called === true. This is a very tight margin and can be flaky under CI load or slower environments where afterResponse scheduling may not complete within that window.

Consider either:

  • Increasing the wait to a more conservative value (e.g. 50–100ms), or
  • Refactoring to assert via a polling loop or explicit signal from the scheduler instead of a hard-coded timeout.

Additionally, the large commented-out block of duplicate "handle query array reference…" tests immediately below this test should be deleted entirely rather than left commented.

🤖 Prompt for AI Agents
In test/core/dynamic.test.ts around lines 697 to 716, the test uses a very short
fixed delay (await Bun.sleep(6.7)) before asserting the afterResponse hook was
called, which is flaky; replace that hard-coded 6.7ms wait with a more robust
approach such as increasing the delay to a conservative 50–100ms or, better,
poll/wait until the `called` flag becomes true with a short timeout to avoid
flakiness; also remove the large commented-out block of duplicate "handle query
array reference…" tests immediately below this test.

Copy link
Copy Markdown
Contributor

@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: 0

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

591-609: Form-data detection via resolveSchema is sound; consider deduplicating helper

Using resolveSchema + hasType('File' | 'Files') to switch between coerceFormData() and coercePrimitiveRoot() is a solid way to support nested/file schemas, including refs and named models, in both precompiled and lazy body validators.

You have essentially identical closures in the precompile and lazy branches; factoring this into a small local helper (e.g., getBodyCoercion(cloned.body, models, modules)) would remove duplication without changing behavior.

Also applies to: 671-685


3840-3873: Empty-prefix group typing fix aligns with new tests; optional schema-path tweak

Switching the grouped instance’s BasePath to Prefix extends '' ? BasePath : JoinPath<BasePath, Prefix> fixes the double‑slash / extra segment issue for group('') at the type level, matching the new Routes = 'ok' test.

For full internal consistency, you might also want to apply the same conditional inside this overload’s UnwrapRoute<..., JoinPath<BasePath, Prefix>> arguments (and possibly in the second overload), so schema unwrapping uses the same notion of “effective base path” when Prefix is ''. That’s type-level only and can be done later if you start relying on those paths more heavily.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 620d96b and daaea8b.

📒 Files selected for processing (4)
  • CHANGELOG.md (1 hunks)
  • example/a.ts (1 hunks)
  • src/index.ts (6 hunks)
  • test/types/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • example/a.ts
🧰 Additional context used
🧬 Code graph analysis (2)
test/types/index.ts (1)
src/index.ts (2)
  • Elysia (190-8153)
  • Elysia (8155-8155)
src/index.ts (2)
src/schema.ts (2)
  • resolveSchema (130-146)
  • hasType (148-184)
src/types.ts (1)
  • JoinPath (2582-2585)
🔇 Additional comments (2)
test/types/index.ts (1)

2925-2937: Empty-prefix group type test matches intent and implementation

The new group('', ...) test correctly asserts that ~Routes exposes a single 'ok' key, matching the updated group typing for empty prefixes. Looks consistent with surrounding type tests and the group overload change in src/index.ts.

src/index.ts (1)

45-52: Schema/coercion imports and new exports look consistent

Importing resolveSchema alongside hasType, moving coercion helpers (coercePrimitiveRoot, coerceFormData, queryCoercions, stringToStructureCoercions) into their own module, and re‑exporting getSchemaValidator / getResponseSchemaValidator / replaceSchemaType from the root all line up cleanly with the rest of the file and broaden the public API without altering existing behavior.

Also applies to: 167-172, 8175-8176

Copy link
Copy Markdown
Contributor

@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/index.ts (1)

943-1037: Fix precompile crash + make _compiled explicitly optional.

When shouldPrecompile is true for a new route (oldIndex === undefined), route[index] doesn’t exist yet, but the handler object later reads route[index].composed (can throw). Also _compiled is used as optional but typed as required.

Suggested patch (adjust to exact local context):

-		// For pre-compilation stage, eg. Cloudflare Worker
-		let _compiled: ComposedHandler
+		// For pre-compilation stage, eg. Cloudflare Worker
+		let _compiled: ComposedHandler | undefined
 		const compile = () => {
 			if (_compiled) return _compiled

 			const compiled = composeHandler({
 				app: this,
 				path,
 				method,
 				hooks,
 				validator: createValidator(),
 				handler:
 					typeof handle !== 'function' &&
 					typeof adapter.createStaticHandler !== 'function'
 						? () => handle
 						: handle,
 				allowMeta,
 				inference: this.inference
 			})

 			if (this.router.history[index])
 				_compiled = this.router.history[index].composed = compiled

 			return compiled
 		}

@@
-		const mainHandler = shouldPrecompile
-			? compile()
+		const mainHandler = shouldPrecompile
+			? compile()
 			: (ctx: Context) =>
 					_compiled
 						? _compiled(ctx)
 						: (
 								(route[index].composed =
 									compile!()) as ComposedHandler
 							)(ctx)

@@
-		const handler = {
-			handler: shouldPrecompile
-				? (route[index].composed as ComposedHandler)
-				: undefined,
+		const handler = {
+			// route[index] may not exist yet for new routes; use the computed mainHandler
+			handler: shouldPrecompile ? (mainHandler as ComposedHandler) : undefined,
 			compile() {
 				return (this.handler = compile!())
 			}
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daaea8b and b0be99c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • example/a.ts (1 hunks)
  • src/adapter/cloudflare-worker/index.ts (2 hunks)
  • src/compose.ts (8 hunks)
  • src/index.ts (10 hunks)
  • test/cloudflare/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
🧰 Additional context used
🧬 Code graph analysis (5)
test/cloudflare/src/index.ts (2)
src/index.ts (2)
  • Elysia (190-8160)
  • Elysia (8162-8162)
src/adapter/cloudflare-worker/index.ts (1)
  • CloudflareAdapter (43-77)
example/a.ts (1)
src/index.ts (2)
  • Elysia (190-8160)
  • Elysia (8162-8162)
src/adapter/cloudflare-worker/index.ts (1)
src/compose.ts (1)
  • composeErrorHandler (2537-2762)
src/compose.ts (1)
src/utils.ts (1)
  • hasSetImmediate (402-402)
src/index.ts (3)
src/schema.ts (2)
  • resolveSchema (130-146)
  • hasType (148-184)
src/types.ts (2)
  • ComposedHandler (1883-1883)
  • JoinPath (2582-2585)
src/type-system/utils.ts (1)
  • compile (38-76)
🔇 Additional comments (12)
example/a.ts (1)

1-8: Example looks fine; intentional failure route is clear.

src/index.ts (5)

51-52: Good: schema-aware coercion imports are now wired in.

Also applies to: 167-172


597-609: Coercion selection via resolveSchema() is a solid fix for File/Files detection.

Also applies to: 672-684


3847-3853: Type-level fix for group('') base path inference is on point.


8026-8041: Good: web-standard compile() now wires _handle consistently.


8182-8184: Public exports added—worth double-checking API intent + docs.

This changes the package surface area (getSchemaValidator, getResponseSchemaValidator, replaceSchemaType). Ensure it’s intended for semver + documented in the changelog/website.

src/adapter/cloudflare-worker/index.ts (2)

3-3: Cloudflare beforeCompile wiring looks consistent with composed error handling.

Also applies to: 65-69


70-76: listen() warning behavior is fine (Cloudflare Workers).

test/cloudflare/src/index.ts (1)

4-8: Good: test now exercises .use(sub) composition under Cloudflare + AOT.

src/compose.ts (3)

22-31: Nice: runtime-agnostic scheduling primitive fits the codegen model.

Also applies to: 448-451


812-845: Scheduling via ${setImmediateFn} is consistent; consider whether microtask timing is acceptable for afterResponse.

In runtimes without setImmediate, this becomes Promise.resolve().then(...) (microtask). That can run earlier than a macrotask-based defer; please sanity-check expected ordering for afterResponse/stream listeners in Worker-like environments.

Also applies to: 1786-1792, 2350-2360, 2584-2612


2225-2227: Good: avoids crashing on process-less runtimes when composition fails.

Copy link
Copy Markdown
Contributor

@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 (2)
src/index.ts (2)

942-990: Fix _compiled typing and avoid “used before assigned” semantics.
let _compiled: ComposedHandler is read before assignment; make it optional/undefinedable to match the control flow.

-		let _compiled: ComposedHandler
+		let _compiled: ComposedHandler | undefined

8019-8031: Blocker: compile() assigns to fetch getter → TS2540 (pipeline failure).
You’re correctly caching fetch via Object.defineProperty in the getter, but compile() still does this.fetch = .... Switch compile() to define the instance property instead of assignment.

 		if (this['~adapter'].isWebStandard) {
-			this._handle = this.fetch = this.config.aot
-				? composeGeneralHandler(this)
-				: createDynamicHandler(this)
+			const fetch = this.config.aot
+				? composeGeneralHandler(this)
+				: createDynamicHandler(this)
+
+			this._handle = fetch
+			Object.defineProperty(this, 'fetch', {
+				value: fetch,
+				configurable: true,
+				writable: true
+			})

 			if (typeof this.server?.reload === 'function')
 				this.server.reload({
 					...(this.server || {}),
-					fetch: this.fetch
+					fetch
 				})

 			return this
 		}

Also applies to: 8048-8060

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aea3b06 and 7f2bdd6.

📒 Files selected for processing (4)
  • CHANGELOG.md (1 hunks)
  • example/a.ts (1 hunks)
  • src/adapter/bun/index.ts (2 hunks)
  • src/index.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-22T23:46:33.200Z
Learnt from: 81reap
Repo: elysiajs/elysia PR: 1431
File: src/dynamic-handle.ts:127-131
Timestamp: 2025-09-22T23:46:33.200Z
Learning: The File System Routing feature in Elysia is Bun-specific and only works with Bun.FileSystemRouter, so Bun-specific import patterns and path resolution using import.meta.url are acceptable in this context.

Applied to files:

  • example/a.ts
🧬 Code graph analysis (1)
src/adapter/bun/index.ts (2)
test/types/index.ts (2)
  • message (1220-1220)
  • message (2465-2467)
src/ws/index.ts (1)
  • message (22-24)
🪛 GitHub Actions: Build and Test
src/index.ts

[error] 8020-8020: TS2540: Cannot assign to 'fetch' because it is a read-only property. Build failed. Command 'bun run build' exited with code 1.

🔇 Additional comments (6)
src/adapter/bun/index.ts (2)

442-448: LGTM!

The multi-line formatting improves readability of the nested ternary without changing the validation logic. Both validator branches correctly return truthy values when validation fails.


574-577: LGTM!

The multi-line if-condition formatting improves readability while preserving the same short-circuit evaluation logic for WebSocket message validation.

src/index.ts (2)

3846-3860: Group prefix typing improvement looks good.
Handling Prefix extends '' avoids producing JoinPath<BasePath, ''>-style artifacts and matches expected ergonomics for “no prefix” groups.


8181-8182: Public export rename: confirm this is intentional and documented.
replaceSchemaTypeFromManyOptions as replaceSchemaType looks like a public surface change; please confirm downstream docs/tests align and no older named export is relied upon.

example/a.ts (2)

8-11: Passing app.fetch into Bun.serve is fine with the lazy getter.
The getter returns a stable handler function and will cache itself via Object.defineProperty.


3-6: Remove misleading comment about aot being broken on Bun.

The comment "aot: true by default in 1.4 (broken on Bun)" is incorrect. The Bun adapter explicitly requires both aot: true and systemRouter: true to enable route optimization. Simply remove the comment or update it to reflect actual behavior.

Comment on lines 595 to 608
additionalCoerce: (() => {
const resolved = resolveSchema(cloned.body, models, modules)
const resolved = resolveSchema(
cloned.body,
models,
modules
)
// Only check for Files if resolved schema is a TypeBox schema (has Kind symbol)
return (resolved && Kind in resolved && (hasType('File', resolved) || hasType('Files', resolved)))
return resolved &&
Kind in resolved &&
(hasType('File', resolved) ||
hasType('Files', resolved))
? coerceFormData()
: coercePrimitiveRoot()
})(),
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Dec 13, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard Kind in resolved against non-object returns from resolveSchema.
resolved && Kind in resolved will throw if resolved is a truthy primitive. Safer to gate on typeof resolved === 'object' || typeof resolved === 'function'.

-								return resolved &&
-									Kind in resolved &&
+								return resolved &&
+									(typeof resolved === 'object' ||
+										typeof resolved === 'function') &&
+									Kind in resolved &&
 									(hasType('File', resolved) ||
 										hasType('Files', resolved))
 									? coerceFormData()
 									: coercePrimitiveRoot()

Also applies to: 670-683

🤖 Prompt for AI Agents
In src/index.ts around lines 595-608 (and similarly around 670-683), the code
uses "resolved && Kind in resolved" which will throw if resolved is a truthy
primitive; replace that guard with an explicit object/function check such as
"resolved !== null && (typeof resolved === 'object' || typeof resolved ===
'function') && Kind in resolved" (or equivalent) so the in-check only runs for
objects/functions, and apply the same fix to the second occurrence.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is fine because we enforced resolved to be StandardSchemaV1Like elsewhere which enforce to always be either object or undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@SaltyAom, understood! If resolveSchema is typed to return StandardSchemaV1Like | undefined and StandardSchemaV1Like is constrained to object types only, then the check is indeed safe:

  • When resolved is undefined, the condition short-circuits at resolved &&
  • When resolved is truthy, TypeScript guarantees it's an object, making Kind in resolved safe

The type system enforcement eliminates the need for additional runtime guards. Thanks for clarifying the type constraints!

@SaltyAom SaltyAom merged commit 4641b18 into main Dec 13, 2025
2 of 3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 3, 2026
@coderabbitai coderabbitai bot mentioned this pull request Jan 14, 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.

1 participant