diff --git a/src/content/docs/en/reference/content-loader-reference.mdx b/src/content/docs/en/reference/content-loader-reference.mdx index 80a59aeaecc6a..6628994dca441 100644 --- a/src/content/docs/en/reference/content-loader-reference.mdx +++ b/src/content/docs/en/reference/content-loader-reference.mdx @@ -199,9 +199,9 @@ export function myLoader(options: { url: string, apiKey: string }): Loader { name: "feed-loader", load: async ({ store, parseData }) => { const feed = await loadFeedData(feedUrl, options.apiKey); - + store.clear(); - + for (const item of feed.items) { const id = item.guid; const data = await parseData({ @@ -212,8 +212,8 @@ export function myLoader(options: { url: string, apiKey: string }): Loader { id, data, }); - }, - }, + } + }, // 4. Define the schema of an entry. schema: z.object({ // ... @@ -246,7 +246,7 @@ For simple data fetches that do not need custom data store handling, validation, The function can be async and must return either an array of entries that each contain a unique `id` field, or an object where each key is a unique ID and each value is the entry. -This pattern provides a convenient shorthand to accomplish the basic tasks normally performed by the `load()` function to [load collections into the data store](/#loading-collections-into-the-data-store). At build-time, the loader will automatically clear the data store and reload all the entries. No further customization options or helpers for data handling are provided. +This pattern provides a convenient shorthand to accomplish the basic tasks normally performed by the `load()` function to [load collections into the data store](#loading-collections-into-the-data-store). At build-time, the loader will automatically clear the data store and reload all the entries. No further customization options or helpers for data handling are provided. These loaders are often simple enough that you may choose to define them inline in the `src/content.config.ts` file: @@ -281,7 +281,7 @@ Most live loaders will export a function that accepts configuration options and To return data about your collection, you must provide a [`loadCollection()`](#loadcollection) function that fetches data, and returns an array of content [`entries`](#entries-1) or an error. -To return a single live colledction entry, you must provide a [`loadEntry()`](#loadentry) function that fetches data filtered for a given `id`, and returns a single [`entry`](#livedataentry), `undefined`, or an error. +To return a single live collection entry, you must provide a [`loadEntry()`](#loadentry) function that fetches data filtered for a given `id`, and returns a single [`entry`](#livedataentry), `undefined`, or an error. The data fetching for both of these functions is typically done using a [`try...catch` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) to [handle errors when accessing live data](#error-handling-in-live-loaders). @@ -302,11 +302,19 @@ import { fetchFromCMS } from './cms-client.js'; interface Article { id: string; title: string; - content: string; + htmlContent: string; author: string; } -export function articleLoader(config: { apiKey: string }): LiveLoader
{ +interface EntryFilter { + id: string; +} + +interface CollectionFilter { + author?: string; +} + +export function articleLoader(config: { apiKey: string }): LiveLoader { return { name: 'article-loader', loadCollection: async ({ filter }) => { @@ -325,7 +333,7 @@ export function articleLoader(config: { apiKey: string }): LiveLoader
{ }; } catch (error) { return { - error: new Error(`Failed to load articles: ${error.message}`), + error: new Error('Failed to load articles', { cause: error }), }; } }, @@ -353,7 +361,7 @@ export function articleLoader(config: { apiKey: string }): LiveLoader
{ }; } catch (error) { return { - error: new Error(`Failed to load article: ${error.message}`), + error: new Error('Failed to load article', { cause: error }), }; } }, @@ -391,12 +399,12 @@ Astro will generate some errors itself, depending on the response from the live ```ts title="my-loader.ts" {10-12} import type { LiveLoader } from 'astro/loaders'; import type { MyData } from "./types"; -import { MyLoaderError } from './errors.js'; +import { MyLoaderError } from './errors'; -export function myLoader(config): LiveLoader { +export function myLoader(config): LiveLoader { return { name: 'my-loader', - loadCollection: async ({ filter }) => { + loadCollection: async () => { // Return your custom error type return { error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), @@ -422,10 +430,10 @@ export class MyLoaderError extends Error { } } -export function myLoader(config): LiveLoader { +export function myLoader(config): LiveLoader { return { name: 'my-loader', - loadCollection: async ({ filter }) => { + loadCollection: async () => { // Return your custom error type return { error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), @@ -531,9 +539,9 @@ Live loaders can provide cache hints to help with response caching. You can use ```ts title="my-loader.ts" import type { LiveLoader } from "astro/loaders"; import { loadStoreProduct, loadStoreProducts, getLastModifiedDate } from "./store"; -import type { MyData } from "./types"; +import type { Product, ProductEntryFilter, ProductCollectionFilter } from "./types"; -export function myLoader(config): LiveLoader { +export function myLoader(config): LiveLoader { return { name: 'cached-loader', loadCollection: async ({ filter }) => { @@ -607,7 +615,7 @@ Cache hints only provide values that can be used in other parts of your project Loaders can be defined in your site or as a separate npm package. If you want to share your loader with the community, you can [publish it to npm with the `withastro` and `astro-loader` keywords](/en/reference/publish-to-npm/#packagejson-data). -The live loader should export a function that returns the `LiveLoader` object, allowing users to configure it with their own settings. +A loader should export a function that returns a `LiveLoader` object for live loaders or a `Loader` object for build-time loaders, allowing users to configure it with their own settings. ## Object loader API @@ -626,7 +634,7 @@ The loader object has the following properties: A unique name for the loader, used in logs and [for conditional loading](/en/reference/integrations-reference/#refreshcontent-option). -#### `load` +#### `load()`

@@ -739,7 +747,7 @@ return { }; ``` -#### `parseData` +#### `parseData()`

@@ -777,7 +785,7 @@ export function feedLoader({ url }): Loader { } ``` -#### `renderMarkdown` +#### `renderMarkdown()`

@@ -816,7 +824,7 @@ export function myLoader(settings): Loader { } ``` -#### `generateDigest` +#### `generateDigest()`

@@ -916,7 +924,7 @@ export function myLoader(options: { url: string }): Loader { The data store is a loader's interface to the content collection data. It is a key-value (KV) store, scoped to the collection, and therefore a loader can only access the data for its own collection. -#### `get` +#### `get()`

@@ -931,7 +939,7 @@ const existingEntry = store.get("my-entry"); The returned object is a [`DataEntry`](#dataentry) object. -#### `set` +#### `set()`

@@ -959,16 +967,16 @@ Used after data has been [validated and parsed](#parsedata) to add an entry to t } ``` -#### `entries` +#### `entries()`

-**Type**: `() => Array<[id: string, DataEntry]>` +**Type**: () => Array\<[id: string, DataEntry]\>

Get all entries in the collection as an array of key-value pairs. -#### `keys` +#### `keys()`

@@ -977,16 +985,16 @@ Get all entries in the collection as an array of key-value pairs. Get all the keys of the entries in the collection. -#### `values` +#### `values()`

-**Type**: `() => Array` +**Type**: () => Array\<DataEntry\>

Get all entries in the collection as an array. -#### `delete` +#### `delete()`

@@ -995,7 +1003,7 @@ Get all entries in the collection as an array. Delete an entry from the store by its ID. -#### `clear` +#### `clear()`

@@ -1004,7 +1012,7 @@ Delete an entry from the store by its ID. Clear all entries from the collection. -#### `has` +#### `has()`

@@ -1024,7 +1032,7 @@ This is the type of the object that is stored in the data store. It has the foll **Type**: `string`

-An identifier for the entry, which must be unique within the collection. This is used to look up the entry in the store and is the key used with `getEntry` for that collection. +An identifier for the entry, which must be unique within the collection. This is used to look up the entry in the store and is the key used with [`getEntry()`](/en/reference/modules/astro-content/#getentry) for that collection. #### `data` @@ -1035,7 +1043,7 @@ An identifier for the entry, which must be unique within the collection. This is The actual data for the entry. When a user accesses the collection, this will have TypeScript types generated according to the collection schema. -It is the loader's responsibility to use [`parseData`](#parsedata) to validate and parse the data before storing it in the data store: no validation is done when getting or setting the data. +It is the loader's responsibility to use [`parseData()`](#parsedata) to validate and parse the data before storing it in the data store: no validation is done when getting or setting the data. #### `filePath` @@ -1104,15 +1112,29 @@ If the entry has Markdown content then you can use the [`renderMarkdown()`](#ren ## Live loader API +

+ This section shows the API for defining a [live loader](#live-loaders). ### The `LiveLoader` object -A generic type to provide [type safety in your loader](/en/guides/content-collections/#type-safety-in-live-loaders). This describes an object and accepts four type parameters to describe, in this order: your data structure, your filters when querying a collection, your filters when querying a single entry, and errors. +

-This object contains the following properties: +**Type:** `LiveLoader` +

-#### `name` +A live loader function returns an object with [three required live loader properties](#live-loader-properties). In addition to providing a name for the loader, this object describes how to fetch both single entries and an entire collection from your live data source. + +Use the `LiveLoader` generic type to provide [type safety in your loader](/en/guides/content-collections/#type-safety-in-live-loaders). This type accepts the following type parameters, in this order: + +- **`TData`** (defaults to `Record`): The data structure of each entry returned by the loader. +- **`TEntryFilter`** (defaults to `never`): The filter object type accepted by [`getLiveEntry()`](/en/reference/modules/astro-content/#getliveentry) and accessible in [`loadEntry()`](#loadentry). Use `never` when you don't support filtering single entries. +- **`TCollectionFilter`** (defaults to `never`): The filter object type accepted by [`getLiveCollection()`](/en/reference/modules/astro-content/#getlivecollection) and accessible in [`loadCollection()`](#loadcollection). Use `never` when you don't support filtering collections. +- **`TError`** (defaults to `Error`): A [custom `Error` class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types) that can be returned by the loader for more granular error handling. + +#### Live loader properties + +##### `name`

@@ -1122,77 +1144,80 @@ This object contains the following properties: A unique name for the loader, used in logs. -#### `loadCollection()` +##### `loadCollection()`

-**Type:** `(context: LoadCollectionContext) => Promise | { error: TError; }>` +**Type:** (context: LoadCollectionContext\) => Promise\<LiveDataCollection\ | \{ error: TError; \}\>

Defines a method to load a collection of entries. This function receives a [context object](#loadcollectioncontext) containing an optional `filter` property and must return the data associated with this collection or the errors. -#### `loadEntry()` +##### `loadEntry()`

-**Type:** `(context: LoadEntryContext) => Promise | undefined | { error: TError; }>` +**Type:** (context: LoadEntryContext\) => Promise\<LiveDataEntry\ | undefined | \{ error: TError; \}\>

Defines a method to load a single entry. This function receives a [context object](#loadentrycontext) containing a `filter` property and returns either the data associated with the requested entry, `undefined` when the entry cannot be found, or the errors. ### `LoadCollectionContext` +

+ +**Type:** `{ filter?: TCollectionFilter; }` +

+ This object is passed to the [`loadCollection()` method](#loadcollection) of the loader and contains the following properties: #### `filter`

-**Type:** `Record` +**Type:** `Record | never`
+**Default:** `never`

-An object describing the filters supported by your loader. +An object describing the [filters supported by your loader](#defining-custom-filter-types). ### `LoadEntryContext` +

+ +**Type:** `{ filter: TEntryFilter; }` +

+ This object is passed to the [`loadEntry()` method](#loadentry) of the loader and contains the following properties: #### `filter`

-**Type:** `Record` +**Type:** `Record | never`
+**Default:** `never`

-An object describing the filters supported by your loader. +An object describing the [filters supported by your loader](#defining-custom-filter-types). -### LiveDataEntry +### `LiveDataEntry` -```ts -export interface LiveDataEntry = Record> { - /** The ID of the entry. Unique per collection. */ - id: string; - /** The parsed entry data */ - data: TData; - /** Optional rendered content */ - rendered?: { - html: string; - }; - /** A hint for how to cache this entry */ - cacheHint?: CacheHint; -} -``` +

+ +**Type:** \{ id: string; data: TData; rendered?: \{ html: string \}; cacheHint?: CacheHint; \} +

This is the type object that is returned by the [`loadEntry()`](#loadentry) method. It contains the following properties: #### `id` +

**Type**: `string`

-An identifier for the entry, which must be unique within the collection. This is the key used with `getLiveEntry()` for that collection. +An identifier for the entry, which must be unique within the collection. This is the key used with [`getLiveEntry()`](/en/reference/modules/astro-content/#getliveentry) for that collection. #### `data` @@ -1205,12 +1230,11 @@ The actual data for the entry. When a user accesses the collection, this will ha It is the loader's responsibility to validate and parse the data before returning it. - #### `rendered`

-**Type**: `{html: string}` +**Type**: `{ html: string }`

An object with an entry's rendered content if it has been rendered to HTML. For example, this can be the rendered content of a Markdown entry, or HTML from a CMS. @@ -1219,103 +1243,82 @@ If this field is provided, then [the `render()` function and `` compo If the loader does not return a `rendered` property for an entry, the `` component will render nothing. -If the entry has Markdown content then you can use the [`renderMarkdown()`](#rendermarkdown) function to generate this object from the Markdown string. - #### `cacheHint` -See [`CacheHint`](#cachehint-4). - -### `LiveDataCollection` - -```ts -export interface LiveDataCollection = Record> { - entries: Array>; - /** A hint for how to cache this collection. Individual entries can also have cache hints */ - cacheHint?: CacheHint; -} -``` -This is the type object that is returned by the [`loadCollection()`](#loadcollection) method. It contains the following properties: - -#### `entries` +

-An array of [`LiveDataEntry`](#livedataentry) objects. +**Type:** [`CacheHint`](#cachehint-2) +

-#### `cacheHint` +An optional object to provide a hint for how to cache this specific entry. -See [`CacheHint`](#cachehint-4). +### `LiveDataCollection` +

-### `LiveDataCollectionResult` +**Type:** \{ entries: Array\<LiveDataEntry\\>; cacheHint?: CacheHint; \} +

-```ts -export interface LiveDataCollectionResult< - TData extends Record = Record, - TError extends Error = Error, -> { - entries?: Array>; - error?: TError | LiveCollectionError; - cacheHint?: CacheHint; -} -``` +This is the type object that is returned by the [`loadCollection()`](#loadcollection) method. It contains the following properties: #### `entries` -An array of [`LiveDataEntry`](#livedataentry) objects returned by the loader. +

-#### `error` +**Type:** Array\<LiveDataEntry\\> +

-The error returned if the loader failed to load the collection. +An array of [`LiveDataEntry`](#livedataentry) objects. #### `cacheHint` -See [`CacheHint`](#cachehint-4). +

+**Type:** [`CacheHint`](#cachehint-2) +

-### `LiveDataEntryResult` +An optional object providing guidance on how to cache this collection. This object will be merged with the cache hints defined for each individual entry, if provided. -```ts -export interface LiveDataEntryResult< - TData extends Record = Record, - TError extends Error = Error, -> { - entry?: LiveDataEntry; - error?: TError | LiveCollectionError; - cacheHint?: CacheHint; -} -``` - -#### `entry` +### `CacheHint` -The [`LiveDataEntry`](#livedataentry) object returned by the loader. +An object that loaders can return through the `cacheHint` property in [`LiveDataCollection`](#livedatacollection) or [`LiveDataEntry`](#livedataentry) to provide hints to assist in caching the response. This contains the following properties: -#### `error` +#### `tags` -The error returned if the loader failed to load the entry. +

-#### `cacheHint` +**Type:** `Array` +

-See [`CacheHint`](#cachehint-4). +An array of string identifiers allowing fine-grained cache control. This allows you to group related content and selectively invalidate cached responses when specific content changes. +The following example defines cache hint tags for a collection of posts filtered by author: -### CacheHint +```ts +return { + /* ... */ + cacheHint: { + tags: ["posts", `posts-${filter.author}`], + }, +}; +``` -This is the type object returned by `cacheHint`. It contains the following properties: +#### `lastModified` -#### `tags` +

- // All fields are optional, and are combined with each entry's cache hints - // tags are merged from all entries - // +**Type:** `Date` +

-#### `lastModified` +The date of the last modification of the content (e.g., the last update of an entry in a collection). This can be used to set HTTP cache headers like [`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified) and [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since). -The most recent last modified time of all entries and the collection +The following example defines a cache hint for a single product using its last update date: ```ts -export interface CacheHint { - /** Cache tags */ - tags?: Array; - /** Last modified time of the content */ - lastModified?: Date; -} +return { + /* ... */ + cacheHint: { + lastModified: new Date(product.updatedAt) + }, +}; ``` diff --git a/src/content/docs/en/reference/modules/astro-content.mdx b/src/content/docs/en/reference/modules/astro-content.mdx index bf4a2510da1db..0062de74155e9 100644 --- a/src/content/docs/en/reference/modules/astro-content.mdx +++ b/src/content/docs/en/reference/modules/astro-content.mdx @@ -23,7 +23,7 @@ For features and usage examples, [see our content collections guide](/en/guides/ ## Imports from `astro:content` ```js -import { +import { z, defineCollection, defineLiveCollection, @@ -34,7 +34,7 @@ import { getEntries, reference, render - } from 'astro:content'; +} from 'astro:content'; ``` ### `defineCollection()` @@ -219,7 +219,7 @@ const draftBlogPosts = await getCollection('blog', ({ data }) => {

-**Type:** `(collection: string, filter?: LiveLoaderCollectionFilterType) => Promise` +**Type:** (collection: string, filter?: LiveLoaderCollectionFilterType) => Promise\<LiveDataCollectionResult\>

@@ -232,10 +232,10 @@ It returns all items in the collection by default, and accepts an optional `filt import { getLiveCollection } from 'astro:content'; // Get all `products` entries from your API -const allProducts = await getLiveCollection('products'); +const { entries: allProducts } = await getLiveCollection('products'); // Only return `products` that should be featured -const featuredProducts = await getLiveCollection('products', { featured: true }); +const { entries: featuredProducts } = await getLiveCollection('products', { featured: true }); --- ``` @@ -274,7 +274,7 @@ const enterpriseCaptainProfile = await getEntry(enterprisePost.data.captain);

-**Type:** `(collection: string, filter: string | LiveLoaderEntryFilterType) => Promise` +**Type:** (collection: string, filter: string | LiveLoaderEntryFilterType) => Promise\<LiveDataEntryResult\>

@@ -284,8 +284,8 @@ A function that retrieves a single live collection entry by collection name and --- import { getLiveEntry } from 'astro:content'; -const liveCollectionsPost = await getLiveEntry('blog', Astro.params.id); -const mattDraft = await getLiveEntry('blog', { +const { entry: liveCollectionsPost } = await getLiveEntry('blog', Astro.params.id); +const { entry: mattDraft } = await getLiveEntry('blog', { status: 'draft', author: 'matt', }); @@ -351,7 +351,7 @@ import type { CollectionEntry, CollectionKey, SchemaContext, - } from 'astro:content'; +} from 'astro:content'; ``` ### `CollectionEntry` @@ -468,3 +468,173 @@ const blog = defineCollection({ }), }); ``` + +## `astro` types + +```ts +import type { + LiveDataCollectionResult, + LiveDataEntryResult, +} from "astro"; +``` + +### `LiveDataCollectionResult` + +

+ +**Type:** \{ entries?: Array\<LiveDataEntry\\>; error?: TError | LiveCollectionError; cacheHint?: CacheHint; \}
+ +

+ +An object returned by [`getLiveCollection()`](#getlivecollection) containing the data fetched by the live loader. It has the following properties: + +#### `entries` + +

+ +**Type:** Array\<LiveDataEntry\\> | undefined +

+ +An array of [`LiveDataEntry`](/en/reference/content-loader-reference/#livedataentry) objects returned by the loader. + +The following example accesses the returned entries for a live collection named `products`: + +```astro title="src/pages/shop/index.astro" +--- +import { getLiveCollection } from 'astro:content'; + +const { entries: allProducts } = await getLiveCollection('products'); +--- +``` + +Learn how to [access live data](/en/guides/content-collections/#accessing-live-data) with guided explanations and example usage. + +#### `error` + +

+ +**Type:** `TError | LiveCollectionError | undefined` +

+ +An error returned when the loader failed to load the collection. This can be a custom error defined by the loader or a built-in error. + +The following example accesses the error returned when retrieving data from a live collection named `products`: + +```astro title="src/pages/shop/index.astro" +--- +import { getLiveCollection } from 'astro:content'; + +const { error } = await getLiveCollection('products'); +--- +``` + +Learn more about [error handling](/en/guides/content-collections/#error-handling) with guided explanations and example usage. + +#### `cacheHint` + +

+ +**Type:** CacheHint | undefined +

+ +An object providing guidance on how to cache this collection. + +The following example accesses the cache hint for entries in a live collection named `products` and uses it to set the response headers: + +```astro title="src/pages/shop/index.astro" +--- +import { getLiveCollection } from 'astro:content'; + +const { cacheHint } = await getLiveCollection('products'); + +if (cacheHint?.tags) { + Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(',')); +} +if (cacheHint?.lastModified) { + Astro.response.headers.set('Last-Modified', cacheHint.lastModified.toUTCString()); +} +--- +``` + +### `LiveDataEntryResult` + +

+ +**Type:** \{ entry?: LiveDataEntry\; error?: TError | LiveCollectionError; cacheHint?: CacheHint; }
+ +

+ +An object returned by [`getLiveEntry()`](#getliveentry) containing the data fetched by the live loader. It has the following properties: + +#### `entry` + +

+ +**Type:** LiveDataEntry\ | undefined +

+ +The [`LiveDataEntry`](/en/reference/content-loader-reference/#livedataentry) object returned by the loader. + +The following example accesses the requested entry in a live collection named `products`: + +```astro title="src/pages/shop/[id].astro" +--- +import { getLiveEntry } from 'astro:content'; + +const { entry } = await getLiveEntry('products', Astro.params.id); +--- +``` + +Learn how to [access live data](/en/guides/content-collections/#accessing-live-data) with guided explanations and example usage. + +#### `error` + +

+ +**Type:** `TError | LiveCollectionError | undefined` +

+ +An error returned when the loader failed to load the entry. This can be a custom error defined by the loader or a built-in error. + +The following example accesses the requested entry in a live collection named `products` and any error, and redirects to the 404 page if an error exists: + +```astro title="src/pages/shop/[id].astro" +--- +import { getLiveEntry } from 'astro:content'; + +const { entry, error } = await getLiveEntry('products', Astro.params.id); + +if (error) { + return Astro.redirect('/404'); +} +--- +

{entry.data.name}

+``` + +Learn more about [error handling](/en/guides/content-collections/#error-handling) with guided explanations and example usage. + +#### `cacheHint` + +

+ +**Type:** CacheHint | undefined +

+ +An object providing data that can be used to set response headers to inform a caching strategy. + +The following example accesses the cache hint for an entry in a live collection named `products` and uses it to set the response headers: + +```astro title="src/pages/shop/[id].astro" +--- +import { getLiveEntry } from 'astro:content'; + +const { cacheHint } = await getLiveEntry('products', Astro.params.id); + +if (cacheHint?.tags) { + Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(',')); +} +if (cacheHint?.lastModified) { + Astro.response.headers.set('Last-Modified', cacheHint.lastModified.toUTCString()); +} +--- +```