moongql — a code-first GraphQL library for MoonBit (← strawberry-graphql): define object types and fields in code, emit the schema SDL.
Moved on mooncakes from Lfan-ke/moongql to moonbitstack/moongql.
let s = @moongql.Schema::new()
let q = s.object("Query")
q.field("hello", NonNull(Scalar("String")))
q.field("user", Named("User"))
let u = s.object("User")
u.field("id", NonNull(Scalar("ID")))
u.field("name", NonNull(Scalar("String")))
u.field("tags", ListOf(NonNull(Scalar("String"))))
let sdl = s.to_sdl()schema {
query: Query
}
type Query {
hello: String!
user: User
}
type User {
id: ID!
name: String!
tags: [String!]
}| Package | What it holds | Depends on |
|---|---|---|
| moongql/lang | the document syntax tree, tokenize, parse, GqlError | nothing |
| moongql/schema | types, directives, the built-in scalars and meta-fields, SDL | lang |
| moongql/valid | validate | lang, schema |
| moongql/exec | execute, subscriptions, introspection | lang, schema, valid |
| moongql/federation | Apollo Federation v1 and v2 | schema, exec |
| moongql/server | the HTTP endpoint and graphql-transport-ws | exec and below, moonasgi |
| moongql/loader | DataLoader | nothing |
let doc = @moongql.parse(
"query ($id: ID!) { user(id: $id) { name ...fields @skip(if: false) } }",
)
// doc.definitions[0] is an OperationDefinition; doc.to_query() prints it back.let s = @moongql.Schema::new()
let q = s.object("Query")
q.field_args("user", [("id", NonNull(Scalar("ID")))],
Named("User"))
let u = s.object("User")
u.field("id", NonNull(Scalar("ID")))
u.field("name", NonNull(Scalar("String")))
let r = @moongql.Resolvers::new()
r.field("Query", "user", fn(info) {
match info.arg("id") {
String("1") => { "id": "1", "name": "Alice" }
_ => Json::null()
}
})
// User.id / User.name fall back to the default resolver (read off the parent).
let vars : Map[String, Json] = { "uid": "1" }
let res = @moongql.execute(s, r,
"query ($uid: ID!) { hero: user(id: $uid) { id name } }", variables=vars)
// res.stringify() == {"data":{"hero":{"id":"1","name":"Alice"}}}let res = @moongql.execute(s, r, "query Hero {\n user(id: \"1\") {\n nope\n }\n}")
// {"errors":[{"message":"Cannot query field 'nope' on type 'User'",
// "locations":[{"line":3,"column":5}]}]}let res = @moongql.execute(s, r, "{ user(id: \"1\") { name } }",
extensions={ "traceId": "qwq-233" })
// {"data":{...},"extensions":{"traceId":"qwq-233"}}let s = @moongql.Schema::new()
let node = s.interface("Node")
node.field("id", NonNull(Scalar("ID")))
let q = s.object("Query")
q.field_args(
"user",
[("id", NonNull(Scalar("ID")))],
Named("User"),
)
let u = s.object("User")
u.implements("Node")
u.field("id", NonNull(Scalar("ID")))
let inp = s.input("UserFilter")
inp.field("name", Scalar("String"))
s.enum_("Role", ["ADMIN", "USER"])s.union("SearchResult", ["Book", "Author"])
// { search { __typename ... on Book { title } ... on Author { name } } }s.scalar("DateTime",
serialize=fn(j) { j }, // value -> output JSON
parse_value=fn(j) { j }) // input JSON -> valuelet subs = @moongql.Subscribers::new()
subs.field("Subscription", "messageAdded", fn(info) {
// one Json payload per event, in delivery order
[msg1, msg2, msg3]
})
let payloads = @moongql.execute_subscription(schema, resolvers, subs,
"subscription { messageAdded(channel: \"general\") { id body } }")
// payloads[0].stringify() == {"data":{"messageAdded":{"id":"1","body":"hello"}}}let loader : @moongql.DataLoader[Int, String] = @moongql.DataLoader::new(
fn(ids) { load_authors(ids) }, // called once per pass
fn(id) { id.to_string() }, // cache-key function
)
loader.load(1); loader.load(2); loader.load(1) // three requests, two keys
loader.dispatch() // one batch call for {1, 2}
loader.get(1) // Some("author#1")let handler = @moongql.graphql_handler(schema, resolvers) // path defaults to /graphql
let app = @moongql.graphql_app(schema, resolvers) // the same, lifted onto an AsgiApplet client = @moonasgi.TestClient::new(@moongql.graphql_handler(schema, resolvers))
let body = @utf8.encode(
"{\"query\":\"{ user(id: \\\"1\\\") { name } }\"}",
)
let resp = client.post("/graphql", body~, headers=[
("content-type", "application/json"),
("accept", "application/graphql-response+json"),
])
// resp.status == 200, resp.text() == {"data":{"user":{"name":"Alice"}}}let ws = @moongql.GqlWs::new(schema, resolvers, subs, init_timeout=3000, ping_interval=12000)
let handler = ws.handler() // a moonasgi WebSocketHandler
ws.tick(now_ms) // the server's clock, as often as it likeslet fed = @moongql.Federation::new()
fed.entity(name="User", key="id", resolve=(rep, _ctx) => {
let id = match rep {
Object(m) => match m.get("id") { Some(String(s)) => s; _ => "" }
_ => ""
}
{ "id": id, "name": "User#" + id }.to_json() // materialise from the key
})
fed.apply(schema, resolvers)
// { _service { sdl } } -> the subgraph SDL with `type User @key(fields: "id")`
// _entities(representations: [{ __typename: "User", id: "7" }]) -> the User objectlet fed = @moongql.Federation::new(v2=true)
fed.entity(name="Product", key="upc", extends=true, external=["weight"],
resolve=(rep, _ctx) => {
// @requires(fields: "weight"): the gateway ships the external weight in the
// representation, so the resolver can read it to compute the estimate.
let weight = read_int(rep, "weight")
{ "upc": read_str(rep, "upc"), "shippingEstimate": weight * 2 }.to_json()
})
fed.shareable("Product", "name")
fed.override_("Product", "price", from="legacy")
fed.requires("Product", "shippingEstimate", fields="weight")
fed.inaccessible("User", "ssn") // present in the subgraph, hidden from the composed schema
fed.apply(schema, resolvers)s.directive("prefix",
locations=["FIELD"],
args=[("text", NonNull(Scalar("String")))],
on_field=Some((value, args) => match (value, args.get("text")) {
(String(x), Some(String(p))) => (p + x).to_json()
_ => value
}))
// { greeting @prefix(text: "Hello, ") } -> "Hello, world"| Rule | Spec |
|---|---|
| Operation name uniqueness | §5.2.1.1 |
| Lone anonymous operation | §5.2.2.1 |
| Argument uniqueness | §5.4.2 |
| Fragment name uniqueness | §5.5.1.1 |
| Fragments must be used | §5.5.1.4 |
| Fragment spreads must not form cycles | §5.5.2.2 |
| Directives are defined | §5.7.1 |
| Directives are in valid locations | §5.7.2 |
| Directive repeatability | §5.7.3 |
| Variable uniqueness | §5.8.1 |
| All variables used | §5.8.4 |
fn create_source_event_stream(schema : Schema, resolvers : Resolvers, subscribers : Subscribers, query : String, variables? : Map[String, Json], operation_name? : String, root_value? : Json, context? : Json) -> SubscriptionResultfn graphiql_html(endpoint? : String) -> Stringfn graphql_ws_handler(schema : Schema, resolvers : Resolvers, subscribers : Subscribers, root_value? : Json, context? : Json) -> WebSocketHandlerInstall
Download zipmoongql — a code-first GraphQL library for MoonBit (← strawberry-graphql): define object types and fields in code, emit the schema SDL.