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

@campoint/graphql-contracts

v0.3.0

Published

Canonical shared GraphQL SDL contracts (scalars, etc.) to be consistent across services.

Readme

@campoint/graphql-contracts

Native npm distribution of the canonical GraphQL SDL contracts from contracts/ in the con-graphql repo.

Usage

const { scalars, objects, interfaces } = require("@campoint/graphql-contracts");
const fs = require("fs");

const dateTimeSdl = fs.readFileSync(scalars.DateTime, "utf8");
const pageInfoSdl = fs.readFileSync(objects.PageInfo, "utf8");
const nodeSdl = fs.readFileSync(interfaces.Node, "utf8");

Source of truth

This package is a thin wrapper — it does not maintain its own copy of the SDL. scripts/copy-contracts.js pulls ../../contracts into this package's contracts/ directory on every npm pack/npm publish (via the prepack script), so this package is always in lockstep with the canonical contract files at the tagged commit.

Versioning

Published from CI whenever a vX.Y.Z git tag is pushed to the repo; the package version always matches the tag (minus the v prefix).

Verify your integration

This package only ships the contract — it does not check that your service's scalar matches it. That check belongs in your own service's CI, using your own test framework. Here's a Jest example for a service using graphql-scalars:

// datetime.contract.test.ts
import { readFileSync } from "fs";
import { scalars } from "@campoint/graphql-contracts";
import { DateTimeResolver } from "graphql-scalars";

function readContract() {
  const sdl = readFileSync(scalars.DateTime, "utf8");
  const nameMatch = sdl.match(/scalar\s+(\w+)/);
  const specifiedByMatch = sdl.match(/@specifiedBy\(url:\s*"([^"]+)"\)/);
  if (!nameMatch || !specifiedByMatch) {
    throw new Error("Could not parse the shared DateTime contract");
  }
  return { name: nameMatch[1], specifiedByUrl: specifiedByMatch[1] };
}

describe("DateTime scalar contract", () => {
  const contract = readContract();

  it("this service's scalar name matches the shared contract", () => {
    expect(contract.name).toBe("DateTime");
  });

  it("this service's specifiedBy URL matches the shared contract", () => {
    expect(DateTimeResolver.specifiedByURL).toBe(contract.specifiedByUrl);
  });
});

Steps to adopt this in your service:

  1. npm install --save-dev @campoint/graphql-contracts
  2. Add a test like the one above (adjust the assertions to match whichever scalar library your service actually uses).
  3. Run it as part of your own service's CI pipeline — not this repo's.
  4. Re-run it whenever you bump @campoint/graphql-contracts to catch drift early.

Object types (PageInfo / OffsetPageInfo / SignedUrl / SignedUrlVariant)

Object contracts are exposed under objects and checked against the field set of your schema rather than a scalar's metadata. graphql's own parser compares names, types and nullability, order-insensitively:

// pageinfo.contract.test.ts
import { readFileSync } from "fs";
import { parse, print, printSchema, Kind } from "graphql";
import { objects } from "@campoint/graphql-contracts";
import { mySchema } from "../src/schema";

function fieldsOf(sdl: string, typeName: string): string[] {
  const def = parse(sdl).definitions.find(
    (d) => d.kind === Kind.OBJECT_TYPE_DEFINITION && d.name.value === typeName,
  );
  if (!def || !("fields" in def) || !def.fields) {
    throw new Error(`${typeName} not found`);
  }
  return def.fields.map((f) => `${f.name.value}: ${print(f.type)}`).sort();
}

it("this service's PageInfo matches the shared contract", () => {
  expect(fieldsOf(printSchema(mySchema), "PageInfo")).toEqual(
    fieldsOf(readFileSync(objects.PageInfo, "utf8"), "PageInfo"),
  );
});

The same test works for OffsetPageInfo, SignedUrl and SignedUrlVariant — swap the type name and objects.PageInfo for the matching objects.* entry. This checks the shape only; for the pagination pair it does not check that your service adopted the right one of the two. PageInfo and OffsetPageInfo are alternatives, not to be combined on the same list — see docs/objects/PageInfo.md and docs/objects/OffsetPageInfo.md.

Because the comparison prints field types, it also covers the scalars these types depend on — a service that left startCursor as String instead of Cursor, totalCount as Int! instead of Long!, or expiresAt as String! instead of DateTime!, fails here, with no separate scalar test needed.

SignedUrl carries three further clauses that are behavioural — the expiry must be the one your CDN actually enforces, no no-expiry link may be served, and entitlement must be re-checked at each mint — which no SDL comparison can see. See docs/objects/SignedUrl.md.

SignedUrlVariant adds two clauses shape cannot reach either: a variant with no expiry is dropped from the set rather than emitted unexpiring, and the type is not independently adoptable — a service exposing it must match SignedUrl too, so assert both or neither. See docs/objects/SignedUrlVariant.md.

Interfaces (Node)

Node is exposed under interfaces. Because the Global Object Identification spec says "exactly one field", name plus shape really is identity here — so the field-set comparison above is the whole structural check, run against Kind.INTERFACE_TYPE_DEFINITION instead:

// node.contract.test.ts
import { readFileSync } from "fs";
import { parse, print, printSchema, Kind } from "graphql";
import { interfaces } from "@campoint/graphql-contracts";
import { mySchema } from "../src/schema";

function interfaceFieldsOf(sdl: string, typeName: string): string[] {
  const def = parse(sdl).definitions.find(
    (d) => d.kind === Kind.INTERFACE_TYPE_DEFINITION && d.name.value === typeName,
  );
  if (!def || !("fields" in def) || !def.fields) {
    throw new Error(`${typeName} not found`);
  }
  return def.fields.map((f) => `${f.name.value}: ${print(f.type)}`).sort();
}

it("this service's Node matches the shared contract", () => {
  expect(interfaceFieldsOf(printSchema(mySchema), "Node")).toEqual(
    interfaceFieldsOf(readFileSync(interfaces.Node, "utf8"), "Node"),
  );
});

it("this service's Node carries the contract marker", () => {
  const type = mySchema.getType("Node");
  expect(type?.description).toContain(
    "https://bitbucket.org/campoint/con-graphql/src/main/docs/interfaces/Node.md",
  );
});

it("this service exposes the root node lookup", () => {
  const node = mySchema.getQueryType()?.getFields().node;
  expect(node && print(node.astNode!.type)).toBe("Node");
  expect(node?.args.map((a) => `${a.name}: ${print(a.astNode!.type)}`)).toEqual(["id: ID!"]);
});

Three things the structural test cannot see, and one it deliberately checks loosely:

  • @specifiedBy is unusable here — it is SCALAR-only, and applied directives do not survive introspection at all. The Contract: line in the type description is the adoption marker instead, which is why the second test above matches it by substring rather than by equality: the URL moves if the spec-page question in docs/candidates.md is settled differently.
  • Adopting Node makes id mean Relay identity across your whole surface, input objects and ID-list filters included. A Relay id arriving where a domain id is expected does not error — it matches nothing.
  • Global uniqueness, lifetime stability, and authorization parity between node(id:) and the equivalent domain lookup need a runtime probe, not an SDL comparison.

The full rules, including what a stitching gateway must check before merging this interface across services, are in docs/interfaces/Node.md.

What it does not reach is the Connection and Edge types around PageInfo, which are per-service and have no shared SDL contract. The recommended (not yet binding) naming rule for those — every *Connection has edges/pageInfo, every *Edge has cursor: Cursor!/node — and a sample assertion for it are in docs/objects/PageInfo.md.