@react-router/node 7.15.0 → 7.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # `@react-router/node`
2
2
 
3
+ ## v7.16.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Honor Node writable backpressure in `writeReadableStreamToWritable` and `writeAsyncIterableToWritable` ([#15071](https://github.com/remix-run/react-router/pull/15071))
8
+
9
+ - Await `'drain'` when `writable.write()` returns `false` instead of letting chunks accumulate in the writable's internal buffer.
10
+ - Reject (rather than hang) if the writable errors or closes mid-stream.
11
+ - Updated dependencies:
12
+ - [`react-router@7.16.0`](https://github.com/remix-run/react-router/releases/tag/react-router@7.16.0)
13
+
14
+ ## v7.15.1
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies:
19
+ - [`react-router@7.15.1`](https://github.com/remix-run/react-router/releases/tag/react-router@7.15.1)
20
+
3
21
  ## v7.15.0
4
22
 
5
23
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/node v7.15.0
2
+ * @react-router/node v7.16.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -144,32 +144,131 @@ var import_node_stream = require("stream");
144
144
  async function writeReadableStreamToWritable(stream, writable) {
145
145
  let reader = stream.getReader();
146
146
  let flushable = writable;
147
+ let writableError = monitorWritableError(writable);
147
148
  try {
148
149
  while (true) {
149
- let { done, value } = await reader.read();
150
+ writableError.throwIfClosed();
151
+ let { done, value } = await writableError.race(reader.read());
150
152
  if (done) {
151
153
  writable.end();
152
154
  break;
153
155
  }
154
- writable.write(value);
156
+ writableError.throwIfClosed();
157
+ let canContinueWriting = writable.write(value);
155
158
  if (typeof flushable.flush === "function") {
156
159
  flushable.flush();
157
160
  }
161
+ if (!canContinueWriting) {
162
+ await waitForDrain(writable, writableError);
163
+ }
158
164
  }
159
165
  } catch (error) {
166
+ try {
167
+ reader.cancel(error).catch(() => {
168
+ });
169
+ } catch {
170
+ }
160
171
  writable.destroy(error);
161
172
  throw error;
173
+ } finally {
174
+ writableError.cleanup();
175
+ try {
176
+ reader.releaseLock();
177
+ } catch {
178
+ }
162
179
  }
163
180
  }
181
+ function monitorWritableError(writable) {
182
+ let settled = false;
183
+ let writableError;
184
+ let rejectWritableError;
185
+ let writableErrorPromise = new Promise((_, reject2) => {
186
+ rejectWritableError = reject2;
187
+ });
188
+ writableErrorPromise.catch(() => {
189
+ });
190
+ function cleanup() {
191
+ writable.off("error", onError);
192
+ writable.off("close", onClose);
193
+ }
194
+ function reject(error) {
195
+ if (settled) {
196
+ return;
197
+ }
198
+ settled = true;
199
+ writableError = error;
200
+ cleanup();
201
+ rejectWritableError(error);
202
+ }
203
+ function onError(error) {
204
+ reject(error);
205
+ }
206
+ function onClose() {
207
+ reject(new Error("Writable closed before stream finished"));
208
+ }
209
+ writable.once("error", onError);
210
+ writable.once("close", onClose);
211
+ return {
212
+ cleanup,
213
+ race(promise) {
214
+ return Promise.race([promise, writableErrorPromise]);
215
+ },
216
+ throwIfClosed() {
217
+ if (writableError) {
218
+ throw writableError;
219
+ }
220
+ if (writable.destroyed || writable.writableEnded) {
221
+ throw new Error("Cannot write to a destroyed or ended writable stream");
222
+ }
223
+ }
224
+ };
225
+ }
226
+ function waitForDrain(writable, writableError) {
227
+ let cleanup = () => {
228
+ };
229
+ let drainPromise = new Promise((resolve) => {
230
+ function onDrain() {
231
+ cleanup();
232
+ resolve();
233
+ }
234
+ cleanup = function cleanup2() {
235
+ writable.off("drain", onDrain);
236
+ };
237
+ writable.once("drain", onDrain);
238
+ });
239
+ return writableError.race(drainPromise).finally(cleanup);
240
+ }
164
241
  async function writeAsyncIterableToWritable(iterable, writable) {
242
+ let writableError = monitorWritableError(writable);
243
+ let iterator = iterable[Symbol.asyncIterator]();
244
+ let completed = false;
165
245
  try {
166
- for await (let chunk of iterable) {
167
- writable.write(chunk);
246
+ while (true) {
247
+ writableError.throwIfClosed();
248
+ let { done, value: chunk } = await writableError.race(iterator.next());
249
+ if (done) {
250
+ completed = true;
251
+ break;
252
+ }
253
+ writableError.throwIfClosed();
254
+ let canContinueWriting = writable.write(chunk);
255
+ if (!canContinueWriting) {
256
+ await waitForDrain(writable, writableError);
257
+ }
168
258
  }
169
259
  writable.end();
170
260
  } catch (error) {
261
+ if (!completed) {
262
+ try {
263
+ Promise.resolve(iterator.return?.()).catch(() => {
264
+ });
265
+ } catch {
266
+ }
267
+ }
171
268
  writable.destroy(error);
172
269
  throw error;
270
+ } finally {
271
+ writableError.cleanup();
173
272
  }
174
273
  }
175
274
  async function readableStreamToString(stream, encoding) {
@@ -235,7 +334,7 @@ var StreamPump = class {
235
334
  if (available <= 0) {
236
335
  this.pause();
237
336
  }
238
- } catch (error) {
337
+ } catch (e) {
239
338
  this.controller.error(
240
339
  new Error(
241
340
  "Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object"
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/node v7.15.0
2
+ * @react-router/node v7.16.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -104,32 +104,131 @@ import { Stream } from "stream";
104
104
  async function writeReadableStreamToWritable(stream, writable) {
105
105
  let reader = stream.getReader();
106
106
  let flushable = writable;
107
+ let writableError = monitorWritableError(writable);
107
108
  try {
108
109
  while (true) {
109
- let { done, value } = await reader.read();
110
+ writableError.throwIfClosed();
111
+ let { done, value } = await writableError.race(reader.read());
110
112
  if (done) {
111
113
  writable.end();
112
114
  break;
113
115
  }
114
- writable.write(value);
116
+ writableError.throwIfClosed();
117
+ let canContinueWriting = writable.write(value);
115
118
  if (typeof flushable.flush === "function") {
116
119
  flushable.flush();
117
120
  }
121
+ if (!canContinueWriting) {
122
+ await waitForDrain(writable, writableError);
123
+ }
118
124
  }
119
125
  } catch (error) {
126
+ try {
127
+ reader.cancel(error).catch(() => {
128
+ });
129
+ } catch {
130
+ }
120
131
  writable.destroy(error);
121
132
  throw error;
133
+ } finally {
134
+ writableError.cleanup();
135
+ try {
136
+ reader.releaseLock();
137
+ } catch {
138
+ }
122
139
  }
123
140
  }
141
+ function monitorWritableError(writable) {
142
+ let settled = false;
143
+ let writableError;
144
+ let rejectWritableError;
145
+ let writableErrorPromise = new Promise((_, reject2) => {
146
+ rejectWritableError = reject2;
147
+ });
148
+ writableErrorPromise.catch(() => {
149
+ });
150
+ function cleanup() {
151
+ writable.off("error", onError);
152
+ writable.off("close", onClose);
153
+ }
154
+ function reject(error) {
155
+ if (settled) {
156
+ return;
157
+ }
158
+ settled = true;
159
+ writableError = error;
160
+ cleanup();
161
+ rejectWritableError(error);
162
+ }
163
+ function onError(error) {
164
+ reject(error);
165
+ }
166
+ function onClose() {
167
+ reject(new Error("Writable closed before stream finished"));
168
+ }
169
+ writable.once("error", onError);
170
+ writable.once("close", onClose);
171
+ return {
172
+ cleanup,
173
+ race(promise) {
174
+ return Promise.race([promise, writableErrorPromise]);
175
+ },
176
+ throwIfClosed() {
177
+ if (writableError) {
178
+ throw writableError;
179
+ }
180
+ if (writable.destroyed || writable.writableEnded) {
181
+ throw new Error("Cannot write to a destroyed or ended writable stream");
182
+ }
183
+ }
184
+ };
185
+ }
186
+ function waitForDrain(writable, writableError) {
187
+ let cleanup = () => {
188
+ };
189
+ let drainPromise = new Promise((resolve) => {
190
+ function onDrain() {
191
+ cleanup();
192
+ resolve();
193
+ }
194
+ cleanup = function cleanup2() {
195
+ writable.off("drain", onDrain);
196
+ };
197
+ writable.once("drain", onDrain);
198
+ });
199
+ return writableError.race(drainPromise).finally(cleanup);
200
+ }
124
201
  async function writeAsyncIterableToWritable(iterable, writable) {
202
+ let writableError = monitorWritableError(writable);
203
+ let iterator = iterable[Symbol.asyncIterator]();
204
+ let completed = false;
125
205
  try {
126
- for await (let chunk of iterable) {
127
- writable.write(chunk);
206
+ while (true) {
207
+ writableError.throwIfClosed();
208
+ let { done, value: chunk } = await writableError.race(iterator.next());
209
+ if (done) {
210
+ completed = true;
211
+ break;
212
+ }
213
+ writableError.throwIfClosed();
214
+ let canContinueWriting = writable.write(chunk);
215
+ if (!canContinueWriting) {
216
+ await waitForDrain(writable, writableError);
217
+ }
128
218
  }
129
219
  writable.end();
130
220
  } catch (error) {
221
+ if (!completed) {
222
+ try {
223
+ Promise.resolve(iterator.return?.()).catch(() => {
224
+ });
225
+ } catch {
226
+ }
227
+ }
131
228
  writable.destroy(error);
132
229
  throw error;
230
+ } finally {
231
+ writableError.cleanup();
133
232
  }
134
233
  }
135
234
  async function readableStreamToString(stream, encoding) {
@@ -195,7 +294,7 @@ var StreamPump = class {
195
294
  if (available <= 0) {
196
295
  this.pause();
197
296
  }
198
- } catch (error) {
297
+ } catch (e) {
199
298
  this.controller.error(
200
299
  new Error(
201
300
  "Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/node",
3
- "version": "7.15.0",
3
+ "version": "7.16.0",
4
4
  "description": "Node.js platform abstractions for React Router",
5
5
  "bugs": {
6
6
  "url": "https://github.com/remix-run/react-router/issues"
@@ -53,11 +53,11 @@
53
53
  "tsup": "^8.3.0",
54
54
  "typescript": "^5.4.5",
55
55
  "wireit": "0.14.9",
56
- "react-router": "7.15.0"
56
+ "react-router": "7.16.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "typescript": "^5.1.0 || ^6.0.0",
60
- "react-router": "7.15.0"
60
+ "react-router": "7.16.0"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "typescript": {