@web-ts-toolkit/express-response-handler 0.42.2 → 0.44.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/llms.txt CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  FastAPI-style return-value response handling for Express: route handlers return plain values, `HttpResponse` wrappers, or throw typed HTTP errors.
4
4
 
5
+ ## Installation
6
+
7
+ Requires Node.js `>=22` and Express `^5` (peer, installed separately).
8
+
9
+ ```sh
10
+ pnpm add @web-ts-toolkit/express-response-handler @web-ts-toolkit/http-errors express
11
+ pnpm add -D typescript @types/express @types/node
12
+ ```
13
+
14
+ The quickstart imports `express` and `@web-ts-toolkit/http-errors` directly,
15
+ so both must be direct dependencies (transitive packages are not importable
16
+ from an isolated pnpm consumer).
17
+
5
18
  ## Main Patterns
6
19
 
7
20
  ```ts
@@ -40,17 +53,23 @@ import { Created, NoContent } from '@web-ts-toolkit/express-response-handler/res
40
53
 
41
54
  ## Gotchas
42
55
 
56
+ - requires Node.js `>=22` and Express `^5` (peer dependency)
57
+ - install direct imports explicitly: `@web-ts-toolkit/http-errors` (typed thrown errors) and `express` are not usable transitively under isolated installs
58
+ - default export is the handler instance; named exports (`handleResponse`, `HttpResponse`, `createHandler`, `ErrorFormats`) are also available; use one style consistently within a module
43
59
  - depends on `@web-ts-toolkit/http-errors` for typed thrown errors
44
- - default export is the handler instance; named exports (`handleResponse`, `HttpResponse`, `createHandler`, `ErrorFormats`) are also available
45
60
  - return plain values for `200 OK`; use `HttpResponse.*` wrappers for other status codes
46
61
  - `ErrorFormats` selects between `simple`, `aip193`, and `rfc9457` error payload shapes
47
62
  - unexpected non-HTTP errors return `500` with `Internal Server Error`; raw thrown messages are kept for hooks/logging and are not sent to clients by default
48
63
  - only finite integer `4xx` and `5xx` status codes are serialized as HTTP errors; invalid status values are rejected before headers are written
49
64
  - hooks (`preJson`, `postJson`, `preError`, `postError`) are observational `void | Promise<void>` side effects; returned values never transform payloads
50
- - `postJson` and `postError` run after successful HTTP `finish`; they do not run on client `close`, failed serialization, or `undefined` handler returns
65
+ - `preJson` is skipped for `undefined` returns and manual ownership; `postJson` never runs for manual responses or fallback JSON errors
66
+ - success-path serialization, rejected `preJson`, and CSV-before-output failures run one bounded error lifecycle: one `preError` with the original, one redacted body, one finish-timed `postError`, never `postJson`, never re-entering a failing `preError`/provider
67
+ - owned partial-write failures delegate to Express error middleware with `next(err)` and no second body
68
+ - `postJson` and `postError` run after successful HTTP `finish`; they do not run on client `close`, partial responses without an error body, or `undefined` handler returns
51
69
  - pre-hook failures go through the normal error response path; post-hook failures are passed to Express with `next(err)` after the response has finished
52
70
  - the default export is a mutable process-wide singleton; use `createHandler()` for isolated hook and error-provider state
53
71
  - response wrappers (`Created`, `NoContent`, `CSVResponse`, etc.) are also exported from the root entrypoint, but the subpaths give tree-shakeable access
72
+ - direct `streamCsv(res)` keeps its optional error-owner callback: pre-output failures with an owner invoke it once (owner terminates; throwing owners are contained and the destination is destroyed with the original), without an owner the destination is destroyed with the normalized failure; post-output failures always destroy
54
73
 
55
74
  ## Pointers
56
75
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@web-ts-toolkit/express-response-handler",
3
3
  "description": "FastAPI-style return-value response handling for Express",
4
4
  "homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-response-handler",
5
- "version": "0.42.2",
5
+ "version": "0.44.0",
6
6
  "sideEffects": false,
7
7
  "keywords": [
8
8
  "express",
@@ -16,31 +16,51 @@
16
16
  "types": "./index.d.ts",
17
17
  "exports": {
18
18
  ".": {
19
- "types": "./index.d.ts",
19
+ "types": {
20
+ "import": "./index.d.mts",
21
+ "require": "./index.d.ts",
22
+ "default": "./index.d.ts"
23
+ },
20
24
  "import": "./index.mjs",
21
25
  "require": "./index.js",
22
26
  "default": "./index.js"
23
27
  },
24
28
  "./types": {
25
- "types": "./public-types.d.ts",
29
+ "types": {
30
+ "import": "./public-types.d.mts",
31
+ "require": "./public-types.d.ts",
32
+ "default": "./public-types.d.ts"
33
+ },
26
34
  "import": "./public-types.mjs",
27
35
  "require": "./public-types.js",
28
36
  "default": "./public-types.js"
29
37
  },
30
38
  "./responses": {
31
- "types": "./responses/index.d.ts",
39
+ "types": {
40
+ "import": "./responses/index.d.mts",
41
+ "require": "./responses/index.d.ts",
42
+ "default": "./responses/index.d.ts"
43
+ },
32
44
  "import": "./responses/index.mjs",
33
45
  "require": "./responses/index.js",
34
46
  "default": "./responses/index.js"
35
47
  },
36
48
  "./responses/csv": {
37
- "types": "./responses/csv.d.ts",
49
+ "types": {
50
+ "import": "./responses/csv.d.mts",
51
+ "require": "./responses/csv.d.ts",
52
+ "default": "./responses/csv.d.ts"
53
+ },
38
54
  "import": "./responses/csv.mjs",
39
55
  "require": "./responses/csv.js",
40
56
  "default": "./responses/csv.js"
41
57
  },
42
58
  "./responses/success": {
43
- "types": "./responses/success.d.ts",
59
+ "types": {
60
+ "import": "./responses/success.d.mts",
61
+ "require": "./responses/success.d.ts",
62
+ "default": "./responses/success.d.ts"
63
+ },
44
64
  "import": "./responses/success.mjs",
45
65
  "require": "./responses/success.js",
46
66
  "default": "./responses/success.js"
@@ -52,8 +72,8 @@
52
72
  "dependencies": {
53
73
  "@fast-csv/format": "^5.0.7",
54
74
  "@types/express-serve-static-core": "^5.1.1",
55
- "@web-ts-toolkit/http-errors": "0.42.2",
56
- "@web-ts-toolkit/utils": "0.42.2"
75
+ "@web-ts-toolkit/http-errors": "0.44.0",
76
+ "@web-ts-toolkit/utils": "0.44.0"
57
77
  },
58
78
  "peerDependencies": {
59
79
  "express": "^5.0.0"
@@ -31,8 +31,17 @@ type MiddlewareFunction<Params = ParamsDictionary, ResBody = unknown, ReqBody =
31
31
  type RouterFunction<Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>> = RequestHandler<Params, ResBody, ReqBody, ReqQuery, Locals>;
32
32
  type HandleResponse = {
33
33
  <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fn: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>;
34
- <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
35
- <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
34
+ (fns: readonly []): never;
35
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly [MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>]): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>;
36
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly [
37
+ MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>,
38
+ MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>,
39
+ ...Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>
40
+ ]): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
41
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals> | Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
42
+ (): never;
43
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fn1: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>, fn2: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>, ...rest: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
44
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals> | Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
36
45
  };
37
46
  type ErrorWithPayload = {
38
47
  statusCode?: number;
package/public-types.d.ts CHANGED
@@ -31,8 +31,17 @@ type MiddlewareFunction<Params = ParamsDictionary, ResBody = unknown, ReqBody =
31
31
  type RouterFunction<Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>> = RequestHandler<Params, ResBody, ReqBody, ReqQuery, Locals>;
32
32
  type HandleResponse = {
33
33
  <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fn: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>;
34
- <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
35
- <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
34
+ (fns: readonly []): never;
35
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly [MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>]): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>;
36
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly [
37
+ MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>,
38
+ MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>,
39
+ ...Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>
40
+ ]): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
41
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns: readonly MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>[]): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals> | Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
42
+ (): never;
43
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fn1: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>, fn2: MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>, ...rest: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
44
+ <Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(...fns: Array<MiddlewareFunction<Params, ResBody, ReqBody, ReqQuery, Locals, Return>>): RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals> | Array<RouterFunction<Params, ResBody, ReqBody, ReqQuery, Locals>>;
36
45
  };
37
46
  type ErrorWithPayload = {
38
47
  statusCode?: number;
@@ -24,6 +24,29 @@ declare class CSVResponse {
24
24
  readonly processor: CsvProcessor;
25
25
  readonly headers?: CsvHeaders;
26
26
  constructor(dataset?: CsvSource, options?: CsvResponseOptions);
27
+ /**
28
+ * Streams the dataset as CSV into `res`.
29
+ *
30
+ * Contract choice (B-ERH-06): minimal runtime-safe fallback, no breaking
31
+ * signature change. `onBeforeOutputError` stays optional.
32
+ *
33
+ * - Pre-output failure (invalid filename, failed first read, first-row
34
+ * processor throw, before headers are sent) with an error owner:
35
+ * the owner is invoked exactly once with the normalized failure and owns
36
+ * destination termination (the handler owner renders a single redacted
37
+ * JSON error; direct callers must end/destroy `res` themselves). A
38
+ * throwing owner is contained and the destination is then destroyed with
39
+ * the original failure so it never stays open.
40
+ * - Pre-output failure without an error owner: deterministic fallback —
41
+ * the destination is destroyed with the normalized failure (or ended
42
+ * when `destroy` is unavailable), observable via `error` + `close`.
43
+ * - Post-output failure (after piping starts): the destination is always
44
+ * destroyed with the failure, with or without an owner.
45
+ *
46
+ * Non-`Error` failures are wrapped in an `Error` with the original kept as
47
+ * `cause`. A throwing owner never displaces the original failure and never
48
+ * escapes as a process-level error.
49
+ */
27
50
  streamCsv(res: CsvStreamResponse, onBeforeOutputError?: CsvErrorHandler): void;
28
51
  }
29
52
 
@@ -24,6 +24,29 @@ declare class CSVResponse {
24
24
  readonly processor: CsvProcessor;
25
25
  readonly headers?: CsvHeaders;
26
26
  constructor(dataset?: CsvSource, options?: CsvResponseOptions);
27
+ /**
28
+ * Streams the dataset as CSV into `res`.
29
+ *
30
+ * Contract choice (B-ERH-06): minimal runtime-safe fallback, no breaking
31
+ * signature change. `onBeforeOutputError` stays optional.
32
+ *
33
+ * - Pre-output failure (invalid filename, failed first read, first-row
34
+ * processor throw, before headers are sent) with an error owner:
35
+ * the owner is invoked exactly once with the normalized failure and owns
36
+ * destination termination (the handler owner renders a single redacted
37
+ * JSON error; direct callers must end/destroy `res` themselves). A
38
+ * throwing owner is contained and the destination is then destroyed with
39
+ * the original failure so it never stays open.
40
+ * - Pre-output failure without an error owner: deterministic fallback —
41
+ * the destination is destroyed with the normalized failure (or ended
42
+ * when `destroy` is unavailable), observable via `error` + `close`.
43
+ * - Post-output failure (after piping starts): the destination is always
44
+ * destroyed with the failure, with or without an owner.
45
+ *
46
+ * Non-`Error` failures are wrapped in an `Error` with the original kept as
47
+ * `cause`. A throwing owner never displaces the original failure and never
48
+ * escapes as a process-level error.
49
+ */
27
50
  streamCsv(res: CsvStreamResponse, onBeforeOutputError?: CsvErrorHandler): void;
28
51
  }
29
52
 
package/responses/csv.js CHANGED
@@ -27,7 +27,6 @@ __export(csv_exports, {
27
27
  module.exports = __toCommonJS(csv_exports);
28
28
  var import_format = require("@fast-csv/format");
29
29
  var import_utils = require("@web-ts-toolkit/utils");
30
- var import_events = require("events");
31
30
  var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
32
31
  var isIterable = (value) => typeof value === "object" && value !== null && Symbol.iterator in value;
33
32
  var isAsyncIterable = (value) => typeof value === "object" && value !== null && Symbol.asyncIterator in value;
@@ -91,28 +90,66 @@ var CSVResponse = class {
91
90
  this.headers = (0, import_utils.isPlainObject)(this.dataset);
92
91
  }
93
92
  }
93
+ /**
94
+ * Streams the dataset as CSV into `res`.
95
+ *
96
+ * Contract choice (B-ERH-06): minimal runtime-safe fallback, no breaking
97
+ * signature change. `onBeforeOutputError` stays optional.
98
+ *
99
+ * - Pre-output failure (invalid filename, failed first read, first-row
100
+ * processor throw, before headers are sent) with an error owner:
101
+ * the owner is invoked exactly once with the normalized failure and owns
102
+ * destination termination (the handler owner renders a single redacted
103
+ * JSON error; direct callers must end/destroy `res` themselves). A
104
+ * throwing owner is contained and the destination is then destroyed with
105
+ * the original failure so it never stays open.
106
+ * - Pre-output failure without an error owner: deterministic fallback —
107
+ * the destination is destroyed with the normalized failure (or ended
108
+ * when `destroy` is unavailable), observable via `error` + `close`.
109
+ * - Post-output failure (after piping starts): the destination is always
110
+ * destroyed with the failure, with or without an owner.
111
+ *
112
+ * Non-`Error` failures are wrapped in an `Error` with the original kept as
113
+ * `cause`. A throwing owner never displaces the original failure and never
114
+ * escapes as a process-level error.
115
+ */
94
116
  streamCsv(res, onBeforeOutputError) {
95
117
  const stream = (0, import_format.format)({ headers: this.headers });
96
118
  let outputStarted = false;
97
119
  let failed = false;
98
120
  let finished = false;
121
+ let cleaned = false;
99
122
  let activeIterator = null;
100
- let abortStreaming = () => void 0;
101
- const abortSignal = new Promise((_, reject) => {
102
- abortStreaming = reject;
103
- });
104
- abortSignal.catch(() => void 0);
123
+ const abortWaiters = /* @__PURE__ */ new Set();
124
+ const notifyAborted = () => {
125
+ for (const notify of Array.from(abortWaiters)) {
126
+ abortWaiters.delete(notify);
127
+ try {
128
+ notify();
129
+ } catch {
130
+ }
131
+ }
132
+ };
105
133
  const cleanup = () => {
134
+ if (cleaned) {
135
+ return;
136
+ }
137
+ cleaned = true;
106
138
  stream.off("error", fail);
107
139
  res.off?.("error", fail);
108
- res.off?.("close", abort);
109
- res.off?.("finish", markFinished);
140
+ res.off?.("close", handleClose);
141
+ res.off?.("finish", handleFinish);
110
142
  };
111
143
  const closeActiveIterator = () => {
112
144
  const iterator = activeIterator;
113
145
  activeIterator = null;
114
- if (iterator?.return) {
146
+ if (!iterator?.return) {
147
+ return;
148
+ }
149
+ try {
115
150
  void Promise.resolve(iterator.return()).catch(() => void 0);
151
+ } catch {
152
+ return;
116
153
  }
117
154
  };
118
155
  const closeResponse = (error) => {
@@ -127,58 +164,129 @@ var CSVResponse = class {
127
164
  return;
128
165
  }
129
166
  failed = true;
130
- cleanup();
131
- const normalizedError = error instanceof Error ? error : new Error("CSV response streaming failed");
132
- abortStreaming(normalizedError);
167
+ notifyAborted();
168
+ const normalizedError = error instanceof Error ? error : (() => {
169
+ const wrapped = new Error("CSV response streaming failed");
170
+ try {
171
+ wrapped.cause = error;
172
+ } catch {
173
+ }
174
+ return wrapped;
175
+ })();
133
176
  closeActiveIterator();
134
177
  if (!outputStarted && !res.headersSent) {
135
- onBeforeOutputError?.(normalizedError);
178
+ stream.destroy();
179
+ if (onBeforeOutputError) {
180
+ cleanup();
181
+ try {
182
+ onBeforeOutputError(normalizedError);
183
+ } catch {
184
+ try {
185
+ res.on?.("error", () => void 0);
186
+ } catch {
187
+ }
188
+ try {
189
+ closeResponse(normalizedError);
190
+ } catch {
191
+ }
192
+ }
193
+ return;
194
+ }
195
+ try {
196
+ closeResponse(normalizedError);
197
+ } catch {
198
+ cleanup();
199
+ }
136
200
  return;
137
201
  }
138
202
  stream.destroy();
139
203
  closeResponse(normalizedError);
140
204
  };
141
- const abort = () => {
142
- if (!finished) {
205
+ const handleClose = () => {
206
+ if (!finished && !failed) {
143
207
  fail(new Error("CSV response streaming aborted"));
208
+ cleanup();
209
+ return;
144
210
  }
211
+ cleanup();
145
212
  };
146
- const markFinished = () => {
213
+ const handleFinish = () => {
147
214
  finished = true;
215
+ cleanup();
148
216
  };
149
217
  const writeRow = async (row) => {
218
+ if (failed) {
219
+ return;
220
+ }
150
221
  outputStarted = true;
151
222
  if (!stream.write(row)) {
152
- await Promise.race([(0, import_events.once)(stream, "drain"), abortSignal]);
223
+ await new Promise((resolve) => {
224
+ if (failed) {
225
+ resolve();
226
+ return;
227
+ }
228
+ const onDrain = () => {
229
+ stream.off("drain", onDrain);
230
+ abortWaiters.delete(onAbort);
231
+ resolve();
232
+ };
233
+ const onAbort = () => {
234
+ stream.off("drain", onDrain);
235
+ resolve();
236
+ };
237
+ abortWaiters.add(onAbort);
238
+ stream.once("drain", onDrain);
239
+ });
153
240
  }
154
241
  };
155
242
  stream.on("error", fail);
156
- stream.on("end", cleanup);
157
243
  res.on?.("error", fail);
158
- res.on?.("close", abort);
159
- res.on?.("finish", markFinished);
244
+ res.on?.("close", handleClose);
245
+ res.on?.("finish", handleFinish);
160
246
  const pump = async () => {
161
247
  try {
162
248
  const iterator = createAsyncIterator(this.dataset);
163
249
  activeIterator = iterator;
164
250
  const first = await iterator.next();
165
- const firstRow = first.done ? void 0 : this.processor(first.value);
251
+ if (failed) {
252
+ return;
253
+ }
254
+ let firstRow;
255
+ if (!first.done) {
256
+ firstRow = this.processor(first.value);
257
+ if (failed) {
258
+ return;
259
+ }
260
+ }
166
261
  const contentDisposition = createAttachmentContentDisposition(this.filename);
262
+ if (failed) {
263
+ return;
264
+ }
167
265
  res.set("Content-Type", "text/csv");
168
266
  res.set("Content-Disposition", contentDisposition);
169
267
  stream.pipe(res);
170
- if (!first.done) {
171
- await writeRow(firstRow);
268
+ if (first.done) {
269
+ if (!failed) {
270
+ stream.end();
271
+ }
272
+ return;
172
273
  }
274
+ await writeRow(firstRow);
173
275
  while (!failed) {
174
- const row = await Promise.race([iterator.next(), abortSignal]);
175
- if (row.done) {
276
+ const result = await iterator.next();
277
+ if (failed) {
278
+ return;
279
+ }
280
+ if (result.done) {
176
281
  break;
177
282
  }
178
- await writeRow(this.processor(row.value));
283
+ const processed = this.processor(result.value);
284
+ if (failed) {
285
+ return;
286
+ }
287
+ await writeRow(processed);
179
288
  }
180
289
  if (!failed) {
181
- finished = true;
182
290
  stream.end();
183
291
  }
184
292
  } catch (error) {
@@ -187,7 +295,7 @@ var CSVResponse = class {
187
295
  activeIterator = null;
188
296
  }
189
297
  };
190
- void pump();
298
+ void pump().then(void 0, () => void 0);
191
299
  }
192
300
  };
193
301
  // Annotate the CommonJS export names for ESM import in node: