@reckona/mreact-router 0.0.203 → 0.0.205

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.
Files changed (140) hide show
  1. package/README.md +53 -9
  2. package/dist/actions.d.ts +6 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +70 -10
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts +15 -4
  7. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  8. package/dist/adapters/aws-lambda.js +301 -63
  9. package/dist/adapters/aws-lambda.js.map +1 -1
  10. package/dist/adapters/cloudflare.d.ts +2 -1
  11. package/dist/adapters/cloudflare.d.ts.map +1 -1
  12. package/dist/adapters/cloudflare.js +18 -10
  13. package/dist/adapters/cloudflare.js.map +1 -1
  14. package/dist/adapters/edge.d.ts +1 -1
  15. package/dist/adapters/edge.d.ts.map +1 -1
  16. package/dist/adapters/edge.js.map +1 -1
  17. package/dist/adapters/node.d.ts +5 -3
  18. package/dist/adapters/node.d.ts.map +1 -1
  19. package/dist/adapters/node.js +2 -1
  20. package/dist/adapters/node.js.map +1 -1
  21. package/dist/adapters/static.d.ts.map +1 -1
  22. package/dist/adapters/static.js +24 -2
  23. package/dist/adapters/static.js.map +1 -1
  24. package/dist/build.d.ts +1 -0
  25. package/dist/build.d.ts.map +1 -1
  26. package/dist/build.js +91 -5
  27. package/dist/build.js.map +1 -1
  28. package/dist/built-runtime.d.ts +2 -0
  29. package/dist/built-runtime.d.ts.map +1 -1
  30. package/dist/built-runtime.js +16 -0
  31. package/dist/built-runtime.js.map +1 -1
  32. package/dist/cache.d.ts +3 -1
  33. package/dist/cache.d.ts.map +1 -1
  34. package/dist/cache.js +211 -13
  35. package/dist/cache.js.map +1 -1
  36. package/dist/cli.js +12 -1
  37. package/dist/cli.js.map +1 -1
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +32 -1
  40. package/dist/client.js.map +1 -1
  41. package/dist/config.d.ts +3 -0
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +21 -2
  44. package/dist/config.js.map +1 -1
  45. package/dist/csp.d.ts +1 -0
  46. package/dist/csp.d.ts.map +1 -1
  47. package/dist/csp.js +11 -6
  48. package/dist/csp.js.map +1 -1
  49. package/dist/dehydrate-policy.d.ts +3 -0
  50. package/dist/dehydrate-policy.d.ts.map +1 -0
  51. package/dist/dehydrate-policy.js +16 -0
  52. package/dist/dehydrate-policy.js.map +1 -0
  53. package/dist/dev-server.d.ts +7 -2
  54. package/dist/dev-server.d.ts.map +1 -1
  55. package/dist/dev-server.js +120 -11
  56. package/dist/dev-server.js.map +1 -1
  57. package/dist/http.d.ts +1 -1
  58. package/dist/http.d.ts.map +1 -1
  59. package/dist/http.js +147 -7
  60. package/dist/http.js.map +1 -1
  61. package/dist/index.d.ts +4 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +1 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/logger.d.ts +22 -1
  66. package/dist/logger.d.ts.map +1 -1
  67. package/dist/logger.js +22 -8
  68. package/dist/logger.js.map +1 -1
  69. package/dist/metadata.d.ts +8 -0
  70. package/dist/metadata.d.ts.map +1 -1
  71. package/dist/metadata.js +60 -22
  72. package/dist/metadata.js.map +1 -1
  73. package/dist/navigation-marker.d.ts.map +1 -1
  74. package/dist/navigation-marker.js +72 -9
  75. package/dist/navigation-marker.js.map +1 -1
  76. package/dist/navigation.d.ts +1 -0
  77. package/dist/navigation.d.ts.map +1 -1
  78. package/dist/navigation.js +1 -1
  79. package/dist/navigation.js.map +1 -1
  80. package/dist/node-server.d.ts +5 -2
  81. package/dist/node-server.d.ts.map +1 -1
  82. package/dist/node-server.js +75 -6
  83. package/dist/node-server.js.map +1 -1
  84. package/dist/prerender-entry.d.ts +14 -0
  85. package/dist/prerender-entry.d.ts.map +1 -1
  86. package/dist/prerender-entry.js +40 -3
  87. package/dist/prerender-entry.js.map +1 -1
  88. package/dist/render.d.ts +9 -1
  89. package/dist/render.d.ts.map +1 -1
  90. package/dist/render.js +102 -123
  91. package/dist/render.js.map +1 -1
  92. package/dist/serve.d.ts +8 -2
  93. package/dist/serve.d.ts.map +1 -1
  94. package/dist/serve.js +22 -20
  95. package/dist/serve.js.map +1 -1
  96. package/dist/session.d.ts.map +1 -1
  97. package/dist/session.js +52 -19
  98. package/dist/session.js.map +1 -1
  99. package/dist/upgrade.d.ts +59 -2
  100. package/dist/upgrade.d.ts.map +1 -1
  101. package/dist/upgrade.js +282 -1
  102. package/dist/upgrade.js.map +1 -1
  103. package/dist/vite-config.d.ts +2 -0
  104. package/dist/vite-config.d.ts.map +1 -1
  105. package/dist/vite-config.js +2 -0
  106. package/dist/vite-config.js.map +1 -1
  107. package/dist/vite.d.ts +4 -1
  108. package/dist/vite.d.ts.map +1 -1
  109. package/dist/vite.js +117 -6
  110. package/dist/vite.js.map +1 -1
  111. package/package.json +11 -11
  112. package/src/actions.ts +81 -11
  113. package/src/adapters/aws-lambda.ts +426 -115
  114. package/src/adapters/cloudflare.ts +29 -13
  115. package/src/adapters/edge.ts +2 -0
  116. package/src/adapters/node.ts +8 -10
  117. package/src/adapters/static.ts +31 -2
  118. package/src/build.ts +117 -10
  119. package/src/built-runtime.ts +28 -0
  120. package/src/cache.ts +262 -15
  121. package/src/cli.ts +14 -1
  122. package/src/client.ts +32 -1
  123. package/src/config.ts +43 -6
  124. package/src/csp.ts +14 -11
  125. package/src/dehydrate-policy.ts +23 -0
  126. package/src/dev-server.ts +188 -15
  127. package/src/http.ts +172 -6
  128. package/src/index.ts +14 -1
  129. package/src/logger.ts +47 -10
  130. package/src/metadata.ts +125 -60
  131. package/src/navigation-marker.ts +79 -9
  132. package/src/navigation.ts +1 -1
  133. package/src/node-server.ts +128 -22
  134. package/src/prerender-entry.ts +52 -3
  135. package/src/render.ts +807 -753
  136. package/src/serve.ts +49 -56
  137. package/src/session.ts +56 -25
  138. package/src/upgrade.ts +402 -3
  139. package/src/vite-config.ts +6 -0
  140. package/src/vite.ts +157 -5
package/src/serve.ts CHANGED
@@ -23,10 +23,7 @@ import {
23
23
  loadBuiltServerModuleArtifacts,
24
24
  loadBuiltServerModuleArtifactsForRequest,
25
25
  } from "./built-server-module-artifacts.js";
26
- import {
27
- type AppRoute,
28
- type MatchedRoute,
29
- } from "./routes.js";
26
+ import { type AppRoute, type MatchedRoute } from "./routes.js";
30
27
  import type { AppRouterServerActionOptions } from "./actions.js";
31
28
  import type { AppRouterImportPolicy } from "./import-policy.js";
32
29
  import {
@@ -44,26 +41,21 @@ import {
44
41
  traceContextFromRequest,
45
42
  type RouterInstrumentation,
46
43
  } from "./trace.js";
47
- import {
48
- bytesResponse,
49
- htmlResponse,
50
- } from "./http.js";
51
- import {
52
- emitRouterLog,
53
- logDurationMs,
54
- logNow,
55
- type AppRouterLogger,
56
- } from "./logger.js";
44
+ import { bytesResponse, htmlResponse } from "./http.js";
45
+ import { emitRouterLog, logDurationMs, logNow, type AppRouterLogger } from "./logger.js";
57
46
  import { startNodeRequestServer } from "./node-server.js";
58
47
  import { builtAppRuntimePreloadPlan } from "./preload-policy.js";
59
48
  import { normalizeRoutePath } from "./route-path.js";
60
- import type { HttpUpgradeHandler } from "./upgrade.js";
49
+ import type { HttpUpgradeOriginPolicy, ManagedHttpUpgradeHandler } from "./upgrade.js";
61
50
  import {
62
51
  isCurrentPrerenderedRoute,
63
52
  isVisitorDependentResponse,
53
+ mergePrerenderedNavigationHtml,
54
+ protectNonceBearingResponse,
64
55
  PRERENDERED_ROUTE_SCHEMA_VERSION,
65
56
  replayedPrerenderedRouteHeaders,
66
57
  storedPrerenderedRouteHeaders,
58
+ validatedPrerenderedNavigationHtml,
67
59
  } from "./prerender-entry.js";
68
60
 
69
61
  interface BuiltRuntimeCacheEntry {
@@ -168,7 +160,11 @@ export interface StartServerOptions {
168
160
  // a generic "Internal Server Error" body and logs the stack to stderr
169
161
  // via console.error. Issue 071: stack traces must never end up in
170
162
  // production responses.
171
- errorHandler?: (error: unknown) => { body: string; status: number; headers?: Record<string, string> };
163
+ errorHandler?: (error: unknown) => {
164
+ body: string;
165
+ status: number;
166
+ headers?: Record<string, string>;
167
+ };
172
168
  // When set, an incoming Host header that does not exactly match one of
173
169
  // the listed values is replaced with the configured hostname/port for
174
170
  // origin reconstruction. Use this in front of public deployments to
@@ -185,7 +181,13 @@ export interface StartServerOptions {
185
181
  routeCache?: AppRouterCache | undefined;
186
182
  serverActions?: AppRouterServerActionOptions | undefined;
187
183
  sinkStrategy?: ResponseSinkStrategy;
188
- onUpgrade?: HttpUpgradeHandler | undefined;
184
+ onUpgrade?: ManagedHttpUpgradeHandler | undefined;
185
+ /** Maximum time to wait for application-owned upgrade sockets during shutdown. */
186
+ upgradeCloseTimeoutMs?: number | undefined;
187
+ /** Maximum time an asynchronous upgrade handler may leave ownership undecided. */
188
+ upgradeDecisionTimeoutMs?: number | undefined;
189
+ /** Origin policy for application HTTP upgrades. Defaults to exact same-origin validation. */
190
+ upgradeOriginPolicy?: HttpUpgradeOriginPolicy | undefined;
189
191
  /**
190
192
  * Trusts the first `X-Forwarded-Proto` value when the socket is not TLS.
191
193
  *
@@ -224,7 +226,7 @@ export function warnIfImplicitHostTrust(options: {
224
226
 
225
227
  warnedImplicitHostTrust = true;
226
228
  console.error(
227
- "[mreact] Host header trust is implicit because neither allowedHosts nor hostPolicy is configured. Set allowedHosts for public deployments, hostPolicy: \"strict\" to reject unlisted Host headers, or hostPolicy: \"trusted-proxy\" when a trusted reverse proxy normalizes Host.",
229
+ '[mreact] Host header trust is implicit because neither allowedHosts nor hostPolicy is configured. Set allowedHosts for public deployments, hostPolicy: "strict" to reject unlisted Host headers, or hostPolicy: "trusted-proxy" when a trusted reverse proxy normalizes Host.',
228
230
  );
229
231
  }
230
232
 
@@ -260,6 +262,7 @@ export async function createBuiltRequestRuntime(
260
262
  emitBuiltRequestStart(renderOptions.instrumentation, request);
261
263
  const response = await renderBuiltAppRequestWithRuntime({
262
264
  ...renderOptions,
265
+ dehydrateOptions: renderOptions.dehydrateOptions ?? runtime.dehydrateOptions,
263
266
  instrumentation: withoutBuiltRequestInstrumentation(renderOptions.instrumentation),
264
267
  importPolicy:
265
268
  renderOptions.importPolicy === undefined
@@ -405,8 +408,11 @@ function withoutBuiltRequestInstrumentation(
405
408
  return undefined;
406
409
  }
407
410
 
408
- const { onRequestEnd: _onRequestEnd, onRequestStart: _onRequestStart, ...remaining } =
409
- instrumentation;
411
+ const {
412
+ onRequestEnd: _onRequestEnd,
413
+ onRequestStart: _onRequestStart,
414
+ ...remaining
415
+ } = instrumentation;
410
416
  return remaining;
411
417
  }
412
418
 
@@ -470,21 +476,14 @@ async function renderBuiltAppRequestWithRuntime(
470
476
  // no middleware at all. Otherwise a middleware that gates access (auth,
471
477
  // geo blocking, maintenance mode) would be bypassed for exactly the routes
472
478
  // it is most often used to protect.
473
- if (
474
- !options.runtime.hasMiddleware &&
475
- (request.method === "GET" || request.method === "HEAD")
476
- ) {
479
+ if (!options.runtime.hasMiddleware && (request.method === "GET" || request.method === "HEAD")) {
477
480
  // Sync fast path when no external prerender store is configured (the
478
481
  // common case): skip the Promise wrap that `readPrerenderedRoute`
479
482
  // would otherwise introduce just to satisfy the async signature.
480
483
  const prerendered =
481
484
  options.prerenderStore === undefined
482
485
  ? options.runtime.prerenderedRoutes.get(normalizedPath)
483
- : await readPrerenderedRoute(
484
- options.runtime,
485
- normalizedPath,
486
- options.prerenderStore,
487
- );
486
+ : await readPrerenderedRoute(options.runtime, normalizedPath, options.prerenderStore);
488
487
  const prerenderedResponse = prerenderedRouteResponse(prerendered, request);
489
488
 
490
489
  if (prerenderedResponse !== undefined) {
@@ -508,18 +507,11 @@ async function renderBuiltAppRequestWithRuntime(
508
507
  // Middleware apps resolve prerendered HTML here instead, so that a
509
508
  // middleware response or rewrite is honoured before the stored HTML is
510
509
  // served. The lookup uses the post-middleware path.
511
- if (
512
- options.runtime.hasMiddleware &&
513
- (request.method === "GET" || request.method === "HEAD")
514
- ) {
510
+ if (options.runtime.hasMiddleware && (request.method === "GET" || request.method === "HEAD")) {
515
511
  const prerendered =
516
512
  options.prerenderStore === undefined
517
513
  ? options.runtime.prerenderedRoutes.get(normalizedPath)
518
- : await readPrerenderedRoute(
519
- options.runtime,
520
- normalizedPath,
521
- options.prerenderStore,
522
- );
514
+ : await readPrerenderedRoute(options.runtime, normalizedPath, options.prerenderStore);
523
515
  const prerenderedResponse = prerenderedRouteResponse(prerendered, request);
524
516
 
525
517
  if (prerenderedResponse !== undefined) {
@@ -545,15 +537,9 @@ async function renderBuiltAppRequestWithRuntime(
545
537
  requestUrl: new URL(request.url),
546
538
  });
547
539
 
548
- await applyBuiltPrerenderInvalidations(
549
- options.runtime,
550
- response,
551
- options.prerenderStore,
552
- );
540
+ await applyBuiltPrerenderInvalidations(options.runtime, response, options.prerenderStore);
553
541
 
554
- return options.sinkStrategy === "buffer"
555
- ? await materializeResponseAsBuffer(response)
556
- : response;
542
+ return options.sinkStrategy === "buffer" ? await materializeResponseAsBuffer(response) : response;
557
543
  }
558
544
 
559
545
  function prerenderedRouteResponse(
@@ -565,7 +551,7 @@ function prerenderedRouteResponse(
565
551
  }
566
552
 
567
553
  const navigation = request.headers.get("x-mreact-navigation") === "1";
568
- const html = navigation ? prerendered.navigationHtml : prerendered.html;
554
+ const html = navigation ? validatedPrerenderedNavigationHtml(prerendered) : prerendered.html;
569
555
  if (html === undefined) {
570
556
  return undefined;
571
557
  }
@@ -673,7 +659,7 @@ async function applyBuiltAppResponseHook(
673
659
  request: options.request,
674
660
  });
675
661
 
676
- return hooked instanceof Response ? hooked : response;
662
+ return protectNonceBearingResponse(hooked instanceof Response ? hooked : response);
677
663
  }
678
664
 
679
665
  async function materializeResponseAsBuffer(response: Response): Promise<Response> {
@@ -718,6 +704,9 @@ export async function startServer(
718
704
  port: options.port,
719
705
  resolveHost: resolveRequestHost,
720
706
  trustForwardedProto: options.trustForwardedProto,
707
+ upgradeCloseTimeoutMs: options.upgradeCloseTimeoutMs,
708
+ upgradeDecisionTimeoutMs: options.upgradeDecisionTimeoutMs,
709
+ upgradeOriginPolicy: options.upgradeOriginPolicy,
721
710
  render: (request) =>
722
711
  runtime.render(request, {
723
712
  dehydrateOptions: options.dehydrateOptions,
@@ -973,7 +962,9 @@ function builtRenderAppRequestOptions(
973
962
 
974
963
  function mergeBuiltServerActionOptions(
975
964
  options: AppRouterServerActionOptions | undefined,
976
- allowedActions: readonly { moduleId: string; exportName: string; inferred?: boolean }[] | undefined,
965
+ allowedActions:
966
+ | readonly { moduleId: string; exportName: string; inferred?: boolean }[]
967
+ | undefined,
977
968
  ): AppRouterServerActionOptions | undefined {
978
969
  if (allowedActions === undefined) {
979
970
  return options;
@@ -990,9 +981,7 @@ async function renderAndCachePrerenderWithLock(
990
981
  path: string,
991
982
  ): Promise<Response> {
992
983
  const lockKey =
993
- options.request.headers.get("x-mreact-navigation") === "1"
994
- ? `${path}\0navigation`
995
- : path;
984
+ options.request.headers.get("x-mreact-navigation") === "1" ? `${path}\0navigation` : path;
996
985
  const existing = options.runtime.prerenderLocks.get(lockKey);
997
986
 
998
987
  if (existing !== undefined) {
@@ -1019,9 +1008,7 @@ async function runPrerenderRegeneration(
1019
1008
  path: string,
1020
1009
  ): Promise<{ response: Response; shareable: boolean }> {
1021
1010
  const lockKey =
1022
- options.request.headers.get("x-mreact-navigation") === "1"
1023
- ? `${path}\0navigation`
1024
- : path;
1011
+ options.request.headers.get("x-mreact-navigation") === "1" ? `${path}\0navigation` : path;
1025
1012
  const regenerate = async () => {
1026
1013
  const stored = await readPrerenderedRoute(options.runtime, path, options.prerenderStore);
1027
1014
  const storedResponse = prerenderedRouteResponse(stored, options.request);
@@ -1128,7 +1115,13 @@ async function cacheRegeneratedPrerenderedRoute(
1128
1115
  status: response.status,
1129
1116
  ...(strictTransportSecurity === undefined ? {} : { strictTransportSecurity }),
1130
1117
  }
1131
- : { ...existing, navigationHtml: body };
1118
+ : mergePrerenderedNavigationHtml(existing, body);
1119
+ if (entry === existing) {
1120
+ return htmlResponse(body, {
1121
+ headers: response.headers,
1122
+ status: response.status,
1123
+ });
1124
+ }
1132
1125
  runtime.prerenderedRoutes.set(path, entry);
1133
1126
  await store?.set(path, entry);
1134
1127
 
package/src/session.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { deleteCookie, parseCookieHeader, setCookie } from "./cookies.js";
1
+ import { deleteCookie, parseCookieHeader, serializeCookie, setCookie } from "./cookies.js";
2
2
 
3
3
  /**
4
4
  * Stores session data and expiration metadata for one session id.
@@ -43,32 +43,54 @@ const DEFAULT_MAX_AGE_SECONDS = 7 * 24 * 60 * 60;
43
43
  const SESSION_COOKIE_DEV = "mreact.session";
44
44
  const SESSION_COOKIE_PROD = "__Host-mreact.session";
45
45
 
46
- function isProduction(): boolean {
47
- return process.env.NODE_ENV === "production";
46
+ function usesHardenedCookieDefaults(): boolean {
47
+ const nodeEnv = typeof process === "undefined" ? undefined : process["env"]?.["NODE_ENV"];
48
+ return nodeEnv !== "development" && nodeEnv !== "test";
48
49
  }
49
50
 
50
- function defaultCookieName(): string {
51
- return isProduction() ? SESSION_COOKIE_PROD : SESSION_COOKIE_DEV;
52
- }
53
-
54
- function sessionCookieOptions(options: SessionCookieOptions = {}) {
55
- const production = isProduction();
56
-
57
- return {
51
+ function resolveSessionCookie(options: SessionCookieOptions = {}) {
52
+ const hardened = usesHardenedCookieDefaults();
53
+ const cookieOptions = {
58
54
  httpOnly: true,
59
55
  maxAge: options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS,
60
- path: production ? "/" : options.path ?? "/",
56
+ path: options.path ?? "/",
61
57
  sameSite: options.sameSite ?? "Lax",
62
- secure: options.secure ?? production,
58
+ secure: options.secure ?? hardened,
63
59
  } as const;
60
+ const canUseHostPrefix = cookieOptions.secure && cookieOptions.path === "/";
61
+
62
+ return {
63
+ name:
64
+ options.cookieName ??
65
+ (hardened && canUseHostPrefix ? SESSION_COOKIE_PROD : SESSION_COOKIE_DEV),
66
+ options: cookieOptions,
67
+ };
64
68
  }
65
69
 
66
- function readSessionId(
67
- request: Request,
68
- options: SessionCookieOptions = {},
69
- ): string | undefined {
70
+ function validateResolvedSessionCookie(cookie: ReturnType<typeof resolveSessionCookie>): void {
71
+ serializeCookie(cookie.name, "", cookie.options);
72
+ }
73
+
74
+ function assertMutableResponseHeaders(response: Response): void {
75
+ const probeName = "x-mreact-session-header-probe";
76
+ const previous = response.headers.get(probeName);
77
+
78
+ try {
79
+ response.headers.set(probeName, "1");
80
+ if (previous === null) {
81
+ response.headers.delete(probeName);
82
+ } else {
83
+ response.headers.set(probeName, previous);
84
+ }
85
+ } catch (cause) {
86
+ throw new TypeError("Session helpers require a Response with mutable headers.", { cause });
87
+ }
88
+ }
89
+
90
+ function readSessionId(request: Request, options: SessionCookieOptions = {}): string | undefined {
91
+ const cookie = resolveSessionCookie(options);
70
92
  const values = parseCookieHeader(request.headers.get("cookie"));
71
- return values.get(options.cookieName ?? defaultCookieName());
93
+ return values.get(cookie.name);
72
94
  }
73
95
 
74
96
  function createSessionId(): string {
@@ -208,6 +230,9 @@ export async function createSession<TData>(
208
230
  ): Promise<SessionRecord<TData>> {
209
231
  const now = Date.now();
210
232
  const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
233
+ const cookie = resolveSessionCookie({ ...options, maxAgeSeconds });
234
+ validateResolvedSessionCookie(cookie);
235
+ assertMutableResponseHeaders(response);
211
236
  const record: SessionRecord<TData> = {
212
237
  createdAt: now,
213
238
  data,
@@ -216,9 +241,7 @@ export async function createSession<TData>(
216
241
  };
217
242
 
218
243
  await store.set(record);
219
- setCookie(response, options.cookieName ?? defaultCookieName(), record.id, {
220
- ...sessionCookieOptions({ ...options, maxAgeSeconds }),
221
- });
244
+ setCookie(response, cookie.name, record.id, cookie.options);
222
245
 
223
246
  return record;
224
247
  }
@@ -232,16 +255,22 @@ export async function destroySession<TData>(
232
255
  store: SessionStore<TData>,
233
256
  options: SessionCookieOptions = {},
234
257
  ): Promise<void> {
258
+ const cookie = resolveSessionCookie(options);
259
+ validateResolvedSessionCookie({
260
+ name: cookie.name,
261
+ options: { ...cookie.options, maxAge: 0 },
262
+ });
263
+ assertMutableResponseHeaders(response);
235
264
  const id = readSessionId(request, options);
236
265
 
237
266
  if (id !== undefined) {
238
267
  await store.delete(id);
239
268
  }
240
269
 
241
- deleteCookie(response, options.cookieName ?? defaultCookieName(), {
242
- path: isProduction() ? "/" : options.path ?? "/",
243
- sameSite: options.sameSite ?? "Lax",
244
- secure: options.secure ?? isProduction(),
270
+ deleteCookie(response, cookie.name, {
271
+ path: cookie.options.path,
272
+ sameSite: cookie.options.sameSite,
273
+ secure: cookie.options.secure,
245
274
  });
246
275
  }
247
276
 
@@ -256,6 +285,8 @@ export async function rotateSession<TData>(
256
285
  store: SessionStore<TData>,
257
286
  options: SessionCookieOptions = {},
258
287
  ): Promise<SessionRecord<TData> | undefined> {
288
+ validateResolvedSessionCookie(resolveSessionCookie(options));
289
+ assertMutableResponseHeaders(response);
259
290
  const current = await getSession(request, store, options);
260
291
 
261
292
  if (current === undefined) {