@gjsify/stream 0.4.0 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,156 +0,0 @@
1
- // finished + addAbortSignal + is* helpers.
2
- //
3
- // Reference: refs/node/lib/internal/streams/end-of-stream.js
4
- // refs/node/lib/internal/streams/utils.js
5
- // Reimplemented for GJS using @gjsify/utils microtask scheduling.
6
-
7
- import { queueMicrotask } from '@gjsify/utils';
8
- import type { FinishedOptions } from 'node:stream';
9
-
10
- import { Stream_ } from '../stream-base.js';
11
- import type { Readable_ } from '../readable.js';
12
- import type { Writable_ } from '../writable.js';
13
-
14
- type AnyStream = Stream_ | Readable_ | Writable_;
15
-
16
- export function finished(stream: AnyStream, callback: (err?: Error | null) => void): () => void;
17
- export function finished(stream: AnyStream, opts: FinishedOptions, callback: (err?: Error | null) => void): () => void;
18
- export function finished(stream: AnyStream, optsOrCb: FinishedOptions | ((err?: Error | null) => void), callback?: (err?: Error | null) => void): () => void {
19
- let cb: (err?: Error | null) => void;
20
-
21
- if (typeof optsOrCb === 'function') {
22
- cb = optsOrCb;
23
- } else {
24
- // opts not currently consumed — Node uses it for error/readable/writable filters.
25
- cb = callback!;
26
- }
27
-
28
- let called = false;
29
- function done(err?: Error | null) {
30
- if (!called) {
31
- called = true;
32
- cb(err);
33
- }
34
- }
35
-
36
- const onFinish = () => done();
37
- const onEnd = () => done();
38
- const onError = (err: Error) => done(err);
39
- const onClose = () => {
40
- const w = stream as Writable_;
41
- const r = stream as Readable_;
42
- if (!w.writableFinished && !r.readableEnded) {
43
- done(new Error('premature close'));
44
- }
45
- };
46
-
47
- stream.on('finish', onFinish);
48
- stream.on('end', onEnd);
49
- stream.on('error', onError);
50
- stream.on('close', onClose);
51
-
52
- // Check initial state — handle already-finished/destroyed streams
53
- // Reference: refs/node/lib/internal/streams/end-of-stream.js lines 228-249
54
- const s = stream as unknown as Record<string, unknown>;
55
- const isWritableStream = typeof (stream as Writable_).write === 'function';
56
- const isReadableStream = typeof (stream as Readable_).read === 'function';
57
- const writableFinished = s.writableFinished === true;
58
- const readableEnded = s.readableEnded === true;
59
- const destroyed = s.destroyed === true;
60
-
61
- if (destroyed) {
62
- const storedErr = s._err as Error | null | undefined;
63
- if (storedErr) {
64
- // Stream was destroyed with an error (may have fired before we registered listener)
65
- queueMicrotask(() => done(storedErr));
66
- } else if ((isWritableStream && writableFinished) || (isReadableStream && readableEnded)) {
67
- // Stream was destroyed after completing normally — treat as success
68
- queueMicrotask(() => done());
69
- } else {
70
- // Stream was destroyed without completing — premature close
71
- queueMicrotask(() => done(new Error('premature close')));
72
- }
73
- } else if (isWritableStream && !isReadableStream && writableFinished) {
74
- queueMicrotask(() => done());
75
- } else if (!isWritableStream && isReadableStream && readableEnded) {
76
- queueMicrotask(() => done());
77
- } else if (isWritableStream && isReadableStream && writableFinished && readableEnded) {
78
- queueMicrotask(() => done());
79
- }
80
-
81
- return function cleanup() {
82
- stream.removeListener('finish', onFinish);
83
- stream.removeListener('end', onEnd);
84
- stream.removeListener('error', onError);
85
- stream.removeListener('close', onClose);
86
- };
87
- }
88
-
89
- export function addAbortSignal<T extends AnyStream>(signal: AbortSignal, stream: T): T {
90
- if (!(signal instanceof AbortSignal)) {
91
- throw new TypeError('The first argument must be an AbortSignal');
92
- }
93
- if (!(stream instanceof Stream_)) {
94
- throw new TypeError('The second argument must be a Stream');
95
- }
96
-
97
- const destroyable = stream as Readable_ | Writable_;
98
-
99
- if (signal.aborted) {
100
- destroyable.destroy(new Error('The operation was aborted'));
101
- } else {
102
- const onAbort = () => {
103
- destroyable.destroy(new Error('The operation was aborted'));
104
- };
105
- signal.addEventListener('abort', onAbort, { once: true });
106
- // Cleanup when stream closes
107
- stream.once('close', () => {
108
- signal.removeEventListener('abort', onAbort);
109
- });
110
- }
111
-
112
- return stream;
113
- }
114
-
115
- // ---- Utility functions ----
116
-
117
- export function isReadable(stream: unknown): boolean {
118
- if (stream == null) return false;
119
- const s = stream as Record<string, unknown>;
120
- if (typeof s.readable !== 'boolean') return false;
121
- if (typeof s.read !== 'function') return false;
122
- if (s.destroyed === true) return false;
123
- if (s.readableEnded === true) return false;
124
- return s.readable === true;
125
- }
126
-
127
- export function isWritable(stream: unknown): boolean {
128
- if (stream == null) return false;
129
- const s = stream as Record<string, unknown>;
130
- if (typeof s.writable !== 'boolean') return false;
131
- if (typeof s.write !== 'function') return false;
132
- if (s.destroyed === true) return false;
133
- if (s.writableEnded === true) return false;
134
- return s.writable === true;
135
- }
136
-
137
- export function isDestroyed(stream: unknown): boolean {
138
- if (stream == null) return false;
139
- return (stream as Record<string, unknown>).destroyed === true;
140
- }
141
-
142
- export function isDisturbed(stream: unknown): boolean {
143
- if (stream == null) return false;
144
- const s = stream as Record<string, unknown>;
145
- // A stream is disturbed if data has been read from it
146
- return s.readableDidRead === true || (s.readableFlowing !== null && s.readableFlowing !== undefined);
147
- }
148
-
149
- export function isErrored(stream: unknown): boolean {
150
- if (stream == null) return false;
151
- // Check for errored state on either side
152
- const s = stream as Record<string, unknown>;
153
- if (s.destroyed === true && typeof s.readable === 'boolean' && s.readable === false) return true;
154
- if (s.destroyed === true && typeof s.writable === 'boolean' && s.writable === false) return true;
155
- return false;
156
- }
package/src/utils/pipe.ts DELETED
@@ -1,113 +0,0 @@
1
- // Free-function pipe helper used by Stream_.pipe.
2
- //
3
- // Reference: refs/node/lib/internal/streams/legacy.js Stream.prototype.pipe.
4
- // Reimplemented for GJS — extracted so stream-base.ts (Stream_) can stay
5
- // dependency-free of the per-class files. The Readable_ instance check is
6
- // resolved lazily at call time, breaking what would otherwise be a top-level
7
- // import cycle (stream-base → pipe → readable → stream-base).
8
-
9
- import { _setPipeImpl, type Stream_ } from '../stream-base.js';
10
- import { Readable_ } from '../readable.js';
11
- import type { Writable_ } from '../writable.js';
12
- import type { StreamLike } from '../internal/types.js';
13
-
14
- /** Tracked pipe destination for unpipe support. */
15
- export interface PipeState {
16
- dest: Writable_;
17
- cleanup: () => void;
18
- }
19
-
20
- export function pipe<T extends Writable_>(
21
- sourceStream: Stream_,
22
- destination: T,
23
- options?: { end?: boolean },
24
- ): T {
25
- // The source is conceptually Readable-shaped; the legacy Stream signature
26
- // allows any EventEmitter that emits 'data'/'end'/'close', so we keep the
27
- // structural cast and only branch on the modern Readable_ check below.
28
- const source = sourceStream as unknown as Readable_;
29
- const doEnd = options?.end !== false;
30
-
31
- // Drain listener is added lazily only when backpressure occurs.
32
- let drainListenerAdded = false;
33
- const ondrain = () => {
34
- drainListenerAdded = false;
35
- destination.removeListener('drain', ondrain);
36
- const s = source as StreamLike;
37
- if (typeof s.resume === 'function') {
38
- s.resume();
39
- }
40
- };
41
-
42
- const ondata = (chunk: unknown) => {
43
- if (destination.writable) {
44
- const s = source as StreamLike;
45
- if (destination.write(chunk) === false && typeof s.pause === 'function') {
46
- s.pause();
47
- if (!drainListenerAdded) {
48
- drainListenerAdded = true;
49
- destination.on('drain', ondrain);
50
- }
51
- }
52
- }
53
- };
54
-
55
- source.on('data', ondata);
56
-
57
- let didEnd = false;
58
-
59
- const onend = () => {
60
- if (didEnd) return;
61
- didEnd = true;
62
- if (doEnd) destination.end();
63
- };
64
-
65
- const onclose = () => {
66
- if (didEnd) return;
67
- didEnd = true;
68
- if (doEnd) {
69
- // Modern Readable streams (Readable_) do NOT destroy dest on source close —
70
- // only call dest.end/destroy for legacy Stream objects (no Readable_ prototype).
71
- if (!(source instanceof Readable_)) {
72
- const d = destination as unknown as { destroy?: () => void };
73
- if (typeof d.destroy === 'function') {
74
- d.destroy();
75
- }
76
- }
77
- }
78
- };
79
-
80
- if (doEnd) {
81
- source.on('end', onend);
82
- source.on('close', onclose);
83
- }
84
-
85
- const cleanup = () => {
86
- source.removeListener('data', ondata);
87
- if (drainListenerAdded) destination.removeListener('drain', ondrain);
88
- source.removeListener('end', onend);
89
- source.removeListener('close', onclose);
90
- // Self-remove from both end and close
91
- source.removeListener('end', cleanup);
92
- source.removeListener('close', cleanup);
93
- destination.removeListener('close', cleanup);
94
- };
95
-
96
- source.on('end', cleanup);
97
- source.on('close', cleanup);
98
- destination.on('close', cleanup);
99
-
100
- // Track piped destinations for unpipe
101
- if (source instanceof Readable_) {
102
- source._pipeDests.push({ dest: destination, cleanup });
103
- source._readableState.pipes.push(destination);
104
- }
105
-
106
- destination.emit('pipe', source);
107
- return destination;
108
- }
109
-
110
- // Wire the implementation back into Stream_.prototype.pipe — see _setPipeImpl
111
- // in ../stream-base.ts for why this is done at module-load time rather than
112
- // via a static top-level import.
113
- _setPipeImpl(pipe);
@@ -1,59 +0,0 @@
1
- // pipeline — chain streams and propagate destroy on error.
2
- //
3
- // Reference: refs/node/lib/internal/streams/pipeline.js
4
- // Reimplemented for GJS — minimal, callback-based variant. The promise form
5
- // lives under `@gjsify/stream/promises` and wraps this.
6
-
7
- import type { Stream_ } from '../stream-base.js';
8
- import type { Writable_ } from '../writable.js';
9
-
10
- export type PipelineCallback = (err: Error | null) => void;
11
-
12
- /** A stream that can be destroyed (duck-typed for pipeline). */
13
- export interface DestroyableStream extends Stream_ {
14
- destroy?(error?: Error): void;
15
- }
16
-
17
- export function pipeline(...args: [...streams: DestroyableStream[], callback: PipelineCallback] | DestroyableStream[]): DestroyableStream {
18
- const argList = args as Array<DestroyableStream | PipelineCallback>;
19
- const last = argList[argList.length - 1];
20
- const callback = typeof last === 'function' ? (argList.pop() as PipelineCallback) : undefined;
21
- const streams = argList as DestroyableStream[];
22
-
23
- if (streams.length < 2) {
24
- throw new Error('pipeline requires at least 2 streams');
25
- }
26
-
27
- let error: Error | null = null;
28
-
29
- function onError(err: Error) {
30
- if (!error) {
31
- error = err;
32
- // Destroy all streams
33
- for (const stream of streams) {
34
- if (typeof stream.destroy === 'function') {
35
- stream.destroy();
36
- }
37
- }
38
- if (callback) callback(err);
39
- }
40
- }
41
-
42
- // Pipe streams together
43
- let current: Stream_ = streams[0];
44
- for (let i = 1; i < streams.length; i++) {
45
- const next = streams[i];
46
- current.pipe(next as unknown as Writable_);
47
- current.on('error', onError);
48
- current = next;
49
- }
50
-
51
- // Listen for end on last stream
52
- const lastStream = streams[streams.length - 1];
53
- lastStream.on('error', onError);
54
- lastStream.on('finish', () => {
55
- if (callback && !error) callback(null);
56
- });
57
-
58
- return lastStream;
59
- }
package/src/web/index.ts DELETED
@@ -1,32 +0,0 @@
1
- // stream/web — Re-export WHATWG Streams API
2
- // Uses @gjsify/web-streams polyfill (provides native on Node.js, polyfill on GJS)
3
-
4
- export {
5
- ReadableStream,
6
- WritableStream,
7
- TransformStream,
8
- ByteLengthQueuingStrategy,
9
- CountQueuingStrategy,
10
- TextEncoderStream,
11
- TextDecoderStream,
12
- } from '@gjsify/web-streams';
13
-
14
- import {
15
- ReadableStream,
16
- WritableStream,
17
- TransformStream,
18
- ByteLengthQueuingStrategy,
19
- CountQueuingStrategy,
20
- TextEncoderStream,
21
- TextDecoderStream,
22
- } from '@gjsify/web-streams';
23
-
24
- export default {
25
- ReadableStream,
26
- WritableStream,
27
- TransformStream,
28
- ByteLengthQueuingStrategy,
29
- CountQueuingStrategy,
30
- TextEncoderStream,
31
- TextDecoderStream,
32
- } as Record<string, any>;