Graphql-executor allow a service to provide the api server with more advanced logic thus becoming highly reactive for extremly low overhead. Acting as the lowest level it can be used by any server implementation to provide advanced fine graining.
You must have access to a server to -> (server | browser | client) streaming ability like (but not limited to) Websockets, gRpc, sockets, SSE..
When it comes to designing infrastructures we all come to the difficult point where we have to find what kind of trade-off is acceptable in order to gain time and momentum, which is something you only buy with experience.
Choose high-level and you'll start fast then run into limitations due to lack of control, choose low-level and you'll die before releasing anything.
The way execution is described in the GraphQL SDL can be interpreted in different ways, thus i present you the Graphql Execution definition tale as seen and narrated by myself, by trading off a stateless ecosystem.
The text above is irrelevant and just a flex to looks like i'm not another javascript dev thinking he's making something cool in his life, but i'm lying to myself this lib is just some random drunk written experimental bullshit driven by hype, capitalism and soviet isotopes
- Installation
- Specification diff
- How it works
- What Graphql-executor is not
- Should i use it in production ?
- Exemple
- Usage
- Caveats
npm install @hydre/graphql-executorwhich has been inspired and written while listening lo-fi
- GraphQL generates a response from a request via execution.
+ GraphQL may generate a response, a stream of response
+ or nothing from a request via execution.
A request for execution consists of a few pieces of information:
* The schema to use, typically solely provided by the GraphQL service.
- * A Document which must contain GraphQL OperationDefinition
+ * A Document which may contain some Graphql OperationDefinitions
and may contain FragmentDefinition.
- * Optionally: The name of the Operation in the Document to execute.
- * Optionally: Values for any Variables defined by the Operation.
+ * Optionally: Values for some Variables defined by the Operation.
* An initial value corresponding to the root type being executed.
Conceptually, an initial value represents the “universe” of data available
via a GraphQL Service. It is common for a GraphQL Service to always
use the same initial value for every request.
Given this information, the result of ExecuteRequest() produces
- the response, to be formatted according to the Response section below.
+ maybe a response, maybe a stream of non exhaustive datas patch,
+ maybe a pack of beer or an infinity stone, we don't know..
+ to be formatted according to the Response section below.To execute a request, the executor must have a parsed Document
- and a selected operation name to run
- if the document defines multiple operations,
- otherwise the document is expected to only contain a single operation.
- The result of the request is determined by the result of executing
- this operation according to the “Executing Operations” section below.
+ which can contains multiple operations and fragments.
+ Each operation will be executed or awaited if it require arguments
+ exported by another operation, the result of the request will respect the
+ type definitions but will not be known ahead of time
ExecuteRequest(schema, document, operationName, variableValues, initialValue)
1. Let operation be [...]
+ Too long didn't write lollet's code golf the spec
- A GraphQL document is sent to the executor
- it may contains some
Queries - it may contains some
Mutations - it may contains some
Fragments - it may contains some
Subscription
- it may contains some
- Operations are resolved in parallel
- Execution order inside an operation doesn't change, still parallel for queries and sequencial for mutations
- Each Mutation and Query resolvers can expose 3 functions, all optionals
buildwhich allow native query transforms, can be thinked of as a better dataLoader- the result is stored in the rootValue of every operations under a
Symbol buildcan be a value, or a function which return avalue, apromise, aniteratoror anasyncIterator- in case of an iterator, it will buffer until completion
- if none is supplied, the defaultBuildResolver is used
- the result is stored in the rootValue of every operations under a
resolvewhich allow server side optimistic result or vanilla resolving as per the specresolvecan be a value, or a function which return avalue, apromise, aniteratoror anasyncIterator- in case of an iterator, it will buffer until completion
- if none is supplied, the defaultResolver is used
subscribewhich allow@livedirective support and return a stream ofpatchssubscribemust be a function which return anasyncIterator- this time every iterated value is sent back in the main stream as a patch
- each nested
asyncIteratoris asynchronously piped into the parent - if none is supplied, the defaultSubscriptionResolver is used
- Each Subscription keep the original behavior, by calling
subscribe()on the top layer thenresolve()for nested selections. Only difference is as per our new spec it inherits thebuildresult - Processing :
- Let
pidbe the reference to the current process- used to handle any kinds of disconnection or ressource cleaning
- used as root reference in the main stream
- used to receive varmap update from the client (such reactive 🐶 wow)
- Let
varmapbe the cached query Variables - Let
export_varmapbe the reference of all@exportvariables - Let
reactive_opsbe the operations not satisfied byvarmap - Fail fast if any
reactive_opswill never be satisfied byexport_varmap- could be missing or cyclic dependencies
- Let
satisfied_opsbe the operations satisfied byvarmap - Execute each
satisfied_opsin parallel- Each operation using variables are removed and inserted into
reactive_ops - All others are freed
subscribe()remains active until finished, updated by avarmapupdate, or closed by apidkill
- Each operation using variables are removed and inserted into
- Each time the
@exportdirective is triggered,varmapis updated- this is basically client side scripting and come with caveats that'll need to be adressed
- Pull newly satisfied operations by
varmapfromreactive_opstosatisfied_ops - While
satisfied_opsis not empty, [go to 7.] - All done
pidrun in the background and update queries by watchingvarmapwhich [go to 7.]pidcan be killed by the server or a client requeststreamsare fully handled in the background and can killpidon any non GraphQL error
- Let
Graphql-executor is not a Graphql server, it's basically a function and must be used
by servers in replacement of the Graphql-js
execute function. Graphql-executor tightly follow Graphql-js implementation and use it
underneath at a lower level to provide a different way of executing request.
The code is really light and we use it in production at @sidy it's pretty close to the official graphql implementation, your concerns should tend more on the conceptual design of such an experimental draft as we have yet to observe every issues
The concept of a server side state initiated by a client must be analyzed and heavily restricted.
there are many solutions to explore