summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/with-content/src/content/config.ts4
-rw-r--r--examples/with-content/src/content/types.generated.d.ts98
-rw-r--r--packages/astro/src/content/template/types.generated.d.ts21
-rw-r--r--packages/astro/src/content/utils.ts14
-rw-r--r--packages/astro/test/content-collections.test.js29
-rw-r--r--packages/astro/test/fixtures/content-collections/src/content/config.ts24
-rw-r--r--packages/astro/test/fixtures/content-collections/src/content/with-union-schema/newsletter.md6
-rw-r--r--packages/astro/test/fixtures/content-collections/src/content/with-union-schema/post.md7
-rw-r--r--packages/astro/test/fixtures/content-collections/src/pages/collections.json.js4
-rw-r--r--packages/astro/test/fixtures/content-collections/src/pages/entries.json.js4
-rw-r--r--packages/astro/test/fixtures/content-ssr-integration/src/content/config.ts4
-rw-r--r--packages/astro/test/fixtures/content-static-paths-integration/src/content/config.ts4
-rw-r--r--packages/astro/test/fixtures/content/src/content/config.ts4
13 files changed, 162 insertions, 61 deletions
diff --git a/examples/with-content/src/content/config.ts b/examples/with-content/src/content/config.ts
index 9d436060a..30cbbf293 100644
--- a/examples/with-content/src/content/config.ts
+++ b/examples/with-content/src/content/config.ts
@@ -2,7 +2,7 @@ import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Type-check frontmatter using a schema
- schema: {
+ schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
@@ -12,7 +12,7 @@ const blog = defineCollection({
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
heroImage: z.string().optional(),
- },
+ }),
});
export const collections = { blog };
diff --git a/examples/with-content/src/content/types.generated.d.ts b/examples/with-content/src/content/types.generated.d.ts
index a5247b7e4..7a253b039 100644
--- a/examples/with-content/src/content/types.generated.d.ts
+++ b/examples/with-content/src/content/types.generated.d.ts
@@ -3,17 +3,30 @@ declare module 'astro:content' {
export type CollectionEntry<C extends keyof typeof entryMap> =
(typeof entryMap)[C][keyof (typeof entryMap)[C]] & Render;
- type BaseCollectionConfig<S extends import('astro/zod').ZodRawShape> = {
+ type BaseSchemaWithoutEffects =
+ | import('astro/zod').AnyZodObject
+ | import('astro/zod').ZodUnion<import('astro/zod').AnyZodObject[]>
+ | import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
+ | import('astro/zod').ZodIntersection<
+ import('astro/zod').AnyZodObject,
+ import('astro/zod').AnyZodObject
+ >;
+
+ type BaseSchema =
+ | BaseSchemaWithoutEffects
+ | import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
+
+ type BaseCollectionConfig<S extends BaseSchema> = {
schema?: S;
slug?: (entry: {
id: CollectionEntry<keyof typeof entryMap>['id'];
defaultSlug: string;
collection: string;
body: string;
- data: import('astro/zod').infer<import('astro/zod').ZodObject<S>>;
+ data: import('astro/zod').infer<S>;
}) => string | Promise<string>;
};
- export function defineCollection<S extends import('astro/zod').ZodRawShape>(
+ export function defineCollection<S extends BaseSchema>(
input: BaseCollectionConfig<S>
): BaseCollectionConfig<S>;
@@ -30,7 +43,7 @@ declare module 'astro:content' {
): Promise<((typeof entryMap)[C][E] & Render)[]>;
type InferEntrySchema<C extends keyof typeof entryMap> = import('astro/zod').infer<
- import('astro/zod').ZodObject<Required<ContentConfig['collections'][C]>['schema']>
+ Required<ContentConfig['collections'][C]>['schema']
>;
type Render = {
@@ -42,44 +55,45 @@ declare module 'astro:content' {
};
const entryMap: {
- blog: {
- 'first-post.md': {
- id: 'first-post.md';
- slug: 'first-post';
- body: string;
- collection: 'blog';
- data: InferEntrySchema<'blog'>;
- };
- 'markdown-style-guide.md': {
- id: 'markdown-style-guide.md';
- slug: 'markdown-style-guide';
- body: string;
- collection: 'blog';
- data: InferEntrySchema<'blog'>;
- };
- 'second-post.md': {
- id: 'second-post.md';
- slug: 'second-post';
- body: string;
- collection: 'blog';
- data: InferEntrySchema<'blog'>;
- };
- 'third-post.md': {
- id: 'third-post.md';
- slug: 'third-post';
- body: string;
- collection: 'blog';
- data: InferEntrySchema<'blog'>;
- };
- 'using-mdx.mdx': {
- id: 'using-mdx.mdx';
- slug: 'using-mdx';
- body: string;
- collection: 'blog';
- data: InferEntrySchema<'blog'>;
- };
- };
+ "blog": {
+"first-post.md": {
+ id: "first-post.md",
+ slug: "first-post",
+ body: string,
+ collection: "blog",
+ data: InferEntrySchema<"blog">
+},
+"markdown-style-guide.md": {
+ id: "markdown-style-guide.md",
+ slug: "markdown-style-guide",
+ body: string,
+ collection: "blog",
+ data: InferEntrySchema<"blog">
+},
+"second-post.md": {
+ id: "second-post.md",
+ slug: "second-post",
+ body: string,
+ collection: "blog",
+ data: InferEntrySchema<"blog">
+},
+"third-post.md": {
+ id: "third-post.md",
+ slug: "third-post",
+ body: string,
+ collection: "blog",
+ data: InferEntrySchema<"blog">
+},
+"using-mdx.mdx": {
+ id: "using-mdx.mdx",
+ slug: "using-mdx",
+ body: string,
+ collection: "blog",
+ data: InferEntrySchema<"blog">
+},
+},
+
};
- type ContentConfig = typeof import('./config');
+ type ContentConfig = typeof import("./config");
}
diff --git a/packages/astro/src/content/template/types.generated.d.ts b/packages/astro/src/content/template/types.generated.d.ts
index 1579016db..73d6c21ab 100644
--- a/packages/astro/src/content/template/types.generated.d.ts
+++ b/packages/astro/src/content/template/types.generated.d.ts
@@ -3,17 +3,30 @@ declare module 'astro:content' {
export type CollectionEntry<C extends keyof typeof entryMap> =
(typeof entryMap)[C][keyof (typeof entryMap)[C]] & Render;
- type BaseCollectionConfig<S extends import('astro/zod').ZodRawShape> = {
+ type BaseSchemaWithoutEffects =
+ | import('astro/zod').AnyZodObject
+ | import('astro/zod').ZodUnion<import('astro/zod').AnyZodObject[]>
+ | import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
+ | import('astro/zod').ZodIntersection<
+ import('astro/zod').AnyZodObject,
+ import('astro/zod').AnyZodObject
+ >;
+
+ type BaseSchema =
+ | BaseSchemaWithoutEffects
+ | import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
+
+ type BaseCollectionConfig<S extends BaseSchema> = {
schema?: S;
slug?: (entry: {
id: CollectionEntry<keyof typeof entryMap>['id'];
defaultSlug: string;
collection: string;
body: string;
- data: import('astro/zod').infer<import('astro/zod').ZodObject<S>>;
+ data: import('astro/zod').infer<S>;
}) => string | Promise<string>;
};
- export function defineCollection<S extends import('astro/zod').ZodRawShape>(
+ export function defineCollection<S extends BaseSchema>(
input: BaseCollectionConfig<S>
): BaseCollectionConfig<S>;
@@ -30,7 +43,7 @@ declare module 'astro:content' {
): Promise<((typeof entryMap)[C][E] & Render)[]>;
type InferEntrySchema<C extends keyof typeof entryMap> = import('astro/zod').infer<
- import('astro/zod').ZodObject<Required<ContentConfig['collections'][C]>['schema']>
+ Required<ContentConfig['collections'][C]>['schema']
>;
type Render = {
diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts
index 8d52fd01e..a6fcd13b1 100644
--- a/packages/astro/src/content/utils.ts
+++ b/packages/astro/src/content/utils.ts
@@ -68,8 +68,20 @@ export async function getEntrySlug(entry: Entry, collectionConfig: CollectionCon
export async function getEntryData(entry: Entry, collectionConfig: CollectionConfig) {
let data = entry.data;
if (collectionConfig.schema) {
+ // TODO: remove for 2.0 stable release
+ if (
+ typeof collectionConfig.schema === 'object' &&
+ !('safeParseAsync' in collectionConfig.schema)
+ ) {
+ throw new AstroError({
+ title: 'Invalid content collection config',
+ message: `New: Content collection schemas must be Zod objects. Update your collection config to use \`schema: z.object({...})\` instead of \`schema: {...}\`.`,
+ hint: 'See https://docs.astro.build/en/reference/api-reference/#definecollection for an example.',
+ code: 99999,
+ });
+ }
// Use `safeParseAsync` to allow async transforms
- const parsed = await z.object(collectionConfig.schema).safeParseAsync(entry.data, { errorMap });
+ const parsed = await collectionConfig.schema.safeParseAsync(entry.data, { errorMap });
if (parsed.success) {
data = parsed.data;
} else {
diff --git a/packages/astro/test/content-collections.test.js b/packages/astro/test/content-collections.test.js
index 934f50017..66b4fdf36 100644
--- a/packages/astro/test/content-collections.test.js
+++ b/packages/astro/test/content-collections.test.js
@@ -103,6 +103,25 @@ describe('Content Collections', () => {
const slugs = json.withSlugConfig.map((item) => item.slug);
expect(slugs).to.deep.equal(['fancy-one.md', 'excellent-three.md', 'interesting-two.md']);
});
+
+ it('Returns `with union schema` collection', async () => {
+ expect(json).to.haveOwnProperty('withUnionSchema');
+ expect(Array.isArray(json.withUnionSchema)).to.equal(true);
+
+ const post = json.withUnionSchema.find((item) => item.id === 'post.md');
+ expect(post).to.not.be.undefined;
+ expect(post.data).to.deep.equal({
+ type: 'post',
+ title: 'My Post',
+ description: 'This is my post',
+ });
+ const newsletter = json.withUnionSchema.find((item) => item.id === 'newsletter.md');
+ expect(newsletter).to.not.be.undefined;
+ expect(newsletter.data).to.deep.equal({
+ type: 'newsletter',
+ subject: 'My Newsletter',
+ });
+ });
});
describe('Entry', () => {
@@ -130,6 +149,16 @@ describe('Content Collections', () => {
expect(json).to.haveOwnProperty('twoWithSlugConfig');
expect(json.twoWithSlugConfig.slug).to.equal('interesting-two.md');
});
+
+ it('Returns `with union schema` collection entry', async () => {
+ expect(json).to.haveOwnProperty('postWithUnionSchema');
+ expect(json.postWithUnionSchema.id).to.equal('post.md');
+ expect(json.postWithUnionSchema.data).to.deep.equal({
+ type: 'post',
+ title: 'My Post',
+ description: 'This is my post',
+ });
+ });
});
});
diff --git a/packages/astro/test/fixtures/content-collections/src/content/config.ts b/packages/astro/test/fixtures/content-collections/src/content/config.ts
index eadc52a74..f3b4e921a 100644
--- a/packages/astro/test/fixtures/content-collections/src/content/config.ts
+++ b/packages/astro/test/fixtures/content-collections/src/content/config.ts
@@ -2,23 +2,39 @@ import { z, defineCollection } from 'astro:content';
const withSlugConfig = defineCollection({
slug({ id, data }) {
+ console.log({id, data})
return `${data.prefix}-${id}`;
},
- schema: {
+ schema: z.object({
prefix: z.string(),
- }
+ }),
});
const withSchemaConfig = defineCollection({
- schema: {
+ schema: z.object({
title: z.string(),
isDraft: z.boolean().default(false),
lang: z.enum(['en', 'fr', 'es']).default('en'),
publishedAt: z.date().transform((val) => new Date(val)),
- }
+ })
+});
+
+const withUnionSchema = defineCollection({
+ schema: z.discriminatedUnion('type', [
+ z.object({
+ type: z.literal('post'),
+ title: z.string(),
+ description: z.string(),
+ }),
+ z.object({
+ type: z.literal('newsletter'),
+ subject: z.string(),
+ }),
+ ]),
});
export const collections = {
'with-slug-config': withSlugConfig,
'with-schema-config': withSchemaConfig,
+ 'with-union-schema': withUnionSchema,
}
diff --git a/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/newsletter.md b/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/newsletter.md
new file mode 100644
index 000000000..6e8703a1b
--- /dev/null
+++ b/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/newsletter.md
@@ -0,0 +1,6 @@
+---
+type: newsletter
+subject: My Newsletter
+---
+
+# It's a newsletter!
diff --git a/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/post.md b/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/post.md
new file mode 100644
index 000000000..fb260d664
--- /dev/null
+++ b/packages/astro/test/fixtures/content-collections/src/content/with-union-schema/post.md
@@ -0,0 +1,7 @@
+---
+type: post
+title: My Post
+description: This is my post
+---
+
+# It's a post!
diff --git a/packages/astro/test/fixtures/content-collections/src/pages/collections.json.js b/packages/astro/test/fixtures/content-collections/src/pages/collections.json.js
index 007f8a383..897f2ebdd 100644
--- a/packages/astro/test/fixtures/content-collections/src/pages/collections.json.js
+++ b/packages/astro/test/fixtures/content-collections/src/pages/collections.json.js
@@ -6,7 +6,9 @@ export async function get() {
const withoutConfig = stripAllRenderFn(await getCollection('without-config'));
const withSchemaConfig = stripAllRenderFn(await getCollection('with-schema-config'));
const withSlugConfig = stripAllRenderFn(await getCollection('with-slug-config'));
+ const withUnionSchema = stripAllRenderFn(await getCollection('with-union-schema'));
+
return {
- body: devalue.stringify({withoutConfig, withSchemaConfig, withSlugConfig}),
+ body: devalue.stringify({withoutConfig, withSchemaConfig, withSlugConfig, withUnionSchema}),
}
}
diff --git a/packages/astro/test/fixtures/content-collections/src/pages/entries.json.js b/packages/astro/test/fixtures/content-collections/src/pages/entries.json.js
index 141a7b7d1..7c9d8f1d4 100644
--- a/packages/astro/test/fixtures/content-collections/src/pages/entries.json.js
+++ b/packages/astro/test/fixtures/content-collections/src/pages/entries.json.js
@@ -6,7 +6,9 @@ export async function get() {
const columbiaWithoutConfig = stripRenderFn(await getEntry('without-config', 'columbia.md'));
const oneWithSchemaConfig = stripRenderFn(await getEntry('with-schema-config', 'one.md'));
const twoWithSlugConfig = stripRenderFn(await getEntry('with-slug-config', 'two.md'));
+ const postWithUnionSchema = stripRenderFn(await getEntry('with-union-schema', 'post.md'));
+
return {
- body: devalue.stringify({columbiaWithoutConfig, oneWithSchemaConfig, twoWithSlugConfig}),
+ body: devalue.stringify({columbiaWithoutConfig, oneWithSchemaConfig, twoWithSlugConfig, postWithUnionSchema}),
}
}
diff --git a/packages/astro/test/fixtures/content-ssr-integration/src/content/config.ts b/packages/astro/test/fixtures/content-ssr-integration/src/content/config.ts
index f32eba6de..d22a45648 100644
--- a/packages/astro/test/fixtures/content-ssr-integration/src/content/config.ts
+++ b/packages/astro/test/fixtures/content-ssr-integration/src/content/config.ts
@@ -1,7 +1,7 @@
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
- schema: {
+ schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.string().transform((str) => new Date(str)),
@@ -10,7 +10,7 @@ const blog = defineCollection({
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
heroImage: z.string().optional(),
- },
+ }),
});
export const collections = { blog };
diff --git a/packages/astro/test/fixtures/content-static-paths-integration/src/content/config.ts b/packages/astro/test/fixtures/content-static-paths-integration/src/content/config.ts
index f32eba6de..d22a45648 100644
--- a/packages/astro/test/fixtures/content-static-paths-integration/src/content/config.ts
+++ b/packages/astro/test/fixtures/content-static-paths-integration/src/content/config.ts
@@ -1,7 +1,7 @@
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
- schema: {
+ schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.string().transform((str) => new Date(str)),
@@ -10,7 +10,7 @@ const blog = defineCollection({
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
heroImage: z.string().optional(),
- },
+ }),
});
export const collections = { blog };
diff --git a/packages/astro/test/fixtures/content/src/content/config.ts b/packages/astro/test/fixtures/content/src/content/config.ts
index 5c37f2755..27c9d91b5 100644
--- a/packages/astro/test/fixtures/content/src/content/config.ts
+++ b/packages/astro/test/fixtures/content/src/content/config.ts
@@ -1,10 +1,10 @@
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
- schema: {
+ schema: z.object({
title: z.string(),
description: z.string().max(60, 'For SEO purposes, keep descriptions short!'),
- },
+ }),
});
export const collections = { blog };