@cloudflare/pages-shared 0.11.71 → 0.12.0

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.
@@ -25,10 +25,8 @@ type BodyEncoding = "manual" | "automatic";
25
25
  // Before serving a 404, we check the cache to see if we've served this asset recently
26
26
  // and if so, serve it from the cache instead of responding with a 404.
27
27
  // This gives a bit of a grace period between deployments for any clients browsing the old deployment.
28
- export const ASSET_PRESERVATION_CACHE_V1 = "assetPreservationCache";
29
- // V2 stores the content hash instead of the asset.
30
- // TODO: Remove V1 once we've fully migrated to V2
31
- export const ASSET_PRESERVATION_CACHE_V2 = "assetPreservationCacheV2";
28
+ // Only the content hash is actually stored in the body.
29
+ export const ASSET_PRESERVATION_CACHE = "assetPreservationCacheV2";
32
30
  const CACHE_CONTROL_PRESERVATION = "public, s-maxage=604800"; // 1 week
33
31
 
34
32
  /** The preservation cache should be periodically
@@ -74,6 +72,25 @@ type FindAssetEntryForPath<AssetEntry> = (
74
72
  path: string
75
73
  ) => Promise<null | AssetEntry>;
76
74
 
75
+ function generateETagHeader(assetKey: string) {
76
+ // https://support.cloudflare.com/hc/en-us/articles/218505467-Using-ETag-Headers-with-Cloudflare
77
+ // We sometimes remove etags unless they are wrapped in quotes
78
+ const strongETag = `"${assetKey}"`;
79
+ const weakETag = `W/"${assetKey}"`;
80
+ return { strongETag, weakETag };
81
+ }
82
+
83
+ function checkIfNoneMatch(
84
+ request: Request,
85
+ strongETag: string,
86
+ weakETag: string
87
+ ) {
88
+ const ifNoneMatch = request.headers.get("if-none-match");
89
+
90
+ // We sometimes downgrade strong etags to a weak ones, so we need to check for both
91
+ return ifNoneMatch === weakETag || ifNoneMatch === strongETag;
92
+ }
93
+
77
94
  type ServeAsset<AssetEntry> = (
78
95
  assetEntry: AssetEntry,
79
96
  options?: { preserve: boolean }
@@ -82,7 +99,10 @@ type ServeAsset<AssetEntry> = (
82
99
  type CacheStatus = "hit" | "miss";
83
100
  type CacheResult<A extends string> = `${A}-${CacheStatus}`;
84
101
  export type HandlerMetrics = {
85
- preservationCacheResult?: CacheResult<"checked"> | "disabled";
102
+ preservationCacheResult?:
103
+ | CacheResult<"checked">
104
+ | "not-modified"
105
+ | "disabled";
86
106
  earlyHintsResult?: CacheResult<"used" | "notused"> | "disabled";
87
107
  };
88
108
 
@@ -520,22 +540,16 @@ export async function generateHandler<
520
540
 
521
541
  const assetKey = getAssetKey(servingAssetEntry, content);
522
542
 
523
- // https://support.cloudflare.com/hc/en-us/articles/218505467-Using-ETag-Headers-with-Cloudflare
524
- // We sometimes remove etags unless they are wrapped in quotes
525
- const etag = `"${assetKey}"`;
526
- const weakEtag = `W/${etag}`;
527
-
528
- const ifNoneMatch = request.headers.get("if-none-match");
529
-
530
- // We sometimes downgrade strong etags to a weak ones, so we need to check for both
531
- if (ifNoneMatch === weakEtag || ifNoneMatch === etag) {
543
+ const { strongETag, weakETag } = generateETagHeader(assetKey);
544
+ const isIfNoneMatch = checkIfNoneMatch(request, strongETag, weakETag);
545
+ if (isIfNoneMatch) {
532
546
  return new NotModifiedResponse();
533
547
  }
534
548
 
535
549
  try {
536
550
  const asset = await fetchAsset(assetKey);
537
551
  const headers: Record<string, string> = {
538
- etag,
552
+ etag: strongETag,
539
553
  "content-type": asset.contentType,
540
554
  };
541
555
  let encodeBody: BodyEncoding = "automatic";
@@ -576,14 +590,14 @@ export async function generateHandler<
576
590
  waitUntil(
577
591
  (async () => {
578
592
  try {
579
- const assetPreservationCacheV2 = await caches.open(
580
- ASSET_PRESERVATION_CACHE_V2
593
+ const assetPreservationCache = await caches.open(
594
+ ASSET_PRESERVATION_CACHE
581
595
  );
582
596
 
583
597
  // Check if the asset has changed since last written to cache
584
598
  // or if the cached entry is getting too old and should have
585
599
  // it's expiration reset.
586
- const match = await assetPreservationCacheV2.match(request);
600
+ const match = await assetPreservationCache.match(request);
587
601
  if (
588
602
  !match ||
589
603
  assetKey !== (await match.text()) ||
@@ -599,7 +613,7 @@ export async function generateHandler<
599
613
  );
600
614
  preservedResponse.headers.set("x-robots-tag", "noindex");
601
615
 
602
- await assetPreservationCacheV2.put(
616
+ await assetPreservationCache.put(
603
617
  request.url,
604
618
  preservedResponse
605
619
  );
@@ -637,30 +651,13 @@ export async function generateHandler<
637
651
  async function notFound(): Promise<Response> {
638
652
  if (caches) {
639
653
  try {
640
- const assetPreservationCacheV2 = await caches.open(
641
- ASSET_PRESERVATION_CACHE_V2
654
+ const assetPreservationCache = await caches.open(
655
+ ASSET_PRESERVATION_CACHE
642
656
  );
643
- let preservedResponse = await assetPreservationCacheV2.match(
657
+ const preservedResponse = await assetPreservationCache.match(
644
658
  request.url
645
659
  );
646
660
 
647
- // Continue serving from V1 preservation cache for some time to
648
- // prevent 404s during the migration to V2
649
- const cutoffDate = new Date("2024-05-17");
650
- if (!preservedResponse && Date.now() < cutoffDate.getTime()) {
651
- const assetPreservationCacheV1 = await caches.open(
652
- ASSET_PRESERVATION_CACHE_V1
653
- );
654
- preservedResponse = await assetPreservationCacheV1.match(request.url);
655
- if (preservedResponse) {
656
- // V1 cache contains full response bodies so we return it directly
657
- if (setMetrics) {
658
- setMetrics({ preservationCacheResult: "checked-hit" });
659
- }
660
- return preservedResponse;
661
- }
662
- }
663
-
664
661
  // V2 cache only contains the asset key, rather than the asset body:
665
662
  if (preservedResponse) {
666
663
  if (setMetrics) {
@@ -673,7 +670,21 @@ export async function generateHandler<
673
670
  return new Response(null, preservedResponse);
674
671
  }
675
672
  if (assetKey) {
673
+ const { strongETag, weakETag } = generateETagHeader(assetKey);
674
+ const isIfNoneMatch = checkIfNoneMatch(
675
+ request,
676
+ strongETag,
677
+ weakETag
678
+ );
679
+ if (isIfNoneMatch) {
680
+ if (setMetrics) {
681
+ setMetrics({ preservationCacheResult: "not-modified" });
682
+ }
683
+ return new NotModifiedResponse();
684
+ }
685
+
676
686
  const asset = await fetchAsset(assetKey);
687
+
677
688
  if (asset) {
678
689
  // We know the asset hasn't changed, so use the cached headers.
679
690
  return new Response(asset.body, preservedResponse);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/pages-shared",
3
- "version": "0.11.71",
3
+ "version": "0.12.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudflare/workers-sdk.git",
@@ -13,10 +13,10 @@
13
13
  "metadata-generator/**/*"
14
14
  ],
15
15
  "dependencies": {
16
- "miniflare": "3.20241205.0"
16
+ "miniflare": "3.20241218.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@cloudflare/workers-types": "^4.20241205.0",
19
+ "@cloudflare/workers-types": "^4.20241218.0",
20
20
  "@miniflare/cache": "^2.14.2",
21
21
  "@miniflare/core": "^2.14.2",
22
22
  "@miniflare/html-rewriter": "^2.14.2",