tendant / graphql-clj Goto Github PK
View Code? Open in Web Editor NEWA Clojure library that provides GraphQL implementation.
License: Eclipse Public License 1.0
A Clojure library that provides GraphQL implementation.
License: Eclipse Public License 1.0
It would be super helpful if there were utilities for things like merging, diffing and exporting schema.
Validated statement also contains the whole schema. They might need to be purged including other information which is not needed during execution.
See #40 for details.
First off, thanks for the work on this – it's exciting stuff! I'm aware that any optimisation efforts are probably still far on the horizon but I took the liberty of creating a small set of benchmarks in this repository, currently only for query parsing.
Unfortunately, as of now, it seems that parsing incurs nearly intolerable overhead. For example, the following query (which I don't feel is an unfairly contrived example) clocks in at around 50ms mean parsing time:
{
newestUsers { name, image },
topUser: firstUser (sort: "rank", order: "desc") {
name,
projects {
__type,
name,
...Spreadsheet,
...Painting
}
}
}
fragment Spreadsheet on SpreadsheetProject {
rowCount,
columnCount
}
fragment Paiting on PaintingProject {
dominantColor { name, hexCode }
}
Raw Output for the :complex-query
testcase (here):
Case: :complex-query
Evaluation count : 1320 in 60 samples of 22 calls.
Execution time mean : 47.453645 ms
Execution time std-deviation : 2.097021 ms
Execution time lower quantile : 45.597164 ms ( 2.5%)
Execution time upper quantile : 52.667917 ms (97.5%)
Found 3 outliers in 60 samples (5.0000 %)
low-severe 2 (3.3333 %)
low-mild 1 (1.6667 %)
Variance from outliers : 30.3294 % Variance is moderately inflated by outliers
Most of this seems to be instaparse, though, as shown by :complex-query-instaparse-only
:
Case: :complex-query-instaparse-only
Evaluation count : 1380 in 60 samples of 23 calls.
Execution time mean : 45.664096 ms
Execution time std-deviation : 333.091255 µs
Execution time lower quantile : 45.152926 ms ( 2.5%)
Execution time upper quantile : 46.305994 ms (97.5%)
Anyway, I just wanted to bring this to your attention. Things like query caching would surely help in a production context, but parsing overhead seems to me like something that can't hurt to reduce. Of course, if GraphQL is just a hard to parse language, there's nothing to be done.
Update: There was a problem with the benchmarks, causing a higher mean (~120ms) than the actual result – which is a lot better but 50ms might still contribute massively to resolution times.
The GraphQL spec (https://github.com/facebook/graphql/blob/af50a0a7e4fec972a6b7e1f8aaa518e41b459116/README.md) mentions that the special field __typename
is defined on every object, and it appears this is not supported in graphql-clj 0.1.20.
I would never have noticed except the Apollo client automatically adds the __typename
field to queries, and my graphql-clj based app is returning:
{"errors":[{"error":"Cannot query field '__typename' on type 'Account'.","loc":{"line":1,"column":37}}]}
I'm a clojure noob so any suggestions much appreciated
Thanks
Because of this line: https://github.com/tendant/graphql-clj/blob/master/src/graphql_clj/validator/rules/variables_in_allowed_position.clj#L42
There is no effective validation for list type variables. A failing test case that illustrates this bug:
Related: #43
Resolver: Provide mapping from (Type, field) -> fn. We need figure out what is good definition of resolver-fn.
Current resolver-fn has definition:
(fn [context parent args])
context: is provided by caller of Executor. It contains any context from execution environment, it will be passed directly to resolver function.
parent: is the resolved result of parent Type.
args: it contains arguments of field, including default value of arguments combined with provided arguments.
Hi! I'll preface this with that I'm relatively new to graphql, so this could be just a noob mistake!
I'm putting together a simple project to play around with graphql-clj (and graphql in general), but I can't seem to alias my queries. I've looked at your tests and I know aliasing should work, but I can't seem to make it happen.
I've included a screenshot of the entirety of my codebase (super small!). Would you perhaps be able to point out an incorrect use of the library (or perhaps a mistake in the query/schema) that is preventing my attempts at aliasing? I want to make sure this is a mistake on my end, and not some bug.
If I reorder the aliased lines in the query, I always receive only the last-requested user, and the returned data is not aliased.
Thanks for your help! And thanks for the lib!
Ideally, the flow for graphql engine could be:
Query/Schema String -- Parser --> Parsed AST -- Transformation --> Transformed Map -- Validator --> Validation Error or Validated Map -- Executor --> Executor Error or Result
"Transformed Map" will be used by Validator and Executor for further processing. And also it will make validation and execution pluggable.
Given the following schema:
(def schema-str "schema {
query: Query
}
type Query {
a: A
x: X
}
type A {
b: B
}
type B {
c(foo: Int): String
}
type X {
y(foo: Int): String
}
")
(def type-schema (-> schema-str parser/parse validator/validate-schema))
I'm getting validation error for this query:
(-> "{ a { b { c(foo: 1) } } }" parser/parse (validator/validate-statement type-schema))
; => {:errors [{:error "Unknown argument 'foo' on field 'c' of type 'String'.", :loc {:line 1, :column 13}}]}
While this one works:
(-> "{ x { y(foo: 1) } }" parser/parse (validator/validate-statement type-schema))
; => {:document {:operation-definitions [{:section :operation-definitions, :node-type :operation-definition, :operation-type {:type "query"}, :selection-set [{:selection-set [{:field-name "y", :node-type :field, :args-fn #function[clojure.core/constantly/fn--4614], :kind :SCALAR, :parent-type-name "X"}], :field-name "x", :node-type :field, :kind :OBJECT, :parent-type-name "Query"}]}], :type-system-definitions nil, :fragment-definitions nil}}
This is on 0.1.20.
The library will not throw exception if I pass null value to the required field.
For example, if I define
input TaskInput {
name: String!,
}
I can set name
to null or even don't provide this field at all.
In Clojure, keyword keys would be more idiomatic than string keys in a map. graphql-clj seems to prefer string keys, and keyword keys on not consistently supported.
See that article - if we were able to pull info, we could optimize greatly on the back end, what fields are selected from a DB query or other backend data source.
Current implementation of executor is hard-coded logic as Clojure function to call up resolver function.
Ideally, an Executor can walk through "Transformed Map" and realize the result map.
Related project: https://github.com/xsc/claro and https://github.com/kachayev/muse
Whenever a schema has an input object within another input object, I get a "Expected 'SomeType', found not an object." error. Not sure what's going on.
Given schema:
schema {
query: Query
}
input TextInput {
value: String
}
input WorldInput {
text: TextInput
}
type Query {
hello(world: WorldInput): String
}
and query:
{
hello(world: {text: "World"})
}
The response from the server is
"errors": [
{
"error": "Argument 'world' of type 'WorldInput' has invalid value: {text: World}. Reason: Expected 'WorldInput', found not an object.",
"loc": {
"line": 2,
"column": 16
}
}
]
}
(def type-schema (validator/validate-schema (parser/parse "type Query {
people: String
}
type Mutation {
createPeople(emails: [String!]): String
}
schema {
query: Query
mutation: Mutation
}")))
(validator/validate-statement (parser/parse "mutation($emails: [String!]) {
createPeople(emails: $emails)
}") type-schema)
{:state {:errors [{:error "clojure.spec$nilable_impl$reify__41165 cannot be cast to clojure.lang.Named"}]}}
Hi,
It seems that the list type argument isn't passed to the resolver function now. I wonder if I made a mistake or there is some regression in the code.
Below is the schema and mutation call I was testing. The resolver function createPerson
is called with args :friend nil
(the :name
argument is fine):
type Person {
id: String!
name: String
friends: [Person]
}
type Mutation {
createPerson(name: String, friends: [String]): Person
}
schema {
mutation: Mutation
}
mutation {
createPerson(name: "John", friends: ["1"]) {
name,
id,
friends {
name
}
}
}
I wonder if the args-fn
needs to handle the list type. https://github.com/tendant/graphql-clj/blob/master/src/graphql_clj/executor.clj#L13-L17
Thank you! And just to take to opportunity to say that this is a great tool you are making. The well designed and easy to use API makes it more of a joy to code with. Thank you very much!
Generic data-loader can be used anywhere to provide caching and solution for n+1 problem.
For example: https://github.com/facebook/dataloader. It could be a independent library.
Would be great if there was a link to some documentation, even if it was just with with https://cljdoc.org. (You can see the auto-generated docs here: https://cljdoc.org/d/graphql-clj/graphql-clj/0.2.7/doc/readme)
According to http://graphql.org/learn/schema/#arguments you can give arguments default values and then you can skip them in the query. I tried that but I'm getting {:error [{:message "Missing arguments: #{\"id\"}, for field (foo) in type (Query)."}]}
. I briefly looked at the source and I don't see any code dealing with default values. What's the status of this? I may help with implementing this.
I'm trying to upgrade from 0.1.17 to 0.1.20 and I'm getting this:
Exception in thread "main" java.io.FileNotFoundException: Could not locate clojure/spec__init.class or clojure/spec.clj on classpath., compiling:(graphql_clj/spec.clj:1:1)
Is 0.1.20 no longer supporting Clojure < 1.9 ?
for example, in
type User {
id: String!
}
if id's resolver function returns nil, the connection will be closed.
But there is no stack trace or Exception printed to the console, and the following resolver function will throw error, which is very misleading
Hi:
First of all, great work \o/
My question is about scalar types. Sometimes is annoying to be dealing with data conversion in my application logic and I'm wondering if it's possible to register a new scalar converter somehow.
Thanks
It's possible to create a schema despite a parse failure, which leads to more subtle failures ("Field does not exist on Type", etc) when trying to actually use the schema. Instead, the parse failure should throw an exception at some point (preferably the parser/parse
or type/create-schema
call).
(Incidentally, in this case the parse failure is because the open quote is immediately followed by a newline. Moving the open quote to the first line of the schema string fixes this failure)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.