网站搜索

zod - 如何为“草稿”和“已发布”文章创建单独的模式,以及如何让 TypeScript 轻松区分它们(Astro v2 内容集合)


我正在尝试为我的博客在Astro中设置内容集合(Content Collections)。我几乎立刻就遇到了草稿文章的问题:它们缺少一些模式字段,Zod对此不满意。

深入研究 Zod 的文档后,我想到了 z.discriminatedUnion,因此“草稿”的架构将比已发布的帖子宽松得多:

// 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,
};

这可行,但我仍有两个小问题:

1. 这迫使我在所有已发布的文章上都必须有一个字面量的 null “draft” 字段。我更希望能够启用 null、false 和 undefined 值,但 z.discriminatedUnion 在判别字段上只接受字面量。有解决方法吗?

2. 生成的类型是一个判别联合类型,但仍然需要很多工作来确保 TypeScript 工具能确认我传递下去的内容不是可空的:

这不起作用(TypeScript 错误):

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`…
);

这确实有效,但是……有点啰嗦:

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.
);

这是处理模式的“预期”方式吗,还是我遗漏了什么重要的东西?