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

@absolutejs/changelog

v0.7.0

Published

The changelog contract for the AbsoluteJS packages. Changes are written as typed entries the compiler checks, the release gate reconciles them against the package's own published types so a change nobody wrote down cannot ship, and migrations are data rat

Readme

@absolutejs/changelog

The changelog contract for the AbsoluteJS packages.

A changelog is worth having when it can be trusted and acted on. Prose changelogs manage neither: they rot because writing them is a discipline, and they cannot be acted on because "refactored the server options" is a true sentence that does not say which export moved or what to write instead.

This package makes both possible.

  • Entries are typed. A change is written as TypeScript, so the compiler insists that a breaking change names the exports it breaks and says how to migrate. The entries that cost somebody an afternoon are the ones that cannot be filed empty.
  • The release gate checks them against the package. Before a release goes out, the exports that vanished or changed shape are compared with the exports the entries name. Forget an entry and the release stops. Nobody has to remember.
  • Migrations are data. A rename and a move are the overwhelming majority of real breaking changes, and both are mechanical. Written as data they can be applied — by a CLI, an editor, or a hosted upgrade button — without running a line of code that came from a registry.

Adopting it

bun add -d @absolutejs/changelog
bunx absolute-changelog adopt

adopt creates changelog/unreleased/, keeps whatever CHANGELOG.md already said as changelog/history.md, adds changelog.json and CHANGELOG.md to the package's files, and makes sure your tsconfig compiles the entries — that last one matters, because an entry nothing compiles is an entry nobody checks.

Then add the gate to the release chain:

"check:package": "bun run typecheck && bun run build && bun run test && absolute-changelog check"

Put it after build: the check compares the types you are about to publish against the ones you published last time, and it needs dist to exist.

Writing a change

One file per change, so two branches adding one do not meet over the same line:

bunx absolute-changelog add --kind added --summary "listen accepts a unix socket path"

bunx absolute-changelog add \
  --kind breaking \
  --summary "start is now listen" \
  --symbol start \
  --instruction "Import listen instead of start." \
  --rename-to listen

Which writes changelog/unreleased/start-is-now-listen.ts:

import type { Change } from "@absolutejs/changelog";

export const change: Change = {
  kind: "breaking",
  migration: {
    instruction: "Import listen instead of start.",
    rename: { from: "start", to: "listen" },
  },
  summary: "start is now listen",
  symbols: ["start"],
};

The import is type-only, so an entry has nothing to resolve at run time and the gate works in a checkout with no dependencies installed.

Checked where you type it

An entry can be written against the package's own API, and add scaffolds it that way when it finds one:

import type * as Api from "../../src/index";
import type { Change } from "@absolutejs/changelog";

export const change: Change<typeof Api> = {
  kind: "fixed",
  summary: "stops throwing on an empty list",
  symbols: ["listen"], // completed from the package's exports
};

Suggested rather than required, for two reasons: a removed entry names something that has just stopped existing, and typeof Api cannot see type-only exports at all. The gate is the certain half — it reads the published .d.ts, which carries them.

The kinds

breaking and removed cost a consumer work, and the type refuses them without symbols and a migration. added, changed, deprecated, fixed, security and internal do not.

The migrations

| Shape | What it means | Applied | | ----------------------------- | ----------------------------------------------- | ------- | | rename: { from, to } | An export kept its meaning and changed its name | yes | | moved: { symbol, from, to } | An export moved to another entry point | yes | | resubpath: { from, to } | A whole entry point moved | yes | | manual: true | Somebody has to read the call | no |

manual is not a failure. A changed meaning is not a rewrite anybody should automate, and pretending otherwise is worse than saying so.

Releasing

bunx absolute-changelog release

Works out the version from the entries — a breaking change moves the minor below 1.0.0 and the major above it, and a version already on a prerelease line moves along that line — then writes changelog.json and CHANGELOG.md, bumps package.json, and deletes the entries it consumed.

--as major|minor|patch|prerelease overrides the inference, --version x.y.z overrides it entirely, and --dry shows the release without writing anything.

The gate

bunx absolute-changelog check
  • every entry parses, and the disruptive ones carry what they must;
  • CHANGELOG.md is what the entries say it should be — it is generated, and editing it is how the two copies drift;
  • the version in package.json and the newest release agree;
  • package.json still ships both documents, and nothing is sitting in the entry directory that no release will read;
  • the entries name every export that moved since the newest published version — not the one in package.json, which between release and publish is a version nobody can fetch;
  • and every migration describes what actually happened: a rename whose destination this version does not export, a rename whose source is still exported, a move to an entry point the package does not have, a removal of something still there.

That last one is what makes an applicable migration safe to apply. A migration that reads correctly and rewrites working code into something that does not compile is the whole risk of automating an upgrade, and it fails the release instead.

--offline skips the two that talk to the registry.

adopt also writes prepublishOnly, so the gate runs however a publish was started — a release script, a bare npm publish, a CI job. A rule that can be walked around eventually is.

Reading somebody else's

Anything deciding whether an upgrade is safe reads the published document:

import {
  applyMigrations,
  migrationsBetween,
  publishedChangelog,
} from "@absolutejs/changelog";

const changelog = await publishedChangelog("@absolutejs/example", "2.1.0");
const migrations = changelog
  ? migrationsBetween(changelog, "1.4.0", "2.1.0")
  : [];

const upgraded = applyMigrations(source, {
  migrations: migrations.map((at) => at.migration),
  packageName: "@absolutejs/example",
});

For a package that has not adopted this yet, publishedSurface and diffSurface compare the published types of two versions instead — less than a changelog, and much more than nothing.

Licence

MIT.