aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-24 16:37:49 -0700
committernetop://ウィビ <paul@webb.page>2026-04-24 16:37:49 -0700
commitc013ed57bf3d7dd83bd59a9b984d87aebde6003c (patch)
tree31d25456496f013f13f3cae1ded376d5323b3200 /README.md
parent510fd8cbe53abb39cba2c7cbaaefcf2783dc0066 (diff)
downloadgraphiql-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.md280
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