diff options
| author | netop://ウィビ <paul@webb.page> | 2026-04-24 16:37:49 -0700 |
|---|---|---|
| committer | netop://ウィビ <paul@webb.page> | 2026-04-24 16:37:49 -0700 |
| commit | c013ed57bf3d7dd83bd59a9b984d87aebde6003c (patch) | |
| tree | 31d25456496f013f13f3cae1ded376d5323b3200 /README.md | |
| parent | 510fd8cbe53abb39cba2c7cbaaefcf2783dc0066 (diff) | |
| download | graphiql-c013ed57bf3d7dd83bd59a9b984d87aebde6003c.tar.gz graphiql-c013ed57bf3d7dd83bd59a9b984d87aebde6003c.zip | |
Migrate from Deno/JSR to npm publishing
- @sveltejs/package builds dist/ for @eeeooolll/graphiql with three entry
points (./, ./component, ./splitter)
- Vitest + svelte-check replace Deno test/check; runes shim no longer
needed since the Svelte plugin compiles .svelte.ts at runtime
- Drop $app/environment dep in Editor.svelte to support non-SvelteKit
consumers
- Refactor TabBar tab element from nested <button> to role=tab <div> per
PLAN.md gotcha; svelte-check flagged the invalid HTML
- README now documents npm install, integration patterns for Yoga,
Apollo, graphql-modules, Hono/Bun/Deno, plus APQ + keyboard table
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8d9132 --- /dev/null +++ b/README.md @@ -0,0 +1,280 @@ +# @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 +<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 + +```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 +<!-- +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 + +```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 +<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 + +```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<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: + +```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 +<script lang="ts"> + import { lightTheme } from "@eeeooolll/graphiql"; + import GraphiQL from "@eeeooolll/graphiql/component"; +</script> + +<GraphiQL {fetcher} theme={lightTheme}/> +``` + +## License + +MIT |