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