-
Notifications
You must be signed in to change notification settings - Fork 0
Concepts: streaming
double-meh streams over Web streams (ReadableStream / WritableStream) in both directions —
the same API in the browser and on the CLI, and it composes directly with
stream-chain. There is no node:stream code; bridge
to Node streams with Readable.fromWeb / Readable.toWeb when you need to.
Streaming has two independent axes:
| Axis | How you turn it on |
|---|---|
| Response (read the body as a stream) | the stream: true option, or the io.stream namespace |
| Request (send a stream as the body) | pass a ReadableStream / Blob / File as the body — no flag |
They compose freely: one axis each on a write = stream up and down (that is what ios gives you).
{stream: true} makes the pipeline hand back the raw response.body instead of parsing it:
const body = await io.get(url, null, {stream: true}); // a ReadableStreamio.stream.get is the GET-only sugar for that — no null placeholder:
const body = await io.stream.get(url); // ≡ io.get(url, null, {stream: true})
const rows = await io.stream.get(url, {page: 2}); // query still composesio.stream is a return-shape namespace alongside io.full. It is GET-only: HEAD/OPTIONS/DELETE
have no body to stream, and write verbs take the body positionally, so io.post(url, data, {stream: true}) already streams the response with no placeholder. track and
cache stand down for streamed responses (a stream is single-consumer).
Pass a Web ReadableStream (or Blob / File / FormData) as the body and it streams up — the
fetch transport sets duplex: 'half' automatically, and
retry stands down (a stream cannot be replayed):
await io.put(url, fileStream, {as: 'octet'}); // Content-Type application/octet-streamA streamed body carries no inherent Content-Type (unlike a Blob), so set one — the terse way is the
as shorthand ({as: 'ndjson'}, or a full type {as: 'application/x-ndjson'}). See
Requests & options.
ios is request-body streaming for writes only — ios.put / ios.post / ios.patch
(url, options?) and the callable ios(options). It returns a Web duplex synchronously, so it
drops straight into a stream-chain pipe as a node:
import {ios} from 'double-meh';
const up = ios.put('https://example.com/data', {as: 'ndjson'});
// up.writable → the request body streams up (duplex:'half')
// up.readable → the streamed response body
// up.response → a promise for the envelope (status/headers), resolved at headers-time-
No
dataarg — the body arrives through the pipe, so the 2nd positional isoptions. -
Non-2xx errors
up.readableand rejectsup.response; pass{ignoreBadStatus: true}to stream the error-response body through instead. - Response streaming is intrinsic —
iosnever takes{stream: true}. - There is no read-verb
ios(GET has no body to stream — useio.stream.get).
io.put accepts a stream-chain chain (or any {readable} duplex) directly and uses its .readable
as the body — so you can terminate a pipeline into an upload:
const pipe = chain([source, transform, jsonlStringer()]);
await io.put(url, pipe, {as: 'ndjson'}); // or io.put(url, pipe.readable, …)Import from stream-chain/web (its Web-streams build). io.stream.get is a pipe source;
ios.put is a mid-pipe duplex (upload, then the server's streamed response flows on):
import {chain} from 'stream-chain/web';
import io, {ios} from 'double-meh';
const pipe = chain([
await io.stream.get(inputUrl), // source: streamed GET
jsonlParser(), // bytes → records
record => ((record.ts = Date.now()), record),
jsonlStringer(), // records → bytes
ios.put(outputUrl, {as: 'ndjson'}) // duplex: upload, response streams back
]);More recipes: Cookbook: streaming pipelines.
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook