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

bbmd

v0.0.4

Published

The Markdown toolkit for writing complex, embeddable documents in Typescript

Readme

bbmd - block based markdown documents

The Markdown toolkit for writing complex, embeddable documents in Typescript

What is bbmd?

By using a block architecture, bbmd allows you to write complex Markdown documents that are context-agnostic and always render well. Your documents can easily interlace defaults, hide blocks conditionally, and adjust their structure and styling automatically when injected into other bbmd documents.

import { b } from "bbmd";

type User = { name?: string; email?: string; alternateEmail?: string };

const createUserDoc = (user: User): MarkdownDocument => {
  const footnote = b
    .footnote(`Alternate email: ${user.alternateEmail}`)
    .if(user.alternateEmail);
  return b
    .doc(
      b.heading("User details"),
      b.p`The users name is ${b.p(user.name).default("unknown")}`,
      b.p`${b.b`The users email is`}: ${user.email}${footnote}`.if(user.email),
    )
    .if(user.name || user.email);
};

const user: User = { email: "[email protected]", alternateEmail: "[email protected]" };
const userDoc = createUserDoc(user);

console.log(`${userDoc}`);
// # User details
// The users name is unknown
// **The users email is**: [email protected][^1]
//
// [^1]: Alternate email: [email protected]

const prompt = b
  .doc(
    b.heading("Instructions"),
    "Greet the user and introduce yourself as a helpful AI assistant.",
    userDoc,
    "Once you have done this, call the `welcomeGiven` tool.",
  )
  .setRenderingOptions({
    enforce: { bold: { style: "__" } },
    newlineStrategy: "between_blocks",
  });

console.log(String(prompt));
// # Instructions
//
// Greet the user and introduce yourself as a helpful AI assistant.
//
// ## User details
//
// The users name is unknown
//
// __The users email is__: [email protected][^1]
//
// Once you have done this, call the `welcomeGiven` tool.
//
// [^1]: Alternate email: [email protected]

Notice that in this example:

  • The user document reacts to the data using a simple, declarative syntax
  • Embedded documents get structurally reorganized to make sense in their host context. In this example, the userDoc heading was shifted up its the footnote moved to the bottom of the document
  • You can enforce consistent rendering at the root document level, which applies recursively to sub-documents
  • The document is readable by embedding it in template literals, passing to String(), or calling the .toString() method directly from the document

This is the magic of a block-based architecture. By keeping a walkable block structure until the document needs to be rendered, documents are able to be context-agnostic and react to where they're embedded in their hosts.

console.log(userDoc.inspect());
// MarkdownDocument
// ├── MarkdownHeadingBlock
// │   └── "User details"
// ├── MarkdownLiteral [trimmed]
// │   ├── "The users name is "
// │   └── MarkdownParagraphBlock
// │       └── "unknown"
// └── MarkdownLiteral [trimmed]
//     ├── MarkdownBoldBlock
//     │   └── "The users email is"
//     ├── ": "
//     ├── "[email protected]"
//     └── MarkdownFootnoteBlock
//         └── footer
//             └── "Alternate email: [email protected]"

It also enables you to use a comfortable, chainable syntax to create your documents, which is perfect for building complex prompts programmatically.

type PullRequest = { title: string; reviewer: string; approved: boolean };
const createPrDoc = (pr: PullRequest): MarkdownDocument => {
  const reviewer = b.b(pr.reviewer).change((block) => {
    if (pr.approved) return block.strikethrough();
    return block;
  });
  return b.doc(b.b(pr.title).h(), b.p("Reviewer: ", reviewer));
};

const pr: PullRequest = { title: "100", reviewer: "John", approved: true };
console.log(String(createPrDoc(pr)));
// # **100**
// Reviewer: ~~**John**~~

console.log(String(createPrDoc({ ...pr, approved: false })));
// # **100**
// Reviewer: **John**

Because bbmd allows all primitive data types as inputs, and it's resulting documents are swappable to anywhere you currently use strings, it's very easy to incrementally adopt bbmd.

For near instant adoption anywhere you use template literals today, just prefix them with b.md. By default, b.md detects injected blocks and encapsulates them (see the first example above), but if you would prefer to write markdown as you normally would, you can combine b.md with the .parse() method.

//                       👇 add `b.md` to existing template literals
const existingPrompt = b.md`
  # About our company
  We are a company that makes widgets.

  ## Our process
  We follow a _rigorous_ process to make ==widgets==.

  ## Our customers
  | Name       | Email                  |
  |------------|------------------------|
  | John Doe   | [email protected]   |
  | Jane Smith | [email protected] |
`.parse(); // 👈 and then `.parse()` it to convert it automatically

console.log(existingPrompt.inspect());
// MarkdownDocument
// ├── MarkdownHeadingBlock
// │   └── "About our company"
// ├── MarkdownParagraphBlock
// │   └── "We are a company that makes widgets."
// ├── MarkdownLineBreakBlock
// ├── MarkdownSectionBlock
// │   ├── MarkdownHeadingBlock
// │   │   └── "Our process"
// │   ├── MarkdownParagraphBlock
// │   │   ├── "We follow a "
// │   │   ├── MarkdownItalicBlock [style=_]
// │   │   │   └── "rigorous"
// │   │   ├── " process to make "
// │   │   ├── MarkdownHighlightBlock
// │   │   │   └── "widgets"
// │   │   └── "."
// │   └── MarkdownLineBreakBlock
// └── MarkdownSectionBlock
//     ├── MarkdownHeadingBlock
//     │   └── "Our customers"
//     └── MarkdownTableBlock [columns=Name,Email, rows=2]
//         ├── columns
//         │   ├── "Name"
//         │   └── "Email"
//         └── rows
//             ├── row 0
//             │   ├── "John Doe"
//             │   └── "[email protected]"
//             └── row 1
//                 ├── "Jane Smith"
//                 └── "[email protected]"

Did you notice the extra bit of magic in the example above? b.md also improves on standard template literals by automatically removing empty lines at the top and bottom of your document, and removes leading whitespace from each line, unless it's a code block or indented list, meaning you can forget about causing indentation issues.

Features

  • Full support for standard, extended, and Github Flavored Markdown syntax specifications
  • Additional support for common Markdown hacks like underlines, comments, details, and image captions
  • Sub-document and section handling renders your blocks perfectly wherever they're injected
  • Concise chaining API that focuses on terseness
  • Simple return interfaces enable easy typing for document factories
  • Automatic parsing using b.md``.parse() enables quick adoption
  • Documents convert to strings automatically using String(doc) or template literals using ${doc} and support .toString()
  • Zero dependencies and minimal bundle size

Getting started

Run the install command using your package manger of choice:

npm install bbmd
yarn add bbmd
pnpm add bbmd

Then import b anywhere in your application:

import { b } from "bbmd";

Advice

Encode as much block data as possible

The most important thing to understand about bbmd is that the more metadata you encode in the block system, the more able it is to ensure that your documents are truly context-agnostic.

bbmd exposes many ways to achieve this, which allows you to pick whichever one suits your use case best. Parse encoding is best for quickly adopting existing template literals, functional encoding works best for constructing complex, conditional documents, and template encoding offers a mix of the conveniences of both syntaxes.

const templateEncoding = b.md`
${b.h("Example document").l(2).id("example-document")}
${b.b("Important text")}${b.fn("example footnote")}
`;

const functionalEncoding = b.doc(
  b.h("Example document").l(2).id("example-document"),
  b.p(b.b("Important text"), b.fn("example footnote")),
);

const parseEncoding = b.md`
## Example document {#example-document}
**Important text**[^1]

[^1]: example footnote
`.parse();

expect(String(templateEncoding)).toBe(String(functionalEncoding));
expect(String(functionalEncoding)).toBe(String(parseEncoding));

Use chaining to express conditions

When creating complex documents that need to respond to state, use the provided methods to easily handle most scenarios. .if(), .default(), and .change() should cover most use cases.

const createUserTemplate = (
  userName: string,
  isWorking: boolean,
  status: string,
): MarkdownInlineBlock => {
  return b.p`${userName}`
    .if(isWorking)
    .default("Unknown")
    .change((block) => {
      if (status === "active") return block.bold();
      if (status === "inactive") return block.strikethrough();
      return block;
    });
};

expect(String(createUserTemplate("", true, "unknown"))).toBe("Unknown");
expect(String(createUserTemplate("John", false, "active"))).toBe("**Unknown**");
expect(String(createUserTemplate("John", true, "active"))).toBe("**John**");
expect(String(createUserTemplate("John", true, "inactive"))).toBe("~~John~~");
expect(String(createUserTemplate("John", true, "unknown"))).toBe("John");

Keep typing as simple as possible

Types in bbmd have been designed carefully to avoid complexity. There is a three-tier hierarchy of types which will help keep your I/O extremely lean when embedding/returning bbmd blocks.

Tier 1 (all blocks): MarkdownBlock
└── Tier 2 (structural blocks): MarkdownInlineBlock | MarkdownLineBlock | MarkdownMultilineBlock
    └── Tier 3 (concrete implementations): Specific Markdown blocks (MarkdownBoldBlock etc)

As a general rule, use the highest level of specificity that it is convenient for a function to accept/return. For the most part, bbmd should type to tier 3 for you automatically, however if you need to declare type signatures yourself, it can be more convenient to duck down to the next lowest tier.

const createUserTemplate = (
  userName: string,
  status: string,
): MarkdownInlineBlock => {
  // 👆 the inferred return type is
  //    MarkdownParagraphBlock | MarkdownBoldBlock | MarkdownStrikethroughBlock
  //    however, it was more convenient to explicitly type the return as MarkdownInlineBlock
  return b.p(userName).change((block) => {
    if (status === "active") return block.bold();
    if (status === "inactive") return block.strikethrough();
    return block;
  });
};

Set rendering options at call time

Because rendering a document walks the entire block structure, documents can adjust both structurally and stylistically to ensure they look good however they're embedded/shared.

Structural adjustments (like keeping footers at the bottom of the document) occur automatically for you, however styling adjustments are left to the root document to define.

You can override a variety of styling options, which are enforced on the entire document at render time.

// You can define a re-usable set of rendering options
const defaultRenderingOptions = b.renderingOptions({
  enforce: {
    bold: { style: "__" },
    horizontalRule: { style: "*" },
    unorderedListItem: { style: "+" },
    list: { indent: 2 },
  },
  newlineStrategy: "between_blocks",
});

// Your actual documents can use inconsistent styling
const exampleDoc = b.md`
  # Heading 1
  This paragraph has some **bold** text. This __bold__ text uses inconsistent styling.
  - This list
  * Has different bullet styles
      - Nested list with 4 tab indent
  + For each point
  ---
  The document feels crammed at first.



  But then includes lot's of newlines



  ...between every line.
`
  .parse()
  .setRenderingOptions(defaultRenderingOptions);

// But you can always enforce consistency and newline strategies at your final call-sites
console.log(String(exampleDoc));
// # Heading 1
//
// This paragraph has some __bold__ text. This __bold__ text uses inconsistent styling.
//
// + This list
// + Has different bullet styles
//   + Nested list with 4 tab indent
// + For each point
//
// ***
//
// The document feels crammed at first.
//
// But then includes lot's of newlines
//
// ...between every line.