@web-ts-toolkit/express-response-handler 0.32.0 → 0.33.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 +50 -2
- package/create-handler.d.mts +2 -1
- package/create-handler.d.ts +2 -1
- package/create-handler.js +358 -125
- package/create-handler.mjs +361 -126
- package/error-format.d.mts +10 -3
- package/error-format.d.ts +10 -3
- package/error-format.js +69 -15
- package/error-format.mjs +65 -15
- package/http-response.d.mts +2 -6
- package/http-response.d.ts +2 -6
- package/http-response.js +140 -13
- package/http-response.mjs +140 -13
- package/index.d.mts +5 -3
- package/index.d.ts +5 -3
- package/index.js +365 -129
- package/index.mjs +366 -129
- package/llms.txt +6 -0
- package/package.json +8 -4
- package/public-types.d.mts +13 -17
- package/public-types.d.ts +13 -17
- package/responses/csv.d.mts +12 -6
- package/responses/csv.d.ts +12 -6
- package/responses/csv.js +133 -12
- package/responses/csv.mjs +133 -12
- package/responses/index.d.mts +2 -1
- package/responses/index.d.ts +2 -1
- package/responses/index.js +11 -3
- package/responses/index.mjs +9 -2
- package/responses/success.js +7 -1
- package/responses/success.mjs +7 -1
package/index.js
CHANGED
|
@@ -45,7 +45,8 @@ __export(index_exports, {
|
|
|
45
45
|
ResetContent: () => ResetContent,
|
|
46
46
|
Response: () => Response,
|
|
47
47
|
createHandler: () => createHandler,
|
|
48
|
-
default: () => index_default
|
|
48
|
+
default: () => index_default,
|
|
49
|
+
handleResponse: () => handleResponse
|
|
49
50
|
});
|
|
50
51
|
module.exports = __toCommonJS(index_exports);
|
|
51
52
|
|
|
@@ -56,6 +57,41 @@ var import_utils3 = require("@web-ts-toolkit/utils");
|
|
|
56
57
|
// src/responses/csv.ts
|
|
57
58
|
var import_format = require("@fast-csv/format");
|
|
58
59
|
var import_utils = require("@web-ts-toolkit/utils");
|
|
60
|
+
var import_events = require("events");
|
|
61
|
+
var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
|
|
62
|
+
var isIterable = (value) => typeof value === "object" && value !== null && Symbol.iterator in value;
|
|
63
|
+
var isAsyncIterable = (value) => typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
64
|
+
var isLazyCsvSource = (value) => !Array.isArray(value) && (isIterable(value) || isAsyncIterable(value));
|
|
65
|
+
var createAsyncIterator = (source) => {
|
|
66
|
+
if (isAsyncIterable(source)) {
|
|
67
|
+
return source[Symbol.asyncIterator]();
|
|
68
|
+
}
|
|
69
|
+
const iterator = (isIterable(source) ? source : (0, import_utils.castArray)(source))[Symbol.iterator]();
|
|
70
|
+
return {
|
|
71
|
+
next: () => Promise.resolve(iterator.next()),
|
|
72
|
+
return: (value) => {
|
|
73
|
+
if (iterator.return) {
|
|
74
|
+
return Promise.resolve(iterator.return(value));
|
|
75
|
+
}
|
|
76
|
+
return Promise.resolve({ done: true, value });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
var encodeRFC5987Value = (value) => encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
81
|
+
var createQuotedFilenameFallback = (filename) => {
|
|
82
|
+
const fallback = Array.from(filename.replace(/[\\/]/g, "_"), (char) => {
|
|
83
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
84
|
+
return codePoint >= 32 && codePoint <= 126 ? char : "_";
|
|
85
|
+
}).join("");
|
|
86
|
+
return fallback.replace(/(["\\])/g, "\\$1");
|
|
87
|
+
};
|
|
88
|
+
var createAttachmentContentDisposition = (filename) => {
|
|
89
|
+
if (CONTROL_CHARACTER_PATTERN.test(filename)) {
|
|
90
|
+
throw new TypeError("CSV filename cannot contain control characters");
|
|
91
|
+
}
|
|
92
|
+
const fallback = createQuotedFilenameFallback(filename);
|
|
93
|
+
return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeRFC5987Value(filename)}`;
|
|
94
|
+
};
|
|
59
95
|
var csvResponseBrand = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/express-response-handler.csv-response");
|
|
60
96
|
var isCSVResponse = (value) => {
|
|
61
97
|
if (value instanceof CSVResponse) {
|
|
@@ -72,35 +108,127 @@ _a = csvResponseBrand;
|
|
|
72
108
|
var CSVResponse = class {
|
|
73
109
|
constructor(dataset = [], options = {}) {
|
|
74
110
|
this[_a] = true;
|
|
75
|
-
this.dataset =
|
|
111
|
+
this.dataset = dataset;
|
|
76
112
|
this.filename = options.filename || "download.csv";
|
|
77
113
|
this.processor = options.processor || ((value) => value);
|
|
78
|
-
if ((0, import_utils.isBoolean)(options.headers)) {
|
|
114
|
+
if ((0, import_utils.isBoolean)(options.headers) || Array.isArray(options.headers)) {
|
|
79
115
|
this.headers = options.headers;
|
|
80
|
-
} else if (this.dataset.length > 0) {
|
|
116
|
+
} else if (Array.isArray(this.dataset) && this.dataset.length > 0) {
|
|
81
117
|
this.headers = (0, import_utils.isPlainObject)(this.dataset[0]);
|
|
118
|
+
} else if (isLazyCsvSource(this.dataset)) {
|
|
119
|
+
throw new TypeError("CSV lazy sources require an explicit headers option");
|
|
120
|
+
} else {
|
|
121
|
+
this.headers = (0, import_utils.isPlainObject)(this.dataset);
|
|
82
122
|
}
|
|
83
123
|
}
|
|
84
|
-
streamCsv(res) {
|
|
124
|
+
streamCsv(res, onBeforeOutputError) {
|
|
85
125
|
const stream = (0, import_format.format)({ headers: this.headers });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
let outputStarted = false;
|
|
127
|
+
let failed = false;
|
|
128
|
+
let finished = false;
|
|
129
|
+
let activeIterator = null;
|
|
130
|
+
let abortStreaming = () => void 0;
|
|
131
|
+
const abortSignal = new Promise((_, reject) => {
|
|
132
|
+
abortStreaming = reject;
|
|
133
|
+
});
|
|
134
|
+
abortSignal.catch(() => void 0);
|
|
135
|
+
const cleanup = () => {
|
|
136
|
+
stream.off("error", fail);
|
|
137
|
+
res.off?.("error", fail);
|
|
138
|
+
res.off?.("close", abort);
|
|
139
|
+
res.off?.("finish", markFinished);
|
|
140
|
+
};
|
|
141
|
+
const closeActiveIterator = () => {
|
|
142
|
+
const iterator = activeIterator;
|
|
143
|
+
activeIterator = null;
|
|
144
|
+
if (iterator?.return) {
|
|
145
|
+
void Promise.resolve(iterator.return()).catch(() => void 0);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const closeResponse = (error) => {
|
|
90
149
|
if (res.destroy) {
|
|
91
150
|
res.destroy(error);
|
|
92
151
|
return;
|
|
93
152
|
}
|
|
94
153
|
res.end();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
154
|
+
};
|
|
155
|
+
const fail = (error) => {
|
|
156
|
+
if (failed) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
failed = true;
|
|
160
|
+
cleanup();
|
|
161
|
+
const normalizedError = error instanceof Error ? error : new Error("CSV response streaming failed");
|
|
162
|
+
abortStreaming(normalizedError);
|
|
163
|
+
closeActiveIterator();
|
|
164
|
+
if (!outputStarted && !res.headersSent) {
|
|
165
|
+
onBeforeOutputError?.(normalizedError);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
stream.destroy();
|
|
169
|
+
closeResponse(normalizedError);
|
|
170
|
+
};
|
|
171
|
+
const abort = () => {
|
|
172
|
+
if (!finished) {
|
|
173
|
+
fail(new Error("CSV response streaming aborted"));
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const markFinished = () => {
|
|
177
|
+
finished = true;
|
|
178
|
+
};
|
|
179
|
+
const writeRow = async (row) => {
|
|
180
|
+
outputStarted = true;
|
|
181
|
+
if (!stream.write(row)) {
|
|
182
|
+
await Promise.race([(0, import_events.once)(stream, "drain"), abortSignal]);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
stream.on("error", fail);
|
|
186
|
+
stream.on("end", cleanup);
|
|
187
|
+
res.on?.("error", fail);
|
|
188
|
+
res.on?.("close", abort);
|
|
189
|
+
res.on?.("finish", markFinished);
|
|
190
|
+
const pump = async () => {
|
|
191
|
+
try {
|
|
192
|
+
const iterator = createAsyncIterator(this.dataset);
|
|
193
|
+
activeIterator = iterator;
|
|
194
|
+
const first = await iterator.next();
|
|
195
|
+
const firstRow = first.done ? void 0 : this.processor(first.value);
|
|
196
|
+
const contentDisposition = createAttachmentContentDisposition(this.filename);
|
|
197
|
+
res.set("Content-Type", "text/csv");
|
|
198
|
+
res.set("Content-Disposition", contentDisposition);
|
|
199
|
+
stream.pipe(res);
|
|
200
|
+
if (!first.done) {
|
|
201
|
+
await writeRow(firstRow);
|
|
202
|
+
}
|
|
203
|
+
while (!failed) {
|
|
204
|
+
const row = await Promise.race([iterator.next(), abortSignal]);
|
|
205
|
+
if (row.done) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
await writeRow(this.processor(row.value));
|
|
209
|
+
}
|
|
210
|
+
if (!failed) {
|
|
211
|
+
finished = true;
|
|
212
|
+
stream.end();
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
fail(error);
|
|
216
|
+
} finally {
|
|
217
|
+
activeIterator = null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
void pump();
|
|
99
221
|
}
|
|
100
222
|
};
|
|
101
223
|
|
|
102
224
|
// src/responses/index.ts
|
|
103
225
|
var responseBrand = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/express-response-handler.response");
|
|
226
|
+
var validateHttpStatusCode = (statusCode) => {
|
|
227
|
+
if (typeof statusCode !== "number" || !Number.isFinite(statusCode) || !Number.isInteger(statusCode) || statusCode < 100 || statusCode > 599) {
|
|
228
|
+
throw new TypeError("statusCode must be an integer HTTP status code between 100 and 599");
|
|
229
|
+
}
|
|
230
|
+
return statusCode;
|
|
231
|
+
};
|
|
104
232
|
var isResponse = (value) => {
|
|
105
233
|
if (value instanceof Response) {
|
|
106
234
|
return true;
|
|
@@ -116,7 +244,7 @@ _a2 = responseBrand;
|
|
|
116
244
|
var Response = class {
|
|
117
245
|
constructor(statusCode = 200, data) {
|
|
118
246
|
this[_a2] = true;
|
|
119
|
-
this.statusCode = statusCode;
|
|
247
|
+
this.statusCode = validateHttpStatusCode(statusCode);
|
|
120
248
|
this.data = data;
|
|
121
249
|
}
|
|
122
250
|
};
|
|
@@ -220,11 +348,17 @@ var HttpResponse = {
|
|
|
220
348
|
// src/error-format.ts
|
|
221
349
|
var import_http_errors2 = require("@web-ts-toolkit/http-errors");
|
|
222
350
|
var import_utils2 = require("@web-ts-toolkit/utils");
|
|
223
|
-
var
|
|
224
|
-
|
|
225
|
-
|
|
351
|
+
var FALLBACK_ERROR_STATUS = 500;
|
|
352
|
+
var FALLBACK_ERROR_MESSAGE = "Internal Server Error";
|
|
353
|
+
var validateErrorStatusCode = (statusCode, source) => {
|
|
354
|
+
return (0, import_http_errors2.validateHttpErrorStatusCode)(statusCode, source);
|
|
355
|
+
};
|
|
356
|
+
var toErrorStatusCode = (status, code, fallbackStatusCode = FALLBACK_ERROR_STATUS) => {
|
|
357
|
+
const statusCode = typeof status === "number" ? status : code;
|
|
358
|
+
if (statusCode === void 0) {
|
|
359
|
+
return fallbackStatusCode;
|
|
226
360
|
}
|
|
227
|
-
return
|
|
361
|
+
return validateErrorStatusCode(statusCode, "error status");
|
|
228
362
|
};
|
|
229
363
|
var toOptionalString = (value) => typeof value === "string" ? value : void 0;
|
|
230
364
|
var toMetadata = (value) => {
|
|
@@ -236,6 +370,41 @@ var toArray = (value) => {
|
|
|
236
370
|
}
|
|
237
371
|
return (0, import_utils2.isArray)(value) ? value : [value];
|
|
238
372
|
};
|
|
373
|
+
var describeThrown = (value) => {
|
|
374
|
+
try {
|
|
375
|
+
if (value === null) {
|
|
376
|
+
return "null";
|
|
377
|
+
}
|
|
378
|
+
if (value === void 0) {
|
|
379
|
+
return "undefined";
|
|
380
|
+
}
|
|
381
|
+
if (typeof value === "symbol") {
|
|
382
|
+
return value.toString();
|
|
383
|
+
}
|
|
384
|
+
const stringified = typeof value === "object" && value !== null ? value.message : void 0;
|
|
385
|
+
if (typeof stringified === "string" && stringified.length > 0) {
|
|
386
|
+
return stringified;
|
|
387
|
+
}
|
|
388
|
+
return String(value);
|
|
389
|
+
} catch {
|
|
390
|
+
return FALLBACK_ERROR_MESSAGE;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
var normalizeThrownError = (value) => {
|
|
394
|
+
if (value instanceof Error) {
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
const message = describeThrown(value);
|
|
398
|
+
if ((0, import_utils2.isPlainObject)(value)) {
|
|
399
|
+
const record = value;
|
|
400
|
+
const normalized = {
|
|
401
|
+
...record,
|
|
402
|
+
message
|
|
403
|
+
};
|
|
404
|
+
return normalized;
|
|
405
|
+
}
|
|
406
|
+
return { message };
|
|
407
|
+
};
|
|
239
408
|
var toHttpErrorShape = (error, fallbackDomain) => ({
|
|
240
409
|
statusCode: error.statusCode ?? 500,
|
|
241
410
|
status: error.status,
|
|
@@ -256,8 +425,8 @@ var toProblemDetailsSource = (result) => {
|
|
|
256
425
|
return (0, import_utils2.isPlainObject)(result) ? result : void 0;
|
|
257
426
|
};
|
|
258
427
|
var defaultErrorMessageProvider = function(error) {
|
|
259
|
-
|
|
260
|
-
return
|
|
428
|
+
void error;
|
|
429
|
+
return FALLBACK_ERROR_MESSAGE;
|
|
261
430
|
};
|
|
262
431
|
var toSimpleErrorPayload = (result) => (0, import_utils2.isString)(result) ? { message: result } : { ...result };
|
|
263
432
|
var toStructuredHttpErrorPayload = (error, errorDomain) => (0, import_http_errors2.toAip193ErrorPayload)(toHttpErrorShape(error, errorDomain), errorDomain);
|
|
@@ -265,7 +434,7 @@ var toRfc9457HttpErrorPayload = (error, errorDomain) => (0, import_http_errors2.
|
|
|
265
434
|
var toStructuredGenericErrorPayload = (result, errorDomain) => {
|
|
266
435
|
if ((0, import_utils2.isPlainObject)(result) && (0, import_utils2.isPlainObject)(result.error)) {
|
|
267
436
|
const error = result.error;
|
|
268
|
-
const statusCode =
|
|
437
|
+
const statusCode = toErrorStatusCode(error.code, void 0);
|
|
269
438
|
const status = typeof error.status === "string" ? error.status : (0, import_http_errors2.getCanonicalStatus)(statusCode);
|
|
270
439
|
const message = typeof error.message === "string" ? error.message : "";
|
|
271
440
|
const details = toArray(error.details);
|
|
@@ -279,7 +448,7 @@ var toStructuredGenericErrorPayload = (result, errorDomain) => {
|
|
|
279
448
|
};
|
|
280
449
|
}
|
|
281
450
|
if ((0, import_utils2.isPlainObject)(result)) {
|
|
282
|
-
const statusCode =
|
|
451
|
+
const statusCode = toErrorStatusCode(result.code, void 0);
|
|
283
452
|
const status = typeof result.status === "string" ? result.status : (0, import_http_errors2.getCanonicalStatus)(statusCode);
|
|
284
453
|
const message = typeof result.message === "string" ? result.message : "";
|
|
285
454
|
const details = toArray(result.details) || [(0, import_http_errors2.createAip193ErrorInfoDetail)(status, errorDomain)];
|
|
@@ -294,17 +463,17 @@ var toStructuredGenericErrorPayload = (result, errorDomain) => {
|
|
|
294
463
|
}
|
|
295
464
|
return {
|
|
296
465
|
error: {
|
|
297
|
-
code:
|
|
298
|
-
status: (0, import_http_errors2.getCanonicalStatus)(
|
|
466
|
+
code: FALLBACK_ERROR_STATUS,
|
|
467
|
+
status: (0, import_http_errors2.getCanonicalStatus)(FALLBACK_ERROR_STATUS),
|
|
299
468
|
message: String(result),
|
|
300
|
-
details: [(0, import_http_errors2.createAip193ErrorInfoDetail)((0, import_http_errors2.getCanonicalStatus)(
|
|
469
|
+
details: [(0, import_http_errors2.createAip193ErrorInfoDetail)((0, import_http_errors2.getCanonicalStatus)(FALLBACK_ERROR_STATUS), errorDomain)]
|
|
301
470
|
}
|
|
302
471
|
};
|
|
303
472
|
};
|
|
304
473
|
var toRfc9457GenericErrorPayload = (result) => {
|
|
305
474
|
const problem = toProblemDetailsSource(result);
|
|
306
475
|
if (problem) {
|
|
307
|
-
const statusCode =
|
|
476
|
+
const statusCode = toErrorStatusCode(problem.status, problem.code);
|
|
308
477
|
return (0, import_http_errors2.toRfc9457ErrorPayload)({
|
|
309
478
|
statusCode,
|
|
310
479
|
message: toOptionalString(problem.detail) ?? toOptionalString(problem.message) ?? "",
|
|
@@ -315,7 +484,7 @@ var toRfc9457GenericErrorPayload = (result) => {
|
|
|
315
484
|
});
|
|
316
485
|
}
|
|
317
486
|
return (0, import_http_errors2.toRfc9457ErrorPayload)({
|
|
318
|
-
statusCode:
|
|
487
|
+
statusCode: FALLBACK_ERROR_STATUS,
|
|
319
488
|
message: String(result)
|
|
320
489
|
});
|
|
321
490
|
};
|
|
@@ -328,11 +497,9 @@ var ErrorFormats = {
|
|
|
328
497
|
};
|
|
329
498
|
|
|
330
499
|
// src/create-handler.ts
|
|
331
|
-
var promisify = (fn) => (value) => Promise.resolve().then(() => fn(value));
|
|
332
|
-
var invokePostHook = (hook, value) => {
|
|
333
|
-
void hook(value).catch(() => void 0);
|
|
334
|
-
};
|
|
500
|
+
var promisify = (fn) => (value) => Promise.resolve().then(() => fn(value)).then(() => void 0);
|
|
335
501
|
var RFC_9457_CONTENT_TYPE = "application/problem+json";
|
|
502
|
+
var SUPPORTED_RFC_9457_CONTENT_TYPES = /* @__PURE__ */ new Set(["application/problem+json", "application/json"]);
|
|
336
503
|
var shouldSkipResponse = (res, event) => res.headersSent || event.canceled;
|
|
337
504
|
var sendProblemJson = (res, statusCode, payload, contentType) => {
|
|
338
505
|
res.status(statusCode);
|
|
@@ -341,30 +508,34 @@ var sendProblemJson = (res, statusCode, payload, contentType) => {
|
|
|
341
508
|
};
|
|
342
509
|
var sendHttpErrorByFormat = {
|
|
343
510
|
[ErrorFormats.simple]: (res, error) => {
|
|
511
|
+
const statusCode = validateErrorStatusCode(error.statusCode ?? FALLBACK_ERROR_STATUS, "error.statusCode");
|
|
344
512
|
const payload = { message: error.message ?? "" };
|
|
345
513
|
if (error.errors !== void 0) {
|
|
346
514
|
payload.errors = error.errors;
|
|
347
515
|
}
|
|
348
|
-
res.status(
|
|
516
|
+
res.status(statusCode).send(payload);
|
|
349
517
|
},
|
|
350
518
|
[ErrorFormats.aip193]: (res, error, domain) => {
|
|
351
|
-
|
|
519
|
+
const statusCode = validateErrorStatusCode(error.statusCode ?? FALLBACK_ERROR_STATUS, "error.statusCode");
|
|
520
|
+
res.status(statusCode).send(toStructuredHttpErrorPayload(error, domain));
|
|
352
521
|
},
|
|
353
522
|
[ErrorFormats.rfc9457]: (res, error, domain) => {
|
|
354
|
-
|
|
523
|
+
const statusCode = validateErrorStatusCode(error.statusCode ?? FALLBACK_ERROR_STATUS, "error.statusCode");
|
|
524
|
+
sendProblemJson(res, statusCode, toRfc9457HttpErrorPayload(error, domain), RFC_9457_CONTENT_TYPE);
|
|
355
525
|
}
|
|
356
526
|
};
|
|
357
527
|
var sendGenericErrorByFormat = {
|
|
358
528
|
[ErrorFormats.simple]: (res, result) => {
|
|
359
|
-
res.status(
|
|
529
|
+
res.status(FALLBACK_ERROR_STATUS).send(toSimpleErrorPayload(result));
|
|
360
530
|
},
|
|
361
531
|
[ErrorFormats.aip193]: (res, result, domain) => {
|
|
362
532
|
const payload = toStructuredGenericErrorPayload(result, domain);
|
|
363
|
-
res.status(payload.error.code).send(payload);
|
|
533
|
+
res.status(validateErrorStatusCode(payload.error.code, "error.code")).send(payload);
|
|
364
534
|
},
|
|
365
535
|
[ErrorFormats.rfc9457]: (res, result) => {
|
|
366
536
|
const payload = toRfc9457GenericErrorPayload(result);
|
|
367
|
-
|
|
537
|
+
const statusCode = payload.status ?? FALLBACK_ERROR_STATUS;
|
|
538
|
+
sendProblemJson(res, validateErrorStatusCode(statusCode, "problem.status"), payload, RFC_9457_CONTENT_TYPE);
|
|
368
539
|
}
|
|
369
540
|
};
|
|
370
541
|
var assertMiddleware = (fn) => {
|
|
@@ -384,10 +555,23 @@ var normalizeMiddlewareList = (fns) => {
|
|
|
384
555
|
assertMiddleware(fns[0]);
|
|
385
556
|
return [fns[0]];
|
|
386
557
|
};
|
|
387
|
-
|
|
558
|
+
var validateHandlerConfig = (options) => {
|
|
388
559
|
const errorFormat = options.errorFormat ?? ErrorFormats.simple;
|
|
389
560
|
const errorDomain = options.errorDomain ?? "express-response-handler";
|
|
390
561
|
const rfc9457ContentType = options.rfc9457ContentType ?? RFC_9457_CONTENT_TYPE;
|
|
562
|
+
import_assert.default.ok(
|
|
563
|
+
Object.values(ErrorFormats).includes(errorFormat),
|
|
564
|
+
`errorFormat must be one of: ${Object.values(ErrorFormats).join(", ")}`
|
|
565
|
+
);
|
|
566
|
+
import_assert.default.ok(typeof errorDomain === "string" && errorDomain.length > 0, "errorDomain must be a non-empty string");
|
|
567
|
+
import_assert.default.ok(
|
|
568
|
+
SUPPORTED_RFC_9457_CONTENT_TYPES.has(rfc9457ContentType),
|
|
569
|
+
"rfc9457ContentType must be one of: application/problem+json, application/json"
|
|
570
|
+
);
|
|
571
|
+
return Object.freeze({ errorFormat, errorDomain, rfc9457ContentType });
|
|
572
|
+
};
|
|
573
|
+
function createHandler(options = {}) {
|
|
574
|
+
const config = validateHandlerConfig(options);
|
|
391
575
|
let errorMessageProvider = defaultErrorMessageProvider;
|
|
392
576
|
let preJson = null;
|
|
393
577
|
let postJson = null;
|
|
@@ -407,153 +591,202 @@ function createHandler(options = {}) {
|
|
|
407
591
|
setState(fn, promisify(fn));
|
|
408
592
|
rebuild();
|
|
409
593
|
};
|
|
410
|
-
const
|
|
594
|
+
const noopRebuild = () => {
|
|
595
|
+
};
|
|
596
|
+
const sendBaseJson = function(res, data, event, onBeforeOutputError) {
|
|
411
597
|
if (shouldSkipResponse(res, event)) {
|
|
412
|
-
return;
|
|
598
|
+
return false;
|
|
413
599
|
}
|
|
414
600
|
if (data === void 0) {
|
|
415
|
-
return;
|
|
601
|
+
return false;
|
|
416
602
|
}
|
|
417
603
|
if (isResponse(data)) {
|
|
418
|
-
res.status(data.statusCode).json(data.data);
|
|
419
|
-
return;
|
|
604
|
+
res.status(validateHttpStatusCode(data.statusCode)).json(data.data);
|
|
605
|
+
return true;
|
|
420
606
|
}
|
|
421
607
|
if (isCSVResponse(data)) {
|
|
422
|
-
data.streamCsv(res);
|
|
423
|
-
return;
|
|
608
|
+
data.streamCsv(res, onBeforeOutputError);
|
|
609
|
+
return true;
|
|
424
610
|
}
|
|
425
611
|
res.json(data);
|
|
612
|
+
return true;
|
|
426
613
|
};
|
|
427
614
|
const sendBaseError = function(res, err, event) {
|
|
428
615
|
if (shouldSkipResponse(res, event)) {
|
|
429
|
-
return;
|
|
616
|
+
return false;
|
|
430
617
|
}
|
|
431
|
-
const error = err;
|
|
432
|
-
if (error.statusCode) {
|
|
433
|
-
|
|
618
|
+
const error = normalizeThrownError(err);
|
|
619
|
+
if (error.statusCode !== void 0) {
|
|
620
|
+
const statusCode = validateErrorStatusCode(error.statusCode, "error.statusCode");
|
|
621
|
+
if (config.errorFormat === ErrorFormats.rfc9457) {
|
|
434
622
|
sendProblemJson(
|
|
435
623
|
res,
|
|
436
|
-
|
|
437
|
-
toRfc9457HttpErrorPayload(error, errorDomain),
|
|
438
|
-
rfc9457ContentType
|
|
624
|
+
statusCode,
|
|
625
|
+
toRfc9457HttpErrorPayload(error, config.errorDomain),
|
|
626
|
+
config.rfc9457ContentType
|
|
439
627
|
);
|
|
440
|
-
return;
|
|
628
|
+
return true;
|
|
441
629
|
}
|
|
442
|
-
sendHttpErrorByFormat[errorFormat](res, error, errorDomain);
|
|
443
|
-
return;
|
|
630
|
+
sendHttpErrorByFormat[config.errorFormat](res, error, config.errorDomain);
|
|
631
|
+
return true;
|
|
444
632
|
}
|
|
445
633
|
const result = errorMessageProvider(err);
|
|
446
|
-
if (errorFormat === ErrorFormats.rfc9457) {
|
|
634
|
+
if (config.errorFormat === ErrorFormats.rfc9457) {
|
|
447
635
|
const payload = toRfc9457GenericErrorPayload(result);
|
|
448
|
-
|
|
449
|
-
|
|
636
|
+
const statusCode = payload.status ?? FALLBACK_ERROR_STATUS;
|
|
637
|
+
sendProblemJson(res, validateErrorStatusCode(statusCode, "problem.status"), payload, config.rfc9457ContentType);
|
|
638
|
+
return true;
|
|
450
639
|
}
|
|
451
|
-
sendGenericErrorByFormat[errorFormat](res, result, errorDomain);
|
|
640
|
+
sendGenericErrorByFormat[config.errorFormat](res, result, config.errorDomain);
|
|
641
|
+
return true;
|
|
642
|
+
};
|
|
643
|
+
const invokePostHook = (hook, value, onFailure) => {
|
|
644
|
+
hook(value).then(
|
|
645
|
+
() => void 0,
|
|
646
|
+
(err) => onFailure(err)
|
|
647
|
+
);
|
|
452
648
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (!preJsonHook && !postJsonHook) {
|
|
457
|
-
sendJson = sendBaseJson;
|
|
649
|
+
const invokePostHookOnFinish = (res, hook, value, onFailure) => {
|
|
650
|
+
if ((0, import_utils3.isFunction)(res.once)) {
|
|
651
|
+
res.once("finish", () => invokePostHook(hook, value, onFailure));
|
|
458
652
|
return;
|
|
459
653
|
}
|
|
460
|
-
|
|
461
|
-
const finalize = () => {
|
|
462
|
-
try {
|
|
463
|
-
sendBaseJson(res, data, event);
|
|
464
|
-
} catch (err) {
|
|
465
|
-
sendError(res, err, event);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
if (postJsonHook) {
|
|
469
|
-
invokePostHook(postJsonHook, data);
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
if (preJsonHook) {
|
|
473
|
-
preJsonHook(data).then(finalize, (err) => sendError(res, err, event));
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
finalize();
|
|
477
|
-
};
|
|
654
|
+
invokePostHook(hook, value, onFailure);
|
|
478
655
|
};
|
|
479
|
-
const
|
|
480
|
-
if (
|
|
481
|
-
sendError = sendBaseError;
|
|
656
|
+
const terminalErrorBoundary = (res, next, terminalError, event) => {
|
|
657
|
+
if (event.canceled) {
|
|
482
658
|
return;
|
|
483
659
|
}
|
|
484
|
-
|
|
485
|
-
const finalize = () => {
|
|
486
|
-
sendBaseError(res, err, event);
|
|
487
|
-
if (postErrorHook) {
|
|
488
|
-
invokePostHook(postErrorHook, err);
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
if (preErrorHook) {
|
|
492
|
-
preErrorHook(err).then(finalize, (hookErr) => sendBaseError(res, hookErr, event));
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
finalize();
|
|
496
|
-
};
|
|
660
|
+
next(terminalError);
|
|
497
661
|
};
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
662
|
+
const dispatchError = (res, next, err, event) => {
|
|
663
|
+
const reportFailure = (failure) => {
|
|
664
|
+
terminalErrorBoundary(res, next, failure, event);
|
|
665
|
+
};
|
|
666
|
+
const sendFormatted = (failure) => {
|
|
667
|
+
let didSend;
|
|
668
|
+
try {
|
|
669
|
+
didSend = sendBaseError(res, failure, event);
|
|
670
|
+
} catch (senderFailure) {
|
|
671
|
+
reportFailure(senderFailure === void 0 ? failure : senderFailure);
|
|
502
672
|
return;
|
|
503
673
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (
|
|
674
|
+
if (didSend && postErrorHook) {
|
|
675
|
+
invokePostHookOnFinish(res, postErrorHook, failure, reportFailure);
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
if (event.canceled) {
|
|
509
679
|
return;
|
|
510
680
|
}
|
|
511
|
-
|
|
512
|
-
|
|
681
|
+
const runSender = () => sendFormatted(err);
|
|
682
|
+
if (preErrorHook) {
|
|
683
|
+
preErrorHook(err).then(
|
|
684
|
+
() => sendFormatted(err),
|
|
685
|
+
(hookErr) => sendFormatted(hookErr === void 0 ? err : hookErr)
|
|
686
|
+
);
|
|
513
687
|
return;
|
|
514
688
|
}
|
|
515
|
-
|
|
516
|
-
|
|
689
|
+
runSender();
|
|
690
|
+
};
|
|
691
|
+
const dispatchValue = (res, next, data, event, onSerialized) => {
|
|
692
|
+
const reportFailure = (failure) => {
|
|
693
|
+
terminalErrorBoundary(res, next, failure, event);
|
|
694
|
+
};
|
|
695
|
+
const sendFormattedError = (failure) => {
|
|
696
|
+
try {
|
|
697
|
+
sendBaseError(res, failure, event);
|
|
698
|
+
} catch (senderFailure) {
|
|
699
|
+
reportFailure(senderFailure === void 0 ? failure : senderFailure);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
const runSender = () => {
|
|
703
|
+
let didSend;
|
|
704
|
+
try {
|
|
705
|
+
didSend = sendBaseJson(res, data, event, sendFormattedError);
|
|
706
|
+
} catch (senderFailure) {
|
|
707
|
+
const failure = senderFailure === void 0 ? new Error("response serialization failed") : senderFailure;
|
|
708
|
+
if (res.headersSent) {
|
|
709
|
+
reportFailure(failure);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
sendFormattedError(failure);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (didSend && postJsonHook) {
|
|
716
|
+
invokePostHookOnFinish(res, postJsonHook, data, reportFailure);
|
|
717
|
+
}
|
|
718
|
+
onSerialized();
|
|
719
|
+
};
|
|
720
|
+
if (preJsonHook) {
|
|
721
|
+
preJsonHook(data).then(
|
|
722
|
+
runSender,
|
|
723
|
+
(hookErr) => sendFormattedError(hookErr === void 0 ? new Error("pre-json hook failed") : hookErr)
|
|
724
|
+
);
|
|
517
725
|
return;
|
|
518
726
|
}
|
|
519
|
-
|
|
727
|
+
runSender();
|
|
520
728
|
};
|
|
521
729
|
const nextFn = function(event, next) {
|
|
522
|
-
return function(
|
|
523
|
-
if (
|
|
730
|
+
return function(error) {
|
|
731
|
+
if (event.canceled) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (error === void 0) {
|
|
524
735
|
event.canceled = true;
|
|
525
736
|
next();
|
|
526
737
|
return;
|
|
527
738
|
}
|
|
528
|
-
|
|
529
|
-
|
|
739
|
+
event.canceled = true;
|
|
740
|
+
next(error);
|
|
741
|
+
};
|
|
742
|
+
};
|
|
743
|
+
const runLifecycle = (res, next, result, event) => {
|
|
744
|
+
const finalize = (resolved) => {
|
|
745
|
+
if (event.canceled) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (event.nextError) {
|
|
749
|
+
dispatchError(res, next, event.nextError, event);
|
|
530
750
|
return;
|
|
531
751
|
}
|
|
532
|
-
|
|
752
|
+
dispatchValue(res, next, resolved, event, () => void 0);
|
|
533
753
|
};
|
|
754
|
+
if ((0, import_utils3.isPromise)(result)) {
|
|
755
|
+
Promise.resolve(result).then(
|
|
756
|
+
(resolved) => finalize(resolved),
|
|
757
|
+
(err) => {
|
|
758
|
+
if (res.headersSent) {
|
|
759
|
+
next(err);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
dispatchError(res, next, err, event);
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
finalize(result);
|
|
534
768
|
};
|
|
535
769
|
const routerFn = function(fn) {
|
|
536
770
|
return function(req, res, next) {
|
|
537
771
|
const event = { canceled: false, nextError: null };
|
|
538
772
|
try {
|
|
539
773
|
const result = fn(req, res, nextFn(event, next));
|
|
540
|
-
|
|
774
|
+
runLifecycle(res, next, result, event);
|
|
541
775
|
} catch (err) {
|
|
542
776
|
if (res.headersSent) {
|
|
777
|
+
next(err);
|
|
543
778
|
return;
|
|
544
779
|
}
|
|
545
|
-
|
|
780
|
+
dispatchError(res, next, err, event);
|
|
546
781
|
}
|
|
547
782
|
};
|
|
548
783
|
};
|
|
549
|
-
const
|
|
784
|
+
const handleResponse2 = function(...fns) {
|
|
550
785
|
const middlewares = normalizeMiddlewareList(fns);
|
|
551
786
|
return middlewares.length === 1 ? routerFn(middlewares[0]) : middlewares.map(routerFn);
|
|
552
787
|
};
|
|
553
|
-
|
|
554
|
-
handleResponse,
|
|
555
|
-
handleResult,
|
|
556
|
-
handlePromise,
|
|
788
|
+
const handler = {
|
|
789
|
+
handleResponse: handleResponse2,
|
|
557
790
|
HttpResponse,
|
|
558
791
|
createHandler,
|
|
559
792
|
get errorMessageProvider() {
|
|
@@ -574,7 +807,7 @@ function createHandler(options = {}) {
|
|
|
574
807
|
preJson = syncHook;
|
|
575
808
|
preJsonHook = asyncHook;
|
|
576
809
|
},
|
|
577
|
-
|
|
810
|
+
noopRebuild
|
|
578
811
|
);
|
|
579
812
|
},
|
|
580
813
|
get postJson() {
|
|
@@ -588,7 +821,7 @@ function createHandler(options = {}) {
|
|
|
588
821
|
postJson = syncHook;
|
|
589
822
|
postJsonHook = asyncHook;
|
|
590
823
|
},
|
|
591
|
-
|
|
824
|
+
noopRebuild
|
|
592
825
|
);
|
|
593
826
|
},
|
|
594
827
|
get preError() {
|
|
@@ -602,7 +835,7 @@ function createHandler(options = {}) {
|
|
|
602
835
|
preError = syncHook;
|
|
603
836
|
preErrorHook = asyncHook;
|
|
604
837
|
},
|
|
605
|
-
|
|
838
|
+
noopRebuild
|
|
606
839
|
);
|
|
607
840
|
},
|
|
608
841
|
get postError() {
|
|
@@ -616,14 +849,16 @@ function createHandler(options = {}) {
|
|
|
616
849
|
postError = syncHook;
|
|
617
850
|
postErrorHook = asyncHook;
|
|
618
851
|
},
|
|
619
|
-
|
|
852
|
+
noopRebuild
|
|
620
853
|
);
|
|
621
854
|
}
|
|
622
855
|
};
|
|
856
|
+
return handler;
|
|
623
857
|
}
|
|
624
858
|
|
|
625
859
|
// src/index.ts
|
|
626
860
|
var apiHandler = createHandler();
|
|
861
|
+
var { handleResponse } = apiHandler;
|
|
627
862
|
var index_default = apiHandler;
|
|
628
863
|
// Annotate the CommonJS export names for ESM import in node:
|
|
629
864
|
0 && (module.exports = {
|
|
@@ -641,5 +876,6 @@ var index_default = apiHandler;
|
|
|
641
876
|
PartialContent,
|
|
642
877
|
ResetContent,
|
|
643
878
|
Response,
|
|
644
|
-
createHandler
|
|
879
|
+
createHandler,
|
|
880
|
+
handleResponse
|
|
645
881
|
});
|