├── .DS_Store ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── README.md ├── codegen └── schema │ ├── .gitignore │ ├── README.md │ ├── get-gql-schema.mjs │ ├── index.mjs │ ├── spago.lock │ ├── spago.yaml │ ├── src │ ├── Data │ │ └── GraphQL │ │ │ ├── AST.purs │ │ │ └── Parser.purs │ └── GraphQL │ │ └── Client │ │ └── CodeGen │ │ ├── Directive.purs │ │ ├── DocumentFromIntrospection.purs │ │ ├── GetSymbols.purs │ │ ├── IntrospectionResult.purs │ │ ├── Js.purs │ │ ├── Lines.purs │ │ ├── Query.purs │ │ ├── Schema.purs │ │ ├── SchemaCst.purs │ │ ├── Template │ │ └── Enum.purs │ │ ├── Transform │ │ └── NullableOverrides.purs │ │ ├── Types.purs │ │ ├── Util.purs │ │ └── UtilCst.purs │ ├── test │ ├── Client │ │ └── CodeGen │ │ │ └── QueryFromGqlToPurs.Test.purs │ ├── Data │ │ └── GraphQL │ │ │ ├── ParseFull0.purs │ │ │ ├── ParseFull1.purs │ │ │ ├── ParseFull2.purs │ │ │ ├── ParseFull3.purs │ │ │ ├── ParseSadistic0.purs │ │ │ ├── ParseSadistic1.purs │ │ │ ├── ParseSimple.purs │ │ │ └── RetrieveStringTypes.purs │ └── Main.purs │ └── write-purs-schema.mjs ├── e2e ├── 1-affjax │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs └── 2-comments │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ └── Main.purs │ └── test.mjs ├── examples ├── 1-simple │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 10-aliases │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 11-unions │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Test │ │ └── Main.purs ├── 12-directives │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 13-error-boundaries │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 2-codegen │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.js │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 3-enums │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 4-mutation │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 5-subscription │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 6-watch-query │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ ├── test │ │ └── Main.purs │ └── yarn.lock ├── 7-field-type-overrides │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ ├── DataTypes.purs │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs ├── 8-custom-gql-types │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ ├── test.mjs │ └── test │ │ └── Main.purs └── 9-variables │ ├── .gitignore │ ├── README.md │ ├── generate-purs-schema.mjs │ ├── package-lock.json │ ├── package.json │ ├── server-fn.js │ ├── server.js │ ├── spago.lock │ ├── spago.yaml │ ├── src │ └── Main.purs │ ├── test.mjs │ └── test │ └── Main.purs ├── gen-schema-bundled.mjs ├── package-lock.json ├── package.json ├── run-example-tests.js ├── run-in-examples.js ├── run-should-fail-tests.js ├── should-fail-tests ├── not-null-arg-ignored │ ├── .gitignore │ ├── expected-error.txt │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ └── test │ │ └── Main.purs ├── not-null-arg-is-nothing │ ├── .gitignore │ ├── expected-error.txt │ ├── spago.lock │ ├── spago.yaml │ ├── src │ │ └── Main.purs │ └── test │ │ └── Main.purs └── not-null-arg-not-there │ ├── .gitignore │ ├── expected-error.txt │ ├── spago.lock │ ├── spago.yaml │ ├── src │ └── Main.purs │ └── test │ └── Main.purs ├── spago.lock ├── spago.yaml ├── src └── GraphQL │ ├── Client │ ├── Alias.purs │ ├── Alias │ │ └── Dynamic.purs │ ├── Args.purs │ ├── Args │ │ └── AllowedMismatch.purs │ ├── ArrayOf.purs │ ├── AsGql.purs │ ├── BaseClients │ │ ├── Affjax │ │ │ ├── Internal.purs │ │ │ ├── Node.purs │ │ │ └── Web.purs │ │ ├── Apollo.js │ │ ├── Apollo.purs │ │ ├── Apollo │ │ │ ├── ErrorPolicy.purs │ │ │ └── FetchPolicy.purs │ │ ├── Urql.js │ │ └── Urql.purs │ ├── Directive.purs │ ├── Directive │ │ ├── Definition.purs │ │ └── Location.purs │ ├── ErrorBoundary.purs │ ├── GqlError.purs │ ├── GqlType.purs │ ├── ID.purs │ ├── NullArray.purs │ ├── Operation.purs │ ├── Query.purs │ ├── QueryReturns.purs │ ├── SafeQueryName.purs │ ├── Subscription.purs │ ├── ToGqlString.purs │ ├── Types.purs │ ├── Union.purs │ ├── Variable.purs │ ├── Variables.purs │ └── WatchQuery.purs │ └── Hasura │ ├── ComparisonExp.purs │ ├── Decode.purs │ ├── DecodeLiberal.purs │ └── Encode.purs └── test ├── GraphQL ├── Client │ ├── Directive.purs │ ├── QueryReturns.Test.purs │ ├── ToGqlString.Test.purs │ └── Variable.Test.purs └── Hasura │ ├── Decode.purs │ └── Encode.purs └── Main.purs /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OxfordAbstracts/purescript-graphql-client/f9c5b5759a0f5dc5af797ce9c725cef3fc0276f2/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - uses: purescript-contrib/setup-purescript@main 12 | with: 13 | purescript: 0.15.15 14 | psa: 0.8.2 15 | spago: unstable 16 | 17 | - name: Cache PureScript dependencies 18 | uses: actions/cache@v4 19 | # This cache uses the .lock files to know when it should reinstall 20 | # and rebuild packages. It caches both the installed packages from 21 | # the `.spago` directory and compilation artifacts from the `output` 22 | # directory. When restored the compiler will rebuild any files that 23 | # have changed. If you do not want to cache compiled output, remove 24 | # the `output` path. 25 | with: 26 | key: k1-${{ runner.os }}-spago-${{ hashFiles('**/*.lock') }} 27 | path: | 28 | .spago 29 | output 30 | codegen/schema/.spago 31 | codegen/schema/output 32 | examples/1-simple/.spago 33 | examples/1-simple/output 34 | examples/2-codegen/.spago 35 | examples/2-codegen/output 36 | examples/3-enums/.spago 37 | examples/3-enums/output 38 | examples/4-mutation/.spago 39 | examples/4-mutation/output 40 | examples/5-subscription/.spago 41 | examples/5-subscription/output 42 | examples/6-watch-query/.spago 43 | examples/6-watch-query/output 44 | examples/7-field-type-overrides/.spago 45 | examples/7-field-type-overrides/output 46 | examples/8-custom-gql-types/.spago 47 | examples/8-custom-gql-types/output 48 | examples/9-variables/.spago 49 | examples/9-variables/output 50 | examples/10-aliases/.spago 51 | examples/10-aliases/output 52 | examples/11-unions/.spago 53 | examples/11-unions/output 54 | examples/12-directives/.spago 55 | examples/12-directives/output 56 | examples/13-error-boundaries/.spago 57 | examples/13-error-boundaries/output 58 | 59 | - run: spago build 60 | - run: npm i 61 | - run: npm run bundle 62 | - run: npm t 63 | 64 | - name: Run Example Tests 65 | run: node run-example-tests.js 66 | 67 | - name: Run Should Fail Tests 68 | run: node run-should-fail-tests.js 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | node_modules/ 3 | /.pulp-cache/ 4 | output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /.vscode 12 | /src/test-output/ 13 | .env 14 | /cache/ 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /.vscode 12 | /src/test-output/ 13 | /src/ 14 | .env 15 | /cache/ 16 | /codegen/ 17 | /e2e/ 18 | /examples/ 19 | /should-fail-tests/ 20 | -------------------------------------------------------------------------------- /codegen/schema/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | node_modules/ 3 | /.pulp-cache/ 4 | output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /.vscode 12 | /src/test-output/ 13 | .env 14 | /cache/ 15 | -------------------------------------------------------------------------------- /codegen/schema/get-gql-schema.mjs: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { 3 | getIntrospectionQuery, 4 | } from 'graphql'; 5 | 6 | export async function getGqlSchema ({ moduleName, cache, url, token }) { 7 | try { 8 | const introspectionQuery = getIntrospectionQuery() 9 | 10 | const response = await fetch( 11 | url, 12 | { 13 | method: 'POST', 14 | headers: 15 | token 16 | ? { 17 | 'Content-Type': 'application/json', 18 | Authorization: `Bearer ${token}` 19 | } 20 | : { 21 | 'Content-Type': 'application/json' 22 | }, 23 | body: JSON.stringify({ query: introspectionQuery }) 24 | } 25 | ) 26 | 27 | const { data: schema, errors } = await response.json() 28 | 29 | if (errors) { 30 | throw new Error(errors 31 | .map(err => "- " + err.message) 32 | .join('\n') 33 | ) 34 | } 35 | 36 | return { moduleName, cache, schema } 37 | } catch (err) { 38 | console.error('failed to get gql schema', err) 39 | throw (err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /codegen/schema/index.mjs: -------------------------------------------------------------------------------- 1 | import { writePursSchemas } from './write-purs-schema.mjs' 2 | import { getGqlSchema } from './get-gql-schema.mjs' 3 | import { promisify } from 'util' 4 | import mkdirp from 'mkdirp' 5 | import rimraf from 'rimraf' 6 | const rm = promisify(rimraf) 7 | 8 | export async function generateSchemas (opts, gqlEndpoints) { 9 | if (!Array.isArray(gqlEndpoints)) { 10 | gqlEndpoints = [gqlEndpoints] 11 | } 12 | await rm(opts.dir) 13 | await mkdirp(opts.dir) 14 | await mkdirp(opts.dir + '/Schema') 15 | await mkdirp(opts.dir + '/Enum') 16 | await mkdirp(opts.dir + '/Directives') 17 | const schemas = await Promise.all(gqlEndpoints.map(getGqlSchema)) 18 | 19 | return await writePursSchemas(opts, schemas) 20 | } 21 | 22 | export async function generateSchema (opts) { 23 | const { modulePath, url } = opts 24 | const moduleName = modulePath[modulePath.length - 1] 25 | 26 | return generateSchemas({ ...opts, modulePath: modulePath.slice(0, -1) }, [{ moduleName, url }]) 27 | } 28 | -------------------------------------------------------------------------------- /codegen/schema/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: schema 3 | dependencies: 4 | - aff 5 | - aff-promise 6 | - argonaut-codecs 7 | - argonaut-core 8 | - argonaut-generic 9 | - arrays 10 | - bifunctors 11 | - console 12 | - control 13 | - datetime 14 | - debug 15 | - effect 16 | - either 17 | - enums 18 | - exceptions 19 | - filterable 20 | - foldable-traversable 21 | - foreign 22 | - foreign-object 23 | - functions 24 | - http-methods 25 | - integers 26 | - language-cst-parser 27 | - lists 28 | - literals 29 | - maybe 30 | - media-types 31 | - newtype 32 | - node-buffer 33 | - node-fs 34 | - now 35 | - nullable 36 | - numbers 37 | - ordered-collections 38 | - parsing 39 | - partial 40 | - prelude 41 | - profunctor 42 | - profunctor-lenses 43 | - psci-support 44 | - quickcheck 45 | - record 46 | - record-studio 47 | - strings 48 | - strings-extra 49 | - tidy 50 | - tidy-codegen 51 | - transformers 52 | - tuples 53 | - unfoldable 54 | - unicode 55 | - unsafe-coerce 56 | - untagged-union 57 | - variant 58 | test: 59 | main: Test.Main 60 | dependencies: 61 | - spec 62 | - spec-discovery 63 | workspace: 64 | packageSet: 65 | registry: 60.2.1 66 | extraPackages: 67 | graphql-client: 68 | path: ../../graphql-client 69 | tidy-codegen: 70 | dependencies: 71 | - aff 72 | - ansi 73 | - arrays 74 | - avar 75 | - bifunctors 76 | - console 77 | - control 78 | - dodo-printer 79 | - effect 80 | - either 81 | - enums 82 | - exceptions 83 | - filterable 84 | - foldable-traversable 85 | - free 86 | - identity 87 | - integers 88 | - language-cst-parser 89 | - lazy 90 | - lists 91 | - maybe 92 | - newtype 93 | - node-buffer 94 | - node-child-process 95 | - node-path 96 | - node-process 97 | - node-streams 98 | - ordered-collections 99 | - parallel 100 | - partial 101 | - posix-types 102 | - prelude 103 | - record 104 | - safe-coerce 105 | - st 106 | - strings 107 | - tidy 108 | - transformers 109 | - tuples 110 | - type-equality 111 | - unicode 112 | repo: https://github.com/natefaubion/purescript-tidy-codegen.git 113 | version: v4.0.0 114 | tidy: 115 | dependencies: 116 | - arrays 117 | - dodo-printer 118 | - foldable-traversable 119 | - lists 120 | - maybe 121 | - ordered-collections 122 | - partial 123 | - prelude 124 | - language-cst-parser 125 | - strings 126 | - tuples 127 | repo: https://github.com/natefaubion/purescript-tidy.git 128 | version: v0.10.0 129 | dodo-printer: 130 | repo: https://github.com/natefaubion/purescript-dodo-printer.git 131 | version: v2.2.1 132 | dependencies: 133 | - aff 134 | - ansi 135 | - arrays 136 | - avar 137 | - console 138 | - control 139 | - effect 140 | - either 141 | - exceptions 142 | - foldable-traversable 143 | - integers 144 | - lists 145 | - maybe 146 | - minibench 147 | - newtype 148 | - node-buffer 149 | - node-child-process 150 | - node-path 151 | - node-process 152 | - node-streams 153 | - parallel 154 | - partial 155 | - posix-types 156 | - prelude 157 | - safe-coerce 158 | - strings 159 | - tuples 160 | 161 | -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/GetSymbols.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.GetSymbols where 2 | 3 | import Prelude 4 | 5 | import Data.Array (elem) 6 | import Data.Array as Array 7 | import Data.Foldable (class Foldable) 8 | import Data.GraphQL.AST as AST 9 | import Data.List (List, filter, foldMap, nub, sort, (:)) 10 | import Data.Maybe (maybe) 11 | import Data.Newtype (unwrap) 12 | import Data.String as String 13 | import Data.String (toLower) 14 | 15 | symbolsToCode :: forall f. Foldable f => String -> f String -> String 16 | symbolsToCode modulePrefix symbols = 17 | """module """ <> modulePrefix 18 | <> 19 | """Symbols where 20 | 21 | import Type.Proxy (Proxy(..)) 22 | """ 23 | <> symbolsString 24 | where 25 | symbolsString = 26 | symbols 27 | # Array.fromFoldable 28 | # Array.nub 29 | # Array.filter (lower1stChar && not keyword) 30 | # foldMap 31 | ( \s -> "\n" <> s <> " = Proxy :: Proxy " <> show s 32 | ) 33 | 34 | getSymbols :: AST.Document -> List String 35 | getSymbols doc = 36 | unwrap doc 37 | >>= definitionToSymbols 38 | # filter (lower1stChar && not keyword) 39 | # nub 40 | # sort 41 | where 42 | definitionToSymbols :: AST.Definition -> List String 43 | definitionToSymbols = case _ of 44 | AST.Definition_ExecutableDefinition _ -> mempty 45 | AST.Definition_TypeSystemDefinition def -> typeSystemDefinitionToSymbols def 46 | AST.Definition_TypeSystemExtension _ -> mempty 47 | 48 | typeSystemDefinitionToSymbols :: AST.TypeSystemDefinition -> List String 49 | typeSystemDefinitionToSymbols = case _ of 50 | AST.TypeSystemDefinition_SchemaDefinition _ -> mempty 51 | AST.TypeSystemDefinition_TypeDefinition typeDefinition -> typeDefinitionToSymbols typeDefinition 52 | AST.TypeSystemDefinition_DirectiveDefinition _ -> mempty 53 | 54 | typeDefinitionToSymbols :: AST.TypeDefinition -> List String 55 | typeDefinitionToSymbols = case _ of 56 | AST.TypeDefinition_ScalarTypeDefinition _ -> mempty 57 | AST.TypeDefinition_ObjectTypeDefinition objectTypeDefinition -> objectTypeDefinitionToSymbols objectTypeDefinition 58 | AST.TypeDefinition_InterfaceTypeDefinition _ -> mempty 59 | AST.TypeDefinition_UnionTypeDefinition _ -> mempty 60 | AST.TypeDefinition_EnumTypeDefinition _ -> mempty 61 | AST.TypeDefinition_InputObjectTypeDefinition _ -> mempty 62 | 63 | objectTypeDefinitionToSymbols :: AST.ObjectTypeDefinition -> List String 64 | objectTypeDefinitionToSymbols 65 | ( AST.ObjectTypeDefinition 66 | { fieldsDefinition 67 | } 68 | ) = maybe mempty fieldsDefinitionToSymbols fieldsDefinition 69 | 70 | fieldsDefinitionToSymbols :: AST.FieldsDefinition -> List String 71 | fieldsDefinitionToSymbols (AST.FieldsDefinition fieldsDefinition) = fieldsDefinition >>= fieldDefinitionToSymbols 72 | 73 | fieldDefinitionToSymbols :: AST.FieldDefinition -> List String 74 | fieldDefinitionToSymbols 75 | ( AST.FieldDefinition 76 | { name 77 | , argumentsDefinition 78 | } 79 | ) = name : maybe mempty argumentsDefinitionToSymbols argumentsDefinition 80 | 81 | argumentsDefinitionToSymbols :: AST.ArgumentsDefinition -> List String 82 | argumentsDefinitionToSymbols (AST.ArgumentsDefinition inputValueDefinitions) = inputValueDefinitions >>= inputValueDefinitionsToSymbols 83 | 84 | inputValueDefinitionsToSymbols = mempty 85 | 86 | keyword :: String -> Boolean 87 | keyword = flip elem [ "data", "type", "instance", "if", "then", "else" ] 88 | 89 | lower1stChar :: String -> Boolean 90 | lower1stChar s = head == toLower head 91 | where 92 | head = String.take 1 s -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/IntrospectionResult.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.IntrospectionResult where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson, decodeJson) 6 | import Data.Maybe (Maybe) 7 | import Data.Newtype (class Newtype, wrap) 8 | 9 | type IntrospectionResult = 10 | { __schema :: Schema 11 | } 12 | 13 | type Schema = 14 | { queryType :: TypeName 15 | , mutationType :: Maybe TypeName 16 | , subscriptionType :: Maybe TypeName 17 | , types :: Array FullType 18 | , directives :: Array Directive 19 | 20 | } 21 | 22 | type Directive = 23 | { name :: String 24 | , description :: Maybe String 25 | , locations :: Array String 26 | , args :: Array InputValue 27 | } 28 | 29 | type TypeName = { name :: Maybe String } 30 | 31 | type FullType = 32 | { kind :: String 33 | , name :: Maybe String 34 | , description :: Maybe String 35 | , fields :: Maybe (Array IField) 36 | , inputFields :: Maybe (Array InputValue) 37 | , interfaces :: Maybe (Array TypeRef) 38 | , enumValues :: Maybe (Array EnumValue) 39 | , possibleTypes :: Maybe (Array TypeRef) 40 | } 41 | 42 | type EnumValue = 43 | { name :: String 44 | , description :: Maybe String 45 | , isDeprecated :: Boolean 46 | , deprecationReason :: Maybe String 47 | } 48 | 49 | type IField = 50 | { name :: String 51 | , description :: Maybe String 52 | , args :: Maybe (Array InputValue) 53 | , type :: TypeRef 54 | , isDeprecated :: Boolean 55 | , deprecationReason :: Maybe String 56 | } 57 | 58 | type InputValue = 59 | { name :: String 60 | , description :: Maybe String 61 | , type :: TypeRef 62 | , defaultValue :: Maybe String 63 | } 64 | 65 | newtype TypeRef = TypeRef 66 | { kind :: String 67 | , name :: Maybe String 68 | , ofType :: Maybe TypeRef 69 | } 70 | 71 | derive instance Newtype TypeRef _ 72 | 73 | derive instance Eq TypeRef 74 | 75 | instance DecodeJson TypeRef where 76 | decodeJson a = wrap <$> decodeJson a -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Js.purs: -------------------------------------------------------------------------------- 1 | -- | Schema codegen functions that are designed to be called by Javascript code 2 | module GraphQL.Client.CodeGen.Js where 3 | 4 | import Prelude 5 | 6 | import Control.Promise (Promise, fromAff, toAff) 7 | import Data.Argonaut.Core (Json) 8 | import Data.Either (either) 9 | import Data.Function.Uncurried (Fn2, mkFn2) 10 | import Data.Map as Map 11 | import Data.Maybe (fromMaybe) 12 | import Data.Nullable (Nullable, toMaybe) 13 | import Foreign.Object (Object) 14 | import Foreign.Object as Object 15 | import GraphQL.Client.CodeGen.Schema (schemasFromGqlToPurs) 16 | import GraphQL.Client.CodeGen.Types (GqlInput, JsResult, QualifiedType) 17 | import Parsing (parseErrorMessage) 18 | import Untagged.Union (type (|+|), toEither1) 19 | 20 | schemasFromGqlToPursJs :: Fn2 InputOptionsJs (Array GqlInput) JsResult 21 | schemasFromGqlToPursJs = mkFn2 go 22 | where 23 | go :: InputOptionsJs -> Array GqlInput -> JsResult 24 | go optsJs = 25 | schemasFromGqlToPurs opts 26 | >>> map (either getError ({ result: _, parseError: "", argsTypeError: "" })) 27 | >>> fromAff 28 | where 29 | opts = 30 | { gqlToPursTypes: Map.fromFoldableWithIndex (fromQualifiedTypeJs <$> fromNullable Object.empty optsJs.gqlToPursTypes) 31 | , fieldTypeOverrides: Map.fromFoldableWithIndex <$> Map.fromFoldableWithIndex (map fromQualifiedTypeJs <$> fromNullable Object.empty optsJs.fieldTypeOverrides) 32 | , argTypeOverrides: (map (map fromQualifiedTypeJs <<< Map.fromFoldableWithIndex) <<< Map.fromFoldableWithIndex) <$> Map.fromFoldableWithIndex (fromNullable Object.empty optsJs.argTypeOverrides) 33 | , nullableOverrides: Map.fromFoldableWithIndex <$> Map.fromFoldableWithIndex (fromNullable Object.empty optsJs.nullableOverrides) 34 | , dir: fromNullable "" optsJs.dir 35 | , modulePath: fromNullable [] optsJs.modulePath 36 | , useNewtypesForRecords: fromNullable true optsJs.useNewtypesForRecords 37 | , enumImports: fromNullable [] optsJs.enumImports 38 | , customEnumCode: fromNullable (const "") optsJs.customEnumCode 39 | , idImport: map fromQualifiedTypeJs $ toMaybe optsJs.idImport 40 | , enumValueNameTransform: toMaybe optsJs.enumValueNameTransform 41 | , cache: 42 | toMaybe optsJs.cache 43 | <#> \{ get, set } -> 44 | { get: map (toAff >>> map toMaybe) get 45 | , set: map toAff set 46 | } 47 | 48 | } 49 | 50 | getError err = 51 | { parseError: parseErrorMessage err 52 | , argsTypeError: mempty 53 | , result: mempty 54 | } 55 | 56 | fromNullable :: forall a. a -> Nullable a -> a 57 | fromNullable a = fromMaybe a <<< toMaybe 58 | 59 | fromQualifiedTypeJs :: QualifiedTypeJs -> QualifiedType 60 | fromQualifiedTypeJs = toEither1 >>> either identity { typeName: _, moduleName: "" } 61 | 62 | type InputOptionsJs = 63 | { gqlToPursTypes :: 64 | Nullable 65 | ( Object QualifiedTypeJs 66 | ) 67 | , fieldTypeOverrides :: 68 | Nullable 69 | ( Object 70 | ( Object QualifiedTypeJs 71 | ) 72 | ) 73 | , argTypeOverrides :: 74 | Nullable 75 | ( Object 76 | ( Object 77 | ( Object QualifiedTypeJs 78 | ) 79 | ) 80 | ) 81 | , nullableOverrides :: Nullable (Object (Object Boolean)) 82 | , idImport :: Nullable QualifiedTypeJs 83 | , dir :: Nullable String 84 | , modulePath :: Nullable (Array String) 85 | , useNewtypesForRecords :: Nullable Boolean 86 | , enumImports :: Nullable (Array String) 87 | , customEnumCode :: Nullable ({ name :: String, values :: Array { gql :: String, transformed :: String } } -> String) 88 | , cache :: 89 | Nullable 90 | { get :: String -> Promise (Nullable Json) 91 | , set :: { key :: String, val :: Json } -> Promise Unit 92 | } 93 | , enumValueNameTransform :: Nullable (String -> String) 94 | } 95 | 96 | type QualifiedTypeJs = QualifiedType |+| String -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Lines.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.Lines where 2 | 3 | import Prelude 4 | 5 | import Data.Foldable (class Foldable, foldMap) 6 | import Data.String (joinWith) 7 | import Data.String.Regex (split) 8 | import Data.String.Regex.Flags (global) 9 | import Data.String.Regex.Unsafe (unsafeRegex) 10 | 11 | docComment :: forall m. Foldable m => m String -> String 12 | docComment = foldMap (\str -> "\n" <> prependLines commentPrefix str <> "\n") 13 | 14 | commentPrefix = " -- | " :: String 15 | 16 | indent :: String -> String 17 | indent = prependLines " " 18 | 19 | prependLines :: String -> String -> String 20 | prependLines pre = 21 | toLines 22 | >>> map (\l -> if l == "" then l else pre <> l) 23 | >>> fromLines 24 | 25 | toLines :: String -> Array String 26 | toLines = split (unsafeRegex """\n""" global) 27 | 28 | fromLines :: Array String -> String 29 | fromLines = joinWith "\n" -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Query.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.Query (queryFromGqlToPurs) where 2 | 3 | import Prelude 4 | 5 | import Data.Either (Either) 6 | import Data.GraphQL.AST as AST 7 | import Data.GraphQL.Parser (document) 8 | import Data.List (fold, foldMap, intercalate, mapMaybe) 9 | import Data.Maybe (Maybe(..)) 10 | import Data.Monoid (guard) 11 | import Data.Newtype (unwrap) 12 | import Data.String as String 13 | import GraphQL.Client.CodeGen.Lines (indent) 14 | import Parsing (ParseError, runParser) 15 | 16 | queryFromGqlToPurs :: String -> Either ParseError String 17 | queryFromGqlToPurs gql = runParser gql document <#> toPurs 18 | where 19 | toPurs :: AST.Document -> String 20 | toPurs = unwrap >>> mapMaybe definitionToPurs >>> intercalate "\n\n" 21 | 22 | definitionToPurs :: AST.Definition -> Maybe String 23 | definitionToPurs = case _ of 24 | AST.Definition_ExecutableDefinition def -> executableDefinitionToPurs def 25 | AST.Definition_TypeSystemDefinition _ -> Nothing 26 | AST.Definition_TypeSystemExtension _ -> Nothing 27 | 28 | executableDefinitionToPurs :: AST.ExecutableDefinition -> Maybe String 29 | executableDefinitionToPurs = case _ of 30 | (AST.ExecutableDefinition_OperationDefinition op) -> Just $ operationDefinitionToPurs op 31 | (AST.ExecutableDefinition_FragmentDefinition _) -> Nothing 32 | 33 | operationDefinitionToPurs :: AST.OperationDefinition -> String 34 | operationDefinitionToPurs = case _ of 35 | (AST.OperationDefinition_SelectionSet set) -> selectionSetToPurs set 36 | (AST.OperationDefinition_OperationType opType) -> operationTypeToPurs opType 37 | 38 | operationTypeToPurs 39 | { name 40 | , selectionSet 41 | } = lowerCaseFirst (fold name) <> " = " <> selectionSetToPurs selectionSet 42 | 43 | selectionSetToPurs :: AST.SelectionSet -> String 44 | selectionSetToPurs (AST.SelectionSet selections) = 45 | indent 46 | $ "\n{ " 47 | <> intercalate "\n, " (mapMaybe selectionToPurs selections) 48 | <> "\n}" 49 | 50 | selectionToPurs :: AST.Selection -> Maybe String 51 | selectionToPurs = case _ of 52 | (AST.Selection_Field field) -> Just $ fieldToPurs field 53 | (AST.Selection_FragmentSpread _) -> Nothing 54 | (AST.Selection_InlineFragment _) -> Nothing 55 | 56 | fieldToPurs :: AST.Field -> String 57 | fieldToPurs (AST.Field 58 | { alias 59 | , name 60 | , arguments 61 | , selectionSet 62 | }) = 63 | 64 | foldMap (\a -> a <> ": ") alias 65 | <> name 66 | <> guard (body /= "") ":" 67 | <> foldMap argumentsToPurs arguments 68 | <> foldMap selectionSetToPurs selectionSet 69 | where 70 | body = foldMap argumentsToPurs arguments 71 | <> foldMap selectionSetToPurs selectionSet 72 | 73 | 74 | argumentsToPurs :: AST.Arguments -> String 75 | argumentsToPurs (AST.Arguments args) = 76 | indent 77 | $ "\n{ " 78 | <> intercalate "\n, " (map argumentToPurs args) 79 | <> "\n} =>>" 80 | 81 | argumentToPurs :: AST.Argument -> String 82 | argumentToPurs (AST.Argument {name, value}) = name <> ": " <> valueToPurs value 83 | 84 | valueToPurs :: AST.Value -> String 85 | valueToPurs = case _ of 86 | AST.Value_Variable _ -> "Value_Variable not yet implemented" 87 | AST.Value_IntValue val -> show $ unwrap val 88 | AST.Value_FloatValue val -> show $ unwrap val 89 | AST.Value_StringValue val -> show $ unwrap val 90 | AST.Value_BooleanValue val -> show $ unwrap val 91 | AST.Value_NullValue _ -> "NullValue variable not yet implemented" 92 | AST.Value_EnumValue val -> unwrap val 93 | AST.Value_ListValue val -> listValueToPurs val 94 | AST.Value_ObjectValue val -> objectValueToPurs val 95 | 96 | 97 | objectValueToPurs :: AST.ObjectValue -> String 98 | objectValueToPurs (AST.ObjectValue args) = 99 | indent 100 | $ "\n{ " 101 | <> intercalate "\n, " (map argumentToPurs args) 102 | <> "\n}" 103 | 104 | listValueToPurs :: AST.ListValue -> String 105 | listValueToPurs (AST.ListValue args) = 106 | indent 107 | $ "( " 108 | <> intercalate " ++ " (map valueToPurs args) 109 | <> " )" 110 | 111 | lowerCaseFirst :: String -> String 112 | lowerCaseFirst str = String.toLower (String.take 1 str) <> (String.drop 1 str) -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Transform/NullableOverrides.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.Transform.NullableOverrides where 2 | 3 | import Prelude 4 | 5 | import Data.GraphQL.AST as AST 6 | import Data.Lens (class Wander, over, prism', traversed) 7 | import Data.Lens.Iso.Newtype (_Newtype) 8 | import Data.Map (Map, lookup) 9 | import Data.Maybe (Maybe(..)) 10 | import Data.Profunctor.Choice (class Choice) 11 | import Data.Traversable (class Traversable) 12 | import Data.Tuple (Tuple, uncurry) 13 | 14 | inputObjectTypeDefinitionLens :: forall c. Choice c => Wander c => c AST.InputObjectTypeDefinition AST.InputObjectTypeDefinition -> c AST.Document AST.Document 15 | inputObjectTypeDefinitionLens = uPrism AST._Document 16 | <<< traversed 17 | <<< uPrism AST._Definition_TypeSystemDefinition 18 | <<< uPrism AST._TypeSystemDefinition_TypeDefinition 19 | <<< uPrism AST._TypeDefinition_InputObjectTypeDefinition 20 | 21 | objectTypeDefinitionLens :: forall c. Choice c => Wander c => c AST.ObjectTypeDefinition AST.ObjectTypeDefinition -> c AST.Document AST.Document 22 | objectTypeDefinitionLens = uPrism AST._Document 23 | <<< traversed 24 | <<< uPrism AST._Definition_TypeSystemDefinition 25 | <<< uPrism AST._TypeSystemDefinition_TypeDefinition 26 | <<< uPrism AST._TypeDefinition_ObjectTypeDefinition 27 | 28 | inputFieldsLens :: forall l w. Traversable l => Wander w => w AST.InputValueDefinition AST.InputValueDefinition -> w (l AST.InputFieldsDefinition) (l AST.InputFieldsDefinition) 29 | inputFieldsLens = 30 | traversed 31 | <<< uPrism AST._InputFieldsDefinition 32 | <<< traversed 33 | 34 | uPrism :: forall s a c. Tuple (a -> s) (s -> Maybe a) -> (Choice c => c a a -> c s s) 35 | uPrism = uncurry prism' 36 | 37 | applyNullableOverrides :: Map String (Map String Boolean) -> AST.Document -> AST.Document 38 | applyNullableOverrides overrides = 39 | over (inputObjectTypeDefinitionLens <<< _Newtype) applyToInputDefinition 40 | >>> over (objectTypeDefinitionLens <<< _Newtype) applyToTypeDefinition 41 | where 42 | applyToInputDefinition def@{ name, inputFieldsDefinition } | Just objOverrides <- lookup name overrides = 43 | def { inputFieldsDefinition = over (inputFieldsLens <<< _Newtype) (applyToInputFieldsDefinition objOverrides) inputFieldsDefinition } 44 | applyToInputDefinition def = def 45 | 46 | applyToInputFieldsDefinition objOverrides def@{ name, type: tipe } | Just nullable <- lookup name objOverrides = 47 | def { type = setNullable nullable tipe } 48 | applyToInputFieldsDefinition _ def = def 49 | 50 | applyToTypeDefinition def@{ name, fieldsDefinition } | Just objOverrides <- lookup name overrides = 51 | def 52 | { fieldsDefinition = over (traversed <<< _Newtype <<< traversed <<< _Newtype) (applyToFieldsDefinition objOverrides) fieldsDefinition 53 | } 54 | 55 | applyToTypeDefinition def = def 56 | 57 | applyToFieldsDefinition objOverrides def@{ name, type: tipe } | Just nullable <- lookup name objOverrides = 58 | def { type = setNullable nullable tipe } 59 | 60 | applyToFieldsDefinition _ def = def 61 | 62 | setNullable :: Boolean -> AST.Type -> AST.Type 63 | setNullable = case _, _ of 64 | true, AST.Type_NonNullType (AST.NonNullType_NamedType t) -> AST.Type_NamedType t 65 | true, AST.Type_NonNullType (AST.NonNullType_ListType t) -> AST.Type_ListType t 66 | false, AST.Type_NamedType t -> AST.Type_NonNullType $ AST.NonNullType_NamedType t 67 | false, AST.Type_ListType t -> AST.Type_NonNullType $ AST.NonNullType_ListType t 68 | _, t -> t -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Types.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.Types 2 | ( FileToWrite 3 | , FilesToWrite 4 | , GqlEnum 5 | , GqlInput 6 | , GqlPath(..) 7 | , InputOptions 8 | , JsResult 9 | , PursGql 10 | , QualifiedType 11 | , defaultInputOptions 12 | ) where 13 | 14 | import Prelude hiding (between) 15 | 16 | import Control.Promise (Promise) 17 | import Data.Argonaut.Core (Json) 18 | import Data.Map (Map) 19 | import Data.Map as Map 20 | import Data.Maybe (Maybe(..)) 21 | import Effect (Effect) 22 | import Effect.Aff (Aff) 23 | import PureScript.CST.Types (Module) 24 | import PureScript.CST.Types as CST 25 | 26 | type InputOptions = 27 | -- | Map from GraphQL type name to PureScript module name and type name 28 | { gqlToPursTypes :: Map String QualifiedType 29 | -- | override types by typename then fieldname 30 | , fieldTypeOverrides :: 31 | Map String 32 | ( Map String QualifiedType 33 | ) 34 | -- | override nullability by typename then fieldname 35 | , nullableOverrides :: Map String (Map String Boolean) 36 | -- | override arg types by typename then fieldname then arg name 37 | , argTypeOverrides :: 38 | Map String 39 | ( Map String 40 | ( Map String QualifiedType 41 | ) 42 | ) 43 | , idImport :: Maybe QualifiedType 44 | , dir :: String 45 | , useNewtypesForRecords :: Boolean 46 | , modulePath :: Array String 47 | , enumImports :: Array String 48 | , customEnumCode :: { name :: String, values :: Array { gql :: String, transformed :: String } } -> String 49 | , cache :: 50 | Maybe 51 | { get :: String -> Aff (Maybe Json) 52 | , set :: { key :: String, val :: Json } -> Aff Unit 53 | } 54 | , enumValueNameTransform :: Maybe (String -> String) 55 | } 56 | 57 | type QualifiedType = { moduleName :: String, typeName :: String } 58 | 59 | data GqlPath = SelectionSet GqlPath | Args GqlPath | Node String 60 | 61 | defaultInputOptions :: InputOptions 62 | defaultInputOptions = 63 | { gqlToPursTypes: Map.empty 64 | , fieldTypeOverrides: Map.empty 65 | , nullableOverrides: Map.empty 66 | , argTypeOverrides: Map.empty 67 | , idImport: Nothing 68 | , dir: "" 69 | , useNewtypesForRecords: true 70 | , modulePath: [] 71 | , enumImports: [] 72 | , customEnumCode: const "" 73 | , cache: Nothing 74 | , enumValueNameTransform: Nothing 75 | } 76 | 77 | type GqlInput = { schema :: Json, moduleName :: String } 78 | 79 | type GqlEnum = { name :: String, description :: Maybe String, values :: Array String } 80 | 81 | type GqlEnumCst = { type :: CST.Type Void, description :: Maybe String, values :: Array String } 82 | 83 | type PursGql = 84 | { moduleName :: String 85 | , mainSchemaCode :: String 86 | , directives :: String 87 | , symbols :: Array String 88 | , enums :: Array GqlEnum 89 | } 90 | 91 | type PursGqlCst = 92 | { moduleName :: String 93 | , schema :: Module Void 94 | , directives :: Module Void 95 | , symbols :: Array String 96 | , enums :: Array GqlEnum 97 | } 98 | 99 | type JsResult = Effect 100 | ( Promise 101 | { argsTypeError :: String 102 | , parseError :: String 103 | , result :: FilesToWrite 104 | } 105 | ) 106 | 107 | type FileToWrite = 108 | { path :: String 109 | , code :: String 110 | } 111 | 112 | type FilesToWrite = 113 | { schemas :: Array FileToWrite 114 | , directives :: Array FileToWrite 115 | , enums :: Array FileToWrite 116 | , symbols :: FileToWrite 117 | } 118 | 119 | -------------------------------------------------------------------------------- /codegen/schema/src/GraphQL/Client/CodeGen/Util.purs: -------------------------------------------------------------------------------- 1 | -- | Utilities for making purs code gen 2 | module GraphQL.Client.CodeGen.Util where 3 | 4 | import Prelude 5 | 6 | import Data.Foldable (class Foldable, foldMap, intercalate) 7 | import Data.GraphQL.AST as AST 8 | import Data.Map (Map, lookup) 9 | import Data.Maybe (Maybe, fromMaybe') 10 | import Data.String.Extra (pascalCase) 11 | import GraphQL.Client.CodeGen.Lines (indent) 12 | 13 | namedTypeToPurs :: Map String String -> AST.NamedType -> String 14 | namedTypeToPurs gqlScalarsToPursTypes (AST.NamedType str) = typeName gqlScalarsToPursTypes str 15 | 16 | inlineComment :: Maybe String -> String 17 | inlineComment = foldMap (\str -> "\n{- " <> str <> " -}\n") 18 | 19 | typeName :: Map String String -> String -> String 20 | typeName gqlScalarsToPursTypes str = 21 | lookup str gqlScalarsToPursTypes 22 | # fromMaybe' \_ -> case pascalCase str of 23 | "Id" -> "ID" 24 | "Float" -> "Number" 25 | "Numeric" -> "Number" 26 | "Bigint" -> "Number" 27 | "Smallint" -> "Int" 28 | "Integer" -> "Int" 29 | "Int" -> "Int" 30 | "Int2" -> "Int" 31 | "Int4" -> "Int" 32 | "Int8" -> "Int" 33 | "Text" -> "String" 34 | "Citext" -> "String" 35 | "Jsonb" -> "Json" 36 | "Timestamp" -> "DateTime" 37 | "Timestamptz" -> "DateTime" 38 | s -> s 39 | 40 | argumentsDefinitionToPurs :: Map String String -> AST.ArgumentsDefinition -> String 41 | argumentsDefinitionToPurs gqlScalarsToPursTypes (AST.ArgumentsDefinition inputValueDefinitions) = 42 | indent $ inputValueDefinitionsToPurs gqlScalarsToPursTypes inputValueDefinitions <> "==> " 43 | 44 | inputValueDefinitionsToPurs :: forall f. Foldable f => Functor f => Map String String -> f AST.InputValueDefinition -> String 45 | inputValueDefinitionsToPurs gqlScalarsToPursTypes inputValueDefinitions = 46 | "\n{ " 47 | <> intercalate "\n, " (map (inputValueDefinitionToPurs gqlScalarsToPursTypes) inputValueDefinitions) 48 | <> "\n}\n" 49 | 50 | 51 | inputValueDefinitionToPurs :: Map String String -> AST.InputValueDefinition -> String 52 | inputValueDefinitionToPurs gqlScalarsToPursTypes ( AST.InputValueDefinition 53 | { description 54 | , name 55 | , type: tipe 56 | } 57 | ) = 58 | inlineComment description 59 | <> name 60 | <> " :: " 61 | <> argTypeToPurs gqlScalarsToPursTypes tipe 62 | 63 | argTypeToPurs :: Map String String -> AST.Type -> String 64 | argTypeToPurs gqlScalarsToPursTypes = case _ of 65 | (AST.Type_NamedType namedType) -> namedTypeToPurs gqlScalarsToPursTypes namedType 66 | (AST.Type_ListType listType) -> argListTypeToPurs gqlScalarsToPursTypes listType 67 | (AST.Type_NonNullType notNullType) -> wrapNotNull $ argNotNullTypeToPurs gqlScalarsToPursTypes notNullType 68 | 69 | argNotNullTypeToPurs :: Map String String -> AST.NonNullType -> String 70 | argNotNullTypeToPurs gqlScalarsToPursTypes = case _ of 71 | AST.NonNullType_NamedType t -> namedTypeToPurs gqlScalarsToPursTypes t 72 | AST.NonNullType_ListType t -> argListTypeToPurs gqlScalarsToPursTypes t 73 | 74 | argListTypeToPurs :: Map String String -> AST.ListType -> String 75 | argListTypeToPurs gqlScalarsToPursTypes (AST.ListType t) = "(Array " <> argTypeToPurs gqlScalarsToPursTypes t <> ")" 76 | 77 | wrapNotNull :: String -> String 78 | wrapNotNull s = "(NotNull " <> s <> ")" 79 | -------------------------------------------------------------------------------- /codegen/schema/test/Client/CodeGen/QueryFromGqlToPurs.Test.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.CodeGen.Query.Test where 2 | 3 | import Prelude 4 | 5 | import Data.Either (Either(..)) 6 | import GraphQL.Client.CodeGen.Query (queryFromGqlToPurs) 7 | import Test.Spec (Spec, describe, it) 8 | import Test.Spec.Assertions (shouldEqual) 9 | 10 | spec :: Spec Unit 11 | spec = 12 | describe "GraphQL.Client.CodeGen.Query" do 13 | describe "toGqlQueryString mainSchemaCode" do 14 | it "converts a single prop query" do 15 | """query MyQuery { 16 | users { 17 | first_name 18 | } 19 | }""" 20 | `shouldBeAsPurs` 21 | """myQuery = 22 | { users: 23 | { first_name 24 | } 25 | }""" 26 | it "converts a 2 prop query" do 27 | """query MyQuery { 28 | users { 29 | first_name 30 | last_name 31 | } 32 | }""" 33 | `shouldBeAsPurs` 34 | """myQuery = 35 | { users: 36 | { first_name 37 | , last_name 38 | } 39 | }""" 40 | it "converts queries with arguments" do 41 | """query MyQuery { 42 | users(limit: 10) { 43 | first_name 44 | } 45 | }""" 46 | `shouldBeAsPurs` 47 | """myQuery = 48 | { users: 49 | { limit: 10 50 | } =>> 51 | { first_name 52 | } 53 | }""" 54 | it "converts queries with aliases" do 55 | """query MyQuery { 56 | users_alias: users { 57 | first_name 58 | } 59 | }""" 60 | `shouldBeAsPurs` 61 | """myQuery = 62 | { users_alias: users: 63 | { first_name 64 | } 65 | }""" 66 | where 67 | shouldBeAsPurs l r = queryFromGqlToPurs l `shouldEqual` Right r 68 | -------------------------------------------------------------------------------- /codegen/schema/test/Data/GraphQL/ParseFull1.purs: -------------------------------------------------------------------------------- 1 | module Data.GraphQL.ParseFull1.Test where 2 | 3 | import Prelude 4 | 5 | import Data.Either (either) 6 | import Data.GraphQL.AST (ObjectValue(..)) 7 | import Data.GraphQL.AST as AST 8 | import Data.GraphQL.Parser as GP 9 | import Data.Lens (class Wander, _Just) 10 | import Data.Lens as L 11 | import Data.Lens.Index as LI 12 | import Data.Lens.Record as LR 13 | import Data.List (singleton) 14 | import Data.Maybe (Maybe(..)) 15 | import Data.Profunctor.Choice (class Choice) 16 | import Type.Proxy (Proxy(..)) 17 | import Data.Tuple (uncurry) 18 | import Effect.Aff (Aff) 19 | import Effect.Class (liftEffect) 20 | import Effect.Exception (throw) 21 | import Test.Spec (Spec, before, describe, it) 22 | import Test.Spec.Assertions (shouldEqual) 23 | import Parsing (runParser) 24 | 25 | parseDocument ∷ String → Aff (AST.Document) 26 | parseDocument t = liftEffect (either (throw <<< show) pure (runParser t GP.document)) 27 | 28 | query = 29 | """query($id: ID!) { 30 | id 31 | user(id: $id, name: { equals: "bob" }, foo: "bar") { 32 | name 33 | } 34 | } 35 | 36 | mutation MyMutation { 37 | id # then an inline fragment 38 | ... on User { 39 | friends { 40 | count 41 | } 42 | } 43 | } 44 | 45 | # tah dah! 46 | """ ∷ 47 | String 48 | 49 | lensToQueryDefinition ∷ ∀ m. Choice m ⇒ Wander m ⇒ m AST.OperationDefinition AST.OperationDefinition → m AST.Document AST.Document 50 | lensToQueryDefinition = 51 | ( uncurry L.prism' AST._Document 52 | <<< LI.ix 0 53 | <<< uncurry L.prism' AST._Definition_ExecutableDefinition 54 | <<< uncurry L.prism' AST._ExecutableDefinition_OperationDefinition 55 | ) 56 | 57 | getFirstQueryVarDef ∷ AST.Document → Maybe AST.VariableDefinition 58 | getFirstQueryVarDef = 59 | L.preview 60 | $ ( lensToQueryDefinition 61 | <<< uncurry L.prism' AST._OperationDefinition_OperationType 62 | <<< LR.prop (Proxy ∷ Proxy "variableDefinitions") 63 | <<< L._Just 64 | <<< uncurry L.prism' AST._VariableDefinitions 65 | <<< LI.ix 0 66 | ) 67 | 68 | getNameDef ∷ AST.Document → Maybe AST.Argument 69 | getNameDef = 70 | L.preview 71 | $ ( lensToQueryDefinition 72 | <<< uncurry L.prism' AST._OperationDefinition_OperationType 73 | <<< LR.prop (Proxy ∷ Proxy "selectionSet") 74 | <<< uncurry L.prism' AST._SelectionSet 75 | <<< LI.ix 1 76 | <<< uncurry L.prism' AST._Selection_Field 77 | <<< uncurry L.prism' AST._Field 78 | <<< LR.prop (Proxy ∷ Proxy "arguments") 79 | <<< _Just 80 | <<< uncurry L.prism' AST._Arguments 81 | <<< LI.ix 1 82 | ) 83 | 84 | spec ∷ Spec Unit 85 | spec = 86 | describe "test full query" do 87 | before (parseDocument query) 88 | $ do 89 | it "should parse $id:ID!" \doc → do 90 | getFirstQueryVarDef doc `shouldEqual` (Just $ AST.VariableDefinition { variable: AST.Variable "id", type: AST.Type_NonNullType (AST.NonNullType_NamedType $ AST.NamedType "ID"), defaultValue: Nothing }) 91 | it "should parse name:{equals:\"bob\"}" \doc → do 92 | getNameDef doc `shouldEqual` (Just $ AST.Argument { name: "name", value: AST.Value_ObjectValue $ ObjectValue (singleton (AST.Argument { name: "equals", value: AST.Value_StringValue $ AST.StringValue "bob" })) }) 93 | -------------------------------------------------------------------------------- /codegen/schema/test/Data/GraphQL/ParseFull3.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.GraphQL.ParseFull3 where 2 | 3 | import Prelude 4 | 5 | import Data.Either (either) 6 | import Data.GraphQL.AST as AST 7 | import Data.GraphQL.Parser as GP 8 | import Data.Lens (class Wander, Prism', _2, _Just, preview, prism', toListOf, traversed) 9 | import Data.Lens.Common (simple) 10 | import Data.Lens.Iso.Newtype (_Newtype) 11 | import Data.Lens.Record (prop) 12 | import Data.List (List(..), length, (:)) 13 | import Data.Maybe (Maybe(..)) 14 | import Data.Profunctor.Choice (class Choice) 15 | import Data.Tuple (Tuple(..), uncurry) 16 | import Effect.Aff (Aff) 17 | import Effect.Class (liftEffect) 18 | import Effect.Exception (throw) 19 | import Node.Encoding (Encoding(..)) 20 | import Node.FS.Sync (readTextFile) 21 | import Parsing (runParser) 22 | import Test.Spec (SpecT, before, describe, it) 23 | import Test.Spec.Assertions (shouldEqual) 24 | import Type.Proxy (Proxy(..)) 25 | 26 | parseDocument :: String -> Aff AST.Document 27 | parseDocument t = do 28 | rtf <- liftEffect $ readTextFile UTF8 t 29 | liftEffect (either (throw <<< show) pure (runParser rtf GP.document)) 30 | 31 | lensToObjectDefinitions ∷ ∀ m. Choice m ⇒ Wander m ⇒ m AST.ObjectTypeDefinition AST.ObjectTypeDefinition → m AST.Document AST.Document 32 | lensToObjectDefinitions = 33 | ( uncurry prism' AST._Document 34 | <<< traversed 35 | <<< uncurry prism' AST._Definition_TypeSystemDefinition 36 | <<< uncurry prism' AST._TypeSystemDefinition_TypeDefinition 37 | <<< uncurry prism' AST._TypeDefinition_ObjectTypeDefinition 38 | ) 39 | 40 | peel :: forall a. Prism' (List a) (Tuple a (List a)) 41 | peel = 42 | prism' (\(Tuple f s) -> (Cons f s)) 43 | ( \l -> case l of 44 | Nil -> Nothing 45 | (Cons a b) -> Just $ Tuple a b 46 | ) 47 | 48 | testSwapi ∷ ∀ m. Monad m ⇒ SpecT Aff Unit m Unit 49 | testSwapi = 50 | describe "test swapi" do 51 | before (parseDocument "schemas/swapi.graphql") 52 | $ do 53 | -- 52 was confirmed by a quick n' dirty parsing of the document in python 54 | it "should get 52 type definitions" \doc → do 55 | (length (toListOf lensToObjectDefinitions doc)) `shouldEqual` 52 56 | it "should have a type Film that implements an interface called Node" \doc → do 57 | preview 58 | ( simple _Newtype 59 | <<< peel 60 | <<< _2 61 | <<< traversed 62 | <<< uncurry prism' AST._Definition_TypeSystemDefinition 63 | <<< uncurry prism' AST._TypeSystemDefinition_TypeDefinition 64 | <<< uncurry prism' AST._TypeDefinition_ObjectTypeDefinition 65 | <<< simple _Newtype 66 | <<< (prop (Proxy :: Proxy "implementsInterfaces")) 67 | <<< _Just 68 | ) 69 | doc 70 | `shouldEqual` 71 | Just (AST.ImplementsInterfaces (AST.NamedType "Node" : Nil)) 72 | -------------------------------------------------------------------------------- /codegen/schema/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Effect (Effect) 5 | import Effect.Aff (launchAff_) 6 | import Test.Spec.Discovery (discover) 7 | import Test.Spec.Reporter.Console (consoleReporter) 8 | import Test.Spec.Runner (runSpec) 9 | 10 | main :: Effect Unit 11 | main = launchAff_ do 12 | specs <- discover """.*\.Test""" 13 | runSpec [consoleReporter] specs 14 | -------------------------------------------------------------------------------- /codegen/schema/write-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | import { schemasFromGqlToPursJs } from '../../gen-schema-bundled.mjs'; 2 | import fs from 'fs'; 3 | import { promisify } from 'util'; 4 | import {dirname} from 'path'; 5 | 6 | const writeFileRec = promisify((path, contents, cb) => { 7 | fs.mkdir(dirname(path), { recursive: true}, function (err) { 8 | if (err) return cb(err); 9 | 10 | fs.writeFile(path, contents, cb); 11 | }); 12 | }) 13 | 14 | export async function writePursSchemas (opts, gqlSchemas) { 15 | const { argsTypeError, parseError, result } = 16 | await schemasFromGqlToPursJs(opts, gqlSchemas)() 17 | 18 | if (argsTypeError) { 19 | throw new Error(argsTypeError) 20 | } 21 | 22 | if (parseError) { 23 | throw new Error(parseError) 24 | } 25 | 26 | const { schemas, enums, directives, symbols } = result 27 | 28 | await Promise.all([...schemas, ...enums, ...directives, symbols].map(({ path, code }) => writeFileRec(path, code))) 29 | 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /e2e/1-affjax/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /e2e/1-affjax/README.md: -------------------------------------------------------------------------------- 1 | A simple affjax example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /e2e/1-affjax/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1-affjax", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && spago build && node ./test.mjs", 11 | "start": "node server.js" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "express": "^4.17.1", 17 | "express-graphql": "^0.12.0", 18 | "graphql": "^15.4.0", 19 | "isomorphic-ws": "^5.0.0", 20 | "xhr2": "^0.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /e2e/1-affjax/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(id: Int): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: Int 15 | name: String! 16 | } 17 | `) 18 | 19 | const root = { 20 | prop: () => { 21 | return 'Hello world!' 22 | }, 23 | widgets: ({ id }) => 24 | widgets.filter(w => !id || id === w.id) 25 | 26 | } 27 | 28 | const widgets = [ 29 | { id: 1, name: 'one' }, 30 | { id: 2, name: 'two' } 31 | ] 32 | 33 | const app = express() 34 | 35 | app.use('/graphql', graphqlHTTP({ 36 | schema, 37 | rootValue: root, 38 | graphiql: true 39 | })) 40 | 41 | app.listen(4892, onListening) 42 | } 43 | -------------------------------------------------------------------------------- /e2e/1-affjax/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /e2e/1-affjax/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 1-affjax 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /e2e/1-affjax/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson, decodeJson) 6 | import Data.Maybe (isJust) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff, launchAff_) 9 | import Effect.Class.Console (logShow) 10 | import GraphQL.Client.Args ((=>>)) 11 | import GraphQL.Client.Operation (OpQuery) 12 | import GraphQL.Client.BaseClients.Affjax.Node (AffjaxNodeClient(..)) 13 | import GraphQL.Client.Query (query, queryFullRes) 14 | import GraphQL.Client.Types (class GqlQuery, Client(..)) 15 | import Type.Data.List (Nil') 16 | import Type.Proxy (Proxy(..)) 17 | 18 | main :: Effect Unit 19 | main = 20 | launchAff_ do 21 | { widgets } <- 22 | queryGql "Widget names with id 1" 23 | { widgets: { id: 1 } =>> { name } } 24 | logShow $ map _.name widgets 25 | fullResult <- 26 | queryFullRes decodeJson identity client "Widget names with id 1" 27 | { widgets: { id: 1 } =>> { name } } 28 | 29 | logShow fullResult.data_ 30 | logShow $ isJust fullResult.errors 31 | logShow $ isJust fullResult.errors_json 32 | 33 | -- Run gql query 34 | queryGql 35 | :: forall query returns 36 | . GqlQuery Nil' OpQuery Schema query returns 37 | => DecodeJson returns 38 | => String 39 | -> query 40 | -> Aff returns 41 | queryGql = query client 42 | 43 | client 44 | :: Client AffjaxNodeClient 45 | { directives :: Proxy Nil' 46 | , query :: Schema 47 | , mutation :: Void 48 | , subscription :: Void 49 | } 50 | client = (Client $ AffjaxNodeClient "http://localhost:4892/graphql" []) 51 | 52 | -- Schema 53 | type Schema = 54 | { prop :: String 55 | , widgets :: { id :: Int } -> Array Widget 56 | } 57 | 58 | type Widget = 59 | { name :: String 60 | , id :: Int 61 | } 62 | 63 | -- Symbols 64 | prop :: Proxy "prop" 65 | prop = Proxy 66 | 67 | name :: Proxy "name" 68 | name = Proxy 69 | -------------------------------------------------------------------------------- /e2e/1-affjax/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import serverFn from './server-fn.js'; 3 | import {main} from './output/Main/index.js'; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | serverFn(main) 13 | 14 | setTimeout(() => { 15 | deepStrictEqual(logs, [ 16 | '["one"]', 17 | '(Right { widgets: [{ name: "one" }] })', 18 | 'false', 19 | 'false' 20 | ]) 21 | console.info('tests passed') 22 | process.exit(0) 23 | }, 250) 24 | -------------------------------------------------------------------------------- /e2e/1-affjax/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /e2e/2-comments/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /e2e/2-comments/README.md: -------------------------------------------------------------------------------- 1 | Handle enums and unions with code gen. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /e2e/2-comments/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: true 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /e2e/2-comments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2-comments", 3 | "version": "1.0.0", 4 | "description": "Handle comments in with code gen.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "console": "^0.7.2", 18 | "express": "4.17.1", 19 | "express-graphql": "0.12.0", 20 | "graphql": "15.4.0", 21 | "isomorphic-ws": "^5.0.0", 22 | "mkdirp": "1.0.4", 23 | "rimraf": "3.0.2", 24 | "xhr2": "0.2.0" 25 | }, 26 | "devDependencies": { 27 | "exec-sh": "^0.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /e2e/2-comments/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(colour: Colour): [Widget!]! 11 | } 12 | #Comment here 13 | type Widget { 14 | id: Int 15 | name: String! 16 | colour: Colour! 17 | } 18 | "colour description" 19 | enum Colour { 20 | "red description" 21 | RED 22 | GREEN 23 | BLUE 24 | yellow 25 | } 26 | `) 27 | 28 | const root = { 29 | prop: () => { 30 | return 'Hello world!' 31 | }, 32 | widgets: ({ colour }) => 33 | widgets.filter(w => !colour || colour === w.colour) 34 | } 35 | 36 | const widgets = [ 37 | { id: 1, name: 'one', colour: 'RED' }, 38 | { id: 2, name: 'two', colour: 'GREEN' } 39 | ] 40 | 41 | const app = express() 42 | 43 | app.use('/graphql', graphqlHTTP({ 44 | schema, 45 | rootValue: root, 46 | graphiql: true 47 | })) 48 | 49 | app.listen(4892, onListening) 50 | } 51 | -------------------------------------------------------------------------------- /e2e/2-comments/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /e2e/2-comments/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 2-comments 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /e2e/2-comments/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Newtype (unwrap) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff, launchAff_) 9 | import Effect.Class.Console (logShow) 10 | import Generated.Gql.Schema.Admin (Query) 11 | import Generated.Gql.Schema.Admin.Enum.Colour (Colour(..)) 12 | import Generated.Gql.Symbols (colour) 13 | import GraphQL.Client.Args ((=>>)) 14 | import GraphQL.Client.Operation (OpQuery) 15 | import GraphQL.Client.Query (query_) 16 | import GraphQL.Client.Types (class GqlQuery) 17 | import Type.Data.List (Nil') 18 | import GraphQL.Client.Union (GqlUnion(..)) 19 | import Type.Proxy (Proxy(..)) 20 | 21 | main :: Effect Unit 22 | main = 23 | launchAff_ do 24 | { widgets } <- 25 | queryGql "WidgetColoursWithId1" 26 | { widgets: { colour: RED } =>> { colour } } 27 | 28 | -- Will log [ RED ] as there is one red widget 29 | logShow $ map _.colour widgets 30 | 31 | -- Run gql query 32 | queryGql :: 33 | forall query returns. 34 | GqlQuery Nil' OpQuery Query query returns => 35 | DecodeJson returns => 36 | String -> query -> Aff returns 37 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 38 | -------------------------------------------------------------------------------- /e2e/2-comments/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | 15 | serverFn(async () => { 16 | try { 17 | await gps(); 18 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 19 | const { main } = await import('./output/Main/index.js') 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ['[RED]']) 23 | console.info('tests passed') 24 | process.exit(0) 25 | }, 250) 26 | } catch (err) { 27 | console.error('test error', err) 28 | process.exit(1) 29 | } 30 | }) 31 | 32 | setTimeout(() => { 33 | console.error('Timeout') 34 | process.exit(1) 35 | }, 60000) 36 | -------------------------------------------------------------------------------- /examples/1-simple/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /examples/1-simple/README.md: -------------------------------------------------------------------------------- 1 | A simple example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/1-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1-simple", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && spago build && node ./test.mjs", 11 | "start": "node server.js" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@urql/core": "^1.16.2", 17 | "express": "^4.17.1", 18 | "express-graphql": "^0.12.0", 19 | "graphql": "^15.4.0", 20 | "graphql-ws": "^6.0.4", 21 | "isomorphic-unfetch": "^4.0.2", 22 | "isomorphic-ws": "^5.0.0", 23 | "xhr2": "^0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/1-simple/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(id: Int): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: Int 15 | name: String! 16 | } 17 | `) 18 | 19 | const root = { 20 | prop: () => { 21 | return 'Hello world!' 22 | }, 23 | widgets: ({ id }) => 24 | widgets.filter(w => !id || id === w.id) 25 | 26 | } 27 | 28 | const widgets = [ 29 | { id: 1, name: 'one' }, 30 | { id: 2, name: 'two' } 31 | ] 32 | 33 | const app = express() 34 | 35 | app.use('/graphql', graphqlHTTP({ 36 | schema, 37 | rootValue: root, 38 | graphiql: true 39 | })) 40 | 41 | app.listen(4892, onListening) 42 | } 43 | -------------------------------------------------------------------------------- /examples/1-simple/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/1-simple/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 1-simple 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/1-simple/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import GraphQL.Client.Args ((=>>)) 10 | import GraphQL.Client.Operation (OpQuery) 11 | import GraphQL.Client.Query (query_) 12 | import GraphQL.Client.Types (class GqlQuery) 13 | import Type.Data.List (Nil') 14 | import Type.Proxy (Proxy(..)) 15 | 16 | main :: Effect Unit 17 | main = 18 | launchAff_ do 19 | { widgets } <- 20 | queryGql "Widget names with id 1" 21 | { widgets: { id: 1 } =>> { name } } 22 | logShow $ map _.name widgets 23 | 24 | -- Run gql query 25 | queryGql :: 26 | forall query returns. 27 | GqlQuery Nil' OpQuery Schema query returns => 28 | DecodeJson returns => 29 | String -> query -> Aff returns 30 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Schema) 31 | 32 | -- Schema 33 | type Schema 34 | = { prop :: String 35 | , widgets :: { id :: Int } -> Array Widget 36 | } 37 | 38 | type Widget 39 | = { name :: String 40 | , id :: Int 41 | } 42 | 43 | -- Symbols 44 | prop :: Proxy "prop" 45 | prop = Proxy 46 | 47 | name :: Proxy "name" 48 | name = Proxy 49 | -------------------------------------------------------------------------------- /examples/1-simple/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import serverFn from './server-fn.js'; 3 | import {main} from './output/Main/index.js'; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | serverFn(main) 13 | 14 | setTimeout(() => { 15 | deepStrictEqual(logs, ['["one"]']) 16 | console.info('tests passed') 17 | process.exit(0) 18 | }, 250) 19 | -------------------------------------------------------------------------------- /examples/1-simple/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/10-aliases/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /examples/10-aliases/README.md: -------------------------------------------------------------------------------- 1 | A simple example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/10-aliases/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "10-aliases", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && spago build && node ./test.mjs", 11 | "start": "node server.js" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@urql/core": "^1.16.2", 17 | "express": "^4.17.1", 18 | "express-graphql": "^0.12.0", 19 | "graphql": "^15.4.0", 20 | "graphql-ws": "^6.0.4", 21 | "isomorphic-unfetch": "^4.0.2", 22 | "isomorphic-ws": "^5.0.0", 23 | "xhr2": "^0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/10-aliases/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(id: Int): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: Int 15 | name: String! 16 | } 17 | `) 18 | 19 | const root = { 20 | prop: () => { 21 | return 'Hello world!' 22 | }, 23 | widgets: ({ id }) => 24 | widgets.filter(w => !id || id === w.id) 25 | 26 | } 27 | 28 | const widgets = [ 29 | { id: 1, name: 'one' }, 30 | { id: 2, name: 'two' } 31 | ] 32 | 33 | const app = express() 34 | 35 | app.use('/graphql', graphqlHTTP({ 36 | schema, 37 | rootValue: root, 38 | graphiql: true 39 | })) 40 | 41 | app.listen(4892, onListening) 42 | } 43 | -------------------------------------------------------------------------------- /examples/10-aliases/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/10-aliases/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 10-aliases 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../../ 17 | -------------------------------------------------------------------------------- /examples/10-aliases/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Newtype (unwrap) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff, launchAff_) 9 | import Effect.Class.Console (logShow) 10 | import GraphQL.Client.Alias ((:)) 11 | import GraphQL.Client.Alias.Dynamic (Spread(..)) 12 | import GraphQL.Client.Args ((=>>)) 13 | import GraphQL.Client.Operation (OpQuery) 14 | import GraphQL.Client.Query (query_) 15 | import GraphQL.Client.Types (class GqlQuery) 16 | import Type.Data.List (Nil') 17 | import Type.Proxy (Proxy(..)) 18 | 19 | main :: Effect Unit 20 | main = 21 | launchAff_ do 22 | { renamed } <- 23 | queryGql "widgets_aliased" 24 | { renamed: widgets : { id: 1 } =>> { name } 25 | } 26 | logShow $ map _.name renamed 27 | 28 | dynamic <- 29 | map unwrap $ queryGql "dynamic_alias" 30 | $ Spread widgets 31 | [ { id: 1 }, { id: 2 } ] 32 | { name } 33 | 34 | logShow dynamic 35 | 36 | -- Run gql query 37 | queryGql :: 38 | forall query returns. 39 | GqlQuery Nil' OpQuery Schema query returns => 40 | DecodeJson returns => 41 | String -> query -> Aff returns 42 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Schema) 43 | 44 | -- Schema 45 | type Schema 46 | = { prop :: String 47 | , widgets :: { id :: Int } -> Array Widget 48 | } 49 | 50 | type Widget 51 | = { name :: String 52 | , id :: Int 53 | } 54 | 55 | -- Symbols 56 | prop :: Proxy "prop" 57 | prop = Proxy 58 | 59 | name :: Proxy "name" 60 | name = Proxy 61 | 62 | widgets :: Proxy "widgets" 63 | widgets = Proxy 64 | -------------------------------------------------------------------------------- /examples/10-aliases/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import serverFn from './server-fn.js'; 3 | import {main} from './output/Main/index.js'; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | serverFn(main) 13 | 14 | setTimeout(() => { 15 | deepStrictEqual( 16 | logs, 17 | ['["one"]', 18 | '[[{ name: "one" }],[{ name: "two" }]]' 19 | ]) 20 | console.info('tests passed') 21 | process.exit(0) 22 | }, 250) 23 | -------------------------------------------------------------------------------- /examples/10-aliases/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/11-unions/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/11-unions/README.md: -------------------------------------------------------------------------------- 1 | Handle enums and unions with code gen. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/11-unions/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/11-unions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "11-unions", 3 | "version": "1.0.0", 4 | "description": "Handle unions in with code gen.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "console": "^0.7.2", 18 | "express": "4.17.1", 19 | "express-graphql": "0.12.0", 20 | "graphql": "15.4.0", 21 | "isomorphic-ws": "^5.0.0", 22 | "mkdirp": "1.0.4", 23 | "rimraf": "3.0.2", 24 | "xhr2": "0.2.0" 25 | }, 26 | "devDependencies": { 27 | "exec-sh": "^0.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/11-unions/server-fn.js: -------------------------------------------------------------------------------- 1 | module.exports = (onListening) => { 2 | const express = require("express"); 3 | const { graphqlHTTP } = require("express-graphql"); 4 | const { buildSchema } = require("graphql"); 5 | 6 | const schema = buildSchema(` 7 | type Query { 8 | character(id: Int): Character! 9 | } 10 | 11 | type Human { 12 | id: Int! 13 | name: String! 14 | height: Float! 15 | } 16 | 17 | type Droid { 18 | id: Int! 19 | name: String! 20 | primaryFunction: String! 21 | } 22 | 23 | union Character = Human | Droid 24 | `); 25 | 26 | const root = { 27 | character: ({id}) => { 28 | if (id === 1) { 29 | return { 30 | __typename: "Human", 31 | name: "Han Solo", 32 | height: 1.8, 33 | id: 1, 34 | }; 35 | }else { 36 | return { 37 | __typename: "Droid", 38 | name: "R2D2", 39 | height: 0.5, 40 | id: 2, 41 | }; 42 | } 43 | }, 44 | }; 45 | 46 | const app = express(); 47 | 48 | app.use( 49 | "/graphql", 50 | graphqlHTTP({ 51 | schema, 52 | rootValue: root, 53 | graphiql: true, 54 | }) 55 | ); 56 | 57 | app.listen(4892, onListening); 58 | }; 59 | -------------------------------------------------------------------------------- /examples/11-unions/server.js: -------------------------------------------------------------------------------- 1 | require('./server-fn')(() => { 2 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 3 | }) 4 | -------------------------------------------------------------------------------- /examples/11-unions/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 11-unions 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/11-unions/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Newtype (unwrap) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff, launchAff_) 9 | import Effect.Class.Console (logShow) 10 | import Generated.Gql.Schema.Admin (Query) 11 | import GraphQL.Client.Args ((=>>)) 12 | import GraphQL.Client.Operation (OpQuery) 13 | import GraphQL.Client.Query (query_) 14 | import GraphQL.Client.Types (class GqlQuery) 15 | import GraphQL.Client.Union (GqlUnion(..)) 16 | import Type.Data.List (Nil') 17 | import Type.Proxy (Proxy(..)) 18 | 19 | main :: Effect Unit 20 | main = 21 | launchAff_ do 22 | { character } <- 23 | queryGql "HanSolo" 24 | { character: 25 | { id: 1 } =>> GqlUnion 26 | { "Human": { height: unit } 27 | , "Droid": { name: unit } 28 | } 29 | } 30 | 31 | -- Will log [ (inj @"Human" { height: 1.8 }) ] as the union is a human. 32 | logShow $ unwrap character 33 | 34 | -- Run gql query 35 | queryGql 36 | :: forall query returns 37 | . GqlQuery Nil' OpQuery Query query returns 38 | => DecodeJson returns 39 | => String 40 | -> query 41 | -> Aff returns 42 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 43 | -------------------------------------------------------------------------------- /examples/11-unions/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ['(inj @"Human" { height: 1.8 })']) 23 | console.info('tests passed') 24 | process.exit(0) 25 | }, 250) 26 | } catch (err) { 27 | console.error('test error', err) 28 | process.exit(1) 29 | } 30 | }) 31 | 32 | setTimeout(() => { 33 | console.error('Timeout') 34 | process.exit(1) 35 | }, 60000) -------------------------------------------------------------------------------- /examples/11-unions/test/Test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍕" 11 | log "You should add some tests." 12 | 13 | -------------------------------------------------------------------------------- /examples/12-directives/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/12-directives/README.md: -------------------------------------------------------------------------------- 1 | A simple example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/12-directives/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/12-directives/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OxfordAbstracts/purescript-graphql-client/f9c5b5759a0f5dc5af797ce9c725cef3fc0276f2/examples/12-directives/index.js -------------------------------------------------------------------------------- /examples/12-directives/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "12-directives", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "^4.17.1", 18 | "express-graphql": "^0.12.0", 19 | "graphql": "^15.4.0", 20 | "xhr2": "^0.2.0" 21 | }, 22 | "devDependencies": { 23 | "exec-sh": "^0.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/12-directives/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | 9 | directive @cached( 10 | """measured in seconds""" 11 | ttl: Int = 60 12 | 13 | """refresh the cache entry""" 14 | refresh: Boolean = false 15 | ) on QUERY 16 | 17 | type Query { 18 | prop: String 19 | widgets(id: Int): [Widget!]! 20 | } 21 | 22 | type Widget { 23 | id: Int 24 | name: String! 25 | } 26 | `) 27 | 28 | const root = { 29 | prop: () => { 30 | return 'Hello world!' 31 | }, 32 | widgets: ({ id }) => 33 | widgets.filter(w => !id || id === w.id) 34 | 35 | } 36 | 37 | const widgets = [ 38 | { id: 1, name: 'one' }, 39 | { id: 2, name: 'two' } 40 | ] 41 | 42 | const app = express() 43 | 44 | app.use('/graphql', graphqlHTTP({ 45 | schema, 46 | rootValue: root, 47 | graphiql: true 48 | })) 49 | 50 | app.listen(4892, onListening) 51 | } 52 | -------------------------------------------------------------------------------- /examples/12-directives/server.js: -------------------------------------------------------------------------------- 1 | require('./server-fn')(() => { 2 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 3 | }) 4 | -------------------------------------------------------------------------------- /examples/12-directives/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 12-directives 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/12-directives/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | import Data.Argonaut.Decode (class DecodeJson) 5 | import Effect (Effect) 6 | import Effect.Aff (Aff, launchAff_) 7 | import Effect.Class.Console (logShow) 8 | import Generated.Gql.Directives.Admin (Directives, cached) 9 | import Generated.Gql.Schema.Admin (Query) 10 | import GraphQL.Client.Args ((=>>)) 11 | import GraphQL.Client.Operation (OpQuery) 12 | import GraphQL.Client.Query (query_) 13 | import GraphQL.Client.Types (class GqlQuery) 14 | import Type.Proxy (Proxy(..)) 15 | 16 | main :: Effect Unit 17 | main = 18 | launchAff_ do 19 | { widgets } <- 20 | queryGql "widgets_cached" 21 | $ cached { refresh: false } 22 | { widgets: { id: 1 } =>> { name: unit } 23 | } 24 | logShow $ map _.name widgets 25 | 26 | -- Run gql query 27 | queryGql :: 28 | forall query returns. 29 | GqlQuery Directives OpQuery Query query returns => 30 | DecodeJson returns => 31 | String -> query -> Aff returns 32 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 33 | -------------------------------------------------------------------------------- /examples/12-directives/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | const logs = [] 5 | 6 | console.log = (log) => { 7 | console.info(log) 8 | logs.push(log) 9 | } 10 | import serverFn from './server-fn.js' 11 | import gps from './generate-purs-schema.mjs' 12 | serverFn(async () => { 13 | try { 14 | await gps() 15 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 16 | const { main } = await import('./output/Main/index.js') 17 | 18 | main() 19 | setTimeout(() => { 20 | deepStrictEqual(logs, ['["one"]']) 21 | console.info('tests passed') 22 | process.exit(0) 23 | }, 250) 24 | } catch (err) { 25 | console.error('test error', err) 26 | process.exit(1) 27 | } 28 | }) 29 | 30 | setTimeout(() => { 31 | console.error('Timeout') 32 | process.exit(1) 33 | }, 60000) 34 | -------------------------------------------------------------------------------- /examples/12-directives/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/README.md: -------------------------------------------------------------------------------- 1 | A simple example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/13-error-boundaries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "13-error-boundaries", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && spago build && node ./test.mjs", 11 | "start": "node server.js" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "express": "^4.17.1", 17 | "express-graphql": "^0.12.0", 18 | "graphql": "^15.4.0", 19 | "isomorphic-ws": "^5.0.0", 20 | "xhr2": "^0.2.0" 21 | }, 22 | "devDependencies": { 23 | "exec-sh": "^0.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/server-fn.js: -------------------------------------------------------------------------------- 1 | module.exports = (onListening) => { 2 | const express = require("express"); 3 | const { graphqlHTTP } = require("express-graphql"); 4 | const { buildSchema } = require("graphql"); 5 | 6 | const schema = buildSchema(` 7 | type Query { 8 | prop: String 9 | widgets(id: Int): [Widget!]! 10 | } 11 | 12 | type Widget { 13 | id: Int 14 | name: String! 15 | contains_bad_type: BadType 16 | } 17 | 18 | type BadType { 19 | id: Int! 20 | incorrect_type: Int! 21 | } 22 | `); 23 | 24 | const root = { 25 | prop: () => { 26 | return "Hello world!"; 27 | }, 28 | widgets: ({ id }) => widgets.filter((w) => !id || id === w.id), 29 | }; 30 | 31 | const contains_bad_type = { 32 | id: 1, 33 | incorrect_type: () => { 34 | throw new Error("OOOPS!"); 35 | }, 36 | }; 37 | 38 | const widgets = [ 39 | { id: 1, name: "one", contains_bad_type }, 40 | { id: 2, name: "two", contains_bad_type }, 41 | ]; 42 | 43 | const app = express(); 44 | 45 | app.use( 46 | "/graphql", 47 | graphqlHTTP({ 48 | schema, 49 | rootValue: root, 50 | graphiql: true, 51 | }) 52 | ); 53 | 54 | app.listen(4892, onListening); 55 | }; 56 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 13-error-boundaries 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson, decodeJson) 6 | import Data.Either (Either(..)) 7 | import Data.Foldable (fold) 8 | import Data.Maybe (Maybe(..)) 9 | import Effect (Effect) 10 | import Effect.Aff (Aff, launchAff_) 11 | import Effect.Class (liftEffect) 12 | import Effect.Class.Console (logShow) 13 | import GraphQL.Client.Args ((=>>)) 14 | import GraphQL.Client.BaseClients.Urql (UrqlClient, createClient) 15 | import GraphQL.Client.ErrorBoundary (BoundaryResult(..), ErrorBoundary(..), putErrorsInPaths) 16 | import GraphQL.Client.Operation (OpQuery) 17 | import GraphQL.Client.Query (queryFullRes) 18 | import GraphQL.Client.Types (class GqlQuery, Client, GqlRes) 19 | import Type.Data.List (Nil') 20 | import Type.Proxy (Proxy(..)) 21 | 22 | main :: Effect Unit 23 | main = 24 | launchAff_ do 25 | { data_, errors } <- 26 | queryGql "widgets_aliased" 27 | { widgets: 28 | { id: 1 29 | } =>> 30 | { name 31 | , contains_bad_type: ErrorBoundary 32 | { id: unit 33 | , incorrect_type: unit 34 | } 35 | } 36 | } 37 | logShow data_ -- will log the data with tge decode error at the error boundary but without error details 38 | 39 | let 40 | withErrors = putErrorsInPaths (fold errors) data_ -- will contain the data with the decode error and gql error details at the error boundaries 41 | 42 | errorPath = case withErrors of 43 | Right { widgets: [ { contains_bad_type: Error _ [ { path } ] } ] } -> path 44 | _ -> Nothing 45 | 46 | logShow errorPath -- will log the error path from the gql error details 47 | 48 | -- Run gql query 49 | queryGql 50 | :: forall query returns 51 | . GqlQuery Nil' OpQuery Schema query returns 52 | => DecodeJson returns 53 | => String 54 | -> query 55 | -> Aff (GqlRes returns) 56 | queryGql name_ q = do 57 | client <- 58 | liftEffect 59 | $ createClient 60 | { url: "http://localhost:4892/graphql" 61 | , headers: [] 62 | } 63 | queryFullRes decodeJson identity (client :: Client UrqlClient { directives :: Proxy Nil', query :: Schema | _ }) name_ q 64 | 65 | -- Schema 66 | type Schema = 67 | { prop :: String 68 | , widgets :: { id :: Int } -> Array Widget 69 | } 70 | 71 | type Widget = 72 | { name :: String 73 | , id :: Int 74 | , contains_bad_type :: BadType 75 | } 76 | 77 | type BadType = 78 | { id :: Int 79 | , incorrect_type :: Int 80 | } 81 | 82 | -- Symbols 83 | prop :: Proxy "prop" 84 | prop = Proxy 85 | 86 | name :: Proxy "name" 87 | name = Proxy 88 | 89 | widgets :: Proxy "widgets" 90 | widgets = Proxy 91 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from "assert"; 2 | import serverFn from "./server-fn.js"; 3 | import { main } from "./output/Main/index.js"; 4 | 5 | const logs = []; 6 | 7 | console.log = (log) => { 8 | console.info(log); 9 | logs.push(log); 10 | }; 11 | 12 | serverFn(main); 13 | 14 | setTimeout(() => { 15 | deepStrictEqual(logs, [ 16 | '(Right { widgets: [{ contains_bad_type: (Error (TypeMismatch "Object") unit), name: "one" }] })', 17 | '(Just [(Right "widgets"),(Left 0),(Right "contains_bad_type"),(Right "incorrect_type")])', 18 | ]); 19 | console.info("tests passed"); 20 | process.exit(0); 21 | }, 250); 22 | -------------------------------------------------------------------------------- /examples/13-error-boundaries/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/2-codegen/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/2-codegen/README.md: -------------------------------------------------------------------------------- 1 | A codegen example, similar to the simple example, but the schema 2 | and symbols are created for you. 3 | 4 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/2-codegen/generate-purs-schema.js: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // const { generateSchema } = require('purescript-graphql-client') 3 | import { generateSchema } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchema({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql', 'Admin'], 9 | useNewtypesForRecords: false, 10 | url: 'http://localhost:4892/graphql' 11 | }) 12 | -------------------------------------------------------------------------------- /examples/2-codegen/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // const { generateSchema } = require('purescript-graphql-client') 3 | import { generateSchema } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchema({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql', 'Admin'], 9 | useNewtypesForRecords: false, 10 | url: 'http://localhost:4892/graphql' 11 | }) 12 | -------------------------------------------------------------------------------- /examples/2-codegen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2-codegen", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "4.17.1", 18 | "express-graphql": "0.12.0", 19 | "graphql": "15.4.0", 20 | "isomorphic-ws": "^5.0.0", 21 | "mkdirp": "^1.0.4", 22 | "rimraf": "3.0.2", 23 | "xhr2": "0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/2-codegen/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(id: ID): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: ID 15 | int: Int 16 | name: String! 17 | } 18 | `) 19 | 20 | const root = { 21 | prop: () => { 22 | return 'Hello world!' 23 | }, 24 | widgets: ({ id }) => 25 | widgets.filter(w => !id || id === w.id) 26 | 27 | } 28 | 29 | const widgets = [ 30 | { id: '1', int: 1, name: 'one' }, 31 | { id: '2', int: 2, name: 'two' } 32 | ] 33 | 34 | const app = express() 35 | 36 | app.use('/graphql', graphqlHTTP({ 37 | schema, 38 | rootValue: root, 39 | graphiql: true 40 | })) 41 | 42 | app.listen(4892, onListening) 43 | } 44 | -------------------------------------------------------------------------------- /examples/2-codegen/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/2-codegen/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 2-codegen 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/2-codegen/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin (Query) 10 | import Generated.Gql.Symbols (name) 11 | import GraphQL.Client.Args ((=>>)) 12 | import GraphQL.Client.ID (ID(..)) 13 | import GraphQL.Client.Operation (OpQuery(..)) 14 | import GraphQL.Client.Query (query_) 15 | import GraphQL.Client.Types (class GqlQuery) 16 | import Type.Data.List (Nil') 17 | import Type.Proxy (Proxy(..)) 18 | 19 | main :: Effect Unit 20 | main = 21 | launchAff_ do 22 | { widgets } <- 23 | queryGql "Widget names with id 1" 24 | { widgets: { id: ID "1" } =>> { name } } 25 | logShow $ map _.name widgets 26 | 27 | -- Run gql query 28 | queryGql :: 29 | forall query returns. 30 | GqlQuery Nil' OpQuery Query query returns => 31 | DecodeJson returns => 32 | String -> query -> Aff returns 33 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 34 | -------------------------------------------------------------------------------- /examples/2-codegen/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ['["one"]']) 23 | console.info('tests passed') 24 | process.exit(0) 25 | }, 500) 26 | } catch (err) { 27 | console.error('test error', err) 28 | process.exit(1) 29 | } 30 | }) 31 | 32 | setTimeout(() => { 33 | console.error('Timeout') 34 | process.exit(1) 35 | }, 60000) 36 | -------------------------------------------------------------------------------- /examples/2-codegen/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/3-enums/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/3-enums/README.md: -------------------------------------------------------------------------------- 1 | Handle enums with code gen. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/3-enums/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/3-enums/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3-enums", 3 | "version": "1.0.0", 4 | "description": "Handle enums in with code gen.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "4.17.1", 18 | "express-graphql": "0.12.0", 19 | "graphql": "15.4.0", 20 | "isomorphic-ws": "^5.0.0", 21 | "mkdirp": "^1.0.4", 22 | "rimraf": "3.0.2", 23 | "xhr2": "0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/3-enums/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(colour: Colour): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: Int 15 | name: String! 16 | colour: Colour! 17 | } 18 | "colour description" 19 | enum Colour { 20 | RED 21 | GREEN 22 | BLUE 23 | yellow 24 | } 25 | 26 | `) 27 | 28 | const root = { 29 | prop: () => { 30 | return 'Hello world!' 31 | }, 32 | widgets: ({ colour }) => 33 | widgets.filter(w => !colour || colour === w.colour) 34 | 35 | } 36 | 37 | const widgets = [ 38 | { id: 1, name: 'one', colour: 'RED' }, 39 | { id: 2, name: 'two', colour: 'GREEN' } 40 | ] 41 | 42 | const app = express() 43 | 44 | app.use('/graphql', graphqlHTTP({ 45 | schema, 46 | rootValue: root, 47 | graphiql: true 48 | })) 49 | 50 | app.listen(4892, onListening) 51 | } 52 | -------------------------------------------------------------------------------- /examples/3-enums/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/3-enums/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 3-enums 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/3-enums/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin.Enum.Colour (Colour(..)) 10 | import Generated.Gql.Schema.Admin (Query) 11 | import Generated.Gql.Symbols (colour) 12 | import GraphQL.Client.Args ((=>>)) 13 | import GraphQL.Client.Operation (OpQuery(..)) 14 | import GraphQL.Client.Query (query_) 15 | import GraphQL.Client.Types (class GqlQuery) 16 | import Type.Data.List (Nil') 17 | import Type.Proxy (Proxy(..)) 18 | 19 | main :: Effect Unit 20 | main = 21 | launchAff_ do 22 | { widgets } <- 23 | queryGql "WidgetColoursWithId1" 24 | { widgets: { colour: RED } =>> { colour } } 25 | 26 | -- Will log [ RED ] as there is one red widget 27 | logShow $ map _.colour widgets 28 | 29 | -- Run gql query 30 | queryGql :: 31 | forall query returns. 32 | GqlQuery Nil' OpQuery Query query returns => 33 | DecodeJson returns => 34 | String -> query -> Aff returns 35 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 36 | -------------------------------------------------------------------------------- /examples/3-enums/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | main() 20 | setTimeout(() => { 21 | deepStrictEqual(logs, ['[RED]']) 22 | console.info('tests passed') 23 | process.exit(0) 24 | }, 250) 25 | } catch (err) { 26 | console.error('test error', err) 27 | process.exit(1) 28 | } 29 | }) 30 | 31 | setTimeout(() => { 32 | console.error('Timeout') 33 | process.exit(1) 34 | }, 60000) 35 | -------------------------------------------------------------------------------- /examples/3-enums/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/4-mutation/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/4-mutation/README.md: -------------------------------------------------------------------------------- 1 | Handles mutations. Also explicitly creates a graphQL client. 2 | 3 | Run `npm t` to see it in action 4 | -------------------------------------------------------------------------------- /examples/4-mutation/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/4-mutation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-mutation", 3 | "version": "1.0.0", 4 | "description": "Handle mutations in with code gen.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@apollo/client": "3.6", 18 | "express": "4.17.1", 19 | "express-graphql": "0.12.0", 20 | "graphql": "^16.5.0", 21 | "graphql-ws": "^5.9.1", 22 | "isomorphic-ws": "^5.0.0", 23 | "mkdirp": "1.0.4", 24 | "rimraf": "3.0.2", 25 | "subscriptions-transport-ws": "^0.11.0", 26 | "xhr2": "0.2.0" 27 | }, 28 | "devDependencies": { 29 | "exec-sh": "^0.4.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/4-mutation/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | const widgets = [ 3 | { id: 1, name: 'one', colour: 'RED' }, 4 | { id: 2, name: 'two', colour: 'GREEN' } 5 | ] 6 | 7 | module.exports = (onListening) => { 8 | const express = require('express') 9 | const { graphqlHTTP } = require('express-graphql') 10 | const { buildSchema } = require('graphql') 11 | 12 | const schema = buildSchema(` 13 | type Query { 14 | prop: String 15 | widgets(id: Int): [Widget!]! 16 | } 17 | 18 | type Widget { 19 | id: Int 20 | name: String! 21 | colour: Colour! 22 | } 23 | 24 | enum Colour { 25 | RED 26 | GREEN 27 | BLUE 28 | yellow 29 | } 30 | 31 | type Mutation { 32 | set_widget_colour(id: Int!, colour: Colour!): Int! 33 | } 34 | 35 | `) 36 | 37 | const root = { 38 | prop: () => 'Hello world!', 39 | widgets: ({ id }) => 40 | widgets.filter(w => !id || id === w.id), 41 | set_widget_colour: ({ id, colour }) => { 42 | let count = 0 43 | widgets.forEach((w) => { 44 | if (w.id === id) { 45 | w.colour = colour 46 | count++ 47 | } 48 | }) 49 | return count 50 | } 51 | } 52 | 53 | const app = express() 54 | 55 | app.use('/graphql', graphqlHTTP({ 56 | schema, 57 | rootValue: root, 58 | graphiql: true 59 | })) 60 | 61 | app.listen(4892, onListening) 62 | } 63 | -------------------------------------------------------------------------------- /examples/4-mutation/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/4-mutation/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 4-mutation 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/4-mutation/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Effect (Effect) 7 | import Effect.Aff (launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin (Mutation, Query, Schema) 10 | import Generated.Gql.Schema.Admin.Enum.Colour (Colour(..)) 11 | import Generated.Gql.Symbols (colour, id) 12 | import GraphQL.Client.Args (onlyArgs, (=>>)) 13 | import GraphQL.Client.BaseClients.Apollo (createClient, updateCacheJson) 14 | import GraphQL.Client.BaseClients.Apollo.FetchPolicy (FetchPolicy(..)) 15 | import GraphQL.Client.Query (mutationOpts, query, queryOpts) 16 | import GraphQL.Client.Types (Client) 17 | import Type.Data.List (Nil') 18 | 19 | main :: Effect Unit 20 | main = do 21 | client :: Client _ Schema <- 22 | createClient 23 | { url: "http://localhost:4892/graphql" 24 | , authToken: Nothing 25 | , headers: [] 26 | } 27 | launchAff_ do 28 | let getWidgets = { widgets: { id: 1 } =>> { colour, id } } 29 | { widgets } <- 30 | query client "Widget_1_colour" 31 | getWidgets 32 | 33 | -- Will log [ RED ] 34 | logShow $ map _.colour widgets 35 | 36 | { set_widget_colour: affectedCount } <- 37 | mutationOpts 38 | _ 39 | { update = Just $ updateCacheJson client getWidgets \{ widgets: cacheWidgets } -> 40 | { widgets: cacheWidgets <#> \w -> w { colour = if w.id == Just 1 then GREEN else w.colour } 41 | } 42 | } 43 | client 44 | "Update_widget_colour" 45 | { set_widget_colour: onlyArgs { id: 1, colour: GREEN } 46 | } 47 | 48 | -- Will log 1 49 | logShow affectedCount 50 | 51 | { widgets: updatedWidgets } <- 52 | query client "Widget_1_colour_updated" getWidgets 53 | 54 | -- Will now log [ GREEN ] 55 | logShow $ map _.colour updatedWidgets 56 | 57 | { widgets: updatedWidgetsWithoutCache } <- 58 | queryOpts _ { fetchPolicy = Just NoCache } client "Widget_1_colour_no_cache" getWidgets 59 | 60 | -- Will also log [ GREEN ] 61 | logShow $ map _.colour updatedWidgetsWithoutCache 62 | -------------------------------------------------------------------------------- /examples/4-mutation/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | main() 20 | setTimeout(() => { 21 | console.info('logs', logs) 22 | deepStrictEqual(logs, ['[RED]', '1', '[GREEN]', '[GREEN]']) 23 | console.info('tests passed') 24 | process.exit(0) 25 | }, 400) 26 | } catch (err) { 27 | console.error('test error', err) 28 | process.exit(1) 29 | } 30 | }) 31 | 32 | setTimeout(() => { 33 | console.error('Timeout') 34 | process.exit(1) 35 | }, 60000) 36 | -------------------------------------------------------------------------------- /examples/4-mutation/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/5-subscription/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/5-subscription/README.md: -------------------------------------------------------------------------------- 1 | Demos subscriptions. 2 | 3 | Run `npm t` to see it in action 4 | -------------------------------------------------------------------------------- /examples/5-subscription/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/5-subscription/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5-subscription", 3 | "version": "1.0.0", 4 | "description": "Handle enums in with code gen.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@apollo/client": "3.6", 18 | "apollo-server": "^2.19.2", 19 | "apollo-server-core": "3.6", 20 | "express": "4.17.1", 21 | "express-graphql": "0.12.0", 22 | "graphql": "15.4.0", 23 | "graphql-ws": "^5.9.1", 24 | "isomorphic-ws": "^4.0.1", 25 | "mkdirp": "1.0.4", 26 | "rimraf": "3.0.2", 27 | "ws": "^8.8.1", 28 | "xhr2": "0.2.0" 29 | }, 30 | "devDependencies": { 31 | "exec-sh": "^0.4.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/5-subscription/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = async (onListening) => { 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const { ApolloServer, gql, makeExecutableSchema } = require('apollo-server-express') 6 | const { createServer } = require('http') 7 | const { execute, subscribe } = require('graphql') 8 | const { PubSub } = require('graphql-subscriptions') 9 | const { WebSocketServer } = require('ws'); 10 | const { useServer } = require("graphql-ws/lib/use/ws"); 11 | const { ApolloServerPluginDrainHttpServer } = require("apollo-server-core"); 12 | 13 | const typeDefs = gql(` 14 | type Subscription { 15 | postAdded: Post! 16 | } 17 | 18 | type Query { 19 | posts: [Post!]! 20 | } 21 | 22 | type Mutation { 23 | addPost(author: String, comment: String): Post 24 | } 25 | 26 | type Post { 27 | author: String! 28 | comment: String! 29 | } 30 | `) 31 | const PORT = 4892 32 | const app = express() 33 | 34 | app.use('/graphql', bodyParser.json()) 35 | const POST_ADDED = 'POST_ADDED' 36 | const pubsub = new PubSub() 37 | 38 | const resolvers = { 39 | Subscription: { 40 | postAdded: { 41 | // Additional event labels can be passed to asyncIterator creation 42 | subscribe: (args) => { 43 | return pubsub.asyncIterator([POST_ADDED]) 44 | } 45 | } 46 | }, 47 | Query: { 48 | posts (root, args, context) { 49 | return posts 50 | } 51 | }, 52 | Mutation: { 53 | addPost (root, args, context) { 54 | pubsub.publish(POST_ADDED, { postAdded: args }) 55 | posts.push(args) 56 | return args 57 | } 58 | } 59 | } 60 | 61 | 62 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 63 | 64 | const httpServer = createServer(app); 65 | 66 | const wsServer = new WebSocketServer({ 67 | server: httpServer, 68 | path: "/graphql" 69 | }); 70 | 71 | const serverCleanup = useServer({ schema, onMessage: (s) => console.log(s) }, wsServer); 72 | 73 | const server = new ApolloServer({ 74 | schema, 75 | plugins: [ 76 | // Proper shutdown for the HTTP server. 77 | ApolloServerPluginDrainHttpServer({ httpServer }), 78 | 79 | // Proper shutdown for the WebSocket server. 80 | { 81 | async serverWillStart() { 82 | return { 83 | async drainServer() { 84 | await serverCleanup.dispose(); 85 | }, 86 | }; 87 | }, 88 | }, 89 | ], 90 | }); 91 | 92 | await server.start(); 93 | server.applyMiddleware({ app }); 94 | 95 | httpServer.listen(PORT, () => { 96 | onListening(server, server, PORT) 97 | }); 98 | } 99 | 100 | const posts = 101 | [ { author: 'author 1', comment: 'comment 1' }, 102 | { author: 'author 2', comment: 'comment 2' } 103 | ] 104 | -------------------------------------------------------------------------------- /examples/5-subscription/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn((server, PORT) => { 3 | console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`) 4 | console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`) 5 | }) 6 | -------------------------------------------------------------------------------- /examples/5-subscription/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 5-subscription 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/5-subscription/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Effect (Effect) 7 | import Effect.Aff (Milliseconds(..), delay, launchAff_) 8 | import Effect.Class.Console (log, logShow) 9 | import Generated.Gql.Schema.Admin (Schema) 10 | import GraphQL.Client.Args ((=>>)) 11 | import GraphQL.Client.BaseClients.Apollo (createSubscriptionClient) 12 | import GraphQL.Client.Query (mutation) 13 | import GraphQL.Client.Subscription (subscription) 14 | import GraphQL.Client.Types (Client) 15 | import Halogen.Subscription as HS 16 | 17 | main :: Effect Unit 18 | main = do 19 | client :: Client _ Schema <- 20 | createSubscriptionClient 21 | { url: "http://localhost:4892/graphql" 22 | , authToken: Nothing 23 | , headers: [] 24 | , websocketUrl: "ws://localhost:4892/graphql" 25 | } 26 | let 27 | event = subscription client "get_props" { postAdded: { author: unit, comment: unit } } 28 | 29 | cancel <- 30 | HS.subscribe event \e -> do 31 | log "Event recieved" 32 | logShow e 33 | launchAff_ do 34 | delay $ Milliseconds 25.0 35 | void 36 | $ mutation client "make_post" 37 | { addPost: { author: "joe bloggs", comment: "great" } =>> { author: unit } 38 | } 39 | void 40 | $ mutation client "make_post" 41 | { addPost: { author: "joe bloggs", comment: "bad" } =>> { author: unit } 42 | } 43 | -------------------------------------------------------------------------------- /examples/5-subscription/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, [ 23 | 'Event recieved', 24 | '(Right { postAdded: { author: "joe bloggs", comment: "great" } })', 25 | 'Event recieved', 26 | '(Right { postAdded: { author: "joe bloggs", comment: "bad" } })' 27 | ]) 28 | console.info('tests passed') 29 | process.exit(0) 30 | }, 500) 31 | } catch (err) { 32 | console.error('test error', err) 33 | process.exit(1) 34 | } 35 | }) 36 | 37 | setTimeout(() => { 38 | console.error('Timeout') 39 | process.exit(1) 40 | }, 60000) 41 | -------------------------------------------------------------------------------- /examples/5-subscription/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/6-watch-query/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/6-watch-query/README.md: -------------------------------------------------------------------------------- 1 | Demos an apollo watchQuery. 2 | 3 | Run `npm t` to see it in action 4 | -------------------------------------------------------------------------------- /examples/6-watch-query/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/6-watch-query/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "6-watch-query", 3 | "version": "1.0.0", 4 | "description": "Use a live query that watches for cache updates.", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@apollo/client": "3.6", 18 | "apollo-server": "^2.19.2", 19 | "apollo-server-core": "3.6", 20 | "express": "4.17.1", 21 | "express-graphql": "0.12.0", 22 | "graphql": "15.4.0", 23 | "graphql-ws": "^5.9.1", 24 | "isomorphic-ws": "^4.0.1", 25 | "mkdirp": "1.0.4", 26 | "rimraf": "3.0.2", 27 | "ws": "^8.8.1", 28 | "xhr2": "0.2.0" 29 | }, 30 | "devDependencies": { 31 | "exec-sh": "^0.4.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/6-watch-query/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = async (onListening) => { 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const { ApolloServer, gql, makeExecutableSchema } = require('apollo-server-express') 6 | const { createServer } = require('http') 7 | const { execute, subscribe } = require('graphql') 8 | const { PubSub } = require('graphql-subscriptions') 9 | const { WebSocketServer } = require('ws'); 10 | const { useServer } = require("graphql-ws/lib/use/ws"); 11 | const { ApolloServerPluginDrainHttpServer } = require("apollo-server-core"); 12 | 13 | const typeDefs = gql(` 14 | type Subscription { 15 | postAdded: Post! 16 | } 17 | 18 | type Query { 19 | posts: [Post!]! 20 | } 21 | 22 | type Mutation { 23 | addPost(author: String, comment: String): Post 24 | } 25 | 26 | type Post { 27 | author: String! 28 | comment: String! 29 | } 30 | `) 31 | const PORT = 4892 32 | const app = express() 33 | 34 | app.use('/graphql', bodyParser.json()) 35 | const POST_ADDED = 'POST_ADDED' 36 | const pubsub = new PubSub() 37 | 38 | const resolvers = { 39 | Subscription: { 40 | postAdded: { 41 | // Additional event labels can be passed to asyncIterator creation 42 | subscribe: (args) => { 43 | return pubsub.asyncIterator([POST_ADDED]) 44 | } 45 | } 46 | }, 47 | Query: { 48 | posts (root, args, context) { 49 | return posts 50 | } 51 | }, 52 | Mutation: { 53 | addPost (root, args, context) { 54 | pubsub.publish(POST_ADDED, { postAdded: args }) 55 | posts.push(args) 56 | return args 57 | } 58 | } 59 | } 60 | 61 | 62 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 63 | 64 | const httpServer = createServer(app); 65 | 66 | const wsServer = new WebSocketServer({ 67 | server: httpServer, 68 | path: "/graphql" 69 | }); 70 | 71 | const serverCleanup = useServer({ schema, onMessage: (s) => console.log(s) }, wsServer); 72 | 73 | const server = new ApolloServer({ 74 | schema, 75 | plugins: [ 76 | // Proper shutdown for the HTTP server. 77 | ApolloServerPluginDrainHttpServer({ httpServer }), 78 | 79 | // Proper shutdown for the WebSocket server. 80 | { 81 | async serverWillStart() { 82 | return { 83 | async drainServer() { 84 | await serverCleanup.dispose(); 85 | }, 86 | }; 87 | }, 88 | }, 89 | ], 90 | }); 91 | 92 | await server.start(); 93 | server.applyMiddleware({ app }); 94 | 95 | httpServer.listen(PORT, () => { 96 | onListening(server, server, PORT) 97 | }); 98 | } 99 | 100 | const posts = 101 | [ { author: 'author 1', comment: 'comment 1' }, 102 | { author: 'author 2', comment: 'comment 2' } 103 | ] 104 | -------------------------------------------------------------------------------- /examples/6-watch-query/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn((server, PORT) => { 3 | console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`) 4 | console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`) 5 | }) 6 | -------------------------------------------------------------------------------- /examples/6-watch-query/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 6-watch-query 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/6-watch-query/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Effect (Effect) 7 | import Effect.Aff (Milliseconds(..), delay, launchAff_) 8 | import Effect.Class.Console (log, logShow) 9 | import Generated.Gql.Schema.Admin (Schema) 10 | import GraphQL.Client.Args ((=>>)) 11 | import GraphQL.Client.BaseClients.Apollo (createSubscriptionClient, updateCacheJson) 12 | import GraphQL.Client.Query (mutationOpts) 13 | import GraphQL.Client.Types (Client) 14 | import GraphQL.Client.WatchQuery (watchQuery) 15 | import Halogen.Subscription as HS 16 | import Type.Data.List (Nil') 17 | 18 | main :: Effect Unit 19 | main = do 20 | client :: Client _ Schema <- 21 | createSubscriptionClient 22 | { url: "http://localhost:4892/graphql" 23 | , authToken: Nothing 24 | , headers: [] 25 | , websocketUrl: "ws://localhost:4892/subscriptions" 26 | } 27 | let 28 | myQuery = { posts: { author: unit, comment: unit } } 29 | event = watchQuery client "get_props" myQuery 30 | 31 | cancel <- 32 | HS.subscribe event \e -> do 33 | log "Event recieved" 34 | logShow e 35 | 36 | let 37 | addComment author comment = 38 | let 39 | update = updateCacheJson client myQuery \{ posts } -> 40 | {posts: posts <> [{author, comment}]} 41 | in 42 | void 43 | $ mutationOpts _ {update = Just update} client "make_post" 44 | { addPost: { author, comment } =>> { author: unit } 45 | } 46 | 47 | launchAff_ do 48 | delay $ Milliseconds 25.0 49 | addComment "joe bloggs" "good" 50 | -------------------------------------------------------------------------------- /examples/6-watch-query/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe', stderr: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, [ 23 | 'Event recieved', 24 | '(Right { posts: [{ author: "author 1", comment: "comment 1" },{ author: "author 2", comment: "comment 2" }] })', 25 | 'Event recieved', 26 | '(Right { posts: [{ author: "author 1", comment: "comment 1" },{ author: "author 2", comment: "comment 2" },{ author: "joe bloggs", comment: "good" }] })' 27 | ]) 28 | console.info('tests passed') 29 | process.exit(0) 30 | }, 250) 31 | } catch (err) { 32 | console.error('test error', err) 33 | process.exit(1) 34 | } 35 | }) 36 | 37 | setTimeout(() => { 38 | console.error('Timeout') 39 | process.exit(1) 40 | }, 60000) 41 | -------------------------------------------------------------------------------- /examples/6-watch-query/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/7-field-type-overrides/README.md: -------------------------------------------------------------------------------- 1 | A codegen example using custom graphQL types. Look in `generate-purs-schema.js` to 2 | see how types are mapped from graphQL to purescript. 3 | 4 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/7-field-type-overrides/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchema } = from 'purescript-graphql-client' 3 | import { generateSchema } from "../../codegen/schema/index.mjs"; 4 | 5 | export default () => 6 | generateSchema({ 7 | dir: "./src/generated", 8 | modulePath: ["Generated", "Gql", "Admin"], 9 | useNewtypesForRecords: false, 10 | url: "http://localhost:4892/graphql", 11 | fieldTypeOverrides: { 12 | Widget: { 13 | special_string: { moduleName: "DataTypes", typeName: "MyNewtype" }, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "7-field-type-overrides", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "4.17.1", 18 | "express-graphql": "0.12.0", 19 | "graphql": "15.4.0", 20 | "isomorphic-ws": "^5.0.0", 21 | "mkdirp": "1.0.4", 22 | "rimraf": "3.0.2", 23 | "xhr2": "0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | widgets(id: ID): [Widget!]! 10 | } 11 | 12 | type Widget { 13 | id: ID 14 | special_string: String! 15 | } 16 | `) 17 | 18 | const root = { 19 | widgets: () => widgets 20 | 21 | } 22 | 23 | const widgets = [ 24 | { id: '1', int: 1, special_string: 'one' }, 25 | { id: '2', int: 2, special_string: 'two' } 26 | ] 27 | 28 | const app = express() 29 | 30 | app.use('/graphql', graphqlHTTP({ 31 | schema, 32 | rootValue: root, 33 | graphiql: true 34 | })) 35 | 36 | app.listen(4892, onListening) 37 | } 38 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 7-field-type-overrides 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: 11 | - argonaut-generic 12 | workspace: 13 | packageSet: 14 | registry: 64.6.0 15 | extraPackages: 16 | graphql-client: 17 | path: ../.. 18 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/src/DataTypes.purs: -------------------------------------------------------------------------------- 1 | module DataTypes where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Argonaut.Decode.Generic (genericDecodeJson) 7 | import Data.Generic.Rep (class Generic) 8 | import Data.Newtype (class Newtype) 9 | import Data.Show.Generic (genericShow) 10 | 11 | 12 | newtype MyNewtype = MyNewtype String 13 | 14 | derive instance Newtype MyNewtype _ 15 | 16 | derive instance Generic MyNewtype _ 17 | 18 | derive newtype instance DecodeJson MyNewtype 19 | 20 | instance Show MyNewtype where 21 | show = genericShow 22 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin (Query) 10 | import GraphQL.Client.Operation (OpQuery) 11 | import GraphQL.Client.Query (query_) 12 | import GraphQL.Client.Types (class GqlQuery) 13 | import Type.Data.List (Nil') 14 | import Type.Proxy (Proxy(..)) 15 | 16 | main :: Effect Unit 17 | main = 18 | launchAff_ do 19 | { widgets } <- 20 | queryGql "Widget jsons" 21 | { widgets: { special_string: unit } } 22 | logShow $ map (_.special_string) widgets 23 | 24 | -- Run gql query 25 | queryGql :: 26 | forall query returns. 27 | GqlQuery Nil' OpQuery Query query returns => 28 | DecodeJson returns => 29 | String -> query -> Aff returns 30 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 31 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from "assert"; 2 | import execSh from "exec-sh"; 3 | const { promise: exec } = execSh; 4 | 5 | const logs = []; 6 | 7 | console.log = (log) => { 8 | console.info(log); 9 | logs.push(log); 10 | }; 11 | 12 | import serverFn from "./server-fn.js"; 13 | import gps from "./generate-purs-schema.mjs"; 14 | serverFn(async () => { 15 | try { 16 | await gps(); 17 | await exec("npm run build", { stdio: "pipe" }); 18 | const { main } = await import("./output/Main/index.js"); 19 | 20 | main(); 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ['[(MyNewtype "one"),(MyNewtype "two")]']); 23 | console.info("tests passed"); 24 | process.exit(0); 25 | }, 500); 26 | } catch (err) { 27 | console.error("test error", err); 28 | process.exit(1); 29 | } 30 | }); 31 | 32 | setTimeout(() => { 33 | console.error("Timeout"); 34 | process.exit(1); 35 | }, 60000); 36 | -------------------------------------------------------------------------------- /examples/7-field-type-overrides/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/8-custom-gql-types/README.md: -------------------------------------------------------------------------------- 1 | A codegen example using custom graphQL types. Look in `generate-purs-schema.js` to 2 | see how types are mapped from graphQL to purescript. 3 | 4 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/8-custom-gql-types/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchema } = from 'purescript-graphql-client' 3 | import { generateSchema } from "../../codegen/schema/index.mjs"; 4 | 5 | export default () => 6 | generateSchema({ 7 | dir: "./src/generated", 8 | modulePath: ["Generated", "Gql", "Admin"], 9 | useNewtypesForRecords: false, 10 | url: "http://localhost:4892/graphql", 11 | gqlToPursTypes: { 12 | GqlTypeThatIsAString: { typeName: "String", moduleName: "" }, 13 | GqlTypeThatIsAnInt: { typeName: "Int", moduleName: "" }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "8-custom-gql-types", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "4.17.1", 18 | "express-graphql": "0.12.0", 19 | "graphql": "15.4.0", 20 | "isomorphic-ws": "^5.0.0", 21 | "mkdirp": "1.0.4", 22 | "rimraf": "3.0.2", 23 | "xhr2": "0.2.0" 24 | }, 25 | "devDependencies": { 26 | "exec-sh": "^0.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: GqlTypeThatIsAString 10 | widgets(id: ID): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: ID 15 | int: GqlTypeThatIsAnInt 16 | name: GqlTypeThatIsAString! 17 | } 18 | 19 | scalar GqlTypeThatIsAnInt 20 | scalar GqlTypeThatIsAString 21 | `) 22 | 23 | const root = { 24 | prop: () => { 25 | return 'Hello world!' 26 | }, 27 | widgets: ({ id }) => 28 | widgets.filter(w => !id || id === w.id) 29 | 30 | } 31 | 32 | const widgets = [ 33 | { id: '1', int: 1, name: 'one' }, 34 | { id: '2', int: 2, name: 'two' } 35 | ] 36 | 37 | const app = express() 38 | 39 | app.use('/graphql', graphqlHTTP({ 40 | schema, 41 | rootValue: root, 42 | graphiql: true 43 | })) 44 | 45 | app.listen(4892, onListening) 46 | } 47 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 8-custom-gql-types 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin (Query) 10 | import Generated.Gql.Symbols (name) 11 | import GraphQL.Client.Args ((=>>)) 12 | import GraphQL.Client.ID (ID(..)) 13 | import GraphQL.Client.Operation (OpQuery(..)) 14 | import GraphQL.Client.Query (query_) 15 | import GraphQL.Client.Types (class GqlQuery) 16 | import Type.Data.List (Nil') 17 | import Type.Proxy (Proxy(..)) 18 | 19 | main :: Effect Unit 20 | main = 21 | launchAff_ do 22 | { widgets } <- 23 | queryGql "Widget names with id 1" 24 | { widgets: { id: ID "1" } =>> { name } } 25 | logShow $ map _.name widgets 26 | 27 | -- Run gql query 28 | queryGql :: 29 | forall query returns. 30 | GqlQuery Nil' OpQuery Query query returns => 31 | DecodeJson returns => 32 | String -> query -> Aff returns 33 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 34 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from 'assert' 2 | import execSh from 'exec-sh'; 3 | const {promise: exec} = execSh; 4 | 5 | const logs = [] 6 | 7 | console.log = (log) => { 8 | console.info(log) 9 | logs.push(log) 10 | } 11 | 12 | import serverFn from './server-fn.js' 13 | import gps from './generate-purs-schema.mjs' 14 | serverFn(async () => { 15 | try { 16 | await gps() 17 | await exec('npm run build', { stdio: 'pipe' }) 18 | const { main } = await import('./output/Main/index.js') 19 | 20 | main() 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ['["one"]']) 23 | console.info('tests passed') 24 | process.exit(0) 25 | }, 500) 26 | } catch (err) { 27 | console.error('test error', err) 28 | process.exit(1) 29 | } 30 | }) 31 | 32 | setTimeout(() => { 33 | console.error('Timeout') 34 | process.exit(1) 35 | }, 60000) 36 | -------------------------------------------------------------------------------- /examples/8-custom-gql-types/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /examples/9-variables/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | /src/generated -------------------------------------------------------------------------------- /examples/9-variables/README.md: -------------------------------------------------------------------------------- 1 | A simple example that makes a graphql query and logs the result. 2 | 3 | Run `npm t` to see it in action -------------------------------------------------------------------------------- /examples/9-variables/generate-purs-schema.mjs: -------------------------------------------------------------------------------- 1 | // In your code replace this line with the npm package: 2 | // import { generateSchemas } from 'purescript-graphql-client' 3 | import { generateSchemas } from '../../codegen/schema/index.mjs' 4 | 5 | export default () => 6 | generateSchemas({ 7 | dir: './src/generated', 8 | modulePath: ['Generated', 'Gql'], 9 | useNewtypesForRecords: false 10 | }, [ 11 | { 12 | url: 'http://localhost:4892/graphql', 13 | moduleName: 'Admin' 14 | } 15 | ]) 16 | -------------------------------------------------------------------------------- /examples/9-variables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "9-variables", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm i && spago install && node ./test.mjs", 11 | "build": "spago build", 12 | "start": "node server.js" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "express": "^4.17.1", 18 | "express-graphql": "^0.12.0", 19 | "graphql": "^15.4.0", 20 | "isomorphic-ws": "^5.0.0", 21 | "xhr2": "^0.2.0" 22 | }, 23 | "devDependencies": { 24 | "exec-sh": "^0.4.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/9-variables/server-fn.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (onListening) => { 3 | const express = require('express') 4 | const { graphqlHTTP } = require('express-graphql') 5 | const { buildSchema } = require('graphql') 6 | 7 | const schema = buildSchema(` 8 | type Query { 9 | prop: String 10 | widgets(colour: Colour, ids: [Int!], ids_2: [Int]!): [Widget!]! 11 | } 12 | 13 | type Widget { 14 | id: Int 15 | name: String! 16 | colour: Colour! 17 | } 18 | "colour description" 19 | enum Colour { 20 | RED 21 | GREEN 22 | BLUE 23 | yellow 24 | } 25 | 26 | `) 27 | 28 | const root = { 29 | prop: () => { 30 | return 'Hello world!' 31 | }, 32 | widgets: ({ colour, ids }) => 33 | widgets 34 | .filter(w => !colour || colour === w.colour) 35 | .filter(w => !ids || ids.includes(w.id)) 36 | 37 | } 38 | 39 | const widgets = [ 40 | { id: 1, name: 'one', colour: 'RED' }, 41 | { id: 2, name: 'two', colour: 'GREEN' }, 42 | { id: 3, name: 'three', colour: 'GREEN' } 43 | ] 44 | 45 | const app = express() 46 | 47 | app.use('/graphql', graphqlHTTP({ 48 | schema, 49 | rootValue: root, 50 | graphiql: true 51 | })) 52 | 53 | app.listen(4892, onListening) 54 | } 55 | -------------------------------------------------------------------------------- /examples/9-variables/server.js: -------------------------------------------------------------------------------- 1 | import serverFn from './server-fn.js' 2 | serverFn(() => { 3 | console.info('Running a GraphQL API server at http://localhost:4892/graphql') 4 | }) 5 | -------------------------------------------------------------------------------- /examples/9-variables/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: 9-variables 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /examples/9-variables/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Effect (Effect) 7 | import Effect.Aff (Aff, launchAff_) 8 | import Effect.Class.Console (logShow) 9 | import Generated.Gql.Schema.Admin (Query) 10 | import Generated.Gql.Schema.Admin.Enum.Colour (Colour(..)) 11 | import Generated.Gql.Symbols (colour, widgets) 12 | import GraphQL.Client.Alias ((:)) 13 | import GraphQL.Client.Args (NotNull, (=>>)) 14 | import GraphQL.Client.Operation (OpQuery) 15 | import GraphQL.Client.Query (query_) 16 | import GraphQL.Client.Types (class GqlQuery) 17 | import GraphQL.Client.Variable (Var(..)) 18 | import GraphQL.Client.Variables (withVars) 19 | import Type.Data.List (Nil') 20 | import Type.Proxy (Proxy(..)) 21 | 22 | main :: Effect Unit 23 | main = 24 | launchAff_ do 25 | { red_widgets, widgets } <- 26 | queryGql "get_widgets" 27 | $ 28 | { red_widgets: widgets 29 | : 30 | { colour: Var :: _ "colourVar" Colour 31 | , ids_2: Var :: _ "ids_2" ((Array Int)) 32 | } 33 | =>> { colour } 34 | , widgets: 35 | { ids: Var :: _ "ids" (Array Int) 36 | , ids_2: Var :: _ "ids_2" ((Array Int)) 37 | } =>> { id: unit } 38 | } 39 | `withVars` 40 | { colourVar: RED 41 | , ids: [ 1, 2 ] 42 | , ids_2: [ 3, 4 ] 43 | } 44 | -- Will log [ RED ] as there is one red widget 45 | logShow $ map _.colour red_widgets 46 | 47 | logShow widgets 48 | 49 | -- Run gql query 50 | queryGql 51 | :: forall query returns 52 | . GqlQuery Nil' OpQuery Query query returns 53 | => DecodeJson returns 54 | => String 55 | -> query 56 | -> Aff returns 57 | queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Query) 58 | -------------------------------------------------------------------------------- /examples/9-variables/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual } from "assert"; 2 | import execSh from "exec-sh"; 3 | const { promise: exec } = execSh; 4 | 5 | const logs = []; 6 | 7 | console.log = (log) => { 8 | console.info(log); 9 | logs.push(log); 10 | }; 11 | 12 | import serverFn from "./server-fn.js"; 13 | import gps from "./generate-purs-schema.mjs"; 14 | serverFn(async () => { 15 | try { 16 | await gps(); 17 | await exec("npm run build", { stdio: "pipe", stderr: "pipe" }); 18 | const { main } = await import("./output/Main/index.js"); 19 | 20 | main(); 21 | setTimeout(() => { 22 | deepStrictEqual(logs, ["[RED]", "[{ id: (Just 1) },{ id: (Just 2) }]"]); 23 | console.info("tests passed"); 24 | process.exit(0); 25 | }, 250); 26 | } catch (err) { 27 | console.error("test error", err); 28 | process.exit(1); 29 | } 30 | }); 31 | 32 | setTimeout(() => { 33 | console.error("Timeout"); 34 | process.exit(1); 35 | }, 60000); 36 | -------------------------------------------------------------------------------- /examples/9-variables/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-graphql-client", 3 | "version": "10.0.7", 4 | "description": "A typesafe graphql client for purescript.", 5 | "main": "codegen/schema/index.mjs", 6 | "type": "module", 7 | "directories": { 8 | "main": "./codegen/schema/index.mjs", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "spago test && npm run test-codegen", 13 | "test-codegen": "cd codegen/schema && spago test", 14 | "should-fail-tests": "node ./run-should-fail-tests", 15 | "bundle": "cd codegen/schema && spago bundle --bundle-type module --module GraphQL.Client.CodeGen.Js --outfile '../../gen-schema-bundled.mjs'", 16 | "patch": "npm run bundle && gcam 'update bundle' --allow-empty && npm version patch && npm publish && pulp publish", 17 | "npm-major-version": "npm run bundle && gcam 'update bundle' --allow-empty && npm version major && npm publish", 18 | "patch-old": "npm run bundle && gcam 'update bundle' --allow-empty && npm version patch && pulp version patch && npm publish && pulp publish" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/OxfordAbstracts/purescript-graphql-client.git" 23 | }, 24 | "author": "", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/OxfordAbstracts/purescript-graphql-client/issues" 28 | }, 29 | "homepage": "https://github.com/OxfordAbstracts/purescript-graphql-client#readme", 30 | "dependencies": { 31 | "@urql/core": "^1.16.2", 32 | "graphql": "^16.5.0", 33 | "graphql-ws": "^5.9.1", 34 | "isomorphic-unfetch": "^3.1.0", 35 | "isomorphic-ws": "^5.0.0", 36 | "mkdirp": "^1.0.4", 37 | "rimraf": "3.0.2" 38 | }, 39 | "devDependencies": { 40 | "@apollo/client": "3.6", 41 | "esbuild": "^0.14.51", 42 | "exec-sh": "^0.4.0", 43 | "glob": "^7.2.0", 44 | "wonka": "^4.0.15" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /run-example-tests.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | import { promisify } from 'util' 3 | import execSh from 'exec-sh' 4 | const exec = promisify(execSh) 5 | 6 | const getDirectories = source => 7 | readdirSync(source, { withFileTypes: true }) 8 | .filter(dirent => dirent.isDirectory()) 9 | .map(dirent => dirent.name) 10 | 11 | 12 | const go = async () => { 13 | const e2e = getDirectories('./e2e') 14 | 15 | for (const p of e2e) { 16 | console.log(`Testing ${p}`) 17 | try { 18 | await exec(`cd "./e2e/${p}" && npm t`) 19 | } catch (err) { 20 | console.error(`Test threw error: ${p}`) 21 | process.exit(1) 22 | } 23 | } 24 | 25 | const examples = getDirectories('./examples') 26 | 27 | for (const p of examples) { 28 | console.log(`Testing ${p}`) 29 | try { 30 | await exec(`cd "./examples/${p}" && npm t`) 31 | } catch (err) { 32 | console.error(`Test threw error: ${p}`) 33 | process.exit(1) 34 | } 35 | } 36 | } 37 | 38 | go() 39 | -------------------------------------------------------------------------------- /run-in-examples.js: -------------------------------------------------------------------------------- 1 | // run a command in every e2e, example and should-fail-test folder. 2 | // Useful for installing or uninstalling packages 3 | 4 | import { readdirSync } from 'fs' 5 | 6 | const getDirectories = source => 7 | readdirSync(source, { withFileTypes: true }) 8 | .filter(dirent => dirent.isDirectory()) 9 | .map(dirent => dirent.name) 10 | 11 | const exec = require('exec-sh') 12 | 13 | const go = async () => { 14 | await Promise.all(getDirectories('./e2e').map((dir) => { 15 | return exec(`cd e2e/${dir} && ${process.env.CMD}`) 16 | })) 17 | await Promise.all(getDirectories('./examples').map((dir) => { 18 | return exec(`cd examples/${dir} && ${process.env.CMD}`) 19 | })) 20 | await Promise.all(getDirectories('./should-fail-tests').map((dir) => { 21 | return exec(`cd should-fail-tests/${dir} && ${process.env.CMD}`) 22 | })) 23 | } 24 | go() 25 | -------------------------------------------------------------------------------- /run-should-fail-tests.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | import { readFile } from 'fs/promises' 3 | import execSh from 'exec-sh' 4 | const { promise: exec } = execSh 5 | 6 | import { ok } from 'assert' 7 | 8 | const getDirectories = source => 9 | readdirSync(source, { withFileTypes: true }) 10 | .filter(dirent => dirent.isDirectory()) 11 | .map(dirent => dirent.name) 12 | 13 | const go = async () => { 14 | const packages = getDirectories('./should-fail-tests') 15 | let toTest = packages.length 16 | 17 | for (const p of packages) { 18 | const expectedError = (await readFile(`./should-fail-tests/${p}/expected-error.txt`)).toString() 19 | 20 | try { 21 | console.log(`testing: ${p}`) 22 | 23 | await exec(`cd "./should-fail-tests/${p}" && spago install`, true) 24 | await exec(`cd "./should-fail-tests/${p}" && spago build`, true) 25 | 26 | console.error(`${p} compiled. Test failed`) 27 | process.exit(1) 28 | } catch (err) { 29 | const { stderr } = err 30 | try { 31 | ok(simplify(stderr, 0, 100).includes(simplify(expectedError, 4, 20))) 32 | } catch (e) { 33 | console.log('stderr: \n', stderr) 34 | console.info('simplified error', simplify(stderr, 0, 100)) 35 | throw e 36 | } 37 | console.log(`test passed: ${p}`) 38 | toTest-- 39 | if (toTest === 0) { 40 | console.log('all tests passed') 41 | process.exit(0) 42 | } 43 | } 44 | } 45 | } 46 | 47 | const simplify = (str, startLine, endLine) => 48 | str 49 | .trim() 50 | .split('\n') 51 | .map(l => l.trim()) 52 | .filter(l => l) 53 | .filter(l => !l.includes('Compiling ')) 54 | .slice(startLine, endLine) 55 | .join('\n') 56 | .trim() 57 | 58 | go() 59 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-ignored/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-ignored/expected-error.txt: -------------------------------------------------------------------------------- 1 | Custom error: 2 | An `IgnoreArg` query argument cannot be used with with required schema argument: 3 | Schema: Boolean 4 | Query: IgnoreArg 5 | At: "online" 6 | while solving type class constraint 7 | GraphQL.Client.Args.IsNotNull "online" 8 | Boolean 9 | IgnoreArg 10 | while applying a function queryReturns 11 | of type QueryReturns @t0 @Type @t1 t2 t3 t4 => Proxy @t0 t2 -> t3 -> Proxy @t1 t4 12 | to argument testSchemaProxy 13 | while inferring the type of queryReturns testSchemaProxy 14 | in value declaration passing1 15 | where t0 is an unknown type 16 | t1 is an unknown type 17 | t2 is an unknown type 18 | t3 is an unknown type 19 | t4 is an unknown type 20 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-ignored/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: not-null-arg-ignored 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-ignored/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import GraphQL.Client.Args (IgnoreArg(..), NotNull, OrArg(..), (=>>)) 7 | import GraphQL.Client.QueryReturns (queryReturns) 8 | import Type.Proxy (Proxy(..)) 9 | 10 | main :: Effect Unit 11 | main = pure unit 12 | 13 | testSchemaProxy :: Proxy TestNotNullParamsSchema 14 | testSchemaProxy = Proxy 15 | 16 | type TestNotNullParamsSchema 17 | = { users :: 18 | { online :: NotNull Boolean 19 | } 20 | -> Array 21 | { id :: Int 22 | } 23 | } 24 | 25 | id :: Proxy "id" 26 | id = Proxy 27 | 28 | passing1 :: Unit 29 | passing1 = 30 | const unit 31 | $ queryReturns testSchemaProxy 32 | { users: { online: (ArgL IgnoreArg) :: OrArg IgnoreArg Boolean } =>> { id } 33 | } 34 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-ignored/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-is-nothing/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-is-nothing/expected-error.txt: -------------------------------------------------------------------------------- 1 | Custom error: 2 | A `Maybe` query argument cannot be used with with required schema argument: 3 | Schema: Boolean 4 | Query: Boolean 5 | At: "online" 6 | while solving type class constraint 7 | GraphQL.Client.Args.IsNotNull "online" 8 | Boolean 9 | (Maybe Boolean) 10 | while applying a function queryReturns 11 | of type QueryReturns @t0 @Type @t1 t2 t3 t4 => Proxy @t0 t2 -> t3 -> Proxy @t1 t4 12 | to argument testSchemaProxy 13 | while inferring the type of queryReturns testSchemaProxy 14 | in value declaration passing1 15 | where t0 is an unknown type 16 | t1 is an unknown type 17 | t2 is an unknown type 18 | t3 is an unknown type 19 | t4 is an unknown type 20 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-is-nothing/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: not-null-arg-is-nothing 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-is-nothing/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Effect (Effect) 7 | import GraphQL.Client.Args (NotNull, (=>>)) 8 | import GraphQL.Client.QueryReturns (queryReturns) 9 | import Type.Proxy (Proxy(..)) 10 | 11 | main :: Effect Unit 12 | main = pure unit 13 | 14 | testSchemaProxy :: Proxy TestNotNullParamsSchema 15 | testSchemaProxy = Proxy 16 | 17 | type TestNotNullParamsSchema 18 | = { users :: 19 | { online :: NotNull Boolean 20 | } 21 | -> Array 22 | { id :: Int 23 | } 24 | } 25 | 26 | id :: Proxy "id" 27 | id = Proxy 28 | 29 | passing1 :: Unit 30 | passing1 = 31 | const unit 32 | $ queryReturns testSchemaProxy 33 | { users: { online: (Nothing :: Maybe Boolean) } =>> { id } 34 | } 35 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-is-nothing/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-not-there/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | /.spago 11 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-not-there/expected-error.txt: -------------------------------------------------------------------------------- 1 | Could not match type 2 | ( online :: t5 3 | | t6 4 | ) 5 | with type 6 | () 7 | while solving type class constraint 8 | Prim.Row.Cons "online" 9 | t5 10 | t6 11 | (() @Type) 12 | while applying a function queryReturns 13 | of type QueryReturns @t0 @Type @t1 t2 t3 t4 => Proxy @t0 t2 -> t3 -> Proxy @t1 t4 14 | to argument testSchemaProxy 15 | while inferring the type of queryReturns testSchemaProxy 16 | in value declaration passing1 17 | where t0 is an unknown type 18 | t1 is an unknown type 19 | t2 is an unknown type 20 | t3 is an unknown type 21 | t4 is an unknown type 22 | t5 is an unknown type 23 | t6 is an unknown type 24 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-not-there/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: not-null-arg-not-there 3 | dependencies: 4 | - console 5 | - effect 6 | - graphql-client 7 | - prelude 8 | test: 9 | main: Test.Main 10 | dependencies: [] 11 | workspace: 12 | packageSet: 13 | registry: 64.6.0 14 | extraPackages: 15 | graphql-client: 16 | path: ../.. 17 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-not-there/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import GraphQL.Client.Args (NotNull, (=>>)) 7 | import GraphQL.Client.QueryReturns (queryReturns) 8 | import Type.Proxy (Proxy(..)) 9 | 10 | main :: Effect Unit 11 | main = pure unit 12 | 13 | testSchemaProxy :: Proxy TestNotNullParamsSchema 14 | testSchemaProxy = Proxy 15 | 16 | type TestNotNullParamsSchema 17 | = { users :: 18 | { online :: NotNull Boolean 19 | } 20 | -> Array 21 | { id :: Int 22 | } 23 | } 24 | 25 | id :: Proxy "id" 26 | id = Proxy 27 | 28 | passing1 :: Unit 29 | passing1 = 30 | const unit 31 | $ queryReturns testSchemaProxy 32 | { users: {} =>> { id } 33 | } 34 | -------------------------------------------------------------------------------- /should-fail-tests/not-null-arg-not-there/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: graphql-client 3 | dependencies: 4 | - aff 5 | - affjax 6 | - affjax-node 7 | - affjax-web 8 | - argonaut-codecs 9 | - argonaut-core 10 | - arrays 11 | - bifunctors 12 | - console 13 | - control 14 | - datetime 15 | - debug 16 | - effect 17 | - either 18 | - enums 19 | - filterable 20 | - foldable-traversable 21 | - foreign 22 | - foreign-object 23 | - functions 24 | - halogen-subscriptions 25 | - heterogeneous 26 | - http-methods 27 | - identity 28 | - integers 29 | - lists 30 | - maybe 31 | - media-types 32 | - newtype 33 | - now 34 | - nullable 35 | - numbers 36 | - ordered-collections 37 | - partial 38 | - prelude 39 | - psci-support 40 | - quickcheck 41 | - record 42 | - spec 43 | - spec-discovery 44 | - string-parsers 45 | - strings 46 | - transformers 47 | - tuples 48 | - typelevel-lists 49 | - unfoldable 50 | - unicode 51 | - unsafe-coerce 52 | - variant 53 | test: 54 | main: Test.Main 55 | dependencies: 56 | - console 57 | - argonaut-generic 58 | workspace: 59 | packageSet: 60 | registry: 60.2.1 61 | extraPackages: {} 62 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Alias.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Alias where 2 | 3 | data Alias al t = Alias al t 4 | 5 | infixr 6 Alias as : 6 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Alias/Dynamic.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Alias.Dynamic where 2 | 3 | import Prelude 4 | import Data.Argonaut.Core (Json) 5 | import Data.Argonaut.Core as Argonaut 6 | import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError, decodeJson) 7 | import Data.Argonaut.Decode.Decoders (decodeJObject) 8 | import Data.Argonaut.Encode (class EncodeJson, encodeJson) 9 | import Data.Array (mapWithIndex) 10 | import Data.Array as Array 11 | import Data.Either (Either) 12 | import Data.Generic.Rep (class Generic) 13 | import Data.Newtype (class Newtype, unwrap) 14 | import Data.Show.Generic (genericShow) 15 | import Data.Traversable (traverse) 16 | import Data.Tuple (Tuple(..)) 17 | import Foreign.Object as Object 18 | import GraphQL.Hasura.Decode (class DecodeHasura, decodeHasura) 19 | import GraphQL.Hasura.Encode (class EncodeHasura, encodeHasura) 20 | 21 | -- | Used for creating spread aliases dynamically 22 | -- | eq: 23 | -- | ``` 24 | -- | mutation myUpdates { 25 | -- | _1: update_users(where: {id : 1}, _set: { value: 10 }) { affected_rows } 26 | -- | _2: update_users(where: {id : 2}, _set: { value: 15 }) { affected_rows } 27 | -- | _3: update_users(where: {id : 3}, _set: { value: 20 }) { affected_rows } 28 | -- | } 29 | -- | ``` 30 | data Spread alias args fields = Spread alias (Array args) fields 31 | 32 | -- | The return type of a query made with a dynamic alias spread 33 | -- | This type has encodes/decodes as an object with "_n" as a key 34 | newtype SpreadRes a = SpreadRes (Array a) 35 | 36 | derive instance genericSpreadRes :: Generic (SpreadRes a) _ 37 | 38 | derive instance newtypeSpreadRes :: Newtype (SpreadRes a) _ 39 | 40 | derive instance eqSpreadRes :: Eq a => Eq (SpreadRes a) 41 | 42 | derive instance ordSpreadRes :: Ord a => Ord (SpreadRes a) 43 | 44 | instance showSpreadRes :: Show a => Show (SpreadRes a) where 45 | show = genericShow 46 | 47 | decodeSpreadRes :: forall a. (Json -> Either JsonDecodeError a) -> Json -> Either JsonDecodeError (SpreadRes a) 48 | decodeSpreadRes decoder json = decodeJObject json <#> Array.fromFoldable >>= traverse decoder <#> SpreadRes 49 | 50 | instance decodeHasuraSpreadRes :: DecodeHasura a => DecodeHasura (SpreadRes a) where 51 | decodeHasura = decodeSpreadRes decodeHasura 52 | 53 | instance decodeJsonSpreadRes :: DecodeJson a => DecodeJson (SpreadRes a) where 54 | decodeJson = decodeSpreadRes decodeJson 55 | 56 | encodeSpreadRes :: forall a. (a -> Json) -> (SpreadRes a) -> Json 57 | encodeSpreadRes encoder = 58 | unwrap 59 | >>> mapWithIndex (\idx a -> Tuple ("_" <> show idx) (encoder a)) 60 | >>> Object.fromFoldable 61 | >>> Argonaut.fromObject 62 | 63 | instance encodeHasuraSpreadRes :: EncodeHasura a => EncodeHasura (SpreadRes a) where 64 | encodeHasura = encodeSpreadRes encodeHasura 65 | 66 | instance encodeJsonSpreadRes :: EncodeJson a => EncodeJson (SpreadRes a) where 67 | encodeJson = encodeSpreadRes encodeJson 68 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Args/AllowedMismatch.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Args.AllowedMismatch where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Argonaut.Encode (class EncodeJson) 7 | import Data.Newtype (class Newtype) 8 | import GraphQL.Hasura.Decode (class DecodeHasura) 9 | import GraphQL.Hasura.Encode (class EncodeHasura) 10 | 11 | -- | Allow a type does not match the schema to be used in a query as an argument. 12 | newtype AllowedMismatch :: forall k. k -> Type -> Type 13 | newtype AllowedMismatch schemaType arg = AllowedMismatch arg 14 | 15 | derive instance Newtype (AllowedMismatch schemaType arg) _ 16 | 17 | derive newtype instance DecodeJson arg => DecodeJson (AllowedMismatch schemaType arg) 18 | derive newtype instance EncodeJson arg => EncodeJson (AllowedMismatch schemaType arg) 19 | derive newtype instance DecodeHasura arg => DecodeHasura (AllowedMismatch schemaType arg) 20 | derive newtype instance EncodeHasura arg => EncodeHasura (AllowedMismatch schemaType arg) 21 | derive newtype instance Eq arg => Eq (AllowedMismatch schemaType arg) 22 | 23 | -------------------------------------------------------------------------------- /src/GraphQL/Client/ArrayOf.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.ArrayOf 2 | ( ArrayOf(..) 3 | , arrayOf 4 | ) where 5 | 6 | import Data.Newtype (class Newtype) 7 | 8 | -- | An array type for more control over type inference. 9 | -- 10 | -- This is useful for distinguishing between `f (Array a)`` and `Array (f a)` in the return type, 11 | -- where `f` is `Identity` or `ErrorBoundary`. 12 | newtype ArrayOf a = ArrayOf a 13 | 14 | derive instance newtypeArrayOf :: Newtype (ArrayOf a) _ 15 | 16 | arrayOf :: forall q. q -> ArrayOf q 17 | arrayOf = ArrayOf 18 | -------------------------------------------------------------------------------- /src/GraphQL/Client/AsGql.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.AsGql where 2 | 3 | data AsGql :: Symbol -> Type -> Type 4 | data AsGql sym t = AsGql -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Affjax/Internal.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.BaseClients.Affjax.Internal where 2 | 3 | import Prelude 4 | 5 | import Affjax (Error, Request, URL, defaultRequest, printError) 6 | import Affjax.RequestBody as RequestBody 7 | import Affjax.RequestHeader (RequestHeader(..)) 8 | import Affjax.ResponseFormat as ResponseFormat 9 | import Data.Argonaut.Core (Json) 10 | import Data.Argonaut.Encode (encodeJson) 11 | import Data.Either (Either(..)) 12 | import Data.HTTP.Method as Method 13 | import Data.Maybe (Maybe(..)) 14 | import Data.MediaType.Common (applicationJSON) 15 | import Effect.Aff (Aff, error, throwError) 16 | 17 | throwLeft :: forall r body. Either Error { body :: body | r } -> Aff body 18 | throwLeft = case _ of 19 | Left err -> throwError $ error $ printError err 20 | Right { body } -> pure body 21 | 22 | makeAffjaxGqlRequest 23 | :: String -> URL -> Array RequestHeader -> String -> String -> Json -> Request Json 24 | makeAffjaxGqlRequest opStr url headers queryName q vars = 25 | defaultRequest 26 | { withCredentials = true 27 | , url = url 28 | , method = Left Method.POST 29 | , responseFormat = ResponseFormat.json 30 | , content = 31 | Just 32 | $ RequestBody.Json 33 | $ encodeJson 34 | { query: opStr <> " " <> queryName <> " " <> q 35 | , variables: vars 36 | , operationName: queryName 37 | } 38 | , headers = headers <> [ ContentType applicationJSON ] 39 | } 40 | -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Affjax/Node.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.BaseClients.Affjax.Node (AffjaxNodeClient(..)) where 2 | 3 | import Prelude 4 | 5 | import Affjax.Node (Error, Response, URL, request) 6 | import Affjax.RequestHeader (RequestHeader) 7 | import Data.Argonaut.Core (Json) 8 | import Data.Either (Either) 9 | import Effect.Aff (Aff) 10 | import GraphQL.Client.BaseClients.Affjax.Internal (makeAffjaxGqlRequest, throwLeft) 11 | import GraphQL.Client.Types (class QueryClient) 12 | 13 | data AffjaxNodeClient = AffjaxNodeClient URL (Array RequestHeader) 14 | 15 | instance queryClient :: QueryClient AffjaxNodeClient Unit Unit where 16 | clientQuery _ (AffjaxNodeClient url headers) name q vars = throwLeft =<< queryPostForeign "query" url headers name q vars 17 | clientMutation _ (AffjaxNodeClient url headers) name q vars = throwLeft =<< queryPostForeign "mutation" url headers name q vars 18 | defQueryOpts = const unit 19 | defMutationOpts = const unit 20 | 21 | queryPostForeign 22 | :: String -> URL -> Array RequestHeader -> String -> String -> Json -> Aff (Either Error (Response Json)) 23 | queryPostForeign opStr url headers queryName q vars = 24 | request (makeAffjaxGqlRequest opStr url headers queryName q vars) -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Affjax/Web.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.BaseClients.Affjax.Web (AffjaxWebClient(..)) where 2 | 3 | import Prelude 4 | 5 | import Affjax.RequestHeader (RequestHeader) 6 | import Affjax.Web (Error, Response, URL, request) 7 | import Data.Argonaut.Core (Json) 8 | import Data.Either (Either) 9 | import Effect.Aff (Aff) 10 | import GraphQL.Client.BaseClients.Affjax.Internal (makeAffjaxGqlRequest, throwLeft) 11 | import GraphQL.Client.Types (class QueryClient) 12 | 13 | data AffjaxWebClient = AffjaxWebClient URL (Array RequestHeader) 14 | 15 | instance queryClient :: QueryClient AffjaxWebClient Unit Unit where 16 | clientQuery _ (AffjaxWebClient url headers) name q vars = throwLeft =<< queryPostForeign "query" url headers name q vars 17 | clientMutation _ (AffjaxWebClient url headers) name q vars = throwLeft =<< queryPostForeign "mutation" url headers name q vars 18 | defQueryOpts = const unit 19 | defMutationOpts = const unit 20 | 21 | queryPostForeign 22 | :: String -> URL -> Array RequestHeader -> String -> String -> Json -> Aff (Either Error (Response Json)) 23 | queryPostForeign opStr url headers queryName q vars = 24 | request (makeAffjaxGqlRequest opStr url headers queryName q vars) -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Apollo/ErrorPolicy.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.BaseClients.Apollo.ErrorPolicy where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Encode (class EncodeJson, encodeJson) 6 | 7 | data ErrorPolicy 8 | = None 9 | | Ignore 10 | | All 11 | 12 | derive instance eqErrorPolicy :: Eq ErrorPolicy 13 | 14 | instance encodeJsonErrorPolicy :: EncodeJson ErrorPolicy where 15 | encodeJson = errorPolicyToForeign >>> encodeJson 16 | 17 | errorPolicyToForeign :: ErrorPolicy -> String 18 | errorPolicyToForeign = case _ of 19 | None -> "none" 20 | Ignore -> "ignore" 21 | All -> "all" 22 | 23 | -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Apollo/FetchPolicy.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.BaseClients.Apollo.FetchPolicy where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Encode (class EncodeJson, encodeJson) 6 | 7 | data FetchPolicy 8 | = CacheFirst 9 | | CacheOnly 10 | | CacheAndNetwork 11 | | NetworkOnly 12 | | NoCache 13 | | Standby 14 | 15 | derive instance eqFetchPolicy :: Eq FetchPolicy 16 | 17 | instance encodeJsonFetchPolicy :: EncodeJson FetchPolicy where 18 | encodeJson = fetchPolicyToForeign >>> encodeJson 19 | 20 | fetchPolicyToForeign :: FetchPolicy -> String 21 | fetchPolicyToForeign = case _ of 22 | CacheFirst -> "cache-first" 23 | CacheOnly -> "cache-only" 24 | CacheAndNetwork -> "cache-and-network" 25 | NetworkOnly -> "network-only" 26 | NoCache -> "no-cache" 27 | Standby -> "standby" 28 | -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Urql.js: -------------------------------------------------------------------------------- 1 | import 'isomorphic-unfetch'; 2 | import { createClient, defaultExchanges, subscriptionExchange } from '@urql/core'; 3 | import {createClient as createWsClient} from 'graphql-ws'; 4 | 5 | const createClient_ = function (opts) { 6 | 7 | let wsClient = null 8 | 9 | if (opts.websocketUrl) { 10 | wsClient = createWsClient({ 11 | url: opts.websocketUrl 12 | }) 13 | } 14 | 15 | const otherExchanges = opts.websocketUrl 16 | ? [ 17 | subscriptionExchange({ 18 | forwardSubscription (operation) { 19 | return { 20 | subscribe: function (sink) { 21 | const dispose = wsClient.subscribe(operation, sink) 22 | return { 23 | unsubscribe: dispose 24 | } 25 | } 26 | } 27 | } 28 | }) 29 | ] 30 | : [] 31 | 32 | return createClient({ 33 | url: opts.url, 34 | fetchOptions: { headers: opts.headers }, 35 | exchanges: defaultExchanges.concat(otherExchanges) 36 | }) 37 | } 38 | let globalClient 39 | 40 | export function createGlobalClientUnsafeImpl (opts) { 41 | return function () { 42 | if (globalClient) { 43 | return globalClient 44 | } 45 | globalClient = createClient_(opts) 46 | 47 | return globalClient 48 | } 49 | } 50 | 51 | export function createClientImpl (opts) { 52 | return function () { 53 | return createClient_(opts) 54 | } 55 | } 56 | 57 | export function createSubscriptionClientImpl (opts) { 58 | return function () { 59 | return createClient_(opts) 60 | } 61 | } 62 | 63 | export function queryImpl (client) { 64 | return function (query) { 65 | return function (variables) { 66 | return function (onError, onSuccess) { 67 | 68 | try { 69 | client 70 | .query(query, variables) 71 | .toPromise() 72 | .then(onSuccess) 73 | .catch(onError) 74 | } catch (err) { 75 | onError(err) 76 | } 77 | return function (cancelError, onCancelerError, onCancelerSuccess) { 78 | onCancelerSuccess() 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | export function mutationImpl (client) { 86 | return function (mutation) { 87 | return function (variables) { 88 | return function (onError, onSuccess) { 89 | try { 90 | client 91 | .mutation(mutation, variables) 92 | .toPromise() 93 | .then(onSuccess) 94 | .catch(onError) 95 | } catch (err) { 96 | onError(err) 97 | } 98 | return function (cancelError, onCancelerError, onCancelerSuccess) { 99 | onCancelerSuccess() 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | export function subscriptionImpl (client) { 107 | const { subscribe, pipe } = require('wonka') 108 | 109 | return function (query) { 110 | return function (variables) { 111 | return function (callback) { 112 | return function () { 113 | const { unsubscribe } = pipe( 114 | client.subscription(query, variables), 115 | subscribe(function (value) { 116 | callback(value)() 117 | }) 118 | ) 119 | return unsubscribe 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/GraphQL/Client/BaseClients/Urql.purs: -------------------------------------------------------------------------------- 1 | -- | Creates GraphQL clients 2 | module GraphQL.Client.BaseClients.Urql 3 | ( UrqlClientOptions 4 | , UrqlSubClientOptions 5 | , UrqlClient 6 | , UrqlSubClient 7 | , createClient 8 | , createGlobalClientUnsafe 9 | , createSubscriptionClient 10 | ) where 11 | 12 | import Prelude 13 | 14 | import Affjax (URL) 15 | import Affjax.RequestHeader (RequestHeader, name, value) 16 | import Data.Argonaut.Core (Json) 17 | import Data.Tuple (Tuple(..)) 18 | import Effect (Effect) 19 | import Effect.Aff (Aff) 20 | import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff) 21 | import Foreign (Foreign, unsafeToForeign) 22 | import Foreign.Object (Object) 23 | import Foreign.Object as Object 24 | import GraphQL.Client.Types (class QueryClient, class SubscriptionClient, Client(..)) 25 | 26 | type UrqlClientOptions = 27 | { url :: URL 28 | , headers :: Array RequestHeader 29 | } 30 | 31 | type UrqlSubClientOptions = 32 | { url :: URL 33 | , websocketUrl :: URL 34 | , headers :: Array RequestHeader 35 | } 36 | 37 | -- | A client to make graphQL queries and mutations. 38 | -- | From the @urql/core npm module 39 | foreign import data UrqlClient :: Type 40 | 41 | -- | A client to make graphQL queries, mutations and subscriptions. 42 | -- | Requires a web socket graphQL server. 43 | -- | From the @urql/core npm module 44 | -- | Requires your server to implement GraphQL over WebSocket Protocol 45 | -- | See https://github.com/enisdenjo/graphql-ws details 46 | foreign import data UrqlSubClient :: Type 47 | 48 | createClient 49 | :: forall schema 50 | . UrqlClientOptions 51 | -> Effect (Client UrqlClient schema) 52 | createClient = clientOptsToForeign >>> createClientImpl >>> map Client 53 | 54 | createGlobalClientUnsafe 55 | :: forall schema 56 | . UrqlClientOptions 57 | -> Effect (Client UrqlClient schema) 58 | createGlobalClientUnsafe = clientOptsToForeign >>> createGlobalClientUnsafeImpl >>> map Client 59 | 60 | createSubscriptionClient 61 | :: forall schema 62 | . UrqlSubClientOptions 63 | -> Effect (Client UrqlSubClient schema) 64 | createSubscriptionClient = clientOptsToForeign >>> createSubscriptionClientImpl >>> map Client 65 | 66 | clientOptsToForeign 67 | :: forall r 68 | . { headers :: Array RequestHeader 69 | | r 70 | } 71 | -> { headers :: Object String 72 | | r 73 | } 74 | clientOptsToForeign opts = 75 | opts 76 | { headers = Object.fromFoldable $ map toTup opts.headers 77 | } 78 | where 79 | toTup header = Tuple (name header) (value header) 80 | 81 | type UrqlClientOptionsForeign = 82 | { url :: URL 83 | , headers :: Object String 84 | } 85 | 86 | type UrqlSubUrqlClientOptionsForeign = 87 | { url :: URL 88 | , websocketUrl :: URL 89 | , headers :: Object String 90 | } 91 | 92 | foreign import createClientImpl :: UrqlClientOptionsForeign -> Effect UrqlClient 93 | 94 | foreign import createGlobalClientUnsafeImpl :: UrqlClientOptionsForeign -> Effect UrqlClient 95 | 96 | foreign import createSubscriptionClientImpl :: UrqlSubUrqlClientOptionsForeign -> Effect UrqlSubClient 97 | 98 | instance queryClient :: QueryClient UrqlClient Unit Unit where 99 | clientQuery _ c = queryForeign false c 100 | clientMutation _ c = queryForeign true c 101 | defQueryOpts = const unit 102 | defMutationOpts = const unit 103 | 104 | instance queryClientSubscription :: QueryClient UrqlSubClient Unit Unit where 105 | clientQuery _ c = queryForeign false c 106 | clientMutation _ c = queryForeign true c 107 | defQueryOpts = const unit 108 | defMutationOpts = const unit 109 | 110 | queryForeign 111 | :: forall client o 112 | . QueryClient client o o 113 | => Boolean 114 | -> client 115 | -> String 116 | -> String 117 | -> Json 118 | -> Aff Json 119 | queryForeign isMutation client name q_ vars = fromEffectFnAff $ fn (unsafeToForeign client) q vars 120 | where 121 | fn = if isMutation then mutationImpl else queryImpl 122 | 123 | opStr = if isMutation then "mutation" else "query" 124 | 125 | q = opStr <> " " <> name <> " " <> q_ 126 | 127 | foreign import queryImpl :: Foreign -> String -> Json -> EffectFnAff Json 128 | 129 | foreign import mutationImpl :: Foreign -> String -> Json -> EffectFnAff Json 130 | 131 | instance subscriptionClient :: SubscriptionClient UrqlSubClient Unit where 132 | clientSubscription _ = subscriptionImpl 133 | defSubOpts _ = unit 134 | 135 | foreign import subscriptionImpl 136 | :: UrqlSubClient 137 | -> String 138 | -> Json 139 | -> (Json -> Effect Unit) 140 | -> Effect (Effect Unit) 141 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Directive.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Directive where 2 | 3 | import Data.Symbol (class IsSymbol) 4 | import GraphQL.Client.Args (class ArgGql, class ArgGqlAt, class SatisfyNotNullParam) 5 | import GraphQL.Client.Directive.Definition (Directive) 6 | import GraphQL.Client.Directive.Location (MUTATION, QUERY, SUBSCRIPTION) 7 | import GraphQL.Client.Operation (class GqlOperation, OpMutation, OpQuery, OpSubscription) 8 | import Prim.Boolean (False, True) 9 | import Type.Data.List (class IsMember, Cons', Nil') 10 | import Type.Proxy (Proxy) 11 | 12 | data ApplyDirective :: forall k. k -> Type -> Type -> Type 13 | data ApplyDirective dir args a = ApplyDirective args a 14 | 15 | -- | Apply a directive 16 | applyDir 17 | :: forall a dir args 18 | . IsSymbol dir 19 | => Proxy dir 20 | -> args 21 | -> a 22 | -> ApplyDirective dir args a 23 | applyDir _ = ApplyDirective 24 | 25 | class DirectivesTypeCheckTopLevel :: forall k1 k2 k3. k1 -> k2 -> k3 -> Constraint 26 | class GqlOperation op <= DirectivesTypeCheckTopLevel directiveDeclarations op q 27 | 28 | instance topLevelTypeCheckPassQuery :: 29 | DirectivesTypeCheckTopLevelLocation QUERY dirs (ApplyDirective name args q) True => 30 | DirectivesTypeCheckTopLevel dirs OpQuery (ApplyDirective name args q) 31 | else instance topLevelTypeCheckPassOpMutation :: 32 | DirectivesTypeCheckTopLevelLocation MUTATION dirs (ApplyDirective name args q) True => 33 | DirectivesTypeCheckTopLevel dirs OpMutation (ApplyDirective name args q) 34 | else instance topLevelTypeCheckPassOpSubscription :: 35 | DirectivesTypeCheckTopLevelLocation SUBSCRIPTION dirs (ApplyDirective name args q) True => 36 | DirectivesTypeCheckTopLevel dirs OpSubscription (ApplyDirective name args q) 37 | else instance topLevelTypeCheckPassNoDirective :: GqlOperation op => DirectivesTypeCheckTopLevel dirs op q 38 | 39 | class DirectivesTypeCheckTopLevelLocation :: forall k1 k2 k3 k4. k1 -> k2 -> k3 -> k4 -> Constraint 40 | class DirectivesTypeCheckTopLevelLocation location directiveDeclarations q bool | location directiveDeclarations q -> bool 41 | 42 | instance directivesTypeCheckTopLevelLocationFound :: 43 | ( IsMember location locations result 44 | , ArgGqlAt name { | params } { | args } 45 | , SatisfyNotNullParam { | params } { | args } 46 | ) => 47 | DirectivesTypeCheckTopLevelLocation location (Cons' (Directive name description { | params } locations) tail) (ApplyDirective name { | args } q) result 48 | else instance directivesTypeCheckTopLevelLocationNotFound :: 49 | DirectivesTypeCheckTopLevelLocation location tail q result => 50 | DirectivesTypeCheckTopLevelLocation location (Cons' head tail) q result 51 | else instance directivesTypeCheckTopLevelLocationNil' :: 52 | DirectivesTypeCheckTopLevelLocation location Nil' q False 53 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Directive/Definition.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Directive.Definition (Directive, directive) where 2 | 3 | import Data.Symbol (class IsSymbol) 4 | 5 | -- | A directive definition 6 | data Directive :: forall k1 k2 k3 k4. k1 -> k2 -> k3 -> k4 -> Type 7 | data Directive name description arguments locations = Directive 8 | 9 | directive 10 | :: forall name description arguments location 11 | . IsSymbol name 12 | => Directive name description { | arguments } location 13 | directive = 14 | Directive 15 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Directive/Location.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Directive.Location where 2 | 3 | class ExecutableDirectiveLocation :: forall k. k -> Constraint 4 | class ExecutableDirectiveLocation a 5 | 6 | data QUERY = QUERY 7 | 8 | instance executableDirectiveLocationQUERY :: ExecutableDirectiveLocation QUERY 9 | 10 | data MUTATION = MUTATION 11 | 12 | instance executableDirectiveLocationMUTATION :: ExecutableDirectiveLocation MUTATION 13 | 14 | data SUBSCRIPTION = SUBSCRIPTION 15 | 16 | instance executableDirectiveLocationSUBSCRIPTION :: ExecutableDirectiveLocation SUBSCRIPTION 17 | -------------------------------------------------------------------------------- /src/GraphQL/Client/ErrorBoundary.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.ErrorBoundary 2 | ( BoundaryResult(..) 3 | , ErrorBoundary(..) 4 | , PropPutErrorsInPaths(..) 5 | , class PutErrorsInPaths 6 | , decodeWith 7 | , putErrorsInPaths 8 | , putErrorsInPathsImpl 9 | , toEither 10 | ) where 11 | 12 | import Prelude 13 | 14 | import Data.Argonaut.Core (Json) 15 | import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError, decodeJson) 16 | import Data.Array (filter, length, take) 17 | import Data.Either (Either(..), either) 18 | import Data.FunctorWithIndex (mapWithIndex) 19 | import Data.Generic.Rep (class Generic) 20 | import Data.Maybe (Maybe, maybe) 21 | import Data.Show.Generic (genericShow) 22 | import Data.Symbol (class IsSymbol, reflectSymbol) 23 | import GraphQL.Client.GqlError (GqlError) 24 | import GraphQL.Hasura.Decode (class DecodeHasura, decodeHasura) 25 | import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex) 26 | import Type.Proxy (Proxy) 27 | 28 | newtype ErrorBoundary a = 29 | ErrorBoundary a 30 | 31 | data BoundaryResult err a 32 | = Result a 33 | | Error JsonDecodeError err 34 | 35 | toEither :: forall err a. BoundaryResult err a -> Either err a 36 | toEither (Result a) = Right a 37 | toEither (Error _ e) = Left e 38 | 39 | decodeWith :: forall res m. Applicative m => (Json -> Either JsonDecodeError res) -> Json -> m (BoundaryResult Unit res) 40 | decodeWith decode = decode >>> either (\err -> Error err unit) Result >>> pure 41 | 42 | instance DecodeJson a => DecodeJson (BoundaryResult Unit a) where 43 | decodeJson = decodeWith decodeJson 44 | 45 | instance DecodeHasura a => DecodeHasura (BoundaryResult Unit a) where 46 | decodeHasura = decodeWith decodeHasura 47 | 48 | derive instance Functor (BoundaryResult err) 49 | derive instance (Eq err, Eq a) => Eq (BoundaryResult err a) 50 | derive instance Generic (BoundaryResult err a) _ 51 | 52 | instance (Show err, Show a) => Show (BoundaryResult err a) where 53 | show = genericShow 54 | 55 | putErrorsInPaths :: forall a b. PutErrorsInPaths a b => Array GqlError -> a -> b 56 | putErrorsInPaths = putErrorsInPathsImpl [] 57 | 58 | class PutErrorsInPaths a b | a -> b where 59 | putErrorsInPathsImpl :: (Array (Either Int String)) -> Array GqlError -> a -> b 60 | 61 | instance PutErrorsInPaths a b => PutErrorsInPaths (BoundaryResult err a) (BoundaryResult (Array GqlError) b) where 62 | putErrorsInPathsImpl path errors (Result a) = Result $ putErrorsInPathsImpl path errors a 63 | putErrorsInPathsImpl path errors (Error decodeErr _) = Error decodeErr $ errors # filter 64 | ( _.path 65 | >>> map (take (length path)) 66 | >>> maybe false (eq path) 67 | ) 68 | 69 | else instance (PutErrorsInPaths a b) => PutErrorsInPaths (Array a) (Array b) where 70 | putErrorsInPathsImpl path errors = mapWithIndex \idx -> (putErrorsInPathsImpl (path <> [ Left idx ]) errors) 71 | 72 | else instance (PutErrorsInPaths a b) => PutErrorsInPaths (Maybe a) (Maybe b) where 73 | putErrorsInPathsImpl path errors = map (putErrorsInPathsImpl path errors) 74 | 75 | else instance HMapWithIndex (PropPutErrorsInPaths) { | a } { | b } => PutErrorsInPaths { | a } { | b } where 76 | putErrorsInPathsImpl path errors = hmapWithIndex (PropPutErrorsInPaths { path, errors }) 77 | 78 | else instance (PutErrorsInPaths a b) => PutErrorsInPaths (Either err a) (Either err b) where 79 | putErrorsInPathsImpl path errors = map (putErrorsInPathsImpl path errors) 80 | 81 | else instance PutErrorsInPaths a a where 82 | putErrorsInPathsImpl _ _ a = a 83 | 84 | newtype PropPutErrorsInPaths = PropPutErrorsInPaths 85 | { path :: Array (Either Int String) 86 | , errors :: Array GqlError 87 | } 88 | 89 | instance propToSchemaTypeAlias :: 90 | ( IsSymbol sym 91 | , PutErrorsInPaths a b 92 | ) => 93 | MappingWithIndex (PropPutErrorsInPaths) (Proxy sym) a b where 94 | mappingWithIndex (PropPutErrorsInPaths { path, errors }) proxy = 95 | putErrorsInPathsImpl (path <> [ Right $ reflectSymbol proxy ]) errors -------------------------------------------------------------------------------- /src/GraphQL/Client/GqlError.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.GqlError where 2 | 3 | import Data.Argonaut.Core (Json) 4 | import Data.Either (Either) 5 | import Data.Maybe (Maybe) 6 | import Foreign.Object (Object) 7 | 8 | type GqlError = 9 | { message :: String 10 | , locations :: ErrorLocations 11 | , path :: Maybe (Array (Either Int String)) 12 | , extensions :: Maybe (Object Json) 13 | } 14 | 15 | type ErrorLocations = Maybe (Array { line :: Int, column :: Int }) 16 | -------------------------------------------------------------------------------- /src/GraphQL/Client/GqlType.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.GqlType where 2 | 3 | import Data.Argonaut.Core (Json) 4 | import Data.Maybe (Maybe) 5 | import GraphQL.Client.Args (NotNull) 6 | import GraphQL.Client.AsGql (AsGql) 7 | 8 | class GqlType :: Type -> Symbol -> Constraint 9 | class GqlType t gqlName | t -> gqlName 10 | 11 | instance GqlType (AsGql name t) name 12 | 13 | instance GqlType Boolean "Boolean" 14 | instance GqlType Int "Int" 15 | instance GqlType Number "Float" 16 | instance GqlType String "String" 17 | instance GqlType Json "Json" 18 | 19 | instance GqlType t gqlName => GqlType (Maybe t) gqlName 20 | instance GqlType t gqlName => GqlType (NotNull t) gqlName 21 | instance GqlType t gqlName => GqlType (Array t) gqlName -------------------------------------------------------------------------------- /src/GraphQL/Client/ID.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.ID where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Decode (class DecodeJson) 6 | import Data.Argonaut.Encode (class EncodeJson) 7 | import Data.Generic.Rep (class Generic) 8 | import Data.Newtype (class Newtype) 9 | import GraphQL.Client.ToGqlString (class GqlArgString) 10 | import GraphQL.Hasura.Decode (class DecodeHasura) 11 | import GraphQL.Hasura.Encode (class EncodeHasura) 12 | 13 | newtype ID = ID String 14 | 15 | derive instance eqID :: Eq ID 16 | derive instance ordID :: Ord ID 17 | derive instance newtypeID :: Newtype ID _ 18 | derive instance genericID :: Generic ID _ 19 | derive newtype instance showID :: Show ID 20 | derive newtype instance gqlArgStringID :: GqlArgString ID 21 | derive newtype instance decodeHasuraID :: DecodeHasura ID 22 | derive newtype instance encodeHasuraID :: EncodeHasura ID 23 | derive newtype instance decodeJsonID :: DecodeJson ID 24 | derive newtype instance encodeJsonID :: EncodeJson ID 25 | -------------------------------------------------------------------------------- /src/GraphQL/Client/NullArray.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.NullArray where 2 | 3 | -- | An empty array that can be used as an argument in a GraphQL query. Useful when you dont want to add type annotations 4 | data NullArray = NullArray -------------------------------------------------------------------------------- /src/GraphQL/Client/Operation.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Operation where 2 | 3 | class GqlOperation :: forall k. k -> Constraint 4 | class GqlOperation a 5 | 6 | data OpQuery = OpQuery 7 | 8 | instance GqlOperation OpQuery 9 | 10 | data OpMutation = OpMutation 11 | 12 | instance GqlOperation OpMutation 13 | 14 | data OpSubscription = OpSubscription 15 | 16 | instance GqlOperation OpSubscription -------------------------------------------------------------------------------- /src/GraphQL/Client/SafeQueryName.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.SafeQueryName (safeQueryName) where 2 | 3 | import Prelude 4 | import Data.Array (filter) 5 | import Data.CodePoint.Unicode (isAlphaNum) 6 | import Data.String.CodeUnits (fromCharArray, toCharArray) 7 | import Data.String.CodePoints (codePointFromChar) 8 | 9 | safeQueryName :: String -> String 10 | safeQueryName name = 11 | name 12 | # toCharArray 13 | <#> (\ch -> if ch == ' ' then '_' else ch) 14 | # filter ((codePointFromChar >>> isAlphaNum) || eq '_') 15 | # fromCharArray 16 | # \s -> if s == "" then "unnamed_query" else s 17 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Subscription.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Subscription where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Core (Json) 6 | import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError, decodeJson) 7 | import Data.Either (Either) 8 | import GraphQL.Client.Operation (OpSubscription) 9 | import GraphQL.Client.Query (decodeGqlRes, getFullRes) 10 | import GraphQL.Client.SafeQueryName (safeQueryName) 11 | import GraphQL.Client.ToGqlString (toGqlQueryString) 12 | import GraphQL.Client.Types (class GqlQuery, class SubscriptionClient, Client(..), GqlRes, GqlResJson(..), subscriptionEventOpts) 13 | import GraphQL.Client.Variables (getVarsJson) 14 | import Halogen.Subscription (Emitter) 15 | import Type.Proxy (Proxy(..)) 16 | 17 | subscriptionOpts 18 | :: forall a returns query schema client directives opts 19 | . SubscriptionClient client opts 20 | => GqlQuery directives OpSubscription schema query returns 21 | => DecodeJson returns 22 | => (opts -> opts) 23 | -> Client client { directives :: Proxy directives, subscription :: schema | a } 24 | -> String 25 | -> query 26 | -> Emitter (Either JsonDecodeError returns) 27 | subscriptionOpts = subscriptionOptsWithDecoder decodeJson 28 | 29 | subscriptionOptsWithDecoder 30 | :: forall client directives opts schema query returns a 31 | . SubscriptionClient client opts 32 | => GqlQuery directives OpSubscription schema query returns 33 | => (Json -> Either JsonDecodeError returns) 34 | -> (opts -> opts) 35 | -> (Client client { directives :: Proxy directives, subscription :: schema | a }) 36 | -> String 37 | -> query 38 | -> Emitter (Either JsonDecodeError returns) 39 | subscriptionOptsWithDecoder decodeFn optsF (Client client) queryNameUnsafe q = 40 | subscriptionEventOpts optsF client query (getVarsJson (Proxy :: _ schema) q) 41 | <#> decodeGqlRes decodeFn 42 | where 43 | queryName = safeQueryName queryNameUnsafe 44 | 45 | query = "subscription " <> queryName <> " " <> toGqlQueryString q 46 | 47 | subscription 48 | :: forall a returns query schema client directives opts 49 | . SubscriptionClient client opts 50 | => GqlQuery directives OpSubscription schema query returns 51 | => DecodeJson returns 52 | => Client client { directives :: Proxy directives, subscription :: schema | a } 53 | -> String 54 | -> query 55 | -> Emitter (Either JsonDecodeError returns) 56 | subscription = subscriptionWithDecoder decodeJson 57 | 58 | subscriptionWithDecoder 59 | :: forall client directives opts schema query returns a 60 | . SubscriptionClient client opts 61 | => GqlQuery directives OpSubscription schema query returns 62 | => (Json -> Either JsonDecodeError returns) 63 | -> (Client client { directives :: Proxy directives, subscription :: schema | a }) 64 | -> String 65 | -> query 66 | -> Emitter (Either JsonDecodeError returns) 67 | subscriptionWithDecoder decodeFn = subscriptionOptsWithDecoder decodeFn identity 68 | 69 | -- | Run a graphQL subscription, getting the full response, 70 | -- | According to https://spec.graphql.org/June2018/#sec-Response-Format 71 | subscriptionFullRes 72 | :: forall client directives schema subscription returns a subOpts 73 | . SubscriptionClient client subOpts 74 | => GqlQuery directives OpSubscription schema subscription returns 75 | => (Json -> Either JsonDecodeError returns) 76 | -> (subOpts -> subOpts) 77 | -> (Client client { directives :: Proxy directives, subscription :: schema | a }) 78 | -> String 79 | -> subscription 80 | -> Emitter (Either JsonDecodeError (GqlRes returns)) 81 | subscriptionFullRes decodeFn optsF (Client client) queryNameUnsafe q = ado 82 | (GqlResJson json) :: GqlResJson schema subscription returns <- subscriptionJson optsF (Client client) queryNameUnsafe q 83 | in pure $ getFullRes decodeFn json 84 | 85 | -- | Run a graphQL subscription, returning the response as json with phantom types 86 | -- | The json will be of the format: https://spec.graphql.org/June2018/#sec-Response-Format 87 | subscriptionJson 88 | :: forall client directives schema subscription returns a subOpts 89 | . SubscriptionClient client subOpts 90 | => GqlQuery directives OpSubscription schema subscription returns 91 | => (subOpts -> subOpts) 92 | -> (Client client { directives :: Proxy directives, subscription :: schema | a }) 93 | -> String 94 | -> subscription 95 | -> Emitter (GqlResJson schema subscription returns) 96 | subscriptionJson optsF (Client client) queryNameUnsafe q = 97 | GqlResJson <$> subscriptionEventOpts optsF client query (getVarsJson (Proxy :: _ schema) q) 98 | where 99 | queryName = safeQueryName queryNameUnsafe 100 | 101 | query = "subscription " <> queryName <> " " <> toGqlQueryString q 102 | 103 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Union.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Union where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Core (Json, caseJsonObject, fromObject, fromString, toString) 6 | import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError(..)) 7 | import Data.Argonaut.Decode.Class (decodeJson) 8 | import Data.Either (Either(..)) 9 | import Data.Maybe (Maybe(..)) 10 | import Data.Newtype (class Newtype) 11 | import Data.Symbol (class IsSymbol, reflectSymbol) 12 | import Data.Variant (Variant, expand, inj) 13 | import Foreign.Object (lookup) 14 | import GraphQL.Hasura.Decode (class DecodeHasura, decodeHasura) 15 | import Prim.Row (class Cons, class Lacks, class Union) 16 | import Prim.RowList as RL 17 | import Type.Proxy (Proxy(..)) 18 | 19 | newtype GqlUnion r = GqlUnion (Record r) 20 | 21 | derive newtype instance Eq (Record r) => Eq (GqlUnion r) 22 | derive newtype instance Ord (Record r) => Ord (GqlUnion r) 23 | derive instance Newtype (GqlUnion r) _ 24 | 25 | newtype UnionReturned r = UnionReturned (Variant r) 26 | 27 | derive newtype instance Eq (Variant r) => Eq (UnionReturned r) 28 | derive newtype instance Ord (Variant r) => Ord (UnionReturned r) 29 | derive instance Newtype (UnionReturned r) _ 30 | 31 | instance (RL.RowToList r rl, DecodeUnion rl r) => DecodeJson (UnionReturned r) where 32 | decodeJson = decodeJsonUnionWith (decodeUnion :: _ -> _ -> Proxy rl -> _) 33 | 34 | instance (RL.RowToList r rl, DecodeHasuraUnion rl r) => DecodeHasura (UnionReturned r) where 35 | decodeHasura = decodeJsonUnionWith (decodeHasuraUnion :: _ -> _ -> Proxy rl -> _) 36 | 37 | class DecodeUnion (rl :: RL.RowList Type) r where 38 | decodeUnion :: String -> Json -> Proxy rl -> Either JsonDecodeError (Variant r) 39 | 40 | instance 41 | ( IsSymbol l 42 | , Lacks l r 43 | , DecodeJson ty 44 | , DecodeUnion rl r 45 | , Cons l ty r r' 46 | , Cons l ty () rd 47 | , Union r rd r' 48 | ) => 49 | DecodeUnion (RL.Cons l ty rl) r' where 50 | decodeUnion = decodeUnionWith decodeUnion decodeJson 51 | 52 | instance DecodeUnion RL.Nil r where 53 | decodeUnion = failedAtTypename 54 | 55 | class DecodeHasuraUnion (rl :: RL.RowList Type) r where 56 | decodeHasuraUnion :: String -> Json -> Proxy rl -> Either JsonDecodeError (Variant r) 57 | 58 | instance 59 | ( IsSymbol l 60 | , Lacks l r 61 | , DecodeHasura ty 62 | , DecodeHasuraUnion rl r 63 | , Cons l ty r r' 64 | , Cons l ty () rd 65 | , Union r rd r' 66 | ) => 67 | DecodeHasuraUnion (RL.Cons l ty rl) r' where 68 | decodeHasuraUnion = decodeUnionWith decodeHasuraUnion decodeHasura 69 | 70 | instance DecodeHasuraUnion RL.Nil r where 71 | decodeHasuraUnion = failedAtTypename 72 | 73 | decodeJsonUnionWith 74 | :: forall (rl :: RL.RowList Type) (r :: Row Type) 75 | . (String -> Json -> Proxy rl -> Either JsonDecodeError (Variant r)) 76 | -> Json 77 | -> Either JsonDecodeError (UnionReturned r) 78 | decodeJsonUnionWith decode = 79 | caseJsonObject (Left $ TypeMismatch "object") \obj -> 80 | case lookup __typename obj of 81 | Nothing -> Left $ AtKey __typename $ MissingValue 82 | Just ty -> case toString ty of 83 | Nothing -> Left $ AtKey __typename $ TypeMismatch "string" 84 | Just ty' -> map UnionReturned $ 85 | decode ty' (fromObject obj) (Proxy :: Proxy rl) 86 | 87 | decodeUnionWith 88 | :: forall r rl l ty r' rd 89 | . IsSymbol l 90 | => Cons l ty r r' 91 | => Cons l ty () rd 92 | => Union r rd r' 93 | => (String -> Json -> Proxy rl -> Either JsonDecodeError (Variant r)) 94 | -> (Json -> Either JsonDecodeError ty) 95 | -> String 96 | -> Json 97 | -> Proxy (RL.Cons l ty rl) 98 | -> Either JsonDecodeError (Variant r') 99 | decodeUnionWith decodeUnion' decodeJson' ty json _ = 100 | if reflectSymbol l == ty then map (inj l) $ decodeJson' json 101 | else map expand (decodeUnion' ty json (Proxy :: Proxy rl) :: Either JsonDecodeError (Variant r)) 102 | where 103 | l :: Proxy l 104 | l = Proxy 105 | 106 | failedAtTypename :: forall x y a. String -> x -> y -> Either JsonDecodeError a 107 | failedAtTypename ty _ _ = Left $ AtKey __typename $ UnexpectedValue $ fromString ty 108 | 109 | __typename :: String 110 | __typename = "__typename" 111 | -------------------------------------------------------------------------------- /src/GraphQL/Client/Variable.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Variable where 2 | 3 | -- | A graphql variable 4 | data Var :: Symbol -> Type -> Type 5 | data Var name a = Var -------------------------------------------------------------------------------- /src/GraphQL/Client/WatchQuery.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.WatchQuery where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Core (Json) 6 | import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError, decodeJson) 7 | import Data.Either (Either) 8 | import GraphQL.Client.Operation (OpQuery) 9 | import GraphQL.Client.Query (decodeGqlRes) 10 | import GraphQL.Client.SafeQueryName (safeQueryName) 11 | import GraphQL.Client.ToGqlString (toGqlQueryString) 12 | import GraphQL.Client.Types (class GqlQuery, class WatchQueryClient, Client(..), watchQueryEventOpts) 13 | import GraphQL.Client.Variables (getVarsJson) 14 | import Halogen.Subscription (Emitter) 15 | import Type.Proxy (Proxy(..)) 16 | 17 | watchQueryOpts 18 | :: forall a returns query schema client directives opts 19 | . WatchQueryClient client opts 20 | => GqlQuery directives OpQuery schema query returns 21 | => DecodeJson returns 22 | => (opts -> opts) 23 | -> Client client { directives :: Proxy directives, query :: schema | a } 24 | -> String 25 | -> query 26 | -> Emitter (Either JsonDecodeError returns) 27 | watchQueryOpts = watchQueryOptsWithDecoder decodeJson 28 | 29 | watchQueryOptsWithDecoder 30 | :: forall client directives opts schema query returns a 31 | . WatchQueryClient client opts 32 | => GqlQuery directives OpQuery schema query returns 33 | => (Json -> Either JsonDecodeError returns) 34 | -> (opts -> opts) 35 | -> (Client client { directives :: Proxy directives, query :: schema | a }) 36 | -> String 37 | -> query 38 | -> Emitter (Either JsonDecodeError returns) 39 | watchQueryOptsWithDecoder decodeFn optsF (Client client) queryNameUnsafe q = 40 | watchQueryEventOpts optsF client query (getVarsJson (Proxy :: _ schema) q) 41 | <#> decodeGqlRes decodeFn 42 | where 43 | queryName = safeQueryName queryNameUnsafe 44 | 45 | query = "query " <> queryName <> " " <> toGqlQueryString q 46 | 47 | watchQuery 48 | :: forall a returns query schema client directives opts 49 | . WatchQueryClient client opts 50 | => GqlQuery directives OpQuery schema query returns 51 | => DecodeJson returns 52 | => Client client { directives :: Proxy directives, query :: schema | a } 53 | -> String 54 | -> query 55 | -> Emitter (Either JsonDecodeError returns) 56 | watchQuery = watchQueryWithDecoder decodeJson 57 | 58 | watchQueryWithDecoder 59 | :: forall client directives opts schema query returns a 60 | . WatchQueryClient client opts 61 | => GqlQuery directives OpQuery schema query returns 62 | => (Json -> Either JsonDecodeError returns) 63 | -> (Client client { directives :: Proxy directives, query :: schema | a }) 64 | -> String 65 | -> query 66 | -> Emitter (Either JsonDecodeError returns) 67 | watchQueryWithDecoder decodeFn = watchQueryOptsWithDecoder decodeFn identity 68 | -------------------------------------------------------------------------------- /src/GraphQL/Hasura/ComparisonExp.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Hasura.ComparisonExp where 2 | 3 | type ComparisonExp t = 4 | { _eq :: t 5 | , _gt :: t 6 | , _gte :: t 7 | , _in :: Array t 8 | , _is_null :: Boolean 9 | , _lt :: t 10 | , _lte :: t 11 | , _neq :: t 12 | , _nin :: Array t 13 | } -------------------------------------------------------------------------------- /src/GraphQL/Hasura/Encode.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Hasura.Encode (class EncodeHasura, EncodeHasuraProp, encodeHasura) where 2 | 3 | import Prelude 4 | import Data.Argonaut.Core (Json) 5 | import Data.Argonaut.Encode (encodeJson) 6 | import Data.Argonaut.Encode.Encoders (encodeString) 7 | import Data.DateTime (Date, DateTime(..), Time(..), day, month, year) 8 | import Data.Enum (class BoundedEnum, fromEnum) 9 | import Data.Maybe (Maybe) 10 | import Heterogeneous.Mapping (class HMap, class Mapping, hmap) 11 | import Unsafe.Coerce (unsafeCoerce) 12 | 13 | class EncodeHasura a where 14 | encodeHasura :: a -> Json 15 | 16 | instance encodeHasuraUnit :: EncodeHasura Unit where 17 | encodeHasura = encodeJson 18 | 19 | instance encodeHasuraBoolean :: EncodeHasura Boolean where 20 | encodeHasura = encodeJson 21 | 22 | instance encodeHasuraString :: EncodeHasura String where 23 | encodeHasura = encodeJson 24 | 25 | instance encodeHasuraInt :: EncodeHasura Int where 26 | encodeHasura = encodeJson 27 | 28 | instance encodeHasuraNumber :: EncodeHasura Number where 29 | encodeHasura = encodeJson 30 | 31 | instance encodeHasuraJson :: EncodeHasura Json where 32 | encodeHasura = encodeJson 33 | 34 | instance encodeHasuraArray :: EncodeHasura a => EncodeHasura (Array a) where 35 | encodeHasura = map encodeHasura >>> encodeJson 36 | 37 | instance encodeHasuraMaybe :: EncodeHasura a => EncodeHasura (Maybe a) where 38 | encodeHasura = map encodeHasura >>> encodeJson 39 | 40 | instance encodeHasuraDateTime :: EncodeHasura DateTime where 41 | encodeHasura (DateTime d t) = encodeString $ dateString d <> "T" <> timeString t 42 | 43 | instance encodeHasuraDate :: EncodeHasura Date where 44 | encodeHasura = dateString >>> encodeString 45 | 46 | dateString :: Date -> String 47 | dateString d = 48 | showEnum (year d) 49 | <> "-" 50 | <> showEnum (month d) 51 | <> "-" 52 | <> showEnum (day d) 53 | 54 | instance encodeHasuraTime :: EncodeHasura Time where 55 | encodeHasura = timeString >>> encodeString 56 | 57 | timeString :: Time -> String 58 | timeString (Time h m s ms) = 59 | showEnum h 60 | <> ":" 61 | <> showEnum m 62 | <> ":" 63 | <> showEnum s 64 | <> "." 65 | <> showEnum ms 66 | 67 | showEnum :: forall b. BoundedEnum b => b -> String 68 | showEnum = show <<< fromEnum 69 | 70 | instance encodeHasuraRecord :: HMap EncodeHasuraProp { | r } jsonRecord => EncodeHasura { | r } where 71 | encodeHasura = encodeRecordProps >>> unsafeToJson 72 | 73 | unsafeToJson :: forall a. a -> Json 74 | unsafeToJson = unsafeCoerce 75 | 76 | data EncodeHasuraProp = EncodeHasuraProp 77 | 78 | instance addOneAndShow :: 79 | EncodeHasura a => 80 | Mapping EncodeHasuraProp a Json where 81 | mapping EncodeHasuraProp = encodeHasura 82 | 83 | encodeRecordProps :: forall r1 r2. HMap EncodeHasuraProp r1 r2 => r1 -> r2 84 | encodeRecordProps = hmap EncodeHasuraProp 85 | -------------------------------------------------------------------------------- /test/GraphQL/Client/Directive.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Client.Directive.Test where 2 | 3 | import Prelude 4 | import GraphQL.Client.Directive (class DirectivesTypeCheckTopLevel, ApplyDirective, applyDir) 5 | import GraphQL.Client.Directive.Definition (Directive) 6 | import GraphQL.Client.Directive.Location (MUTATION, QUERY, SUBSCRIPTION) 7 | import GraphQL.Client.Operation (OpMutation(..), OpQuery(..), OpSubscription(..)) 8 | import Type.Data.List (type (:>), Nil') 9 | import Type.Proxy (Proxy(..)) 10 | 11 | -- type level tests 12 | testTopLevelDir :: 13 | forall directiveDeclarations op q. 14 | DirectivesTypeCheckTopLevel directiveDeclarations op q => 15 | Proxy directiveDeclarations -> 16 | op -> 17 | q -> 18 | Unit 19 | testTopLevelDir _ _ _ = unit 20 | 21 | t1 :: Unit 22 | t1 = testTopLevelDir dirs OpQuery $ cached { ttl: 1 } unit 23 | 24 | t2 :: Unit 25 | t2 = testTopLevelDir dirs OpSubscription $ cached { ttl: 1 } unit 26 | 27 | t3 :: Unit 28 | t3 = testTopLevelDir dirs OpMutation $ at_custom { a: "val" } unit 29 | 30 | cached :: forall q args. args -> q -> ApplyDirective "cached" args q 31 | cached = applyDir (Proxy :: _ "cached") 32 | 33 | at_custom :: 34 | forall q args. 35 | args -> 36 | q -> 37 | ApplyDirective "custom" args q 38 | at_custom = applyDir (Proxy :: _ "custom") 39 | 40 | dirs :: 41 | Proxy 42 | ( (Directive "cached" "" { ttl :: Int } (QUERY :> SUBSCRIPTION :> Nil')) 43 | :> (Directive "custom" "" { a :: String } (MUTATION :> Nil')) 44 | :> Nil' 45 | ) 46 | dirs = Proxy 47 | -------------------------------------------------------------------------------- /test/GraphQL/Hasura/Decode.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Hasura.Decode.Test where 2 | 3 | import Prelude 4 | 5 | import Data.Argonaut.Core (fromString) 6 | import Data.DateTime (DateTime(..), Time(..), canonicalDate) 7 | import Data.Either (Either(..)) 8 | import Data.Enum (class BoundedEnum, toEnum) 9 | import Data.Maybe (fromMaybe) 10 | import GraphQL.Hasura.Decode (decodeHasura) 11 | import Test.Spec (Spec, describe, it) 12 | import Test.Spec.Assertions (shouldEqual) 13 | 14 | spec :: Spec Unit 15 | spec = 16 | describe " GraphQL.Hasura.Decode" do 17 | describe "decodeHasura" do 18 | it "decodes a date time without milliseconds" 19 | $ decodeHasura (fromString "2020-10-09T08:07:06") 20 | `shouldEqual` 21 | ( Right 22 | $ DateTime 23 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 24 | (Time (toEnumB 8) (toEnumB 7) (toEnumB 6) (toEnumB 0)) 25 | ) 26 | it "decodes a date time without milliseconds and a GMT timezone" 27 | $ decodeHasura (fromString "2020-10-09T00:00:00+00:00") 28 | `shouldEqual` 29 | ( Right 30 | $ DateTime 31 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 32 | (Time (toEnumB 0) (toEnumB 0) (toEnumB 0) (toEnumB 0)) 33 | ) 34 | it "decodes a date time with milliseconds" 35 | $ decodeHasura (fromString "2020-10-09T08:07:06.555") 36 | `shouldEqual` 37 | ( Right 38 | $ DateTime 39 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 40 | (Time (toEnumB 8) (toEnumB 7) (toEnumB 6) (toEnumB 555)) 41 | ) 42 | it "decodes a date time with microseconds" 43 | $ decodeHasura (fromString "2020-10-09T08:07:06.555555") 44 | `shouldEqual` 45 | ( Right 46 | $ DateTime 47 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 48 | (Time (toEnumB 8) (toEnumB 7) (toEnumB 6) (toEnumB 555)) 49 | ) 50 | it "decodes a date time with a positive timezone" 51 | $ decodeHasura (fromString "2020-10-09T08:07:06.555555+01:00") 52 | `shouldEqual` 53 | ( Right 54 | $ DateTime 55 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 56 | (Time (toEnumB 7) (toEnumB 7) (toEnumB 6) (toEnumB 555)) 57 | ) 58 | it "decodes a date time with a negative timezone" 59 | $ decodeHasura (fromString "2020-10-09T08:00:00.555555-02:30") 60 | `shouldEqual` 61 | ( Right 62 | $ DateTime 63 | (canonicalDate (toEnumB 2020) (toEnumB 10) (toEnumB 9)) 64 | (Time (toEnumB 10) (toEnumB 30) (toEnumB 0) (toEnumB 555)) 65 | ) 66 | 67 | 68 | toEnumB :: forall a. BoundedEnum a => Int -> a 69 | toEnumB = toEnum >>> fromMaybe bottom -------------------------------------------------------------------------------- /test/GraphQL/Hasura/Encode.purs: -------------------------------------------------------------------------------- 1 | module GraphQL.Hasura.Encode.Test (spec) where 2 | 3 | import Prelude 4 | 5 | import Data.DateTime (DateTime) 6 | import Data.DateTime.Gen (genDateTime) 7 | import Data.Either (Either(..)) 8 | import Data.Maybe (Maybe) 9 | import Effect.Class (liftEffect) 10 | import GraphQL.Hasura.Decode (class DecodeHasura, decodeHasura) 11 | import GraphQL.Hasura.Encode (class EncodeHasura, encodeHasura) 12 | import Test.QuickCheck (class Arbitrary, quickCheck, (===)) 13 | import Test.Spec (Spec, describe, it) 14 | 15 | spec :: Spec Unit 16 | spec = 17 | describe "GraphQL.Hasura.Encode.Test" do 18 | describe "encodeHasura and decodeHasura" do 19 | it "all values encoded by encodeHasura should be successfully decoded by decodeHasura" do 20 | liftEffect 21 | $ quickCheck \(input :: Array Input) -> 22 | decodeHasura (encodeHasura input) === Right input 23 | 24 | type Input 25 | = { string :: String 26 | , strings :: Array String 27 | , stringM :: Maybe String 28 | , boolean :: Boolean 29 | , int :: Int 30 | , ints :: Array Int 31 | , numberM :: Maybe Number 32 | , datetime :: ArbDateTime 33 | , datetimes :: Array ArbDateTime 34 | } 35 | 36 | newtype ArbDateTime 37 | = ArbDateTime DateTime 38 | 39 | derive newtype instance encodeHasuraArbDateTime :: EncodeHasura ArbDateTime 40 | 41 | derive newtype instance decodeHasuraArbDateTime :: DecodeHasura ArbDateTime 42 | 43 | derive newtype instance eqArbDateTime :: Eq ArbDateTime 44 | 45 | derive newtype instance showArbDateTime :: Show ArbDateTime 46 | 47 | instance arbitraryDateTime :: Arbitrary ArbDateTime where 48 | arbitrary = ArbDateTime <$> genDateTime 49 | 50 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Effect (Effect) 5 | import Effect.Aff (launchAff_) 6 | import Test.Spec.Discovery (discover) 7 | import Test.Spec.Reporter.Console (consoleReporter) 8 | import Test.Spec.Runner (runSpec) 9 | 10 | main :: Effect Unit 11 | main = launchAff_ do 12 | specs <- discover """.*\.Test""" 13 | runSpec [consoleReporter] specs 14 | --------------------------------------------------------------------------------