Skip to content

Content schema errors starting with v5.7.7 #13713

@qtzar

Description

@qtzar

Astro Info

Astro                    v5.7.7
Node                     v23.11.0
System                   macOS (arm64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             @astrojs/markdoc
                         astro-icon
                         pagefind
                         relative-links
                         astro-expressive-code
                         @astrojs/mdx

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

After upgrading to Astro 5.7.7 my config.ts file no longer works. I feel this is due to changes in #13706

My schema has about 5 .refines() blocks on it which were working correctly in Astro 5.7.5. If I remove the .refines blocks ( or reduce it to a single .refines() ) then the file is ok and my build works.

When the multiple .refines() blocks are present this is the error I get when attempting a build.

Type 'ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodObject<{ title: ZodString; description: ZodString; date: ZodString; status: ZodEnum<["Draft", "Proposed", "Rejected", "Accepted", "Superseded", "Deprecated", "Open", "Closed", "Published"]>; ... 20 more ...; supersededBy: ZodOptional<...>; }, "pass...' is not assignable to type 'BaseSchema | ((context: SchemaContext) => BaseSchema) | ((context: SchemaContext) => BaseSchema) | ((context: SchemaContext) => BaseSchema) | undefined'.
  Type 'ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodObject<{ title: ZodString; description: ZodString; date: ZodString; status: ZodEnum<["Draft", "Proposed", "Rejected", "Accepted", "Superseded", "Deprecated", "Open", "Closed", "Published"]>; ... 20 more ...; supersededBy: ZodOptional<...>; }, "pass...' is not assignable to type 'ZodEffects<BaseSchemaWithoutEffects, any, any>'.

Here is the collection definition

const records = defineCollection({
	loader: glob({ base: './docs', pattern: '**/*.{md,mdx}' }),
	schema: z.object({
			title: z.string(),
			description: z.string(),
			date: z.string().date(),

			status: z.enum([
				'Draft',
				'Proposed',
				'Rejected',
				'Accepted',
				'Superseded',
				'Deprecated',
				'Open',
				'Closed',
				'Published',
			]),
			type: z.enum(['Principle', 'Decision', 'RFC']),
			domain: z.enum(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']),

			portfolio: z.string().optional(),
			team: z.string().optional(),
			outcome: z.string().optional(),

			authors: z.array(z.string()),

			tags: z.array(z.string()).optional(),

			decisionDate: z.string().date().optional(),
			deciders: z.array(z.string()).optional(),

			linkedRecords: z.array(z.string()).optional(),
			supersedes: z.string().optional(),
			supersededBy: z.string().optional(),
		})

		// Allow additional properties
		.passthrough()

		// Require 'deciders' array to have at least one value if status is in the specified list
		.refine(
			(data) => {
				if (['Accepted', 'Superseded', 'Deprecated'].includes(data.status)) {
					return Array.isArray(data.deciders) && data.deciders.length > 0;
				}
				return true;
			},
			{
				message: 'At least one decider is required when status is Accepted, Superseded, or Deprecated',
				path: ['deciders'], // Attach error to 'deciders' field
			},
		)

		// Require 'decisionDate' to have a value if status is in the specified list
		.refine(
			(data) => {
				if (['Accepted', 'Superseded', 'Deprecated'].includes(data.status)) {
					return data.decisionDate != null;
				}
				return true;
			},
			{
				message: 'The decisionDate is required when status is Accepted, Superseded, or Deprecated',
				path: ['decisionDate'], // Attach error to 'deciders' field
			},
		)

		// When a decision is superseded then you need to fill in alink to the new decision.
		.refine(
			(data) => {
				if (data.status === 'Superseded') {
					return data.supersededBy != null; // Ensures supersededBy is not null or undefined
				}
				return true;
			},
			{
				message: 'When status is Superseded, the supersededBy field must be filled in.',
				path: ['supersededBy'],
			},
		)


		// Ensure that if type is RFC, status can only be Draft, Open, or Closed
		.refine(
			(data) => {
				if (data.type === 'RFC') {
					return ['Draft', 'Open', 'Closed'].includes(data.status);
				}
				return true;
			},
			{
				message: 'RFC type must have a status of Draft, Open, or Closed.',
				path: ['status'], // Attach error to 'status' field
			},
		)

		// Ensure that if type is Decision or Principle, status can only be Draft, Proposed, Accepted, Rejected, Superseded or Deprecated.
		.refine(
			(data) => {
				if (data.type === 'Decision' || data.type === 'Principle') {
					return ['Draft', 'Proposed', 'Accepted', 'Rejected', 'Superseded', 'Deprecated'].includes(data.status);
				}
				return true;
			},
			{
				message:
					'Decisions & Principles must have a status of Draft, Proposed, Accepted, Rejected, Superseded or Deprecated.',
				path: ['status'], // Attach error to 'status' field
			},
		)
});

What's the expected result?

Expect to be able to define as many .refines() blocks as needed for the zodiac schema.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-ra6p4eg4

Participation

  • I am willing to submit a pull request for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageIssue needs to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions