///|
/// The in-browser IDE a `GET` on the GraphQL endpoint serves. Four of them are on
/// offer and the whole thing can be turned off:
///
/// * **the gilded page** (`gilded`, the default) — the library's own IDE, built
/// in `ide_gilded.mbt`. It is one HTML string with no CDN and no bundler: a
/// document on the left, the answer on the right, a schema drawer, variables,
/// and the same linking the documentation uses — a field lit on one side
/// lights the same field on the other. An operation is sent when it is run,
/// over the same `POST` every client uses.
///
/// * **GraphiQL** (`graphiql`) — the GraphQL Foundation's
/// reference IDE, MIT-licensed and the one kept current with the
/// specification. Version 5 carries the Monaco editor, a plugin system (the
/// explorer and history plugins are enabled here) and a fetcher that speaks
/// `graphql-transport-ws`, so it runs subscriptions too.
///
/// * **Apollo Sandbox** (`apollo_sandbox`) — Apollo's Explorer, embedded as an
/// iframe. It needs no Apollo account and introspects the endpoint on load.
///
/// * **Altair** (`altair`) — the fullest of the three: environments, saved
/// collections, and subscriptions over WebSocket or SSE.
///
/// Every asset is pinned to an exact version, so a page served today keeps
/// working when the CDN moves on. The IDE is a development surface: it
/// introspects the schema and runs arbitrary operations, which is why `off`
/// exists for anything but a development deployment.
///|
/// Which IDE the endpoint serves.
pub(all) enum IdeKind {
Gilded
GraphiQL
ApolloSandbox
Altair
Off
} derive(Eq)
///|
/// The IDE a `GET` without a `?query=` is answered with. `subscription_endpoint`
/// is where the IDE opens its WebSocket: left as `None`, each page derives one
/// from the page's own location (the same host, `wss:` over HTTPS), which is
/// right for an endpoint served next to the IDE.
pub(all) struct Ide {
kind : IdeKind
subscription_endpoint : String?
}
///|
/// The gilded page: the library's own IDE, and the default.
pub let gilded : Ide = { kind: Gilded, subscription_endpoint: None, }
///|
/// GraphiQL, the reference IDE.
pub let graphiql : Ide = { kind: GraphiQL, subscription_endpoint: None, }
///|
/// Apollo Sandbox, embedded as an iframe.
pub let apollo_sandbox : Ide = {
kind: ApolloSandbox,
subscription_endpoint: None,
}
///|
/// Altair, the IDE with environments and saved collections.
pub let altair : Ide = { kind: Altair, subscription_endpoint: None, }
///|
/// No IDE at all: a `GET` without a `?query=` is a 405.
pub let off : Ide = { kind: Off, subscription_endpoint: None, }
///|
/// Whether this configuration serves no IDE.
pub fn Ide::is_off(self : Ide) -> Bool {
self.kind == Off
}
///|
/// The page title the IDE's HTML carries.
pub fn Ide::title(self : Ide) -> String {
match self.kind {
Gilded => "MoonQL"
GraphiQL => "GraphiQL"
ApolloSandbox => "Apollo Sandbox"
Altair => "Altair"
Off => ""
}
}
///|
/// The full HTML page mounting this IDE against `endpoint`. `Off` has no page,
/// and answers the empty string.
pub fn Ide::html(self : Ide, endpoint~ : String = "/graphql") -> String {
match self.kind {
Gilded => gilded_page(endpoint)
GraphiQL => graphiql_page(endpoint, self.subscription_endpoint)
ApolloSandbox => apollo_page(endpoint)
Altair => altair_page(endpoint, self.subscription_endpoint)
Off => ""
}
}
///|
/// The GraphiQL page up to the fetcher's endpoint literal. React, GraphiQL and
/// the plugins load from esm.sh through an import map, so nothing is bundled
/// into the page: it is a string, not a build artefact.
fn graphiql_head() -> String {
let head =
#|
#|
#|
#|
#| GraphiQL
#|
#|
#|
#|
#|
#|
#|
#|
Loading GraphiQL…
#|
#|
#|
sub + tail
}
///|
/// The GraphiQL page, wired to fetch against `endpoint`.
fn graphiql_page(endpoint : String, subscription : String?) -> String {
graphiql_head() + endpoint + graphiql_tail(subscription)
}
///|
/// The Apollo Sandbox page: a full-height container the embedded Sandbox mounts
/// an iframe into, pointed at an absolute endpoint URL (the iframe's requests
/// are sent by this page, but a relative URL would read as relative to the
/// iframe). Sandbox opens its subscription socket on the endpoint itself, so
/// `subscription_endpoint` has nothing to say to it.
fn apollo_page(endpoint : String) -> String {
let head =
#|
#|
#|
#|
#| Apollo Sandbox
#|
#|
#|
#|
#|
#|
#|
#|
head + endpoint + tail
}
///|
/// The Altair page. Altair's static build reads its endpoint and its subscription
/// endpoint from the page's globals before its own bundle loads, so the whole
/// configuration is three assignments followed by the two scripts. Every asset is
/// pinned to an exact version, so a page served today keeps working when the CDN
/// moves on.
fn altair_page(endpoint : String, subscription : String?) -> String {
let sub = match subscription {
Some(u) => "'" + u + "'"
None =>
"(u => (u.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + u.host + u.pathname)(new URL(endpoint, location.href))"
}
let head =
#|
#|
#|
#|
#| Altair
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
head + endpoint + mid + " " + sub + tail
}
///|
pub extend IdeKind with Eq::{not_equal, equal}