@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/README.md +29 -8
- package/create-handler.js +376 -117
- package/create-handler.mjs +376 -117
- package/http-response.d.mts +10 -10
- package/http-response.d.ts +10 -10
- package/http-response.js +146 -38
- package/http-response.mjs +146 -38
- package/index.js +376 -117
- package/index.mjs +376 -117
- package/llms.txt +21 -2
- package/package.json +28 -8
- package/public-types.d.mts +11 -2
- package/public-types.d.ts +11 -2
- package/responses/csv.d.mts +23 -0
- package/responses/csv.d.ts +23 -0
- package/responses/csv.js +136 -28
- package/responses/csv.mjs +136 -28
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
|
-
- `
|
|
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.
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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.
|
|
56
|
-
"@web-ts-toolkit/utils": "0.
|
|
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"
|
package/public-types.d.mts
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
|
-
|
|
35
|
-
<Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns:
|
|
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
|
-
|
|
35
|
-
<Params = ParamsDictionary, ResBody = unknown, ReqBody = unknown, ReqQuery = Query, Locals extends Record<string, unknown> = Record<string, unknown>, Return = unknown>(fns:
|
|
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/responses/csv.d.mts
CHANGED
|
@@ -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.d.ts
CHANGED
|
@@ -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
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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",
|
|
109
|
-
res.off?.("finish",
|
|
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
|
-
|
|
131
|
-
const normalizedError = error instanceof Error ? error :
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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",
|
|
159
|
-
res.on?.("finish",
|
|
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
|
-
|
|
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 (
|
|
171
|
-
|
|
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
|
|
175
|
-
if (
|
|
276
|
+
const result = await iterator.next();
|
|
277
|
+
if (failed) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (result.done) {
|
|
176
281
|
break;
|
|
177
282
|
}
|
|
178
|
-
|
|
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:
|