Skip to content

graphql-jit breaks custom Temporal scalars when passed as a string literal #266

@blimmer

Description

@blimmer

Issue

I have a custom scalar defined for working with Temporal objects via the temporal-polyfill package.

import { GraphQLError, GraphQLScalarType, Kind, type GraphQLScalarTypeConfig } from "graphql";
import { Temporal } from "temporal-polyfill";

const parseDate = (date: string): Temporal.PlainDate => {
  return Temporal.PlainDate.from(date);
};

export const serializeDate = (date: Temporal.PlainDate): string => {
  return date.toString();
};
export const GraphQLTemporalDateConfig: GraphQLScalarTypeConfig<Temporal.PlainDate, string> = {
  name: "Date",
  description:
    "A date string, such as 2007-12-03, compliant with the `full-date` " +
    "format outlined in section 5.6 of the RFC 3339 profile of the " +
    "ISO 8601 standard for representation of dates and times using " +
    "the Gregorian calendar.",
  serialize(value) {
    if (value instanceof Temporal.PlainDate) {
      return value.toString();
    } else if (typeof value === "string") {
      return value;
    } else {
      throw new GraphQLError("Date cannot represent a non string, or non Date type " + JSON.stringify(value));
    }
  },
  parseValue(value) {
    if (!(typeof value === "string")) {
      throw new GraphQLError(`Date cannot represent non string type ${JSON.stringify(value)}`);
    }

    return Temporal.PlainDate.from(value);
  },
  parseLiteral(ast) {
    if (ast.kind !== Kind.STRING) {
      throw new GraphQLError(`Date cannot represent non string type ${"value" in ast && ast.value}`, { nodes: ast });
    }
    const { value } = ast;
    return Temporal.PlainDate.from(value);
  },
  extensions: {
    codegenScalarType: "Date | string",
    jsonSchema: {
      type: "string",
      format: "date",
    },
  },
};

/**
 * An RFC 3339 compliant date scalar.
 *
 * Input:
 *    This scalar takes an RFC 3339 date string as input and
 *    parses it to a javascript Date.
 *
 * Output:
 *    This scalar serializes javascript Dates and
 *    RFC 3339 date strings to RFC 3339 date strings.
 */
export const GraphQLTemporalDate = new GraphQLScalarType(GraphQLTemporalDateConfig);

I define this as a custom Date scalar in my schema:

import { createServer } from "node:http";
import { createSchema, createYoga } from "graphql-yoga";
import { GraphQLTemporalDate } from "./scalarResolvers/date.js";
import { Temporal } from "temporal-polyfill";
import { useGraphQlJit } from "@envelop/graphql-jit";

export const schema = createSchema({
  typeDefs: `
    scalar Date

    type Query {
      hello: String
    }

    type Mutation {
      testMutation(input: TestMutationInput!): TestMutationResult!
    }

    input TestMutationInput {
      date: Date!
    }

    type TestMutationResult {
      date: Date!
    }
  `,
  resolvers: {
    Date: GraphQLTemporalDate,
    Query: {
      hello: () => "world",
    },
    Mutation: {
      testMutation: (_, { input }) => {
        const { date } = input;
        if (!(date instanceof Temporal.PlainDate)) {
          throw new Error("Input should be a Temporal.PlainDate");
        }
        return { date };
      },
    },
  },
});

const yoga = createYoga({
  schema,
  graphiql: true,
  plugins: [
    useGraphQlJit(), // comment out this line to see the resolver working properly
  ],
});
const server = createServer(yoga);

server.listen(4000, () => {
  console.info("Server is running on http://localhost:4000/graphql");
});

When jit is enabled and I call the mutation with a string literal, like this:

mutation {
  testMutation(input: { date: "2018-01-01" }) {
    date
  }
}

My resolver fails because input.date is a string, not a Temporal.PlainDate.

When does it work?

  • If I disable jit, input.date is a Temporal.PlainDate.

  • If jit is enabled and I pass input.date as a variable, it works.

    mutation testMutation($input: TestMutationInput!) {
      testMutation(input: $input) {
        date
      }
    }
    {
      "input": {
        "date": "2018-01-01"
      }
    }

I have a repro case defined here: https://github.com/blimmer/graphql-jit-issue. The README has details for how to run the repro case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions