# @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