@gjsify/stream 0.3.20 → 0.4.0
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.
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/consumers/index.js +1 -1
- package/lib/esm/duplex.js +1 -0
- package/lib/esm/index.js +1 -1
- package/lib/esm/internal/state.js +1 -0
- package/lib/esm/internal/types.js +0 -0
- package/lib/esm/passthrough.js +1 -0
- package/lib/esm/promises/index.js +1 -1
- package/lib/esm/readable.js +1 -0
- package/lib/esm/stream-base.js +1 -0
- package/lib/esm/transform.js +1 -0
- package/lib/esm/utils/finished.js +1 -0
- package/lib/esm/utils/pipe.js +1 -0
- package/lib/esm/utils/pipeline.js +1 -0
- package/lib/esm/writable.js +1 -0
- package/lib/types/duplex.d.ts +42 -0
- package/lib/types/index.d.ts +15 -190
- package/lib/types/internal/state.d.ts +4 -0
- package/lib/types/internal/types.d.ts +21 -0
- package/lib/types/passthrough.d.ts +5 -0
- package/lib/types/readable.d.ts +73 -0
- package/lib/types/stream-base.d.ts +26 -0
- package/lib/types/transform.d.ts +11 -0
- package/lib/types/utils/finished.d.ts +14 -0
- package/lib/types/utils/pipe.d.ts +10 -0
- package/lib/types/utils/pipeline.d.ts +7 -0
- package/lib/types/writable.d.ts +47 -0
- package/package.json +6 -6
- package/src/callable.spec.ts +39 -0
- package/src/duplex.ts +317 -0
- package/src/index.ts +49 -1633
- package/src/internal/state.ts +34 -0
- package/src/internal/types.ts +31 -0
- package/src/passthrough.ts +19 -0
- package/src/readable.ts +580 -0
- package/src/stream-base.ts +37 -0
- package/src/transform.ts +108 -0
- package/src/utils/finished.ts +156 -0
- package/src/utils/pipe.ts +113 -0
- package/src/utils/pipeline.ts +59 -0
- package/src/writable.ts +398 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/index.ts
CHANGED
|
@@ -1,1643 +1,54 @@
|
|
|
1
1
|
// Reference: Node.js lib/stream.js, lib/internal/streams/*.js
|
|
2
|
-
// Reimplemented for GJS using EventEmitter and microtask scheduling
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
defaultHighWaterMark = value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Validate a named high-water-mark option and throw ERR_INVALID_ARG_VALUE on NaN/non-number. */
|
|
29
|
-
function validateHighWaterMark(name: string, value: unknown): void {
|
|
30
|
-
if (value === undefined) return;
|
|
31
|
-
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
32
|
-
const err = new TypeError(`The value of "${name}" is invalid. Received ${value}`);
|
|
33
|
-
(err as any).code = 'ERR_INVALID_ARG_VALUE';
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ---- Types ----
|
|
39
|
-
|
|
40
|
-
/** Base options accepted by the Stream constructor (superset used by subclass options). */
|
|
41
|
-
export interface StreamOptions {
|
|
42
|
-
highWaterMark?: number;
|
|
43
|
-
objectMode?: boolean;
|
|
44
|
-
signal?: AbortSignal;
|
|
45
|
-
captureRejections?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type { ReadableOptions, WritableOptions, DuplexOptions, TransformOptions, FinishedOptions };
|
|
49
|
-
|
|
50
|
-
// ---- Stream base class ----
|
|
51
|
-
|
|
52
|
-
/** A stream-like emitter that may have `pause` and `resume` methods (duck-typed). */
|
|
53
|
-
interface StreamLike extends EventEmitter {
|
|
54
|
-
pause?(): void;
|
|
55
|
-
resume?(): void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Tracked pipe destination for unpipe support. */
|
|
59
|
-
interface PipeState {
|
|
60
|
-
dest: Writable;
|
|
61
|
-
cleanup: () => void;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
class Stream_ extends EventEmitter {
|
|
65
|
-
constructor(opts?: StreamOptions) {
|
|
66
|
-
super(opts);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
pipe<T extends Writable>(destination: T, options?: { end?: boolean }): T {
|
|
70
|
-
const source = this as unknown as Readable;
|
|
71
|
-
const doEnd = options?.end !== false;
|
|
72
|
-
|
|
73
|
-
// Drain listener is added lazily only when backpressure occurs.
|
|
74
|
-
let drainListenerAdded = false;
|
|
75
|
-
const ondrain = () => {
|
|
76
|
-
drainListenerAdded = false;
|
|
77
|
-
destination.removeListener('drain', ondrain);
|
|
78
|
-
if (typeof (source as StreamLike).resume === 'function') {
|
|
79
|
-
(source as StreamLike).resume!();
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const ondata = (chunk: unknown) => {
|
|
84
|
-
if (destination.writable) {
|
|
85
|
-
if (destination.write(chunk) === false && typeof (source as StreamLike).pause === 'function') {
|
|
86
|
-
(source as StreamLike).pause!();
|
|
87
|
-
if (!drainListenerAdded) {
|
|
88
|
-
drainListenerAdded = true;
|
|
89
|
-
destination.on('drain', ondrain);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
source.on('data', ondata);
|
|
96
|
-
|
|
97
|
-
let didEnd = false;
|
|
98
|
-
|
|
99
|
-
const onend = () => {
|
|
100
|
-
if (didEnd) return;
|
|
101
|
-
didEnd = true;
|
|
102
|
-
if (doEnd) destination.end();
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const onclose = () => {
|
|
106
|
-
if (didEnd) return;
|
|
107
|
-
didEnd = true;
|
|
108
|
-
if (doEnd) {
|
|
109
|
-
// Modern Readable streams (Readable_) do NOT destroy dest on source close —
|
|
110
|
-
// only call dest.end/destroy for legacy Stream objects (no Readable_ prototype).
|
|
111
|
-
if (!(source instanceof Readable) && typeof (destination as any).destroy === 'function') {
|
|
112
|
-
(destination as any).destroy();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
if (doEnd) {
|
|
118
|
-
source.on('end', onend);
|
|
119
|
-
source.on('close', onclose);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const cleanup = () => {
|
|
123
|
-
source.removeListener('data', ondata);
|
|
124
|
-
if (drainListenerAdded) destination.removeListener('drain', ondrain);
|
|
125
|
-
source.removeListener('end', onend);
|
|
126
|
-
source.removeListener('close', onclose);
|
|
127
|
-
// Self-remove from both end and close
|
|
128
|
-
source.removeListener('end', cleanup);
|
|
129
|
-
source.removeListener('close', cleanup);
|
|
130
|
-
destination.removeListener('close', cleanup);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
source.on('end', cleanup);
|
|
134
|
-
source.on('close', cleanup);
|
|
135
|
-
destination.on('close', cleanup);
|
|
136
|
-
|
|
137
|
-
// Track piped destinations for unpipe
|
|
138
|
-
if (source instanceof Readable) {
|
|
139
|
-
source._pipeDests.push({ dest: destination, cleanup });
|
|
140
|
-
(source as any)._readableState.pipes.push(destination);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
destination.emit('pipe', source);
|
|
144
|
-
return destination;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ---- Readable ----
|
|
149
|
-
|
|
150
|
-
class Readable_ extends Stream_ {
|
|
151
|
-
readable = true;
|
|
152
|
-
readableFlowing: boolean | null = null;
|
|
153
|
-
readableLength = 0;
|
|
154
|
-
readableHighWaterMark: number;
|
|
155
|
-
readableEncoding: string | null;
|
|
156
|
-
readableObjectMode: boolean;
|
|
157
|
-
readableEnded = false;
|
|
158
|
-
readableAborted = false;
|
|
159
|
-
destroyed = false;
|
|
160
|
-
|
|
161
|
-
/** @internal Tracked pipe destinations for unpipe. */
|
|
162
|
-
_pipeDests: PipeState[] = [];
|
|
163
|
-
|
|
164
|
-
private _buffer: unknown[] = [];
|
|
165
|
-
_readableState = { ended: false, endEmitted: false, reading: false, constructed: true, highWaterMark: 0, objectMode: false, pipes: [] as any[] };
|
|
166
|
-
private _readablePending = false;
|
|
167
|
-
private _readImpl: ((size: number) => void) | undefined;
|
|
168
|
-
private _destroyImpl: ((error: Error | null, cb: (error?: Error | null) => void) => void) | undefined;
|
|
169
|
-
private _constructImpl: ((cb: (error?: Error | null) => void) => void) | undefined;
|
|
170
|
-
|
|
171
|
-
constructor(opts?: ReadableOptions) {
|
|
172
|
-
super(opts);
|
|
173
|
-
this.readableHighWaterMark = opts?.highWaterMark ?? getDefaultHighWaterMark(opts?.objectMode ?? false);
|
|
174
|
-
this.readableEncoding = opts?.encoding ?? null;
|
|
175
|
-
this.readableObjectMode = opts?.objectMode ?? false;
|
|
176
|
-
this._readableState.highWaterMark = this.readableHighWaterMark;
|
|
177
|
-
this._readableState.objectMode = this.readableObjectMode;
|
|
178
|
-
if (opts?.read) this._readImpl = opts.read;
|
|
179
|
-
if (opts?.destroy) this._destroyImpl = opts.destroy;
|
|
180
|
-
if (opts?.construct) this._constructImpl = opts.construct;
|
|
181
|
-
|
|
182
|
-
// Call _construct if provided via options or overridden by subclass
|
|
183
|
-
const hasConstruct = this._constructImpl || this._construct !== Readable.prototype._construct;
|
|
184
|
-
if (hasConstruct) {
|
|
185
|
-
this._readableState.constructed = false;
|
|
186
|
-
nextTick(() => {
|
|
187
|
-
this._construct((err) => {
|
|
188
|
-
this._readableState.constructed = true;
|
|
189
|
-
if (err) {
|
|
190
|
-
this.destroy(err);
|
|
191
|
-
} else {
|
|
192
|
-
// If data was requested before construct finished, start reading
|
|
193
|
-
if (this.readableFlowing === true) {
|
|
194
|
-
this._flow();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
_construct(callback: (error?: Error | null) => void): void {
|
|
203
|
-
if (this._constructImpl) {
|
|
204
|
-
this._constructImpl.call(this, callback);
|
|
205
|
-
} else {
|
|
206
|
-
callback();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
_read(_size: number): void {
|
|
211
|
-
if (this._readImpl) {
|
|
212
|
-
this._readImpl.call(this, _size);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
read(size?: number): any {
|
|
217
|
-
// Don't read until constructed
|
|
218
|
-
if (!this._readableState.constructed) return null;
|
|
219
|
-
|
|
220
|
-
if (this._buffer.length === 0) {
|
|
221
|
-
if (this._readableState.ended) return null;
|
|
222
|
-
this._readableState.reading = true;
|
|
223
|
-
this._read(size ?? this.readableHighWaterMark);
|
|
224
|
-
this._readableState.reading = false;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (this._buffer.length === 0) return null;
|
|
228
|
-
|
|
229
|
-
if (size === 0) return null;
|
|
230
|
-
|
|
231
|
-
if (this.readableObjectMode) {
|
|
232
|
-
if (size === undefined) {
|
|
233
|
-
const chunk = this._buffer.shift();
|
|
234
|
-
this.readableLength -= 1;
|
|
235
|
-
if (this._readableState.ended && this._buffer.length === 0 && !this._readableState.endEmitted) {
|
|
236
|
-
this._emitEnd();
|
|
237
|
-
}
|
|
238
|
-
return chunk;
|
|
239
|
-
}
|
|
240
|
-
// In objectMode, size means number of objects
|
|
241
|
-
if (size > this.readableLength) return null;
|
|
242
|
-
const chunk = this._buffer.shift();
|
|
243
|
-
this.readableLength -= 1;
|
|
244
|
-
return chunk;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Byte mode: compute total buffered bytes
|
|
248
|
-
if (size !== undefined && size !== null) {
|
|
249
|
-
if (size > this.readableLength) return null;
|
|
250
|
-
// Partial read: extract exactly `size` bytes from buffer
|
|
251
|
-
return this._readBytes(size);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Read all buffered data
|
|
255
|
-
const result = this._buffer.splice(0);
|
|
256
|
-
this.readableLength = 0;
|
|
257
|
-
if (this._readableState.ended && this._buffer.length === 0 && !this._readableState.endEmitted) {
|
|
258
|
-
this._emitEnd();
|
|
259
|
-
}
|
|
260
|
-
if (result.length === 1) return result[0];
|
|
261
|
-
if (result.length === 0) return null;
|
|
262
|
-
// Concatenate: strings with join, buffers with Buffer.concat
|
|
263
|
-
if (typeof result[0] === 'string') return result.join('');
|
|
264
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
265
|
-
return BufCtor?.concat ? BufCtor.concat(result) : result;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** @internal Extract exactly `size` bytes from the internal buffer. */
|
|
269
|
-
private _readBytes(size: number): any {
|
|
270
|
-
let collected = 0;
|
|
271
|
-
const parts: unknown[] = [];
|
|
272
|
-
while (collected < size && this._buffer.length > 0) {
|
|
273
|
-
const chunk = this._buffer[0];
|
|
274
|
-
const chunkLen = (chunk as any).length ?? 1;
|
|
275
|
-
if (collected + chunkLen <= size) {
|
|
276
|
-
// Take the whole chunk
|
|
277
|
-
parts.push(this._buffer.shift()!);
|
|
278
|
-
collected += chunkLen;
|
|
279
|
-
this.readableLength -= chunkLen;
|
|
280
|
-
} else {
|
|
281
|
-
// Split the chunk
|
|
282
|
-
const needed = size - collected;
|
|
283
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
284
|
-
if (BufCtor && BufCtor.isBuffer(chunk)) {
|
|
285
|
-
parts.push((chunk as any).slice(0, needed));
|
|
286
|
-
this._buffer[0] = (chunk as any).slice(needed);
|
|
287
|
-
} else if (typeof chunk === 'string') {
|
|
288
|
-
parts.push(chunk.slice(0, needed));
|
|
289
|
-
this._buffer[0] = chunk.slice(needed);
|
|
290
|
-
} else {
|
|
291
|
-
// Uint8Array or similar
|
|
292
|
-
parts.push((chunk as Uint8Array).slice(0, needed));
|
|
293
|
-
this._buffer[0] = (chunk as Uint8Array).slice(needed);
|
|
294
|
-
}
|
|
295
|
-
this.readableLength -= needed;
|
|
296
|
-
collected += needed;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (parts.length === 1) return parts[0];
|
|
300
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
301
|
-
return BufCtor?.concat ? BufCtor.concat(parts) : parts;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
push(chunk: any, encoding?: string): boolean {
|
|
305
|
-
if (chunk === null) {
|
|
306
|
-
this._readableState.ended = true;
|
|
307
|
-
this.readableEnded = true;
|
|
308
|
-
if (this._buffer.length === 0 && !this._readableState.endEmitted) {
|
|
309
|
-
nextTick(() => this._emitEnd());
|
|
310
|
-
}
|
|
311
|
-
// Emit 'readable' for listeners waiting on EOF with buffered data
|
|
312
|
-
this._scheduleReadable();
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Validate chunk type for non-objectMode streams (Node.js ERR_INVALID_ARG_TYPE).
|
|
317
|
-
// Accept string, Buffer, or any ArrayBufferView — the latter covers Uint8Array
|
|
318
|
-
// and TypedArrays from any realm (GJS vs host bundle), avoiding cross-realm
|
|
319
|
-
// `instanceof Uint8Array` mismatches that would otherwise reject real Buffers.
|
|
320
|
-
if (!this.readableObjectMode) {
|
|
321
|
-
const isValid = typeof chunk === 'string' || ArrayBuffer.isView(chunk);
|
|
322
|
-
if (!isValid) {
|
|
323
|
-
const err = Object.assign(
|
|
324
|
-
new TypeError(`Invalid non-string/buffer chunk type: ${typeof chunk}`),
|
|
325
|
-
{ code: 'ERR_INVALID_ARG_TYPE' }
|
|
326
|
-
);
|
|
327
|
-
nextTick(() => this.emit('error', err));
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this._buffer.push(chunk);
|
|
333
|
-
this.readableLength += this.readableObjectMode ? 1 : (chunk.length ?? 1);
|
|
334
|
-
|
|
335
|
-
// In flowing mode, schedule draining (unless already flowing)
|
|
336
|
-
if (this.readableFlowing && !this._flowing) {
|
|
337
|
-
nextTick(() => this._flow());
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// In non-flowing mode, emit 'readable' to notify data is available
|
|
341
|
-
if (this.readableFlowing !== true) {
|
|
342
|
-
this._scheduleReadable();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return this.readableLength < this.readableHighWaterMark;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/** Emit 'end' followed by 'close' (matches Node.js autoDestroy behavior). */
|
|
349
|
-
private _emitEnd(): void {
|
|
350
|
-
if (this._readableState.endEmitted) return;
|
|
351
|
-
this._readableState.endEmitted = true;
|
|
352
|
-
this.emit('end');
|
|
353
|
-
nextTick(() => this._autoClose());
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/** Override in subclasses to suppress automatic 'close' after 'end'. */
|
|
357
|
-
protected _autoClose(): void {
|
|
358
|
-
this.emit('close');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Schedule a single 'readable' event per microtask cycle (deduplicates multiple pushes). */
|
|
362
|
-
private _scheduleReadable(): void {
|
|
363
|
-
if (this._readablePending || this.listenerCount('readable') === 0) return;
|
|
364
|
-
this._readablePending = true;
|
|
365
|
-
nextTick(() => {
|
|
366
|
-
this._readablePending = false;
|
|
367
|
-
if (!this.destroyed) this.emit('readable');
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
372
|
-
super.on(event, listener);
|
|
373
|
-
// Attaching a 'data' listener switches to flowing mode (like Node.js)
|
|
374
|
-
if (event === 'data' && this.readableFlowing !== false) {
|
|
375
|
-
this.resume();
|
|
376
|
-
}
|
|
377
|
-
// Attaching a 'readable' listener: if data is already buffered, schedule event
|
|
378
|
-
if (event === 'readable' && (this._buffer.length > 0 || this._readableState.ended)) {
|
|
379
|
-
this._scheduleReadable();
|
|
380
|
-
}
|
|
381
|
-
return this;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
unshift(chunk: any): void {
|
|
385
|
-
this._buffer.unshift(chunk);
|
|
386
|
-
this.readableLength += this.readableObjectMode ? 1 : (chunk.length ?? 1);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
setEncoding(encoding: string): this {
|
|
390
|
-
this.readableEncoding = encoding;
|
|
391
|
-
return this;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
pause(): this {
|
|
395
|
-
this.readableFlowing = false;
|
|
396
|
-
this.emit('pause');
|
|
397
|
-
return this;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
resume(): this {
|
|
401
|
-
if (this.readableFlowing !== true) {
|
|
402
|
-
this.readableFlowing = true;
|
|
403
|
-
this.emit('resume');
|
|
404
|
-
// Start flowing: drain buffered data and call _read
|
|
405
|
-
if (this._readableState.constructed) {
|
|
406
|
-
this._flow();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return this;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
private _flowing = false;
|
|
413
|
-
|
|
414
|
-
private _flow(): void {
|
|
415
|
-
if (this.readableFlowing !== true || this._flowing || this.destroyed) return;
|
|
416
|
-
if (!this._readableState.constructed) return;
|
|
417
|
-
this._flowing = true;
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
// Drain buffered data synchronously (like Node.js flow())
|
|
421
|
-
while (this._buffer.length > 0 && this.readableFlowing && !this.destroyed) {
|
|
422
|
-
let chunk = this._buffer.shift()!;
|
|
423
|
-
this.readableLength -= this.readableObjectMode ? 1 : ((chunk as { length?: number }).length ?? 1);
|
|
424
|
-
// Decode to string when setEncoding was called
|
|
425
|
-
if (this.readableEncoding && typeof chunk !== 'string') {
|
|
426
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
427
|
-
if (BufCtor && BufCtor.isBuffer(chunk)) {
|
|
428
|
-
chunk = (chunk as any).toString(this.readableEncoding);
|
|
429
|
-
} else if (chunk instanceof Uint8Array) {
|
|
430
|
-
chunk = new TextDecoder(this.readableEncoding).decode(chunk as Uint8Array);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
this.emit('data', chunk);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (this.destroyed) return;
|
|
437
|
-
|
|
438
|
-
// If ended and buffer drained, emit end
|
|
439
|
-
if (this._readableState.ended && this._buffer.length === 0 && !this._readableState.endEmitted) {
|
|
440
|
-
nextTick(() => this._emitEnd());
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Call _read to get more data (may push synchronously)
|
|
445
|
-
if (!this._readableState.ended && !this._readableState.reading && !this.destroyed) {
|
|
446
|
-
this._readableState.reading = true;
|
|
447
|
-
this._read(this.readableHighWaterMark);
|
|
448
|
-
this._readableState.reading = false;
|
|
449
|
-
}
|
|
450
|
-
} finally {
|
|
451
|
-
this._flowing = false;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// After _read, if new data was pushed, schedule another flow
|
|
455
|
-
if (this._buffer.length > 0 && this.readableFlowing && !this.destroyed) {
|
|
456
|
-
nextTick(() => this._flow());
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
isPaused(): boolean {
|
|
461
|
-
return this.readableFlowing === false;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
unpipe(destination?: Writable): this {
|
|
465
|
-
if (!destination) {
|
|
466
|
-
// Remove all piped destinations
|
|
467
|
-
for (const state of this._pipeDests) {
|
|
468
|
-
state.cleanup();
|
|
469
|
-
state.dest.emit('unpipe', this);
|
|
470
|
-
}
|
|
471
|
-
this._pipeDests = [];
|
|
472
|
-
this._readableState.pipes = [];
|
|
473
|
-
this.readableFlowing = false;
|
|
474
|
-
} else {
|
|
475
|
-
const idx = this._pipeDests.findIndex(s => s.dest === destination);
|
|
476
|
-
if (idx !== -1) {
|
|
477
|
-
const state = this._pipeDests[idx];
|
|
478
|
-
state.cleanup();
|
|
479
|
-
this._pipeDests.splice(idx, 1);
|
|
480
|
-
const pipeIdx = this._readableState.pipes.indexOf(destination);
|
|
481
|
-
if (pipeIdx !== -1) this._readableState.pipes.splice(pipeIdx, 1);
|
|
482
|
-
destination.emit('unpipe', this);
|
|
483
|
-
if (this._pipeDests.length === 0) {
|
|
484
|
-
this.readableFlowing = false;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return this;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
_destroy(error: Error | null, callback: (error?: Error | null) => void): void {
|
|
492
|
-
if (this._destroyImpl) {
|
|
493
|
-
this._destroyImpl.call(this, error, callback);
|
|
494
|
-
} else {
|
|
495
|
-
callback(error ?? undefined);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
destroy(error?: Error): this {
|
|
500
|
-
if (this.destroyed) return this;
|
|
501
|
-
this.destroyed = true;
|
|
502
|
-
this.readable = false;
|
|
503
|
-
this.readableAborted = !this.readableEnded;
|
|
504
|
-
// Store the error so finished() can retrieve it if called after destroy() but before 'error' fires
|
|
505
|
-
if (error) (this as any)._err = error;
|
|
506
|
-
|
|
507
|
-
const cb = (err?: Error | null) => {
|
|
508
|
-
// Emit error and close in separate nextTick calls (matches Node.js behavior)
|
|
509
|
-
// so an unhandled error doesn't prevent 'close' from firing
|
|
510
|
-
if (err) nextTick(() => this.emit('error', err));
|
|
511
|
-
nextTick(() => this.emit('close'));
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// Dispatch virtually ONLY when the user overrode _destroy on the instance
|
|
515
|
-
// (e.g. tests: `stream._destroy = fn`). Do NOT call a subclass prototype
|
|
516
|
-
// _destroy: net.Socket's prototype `_destroy` synchronously cancels in-flight
|
|
517
|
-
// Gio I/O and would break tests that call destroy() during pending writes.
|
|
518
|
-
// The opts.destroy path still runs via _destroyImpl as before.
|
|
519
|
-
if (Object.prototype.hasOwnProperty.call(this, '_destroy')) {
|
|
520
|
-
(this as any)._destroy(error ?? null, cb);
|
|
521
|
-
} else if (this._destroyImpl) {
|
|
522
|
-
this._destroyImpl.call(this, error ?? null, cb);
|
|
523
|
-
} else {
|
|
524
|
-
cb(error);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return this;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Converts this Node.js Readable to a Web ReadableStream.
|
|
532
|
-
* Used by @hono/node-server to bridge Node.js HTTP → Web Standard Request.
|
|
533
|
-
*/
|
|
534
|
-
static toWeb(nodeReadable: Readable_): ReadableStream<Uint8Array> {
|
|
535
|
-
return new ReadableStream({
|
|
536
|
-
start(controller) {
|
|
537
|
-
nodeReadable.on('data', (chunk: unknown) => {
|
|
538
|
-
if (typeof chunk === 'string') {
|
|
539
|
-
controller.enqueue(new TextEncoder().encode(chunk));
|
|
540
|
-
} else if (chunk instanceof Uint8Array) {
|
|
541
|
-
controller.enqueue(chunk);
|
|
542
|
-
} else if (chunk && typeof (chunk as any).length === 'number') {
|
|
543
|
-
controller.enqueue(new Uint8Array(chunk as any));
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
nodeReadable.on('end', () => {
|
|
547
|
-
controller.close();
|
|
548
|
-
});
|
|
549
|
-
nodeReadable.on('error', (err: Error) => {
|
|
550
|
-
controller.error(err);
|
|
551
|
-
});
|
|
552
|
-
},
|
|
553
|
-
cancel() {
|
|
554
|
-
nodeReadable.destroy();
|
|
555
|
-
},
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Creates a Node.js Readable from a Web ReadableStream.
|
|
561
|
-
*/
|
|
562
|
-
static fromWeb(webStream: ReadableStream<Uint8Array>, options?: ReadableOptions): Readable_ {
|
|
563
|
-
const reader = webStream.getReader();
|
|
564
|
-
return new Readable_({
|
|
565
|
-
...options,
|
|
566
|
-
read() {
|
|
567
|
-
reader.read().then(
|
|
568
|
-
({ done, value }) => {
|
|
569
|
-
if (done) {
|
|
570
|
-
this.push(null);
|
|
571
|
-
} else {
|
|
572
|
-
this.push(value);
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
(err) => {
|
|
576
|
-
this.destroy(err);
|
|
577
|
-
},
|
|
578
|
-
);
|
|
579
|
-
},
|
|
580
|
-
destroy(error, callback) {
|
|
581
|
-
reader.cancel(error?.message).then(() => callback(null), callback);
|
|
582
|
-
},
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
[Symbol.asyncIterator](): AsyncIterableIterator<unknown> {
|
|
587
|
-
const readable = this;
|
|
588
|
-
const buffer: unknown[] = [];
|
|
589
|
-
let done = false;
|
|
590
|
-
let error: Error | null = null;
|
|
591
|
-
let waitingResolve: ((value: IteratorResult<unknown>) => void) | null = null;
|
|
592
|
-
let waitingReject: ((reason: unknown) => void) | null = null;
|
|
593
|
-
|
|
594
|
-
readable.on('data', (chunk: unknown) => {
|
|
595
|
-
if (waitingResolve) {
|
|
596
|
-
const resolve = waitingResolve;
|
|
597
|
-
waitingResolve = null;
|
|
598
|
-
waitingReject = null;
|
|
599
|
-
resolve({ value: chunk, done: false });
|
|
600
|
-
} else {
|
|
601
|
-
buffer.push(chunk);
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
readable.on('end', () => {
|
|
606
|
-
done = true;
|
|
607
|
-
if (waitingResolve) {
|
|
608
|
-
const resolve = waitingResolve;
|
|
609
|
-
waitingResolve = null;
|
|
610
|
-
waitingReject = null;
|
|
611
|
-
resolve({ value: undefined, done: true });
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
readable.on('error', (err: Error) => {
|
|
616
|
-
error = err;
|
|
617
|
-
done = true;
|
|
618
|
-
if (waitingReject) {
|
|
619
|
-
const reject = waitingReject;
|
|
620
|
-
waitingResolve = null;
|
|
621
|
-
waitingReject = null;
|
|
622
|
-
reject(err);
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
next(): Promise<IteratorResult<unknown>> {
|
|
628
|
-
if (error) return Promise.reject(error);
|
|
629
|
-
if (buffer.length > 0) return Promise.resolve({ value: buffer.shift(), done: false });
|
|
630
|
-
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
631
|
-
return new Promise((resolve, reject) => {
|
|
632
|
-
waitingResolve = resolve;
|
|
633
|
-
waitingReject = reject;
|
|
634
|
-
});
|
|
635
|
-
},
|
|
636
|
-
return(): Promise<IteratorResult<unknown>> {
|
|
637
|
-
readable.destroy();
|
|
638
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
639
|
-
},
|
|
640
|
-
[Symbol.asyncIterator]() { return this; }
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
static from(iterable: Iterable<unknown> | AsyncIterable<unknown>, opts?: ReadableOptions): Readable {
|
|
645
|
-
const readable = new Readable({
|
|
646
|
-
objectMode: true,
|
|
647
|
-
...opts,
|
|
648
|
-
read() {}
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
// Buffer, Uint8Array, and strings should be pushed as a single chunk,
|
|
652
|
-
// not iterated element-by-element (matching Node.js Readable.from behavior)
|
|
653
|
-
if (typeof iterable === 'string' || ArrayBuffer.isView(iterable)) {
|
|
654
|
-
readable.push(iterable);
|
|
655
|
-
readable.push(null);
|
|
656
|
-
return readable;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
(async () => {
|
|
660
|
-
try {
|
|
661
|
-
for await (const chunk of iterable as AsyncIterable<unknown>) {
|
|
662
|
-
if (!readable.push(chunk)) {
|
|
663
|
-
// Backpressure — wait for drain
|
|
664
|
-
await new Promise<void>(resolve => readable.once('drain', resolve));
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
readable.push(null);
|
|
668
|
-
} catch (err) {
|
|
669
|
-
readable.destroy(err as Error);
|
|
670
|
-
}
|
|
671
|
-
})();
|
|
672
|
-
|
|
673
|
-
return readable;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// ---- Writable ----
|
|
678
|
-
|
|
679
|
-
class Writable_ extends Stream_ {
|
|
680
|
-
// Allow `duplex instanceof Writable` to return true even though Duplex inherits
|
|
681
|
-
// from Readable_ only. Mirrors Node.js: standard prototype-chain check first,
|
|
682
|
-
// then duck-type fallback — but only when checking against Writable itself, not
|
|
683
|
-
// against a user subclass. Because `Writable` is exported as a makeCallable Proxy,
|
|
684
|
-
// `this === Writable_` fails for `obj instanceof Writable`; however the Proxy's
|
|
685
|
-
// default `get` trap forwards `.prototype` straight to the target, so the
|
|
686
|
-
// prototype reference check below correctly recognises both.
|
|
687
|
-
static [Symbol.hasInstance](obj: any): boolean {
|
|
688
|
-
if (typeof (this as any).prototype !== 'undefined' &&
|
|
689
|
-
Object.prototype.isPrototypeOf.call((this as any).prototype, obj)) return true;
|
|
690
|
-
if ((this as any).prototype !== Writable_.prototype) return false;
|
|
691
|
-
return obj !== null && obj !== undefined && typeof obj.writableHighWaterMark === 'number';
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
writable = true;
|
|
695
|
-
writableHighWaterMark: number;
|
|
696
|
-
writableLength = 0;
|
|
697
|
-
writableObjectMode: boolean;
|
|
698
|
-
writableEnded = false;
|
|
699
|
-
writableFinished = false;
|
|
700
|
-
writableCorked = 0;
|
|
701
|
-
writableNeedDrain = false;
|
|
702
|
-
destroyed = false;
|
|
703
|
-
|
|
704
|
-
private _writableState = { ended: false, finished: false, constructed: true, writing: false };
|
|
705
|
-
private _corkedBuffer: Array<{ chunk: any; encoding: string; callback: (error?: Error | null) => void }> = [];
|
|
706
|
-
private _writeBuffer: Array<{ chunk: any; encoding: string; callback: (error?: Error | null) => void }> = [];
|
|
707
|
-
private _pendingConstruct: Array<{ chunk: any; encoding: string; callback: (error?: Error | null) => void }> = [];
|
|
708
|
-
private _ending = false;
|
|
709
|
-
private _endCallback?: () => void;
|
|
710
|
-
private _pendingEnd: { chunk?: any; encoding?: string; callback?: () => void } | null = null;
|
|
711
|
-
private _writeImpl: ((chunk: any, encoding: string, cb: (error?: Error | null) => void) => void) | undefined;
|
|
712
|
-
private _writev: ((chunks: Array<{ chunk: any; encoding: string }>, cb: (error?: Error | null) => void) => void) | undefined;
|
|
713
|
-
private _finalImpl: ((cb: (error?: Error | null) => void) => void) | undefined;
|
|
714
|
-
private _destroyImpl: ((error: Error | null, cb: (error?: Error | null) => void) => void) | undefined;
|
|
715
|
-
private _constructImpl: ((cb: (error?: Error | null) => void) => void) | undefined;
|
|
716
|
-
private _decodeStrings: boolean;
|
|
717
|
-
private _defaultEncoding = 'utf8';
|
|
718
|
-
|
|
719
|
-
constructor(opts?: WritableOptions) {
|
|
720
|
-
super(opts);
|
|
721
|
-
this.writableHighWaterMark = opts?.highWaterMark ?? getDefaultHighWaterMark(opts?.objectMode ?? false);
|
|
722
|
-
this.writableObjectMode = opts?.objectMode ?? false;
|
|
723
|
-
this._decodeStrings = opts?.decodeStrings !== false;
|
|
724
|
-
if (opts?.write) this._writeImpl = opts.write;
|
|
725
|
-
if (opts?.writev) this._writev = opts.writev;
|
|
726
|
-
if (opts?.final) this._finalImpl = opts.final;
|
|
727
|
-
if (opts?.destroy) this._destroyImpl = opts.destroy;
|
|
728
|
-
if (opts?.construct) this._constructImpl = opts.construct;
|
|
729
|
-
|
|
730
|
-
// Call _construct if provided via options or overridden by subclass
|
|
731
|
-
const hasConstruct = this._constructImpl || this._construct !== Writable.prototype._construct;
|
|
732
|
-
if (hasConstruct) {
|
|
733
|
-
this._writableState.constructed = false;
|
|
734
|
-
nextTick(() => {
|
|
735
|
-
this._construct((err) => {
|
|
736
|
-
this._writableState.constructed = true;
|
|
737
|
-
if (err) {
|
|
738
|
-
this.destroy(err);
|
|
739
|
-
} else {
|
|
740
|
-
this._maybeFlush();
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
_construct(callback: (error?: Error | null) => void): void {
|
|
748
|
-
if (this._constructImpl) {
|
|
749
|
-
this._constructImpl.call(this, callback);
|
|
750
|
-
} else {
|
|
751
|
-
callback();
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
|
|
756
|
-
if (this._writeImpl) {
|
|
757
|
-
this._writeImpl.call(this, chunk, encoding, callback);
|
|
758
|
-
} else {
|
|
759
|
-
callback();
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
_final(callback: (error?: Error | null) => void): void {
|
|
764
|
-
if (this._finalImpl) {
|
|
765
|
-
this._finalImpl.call(this, callback);
|
|
766
|
-
} else {
|
|
767
|
-
callback();
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
private _maybeFlush(): void {
|
|
772
|
-
// Flush writes that were buffered while waiting for _construct
|
|
773
|
-
const pending = this._pendingConstruct.splice(0);
|
|
774
|
-
if (pending.length > 0) {
|
|
775
|
-
// First write goes directly, rest get serialized via _writeBuffer
|
|
776
|
-
const [first, ...rest] = pending;
|
|
777
|
-
this._writeBuffer.push(...rest);
|
|
778
|
-
this._doWrite(first.chunk, first.encoding, first.callback);
|
|
779
|
-
}
|
|
780
|
-
if (this._pendingEnd) {
|
|
781
|
-
const { chunk, encoding, callback } = this._pendingEnd;
|
|
782
|
-
this._pendingEnd = null;
|
|
783
|
-
this._doEnd(chunk, encoding, callback);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
private _doWrite(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
|
|
788
|
-
this._writableState.writing = true;
|
|
789
|
-
// Track whether the user's _write called its callback synchronously. When it
|
|
790
|
-
// does (e.g. Writable with a synchronous `write` option), Node drains the
|
|
791
|
-
// buffer on the same tick — tests that issue N sync writes expect N sync
|
|
792
|
-
// `_write` dispatches. Deferring via nextTick here broke that expectation.
|
|
793
|
-
let sync = true;
|
|
794
|
-
this._write(chunk, encoding, (err) => {
|
|
795
|
-
this.writableLength -= this.writableObjectMode ? 1 : (chunk?.length ?? 1);
|
|
796
|
-
if (sync) {
|
|
797
|
-
// Synchronous completion: defer the user callback + 'drain' emit to
|
|
798
|
-
// nextTick (Node semantics — user code must not see its own write
|
|
799
|
-
// return before the callback on the same tick), but drain the buffer
|
|
800
|
-
// synchronously so follow-up writes fire on the same tick.
|
|
801
|
-
nextTick(() => {
|
|
802
|
-
if (err) {
|
|
803
|
-
callback(err);
|
|
804
|
-
this.emit('error', err);
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
callback();
|
|
808
|
-
if (this.writableNeedDrain && this.writableLength <= this.writableHighWaterMark) {
|
|
809
|
-
this.writableNeedDrain = false;
|
|
810
|
-
this.emit('drain');
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
if (!err) this._drainWriteBuffer();
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
// Asynchronous completion — full defer.
|
|
817
|
-
if (err) {
|
|
818
|
-
nextTick(() => {
|
|
819
|
-
callback(err);
|
|
820
|
-
this.emit('error', err);
|
|
821
|
-
this._drainWriteBuffer();
|
|
822
|
-
});
|
|
823
|
-
} else {
|
|
824
|
-
nextTick(() => {
|
|
825
|
-
callback();
|
|
826
|
-
if (this.writableNeedDrain && this.writableLength <= this.writableHighWaterMark) {
|
|
827
|
-
this.writableNeedDrain = false;
|
|
828
|
-
this.emit('drain');
|
|
829
|
-
}
|
|
830
|
-
this._drainWriteBuffer();
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
sync = false;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
private _drainWriteBuffer(): void {
|
|
838
|
-
if (this._writeBuffer.length > 0) {
|
|
839
|
-
const next = this._writeBuffer.shift()!;
|
|
840
|
-
this._doWrite(next.chunk, next.encoding, next.callback);
|
|
841
|
-
} else {
|
|
842
|
-
// Only release the write lock when the buffer is truly empty.
|
|
843
|
-
this._writableState.writing = false;
|
|
844
|
-
this._maybeFinish();
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
private _maybeFinish(): void {
|
|
849
|
-
if (!this._ending || this._writableState.finished || this._writableState.writing || this._writeBuffer.length > 0) return;
|
|
850
|
-
this._ending = false;
|
|
851
|
-
|
|
852
|
-
this._final((err) => {
|
|
853
|
-
this.writableFinished = true;
|
|
854
|
-
this._writableState.finished = true;
|
|
855
|
-
nextTick(() => {
|
|
856
|
-
if (err) {
|
|
857
|
-
this.emit('error', err);
|
|
858
|
-
}
|
|
859
|
-
this.emit('finish');
|
|
860
|
-
nextTick(() => this.emit('close'));
|
|
861
|
-
if (this._endCallback) this._endCallback();
|
|
862
|
-
});
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
|
867
|
-
if (typeof encoding === 'function') {
|
|
868
|
-
callback = encoding;
|
|
869
|
-
encoding = undefined;
|
|
870
|
-
}
|
|
871
|
-
if (encoding === undefined) encoding = this._defaultEncoding;
|
|
872
|
-
callback = callback || (() => {});
|
|
873
|
-
|
|
874
|
-
// Convert strings to Buffer when decodeStrings is true (default), but not in objectMode
|
|
875
|
-
if (this._decodeStrings && !this.writableObjectMode && typeof chunk === 'string') {
|
|
876
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
877
|
-
if (BufCtor) {
|
|
878
|
-
chunk = BufCtor.from(chunk, encoding);
|
|
879
|
-
encoding = 'buffer';
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
// Set encoding to 'buffer' for Buffer/Uint8Array chunks
|
|
883
|
-
if (typeof chunk !== 'string' && !this.writableObjectMode) {
|
|
884
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
885
|
-
if ((BufCtor && BufCtor.isBuffer(chunk)) || chunk instanceof Uint8Array) {
|
|
886
|
-
encoding = 'buffer';
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
if (this.writableEnded) {
|
|
891
|
-
const err = new Error('write after end');
|
|
892
|
-
nextTick(() => {
|
|
893
|
-
if (callback) callback(err);
|
|
894
|
-
this.emit('error', err);
|
|
895
|
-
});
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
this.writableLength += this.writableObjectMode ? 1 : (chunk?.length ?? 1);
|
|
900
|
-
|
|
901
|
-
// If corked, buffer the write
|
|
902
|
-
if (this.writableCorked > 0) {
|
|
903
|
-
this._corkedBuffer.push({ chunk, encoding: encoding as string, callback });
|
|
904
|
-
return this.writableLength < this.writableHighWaterMark;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// If not yet constructed, buffer writes until construction finishes
|
|
908
|
-
if (!this._writableState.constructed) {
|
|
909
|
-
this._pendingConstruct.push({ chunk, encoding: encoding as string, callback });
|
|
910
|
-
return this.writableLength < this.writableHighWaterMark;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Compute backpressure BEFORE _doWrite (sync transforms may decrement length immediately)
|
|
914
|
-
const belowHWM = this.writableLength < this.writableHighWaterMark;
|
|
915
|
-
if (!belowHWM) {
|
|
916
|
-
this.writableNeedDrain = true;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Serialize writes: only one _write at a time, buffer the rest
|
|
920
|
-
if (this._writableState.writing) {
|
|
921
|
-
this._writeBuffer.push({ chunk, encoding: encoding as string, callback });
|
|
922
|
-
} else {
|
|
923
|
-
this._doWrite(chunk, encoding as string, callback);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
return belowHWM;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
private _doEnd(chunk?: any, encoding?: string, callback?: () => void): void {
|
|
930
|
-
if (chunk !== undefined && chunk !== null) {
|
|
931
|
-
this.write(chunk, encoding as string);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
this.writableEnded = true;
|
|
935
|
-
this._writableState.ended = true;
|
|
936
|
-
this._ending = true;
|
|
937
|
-
this._endCallback = callback;
|
|
938
|
-
|
|
939
|
-
// _maybeFinish will call _final once all pending writes have drained
|
|
940
|
-
this._maybeFinish();
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
end(chunk?: any, encoding?: string | (() => void), callback?: () => void): this {
|
|
944
|
-
if (typeof chunk === 'function') {
|
|
945
|
-
callback = chunk;
|
|
946
|
-
chunk = undefined;
|
|
947
|
-
}
|
|
948
|
-
if (typeof encoding === 'function') {
|
|
949
|
-
callback = encoding;
|
|
950
|
-
encoding = undefined;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// Ignore duplicate end() calls (e.g. from auto-end after half-close)
|
|
954
|
-
if (this.writableEnded) {
|
|
955
|
-
if (callback) nextTick(callback);
|
|
956
|
-
return this;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// If not yet constructed, defer end until construction finishes
|
|
960
|
-
if (!this._writableState.constructed) {
|
|
961
|
-
this._pendingEnd = { chunk, encoding: encoding as string, callback };
|
|
962
|
-
return this;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
this._doEnd(chunk, encoding as string, callback);
|
|
966
|
-
|
|
967
|
-
return this;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
cork(): void {
|
|
971
|
-
this.writableCorked++;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
uncork(): void {
|
|
975
|
-
if (this.writableCorked > 0) {
|
|
976
|
-
this.writableCorked--;
|
|
977
|
-
if (this.writableCorked === 0 && this._corkedBuffer.length > 0) {
|
|
978
|
-
this._flushCorkedBuffer();
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
private _flushCorkedBuffer(): void {
|
|
984
|
-
// If _writev is available, flush as a batch
|
|
985
|
-
if (this._writev && this._corkedBuffer.length > 1) {
|
|
986
|
-
const buffered = this._corkedBuffer.splice(0);
|
|
987
|
-
const chunks = buffered.map(b => ({ chunk: b.chunk, encoding: b.encoding }));
|
|
988
|
-
this._writev.call(this, chunks, (err) => {
|
|
989
|
-
for (const b of buffered) {
|
|
990
|
-
this.writableLength -= this.writableObjectMode ? 1 : (b.chunk?.length ?? 1);
|
|
991
|
-
}
|
|
992
|
-
if (err) {
|
|
993
|
-
for (const b of buffered) b.callback(err);
|
|
994
|
-
this.emit('error', err);
|
|
995
|
-
} else {
|
|
996
|
-
for (const b of buffered) b.callback();
|
|
997
|
-
if (this.writableNeedDrain && this.writableLength <= this.writableHighWaterMark) {
|
|
998
|
-
this.writableNeedDrain = false;
|
|
999
|
-
this.emit('drain');
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
} else {
|
|
1004
|
-
// Flush one by one via serialized write path
|
|
1005
|
-
const buffered = this._corkedBuffer.splice(0);
|
|
1006
|
-
if (buffered.length > 0) {
|
|
1007
|
-
const [first, ...rest] = buffered;
|
|
1008
|
-
this._writeBuffer.push(...rest);
|
|
1009
|
-
this._doWrite(first.chunk, first.encoding, first.callback);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
setDefaultEncoding(encoding: string): this {
|
|
1015
|
-
this._defaultEncoding = encoding;
|
|
1016
|
-
return this;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
destroy(error?: Error): this {
|
|
1020
|
-
if (this.destroyed) return this;
|
|
1021
|
-
this.destroyed = true;
|
|
1022
|
-
this.writable = false;
|
|
1023
|
-
// Store the error so finished() can retrieve it if called after destroy() but before 'error' fires
|
|
1024
|
-
if (error) (this as any)._err = error;
|
|
1025
|
-
|
|
1026
|
-
const cb = (err?: Error | null) => {
|
|
1027
|
-
if (err) nextTick(() => this.emit('error', err));
|
|
1028
|
-
nextTick(() => this.emit('close'));
|
|
1029
|
-
};
|
|
1030
|
-
|
|
1031
|
-
if (this._destroyImpl) {
|
|
1032
|
-
this._destroyImpl.call(this, error ?? null, cb);
|
|
1033
|
-
} else {
|
|
1034
|
-
cb(error);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return this;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
// ---- Duplex ----
|
|
1042
|
-
|
|
1043
|
-
class Duplex_ extends Readable_ {
|
|
1044
|
-
writable = true;
|
|
1045
|
-
writableHighWaterMark: number;
|
|
1046
|
-
writableLength = 0;
|
|
1047
|
-
writableObjectMode: boolean;
|
|
1048
|
-
writableEnded = false;
|
|
1049
|
-
writableFinished = false;
|
|
1050
|
-
writableCorked = 0;
|
|
1051
|
-
writableNeedDrain = false;
|
|
1052
|
-
allowHalfOpen: boolean;
|
|
1053
|
-
private _decodeStrings: boolean;
|
|
1054
|
-
|
|
1055
|
-
// Exposed writable-side state (mirrors Node.js _writableState for split HWM tests)
|
|
1056
|
-
_writableState = { highWaterMark: 0, objectMode: false };
|
|
1057
|
-
|
|
1058
|
-
private _duplexCorkedBuffer: Array<{ chunk: any; encoding: string; callback: (error?: Error | null) => void }> = [];
|
|
1059
|
-
// Write serialization — prevents concurrent write_bytes_async calls (GIO_ERROR_PENDING).
|
|
1060
|
-
// Duplex inherits from Readable, not Writable, so it needs its own queue separate from
|
|
1061
|
-
// Writable_._doWrite/_drainWriteBuffer.
|
|
1062
|
-
private _duplexWriting = false;
|
|
1063
|
-
private _duplexWriteQueue: Array<{ chunk: any; encoding: string; callback: (error?: Error | null) => void }> = [];
|
|
1064
|
-
private _writeImpl: ((chunk: any, encoding: string, cb: (error?: Error | null) => void) => void) | undefined;
|
|
1065
|
-
private _finalImpl: ((cb: (error?: Error | null) => void) => void) | undefined;
|
|
1066
|
-
private _defaultEncoding = 'utf8';
|
|
1067
|
-
private _pendingWrites = 0;
|
|
1068
|
-
private _pendingEndCb: (() => void) | null = null;
|
|
1069
|
-
|
|
1070
|
-
constructor(opts?: DuplexOptions) {
|
|
1071
|
-
super(opts);
|
|
1072
|
-
|
|
1073
|
-
validateHighWaterMark('writableHighWaterMark', opts?.writableHighWaterMark);
|
|
1074
|
-
validateHighWaterMark('readableHighWaterMark', opts?.readableHighWaterMark);
|
|
1075
|
-
|
|
1076
|
-
// Writable side: highWaterMark (shared) takes priority over writableHighWaterMark.
|
|
1077
|
-
this.writableObjectMode = opts?.writableObjectMode ?? opts?.objectMode ?? false;
|
|
1078
|
-
this.writableHighWaterMark = opts?.highWaterMark
|
|
1079
|
-
?? opts?.writableHighWaterMark
|
|
1080
|
-
?? getDefaultHighWaterMark(this.writableObjectMode);
|
|
1081
|
-
this._writableState.highWaterMark = this.writableHighWaterMark;
|
|
1082
|
-
this._writableState.objectMode = this.writableObjectMode;
|
|
1083
|
-
|
|
1084
|
-
// Readable side overrides: Readable_ constructor already applied opts.highWaterMark,
|
|
1085
|
-
// so only override with readableHighWaterMark when highWaterMark was NOT set.
|
|
1086
|
-
if (opts?.highWaterMark === undefined && opts?.readableHighWaterMark !== undefined) {
|
|
1087
|
-
this.readableHighWaterMark = opts.readableHighWaterMark;
|
|
1088
|
-
this._readableState.highWaterMark = opts.readableHighWaterMark;
|
|
1089
|
-
}
|
|
1090
|
-
if (opts?.readableObjectMode !== undefined) {
|
|
1091
|
-
this.readableObjectMode = opts.readableObjectMode;
|
|
1092
|
-
this._readableState.objectMode = opts.readableObjectMode;
|
|
1093
|
-
// Re-derive readable HWM for objectMode when neither readableHighWaterMark nor highWaterMark was set.
|
|
1094
|
-
if (opts?.readableHighWaterMark === undefined && opts?.highWaterMark === undefined) {
|
|
1095
|
-
this.readableHighWaterMark = getDefaultHighWaterMark(opts.readableObjectMode);
|
|
1096
|
-
this._readableState.highWaterMark = this.readableHighWaterMark;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
this.allowHalfOpen = opts?.allowHalfOpen !== false;
|
|
1101
|
-
this._decodeStrings = opts?.decodeStrings !== false;
|
|
1102
|
-
if (opts?.write) this._writeImpl = opts.write;
|
|
1103
|
-
// writev not yet supported on Duplex
|
|
1104
|
-
if (opts?.final) this._finalImpl = opts.final;
|
|
1105
|
-
|
|
1106
|
-
// When allowHalfOpen=false, end writable when readable ends
|
|
1107
|
-
if (!this.allowHalfOpen) {
|
|
1108
|
-
this.once('end', () => {
|
|
1109
|
-
if (!this.writableEnded) {
|
|
1110
|
-
nextTick(() => this.end());
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
|
|
1117
|
-
if (this._writeImpl) {
|
|
1118
|
-
this._writeImpl.call(this, chunk, encoding, callback);
|
|
1119
|
-
} else {
|
|
1120
|
-
callback();
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
_final(callback: (error?: Error | null) => void): void {
|
|
1125
|
-
if (this._finalImpl) {
|
|
1126
|
-
this._finalImpl.call(this, callback);
|
|
1127
|
-
} else {
|
|
1128
|
-
callback();
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
override destroy(error?: Error): this {
|
|
1133
|
-
if (this.destroyed) return this;
|
|
1134
|
-
this.writable = false;
|
|
1135
|
-
return super.destroy(error);
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
|
1139
|
-
if (typeof encoding === 'function') {
|
|
1140
|
-
callback = encoding;
|
|
1141
|
-
encoding = undefined;
|
|
1142
|
-
}
|
|
1143
|
-
if (encoding === undefined) encoding = this._defaultEncoding;
|
|
1144
|
-
|
|
1145
|
-
// Convert strings to Buffer when decodeStrings is true (default), but not in objectMode
|
|
1146
|
-
if (this._decodeStrings && !this.writableObjectMode && typeof chunk === 'string') {
|
|
1147
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
1148
|
-
if (BufCtor) {
|
|
1149
|
-
chunk = BufCtor.from(chunk, encoding);
|
|
1150
|
-
encoding = 'buffer';
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
// Set encoding to 'buffer' for Buffer/Uint8Array chunks
|
|
1154
|
-
if (typeof chunk !== 'string' && !this.writableObjectMode) {
|
|
1155
|
-
const BufCtor = (globalThis as any).Buffer;
|
|
1156
|
-
if ((BufCtor && BufCtor.isBuffer(chunk)) || chunk instanceof Uint8Array) {
|
|
1157
|
-
encoding = 'buffer';
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (this.writableEnded) {
|
|
1162
|
-
const err = new Error('write after end');
|
|
1163
|
-
const cb = callback || (() => {});
|
|
1164
|
-
nextTick(() => {
|
|
1165
|
-
cb(err);
|
|
1166
|
-
this.emit('error', err);
|
|
1167
|
-
});
|
|
1168
|
-
return false;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
this.writableLength += this.writableObjectMode ? 1 : (chunk?.length ?? 1);
|
|
1172
|
-
|
|
1173
|
-
// If corked, buffer the write
|
|
1174
|
-
if (this.writableCorked > 0) {
|
|
1175
|
-
this._duplexCorkedBuffer.push({ chunk, encoding: encoding as string, callback: callback || (() => {}) });
|
|
1176
|
-
return this.writableLength < this.writableHighWaterMark;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Compute backpressure BEFORE _write (sync transforms may decrement length immediately)
|
|
1180
|
-
const belowHWM = this.writableLength < this.writableHighWaterMark;
|
|
1181
|
-
if (!belowHWM) {
|
|
1182
|
-
this.writableNeedDrain = true;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
const cb = callback || (() => {});
|
|
1186
|
-
this._duplexDoWrite(chunk, encoding as string, cb);
|
|
1187
|
-
|
|
1188
|
-
return belowHWM;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
private _duplexDoWrite(chunk: any, encoding: string, cb: (error?: Error | null) => void): void {
|
|
1192
|
-
if (this._duplexWriting) {
|
|
1193
|
-
this._duplexWriteQueue.push({ chunk, encoding, callback: cb });
|
|
1194
|
-
return;
|
|
1195
|
-
}
|
|
1196
|
-
this._duplexWriting = true;
|
|
1197
|
-
this._duplexStartWrite(chunk, encoding, cb);
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// Starts a write assuming _duplexWriting is already true. After the write
|
|
1201
|
-
// completes, either start the next queued write (keeping _duplexWriting=true
|
|
1202
|
-
// to preserve FIFO order) or clear the flag and emit 'drain'. The 'drain'
|
|
1203
|
-
// listener on streamx may synchronously call conn.write() — emitting drain
|
|
1204
|
-
// BEFORE the queue is fully processed would let that new write bypass the
|
|
1205
|
-
// queue, causing out-of-order bytes on the wire (and, for bittorrent-protocol,
|
|
1206
|
-
// desync of piece header vs. piece payload).
|
|
1207
|
-
private _duplexStartWrite(chunk: any, encoding: string, cb: (error?: Error | null) => void): void {
|
|
1208
|
-
this._pendingWrites++;
|
|
1209
|
-
this._write(chunk, encoding, (err) => {
|
|
1210
|
-
this._pendingWrites--;
|
|
1211
|
-
this.writableLength -= this.writableObjectMode ? 1 : (chunk?.length ?? 1);
|
|
1212
|
-
if (err) {
|
|
1213
|
-
nextTick(() => {
|
|
1214
|
-
cb(err);
|
|
1215
|
-
this._duplexWriting = false;
|
|
1216
|
-
this.emit('error', err);
|
|
1217
|
-
if (this._duplexWriteQueue.length > 0) {
|
|
1218
|
-
const next = this._duplexWriteQueue.shift()!;
|
|
1219
|
-
this._duplexWriting = true;
|
|
1220
|
-
this._duplexStartWrite(next.chunk, next.encoding, next.callback);
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
} else {
|
|
1224
|
-
nextTick(() => {
|
|
1225
|
-
cb();
|
|
1226
|
-
if (this._duplexWriteQueue.length > 0) {
|
|
1227
|
-
const next = this._duplexWriteQueue.shift()!;
|
|
1228
|
-
this._duplexStartWrite(next.chunk, next.encoding, next.callback);
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
this._duplexWriting = false;
|
|
1232
|
-
if (this.writableNeedDrain && this.writableLength <= this.writableHighWaterMark) {
|
|
1233
|
-
this.writableNeedDrain = false;
|
|
1234
|
-
this.emit('drain');
|
|
1235
|
-
}
|
|
1236
|
-
if (this._pendingWrites === 0 && this._pendingEndCb) {
|
|
1237
|
-
const endCb = this._pendingEndCb;
|
|
1238
|
-
this._pendingEndCb = null;
|
|
1239
|
-
endCb();
|
|
1240
|
-
}
|
|
1241
|
-
});
|
|
1242
|
-
}
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
private _duplexDrainQueue(): void {
|
|
1247
|
-
if (this._duplexWriteQueue.length > 0) {
|
|
1248
|
-
const next = this._duplexWriteQueue.shift()!;
|
|
1249
|
-
this._duplexDoWrite(next.chunk, next.encoding, next.callback);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
end(chunk?: any, encoding?: string | (() => void), callback?: () => void): this {
|
|
1254
|
-
if (typeof chunk === 'function') {
|
|
1255
|
-
callback = chunk;
|
|
1256
|
-
chunk = undefined;
|
|
1257
|
-
}
|
|
1258
|
-
if (typeof encoding === 'function') {
|
|
1259
|
-
callback = encoding;
|
|
1260
|
-
encoding = undefined;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
if (chunk !== undefined && chunk !== null) {
|
|
1264
|
-
this.write(chunk, encoding as string);
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
this.writableEnded = true;
|
|
1268
|
-
|
|
1269
|
-
const doFinal = () => {
|
|
1270
|
-
this._final((err) => {
|
|
1271
|
-
this.writableFinished = true;
|
|
1272
|
-
// Allow subclasses (Transform) to run post-final hooks (e.g. flush)
|
|
1273
|
-
// before the 'finish' event fires.
|
|
1274
|
-
this._doPrefinishHooks(() => {
|
|
1275
|
-
nextTick(() => {
|
|
1276
|
-
if (err) this.emit('error', err);
|
|
1277
|
-
this.emit('finish');
|
|
1278
|
-
nextTick(() => this.emit('close'));
|
|
1279
|
-
if (callback) callback();
|
|
1280
|
-
});
|
|
1281
|
-
});
|
|
1282
|
-
});
|
|
1283
|
-
};
|
|
1284
|
-
|
|
1285
|
-
// Wait for all pending writes to complete before calling _final.
|
|
1286
|
-
// Transform._write is synchronous (calls user cb in same tick), so _pendingWrites
|
|
1287
|
-
// can be 0 even while follow-up writes sit in _duplexWriteQueue. Check the queue
|
|
1288
|
-
// and the write-in-flight flag too, otherwise end() fires _final — which for
|
|
1289
|
-
// Transform pushes null — before the queued chunks reach _transform.
|
|
1290
|
-
if (this._pendingWrites > 0 || this._duplexWriting || this._duplexWriteQueue.length > 0) {
|
|
1291
|
-
this._pendingEndCb = doFinal;
|
|
1292
|
-
} else {
|
|
1293
|
-
doFinal();
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
return this;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
/** Hook for subclasses to run logic between _final and 'finish'. Default: no-op. */
|
|
1300
|
-
protected _doPrefinishHooks(cb: () => void): void {
|
|
1301
|
-
cb();
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
cork(): void { this.writableCorked++; }
|
|
1305
|
-
|
|
1306
|
-
uncork(): void {
|
|
1307
|
-
if (this.writableCorked > 0) {
|
|
1308
|
-
this.writableCorked--;
|
|
1309
|
-
if (this.writableCorked === 0 && this._duplexCorkedBuffer.length > 0) {
|
|
1310
|
-
const buffered = this._duplexCorkedBuffer.splice(0);
|
|
1311
|
-
for (const { chunk, encoding, callback } of buffered) {
|
|
1312
|
-
this._write(chunk, encoding, (err) => {
|
|
1313
|
-
this.writableLength -= this.writableObjectMode ? 1 : (chunk?.length ?? 1);
|
|
1314
|
-
if (err) {
|
|
1315
|
-
callback(err);
|
|
1316
|
-
this.emit('error', err);
|
|
1317
|
-
} else {
|
|
1318
|
-
callback();
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
if (this.writableNeedDrain && this.writableLength <= this.writableHighWaterMark) {
|
|
1323
|
-
this.writableNeedDrain = false;
|
|
1324
|
-
nextTick(() => this.emit('drain'));
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
setDefaultEncoding(encoding: string): this {
|
|
1331
|
-
this._defaultEncoding = encoding;
|
|
1332
|
-
return this;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// ---- Transform ----
|
|
1337
|
-
|
|
1338
|
-
class Transform_ extends Duplex_ {
|
|
1339
|
-
constructor(opts?: TransformOptions) {
|
|
1340
|
-
// Don't forward transform/flush/final/write — Transform's own method assignments
|
|
1341
|
-
// handle those. Passing write/final through would register them in Duplex_'s
|
|
1342
|
-
// _writeImpl/_finalImpl and bypass Transform's override.
|
|
1343
|
-
super({ ...opts, write: undefined, final: undefined });
|
|
1344
|
-
// Direct assignment mirrors Node.js: opts.transform/flush/final overwrite the
|
|
1345
|
-
// prototype methods on the instance so `t._transform === opts.transform` holds.
|
|
1346
|
-
if (opts?.transform) (this as any)._transform = opts.transform;
|
|
1347
|
-
if (opts?.flush) (this as any)._flush = opts.flush;
|
|
1348
|
-
if (opts?.final) (this as any)._final = opts.final;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
_transform(_chunk: any, _encoding: string, _callback: (error?: Error | null, data?: any) => void): void {
|
|
1352
|
-
// Throw when no implementation was provided (no opts.transform and no subclass override).
|
|
1353
|
-
const err = Object.assign(
|
|
1354
|
-
new Error('The _transform() method is not implemented'),
|
|
1355
|
-
{ code: 'ERR_METHOD_NOT_IMPLEMENTED' }
|
|
1356
|
-
);
|
|
1357
|
-
throw err;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
_flush(callback: (error?: Error | null, data?: any) => void): void {
|
|
1361
|
-
callback();
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
|
|
1365
|
-
let called = false;
|
|
1366
|
-
try {
|
|
1367
|
-
this._transform(chunk, encoding, (err, data) => {
|
|
1368
|
-
if (called) {
|
|
1369
|
-
const e = Object.assign(new Error('Callback called multiple times'), { code: 'ERR_MULTIPLE_CALLBACK' });
|
|
1370
|
-
nextTick(() => this.emit('error', e));
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
called = true;
|
|
1374
|
-
if (err) {
|
|
1375
|
-
callback(err);
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
if (data !== undefined && data !== null) {
|
|
1379
|
-
this.push(data);
|
|
1380
|
-
}
|
|
1381
|
-
callback();
|
|
1382
|
-
});
|
|
1383
|
-
} catch (err: any) {
|
|
1384
|
-
// ERR_METHOD_NOT_IMPLEMENTED must propagate synchronously (test-stream-transform-constructor-set-methods).
|
|
1385
|
-
// User-provided _transform errors are converted to 'error' events.
|
|
1386
|
-
if (err?.code === 'ERR_METHOD_NOT_IMPLEMENTED') throw err;
|
|
1387
|
-
callback(err as Error);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// Transform's built-in _final: calls _flush then pushes null.
|
|
1392
|
-
// This is the default; when the user provides opts.final it is overridden on
|
|
1393
|
-
// the instance and _doPrefinishHooks ensures _flush is still called after it.
|
|
1394
|
-
_final(callback: (error?: Error | null) => void): void {
|
|
1395
|
-
this._flush((err, data) => {
|
|
1396
|
-
if (err) {
|
|
1397
|
-
callback(err);
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
if (data !== undefined && data !== null) {
|
|
1401
|
-
this.push(data);
|
|
1402
|
-
}
|
|
1403
|
-
// Signal readable side is done
|
|
1404
|
-
this.push(null);
|
|
1405
|
-
callback();
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
// When a user-provided _final overrides the prototype method, we still need
|
|
1410
|
-
// to call the built-in flush+push-null logic (mirroring Node.js's prefinish).
|
|
1411
|
-
protected override _doPrefinishHooks(cb: () => void): void {
|
|
1412
|
-
const protoFinal = Transform_.prototype._final;
|
|
1413
|
-
if ((this as any)._final !== protoFinal) {
|
|
1414
|
-
// User replaced _final; call the built-in flush+push-null now.
|
|
1415
|
-
protoFinal.call(this, cb);
|
|
1416
|
-
} else {
|
|
1417
|
-
// _final already ran flush+push-null; nothing extra needed.
|
|
1418
|
-
cb();
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// ---- PassThrough ----
|
|
1424
|
-
|
|
1425
|
-
class PassThrough_ extends Transform_ {
|
|
1426
|
-
constructor(opts?: TransformOptions) {
|
|
1427
|
-
super({
|
|
1428
|
-
...opts,
|
|
1429
|
-
transform(chunk: any, _encoding: string, callback: (error?: Error | null, data?: any) => void) {
|
|
1430
|
-
callback(null, chunk);
|
|
1431
|
-
}
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
// ---- pipeline ----
|
|
1437
|
-
|
|
1438
|
-
type PipelineCallback = (err: Error | null) => void;
|
|
1439
|
-
|
|
1440
|
-
/** A stream that can be destroyed (duck-typed for pipeline). */
|
|
1441
|
-
interface DestroyableStream extends Stream {
|
|
1442
|
-
destroy?(error?: Error): void;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
export function pipeline(...args: [...streams: DestroyableStream[], callback: PipelineCallback] | DestroyableStream[]): DestroyableStream {
|
|
1446
|
-
const callback = typeof args[args.length - 1] === 'function' ? args.pop() as PipelineCallback : undefined;
|
|
1447
|
-
const streams = args as DestroyableStream[];
|
|
1448
|
-
|
|
1449
|
-
if (streams.length < 2) {
|
|
1450
|
-
throw new Error('pipeline requires at least 2 streams');
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
let error: Error | null = null;
|
|
1454
|
-
|
|
1455
|
-
function onError(err: Error) {
|
|
1456
|
-
if (!error) {
|
|
1457
|
-
error = err;
|
|
1458
|
-
// Destroy all streams
|
|
1459
|
-
for (const stream of streams) {
|
|
1460
|
-
if (typeof stream.destroy === 'function') {
|
|
1461
|
-
stream.destroy();
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
if (callback) callback(err);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
// Pipe streams together
|
|
1469
|
-
let current: Stream = streams[0];
|
|
1470
|
-
for (let i = 1; i < streams.length; i++) {
|
|
1471
|
-
const next = streams[i];
|
|
1472
|
-
current.pipe(next as unknown as Writable);
|
|
1473
|
-
current.on('error', onError);
|
|
1474
|
-
current = next;
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
// Listen for end on last stream
|
|
1478
|
-
const last = streams[streams.length - 1];
|
|
1479
|
-
last.on('error', onError);
|
|
1480
|
-
last.on('finish', () => {
|
|
1481
|
-
if (callback && !error) callback(null);
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
|
-
return last;
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// ---- finished ----
|
|
1488
|
-
|
|
1489
|
-
export function finished(stream: Stream | Readable | Writable, callback: (err?: Error | null) => void): () => void;
|
|
1490
|
-
export function finished(stream: Stream | Readable | Writable, opts: FinishedOptions, callback: (err?: Error | null) => void): () => void;
|
|
1491
|
-
export function finished(stream: Stream | Readable | Writable, optsOrCb: FinishedOptions | ((err?: Error | null) => void), callback?: (err?: Error | null) => void): () => void {
|
|
1492
|
-
let cb: (err?: Error | null) => void;
|
|
1493
|
-
let _opts: FinishedOptions = {};
|
|
1494
|
-
|
|
1495
|
-
if (typeof optsOrCb === 'function') {
|
|
1496
|
-
cb = optsOrCb;
|
|
1497
|
-
} else {
|
|
1498
|
-
_opts = optsOrCb || {};
|
|
1499
|
-
cb = callback!;
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
let called = false;
|
|
1503
|
-
function done(err?: Error | null) {
|
|
1504
|
-
if (!called) {
|
|
1505
|
-
called = true;
|
|
1506
|
-
cb(err);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const onFinish = () => done();
|
|
1511
|
-
const onEnd = () => done();
|
|
1512
|
-
const onError = (err: Error) => done(err);
|
|
1513
|
-
const onClose = () => {
|
|
1514
|
-
if (!(stream as Writable).writableFinished && !(stream as Readable).readableEnded) {
|
|
1515
|
-
done(new Error('premature close'));
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
|
|
1519
|
-
stream.on('finish', onFinish);
|
|
1520
|
-
stream.on('end', onEnd);
|
|
1521
|
-
stream.on('error', onError);
|
|
1522
|
-
stream.on('close', onClose);
|
|
1523
|
-
|
|
1524
|
-
// Check initial state — handle already-finished/destroyed streams
|
|
1525
|
-
// Reference: refs/node/lib/internal/streams/end-of-stream.js lines 228-249
|
|
1526
|
-
const isWritableStream = typeof (stream as Writable).write === 'function';
|
|
1527
|
-
const isReadableStream = typeof (stream as Readable).read === 'function';
|
|
1528
|
-
const writableFinished = (stream as unknown as Record<string, unknown>).writableFinished === true;
|
|
1529
|
-
const readableEnded = (stream as unknown as Record<string, unknown>).readableEnded === true;
|
|
1530
|
-
const destroyed = (stream as unknown as Record<string, unknown>).destroyed === true;
|
|
1531
|
-
|
|
1532
|
-
if (destroyed) {
|
|
1533
|
-
const storedErr = (stream as unknown as Record<string, unknown>)._err as Error | null | undefined;
|
|
1534
|
-
if (storedErr) {
|
|
1535
|
-
// Stream was destroyed with an error (may have fired before we registered listener)
|
|
1536
|
-
queueMicrotask(() => done(storedErr));
|
|
1537
|
-
} else if ((isWritableStream && writableFinished) || (isReadableStream && readableEnded)) {
|
|
1538
|
-
// Stream was destroyed after completing normally — treat as success
|
|
1539
|
-
queueMicrotask(() => done());
|
|
1540
|
-
} else {
|
|
1541
|
-
// Stream was destroyed without completing — premature close
|
|
1542
|
-
queueMicrotask(() => done(new Error('premature close')));
|
|
1543
|
-
}
|
|
1544
|
-
} else if (isWritableStream && !isReadableStream && writableFinished) {
|
|
1545
|
-
queueMicrotask(() => done());
|
|
1546
|
-
} else if (!isWritableStream && isReadableStream && readableEnded) {
|
|
1547
|
-
queueMicrotask(() => done());
|
|
1548
|
-
} else if (isWritableStream && isReadableStream && writableFinished && readableEnded) {
|
|
1549
|
-
queueMicrotask(() => done());
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
return function cleanup() {
|
|
1553
|
-
stream.removeListener('finish', onFinish);
|
|
1554
|
-
stream.removeListener('end', onEnd);
|
|
1555
|
-
stream.removeListener('error', onError);
|
|
1556
|
-
stream.removeListener('close', onClose);
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
// ---- addAbortSignal ----
|
|
1561
|
-
|
|
1562
|
-
export function addAbortSignal(signal: AbortSignal, stream: Stream): typeof stream {
|
|
1563
|
-
if (!(signal instanceof AbortSignal)) {
|
|
1564
|
-
throw new TypeError('The first argument must be an AbortSignal');
|
|
1565
|
-
}
|
|
1566
|
-
if (!(stream instanceof Stream)) {
|
|
1567
|
-
throw new TypeError('The second argument must be a Stream');
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
if (signal.aborted) {
|
|
1571
|
-
(stream as Readable | Writable).destroy(new Error('The operation was aborted'));
|
|
1572
|
-
} else {
|
|
1573
|
-
const onAbort = () => {
|
|
1574
|
-
(stream as Readable | Writable).destroy(new Error('The operation was aborted'));
|
|
1575
|
-
};
|
|
1576
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
1577
|
-
// Cleanup when stream closes
|
|
1578
|
-
stream.once('close', () => {
|
|
1579
|
-
signal.removeEventListener('abort', onAbort);
|
|
1580
|
-
});
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
return stream;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
// ---- Utility functions ----
|
|
2
|
+
// Reimplemented for GJS using EventEmitter and microtask scheduling.
|
|
3
|
+
//
|
|
4
|
+
// This file is the public barrel — actual implementations live per-class:
|
|
5
|
+
// - stream-base.ts Stream_ (root EventEmitter + pipe glue)
|
|
6
|
+
// - readable.ts Readable_ (+ Readable_._autoClose protected hook)
|
|
7
|
+
// - writable.ts Writable_ (+ FIFO drain queue)
|
|
8
|
+
// - duplex.ts Duplex_ (Readable_ + writable-half re-implementation)
|
|
9
|
+
// - transform.ts Transform_
|
|
10
|
+
// - passthrough.ts PassThrough_
|
|
11
|
+
// - utils/pipe.ts Stream.pipe() helper
|
|
12
|
+
// - utils/pipeline.ts pipeline()
|
|
13
|
+
// - utils/finished.ts finished(), addAbortSignal(), is{Readable,…}()
|
|
14
|
+
// - internal/state.ts module-singleton defaults + validateHighWaterMark
|
|
15
|
+
// - internal/types.ts shared interfaces
|
|
16
|
+
//
|
|
17
|
+
// Public class names are makeCallable Proxy wrappers around the underscore-suffixed
|
|
18
|
+
// internal classes (`Stream_`, `Readable_`, …) so legacy CJS consumers can do
|
|
19
|
+
// `Stream.call(this)` (npm `send`, `util.inherits(Sub, Stream)`, our own
|
|
20
|
+
// `@gjsify/crypto` `Hash.copy()`). See `./callable.ts` for the rationale.
|
|
21
|
+
//
|
|
22
|
+
// The historical default export shape — the Stream constructor with all classes
|
|
23
|
+
// + helpers attached as static properties — is preserved for `cjs-compat.cjs`.
|
|
1587
24
|
|
|
1588
|
-
|
|
1589
|
-
if (stream == null) return false;
|
|
1590
|
-
const s = stream as Record<string, unknown>;
|
|
1591
|
-
if (typeof s.readable !== 'boolean') return false;
|
|
1592
|
-
if (typeof s.read !== 'function') return false;
|
|
1593
|
-
if (s.destroyed === true) return false;
|
|
1594
|
-
if (s.readableEnded === true) return false;
|
|
1595
|
-
return (s.readable as boolean) === true;
|
|
1596
|
-
}
|
|
25
|
+
import { makeCallable } from './callable.js';
|
|
1597
26
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
}
|
|
27
|
+
import { Stream_ } from './stream-base.js';
|
|
28
|
+
import { Readable_ } from './readable.js';
|
|
29
|
+
// Side-effect import: wires Stream_.prototype.pipe → pipe() at load time.
|
|
30
|
+
// See ./stream-base.ts (_setPipeImpl) for the late-binding rationale.
|
|
31
|
+
import './utils/pipe.js';
|
|
32
|
+
import { Writable_ } from './writable.js';
|
|
33
|
+
import { Duplex_ } from './duplex.js';
|
|
34
|
+
import { Transform_ } from './transform.js';
|
|
35
|
+
import { PassThrough_ } from './passthrough.js';
|
|
36
|
+
import { pipeline } from './utils/pipeline.js';
|
|
37
|
+
import { finished, addAbortSignal, isReadable, isWritable, isDestroyed, isDisturbed, isErrored } from './utils/finished.js';
|
|
38
|
+
import { getDefaultHighWaterMark, setDefaultHighWaterMark } from './internal/state.js';
|
|
1607
39
|
|
|
1608
|
-
|
|
1609
|
-
if (stream == null) return false;
|
|
1610
|
-
return (stream as Record<string, unknown>).destroyed === true;
|
|
1611
|
-
}
|
|
40
|
+
// ---- Re-exports of internal helpers ----
|
|
1612
41
|
|
|
1613
|
-
export
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
// A stream is disturbed if data has been read from it
|
|
1617
|
-
return s.readableDidRead === true || (s.readableFlowing !== null && s.readableFlowing !== undefined);
|
|
1618
|
-
}
|
|
42
|
+
export { getDefaultHighWaterMark, setDefaultHighWaterMark };
|
|
43
|
+
export { pipeline, finished, addAbortSignal };
|
|
44
|
+
export { isReadable, isWritable, isDestroyed, isDisturbed, isErrored };
|
|
1619
45
|
|
|
1620
|
-
|
|
1621
|
-
if (stream == null) return false;
|
|
1622
|
-
// Check for errored state on either side
|
|
1623
|
-
const s = stream as Record<string, unknown>;
|
|
1624
|
-
if (s.destroyed === true && typeof s.readable === 'boolean' && s.readable === false) return true;
|
|
1625
|
-
if (s.destroyed === true && typeof s.writable === 'boolean' && s.writable === false) return true;
|
|
1626
|
-
return false;
|
|
1627
|
-
}
|
|
46
|
+
// ---- Type-only re-exports ----
|
|
1628
47
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
// The class declarations above use underscore-suffixed internal names
|
|
1632
|
-
// (`Stream_`, `Readable_`, etc.) — we wrap each one with `makeCallable` so
|
|
1633
|
-
// legacy CJS consumers can do `Stream.call(this)` (npm `send`,
|
|
1634
|
-
// `util.inherits(Sub, Stream)`, our own `@gjsify/crypto` `Hash.copy()`).
|
|
1635
|
-
// See `./callable.ts` for the rationale and implementation.
|
|
1636
|
-
//
|
|
1637
|
-
// Public API preserves the historical names. Both value and type positions
|
|
1638
|
-
// work because of the `type X = X_` aliases below.
|
|
48
|
+
export type { ReadableOptions, WritableOptions, DuplexOptions, TransformOptions, FinishedOptions } from 'node:stream';
|
|
49
|
+
export type { StreamOptions } from './internal/types.js';
|
|
1639
50
|
|
|
1640
|
-
|
|
51
|
+
// ---- Class wrappers (callable for legacy CJS) ----
|
|
1641
52
|
|
|
1642
53
|
export const Stream = makeCallable(Stream_) as typeof Stream_;
|
|
1643
54
|
export const Readable = makeCallable(Readable_) as typeof Readable_;
|
|
@@ -1653,7 +64,12 @@ export type Duplex = Duplex_;
|
|
|
1653
64
|
export type Transform = Transform_;
|
|
1654
65
|
export type PassThrough = PassThrough_;
|
|
1655
66
|
|
|
1656
|
-
// Default export
|
|
67
|
+
// ---- Default export ----
|
|
68
|
+
//
|
|
69
|
+
// Node returns the Stream constructor with sub-classes + helpers hung off it.
|
|
70
|
+
// `cjs-compat.cjs` resolves `mod.default || mod` to this exact value, so the
|
|
71
|
+
// legacy `util.inherits(Sub, require('stream'))` pattern keeps working.
|
|
72
|
+
|
|
1657
73
|
const _default = Object.assign(Stream, {
|
|
1658
74
|
Stream,
|
|
1659
75
|
Readable,
|