Skip to content

feat(fetch): Add streaming response support #108

@damusix

Description

@damusix

Problem

The current @logosdx/fetch implementation cannot support streaming responses (SSE, chunked transfers, etc.) because the response body is fully consumed before returning to the caller.

In engine.ts, the #makeCall method always waits for the entire response:

const [data, parseErr] = await attempt(async () => {
    const { type, isJson } = this.#determineType(response);
    
    if (isJson) {
        const text = await response.text();  // ← Blocks until stream closes
        return text ? JSON.parse(text) : null;
    }
    else {
        return await response[type]();  // ← Also blocks
    }
});

This means SSE endpoints would block until the entire session ends before returning any data.

Proposed Solution

Add a 'stream' type that bypasses response parsing and returns the raw Response object.

Per-Request Stream Configuration

Streaming should be configurable on individual requests via a stream option:

// Option 1: Simple boolean
const { data: response } = await api.get('/api/events', {
    stream: true
});

// Option 2: Explicit type (existing pattern)
const { data: response } = await api.get('/api/events', {
    type: 'stream'
});

This allows mixing streaming and non-streaming requests to the same endpoint based on context.

Implementation

// In #makeCall, before parsing:
if (type === 'stream' || config.stream === true) {

    return {
        data: response,  // Raw Response - user calls response.body.getReader()
        headers: Object.fromEntries(response.headers),
        status: response.status,
        request: new Request(url, fetchOpts),
        config
    };
}

Changes Required

File Change
types.ts Add 'stream' to the Type union
types.ts Add optional stream?: boolean to request config
engine.ts Skip body consumption when type === 'stream' or stream: true, return raw Response
helpers.ts Add validation to warn/disable caching and deduplication for stream requests
Tests Add streaming response test cases
Docs Update with streaming usage examples

Usage Example

const { data: response } = await api.get('/api/events', {
    stream: true
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {

    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    // Parse SSE: data: {...}\n\n
}

Benefits Preserved for Streaming Requests

  • Request building (headers, params, base URL)
  • Interceptors/middleware on the request side
  • AbortController integration
  • Authentication headers
  • Event hooks (fetch-before, etc.)

Features Disabled for Streams

  • Response body parsing (user handles)
  • Caching (streams can only be read once)
  • Deduplication (streams can't be shared)
  • Response transformation

Notes

  • Request body streaming is already supported (sending ReadableStream as payload works today)
  • This change is localized and should not affect existing functionality

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions