diff --git a/index.d.ts b/index.d.ts index 17a159d..a7398cf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,6 +6,15 @@ import { Transform as TransformStream, } from 'node:stream'; +export type Options = { + /** + When this option is `true`, the method returns `false` if the stream has already been closed. + + @default: `false` with `isStream()`, `true` with the other methods + */ + checkOpen?: boolean; +}; + /** @returns Whether `stream` is a [`Stream`](https://nodejs.org/api/stream.html#stream_stream). @@ -21,7 +30,7 @@ isStream({}); //=> false ``` */ -export function isStream(stream: unknown): stream is Stream; +export function isStream(stream: unknown, options?: Options): stream is Stream; /** @returns Whether `stream` is a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable), an [`http.OutgoingMessage`](https://nodejs.org/api/http.html#class-httpoutgoingmessage), an [`http.ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) or an [`http.ClientRequest`](https://nodejs.org/api/http.html#class-httpserverresponse). @@ -35,7 +44,7 @@ isWritableStream(fs.createWriteStrem('unicorn.txt')); //=> true ``` */ -export function isWritableStream(stream: unknown): stream is WritableStream; +export function isWritableStream(stream: unknown, options?: Options): stream is WritableStream; /** @returns Whether `stream` is a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable) or an [`http.IncomingMessage`](https://nodejs.org/api/http.html#class-httpincomingmessage). @@ -49,7 +58,7 @@ isReadableStream(fs.createReadStream('unicorn.png')); //=> true ``` */ -export function isReadableStream(stream: unknown): stream is ReadableStream; +export function isReadableStream(stream: unknown, options?: Options): stream is ReadableStream; /** @returns Whether `stream` is a [`stream.Duplex`](https://nodejs.org/api/stream.html#stream_class_stream_duplex). @@ -63,7 +72,7 @@ isDuplexStream(new DuplexStream()); //=> true ``` */ -export function isDuplexStream(stream: unknown): stream is DuplexStream; +export function isDuplexStream(stream: unknown, options?: Options): stream is DuplexStream; /** @returns Whether `stream` is a [`stream.Transform`](https://nodejs.org/api/stream.html#stream_class_stream_transform). @@ -78,4 +87,4 @@ isTransformStream(StringifyStream()); //=> true ``` */ -export function isTransformStream(stream: unknown): stream is TransformStream; +export function isTransformStream(stream: unknown, options?: Options): stream is TransformStream; diff --git a/index.js b/index.js index 29a552f..d51dce2 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,13 @@ -export function isStream(stream) { +export function isStream(stream, {checkOpen = true} = {}) { return stream !== null && typeof stream === 'object' + && (stream.writable || stream.readable || !checkOpen || (stream.writable === undefined && stream.readable === undefined)) && typeof stream.pipe === 'function'; } -export function isWritableStream(stream) { - return isStream(stream) - && stream.writable !== false +export function isWritableStream(stream, {checkOpen = true} = {}) { + return isStream(stream, {checkOpen}) + && (stream.writable || !checkOpen) && typeof stream.write === 'function' && typeof stream.end === 'function' && typeof stream.writable === 'boolean' @@ -15,9 +16,9 @@ export function isWritableStream(stream) { && typeof stream.destroyed === 'boolean'; } -export function isReadableStream(stream) { - return isStream(stream) - && stream.readable !== false +export function isReadableStream(stream, {checkOpen = true} = {}) { + return isStream(stream, {checkOpen}) + && (stream.readable || !checkOpen) && typeof stream.read === 'function' && typeof stream.readable === 'boolean' && typeof stream.readableObjectMode === 'boolean' @@ -25,12 +26,12 @@ export function isReadableStream(stream) { && typeof stream.destroyed === 'boolean'; } -export function isDuplexStream(stream) { - return isWritableStream(stream) - && isReadableStream(stream); +export function isDuplexStream(stream, options) { + return isWritableStream(stream, options) + && isReadableStream(stream, options); } -export function isTransformStream(stream) { - return isDuplexStream(stream) +export function isTransformStream(stream, options) { + return isDuplexStream(stream, options) && typeof stream._transform === 'function'; } diff --git a/index.test-d.ts b/index.test-d.ts index cab00b2..a0990be 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -5,13 +5,14 @@ import { Duplex as DuplexStream, Transform as TransformStream, } from 'node:stream'; -import {expectAssignable} from 'tsd'; +import {expectType, expectAssignable} from 'tsd'; import { isStream, isWritableStream, isReadableStream, isDuplexStream, isTransformStream, + Options, } from './index.js'; const foo = ''; @@ -35,3 +36,16 @@ if (isDuplexStream(foo)) { if (isTransformStream(foo)) { expectAssignable(foo); } + +isStream(foo, {}); +isStream(foo, {checkOpen: false}); +isWritableStream(foo, {}); +isWritableStream(foo, {checkOpen: false}); +isReadableStream(foo, {}); +isReadableStream(foo, {checkOpen: false}); +isDuplexStream(foo, {}); +isDuplexStream(foo, {checkOpen: false}); +isTransformStream(foo, {}); +isTransformStream(foo, {checkOpen: false}); + +expectType({} as Options['checkOpen']); diff --git a/readme.md b/readme.md index 2ea18aa..ecdf94d 100644 --- a/readme.md +++ b/readme.md @@ -23,26 +23,35 @@ isStream({}); ## API -### isStream(stream) +### isStream(stream, options?) Returns a `boolean` for whether it's a [`Stream`](https://nodejs.org/api/stream.html#stream_stream). -#### isWritableStream(stream) +### isWritableStream(stream, options?) Returns a `boolean` for whether it's a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable), an [`http.OutgoingMessage`](https://nodejs.org/api/http.html#class-httpoutgoingmessage), an [`http.ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) or an [`http.ClientRequest`](https://nodejs.org/api/http.html#class-httpserverresponse). -#### isReadableStream(stream) +### isReadableStream(stream, options?) Returns a `boolean` for whether it's a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable) or an [`http.IncomingMessage`](https://nodejs.org/api/http.html#class-httpincomingmessage). -#### isDuplexStream(stream) +### isDuplexStream(stream, options?) Returns a `boolean` for whether it's a [`stream.Duplex`](https://nodejs.org/api/stream.html#stream_class_stream_duplex). -#### isTransformStream(stream) +### isTransformStream(stream, options?) Returns a `boolean` for whether it's a [`stream.Transform`](https://nodejs.org/api/stream.html#stream_class_stream_transform). +### Options + +#### checkOpen + +Type: `boolean`\ +Default: `false` with [`isStream()`](#isstreamstream-options), `true` with the other methods + +When this option is `true`, the method returns `false` if the stream has already been closed. + ## Related - [is-file-stream](https://github.com/jamestalmage/is-file-stream) - Detect if a stream is a file stream diff --git a/test.js b/test.js index 2f78f35..e7b1a60 100644 --- a/test.js +++ b/test.js @@ -111,3 +111,35 @@ test('isTransformStream()', t => { t.false(isTransformStream(undefined)); t.false(isTransformStream('')); }); + +// eslint-disable-next-line max-params +const testStreamOpen = (t, stream, checkMethod, expectedResult, options) => { + t.true(checkMethod(stream, options)); + stream.destroy(); + t.is(checkMethod(stream, options), expectedResult); +}; + +test('isStream(readable), no options', testStreamOpen, new Stream.Readable(), isStream, false); +test('isStream(readable, {})', testStreamOpen, new Stream.Readable(), isStream, false, {}); +test('isStream(readable, {checkOpen: true})', testStreamOpen, new Stream.Readable(), isStream, false, {checkOpen: true}); +test('isStream(readable, {checkOpen: false})', testStreamOpen, new Stream.Readable(), isStream, true, {checkOpen: false}); +test('isStream(writable), no options', testStreamOpen, new Stream.Writable(), isStream, false); +test('isStream(writable, {})', testStreamOpen, new Stream.Writable(), isStream, false, {}); +test('isStream(writable, {checkOpen: true})', testStreamOpen, new Stream.Writable(), isStream, false, {checkOpen: true}); +test('isStream(writable, {checkOpen: false})', testStreamOpen, new Stream.Writable(), isStream, true, {checkOpen: false}); +test('isWritableStream(writable), no options', testStreamOpen, new Stream.Writable(), isWritableStream, false); +test('isWritableStream(writable, {})', testStreamOpen, new Stream.Writable(), isWritableStream, false, {}); +test('isWritableStream(writable, {checkOpen: true})', testStreamOpen, new Stream.Writable(), isWritableStream, false, {checkOpen: true}); +test('isWritableStream(writable, {checkOpen: false})', testStreamOpen, new Stream.Writable(), isWritableStream, true, {checkOpen: false}); +test('isReadableStream(readable), no options', testStreamOpen, new Stream.Readable(), isReadableStream, false); +test('isReadableStream(readable, {})', testStreamOpen, new Stream.Readable(), isReadableStream, false, {}); +test('isReadableStream(readable, {checkOpen: true})', testStreamOpen, new Stream.Readable(), isReadableStream, false, {checkOpen: true}); +test('isReadableStream(readable, {checkOpen: false})', testStreamOpen, new Stream.Readable(), isReadableStream, true, {checkOpen: false}); +test('isDuplexStream(duplex), no options', testStreamOpen, new Stream.Duplex(), isDuplexStream, false); +test('isDuplexStream(duplex, {})', testStreamOpen, new Stream.Duplex(), isDuplexStream, false, {}); +test('isDuplexStream(duplex, {checkOpen: true})', testStreamOpen, new Stream.Duplex(), isDuplexStream, false, {checkOpen: true}); +test('isDuplexStream(duplex, {checkOpen: false})', testStreamOpen, new Stream.Duplex(), isDuplexStream, true, {checkOpen: false}); +test('isTransformStream(transform), no options', testStreamOpen, new Stream.Transform(), isTransformStream, false); +test('isTransformStream(transform, {})', testStreamOpen, new Stream.Transform(), isTransformStream, false, {}); +test('isTransformStream(transform, {checkOpen: true})', testStreamOpen, new Stream.Transform(), isTransformStream, false, {checkOpen: true}); +test('isTransformStream(transform, {checkOpen: false})', testStreamOpen, new Stream.Transform(), isTransformStream, true, {checkOpen: false});