npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@graphql-workbench/execute-query

v0.2.0

Published

A web component for GraphQL variables and consumer-owned query execution

Downloads

34

Readme

Execute Query

<gwb-execute-query> is a Lit web component with a variables pane above a response pane. The consumer owns the query editor and handles execution through a DOM event.

The component accepts response chunks through an observer-compatible sink or through an AsyncIterable/ReadableStream property. It can display decoded chunks in arrival order or assemble direct-path incremental GraphQL chunks into one response. Both views record arrival timing and bound retained raw chunk history. Variables and responses use Prism Code Editor's JSON grammar for syntax highlighting and line numbers. The variables editor also suggests names declared in the consumer's current query.

Install

npm install @graphql-workbench/execute-query @preact/signals-core

@preact/signals-core is a peer dependency. Lit and Prism Code Editor are installed as normal runtime dependencies.

Development

Use Node 22.13+ (or Node 24).

npm ci
npm run dev --workspace=@graphql-workbench/execute-query

Run these commands from the monorepo root.

Open the URL printed by Vite to try the consumer demo. Its query editor and simulated Observable live outside the component. The event's request-bound sink receives four out-of-order direct-path chunks; Cancel request demonstrates consumer unsubscription.

npx playwright install chromium firefox webkit
npm run check --workspace=@graphql-workbench/execute-query

check runs lint, formatting, type checking, unit/component tests, three library builds, declaration and packed-consumer checks, bundle budgets, and browser tests. Use npm run check:node for the non-browser gate or npm run test:e2e for browser coverage. CI configuration and manual release checks are still to be added.

Module usage

The package entry registers the element and exports ExecuteQuery and its public types. The snippets below describe local/built package consumption; no package has been published.

<gwb-execute-query id="panel"></gwb-execute-query>
import '@graphql-workbench/execute-query';

const panel = document.querySelector('gwb-execute-query')!;
panel.variablesText = '{ "id": "42" }';
panel.query = queryEditor.getValue();
panel.maxChunks = 500;
panel.responseMode = 'merged';

queryEditor.onDidChangeModelContent(() => {
  panel.query = queryEditor.getValue();
});

panel.addEventListener('gwb-execute-query', ({ detail }) => {
  const { variables, signal, sink } = detail;
  const subscription = graphqlClient
    .execute({
      query: queryEditor.getValue(),
      variables,
    })
    .subscribe(sink);

  const unsubscribe = () => subscription.unsubscribe();
  if (signal.aborted) unsubscribe();
  else signal.addEventListener('abort', unsubscribe, { once: true });
});

sink follows the Observable next/error/complete shape. Each method returns true when the current request accepted the notification and false after it became stale or terminal. The sink is request-bound, so consumers do not carry a request ID through every callback.

For browser or generator streams, assign the correlated response property:

panel.addEventListener('gwb-execute-query', async ({ detail }) => {
  try {
    const stream = await runQueryAsStream({
      query: queryEditor.getValue(),
      variables: detail.variables,
      signal: detail.signal,
    });
    panel.response = { requestId: detail.requestId, stream };
  } catch (error) {
    panel.response = {
      requestId: detail.requestId,
      error: { message: error instanceof Error ? error.message : String(error) },
    };
  }
});

Signal store

createExecuteQueryStore() exposes the execution state machine without requiring a component. Consumer-controlled inputs are writable Preact signals; lifecycle and output state are readonly signals. Actions preserve validation, request correlation, cancellation, source replacement, response retention, merging, and disposal invariants. Use @graphql-workbench/execute-query/store when only the DOM-free state machine is needed in Node or a coordinator; the root entry additionally registers web components.

import { createExecuteQueryStore, type ExecuteQuery } from '@graphql-workbench/execute-query';

const store = createExecuteQueryStore();
store.query.value = queryEditor.getValue();
store.variablesText.value = '{ "id": "42" }';
store.responseMode.value = 'merged';

store.events.subscribe('execute-requested', ({ query, variables, signal, sink }) => {
  const subscription = graphqlClient.execute({ query, variables }).subscribe(sink);
  const unsubscribe = () => subscription.unsubscribe();
  if (signal.aborted) unsubscribe();
  else signal.addEventListener('abort', unsubscribe, { once: true });
});

store.execute();

The store's execute-requested event includes immutable query and variables snapshots. variables-change is emitted only by editVariables(); assigning store.variablesText.value directly is programmatic and does not echo. An explicit cancel() emits execution-cancelled, aborts the request signal, and rejects stale delivery. Use acceptResponse() for a correlated AsyncIterable, ReadableStream, or pre-stream error. Without a subscriber or response, the store remains in waiting and does not invent a timeout. If an execute-requested listener throws, execute() rethrows that error, ends the request in error, aborts its signal, and permits another execution. Call dispose() when the owner is finished.

Assign the store to one or more components to share the same execution state:

const first = document.querySelector<ExecuteQuery>('#first')!;
const second = document.querySelector<ExecuteQuery>('#second')!;
first.store = store;
second.store = store;

An injected store is authoritative and remains owned by the consumer: disconnecting or rebinding a view does not cancel or dispose it. With no injected store, <gwb-execute-query> creates its own store and retains the standalone DOM-event contract. A component-originated execution publishes execute-requested first and then emits gwb-execute-query once from the originating component. Connect a transport to either the typed store event or the compatibility DOM event, not both, to avoid executing the request twice. Direct store actions update bound views without synthesizing DOM events.

Use store.snapshot() for persistence and store.restore(value) to restore durable execution preferences. Snapshots are versioned, detached, deeply frozen, and JSON-serializable. They retain query and variables editor text—including invalid variables text—plus response mode, theme, disabled preference, and chunk limit. Request IDs, sinks, streams, output, errors, timing, and active status are excluded. Restoring an active store cancels its source and aborts its signal, then resets execution to idle without emitting a cancellation event. Use parseExecuteQueryStoreSnapshot(value) to prevalidate a slice before mutating it.

Direct pane composition

Consumers that own the execution state machine can instantiate the variables and response panes separately. Importing the package registers both custom elements and exports their classes, parsing helpers, and display model types.

The panes can share the narrow slices of an ExecuteQueryStore directly:

variables.store = store;
response.store = store;

In this mode, the variables pane applies user edits and starts execution through the store, while the response pane renders its authoritative lifecycle and response state. The root component keeps its internal panes controlled rather than injecting the store into them, preventing duplicate execution. In a composed workbench, @graphql-workbench/graphql-workbench-store owns this execution slice and the schema slice. Pass workbench.execution to the root component or directly to both panes, but do not bind the same panes independently inside <gwb-execute-query>. Keep the query editor and transport consumer-owned. A schema selection does not modify query text. Dispose the workbench only after all borrowing views are finished.

<gwb-variables-pane id="variables"></gwb-variables-pane>
<gwb-response-pane id="response"></gwb-response-pane>
import {
  extractVariableNames,
  parseVariables,
  type ResponseChunk,
  type ResponsePane,
  type VariablesPane,
} from '@graphql-workbench/execute-query';

const variables = document.querySelector<VariablesPane>('#variables')!;
const response = document.querySelector<ResponsePane>('#response')!;

variables.variableNames = extractVariableNames('query Viewer($id: ID!) { viewer(id: $id) { id } }');
variables.addEventListener('gwb-variables-input', ({ detail }) => {
  variables.value = detail.value;
  variables.error = '';
});
variables.addEventListener('gwb-execute', () => {
  const value = parseVariables(variables.value);
  response.status = 'waiting';
  // Execute with value, then assign immutable ResponseChunk records and terminal state.
});

VariablesPane exposes store, value, variableNames, error, disabled, and focusEditor(). It emits bubbling, composed gwb-variables-input and gwb-execute events. ResponsePane renders consumer-owned status, mode, chunks, truncation, error, timing, and merged-response properties, or accepts a store that is authoritative over those presentation properties. Without a pane store, direct consumers own request IDs, cancellation, timing, response serialization, and immutable state updates; use the root <gwb-execute-query> element when those behaviors should be supplied by the package.

Variables must be a JSON object; blank input means {}. Invalid input focuses the editor and shows an inline error without emitting an execution event. Each execution supplies a new request ID and a deeply frozen variables snapshot. User edits emit gwb-variables-change with { value }; programmatic assignments do not emit that event. Both public events bubble and cross shadow boundaries.

Set query whenever the consumer-owned query editor changes. The component scans its operation variable declarations and suggests them for top-level variables object keys. Typing filters the list; Ctrl+Space opens it explicitly, arrow keys move through it, Enter or Tab accepts a name, and Escape closes it. For documents containing multiple operations, suggestions include the unique declarations from all operations. The query is metadata for suggestions only and is not added to gwb-execute-query event details.

The button disables while waiting or streaming, or when panel.disabled is true. Variables remain editable for the next request. Setting disabled does not abort an active request. panel.cancel() aborts the event signal and cancels an accepted stream. Observable consumers connect that signal to unsubscribe() as shown above. Removing a standalone component cancels its private active request; removing a view backed by an injected store leaves the shared request running. Cmd/Ctrl+Enter in the variables editor follows the same execution rules as a click. The component does not execute requests, retry, or set a timeout on the consumer's behalf.

Public API

| Member | Type/default | | ----------------- | ---------------------------------------------------------------------------- | | variablesText | Property only; string, default '{}' | | query | Property only; string, default ''; supplies variable-name suggestions | | response | Property only; correlated ResponseInput, default null | | responseMode | Reflected response-mode; 'chunks' \| 'merged', default 'chunks' | | theme | Reflected attribute; 'auto' \| 'light' \| 'dark', default 'auto' | | disabled | Boolean property/attribute; default false | | maxChunks | Number property/max-chunks attribute; default 500, minimum effective 1 | | status | Readonly idle, waiting, streaming, complete, error, or cancelled | | executionTimeMs | Readonly total duration in milliseconds; null until a run ends | | store | Property only; optional consumer-owned ExecuteQueryStore, default null | | cancel() | Cancels a waiting/streaming execution and preserves chunks already received |

Public types also include ResponseSink, ResponseInput, ResponseStream, ResponseMode, ResponseChunk, ExecuteQueryStore, VariablesPaneStore, ResponsePaneStore, ExecuteQueryStoreEventMap, ExecuteRequestedDetail, ExecutionCancelledDetail, VariablesPaneInputDetail, and the pane input types. document.createElement('gwb-execute-query') infers ExecuteQuery, and event details are typed through HTMLElementEventMap.

Each accepted chunk is serialized immediately as a display snapshot. GraphQL payloads containing both data and errors remain ordinary chunks. A source failure preserves earlier chunks and displays an error. Stream exhaustion completes the response, even with zero chunks; fields such as hasNext do not control consumption. Assigning a new source for the active request replaces the previous source and clears its output. Stale request IDs, old sinks, and delayed values from replaced sources are ignored.

The component retains the newest maxChunks blocks and displays how many earlier chunks were omitted while keeping their original sequence numbers. Numbering starts at 1 for each execution and restarts when its response source is replaced. Invalid, non-finite, or missing limits use 500; finite values are floored and clamped to at least 1. Changing the limit trims displayed output immediately, but cannot restore chunks that were already omitted. Same-turn emissions are batched into one reactive update.

Each chunk shows its UTC arrival time and milliseconds elapsed since execution began. The component starts its monotonic timer immediately before dispatching gwb-execute-query and stops it on completion, failure, explicit cancellation, or disconnection. Stream replacement keeps the original execution start. executionTimeMs exposes the rounded total duration after a terminal state and resets to null when the next valid execution begins. Wall-clock changes can affect the displayed UTC timestamp but cannot make elapsed or total durations negative.

Inputs must contain decoded, JSON-serializable values. Response inputs use unknown so typed GraphQL result interfaces can be passed directly to Observable and stream APIs; values are serialized when received. Consumers remain responsible for HTTP, SSE, or multipart framing.

Merged response mode

Set responseMode = 'merged' or response-mode="merged" before execution to assemble one response. The value is captured when execution starts, so changing it during an active request affects the next request. The default chunks mode retains the original one-block-per-chunk behavior.

Merged mode supports decoded direct-path chunks of this form:

sink.next({ data: { post: { title: 'Post', comments: [] } } }); // primary: no path
sink.next({ data: { id: 'c2' }, path: ['post', 'comments', 1] });
sink.next({ data: { id: 'c1' }, path: ['post', 'comments', 0] });
sink.next({
  data: null,
  path: ['post', 'comments', 2],
  errors: [{ message: 'Comment failed', path: ['post', 'comments', 2] }],
});
sink.complete();

Paths are relative to the primary chunk's data. String segments select object fields; non-negative integers select array positions. Object payloads merge recursively over existing object data, while arrays, scalars, and null replace the selected value. Patch errors are appended to the assembled top-level errors array.

The merged response gutter marks when each field or array item arrived. GraphQL errors with a valid path receive an error marker on that field; errors without a path are marked on the top-level errors entry. Focus or hover a marker for its chunk number, arrival time, elapsed time, or error message. The markers remain available when maxChunks trims raw chunk records.

Chunks may arrive before the primary chunk or before a required parent or earlier array item. The component buffers them and retries after each arrival. It never publishes a sparse array: index 2 remains buffered until indices 0 and 1 are present. An explicit data: null, including one paired with GraphQL errors, resolves its position. Completion without a primary chunk or with unresolved predecessors ends in error and preserves the assembled partial response.

This mode supports the direct data + path shape above. It does not decode transports or implement the ID-based pending/incremental/completed defer/stream envelope; adapt that protocol to direct-path chunks in consumer code.

Themes and layout

gwb-execute-query {
  --gwb-eq-font-size: 14px;
  --gwb-eq-variables-height: 160px;
  --gwb-eq-action-bg: #0058d0;
}

theme="auto" follows the system. Set light or dark explicitly to override it. --gwb-eq-height defaults to auto; an explicit height bounds the flex layout. --gwb-eq-variables-height defaults to 144px, with an 80px minimum and vertical resizing. Use enough component height to accommodate the variables pane and response header. The response pane uses the remaining height when bounded. Typography defaults to 13px and a JetBrains Mono/system monospace stack; the component downloads no fonts.

Color tokens: --gwb-eq-bg, --gwb-eq-header-bg, --gwb-eq-text-primary, --gwb-eq-text-secondary, --gwb-eq-border, --gwb-eq-action-bg, --gwb-eq-action-text, --gwb-eq-focus-ring, --gwb-eq-error-text. Public parts: variables-pane, variables-editor, execute-button, response-pane, response-content, status, error, truncation, duration, chunk-timing, merged-response, merge-pending, code-view, annotation-marker, and annotation-tooltip.

Standalone browser bundle

<script src="dist/execute-query.umd.js"></script>
<gwb-execute-query></gwb-execute-query>

UMD bundles Lit, Preact Signals Core, the shared event channel, and Prism Code Editor. It exposes the component as ExecuteQuery.ExecuteQuery and the store factory as ExecuteQuery.createExecuteQueryStore. ES and CommonJS externalize Lit, Signals Core, and @graphql-workbench/graphql-store-core while bundling the JSON response renderer. CommonJS is intended for browser-capable, ESM-aware bundlers; the root entry registers web components and is not a DOM-free server entry. npm run build also emits TypeScript declarations and source maps.

See AGENTS.md for project guidance and DESIGN.md for the full streaming contract and remaining design choices.