aboutsummaryrefslogtreecommitdiff

@eeeooolll/graphiql

A Svelte 5 GraphiQL alternative. CodeMirror 6 under the hood, runes-only state, SSR-safe, zero build config for SvelteKit consumers.

Published on npm as @eeeooolll/graphiql.

Install

bun add @eeeooolll/graphiql

Requires Svelte 5. The package ships .svelte sources — your SvelteKit/Vite build compiles them against your own Svelte version.

Entry points

  • @eeeooolll/graphiql — utilities, fetchers, stores, types (TS-only)
  • @eeeooolll/graphiql/component — the GraphiQL Svelte component (default export)
  • @eeeooolll/graphiql/splitter — the Splitter Svelte component (default export)

Component entry points are separate from the TS API so SvelteKit/Vite can resolve .svelte SFCs through their dedicated bundler hooks.

Usage

<script lang="ts">
  import { createHttpFetcher } from "@eeeooolll/graphiql";
  import GraphiQL from "@eeeooolll/graphiql/component";

  const fetcher = createHttpFetcher({ url: "/graphql" });
</script>

<GraphiQL {fetcher}/>

Full prop list:

Prop Type Default
fetcher Fetcher (required)
initialQuery string ""
namespace string "eol-graphiql"
resultFooter Snippet<[{ result: string }]> undefined
storage Storage localStorage-based
subscriptionMode "append" \| "replace" "append"
tabExtras Snippet<[{ tab: Tab }]> undefined
theme Extension (CodeMirror) oneDark
toolbarExtras Snippet undefined

Integration

The component only needs a Fetcher — a function that takes { query, variables, operationName, headers } and returns either a Promise<FetcherResult> (HTTP) or an AsyncIterable<FetcherResult> (SSE/WS). That's the full seam. Any GraphQL server that speaks HTTP JSON works out of the box via createHttpFetcher.

GraphQL Yoga

// server.ts
import { createYoga, createSchema } from "graphql-yoga";

export const yoga = createYoga({
  schema: createSchema({
    resolvers: { Query: { hello: () => "world" } },
    typeDefs: /* GraphQL */ `type Query { hello: String }`
  })
});
<!-- +page.svelte -->
<script lang="ts">
  import { createHttpFetcher } from "@eeeooolll/graphiql";
  import GraphiQL from "@eeeooolll/graphiql/component";

  const fetcher = createHttpFetcher({ url: "/graphql" });
</script>

<GraphiQL {fetcher}/>

Yoga's /graphql endpoint speaks standard JSON; no adapter needed.

Apollo Server

// server.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
const fetcher = createHttpFetcher({
  headers: { "apollo-require-preflight": "true" },
  url: "http://localhost:4000/"
});

The apollo-require-preflight header satisfies Apollo's CSRF mitigation for non-browser clients; drop it if you disable that check.

graphql-modules

graphql-modules builds a composed schema; you still expose it over HTTP via Yoga, Apollo, or raw graphql-http. The GraphiQL wiring is identical — point the fetcher at whatever endpoint you mounted.

// modules/app.ts
import { createApplication, createModule, gql } from "graphql-modules";

const userModule = createModule({
  id: "user",
  resolvers: { Query: { me: () => ({ id: "1", name: "Ada" }) } },
  typeDefs: gql`type Query { me: User } type User { id: ID! name: String! }`
});

export const app = createApplication({ modules: [userModule] });
// server.ts (Yoga host)
import { createYoga } from "graphql-yoga";
import { app } from "./modules/app.ts";

export const yoga = createYoga({
  plugins: [app.createSubscription()],
  schema: app.createSchemaForApollo()
});
<script lang="ts">
  import { createHttpFetcher } from "@eeeooolll/graphiql";
  import GraphiQL from "@eeeooolll/graphiql/component";

  const fetcher = createHttpFetcher({ url: "/graphql" });
</script>

<GraphiQL {fetcher}/>

Hono / Bun / Deno

// deno
import { createHttpFetcher } from "@eeeooolll/graphiql";

const fetcher = createHttpFetcher({
  fetch: globalThis.fetch,
  url: "https://countries.trevorblades.com/"
});

The injectable fetch is how you plug in undici, a mocked fetch for tests, or a custom one that attaches auth headers.

Custom headers (auth, tenancy)

Two places to set headers:

  • Per-request, server-wide — pass headers to createHttpFetcher. Applied to every request.
  • Per-tab, user-editable — use the Headers pane in the UI. Merged on top of fetcher-level headers.
const fetcher = createHttpFetcher({
  headers: { authorization: `Bearer ${token}` },
  url: "/graphql"
});

Subscriptions

SSE (graphql-sse protocol):

import { createSseFetcher, createHttpFetcher } from "@eeeooolll/graphiql";

const http = createHttpFetcher({ url: "/graphql" });
const sse = createSseFetcher({ url: "/graphql/stream" });

// Dispatch by operation type in a wrapper:
const fetcher: Fetcher = (req) => /subscription\s/.test(req.query) ? sse(req) : http(req);

WebSocket (graphql-ws protocol):

import { createWsFetcher } from "@eeeooolll/graphiql";

const ws = createWsFetcher({ url: "ws://localhost:4000/graphql" });

Either transport returns an AsyncIterable<FetcherResult>; the component handles streaming into the result pane per the subscriptionMode prop.

Custom fetcher

Anything that matches the Fetcher signature works. Useful for request batching or injecting trace headers:

import type { Fetcher } from "@eeeooolll/graphiql";

const traced: Fetcher = async (req) => {
  const traceId = crypto.randomUUID();

  const response = await fetch("/graphql", {
    body: JSON.stringify(req),
    headers: { "content-type": "application/json", "x-trace-id": traceId },
    method: "POST"
  });

  return response.json();
};

Persisted queries (APQ)

createApqFetcher implements the Apollo Automatic Persisted Queries protocol — first request sends only the SHA-256 hash; on PersistedQueryNotFound the fetcher retries with the full query and the server caches the mapping. HTTP-only.

import { createApqFetcher } from "@eeeooolll/graphiql";

const fetcher = createApqFetcher({
  url: "/graphql"
});

Pass disable: true to bypass the two-step dance (full query on every request) for debugging. Hashes are cached per-fetcher in memory; no disk persistence.

Keyboard shortcuts

Shortcut Action
Cmd/Ctrl + Enter Run query
Cmd/Ctrl + Shift + Enter New tab
Cmd/Ctrl + Shift + W Close tab
Cmd/Ctrl + Shift + F Format query
Cmd/Ctrl + Alt + Right/Left Next / prev tab

Cmd+T and Cmd+W aren't used because browsers reserve them; embedders running in Tauri/Electron can remap via matchShortcut (exported from the package).

Session export/import

import { validateSessionExport } from "@eeeooolll/graphiql";

const json = JSON.parse(rawText);
const result = validateSessionExport(json);

if ("error" in result)
  console.error(result.error);
else
  console.log(`${result.tabs.length} tabs ready to import`);

The History panel ships Export/Import buttons that round-trip through this validator. Import accepts version-1 exports, caps at 50 tabs, and rejects strings over 1 MB.

Theming

CSS custom properties drive the chrome:

  • --graphiql-accent
  • --graphiql-bg
  • --graphiql-border
  • --graphiql-fg
  • --graphiql-font
  • --graphiql-muted
  • --graphiql-panel

The editor theme is a separate CodeMirror Extension passed via the theme prop. Ships with oneDark (default) and lightTheme:

<script lang="ts">
  import { lightTheme } from "@eeeooolll/graphiql";
  import GraphiQL from "@eeeooolll/graphiql/component";
</script>

<GraphiQL {fetcher} theme={lightTheme}/>

License

MIT