# @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`](https://www.npmjs.com/package/@eeeooolll/graphiql).
## Install
```sh
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
```svelte
```
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` (HTTP) or an `AsyncIterable` (SSE/WS). That's the full seam. Any GraphQL server that speaks HTTP JSON works out of the box via `createHttpFetcher`.
### GraphQL Yoga
```ts
// server.ts
import { createYoga, createSchema } from "graphql-yoga";
export const yoga = createYoga({
schema: createSchema({
resolvers: { Query: { hello: () => "world" } },
typeDefs: /* GraphQL */ `type Query { hello: String }`
})
});
```
```svelte
```
Yoga's `/graphql` endpoint speaks standard JSON; no adapter needed.
### Apollo Server
```ts
// 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 } });
```
```ts
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.
```ts
// 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] });
```
```ts
// 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()
});
```
```svelte
```
### Hono / Bun / Deno
```ts
// 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.
```ts
const fetcher = createHttpFetcher({
headers: { authorization: `Bearer ${token}` },
url: "/graphql"
});
```
### Subscriptions
**SSE** (`graphql-sse` protocol):
```ts
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):
```ts
import { createWsFetcher } from "@eeeooolll/graphiql";
const ws = createWsFetcher({ url: "ws://localhost:4000/graphql" });
```
Either transport returns an `AsyncIterable`; 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:
```ts
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.
```ts
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
```ts
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`:
```svelte
```
## License
MIT