|
| 1 | +# Hive Laboratory |
| 2 | + |
| 3 | +[Hive](https://the-guild.dev/graphql/hive) is a fully open-source schema registry, analytics, |
| 4 | +metrics and gateway for [GraphQL federation](https://the-guild.dev/graphql/hive/federation) and |
| 5 | +other GraphQL APIs. |
| 6 | + |
| 7 | +`@graphql-hive/laboratory` is Hive's embeddable, in-browser GraphQL IDE (the "Lab"): an editor and |
| 8 | +runner for GraphQL operations, in the spirit of GraphiQL. It powers the Laboratory page inside the |
| 9 | +Hive Console and can be embedded into any page that talks to a GraphQL endpoint. |
| 10 | + |
| 11 | +> **Pre-1.0.** The public API (see [`LaboratoryApi`](src/components/laboratory/context.tsx)) is |
| 12 | +> still evolving and can change between patch releases. |
| 13 | +
|
| 14 | +## Features |
| 15 | + |
| 16 | +- Query builder: click schema fields/arguments to build the operation |
| 17 | +- Schema explorer with search (list and tree modes) |
| 18 | +- Collections (saved operations) and request history |
| 19 | +- Preflight scripts: run JavaScript before a request in a sandboxed Web Worker |
| 20 | +- Environment variables with `{{variable}}` interpolation |
| 21 | +- Renders a federation query plan when a server includes one in the response `extensions` |
| 22 | + (`extensions.queryPlan`), e.g. [Hive Router](https://the-guild.dev/graphql/hive/docs/router) or |
| 23 | + Hive Gateway |
| 24 | +- Plugin system for adding tabs, command-palette entries and preflight APIs |
| 25 | + |
| 26 | +## Consumers |
| 27 | + |
| 28 | +This package is storage- and transport-agnostic. It exposes state as props and reports changes via |
| 29 | +callbacks; the host decides where data lives. |
| 30 | + |
| 31 | +- **Hive Console** (`packages/web/app`) embeds the `<Laboratory>` React component directly in |
| 32 | + [`target-laboratory-new.tsx`](../../web/app/src/pages/target-laboratory-new.tsx) and wires the |
| 33 | + callbacks to the Hive GraphQL API and `localStorage`. |
| 34 | +- **Hive Gateway** serves the Lab as its GraphiQL replacement via |
| 35 | + **`@graphql-hive/render-laboratory`** ([`../render-laboratory`](../render-laboratory)), which |
| 36 | + wraps this package's UMD bundle plus the Monaco workers into a self-contained HTML page for |
| 37 | + `graphql-yoga` servers. |
| 38 | +- **Hive Router** embeds this package's UMD bundle (`dist/hive-laboratory.umd.js`) directly at build |
| 39 | + time, generating a static page that calls the `HiveLaboratory.renderLaboratory()` global. |
| 40 | + |
| 41 | +## Installation |
| 42 | + |
| 43 | +```bash |
| 44 | +pnpm add @graphql-hive/laboratory |
| 45 | +``` |
| 46 | + |
| 47 | +## Usage |
| 48 | + |
| 49 | +Two entry points are exported from [`src/index.tsx`](src/index.tsx): |
| 50 | + |
| 51 | +**`renderLaboratory(el, props)`** mounts the Lab into a DOM node and wires all state to |
| 52 | +`localStorage` for you. This is the quickest way to embed it: |
| 53 | + |
| 54 | +```ts |
| 55 | +import { renderLaboratory } from '@graphql-hive/laboratory' |
| 56 | + |
| 57 | +renderLaboratory(document.getElementById('root')!, { |
| 58 | + defaultEndpoint: 'https://example.com/graphql' |
| 59 | +}) |
| 60 | +``` |
| 61 | + |
| 62 | +**`<Laboratory />`** is the React component when you want to own persistence. Every piece of state |
| 63 | +follows the same controlled/uncontrolled contract: a `defaultX` prop seeds the initial value and an |
| 64 | +`onXChange` callback fires whenever it changes. |
| 65 | + |
| 66 | +```tsx |
| 67 | +import { Laboratory } from '@graphql-hive/laboratory' |
| 68 | + |
| 69 | +function LabPage() { |
| 70 | + return ( |
| 71 | + <Laboratory |
| 72 | + theme="dark" |
| 73 | + defaultEndpoint={endpoint} |
| 74 | + onEndpointChange={setEndpoint} |
| 75 | + defaultCollections={collections} |
| 76 | + onCollectionsChange={saveCollections} |
| 77 | + defaultHistory={history} |
| 78 | + onHistoryChange={saveHistory} |
| 79 | + // ...tabs, operations, env, preflight, settings, plugins |
| 80 | + /> |
| 81 | + ) |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +The full prop surface (endpoint, collections, operations, history, tabs, env, preflight, settings, |
| 86 | +tests, plugins, plus granular `onXCreate`/`onXUpdate`/`onXDelete` callbacks and a `permissions` |
| 87 | +object) is defined by [`LaboratoryApi`](src/components/laboratory/context.tsx). Treat that interface |
| 88 | +as the source of truth rather than this README. |
| 89 | + |
| 90 | +### Permissions |
| 91 | + |
| 92 | +Pass a `permissions` object to gate actions per resource (`preflight`, `collections`, |
| 93 | +`collectionsOperations`) with `read`/`create`/`update`/`delete` flags. Gating is applied in the UI |
| 94 | +(controls are hidden/disabled) with a backstop in the operations logic; anything unspecified |
| 95 | +defaults to allowed. |
| 96 | + |
| 97 | +### Styling and rendering |
| 98 | + |
| 99 | +The Lab bundles its own styles and injects them into its shadow root, so there is no CSS file to |
| 100 | +import. It is client-side only (it uses Web Workers, Shadow DOM and Monaco), so mount it in the |
| 101 | +browser rather than during server-side rendering. |
| 102 | + |
| 103 | +## Local development |
| 104 | + |
| 105 | +```bash |
| 106 | +pnpm dev # Vite dev server on http://localhost:5173 |
| 107 | +pnpm build # library build (ES + CJS) and UMD build |
| 108 | +pnpm lint # eslint |
| 109 | +``` |
| 110 | + |
| 111 | +`pnpm dev` mounts the Lab via [`src/main.tsx`](src/main.tsx) / [`index.html`](index.html), a thin |
| 112 | +harness that persists all state to `localStorage`. No backend is required; set an endpoint in the UI |
| 113 | +and go. |
| 114 | + |
| 115 | +Tests run from the monorepo root (this package has no `test` script): |
| 116 | + |
| 117 | +```bash |
| 118 | +# from the repo root |
| 119 | +pnpm vitest run packages/libraries/laboratory |
| 120 | +``` |
| 121 | + |
| 122 | +## Architecture |
| 123 | + |
| 124 | +Each feature is a `useX` hook (state + actions) under [`src/lib`](src/lib), paired with a component |
| 125 | +under [`src/components/laboratory`](src/components/laboratory). All the hooks are composed into one |
| 126 | +context in [`laboratory.tsx`](src/components/laboratory/laboratory.tsx) and consumed via |
| 127 | +`useLaboratory()`. |
| 128 | + |
| 129 | +| Feature | Hook (`src/lib`) | UI (`src/components/laboratory`) | |
| 130 | +| ------------------------------- | -------------------------------------- | --------------------------------- | |
| 131 | +| Endpoint + schema introspection | `endpoint.ts` | (implicit) | |
| 132 | +| Operations + run/abort | `operations.ts`, `operations.utils.ts` | `operation.tsx`, `builder.tsx` | |
| 133 | +| Collections | `collections.ts` | `collections.tsx` | |
| 134 | +| History | `history.ts` | `history.tsx`, `history-item.tsx` | |
| 135 | +| Preflight scripts | `preflight.ts` | `preflight.tsx` | |
| 136 | +| Environment variables | `env.ts` | `env.tsx` | |
| 137 | +| Settings | `settings.ts` | `settings.tsx` | |
| 138 | +| Tabs | `tabs.ts` | `tabs.tsx` | |
| 139 | +| Query plan | `query-plan/` | `flow.tsx` | |
| 140 | + |
| 141 | +### Style isolation (Shadow DOM) |
| 142 | + |
| 143 | +Because the Lab is embedded into pages it does not control, it renders inside a Shadow DOM |
| 144 | +(`ShadowRootContainer` in [`laboratory.tsx`](src/components/laboratory/laboratory.tsx)) and injects |
| 145 | +its CSS (Tailwind v4) plus Monaco's CSS inline. This gives two-way isolation from the host page's |
| 146 | +styles. A consequence worth knowing: Radix portals (dropdowns, tooltips, dialogs) must target the |
| 147 | +Lab's `container` (exposed on the context) rather than `document.body`, or they render unstyled. |
| 148 | + |
| 149 | +The UI is built on shadcn/Radix primitives ([`src/components/ui`](src/components/ui)), Monaco, and |
| 150 | +`@tanstack/react-form`. |
| 151 | + |
| 152 | +## Plugins |
| 153 | + |
| 154 | +A plugin can add tabs, command-palette commands, and objects injected into the preflight sandbox. |
| 155 | +See [`LaboratoryPlugin`](src/lib/plugins.ts) for the shape and |
| 156 | +[`src/plugins/target-env.tsx`](src/plugins/target-env.tsx) for a worked example (the Target |
| 157 | +Environment plugin used by Hive Console). Register plugins via the `plugins` prop. |
| 158 | + |
| 159 | +## Releases |
| 160 | + |
| 161 | +This package is published to npm via [Changesets](https://github.com/changesets/changesets) from the |
| 162 | +monorepo: |
| 163 | + |
| 164 | +1. Add a changeset from the repo root: `pnpm changeset` (pick `@graphql-hive/laboratory`, choose the |
| 165 | + bump, write the summary that becomes the changelog entry). |
| 166 | +2. Opening a PR publishes an `alpha` snapshot to npm for testing. |
| 167 | +3. Merging to `main` accumulates changes into an "Upcoming Release Changes" PR. |
| 168 | +4. Merging that PR versions the package and publishes it to npm. |
| 169 | + |
| 170 | +See [`CHANGELOG.md`](CHANGELOG.md) for release history. |
| 171 | + |
| 172 | +## License |
| 173 | + |
| 174 | +Licensed under the [MIT License](LICENSE). |
0 commit comments