-
-
Notifications
You must be signed in to change notification settings - Fork 377
feat: Add stream autoCancellation configuration option for streams #1521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis change introduces a new Changes
Sequence DiagramsequenceDiagram
participant Client
participant Handler
participant StreamController
participant Generator
Client->>Handler: Request stream
Handler->>StreamController: Create stream
alt autoCancellation: true
Client->>Handler: Disconnect/Abort
Handler->>StreamController: Attach abort listener
StreamController-->>Handler: Abort event fired
Handler->>StreamController: controller.close()
StreamController->>Generator: Stop iteration
else autoCancellation: false
Client->>Handler: Disconnect/Abort
Handler->>StreamController: No abort listener attached
Generator->>StreamController: Continue yielding values
StreamController->>Client: Send remaining chunks
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 (2)
test/response/stream.test.ts (2)
565-621: Good test coverage for autoCancellation disabled behavior.The test correctly verifies that when
autoCancellation: false, the generator continues executing after client disconnect (all three side effects occur).Consider adding an integration test that uses the actual Elysia config option:
it('use autoCancellation from Elysia config', async () => { const sideEffects: string[] = [] const app = new Elysia({ stream: { autoCancellation: false } }).get('/', async function* () { sideEffects.push('a') yield 'a' await Bun.sleep(20) sideEffects.push('b') yield 'b' await Bun.sleep(20) sideEffects.push('c') yield 'c' }) // ... rest of test })
565-680: Consider reducing test duplication.Both tests share nearly identical structure. Consider extracting a helper function to reduce duplication:
async function testStreamCancellation( autoCancellation: boolean, expectedSideEffects: string[] ) { const sideEffects: string[] = [] const streamHandler = createStreamHandler({ mapResponse: (value: any) => new Response(value), mapCompactResponse: (value: any) => new Response(value), streamOptions: { autoCancellation } }) async function* testGenerator() { sideEffects.push('a') yield 'a' await Bun.sleep(20) sideEffects.push('b') yield 'b' await Bun.sleep(20) sideEffects.push('c') yield 'c' } // ... rest of test logic expect(sideEffects).toEqual(expectedSideEffects) } it('continue stream when autoCancellation is disabled', async () => { await testStreamCancellation(false, ['a', 'b', 'c']) }) it('stop stream when autoCancellation is enabled', async () => { await testStreamCancellation(true, ['a', 'b']) })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/adapter/bun/handler.ts(1 hunks)src/adapter/utils.ts(2 hunks)src/adapter/web-standard/handler.ts(1 hunks)src/types.ts(1 hunks)test/response/stream.test.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/response/stream.test.ts (1)
src/adapter/utils.ts (1)
createStreamHandler(154-302)
🔇 Additional comments (4)
src/types.ts (1)
171-184: LGTM! Clear and well-documented configuration option.The new
streamconfiguration block is well-structured with clear JSDoc documentation explaining the behavior and default value. The default oftruemaintains backward compatibility while allowing opt-out when needed.src/adapter/utils.ts (3)
146-148: LGTM! Clear interface extension.The
streamOptionsparameter is properly typed as optional with a well-defined shape.
152-152: LGTM! Appropriate default behavior.The default from the environment variable uses
!== 'false', meaning it defaults totrueunless explicitly set to'false'. This maintains backward compatibility (auto-cancellation on by default) while allowing opt-out via environment variable.
222-230: LGTM! Clean conditional abort handling.The abort listener is only attached when
autoCancellationis true, which correctly implements the feature. The try-catch aroundcontroller.close()handles potential errors gracefully.
| const handleStream = createStreamHandler({ | ||
| mapResponse, | ||
| mapCompactResponse | ||
| mapCompactResponse, | ||
| streamOptions: {} | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify that ElysiaConfig.stream is propagated to this handler.
The streamOptions is hardcoded to an empty object {}, which means the autoCancellation setting will always fall back to the environment variable default. There's no visible mechanism to pass the ElysiaConfig.stream configuration (defined in src/types.ts lines 171-184) to this handler.
Run the following script to check if there's a code path that passes the config from ElysiaConfig to the handlers:
🏁 Script executed:
#!/bin/bash
# Search for where ElysiaConfig.stream is accessed and passed to handlers
rg -n -C3 "config\.stream|\.stream\s*\?" --type=ts -g '!test/**' -g '!node_modules/**'Length of output: 0
I need to investigate this more thoroughly with targeted searches. The initial pattern returned no results, so let me try different approaches to understand how the configuration flows.
Let me search the actual codebase to examine the ElysiaConfig definition and handler implementation.
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for ElysiaConfig type definition and stream property
rg -n "type ElysiaConfig|interface ElysiaConfig" --type=ts -A 30 -g '!test/**' -g '!node_modules/**'
Length of output: 1130
🏁 Script executed:
#!/bin/bash
# Get the content around the handler initialization
cat src/adapter/bun/handler.ts | sed -n '560,580p'Length of output: 231
🏁 Script executed:
#!/bin/bash
# Search for createStreamHandler definition
rg -n "createStreamHandler|export.*createStreamHandler" --type=ts -B 5 -A 10 -g '!test/**' -g '!node_modules/**'Length of output: 4831
I need to see the full ElysiaConfig definition and find where the stream property is defined, plus understand how the handlers are invoked with configuration.
---
🏁 Script executed:
```shell
#!/bin/bash
# Get more of the ElysiaConfig definition, especially around lines 171-184
cat src/types.ts | sed -n '79,200p'
Length of output: 2774
🏁 Script executed:
#!/bin/bash
# Search for where handleStream is actually called/used
rg -n "handleStream\(" --type=ts -B 3 -A 3 -g '!test/**' -g '!node_modules/**'Length of output: 5489
🏁 Script executed:
#!/bin/bash
# Search for stream property in ElysiaConfig or related config
rg -n "stream\s*\?:|stream:" --type=ts -B 2 -A 2 -g '!test/**' -g '!node_modules/**' | head -40Length of output: 517
I need to find where these handlers are exported and how the Elysia class uses them with the configuration.
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for where handleStream/handleResponse are exported from handlers
rg -n "export.*handleStream|export.*handleResponse" --type=ts -g '!test/**' -g '!node_modules/**'
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find the Elysia class that would use these handlers
rg -n "class Elysia|export.*class Elysia" --type=ts -A 20 -g '!test/**' -g '!node_modules/**' | head -50Length of output: 2891
🏁 Script executed:
#!/bin/bash
# Search for where createHandler is called to see if config is passed
rg -n "createStreamHandler\(" --type=ts -B 5 -A 5 -g '!test/**' -g '!node_modules/**'Length of output: 1644
Let me search for the main Elysia class and how it uses these handler modules:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find main Elysia class definition (not ElysiaRequest, etc)
find src -name "*.ts" ! -path "*/test/*" ! -path "*/node_modules/*" -exec grep -l "^export class Elysia" {} \;
Length of output: 75
🏁 Script executed:
#!/bin/bash
# Search for imports of the bun handler module
rg -n "from.*adapter/bun/handler|import.*handler.*from" --type=ts -B 2 -A 2 -g '!test/**' -g '!node_modules/**' | head -50Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for where adapter handlers are instantiated with config
rg -n "new.*Adapter|adapter\(" --type=ts -B 3 -A 3 -g '!test/**' -g '!node_modules/**' | head -60Length of output: 0
Let me search the repository structure more broadly:
---
🏁 Script executed:
```shell
#!/bin/bash
# List src directory structure
ls -la src/
Length of output: 1222
🏁 Script executed:
#!/bin/bash
# Search for all class definitions more broadly
rg -n "^export (default )?class" --type=ts -g '!test/**' -g '!node_modules/**'Length of output: 847
🏁 Script executed:
#!/bin/bash
# Look for index files that might be entry points
cat src/index.ts 2>/dev/null || echo "No src/index.ts found"Length of output: 187196
ElysiaConfig.stream is not propagated to handler instantiation.
ElysiaConfig defines a stream property (src/types.ts:174) with autoCancellation?: boolean, but the stream handlers are instantiated at module load time with hardcoded empty options. At src/adapter/bun/handler.ts:568-572 and src/adapter/web-standard/handler.ts:601-605, createStreamHandler is called with streamOptions: {}. The createStreamHandler function falls back to environment variable defaults when streamOptions is empty, meaning instance-level config is ignored entirely.
To fix this, streamOptions must be populated from the Elysia instance's config at handler composition time, not at module load time. This likely requires refactoring the handler instantiation to be request-aware or instance-aware rather than module-level singletons.
🤖 Prompt for AI Agents
In src/adapter/bun/handler.ts around lines 568-572, the stream handler is
created at module load with streamOptions: {} so per-instance
ElysiaConfig.stream (autoCancellation etc.) is never applied; change the code to
instantiate the stream handler at handler-composition time using the Elysia
instance/config (or accept streamOptions as an argument) and pass
ElysiaConfig.stream into createStreamHandler instead of {} so instance-level
stream settings are respected; apply the same refactor to
src/adapter/web-standard/handler.ts where createStreamHandler is currently
called with an empty options object.
| const handleStream = createStreamHandler({ | ||
| mapResponse, | ||
| mapCompactResponse | ||
| mapCompactResponse, | ||
| streamOptions: {} | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify that ElysiaConfig.stream is propagated to this handler.
The streamOptions is hardcoded to an empty object {}, which means the autoCancellation setting will always fall back to the environment variable default. There's no visible mechanism to pass the ElysiaConfig.stream configuration (defined in src/types.ts lines 171-184) to this handler.
Run the following script to check if there's a code path that passes the config from ElysiaConfig to the handlers:
🏁 Script executed:
#!/bin/bash
# Search for where ElysiaConfig.stream is accessed and passed to handlers
rg -n -C3 "config\.stream|\.stream\s*\?" --type=ts -g '!test/**' -g '!node_modules/**'Length of output: 0
I'll conduct a more comprehensive search to trace how configuration flows through the codebase. The initial pattern search produced no results, so I need a broader investigation.
I need to examine the actual codebase to understand the configuration flow. Let me search more broadly in the handler file and find where the configuration is passed.
---
🏁 Script executed:
```shell
#!/bin/bash
# View the handler.ts file context around lines 601-605
sed -n '590,620p' src/adapter/web-standard/handler.ts
Length of output: 296
🏁 Script executed:
#!/bin/bash
# Search for createStreamHandler definition and calls
rg -n "createStreamHandler" --type=ts -g '!node_modules/**' -B2 -A5Length of output: 4601
🏁 Script executed:
#!/bin/bash
# Find ElysiaConfig type definition
rg -n "interface ElysiaConfig|type ElysiaConfig" --type=ts -g '!node_modules/**' -A20Length of output: 802
🏁 Script executed:
#!/bin/bash
# Search for how stream config is used across the codebase
rg -n "stream\s*:" --type=ts -g '!node_modules/**' -C2 | head -50Length of output: 3617
🏁 Script executed:
#!/bin/bash
# Find where the handler function is called/created with config
rg -n "createStreamHandler\s*\(" --type=ts -g '!node_modules/**' -B5 -A10Length of output: 3789
I need to verify the complete ElysiaConfig definition to see if a stream property exists.
---
🏁 Script executed:
```shell
#!/bin/bash
# View the full ElysiaConfig interface, especially around lines 171-184
sed -n '79,200p' src/types.ts
Length of output: 2774
🏁 Script executed:
#!/bin/bash
# Search for stream property in ElysiaConfig
rg -n "stream" src/types.ts -C3Length of output: 2050
🏁 Script executed:
#!/bin/bash
# Check how handlers are instantiated and if config is passed at runtime
rg -n "handleStream|handleResponse" src/adapter/web-standard/handler.ts -C5Length of output: 3649
🏁 Script executed:
#!/bin/bash
# Look for where ElysiaConfig is used in handler initialization
rg -n "ElysiaConfig" src/adapter/web-standard/handler.ts -C3Length of output: 0
Pass ElysiaConfig.stream configuration to createStreamHandler in both adapters.
ElysiaConfig.stream defines an autoCancellation property (lines 174-182 of src/types.ts), but the handlers in both adapters hardcode streamOptions: {} at module initialization, ignoring user configuration. This forces autoCancellation to fall back to the environment variable default instead of respecting the per-instance config.
Both src/adapter/web-standard/handler.ts (lines 601-605) and src/adapter/bun/handler.ts (lines 568-572) need to pass the ElysiaConfig.stream value to createStreamHandler. Since handlers are module-level constants, you'll need to refactor them to be created dynamically per Elysia instance or injected with config at runtime.
|
Hi SaltyAom, |
This PR adds a configuration option to control automatic cancellation of generator-based streams when clients disconnect.
Changes:
By default, autoCancellation is enabled (true) to maintain backward compatibility.
Usage:
Addresses the use case where developers need generator functions to continue executing even after a client disconnects.
Motivation:
Elysia's automatic stream cancellation on client disconnect prevents valid use cases where server operations should complete regardless of connection status (e.g., AI agent executions that persist results to database, paid API calls that cannot be resumed, background job processing).
This PR adds an opt-in configuration flag to disable auto-cancellation while maintaining backward compatibility, enabling developers to build resilient systems where long-running operations complete even when clients disconnect and reconnect later.
Summary by CodeRabbit
Release Notes
New Features
Tests