1

I am trying to set up the Content Collections for my blog in Astro. I almost immediately ran into problems with draft posts: they were missing some schema fields, and Zod didn't like that.

Digging into Zod's docs, I came up with z.discriminatedUnion, so the schema for "drafts" will be much looser than for published posts:

// src/content/config.ts
// (simplified example)

const publishedPostSchema = z.object({
  title: z.string(),
  author: z.enum(["John Doe", "Jane Doe"]),
  dateCreated: z.string().transform((str) => new Date(str)),
  description: z.string(),
  draft: z.literal(null),
});

const draftPostSchema = z.object({
  title: z.string().nullable(),
  author: z.enum(["John Doe", "Jane Doe"]).nullable(),
  dateCreated: z
    .string()
    .transform((str) => new Date(str))
    .nullable(),
  draft: z.literal(true),
});

const exampleBlogCollection = defineCollection({
  schema: z.discriminatedUnion("draft", [publishedPostSchema, draftPostSchema]),
});

export const collections = {
  blog: exampleBlogCollection,
};

This works, but I still have two minor issues:

 1. This forces me to have a literal null "draft" field on all published posts. I would prefer to enable null, false and undefined values, but z.discriminatedUnion accepts only literals on the discriminatory field. Is there a workaround to that?

 2. The resulting type is a discriminated union, but it still needs a lot of work to ensure the TypeScript tooling, that what am I passing down is not nullable:

This does not work (TypeScript errors):

const allPosts = await getCollection('blog')

const publishedPosts = rawPosts.filter(post => post.data.draft === null)

publishedPosts .sort(
  (a, b) =>
    +(b.data.dateCreated) - +(a.data.dateCreated) // TS error: `dateCreated` can be `null`…
);

This does work, but… it's kind of verbose:

const allPosts = await getCollection('blog')

type PublishedPost = CollectionEntry<"blog"> & { data: { draft: null }}


function isPublishedPost(post: typeof allPosts[number]): post is PublishedPost {
    return post.data.draft === null
}

const publishedPosts = allPosts.filter(isPublishedPost)

publishedPosts .sort(
  (a, b) =>
    +(b.data.dateCreated) - +(a.data.dateCreated) // O.K.
);

Is this the "expected" way to deal with schemas, or am I missing something important?

HynekS
  • 2,738
  • 1
  • 19
  • 34

0 Answers0