A library that makes it easier to work with Streams in the browser.
Workbox Streams provides utilities for working with ReadableStreams in service workers and web applications. It offers functions to concatenate multiple stream sources into a single ReadableStream, create HTTP responses from concatenated streams, detect browser support for streaming APIs, and implement streaming strategies for Workbox routing.
npm install workbox-streamsimport {
concatenate,
concatenateToResponse,
isSupported,
strategy,
type StreamSource,
type StreamsHandlerCallback
} from "workbox-streams";For CommonJS:
const {
concatenate,
concatenateToResponse,
isSupported,
strategy
} = require("workbox-streams");import {
concatenate,
concatenateToResponse,
isSupported,
strategy
} from "workbox-streams";
// Check browser support for streaming
if (isSupported()) {
// Concatenate multiple stream sources
const sourcePromises = [
fetch('/api/header'),
fetch('/api/content'),
fetch('/api/footer')
];
const {done, stream} = concatenate(sourcePromises);
// Or create a Response directly
const {done: responseDone, response} = concatenateToResponse(
sourcePromises,
{'content-type': 'text/html'}
);
// Use in service worker
self.addEventListener('fetch', (event) => {
event.waitUntil(responseDone);
event.respondWith(response);
});
}Workbox Streams is built around streaming and fallback patterns:
Combines multiple stream sources into a single ReadableStream with sequential data flow.
/**
* Takes multiple source Promises and returns a ReadableStream with sequential data
* @param sourcePromises - Array of Promises resolving to StreamSource objects
* @returns Object with completion Promise and ReadableStream
*/
function concatenate(sourcePromises: Promise<StreamSource>[]): {
done: Promise<void>;
stream: ReadableStream;
};Usage Example:
import { concatenate } from "workbox-streams";
const sourcePromises = [
Promise.resolve(new Response('Hello ')),
Promise.resolve(new Response('World!')),
fetch('/api/data')
];
const {done, stream} = concatenate(sourcePromises);
// Use the stream
const response = new Response(stream, {
headers: {'content-type': 'text/plain'}
});
// Wait for completion
await done;Creates an HTTP Response with body consisting of concatenated stream data.
/**
* Takes multiple source Promises and returns a Response with concatenated data
* @param sourcePromises - Array of Promises resolving to StreamSource objects
* @param headersInit - Optional headers for the response (if no 'Content-Type', defaults to 'text/html')
* @returns Object with completion Promise and Response
*/
function concatenateToResponse(
sourcePromises: Promise<StreamSource>[],
headersInit?: HeadersInit
): {
done: Promise<void>;
response: Response;
};Usage Example:
import { concatenateToResponse } from "workbox-streams";
const sourcePromises = [
fetch('/template/header'),
fetch('/content/body'),
fetch('/template/footer')
];
// With headers
const {done, response} = concatenateToResponse(
sourcePromises,
{
'content-type': 'text/html',
'cache-control': 'max-age=3600'
}
);
// Without headers (defaults to 'text/html')
const {done: done2, response: response2} = concatenateToResponse(sourcePromises);
// Use in service worker
self.addEventListener('fetch', (event) => {
if (event.request.url.endsWith('/composite-page')) {
event.waitUntil(done);
event.respondWith(response);
}
});Determines whether the current browser supports streaming features.
/**
* Checks if browser supports ReadableStream construction for streaming responses
* @returns true if streaming is supported, false otherwise
*/
function isSupported(): boolean;Usage Example:
import { isSupported, concatenateToResponse } from "workbox-streams";
function createCompositeResponse(sources: Promise<StreamSource>[], headers: HeadersInit) {
if (isSupported()) {
// Use streaming concatenation
return concatenateToResponse(sources, headers);
} else {
// Fallback to waiting for all sources
return Promise.all(sources).then(resolvedSources => {
// Manual concatenation fallback
return new Response(/* combined content */, {headers});
});
}
}Creates a strategy function compatible with Workbox routing that handles streaming with automatic fallback.
/**
* Creates a Workbox-compatible strategy for streaming responses with fallback
* @param sourceFunctions - Array of handler functions returning StreamSource objects
* @param headersInit - Optional headers for responses (if no 'Content-Type', defaults to 'text/html')
* @returns RouteHandlerCallback compatible with Workbox routing
*/
function strategy(
sourceFunctions: StreamsHandlerCallback[],
headersInit?: HeadersInit
): RouteHandlerCallback;Usage Example:
import { strategy } from "workbox-streams";
import { registerRoute } from "workbox-routing";
// Define source functions
const sourceFunctions = [
// Header source
({url, request}) => fetch('/api/header'),
// Dynamic content based on URL
({url, request}) => fetch(`/api/content${url.pathname}`),
// Footer source
({url, request}) => fetch('/api/footer')
];
// Create streaming strategy with headers
const streamingStrategy = strategy(sourceFunctions, {
'content-type': 'text/html',
'cache-control': 'no-cache'
});
// Or without headers (defaults to 'text/html')
const basicStrategy = strategy(sourceFunctions);
// Register with Workbox routing
registerRoute(
({url}) => url.pathname.startsWith('/dynamic/'),
streamingStrategy
);Union type representing valid stream sources.
/**
* Valid source types for streaming operations
*/
type StreamSource = Response | ReadableStream | BodyInit;Where BodyInit includes: Blob | BufferSource | FormData | URLSearchParams | string
Interface for handler functions used in streaming strategies.
/**
* Handler function interface for streaming strategies
*/
interface StreamsHandlerCallback {
(options: RouteHandlerCallbackOptions): Promise<StreamSource> | StreamSource;
}
/**
* Generic object interface for key-value mapping (from workbox-core)
*/
interface MapLikeObject {
[key: string]: any;
}
/**
* Options passed to handler callbacks (from workbox-core)
*/
interface RouteHandlerCallbackOptions {
event: ExtendableEvent;
request: Request;
url: URL;
params?: string[] | MapLikeObject;
}
/**
* Route handler callback interface (from workbox-core)
*/
interface RouteHandlerCallback {
(options: RouteHandlerCallbackOptions): Promise<Response>;
}When stream processing fails, errors are propagated through the Promise chain:
import { concatenate } from "workbox-streams";
const {done, stream} = concatenate([
fetch('/api/source1'),
fetch('/api/source2')
]);
// Handle errors in the completion promise
done.catch(error => {
console.error('Stream concatenation failed:', error);
});
// Errors also propagate through the stream
const response = new Response(stream);
response.text().catch(error => {
console.error('Stream reading failed:', error);
});Workbox Streams throws a WorkboxError when attempting to stream opaque responses:
// This will throw WorkboxError: 'opaque-streams-source'
const opaqueResponse = await fetch('https://external-api.com/data', {
mode: 'no-cors' // Creates opaque response
});
const {stream} = concatenate([Promise.resolve(opaqueResponse)]);
// Error: Cannot read from opaque responseInstall with Tessl CLI
npx tessl i tessl/npm-workbox-streams