@decocms/tanstack 7.22.0 → 7.22.1
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 +4 -4
- package/src/sdk/workerEntry.test.ts +50 -0
- package/src/sdk/workerEntry.ts +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/tanstack",
|
|
3
|
-
"version": "7.22.
|
|
3
|
+
"version": "7.22.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco framework binding for TanStack Start + Cloudflare Workers",
|
|
6
6
|
"repository": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"lint:unused": "knip"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@decocms/blocks": "7.22.
|
|
28
|
-
"@decocms/blocks-admin": "7.22.
|
|
29
|
-
"@decocms/blocks-cli": "7.22.
|
|
27
|
+
"@decocms/blocks": "7.22.1",
|
|
28
|
+
"@decocms/blocks-admin": "7.22.1",
|
|
29
|
+
"@decocms/blocks-cli": "7.22.1",
|
|
30
30
|
"@deco-cx/warp-node": "^0.3.16",
|
|
31
31
|
"fast-json-patch": "^3.1.0",
|
|
32
32
|
"ws": "^8.18.0"
|
|
@@ -430,3 +430,53 @@ describe("POST /_cache/purge-loaders", () => {
|
|
|
430
430
|
expect(res.status).toBe(404);
|
|
431
431
|
});
|
|
432
432
|
});
|
|
433
|
+
|
|
434
|
+
describe("X-Cache-Segment — custom SegmentKey fields (#284)", () => {
|
|
435
|
+
afterEach(() => {
|
|
436
|
+
__resetKvHydrationStateForTests();
|
|
437
|
+
setBlocks({});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const HTML_SERVER_ENTRY = {
|
|
441
|
+
fetch: async (_req: Request) =>
|
|
442
|
+
new Response("<html>hi</html>", {
|
|
443
|
+
status: 200,
|
|
444
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
445
|
+
}),
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
it("includes a custom segment field in X-Cache-Segment instead of dropping it", async () => {
|
|
449
|
+
const worker = createDecoWorkerEntry(HTML_SERVER_ENTRY, {
|
|
450
|
+
observability: false,
|
|
451
|
+
buildSegment: () => ({ device: "desktop", regionId: "RJ", delivery: "store-42" }),
|
|
452
|
+
});
|
|
453
|
+
const res = await worker.fetch(new Request("https://example.com/"), EMPTY_ENV, MOCK_CTX);
|
|
454
|
+
expect(res.headers.get("X-Cache-Segment")).toBe("desktop|r=RJ|delivery=store-42");
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("produces different X-Cache-Segment hashes for requests that only differ by a custom field", async () => {
|
|
458
|
+
let delivery = "store-1";
|
|
459
|
+
const worker = createDecoWorkerEntry(HTML_SERVER_ENTRY, {
|
|
460
|
+
observability: false,
|
|
461
|
+
buildSegment: () => ({ device: "desktop", delivery }),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const first = await worker.fetch(new Request("https://example.com/"), EMPTY_ENV, MOCK_CTX);
|
|
465
|
+
const firstSegment = first.headers.get("X-Cache-Segment");
|
|
466
|
+
|
|
467
|
+
delivery = "store-2";
|
|
468
|
+
const second = await worker.fetch(new Request("https://example.com/"), EMPTY_ENV, MOCK_CTX);
|
|
469
|
+
const secondSegment = second.headers.get("X-Cache-Segment");
|
|
470
|
+
|
|
471
|
+
expect(firstSegment).not.toBe(secondSegment);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("keeps the existing hash format for segments using only known fields (back-compat)", async () => {
|
|
475
|
+
const worker = createDecoWorkerEntry(HTML_SERVER_ENTRY, {
|
|
476
|
+
observability: false,
|
|
477
|
+
buildSegment: () => ({ device: "desktop", salesChannel: "1", flags: ["b", "a"] }),
|
|
478
|
+
});
|
|
479
|
+
const res = await worker.fetch(new Request("https://example.com/"), EMPTY_ENV, MOCK_CTX);
|
|
480
|
+
expect(res.headers.get("X-Cache-Segment")).toBe("desktop|sc=1|f=a,b");
|
|
481
|
+
});
|
|
482
|
+
});
|
package/src/sdk/workerEntry.ts
CHANGED
|
@@ -172,6 +172,13 @@ export interface SegmentKey {
|
|
|
172
172
|
regionId?: string;
|
|
173
173
|
/** Sorted list of active A/B flag names for cache cohort splitting. */
|
|
174
174
|
flags?: string[];
|
|
175
|
+
/**
|
|
176
|
+
* Custom cache dimensions beyond the known fields above (e.g. a
|
|
177
|
+
* store/CEP-based delivery selector). `hashSegment` serializes these
|
|
178
|
+
* automatically — unlike the known fields, there's no dedicated shorthand
|
|
179
|
+
* key, so don't reuse a name already declared above.
|
|
180
|
+
*/
|
|
181
|
+
[customField: string]: string | boolean | string[] | undefined;
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
/**
|
|
@@ -983,12 +990,34 @@ export function createDecoWorkerEntry(
|
|
|
983
990
|
return detectCacheProfile(target);
|
|
984
991
|
}
|
|
985
992
|
|
|
993
|
+
const KNOWN_SEGMENT_FIELDS = new Set([
|
|
994
|
+
"device",
|
|
995
|
+
"loggedIn",
|
|
996
|
+
"salesChannel",
|
|
997
|
+
"regionId",
|
|
998
|
+
"flags",
|
|
999
|
+
]);
|
|
1000
|
+
|
|
986
1001
|
function hashSegment(seg: SegmentKey): string {
|
|
987
1002
|
const parts: string[] = [seg.device];
|
|
988
1003
|
if (seg.loggedIn) parts.push("auth");
|
|
989
1004
|
if (seg.salesChannel) parts.push(`sc=${seg.salesChannel}`);
|
|
990
1005
|
if (seg.regionId) parts.push(`r=${seg.regionId}`);
|
|
991
1006
|
if (seg.flags?.length) parts.push(`f=${seg.flags.sort().join(",")}`);
|
|
1007
|
+
// Custom dimensions (anything beyond the known fields above) are
|
|
1008
|
+
// serialized generically so they aren't silently dropped from the
|
|
1009
|
+
// cache key — see #284.
|
|
1010
|
+
const customKeys = Object.keys(seg)
|
|
1011
|
+
.filter((k) => !KNOWN_SEGMENT_FIELDS.has(k) && seg[k] !== undefined)
|
|
1012
|
+
.sort();
|
|
1013
|
+
for (const k of customKeys) {
|
|
1014
|
+
const v = seg[k];
|
|
1015
|
+
if (v === "" || v === false || (Array.isArray(v) && v.length === 0)) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
const serialized = Array.isArray(v) ? [...v].sort().join(",") : v === true ? "" : String(v);
|
|
1019
|
+
parts.push(serialized ? `${k}=${serialized}` : k);
|
|
1020
|
+
}
|
|
992
1021
|
return parts.join("|");
|
|
993
1022
|
}
|
|
994
1023
|
|