feat: add fetchContent to .astro

This commit is contained in:
bholmesdev 2022-10-24 11:02:28 -04:00
parent e018648545
commit a2218fa905
5 changed files with 123 additions and 14 deletions

View file

@ -11,3 +11,14 @@ export declare const contentMap: {
export declare const schemaMap: { export declare const schemaMap: {
// GENERATED_SCHEMA_MAP_ENTRIES // GENERATED_SCHEMA_MAP_ENTRIES
}; };
export declare function fetchContentByEntry<
C extends keyof typeof contentMap,
E extends keyof typeof contentMap[C]
>(collection: C, entryKey: E): Promise<typeof contentMap[C][E]>;
export declare function fetchContent<
C extends keyof typeof contentMap,
E extends keyof typeof contentMap[C]
>(
collection: C,
filter?: (data: typeof contentMap[C][E]['data']) => boolean
): Promise<typeof contentMap[C][keyof typeof contentMap[C]][]>;

View file

@ -1,3 +1,6 @@
import { z } from 'zod';
import { getFrontmatterErrorLine, errorMap } from 'astro/content-internals';
const NO_SCHEMA_MSG = (/** @type {string} */ collection) => const NO_SCHEMA_MSG = (/** @type {string} */ collection) =>
`${collection} does not have a ~schema file. We suggest adding one for type safety!`; `${collection} does not have a ~schema file. We suggest adding one for type safety!`;
@ -9,6 +12,39 @@ const defaultSchemaFile = (/** @type {string} */ collection) =>
resolve(defaultSchemaFileResolved); resolve(defaultSchemaFileResolved);
}); });
const getSchemaError = (collection) =>
new Error(`${collection}/~schema needs a named \`schema\` export.`);
async function parseEntryData(
/** @type {string} */ collection,
/** @type {string} */ entryKey,
/** @type {{ data: any; rawData: string; }} */ unparsedEntry,
/** @type {{ schemaMap: any }} */ { schemaMap }
) {
const defineSchemaResult = await schemaMap[collection];
if (!defineSchemaResult) throw getSchemaError(collection);
const { schema } = defineSchemaResult;
try {
return schema.parse(unparsedEntry.data, { errorMap });
} catch (e) {
if (e instanceof z.ZodError) {
const formattedError = new Error(
[
`Could not parse frontmatter in ${String(collection)}${String(entryKey)}`,
...e.errors.map((e) => e.message),
].join('\n')
);
formattedError.loc = {
file: 'TODO',
line: getFrontmatterErrorLine(unparsedEntry.rawData, String(e.errors[0].path[0])),
column: 1,
};
throw formattedError;
}
}
}
export const contentMap = { export const contentMap = {
// GENERATED_CONTENT_MAP_ENTRIES // GENERATED_CONTENT_MAP_ENTRIES
}; };
@ -16,3 +52,38 @@ export const contentMap = {
export const schemaMap = { export const schemaMap = {
// GENERATED_SCHEMA_MAP_ENTRIES // GENERATED_SCHEMA_MAP_ENTRIES
}; };
export async function fetchContentByEntry(
/** @type {string} */ collection,
/** @type {string} */ entryKey
) {
const entry = contentMap[collection][entryKey];
return {
id: entry.id,
slug: entry.slug,
body: entry.body,
data: await parseEntryData(collection, entryKey, entry, { schemaMap }),
};
}
export async function fetchContent(
/** @type {string} */ collection,
/** @type {undefined | (() => boolean)} */ filter
) {
const entries = Promise.all(
Object.entries(contentMap[collection]).map(async ([key, entry]) => {
return {
id: entry.id,
slug: entry.slug,
body: entry.body,
data: await parseEntryData(collection, key, entry, { schemaMap }),
};
})
);
if (typeof filter === 'function') {
return (await entries).filter(filter);
} else {
return entries;
}
}

View file

@ -42,6 +42,7 @@
"types": "./config.d.ts", "types": "./config.d.ts",
"default": "./config.mjs" "default": "./config.mjs"
}, },
"./content-internals": "./dist/content-internals/index.js",
"./app": "./dist/core/app/index.js", "./app": "./dist/core/app/index.js",
"./app/node": "./dist/core/app/node.js", "./app/node": "./dist/core/app/node.js",
"./client/*": "./dist/runtime/client/*", "./client/*": "./dist/runtime/client/*",

View file

@ -0,0 +1,26 @@
import { z } from 'zod';
const flattenPath = (path: (string | number)[]) => path.join('.');
export const errorMap: z.ZodErrorMap = (error, ctx) => {
if (error.code === 'invalid_type') {
const badKeyPath = JSON.stringify(flattenPath(error.path));
if (error.received === 'undefined') {
return { message: `${badKeyPath} is required.` };
} else {
return { message: `${badKeyPath} should be ${error.expected}, not ${error.received}.` };
}
}
return { message: ctx.defaultError };
};
// WARNING: MAXIMUM JANK AHEAD
export function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) {
console.log({ rawFrontmatter, frontmatterKey });
const indexOfFrontmatterKey = rawFrontmatter.indexOf(`\n${frontmatterKey}`);
if (indexOfFrontmatterKey === -1) return 0;
const frontmatterBeforeKey = rawFrontmatter.substring(0, indexOfFrontmatterKey + 1);
const numNewlinesBeforeKey = frontmatterBeforeKey.split('\n').length;
return numNewlinesBeforeKey;
}

View file

@ -190,8 +190,8 @@ async function getEntriesByCollection(
const id = path.relative(contentDir.pathname, filePath); const id = path.relative(contentDir.pathname, filePath);
const slug = entryKey.replace(/\.mdx?$/, ''); const slug = entryKey.replace(/\.mdx?$/, '');
const body = await fs.readFile(filePath, 'utf-8'); const body = await fs.readFile(filePath, 'utf-8');
const { data, matter: rawData } = parseFrontmatter(body, filePath); const { data, matter } = parseFrontmatter(body, filePath);
return [entryKey, { id, slug, body, data, rawData }]; return [entryKey, { id, slug, body, data, rawData: matter ?? '' }];
}) })
); );
} }