From b2ea0a08633fb37d0bdb29c650d5b21023475042 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 8 May 2025 10:27:19 +0100 Subject: [PATCH 1/3] feat: add cache hint support to live loaders --- packages/astro/src/content/runtime.ts | 36 +++++++++++++++++++ packages/astro/src/core/errors/errors-data.ts | 35 +++++++++++++++--- packages/astro/src/types/public/content.ts | 12 ++++++- .../fixtures/live-loaders/src/live.config.ts | 12 ++++++- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index 06d42a23e534..4589d0b2743f 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -121,6 +121,11 @@ export function createCollectionToGlobResultMap({ return collectionToGlobResultMap; } +const cacheHintSchema = z.object({ + tags: z.array(z.string()).optional(), + maxAge: z.number().optional(), +}); + async function parseLiveEntry( entry: LiveDataEntry, schema: z.ZodType, @@ -137,6 +142,21 @@ async function parseLiveEntry( ), }); } + if (entry.cacheHint) { + const cacheHint = cacheHintSchema.safeParse(entry.cacheHint); + + if (!cacheHint.success) { + throw new AstroError({ + ...AstroErrorData.InvalidCacheHintError, + message: AstroErrorData.InvalidCacheHintError.message( + collection, + entry.id, + cacheHint.error, + ), + }); + } + entry.cacheHint = cacheHint.data; + } return { ...entry, data: parsed.data, @@ -184,6 +204,22 @@ export function createGetCollection({ ); } + if (response.cacheHint) { + const cacheHint = cacheHintSchema.safeParse(response.cacheHint); + + if (!cacheHint.success) { + throw new AstroError({ + ...AstroErrorData.InvalidCacheHintError, + message: AstroErrorData.InvalidCacheHintError.message( + collection, + undefined, + cacheHint.error, + ), + }); + } + response.cacheHint = cacheHint.data; + } + return { ...response, collection, diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 2319c4cb5069..8482a0377885 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1614,8 +1614,9 @@ export const InvalidContentEntryDataError = { title: 'Content entry data does not match schema.', message(collection: string, entryId: string, error: ZodError) { return [ - `**${String(collection)} → ${String(entryId)}** data does not match collection schema.`, - ...error.errors.map((zodError) => zodError.message), + `**${String(collection)} → ${String(entryId)}** data does not match collection schema.\n`, + ...error.errors.map((zodError) => ` **${zodError.path.join('.')}**: ${zodError.message}`), + '', ].join('\n'); }, hint: 'See https://docs.astro.build/en/guides/content-collections/ for more information on content schemas.', @@ -1665,13 +1666,39 @@ export const ContentEntryDataError = { title: 'Content entry data does not match schema.', message(collection: string, entryId: string, error: ZodError) { return [ - `**${String(collection)} → ${String(entryId)}** data does not match collection schema.`, - ...error.errors.map((zodError) => zodError.message), + `**${String(collection)} → ${String(entryId)}** data does not match collection schema.\n`, + ...error.errors.map((zodError) => ` **${zodError.path.join('.')}**: ${zodError.message}`), + '', ].join('\n'); }, hint: 'See https://docs.astro.build/en/guides/content-collections/ for more information on content schemas.', } satisfies ErrorData; +/** + * @docs + * @message + * **Example error message:**
+ * **blog** → **post** returned an invalid cache hint.
+ * **maxAge**: Expected number, received string + * @description + * The loader for a live content collection returned an invalid cache hint. + * Make sure that if you are using a cache hint, it is a valid object. + * @see + * - [Experimental live content](https://astro.build/en/reference/experimental-flags/live-content/) + */ +export const InvalidCacheHintError = { + name: 'InvalidCacheHintError', + title: 'Invalid cache hint.', + message(collection: string, entryId: string | undefined, error: ZodError) { + return [ + `**${String(collection)}${entryId ? ` → ${String(entryId)}` : ''}** returned an invalid cache hint.\n`, + ...error.errors.map((zodError) => ` **${zodError.path.join('.')}**: ${zodError.message}`), + '', + ].join('\n'); + }, + hint: 'See https://docs.astro.build/en/reference/experimental-flags/live-content/ for more information.', +} satisfies ErrorData; + /** * @docs * @message diff --git a/packages/astro/src/types/public/content.ts b/packages/astro/src/types/public/content.ts index 9810ac1c480b..b1fac5712c38 100644 --- a/packages/astro/src/types/public/content.ts +++ b/packages/astro/src/types/public/content.ts @@ -125,16 +125,26 @@ export interface DataEntryType { export type GetDataEntryInfoReturnType = { data: Record; rawData?: string }; +export interface CacheHint { + /** Cache tags */ + tags?: Array; + /** Maximum age of the response in seconds */ + maxAge?: number; +} + export interface LiveDataEntry = Record> { /** The ID of the entry. Unique per collection. */ id: string; /** The parsed entry data */ data: TData; + /** A hint for how to cache this entry */ + cacheHint?: CacheHint; } export interface LiveDataCollection< TData extends Record = Record, > { entries: Array>; - // TODO: pagination etc. + /** A hint for how to cache this collection. Individual entries can also have cache hints */ + cacheHint?: CacheHint; } 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 c44259114788..e1d259a0fcb2 100644 --- a/packages/astro/test/fixtures/live-loaders/src/live.config.ts +++ b/packages/astro/test/fixtures/live-loaders/src/live.config.ts @@ -14,11 +14,21 @@ const entries = { const loader: LiveLoader = { name: 'test-loader', loadEntry: async (context) => { - return entries[context.filter.id] || null; + return { + ...(entries[context.filter.id] || null), + cacheHint: { + tags: [`page:${context.filter.id}`], + maxAge: 60, + }, + }; }, loadCollection: async (context) => { return { entries: Object.values(entries), + cacheHint: { + tags: ['page'], + maxAge: 60, + }, }; }, }; From 43160050efc36f62afd709f11ebfce3a8963caf1 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 8 May 2025 10:32:15 +0100 Subject: [PATCH 2/3] Tidy loader in fixture --- packages/astro/test/fixtures/live-loaders/src/live.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 e1d259a0fcb2..70f132ac39e0 100644 --- a/packages/astro/test/fixtures/live-loaders/src/live.config.ts +++ b/packages/astro/test/fixtures/live-loaders/src/live.config.ts @@ -14,8 +14,11 @@ const entries = { const loader: LiveLoader = { name: 'test-loader', loadEntry: async (context) => { + if(!entries[context.filter.id]) { + return; + } return { - ...(entries[context.filter.id] || null), + ...entries[context.filter.id], cacheHint: { tags: [`page:${context.filter.id}`], maxAge: 60, From 9c8beb8252d0c0d03551d49bbe43fe6870b30ce6 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 15 May 2025 11:52:59 +0100 Subject: [PATCH 3/3] Error description --- packages/astro/src/core/errors/errors-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 8482a0377885..016648da3229 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1682,7 +1682,7 @@ export const ContentEntryDataError = { * **maxAge**: Expected number, received string * @description * The loader for a live content collection returned an invalid cache hint. - * Make sure that if you are using a cache hint, it is a valid object. + * Make sure that `cacheHint` is an object with the correct properties, or is undefined. * @see * - [Experimental live content](https://astro.build/en/reference/experimental-flags/live-content/) */