Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/olive-lines-cough.md
Original file line number Diff line number Diff line change
@@ -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`.
28 changes: 21 additions & 7 deletions packages/astro/src/content/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
unescapeHTML,
} from '../runtime/server/index.js';
import type {
CacheHint,
LiveDataCollectionResult,
LiveDataEntry,
LiveDataEntryResult,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -529,33 +531,45 @@ export function createGetLiveCollection({
if (processedEntries.length > 0) {
const entryTags = new Set<string>();
let minMaxAge: number | undefined;
let latestModified: Date | undefined;

for (const entry of processedEntries) {
if (entry.cacheHint) {
if (entry.cacheHint.tags) {
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])];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to add a comment explaining why the use of Set instead of a simple array

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point (it's to dedupe them)

}
if (cacheHint?.maxAge !== undefined || minMaxAge !== undefined) {
mergedCacheHint.maxAge =
cacheHint?.maxAge !== undefined && minMaxAge !== undefined
? 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;
}
Comment on lines +567 to +572
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, do timezones have a role in this logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they're Date objects so it compares the timestamp

cacheHint = mergedCacheHint;
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/types/public/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export interface CacheHint {
tags?: Array<string>;
/** Maximum age of the response in seconds */
maxAge?: number;
/** Last modified time of the content */
lastModified?: Date;
}

export interface LiveDataEntry<TData extends Record<string, any> = Record<string, unknown>> {
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/test/fixtures/live-loaders/src/live.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const loader: LiveLoader<Entry, EntryFilter, CollectionFilter, CustomError> = {
cacheHint: {
tags: [`page:${filter.id}`],
maxAge: 60,
lastModified: new Date('2025-01-01T00:00:00.000Z'),
},
};
},
Expand All @@ -69,6 +70,7 @@ const loader: LiveLoader<Entry, EntryFilter, CollectionFilter, CustomError> = {
cacheHint: {
tags: ['page'],
maxAge: 60,
lastModified: new Date('2025-01-02T00:00:00.000Z'),
},
};
},
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/test/live-loaders.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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, {
Expand All @@ -86,6 +90,7 @@ describe('Live content collections', () => {
cacheHint: {
tags: ['page'],
maxAge: 60,
lastModified: '2025-01-02T00:00:00.000Z',
},
});
});
Expand All @@ -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,
},
Expand Down Expand Up @@ -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,
},
Expand All @@ -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,
},
Expand Down