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

@glubean/graphql

v0.2.4

Published

GraphQL for [Glubean](https://glubean.dev). This package owns two layers:

Readme

@glubean/graphql

GraphQL for Glubean. This package owns two layers:

  • Contract — author GraphQL API intent as a single artifact (contract.graphql.with(...)). Executable spec, agent-readable, fits contract.flow() composition. Recommended for new work.
  • Transport / test plugin — thin wrapper over ctx.http with operation-name tracing, used via configure({ plugins: { ... } }) or createGraphQLClient(...). Still supported for test-after / exploratory work.

v0.2.0 single-package release note: in earlier drafts, GraphQL contract was planned as a separate @glubean/contract-graphql package. Decision 2026-04-20: one package per protocol. The package now ships a plugin manifest; install it explicitly from glubean.setup.ts to enable contract.graphql.

Install

npm install @glubean/graphql

No native peer dependencies — the client runs over ctx.http (ky).

Install the contract plugin in your project setup:

// glubean.setup.ts
import { installPlugin } from "@glubean/sdk";
import graphqlPlugin from "@glubean/graphql";

await installPlugin(graphqlPlugin);

Quick Start — Contract

import { contract, configure } from "@glubean/sdk";
import { graphql, gql } from "@glubean/graphql";
import { z } from "zod";

const { api } = configure({
  plugins: {
    api: graphql({
      endpoint: "{{GRAPHQL_URL}}",
      headers: { Authorization: "Bearer {{API_TOKEN}}" },
    }),
  },
});

const userContracts = contract.graphql.with("user-api", {
  client: api,
});

export const getUser = userContracts("get-user", {
  endpoint: "/graphql",
  description: "Fetch a user by id",
  cases: {
    happy: {
      description: "existing user returns name + email",
      needs: z.object({ id: z.string() }),
      query: gql`
        query GetUser($id: ID!) {
          user(id: $id) { id name email }
        }
      `,
      variables: ({ id }) => ({ id }),
      expect: {
        httpStatus: 200,
        data: { user: { id: "u_123", name: "Alice" } },
        errors: "absent",
      },
    },
    unauth: {
      description: "missing token yields 401",
      query: `query Me { me { id } }`,
      headers: {},
      expect: { httpStatus: 401, errors: "any" },
    },
    forbidden: {
      description: "server returns FORBIDDEN on scope mismatch",
      query: `query AdminOnly { admin { key } }`,
      expect: {
        httpStatus: 200,
        errors: [{ extensions: { code: "FORBIDDEN" } }],
      },
    },
  },
});

Run with glubean run. Each case becomes a first-class test; failure surfaces HTTP status + GraphQL errors on the trace.

Cases in a flow

Contract cases compose into contract.flow() steps — the same artifact serves both single-case and multi-step verification, across protocols:

import { contract } from "@glubean/sdk";
import { createOrder } from "./orders.contract.ts";   // HTTP
import { completePayment } from "./payment.contract.ts"; // gRPC
import { notifyUser } from "./notify.contract.ts";    // GraphQL

export const checkoutFlow = contract
  .flow("checkout-with-notify")
  .meta({
    description: "Create order → complete payment → notify user",
    tags: ["e2e"],
  })
  // Step 1: HTTP — create order
  .step(createOrder.case("happy"), {
    out: (_s, res: any) => ({ orderId: res.body.id, userId: res.body.userId }),
  })
  // Step 2: gRPC — complete payment
  .step(completePayment.case("happy"), {
    in: (s: any) => ({ orderId: s.orderId }),
    out: (s, res: any) => ({ ...s, paymentId: res.message.paymentId }),
  })
  // Step 3: GraphQL — notify
  .step(notifyUser.case("orderComplete"), {
    in: (s: any) => ({ userId: s.userId, orderId: s.orderId }),
  });

Flow state threads through via typed in / out lenses.

What you get

  • Selection-set-per-case — each case owns its own query and response schema (expect.schema or partial data). Contract-level types declaration is optional (types: { User: { id: "ID!", ... } }, Phase 2 projection hint).
  • Case-level lifecycle — mark cases deferred (with reason) or deprecated (with replacement hint).
  • Structured failure classification (3-layer) — HTTP transport (4xx/5xx) → payload errors (with extensions.code) → error shape. Maps to transient / client / semantic / auth / server kinds; 429 / 503 / 504 marked retryable.
  • Envelope exposureGraphqlCaseResult surfaces httpStatus, headers, rawBody alongside data / errors for negative-case assertions and flow out lens inspection.
  • Projection to Markdown — case inventory with operation / operationName / query snippets, via glubean contracts.
  • Flow composition — mix with HTTP / gRPC cases, same artifact.
  • Scanner + MCP integrationglubean scan, glubean_extract_contracts MCP tool, all work unchanged for contract.graphql.with(...).

Quick Start — Transport / test plugin (low-level)

For quick tests or exploratory work that doesn't need a declared contract:

import { test, configure } from "@glubean/sdk";
import { graphql } from "@glubean/graphql";

const { gql } = configure({
  plugins: {
    gql: graphql({
      endpoint: "{{GRAPHQL_URL}}",
      headers: { Authorization: "Bearer {{API_TOKEN}}" },
    }),
  },
});

export const getUser = test("get-user", async (ctx) => {
  const { data, errors } = await gql.query<{ user: { name: string } }>(`
    query GetUser($id: ID!) { user(id: $id) { name } }
  `, { variables: { id: "u_123" } });

  ctx.expect(errors).toBeUndefined();
  ctx.expect(data?.user.name).toBe("Alice");
});

Standalone (without configure())

import { test } from "@glubean/sdk";
import { createGraphQLClient } from "@glubean/graphql";

export const quick = test("quick-gql", async (ctx) => {
  const gql = createGraphQLClient(ctx.http, {
    endpoint: "https://api.example.com/graphql",
  });
  const res = await gql.query(`{ health }`);
  ctx.assert(res.data?.health === "ok", "Service healthy");
  ctx.expect(res.httpStatus).toBe(200);
});

API Reference

Contract

contract.graphql.with(instanceName, defaults?)

Returns a scoped factory. Direct contract.graphql("id", spec) is not supported — use .with(...) first.

Instance defaults (GraphqlContractDefaults):

| Option | Type | Description | |--------|------|-------------| | client | GraphQLClient | Default client (from configure({ plugins })) | | endpoint | string | Projection-only. Travels on meta.endpoint for markdown / scanner / MCP display. Does NOT redirect the runtime call — the call goes through client, whose endpoint is fixed at construction. Multi-endpoint = multiple clients. | | tags | string[] | Tags inherited by all contracts in this instance | | feature | string | Grouping key for projection | | headers | Record<string, string> | Default headers merged into every case | | extensions | Extensions | Projection-level extensions (x-* keys) |

contract.graphql.with(...)("contractId", spec)

Creates one contract. Spec shape (GraphqlContractSpec):

| Field | Type | Description | |-------|------|-------------| | endpoint | string | Projection-only. Shown in projection meta and markdown; does not override the runtime client's endpoint. See GraphqlContractDefaults.endpoint above. | | description | string | Contract-level description | | types | GraphqlTypeDefs | Explicit type declarations (Phase 2 .gql projection hint; opaque in Phase 1) | | defaultOperation | "query" \| "mutation" | Default operation type for cases (default: "query") | | variablesSchema | SchemaLike<Vars> | Contract-level variables schema | | responseSchema | SchemaLike<Res> | Contract-level response schema (rare — per-case expect.schema is the primary home) | | defaultVariables | Partial<Vars> | Deep-merged under each case's variables | | defaultHeaders | Record<string, string> | Merged under each case's headers | | client | GraphQLClient | Override instance client | | cases | Record<string, GraphqlContractCase> | Named cases — required |

Case shape (GraphqlContractCase<Vars, Res, S>):

| Field | Type | Description | |-------|------|-------------| | description | string | Required — why this case exists | | query | string | Required — GraphQL document (inline, gql tag, or fromGql("./file.gql")) | | operation | "query" \| "mutation" | Override spec-level default (subscription is Phase 2) | | operationName | string | Display hint; defaults to parse from query | | variables | Vars \| (state) => Vars | Variables; deep-merged over defaultVariables | | headers | Record<string, string> \| fn | Per-call headers | | expect | GraphqlContractExpect<Res> | httpStatus / data / errors / schema / headers / headersMatch | | setup / teardown | (ctx, state?) => Promise<void> | Lifecycle | | verify | (ctx, GraphqlCaseResult) => Promise<void> | Business-logic check after transport + schema + data assertions | | deferred | string | Skip with reason | | deprecated | string | Deprecate with reason | | tags / severity / requires / defaultRun | — | Standard case metadata |

expect fields:

| Field | Type | Description | |-------|------|-------------| | httpStatus | number | Expected HTTP status from the POST (default: 200) | | schema | SchemaLike<Res> | Per-case response schema (selection-set-coupled); validated via ctx.validate | | data | Partial<Res> | Partial match on response data | | errors | GraphqlErrorsExpect | "absent" (default) | "any" | Array<Partial<GraphQLError>> | | headers | SchemaLike<Record<string, string \| string[]>> | Schema for response headers | | headersMatch | Record<string, string> | Partial match on response headers |

GraphqlCaseResult<Res> — shape passed to verify and flow out lens:

| Field | Type | Description | |-------|------|-------------| | data | Res \| null | Decoded data field (null if all fields errored or transport failed) | | errors | GraphQLError[] \| undefined | Payload errors array | | extensions | Record<string, unknown> | Server-side tracing/cost/etc | | httpStatus | number | HTTP status from the underlying POST | | headers | Record<string, string \| string[]> | Response headers (lowercased keys) | | rawBody | string \| null | Raw response body (null on network error) | | operationName | string | Resolved operation name | | duration | number | Call duration in ms |

Transport

graphql(options) — Plugin Factory

For use with configure({ plugins }). Supports {{template}} placeholders in endpoint and headers values, resolved from Glubean vars and secrets.

| Option | Type | Description | |--------|------|-------------| | endpoint | string | GraphQL endpoint URL, supports {{VAR}} | | headers | Record<string, string> | Default headers, supports {{VAR}} | | throwOnGraphQLErrors | boolean | Throw GraphQLResponseError when the response carries errors (default: false) |

createGraphQLClient(http, options) — Standalone

Returns a GraphQLClient bound to http (typically ctx.http).

client.query(query, options?) / client.mutate(mutation, options?)

Returns GraphQLResult<T>:

| Field | Type | Description | |-------|------|-------------| | data | T \| null | Parsed response data | | errors | GraphQLError[] \| undefined | Payload errors | | extensions | Record<string, unknown> | Server extensions | | httpStatus | number | HTTP status | | headers | Record<string, string \| string[]> | Response headers | | rawBody | string \| null | Raw body |

Options:

| Option | Type | Description | |--------|------|-------------| | variables | Record<string, unknown> | Query variables | | operationName | string | Override auto-parsed name | | headers | Record<string, string> | Extra per-request headers |

Errors don't throw by default — inspect errors / httpStatus for assertion-friendly testing. Opt into throws via throwOnGraphQLErrors: true.

gql — tagged template

Identity function; exists so IDE GraphQL extensions pick up syntax highlighting.

fromGql(path).gql file loader

Reads a GraphQL document file relative to the test file. Prefer this for full IDE support (autocomplete, schema validation) when you've got a .graphqlrc.


Custom matchers

Installing the @glubean/graphql plugin manifest from glubean.setup.ts registers GraphQL matchers onto the shared ctx.expect() surface.

// Works on GraphQLResult (transport) and GraphqlCaseResult (contract verify / flow out lens)
ctx.expect(res).toHaveHttpStatus(200);                // transport-layer
ctx.expect(res).toHaveGraphqlNoErrors();              // errors absent / empty
ctx.expect(res).toHaveGraphqlData({ user: { name: "Alice" } }); // partial data match
ctx.expect(res).toHaveGraphqlErrorCode("UNAUTHENTICATED");       // case-insensitive
ctx.expect(res).toHaveGraphqlExtension("tracing");    // extensions key present
ctx.expect(res).not.toHaveGraphqlNoErrors();          // negation

Why toHaveHttpStatus and not toHaveStatus? The GraphQL envelope (CG-10) uses httpStatus instead of status so it doesn't shadow the native Response.status semantics. The built-in toHaveStatus reads actual.status and won't find the envelope's status; use toHaveHttpStatus for GraphQL responses.

All matchers inherit .not negation, .orFail() chaining, and soft-by-default semantics from @glubean/sdk's Expectation. Types come through CustomMatchers<T> declaration merging automatically — no user-side declare module required.


Tracing

Every GraphQL call inherits HTTP-level tracing via ctx.http and injects X-Glubean-Op: <operationName> so individual operations are distinguishable in the dashboard instead of showing a generic POST /graphql.

The underlying HTTP trace event already carries status, timing, and request/response bodies. At the contract layer, classifyFailure consumes graphql_response / http_response events and maps to the repair-loop FailureKind values.

Auth

Static headers (including auth tokens) are sent with every call:

graphql({
  // ...
  headers: { Authorization: "Bearer {{API_TOKEN}}" },
});

Per-call headers override static values:

await gql.query(`{ me { id } }`, {
  headers: { Authorization: "Bearer per-call-token" },
});

At the contract layer, headers merge in this order (right wins): instance defaults.headers < contract defaultHeaders < case headers < flow-step in lens headers.


Migration: 0.1.x → 0.2.0

What's new:

  • Contract adapter shipped inside this package. Install the manifest from glubean.setup.ts to enable contract.graphql.with(...) — a bare import "@glubean/graphql" is not enough.
  • Single-package model: no separate @glubean/contract-graphql package.
  • GraphQLClient.query / .mutate return GraphQLResult<T> — additive over GraphQLResponse<T>: same data / errors / extensions, plus new httpStatus / headers / rawBody.

What's not broken:

  • Existing configure({ plugins: { x: graphql({ ... }) } }) usage is unchanged.
  • Existing createGraphQLClient(...) usage is unchanged.
  • Code that destructures { data, errors } from query/mutation calls continues to work — new fields are additive.
  • All 0.1.x transport tests still pass without modification.

Only additive API changes:

  • contract.graphql.with(...) available after calling installPlugin(graphqlManifest) in glubean.setup.ts.
  • GraphQLResult<T> is exported alongside GraphQLResponse<T> and is returned from client methods.
  • Export surface gained contract types (GraphqlContractSpec, GraphqlContractCase, etc.).

If you currently use @glubean/graphql only as a transport plugin and do not import contract.graphql anywhere, you only need to rebuild; no source changes are required.

Known Phase 1 limitations

A few edges are deliberately simple in Phase 1. Reviewed in ../internal/30-execution/2026-04-20-multi-protocol-contract/request-for-review-graphql.md.

  1. endpoint is projection-only. Shown in meta / markdown for scanner + MCP, but the adapter does not override the bound endpoint of the supplied GraphQLClient. Express multi-endpoint via multiple clients (api_v1 = graphql({endpoint: "/v1"}); api_v2 = ...).
  2. variablesSchema is contract-level only. If two cases in the same contract have materially different variables shapes (e.g. different operations), a contract-level schema will lose fidelity. Workaround: split into separate contracts, or run ad-hoc per-case variable assertions via verify. Phase 2 ergonomics work may add per-case variablesSchema override.
  3. Use throwOnGraphQLErrors: false (default) with contracts. If you construct the underlying client with throwOnGraphQLErrors: true the adapter will observe a thrown GraphQLResponseError instead of the normal envelope — this bypasses the 3-layer assertion path (expect.errors, expect.data, expect.schema no longer runs). The graphql({...}) plugin factory defaults to false; only standalone createGraphQLClient(http, { throwOnGraphQLErrors: true }) is affected.
  4. Scoped-style authoring (contract.graphql.with("api", ...)(...)) requires runtime import for scanner discovery. The static extractor regex matches only contract.graphql("id", {...}) (the direct form, which the runtime rejects). Scoped authoring therefore depends on glubean scan's runtime-import fallback — same behavior as gRPC, same caveat as @glubean/grpc v0.2.0.

Scope

Phase 1 (shipped)

  • Query + mutation contracts (selection-set-per-case)
  • 3-layer failure classification (transport / payload / error shape)
  • Per-case schema validation (selection-set coupled)
  • Headers + variables merge through contract → instance → case → flow-step
  • Envelope exposure (httpStatus / headers / rawBody)
  • Markdown projection (case list + operation + query snippets + lifecycle markers)
  • Cross-protocol flow composition (HTTP + gRPC + GraphQL verified end-to-end)

Phase 2 (planned)

  • Subscription support sharing the same streaming case design as gRPC streaming
  • .gql / SDL projection from types declaration (see proposal §7b — solvable, sequencing deferral)
  • See internal/40-discovery/proposals/contract-async-protocol-plugins.md

Out of scope (Phase 3+)

  • Federated gateways / schema stitching
  • Apollo Studio / Hasura registry integration
  • Persistent queries
  • Automatic .gql SDL generation as the only source of truth — see proposal §7b.4 for long-term framing

License

MIT