@jaypie/express 1.2.4-rc1 → 1.2.4-rc11

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/dist/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Readable, Writable } from 'node:stream';
2
+ import { ServerResponse } from 'node:http';
2
3
  import { CorsError, BadRequestError, UnhandledError, GatewayTimeoutError, UnavailableError, BadGatewayError, InternalError, TeapotError, GoneError, MethodNotAllowedError, NotFoundError, ForbiddenError, UnauthorizedError, NotImplementedError } from '@jaypie/errors';
3
4
  import { force, envBoolean, JAYPIE, HTTP, getHeaderFrom, jaypieHandler } from '@jaypie/kit';
4
5
  import expressCors from 'cors';
@@ -159,6 +160,10 @@ function createLambdaRequest(event, context) {
159
160
  //
160
161
  // Constants
161
162
  //
163
+ // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
164
+ // This is needed for compatibility with Datadog dd-trace instrumentation,
165
+ // which patches HTTP methods and expects this internal state to exist.
166
+ const kOutHeaders$1 = Object.getOwnPropertySymbols(ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
162
167
  const BINARY_CONTENT_TYPE_PATTERNS = [
163
168
  /^application\/octet-stream$/,
164
169
  /^application\/pdf$/,
@@ -185,10 +190,53 @@ class LambdaResponseBuffered extends Writable {
185
190
  this.socket = {
186
191
  remoteAddress: "127.0.0.1",
187
192
  };
193
+ // Internal state exposed for direct manipulation by safe response methods
194
+ // that need to bypass dd-trace interception of stream methods
188
195
  this._chunks = [];
189
196
  this._headers = new Map();
190
197
  this._headersSent = false;
191
198
  this._resolve = null;
199
+ // Initialize Node's internal kOutHeaders for dd-trace compatibility.
200
+ // dd-trace patches HTTP methods and expects this internal state.
201
+ if (kOutHeaders$1) {
202
+ this[kOutHeaders$1] = Object.create(null);
203
+ }
204
+ }
205
+ //
206
+ // Internal bypass methods - completely avoid prototype chain lookup
207
+ // These directly access _headers Map, safe from dd-trace interception
208
+ //
209
+ _internalGetHeader(name) {
210
+ const value = this._headers.get(name.toLowerCase());
211
+ return value ? String(value) : undefined;
212
+ }
213
+ _internalSetHeader(name, value) {
214
+ if (!this._headersSent) {
215
+ const lowerName = name.toLowerCase();
216
+ this._headers.set(lowerName, value);
217
+ // Also sync kOutHeaders for any code that expects it
218
+ if (kOutHeaders$1) {
219
+ const outHeaders = this[kOutHeaders$1];
220
+ if (outHeaders) {
221
+ outHeaders[lowerName] = [name, value];
222
+ }
223
+ }
224
+ }
225
+ }
226
+ _internalHasHeader(name) {
227
+ return this._headers.has(name.toLowerCase());
228
+ }
229
+ _internalRemoveHeader(name) {
230
+ if (!this._headersSent) {
231
+ const lowerName = name.toLowerCase();
232
+ this._headers.delete(lowerName);
233
+ if (kOutHeaders$1) {
234
+ const outHeaders = this[kOutHeaders$1];
235
+ if (outHeaders) {
236
+ delete outHeaders[lowerName];
237
+ }
238
+ }
239
+ }
192
240
  }
193
241
  //
194
242
  // Promise-based API for getting final result
@@ -211,14 +259,31 @@ class LambdaResponseBuffered extends Writable {
211
259
  // In production, log warning but don't throw to match Express behavior
212
260
  return this;
213
261
  }
214
- this._headers.set(name.toLowerCase(), String(value));
262
+ const lowerName = name.toLowerCase();
263
+ this._headers.set(lowerName, String(value));
264
+ // Sync with kOutHeaders for dd-trace compatibility
265
+ // Node stores as { 'header-name': ['Header-Name', value] }
266
+ if (kOutHeaders$1) {
267
+ const outHeaders = this[kOutHeaders$1];
268
+ if (outHeaders) {
269
+ outHeaders[lowerName] = [name, String(value)];
270
+ }
271
+ }
215
272
  return this;
216
273
  }
217
274
  getHeader(name) {
218
275
  return this._headers.get(name.toLowerCase());
219
276
  }
220
277
  removeHeader(name) {
221
- this._headers.delete(name.toLowerCase());
278
+ const lowerName = name.toLowerCase();
279
+ this._headers.delete(lowerName);
280
+ // Sync with kOutHeaders for dd-trace compatibility
281
+ if (kOutHeaders$1) {
282
+ const outHeaders = this[kOutHeaders$1];
283
+ if (outHeaders) {
284
+ delete outHeaders[lowerName];
285
+ }
286
+ }
222
287
  }
223
288
  getHeaders() {
224
289
  const headers = {};
@@ -233,6 +298,43 @@ class LambdaResponseBuffered extends Writable {
233
298
  getHeaderNames() {
234
299
  return Array.from(this._headers.keys());
235
300
  }
301
+ /**
302
+ * Proxy for direct header access (e.g., res.headers['content-type']).
303
+ * Required for compatibility with middleware like helmet that access headers directly.
304
+ */
305
+ get headers() {
306
+ return new Proxy({}, {
307
+ deleteProperty: (_target, prop) => {
308
+ this.removeHeader(String(prop));
309
+ return true;
310
+ },
311
+ get: (_target, prop) => {
312
+ if (typeof prop === "symbol")
313
+ return undefined;
314
+ return this.getHeader(String(prop));
315
+ },
316
+ getOwnPropertyDescriptor: (_target, prop) => {
317
+ if (this.hasHeader(String(prop))) {
318
+ return {
319
+ configurable: true,
320
+ enumerable: true,
321
+ value: this.getHeader(String(prop)),
322
+ };
323
+ }
324
+ return undefined;
325
+ },
326
+ has: (_target, prop) => {
327
+ return this.hasHeader(String(prop));
328
+ },
329
+ ownKeys: () => {
330
+ return this.getHeaderNames();
331
+ },
332
+ set: (_target, prop, value) => {
333
+ this.setHeader(String(prop), value);
334
+ return true;
335
+ },
336
+ });
337
+ }
236
338
  writeHead(statusCode, statusMessageOrHeaders, headers) {
237
339
  this.statusCode = statusCode;
238
340
  let headersToSet;
@@ -260,6 +362,25 @@ class LambdaResponseBuffered extends Writable {
260
362
  //
261
363
  // Express compatibility methods
262
364
  //
365
+ /**
366
+ * Express-style alias for getHeader().
367
+ * Used by middleware like decorateResponse that use res.get().
368
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
369
+ */
370
+ get(name) {
371
+ return this._headers.get(name.toLowerCase());
372
+ }
373
+ /**
374
+ * Express-style alias for setHeader().
375
+ * Used by middleware like decorateResponse that use res.set().
376
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
377
+ */
378
+ set(name, value) {
379
+ if (!this._headersSent) {
380
+ this._headers.set(name.toLowerCase(), String(value));
381
+ }
382
+ return this;
383
+ }
263
384
  status(code) {
264
385
  this.statusCode = code;
265
386
  return this;
@@ -276,6 +397,26 @@ class LambdaResponseBuffered extends Writable {
276
397
  this.end(body);
277
398
  return this;
278
399
  }
400
+ /**
401
+ * Add a field to the Vary response header.
402
+ * Used by CORS middleware to indicate response varies by Origin.
403
+ */
404
+ vary(field) {
405
+ const existing = this.getHeader("vary");
406
+ if (!existing) {
407
+ this.setHeader("vary", field);
408
+ }
409
+ else {
410
+ // Append to existing Vary header if field not already present
411
+ const fields = String(existing)
412
+ .split(",")
413
+ .map((f) => f.trim().toLowerCase());
414
+ if (!fields.includes(field.toLowerCase())) {
415
+ this.setHeader("vary", `${existing}, ${field}`);
416
+ }
417
+ }
418
+ return this;
419
+ }
279
420
  //
280
421
  // Writable stream implementation
281
422
  //
@@ -299,7 +440,8 @@ class LambdaResponseBuffered extends Writable {
299
440
  //
300
441
  buildResult() {
301
442
  const body = Buffer.concat(this._chunks);
302
- const contentType = this.getHeader("content-type") || "";
443
+ // Use direct _headers access to bypass dd-trace interception
444
+ const contentType = this._headers.get("content-type") || "";
303
445
  // Determine if response should be base64 encoded
304
446
  const isBase64Encoded = this.isBinaryContentType(contentType);
305
447
  // Build headers object
@@ -336,6 +478,14 @@ class LambdaResponseBuffered extends Writable {
336
478
  }
337
479
  }
338
480
 
481
+ //
482
+ //
483
+ // Constants
484
+ //
485
+ // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
486
+ // This is needed for compatibility with Datadog dd-trace instrumentation,
487
+ // which patches HTTP methods and expects this internal state to exist.
488
+ const kOutHeaders = Object.getOwnPropertySymbols(ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
339
489
  //
340
490
  //
341
491
  // LambdaResponseStreaming Class
@@ -353,11 +503,54 @@ class LambdaResponseStreaming extends Writable {
353
503
  this.socket = {
354
504
  remoteAddress: "127.0.0.1",
355
505
  };
506
+ // Internal state exposed for direct manipulation by safe response methods
507
+ // that need to bypass dd-trace interception
356
508
  this._headers = new Map();
357
509
  this._headersSent = false;
358
510
  this._pendingWrites = [];
359
511
  this._wrappedStream = null;
360
512
  this._responseStream = responseStream;
513
+ // Initialize Node's internal kOutHeaders for dd-trace compatibility.
514
+ // dd-trace patches HTTP methods and expects this internal state.
515
+ if (kOutHeaders) {
516
+ this[kOutHeaders] = Object.create(null);
517
+ }
518
+ }
519
+ //
520
+ // Internal bypass methods - completely avoid prototype chain lookup
521
+ // These directly access _headers Map, safe from dd-trace interception
522
+ //
523
+ _internalGetHeader(name) {
524
+ const value = this._headers.get(name.toLowerCase());
525
+ return value ? String(value) : undefined;
526
+ }
527
+ _internalSetHeader(name, value) {
528
+ if (!this._headersSent) {
529
+ const lowerName = name.toLowerCase();
530
+ this._headers.set(lowerName, value);
531
+ // Also sync kOutHeaders for any code that expects it
532
+ if (kOutHeaders) {
533
+ const outHeaders = this[kOutHeaders];
534
+ if (outHeaders) {
535
+ outHeaders[lowerName] = [name, value];
536
+ }
537
+ }
538
+ }
539
+ }
540
+ _internalHasHeader(name) {
541
+ return this._headers.has(name.toLowerCase());
542
+ }
543
+ _internalRemoveHeader(name) {
544
+ if (!this._headersSent) {
545
+ const lowerName = name.toLowerCase();
546
+ this._headers.delete(lowerName);
547
+ if (kOutHeaders) {
548
+ const outHeaders = this[kOutHeaders];
549
+ if (outHeaders) {
550
+ delete outHeaders[lowerName];
551
+ }
552
+ }
553
+ }
361
554
  }
362
555
  //
363
556
  // Header management
@@ -368,7 +561,16 @@ class LambdaResponseStreaming extends Writable {
368
561
  // Headers cannot be changed after body starts
369
562
  return this;
370
563
  }
371
- this._headers.set(name.toLowerCase(), String(value));
564
+ const lowerName = name.toLowerCase();
565
+ this._headers.set(lowerName, String(value));
566
+ // Sync with kOutHeaders for dd-trace compatibility
567
+ // Node stores as { 'header-name': ['Header-Name', value] }
568
+ if (kOutHeaders) {
569
+ const outHeaders = this[kOutHeaders];
570
+ if (outHeaders) {
571
+ outHeaders[lowerName] = [name, String(value)];
572
+ }
573
+ }
372
574
  return this;
373
575
  }
374
576
  getHeader(name) {
@@ -376,7 +578,15 @@ class LambdaResponseStreaming extends Writable {
376
578
  }
377
579
  removeHeader(name) {
378
580
  if (!this._headersSent) {
379
- this._headers.delete(name.toLowerCase());
581
+ const lowerName = name.toLowerCase();
582
+ this._headers.delete(lowerName);
583
+ // Sync with kOutHeaders for dd-trace compatibility
584
+ if (kOutHeaders) {
585
+ const outHeaders = this[kOutHeaders];
586
+ if (outHeaders) {
587
+ delete outHeaders[lowerName];
588
+ }
589
+ }
380
590
  }
381
591
  }
382
592
  getHeaders() {
@@ -392,6 +602,43 @@ class LambdaResponseStreaming extends Writable {
392
602
  getHeaderNames() {
393
603
  return Array.from(this._headers.keys());
394
604
  }
605
+ /**
606
+ * Proxy for direct header access (e.g., res.headers['content-type']).
607
+ * Required for compatibility with middleware like helmet that access headers directly.
608
+ */
609
+ get headers() {
610
+ return new Proxy({}, {
611
+ deleteProperty: (_target, prop) => {
612
+ this.removeHeader(String(prop));
613
+ return true;
614
+ },
615
+ get: (_target, prop) => {
616
+ if (typeof prop === "symbol")
617
+ return undefined;
618
+ return this.getHeader(String(prop));
619
+ },
620
+ getOwnPropertyDescriptor: (_target, prop) => {
621
+ if (this.hasHeader(String(prop))) {
622
+ return {
623
+ configurable: true,
624
+ enumerable: true,
625
+ value: this.getHeader(String(prop)),
626
+ };
627
+ }
628
+ return undefined;
629
+ },
630
+ has: (_target, prop) => {
631
+ return this.hasHeader(String(prop));
632
+ },
633
+ ownKeys: () => {
634
+ return this.getHeaderNames();
635
+ },
636
+ set: (_target, prop, value) => {
637
+ this.setHeader(String(prop), value);
638
+ return true;
639
+ },
640
+ });
641
+ }
395
642
  writeHead(statusCode, statusMessageOrHeaders, headers) {
396
643
  if (this._headersSent) {
397
644
  return this;
@@ -443,6 +690,25 @@ class LambdaResponseStreaming extends Writable {
443
690
  //
444
691
  // Express compatibility methods
445
692
  //
693
+ /**
694
+ * Express-style alias for getHeader().
695
+ * Used by middleware like decorateResponse that use res.get().
696
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
697
+ */
698
+ get(name) {
699
+ return this._headers.get(name.toLowerCase());
700
+ }
701
+ /**
702
+ * Express-style alias for setHeader().
703
+ * Used by middleware like decorateResponse that use res.set().
704
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
705
+ */
706
+ set(name, value) {
707
+ if (!this._headersSent) {
708
+ this._headers.set(name.toLowerCase(), String(value));
709
+ }
710
+ return this;
711
+ }
446
712
  status(code) {
447
713
  this.statusCode = code;
448
714
  return this;
@@ -459,6 +725,26 @@ class LambdaResponseStreaming extends Writable {
459
725
  this.end(body);
460
726
  return this;
461
727
  }
728
+ /**
729
+ * Add a field to the Vary response header.
730
+ * Used by CORS middleware to indicate response varies by Origin.
731
+ */
732
+ vary(field) {
733
+ const existing = this.getHeader("vary");
734
+ if (!existing) {
735
+ this.setHeader("vary", field);
736
+ }
737
+ else {
738
+ // Append to existing Vary header if field not already present
739
+ const fields = String(existing)
740
+ .split(",")
741
+ .map((f) => f.trim().toLowerCase());
742
+ if (!fields.includes(field.toLowerCase())) {
743
+ this.setHeader("vary", `${existing}, ${field}`);
744
+ }
745
+ }
746
+ return this;
747
+ }
462
748
  //
463
749
  // Writable stream implementation
464
750
  //
@@ -915,6 +1201,73 @@ function getCurrentInvokeUuid(req) {
915
1201
  return getWebAdapterUuid();
916
1202
  }
917
1203
 
1204
+ //
1205
+ //
1206
+ // Helpers
1207
+ //
1208
+ /**
1209
+ * Safely get a header value from response.
1210
+ * Handles both Express Response and Lambda adapter responses.
1211
+ * Defensive against dd-trace instrumentation issues.
1212
+ */
1213
+ function safeGetHeader(res, name) {
1214
+ try {
1215
+ // Try internal method first (completely bypasses dd-trace)
1216
+ if (typeof res._internalGetHeader === "function") {
1217
+ return res._internalGetHeader(name);
1218
+ }
1219
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1220
+ if (res._headers instanceof Map) {
1221
+ const value = res._headers.get(name.toLowerCase());
1222
+ return value ? String(value) : undefined;
1223
+ }
1224
+ // Fall back to getHeader (more standard than get)
1225
+ if (typeof res.getHeader === "function") {
1226
+ const value = res.getHeader(name);
1227
+ return value ? String(value) : undefined;
1228
+ }
1229
+ // Last resort: try get
1230
+ if (typeof res.get === "function") {
1231
+ const value = res.get(name);
1232
+ return value ? String(value) : undefined;
1233
+ }
1234
+ }
1235
+ catch {
1236
+ // Silently fail - caller will handle missing value
1237
+ }
1238
+ return undefined;
1239
+ }
1240
+ /**
1241
+ * Safely set a header value on response.
1242
+ * Handles both Express Response and Lambda adapter responses.
1243
+ * Defensive against dd-trace instrumentation issues.
1244
+ */
1245
+ function safeSetHeader(res, name, value) {
1246
+ try {
1247
+ // Try internal method first (completely bypasses dd-trace)
1248
+ if (typeof res._internalSetHeader === "function") {
1249
+ res._internalSetHeader(name, value);
1250
+ return;
1251
+ }
1252
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1253
+ if (res._headers instanceof Map) {
1254
+ res._headers.set(name.toLowerCase(), value);
1255
+ return;
1256
+ }
1257
+ // Fall back to setHeader (more standard than set)
1258
+ if (typeof res.setHeader === "function") {
1259
+ res.setHeader(name, value);
1260
+ return;
1261
+ }
1262
+ // Last resort: try set
1263
+ if (typeof res.set === "function") {
1264
+ res.set(name, value);
1265
+ }
1266
+ }
1267
+ catch {
1268
+ // Silently fail - header just won't be set
1269
+ }
1270
+ }
918
1271
  //
919
1272
  //
920
1273
  // Main
@@ -931,36 +1284,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
931
1284
  log$1.warn("decorateResponse called but response is not an object");
932
1285
  return;
933
1286
  }
1287
+ const extRes = res;
934
1288
  try {
935
1289
  //
936
1290
  //
937
1291
  // Decorate Headers
938
1292
  //
939
1293
  // X-Powered-By, override "Express" but nothing else
940
- if (!res.get(HTTP.HEADER.POWERED_BY) ||
941
- res.get(HTTP.HEADER.POWERED_BY) === "Express") {
942
- res.set(HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
1294
+ const currentPoweredBy = safeGetHeader(extRes, HTTP.HEADER.POWERED_BY);
1295
+ if (!currentPoweredBy || currentPoweredBy === "Express") {
1296
+ safeSetHeader(extRes, HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
943
1297
  }
944
1298
  // X-Project-Environment
945
1299
  if (process.env.PROJECT_ENV) {
946
- res.set(HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
1300
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
947
1301
  }
948
1302
  // X-Project-Handler
949
1303
  if (handler) {
950
- res.set(HTTP.HEADER.PROJECT.HANDLER, handler);
1304
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.HANDLER, handler);
951
1305
  }
952
1306
  // X-Project-Invocation
953
1307
  const currentInvoke = getCurrentInvokeUuid();
954
1308
  if (currentInvoke) {
955
- res.set(HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
1309
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
956
1310
  }
957
1311
  // X-Project-Key
958
1312
  if (process.env.PROJECT_KEY) {
959
- res.set(HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
1313
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
960
1314
  }
961
1315
  // X-Project-Version
962
1316
  if (version) {
963
- res.set(HTTP.HEADER.PROJECT.VERSION, version);
1317
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.VERSION, version);
964
1318
  }
965
1319
  //
966
1320
  //
@@ -1020,6 +1374,76 @@ function summarizeResponse(res, extras) {
1020
1374
 
1021
1375
  // Cast logger to extended interface for runtime features not in type definitions
1022
1376
  const logger$1 = log;
1377
+ //
1378
+ //
1379
+ // Helpers - Safe response methods to bypass dd-trace interception
1380
+ //
1381
+ /**
1382
+ * Check if response is a Lambda mock response with direct internal access.
1383
+ */
1384
+ function isLambdaMockResponse(res) {
1385
+ const mock = res;
1386
+ return (mock._headers instanceof Map &&
1387
+ Array.isArray(mock._chunks) &&
1388
+ typeof mock.buildResult === "function");
1389
+ }
1390
+ /**
1391
+ * Safely send a JSON response, avoiding dd-trace interception.
1392
+ * For Lambda mock responses, directly manipulates internal state instead of
1393
+ * using stream methods (write/end) which dd-trace intercepts.
1394
+ */
1395
+ function safeSendJson(res, statusCode, data) {
1396
+ if (isLambdaMockResponse(res)) {
1397
+ // Use internal method to set header (completely bypasses dd-trace)
1398
+ if (typeof res._internalSetHeader === "function") {
1399
+ res._internalSetHeader("content-type", "application/json");
1400
+ }
1401
+ else {
1402
+ // Fall back to direct _headers manipulation
1403
+ res._headers.set("content-type", "application/json");
1404
+ }
1405
+ res.statusCode = statusCode;
1406
+ // Directly push to chunks array instead of using stream write/end
1407
+ const chunk = Buffer.from(JSON.stringify(data));
1408
+ res._chunks.push(chunk);
1409
+ res._headersSent = true;
1410
+ // Signal completion if a promise is waiting
1411
+ if (res._resolve) {
1412
+ res._resolve(res.buildResult());
1413
+ }
1414
+ return;
1415
+ }
1416
+ // Fall back to standard Express methods for real responses
1417
+ res.status(statusCode).json(data);
1418
+ }
1419
+ /**
1420
+ * Safely send a response body, avoiding dd-trace interception.
1421
+ * For Lambda mock responses, directly manipulates internal state instead of
1422
+ * using stream methods (write/end) which dd-trace intercepts.
1423
+ */
1424
+ function safeSend(res, statusCode, body) {
1425
+ if (isLambdaMockResponse(res)) {
1426
+ // Direct internal state manipulation - bypasses dd-trace completely
1427
+ res.statusCode = statusCode;
1428
+ if (body !== undefined) {
1429
+ const chunk = Buffer.from(body);
1430
+ res._chunks.push(chunk);
1431
+ }
1432
+ res._headersSent = true;
1433
+ // Signal completion if a promise is waiting
1434
+ if (res._resolve) {
1435
+ res._resolve(res.buildResult());
1436
+ }
1437
+ return;
1438
+ }
1439
+ // Fall back to standard Express methods for real responses
1440
+ if (body !== undefined) {
1441
+ res.status(statusCode).send(body);
1442
+ }
1443
+ else {
1444
+ res.status(statusCode).send();
1445
+ }
1446
+ }
1023
1447
  function expressHandler(handlerOrOptions, optionsOrHandler) {
1024
1448
  /* eslint-enable no-redeclare */
1025
1449
  let handler;
@@ -1236,30 +1660,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1236
1660
  if (typeof response === "object") {
1237
1661
  if (typeof response.json ===
1238
1662
  "function") {
1239
- res.json(response.json());
1663
+ safeSendJson(res, status, response.json());
1240
1664
  }
1241
1665
  else {
1242
- res.status(status).json(response);
1666
+ safeSendJson(res, status, response);
1243
1667
  }
1244
1668
  }
1245
1669
  else if (typeof response === "string") {
1246
1670
  try {
1247
- res.status(status).json(JSON.parse(response));
1671
+ safeSendJson(res, status, JSON.parse(response));
1248
1672
  }
1249
1673
  catch {
1250
- res.status(status).send(response);
1674
+ safeSend(res, status, response);
1251
1675
  }
1252
1676
  }
1253
1677
  else if (response === true) {
1254
- res.status(HTTP.CODE.CREATED).send();
1678
+ safeSend(res, HTTP.CODE.CREATED);
1255
1679
  }
1256
1680
  else {
1257
- res.status(status).send(response);
1681
+ safeSend(res, status, response);
1258
1682
  }
1259
1683
  }
1260
1684
  else {
1261
1685
  // No response
1262
- res.status(HTTP.CODE.NO_CONTENT).send();
1686
+ safeSend(res, HTTP.CODE.NO_CONTENT);
1263
1687
  }
1264
1688
  }
1265
1689
  else {