@jaypie/express 1.2.4-rc0 → 1.2.4-rc10
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/cjs/adapter/LambdaRequest.d.ts +5 -5
- package/dist/cjs/adapter/LambdaResponseBuffered.d.ts +31 -9
- package/dist/cjs/adapter/LambdaResponseStreaming.d.ts +28 -6
- package/dist/cjs/adapter/index.d.ts +3 -3
- package/dist/cjs/adapter/types.d.ts +34 -2
- package/dist/cjs/index.cjs +497 -40
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/esm/adapter/LambdaRequest.d.ts +5 -5
- package/dist/esm/adapter/LambdaResponseBuffered.d.ts +31 -9
- package/dist/esm/adapter/LambdaResponseStreaming.d.ts +28 -6
- package/dist/esm/adapter/index.d.ts +3 -3
- package/dist/esm/adapter/types.d.ts +34 -2
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +497 -40
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -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');
|
|
@@ -81,16 +82,63 @@ class LambdaRequest extends node_stream.Readable {
|
|
|
81
82
|
}
|
|
82
83
|
//
|
|
83
84
|
//
|
|
85
|
+
// Type Guards
|
|
86
|
+
//
|
|
87
|
+
/**
|
|
88
|
+
* Check if event is a Function URL / HTTP API v2 event.
|
|
89
|
+
*/
|
|
90
|
+
function isFunctionUrlEvent(event) {
|
|
91
|
+
return "requestContext" in event && "http" in event.requestContext;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if event is an API Gateway REST API v1 event.
|
|
95
|
+
*/
|
|
96
|
+
function isApiGatewayV1Event(event) {
|
|
97
|
+
return "httpMethod" in event;
|
|
98
|
+
}
|
|
99
|
+
//
|
|
100
|
+
//
|
|
84
101
|
// Factory Function
|
|
85
102
|
//
|
|
86
103
|
/**
|
|
87
|
-
* Create a LambdaRequest from a Function URL
|
|
104
|
+
* Create a LambdaRequest from a Lambda event (Function URL, HTTP API v2, or REST API v1).
|
|
88
105
|
*/
|
|
89
106
|
function createLambdaRequest(event, context) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
let url;
|
|
108
|
+
let method;
|
|
109
|
+
let protocol;
|
|
110
|
+
let remoteAddress;
|
|
111
|
+
const headers = { ...event.headers };
|
|
112
|
+
if (isFunctionUrlEvent(event)) {
|
|
113
|
+
// Function URL / HTTP API v2 format
|
|
114
|
+
url = event.rawQueryString
|
|
115
|
+
? `${event.rawPath}?${event.rawQueryString}`
|
|
116
|
+
: event.rawPath;
|
|
117
|
+
method = event.requestContext.http.method;
|
|
118
|
+
protocol = event.requestContext.http.protocol.split("/")[0].toLowerCase();
|
|
119
|
+
remoteAddress = event.requestContext.http.sourceIp;
|
|
120
|
+
// Normalize cookies into Cookie header if not already present
|
|
121
|
+
if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
|
|
122
|
+
headers.cookie = event.cookies.join("; ");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (isApiGatewayV1Event(event)) {
|
|
126
|
+
// API Gateway REST API v1 format
|
|
127
|
+
const queryParams = event.queryStringParameters;
|
|
128
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
129
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
130
|
+
url = `${event.path}?${queryString}`;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
url = event.path;
|
|
134
|
+
}
|
|
135
|
+
method = event.httpMethod;
|
|
136
|
+
protocol = event.requestContext.protocol.split("/")[0].toLowerCase();
|
|
137
|
+
remoteAddress = event.requestContext.identity.sourceIp;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw new Error("Unsupported Lambda event format. Expected Function URL, HTTP API v2, or REST API v1 event.");
|
|
141
|
+
}
|
|
94
142
|
// Decode body if present
|
|
95
143
|
let body = null;
|
|
96
144
|
if (event.body) {
|
|
@@ -98,19 +146,14 @@ function createLambdaRequest(event, context) {
|
|
|
98
146
|
? Buffer.from(event.body, "base64")
|
|
99
147
|
: Buffer.from(event.body, "utf8");
|
|
100
148
|
}
|
|
101
|
-
// Normalize cookies into Cookie header if not already present
|
|
102
|
-
const headers = { ...event.headers };
|
|
103
|
-
if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
|
|
104
|
-
headers.cookie = event.cookies.join("; ");
|
|
105
|
-
}
|
|
106
149
|
return new LambdaRequest({
|
|
107
150
|
body,
|
|
108
151
|
headers,
|
|
109
152
|
lambdaContext: context,
|
|
110
153
|
lambdaEvent: event,
|
|
111
|
-
method
|
|
112
|
-
protocol
|
|
113
|
-
remoteAddress
|
|
154
|
+
method,
|
|
155
|
+
protocol,
|
|
156
|
+
remoteAddress,
|
|
114
157
|
url,
|
|
115
158
|
});
|
|
116
159
|
}
|
|
@@ -119,6 +162,10 @@ function createLambdaRequest(event, context) {
|
|
|
119
162
|
//
|
|
120
163
|
// Constants
|
|
121
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)");
|
|
122
169
|
const BINARY_CONTENT_TYPE_PATTERNS = [
|
|
123
170
|
/^application\/octet-stream$/,
|
|
124
171
|
/^application\/pdf$/,
|
|
@@ -143,16 +190,55 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
143
190
|
this.statusMessage = "OK";
|
|
144
191
|
// Mock socket to satisfy Express/finalhandler checks
|
|
145
192
|
this.socket = {
|
|
146
|
-
cork: () => { },
|
|
147
|
-
destroy: () => { },
|
|
148
193
|
remoteAddress: "127.0.0.1",
|
|
149
|
-
uncork: () => { },
|
|
150
|
-
writable: true,
|
|
151
194
|
};
|
|
195
|
+
// Internal state exposed for direct manipulation by safe response methods
|
|
196
|
+
// that need to bypass dd-trace interception of stream methods
|
|
152
197
|
this._chunks = [];
|
|
153
198
|
this._headers = new Map();
|
|
154
199
|
this._headersSent = false;
|
|
155
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
|
+
}
|
|
156
242
|
}
|
|
157
243
|
//
|
|
158
244
|
// Promise-based API for getting final result
|
|
@@ -175,14 +261,31 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
175
261
|
// In production, log warning but don't throw to match Express behavior
|
|
176
262
|
return this;
|
|
177
263
|
}
|
|
178
|
-
|
|
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
|
+
}
|
|
179
274
|
return this;
|
|
180
275
|
}
|
|
181
276
|
getHeader(name) {
|
|
182
277
|
return this._headers.get(name.toLowerCase());
|
|
183
278
|
}
|
|
184
279
|
removeHeader(name) {
|
|
185
|
-
|
|
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
|
+
}
|
|
186
289
|
}
|
|
187
290
|
getHeaders() {
|
|
188
291
|
const headers = {};
|
|
@@ -197,6 +300,43 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
197
300
|
getHeaderNames() {
|
|
198
301
|
return Array.from(this._headers.keys());
|
|
199
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
|
+
}
|
|
200
340
|
writeHead(statusCode, statusMessageOrHeaders, headers) {
|
|
201
341
|
this.statusCode = statusCode;
|
|
202
342
|
let headersToSet;
|
|
@@ -224,6 +364,25 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
224
364
|
//
|
|
225
365
|
// Express compatibility methods
|
|
226
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
|
+
}
|
|
227
386
|
status(code) {
|
|
228
387
|
this.statusCode = code;
|
|
229
388
|
return this;
|
|
@@ -240,6 +399,26 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
240
399
|
this.end(body);
|
|
241
400
|
return this;
|
|
242
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
|
+
}
|
|
243
422
|
//
|
|
244
423
|
// Writable stream implementation
|
|
245
424
|
//
|
|
@@ -300,6 +479,14 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
300
479
|
}
|
|
301
480
|
}
|
|
302
481
|
|
|
482
|
+
//
|
|
483
|
+
//
|
|
484
|
+
// Constants
|
|
485
|
+
//
|
|
486
|
+
// Get Node's internal kOutHeaders symbol from ServerResponse prototype.
|
|
487
|
+
// This is needed for compatibility with Datadog dd-trace instrumentation,
|
|
488
|
+
// which patches HTTP methods and expects this internal state to exist.
|
|
489
|
+
const kOutHeaders = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
|
|
303
490
|
//
|
|
304
491
|
//
|
|
305
492
|
// LambdaResponseStreaming Class
|
|
@@ -315,17 +502,56 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
315
502
|
this.statusMessage = "OK";
|
|
316
503
|
// Mock socket to satisfy Express/finalhandler checks
|
|
317
504
|
this.socket = {
|
|
318
|
-
cork: () => { },
|
|
319
|
-
destroy: () => { },
|
|
320
505
|
remoteAddress: "127.0.0.1",
|
|
321
|
-
uncork: () => { },
|
|
322
|
-
writable: true,
|
|
323
506
|
};
|
|
507
|
+
// Internal state exposed for direct manipulation by safe response methods
|
|
508
|
+
// that need to bypass dd-trace interception
|
|
324
509
|
this._headers = new Map();
|
|
325
510
|
this._headersSent = false;
|
|
326
511
|
this._pendingWrites = [];
|
|
327
512
|
this._wrappedStream = null;
|
|
328
513
|
this._responseStream = responseStream;
|
|
514
|
+
// Initialize Node's internal kOutHeaders for dd-trace compatibility.
|
|
515
|
+
// dd-trace patches HTTP methods and expects this internal state.
|
|
516
|
+
if (kOutHeaders) {
|
|
517
|
+
this[kOutHeaders] = Object.create(null);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
//
|
|
521
|
+
// Internal bypass methods - completely avoid prototype chain lookup
|
|
522
|
+
// These directly access _headers Map, safe from dd-trace interception
|
|
523
|
+
//
|
|
524
|
+
_internalGetHeader(name) {
|
|
525
|
+
const value = this._headers.get(name.toLowerCase());
|
|
526
|
+
return value ? String(value) : undefined;
|
|
527
|
+
}
|
|
528
|
+
_internalSetHeader(name, value) {
|
|
529
|
+
if (!this._headersSent) {
|
|
530
|
+
const lowerName = name.toLowerCase();
|
|
531
|
+
this._headers.set(lowerName, value);
|
|
532
|
+
// Also sync kOutHeaders for any code that expects it
|
|
533
|
+
if (kOutHeaders) {
|
|
534
|
+
const outHeaders = this[kOutHeaders];
|
|
535
|
+
if (outHeaders) {
|
|
536
|
+
outHeaders[lowerName] = [name, value];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
_internalHasHeader(name) {
|
|
542
|
+
return this._headers.has(name.toLowerCase());
|
|
543
|
+
}
|
|
544
|
+
_internalRemoveHeader(name) {
|
|
545
|
+
if (!this._headersSent) {
|
|
546
|
+
const lowerName = name.toLowerCase();
|
|
547
|
+
this._headers.delete(lowerName);
|
|
548
|
+
if (kOutHeaders) {
|
|
549
|
+
const outHeaders = this[kOutHeaders];
|
|
550
|
+
if (outHeaders) {
|
|
551
|
+
delete outHeaders[lowerName];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
329
555
|
}
|
|
330
556
|
//
|
|
331
557
|
// Header management
|
|
@@ -336,7 +562,16 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
336
562
|
// Headers cannot be changed after body starts
|
|
337
563
|
return this;
|
|
338
564
|
}
|
|
339
|
-
|
|
565
|
+
const lowerName = name.toLowerCase();
|
|
566
|
+
this._headers.set(lowerName, String(value));
|
|
567
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
568
|
+
// Node stores as { 'header-name': ['Header-Name', value] }
|
|
569
|
+
if (kOutHeaders) {
|
|
570
|
+
const outHeaders = this[kOutHeaders];
|
|
571
|
+
if (outHeaders) {
|
|
572
|
+
outHeaders[lowerName] = [name, String(value)];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
340
575
|
return this;
|
|
341
576
|
}
|
|
342
577
|
getHeader(name) {
|
|
@@ -344,7 +579,15 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
344
579
|
}
|
|
345
580
|
removeHeader(name) {
|
|
346
581
|
if (!this._headersSent) {
|
|
347
|
-
|
|
582
|
+
const lowerName = name.toLowerCase();
|
|
583
|
+
this._headers.delete(lowerName);
|
|
584
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
585
|
+
if (kOutHeaders) {
|
|
586
|
+
const outHeaders = this[kOutHeaders];
|
|
587
|
+
if (outHeaders) {
|
|
588
|
+
delete outHeaders[lowerName];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
348
591
|
}
|
|
349
592
|
}
|
|
350
593
|
getHeaders() {
|
|
@@ -360,6 +603,43 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
360
603
|
getHeaderNames() {
|
|
361
604
|
return Array.from(this._headers.keys());
|
|
362
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Proxy for direct header access (e.g., res.headers['content-type']).
|
|
608
|
+
* Required for compatibility with middleware like helmet that access headers directly.
|
|
609
|
+
*/
|
|
610
|
+
get headers() {
|
|
611
|
+
return new Proxy({}, {
|
|
612
|
+
deleteProperty: (_target, prop) => {
|
|
613
|
+
this.removeHeader(String(prop));
|
|
614
|
+
return true;
|
|
615
|
+
},
|
|
616
|
+
get: (_target, prop) => {
|
|
617
|
+
if (typeof prop === "symbol")
|
|
618
|
+
return undefined;
|
|
619
|
+
return this.getHeader(String(prop));
|
|
620
|
+
},
|
|
621
|
+
getOwnPropertyDescriptor: (_target, prop) => {
|
|
622
|
+
if (this.hasHeader(String(prop))) {
|
|
623
|
+
return {
|
|
624
|
+
configurable: true,
|
|
625
|
+
enumerable: true,
|
|
626
|
+
value: this.getHeader(String(prop)),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return undefined;
|
|
630
|
+
},
|
|
631
|
+
has: (_target, prop) => {
|
|
632
|
+
return this.hasHeader(String(prop));
|
|
633
|
+
},
|
|
634
|
+
ownKeys: () => {
|
|
635
|
+
return this.getHeaderNames();
|
|
636
|
+
},
|
|
637
|
+
set: (_target, prop, value) => {
|
|
638
|
+
this.setHeader(String(prop), value);
|
|
639
|
+
return true;
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
}
|
|
363
643
|
writeHead(statusCode, statusMessageOrHeaders, headers) {
|
|
364
644
|
if (this._headersSent) {
|
|
365
645
|
return this;
|
|
@@ -411,6 +691,25 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
411
691
|
//
|
|
412
692
|
// Express compatibility methods
|
|
413
693
|
//
|
|
694
|
+
/**
|
|
695
|
+
* Express-style alias for getHeader().
|
|
696
|
+
* Used by middleware like decorateResponse that use res.get().
|
|
697
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
698
|
+
*/
|
|
699
|
+
get(name) {
|
|
700
|
+
return this._headers.get(name.toLowerCase());
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Express-style alias for setHeader().
|
|
704
|
+
* Used by middleware like decorateResponse that use res.set().
|
|
705
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
706
|
+
*/
|
|
707
|
+
set(name, value) {
|
|
708
|
+
if (!this._headersSent) {
|
|
709
|
+
this._headers.set(name.toLowerCase(), String(value));
|
|
710
|
+
}
|
|
711
|
+
return this;
|
|
712
|
+
}
|
|
414
713
|
status(code) {
|
|
415
714
|
this.statusCode = code;
|
|
416
715
|
return this;
|
|
@@ -427,6 +726,26 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
427
726
|
this.end(body);
|
|
428
727
|
return this;
|
|
429
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Add a field to the Vary response header.
|
|
731
|
+
* Used by CORS middleware to indicate response varies by Origin.
|
|
732
|
+
*/
|
|
733
|
+
vary(field) {
|
|
734
|
+
const existing = this.getHeader("vary");
|
|
735
|
+
if (!existing) {
|
|
736
|
+
this.setHeader("vary", field);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
// Append to existing Vary header if field not already present
|
|
740
|
+
const fields = String(existing)
|
|
741
|
+
.split(",")
|
|
742
|
+
.map((f) => f.trim().toLowerCase());
|
|
743
|
+
if (!fields.includes(field.toLowerCase())) {
|
|
744
|
+
this.setHeader("vary", `${existing}, ${field}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
430
749
|
//
|
|
431
750
|
// Writable stream implementation
|
|
432
751
|
//
|
|
@@ -883,6 +1202,73 @@ function getCurrentInvokeUuid(req) {
|
|
|
883
1202
|
return getWebAdapterUuid();
|
|
884
1203
|
}
|
|
885
1204
|
|
|
1205
|
+
//
|
|
1206
|
+
//
|
|
1207
|
+
// Helpers
|
|
1208
|
+
//
|
|
1209
|
+
/**
|
|
1210
|
+
* Safely get a header value from response.
|
|
1211
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1212
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1213
|
+
*/
|
|
1214
|
+
function safeGetHeader(res, name) {
|
|
1215
|
+
try {
|
|
1216
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1217
|
+
if (typeof res._internalGetHeader === "function") {
|
|
1218
|
+
return res._internalGetHeader(name);
|
|
1219
|
+
}
|
|
1220
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1221
|
+
if (res._headers instanceof Map) {
|
|
1222
|
+
const value = res._headers.get(name.toLowerCase());
|
|
1223
|
+
return value ? String(value) : undefined;
|
|
1224
|
+
}
|
|
1225
|
+
// Fall back to getHeader (more standard than get)
|
|
1226
|
+
if (typeof res.getHeader === "function") {
|
|
1227
|
+
const value = res.getHeader(name);
|
|
1228
|
+
return value ? String(value) : undefined;
|
|
1229
|
+
}
|
|
1230
|
+
// Last resort: try get
|
|
1231
|
+
if (typeof res.get === "function") {
|
|
1232
|
+
const value = res.get(name);
|
|
1233
|
+
return value ? String(value) : undefined;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
catch {
|
|
1237
|
+
// Silently fail - caller will handle missing value
|
|
1238
|
+
}
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Safely set a header value on response.
|
|
1243
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1244
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1245
|
+
*/
|
|
1246
|
+
function safeSetHeader(res, name, value) {
|
|
1247
|
+
try {
|
|
1248
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1249
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1250
|
+
res._internalSetHeader(name, value);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1254
|
+
if (res._headers instanceof Map) {
|
|
1255
|
+
res._headers.set(name.toLowerCase(), value);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
// Fall back to setHeader (more standard than set)
|
|
1259
|
+
if (typeof res.setHeader === "function") {
|
|
1260
|
+
res.setHeader(name, value);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
// Last resort: try set
|
|
1264
|
+
if (typeof res.set === "function") {
|
|
1265
|
+
res.set(name, value);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
catch {
|
|
1269
|
+
// Silently fail - header just won't be set
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
886
1272
|
//
|
|
887
1273
|
//
|
|
888
1274
|
// Main
|
|
@@ -899,36 +1285,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
|
|
|
899
1285
|
log.warn("decorateResponse called but response is not an object");
|
|
900
1286
|
return;
|
|
901
1287
|
}
|
|
1288
|
+
const extRes = res;
|
|
902
1289
|
try {
|
|
903
1290
|
//
|
|
904
1291
|
//
|
|
905
1292
|
// Decorate Headers
|
|
906
1293
|
//
|
|
907
1294
|
// X-Powered-By, override "Express" but nothing else
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1295
|
+
const currentPoweredBy = safeGetHeader(extRes, kit.HTTP.HEADER.POWERED_BY);
|
|
1296
|
+
if (!currentPoweredBy || currentPoweredBy === "Express") {
|
|
1297
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
|
|
911
1298
|
}
|
|
912
1299
|
// X-Project-Environment
|
|
913
1300
|
if (process.env.PROJECT_ENV) {
|
|
914
|
-
|
|
1301
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
|
|
915
1302
|
}
|
|
916
1303
|
// X-Project-Handler
|
|
917
1304
|
if (handler) {
|
|
918
|
-
|
|
1305
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.HANDLER, handler);
|
|
919
1306
|
}
|
|
920
1307
|
// X-Project-Invocation
|
|
921
1308
|
const currentInvoke = getCurrentInvokeUuid();
|
|
922
1309
|
if (currentInvoke) {
|
|
923
|
-
|
|
1310
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
|
|
924
1311
|
}
|
|
925
1312
|
// X-Project-Key
|
|
926
1313
|
if (process.env.PROJECT_KEY) {
|
|
927
|
-
|
|
1314
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
|
|
928
1315
|
}
|
|
929
1316
|
// X-Project-Version
|
|
930
1317
|
if (version) {
|
|
931
|
-
|
|
1318
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.VERSION, version);
|
|
932
1319
|
}
|
|
933
1320
|
//
|
|
934
1321
|
//
|
|
@@ -988,6 +1375,76 @@ function summarizeResponse(res, extras) {
|
|
|
988
1375
|
|
|
989
1376
|
// Cast logger to extended interface for runtime features not in type definitions
|
|
990
1377
|
const logger$1 = logger$2.log;
|
|
1378
|
+
//
|
|
1379
|
+
//
|
|
1380
|
+
// Helpers - Safe response methods to bypass dd-trace interception
|
|
1381
|
+
//
|
|
1382
|
+
/**
|
|
1383
|
+
* Check if response is a Lambda mock response with direct internal access.
|
|
1384
|
+
*/
|
|
1385
|
+
function isLambdaMockResponse(res) {
|
|
1386
|
+
const mock = res;
|
|
1387
|
+
return (mock._headers instanceof Map &&
|
|
1388
|
+
Array.isArray(mock._chunks) &&
|
|
1389
|
+
typeof mock.buildResult === "function");
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Safely send a JSON response, avoiding dd-trace interception.
|
|
1393
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1394
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1395
|
+
*/
|
|
1396
|
+
function safeSendJson(res, statusCode, data) {
|
|
1397
|
+
if (isLambdaMockResponse(res)) {
|
|
1398
|
+
// Use internal method to set header (completely bypasses dd-trace)
|
|
1399
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1400
|
+
res._internalSetHeader("content-type", "application/json");
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
// Fall back to direct _headers manipulation
|
|
1404
|
+
res._headers.set("content-type", "application/json");
|
|
1405
|
+
}
|
|
1406
|
+
res.statusCode = statusCode;
|
|
1407
|
+
// Directly push to chunks array instead of using stream write/end
|
|
1408
|
+
const chunk = Buffer.from(JSON.stringify(data));
|
|
1409
|
+
res._chunks.push(chunk);
|
|
1410
|
+
res._headersSent = true;
|
|
1411
|
+
// Signal completion if a promise is waiting
|
|
1412
|
+
if (res._resolve) {
|
|
1413
|
+
res._resolve(res.buildResult());
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
// Fall back to standard Express methods for real responses
|
|
1418
|
+
res.status(statusCode).json(data);
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Safely send a response body, avoiding dd-trace interception.
|
|
1422
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1423
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1424
|
+
*/
|
|
1425
|
+
function safeSend(res, statusCode, body) {
|
|
1426
|
+
if (isLambdaMockResponse(res)) {
|
|
1427
|
+
// Direct internal state manipulation - bypasses dd-trace completely
|
|
1428
|
+
res.statusCode = statusCode;
|
|
1429
|
+
if (body !== undefined) {
|
|
1430
|
+
const chunk = Buffer.from(body);
|
|
1431
|
+
res._chunks.push(chunk);
|
|
1432
|
+
}
|
|
1433
|
+
res._headersSent = true;
|
|
1434
|
+
// Signal completion if a promise is waiting
|
|
1435
|
+
if (res._resolve) {
|
|
1436
|
+
res._resolve(res.buildResult());
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
// Fall back to standard Express methods for real responses
|
|
1441
|
+
if (body !== undefined) {
|
|
1442
|
+
res.status(statusCode).send(body);
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
res.status(statusCode).send();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
991
1448
|
function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
992
1449
|
/* eslint-enable no-redeclare */
|
|
993
1450
|
let handler;
|
|
@@ -1204,30 +1661,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
|
1204
1661
|
if (typeof response === "object") {
|
|
1205
1662
|
if (typeof response.json ===
|
|
1206
1663
|
"function") {
|
|
1207
|
-
res
|
|
1664
|
+
safeSendJson(res, status, response.json());
|
|
1208
1665
|
}
|
|
1209
1666
|
else {
|
|
1210
|
-
res
|
|
1667
|
+
safeSendJson(res, status, response);
|
|
1211
1668
|
}
|
|
1212
1669
|
}
|
|
1213
1670
|
else if (typeof response === "string") {
|
|
1214
1671
|
try {
|
|
1215
|
-
res
|
|
1672
|
+
safeSendJson(res, status, JSON.parse(response));
|
|
1216
1673
|
}
|
|
1217
1674
|
catch {
|
|
1218
|
-
res
|
|
1675
|
+
safeSend(res, status, response);
|
|
1219
1676
|
}
|
|
1220
1677
|
}
|
|
1221
1678
|
else if (response === true) {
|
|
1222
|
-
res
|
|
1679
|
+
safeSend(res, kit.HTTP.CODE.CREATED);
|
|
1223
1680
|
}
|
|
1224
1681
|
else {
|
|
1225
|
-
res
|
|
1682
|
+
safeSend(res, status, response);
|
|
1226
1683
|
}
|
|
1227
1684
|
}
|
|
1228
1685
|
else {
|
|
1229
1686
|
// No response
|
|
1230
|
-
res
|
|
1687
|
+
safeSend(res, kit.HTTP.CODE.NO_CONTENT);
|
|
1231
1688
|
}
|
|
1232
1689
|
}
|
|
1233
1690
|
else {
|