Skip to content

Migrate to zod 4 api#294

Open
PhilDL wants to merge 11 commits intomainfrom
migrate-to-zod-4-api
Open

Migrate to zod 4 api#294
PhilDL wants to merge 11 commits intomainfrom
migrate-to-zod-4-api

Conversation

@PhilDL
Copy link
Owner

@PhilDL PhilDL commented Jan 3, 2026

Closes #

Breaking Changes

TODO

Checklist

  • I have followed the steps listed in the Contributing guide.
  • If necessary, I have added documentation related to the changes made.
  • I have added or updated the tests related to the changes made.

Greptile Summary

This PR migrates the entire ts-ghost codebase from Zod 3 to Zod 4, affecting 57 files across the core API, admin API, and content API packages.

Key Changes

  • Import updates: All imports migrated from zod/v3 to zod (Zod 4 default export)
  • API changes: Parse calls updated from schema.parse(data) to z.parse(schema, data) or z4.parse(schema, data)
  • Type utilities: Replaced deprecated z.noUnrecognized with custom NoUnrecognizedKeys type
  • Schema definitions: Migrated from inline description options to .meta() method (e.g., z.string({ description: "..." })z.string().meta({ description: "..." }))
  • Validator shortcuts: Updated z.string().url() to z.url() and z.string().email() to z.email()
  • Deprecated APIs: Replaced z.string().nonempty() with z.string().min(1)
  • Test updates: Tests now expect runtime errors for invalid fields, reflecting stricter validation

Additional Changes

  • Fixed typo: 'Specifing' → 'Specifying' in schema descriptions
  • Added BrowseFetcher and ReadFetcher to public exports
  • Added debug logging support to browse-fetcher (console.log calls)
  • Enhanced include field handling with dot-notation filtering

Migration Consistency

The migration shows a mixed import strategy in api-composer.ts (importing both z from "zod" and z4 from "zod/v4/core"), which leads to inconsistent parse API usage across the file. The debug logging additions should be reviewed for production readiness.

Confidence Score: 4/5

  • This PR is largely safe to merge with minor style inconsistencies that should be addressed
  • The migration is comprehensive and follows Zod 4 patterns correctly. Tests have been updated appropriately. The score is 4 instead of 5 due to: (1) inconsistent parse API usage mixing z.parse() and z4.parse() in the same file, (2) debug console.log statements that may not be intended for production, and (3) mixed parsing styles (method chaining vs standalone function). These are style issues rather than functional bugs.
  • packages/ts-ghost-core-api/src/api-composer.ts needs parse API consistency, and packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts should review debug logging

Important Files Changed

Filename Overview
packages/ts-ghost-core-api/src/api-composer.ts Migrates to Zod 4 with mixed import strategy (z from zod and z4 from zod/v4/core). Changes parse API from schema.parse() to z.parse(schema, data) and z4.parse(). Updates type constraints and removes deprecated .nonempty() with .min(1).
packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts Replaces deprecated z.noUnrecognized with custom NoUnrecognizedKeys type. Adds debug logging capability and fixes field filtering to exclude 'count' field. Handles dot-notation in include keys.
packages/ts-ghost-core-api/src/fetchers/read-fetcher.ts Updates imports from zod/v3 to zod and replaces z.noUnrecognized with NoUnrecognizedKeys type. Adds dot-notation filtering for include keys.
packages/ts-ghost-admin-api/src/schemas/webhooks.ts Migrates schema definitions from Zod 3 to Zod 4 by replacing description options parameter with .meta() method. Changes z.string().url() to z.url() for URL validation.
packages/ts-ghost-content-api/src/posts/posts.test.ts Updates tests to expect runtime errors (throw) when invalid fields are passed, reflecting stricter Zod 4 validation behavior. Comments out field assertions.

@changeset-bot
Copy link

changeset-bot bot commented Jan 3, 2026

⚠️ No Changeset found

Latest commit: 5d7f81c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jan 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
ts-ghost-docs Ready Ready Preview, Comment Jan 4, 2026 6:24am

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

57 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. packages/ts-ghost-core-api/src/helpers/http-client.ts, line 25 (link)

    logic: Interface definition doesn't include the debug option added to the implementation

57 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

57 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 96 to 99
const parsedData = z.parse(this.config.createSchema, data);
const parsedOptions =
this.config.createOptionsSchema && options
? this.config.createOptionsSchema.parse(options)
? z4.parse(this.config.createOptionsSchema, options)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Inconsistent parse API usage - line 96 uses z.parse() while line 99 uses z4.parse(). Both schemas are from the same source (config), should use consistent API.

Suggested change
const parsedData = z.parse(this.config.createSchema, data);
const parsedOptions =
this.config.createOptionsSchema && options
? this.config.createOptionsSchema.parse(options)
? z4.parse(this.config.createOptionsSchema, options)
const parsedData = z4.parse(this.config.createSchema, data);
const parsedOptions =
this.config.createOptionsSchema && options
? z4.parse(this.config.createOptionsSchema, options)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ts-ghost-core-api/src/api-composer.ts
Line: 96:99

Comment:
**style:** Inconsistent parse API usage - line 96 uses `z.parse()` while line 99 uses `z4.parse()`. Both schemas are from the same source (config), should use consistent API.

```suggestion
    const parsedData = z4.parse(this.config.createSchema, data);
    const parsedOptions =
      this.config.createOptionsSchema && options
        ? z4.parse(this.config.createOptionsSchema, options)
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 202 to 211
if (options?.debug) {
console.log("_urlSearchParams", this._urlSearchParams);
}
const result = await this.httpClient.fetch({
resource: this.resource,
searchParams: this._urlSearchParams,
options,
});
if (options?.debug) {
console.log("result", result);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Debug console.log statements should be removed before production or use a proper debug logger instead of console.log.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ts-ghost-core-api/src/fetchers/browse-fetcher.ts
Line: 202:211

Comment:
**style:** Debug console.log statements should be removed before production or use a proper debug logger instead of console.log.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +127 to +129
const parsedData = z.parse(updateSchema, data);
const parsedOptions =
this.config.updateOptionsSchema && options ? this.config.updateOptionsSchema.parse(options) : {};
this.config.updateOptionsSchema && options ? z.parse(this.config.updateOptionsSchema, options) : {};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Inconsistent parse API - mixes z.string().min(1).parse() (method chaining) with z.parse() (standalone function). Should use consistent approach throughout.

Suggested change
const parsedData = z.parse(updateSchema, data);
const parsedOptions =
this.config.updateOptionsSchema && options ? this.config.updateOptionsSchema.parse(options) : {};
this.config.updateOptionsSchema && options ? z.parse(this.config.updateOptionsSchema, options) : {};
const cleanId = z4.parse(z.string().min(1), id);
const parsedData = z4.parse(updateSchema, data);
const parsedOptions =
this.config.updateOptionsSchema && options ? z4.parse(this.config.updateOptionsSchema, options) : {};

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ts-ghost-core-api/src/api-composer.ts
Line: 127:129

Comment:
**style:** Inconsistent parse API - mixes `z.string().min(1).parse()` (method chaining) with `z.parse()` (standalone function). Should use consistent approach throughout.

```suggestion
    const cleanId = z4.parse(z.string().min(1), id);
    const parsedData = z4.parse(updateSchema, data);
    const parsedOptions =
      this.config.updateOptionsSchema && options ? z4.parse(this.config.updateOptionsSchema, options) : {};
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile z.parse is used when we are in a class using generic $ZodType or $ZodObject interface/type (from core zod), these ones don't have method like parse, they have to be used with the top level z.parse. But when it's an actual ZodObject (from zod/v4) interface it has that method

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand! Thanks for clarifying the distinction. You're right that $ZodType and $ZodObject interfaces from zod/v4/core don't have instance methods like .parse(), so they need the top-level z.parse() function. However, actual ZodObject instances from the main zod package do have the .parse() method.

Looking at your code, I can see the mixed usage is intentional based on the type being used. For consistency within this method where you're working with the same schemas, here's the corrected approach:

Suggested change
const parsedData = z.parse(updateSchema, data);
const parsedOptions =
this.config.updateOptionsSchema && options ? this.config.updateOptionsSchema.parse(options) : {};
this.config.updateOptionsSchema && options ? z.parse(this.config.updateOptionsSchema, options) : {};
const cleanId = z4.parse(z.string().min(1), id);
const parsedData = z4.parse(updateSchema, data);
const parsedOptions =
this.config.updateOptionsSchema && options ? z4.parse(this.config.updateOptionsSchema, options) : {};

This keeps all the parsing in this method consistent using z4.parse() since you're dealing with the generic $ZodType/$ZodObject interfaces for updateSchema and updateOptionsSchema.

…o possible...

... to override it at the .fetch() level
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant