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.
Files changed (144) hide show
  1. package/README.md +1 -1
  2. package/README_zh.md +1 -1
  3. package/THIRD_PARTY_NOTICES.md +34 -2
  4. package/dist/esm/modules/archive/core/byte-queue.js +2 -1
  5. package/dist/esm/modules/archive/unzip/stream.browser.js +6 -0
  6. package/dist/esm/modules/csv/parse/async.js +2 -1
  7. package/dist/esm/modules/excel/bridge/csv-bridge.js +1 -1
  8. package/dist/esm/modules/excel/core/xlsx-io.browser.js +5 -9
  9. package/dist/esm/modules/excel/core/xlsx-io.js +1 -3
  10. package/dist/esm/modules/excel/core/xlsx-stream.js +3 -1
  11. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +10 -10
  12. package/dist/esm/modules/excel/stream/workbook-reader.js +30 -6
  13. package/dist/esm/modules/stream/browser/collector.js +3 -5
  14. package/dist/esm/modules/stream/browser/compose.js +13 -228
  15. package/dist/esm/modules/stream/browser/duplex-pair.js +71 -0
  16. package/dist/esm/modules/stream/browser/factories.js +35 -217
  17. package/dist/esm/modules/stream/browser/pipeline.js +43 -478
  18. package/dist/esm/modules/stream/browser/utils.js +21 -247
  19. package/dist/esm/modules/stream/browser/web.js +25 -0
  20. package/dist/esm/modules/stream/buffered-stream.js +8 -6
  21. package/dist/esm/modules/stream/core/factories.js +166 -0
  22. package/dist/esm/modules/stream/core/pipeline-api.js +32 -0
  23. package/dist/esm/modules/stream/core/sink-adapter.js +94 -24
  24. package/dist/esm/modules/stream/core/stream-helpers.js +160 -0
  25. package/dist/esm/modules/stream/core/utils.js +0 -23
  26. package/dist/esm/modules/stream/core/web-conversions.js +31 -0
  27. package/dist/esm/modules/stream/core/web-pipeline.js +90 -0
  28. package/dist/esm/modules/stream/index.base.js +2 -2
  29. package/dist/esm/modules/stream/index.browser.js +13 -19
  30. package/dist/esm/modules/stream/index.js +6 -10
  31. package/dist/esm/modules/stream/kernel/classes.js +618 -0
  32. package/dist/esm/modules/stream/kernel/compose-impl.js +225 -0
  33. package/dist/esm/modules/stream/kernel/destroy.js +255 -0
  34. package/dist/esm/modules/stream/kernel/duplexify.js +355 -0
  35. package/dist/esm/modules/stream/kernel/end-of-stream.js +212 -0
  36. package/dist/esm/modules/stream/kernel/errors.js +215 -0
  37. package/dist/esm/modules/stream/kernel/from.js +202 -0
  38. package/dist/esm/modules/stream/kernel/legacy.js +35 -0
  39. package/dist/esm/modules/stream/kernel/operators.js +285 -0
  40. package/dist/esm/modules/stream/kernel/pipeline-impl.js +561 -0
  41. package/dist/esm/modules/stream/kernel/readable-impl.js +963 -0
  42. package/dist/esm/modules/stream/kernel/state.js +253 -0
  43. package/dist/esm/modules/stream/kernel/stream-utils.js +212 -0
  44. package/dist/esm/modules/stream/kernel/web-adapters.js +584 -0
  45. package/dist/esm/modules/stream/kernel/writable.js +651 -0
  46. package/dist/esm/modules/stream/node/collector.js +7 -1
  47. package/dist/esm/modules/stream/node/compose.js +21 -206
  48. package/dist/esm/modules/stream/node/factories.js +34 -238
  49. package/dist/esm/modules/stream/node/pipeline.js +40 -94
  50. package/dist/esm/modules/stream/node/utils.js +15 -239
  51. package/dist/esm/modules/stream/node/web.js +27 -0
  52. package/dist/esm/modules/stream/pull-stream.js +15 -9
  53. package/dist/esm/modules/word/writer/streaming-writer.js +3 -0
  54. package/dist/iife/THIRD_PARTY_NOTICES.md +34 -2
  55. package/dist/iife/documonster.archive.iife.min.js +4 -4
  56. package/dist/iife/documonster.csv.iife.min.js +19 -19
  57. package/dist/iife/documonster.draw.iife.min.js +1 -1
  58. package/dist/iife/documonster.excel.iife.min.js +50 -50
  59. package/dist/iife/documonster.formula.iife.min.js +1 -1
  60. package/dist/iife/documonster.markdown.iife.min.js +1 -1
  61. package/dist/iife/documonster.mermaid.iife.min.js +1 -1
  62. package/dist/iife/documonster.pdf.iife.min.js +1 -1
  63. package/dist/iife/documonster.stream.iife.min.js +2 -2
  64. package/dist/iife/documonster.word.iife.min.js +60 -60
  65. package/dist/iife/documonster.xml.iife.min.js +1 -1
  66. package/dist/types/modules/excel/core/xlsx-io.browser.d.ts +5 -9
  67. package/dist/types/modules/excel/core/xlsx-io.d.ts +1 -3
  68. package/dist/types/modules/excel/core/xlsx-stream.d.ts +3 -2
  69. package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +2 -1
  70. package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
  71. package/dist/types/modules/stream/browser/collector.d.ts +1 -1
  72. package/dist/types/modules/stream/browser/compose.d.ts +14 -15
  73. package/dist/types/modules/stream/browser/duplex-pair.d.ts +10 -0
  74. package/dist/types/modules/stream/browser/factories.d.ts +22 -79
  75. package/dist/types/modules/stream/browser/pipeline.d.ts +8 -40
  76. package/dist/types/modules/stream/browser/utils.d.ts +6 -47
  77. package/dist/types/modules/stream/browser/web.d.ts +18 -0
  78. package/dist/types/modules/stream/core/add-abort-signal.d.ts +2 -1
  79. package/dist/types/modules/stream/core/consumers.d.ts +1 -1
  80. package/dist/types/modules/stream/core/event-utils.d.ts +2 -1
  81. package/dist/types/modules/stream/core/factories.d.ts +129 -0
  82. package/dist/types/modules/stream/core/pipeline-api.d.ts +10 -0
  83. package/dist/types/modules/stream/core/sink-adapter.d.ts +18 -1
  84. package/dist/types/modules/stream/core/stream-helpers.d.ts +76 -0
  85. package/dist/types/modules/stream/core/utils.d.ts +0 -9
  86. package/dist/types/modules/stream/core/web-conversions.d.ts +68 -0
  87. package/dist/types/modules/stream/core/web-pipeline.d.ts +27 -0
  88. package/dist/types/modules/stream/index.base.d.ts +6 -4
  89. package/dist/types/modules/stream/index.browser.d.ts +13 -14
  90. package/dist/types/modules/stream/index.d.ts +7 -10
  91. package/dist/types/modules/stream/kernel/classes.d.ts +220 -0
  92. package/dist/types/modules/stream/kernel/compose-impl.d.ts +10 -0
  93. package/dist/types/modules/stream/kernel/destroy.d.ts +21 -0
  94. package/dist/types/modules/stream/kernel/duplexify.d.ts +31 -0
  95. package/dist/types/modules/stream/kernel/end-of-stream.d.ts +28 -0
  96. package/dist/types/modules/stream/kernel/errors.d.ts +42 -0
  97. package/dist/types/modules/stream/kernel/from.d.ts +17 -0
  98. package/dist/types/modules/stream/kernel/legacy.d.ts +20 -0
  99. package/dist/types/modules/stream/kernel/operators.d.ts +44 -0
  100. package/dist/types/modules/stream/kernel/pipeline-impl.d.ts +32 -0
  101. package/dist/types/modules/stream/kernel/readable-impl.d.ts +34 -0
  102. package/dist/types/modules/stream/kernel/state.d.ts +223 -0
  103. package/dist/types/modules/stream/kernel/stream-utils.d.ts +34 -0
  104. package/dist/types/modules/stream/kernel/web-adapters.d.ts +65 -0
  105. package/dist/types/modules/stream/kernel/writable.d.ts +107 -0
  106. package/dist/types/modules/stream/node/collector.d.ts +7 -2
  107. package/dist/types/modules/stream/node/compose.d.ts +31 -5
  108. package/dist/types/modules/stream/node/factories.d.ts +22 -85
  109. package/dist/types/modules/stream/node/pipeline.d.ts +4 -15
  110. package/dist/types/modules/stream/node/utils.d.ts +9 -49
  111. package/dist/types/modules/stream/node/web.d.ts +18 -0
  112. package/dist/types/modules/stream/pull-stream.d.ts +2 -2
  113. package/dist/types/modules/stream/types.d.ts +24 -6
  114. package/package.json +5 -3
  115. package/dist/esm/modules/stream/browser/_lazy.js +0 -30
  116. package/dist/esm/modules/stream/browser/chunk-buffer.js +0 -223
  117. package/dist/esm/modules/stream/browser/duplex.js +0 -979
  118. package/dist/esm/modules/stream/browser/helpers.js +0 -44
  119. package/dist/esm/modules/stream/browser/microtask-context.js +0 -60
  120. package/dist/esm/modules/stream/browser/passthrough.js +0 -16
  121. package/dist/esm/modules/stream/browser/pipe-manager.js +0 -161
  122. package/dist/esm/modules/stream/browser/readable.js +0 -2208
  123. package/dist/esm/modules/stream/browser/transform.js +0 -1241
  124. package/dist/esm/modules/stream/browser/writable.js +0 -1316
  125. package/dist/esm/modules/stream/core/end-args.js +0 -24
  126. package/dist/esm/modules/stream/node/writable.js +0 -125
  127. package/dist/esm/modules/stream/utils.base.js +0 -126
  128. package/dist/esm/modules/stream/utils.browser.js +0 -80
  129. package/dist/esm/modules/stream/utils.js +0 -81
  130. package/dist/types/modules/stream/browser/_lazy.d.ts +0 -20
  131. package/dist/types/modules/stream/browser/chunk-buffer.d.ts +0 -61
  132. package/dist/types/modules/stream/browser/duplex.d.ts +0 -299
  133. package/dist/types/modules/stream/browser/helpers.d.ts +0 -13
  134. package/dist/types/modules/stream/browser/microtask-context.d.ts +0 -44
  135. package/dist/types/modules/stream/browser/passthrough.d.ts +0 -11
  136. package/dist/types/modules/stream/browser/pipe-manager.d.ts +0 -38
  137. package/dist/types/modules/stream/browser/readable.d.ts +0 -379
  138. package/dist/types/modules/stream/browser/transform.d.ts +0 -314
  139. package/dist/types/modules/stream/browser/writable.d.ts +0 -238
  140. package/dist/types/modules/stream/core/end-args.d.ts +0 -20
  141. package/dist/types/modules/stream/node/writable.d.ts +0 -53
  142. package/dist/types/modules/stream/utils.base.d.ts +0 -63
  143. package/dist/types/modules/stream/utils.browser.d.ts +0 -23
  144. 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
- }