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

@verifyhash/graphql-sdl-diff

v0.1.0

Published

Pure, dependency-free-at-runtime diff of two GraphQL SDL strings into a classified list of breaking / dangerous / safe changes, built on a vendored graphql-js.

Readme

graphql-sdl-diff

Compare two GraphQL SDL strings and get back a flat, classified list of what changed between them — every entry graded breaking, dangerous, or safe. It is a thin, honest layer over the reference graphql-js implementation, which is vendored and pinned in vendor/graphql/ (see VENDOR.md), so there is no network access, no filesystem access, and no install step at runtime.

Who it's for

Anyone who evolves a GraphQL API and wants a machine-checkable answer to "will this schema change break my clients?" — CI gates, migration reviews, changelog generation, or a schema-registry pre-flight check.

Honest limits (read these first)

This is a small, conservative tool, not a schema-registry replacement. Before you rely on it, know exactly what it does and does not do:

  • Conservative rail — never a false safe. Anything the diff cannot prove backward-compatible is graded dangerous and tagged "review manually", never quietly downgraded. safe is emitted only for provably additive output changes (a new type, a new output field) and for noted @deprecated changes.
  • Directives and custom scalars are flagged, not resolved. A directive's or custom scalar's runtime effect lives in your executor, outside the SDL, so this tool surfaces those changes as dangerous + "review manually" rather than guessing whether they break clients.
  • Single-SDL / stitched-SDL input only (v1). It diffs two complete SDL strings. It does not stitch multiple .graphql files for you, and it does not resolve Apollo Federation directives (@key, @external, …) — feed it the already-composed schema text.
  • Type-system breakage only. It cannot detect behavioural breakage — a field that keeps its type but changes its meaning — because no schema differ can.
  • Backward-compatible relaxations report as no change, not safe. Only added types and added output fields (and noted @deprecated) are emitted as safe. A change that is merely non-breaking — an input field made optional (String!String), an output field made non-null (StringString!), or an output list strengthened ([String][String!]!) — produces an empty change list. Note the direction is opposite between input and output: on an input field adding ! is breaking (callers must now supply it) while removing ! is a no-change; on an output field removing ! is breaking (clients can now receive null) while adding ! is a no-change. Absence of an entry therefore means "nothing your clients depend on broke", not "identical".
  • GraphQL names must be ASCII. Per the GraphQL spec, type/field/argument names match /[_A-Za-z][_0-9A-Za-z]*/; non-ASCII identifiers (e.g. café, 你好) are not valid SDL. Feeding them in returns a structured { error } — never a throw and never a silent partial diff. (Unicode inside descriptions is fine.)
  • Built on vendored graphql-js. The parser and the core breaking/dangerous taxonomy come from a pinned, vendored copy of graphql-js 16.11.0 (see VENDOR.md); this library is the honest classification layer on top, not a from-scratch GraphQL implementation.

The full taxonomy and the exact edge-case behaviour are documented in What each severity means and Honesty rail (and its limits) below.

Install / run

There is nothing to install. Clone the folder and require it:

const { diffSchemas } = require('./core.js');

const before = `
  type Query { user(id: ID!): User }
  type User { id: ID! name: String }
`;
const after = `
  type Query { user(id: ID!): User }
  type User { id: ID! name: String! email: String }
`;

console.log(diffSchemas(before, after));
// [
//   { type: 'FIELD_CHANGED_KIND', severity: 'breaking',
//     description: 'User.name changed type from String to String!.' },
//   { type: 'FIELD_ADDED', severity: 'safe',
//     description: 'User.email was added.' }
// ]

API

diffSchemas(oldSDL, newSDL) -> Change[] | { error }

  • Returns an array of Change objects on success, ordered breaking → dangerous → safe.
  • Never throws. Invalid SDL on either side (or a non-string argument) returns a structured { error: string } instead — the message names which side failed and includes the underlying parse error.

Each Change is:

{
  type: string,        // e.g. 'FIELD_REMOVED', 'VALUE_ADDED_TO_ENUM', 'TYPE_ADDED'
  severity: 'breaking' | 'dangerous' | 'safe',
  description: string  // human-readable, one line
}

What each severity means

| severity | meaning | examples | | --- | --- | --- | | breaking | Existing valid client operations can now fail. | removed type/field, field type changed, argument made required, new required argument, enum value removed, union member removed, interface no longer implemented, output field T!T, input field TT! | | dangerous | Compiles for everyone, but can surprise clients/servers at runtime. | enum value added (an input can now receive an unseen value), optional input field added, optional argument added, arg default changed | | safe | Purely additive to the output surface; existing clients are unaffected. | a new type; a new field on an existing object/interface type |

What we add on top of graphql-js

graphql-js's findBreakingChanges / findDangerousChanges are authoritative in the unsafe direction, but they intentionally (or, for a few categories, simply do not) report several things. Our own pure layer fills those gaps.

Additive safe layer — graphql-js says nothing for purely additive output changes; we classify them safe:

  1. Added types — a type present in the new schema but not the old.
  2. Added output fields — a new field on an object/interface type that exists in both schemas.

Conservative-rail semantic layer — graphql-js (as of the pinned 16.x) reports nothing at all for the following, which would otherwise be an invisible false safe. We detect each ourselves:

| case | our verdict | why | | --- | --- | --- | | directive applied/removed/changed on a type, field, arg, input field or enum value (DIRECTIVE_USAGE_CHANGED) | dangerous + review manually | a directive's runtime effect (auth, rate-limit, formatting…) is opaque to a type-only diff | | custom directive definition add/remove/change (DIRECTIVE_DEFINITION_CHANGED) | dangerous + review manually | same — behaviour lives in the executor, not the SDL | | custom scalar definition change, e.g. @specifiedBy url or description (CUSTOM_SCALAR_CHANGED) | dangerous + review manually | a scalar's serialize/parse contract is defined outside the SDL | | input-field default-value change (INPUT_FIELD_DEFAULT_VALUE_CHANGE) | dangerous | clients that omit the field now send a different value (graphql-js reports the argument equivalent, but not input fields) | | @deprecated added / removed / reason changed (DEPRECATION_CHANGED) | safe, but explicitly noted | deprecation is a standardised, backward-compatible mechanism — surfaced, never silent |

@deprecated is deliberately excluded from the directive-usage rail above: it is the one directive whose semantics are standardised and backward-compatible, so it is the only case this layer ever grades safe — and even then it is labelled, not dropped.

Everything else — every removal, mutation, tightening, and every input-side addition (enum values, input-object fields, arguments) — is delegated to graphql-js, which grades it breaking or dangerous.

Honesty rail (and its limits)

This tool is deliberately conservative: safe is only ever emitted for the two additive cases above, which are provably backward-compatible. If graphql-js were ever to emit a change category this layer doesn't recognise, it is kept dangerous and tagged "review manually" rather than silently downgraded. The tool never invents a false safe.

Honest limits:

  • Making an output field non-null (StringString!) is genuinely safe for consumers, and graphql-js reports nothing for it — so this tool also reports nothing (no entry), rather than a safe line. It is a silent, compatible change, not a labelled one.
  • "Breaking" here means type-system breaking. It cannot know about behavioural breakage (a field that keeps its type but changes its meaning); no schema differ can.
  • Directive and custom-scalar changes cannot be proven safe from SDL alone, so the conservative-rail layer reports them dangerous + "review manually" rather than guessing. That is intentional over-caution: a human confirms them once.
  • Description-only edits are not breaking and are not invented as changes; the differ round-trips descriptions and comments without noise.

Tests

npm test

Runs two files, both pure Node assert with no external test dependencies:

  • test/vectors.test.js — hand-verified golden vectors covering the full graphql-js taxonomy (removed type, removed field, field type changed, argument made required, new required argument, enum value removed/added, nullable↔non-null flips in both directions, added optional field, added type, union member removed, interface no longer implemented) plus the invalid-SDL and no-false-positive contracts.
  • test/adversarial.test.js — conservative-rail vectors that pin the cases graphql-js under-covers: directives added/removed/changed (usage and definition), custom-scalar changes, arg vs input-field default-value changes, @deprecated (safe + noted), root-type renames, description/comment round-trip stability, a 500+ type schema (asserts no hang and prints the runtime), and duplicate/conflicting type names (structured error, never a throw). Each asserts the rail law: no uncertain change is ever a silent safe.

License

MIT (this project). The vendored graphql-js is MIT — see VENDOR.md.