Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom scalars #368

Open
Akryum opened this issue Jul 27, 2018 · 87 comments
Open

Support for custom scalars #368

Akryum opened this issue Jul 27, 2018 · 87 comments
Labels
core Feature requests related to core functionality

Comments

@Akryum
Copy link

Akryum commented Jul 27, 2018

Migrated from: apollographql/apollo-client#585

@fbartho
Copy link

fbartho commented Jul 31, 2018

It'd be great if we had support for custom scalars in Apollo Client. A pretty crucial feature would be for custom scalars to be provided by native code. apollo-link-state users would particularly benefit from this.

The canonical example is Date objects (from ISOString or Unix Timestamp "raw" formats), but it would be valuable to vend other complex objects from a local object pool in certain circumstances beyond just dates.

In the original thread there was a reference that maybe a library-provided semi-static list of common scalars might be an acceptable compromise, but immediately, people would want momentjs dates instead of JS Dates.

@bebbi
Copy link

bebbi commented Sep 14, 2018

The thumbs up total of this and OPs of issues linking here are 22+15+37=74.
Most need json parse and date formats.

@brunoreis
Copy link

I'm thinking about creating a hoc where I can pass a query, the custom scalar type and a conversion function. I would compose that hoc adding it just after the query hoc so that it would convert data injected from that query.

Can someone point me how to inspect the schema of a specific query to detect what are the paths to the fields that are of that custom scalar type?

@jedwards1211
Copy link

@bebbi why do people need to parse json types on the client side? Unlike dates, json can be serialized just fine by server-side custom scalars and will be received as json by the client.

@jedwards1211
Copy link

jedwards1211 commented Nov 6, 2018

@brunoreis you may be able to use graphql's undocumented visitWithTypeInfo, which allows you to traverse the queries while keeping track of the type of each field.

However, it may not be ideal for use on the client because you have to construct a TypeInfo object from an entire GraphQLSchema. I don't know if it would support a partial schema containing only the bare minimum fields necessary to determine what to parse on the client.

Another option is to fetch the type metadata from the server and build a custom index to it; I did this in apollo-magic-refetch. But I had to write my own code to keep track of the types while traversing the query with visit, and there are edge cases I haven't taken care of yet.

I bet there are some hooks in the Apollo caching layer that would allow you to handle custom scalars anywhere in the schema this way, without having to wrap a bunch of your components in an HoC.

@couturecraigj
Copy link

couturecraigj commented Nov 10, 2018

@jedwards1211 wouldn't that defeat the purpose of a type system? Maybe somebody wants to store mixed data to graphql in certain instances. That is when they would use a JSON type. I know it is not ideal but geo-data for instance can be incrementally adopted through GeoJSON in JSON (mixed) data type that is just stored as a string. Not having to parse it each time the client receives it would be huge.

@jedwards1211
Copy link

@couturecraigj I'm not sure what you're thinking would defeat the purpose if a type system, I was suggesting that there's probably a way to make the apollo cache parse all raw values of a date type (a custom scalar in the schema that serialize to an ISO string on the wire) back into a Date object and store them as such in the cache.

@slorber
Copy link

slorber commented Nov 14, 2018

Hi,

In addition to custom scalar types, I want to add that it is very important for me that the choosen solution works with codegen tools. If my Date scalar is serialized as String and deserialized or injected as props as JS Date on the client, it's important the codegen tools give me a JS Date type in the end and not a string. This seems complicated to solve, but important to mention.

@bebbi
Copy link

bebbi commented Nov 14, 2018

@jedwards1211 In my case for example, I'd like not having to worry about de-serialization, because this seems to create multiple redundant places where I worry about the data model.

@jedwards1211
Copy link

@bebbi I not sure I fully understand you.
Without worrying about de-serialization, any date field in your schema will be received by the client as a string or number (whichever you choose to serialize it as in the custom scalar type on the server).

The only way to convert back to a date on the client is to have de-serialization code.

Of course it would be terrible to have to write code for every single date field or every query that requests a date field -- which is why I'm suggesting that if the client has enough information about the schema to know which fields are dates, it would be possible to write a single piece of code that deserializes any date field encountered in the schema.

@bebbi
Copy link

bebbi commented Nov 14, 2018

@jedwards1211 As far as I can understand we're on the same page.
It's json in my case. I've got various queries using fragments using that json type and would like de-serialization of that type to be controlled in one single place.

@jedwards1211
Copy link

jedwards1211 commented Nov 14, 2018

@bebbi so when your server loads the JSON it needs to send from the database or a file or whatever, is it a raw string instead of an object?

If you use graphql-type-json for the field and your resolver returns an object instead of a raw string, it will be sent to the client as an object and the client won't need to deserialize at all.

@gullitmiranda
Copy link

@jedwards1211 the https://github.com/taion/graphql-type-json works only to send json to the server, not the opposite.
what we need is to the client parse a json send by the server.

@jedwards1211
Copy link

jedwards1211 commented Nov 14, 2018

@gullitmiranda I don't understand why that's been your experience, seems to me something must be misconfigured. Here is proof that it's able to send json to the client in my project (the jsonFieldMappings field below uses graphql-type-json). You can see that the client (GraphiQL) receives it as an object instead of a raw string:

image

@gullitmiranda
Copy link

interesting @jedwards1211 . was not aware of this since I don't use the graphql-js on the server side. but from what I've been researching, this was not a standard that is accepted by the default implementation of the graphql, so many libs do not support or do not intend to support this.

some conversations:

@jedwards1211
Copy link

jedwards1211 commented Nov 17, 2018

even if it's not exactly standard, the GraphQL docs do say that

A standard GraphQL POST request should use the application/json content type, and include a JSON-encoded body

Regardless of the method by which the query and variables were sent, the response should be returned in the body of the request in JSON format

In most GraphQL service implementations, there is also a way to specify custom scalar types

So for built-in types, a standard GraphQL server is already serializing to and from JSON, and the most basic possible custom scalar system -- one that just hands you a JSON node representing the scalar in the request or accepts a JSON node from you to represent the variable in the response -- would only necessitate using the identity function for JSON custom scalar serialization/deserialization**.

Which is exactly what a commenter recently mentioned in the issue you linked:

scalar :raw_json do
  parse fn _, _ -> raise "not relevant" end
  serialize fn value -> value end
end

So in other words, any GraphQL server that supports custom scalars is capable of sending a field value that the client will receive as a JSON object instead of a string.

** The only caveat is that it also has to be possible to represent the value as a literal within the GraphQL query itself, not just within the JSON request/response body. But there is a clean mapping between JSON and GraphQL AST nodes, as demonstrated in graphql-type-json's parseLiteral function.

@jedwards1211
Copy link

@slorber do you have enough control over the codegen tools you use to tell them what JS type is associated with a given GraphQL custom scalar type?

@gullitmiranda
Copy link

ye @jedwards1211

Which is exactly what a commenter recently mentioned in the issue you linked:

i see and try to use in outputs and worked. don't work with input's for now, but it's no big deal.

@jedwards1211
Copy link

Cool. I don't know elixir but it looks like inputs may work if you make the parse fn return the argument instead of raising an error.

@gullitmiranda
Copy link

the parse really work, but the absinthe add errors because the typing.

  1) test create template mutation succeeds with valid params (MyAppWeb.Schema.MyContext.TemplateMutationTest)
     test/my_app_web/schema/my_context/mutations/template_mutation_test.exs:12
     match (=) failed
     code:  assert {:ok, data} = run(create_template_mutation(), variables: to_input(params), context: context)
     right: {:error,
             [
               %{
                 locations: [],
                 message: "Argument \"input\" has invalid value $input.\nIn field \"fields\": Expected type \"JSON\", found {key1: \"value\", key2: \"value2\", list1: [1, {keyInList: \"value_in_list\"}]}.\nIn field \"key1\": Unknown field.\nIn field \"key2\": Unknown field.\nIn field \"list1\": Unknown field."
               }
             ]}
     stacktrace:
       test/my_app_web/schema/my_context/mutations/template_mutation_test.exs:54: (test)

@dan-turner
Copy link

Any movement on this?

@jamiewinder
Copy link

I've noticed support for custom scalars is referenced in the GraphQL Tools docs, but as far as I can see it isn't in the pipeline for apollo-client (including 3.0, or at all). Could someone in the know confirm this?

@thomasporez-tinyclues
Copy link

This is definitely a must have ! Any news about appolo-client plans for support ?
In the meantime, did someone can point me to a workaround or something because it's so convenient !

@cmhuynh
Copy link

cmhuynh commented Sep 25, 2019

While a complete fix needs thoughtful architecture and discussion (from the original ticket and cloned to this), a fix for Date might be "isolated" from that, can benefit a lot.

Discussion of Date as a custom scalars can impact onboard experience to this library, like this

  • Found the library, follow Basics example, it runs so smoothly on CodeSandbox
  • The example work for string, simply change to Date and found it doesn't work.

Re-produce the issue at

I'd love to have Date supported, not custom type and therefore upvote this (part) the ticket.

@danielkcz
Copy link

danielkcz commented Oct 3, 2019

I don't get it. I went through 2 years old history discussing this and nothing viable seems to come out of it?

I wonder, when I look at the example from graphql-tools page, they are using resolvers, but we do have resolvers option when instantiating ApolloClient instance. Could it be possibly that simple to just add resolver? I am definitely going to try that in the following days.

import { makeExecutableSchema } from 'graphql-tools';
import GraphQLJSON from 'graphql-type-json';

const schemaString = `

scalar JSON

type Foo {
  aField: JSON
}

type Query {
  foo: Foo
}

`;

const resolveFunctions = {
  JSON: GraphQLJSON
};

const jsSchema = makeExecutableSchema({ typeDefs: schemaString, resolvers: resolveFunctions });

Edit: Nevermind, turns out that resolvers on ApolloClient is reserved for LocalState only :/

@danielkcz
Copy link

Ok, I think I've found fairly good approach, at least to handle ISO dates. With that link, I can easily transform iso date strings to Date instances before they get to components. It is surely slightly fragile as we need to remember to add more fields when they are used. Eventually, I want to make some simple generator based on the schema to remove that barrier.

https://github.com/with-heart/apollo-link-response-resolver

    BusinessHourInterval: {
      openAt: parseISO,
      closeAt: parseISO,
    },

For the reverse direction when I am using Date for the mutation variables, it actually works out of the box and dates are converted. Not sure why such "magic" exists only in one direction, though.

As for the TypeScript, we are using graphql-code-generator and that's simple scalars config to set proper types and works very nicely.

@eturino
Copy link

eturino commented Nov 30, 2019

I have been working on a custom ApolloLink to deal with this situation.

https://github.com/eturino/apollo-link-scalars

If you pass it a GraphQLSchema and optionally a map of parsing/serializing functions per scalar type, it will parse all scalars on the responses, and serialize all the scalars on the inputs.

It can optionally also validate enums, throwing an error if an invalid enum value is received.

By default, for each scalar, it will apply the parsing/serializing functions defined for that type in the GraphQLSchema. The functions passed in the typesMap argument take precedence.

As a small disclaimer: this is a POC. I just finished testing it on my team's app but I haven't tested performance.

import { withScalars } from "apollo-link-scalars"
import { ApolloLink } from "apollo-link";
import { HttpLink } from "apollo-link-http";
import { schema } from './my-schema'

const link = ApolloLink.from([
  withScalars({ schema }),
  new HttpLink({ uri: "http://example.org/graphql" })
]);

// we can also pass a custom map of functions. These will have priority over the GraphQLTypes parsing and serializing functions from the Schema.
const typesMap = {
  CustomScalar: {
    serialize: (parsed: CustomScalar) => parsed.toString(),
    parseValue: (raw: string | number | null): CustomScalar | null => {
      return raw ? new CustomScalar(raw) : null
    }
  }
};

const link2 = ApolloLink.from([
  withScalars({ schema, typesMap }),
  new HttpLink({ uri: "http://example.org/graphql" })
]);

//cc @FredyC

eturino referenced this issue in eturino/apollo-link-scalars Dec 1, 2019
eturino referenced this issue in eturino/apollo-link-scalars Dec 9, 2019
@alfechner
Copy link

There is FieldPolicy in Apollo Client 3.

It allows us to serialize/unserialize values when read/write from/to the cache.

It's on field rather than on type level. Thus one needs to configure the transformation for every field again. However it mitigates the problem of serializing the cache.

@phryneas
Copy link
Member

phryneas commented Mar 14, 2023

@rjdestigter the example above doesn't need custom scalar support from Apollo Client. You don't need any runtime code at all - this just exists on a type level.

Have your codegen output your type as

        scalars: {
          UserId: 'src/user/types#UserId_UsingSymbolAsBrand',
        },

(see docs for typescript-operations)
and define a type UserId_UsingSymbolAsBrand as

declare const tagSymbol: unique symbol
type UserId_UsingSymbolAsBrand = string & { [tagSymbol]: "UserId"}

From that moment on, all UserId data coming from queries will be of type UserId_UsingSymbolAsBrand on the type level while being a normal string on runtime level, without any extra overhead.

Tagged types are a type-level construct. That tagSymbol property doesn't really have to be there - it might even make things more problematic on runtime level, converting string values into String instances.

Edit: edited in the correct type referencing method shown further down by @floriancargoet

@rjdestigter
Copy link

Ah, ok. My FUD was how to refer in the to an imported type in codegen.yml but I resolved that by making it an import statement:

config:
      ...
      scalars:
         UserId: "import(\"src/user/types.ts\").UserId"

@floriancargoet
Copy link

floriancargoet commented Mar 14, 2023

@rjdestigter I can't find it in the docs but there's a syntax to magically import custom scalars in codegen.yml.

config:
  scalars:
    Date: types/custom-scalars#Date

This will be turned into import { Date } from "types/custom-scalars";

For your example, it would be:

config:
      scalars:
         UserId: src/user/types#UserId

@Xample
Copy link

Xample commented Apr 20, 2023

@jpvajda Hi, regarding the message you did post on the 15th of December. Our use case sounds straightforward but let's describe it as follows:

Our graphql schema has 2 scalars DateTime and PlainDate, as json data is sent over the line, I will get the serialized version of those on the client for instance 1977-04-22T06:00:00Z and 2022-01-01 respectively.

On the client side, I'm generating typescript types using codegen from the graphql schema so that everything is typed properly.

By default, any scalar unknown by codegen will be typed "any", here is an extract of the generated graphql.ts file

/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  /** A date containing the hour. Ex. 1977-04-22T06:00:00Z */
  DateTime: any;
  /** A date without an hour. Ex 2022-01-01 */
  PlainDate: any;
};

As is, in this particular case, I could without taking any risk configure codegen to type the string format of DateTime and PlainDate as a template literal.

For instance (dummy code generated for the example)

scalars.ts

type PlainDate = `${number}-${number}-${number}`;
type DateTime = `${number}-${number}-${number}T${number}:${number}:${number}.${number}`;

And let codegen know about those types using:

config:
      scalars:
         PlainDate: path/to/scalars#PlainDate
         DateTime: path/to/scalars#DateTime

So that the new generated file will be typed as follows

export type Scalars = {
  ...
  /** A date containing the hour. Ex. 1977-04-22T06:00:00Z */
  DateTime: DateTime;
  /** A date without an hour. Ex 2022-01-01 */
  PlainDate: PlainDate;
};

At this point, there is no problem because a template literal is a string, and the gql transport layer will send this string without any problem.

Now, instead of receiving a string object, whatever it's format, I would prefer if I could receive an object I can directly use without having to revive it manually.

While I could type the DateTime and PlainDate as follows:

export type Scalars = {
  ...
  /** A date containing the hour. Ex. 1977-04-22T06:00:00Z */
  DateTime: Date;
  /** A date without an hour. Ex 2022-01-01 */
  PlainDate: Temporal.PlainDate;
};

Without a reviver on the client side, I will obvioulsy still receive a string, and therefore wrongly type this string.

Now, as I read in a comment, Apollo Client is not server and does not have the schema by itself, it therefore has no information on the expected scalar type. It surely has the object's __typename at runtime, but nothing on scalars. This makes me think that a solution to automate this conversion (reviver) should either come from an external tool which knows the schema (such as codegen) or would require a manual process or schema baking on apollo to still keep this information somewhere.

This is pretty much what this plugin is trying to achieve as seen in this article (which itself mentioned this git issue, «salut Patrick si tu me lis»)

To summarize:

  • Apollo client knows nothing about the schema but is responsible for reviving the json data
  • codegen knows the schema but is not responsible to do anything during the runtime

It's a little bit like dynamic vs static or runtime vs compile

My understanding is that apollo client should provide an official way to manually revive those scalars so that a codegen plugin could then automate this process.

I see 2 things to be done

  • A function to revive any scalar in apollo client (something similar to the server side version)
  • A function where we can register the mapping properties -> scalartype for any typename still in apollo. Should it be part of typePolicies ? I've no idea, I don't know the architecture under the hood well enough to suggest anything.

Of course, this is not very sexy having to manually register each scalar within a type to let apollo know which property should be revived, but if it can be manually done, then it will be feasible for codegen to build something on top of it.

Another problem: while reviving parameters from the data using the typename is what we need, we have to think about serializing the inputs back to json so that they can be sent over the line. By default, the method toString() (or is it JSON.serialize ?) will be called to serialize the object, which is the reason why passing a date into an input will just "work". A dirty way is to overload the toString method of any scalar representation on the client side, but it's not a clean solution. Luckily inputs have no union, the input type can only be unique, but we still don't know it at runtime, we should however also have a way to register custom serializers based on the type. The best is if we had the input type and the property name so that we could serialize it properly. The problem is : there is no __typename for inputs and the variables being a silly JSON we face back the same problem that codegen will know the type but not apollo while apollo will be required to serialize the scalars. A simple but "dangerous" approach is to check any input property and if they fit a given type (like using the instanceOf, or any type guard) once matched we serialize using the custom serializer. We need to be very careful because we risk type collision i.e. if 2 different scalars match the same rule.

Also, how would chrome's apollo works with those transformations ? my understanding is that it would stay with the underlying JSON data.

@alessbell alessbell transferred this issue from apollographql/apollo-client Apr 28, 2023
@alessbell alessbell added the project-apollo-client (legacy) LEGACY TAG DO NOT USE label Apr 28, 2023
@fanckush
Copy link

fanckush commented Jun 6, 2023

Is there at least a way to manually configure these scalars to something other than any?

can I configure codegen so that instead of

/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  /** A date containing the hour. Ex. 1977-04-22T06:00:00Z */
  DateTime: any;
};

it generates

/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  /** A date containing the hour. Ex. 1977-04-22T06:00:00Z */
  DateTime: string; // ----> generate this instead of `any`
};

any is very harmful, if we can at least configure it this way this is better than the current state

@jerelmiller
Copy link
Member

@fanckush assuming you're using GraphQL Codegen, you can configure the scalars value to change the type of your custom scalar. Try that in your codegen config.

If you'd prefer a different default instead of any for unknown scalars, you can configure this with defaultScalarType.

Hope this helps!

@fanckush
Copy link

fanckush commented Jun 7, 2023

@jerelmiller thanks that's exactly what I meant. Just to clarify, this would only change the type and doesn't perform any transformation or modification to the actual fields, right?

@eturino
Copy link

eturino commented Jun 7, 2023

correct

@Yimiprod
Copy link

@jerelmiller how do we actually make the transformation appends in apollo client ? Do we have to use Apollo Client Link ? The main used example atm is Date, since it's the most popular non parsable in JSON value, but there's others use case that has problems, like resolving a string to an Enum or a litteral string union.

@jerelmiller
Copy link
Member

how do we actually make the transformation appends in apollo client ?

@Yimiprod Could you clarify what you mean by "appends" here? I want to make sure to best answer your question.


Just to clarify, this would only change the type and doesn't perform any transformation or modification to the actual fields, right?

@fanckush that is correct, the change I mentioned only changes types, not the actual parsed value. We do plan to support custom scalars sometime within the next couple minors (likely 3.10 or 3.11), but we haven't quite yet begun work on this yet. We know this is a highly requested feature (as evidenced by all the activity and reactions here) and want to start thinking about this after our work with Suspense is complete.


As an aside, one of the reasons custom scalars are difficult to handle today, either in Apollo Link or via a type policy, without full support from the client, is because of the interaction with the cache. The cache expects all values to be JSON-serialized values, so parsing scalars in other areas has the potential to do weird things with the cache.

I personally have a vested interest in this feature as I've wanted this so many times in the past. Keep an eye on our roadmap for updates as we will post an update there when we plan to start development. I am definitely looking forward to closing this one out, hopefully sooner than later!

@stephenh
Copy link

Hi all; our codegen plugin graphql-typescript-scalar-type-policies has been linked to a few times here, and just FYI that we just realized a) the npm package wasn't public, and b) any notifications from the repo/github issues were getting ignored with the rest of our internal-only github notifications. :-/

We've fixed both issues, but also looking forward to a native apollo solution as well. Thanks!

@serengetisunset
Copy link

Does anyone know when native support for the Apollo Client will be released? I see a lot of activity on this feature request and wondering if a launch is imminent :)

@liontariai
Copy link

@serengetisunset I don't know if there are any recent plans to natively support, especially since it's 6 years the issue is open. I guess it's not so easy to do anyways because you would need to go through each field in the server response and check if it should be parsed. After that there's still the need to override the type in the codegen, so your types are correct.

If you don't mind trying something new, you might take https://github.com/liontariai/samarium for a test drive, I just implemented the final version of the custom scalar support. And it lazily deserializes your custom scalars with your given function and allows for setting the types for the whole client at once. See explanations here: liontariai/samarium#9 (comment)

It's still beta and might not work for all schemas that apollo works with, so any feedback is greatly appreciated :)

@jerelmiller
Copy link
Member

@serengetisunset this is on our short list of features to pull in next. We've been focused on other features up to this point (React Suspense, data masking, new testing utilities, etc.). We're hoping to pull this in soon as we are very aware of the popularity of this request.


To give some background, the reason we haven't quite pulled this in yet is the complexity required to integrate this. One of the biggest challenges is that Apollo Client isn't aware of your schema (nor would it be feasible to require this because it could add tons of bundle size to your app for large schemas). We need to come up with a way that allows you to define which scalar fields map to a particular custom scalar type in a scalable way (i.e. imagine you have 100 date fields, how do you configure these in a reasonable way without knowledge of the whole schema and that doesn't require you to define 100 type policies).

Some other things that have been on my mind that I think would be awesome:

  • Automatic serialization for data written to the cache
  • Automatic deserialization for data read from the cache
  • Automatic serialization for variables, both sent to the network and used to compute cache keys
  • Allowing scalars to work with no-cache queries.

We're also noodling on whether we can/should enable this for any third party or custom cache implementation. Adding this feature exclusively to InMemoryCache would mean that a subset of users would not be able to utilize this feature. Adding this to the core cache abstraction though adds additional complexity to get right.

Again, we are hoping to pull this in soon! Just wanted to throw some details in here on why its a bit more complex than meets the eye :)


@liontariai cool to hear about other projects in the ecosystem, but if you wouldn't mind keeping this discussion to this particular feature, that would be appreciated.

@liontariai
Copy link

I didn't intend to steer the discussion into a different direction - even though my little reference to the 6 years was on purpose, I myself have been missing this feature for a long time :) ... so I'm happy to hear smth from you.

Also, I agree that it is quite challenging to do, as I pointed out myself... I'm not sure if this is even possible without the help of additional codegen tools. From my understanding external codegen via other libraries is normal practice with apollo client and may also help in this case.

Since you need knowledge of the schema to pull this off, it would make sense to collect the paths to custom scalar types in your queries at code/type-gen time and have the apollo client utilize this? One could probably make a @graphql-codegen plugin for this.

If the output map is something like this:

const scalars = {
    Date: ["query.users.signedUp",...]
}

It could be used by the Cache, or am I missing something? The thing could be implemented with the library I mentioned, since while building the query, the information about the type and the path in the query is there. Just something I was thinking about... to combine both things.

@jerelmiller
Copy link
Member

even though my little reference to the 6 years was on purpose, I myself have been missing this feature for a long time

Haha totally hear you on that and can understand the frustration. Selfishly I've also wanted this feature for some time, so I definitely want to see it sooner than later. Hang with us a little longer!

I'm not sure if this is even possible without the help of additional codegen tools

You bring up a good point here which is that anything we add will likely need to be workable with GraphQL codegen. I'd like to avoid requiring codegen to use this feature, though we'll encourage its use to make configuration significantly easier. My thinking here is that codegen would do something similar to the fragment-matcher plugin but for scalar config. This would avoid having to manually configure each scalar field by hand.

If the output map is something like this:

const scalars = {
    Date: ["query.users.signedUp",...]
}

It could be used by the Cache, or am I missing something?

I'm sure we'll be doing a lot of experimentation on the format here to see what feels right. This brings me back to my comment about the InMemoryCache vs Cache thing. To go in more detail, since we don't control third party cache implementations, it also means we don't control how queries are read or written to that cache. We can't guarantee the third party cache implementation does any kind of traversal on the query AST like InMemoryCache does, which makes knowing when to serialize/deserialize values tricky. We could provide some kind of hook in our base Cache abstraction (which InMemoryCache inherits from), but this would require work on behalf of those other caches to utilize this behavior.

For this reason, we may very well may need to only allow this feature with InMemoryCache because its too difficult to predict how third party caches read/write values (and for performance reasons, we don't want to have to traverse the query AST more than we have to). If this becomes an InMemoryCache feature, we already have type policies which are mappings between the types and their fields. It would feel natural to put this implementation there for that reason. Hence why its difficult to say its as "simple as x syntax" because we'll need to experiment on the format to see what feels most natural without redundancy if we can avoid it.

We should only need a mapping between the type and the field that contains the scalar since we can derive the rest from the response itself (hence why __typename is so crucial to Apollo Client 🙂).

The only other part I'd say is missing is where you'd configure how to actually parse and serialize that scalar. I imagine this will look similar to a GraphQL server where you configure a parseValue and serialize function which will let you add the implementation needed to handle that scalar value. The question becomes whether this is included with the field config or not. I could imagine something like this:

scalars: {
  Date: {
    parse: (rawValue) => { /* ... */ },
    serialize: (parsedValue) => { /* ... */ },
    fields: {
      Project: ['createdAt', 'updatedAt'],
      Post: ['lastSeenAt']
    }
  }
}

Again, not something that is final, but just trying to illustrate some of the parts needed to make this feature work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Feature requests related to core functionality
Projects
None yet
Development

No branches or pull requests