keystone icon indicating copy to clipboard operation
keystone copied to clipboard

Attempted to call validateAndNormalizeDocument() from the server [fields-document]

Open Thinkscape opened this issue 1 year ago • 9 comments

Summary

I believe this is a regression after #8403 @borisno2 After adding "use client" to packages/fields-document/src/validation.ts, things break server-side when validation is triggered by admin hooks.

    "@keystone-6/core": "^5.3.2",
    "@keystone-6/fields-document": "^8.0.0",
    "next": "^13.4.12",

Steps

  1. [email protected]+ app using App Router (default next.config.ts).
  2. A schema, including a list, with a field content: document({})
  3. A vanila /api/graphql route, such as this one.
  4. Go to admin
  5. Try to add an item to the list

Expected

I can add a list with document field.

Actual

GraphQLError: An error occured while resolving input fields.
  - Article.content: Attempted to call validateAndNormalizeDocument() from the server but validateAndNormalizeDocument is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
    at resolverError (webpack-internal:///(sc_server)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_w4vjmt6rg75wwrvq3lhneegeza/node_modules/@keystone-6/core/dist/graphql-errors-473725b1.esm.js:83:12)
    at getResolvedData (webpack-internal:///(sc_server)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_w4vjmt6rg75wwrvq3lhneegeza/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:987:105)
    at async resolveInputForCreateOrUpdate (webpack-internal:///(sc_server)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_w4vjmt6rg75wwrvq3lhneegeza/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:1112:29)
    at async createSingle (webpack-internal:///(sc_server)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_w4vjmt6rg75wwrvq3lhneegeza/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:857:38)
    at async createOne (webpack-internal:///(sc_server)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_w4vjmt6rg75wwrvq3lhneegeza/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:903:38)"
"extensions": {
                "code": "KS_RESOLVER_ERROR",
                "debug": [
                    {
                        "stacktrace": "Error: Attempted to call validateAndNormalizeDocument() from the server but validateAndNormalizeDocument is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\n    at Object.defineProperties.$$typeof.value (webpack-internal:///(rsc)/./node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]/node_modules/next/dist/build/webpack/loaders/next-flight-loader/module-proxy.js:151:23)\n    at inputResolver (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@keystone-6/fields-document/dist/keystone-6-fields-document.esm.js:797:109)\n    at resolve (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@keystone-6/fields-document/dist/keystone-6-fields-document.esm.js:834:32)\n    at eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:974:31)\n    at Array.map (<anonymous>)\n    at getResolvedData (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:967:86)\n    at resolveInputForCreateOrUpdate (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:1112:35)\n    at createSingle (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:857:44)\n    at async createOne (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:903:38)",
                        "message": "Attempted to call validateAndNormalizeDocument() from the server but validateAndNormalizeDocument is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component."
                    }
                ],
                "stacktrace": [
                    "GraphQLError: An error occured while resolving input fields.",
                    "  - Article.content: Attempted to call validateAndNormalizeDocument() from the server but validateAndNormalizeDocument is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.",
                    "    at resolverError (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/graphql-errors-473725b1.esm.js:83:12)",
                    "    at getResolvedData (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:987:105)",
                    "    at async resolveInputForCreateOrUpdate (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:1112:29)",
                    "    at async createSingle (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:857:38)",
                    "    at async createOne (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:903:38)"
                ]
            }

Thinkscape avatar Jul 28 '23 00:07 Thinkscape

Workaround

The only workaround I've found right now is to temporarily put the /api/graphql route in the legacy pages folder, i.e.

src/pages/api/graphql.ts

import { ApolloServer } from "@apollo/server";
import { startServerAndCreateNextHandler } from "@as-integrations/next";
import { getContext, getKeystoneSessionContext } from "../../keystone/context";

const apolloServer = new ApolloServer({
  schema: getContext().graphql.schema,
});

export default startServerAndCreateNextHandler(apolloServer, {
  context: async (req, res) => getKeystoneSessionContext({ req, res }),
});

Thinkscape avatar Jul 28 '23 00:07 Thinkscape

Hey @Thinkscape thanks for the info, just to confirm this is only when you use getContext in a custom GraphQL route handler in NextJS?

borisno2 avatar Jul 31 '23 06:07 borisno2

Hey @Thinkscape thanks for the info, just to confirm this is only when you use getContext in a custom GraphQL route handler in NextJS?

I don't fully understand what you mean by custom in this context.

My route handler is very similar to yours here, i'm also using @as-integrations/next and a version of:

const handler = startServerAndCreateNextHandler<NextRequest>(apolloServer, {
  context: async () => getServerActionContext(),
})

There's nothing special in the implementation.

It only breaks if I import { document } from "@keystone-6/fields-document", otherwise all works fine with other keystone field types.

Thinkscape avatar Aug 01 '23 10:08 Thinkscape

Tried reverting #8403 but then it breaks in other places 🤔 Maybe the validator needs to be split into two different versions? (one of them with "use client", and one meant for server-side)

Thinkscape avatar Aug 01 '23 10:08 Thinkscape

Yes, without #8403, it was impossible to use the document field with getContext with Next App Router, so #8403 was an initial step towards this. Still, you are correct, to support mutations via the NextJS App Router the validation needs to be fundamentally changed so that it doesn't require any client-only side code.

borisno2 avatar Aug 02 '23 22:08 borisno2

When solving similar problems in my apps, I'd usually extract the logic into a isomorphic lib/component, then import it from either server/client consumers. Quite common for stuff like validation, which works surprisingly smoothly with isomorphic zod schemas 🤔 (I use one schema across react-hook-form, gql mutation handlers, inngest functions etc.)

Thinkscape avatar Aug 03 '23 23:08 Thinkscape

@borisno2 I was just hit with this issue too, after using app router I'm unable to update fields which use the document KS fields

mmachatschek avatar Sep 26 '23 07:09 mmachatschek

@Thinkscape @borisno2 I am still having the same issue even tough I have the same code as @borisno2 but still gives me error when creating a new blog post for example, the error is:

An error occurred while resolving input fields.
  - blog.content: Attempted to call validateAndNormalizeDocument() from the server but validateAndNormalizeDocument is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.

That also giving me issue with contentBlocks which might be different matter but still document field is troublesome when used with Keystone-Nextjs monorepo

Do you guys have any solution for that ? Thanks in advance!


Update:

I have worked with pages router since it seems the only thing that works with document field and its customizations and everything works fine, but uploading image while disabling bodyParser of next js gives this weird error

TypeError: RequestInit: duplex option is required when sending a body.

Let me know if you guys have a solution for that! thanks

mstfash avatar Feb 05 '24 11:02 mstfash

Well this is weird, using pages router works with document field but messes my custom image upload based on @borisno2 example of vercel/blob but I am using uploadthing instead and upload gives me 2 errors:

1- This happens with graphql-yoga when having bodyParser as false

TypeError: RequestInit: duplex option is required when sending a body.

2- This happens when using @as-integrations instead of graphql-yoga

POST body missing, invalid Content-Type, or JSON object has no keys.

Any thing regarding that ?

mstfash avatar Feb 08 '24 13:02 mstfash