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