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,505 +1,70 @@
1
1
  /**
2
- * Browser Stream - Pipeline & Finished
2
+ * Browser Stream - pipeline() and finished()
3
+ *
4
+ * The module's calling convention (always a promise, optional trailing
5
+ * callback, `{ signal, end }` options) over `pipelineImpl` and `eos`.
3
6
  */
4
- import { Duplex } from "./duplex.js";
5
- import { createListenerRegistry } from "./helpers.js";
6
- import { Readable } from "./readable.js";
7
- import { Transform } from "./transform.js";
8
- import { Writable } from "./writable.js";
9
7
  import { createFinishedAll } from "../core/finished-all.js";
10
- import { isPipelineOptions } from "../core/options.js";
11
- import { isReadableStream, isTransformStream, isWritableStream } from "../core/type-guards.js";
12
- import { createAbortError } from "../errors.js";
8
+ import { createPipeline } from "../core/pipeline-api.js";
9
+ import { isWebOnlyPipeline, pipeWebStreams } from "../core/web-pipeline.js";
10
+ import { kernel } from "../kernel/classes.js";
11
+ import { eos } from "../kernel/end-of-stream.js";
12
+ import { pipelineImpl } from "../kernel/pipeline-impl.js";
13
+ import { isNodeStream } from "../kernel/stream-utils.js";
13
14
  export { isPipelineOptions } from "../core/options.js";
14
- function asProbe(stream) {
15
- return stream;
16
- }
17
- function supportsReadableSide(stream) {
18
- // Check for readable-side properties/methods. Writable.pipe() is a no-op
19
- // that emits ERR_STREAM_CANNOT_PIPE, so `typeof pipe === "function"` alone
20
- // is not sufficient — we must also see an actual readable indicator.
21
- return "readableEnded" in stream || "readable" in stream || typeof stream.read === "function";
22
- }
23
- function supportsWritableSide(stream) {
24
- return "writableFinished" in stream || "writable" in stream || typeof stream.write === "function";
25
- }
26
- function isStreamCompleted(stream) {
27
- const readableDone = !supportsReadableSide(stream) || !!stream.readableEnded;
28
- const writableDone = !supportsWritableSide(stream) || !!stream.writableFinished;
29
- return readableDone && writableDone;
30
- }
31
- function createPrematureCloseError() {
32
- const err = new Error("Premature close");
33
- err.code = "ERR_STREAM_PREMATURE_CLOSE";
34
- err.name = "Error [ERR_STREAM_PREMATURE_CLOSE]";
35
- return err;
36
- }
37
- /**
38
- * Wait for a stream's 'close' event after it has finished/ended.
39
- * If the stream will emit close (autoDestroy && emitClose), wait for it.
40
- * Otherwise resolve immediately (matching Node.js pipeline behavior where
41
- * autoDestroy:false streams don't wait for close).
42
- */
43
- function waitForClose(stream, done, registry) {
44
- // Already closed — resolve immediately.
45
- if (stream.closed || stream._closed) {
46
- done();
47
- return;
48
- }
49
- const hasInternals = "_emitClose" in stream && "_autoDestroy" in stream;
50
- const willEmitClose = hasInternals && stream._emitClose !== false && stream._autoDestroy !== false;
51
- if (willEmitClose) {
52
- registry.once(stream, "close", () => done());
53
- }
54
- else {
55
- done();
56
- }
57
- }
58
- export function toBrowserPipelineStream(stream) {
59
- if (stream instanceof Readable ||
60
- stream instanceof Writable ||
61
- stream instanceof Transform ||
62
- stream instanceof Duplex) {
63
- return stream;
64
- }
65
- if (isTransformStream(stream)) {
66
- return Transform.fromWeb(stream);
67
- }
68
- if (isReadableStream(stream)) {
69
- return Readable.fromWeb(stream);
70
- }
71
- if (isWritableStream(stream)) {
72
- return Writable.fromWeb(stream);
73
- }
74
- // A bare (async) iterable source — `pipeline([chunk], dest)`. Node's pipeline
75
- // accepts one; without this the browser threw `current.pipe is not a
76
- // function`. Generator STAGES are handled separately (they are functions, and
77
- // consume the previous stage rather than being a source).
78
- if (typeof stream !== "function" && isIterableSource(stream)) {
79
- return Readable.from(stream);
80
- }
81
- return stream;
82
- }
83
- /** `true` for an (async) iterable that is not already one of our streams. */
84
- function isIterableSource(value) {
85
- if (value == null || (typeof value !== "object" && typeof value !== "string")) {
86
- return false;
87
- }
88
- const probe = value;
89
- if (typeof probe.pipe === "function") {
90
- return false;
91
- }
92
- return (typeof probe[Symbol.asyncIterator] === "function" ||
93
- typeof probe[Symbol.iterator] === "function");
94
- }
95
- /**
96
- * Check if a pipeline stage is a generator/async generator function.
97
- * These are used as transform stages: fn(source) => AsyncIterable.
98
- */
99
- function isGeneratorFunction(fn) {
100
- return typeof fn === "function" && !(fn instanceof Readable) && !(fn instanceof Writable);
101
- }
102
15
  /**
103
- * Apply a generator function as a transform stage.
104
- * Consumes the source stream via its async iterator, passes it through the
105
- * generator function, and produces a new Readable from the resulting iterable.
106
- */
107
- function applyGeneratorStage(source, fn) {
108
- const iterable = fn(source);
109
- return Readable.from(iterable);
110
- }
111
- /**
112
- * Pipeline streams together with proper error handling and cleanup.
113
- * Supports both callback and promise-based usage like Node.js.
114
- *
115
- * @example
116
- * // Promise usage
117
- * await pipeline(source, transform, destination);
118
- *
119
- * @example
120
- * // With options
121
- * await pipeline(source, transform, destination, { signal: controller.signal });
16
+ * Pipe `streams` together and settle when all of them are done, destroying
17
+ * every stage on the first error.
122
18
  *
123
- * @example
124
- * // Callback usage
125
- * pipeline(source, transform, destination, (err) => {
126
- * if (err) console.error('Pipeline failed', err);
127
- * });
19
+ * Accepts Node-style streams, Web streams, (async) iterables as a source and
20
+ * generator functions as stages. A pipeline of Web streams only runs on the
21
+ * platform's `pipeTo`.
128
22
  */
23
+ const runPipeline = createPipeline((stages, options) => isWebOnlyPipeline(stages)
24
+ ? pipeWebStreams(stages, options)
25
+ : new Promise((resolve, reject) => {
26
+ pipelineImpl(kernel, stages, err => (err ? reject(err) : resolve()), options);
27
+ }));
129
28
  export function pipeline(...args) {
130
- // Parse arguments
131
- let streams;
132
- let options = {};
133
- let callback;
134
- const lastArg = args[args.length - 1];
135
- if (typeof lastArg === "function") {
136
- callback = lastArg;
137
- // Check for combined style: pipeline(s1, s2, ..., options, callback)
138
- const secondToLast = args[args.length - 2];
139
- if (isPipelineOptions(secondToLast)) {
140
- options = secondToLast;
141
- streams = args.slice(0, -2);
142
- }
143
- else {
144
- // Callback only: pipeline(s1, s2, ..., callback)
145
- streams = args.slice(0, -1);
146
- }
147
- }
148
- else if (isPipelineOptions(lastArg)) {
149
- // Options only: pipeline(s1, s2, ..., { signal })
150
- options = lastArg;
151
- streams = args.slice(0, -1);
152
- }
153
- else {
154
- // No callback or options: pipeline(s1, s2, s3)
155
- streams = args;
156
- }
157
- const promise = new Promise((resolve, reject) => {
158
- if (streams.length < 2) {
159
- const err = new Error("Pipeline requires at least 2 streams");
160
- reject(err);
161
- return;
162
- }
163
- // Pre-process: normalize streams and resolve generator functions.
164
- // Generator functions consume their source as an async iterable and produce
165
- // a new Readable — they do NOT participate in .pipe() chains.
166
- // We build a flat list of pipe-able stream stages.
167
- const rawStages = streams.map(toBrowserPipelineStream);
168
- // allStreams: every stream created (for cleanup on error).
169
- // pipeStages: only the streams that need .pipe() chaining.
170
- const allStreams = [];
171
- const pipeStages = [];
172
- let current = asProbe(rawStages[0]);
173
- allStreams.push(current);
174
- pipeStages.push(current);
175
- for (let i = 1; i < rawStages.length; i++) {
176
- const stage = rawStages[i];
177
- if (isGeneratorFunction(stage)) {
178
- // Generator consumes `current` internally → produces a new Readable.
179
- // This Readable replaces `current` as the source for subsequent stages.
180
- current = asProbe(applyGeneratorStage(current, stage));
181
- allStreams.push(current);
182
- // Replace the last entry in pipeStages (the consumed source) with
183
- // the generator-produced Readable, so the next real stream stage
184
- // will be piped FROM this Readable.
185
- pipeStages[pipeStages.length - 1] = current;
186
- }
187
- else {
188
- const probe = asProbe(stage);
189
- allStreams.push(probe);
190
- pipeStages.push(probe);
191
- current = probe;
192
- }
193
- }
194
- const source = pipeStages[0];
195
- const destination = pipeStages[pipeStages.length - 1];
196
- const transforms = pipeStages.slice(1, -1);
197
- // Check for already-destroyed streams upfront.
198
- // If a stream is already destroyed and its close event has already fired,
199
- // our close listener would never fire — causing the pipeline to hang.
200
- // Node.js detects this and immediately rejects with ERR_STREAM_PREMATURE_CLOSE.
201
- for (const stream of allStreams) {
202
- if (stream.destroyed) {
203
- if (!isStreamCompleted(stream)) {
204
- reject(createPrematureCloseError());
205
- return;
206
- }
207
- }
208
- }
209
- let completed = false;
210
- const registry = createListenerRegistry();
211
- let onAbort;
212
- const cleanupWithSignal = (error) => {
213
- if (completed) {
214
- return;
215
- }
216
- completed = true;
217
- if (onAbort && options.signal) {
218
- options.signal.removeEventListener("abort", onAbort);
219
- }
220
- // Node.js only destroys streams on ERROR, NOT on success.
221
- // On error, pass the error to destroy(); on success, leave streams intact.
222
- if (error) {
223
- const noop = () => { };
224
- for (const stream of allStreams) {
225
- if (typeof stream.on === "function") {
226
- stream.on("error", noop);
227
- }
228
- if (typeof stream.destroy === "function" && !stream.destroyed) {
229
- stream.destroy(error);
230
- }
231
- }
232
- }
233
- registry.cleanup();
234
- if (error) {
235
- reject(error);
236
- }
237
- else {
238
- resolve();
239
- }
240
- };
241
- // Handle abort signal
242
- if (options.signal) {
243
- if (options.signal.aborted) {
244
- cleanupWithSignal(createAbortError(options.signal.reason));
245
- return;
246
- }
247
- onAbort = () => cleanupWithSignal(createAbortError(options.signal.reason));
248
- options.signal.addEventListener("abort", onAbort);
249
- }
250
- // Chain the streams
251
- current = source;
252
- for (const transform of transforms) {
253
- current.pipe(transform);
254
- current = transform;
255
- }
256
- // Pipe to destination
257
- if (options.end !== false) {
258
- current.pipe(destination);
259
- }
260
- else {
261
- // Don't end destination
262
- let paused = false;
263
- let waitingForDrain = false;
264
- const onDrain = () => {
265
- waitingForDrain = false;
266
- if (paused && typeof current.resume === "function") {
267
- paused = false;
268
- current.resume();
269
- }
270
- };
271
- const onData = (chunk) => {
272
- const ok = destination.write(chunk);
273
- if (!ok && !waitingForDrain) {
274
- waitingForDrain = true;
275
- if (!paused && typeof current.pause === "function") {
276
- paused = true;
277
- current.pause();
278
- }
279
- registry.once(destination, "drain", onDrain);
280
- }
281
- };
282
- registry.add(current, "data", onData);
283
- // end:false — wait for source to fully close (not just 'end').
284
- // Node.js pipeline uses finished(source, {writable:false}) internally,
285
- // which waits until the 'close' event when the stream will emit close.
286
- registry.once(current, "end", () => {
287
- // Source ended — now wait for its close event (autoDestroy → close)
288
- // before resolving the pipeline. If the stream has already closed
289
- // (or won't emit close), resolve immediately.
290
- waitForClose(current, cleanupWithSignal, registry);
291
- });
292
- }
293
- // Handle completion — wait for destination to fully close, not just finish.
294
- // Node.js pipeline internally uses finished(destination) which waits for
295
- // 'close' after 'finish' when the stream will emit close (autoDestroy &&
296
- // emitClose). For streams that won't emit close (autoDestroy: false),
297
- // resolve on 'finish' alone (matching Node.js behavior).
298
- if (options.end !== false) {
299
- registry.once(destination, "finish", () => {
300
- waitForClose(destination, cleanupWithSignal, registry);
301
- });
302
- }
303
- // Node parity: close before completion is a premature close error.
304
- for (const stream of allStreams) {
305
- registry.once(stream, "close", () => {
306
- if (completed) {
307
- return;
308
- }
309
- if (!isStreamCompleted(stream)) {
310
- cleanupWithSignal(createPrematureCloseError());
311
- }
312
- });
313
- }
314
- // Handle errors on all streams
315
- for (const stream of allStreams) {
316
- registry.once(stream, "error", (err) => cleanupWithSignal(err));
317
- }
318
- });
319
- // If callback provided, use it
320
- if (callback) {
321
- promise.then(() => callback()).catch(err => callback(err));
322
- }
323
- return promise;
29
+ return runPipeline(...args);
324
30
  }
325
- // =============================================================================
326
- // Finished
327
- // =============================================================================
328
31
  /**
329
- * Wait for a stream to finish, close, or error.
330
- * Node.js compatible with support for options and callbacks.
331
- *
332
- * @example
333
- * // Promise usage
334
- * await finished(stream);
335
- *
336
- * @example
337
- * // With options
338
- * await finished(stream, { readable: false }); // Only wait for writable side
32
+ * Settle when `stream` has finished, errored or closed early.
339
33
  *
340
- * @example
341
- * // Callback usage
342
- * finished(stream, (err) => {
343
- * if (err) console.error('Stream error', err);
344
- * });
34
+ * Web streams are adapted first; that takes their lock, as reading them does.
345
35
  */
346
36
  export function finished(stream, optionsOrCallback, callback) {
347
- let options = {};
37
+ let options;
348
38
  let cb;
349
39
  if (typeof optionsOrCallback === "function") {
350
40
  cb = optionsOrCallback;
351
41
  }
352
42
  else {
353
- options = optionsOrCallback ?? {};
43
+ options = optionsOrCallback;
354
44
  cb = callback;
355
45
  }
356
- const promise = new Promise((resolve, reject) => {
357
- const normalizedStream = toBrowserPipelineStream(stream);
358
- let resolved = false;
359
- const registry = createListenerRegistry();
360
- let onAbort;
361
- const cleanup = () => {
362
- registry.cleanup();
363
- if (onAbort && options.signal) {
364
- options.signal.removeEventListener("abort", onAbort);
365
- }
366
- };
367
- const done = (err) => {
368
- if (resolved) {
369
- return;
370
- }
371
- resolved = true;
372
- cleanup();
373
- if (err) {
374
- reject(err);
375
- }
376
- else {
377
- resolve();
378
- }
379
- };
380
- // Handle abort signal
381
- if (options.signal) {
382
- if (options.signal.aborted) {
383
- done(createAbortError(options.signal.reason));
384
- return;
385
- }
386
- onAbort = () => done(createAbortError(options.signal.reason));
387
- options.signal.addEventListener("abort", onAbort);
388
- }
389
- // Node.js: if the stream is already destroyed, resolve/reject immediately.
390
- // An already-destroyed stream with an error rejects; otherwise it's premature close.
391
- const s = asProbe(normalizedStream);
392
- if (s.destroyed || s._destroyed) {
393
- if (s.errored || s._errored) {
394
- done(s.errored ?? s._errored);
395
- }
396
- else {
397
- // Already destroyed without error — check if it finished gracefully.
398
- // Respect options.readable / options.writable to match Node.js behavior:
399
- // if the caller only cares about one side, only check that side.
400
- const supportsReadable = "readableEnded" in s || "readable" in s || typeof s.read === "function";
401
- const supportsWritable = "writableFinished" in s || "writable" in s || typeof s.write === "function";
402
- const checkReadable = options.readable !== false && supportsReadable;
403
- const checkWritable = options.writable !== false && supportsWritable;
404
- const readableOk = !checkReadable || !!(s.readableEnded || s._endEmitted);
405
- const writableOk = !checkWritable || !!(s.writableFinished || s._finished);
406
- if (readableOk && writableOk) {
407
- done();
408
- }
409
- else {
410
- done(createPrematureCloseError());
411
- }
412
- }
413
- return;
414
- }
415
- const probe = asProbe(normalizedStream);
416
- const supportsReadable = "readableEnded" in probe || "readable" in probe || typeof probe.read === "function";
417
- const supportsWritable = "writableFinished" in probe || "writable" in probe || typeof probe.write === "function";
418
- const checkReadable = options.readable !== false && supportsReadable;
419
- const checkWritable = options.writable !== false && supportsWritable;
420
- let readableDone = !checkReadable || !!probe.readableEnded;
421
- let writableDone = !checkWritable || !!probe.writableFinished;
422
- // Node.js finished() waits for the 'close' event before resolving when
423
- // the stream will actually emit 'close'. Node.js computes willEmitClose as:
424
- // state.autoDestroy && state.emitClose && state.closed === false
425
- // This means: if autoDestroy is false, close won't fire automatically after
426
- // finish, so finished() must NOT wait for it — otherwise it deadlocks.
427
- //
428
- // Guard: only consult _emitClose / _autoDestroy when the stream actually
429
- // exposes them (i.e. our own Readable/Writable/Transform/Duplex instances).
430
- // Third-party or duck-typed streams may lack these internals — for those we
431
- // default to NOT waiting for 'close', which avoids a deadlock.
432
- const s2 = probe;
433
- const hasInternals = "_emitClose" in s2 && "_autoDestroy" in s2;
434
- const willEmitClose = hasInternals && s2._emitClose !== false && s2._autoDestroy !== false && !s2._closed;
435
- const maybeDone = () => {
436
- if (readableDone && writableDone) {
437
- if (willEmitClose) {
438
- // Don't resolve yet — wait for 'close' event
439
- return;
440
- }
441
- done();
442
- }
443
- };
444
- // Already finished?
445
- if (readableDone && writableDone) {
446
- // If the stream is already closed (or won't emit close), resolve now
447
- if (!willEmitClose || probe.closed) {
448
- done();
449
- return;
450
- }
451
- // Otherwise wait for 'close'
452
- }
453
- // Listen for events
454
- if (checkWritable && !writableDone) {
455
- registry.once(probe, "finish", () => {
456
- writableDone = true;
457
- maybeDone();
458
- });
459
- }
460
- if (checkReadable && !readableDone) {
461
- registry.once(probe, "end", () => {
462
- readableDone = true;
463
- maybeDone();
464
- });
465
- }
466
- // Node.js: with error:false, don't listen for the 'error' event.
467
- // Errors are still detected via stream.errored in the close handler.
468
- if (options.error !== false) {
469
- registry.once(probe, "error", (err) => done(err));
470
- }
471
- registry.once(probe, "close", () => {
472
- const closedReadableDone = readableDone || !!probe.readableEnded;
473
- const closedWritableDone = writableDone || !!probe.writableFinished;
474
- if (closedReadableDone && closedWritableDone) {
475
- readableDone = closedReadableDone;
476
- writableDone = closedWritableDone;
477
- // Even with error:false, check stream.errored — Node.js close handler
478
- // still passes errors detected via stream.errored to the callback.
479
- const streamErr = probe.errored ?? probe._errored;
480
- done(streamErr ?? undefined);
481
- return;
482
- }
483
- // Premature close — stream closed before finishing.
484
- // Check for stream error first (e.g. destroyed with error).
485
- const streamErr = probe.errored ?? probe._errored;
486
- if (streamErr) {
487
- done(streamErr);
488
- return;
489
- }
490
- const err = new Error("Premature close");
491
- err.code = "ERR_STREAM_PREMATURE_CLOSE";
492
- err.name = "Error [ERR_STREAM_PREMATURE_CLOSE]";
493
- done(err);
494
- });
495
- });
496
- // If callback provided, use it
46
+ const promise = isNodeStream(stream)
47
+ ? new Promise((resolve, reject) => {
48
+ eos(stream, options ?? null, err => (err ? reject(err) : resolve()));
49
+ })
50
+ : webFinished();
497
51
  if (cb) {
498
- promise.then(() => cb()).catch(err => cb(err));
52
+ const done = cb;
53
+ promise.then(() => done(), err => done(err));
499
54
  }
500
55
  return promise;
501
56
  }
502
57
  /**
503
- * Wait for multiple streams to finish
58
+ * `finished()` on a Web stream is refused in the browser.
59
+ *
60
+ * The standard API has no way to observe a Web stream's completion without
61
+ * locking it, and nothing short of the reader or writer that drains it knows
62
+ * when its queue is empty — the Node-style side having ended does not mean
63
+ * the Web side has. Adapting the stream would take its lock and leave the
64
+ * caller unable to consume or close what it is waiting on.
504
65
  */
66
+ function webFinished() {
67
+ return Promise.reject(new TypeError("finished(): a Web stream's completion cannot be observed in the browser without " +
68
+ "locking it. Await the `closed` promise of the reader or writer you use instead."));
69
+ }
505
70
  export const finishedAll = createFinishedAll(finished);