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

@aweftjs/debug

v0.1.1

Published

Reads a running document or commit back as text.

Readme

@aweftjs/debug

Reads a running document or commit back as text.

Nothing in the stack imports this package, and nothing may: it is registered as an integrator, so the boundary check fails on any package that reaches for it. You import it yourself, in a test, in a script, or in a running server while working out what happened. It is not stripped from a release build, because the moment you most want it is the one you did not plan for.

Every function here returns a string. That is deliberate. The reader is usually looking at test output or a terminal, and a rich object printed into either is worse than a plain line.

Quickstart

import { createObject, observer } from '@aweftjs/core';
import { explain, trace } from '@aweftjs/debug';

const doc = createObject({ title: 'plan', size: 1 });

// What is in here?
console.log(explain(doc));
// object Hh8kQ2...  (title: 'plan', size: 1)

// What is happening to it?
const t = trace(doc);
doc.title = 'plan b';
console.log(t.text());
// commit  (deltas: 1)
//   replace title  (value: 'plan b')
t.stop();

The four readers

explain(subject, document?) takes whatever you are holding and works out what it is: an observable, a commit, or either kind of refusal. It never throws. A value it cannot place comes back saying so, because a debug call that fails leaves you worse off than before you made it.

documentOf(document) reads a whole document as a tree, with each observable under the slot that holds it rather than in the flat id-keyed map a snapshot gives you. An observable reached twice prints once and then as a back-reference, so an alias cycle terminates. One the document no longer holds reads as detached rather than as the tree it used to sit in.

commitOf(commit, document?) reads a commit as the list of what it changed. Pass the document and every id becomes the path that reaches it; leave it out and each delta names its observable by id text.

You will see two spellings for an array slot, and the difference is real rather than sloppy. documentOf prints [0], because it is looking at the whole array and can count. A delta prints [at 018143c4ab], because a delta carries one position key and the index that key sits at depends on every other position in that array, which the delta does not carry. When a commit is read against its document the path resolves and you get the index.

trace(document, limit?) watches a document and keeps what it did. It is an ordinary watcher, so it hears commits after they land and it stops when you call stop(). It keeps the last 200 commits by default; a trace left running on a busy document is a memory leak with a friendly name, so the limit is not optional behaviour you have to remember to add. Each commit is rendered as it lands, so a trace does not change its mind about a commit when the tree above it moves later.

Why console.log is not enough

An observable is a proxy over an internal node. console.log(row) prints that node: raw id bytes, a slots Map whose values print as [Object], listeners, watchers, reach, and circular parent and root back-references. It is many lines, and none of them is your data. JSON.stringify(row) is worse: it walks the whole document from wherever you happened to be standing. explain asks the document what it holds and prints that.

Reading a refusal

Two different errors reach you, and they read differently because they come from different places. explain handles both.

A refusal from the library carries a stable reason, a detail saying what was seen, and a fix saying what to do:

try {
	apply(doc, { deltas: [] });
} catch (e) {
	console.log(explain(e));
	// refusal empty-commit  (message: ..., fix: Drop the commit instead of applying it, ...)
}

Branch on reason, never on the message. The message is written for a person and is allowed to change; the reason is part of the format's contract.

A RefusedError from a guarded document is different, and it is the one you meet most. It comes from a rule your own application wrote, through intercept or through @aweftjs/schema, so it carries a list of refusals rather than one reason, and none of them carries a fix: the library did not write them and has nothing to suggest.

try {
	doc.total = -1;                     // a rule the application registered refuses this
} catch (e) {
	console.log(explain(e));
	// refused  (refusals: 1)
	//   refusal negative-total  (at: 'total', message: 'a total is never negative')
}

The remedy for one of these is whatever your rule meant, so put it in the rule's own message. Whether a Refusal should carry a fix field of its own is an open question.

What this package will not do for you

It does not tell you why a watcher did not fire. It shows you what the document holds and what commits landed, and the answer is usually in those two, but working it out is still yours.

It does not attach to anything on its own. There is no global, no auto-install, and no side effect from importing it.

It covers @aweftjs/core, and only that. Documents, commits and refusals. It does not read a live scope, a mounted node, a link or a store row: those live inside packages that hand out no way to see them, and reaching in would have meant a new permanent export in each. Design 103 is why that seam is not here. explain on one of those objects tells you it cannot place it rather than guessing at it.