@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/create-handler.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { isArray as isArray2, isFunction, isPromise } from "@web-ts-toolkit/util
|
|
|
5
5
|
// src/responses/csv.ts
|
|
6
6
|
import { format } from "@fast-csv/format";
|
|
7
7
|
import { castArray, isBoolean, isPlainObject } from "@web-ts-toolkit/utils";
|
|
8
|
-
import { once } from "events";
|
|
9
8
|
var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
|
|
10
9
|
var isIterable = (value) => typeof value === "object" && value !== null && Symbol.iterator in value;
|
|
11
10
|
var isAsyncIterable = (value) => typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
@@ -69,28 +68,66 @@ var CSVResponse = class {
|
|
|
69
68
|
this.headers = isPlainObject(this.dataset);
|
|
70
69
|
}
|
|
71
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Streams the dataset as CSV into `res`.
|
|
73
|
+
*
|
|
74
|
+
* Contract choice (B-ERH-06): minimal runtime-safe fallback, no breaking
|
|
75
|
+
* signature change. `onBeforeOutputError` stays optional.
|
|
76
|
+
*
|
|
77
|
+
* - Pre-output failure (invalid filename, failed first read, first-row
|
|
78
|
+
* processor throw, before headers are sent) with an error owner:
|
|
79
|
+
* the owner is invoked exactly once with the normalized failure and owns
|
|
80
|
+
* destination termination (the handler owner renders a single redacted
|
|
81
|
+
* JSON error; direct callers must end/destroy `res` themselves). A
|
|
82
|
+
* throwing owner is contained and the destination is then destroyed with
|
|
83
|
+
* the original failure so it never stays open.
|
|
84
|
+
* - Pre-output failure without an error owner: deterministic fallback —
|
|
85
|
+
* the destination is destroyed with the normalized failure (or ended
|
|
86
|
+
* when `destroy` is unavailable), observable via `error` + `close`.
|
|
87
|
+
* - Post-output failure (after piping starts): the destination is always
|
|
88
|
+
* destroyed with the failure, with or without an owner.
|
|
89
|
+
*
|
|
90
|
+
* Non-`Error` failures are wrapped in an `Error` with the original kept as
|
|
91
|
+
* `cause`. A throwing owner never displaces the original failure and never
|
|
92
|
+
* escapes as a process-level error.
|
|
93
|
+
*/
|
|
72
94
|
streamCsv(res, onBeforeOutputError) {
|
|
73
95
|
const stream = format({ headers: this.headers });
|
|
74
96
|
let outputStarted = false;
|
|
75
97
|
let failed = false;
|
|
76
98
|
let finished = false;
|
|
99
|
+
let cleaned = false;
|
|
77
100
|
let activeIterator = null;
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
const abortWaiters = /* @__PURE__ */ new Set();
|
|
102
|
+
const notifyAborted = () => {
|
|
103
|
+
for (const notify of Array.from(abortWaiters)) {
|
|
104
|
+
abortWaiters.delete(notify);
|
|
105
|
+
try {
|
|
106
|
+
notify();
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
83
111
|
const cleanup = () => {
|
|
112
|
+
if (cleaned) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
cleaned = true;
|
|
84
116
|
stream.off("error", fail);
|
|
85
117
|
res.off?.("error", fail);
|
|
86
|
-
res.off?.("close",
|
|
87
|
-
res.off?.("finish",
|
|
118
|
+
res.off?.("close", handleClose);
|
|
119
|
+
res.off?.("finish", handleFinish);
|
|
88
120
|
};
|
|
89
121
|
const closeActiveIterator = () => {
|
|
90
122
|
const iterator = activeIterator;
|
|
91
123
|
activeIterator = null;
|
|
92
|
-
if (iterator?.return) {
|
|
124
|
+
if (!iterator?.return) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
93
128
|
void Promise.resolve(iterator.return()).catch(() => void 0);
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
94
131
|
}
|
|
95
132
|
};
|
|
96
133
|
const closeResponse = (error) => {
|
|
@@ -105,58 +142,129 @@ var CSVResponse = class {
|
|
|
105
142
|
return;
|
|
106
143
|
}
|
|
107
144
|
failed = true;
|
|
108
|
-
|
|
109
|
-
const normalizedError = error instanceof Error ? error :
|
|
110
|
-
|
|
145
|
+
notifyAborted();
|
|
146
|
+
const normalizedError = error instanceof Error ? error : (() => {
|
|
147
|
+
const wrapped = new Error("CSV response streaming failed");
|
|
148
|
+
try {
|
|
149
|
+
wrapped.cause = error;
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
return wrapped;
|
|
153
|
+
})();
|
|
111
154
|
closeActiveIterator();
|
|
112
155
|
if (!outputStarted && !res.headersSent) {
|
|
113
|
-
|
|
156
|
+
stream.destroy();
|
|
157
|
+
if (onBeforeOutputError) {
|
|
158
|
+
cleanup();
|
|
159
|
+
try {
|
|
160
|
+
onBeforeOutputError(normalizedError);
|
|
161
|
+
} catch {
|
|
162
|
+
try {
|
|
163
|
+
res.on?.("error", () => void 0);
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
closeResponse(normalizedError);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
closeResponse(normalizedError);
|
|
175
|
+
} catch {
|
|
176
|
+
cleanup();
|
|
177
|
+
}
|
|
114
178
|
return;
|
|
115
179
|
}
|
|
116
180
|
stream.destroy();
|
|
117
181
|
closeResponse(normalizedError);
|
|
118
182
|
};
|
|
119
|
-
const
|
|
120
|
-
if (!finished) {
|
|
183
|
+
const handleClose = () => {
|
|
184
|
+
if (!finished && !failed) {
|
|
121
185
|
fail(new Error("CSV response streaming aborted"));
|
|
186
|
+
cleanup();
|
|
187
|
+
return;
|
|
122
188
|
}
|
|
189
|
+
cleanup();
|
|
123
190
|
};
|
|
124
|
-
const
|
|
191
|
+
const handleFinish = () => {
|
|
125
192
|
finished = true;
|
|
193
|
+
cleanup();
|
|
126
194
|
};
|
|
127
195
|
const writeRow = async (row) => {
|
|
196
|
+
if (failed) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
128
199
|
outputStarted = true;
|
|
129
200
|
if (!stream.write(row)) {
|
|
130
|
-
await Promise
|
|
201
|
+
await new Promise((resolve) => {
|
|
202
|
+
if (failed) {
|
|
203
|
+
resolve();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const onDrain = () => {
|
|
207
|
+
stream.off("drain", onDrain);
|
|
208
|
+
abortWaiters.delete(onAbort);
|
|
209
|
+
resolve();
|
|
210
|
+
};
|
|
211
|
+
const onAbort = () => {
|
|
212
|
+
stream.off("drain", onDrain);
|
|
213
|
+
resolve();
|
|
214
|
+
};
|
|
215
|
+
abortWaiters.add(onAbort);
|
|
216
|
+
stream.once("drain", onDrain);
|
|
217
|
+
});
|
|
131
218
|
}
|
|
132
219
|
};
|
|
133
220
|
stream.on("error", fail);
|
|
134
|
-
stream.on("end", cleanup);
|
|
135
221
|
res.on?.("error", fail);
|
|
136
|
-
res.on?.("close",
|
|
137
|
-
res.on?.("finish",
|
|
222
|
+
res.on?.("close", handleClose);
|
|
223
|
+
res.on?.("finish", handleFinish);
|
|
138
224
|
const pump = async () => {
|
|
139
225
|
try {
|
|
140
226
|
const iterator = createAsyncIterator(this.dataset);
|
|
141
227
|
activeIterator = iterator;
|
|
142
228
|
const first = await iterator.next();
|
|
143
|
-
|
|
229
|
+
if (failed) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
let firstRow;
|
|
233
|
+
if (!first.done) {
|
|
234
|
+
firstRow = this.processor(first.value);
|
|
235
|
+
if (failed) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
144
239
|
const contentDisposition = createAttachmentContentDisposition(this.filename);
|
|
240
|
+
if (failed) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
145
243
|
res.set("Content-Type", "text/csv");
|
|
146
244
|
res.set("Content-Disposition", contentDisposition);
|
|
147
245
|
stream.pipe(res);
|
|
148
|
-
if (
|
|
149
|
-
|
|
246
|
+
if (first.done) {
|
|
247
|
+
if (!failed) {
|
|
248
|
+
stream.end();
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
150
251
|
}
|
|
252
|
+
await writeRow(firstRow);
|
|
151
253
|
while (!failed) {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
254
|
+
const result = await iterator.next();
|
|
255
|
+
if (failed) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (result.done) {
|
|
154
259
|
break;
|
|
155
260
|
}
|
|
156
|
-
|
|
261
|
+
const processed = this.processor(result.value);
|
|
262
|
+
if (failed) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
await writeRow(processed);
|
|
157
266
|
}
|
|
158
267
|
if (!failed) {
|
|
159
|
-
finished = true;
|
|
160
268
|
stream.end();
|
|
161
269
|
}
|
|
162
270
|
} catch (error) {
|
|
@@ -165,7 +273,7 @@ var CSVResponse = class {
|
|
|
165
273
|
activeIterator = null;
|
|
166
274
|
}
|
|
167
275
|
};
|
|
168
|
-
void pump();
|
|
276
|
+
void pump().then(void 0, () => void 0);
|
|
169
277
|
}
|
|
170
278
|
};
|
|
171
279
|
|
|
@@ -280,16 +388,16 @@ import {
|
|
|
280
388
|
UriTooLongError
|
|
281
389
|
} from "@web-ts-toolkit/http-errors";
|
|
282
390
|
var HttpResponse = {
|
|
283
|
-
ok: (
|
|
284
|
-
created: (
|
|
285
|
-
accepted: (
|
|
286
|
-
nonAuthoritativeInfo: (
|
|
391
|
+
ok: (data) => new OK(data),
|
|
392
|
+
created: (data) => new Created(data),
|
|
393
|
+
accepted: (data) => new Accepted(data),
|
|
394
|
+
nonAuthoritativeInfo: (data) => new NonAuthoritativeInfo(data),
|
|
287
395
|
noContent: (...args) => new NoContent(...args),
|
|
288
|
-
resetContent: (
|
|
289
|
-
partialContent: (
|
|
290
|
-
multiStatus: (
|
|
291
|
-
alreadyReported: (
|
|
292
|
-
imUsed: (
|
|
396
|
+
resetContent: (data) => new ResetContent(data),
|
|
397
|
+
partialContent: (data) => new PartialContent(data),
|
|
398
|
+
multiStatus: (data) => new MultiStatus(data),
|
|
399
|
+
alreadyReported: (data) => new AlreadyReported(data),
|
|
400
|
+
imUsed: (data) => new IMUsed(data),
|
|
293
401
|
badRequest: (...args) => new BadRequestError(...args),
|
|
294
402
|
unauthorized: (...args) => new UnauthorizedError(...args),
|
|
295
403
|
forbidden: (...args) => new ForbiddenError(...args),
|
|
@@ -317,7 +425,7 @@ var HttpResponse = {
|
|
|
317
425
|
tooManyRequests: (...args) => new TooManyRequestsError(...args),
|
|
318
426
|
requestHeaderFieldsTooLarge: (...args) => new RequestHeaderFieldsTooLargeError(...args),
|
|
319
427
|
unavailableForLegalReasons: (...args) => new UnavailableForLegalReasonsError(...args),
|
|
320
|
-
json: (
|
|
428
|
+
json: (data) => new OK(data),
|
|
321
429
|
csv: (...args) => new CSVResponse(...args)
|
|
322
430
|
};
|
|
323
431
|
|
|
@@ -531,9 +639,10 @@ var normalizeMiddlewareList = (fns) => {
|
|
|
531
639
|
return fns;
|
|
532
640
|
}
|
|
533
641
|
if (isArray2(fns[0])) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
642
|
+
const nested = fns[0];
|
|
643
|
+
assert.ok(nested.length > 0, "at least one middleware handler is required");
|
|
644
|
+
nested.forEach(assertMiddleware);
|
|
645
|
+
return [...nested];
|
|
537
646
|
}
|
|
538
647
|
assertMiddleware(fns[0]);
|
|
539
648
|
return [fns[0]];
|
|
@@ -564,17 +673,68 @@ function createHandler(options = {}) {
|
|
|
564
673
|
let postJsonHook = null;
|
|
565
674
|
let preErrorHook = null;
|
|
566
675
|
let postErrorHook = null;
|
|
567
|
-
const updateHook = (fn, name, setState
|
|
676
|
+
const updateHook = (fn, name, setState) => {
|
|
568
677
|
if (fn === null) {
|
|
569
678
|
setState(null, null);
|
|
570
|
-
rebuild();
|
|
571
679
|
return;
|
|
572
680
|
}
|
|
573
681
|
assert.ok(isFunction(fn), `${name} hook must be a function`);
|
|
574
682
|
setState(fn, promisify(fn));
|
|
575
|
-
rebuild();
|
|
576
683
|
};
|
|
577
|
-
const
|
|
684
|
+
const describeTerminalFailure = (failure) => {
|
|
685
|
+
try {
|
|
686
|
+
if (failure === void 0) {
|
|
687
|
+
return "undefined";
|
|
688
|
+
}
|
|
689
|
+
if (failure === null) {
|
|
690
|
+
return "null";
|
|
691
|
+
}
|
|
692
|
+
if (typeof failure === "symbol") {
|
|
693
|
+
return failure.toString();
|
|
694
|
+
}
|
|
695
|
+
if (typeof failure === "string") {
|
|
696
|
+
return failure;
|
|
697
|
+
}
|
|
698
|
+
if (typeof failure === "object" && failure !== null) {
|
|
699
|
+
const message = failure.message;
|
|
700
|
+
if (typeof message === "string" && message.length > 0) {
|
|
701
|
+
return message;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return String(failure);
|
|
705
|
+
} catch {
|
|
706
|
+
return "Internal Server Error";
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const toTerminalError = (failure) => {
|
|
710
|
+
if (failure instanceof Error) {
|
|
711
|
+
return failure;
|
|
712
|
+
}
|
|
713
|
+
const error = new Error(describeTerminalFailure(failure));
|
|
714
|
+
try {
|
|
715
|
+
error.cause = failure;
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
return error;
|
|
719
|
+
};
|
|
720
|
+
const claimSlot = (event, slot) => {
|
|
721
|
+
if (event[slot]) {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
event[slot] = true;
|
|
725
|
+
return true;
|
|
726
|
+
};
|
|
727
|
+
const delegateTerminalError = (_res, next, terminalError, event) => {
|
|
728
|
+
if (!claimSlot(event, "canceled")) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
next(toTerminalError(terminalError));
|
|
732
|
+
};
|
|
733
|
+
const reportHookObservation = (_res, next, observation, event) => {
|
|
734
|
+
if (!claimSlot(event, "reported")) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
next(toTerminalError(observation));
|
|
578
738
|
};
|
|
579
739
|
const sendBaseJson = function(res, data, event, onBeforeOutputError) {
|
|
580
740
|
if (shouldSkipResponse(res, event)) {
|
|
@@ -636,26 +796,33 @@ function createHandler(options = {}) {
|
|
|
636
796
|
}
|
|
637
797
|
invokePostHook(hook, value, onFailure);
|
|
638
798
|
};
|
|
639
|
-
const terminalErrorBoundary = (res, next, terminalError, event) => {
|
|
640
|
-
if (event.canceled) {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
next(terminalError);
|
|
644
|
-
};
|
|
645
799
|
const dispatchError = (res, next, err, event) => {
|
|
646
|
-
const
|
|
647
|
-
|
|
800
|
+
const reportTerminal = (failure) => {
|
|
801
|
+
delegateTerminalError(res, next, failure, event);
|
|
802
|
+
};
|
|
803
|
+
const reportObservation = (failure) => {
|
|
804
|
+
reportHookObservation(res, next, failure, event);
|
|
648
805
|
};
|
|
649
806
|
const sendFormatted = (failure) => {
|
|
807
|
+
if (event.canceled) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
650
810
|
let didSend;
|
|
651
811
|
try {
|
|
652
812
|
didSend = sendBaseError(res, failure, event);
|
|
653
813
|
} catch (senderFailure) {
|
|
654
|
-
|
|
814
|
+
reportTerminal(senderFailure === void 0 ? failure : senderFailure);
|
|
655
815
|
return;
|
|
656
816
|
}
|
|
657
|
-
if (didSend
|
|
658
|
-
|
|
817
|
+
if (!didSend) {
|
|
818
|
+
if (res.headersSent) {
|
|
819
|
+
reportTerminal(failure);
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
claimSlot(event, "canceled");
|
|
824
|
+
if (postErrorHook) {
|
|
825
|
+
invokePostHookOnFinish(res, postErrorHook, failure, reportObservation);
|
|
659
826
|
}
|
|
660
827
|
};
|
|
661
828
|
if (event.canceled) {
|
|
@@ -671,39 +838,151 @@ function createHandler(options = {}) {
|
|
|
671
838
|
}
|
|
672
839
|
runSender();
|
|
673
840
|
};
|
|
674
|
-
const dispatchValue = (res, next, data, event
|
|
675
|
-
|
|
676
|
-
|
|
841
|
+
const dispatchValue = (res, next, data, event) => {
|
|
842
|
+
if (data === void 0) {
|
|
843
|
+
if (event.canceled) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (res.headersSent) {
|
|
847
|
+
claimSlot(event, "canceled");
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (event.canceled) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (res.headersSent) {
|
|
855
|
+
delegateTerminalError(res, next, data, event);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const reportTerminal = (failure) => {
|
|
859
|
+
if (!event.canceled) {
|
|
860
|
+
delegateTerminalError(res, next, failure, event);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
reportHookObservation(res, next, failure, event);
|
|
864
|
+
};
|
|
865
|
+
const reportObservation = (failure) => {
|
|
866
|
+
reportHookObservation(res, next, failure, event);
|
|
677
867
|
};
|
|
678
|
-
|
|
868
|
+
let fallbackOccurred = false;
|
|
869
|
+
let autoSendClaimed = false;
|
|
870
|
+
const schedulePostJson = (value) => {
|
|
871
|
+
const activePostJsonHook = postJsonHook;
|
|
872
|
+
if (!activePostJsonHook) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const guardedHook = (observed) => {
|
|
876
|
+
if (fallbackOccurred) {
|
|
877
|
+
return Promise.resolve();
|
|
878
|
+
}
|
|
879
|
+
return activePostJsonHook(observed);
|
|
880
|
+
};
|
|
881
|
+
invokePostHookOnFinish(res, guardedHook, value, reportObservation);
|
|
882
|
+
};
|
|
883
|
+
const scheduleFallbackPostError = (failure) => {
|
|
884
|
+
const activePostErrorHook = postErrorHook;
|
|
885
|
+
if (!activePostErrorHook) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
invokePostHookOnFinish(res, activePostErrorHook, failure, reportObservation);
|
|
889
|
+
};
|
|
890
|
+
const sendFallbackFormatted = (failure) => {
|
|
891
|
+
if (!autoSendClaimed && event.canceled) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (res.headersSent) {
|
|
895
|
+
reportTerminal(failure);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (autoSendClaimed) {
|
|
899
|
+
const bypassEvent = { canceled: false, reported: event.reported };
|
|
900
|
+
try {
|
|
901
|
+
const didFallbackSend = sendBaseError(res, failure, bypassEvent);
|
|
902
|
+
if (didFallbackSend) {
|
|
903
|
+
scheduleFallbackPostError(failure);
|
|
904
|
+
} else {
|
|
905
|
+
reportTerminal(failure);
|
|
906
|
+
}
|
|
907
|
+
} catch (senderFailure) {
|
|
908
|
+
reportTerminal(senderFailure === void 0 ? failure : senderFailure);
|
|
909
|
+
}
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
679
912
|
try {
|
|
680
|
-
sendBaseError(res, failure, event);
|
|
913
|
+
const didFallbackSend = sendBaseError(res, failure, event);
|
|
914
|
+
if (didFallbackSend) {
|
|
915
|
+
claimSlot(event, "canceled");
|
|
916
|
+
scheduleFallbackPostError(failure);
|
|
917
|
+
} else if (res.headersSent) {
|
|
918
|
+
reportTerminal(failure);
|
|
919
|
+
}
|
|
681
920
|
} catch (senderFailure) {
|
|
682
|
-
|
|
921
|
+
reportTerminal(senderFailure === void 0 ? failure : senderFailure);
|
|
683
922
|
}
|
|
684
923
|
};
|
|
924
|
+
const runFallbackLifecycle = (originalFailure) => {
|
|
925
|
+
if (fallbackOccurred) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
fallbackOccurred = true;
|
|
929
|
+
if (!preErrorHook) {
|
|
930
|
+
sendFallbackFormatted(originalFailure);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
preErrorHook(originalFailure).then(
|
|
934
|
+
() => sendFallbackFormatted(originalFailure),
|
|
935
|
+
(hookErr) => sendFallbackFormatted(hookErr === void 0 ? originalFailure : hookErr)
|
|
936
|
+
);
|
|
937
|
+
};
|
|
938
|
+
const sendFallbackError = (failure) => {
|
|
939
|
+
runFallbackLifecycle(failure);
|
|
940
|
+
};
|
|
685
941
|
const runSender = () => {
|
|
942
|
+
if (event.canceled) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (res.headersSent) {
|
|
946
|
+
delegateTerminalError(res, next, data, event);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
686
949
|
let didSend;
|
|
687
950
|
try {
|
|
688
|
-
didSend = sendBaseJson(res, data, event,
|
|
951
|
+
didSend = sendBaseJson(res, data, event, sendFallbackError);
|
|
689
952
|
} catch (senderFailure) {
|
|
690
953
|
const failure = senderFailure === void 0 ? new Error("response serialization failed") : senderFailure;
|
|
691
954
|
if (res.headersSent) {
|
|
692
|
-
|
|
955
|
+
reportTerminal(failure);
|
|
693
956
|
return;
|
|
694
957
|
}
|
|
695
|
-
|
|
958
|
+
runFallbackLifecycle(failure);
|
|
696
959
|
return;
|
|
697
960
|
}
|
|
698
|
-
if (didSend
|
|
699
|
-
|
|
961
|
+
if (!didSend) {
|
|
962
|
+
if (res.headersSent) {
|
|
963
|
+
delegateTerminalError(res, next, data, event);
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
700
966
|
}
|
|
701
|
-
|
|
967
|
+
claimSlot(event, "canceled");
|
|
968
|
+
autoSendClaimed = true;
|
|
969
|
+
schedulePostJson(data);
|
|
702
970
|
};
|
|
703
971
|
if (preJsonHook) {
|
|
704
972
|
preJsonHook(data).then(
|
|
705
|
-
|
|
706
|
-
|
|
973
|
+
() => {
|
|
974
|
+
runSender();
|
|
975
|
+
},
|
|
976
|
+
(hookErr) => {
|
|
977
|
+
if (event.canceled) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (res.headersSent) {
|
|
981
|
+
runFallbackLifecycle(hookErr === void 0 ? new Error("pre-json hook failed") : hookErr);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
runFallbackLifecycle(hookErr === void 0 ? new Error("pre-json hook failed") : hookErr);
|
|
985
|
+
}
|
|
707
986
|
);
|
|
708
987
|
return;
|
|
709
988
|
}
|
|
@@ -711,15 +990,13 @@ function createHandler(options = {}) {
|
|
|
711
990
|
};
|
|
712
991
|
const nextFn = function(event, next) {
|
|
713
992
|
return function(error) {
|
|
714
|
-
if (event
|
|
993
|
+
if (!claimSlot(event, "canceled")) {
|
|
715
994
|
return;
|
|
716
995
|
}
|
|
717
996
|
if (error === void 0) {
|
|
718
|
-
event.canceled = true;
|
|
719
997
|
next();
|
|
720
998
|
return;
|
|
721
999
|
}
|
|
722
|
-
event.canceled = true;
|
|
723
1000
|
next(error);
|
|
724
1001
|
};
|
|
725
1002
|
};
|
|
@@ -728,18 +1005,17 @@ function createHandler(options = {}) {
|
|
|
728
1005
|
if (event.canceled) {
|
|
729
1006
|
return;
|
|
730
1007
|
}
|
|
731
|
-
|
|
732
|
-
dispatchError(res, next, event.nextError, event);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
dispatchValue(res, next, resolved, event, () => void 0);
|
|
1008
|
+
dispatchValue(res, next, resolved, event);
|
|
736
1009
|
};
|
|
737
1010
|
if (isPromise(result)) {
|
|
738
1011
|
Promise.resolve(result).then(
|
|
739
1012
|
(resolved) => finalize(resolved),
|
|
740
1013
|
(err) => {
|
|
1014
|
+
if (event.canceled) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
741
1017
|
if (res.headersSent) {
|
|
742
|
-
next
|
|
1018
|
+
delegateTerminalError(res, next, err, event);
|
|
743
1019
|
return;
|
|
744
1020
|
}
|
|
745
1021
|
dispatchError(res, next, err, event);
|
|
@@ -751,13 +1027,16 @@ function createHandler(options = {}) {
|
|
|
751
1027
|
};
|
|
752
1028
|
const routerFn = function(fn) {
|
|
753
1029
|
return function(req, res, next) {
|
|
754
|
-
const event = { canceled: false,
|
|
1030
|
+
const event = { canceled: false, reported: false };
|
|
755
1031
|
try {
|
|
756
1032
|
const result = fn(req, res, nextFn(event, next));
|
|
757
1033
|
runLifecycle(res, next, result, event);
|
|
758
1034
|
} catch (err) {
|
|
1035
|
+
if (event.canceled) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
759
1038
|
if (res.headersSent) {
|
|
760
|
-
next
|
|
1039
|
+
delegateTerminalError(res, next, err, event);
|
|
761
1040
|
return;
|
|
762
1041
|
}
|
|
763
1042
|
dispatchError(res, next, err, event);
|
|
@@ -783,57 +1062,37 @@ function createHandler(options = {}) {
|
|
|
783
1062
|
return preJson;
|
|
784
1063
|
},
|
|
785
1064
|
set preJson(fn) {
|
|
786
|
-
updateHook(
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
preJson = syncHook;
|
|
791
|
-
preJsonHook = asyncHook;
|
|
792
|
-
},
|
|
793
|
-
noopRebuild
|
|
794
|
-
);
|
|
1065
|
+
updateHook(fn, "pre-json", (syncHook, asyncHook) => {
|
|
1066
|
+
preJson = syncHook;
|
|
1067
|
+
preJsonHook = asyncHook;
|
|
1068
|
+
});
|
|
795
1069
|
},
|
|
796
1070
|
get postJson() {
|
|
797
1071
|
return postJson;
|
|
798
1072
|
},
|
|
799
1073
|
set postJson(fn) {
|
|
800
|
-
updateHook(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
postJson = syncHook;
|
|
805
|
-
postJsonHook = asyncHook;
|
|
806
|
-
},
|
|
807
|
-
noopRebuild
|
|
808
|
-
);
|
|
1074
|
+
updateHook(fn, "post-json", (syncHook, asyncHook) => {
|
|
1075
|
+
postJson = syncHook;
|
|
1076
|
+
postJsonHook = asyncHook;
|
|
1077
|
+
});
|
|
809
1078
|
},
|
|
810
1079
|
get preError() {
|
|
811
1080
|
return preError;
|
|
812
1081
|
},
|
|
813
1082
|
set preError(fn) {
|
|
814
|
-
updateHook(
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
preError = syncHook;
|
|
819
|
-
preErrorHook = asyncHook;
|
|
820
|
-
},
|
|
821
|
-
noopRebuild
|
|
822
|
-
);
|
|
1083
|
+
updateHook(fn, "pre-error", (syncHook, asyncHook) => {
|
|
1084
|
+
preError = syncHook;
|
|
1085
|
+
preErrorHook = asyncHook;
|
|
1086
|
+
});
|
|
823
1087
|
},
|
|
824
1088
|
get postError() {
|
|
825
1089
|
return postError;
|
|
826
1090
|
},
|
|
827
1091
|
set postError(fn) {
|
|
828
|
-
updateHook(
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
postError = syncHook;
|
|
833
|
-
postErrorHook = asyncHook;
|
|
834
|
-
},
|
|
835
|
-
noopRebuild
|
|
836
|
-
);
|
|
1092
|
+
updateHook(fn, "post-error", (syncHook, asyncHook) => {
|
|
1093
|
+
postError = syncHook;
|
|
1094
|
+
postErrorHook = asyncHook;
|
|
1095
|
+
});
|
|
837
1096
|
}
|
|
838
1097
|
};
|
|
839
1098
|
return handler;
|