@jaypie/express 1.2.32 → 1.2.34

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.
@@ -178,6 +178,13 @@ declare class LambdaResponseBuffered extends Writable {
178
178
  */
179
179
  get headers(): Record<string, string | string[] | undefined>;
180
180
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
181
+ /**
182
+ * Node's implicit-header hook, called before the first body byte commits.
183
+ * Routes through this.writeHead resolved at call time so wrappers installed
184
+ * mid-request (on-headers, and through it express-session, morgan,
185
+ * compression, express-openid-connect) run their listeners.
186
+ */
187
+ _implicitHeader(): void;
181
188
  get headersSent(): boolean;
182
189
  /**
183
190
  * Express-style alias for getHeader().
@@ -241,8 +248,16 @@ declare class LambdaResponseStreaming extends Writable {
241
248
  */
242
249
  get headers(): Record<string, string | string[] | undefined>;
243
250
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
251
+ /**
252
+ * Node's implicit-header hook, called before the first body byte commits.
253
+ * Routes through this.writeHead resolved at call time so wrappers installed
254
+ * mid-request (on-headers, and through it express-session, morgan,
255
+ * compression, express-openid-connect) run their listeners.
256
+ */
257
+ _implicitHeader(): void;
244
258
  get headersSent(): boolean;
245
259
  flushHeaders(): void;
260
+ private _commitHeaders;
246
261
  /**
247
262
  * Express-style alias for getHeader().
248
263
  * Used by middleware like decorateResponse that use res.get().
@@ -36,6 +36,13 @@ export declare class LambdaResponseBuffered extends Writable {
36
36
  */
37
37
  get headers(): Record<string, string | string[] | undefined>;
38
38
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
39
+ /**
40
+ * Node's implicit-header hook, called before the first body byte commits.
41
+ * Routes through this.writeHead resolved at call time so wrappers installed
42
+ * mid-request (on-headers, and through it express-session, morgan,
43
+ * compression, express-openid-connect) run their listeners.
44
+ */
45
+ _implicitHeader(): void;
39
46
  get headersSent(): boolean;
40
47
  /**
41
48
  * Express-style alias for getHeader().
@@ -36,8 +36,16 @@ export declare class LambdaResponseStreaming extends Writable {
36
36
  */
37
37
  get headers(): Record<string, string | string[] | undefined>;
38
38
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
39
+ /**
40
+ * Node's implicit-header hook, called before the first body byte commits.
41
+ * Routes through this.writeHead resolved at call time so wrappers installed
42
+ * mid-request (on-headers, and through it express-session, morgan,
43
+ * compression, express-openid-connect) run their listeners.
44
+ */
45
+ _implicitHeader(): void;
39
46
  get headersSent(): boolean;
40
47
  flushHeaders(): void;
48
+ private _commitHeaders;
41
49
  /**
42
50
  * Express-style alias for getHeader().
43
51
  * Used by middleware like decorateResponse that use res.get().
@@ -0,0 +1,15 @@
1
+ export declare const SET_COOKIE = "set-cookie";
2
+ /**
3
+ * Normalize a header value for storage, preserving arrays.
4
+ * Node's ServerResponse keeps multi-value headers as arrays; stringifying
5
+ * here would fold them into a single comma-separated value.
6
+ */
7
+ export declare function normalizeHeaderValue(value: number | string | string[]): string | string[];
8
+ /**
9
+ * Split stored headers into a single-value header record and a cookies
10
+ * array, matching the Lambda Function URL (v2) response shape.
11
+ */
12
+ export declare function splitCookieHeaders(source: Map<string, string | string[]>): {
13
+ cookies: string[];
14
+ headers: Record<string, string>;
15
+ };
@@ -82,6 +82,7 @@ export interface ResponseStream {
82
82
  write(chunk: string | Uint8Array): void;
83
83
  }
84
84
  export interface HttpResponseStreamMetadata {
85
+ cookies?: string[];
85
86
  headers: Record<string, string>;
86
87
  statusCode: number;
87
88
  }
@@ -178,6 +178,13 @@ declare class LambdaResponseBuffered extends Writable {
178
178
  */
179
179
  get headers(): Record<string, string | string[] | undefined>;
180
180
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
181
+ /**
182
+ * Node's implicit-header hook, called before the first body byte commits.
183
+ * Routes through this.writeHead resolved at call time so wrappers installed
184
+ * mid-request (on-headers, and through it express-session, morgan,
185
+ * compression, express-openid-connect) run their listeners.
186
+ */
187
+ _implicitHeader(): void;
181
188
  get headersSent(): boolean;
182
189
  /**
183
190
  * Express-style alias for getHeader().
@@ -241,8 +248,16 @@ declare class LambdaResponseStreaming extends Writable {
241
248
  */
242
249
  get headers(): Record<string, string | string[] | undefined>;
243
250
  writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
251
+ /**
252
+ * Node's implicit-header hook, called before the first body byte commits.
253
+ * Routes through this.writeHead resolved at call time so wrappers installed
254
+ * mid-request (on-headers, and through it express-session, morgan,
255
+ * compression, express-openid-connect) run their listeners.
256
+ */
257
+ _implicitHeader(): void;
244
258
  get headersSent(): boolean;
245
259
  flushHeaders(): void;
260
+ private _commitHeaders;
246
261
  /**
247
262
  * Express-style alias for getHeader().
248
263
  * Used by middleware like decorateResponse that use res.get().
package/dist/esm/index.js CHANGED
@@ -276,6 +276,50 @@ function createLambdaRequest(event, context) {
276
276
  });
277
277
  }
278
278
 
279
+ //
280
+ //
281
+ // Constants
282
+ //
283
+ // Set-Cookie is the one header that must never be comma-folded: cookie
284
+ // expiry dates contain commas, so a folded value is unparseable. Lambda
285
+ // carries these out-of-band in the `cookies` field of the v2 response
286
+ // payload and of the response-streaming metadata prelude.
287
+ const SET_COOKIE = "set-cookie";
288
+ //
289
+ //
290
+ // Functions
291
+ //
292
+ /**
293
+ * Normalize a header value for storage, preserving arrays.
294
+ * Node's ServerResponse keeps multi-value headers as arrays; stringifying
295
+ * here would fold them into a single comma-separated value.
296
+ */
297
+ function normalizeHeaderValue(value) {
298
+ return Array.isArray(value) ? value.map(String) : String(value);
299
+ }
300
+ /**
301
+ * Split stored headers into a single-value header record and a cookies
302
+ * array, matching the Lambda Function URL (v2) response shape.
303
+ */
304
+ function splitCookieHeaders(source) {
305
+ const cookies = [];
306
+ const headers = {};
307
+ for (const [key, value] of source) {
308
+ if (key === SET_COOKIE) {
309
+ if (Array.isArray(value)) {
310
+ cookies.push(...value);
311
+ }
312
+ else {
313
+ cookies.push(value);
314
+ }
315
+ }
316
+ else {
317
+ headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
318
+ }
319
+ }
320
+ return { cookies, headers };
321
+ }
322
+
279
323
  //
280
324
  //
281
325
  // Constants
@@ -338,6 +382,7 @@ class LambdaResponseBuffered extends Writable {
338
382
  this.getHeaders = this.getHeaders.bind(this);
339
383
  this.getHeaderNames = this.getHeaderNames.bind(this);
340
384
  this.writeHead = this.writeHead.bind(this);
385
+ this._implicitHeader = this._implicitHeader.bind(this);
341
386
  this.get = this.get.bind(this);
342
387
  this.set = this.set.bind(this);
343
388
  this.status = this.status.bind(this);
@@ -413,13 +458,14 @@ class LambdaResponseBuffered extends Writable {
413
458
  return this;
414
459
  }
415
460
  const lowerName = name.toLowerCase();
416
- this._headers.set(lowerName, String(value));
461
+ const normalized = normalizeHeaderValue(value);
462
+ this._headers.set(lowerName, normalized);
417
463
  // Sync with kOutHeaders for dd-trace compatibility
418
464
  // Node stores as { 'header-name': ['Header-Name', value] }
419
465
  if (kOutHeaders$1) {
420
466
  const outHeaders = this[kOutHeaders$1];
421
467
  if (outHeaders) {
422
- outHeaders[lowerName] = [name, String(value)];
468
+ outHeaders[lowerName] = [name, normalized];
423
469
  }
424
470
  }
425
471
  return this;
@@ -493,6 +539,9 @@ class LambdaResponseBuffered extends Writable {
493
539
  });
494
540
  }
495
541
  writeHead(statusCode, statusMessageOrHeaders, headers) {
542
+ if (this._headersSent) {
543
+ return this;
544
+ }
496
545
  this.statusCode = statusCode;
497
546
  let headersToSet;
498
547
  if (typeof statusMessageOrHeaders === "string") {
@@ -507,13 +556,24 @@ class LambdaResponseBuffered extends Writable {
507
556
  // Use direct _headers access to bypass dd-trace interception
508
557
  for (const [key, value] of Object.entries(headersToSet)) {
509
558
  if (value !== undefined) {
510
- this._headers.set(key.toLowerCase(), String(value));
559
+ this._headers.set(key.toLowerCase(), normalizeHeaderValue(value));
511
560
  }
512
561
  }
513
562
  }
514
563
  this._headersSent = true;
515
564
  return this;
516
565
  }
566
+ /**
567
+ * Node's implicit-header hook, called before the first body byte commits.
568
+ * Routes through this.writeHead resolved at call time so wrappers installed
569
+ * mid-request (on-headers, and through it express-session, morgan,
570
+ * compression, express-openid-connect) run their listeners.
571
+ */
572
+ _implicitHeader() {
573
+ if (!this._headersSent) {
574
+ this.writeHead(this.statusCode);
575
+ }
576
+ }
517
577
  get headersSent() {
518
578
  return this._headersSent;
519
579
  }
@@ -535,7 +595,7 @@ class LambdaResponseBuffered extends Writable {
535
595
  */
536
596
  set(name, value) {
537
597
  if (!this._headersSent) {
538
- this._headers.set(name.toLowerCase(), String(value));
598
+ this._headers.set(name.toLowerCase(), normalizeHeaderValue(value));
539
599
  }
540
600
  return this;
541
601
  }
@@ -585,11 +645,13 @@ class LambdaResponseBuffered extends Writable {
585
645
  const buffer = Buffer.isBuffer(chunk)
586
646
  ? chunk
587
647
  : Buffer.from(chunk, encoding);
648
+ this._implicitHeader();
588
649
  this._chunks.push(buffer);
589
650
  this._headersSent = true;
590
651
  callback();
591
652
  }
592
653
  _final(callback) {
654
+ this._implicitHeader();
593
655
  this._ended = true;
594
656
  if (this._resolve) {
595
657
  this._resolve(this.buildResult());
@@ -605,23 +667,8 @@ class LambdaResponseBuffered extends Writable {
605
667
  const contentType = this._headers.get("content-type") || "";
606
668
  // Determine if response should be base64 encoded
607
669
  const isBase64Encoded = this.isBinaryContentType(contentType);
608
- // Build headers object
609
- const headers = {};
610
- const cookies = [];
611
- for (const [key, value] of this._headers) {
612
- if (key === "set-cookie") {
613
- // Collect Set-Cookie headers for v2 response format
614
- if (Array.isArray(value)) {
615
- cookies.push(...value);
616
- }
617
- else {
618
- cookies.push(value);
619
- }
620
- }
621
- else {
622
- headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
623
- }
624
- }
670
+ // Build headers object, carrying Set-Cookie out-of-band (v2 format)
671
+ const { cookies, headers } = splitCookieHeaders(this._headers);
625
672
  const result = {
626
673
  body: isBase64Encoded ? body.toString("base64") : body.toString("utf8"),
627
674
  headers,
@@ -696,6 +743,8 @@ class LambdaResponseStreaming extends Writable {
696
743
  this.getHeaderNames = this.getHeaderNames.bind(this);
697
744
  this.writeHead = this.writeHead.bind(this);
698
745
  this.flushHeaders = this.flushHeaders.bind(this);
746
+ this._implicitHeader = this._implicitHeader.bind(this);
747
+ this._commitHeaders = this._commitHeaders.bind(this);
699
748
  this.get = this.get.bind(this);
700
749
  this.set = this.set.bind(this);
701
750
  this.status = this.status.bind(this);
@@ -754,13 +803,14 @@ class LambdaResponseStreaming extends Writable {
754
803
  return this;
755
804
  }
756
805
  const lowerName = name.toLowerCase();
757
- this._headers.set(lowerName, String(value));
806
+ const normalized = normalizeHeaderValue(value);
807
+ this._headers.set(lowerName, normalized);
758
808
  // Sync with kOutHeaders for dd-trace compatibility
759
809
  // Node stores as { 'header-name': ['Header-Name', value] }
760
810
  if (kOutHeaders) {
761
811
  const outHeaders = this[kOutHeaders];
762
812
  if (outHeaders) {
763
- outHeaders[lowerName] = [name, String(value)];
813
+ outHeaders[lowerName] = [name, normalized];
764
814
  }
765
815
  }
766
816
  return this;
@@ -855,24 +905,36 @@ class LambdaResponseStreaming extends Writable {
855
905
  // Use direct _headers access to bypass dd-trace interception
856
906
  for (const [key, value] of Object.entries(headersToSet)) {
857
907
  if (value !== undefined) {
858
- this._headers.set(key.toLowerCase(), String(value));
908
+ this._headers.set(key.toLowerCase(), normalizeHeaderValue(value));
859
909
  }
860
910
  }
861
911
  }
862
- this.flushHeaders();
912
+ this._commitHeaders();
863
913
  return this;
864
914
  }
915
+ /**
916
+ * Node's implicit-header hook, called before the first body byte commits.
917
+ * Routes through this.writeHead resolved at call time so wrappers installed
918
+ * mid-request (on-headers, and through it express-session, morgan,
919
+ * compression, express-openid-connect) run their listeners.
920
+ */
921
+ _implicitHeader() {
922
+ if (!this._headersSent) {
923
+ this.writeHead(this.statusCode);
924
+ }
925
+ }
865
926
  get headersSent() {
866
927
  return this._headersSent;
867
928
  }
868
929
  flushHeaders() {
930
+ this._implicitHeader();
931
+ }
932
+ _commitHeaders() {
869
933
  if (this._headersSent) {
870
934
  return;
871
935
  }
872
- const headers = {};
873
- for (const [key, value] of this._headers) {
874
- headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
875
- }
936
+ // Set-Cookie travels in metadata.cookies, never folded into headers
937
+ const { cookies, headers } = splitCookieHeaders(this._headers);
876
938
  // Lambda streaming requires body content for metadata to be transmitted.
877
939
  // Convert 204 No Content to 200 OK with empty JSON body as workaround.
878
940
  // See: https://github.com/finlaysonstudio/jaypie/issues/178
@@ -887,6 +949,10 @@ class LambdaResponseStreaming extends Writable {
887
949
  headers,
888
950
  statusCode,
889
951
  };
952
+ // Only include cookies if present (v2 format)
953
+ if (cookies.length > 0) {
954
+ metadata.cookies = cookies;
955
+ }
890
956
  // Create wrapped stream with metadata
891
957
  this._wrappedStream = awslambda.HttpResponseStream.from(this._responseStream, metadata);
892
958
  this._headersSent = true;
@@ -915,7 +981,7 @@ class LambdaResponseStreaming extends Writable {
915
981
  */
916
982
  set(name, value) {
917
983
  if (!this._headersSent) {
918
- this._headers.set(name.toLowerCase(), String(value));
984
+ this._headers.set(name.toLowerCase(), normalizeHeaderValue(value));
919
985
  }
920
986
  return this;
921
987
  }
@@ -969,7 +1035,7 @@ class LambdaResponseStreaming extends Writable {
969
1035
  // Buffer writes until headers are sent
970
1036
  this._pendingWrites.push({ callback: () => callback(), chunk: buffer });
971
1037
  // Auto-flush headers on first write
972
- this.flushHeaders();
1038
+ this._implicitHeader();
973
1039
  }
974
1040
  else {
975
1041
  this._wrappedStream.write(buffer);
@@ -977,9 +1043,7 @@ class LambdaResponseStreaming extends Writable {
977
1043
  }
978
1044
  }
979
1045
  _final(callback) {
980
- if (!this._headersSent) {
981
- this.flushHeaders();
982
- }
1046
+ this._implicitHeader();
983
1047
  // For converted 204 responses, write empty JSON body
984
1048
  // Lambda streaming requires body content for metadata to be transmitted
985
1049
  if (this._convertedFrom204 && this._wrappedStream) {