keystonejs / keystone

The superpowered headless CMS for Node.js — built with GraphQL and React
https://keystonejs.com
MIT License
9.26k stars 1.16k forks source link

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

Closed Thinkscape closed 5 months ago

Thinkscape commented 1 year ago

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. next@13.4.0+ 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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.363.0_@babel+core@7.22.8_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.363.0_@babel+core@7.22.8_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.363.0_@babel+core@7.22.8_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.363.0_@babel+core@7.22.8_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.363.0_@babel+core@7.22.8_@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/next@13.4.12_@babel+core@7.22.9_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/webpack/loaders/next-flight-loader/module-proxy.js:151:23)\n    at inputResolver (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+fields-document@8.0.0_@keystone-6+core@5.3.2/node_modules/@keystone-6/fields-document/dist/keystone-6-fields-document.esm.js:797:109)\n    at resolve (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+fields-document@8.0.0_@keystone-6+core@5.3.2/node_modules/@keystone-6/fields-document/dist/keystone-6-fields-document.esm.js:834:32)\n    at eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:967:86)\n    at resolveInputForCreateOrUpdate (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:1112:35)\n    at createSingle (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@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/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/graphql-errors-473725b1.esm.js:83:12)",
                    "    at getResolvedData (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:987:105)",
                    "    at async resolveInputForCreateOrUpdate (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:1112:29)",
                    "    at async createSingle (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:857:38)",
                    "    at async createOne (webpack-internal:///(rsc)/./node_modules/.pnpm/@keystone-6+core@5.3.2_@aws-sdk+signature-v4-crt@3.378.0_@babel+core@7.22.9_@prisma+generator_msm77dva2wee244pexolhg3lh4/node_modules/@keystone-6/core/dist/createSystem-095bd0da.esm.js:903:38)"
                ]
            }
Thinkscape commented 1 year ago

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 }),
});
borisno2 commented 1 year ago

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

Thinkscape commented 1 year ago

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 commented 1 year ago

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)

borisno2 commented 1 year ago

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.

Thinkscape commented 1 year ago

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

mmachatschek commented 1 year ago

@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

mstfash commented 9 months ago

@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 commented 9 months ago

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 ?