GraphQL

Punk::GraphQL mounts a GraphQL-over-HTTP endpoint into the application, executed by GraphQL::Houtou - an XS-first parser and native-VM runtime. The schema compiles once at boot, in the server parent, so the compiled artifacts are shared copy-on-write across preforked workers.

package MyApp;
use Punk;
use Punk::Plugin::GraphQL;

plugin 'GraphQL';

graphql '/graphql' => 'schema/app.graphql', {
    resolvers => {
        Query    => 'Books',                    # MyApp::Controller::Books
        Mutation => { rename => 'Books#rename' },
    },
    context  => 'Books#context',
    guard    => 'Auth#user',
    graphiql => 1,
};

The schema is a filename, an SDL string, or an already built GraphQL::Houtou::Schema. Multiple graphql declarations mount independent endpoints, each with its own schema and runtime.

Resolvers

Resolvers take the same targets as routes - a coderef or a 'Controller#method' string - so a typo croaks at boot, naming the class and method. A whole type may name a controller class: every field the SDL declares on that type which has a same-named method on the class is wired to it, and fields without one keep the default resolver.

Resolver methods use Houtou's signature - ($source, $args, $context, $info) - not a route handler's ($c). The request crosses over through the context builder, called once per request with the Punk::Context:

sub context {
    my ($c) = @_;
    return ({ db => $c->db });
}

Its return is list-assigned as (context, on_stall) - the optional second value is the batching hook that flushes GraphQL::Houtou::DataLoader instances, which you create inside the builder so their caches are request-scoped.

The request path

Punk's C dispatcher matches the route, the envelope is decoded through the File::Raw::JSON C ABI, Houtou executes on its XS VM and renders straight to UTF-8 JSON bytes, and Punk's C finish path passes them through without re-encoding. guard takes an ordinary Punk guard, and graphiql => 1 serves the in-browser IDE on GET.