{"id":13726330,"url":"https://github.com/mhallin/graphql_ppx","last_synced_at":"2025-04-12T21:24:31.481Z","repository":{"id":23919883,"uuid":"100104727","full_name":"mhallin/graphql_ppx","owner":"mhallin","description":"GraphQL PPX rewriter for Bucklescript/ReasonML","archived":false,"fork":false,"pushed_at":"2022-12-09T08:17:33.000Z","size":4612,"stargazers_count":320,"open_issues_count":45,"forks_count":42,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-10-30T03:40:06.469Z","etag":null,"topics":["bucklescript","graphql","ocaml","ppx","ppx-rewriter","reasonml"],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mhallin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-12T10:16:21.000Z","updated_at":"2024-04-14T20:45:38.000Z","dependencies_parsed_at":"2022-07-25T13:52:03.113Z","dependency_job_id":null,"html_url":"https://github.com/mhallin/graphql_ppx","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhallin%2Fgraphql_ppx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhallin%2Fgraphql_ppx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhallin%2Fgraphql_ppx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhallin%2Fgraphql_ppx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mhallin","download_url":"https://codeload.github.com/mhallin/graphql_ppx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248633052,"owners_count":21136795,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bucklescript","graphql","ocaml","ppx","ppx-rewriter","reasonml"],"created_at":"2024-08-03T01:02:59.559Z","updated_at":"2025-04-12T21:24:31.458Z","avatar_url":"https://github.com/mhallin.png","language":"OCaml","funding_links":[],"categories":["PPXs","OCaml"],"sub_categories":[],"readme":"# GraphQL syntax extension for Bucklescript/ReasonML\n\n[![Build Status](https://travis-ci.org/mhallin/graphql_ppx.svg?branch=master)](https://travis-ci.org/mhallin/graphql_ppx)\n[![Build Status](https://ci.appveyor.com/api/projects/status/mqlrgovk67bcc9a9/branch/master?svg=true)](https://ci.appveyor.com/project/mhallin/graphql-ppx)\n![npm](https://img.shields.io/npm/v/graphql_ppx.svg)\n\nThis library lets you construct type-safe and validated queries at compile time,\nand generates response validation code for you. If you're writing a\n[Bucklescript](https://bucklescript.github.io/bucklescript/Manual.html) app that\ntalks to a [GraphQL](http://graphql.org) server, this library will cut down on\nthe boilerplate you have to write.\n\nIt is compatible with both OCaml and ReasonML syntax. There are no runtime\ndependencies except for `Js.Json` and `Js.Dict`, both included in the\nBucklescript standard library.\n\n## Installation\n\nAssuming that you've already got a Bucklescript project set up, installing this\nsyntax extension consists of two steps:\n\nFirst, add this package as a dependency to your `package.json`:\n\n```sh\nyarn add --dev graphql_ppx\n# or, if you use npm:\nnpm install --saveDev graphql_ppx\n```\n\nSecond, add the PPX to your `bsconfig.json`:\n\n```json\n{\n    \"ppx-flags\": [\n        \"graphql_ppx/ppx\"\n    ]\n}\n```\n\n## Examples\n\nIf you add a field that does not exist, you'll get a compiler error on the exact\nlocation this happens. This automatically works with\n[Merlin](https://github.com/ocaml/merlin), giving you immediate feedback in your\neditor:\n\n![Misspelled field, immediate compiler errors](doc/misspelled_field.gif)\n\nVariables sent to queries and mutations are of course typed too. Nullable\nvariables are translated to optional labelled arguments, while non-null\nvariables become mandatory arguments:\n\n![Remove a variable argument, get a compiler error](doc/missing_variables.gif)\n\n(The `Api.sendQuery` function here is a small wrapper around [bs-fetch](https://github.com/reasonml-community/bs-fetch), check it out below)\n\nThe result of a query is turned into a typed `Js.t` object, which will generate compiler errors if you try to access fields that don't exist:\n\n![Remove a field, get compiler errors](doc/removed_field.gif)\n\nWhile these examples use the [ReasonML](https://reasonml.github.io) syntax,\nusing the standard OCaml syntax works as well.\n\n## Usage\n\n### Download the server schema\n\nThis plugin requires a `graphql_schema.json` file to exist somewhere in the\nproject hierarchy, containing the result of sending an [introspection\nquery](https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js)\nto your backend. To help you with this, a simple script is included to send this\nquery to a server and save the result as `graphql_schema.json` in the current\ndirectory:\n\n```sh\nyarn send-introspection-query http://my-api.example.com/api\n# or, if you use npm\nnpm run send-introspection-query http://my-api.example.com/api\n```\n\n\n#### Custom schema name\n\nIf you've already got a schema file downloaded for other purposes, you can tell\ngraphql_ppx to use that one by updating the \"ppx-flags\" in `bsconfig.json`.\nNote: no space around the equal sign!\n\n```json\n{\n  \"ppx-flags\": [\n    \"graphql_ppx/ppx\\\\ -schema=your_schema.json\"\n  ]\n}\n```\n\nWhile you can pass a path higher up the folder structure, like\n`-schema=../somedir/your_schema.json`, you might result into some path parsing\nproblems with BuckleScript or Merlin.\n\n### Ignore `.graphql_ppx_cache` in your version control\n\nThis plugin will generate a `.graphql_ppx_cache` folder alongside your JSON\nschema to optimize parsing performance for BuckleScript and Merlin. If you're\nusing a version control system, you don't need to check it in.\n\n\n### Send queries\n\nTo define a query, you declare a new module and type the query as a string\ninside the `graphql` extension:\n\n```reason\nmodule HeroQuery = [%graphql {|\n{\n  hero {\n    name\n  }\n}\n|}];\n```\n\nThis module exposes a few functions, but the most useful one is `make`, which\ntakes all arguments to the query/mutation as labelled function arguments, ending\nwith `()`. It an object containing three things: `query`, which is a string\ncontaining the query itself; `variables`, a `Js.Json.t` object containing the\nserialized variables for the query; and `parse`, a function that takes a\n`Js.Json.t` instance and returns a typed object corresponding to the query.\n\nA simple example might make this a bit clear:\n\n```reason\nmodule HeroQuery = [%graphql {| { hero { name } } |}];\n\n/* Construct a \"packaged\" query; HeroQuery takes no arguments: */\nlet heroQuery = HeroQuery.make();\n\n/* Send this query string to the server */\nlet query = heroQuery##query;\n\n/* Let's assume that this was the result we got back from the server */\nlet sampleResponse = \"{ \\\"hero\\\": {\\\"name\\\": \\\"R2-D2\\\"} }\";\n\n/* Convert the response to JSON and parse the result */\nlet result = Js.Json.parseExn(sampleResponse) |\u003e query##parse;\n\n/* Now you've got a well-typed object! */\nJs.log(\"The hero of the story is \" ++ result##hero##name);\n```\n\n### Integrating with the Fetch API\n\n[bs-fetch](https://github.com/reasonml-community/bs-fetch) is a wrapper around\nthe Fetch API. I've been using this simple function to send/parse queries:\n\n```reason\nexception Graphql_error(string);\n\nlet sendQuery = q =\u003e\n  Bs_fetch.(\n    fetchWithInit(\n      \"/graphql\",\n      RequestInit.make(\n        ~method_=Post,\n        ~body=\n          Js.Dict.fromList([\n            (\"query\", Js.Json.string(q##query)),\n            (\"variables\", q##variables)\n          ])\n          |\u003e Js.Json.object_\n          |\u003e Js.Json.stringify\n          |\u003e BodyInit.make,\n        ~credentials=Include,\n        ~headers=\n          HeadersInit.makeWithArray([|(\"content-type\", \"application/json\")|]),\n        ()\n      )\n    )\n    |\u003e Js.Promise.then_(resp =\u003e\n         if (Response.ok(resp)) {\n           Response.json(resp)\n           |\u003e Js.Promise.then_(data =\u003e\n                switch (Js.Json.decodeObject(data)) {\n                | Some(obj) =\u003e\n                  Js.Dict.unsafeGet(obj, \"data\")\n                  |\u003e q##parse\n                  |\u003e Js.Promise.resolve\n                | None =\u003e\n                  Js.Promise.reject(Graphql_error(\"Response is not an object\"))\n                }\n              );\n         } else {\n           Js.Promise.reject(\n             Graphql_error(\"Request failed: \" ++ Response.statusText(resp))\n           );\n         }\n       )\n  );\n```\n\n# Features\n\n* Objects are converted into `Js.t` objects\n* Enums are converted into [polymorphic\n  variants](https://realworldocaml.org/v1/en/html/variants.html)\n* Floats, ints, strings, booleans, id are converted into their corresponding native\n  OCaml types.\n* Custom scalars are parsed as `Js.Json.t`\n* Arguments with input objects\n* Using `@skip` and `@include` will force non-optional fields to become\n  optional.\n* Unions are converted to polymorphic variants, with exhaustiveness checking.\n  This only works for object types, not for unions containing interfaces.\n* Interfaces are also converted into polymorphic variants. Overlapping interface\n  selections and other more uncommon use cases are not yet supported.\n* Basic fragment support\n\n# Limitations\n\nWhile graphql_ppx covers a large portion of the GraphQL spec, there are still\nsome unsupported areas:\n\n* Not all GraphQL validations are implemented. It will *not* validate argument\n  types and do other sanity-checking of the queries. The fact that a query\n  compiles does not mean that it will pass server-side validation.\n* Fragment support is limited and not 100% safe - because graphql_ppx only can\n  perform local reasoning on queries, you can construct queries with fragments\n  that are invalid.\n\n## Extra features\n\nBy using some directives prefixed `bs`, `graphql_ppx` lets you modify how the\nresult of a query is parsed. All these directives will be removed from the query\nat compile time, so your server doesn't have to support them.\n\n### Record conversion\n\nWhile `Js.t` objects often have their advantages, they also come with some\nlimitations. For example, you can't create new objects using the spread (`...`)\nsyntax or pattern match on their contents. Since they are not named, they also\nresult in quite large type error messages when there are mismatches.\n\nOCaml records, on the other hand, can be pattern matched, created using the\nspread syntax, and give nicer error messages when they mismatch. `graphql_ppx`\ngives you the option to decode a field as a record using the `@bsRecord`\ndirective:\n\n```reason\n\ntype hero = {\n  name: string,\n  height: number,\n  mass: number\n};\n\nmodule HeroQuery = [%graphql {|\n{\n  hero @bsRecord {\n    name\n    height\n    mass\n  }\n}\n|}];\n```\n\nNote that the record has to already exist and be in scope for this to work.\n`graphql_ppx` will not _create_ the record. Even though this involves some\nduplication of both names and types, type errors will be generated if there are\nany mismatches.\n\n### Custom field decoders\n\nIf you've got a custom scalar, or just want to convert e.g. an integer to a\nstring to properly fit a record type (see above), you can use the `@bsDecoder`\ndirective to insert a custom function in the decoder:\n\n```reason\nmodule HeroQuery = [%graphql {|\n{\n  hero {\n    name\n    height @bsDecoder(fn: \"string_of_float\")\n    mass\n  }\n}\n|}];\n```\n\nIn this example, `height` will be converted from a number to a string in the\nresult. Using the `fn` argument, you can specify any function literal you want.\n\n### Non-union variant conversion\n\nIf you've got an object which in practice behave like a variant - like `signUp`\nabove, where you *either* get a user *or* a list of errors - you can add a\n`@bsVariant` directive to the field to turn it into a polymorphic variant:\n\n```reason\nmodule SignUpQuery = [%graphql\n  {|\nmutation($name: String!, $email: String!, $password: String!) {\n  signUp(email: $email, email: $email, password: $password) @bsVariant {\n    user {\n      name\n    }\n\n    errors {\n      field\n      message\n    }\n  }\n}\n|}\n];\n\nlet x =\n  SignUpQuery.make(~name=\"My name\", ~email=\"email@example.com\", ~password=\"secret\", ())\n  |\u003e Api.sendQuery |\u003e Promise.then_(response =\u003e\n    switch (response##signUp) {\n    | `User(user) =\u003e Js.log2(\"Signed up a user with name \", user##name)\n    | `Errors(errors) =\u003e Js.log2(\"Errors when signing up: \", errors)\n    } |\u003e Promise.resolve);\n```\n\nThis helps with the fairly common pattern for mutations that can fail with\nuser-readable errors.\n\n### Alternative `Query.make` syntax\n\nWhen you define a query with variables, the `make` function will take\ncorresponding labelled arguments. This is convenient when constructing and\nsending the queries yourself, but might be problematic when trying to abstract\nover multiple queries.\n\nFor this reason, another function called `makeWithVariables` is _also_\ngenerated. This function takes a single `Js.t` object containing all variables.\n\n```reason\nmodule MyQuery = [%graphql {|\n  mutation ($username: String!, $password: String!) {\n    ...\n  }\n|}];\n\n/* You can either use `make` with labelled arguments: */\nlet query = MyQuery.make(~username=\"testUser\", password=\"supersecret\", ());\n\n/* Or, you can use `makeWithVariables`: */\nlet query = MyQuery.makeWithVariables({ \"username\": \"testUser\", \"password\": \"supersecret\" });\n```\n\n### Getting the type of the parsed value\n\nIf you want to get the type of the parsed and decoded value - useful in places\nwhere you can't use OCaml's type inference - use the `t` type of the query\nmodule:\n\n```reason\nmodule MyQuery = [%graphql {| { hero { name height }} |}];\n\n\n/* This is something like Js.t({ . hero: Js.t({ name: string, weight: float }) }) */\ntype resultType = MyQuery.t;\n```\n\n### Verbose mode (Contributors only)\n\nYou can pass `-verbose` in `bsconfig.json` to turn on the verbose mode. You can\nalso use the `Log` module to log into verbose mode.\n\n## Experimental: `graphql-tag` replacement\n\nTo simplify integration with e.g. Apollo, this PPX can write the query AST\ninstead of the raw source, in a way that _should_ be compatible with how\n[graphql-tag](https://github.com/apollographql/graphql-tag) works.\n\nTo enable this, change your `bsconfig.json` to:\n\n```json\n{\n    \"ppx-flags\": [\n        \"graphql_ppx/ppx\\\\ -ast-out\"\n    ]\n}\n```\n\nNow, the `query` field will be a `Js.Json.t` structure instead of a string,\nready to be sent to Apollo.\n\n```reason\nmodule HeroQuery = [%graphql {| { hero { name } } |}];\n\n/* Construct a \"packaged\" query; HeroQuery takes no arguments: */\nlet heroQuery = HeroQuery.make();\n\n/* This is no longer a string, but rather an object structure */\nlet query = heroQuery##query;\n```\n\n## Building manually on unsupported platforms\n\ngraphql_ppx supports 64 bit Linux, Windows, and macOS, as well as 32 bit\nWindows out of the box. If you're on any other platform, please open an issue\non this repository so we can support it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhallin%2Fgraphql_ppx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhallin%2Fgraphql_ppx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhallin%2Fgraphql_ppx/lists"}