-
Notifications
You must be signed in to change notification settings - Fork 0
Traditional Codegen Workflow
In this approach, a developer creates .graphql
files. These files are recognized by the graphql-codegen
cli, which then generates corresponding rescript output.
Prerequisite packages:
graphql
@graphql-codegen/cli
-
@graphql-codegen/near-operation-file-preset
(this requirement might be swapped out for a custom preset in the future)
We provide two plugins particular to rescript:
-
@rescript-graphql-codegen/base-types
: This generates GQL input objects and enums for the overall schema. -
@rescript-graphql-codegen/operations
: This generates query and mutation types for individual.graphql
files.
To install all of these dependencies at once:
# npm
npm i graphql
npm i -D @graphql-codegen/cli @graphql-codegen/near-operation-file-preset @rescript-graphql-codegen/base-types @rescript-graphql-codegen/operations
# yarn
yarn add graphql
yarn add --dev @graphql-codegen/cli @graphql-codegen/near-operation-file-preset @rescript-graphql-codegen/base-types @rescript-graphql-codegen/operations
import type {CodegenConfig} from '@graphql-codegen/cli'
// Absolute name of your scalars module (described later)
const scalarModule = "GraphqlBase__Scalars";
const config: CodegenConfig = {
// Path to GQL schema file
schema: "src/Graphql/schema.graphql",
generates: {
// Entry to generate the base types file
"src/GraphqlBase/GraphqlBase__Types.res": {
plugins: ["@rescript-graphql-codegen/base-types"],
config: {
scalarModule
// Turning this on will generate an array of all enum values (`allvalues`),
// in the event you need to manipulate those in some way.
//
// includeEnumAllValuesArray: true
// By default, nullable GraphQL types are translated to `null<'a>`,
// and list GraphQL types to `array<'a>.
// Setting the following properties allow swapping these types out.
// The effective default values are shown commented out below:
//
// nullType: "null",
// listType: "array",
// Allow optional props in input types.
// See the notes above `optionalVariables` and `optionalOutputs`
// in the operations section for more details.
//
// optionalInputTypes: "wrapped"
// Adding this will append its value to any enum modules.
// See the similar options in the operations plugin for more details.
//
// appendToEnums: "..."
}
},
// Entry for operations files
"src/": {
// Glob to all source files
documents: "src/**/*.graphql",
// Here we make use of a typescript preset that does _roughly_ what we want
preset: "near-operation-file",
presetConfig: {
extension: ".res",
// If baseTypesPath is anything other than '.', the preset will generate
// typescript imports, which is bad news.
baseTypesPath: ".",
// Not required, but adding generated extensions doesn't really play nice
// with rescript's module names; much easier to use folders as ignore targets.
"folder": "__generated__",
},
config: {
// Module name of the generated base types file, as defined above.
baseTypesModule: "GraphqlBase__Types",
scalarModule,
// Without this, the preset will generate typescript imports;
// still not what we want.
globalNamespace: true,
// See the settings with the same names in the base types section.
//
// nullType: "null",
// listType: "array",
// If needed, can also add this import to customize the definition of the gql tag.
// Defaults to "GraphqlTag"
//
// gqlTagModule: "GQLTag"
// Also if needed, this fully-qualified import is the function/constructor that
// fragments will be wrapped in when inserted via the gql tag.
// Defaults to "GraphqlTag\.Document"; if using the provided graphql-tag
// package, no config should be needed.
//
// fragmentWrapper: "GQLTag.document"
// By default, nullable properties will just be wrapped with the `nullType`.
// For example:
// `nullableProp: null<int>`
// If you would like to make these props optional, set this option.
// - A value of `wrapped` will make the prop optional _in addition to_ `nullType`.
// `nullableProp?: null<int>`
// - A value of `unwrapped` will make the prop optional _instead of_ `nullType`.
// `nullableProp?: int`
// Note: `nullType` will still appear in lists; e.g.
// `nullableProp: null<array<null<int>>>`
// will turn into
// `nullableProp?: array<null<int>>`
// This can be configured separately for variables and output types.
//
// optionalVariables: "wrapped"
// optionalOutputs: "unwrapped"
// In practice, it's often useful to add additional code after generating types.
// We currently provide a simplistic way to do that via strings.
// This _should_ be enough for most use cases, thanks to rescript's global namespacing.
// See the 'generating additional code' section for more details
//
// appendToFragments: "...",
// appendToQueries: "...",
// appendToMutations: "...",
// appendToSubscriptions: "...",
},
plugins: [
"@rescript-graphql-codegen/operations"
]
}
}
}
export default config
As seen in codegen.ts
, the plugins expect a particular scalarsModule
to be present in your application. This module should contain one submodule for each scalar defined in your server’s schema; each of these submodules must contain a type t
, describing the runtime value of that scalar type.
This gives you some additional flexibility around using these types. For basic scalars, such as the built-ins, these modules can be very simple:
module Boolean = {
type t = bool
}
module Float = {
type t = float
}
module Id = {
type t = string
}
module Int = {
type t = int
}
module String = {
type t = string
}
At the same time, we can customize the interface of more particular scalars. For example, if we have a Date
type, encoded to a string
, we can create an opaque type to ensure proper encoding and decoding:
module Date: {
type t
let toJsDate: t => Js.Date.t
let fromJsDate: Js.Date.t => t
} = {
type t = string
let toJsDate = ...
let fromJsDate = ...
}
The ever-helpful rescript compiler should point out any missing scalars as they appear in generated code.
@rescript-graphql-codegen/operations
uses the gql
tag to generate document nodes. If your use case doesn’t require compatibility with an existing library, we provide a default type within @rescript-graphql-codegen/lib
, and tag implementation in @rescript-graphql-codegen/graphql-tag
. To install:
# npm
npm i @rescript-graphql-codegen/lib @rescript-graphql-codegen/graphql-tag
# yarn
yarn add @rescript-graphql-codegen/lib @rescript-graphql-codegen/graphql-tag
If needed, this can be replaced with a binding to a custom document type. Assuming `Document.t` is where that is located:
@unboxed
type input =
| String(string)
| Document(Document.t)
@module("graphql-tag") @taggedTemplate
external gql: (array<string>, array<input>) => Document.t = "gql"
For operations, frequently we want to generate some additional runtime code alongside each definition. For example, if we had this binding to @apollo/client
’s useQuery
:
// Apollo.res
type queryConfig<'variables, 'result> = {
variables?: 'variables,
...<snip>
}
type queryResult<'variables, 'result> = {
data: option<'result>,
...<snip>
}
@module("@apollo/client")
external useQuery: (Document.t, config<'variables, 'result>) => queryResult<'variables, 'result> = "useQuery"
We can then automatically generate hooks for generated queries by using the appendToQueries
configuration option:
// in codegen.ts
...
appendToQueries: `
// This code will be inserted at the end of a generated query;
// as such, the generated types ~variables~ and ~t~, along with the matching ~document~, will be in scope.
let use:
Apollo.config<variables, t> => Apollo.queryResult<variables, t> = Apollo.useQuery(document, ...)
`
...
For a more elaborate example, see this repository, which uses custom bindings to @apollo/client
.