documonster 0.20.2 → 0.21.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/README.md +1 -1
- package/README_zh.md +1 -1
- package/THIRD_PARTY_NOTICES.md +34 -2
- package/dist/esm/modules/archive/core/byte-queue.js +2 -1
- package/dist/esm/modules/archive/unzip/stream.browser.js +6 -0
- package/dist/esm/modules/csv/parse/async.js +2 -1
- package/dist/esm/modules/excel/bridge/csv-bridge.js +1 -1
- package/dist/esm/modules/excel/core/xlsx-io.browser.js +5 -9
- package/dist/esm/modules/excel/core/xlsx-io.js +1 -3
- package/dist/esm/modules/excel/core/xlsx-stream.js +3 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +10 -10
- package/dist/esm/modules/excel/stream/workbook-reader.js +30 -6
- package/dist/esm/modules/stream/browser/collector.js +3 -5
- package/dist/esm/modules/stream/browser/compose.js +13 -228
- package/dist/esm/modules/stream/browser/duplex-pair.js +71 -0
- package/dist/esm/modules/stream/browser/factories.js +35 -217
- package/dist/esm/modules/stream/browser/pipeline.js +43 -478
- package/dist/esm/modules/stream/browser/utils.js +21 -247
- package/dist/esm/modules/stream/browser/web.js +25 -0
- package/dist/esm/modules/stream/buffered-stream.js +8 -6
- package/dist/esm/modules/stream/core/factories.js +166 -0
- package/dist/esm/modules/stream/core/pipeline-api.js +32 -0
- package/dist/esm/modules/stream/core/sink-adapter.js +94 -24
- package/dist/esm/modules/stream/core/stream-helpers.js +160 -0
- package/dist/esm/modules/stream/core/utils.js +0 -23
- package/dist/esm/modules/stream/core/web-conversions.js +31 -0
- package/dist/esm/modules/stream/core/web-pipeline.js +90 -0
- package/dist/esm/modules/stream/index.base.js +2 -2
- package/dist/esm/modules/stream/index.browser.js +13 -19
- package/dist/esm/modules/stream/index.js +6 -10
- package/dist/esm/modules/stream/kernel/classes.js +618 -0
- package/dist/esm/modules/stream/kernel/compose-impl.js +225 -0
- package/dist/esm/modules/stream/kernel/destroy.js +255 -0
- package/dist/esm/modules/stream/kernel/duplexify.js +355 -0
- package/dist/esm/modules/stream/kernel/end-of-stream.js +212 -0
- package/dist/esm/modules/stream/kernel/errors.js +215 -0
- package/dist/esm/modules/stream/kernel/from.js +202 -0
- package/dist/esm/modules/stream/kernel/legacy.js +35 -0
- package/dist/esm/modules/stream/kernel/operators.js +285 -0
- package/dist/esm/modules/stream/kernel/pipeline-impl.js +561 -0
- package/dist/esm/modules/stream/kernel/readable-impl.js +963 -0
- package/dist/esm/modules/stream/kernel/state.js +253 -0
- package/dist/esm/modules/stream/kernel/stream-utils.js +212 -0
- package/dist/esm/modules/stream/kernel/web-adapters.js +584 -0
- package/dist/esm/modules/stream/kernel/writable.js +651 -0
- package/dist/esm/modules/stream/node/collector.js +7 -1
- package/dist/esm/modules/stream/node/compose.js +21 -206
- package/dist/esm/modules/stream/node/factories.js +34 -238
- package/dist/esm/modules/stream/node/pipeline.js +40 -94
- package/dist/esm/modules/stream/node/utils.js +15 -239
- package/dist/esm/modules/stream/node/web.js +27 -0
- package/dist/esm/modules/stream/pull-stream.js +15 -9
- package/dist/esm/modules/word/writer/streaming-writer.js +3 -0
- package/dist/iife/THIRD_PARTY_NOTICES.md +34 -2
- package/dist/iife/documonster.archive.iife.min.js +4 -4
- package/dist/iife/documonster.csv.iife.min.js +19 -19
- package/dist/iife/documonster.draw.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +50 -50
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.mermaid.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +1 -1
- package/dist/iife/documonster.stream.iife.min.js +2 -2
- package/dist/iife/documonster.word.iife.min.js +60 -60
- package/dist/iife/documonster.xml.iife.min.js +1 -1
- package/dist/types/modules/excel/core/xlsx-io.browser.d.ts +5 -9
- package/dist/types/modules/excel/core/xlsx-io.d.ts +1 -3
- package/dist/types/modules/excel/core/xlsx-stream.d.ts +3 -2
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +2 -1
- package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/types/modules/stream/browser/collector.d.ts +1 -1
- package/dist/types/modules/stream/browser/compose.d.ts +14 -15
- package/dist/types/modules/stream/browser/duplex-pair.d.ts +10 -0
- package/dist/types/modules/stream/browser/factories.d.ts +22 -79
- package/dist/types/modules/stream/browser/pipeline.d.ts +8 -40
- package/dist/types/modules/stream/browser/utils.d.ts +6 -47
- package/dist/types/modules/stream/browser/web.d.ts +18 -0
- package/dist/types/modules/stream/core/add-abort-signal.d.ts +2 -1
- package/dist/types/modules/stream/core/consumers.d.ts +1 -1
- package/dist/types/modules/stream/core/event-utils.d.ts +2 -1
- package/dist/types/modules/stream/core/factories.d.ts +129 -0
- package/dist/types/modules/stream/core/pipeline-api.d.ts +10 -0
- package/dist/types/modules/stream/core/sink-adapter.d.ts +18 -1
- package/dist/types/modules/stream/core/stream-helpers.d.ts +76 -0
- package/dist/types/modules/stream/core/utils.d.ts +0 -9
- package/dist/types/modules/stream/core/web-conversions.d.ts +68 -0
- package/dist/types/modules/stream/core/web-pipeline.d.ts +27 -0
- package/dist/types/modules/stream/index.base.d.ts +6 -4
- package/dist/types/modules/stream/index.browser.d.ts +13 -14
- package/dist/types/modules/stream/index.d.ts +7 -10
- package/dist/types/modules/stream/kernel/classes.d.ts +220 -0
- package/dist/types/modules/stream/kernel/compose-impl.d.ts +10 -0
- package/dist/types/modules/stream/kernel/destroy.d.ts +21 -0
- package/dist/types/modules/stream/kernel/duplexify.d.ts +31 -0
- package/dist/types/modules/stream/kernel/end-of-stream.d.ts +28 -0
- package/dist/types/modules/stream/kernel/errors.d.ts +42 -0
- package/dist/types/modules/stream/kernel/from.d.ts +17 -0
- package/dist/types/modules/stream/kernel/legacy.d.ts +20 -0
- package/dist/types/modules/stream/kernel/operators.d.ts +44 -0
- package/dist/types/modules/stream/kernel/pipeline-impl.d.ts +32 -0
- package/dist/types/modules/stream/kernel/readable-impl.d.ts +34 -0
- package/dist/types/modules/stream/kernel/state.d.ts +223 -0
- package/dist/types/modules/stream/kernel/stream-utils.d.ts +34 -0
- package/dist/types/modules/stream/kernel/web-adapters.d.ts +65 -0
- package/dist/types/modules/stream/kernel/writable.d.ts +107 -0
- package/dist/types/modules/stream/node/collector.d.ts +7 -2
- package/dist/types/modules/stream/node/compose.d.ts +31 -5
- package/dist/types/modules/stream/node/factories.d.ts +22 -85
- package/dist/types/modules/stream/node/pipeline.d.ts +4 -15
- package/dist/types/modules/stream/node/utils.d.ts +9 -49
- package/dist/types/modules/stream/node/web.d.ts +18 -0
- package/dist/types/modules/stream/pull-stream.d.ts +2 -2
- package/dist/types/modules/stream/types.d.ts +24 -6
- package/package.json +5 -3
- package/dist/esm/modules/stream/browser/_lazy.js +0 -30
- package/dist/esm/modules/stream/browser/chunk-buffer.js +0 -223
- package/dist/esm/modules/stream/browser/duplex.js +0 -979
- package/dist/esm/modules/stream/browser/helpers.js +0 -44
- package/dist/esm/modules/stream/browser/microtask-context.js +0 -60
- package/dist/esm/modules/stream/browser/passthrough.js +0 -16
- package/dist/esm/modules/stream/browser/pipe-manager.js +0 -161
- package/dist/esm/modules/stream/browser/readable.js +0 -2208
- package/dist/esm/modules/stream/browser/transform.js +0 -1241
- package/dist/esm/modules/stream/browser/writable.js +0 -1316
- package/dist/esm/modules/stream/core/end-args.js +0 -24
- package/dist/esm/modules/stream/node/writable.js +0 -125
- package/dist/esm/modules/stream/utils.base.js +0 -126
- package/dist/esm/modules/stream/utils.browser.js +0 -80
- package/dist/esm/modules/stream/utils.js +0 -81
- package/dist/types/modules/stream/browser/_lazy.d.ts +0 -20
- package/dist/types/modules/stream/browser/chunk-buffer.d.ts +0 -61
- package/dist/types/modules/stream/browser/duplex.d.ts +0 -299
- package/dist/types/modules/stream/browser/helpers.d.ts +0 -13
- package/dist/types/modules/stream/browser/microtask-context.d.ts +0 -44
- package/dist/types/modules/stream/browser/passthrough.d.ts +0 -11
- package/dist/types/modules/stream/browser/pipe-manager.d.ts +0 -38
- package/dist/types/modules/stream/browser/readable.d.ts +0 -379
- package/dist/types/modules/stream/browser/transform.d.ts +0 -314
- package/dist/types/modules/stream/browser/writable.d.ts +0 -238
- package/dist/types/modules/stream/core/end-args.d.ts +0 -20
- package/dist/types/modules/stream/node/writable.d.ts +0 -53
- package/dist/types/modules/stream/utils.base.d.ts +0 -63
- package/dist/types/modules/stream/utils.browser.d.ts +0 -23
- package/dist/types/modules/stream/utils.d.ts +0 -24
|
@@ -1,1316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser Stream - Writable
|
|
3
|
-
*/
|
|
4
|
-
import { deferTask, inDeferredContext } from "./microtask-context.js";
|
|
5
|
-
import { Readable } from "./readable.js";
|
|
6
|
-
import { stringToEncodedBytes } from "../core/binary-chunk.js";
|
|
7
|
-
import { parseEndArgs } from "../core/end-args.js";
|
|
8
|
-
import { getDefaultHighWaterMark } from "../core/utils.js";
|
|
9
|
-
import { StreamStateError } from "../errors.js";
|
|
10
|
-
import { decodeBytesToString } from "../../../utils/binary.js";
|
|
11
|
-
import { createAbortError } from "../../../utils/errors.js";
|
|
12
|
-
import { EventEmitter } from "../../../utils/event-emitter.js";
|
|
13
|
-
/**
|
|
14
|
-
* Shared toString implementation for Uint8Array chunks converted from strings.
|
|
15
|
-
* Uses `this`-binding to avoid per-chunk closure allocation.
|
|
16
|
-
* Supports all Node.js Buffer encodings (hex, base64, base64url, ascii, etc.).
|
|
17
|
-
*/
|
|
18
|
-
function encodedBytesToString(enc) {
|
|
19
|
-
return decodeBytesToString(this, enc ?? "utf-8");
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* A wrapper around Web WritableStream that provides Node.js-like API
|
|
23
|
-
*/
|
|
24
|
-
export class Writable extends EventEmitter {
|
|
25
|
-
/**
|
|
26
|
-
* Allow duck-typed instanceof checks.
|
|
27
|
-
* Node.js Duplex passes `instanceof Writable` via Symbol.hasInstance.
|
|
28
|
-
* Our browser Duplex composes a Writable, so we use Symbol.hasInstance
|
|
29
|
-
* to check for key Writable-like methods/properties.
|
|
30
|
-
*/
|
|
31
|
-
static [Symbol.hasInstance](instance) {
|
|
32
|
-
if (instance == null || typeof instance !== "object") {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
// Fast path: actual Writable prototype
|
|
36
|
-
if (Object.prototype.isPrototypeOf.call(Writable.prototype, instance)) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
// Duck-type: must have key Writable methods and the stream brand
|
|
40
|
-
const o = instance;
|
|
41
|
-
return (o.__documonster_stream === true &&
|
|
42
|
-
typeof o.write === "function" &&
|
|
43
|
-
typeof o.end === "function" &&
|
|
44
|
-
typeof o.on === "function" &&
|
|
45
|
-
"writableFinished" in instance);
|
|
46
|
-
}
|
|
47
|
-
constructor(options) {
|
|
48
|
-
super();
|
|
49
|
-
this._stream = null;
|
|
50
|
-
this._writer = null;
|
|
51
|
-
this._ended = false;
|
|
52
|
-
this._finished = false;
|
|
53
|
-
/** @internal Set by Transform._scheduleEnd to allow _doFinish to emit synchronously */
|
|
54
|
-
this._syncFinish = false;
|
|
55
|
-
this._destroyed = false;
|
|
56
|
-
this._errored = null;
|
|
57
|
-
this._errorEmitted = false;
|
|
58
|
-
this._closed = false;
|
|
59
|
-
this._writableLength = 0;
|
|
60
|
-
this._needDrain = false;
|
|
61
|
-
this._corked = 0;
|
|
62
|
-
this._corkedChunks = [];
|
|
63
|
-
this._defaultEncoding = "utf8";
|
|
64
|
-
this._ownsStream = false;
|
|
65
|
-
/** When true, _doWrite calls _writeFunc directly (no Web WritableStream). */
|
|
66
|
-
this._directWrite = false;
|
|
67
|
-
/**
|
|
68
|
-
* Write queue for direct-write mode. When a _writeFunc callback is pending
|
|
69
|
-
* (async), subsequent writes are buffered here and drained one-at-a-time,
|
|
70
|
-
* matching Node.js Writable semantics.
|
|
71
|
-
*/
|
|
72
|
-
this._writeQueue = [];
|
|
73
|
-
/** Whether a _writeFunc call is currently in-flight (callback not yet invoked). */
|
|
74
|
-
this._writing = false;
|
|
75
|
-
/** Pending end() operation waiting for the write queue to drain. */
|
|
76
|
-
this._pendingEnd = null;
|
|
77
|
-
this._constructed = true;
|
|
78
|
-
/** Resolved writev function (from options, subclass, or null). Cached at construction. */
|
|
79
|
-
this._resolvedWritev = null;
|
|
80
|
-
this._objectMode = options?.objectMode ?? false;
|
|
81
|
-
this._highWaterMark = options?.highWaterMark ?? getDefaultHighWaterMark(this._objectMode);
|
|
82
|
-
this._autoDestroy = options?.autoDestroy ?? true;
|
|
83
|
-
this._emitClose = options?.emitClose ?? true;
|
|
84
|
-
this._decodeStrings = options?.decodeStrings ?? true;
|
|
85
|
-
this._defaultEncoding = options?.defaultEncoding ?? "utf8";
|
|
86
|
-
if (options?.stream) {
|
|
87
|
-
// Wrapping an existing Web WritableStream: proxy writes to the underlying
|
|
88
|
-
// stream and ignore user-provided write/final/destroy/construct/writev
|
|
89
|
-
// hooks (matching Node.js behavior where the {stream} option creates a
|
|
90
|
-
// transparent proxy with its own write/final handlers).
|
|
91
|
-
this._stream = options.stream;
|
|
92
|
-
this._ownsStream = false;
|
|
93
|
-
this._directWrite = false;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
// Store user-provided write function
|
|
97
|
-
if (options?.write) {
|
|
98
|
-
this._writeFunc = options.write.bind(this);
|
|
99
|
-
}
|
|
100
|
-
// Store user-provided final function (option takes precedence over prototype)
|
|
101
|
-
if (options?.final) {
|
|
102
|
-
this._finalFunc = options.final.bind(this);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// Node.js: detect subclass _final on the prototype chain, matching
|
|
106
|
-
// the pattern used for _write / _writev detection.
|
|
107
|
-
const subclassFinal = this._getSubclassFinal();
|
|
108
|
-
if (subclassFinal) {
|
|
109
|
-
this._finalFunc = subclassFinal;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// Store user-provided destroy function
|
|
113
|
-
if (options?.destroy) {
|
|
114
|
-
this._destroy = options.destroy.bind(this);
|
|
115
|
-
}
|
|
116
|
-
// Store user-provided construct function
|
|
117
|
-
if (options?.construct) {
|
|
118
|
-
this._constructFunc = options.construct.bind(this);
|
|
119
|
-
}
|
|
120
|
-
// Store user-provided writev function
|
|
121
|
-
if (options?.writev) {
|
|
122
|
-
this._writevFunc = options.writev.bind(this);
|
|
123
|
-
}
|
|
124
|
-
this._ownsStream = true;
|
|
125
|
-
// When we own the stream AND have a user-provided _writeFunc, we bypass
|
|
126
|
-
// Web WritableStream entirely and call _writeFunc directly with a
|
|
127
|
-
// Node.js-style write queue. This ensures:
|
|
128
|
-
// - Synchronous callbacks execute synchronously (fixes cork/uncork, write-during-data)
|
|
129
|
-
// - Async callbacks are properly serialized (fixes Transform async pipeline)
|
|
130
|
-
// - end()/final waits for all in-flight writes (fixes premature end)
|
|
131
|
-
if (this._writeFunc) {
|
|
132
|
-
this._directWrite = true;
|
|
133
|
-
this._stream = null;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
// Check if this is a subclass with _construct — if so, we need direct-write
|
|
137
|
-
// mode to properly gate writes until construction completes. Detect subclass
|
|
138
|
-
// _write on the prototype and wrap it as _writeFunc.
|
|
139
|
-
const hasConstruct = options?.construct || this._hasSubclassConstruct();
|
|
140
|
-
const subclassWrite = this._getSubclassWrite();
|
|
141
|
-
if (hasConstruct && subclassWrite) {
|
|
142
|
-
this._writeFunc = subclassWrite;
|
|
143
|
-
this._directWrite = true;
|
|
144
|
-
this._stream = null;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this._directWrite = false;
|
|
148
|
-
this._stream = new WritableStream({
|
|
149
|
-
write: async (chunk) => {
|
|
150
|
-
// Subclass _write path (no user-provided _writeFunc)
|
|
151
|
-
if (this._write) {
|
|
152
|
-
await new Promise((resolve, reject) => {
|
|
153
|
-
this._write(chunk, "utf8", (err) => {
|
|
154
|
-
if (err) {
|
|
155
|
-
reject(err);
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
resolve();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
close: async () => {
|
|
165
|
-
// Node.js: _final is called before finish is emitted.
|
|
166
|
-
// When using the Web WritableStream path (no direct-write mode),
|
|
167
|
-
// we must still honour a subclass _final if it was detected.
|
|
168
|
-
if (this._finalFunc) {
|
|
169
|
-
const finalErr = await new Promise(resolve => {
|
|
170
|
-
this._finalFunc((err) => {
|
|
171
|
-
resolve(err ?? null);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
if (finalErr) {
|
|
175
|
-
// Node.js: _final error → emit error, do NOT emit finish.
|
|
176
|
-
this._errored = finalErr;
|
|
177
|
-
this._errorEmitted = true;
|
|
178
|
-
this.emit("error", finalErr);
|
|
179
|
-
if (this._autoDestroy) {
|
|
180
|
-
this.destroy(finalErr);
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
this._finished = true;
|
|
186
|
-
this.emit("finish");
|
|
187
|
-
if (this._autoDestroy) {
|
|
188
|
-
this.destroy();
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
abort: reason => {
|
|
192
|
-
this.emit("error", reason);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// M1: signal constructor option — destroy stream when signal aborts
|
|
199
|
-
if (options?.signal) {
|
|
200
|
-
this._setupAbortSignal(options.signal);
|
|
201
|
-
}
|
|
202
|
-
// Cache resolved writev at construction time to avoid prototype walk per drain
|
|
203
|
-
this._resolvedWritev = this._writevFunc ?? this._getWritevHook();
|
|
204
|
-
// L2: _construct hook — if provided, delay writes until constructed
|
|
205
|
-
this._maybeConstruct();
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Run _construct if provided (via options or subclass override).
|
|
209
|
-
* Delays write operations until the callback fires.
|
|
210
|
-
*/
|
|
211
|
-
_maybeConstruct() {
|
|
212
|
-
const hasConstructHook = this._constructFunc || this._hasSubclassConstruct();
|
|
213
|
-
if (!hasConstructHook) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
this._constructed = false;
|
|
217
|
-
deferTask(() => {
|
|
218
|
-
const fn = this._constructFunc ?? this._construct.bind(this);
|
|
219
|
-
fn(err => {
|
|
220
|
-
if (err) {
|
|
221
|
-
this.destroy(err);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
this._constructed = true;
|
|
225
|
-
// Drain any writes that were queued while not yet constructed
|
|
226
|
-
if (this._directWrite && this._writeQueue.length > 0 && !this._writing) {
|
|
227
|
-
this._writing = true;
|
|
228
|
-
this._drainWriteQueue();
|
|
229
|
-
}
|
|
230
|
-
else if (this._pendingEnd && !this._writing) {
|
|
231
|
-
// If end() was called during construct with no queued writes
|
|
232
|
-
const { cb } = this._pendingEnd;
|
|
233
|
-
this._pendingEnd = null;
|
|
234
|
-
this._doFinish(cb);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Check if a subclass defines _construct on its own prototype.
|
|
241
|
-
* Node.js does NOT have _construct on any stream prototype — it only exists
|
|
242
|
-
* when provided via constructor options or defined by a subclass.
|
|
243
|
-
*/
|
|
244
|
-
_hasSubclassConstruct() {
|
|
245
|
-
let proto = Object.getPrototypeOf(this);
|
|
246
|
-
while (proto && proto !== Writable.prototype && proto !== Object.prototype) {
|
|
247
|
-
if (Object.prototype.hasOwnProperty.call(proto, "_construct")) {
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
proto = Object.getPrototypeOf(proto);
|
|
251
|
-
}
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Detect subclass _write on the prototype and return a bound function.
|
|
256
|
-
* Returns null if no subclass _write is found.
|
|
257
|
-
*/
|
|
258
|
-
_getSubclassWrite() {
|
|
259
|
-
let proto = Object.getPrototypeOf(this);
|
|
260
|
-
while (proto && proto !== Writable.prototype && proto !== Object.prototype) {
|
|
261
|
-
if (Object.prototype.hasOwnProperty.call(proto, "_write")) {
|
|
262
|
-
return proto._write.bind(this);
|
|
263
|
-
}
|
|
264
|
-
proto = Object.getPrototypeOf(proto);
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Detect subclass _final on the prototype and return a bound function.
|
|
270
|
-
* Returns null if no subclass _final is found.
|
|
271
|
-
* Matches Node.js behavior where a subclass can define _final() on its
|
|
272
|
-
* prototype and have it called during the end() sequence.
|
|
273
|
-
*/
|
|
274
|
-
_getSubclassFinal() {
|
|
275
|
-
let proto = Object.getPrototypeOf(this);
|
|
276
|
-
while (proto && proto !== Writable.prototype && proto !== Object.prototype) {
|
|
277
|
-
if (Object.prototype.hasOwnProperty.call(proto, "_final")) {
|
|
278
|
-
return proto._final.bind(this);
|
|
279
|
-
}
|
|
280
|
-
proto = Object.getPrototypeOf(proto);
|
|
281
|
-
}
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
/** Detect subclass _writev override or option-provided writev. */
|
|
285
|
-
_getWritevHook() {
|
|
286
|
-
const proto = Object.getPrototypeOf(this);
|
|
287
|
-
if (proto._writev && proto._writev !== Writable.prototype._writev) {
|
|
288
|
-
return proto._writev.bind(this);
|
|
289
|
-
}
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Wire up an AbortSignal to destroy this stream on abort.
|
|
294
|
-
*/
|
|
295
|
-
_setupAbortSignal(signal) {
|
|
296
|
-
if (signal.aborted) {
|
|
297
|
-
this.destroy(createAbortError(signal.reason));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const onAbort = () => {
|
|
301
|
-
cleanup();
|
|
302
|
-
this.destroy(createAbortError(signal.reason));
|
|
303
|
-
};
|
|
304
|
-
const onDone = () => {
|
|
305
|
-
cleanup();
|
|
306
|
-
};
|
|
307
|
-
const cleanup = () => {
|
|
308
|
-
signal.removeEventListener("abort", onAbort);
|
|
309
|
-
this.off("close", onDone);
|
|
310
|
-
this.off("finish", onDone);
|
|
311
|
-
this.off("error", onDone);
|
|
312
|
-
};
|
|
313
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
314
|
-
this.on("close", onDone);
|
|
315
|
-
this.on("finish", onDone);
|
|
316
|
-
this.on("error", onDone);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Set default encoding for string writes
|
|
320
|
-
*/
|
|
321
|
-
setDefaultEncoding(encoding) {
|
|
322
|
-
// Validate encoding (Node.js throws ERR_UNKNOWN_ENCODING)
|
|
323
|
-
stringToEncodedBytes("", encoding);
|
|
324
|
-
this._defaultEncoding = encoding;
|
|
325
|
-
return this;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Buffer writes until uncork() is called
|
|
329
|
-
*/
|
|
330
|
-
cork() {
|
|
331
|
-
this._corked++;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Flush buffered writes from cork()
|
|
335
|
-
*/
|
|
336
|
-
uncork() {
|
|
337
|
-
if (this._corked > 0) {
|
|
338
|
-
this._corked--;
|
|
339
|
-
}
|
|
340
|
-
if (this._corked === 0) {
|
|
341
|
-
const chunks = this._corkedChunks;
|
|
342
|
-
this._corkedChunks = [];
|
|
343
|
-
if (chunks.length === 0) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
// Reset _writableLength for corked chunks first — _doWrite will re-add
|
|
347
|
-
// each chunk's size, so we must subtract the corked total to avoid
|
|
348
|
-
// double-counting (write() already added the size when buffering).
|
|
349
|
-
for (const { chunk } of chunks) {
|
|
350
|
-
this._writableLength -= this._getChunkSize(chunk);
|
|
351
|
-
}
|
|
352
|
-
// L3: If _writev is available and there are multiple chunks, batch them
|
|
353
|
-
const writevFn = this._resolvedWritev;
|
|
354
|
-
if (writevFn && chunks.length > 1) {
|
|
355
|
-
const batchChunks = chunks.map(({ chunk, encoding }) => ({ chunk, encoding }));
|
|
356
|
-
const totalSize = batchChunks.reduce((sum, { chunk }) => sum + this._getChunkSize(chunk), 0);
|
|
357
|
-
this._writableLength += totalSize;
|
|
358
|
-
this._writing = true;
|
|
359
|
-
try {
|
|
360
|
-
writevFn(batchChunks, err => {
|
|
361
|
-
this._writableLength -= totalSize;
|
|
362
|
-
this._writing = false;
|
|
363
|
-
if (err) {
|
|
364
|
-
if (!this._destroyed) {
|
|
365
|
-
this._errored = err;
|
|
366
|
-
this.emit("error", err);
|
|
367
|
-
if (this._autoDestroy) {
|
|
368
|
-
this.destroy(err);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
// Call individual callbacks with error
|
|
372
|
-
for (const { callback } of chunks) {
|
|
373
|
-
callback?.(err);
|
|
374
|
-
}
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (this._needDrain && this._writableLength < this._highWaterMark) {
|
|
378
|
-
this._needDrain = false;
|
|
379
|
-
deferTask(() => this.emit("drain"));
|
|
380
|
-
}
|
|
381
|
-
// Call individual callbacks with success
|
|
382
|
-
for (const { callback } of chunks) {
|
|
383
|
-
callback?.(null);
|
|
384
|
-
}
|
|
385
|
-
// Drain any remaining queued writes or finalize
|
|
386
|
-
this._drainWriteQueue();
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
catch (err) {
|
|
390
|
-
this._writableLength -= totalSize;
|
|
391
|
-
this._writing = false;
|
|
392
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
393
|
-
if (!this._destroyed) {
|
|
394
|
-
this._errored = error;
|
|
395
|
-
this.emit("error", error);
|
|
396
|
-
if (this._autoDestroy) {
|
|
397
|
-
this.destroy(error);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
for (const { callback } of chunks) {
|
|
401
|
-
callback?.(error);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
// No _writev or single chunk — flush one at a time
|
|
407
|
-
for (const { chunk, encoding, callback } of chunks) {
|
|
408
|
-
this._doWrite(chunk, encoding, callback);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
write(chunk, encodingOrCallback, callback) {
|
|
414
|
-
// Node.js: writing null is always an error (even in object mode).
|
|
415
|
-
// Node.js throws this synchronously and does NOT emit an error event.
|
|
416
|
-
if (chunk === null) {
|
|
417
|
-
const err = new TypeError("May not write null values to stream");
|
|
418
|
-
err.code = "ERR_STREAM_NULL_VALUES";
|
|
419
|
-
throw err;
|
|
420
|
-
}
|
|
421
|
-
if (this._destroyed || this._ended) {
|
|
422
|
-
// Node.js distinguishes write-after-destroy (ERR_STREAM_DESTROYED) from
|
|
423
|
-
// write-after-end (ERR_STREAM_WRITE_AFTER_END).
|
|
424
|
-
const isDestroyed = this._destroyed && !this._ended;
|
|
425
|
-
const err = new Error(isDestroyed ? "Cannot call write after a stream was destroyed" : "write after end");
|
|
426
|
-
err.code = isDestroyed ? "ERR_STREAM_DESTROYED" : "ERR_STREAM_WRITE_AFTER_END";
|
|
427
|
-
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
428
|
-
// Node.js: set errored synchronously on write-after-end
|
|
429
|
-
if (!this._errored) {
|
|
430
|
-
this._errored = err;
|
|
431
|
-
}
|
|
432
|
-
// Node.js: callback is deferred via process.nextTick, not called synchronously.
|
|
433
|
-
// The callback fires BEFORE the error event (both deferred).
|
|
434
|
-
if (cb) {
|
|
435
|
-
deferTask(() => cb(err));
|
|
436
|
-
}
|
|
437
|
-
// Node.js: only emit 'error' for write-after-end when NOT destroyed,
|
|
438
|
-
// and only emit once. Setting _errorEmitted here is intentional — a stream
|
|
439
|
-
// should only ever emit one 'error' event (including from later destroy()).
|
|
440
|
-
if (!this._destroyed && !this._errorEmitted) {
|
|
441
|
-
this._errorEmitted = true;
|
|
442
|
-
deferTask(() => this.emit("error", err));
|
|
443
|
-
}
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
const encoding = typeof encodingOrCallback === "string" ? encodingOrCallback : this._defaultEncoding;
|
|
447
|
-
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
448
|
-
// If corked, buffer the write
|
|
449
|
-
if (this._corked > 0) {
|
|
450
|
-
const normalized = this._normalizeWriteChunk(chunk, encoding);
|
|
451
|
-
this._corkedChunks.push({
|
|
452
|
-
chunk: normalized.chunk,
|
|
453
|
-
encoding: normalized.encoding,
|
|
454
|
-
callback: cb
|
|
455
|
-
});
|
|
456
|
-
const chunkSize = this._getChunkSize(normalized.chunk, normalized.encoding);
|
|
457
|
-
this._writableLength += chunkSize;
|
|
458
|
-
const ok = this._writableLength < this._highWaterMark;
|
|
459
|
-
if (!ok) {
|
|
460
|
-
this._needDrain = true;
|
|
461
|
-
}
|
|
462
|
-
return ok;
|
|
463
|
-
}
|
|
464
|
-
const normalized = this._normalizeWriteChunk(chunk, encoding);
|
|
465
|
-
const ok = this._doWrite(normalized.chunk, normalized.encoding, cb);
|
|
466
|
-
if (!ok) {
|
|
467
|
-
this._needDrain = true;
|
|
468
|
-
}
|
|
469
|
-
return ok;
|
|
470
|
-
}
|
|
471
|
-
_doWrite(chunk, encoding, callback) {
|
|
472
|
-
const chunkSize = this._getChunkSize(chunk, encoding);
|
|
473
|
-
this._writableLength += chunkSize;
|
|
474
|
-
if (this._directWrite) {
|
|
475
|
-
// Direct-write path: call _writeFunc directly, with Node.js-style
|
|
476
|
-
// serialization — only one _writeFunc is in-flight at a time.
|
|
477
|
-
// Also queue if _construct has not yet completed.
|
|
478
|
-
if (this._writing || !this._constructed) {
|
|
479
|
-
// Queue the write for later — will be drained when the current
|
|
480
|
-
// _writeFunc callback fires or when _construct completes.
|
|
481
|
-
this._writeQueue.push({ chunk, chunkSize, encoding, callback });
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
this._writing = true;
|
|
485
|
-
this._callWriteFunc(chunk, chunkSize, encoding, callback);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
// Async path: use Web WritableStream (external stream or subclass _write)
|
|
490
|
-
const writer = this._getWriter();
|
|
491
|
-
writer
|
|
492
|
-
.write(chunk)
|
|
493
|
-
.then(() => {
|
|
494
|
-
this._writableLength -= chunkSize;
|
|
495
|
-
if (this._needDrain && this._writableLength < this._highWaterMark) {
|
|
496
|
-
this._needDrain = false;
|
|
497
|
-
deferTask(() => this.emit("drain"));
|
|
498
|
-
}
|
|
499
|
-
callback?.(null);
|
|
500
|
-
})
|
|
501
|
-
.catch(err => {
|
|
502
|
-
this._writableLength -= chunkSize;
|
|
503
|
-
if (!this._destroyed) {
|
|
504
|
-
this._errored = err;
|
|
505
|
-
this._errorEmitted = true;
|
|
506
|
-
this.emit("error", err);
|
|
507
|
-
if (this._autoDestroy) {
|
|
508
|
-
this.destroy(err);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
callback?.(err);
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
// Return false if we've exceeded high water mark (for backpressure)
|
|
515
|
-
return this._writableLength < this._highWaterMark;
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Call _writeFunc for a single chunk. When the callback fires (sync or async),
|
|
519
|
-
* drain the next entry from _writeQueue, or run the pending end() if the
|
|
520
|
-
* queue is empty.
|
|
521
|
-
*/
|
|
522
|
-
_callWriteFunc(chunk, chunkSize, encoding, callback) {
|
|
523
|
-
try {
|
|
524
|
-
// Node.js passes undefined as encoding in objectMode
|
|
525
|
-
const enc = this._objectMode ? undefined : encoding;
|
|
526
|
-
// Guard against user code calling the callback more than once.
|
|
527
|
-
// Node.js has a similar guard in `onwrite()` (lib/internal/streams/writable.js).
|
|
528
|
-
let cbCalled = false;
|
|
529
|
-
// Track whether the callback is invoked synchronously (before _writeFunc
|
|
530
|
-
// returns). Node.js defers error emission and the write callback via
|
|
531
|
-
// process.nextTick when the callback fires synchronously; when it fires
|
|
532
|
-
// asynchronously (e.g. in a setTimeout / Promise callback), the error
|
|
533
|
-
// and callback execute synchronously in that async context.
|
|
534
|
-
let sync = true;
|
|
535
|
-
this._writeFunc(chunk, enc, err => {
|
|
536
|
-
if (cbCalled) {
|
|
537
|
-
// Node.js throws ERR_MULTIPLE_CALLBACK on double invocation.
|
|
538
|
-
const multiErr = new Error("Callback called multiple times");
|
|
539
|
-
multiErr.code = "ERR_MULTIPLE_CALLBACK";
|
|
540
|
-
this.destroy(multiErr);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
cbCalled = true;
|
|
544
|
-
if (err) {
|
|
545
|
-
this._writableLength -= chunkSize;
|
|
546
|
-
if (!this._destroyed) {
|
|
547
|
-
this._errored = err;
|
|
548
|
-
this._errorEmitted = true;
|
|
549
|
-
if (sync) {
|
|
550
|
-
// Node.js: synchronous callback → defer both callback and error
|
|
551
|
-
// via process.nextTick. Callback fires first, then error event.
|
|
552
|
-
deferTask(() => {
|
|
553
|
-
callback?.(err);
|
|
554
|
-
this.emit("error", err);
|
|
555
|
-
if (this._autoDestroy) {
|
|
556
|
-
this.destroy(err);
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
// Async callback → execute synchronously (matches Node.js).
|
|
562
|
-
callback?.(err);
|
|
563
|
-
this.emit("error", err);
|
|
564
|
-
if (this._autoDestroy) {
|
|
565
|
-
this.destroy(err);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
callback?.(err);
|
|
571
|
-
}
|
|
572
|
-
// On error, drain remaining queued writes with the error and
|
|
573
|
-
// don't process pending end.
|
|
574
|
-
this._writing = false;
|
|
575
|
-
this._flushWriteQueueOnError(err);
|
|
576
|
-
// If end() was called and destroy() didn't already claim _pendingEnd
|
|
577
|
-
// (e.g. autoDestroy is false), notify the end() callback of the error.
|
|
578
|
-
if (this._pendingEnd) {
|
|
579
|
-
const { cb: endCb } = this._pendingEnd;
|
|
580
|
-
this._pendingEnd = null;
|
|
581
|
-
endCb?.(err);
|
|
582
|
-
}
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
this._writableLength -= chunkSize;
|
|
586
|
-
if (this._needDrain && this._writableLength < this._highWaterMark) {
|
|
587
|
-
this._needDrain = false;
|
|
588
|
-
deferTask(() => this.emit("drain"));
|
|
589
|
-
}
|
|
590
|
-
callback?.(null);
|
|
591
|
-
// Drain next queued write, or finalize if end() is pending.
|
|
592
|
-
this._drainWriteQueue();
|
|
593
|
-
});
|
|
594
|
-
sync = false;
|
|
595
|
-
}
|
|
596
|
-
catch (err) {
|
|
597
|
-
this._writableLength -= chunkSize;
|
|
598
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
599
|
-
if (!this._destroyed) {
|
|
600
|
-
this._errored = error;
|
|
601
|
-
this.emit("error", error);
|
|
602
|
-
if (this._autoDestroy) {
|
|
603
|
-
this.destroy(error);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
callback?.(error);
|
|
607
|
-
this._writing = false;
|
|
608
|
-
this._flushWriteQueueOnError(error);
|
|
609
|
-
// If end() was called and destroy() didn't already claim _pendingEnd,
|
|
610
|
-
// notify the end() callback of the error.
|
|
611
|
-
if (this._pendingEnd) {
|
|
612
|
-
const { cb: endCb } = this._pendingEnd;
|
|
613
|
-
this._pendingEnd = null;
|
|
614
|
-
endCb?.(error);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
/** Process the next queued write, or run pending end(). */
|
|
619
|
-
_drainWriteQueue() {
|
|
620
|
-
if (this._writeQueue.length === 0) {
|
|
621
|
-
this._writing = false;
|
|
622
|
-
// If end() was called while writes were in-flight, finalize now.
|
|
623
|
-
if (this._pendingEnd) {
|
|
624
|
-
const { cb } = this._pendingEnd;
|
|
625
|
-
this._pendingEnd = null;
|
|
626
|
-
this._doFinish(cb);
|
|
627
|
-
}
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
// If _writev is available and there are multiple queued chunks, batch them.
|
|
631
|
-
// This matches Node.js behavior where _writev is used for naturally-queued
|
|
632
|
-
// writes, not just on uncork.
|
|
633
|
-
const writevFn = this._resolvedWritev;
|
|
634
|
-
if (writevFn && this._writeQueue.length > 1) {
|
|
635
|
-
// Swap array instead of splice(0) to avoid O(n) copy
|
|
636
|
-
const chunks = this._writeQueue;
|
|
637
|
-
this._writeQueue = [];
|
|
638
|
-
let totalSize = 0;
|
|
639
|
-
const batchChunks = new Array(chunks.length);
|
|
640
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
641
|
-
const entry = chunks[i];
|
|
642
|
-
batchChunks[i] = { chunk: entry.chunk, encoding: entry.encoding };
|
|
643
|
-
totalSize += entry.chunkSize;
|
|
644
|
-
}
|
|
645
|
-
try {
|
|
646
|
-
writevFn(batchChunks, err => {
|
|
647
|
-
if (err) {
|
|
648
|
-
this._writableLength -= totalSize;
|
|
649
|
-
if (!this._destroyed) {
|
|
650
|
-
this._errored = err;
|
|
651
|
-
this._errorEmitted = true;
|
|
652
|
-
this.emit("error", err);
|
|
653
|
-
}
|
|
654
|
-
for (const entry of chunks) {
|
|
655
|
-
entry.callback?.(err);
|
|
656
|
-
}
|
|
657
|
-
this._writing = false;
|
|
658
|
-
this._flushWriteQueueOnError(err);
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
this._writableLength -= totalSize;
|
|
662
|
-
if (this._needDrain && this._writableLength < this._highWaterMark) {
|
|
663
|
-
this._needDrain = false;
|
|
664
|
-
deferTask(() => this.emit("drain"));
|
|
665
|
-
}
|
|
666
|
-
for (const entry of chunks) {
|
|
667
|
-
entry.callback?.(null);
|
|
668
|
-
}
|
|
669
|
-
// Continue draining (more writes may have arrived during _writev)
|
|
670
|
-
this._drainWriteQueue();
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
catch (err) {
|
|
674
|
-
this._writableLength -= totalSize;
|
|
675
|
-
this._writing = false;
|
|
676
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
677
|
-
if (!this._destroyed) {
|
|
678
|
-
this._errored = error;
|
|
679
|
-
this.emit("error", error);
|
|
680
|
-
}
|
|
681
|
-
for (const entry of chunks) {
|
|
682
|
-
entry.callback?.(error);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
// Single queued write — use _write
|
|
688
|
-
const next = this._writeQueue.shift();
|
|
689
|
-
this._callWriteFunc(next.chunk, next.chunkSize, next.encoding, next.callback);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
/** Discard queued writes after an error. */
|
|
693
|
-
_flushWriteQueueOnError(err) {
|
|
694
|
-
const queue = this._writeQueue;
|
|
695
|
-
this._writeQueue = [];
|
|
696
|
-
for (const entry of queue) {
|
|
697
|
-
this._writableLength -= entry.chunkSize;
|
|
698
|
-
entry.callback?.(err);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
/**
|
|
702
|
-
* Run _finalFunc and emit finish/close.
|
|
703
|
-
* Events are deferred via deferTask to match Node.js process.nextTick
|
|
704
|
-
* behavior, so listeners registered after end() can still receive them.
|
|
705
|
-
*/
|
|
706
|
-
_doFinish(cb) {
|
|
707
|
-
if (this._finalFunc) {
|
|
708
|
-
this._finalFunc(err => {
|
|
709
|
-
if (err) {
|
|
710
|
-
this._errorEmitted = true;
|
|
711
|
-
this.emit("error", err);
|
|
712
|
-
// Match Node.js: auto-destroy and emit close after _final error.
|
|
713
|
-
// Store cb in _pendingEnd so destroy() fires it after close
|
|
714
|
-
// (Node.js ordering: error → close → end-cb).
|
|
715
|
-
if (cb) {
|
|
716
|
-
this._pendingEnd = { cb };
|
|
717
|
-
}
|
|
718
|
-
if (this._autoDestroy && !this._destroyed) {
|
|
719
|
-
this.destroy(err);
|
|
720
|
-
}
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
const doFinish = () => {
|
|
724
|
-
// If destroyed between end() and this microtask, suppress finish
|
|
725
|
-
// and let destroy() handle close emission. Fire the end-cb so the
|
|
726
|
-
// caller is notified (Node.js fires it via the 'close' listener).
|
|
727
|
-
if (this._destroyed) {
|
|
728
|
-
cb?.();
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
this._finished = true;
|
|
732
|
-
// Node.js ordering: prefinish → end-cb → finish → autoDestroy
|
|
733
|
-
this.emit("prefinish");
|
|
734
|
-
cb?.();
|
|
735
|
-
this.emit("finish");
|
|
736
|
-
// Node.js: autoDestroy calls destroy() after finish, which emits
|
|
737
|
-
// close. Without autoDestroy, close only fires on explicit destroy().
|
|
738
|
-
if (this._autoDestroy) {
|
|
739
|
-
this.destroy();
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
// When Transform signals _syncFinish (via _scheduleEnd for simple
|
|
743
|
-
// Transforms with no pipe destinations), emit finish synchronously
|
|
744
|
-
// to keep it ahead of user Promises. Otherwise defer as usual.
|
|
745
|
-
if (this._syncFinish) {
|
|
746
|
-
this._syncFinish = false;
|
|
747
|
-
doFinish();
|
|
748
|
-
}
|
|
749
|
-
else {
|
|
750
|
-
deferTask(doFinish);
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
const doFinish = () => {
|
|
756
|
-
// If destroyed between end() and this microtask, suppress finish
|
|
757
|
-
// and let destroy() handle close emission.
|
|
758
|
-
if (this._destroyed) {
|
|
759
|
-
cb?.();
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
this._finished = true;
|
|
763
|
-
// Node.js ordering: prefinish → end-cb → finish → autoDestroy
|
|
764
|
-
this.emit("prefinish");
|
|
765
|
-
cb?.();
|
|
766
|
-
this.emit("finish");
|
|
767
|
-
// Node.js: autoDestroy calls destroy() after finish, which emits
|
|
768
|
-
// close. Without autoDestroy, close only fires on explicit destroy().
|
|
769
|
-
if (this._autoDestroy) {
|
|
770
|
-
this.destroy();
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
// When already in a deferred context (e.g. end() called from a
|
|
774
|
-
// pipe end-listener or Transform's _scheduleEnd), execute finish
|
|
775
|
-
// synchronously to keep it ahead of user Promises — matching
|
|
776
|
-
// Node.js process.nextTick nesting behavior.
|
|
777
|
-
//
|
|
778
|
-
// This only affects plain Writables without _finalFunc. Transform's
|
|
779
|
-
// internal writable always has _finalFunc, so it takes the branch
|
|
780
|
-
// above (with _syncFinish gating) instead of this one.
|
|
781
|
-
if (inDeferredContext()) {
|
|
782
|
-
doFinish();
|
|
783
|
-
}
|
|
784
|
-
else {
|
|
785
|
-
deferTask(doFinish);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
_getChunkSize(chunk, encoding) {
|
|
790
|
-
if (this._objectMode) {
|
|
791
|
-
return 1;
|
|
792
|
-
}
|
|
793
|
-
if (chunk instanceof Uint8Array) {
|
|
794
|
-
return chunk.byteLength;
|
|
795
|
-
}
|
|
796
|
-
if (typeof chunk === "string") {
|
|
797
|
-
return getStringByteLength(chunk, encoding);
|
|
798
|
-
}
|
|
799
|
-
return 0;
|
|
800
|
-
}
|
|
801
|
-
_normalizeWriteChunk(chunk, encoding) {
|
|
802
|
-
// In Node.js, decodeStrings: true converts strings to Buffer and sets encoding
|
|
803
|
-
// to "buffer". We match that behavior by converting strings to Uint8Array.
|
|
804
|
-
if (this._objectMode || typeof chunk !== "string") {
|
|
805
|
-
return { chunk, encoding };
|
|
806
|
-
}
|
|
807
|
-
if (!this._decodeStrings) {
|
|
808
|
-
// When decodeStrings is explicitly false, pass the original encoding through
|
|
809
|
-
return { chunk, encoding };
|
|
810
|
-
}
|
|
811
|
-
// decodeStrings: true (default) — convert string to Uint8Array matching Node.js
|
|
812
|
-
// behavior where strings are converted to Buffer with encoding set to "buffer".
|
|
813
|
-
// We also override toString() so that it behaves like Node.js Buffer.toString(),
|
|
814
|
-
// which returns a UTF-8 decoded string by default (not comma-separated byte values).
|
|
815
|
-
const encoded = stringToEncodedBytes(chunk, encoding);
|
|
816
|
-
encoded.toString = encodedBytesToString;
|
|
817
|
-
// Boundary: decodeStrings materializes string chunks into bytes; the encoded
|
|
818
|
-
// Uint8Array is the runtime chunk for element type T.
|
|
819
|
-
return { chunk: encoded, encoding: "buffer" };
|
|
820
|
-
}
|
|
821
|
-
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
822
|
-
if (this._ended) {
|
|
823
|
-
const { chunk, encoding, cb: endCb } = parseEndArgs(chunkOrCallback, encodingOrCallback, callback);
|
|
824
|
-
// Node invokes the end() callback with an error in some paths (write-after-
|
|
825
|
-
// end / already-finished). `parseEndArgs` types it `() => void`, so view it
|
|
826
|
-
// through an error-accepting signature for those Node-parity invocations.
|
|
827
|
-
const errCb = endCb;
|
|
828
|
-
// If a chunk was provided, this is a write-after-end error (Node.js behavior).
|
|
829
|
-
if (chunk !== undefined) {
|
|
830
|
-
this.write(chunk, encoding ?? this._defaultEncoding, err => {
|
|
831
|
-
errCb?.(err ?? null);
|
|
832
|
-
});
|
|
833
|
-
return this;
|
|
834
|
-
}
|
|
835
|
-
// If we've already finished, Node.js calls the callback with
|
|
836
|
-
// ERR_STREAM_ALREADY_FINISHED (but does not emit an error event).
|
|
837
|
-
if (this._finished && endCb) {
|
|
838
|
-
const err = new Error("Cannot call end after a stream was finished");
|
|
839
|
-
err.code = "ERR_STREAM_ALREADY_FINISHED";
|
|
840
|
-
deferTask(() => errCb(err));
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
// Otherwise, a redundant end() is a no-op; if a callback was provided,
|
|
844
|
-
// Node.js calls it with no error.
|
|
845
|
-
deferTask(() => errCb?.(null));
|
|
846
|
-
}
|
|
847
|
-
return this;
|
|
848
|
-
}
|
|
849
|
-
this._ended = true;
|
|
850
|
-
// Node.js: end() resets writable state. Any prior setter override is cleared
|
|
851
|
-
// so the getter reflects the true ended state.
|
|
852
|
-
this._writableOverride = undefined;
|
|
853
|
-
const { chunk, encoding, cb } = parseEndArgs(chunkOrCallback, encodingOrCallback, callback);
|
|
854
|
-
// Node.js: end() auto-uncorks. If the stream is corked, flush all buffered
|
|
855
|
-
// writes before finalizing so that cork() → write() → end() doesn't lose data.
|
|
856
|
-
if (this._corked > 0) {
|
|
857
|
-
this._corked = 1;
|
|
858
|
-
this.uncork();
|
|
859
|
-
}
|
|
860
|
-
if (this._directWrite) {
|
|
861
|
-
// Direct-write path: enqueue final chunk (if any), then wait for the
|
|
862
|
-
// write queue to drain before running _finalFunc + emitting finish.
|
|
863
|
-
if (chunk !== undefined) {
|
|
864
|
-
// Normalize the end chunk (decodeStrings etc.) just like write()
|
|
865
|
-
const normalized = this._normalizeWriteChunk(chunk, encoding ?? this._defaultEncoding);
|
|
866
|
-
this._doWrite(normalized.chunk, normalized.encoding);
|
|
867
|
-
}
|
|
868
|
-
// If the write errored synchronously (e.g. _writeFunc called back with
|
|
869
|
-
// an error before returning), the stream may already be destroyed/errored.
|
|
870
|
-
// Node.js propagates the write error to the end() callback.
|
|
871
|
-
if (this._errored) {
|
|
872
|
-
const endErr = this._errored;
|
|
873
|
-
deferTask(() => cb?.(endErr));
|
|
874
|
-
return this;
|
|
875
|
-
}
|
|
876
|
-
// If writes are still in-flight or queued, defer finalization.
|
|
877
|
-
if (this._writing || this._writeQueue.length > 0) {
|
|
878
|
-
this._pendingEnd = { cb };
|
|
879
|
-
}
|
|
880
|
-
else {
|
|
881
|
-
this._doFinish(cb);
|
|
882
|
-
}
|
|
883
|
-
return this;
|
|
884
|
-
}
|
|
885
|
-
// Async end path — uses Web WritableStream.
|
|
886
|
-
const finish = async () => {
|
|
887
|
-
try {
|
|
888
|
-
const writer = this._getWriter();
|
|
889
|
-
if (chunk !== undefined) {
|
|
890
|
-
// Normalize the end chunk (decodeStrings etc.) just like write() — matches
|
|
891
|
-
// the direct-write path above and Node.js behavior.
|
|
892
|
-
const { chunk: normalized } = this._normalizeWriteChunk(chunk, encoding ?? this._defaultEncoding);
|
|
893
|
-
await writer.write(normalized);
|
|
894
|
-
}
|
|
895
|
-
await writer.close();
|
|
896
|
-
if (this._writer === writer) {
|
|
897
|
-
this._writer = null;
|
|
898
|
-
try {
|
|
899
|
-
writer.releaseLock();
|
|
900
|
-
}
|
|
901
|
-
catch {
|
|
902
|
-
// Ignore
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
// If we own the underlying Web WritableStream, its `close()` handler already
|
|
906
|
-
// emits finish/close. For external streams, we must emit finish ourselves.
|
|
907
|
-
if (!this._ownsStream) {
|
|
908
|
-
this._finished = true;
|
|
909
|
-
this.emit("finish");
|
|
910
|
-
if (this._autoDestroy) {
|
|
911
|
-
this.destroy();
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
if (cb) {
|
|
915
|
-
cb();
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
catch (err) {
|
|
919
|
-
this.emit("error", err);
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
finish();
|
|
923
|
-
return this;
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Destroy the stream
|
|
927
|
-
*/
|
|
928
|
-
destroy(error) {
|
|
929
|
-
if (this._destroyed) {
|
|
930
|
-
return this;
|
|
931
|
-
}
|
|
932
|
-
this._destroyed = true;
|
|
933
|
-
// Node.js: destroy() resets writable override so getter reflects true state
|
|
934
|
-
this._writableOverride = undefined;
|
|
935
|
-
// Set state synchronously (matches Node.js), defer event emission via deferTask
|
|
936
|
-
// to match Node.js process.nextTick behavior
|
|
937
|
-
if (error && !this._errored) {
|
|
938
|
-
this._errored = error;
|
|
939
|
-
}
|
|
940
|
-
// Cancel pending writes in the write queue (matches Node.js behavior).
|
|
941
|
-
// Node.js discards queued writes on destroy, invoking their callbacks with
|
|
942
|
-
// the destroy error so callers are notified.
|
|
943
|
-
if (this._writeQueue.length > 0) {
|
|
944
|
-
this._flushWriteQueueOnError(error ?? new Error("Cannot call write after a stream was destroyed"));
|
|
945
|
-
}
|
|
946
|
-
// Also flush corked chunks — if the stream is destroyed while corked,
|
|
947
|
-
// pending corked-chunk callbacks must be notified.
|
|
948
|
-
if (this._corkedChunks.length > 0) {
|
|
949
|
-
const destroyErr = error ?? new Error("Cannot call write after a stream was destroyed");
|
|
950
|
-
for (const entry of this._corkedChunks) {
|
|
951
|
-
entry.callback?.(destroyErr);
|
|
952
|
-
}
|
|
953
|
-
this._corkedChunks = [];
|
|
954
|
-
}
|
|
955
|
-
// Node.js invokes the end() callback even when destroy() is called
|
|
956
|
-
// while writes are pending. Fire it asynchronously (after error/close).
|
|
957
|
-
const pendingEndCb = this._pendingEnd?.cb;
|
|
958
|
-
this._pendingEnd = null;
|
|
959
|
-
this._writing = false;
|
|
960
|
-
if (this._writer) {
|
|
961
|
-
const writer = this._writer;
|
|
962
|
-
this._writer = null;
|
|
963
|
-
writer
|
|
964
|
-
.abort(error)
|
|
965
|
-
.catch(() => { })
|
|
966
|
-
.finally(() => {
|
|
967
|
-
try {
|
|
968
|
-
writer.releaseLock();
|
|
969
|
-
}
|
|
970
|
-
catch {
|
|
971
|
-
// Ignore
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
// If subclass overrides _destroy, call it and wait for callback before
|
|
976
|
-
// emitting error/close (matches Node.js behavior).
|
|
977
|
-
const afterDestroy = (finalError) => {
|
|
978
|
-
// Node.js: _destroy's callback determines whether an error is emitted.
|
|
979
|
-
// - cb(null) or cb() or cb(undefined): suppress the original error
|
|
980
|
-
// - cb(new Error(...)): replace with the new error, emit it
|
|
981
|
-
// Node.js checks `if (err)` on the callback argument, so null/undefined/no-arg
|
|
982
|
-
// all suppress the error. Only a truthy error value triggers the error event.
|
|
983
|
-
const err = finalError || null;
|
|
984
|
-
if (err) {
|
|
985
|
-
this._errored = err;
|
|
986
|
-
}
|
|
987
|
-
this._closed = true;
|
|
988
|
-
const doEmit = () => {
|
|
989
|
-
if (err && !this._errorEmitted) {
|
|
990
|
-
this._errorEmitted = true;
|
|
991
|
-
this.emit("error", err);
|
|
992
|
-
}
|
|
993
|
-
if (this._emitClose) {
|
|
994
|
-
this.emit("close");
|
|
995
|
-
}
|
|
996
|
-
// Node.js fires the end() callback after close, even on destroy.
|
|
997
|
-
// Pass the error so the end() caller is notified of the failure.
|
|
998
|
-
pendingEndCb?.(err);
|
|
999
|
-
};
|
|
1000
|
-
// When already inside a deferTask callback (e.g. autoDestroy after
|
|
1001
|
-
// 'finish') and there is no async _destroy hook, emit synchronously.
|
|
1002
|
-
// This matches Node.js where nested process.nextTick runs before
|
|
1003
|
-
// interleaved Promise.then callbacks.
|
|
1004
|
-
// Only apply when the stream finished naturally (autoDestroy path) —
|
|
1005
|
-
// user-initiated destroy() from within a listener must still defer
|
|
1006
|
-
// so that async cleanup can complete.
|
|
1007
|
-
if (inDeferredContext() && !this._hasDestroyHook() && this._finished) {
|
|
1008
|
-
doEmit();
|
|
1009
|
-
}
|
|
1010
|
-
else {
|
|
1011
|
-
deferTask(doEmit);
|
|
1012
|
-
}
|
|
1013
|
-
};
|
|
1014
|
-
if (this._hasDestroyHook()) {
|
|
1015
|
-
try {
|
|
1016
|
-
this._destroy(error ?? null, afterDestroy);
|
|
1017
|
-
}
|
|
1018
|
-
catch (err) {
|
|
1019
|
-
afterDestroy(err instanceof Error ? err : new Error(String(err)));
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
else {
|
|
1023
|
-
afterDestroy(error);
|
|
1024
|
-
}
|
|
1025
|
-
return this;
|
|
1026
|
-
}
|
|
1027
|
-
/**
|
|
1028
|
-
* Override in subclass to customise destroy behaviour.
|
|
1029
|
-
* Call `callback(err)` when cleanup is complete.
|
|
1030
|
-
*/
|
|
1031
|
-
_destroy(error, callback) {
|
|
1032
|
-
callback(error);
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Reverse the effects of destroy() so the stream can potentially be reused.
|
|
1036
|
-
* Matches Node.js _undestroy() which resets destroyed and closed flags.
|
|
1037
|
-
*/
|
|
1038
|
-
_undestroy() {
|
|
1039
|
-
this._destroyed = false;
|
|
1040
|
-
this._closed = false;
|
|
1041
|
-
this._errored = null;
|
|
1042
|
-
this._errorEmitted = false;
|
|
1043
|
-
}
|
|
1044
|
-
/** Check if _destroy has been overridden by a subclass or constructor option. */
|
|
1045
|
-
_hasDestroyHook() {
|
|
1046
|
-
return (Object.prototype.hasOwnProperty.call(this, "_destroy") ||
|
|
1047
|
-
Object.getPrototypeOf(this)._destroy !== Writable.prototype._destroy);
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Synchronous dispose — destroys the stream.
|
|
1051
|
-
* Matches Node.js Symbol.dispose support (v20+, experimental).
|
|
1052
|
-
*/
|
|
1053
|
-
[Symbol.dispose]() {
|
|
1054
|
-
if (!this._destroyed) {
|
|
1055
|
-
this.destroy();
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
/**
|
|
1059
|
-
* Async dispose support (using await).
|
|
1060
|
-
* Destroys the stream and resolves after the 'close' event.
|
|
1061
|
-
*/
|
|
1062
|
-
async [Symbol.asyncDispose]() {
|
|
1063
|
-
// Match Node.js behavior:
|
|
1064
|
-
// 1. If not yet destroyed, destroy the stream (always resolves)
|
|
1065
|
-
// 2. If already destroyed by someone else, reject with "Premature close"
|
|
1066
|
-
// unless the stream ended gracefully (writableFinished)
|
|
1067
|
-
const selfInitiated = !this._destroyed;
|
|
1068
|
-
if (selfInitiated) {
|
|
1069
|
-
this.destroy();
|
|
1070
|
-
}
|
|
1071
|
-
return new Promise((resolve, reject) => {
|
|
1072
|
-
const settle = () => {
|
|
1073
|
-
if (selfInitiated || this._finished) {
|
|
1074
|
-
resolve();
|
|
1075
|
-
}
|
|
1076
|
-
else {
|
|
1077
|
-
reject(new Error("Premature close"));
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
if (this._closed) {
|
|
1081
|
-
settle();
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
this.once("close", settle);
|
|
1085
|
-
}
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
/**
|
|
1089
|
-
* Get the underlying Web WritableStream (internal).
|
|
1090
|
-
* @internal
|
|
1091
|
-
*/
|
|
1092
|
-
get _webStream() {
|
|
1093
|
-
if (!this._stream) {
|
|
1094
|
-
// Lazily create a Web WritableStream for sync-write Writables that need interop.
|
|
1095
|
-
this._stream = new WritableStream({
|
|
1096
|
-
write: chunk => new Promise((resolve, reject) => {
|
|
1097
|
-
// Boundary: Node passes undefined as the encoding in objectMode.
|
|
1098
|
-
const enc = this._objectMode ? undefined : this._defaultEncoding;
|
|
1099
|
-
this._writeFunc(chunk, enc, err => {
|
|
1100
|
-
if (err) {
|
|
1101
|
-
reject(err);
|
|
1102
|
-
}
|
|
1103
|
-
else {
|
|
1104
|
-
resolve();
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
}),
|
|
1108
|
-
close: async () => {
|
|
1109
|
-
if (this._finalFunc) {
|
|
1110
|
-
await new Promise((resolve, reject) => {
|
|
1111
|
-
this._finalFunc(err => {
|
|
1112
|
-
if (err) {
|
|
1113
|
-
reject(err);
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
resolve();
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
},
|
|
1122
|
-
abort: reason => {
|
|
1123
|
-
this.emit("error", reason);
|
|
1124
|
-
}
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
return this._stream;
|
|
1128
|
-
}
|
|
1129
|
-
get writable() {
|
|
1130
|
-
if (this._writableOverride !== undefined) {
|
|
1131
|
-
return this._writableOverride;
|
|
1132
|
-
}
|
|
1133
|
-
return !this._destroyed && !this._ended;
|
|
1134
|
-
}
|
|
1135
|
-
set writable(val) {
|
|
1136
|
-
// Node.js setter is a no-op compatibility shim — it stores the override
|
|
1137
|
-
// value but does NOT modify _ended or any other internal state.
|
|
1138
|
-
this._writableOverride = val;
|
|
1139
|
-
}
|
|
1140
|
-
get writableEnded() {
|
|
1141
|
-
return this._ended;
|
|
1142
|
-
}
|
|
1143
|
-
get writableFinished() {
|
|
1144
|
-
return this._finished;
|
|
1145
|
-
}
|
|
1146
|
-
get writableLength() {
|
|
1147
|
-
return this._writableLength;
|
|
1148
|
-
}
|
|
1149
|
-
/** Whether the stream has been destroyed */
|
|
1150
|
-
get destroyed() {
|
|
1151
|
-
return this._destroyed;
|
|
1152
|
-
}
|
|
1153
|
-
set destroyed(val) {
|
|
1154
|
-
this._destroyed = val;
|
|
1155
|
-
}
|
|
1156
|
-
/** The error that destroyed the stream, or null */
|
|
1157
|
-
get errored() {
|
|
1158
|
-
return this._errored;
|
|
1159
|
-
}
|
|
1160
|
-
/** Whether the stream has been closed */
|
|
1161
|
-
get closed() {
|
|
1162
|
-
return this._closed;
|
|
1163
|
-
}
|
|
1164
|
-
/** Whether the stream needs drain (write() returned false and drain not yet emitted) */
|
|
1165
|
-
get writableNeedDrain() {
|
|
1166
|
-
return this._needDrain;
|
|
1167
|
-
}
|
|
1168
|
-
/** How many times cork() has been called without uncork() */
|
|
1169
|
-
get writableCorked() {
|
|
1170
|
-
return this._corked;
|
|
1171
|
-
}
|
|
1172
|
-
/** Whether the stream was destroyed or errored before finishing */
|
|
1173
|
-
get writableAborted() {
|
|
1174
|
-
// Node.js: writable !== false && (destroyed || errored) && !finished
|
|
1175
|
-
// The 'writable !== false' check is for when user explicitly sets stream.writable = false.
|
|
1176
|
-
// We check _writableOverride (not the computed getter which returns false after destroy).
|
|
1177
|
-
if (this._writableOverride === false) {
|
|
1178
|
-
return false;
|
|
1179
|
-
}
|
|
1180
|
-
return (this._destroyed || !!this._errored) && !this._finished;
|
|
1181
|
-
}
|
|
1182
|
-
/** Whether the stream is in object mode */
|
|
1183
|
-
get writableObjectMode() {
|
|
1184
|
-
return this._objectMode;
|
|
1185
|
-
}
|
|
1186
|
-
get writableHighWaterMark() {
|
|
1187
|
-
return this._highWaterMark;
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Get the internal buffer contents as an array (matches Node.js behavior)
|
|
1191
|
-
*/
|
|
1192
|
-
get writableBuffer() {
|
|
1193
|
-
const chunks = this._corkedChunks.map(entry => entry.chunk);
|
|
1194
|
-
for (const entry of this._writeQueue) {
|
|
1195
|
-
chunks.push(entry.chunk);
|
|
1196
|
-
}
|
|
1197
|
-
return chunks;
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Pipe is not supported on Writable streams (matches Node.js behavior).
|
|
1201
|
-
* Node's Writable emits ERR_STREAM_CANNOT_PIPE asynchronously and returns undefined.
|
|
1202
|
-
*/
|
|
1203
|
-
pipe() {
|
|
1204
|
-
const err = new StreamStateError("pipe", "not readable");
|
|
1205
|
-
err.code = "ERR_STREAM_CANNOT_PIPE";
|
|
1206
|
-
// Node.js emits the error asynchronously (via process.nextTick).
|
|
1207
|
-
// In the browser we use deferTask for the same deferred semantics.
|
|
1208
|
-
deferTask(() => this.emit("error", err));
|
|
1209
|
-
return undefined;
|
|
1210
|
-
}
|
|
1211
|
-
_getWriter() {
|
|
1212
|
-
if (!this._writer) {
|
|
1213
|
-
this._writer = this._webStream.getWriter();
|
|
1214
|
-
}
|
|
1215
|
-
return this._writer;
|
|
1216
|
-
}
|
|
1217
|
-
// =========================================================================
|
|
1218
|
-
// Static Methods (Node.js compatibility)
|
|
1219
|
-
// =========================================================================
|
|
1220
|
-
/**
|
|
1221
|
-
* Check if a stream has been disturbed (data read or piped).
|
|
1222
|
-
* In Node.js this is inherited from the Stream base class and exists on
|
|
1223
|
-
* ALL stream classes (Readable, Writable, Duplex, Transform, PassThrough).
|
|
1224
|
-
* Delegates to Readable.isDisturbed, checking internal _readable for Duplex/Transform.
|
|
1225
|
-
*/
|
|
1226
|
-
static isDisturbed(stream) {
|
|
1227
|
-
if (stream && typeof stream === "object") {
|
|
1228
|
-
const inner = stream._readable;
|
|
1229
|
-
if (inner instanceof Readable) {
|
|
1230
|
-
return Readable.isDisturbed(inner);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
return Readable.isDisturbed(stream);
|
|
1234
|
-
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Convert a Web WritableStream to Node.js Writable
|
|
1237
|
-
*/
|
|
1238
|
-
static fromWeb(webStream, options) {
|
|
1239
|
-
return new Writable({ ...options, stream: webStream });
|
|
1240
|
-
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Convert a Node.js Writable to Web WritableStream
|
|
1243
|
-
*/
|
|
1244
|
-
static toWeb(nodeStream) {
|
|
1245
|
-
return nodeStream._webStream;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
// Node.js: Writable.prototype._writev === null (not undefined).
|
|
1249
|
-
Writable.prototype._writev = null;
|
|
1250
|
-
// Node.js: Writable.prototype._write throws ERR_METHOD_NOT_IMPLEMENTED.
|
|
1251
|
-
// This must exist on the prototype so that subclasses can call super._write()
|
|
1252
|
-
// and so that `_getSubclassWrite()` can detect overrides correctly.
|
|
1253
|
-
// Node.js throws synchronously (does NOT call the callback).
|
|
1254
|
-
Writable.prototype._write = function _write(_chunk, _encoding, _callback) {
|
|
1255
|
-
const err = new Error("The _write() method is not implemented");
|
|
1256
|
-
err.code = "ERR_METHOD_NOT_IMPLEMENTED";
|
|
1257
|
-
throw err;
|
|
1258
|
-
};
|
|
1259
|
-
// =============================================================================
|
|
1260
|
-
// Cross-environment stream normalization
|
|
1261
|
-
// =============================================================================
|
|
1262
|
-
/**
|
|
1263
|
-
* Normalize a user-provided writable into this module's Writable.
|
|
1264
|
-
* Keeps Web/Node branching at the stream-module boundary.
|
|
1265
|
-
*
|
|
1266
|
-
* A Node `Writable` is accepted too — it satisfies the structural
|
|
1267
|
-
* {@link WritableLike}. This variant deliberately does not name Node's class:
|
|
1268
|
-
* doing so pulled `@types/node` into the browser declaration graph.
|
|
1269
|
-
*/
|
|
1270
|
-
export function toWritable(stream) {
|
|
1271
|
-
if (stream instanceof Writable) {
|
|
1272
|
-
return stream;
|
|
1273
|
-
}
|
|
1274
|
-
// Web WritableStream
|
|
1275
|
-
if (stream.getWriter) {
|
|
1276
|
-
return new Writable({ stream: stream });
|
|
1277
|
-
}
|
|
1278
|
-
// Already a Node-like writable (e.g. StreamBuf)
|
|
1279
|
-
return stream;
|
|
1280
|
-
}
|
|
1281
|
-
function getStringByteLength(value, encoding) {
|
|
1282
|
-
const normalized = (encoding ?? "utf8").toLowerCase();
|
|
1283
|
-
if (normalized === "ascii" || normalized === "latin1" || normalized === "binary") {
|
|
1284
|
-
return value.length;
|
|
1285
|
-
}
|
|
1286
|
-
if (normalized === "utf16le" || normalized === "utf-16le" || normalized === "ucs2") {
|
|
1287
|
-
return value.length * 2;
|
|
1288
|
-
}
|
|
1289
|
-
// Compute UTF-8 byte length without allocating TextEncoder or encoded buffer
|
|
1290
|
-
return utf8ByteLength(value);
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Compute the UTF-8 byte length of a string without allocating a TextEncoder
|
|
1294
|
-
* or an intermediate Uint8Array. Handles surrogate pairs correctly.
|
|
1295
|
-
*/
|
|
1296
|
-
function utf8ByteLength(str) {
|
|
1297
|
-
let bytes = 0;
|
|
1298
|
-
for (let i = 0; i < str.length; i++) {
|
|
1299
|
-
const code = str.charCodeAt(i);
|
|
1300
|
-
if (code <= 0x7f) {
|
|
1301
|
-
bytes += 1;
|
|
1302
|
-
}
|
|
1303
|
-
else if (code <= 0x7ff) {
|
|
1304
|
-
bytes += 2;
|
|
1305
|
-
}
|
|
1306
|
-
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
1307
|
-
// Surrogate pair — 4 bytes for the full codepoint
|
|
1308
|
-
bytes += 4;
|
|
1309
|
-
i++; // skip low surrogate
|
|
1310
|
-
}
|
|
1311
|
-
else {
|
|
1312
|
-
bytes += 3;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
return bytes;
|
|
1316
|
-
}
|