diff --git a/.changeset/cozy-masks-count.md b/.changeset/cozy-masks-count.md new file mode 100644 index 000000000000..e5286ac91216 --- /dev/null +++ b/.changeset/cozy-masks-count.md @@ -0,0 +1,34 @@ +--- +'astro': minor +--- + +Adds support for returning a Promise from the `parser()` option of the `file()` loader + +This enables you to run asynchronous code such as fetching remote data or using async parsers when loading files with the Content Layer API. + +For example: + +```js +import { defineCollection } from 'astro:content'; +import { file } from 'astro/loaders'; + +const blog = defineCollection({ + loader: file('src/data/blog.json', { + parser: async (text) => { + const data = JSON.parse(text); + + // Perform async operations like fetching additional data + const enrichedData = await fetch(`https://api.example.com/enrich`, { + method: 'POST', + body: JSON.stringify(data), + }).then(res => res.json()); + + return enrichedData; + }, + }), +}); + +export const collections = { blog }; +``` + +See [the `parser()` reference documentation](https://docs.astro.build/en/reference/content-loader-reference/#parser) for more information. diff --git a/packages/astro/src/content/loaders/file.ts b/packages/astro/src/content/loaders/file.ts index c1b4b2a075a6..8fa9c2e8cfdc 100644 --- a/packages/astro/src/content/loaders/file.ts +++ b/packages/astro/src/content/loaders/file.ts @@ -7,14 +7,14 @@ import { AstroError } from '../../core/errors/index.js'; import { posixRelative } from '../utils.js'; import type { Loader, LoaderContext } from './types.js'; +type ParserOutput = Record> | Array>; + interface FileOptions { /** * the parsing function to use for this data * @default JSON.parse or yaml.load, depending on the extension of the file * */ - parser?: ( - text: string, - ) => Record> | Array>; + parser?: (text: string) => Promise | ParserOutput; } /** @@ -54,7 +54,7 @@ export function file(fileName: string, options?: FileOptions): Loader { try { const contents = await fs.readFile(filePath, 'utf-8'); - data = parse!(contents); + data = await parse!(contents); } catch (error: any) { logger.error(`Error reading data from ${fileName}`); logger.debug(error.message); diff --git a/packages/astro/test/content-layer.test.js b/packages/astro/test/content-layer.test.js index 0dc171ceeddf..de48bba5c907 100644 --- a/packages/astro/test/content-layer.test.js +++ b/packages/astro/test/content-layer.test.js @@ -114,6 +114,14 @@ describe('Content Layer', () => { assert.deepEqual(ids, ['bluejay', 'robin', 'sparrow', 'cardinal', 'goldfinch']); }); + it('can use an async parser in `file()` loader', async () => { + assert.ok(json.hasOwnProperty('loaderWithAsyncParse')); + assert.ok(Array.isArray(json.loaderWithAsyncParse)); + + const ids = json.loaderWithAsyncParse.map((item) => item.data.id); + assert.deepEqual(ids, ['bluejay', 'robin', 'sparrow', 'cardinal', 'goldfinch']); + }); + it('Returns yaml `file()` loader collection', async () => { assert.ok(json.hasOwnProperty('yamlLoader')); assert.ok(Array.isArray(json.yamlLoader)); diff --git a/packages/astro/test/fixtures/content-layer/src/content.config.ts b/packages/astro/test/fixtures/content-layer/src/content.config.ts index d3a173ff654d..0c4d7e22c5eb 100644 --- a/packages/astro/test/fixtures/content-layer/src/content.config.ts +++ b/packages/astro/test/fixtures/content-layer/src/content.config.ts @@ -129,6 +129,21 @@ const birds = defineCollection({ }), }); +const birdsWithAsyncParse = defineCollection({ + loader: file('src/data/birds.json', { + parser: async (text) => { + await new Promise((resolve) => setTimeout(resolve, 10)); + return JSON.parse(text).birds; + }, + }), + schema: z.object({ + id: z.string(), + name: z.string(), + breed: z.string(), + age: z.number(), + }), +}); + const plants = defineCollection({ loader: file('src/data/plants.csv', { parser: (text) => { @@ -270,6 +285,7 @@ export const collections = { cats, fish, birds, + birdsWithAsyncParse, plants, numbers, numbersToml, diff --git a/packages/astro/test/fixtures/content-layer/src/pages/collections.json.js b/packages/astro/test/fixtures/content-layer/src/pages/collections.json.js index 0eed94939599..9b83e837ce90 100644 --- a/packages/astro/test/fixtures/content-layer/src/pages/collections.json.js +++ b/packages/astro/test/fixtures/content-layer/src/pages/collections.json.js @@ -32,7 +32,9 @@ export async function GET() { const tomlLoader = await getCollection('songs'); const nestedJsonLoader = await getCollection('birds'); - + + const loaderWithAsyncParse = await getCollection('birdsWithAsyncParse'); + const csvLoader = await getCollection('plants'); const rockets = await getCollection('rockets'); @@ -63,6 +65,7 @@ export async function GET() { yamlLoader, tomlLoader, nestedJsonLoader, + loaderWithAsyncParse, csvLoader, atlantis, spacecraft: spacecraft.map(({id}) => id).sort((a, b) => a.localeCompare(b)),