Skip to content

Conversation

@jeremydw
Copy link
Member

@jeremydw jeremydw commented Dec 3, 2025

In order to eventually add MCP and other tools for AI-assisted workflows we need to control the data that comes into the system and reject data that doesn't conform to the schemas (so MCP clients and AI-assist tools can be subject to validation flows and correct their issues).

  • Add a validateFields method that works by converting a Root CMS schema to a Zod schema, and then invokes Zod's validate method. Zod was chosen because it's used by genkit (https://genkit.dev/docs/models/#structured-output), and presumably we'll be able to easily convert it to the Firebase AI Schema (https://firebase.google.com/docs/reference/unity/class/firebase/a-i/schema) used by the Firebase AI package.
  • Add a new validate option to the SaveDraftDataOptions
  • Create an new updateDraftData method on Root CMS client that is similar to saveDraftData but targets a specific field (identified by its path - e.g., 'hero.title' or 'content.0.text'). Validation is also optional for this.

With these methods we'll be able to move to the next step which will be to add a simple MCP server with tools like "save doc" and "update doc" to allow MCP clients to update CMS content in a structured way.

@@ -0,0 +1,279 @@
import {z} from 'zod';
Copy link
Member

Choose a reason for hiding this comment

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

i'm not a fan of having a dependency on zod for this, it should be easy enough for us to write our own validators for field data.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated this to pull from the zod bundled with genkit - not sure if this follows the "spirit" of your request though. :P

The reason I chose this approach is because when using genkit, you can specify the ai.generate request with a response schema (defined in Zod schema): https://genkit.dev/docs/flows/#defining-and-calling-flows

This essentially guarantees that the AI response follows the structure outlined by the schema. So if we already have Zod in our project we could start using this immediately.

Now, unfortunately, I noticed that the firebase/ai package defines its own Schema and doesn't use Zod: https://github.com/firebase/firebase-js-sdk/blob/5511b4fa7de0e98f3a3b933645584ba758de4405/packages/ai/src/requests/schema-builder.ts#L34

But... this doesn't come with a validate method, so it's not too useful here. :(

Anyway, LMK if after seeing this you'd prefer to remove the dependency entirely.

Copy link
Member

Choose a reason for hiding this comment

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

for validation purposes via the cms client calls we should be able to validate our own data without the conversion to zod (which takes up extra cpu cycles and memory unnecessarily). if we need to convert to zod schemas for passing to genkit then that would make sense but i would consider that a separate call from data validation.

there's a future where we may remove genkit + zod entirely in which case the reason for the zod conversion here would be less clear.

Copy link
Member

Choose a reason for hiding this comment

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

for validation purposes via the cms client calls we should be able to validate our own data without the conversion to zod (which takes up extra cpu cycles and memory unnecessarily). if we need to convert to zod schemas for passing to genkit then that would make sense but i would consider that a separate call from data validation.

there's a future where we may remove genkit + zod entirely in which case the reason for the zod conversion here would be less clear.

Copy link
Member Author

Choose a reason for hiding this comment

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

done - updated to remove zod and wrote our own validators. the test is actually unchanged, so it should be easy to add a zod converter in the future if we need it with genkit, or a firebase/ai Schema converter too

@stevenle
Copy link
Member

can you resolve merge conflicts and then i'll submit?

@jeremydw
Copy link
Member Author

@stevenle - done; FYI, can you also review this commit 7bffcf8? This commit resolved the pnpm test command when invoked from the project root (which was previously failing). Not sure if it's the right fix, so pls review before merging

@stevenle
Copy link
Member

hmm yeah that doens't seem right, can you remove that commit from this PR and follow-up in a separate PR so we can thoroughly test that change?

@jeremydw
Copy link
Member Author

@stevenle - done, that's removed now. let's see if the tests pass on CI (for some reason, they weren't passing locally).

@stevenle
Copy link
Member

seeing a failure on ci but it doesn't seem related to that particular commit:

  5:13:13 PM [vite] (ssr) Error when evaluating SSR module /home/runner/work/rootjs/rootjs/packages/root-cms/dist/project.js: Cannot read properties of undefined (reading 'collection')
        at eval (/home/runner/work/rootjs/rootjs/examples/blog/collections/BlogPostsSandbox.schema.ts:8:66)

@jeremydw
Copy link
Member Author

Gemini suggested the noExternal update; but I worked with Claude who suggested a different update (now committed) where we changed splitting: true -> false. Here's what Claude said:

Summary

The issue was caused by a circular dependency introduced when the feat/validation branch was merged into main. Here's what happened:

Root Cause

When tsup bundled the root-cms package with splitting: false, it inlined the project.ts module code (which uses import.meta.glob with eager: true) directly into core.js. This created a circular dependency:

  1. Schema files (like BlogPostsSandbox.schema.ts) import {schema} from '@blinkk/root-cms'
  2. This loads core.js
  3. core.js (with splitting disabled) contained inlined project.ts code that eagerly loads all .schema.ts files via import.meta.glob
  4. Those schema files try to access schema.collection() before core.js has finished initializing
  5. Result: schema is undefined, causing the error: "Cannot read properties of undefined (reading 'collection')"

The Fix

Changed the tsup configuration in tsup.config.ts:25:

to:

This enables code splitting, which:

  • Keeps the dynamic import of project.js in client.ts truly dynamic
  • Prevents the project.ts code (with its eager import.meta.glob) from being inlined into core.js
  • Breaks the circular dependency by ensuring schema files can load and initialize before SCHEMA_MODULES tries to import them

The fix has been tested and all 8 test suites now pass successfully! ✅

@jeremydw
Copy link
Member Author

Just added a prod integration test that covers a few things we were concerned about related to the bundling and imports.

@stevenle
Copy link
Member

i'll go ahead and merge this now so we can test, hopefully no need to rollback.

@stevenle stevenle merged commit 46045c6 into main Jan 22, 2026
1 check passed
@stevenle stevenle deleted the feat/validation branch January 22, 2026 20:13
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.

3 participants