Commonly used stream patterns for Web Streams API and NodeJS Stream.
If you're iterating over an array more than once, it's time to use streams.
@datastream/core- pipeline
- pipejoin
- result
- streamToArray
- streamToObject
- streamToString
- streamToBuffer
- isReadable
- isWritable
- makeOptions
- createReadableStream
- createPassThroughStream
- createTransformStream
- createWritableStream
- resolveLazy
- timeout
- createChunkDecoder
- concatBytes
- backpressureGauge (Node only)
- Readable: The start of a pipeline of streams that injects data into a stream.
- PassThrough: Does not modify the data, but listens to the data and prepares a result that can be retrieved.
- Transform: Modifies data as it passes through.
- Writable: The end of a pipeline of streams that stores data from the stream.
@datastream/string- stringLengthStream [PassThrough]
- stringCountStream [PassThrough]
- stringMinimumFirstChunkSizeStream [Transform]
- stringMinimumChunkSizeStream [Transform]
- stringSkipConsecutiveDuplicatesStream [Transform]
- stringReplaceStream [Transform]
- stringSplitStream [Transform]
@datastream/object- objectCountStream [PassThrough]
- objectBatchStream [Transform]
- objectPivotLongToWideStream [Transform]
- objectPivotWideToLongStream [Transform]
- objectKeyValueStream [Transform]
- objectKeyValuesStream [Transform]
- objectKeyJoinStream [Transform]
- objectKeyMapStream [Transform]
- objectValueMapStream [Transform]
- objectPickStream [Transform]
- objectOmitStream [Transform]
- objectFromEntriesStream [Transform]
- objectToEntriesStream [Transform]
- objectSkipConsecutiveDuplicatesStream [Transform]
@datastream/file- fileReadStream [Readable]
- fileWriteStream [Writable]
@datastream/fetch- fetchReadableStream (alias fetchResponseStream) [Readable]
- fetchWritableStream (alias fetchRequestStream) [Writable]
- fetchSetDefaults (set default fetch options)
- fetchRateLimit (one rate-limited request, resolves the
Response)
@datastream/base64- base64EncodeStream [Transform]
- base64DecodeStream [Transform]
@datastream/charset[/{detect,decode,encode}]- charsetDetectStream [PassThrough]
- charsetDecodeStream [Transform]
- charsetEncodeStream [Transform]
@datastream/compress[/{gzip,deflate,brotli,zstd}]- gzipCompressStream [Transform]
- gzipDecompressStream [Transform]
- deflateCompressStream [Transform]
- deflateDecompressStream [Transform]
- brotliCompressStream [Transform]
- brotliDecompressStream [Transform]
- zstdCompressStream [Transform] (Node only)
- zstdDecompressStream [Transform] (Node only)
@datastream/digest- digestStream [PassThrough]
@datastream/csv- csvDetectDelimitersStream [Transform] (decodes bytes to text)
- csvDetectHeaderStream [Transform] (decodes bytes to text, strips the header row)
- csvParseStream [Transform]
- csvRemoveEmptyRowsStream [Transform]
- csvRemoveMalformedRowsStream [Transform]
- csvCoerceValuesStream [Transform]
- csvArrayToObjectStream [Transform]
- csvObjectToArrayStream [Transform]
- csvInjectHeaderStream [Transform]
- csvFormatStream [Transform]
- csvQuotedParser, csvUnquotedParser (parsers for
csvParseStream'sparseroption)
@datastream/json- jsonParseStream [Transform]
- jsonFormatStream [Transform]
- ndjsonParseStream [Transform]
- ndjsonFormatStream [Transform]
@datastream/encrypt- encryptStream [Transform]
- decryptStream [Transform]
- generateEncryptionKey
@datastream/validate- validateStream [Transform]
- transpileSchema (precompile a JSON Schema)
@datastream/arrow- arrowDetectSchemaStream [PassThrough]
- arrowBatchFromArrayStream [Transform]
- arrowBatchFromObjectStream [Transform]
- arrowToArrayStream [Transform]
- arrowToObjectStream [Transform]
@datastream/duckdb- duckdbConnect (connection helper)
- duckdbQueryStream [Readable]
- duckdbAppenderStream [Writable]
- duckdbArrowInsertStream [Writable]
@datastream/indexeddb(browser only)- indexedDBConnect (connection helper)
- indexedDBReadStream [Readable]
- indexedDBWriteStream [Writable]
@datastream/ipfs- ipfsGetStream [Readable]
- ipfsAddStream [Writable]
@datastream/avro(Node only)- avroEncodeStream [Transform]
- avroDecodeStream [Transform]
@datastream/protobuf- protobufEncodeStream [Transform]
- protobufDecodeStream [Transform]
- protobufLengthPrefixFrameStream [Transform]
- protobufLengthPrefixUnframeStream [Transform]
@datastream/schema-registry- confluentFrameStream [Transform]
- confluentUnframeStream [Transform]
- glueFrameStream [Transform]
- glueUnframeStream [Transform]
- confluentSchemaRegistryResolver (cached Confluent REST schema lookup)
@datastream/kafka(Node only)- kafkaConnect (producer/consumer connection helper)
- kafkaConsumeStream [Readable]
- kafkaProduceStream [Writable]
- kafkaCommitStream [Writable]
@datastream/sqlite(Node only)- sqliteQueryStream [Readable]
- sqliteInsertStream [Writable]
@datastream/aws/{cloudwatch-logs,dynamodb,dynamodb-streams,kinesis,lambda,s3,sns,sqs}(Node only; import from the subpath)- awsCloudWatchLogsGetLogEventsStream [Readable]
- awsCloudWatchLogsFilterLogEventsStream [Readable]
- awsCloudWatchLogsPutLogEventsStream [Writable]
- awsDynamoDBQueryStream [Readable]
- awsDynamoDBScanStream [Readable]
- awsDynamoDBExecuteStatementStream [Readable]
- awsDynamoDBGetItemStream [Readable]
- awsDynamoDBPutItemStream [Writable]
- awsDynamoDBDeleteItemStream [Writable]
- awsDynamoDBStreamsGetRecordsStream [Readable]
- awsKinesisGetRecordsStream [Readable]
- awsKinesisPutRecordsStream [Writable]
- awsLambdaReadableStream (alias awsLambdaResponseStream) [Readable]
- awsS3GetObjectStream [Readable]
- awsS3ListObjectsStream [Readable]
- awsS3PutObjectStream [PassThrough]
- awsS3ChecksumStream [PassThrough]
- awsSNSPublishMessageStream [Writable]
- awsSQSReceiveMessageStream [Readable]
- awsSQSSendMessageStream [Writable]
- awsSQSDeleteMessageStream [Writable]
- awsCloudWatchLogsSetClient, awsDynamoDBSetClient, awsDynamoDBStreamsSetClient, awsKinesisSetClient, awsLambdaSetClient, awsS3SetClient, awsSNSSetClient, awsSQSSetClient (replace the default client)
@datastream/aws/msk-iam- awsMskIamMechanism (kafkajs OAUTHBEARER SASL config)
@datastream/aws/glue-schema-registry- awsGlueSchemaRegistryResolver (cached GetSchemaVersion lookup)
- awsGlueSchemaRegistrySetClient (replace the default client)
@datastream/azure/{blob,cosmos,event-hubs,queue,service-bus}(Node only)- azureBlobDownloadStream [Readable]
- azureBlobListStream [Readable]
- azureBlobUploadStream [PassThrough]
- azureCosmosQueryStream [Readable]
- azureCosmosUpsertItemStream [Writable]
- azureCosmosDeleteItemStream [Writable]
- azureEventHubsReceiveEventsStream [Readable]
- azureEventHubsSendEventsStream [Writable]
- azureQueueReceiveMessagesStream [Readable]
- azureQueueSendMessageStream [Writable]
- azureQueueDeleteMessageStream [Writable]
- azureServiceBusReceiveMessagesStream [Readable]
- azureServiceBusSendMessagesStream [Writable]
- azureServiceBusCompleteMessageStream [Writable]
- azureServiceBusAbandonMessageStream [Writable]
- azureServiceBusDeadLetterMessageStream [Writable]
@datastream/azure/event-hubs-kafka- azureEventHubsKafkaMechanism (kafkajs OAUTHBEARER SASL config for Entra ID)
@datastream/gcp/{storage,pubsub,bigquery}(Node only; pass in your own Google Cloud SDK objects)- gcpStorageReadStream [Readable]
- gcpStorageWriteStream [Writable]
- gcpStorageListStream [Readable]
- gcpPubSubPublishStream [Writable]
- gcpPubSubSubscribeStream [Readable]
- gcpPubSubAckStream [Writable]
- gcpPubSubNackStream [Writable]
- gcpBigQueryQueryStream [Readable]
- gcpBigQueryLoadStream [Writable]
- gcpBigQueryInsertStream [Writable]
npm install @datastream/core @datastream/{module}Every package has breaking changes. Upgrade all @datastream/* packages together.
- Node.js 26 or newer is required.
- No default exports. Use named imports:
import { csvParseStream } from '@datastream/csv'. - The web build moved from
index.web.mjsand the/webstreamsubpath to thebrowserexport condition and the/browsersubpath (for example@datastream/core/browser). - Features a platform can't support have no build and no export condition for it.
@datastream/avro,@datastream/aws,@datastream/azure,@datastream/gcp,@datastream/kafka,@datastream/sqliteand@datastream/compress/zstdexport only anodecondition (nodefault), so a browser bundle fails at resolve time (Could not resolve "@datastream/aws/s3") instead of at run time.@datastream/indexeddbexports only abrowsercondition (plus the/browsersubpath), so importing it from Node.js fails withERR_PACKAGE_PATH_NOT_EXPORTED. The browser build of@datastream/compressdoes not export the zstd streams. - Limit options (
maxBufferSize,maxFieldSize,maxInputSize,maxOutputSize,maxMessageSize,maxErrorRows,maxPages,maxBodySize, ...):undefinedmeans the documented default,nullmeans unlimited, and going over a limit throws aRangeError. - Every stream factory name ends in
Stream.
- all packages
- Every package declares
sideEffects, so bundlers drop unused imports. A bareimport "@datastream/compress"no longer starts brotli-wasm.
- Every package declares
- validate
- No longer depends on
ajv-cmd(it usesajvand its plugins directly). If you relied onajv-cmdbeing installed transitively, add it yourself.
- No longer depends on
- avro
avscis a required peer dependency.
- core
pipejoin(streams, onError):onErroris gone. Errors surface on the returned stream.createReadableStreamFromStringandcreateReadableStreamFromArrayBufferare gone. UsecreateReadableStream, which accepts strings, arrays, iterables,ArrayBuffers and typed arrays.shallowClone,deepClone,shallowEqualanddeepEqualare no longer exported.backpressureGaugeis not exported by the browser build.streamTo*maxBufferSizeacceptsnulland throws aRangeError.createWritableStreamtakes a newabort(reason)option.- Node.js
pipeline(): when a stream was created with an already-aborted signal, it rejects with thatAbortError(cause =signal.reason) after tearing down the other streams. Before, it rejected withERR_STREAM_UNABLE_TO_PIPEand theAbortErrorcrashed the process as an uncaught exception. - Node.js
pipeline(): a stream destroyed without an error before it was piped (for example a source ended and then destroyed before flowing) rejects withERR_STREAM_PREMATURE_CLOSEand tears the others down. Before, the pipeline could hang. streamOptions.chunkSizeonly sets the slice size ofcreateReadableStream.- Node.js projects using
"moduleResolution": "bundler"without"customConditions": ["node"]now get the browser types: nobackpressureGauge,streamToBufferresolves aUint8Array, and web streams only. Add"customConditions": ["node"]tocompilerOptions. - Browser types:
DatastreamReadable,DatastreamTransform,DatastreamWritable,DatastreamPassThroughandDatastreamStreamare web streams only. Node.js streams no longer type-check against them. - Node.js types:
isReadableandisWritablenarrow to Node.jsReadableandWritable.
- string
stringReadableStreamis gone. UsecreateReadableStream.- Only the
*Streamnames remain:stringMinimumFirstChunkSizeStream,stringMinimumChunkSizeStream,stringSkipConsecutiveDuplicatesStream. - The
stringCountStreamresult key is nowstringCount.
- object
objectReadableStreamis gone. UsecreateReadableStream.- The
objectCountStreamresult key is nowobjectCount.
- csv
csvArrayToObjectis nowcsvArrayToObjectStream, andcsvObjectToArrayis nowcsvObjectToArrayStream.fieldMaxSizeis nowmaxFieldSize. ThechunkSizeoption is gone.csvCoerceValuesStreamno longer adds a result entry.csvFormatStreamescapes formula-like cells by default (escapeFormulae: true).- Error streams keep at most
maxErrorRowsrow indexes per error (default 1000). Each error also has acountwith the true total. csvDetectDelimitersStreamandcsvDetectHeaderStreamare typed asDatastreamTransform<string | Uint8Array, string>(wasDatastreamPassThrough): they decode bytes to text, and the header stream strips the header row.
- json
- The
ndjsonParseStreamresult key is nowndjsonErrors.jsonParseStreamkeepsjsonErrors. - Error entries have a
count, andmaxErrorRowsdefaults to 1000.
- The
- validate
maxErrorRowsdefaults to 1000 (was unlimited).maxErrorKeys(default 1000) caps the number of distinct errors.
- file, encrypt
fileReadStream,fileWriteStream,encryptStreamanddecryptStreamreturn a Promise in every build. Alwaysawaitthem.- The file
typesextension check ignores case (REPORT.CSVmatches.csv).
- ipfs
node.getandnode.addnow receive a second argument,{ signal }, fromstreamOptions.signal. Custom clients that use a second parameter for something else may need adjusting.
- digest
digestStreamreturns the stream directly in both builds.
- fetch
concurrencymoved fromstreamOptionsto the fetch options.fetchWritableStreamtakes aresultKeyoption (defaultoutput).- New limits:
maxPages(default 10,000),maxBodySize(default 16 MiB),retryAfterMax(default 60 s). - GET, HEAD, OPTIONS, PUT and DELETE requests now retry on 500, 502, 503 and 504 and on network errors (
TypeError), up toretryMaxCount(default 10) with backoff andRetry-After. SetretryMaxCount: 1to fail fast. When the retries run out, aRangeError(fetch <status> <method> <url> max retries (n) exceeded) is thrown instead of anError. - New
timeoutoption (ms, per attempt; no default). - URLs must be
http:orhttps:. Other schemes (file:,data:,blob:,javascript:, …) and unparseable URLs throw aTypeErrorbefore any request. In a browser, relative URLs still resolve against the page; in Node they are rejected.
- compress
- zstd is Node.js only. The browser build does not export the zstd streams, and
@datastream/compress/zstdhas no browser build. - Same TypeScript change as core: without
"customConditions": ["node"], bundler resolution gets the browser types, which have no zstd and returnTransformStream. gzipCompressStreamacceptslevel(overridesquality), matchingdeflateCompressStream.
- zstd is Node.js only. The browser build does not export the zstd streams, and
- kafka
- Node.js only.
signalmoved from thekafkaConsumeStreamoptions tostreamOptions.autoCommitdefaults tofalse. Commit offsets after processing, or passautoCommit: true.kafkaConsumeStream().stop()drains first: it ends the readable, waits for every openkafkaCommitStreamon the same consumer to close (so the final commit runs), and only then callsconsumer.stop(). It hangs if such a commit stream is never ended or destroyed.- Aborting the consume stream's
signalis a hard stop:consumer.stop()runs first and pending offsets are not committed. Usestop()for a graceful shutdown that commits. kafkaCommitStreamkeeps committing after a consumer crash and restart: it resets its per-partition high-water marks onGROUP_JOIN. A custom consumer object now needsevents.GROUP_JOIN(no longerevents.STOP).
- protobuf
protobufDecodeStreammaxOutputSizeis nowmaxMessageSize. It is checked per message.
- schema-registry
glueUnframeStreammaxDecompressedBytesis nowmaxOutputSize, andglueFrameStreamalso takesmaxOutputSize(default 256 MiB for both).- The frame stream result keys are now
confluentFrameSchemaIdandglueFrameSchemaVersionId. The unframe streams keepconfluentSchemaIdandglueSchemaVersionId. - Each chunk must hold exactly one whole frame. Frames split across chunks are not reassembled.
- aws
awsCloudWatchLogsPutLogEventsStream: the rejected-events errorcauseis now{ rejectedLogEventsInfo, rejected: { tooNew, tooOld, expired } }(it used to be the rawrejectedLogEventsInfo). An event with a non-finitetimestampor a non-stringmessagethrows aTypeError.- Batching writers (CloudWatch Logs, DynamoDB put/delete, Kinesis, SNS, SQS send/delete): on destroy or abort, buffered entries are attached to the error as
err.failedMessages. A plaindestroy()with entries buffered now errors where it used to drop them silently. - SNS and SQS batch writes: a
SenderFaultentry no longer fails the batch at once. Those entries are set aside while the other failed entries are retried with backoff, then the stream errors with every entry that still failed inerror.cause. Before, anySenderFaulterrored the stream right away and the retryable entries were not retried. - SNS and SQS entry size is counted the way the service counts it (body plus attribute names, types and values, with no JSON escaping).
awsSQSDeleteMessageStreamonly caps batches at 10 entries. awsDynamoDBSetClient: the unusedtranslateConfigparameter is removed.awsDynamoDBGetItemStream: the undocumentedretryCountoption is removed.awsS3PutObjectStreamdefaultspartSizeto 17,179,870 bytes (about 16.4 MiB, was lib-storage's 5 MiB), so objects can be up to about 160 GiB, and memory use is up topartSize×queueSize(about 66 MiB with lib-storage's defaultqueueSizeof 4).awsS3ChecksumStreamalready used this default, so default checksums now match a default upload. PasspartSize: 5 * 1024 * 1024toawsS3PutObjectStreamfor the old upload behaviour.
- azure
azureEventHubsKafkaMechanismthrows aTypeErrorunlessnamespaceis a valid Azure namespace name: 6-50 letters, digits or hyphens, starting with a letter and ending with a letter or digit. Full host names such asx.servicebus.windows.netare rejected.- Destroying or aborting
azureServiceBusSendMessagesStream,azureEventHubsSendEventsStream,azureCosmosUpsertItemStreamorazureCosmosDeleteItemStreamwhile chunks are buffered errors with the unsent chunks onerr.failedMessages. A plaindestroy()used to close silently. - Cosmos bulk streams build their operations at send time, not write time. This only matters if you change a chunk object after writing it.
- duckdb
duckdbAppenderStreamandduckdbArrowInsertStream(Node.js and browser) each run in one transaction: BEGIN on the first write, COMMIT at the end, ROLLBACK on error or abort. A failed pipeline commits nothing, including a table created fromschema. Before, Node.js could commit rows DuckDB had auto-flushed (every 204,800) and the browser committed every insert or batch.- Write streams fail if the connection already has an open transaction. While a write stream runs, other statements on the same connection run inside its transaction, so use a separate connection for them.
duckdbQueryStreamonly interrupts (Node.js) or cancels (browser) the connection while its own query call is in flight, not when destroyed unread or paused.
- indexeddb
- Browser only. The Node.js stub is gone.
indexedDBReadStreamreads in batches of 100 records, each batch in its own transaction, so the read is no longer a single snapshot: writes made between batches show up in later batches. A missing store or index now errors the stream on the first read, not whenindexedDBReadStream()resolves.
stateDiagram-v2
[*] --> fileRead: path
[*] --> fetchResponse: URL
[*] --> duckdbQuery: SQL
[*] --> sqliteQuery: SQL
[*] --> kafkaConsume: topic
[*] --> createReadable: string
[*] --> createReadable: string[]
[*] --> createReadable: object[]
[*] --> createReadable: blob
readable --> charsetDetect: binary
charsetDetect --> [*]
readable --> decryption
decryption --> passThroughBuffer: buffer
readable --> decompression
decompression --> passThroughBuffer: buffer
passThroughBuffer --> charsetDecode: buffer
charsetDecode --> passThroughString: string
passThroughString --> parse: string
parse --> validate: object
validate --> passThroughObject: object
passThroughObject --> transform: object
transform --> format: object
format --> charsetEncode: string
charsetEncode --> compression: buffer
compression --> writable: buffer
charsetEncode --> encryption: buffer
encryption --> writable: buffer
state readable {
fileRead
fetchResponse
duckdbQuery
sqliteQuery
kafkaConsume
createReadable
awsS3Get
awsDynamoDBQuery
awsDynamoDBScan
awsDynamoDBGet
}
state decompression {
brotliDecompress
gzipDecompress
deflateDecompress
zstdDecompress
}
state decryption {
decrypt
}
state parse {
csvParse
jsonParse
ndjsonParse
}
state passThroughBuffer {
digest
}
state passThroughString {
stringLength
stringCount
}
state passThroughObject {
objectCount
}
state transform {
objectBatch
objectPivotLongToWide
objectPivotWideToLong
objectKeyValue
objectKeyValues
}
state format {
csvFormat
jsonFormat
ndjsonFormat
}
state compression {
brotliCompress
gzipCompress
deflateCompress
zstdCompress
}
state encryption {
encrypt
}
state writable {
fileWrite
fetchRequest
duckdbAppender
sqliteInsert
kafkaProduce
awsS3Put
awsDynamoDBPut
awsDynamoDBDelete
}
writable --> [*]
More complete, runnable examples are on datastream.js.org.
Read a gzipped CSV file, validate the rows, rename the columns, then insert them into SQLite or DuckDB.
- fileReadStream
- gzipDecompressStream
- digestStream
- charsetDecodeStream (only for files that are not UTF-8)
- csvDetectDelimitersStream
- csvDetectHeaderStream
- csvParseStream
- csvArrayToObjectStream
- objectCountStream
- validateStream
- objectKeyMapStream (for example
{ FirstName: 'first_name' }) - sqliteInsertStream, or duckdbAppenderStream followed by
COPY ... TO 'out.parquet' (FORMAT parquet)for a Parquet file (example)
<input type="file">(file.stream())- digestStream
- charsetDetectStream
- jsonParseStream
- validateStream
<input type="file">(file.stream())- gzipCompressStream (or brotliCompressStream, which needs the optional
brotli-wasmpeer dependency in the browser) - fetchWritableStream (streaming request bodies need browser support for fetch upload streaming)
- fetchReadableStream
- protobufLengthPrefixUnframeStream
- protobufDecodeStream
- ndjsonFormatStream
Licensed under MIT License. Copyright (c) 2026 will Farrell and contributors.