@decocms/start 1.3.2 → 1.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -27,6 +27,7 @@ import {
27
27
  getRequest,
28
28
  getRequestHeader,
29
29
  getRequestUrl,
30
+ setResponseHeader,
30
31
  } from "@tanstack/react-start/server";
31
32
  import { createElement } from "react";
32
33
  import { preloadSectionComponents } from "../cms/registry";
@@ -227,6 +228,12 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
227
228
  headers: originRequest.headers,
228
229
  });
229
230
  const enriched = await runSingleSectionLoader(section, request);
231
+
232
+ // Signal to the worker entry that this response is safe to edge-cache.
233
+ // Without this header, POST _serverFn responses are passed through
234
+ // without caching (checkout actions, invoke mutations, etc.).
235
+ setResponseHeader("X-Deco-Cacheable", "true");
236
+
230
237
  return normalizeUrlsInObject(enriched);
231
238
  });
232
239
 
@@ -1034,9 +1034,7 @@ export function createDecoWorkerEntry(
1034
1034
 
1035
1035
  // Logged-in users always bypass — personalized content must not leak
1036
1036
  if (sfnSegment?.loggedIn) {
1037
- const body = await request.text();
1038
- const originReq = new Request(request, { body, method: "POST" });
1039
- const origin = await serverEntry.fetch(originReq, env, ctx);
1037
+ const origin = await serverEntry.fetch(request, env, ctx);
1040
1038
  const resp = new Response(origin.body, origin);
1041
1039
  resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
1042
1040
  resp.headers.set("X-Cache", "BYPASS");
@@ -1044,7 +1042,10 @@ export function createDecoWorkerEntry(
1044
1042
  return resp;
1045
1043
  }
1046
1044
 
1047
- // Read body once and create a cloned request for origin fetch
1045
+ // Clone request before consuming body the clone goes to origin
1046
+ // untouched so TanStack Start internals (cookie passthrough, etc.)
1047
+ // work correctly. We only read the body for the cache key hash.
1048
+ const originClone = request.clone();
1048
1049
  const body = await request.text();
1049
1050
  const bodyHash = await hashText(body);
1050
1051
 
@@ -1106,12 +1107,18 @@ export function createDecoWorkerEntry(
1106
1107
  try {
1107
1108
  const bgReq = new Request(request, { body, method: "POST" });
1108
1109
  const bgOrigin = await serverEntry.fetch(bgReq, env, ctx);
1109
- if (bgOrigin.status === 200 && !bgOrigin.headers.has("set-cookie") && serverFnCache) {
1110
+ if (
1111
+ bgOrigin.status === 200 &&
1112
+ bgOrigin.headers.get("X-Deco-Cacheable") === "true" &&
1113
+ !bgOrigin.headers.has("set-cookie") &&
1114
+ serverFnCache
1115
+ ) {
1110
1116
  const ttl = sfnEdge.fresh + Math.max(sfnEdge.swr, sfnEdge.sie);
1111
1117
  const toStore = bgOrigin.clone();
1112
1118
  toStore.headers.set("Cache-Control", `public, max-age=${ttl}`);
1113
1119
  toStore.headers.set("X-Deco-Stored-At", String(Date.now()));
1114
1120
  toStore.headers.delete("CDN-Cache-Control");
1121
+ toStore.headers.delete("X-Deco-Cacheable");
1115
1122
  await serverFnCache.put(sfnCacheKey, toStore);
1116
1123
  }
1117
1124
  } catch { /* background revalidation failed */ }
@@ -1128,32 +1135,41 @@ export function createDecoWorkerEntry(
1128
1135
  }
1129
1136
 
1130
1137
  // Cache MISS — fetch origin with the body we already read
1131
- const originReq = new Request(request, { body, method: "POST" });
1132
- const origin = await serverEntry.fetch(originReq, env, ctx);
1138
+ const origin = await serverEntry.fetch(originClone, env, ctx);
1133
1139
 
1134
- // Never cache responses with Set-Cookie (cart/auth)
1135
- if (origin.headers.has("set-cookie")) {
1140
+ // Only cache responses explicitly marked as cacheable by the handler
1141
+ // (loadDeferredSection sets X-Deco-Cacheable: true). Checkout actions,
1142
+ // invoke mutations, and other server functions are passed through.
1143
+ const isCacheableResponse =
1144
+ origin.headers.get("X-Deco-Cacheable") === "true" &&
1145
+ !origin.headers.has("set-cookie") &&
1146
+ origin.status === 200;
1147
+
1148
+ if (!isCacheableResponse) {
1136
1149
  const resp = new Response(origin.body, origin);
1137
- resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
1138
- resp.headers.delete("CDN-Cache-Control");
1150
+ resp.headers.delete("X-Deco-Cacheable");
1139
1151
  resp.headers.set("X-Cache", "BYPASS");
1140
- resp.headers.set("X-Cache-Reason", "set-cookie");
1152
+ resp.headers.set("X-Cache-Reason", origin.headers.has("set-cookie")
1153
+ ? "set-cookie"
1154
+ : "not-cacheable");
1141
1155
  return resp;
1142
1156
  }
1143
1157
 
1144
1158
  // Store in edge cache
1145
- if (origin.status === 200 && serverFnCache) {
1159
+ if (serverFnCache) {
1146
1160
  try {
1147
1161
  const ttl = sfnEdge.fresh + Math.max(sfnEdge.swr, sfnEdge.sie);
1148
1162
  const toStore = origin.clone();
1149
1163
  toStore.headers.set("Cache-Control", `public, max-age=${ttl}`);
1150
1164
  toStore.headers.set("X-Deco-Stored-At", String(Date.now()));
1151
1165
  toStore.headers.delete("CDN-Cache-Control");
1166
+ toStore.headers.delete("X-Deco-Cacheable");
1152
1167
  ctx.waitUntil(serverFnCache.put(sfnCacheKey, toStore));
1153
1168
  } catch { /* Cache API unavailable */ }
1154
1169
  }
1155
1170
 
1156
1171
  const resp = new Response(origin.body, origin);
1172
+ resp.headers.delete("X-Deco-Cacheable");
1157
1173
  const hdrs = cacheHeaders(sfnProfile);
1158
1174
  for (const [k, v] of Object.entries(hdrs)) resp.headers.set(k, v);
1159
1175
  resp.headers.set("X-Cache", "MISS");