@eeeooolll/graphiql
v0.4.2
Published
A Svelte 5 GraphiQL alternative.
Downloads
684
Readme
@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/graphiqlRequires 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— theGraphiQLSvelte component (default export)@eeeooolll/graphiql/splitter— theSplitterSvelte 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) | lightTheme |
| 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
headerstocreateHttpFetcher. 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
The editor theme is a separate CodeMirror Extension passed via the theme prop. Ships with lightTheme (default):
<script lang="ts">
import { lightTheme } from "@eeeooolll/graphiql";
import GraphiQL from "@eeeooolll/graphiql/component";
</script>
<GraphiQL {fetcher} theme={lightTheme}/>License
MIT
