This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-12-14
Channels
- # adventofcode (62)
- # beginners (78)
- # boot (26)
- # boot-dev (9)
- # cider (73)
- # cljs-dev (33)
- # cljsrn (36)
- # clojure (159)
- # clojure-android (1)
- # clojure-austin (1)
- # clojure-greece (79)
- # clojure-italy (10)
- # clojure-nl (3)
- # clojure-russia (11)
- # clojure-spec (33)
- # clojure-uk (26)
- # clojurescript (107)
- # core-async (22)
- # core-logic (12)
- # cursive (16)
- # datomic (13)
- # devcards (5)
- # duct (36)
- # emacs (4)
- # figwheel (3)
- # fulcro (107)
- # graphql (171)
- # hoplon (27)
- # instaparse (24)
- # jobs-discuss (34)
- # juxt (3)
- # lein-figwheel (1)
- # leiningen (8)
- # lumo (11)
- # off-topic (9)
- # onyx (79)
- # parinfer (1)
- # pedestal (75)
- # re-frame (27)
- # rum (1)
- # shadow-cljs (11)
- # spacemacs (20)
- # specter (17)
- # unrepl (96)
@admay I just did, I'm a little unsure of what to do in the even that I don't want ot return an object? Unless I define that object as something like {:job "done"}
I would warn against not returning anything. the reason being, that’s an incredibly unfriendly design. However, having a status type is not unheard of. The status type I use usually looks something like this.
{:objects
{:Status
{:fields
{:status {:type Int}
:message {:type String}}}}}
BTW, I use https://github.com/jakubroztocil/httpie for command line access
Thanks for the feedback; I have a rough idea of how the tutorial will play out, but it's all in my head.
For the moment, read the lacinia-pedestal documentation carefully for answers about status codes and the like.
@admay whencreating a request that doesn't necessarily get anything from a database, How do I structure that? Currently I have somethign that looks as such:
;; cgg-schema.edn
;; objects....
:Request
{:description "a request"
:fields
{}
}
;;queries
:do_the_thing
{:type :Request
:resolve :query/do-the-thing}}
;; resolver-map
(defn resolver-map
[component]
(let [db (:db component)]
{:query/game-by-id (game-by-id db)
:query/member-by-id (member-by-id db)
:BoardGame/designers (board-game-designers db)
:BoardGame/rating-summary (rating-summary db)
:GameRating/game (game-rating->game db)
:Designer/games (designer-games db)
:Member/ratings (member-ratings db)
:Request/do-the-thing (do-the-thing db)}))
(defn do-the-thing
[db]
(fn [_ args _]
(prn "AAAAAAAAAA")))
I'm learning to use variables. I tried this code (execute my-schema "query { getEntry(id:$id) {id}}" {:id "aa"} nil)
but it returns:
{:errors [{:message "Exception applying arguments to field getEntry': For argument
id', argument references undeclared variable `id'.", :query-path [], :locations [{:line 1, :column 6}], :field :getEntry, :argument :id, :unknown-variable :id, :declared-variables ()}]}
It’s not that you need to have the prefix set to :query
. It’s just that you need one. :query
is more of a convention
thanks @hlship
Good night
So I'm running into the issue that "Field
do_the_thing' (of type Request') must have at least one selection."
I thought I explicitly did not have any args defined
Lacinia might force you to have a return of some type, i’m not sure. How about we try something like this,
;; cgg-schema.edn
;; objects....
:Request
{:description "a request"
:fields
{:message {:type String}}
}
;;queries
:do_the_thing
{:type :Request
:resolve :query/do-the-thing}}
;; resolver-map
(defn resolver-map
[component]
(let [db (:db component)]
{:query/game-by-id (game-by-id db)
:query/member-by-id (member-by-id db)
:BoardGame/designers (board-game-designers db)
:BoardGame/rating-summary (rating-summary db)
:GameRating/game (game-rating->game db)
:Designer/games (designer-games db)
:Member/ratings (member-ratings db)
:query/do-the-thing (do-the-thing db)}))
(defn do-the-thing
[db]
(fn [_ args _]
(do
(println "AHHH")
{:message "Woo!"})))
This should print to the console then return a status message of “Woo!”So what we’re doing here is we defined an object type Request
that has one field message
. Then we defined a query :query/do-the-thing
with a return type of Request
. We map the query :query/do-the-thing
to the function do-the-thing
in the resolver-map
. Then, to finish it up, we define a function do-the-thing
that takes an argument db
and returns a function of three arguments that Lacinia can recognize as a resolver. The way that Lacinia works is, if that resolver function, (do-the-thing db)
returns a map-like thing that has the same shape as the fields for the expected return type Request
, then it can resolve. Try changing the :message
key to something else, say :hoopla
and see how it breaks.
As far as lacinia-pedestal goes, check this project out, https://github.com/sashton/lacinial-pedestal-demo
Specifically, https://github.com/sashton/lacinial-pedestal-demo/blob/master/src/graphql_demo/server.clj
Typically, GraphQL queries are used to return some type of data. The power in GraphQL is that you can pick and choose which data to get back instead of having to get all of it back in a rest style API. The practice of returning a Status
like object isn’t particularly something I want to stick with in the long run as a developer and will most likely be moving away from when I come up with a better approach.
They are still in the process of being written. Keep in mind that Lacinia is still a very new library. @hlship and the rest of the Walmart Labs team are working at light speed lately getting bugs fixed, features added, and documentation written 🙂
Re: Mutations
They are written under the :mutation
top level object (i.e. same level as :objects
and :queries
) and have an identical structure to :queries
.
Re: Restarting the REPL. I’ve integrated component and mount into my workflow to prevent me from ever having to restart my REPL during development.
The reason why is because you have to re-attach the resolvers every time you add or remove one from the resolver-map
. That’s the only time you should have to restart though.
So taking a look at this: https://dev-blog.apollodata.com/designing-graphql-mutations-e09de826ed97 . It appears there are other fields such as types?
Erm, as an example:
type Todo {
id: ID!
text: String
completed: Boolean
}
schema {
# The query types are omitted so we can focus on the mutations!
mutation: RootMutation
}
type RootMutation {
createTodo(input: CreateTodoInput!): CreateTodoPayload
toggleTodoCompleted(input: ToggleTodoCompletedInput!): ToggleTodoCompletedPayload
updateTodoText(input: UpdateTodoTextInput!): UpdateTodoTextPayload
completeAllTodos(input: CompleteAllTodosInput!): CompleteAllTodosPayload
}
That’s just how you define a GraphQL schema in other languages. Lacinia is the only GraphQL library I’ve seen that uses a different syntax as the conventional default.
In other words, in a language like JavaScript, where EDN doesn’t exist, the above is how the schema is defined.
Is it idiomatic to use the mutation
ns inside the resolver-map as such:
(defn resolver-map
[component]
(let [db (:db component)]
{:query/game-by-id (game-by-id db)
:query/member-by-id (member-by-id db)
:BoardGame/designers (board-game-designers db)
:BoardGame/rating-summary (rating-summary db)
:GameRating/game (game-rating->game db)
:Designer/games (designer-games db)
:Member/ratings (member-ratings db)
:query/do-the-thing (do-the-thing db)
:mutation/create-users (create-users db)}))
If you haven’t read through it yet, the official GraphQL intro is a good read, http://graphql.org/learn/
I'm just trying to see how the basics are supposed to work. I honestly didn't expect to stumble as much as I am
Ooof!!!
curl '' \
-H 'authorization: Bearer xxxxxxx' \
-d '{
"query": "mutation($schedule:PipelineScheduleCreateInput!) { pipelineScheduleCreate(input:$schedule) { pipelineScheduleEdge { node { label nextBuildAt cronline } } } }",
"variables": {
"schedule": {
"pipelineID": "UGlwZWxpbmUtLS02MzliNWJjOC0wMGZmLT",
"cronline": "@midnight",
"label": "Nightly build"
}
}
}'
@petr See https://github.com/walmartlabs/lacinia-pedestal#development-mode for a solution to restarting the REPL.
> When developing an application, it is desirable to be able to change the schema without restarting. Lacinia-Pedestal supports this: in the above example, the schema passed to pedestal-service could be a function that returns the compiled schema.
^ yeah we did something similar but with :env variables. So if you are in dev it just has a function that recompiles the schema everytime
Does anyone mind explaining that mutation query to me? In my example I have the following:
;;cgg-schema.edn
:User
{:description "a user"
:fields
{:uid {:type String}
:email {:type String}}}}
:mutations
{:create_users
{:type User
#_:args
#_{:email {:type (not-null ID)}
:password {:type (not-null String)}}
:resolve :mutation/create-users}}
;;shema.clj
(defn resolver-map
[component]
(let [db (:db component)]
{:query/game-by-id (game-by-id db)
:query/member-by-id (member-by-id db)
:BoardGame/designers (board-game-designers db)
:BoardGame/rating-summary (rating-summary db)
:GameRating/game (game-rating->game db)
:Designer/games (designer-games db)
:Member/ratings (member-ratings db)
:query/do-the-thing (do-the-thing db)
:mutation/create-users (create-users db)}))
(defn create-users
[db]
(fn [_ args _]
{:uid "134134-ljdj-lksfj-2094824"
:email ""}))
I"m unsure how to write the mutation requestI got that from here: http://graphql.org/learn/queries/#mutations
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
mutation create_users(email String! password ID!) {
create_users(email: "some-email", password: "someid") {
uid
email
}
}
;;cgg-schema.edn
:User
{:description "a user"
:fields
{:uid {:type String}
:email {:type String}}}}
:mutations
{:create_users
{:type User
#_:args
#_{:email {:type (not-null ID)}
:password {:type (not-null String)}}
:resolve :mutation/create-users}}
OK so... They have CreateReviewForEpisode
and createReview
. Which is which? Which is defined where?
mutation create_users(email String! password ID!) {
create_users(email: "some-email", password: "someid") {
uid
email
}
}
You as the client, are also requesting the data you want backThat would be great, I'm currently trying to understand your tall people short people example
I think it makes sense. So you have the mutation request fn you write. You then add another map with the vars you're sending to it?
I have big plans for the tutorial BUT there's a limit to how much "generic GraphQL" discussion can go into it, vs. the detailed information needed to implement your app in lacinia and lacinia-pedestal.
@hlship at some point, developers and engineers ought to be left alone to discover some things on their own haha
There's a lot of "aha!" moment in GraphQL, such as the idea that you can select the same field in different ways (different arguments) and use aliases to get what you want. Is there room in the tutorial to cover this kind of thing? Only if it comes about organically.
@hlship The documentation so far has been awesome and a great reference! Thanks for all of the work you and your team puts in!
As someone who is coming to it new. I found it difficult to make the jump between what I had read about graphql and what was in your docs. How things are laid out is very different. I would suggest adding one type of each operation a person would expect to perform. I.e. Modify a game
@guy I agree. Without the knowledge of the underlying pattern in GraphQL, it doesn’t matter what library you’re using, it’ll be difficult to execute
i made the mistake of starting with lacinia first then going back and reading the graphql docs 😅
I started with Alumbra before learning how GraphQL really works and what it is. I’m now re-writing the service that I built because the original turned out so poorly haha I love the GitHub GraphiQL explorer, it’s a great way to see GraphQL without having to build something! Just like anything else in the tech-sphere though, newcomers to the technology tend to dive straight into code before understanding the underlying concepts. Especially younger developers. The idea of instant gratification via tutorial is always more appealing than delaying satisfaction and forcing yourself to read technical docs. I see it a lot with React + Redux (Reagent + Re-frame), Angular, D3, RabbitMQ, Elasticsearch, AWS, etc…
@guy, do you mean sharing an id between separate invocations of graphql, or between a resolver and it’s descendent resolvers?
Anyone know anything about resolving objects with qualified keywords? I’m reading maps from a db like so:
{:client/id 1, :client/label "the baddies", :client/modified_date #inst "2017-12-09T02:43:03.931654000-00:00", :client/created_date #inst "2017-12-09T02:43:03.931654000-00:00"}
But since you can only use simple-keywords
in a Lacinia schema (to the best of my knowledge), I can’t resolve that map as a client. Should I just have a ->client
function to coerce the object into the proper shape or is there a better way to do it?I’m just curious as to whether or not Lacinia can work with qualified keywords like this in some way or if the approach is going to be to write a bunch of row->thing
functions
I've always been of the school of thought that the server should receive unqualified keywords(json-style), work internally with fully qualified keywords and return unqualified (json-style)