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

stenographers

v0.4.0

Published

Write an edited document back into the Markdown it was read from, keeping the author's bytes: a stenographer per node writes over the node's old slice in the author's style, every node that still means what it meant keeps its bytes, and only typed text is

Readme

stenographers

Write an edited document back into the Markdown it was read from, keeping the author's bytes. A stenographer is the writer of one kind of node with the old transcript in hand: it writes the node over its old slice in the author's style (the bullet character, the emphasis character, the indent, the fence), every node that still means what it meant keeps its bytes, and only typed text is escaped, for what its position could misread. It is not a serializer: mdast-util-to-markdown rewrites a document in its own style (measured on BelType's manual: 14.3 percent of the lines changed with the defaults), and a stenographer rewrites nothing it was not asked to.

Install

npm install stenographers

What it is made of

Five entries (exports, no index):

  • stenographers/parse: the plain parse, parseMarkdown(text, markdown), markdown the venue's options in core's shape ({ gfm: { autolinks, tables }, mark, supersub, figure, directives }, directives the config's directives: bindings over core's built-ins, their tag, className and defaultTitle): mdast-util-from-markdown with BelType's four syntax extensions (frontmatter, math, directive, GFM's parts in the order BelType registers them) and nothing else, the only parse whose positions are exact.
  • stenographers/html: what Markdown means in HTML, mdast-util-to-hast's translation written as markup and parsed by the browser (hastOf, htmlOf, domOf), and means(node, mdast), the test that lets an unchanged node keep its bytes (a footnote reference's number, the document's count, is left out of it).
  • stenographers/write: writeNode(element, { text, column, defs, markdown }), a node's new bytes over its old slice, for paragraph, heading, list (items inserted with their neighbour's marker, deleted with their gap, the list turned tight or loose, a task's box ticked), list item, figure, table (a cell rewritten alone, its padding kept where it fits; rows added from a neighbour's layout and deleted), blockquote (its > prefixes taken off and put back), directive container (read as core renders it, its tag, class and title paragraph; its fences kept, its label, content and title written, the title inside its attribute's quotes, any other attribute changed, added or removed alone), fenced code and inline content (text, em, strong, del, code, link and reference link, break, image, footnote reference, text directive, and BelType's ==mark==, =k=mark==, ~sub~ and ^sup^); writeFresh(element) for a block with no old bytes; escape. A node is read as the HTML element it means, never by a type name. A stenographer that cannot write its node answers null and its parent writes it; the last step is the lane change, the node written as raw HTML. A paragraph's or a heading's bytes are read back before they are given.
  • stenographers/flow: writeFlow(blocks, old, file), a flow of blocks over its old bytes: the parent owns the gaps (the old gap before a block that follows the one it followed, a blank line otherwise; a deleted block's gap kept when it holds a definition or a comment; two lists that would read as one kept apart by an empty comment).
  • stenographers/writer: Provenance's Markdown writer, by shape (markdownWriter({ markdown }): claims, open giving locate, write, shifted), with outline and writeRun. A path is a JSON Pointer over the heading outline addressing a section's own run (its heading line and the blocks after it, up to the next heading of any rank); the value is the run's blocks, each [html, bytes at the base, unchanged, prior bytes, whether they stand]; only set. Bytes are counted here and nowhere else.

What it does not do

It holds no document, no file and no editor: what changed, and which old block a new one stands for, is the caller's (by node identity in an editor, by the bytes a block had in the writer). It imports no ProseMirror, no BelType and no Provenance: Provenance's writer is met by shape, as its spec asks. It needs a DOM (document, for <template> and element comparison): a page's, or in node one the caller installs. A table's columns and alignment do not change (the table changes lane) and a column is never re-padded; a directive binding's render hook is not run (its directive means the element core would give it without one), and a container's label is only its first paragraph after the title, told from the content by the content's count; a title holding markup changes lane; a parent's prefix is spaces only; a text run whose bytes are not its value (an escape, an entity) is written fresh once touched; a list's items pair with the old by meaning from both ends, so an item edited in the same change as an insertion beside it is written fresh, and an inserted item in an ordered list does not renumber the items below; a loose list turned tight keeps the blank lines inside its items, so it changes lane when they would keep it loose. The notations are read from values, as core reads them, so an escaped delimiter is a delimiter: typed text holding a pair of ^, ~ or ==, which no escape keeps text, makes its paragraph change lane.

Extracted from

The studio, beltype-studio-experiment's src/markdown/stenographers/ at commit 3a526b0 (branch round/markdown-1), as prosemirror-stores was: built and proved there against BelType's manual, the pages and Provenance, then moved when green. The plan is the studio's notes/markdown-plan.md, the record of its round notes/markdown-results.md. The studio keeps what is its own: the read into its schema, the file as a client of its authority, saving, the venue host and the Markdown pane.

Proved by

npm test, under node:test with happy-dom as the DOM (a dev dependency only): 109 tests. Over BelType's manual (fixtures/, its nine files as they were on 2026-10-29, the manual's GFM options): every paragraph, heading, list and list item, written from its style alone, is its slice byte for byte, and every other top-level block keeps its bytes; a key typed at the end of every top-level paragraph and heading reads back as the edited node with only the typed bytes changed (after a colon, the colon escaped too). The writer: the outline's paths and their escapes and ordinals, locate in bytes over UTF-8, every run of a file written again from its own value equal to its bytes, one paragraph changed changing only it, a malformed value refused. The list (test/list.test.js): items inserted, deleted, ticked, a list turned tight and loose, each in exact bytes and read back, on small lists and on every tight list of the manual. The figure (test/figure.test.js): core's figure read, none in a tight list item, caption, alt, source and id each rewritten alone. The notations (test/dialect.test.js): core's elements and classes, positions through escapes, typing inside and beside them, what they cannot carry written as HTML. The containers (test/table.test.js, test/quote.test.js, test/directive.test.js): every table and directive of the manual with one cell or one paragraph changed writes only those bytes; directives read as core renders them, titles and attributes written alone; quotes on small cases, lazy lines and nesting; each with the element serialized without the line ends between its elements. test/review.test.js: the review's findings, one case each. test/layers.test.js: every import is a sibling or a package of the Markdown parse.