diff --git a/.changeset/olive-lines-cough.md b/.changeset/olive-lines-cough.md new file mode 100644 index 000000000000..c5e023f59716 --- /dev/null +++ b/.changeset/olive-lines-cough.md @@ -0,0 +1,7 @@ +--- +'astro': patch +--- + +Adds `lastModified` field to experimental live collection cache hints + +Live loaders can now set a `lastModified` field in the cache hints for entries and collections to indicate when the data was last modified. This is then available in the `cacheHint` field returned by `getCollection` and `getEntry`. diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index 0e3607d3a2c6..2661be436099 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -21,6 +21,7 @@ import { unescapeHTML, } from '../runtime/server/index.js'; import type { + CacheHint, LiveDataCollectionResult, LiveDataEntry, LiveDataEntryResult, @@ -72,6 +73,7 @@ export function createCollectionToGlobResultMap({ const cacheHintSchema = z.object({ tags: z.array(z.string()).optional(), maxAge: z.number().optional(), + lastModified: z.date().optional(), }); async function parseLiveEntry( @@ -529,6 +531,7 @@ export function createGetLiveCollection({ if (processedEntries.length > 0) { const entryTags = new Set(); let minMaxAge: number | undefined; + let latestModified: Date | undefined; for (const entry of processedEntries) { if (entry.cacheHint) { @@ -536,19 +539,24 @@ export function createGetLiveCollection({ entry.cacheHint.tags.forEach((tag) => entryTags.add(tag)); } if (typeof entry.cacheHint.maxAge === 'number') { - minMaxAge = - minMaxAge === undefined - ? entry.cacheHint.maxAge - : Math.min(minMaxAge, entry.cacheHint.maxAge); + if (minMaxAge === undefined || entry.cacheHint.maxAge < minMaxAge) { + minMaxAge = entry.cacheHint.maxAge; + } + } + if (entry.cacheHint.lastModified instanceof Date) { + if (latestModified === undefined || entry.cacheHint.lastModified > latestModified) { + latestModified = entry.cacheHint.lastModified; + } } } } // Merge collection and entry cache hints - if (entryTags.size > 0 || minMaxAge !== undefined || cacheHint) { - const mergedCacheHint: any = {}; + if (entryTags.size > 0 || minMaxAge !== undefined || latestModified || cacheHint) { + const mergedCacheHint: CacheHint = {}; if (cacheHint?.tags || entryTags.size > 0) { - mergedCacheHint.tags = [...(cacheHint?.tags || []), ...entryTags]; + // Merge and dedupe tags + mergedCacheHint.tags = [...new Set([...(cacheHint?.tags || []), ...entryTags])]; } if (cacheHint?.maxAge !== undefined || minMaxAge !== undefined) { mergedCacheHint.maxAge = @@ -556,6 +564,12 @@ export function createGetLiveCollection({ ? Math.min(cacheHint.maxAge, minMaxAge) : (cacheHint?.maxAge ?? minMaxAge); } + if (cacheHint?.lastModified && latestModified) { + mergedCacheHint.lastModified = + cacheHint.lastModified > latestModified ? cacheHint.lastModified : latestModified; + } else if (cacheHint?.lastModified || latestModified) { + mergedCacheHint.lastModified = cacheHint?.lastModified ?? latestModified; + } cacheHint = mergedCacheHint; } } diff --git a/packages/astro/src/types/public/content.ts b/packages/astro/src/types/public/content.ts index 6b1a53397747..3826ab7ea9b4 100644 --- a/packages/astro/src/types/public/content.ts +++ b/packages/astro/src/types/public/content.ts @@ -131,6 +131,8 @@ export interface CacheHint { tags?: Array; /** Maximum age of the response in seconds */ maxAge?: number; + /** Last modified time of the content */ + lastModified?: Date; } export interface LiveDataEntry = Record> { diff --git a/packages/astro/test/fixtures/live-loaders/src/live.config.ts b/packages/astro/test/fixtures/live-loaders/src/live.config.ts index 001f03e19c41..822c6c6f278c 100644 --- a/packages/astro/test/fixtures/live-loaders/src/live.config.ts +++ b/packages/astro/test/fixtures/live-loaders/src/live.config.ts @@ -52,6 +52,7 @@ const loader: LiveLoader = { cacheHint: { tags: [`page:${filter.id}`], maxAge: 60, + lastModified: new Date('2025-01-01T00:00:00.000Z'), }, }; }, @@ -69,6 +70,7 @@ const loader: LiveLoader = { cacheHint: { tags: ['page'], maxAge: 60, + lastModified: new Date('2025-01-02T00:00:00.000Z'), }, }; }, diff --git a/packages/astro/test/live-loaders.test.js b/packages/astro/test/live-loaders.test.js index a2da6b2ab78a..fddb48315b11 100644 --- a/packages/astro/test/live-loaders.test.js +++ b/packages/astro/test/live-loaders.test.js @@ -47,11 +47,13 @@ describe('Live content collections', () => { cacheHint: { tags: [`page:123`], maxAge: 60, + lastModified: '2025-01-01T00:00:00.000Z', }, }, cacheHint: { tags: [`page:123`], maxAge: 60, + lastModified: '2025-01-01T00:00:00.000Z', }, }); assert.deepEqual(data.entryByObject, { @@ -61,11 +63,13 @@ describe('Live content collections', () => { cacheHint: { tags: [`page:456`], maxAge: 60, + lastModified: '2025-01-01T00:00:00.000Z', }, }, cacheHint: { tags: [`page:456`], maxAge: 60, + lastModified: '2025-01-01T00:00:00.000Z', }, }); assert.deepEqual(data.collection, { @@ -86,6 +90,7 @@ describe('Live content collections', () => { cacheHint: { tags: ['page'], maxAge: 60, + lastModified: '2025-01-02T00:00:00.000Z', }, }); }); @@ -101,11 +106,13 @@ describe('Live content collections', () => { id: '456', data: { title: 'Page 456', age: 25 }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:456`], maxAge: 60, }, }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:456`], maxAge: 60, }, @@ -167,11 +174,13 @@ describe('Live content collections', () => { id: '123', data: { title: 'Page 123', age: 10 }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:123`], maxAge: 60, }, }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:123`], maxAge: 60, }, @@ -190,11 +199,13 @@ describe('Live content collections', () => { id: '456', data: { title: 'Page 456', age: 25 }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:456`], maxAge: 60, }, }, cacheHint: { + lastModified: '2025-01-01T00:00:00.000Z', tags: [`page:456`], maxAge: 60, },