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

@venn-lang/fmt

v0.9.0

Published

The fmt namespace: a value in, formatted text out. JSON, tables, YAML, CSV and XML.

Readme

@venn-lang/fmt

The fmt namespace: a value in, formatted text out. JSON, tables, YAML, CSV and XML.

Formatting is kept separate from printing on purpose. Every verb here returns a string, which is a value you can print, assert against, send in a request or write to a file, not something that only ever reaches a terminal. The plugin is pure: it needs no host capability and touches nothing.

Install

@venn-lang/fmt is part of the stdlib the venn CLI and the language server load, so there is nothing to install. A file that formats says so:

import { fmt } from "venn/fmt"

Usage

# report.vn, run with `venn run report.vn`
import { fmt } from "venn/fmt"

const people = [
  { name: "Ada",   age: 36 },
  { name: "Linus", age: 54 }
]

print fmt.table(people)
print fmt.csv(people)
print fmt.json({ count: people.len }, 0)
name  │ age
──────┼────
Ada   │ 36
Linus │ 54
name,age
Ada,36
Linus,54
{"count":2}

print is in the prelude, so that file imports nothing but fmt.

Verbs

| Verb | Signature | What it does | | --- | --- | --- | | fmt.json | (dynamic, number?) -> string | JSON text. Indents by 2 by default; fmt.json(x, 0) puts it on one line. A value that contains itself degrades instead of throwing. | | fmt.table | (list<dynamic>) -> string | An aligned ASCII table of a list of records. | | fmt.yaml | (dynamic) -> string | YAML for a map, a list or a scalar. | | fmt.csv | (list<dynamic>, string?) -> string | CSV with a header row. fmt.csv(rows, ";") changes the separator. | | fmt.xml | (dynamic, string?) -> string | XML text. fmt.xml(x, "user") names the root element, which is root otherwise. |

Every knob is positional, and every verb ends in a string. None of them reads an options map, so none declares one: an options map in the editor's hover that the verb never looks at would quietly strip the keys a caller wrote there.

What each renderer decides

  • table takes the columns from the union of every row's keys, in first-seen order, so a row missing a field still lines up. Each column is padded to its widest cell. An empty list renders as (no rows). A cell holding a map or a list is written the way the language itself writes it (the same renderer behind print and ${}), not as JSON: a table is written for a person to read, and { homework: 95, final: 92 } is what that person would have typed. json, csv, xml and yaml keep their own writers, because they answer to formats outside this language.
  • yaml puts a scalar on its key's line and opens an indented block for a map or a list. A string that would not read back as plain YAML is quoted, and so is the empty string. An empty list is [] and an empty map is {}.
  • csv follows RFC 4180: a field is quoted only when it holds a separator, a quote or a newline, and inner quotes are doubled. A list with no records renders as the empty string.
  • xml turns keys into elements and repeats a tag for each item of a list. Text is escaped (&, <, >, "), and a map with nothing in it becomes a self-closing element.

API

| Export | What it is | | --- | --- | | fmtPlugin (also the default export) | The PluginDefinition: namespace fmt, five actions, no required capability, no types of its own. | | fmtActions | The five ActionDefinitions. | | toJson(value, show, spaces?) | The renderer behind fmt.json. Defaults to 2 spaces. | | toTable(rows, show) | The renderer behind fmt.table. | | toYaml(value, show, indent?) | The renderer behind fmt.yaml. | | toCsv(rows, show, separator?) | The renderer behind fmt.csv. Defaults to a comma. | | toXml({ value, show, tag?, indent? }) | The renderer behind fmt.xml. Defaults to the tag root. | | Show | The type of show: (value: unknown) => string. |

The renderers take no ports and reach for no host capability. Every one of them takes show, the language's own writer, which each verb passes as ctx.show. That is not a convenience: a 250ms reaches a plugin as { kind: "duration", ms: 250 }, and four of these five renderers used to write that envelope out because each kept a private writer of its own. A format decides how a value is delimited; the language decides what the value is.

import { toCsv, type Show, toYaml } from "@venn-lang/fmt";

// Inside an action this is `ctx.show`. Standalone, any writer will do.
const show: Show = (value) => (typeof value === "string" ? value : JSON.stringify(value));

toCsv([{ text: 'say "hi", now', plain: "ok" }], show);
// 'text,plain\n"say ""hi"", now",ok'

toYaml({ name: "Ada", tags: ["a", "b"], nested: { n: 1 } }, show);
// "name: Ada\ntags:\n  - a\n  - b\nnested:\n  n: 1"

See also