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

@crewhaus/tool-data

v0.7.0

Published

Deterministic structured-data tools: JSON/YAML/TOML/CSV/XML parsing, querying, conversion, diffing and reshaping

Readme

@crewhaus/tool-data

Deterministic structured-data tools. Every one is pure: no filesystem, no network, no clock, no randomness. The same input always produces the same bytes.

That property is the point. A harness spends a model call when it needs judgement; it should not spend one to pull two fields out of an API response, join two result sets, or turn a CSV into records. These twenty-three tools cover the mechanical half so the model is left with the part that actually needs it.

tools:
  - all-data          # every tool below
  - -DataConvert      # ...except this one

| Tool | What it does | |---|---| | ColumnsToRecords | Column arrays back to records, short columns padded and named | | CsvParse | RFC 4180 CSV to records or rows, with type inference | | CsvWrite | Records or rows to RFC 4180 CSV, quoting whatever needs it | | DataConvert | JSON ⇄ YAML ⇄ TOML ⇄ CSV ⇄ JSONL | | DataDiff | Structural diff of two documents: added, removed, changed, by path | | DataShape | What is actually in this document — fields, types, fill rates, examples | | DedupeRecords | Drop duplicates by key fields or by the whole value, key order ignored | | FlattenObject | Nested value to dotted keys | | JsonFormat | Re-indent, minify, key-sort, address a subtree, hash the result | | JsonMergePatch | Apply an RFC 7386 merge patch, or derive one between two documents | | JsonPatch | Apply RFC 6902 operations, all-or-nothing | | JsonQuery | Select values with a JSONPath-like expression | | JsonSortKeys | Key-sorted JSON, for diff-stable output | | JsonlParse | Line-delimited JSON, with the line number of any bad record | | JsonlWrite | Values to line-delimited JSON | | RecordsToColumns | Records to column arrays | | SampleRecords | Head, tail, every nth, or evenly spread — never random | | SortRecords | Stable, total ordering by one or more dotted paths | | TableAggregate | Group by, with count/sum/min/max/avg/first/last/distinct | | TableJoin | Inner, left, right or full join of two record arrays | | TableQuery | Filter, project, sort and page records with a small predicate | | UnflattenObject | Dotted keys back to a nested value | | XmlParse | XML or an HTML fragment to a JSON tree |

Two input conventions

A whole document — JSON, YAML, TOML, CSV, XML, JSONL — arrives as text, because that is how it comes off disk or off the wire. A record array arrives as a real array of objects, because that is how it comes out of the previous tool in the chain. Field references inside the record tools are dotted paths (user.team.id), so nested records work without flattening them first.

The parsers are hand-written, so here is exactly what they support

There is no YAML, TOML, XML or CSV dependency behind these tools. That keeps the package free of a supply chain, and it means the supported subset is a contract rather than a hopeful summary. Each module's header comment carries the authoritative version; this is the short form.

JsonQuery's grammar

$.a.b            child
$["odd key"]     quoted child, for keys with dots or spaces
$.a[0]  $.a[-1]  index, negative counts from the end
$.a[1:5]  [::2]  slice, Python semantics, negative step included
$.a[*]   $.*     every element / every value
$..name  $..*    recursive descent
$.u[?(@.age >= 30)]   filter: == != < <= > >= =~ , or a bare existence test

Not supported, each raising a parse error naming the position rather than returning an empty result: unions (['a','b']), functions, parent axes, and && / || inside a filter — chain two filter steps instead. Comparing a number against a string is simply false; there is no coercion, because a quiet "10" < 9 is how a filter silently drops rows.

YAML

Read: block mappings and sequences nested by space indentation, a compact map on the dash line (- name: a), plain/single-quoted/double-quoted scalars with the usual escapes, core-schema typing (null ~ true false, integers, decimals, exponents, 0x, 0o), flow collections ([1, {a: b}]), block scalars | and > with the - and + chomping indicators, # comments, and --- / ... markers.

.inf and .nan read and write correctly, but JSON has no spelling for either, so converting a document that uses them to JSON renders them null. The same is true of TOML's inf and nan.

Refused explicitly, never guessed at: anchors and aliases (&a, *a), merge keys (<<:), tags (!!str), multiple documents in one stream, complex keys (? ), and tabs in indentation. Dates stay strings.

Write: block style, two-space indent, keys in their existing order, with a string quoted whenever leaving it bare would change how it reads back.

TOML

Read: bare, quoted and dotted keys; tables and arrays of tables at any depth; basic, literal and both multi-line string forms, including the backslash line continuation; integers with _ separators and 0x/0o/0b literals; floats, inf, nan; arrays, nested and multi-line; inline tables; # comments. Redefining a table or key is an error, as the spec requires.

The one deliberate departure: dates and times stay strings. TOML has four first-class date-time types and JSON has none, so 2026-01-01 reads back as "2026-01-01". Converting to a Date would make the round trip lossy. Integers outside the JavaScript safe range likewise stay strings rather than being silently rounded.

Redefinition is enforced in both directions the spec names: a [header] cannot reopen a table a dotted key already created, and a dotted key cannot reach into a table a [header] already defined.

Write: scalars first, then sub-tables, then arrays of tables — the order that reads back identically. null has no TOML representation, so it is dropped and the dropped keys are named in the output.

CSV

RFC 4180 in full: quoted fields, delimiters and newlines inside quotes, doubled quotes, a final row with no trailing newline, and a row that is a single quoted empty field ("" is a record, not a blank line). Plus the two departures real files need — a configurable delimiter, and tolerance of bare LF as well as CRLF. A byte-order mark is stripped.

Duplicate and blank column names are made unique before records are built, and the suffix is bumped until the name is genuinely unused, so a,a,a_2 becomes a, a_2, a_2_2 rather than losing a column to a collision.

Type inference is opt-in and conservative: 007, +5 and anything outside the safe integer range stay strings, because turning a zip code into a number loses data. Ragged rows are reported, not silently padded.

XML

A well-formedness-checking parser for the XML that shows up in feeds, SVG and config files. It is not a conformant XML processor and does not claim to be. Elements, attributes, text, CDATA, self-closing tags, the five predefined entities and numeric character references; comments, processing instructions and doctypes skipped; namespace prefixes kept verbatim, not resolved.

DTD-declared and external entities are refused outright — there is no resolver, so this parser cannot be made to fetch anything. A numeric character reference outside the Unicode range, or naming a surrogate, is a reported error rather than a crash. Validation, xml:space and raw-text elements (<script>, <style>) are not supported. html mode adds void elements, unquoted and bare attributes, case-insensitive tag names, and browser-style recovery from a mismatched close tag.

What is deliberately not here

Anything whose core is judgement rather than mechanism: inferring what a column means, reconciling two schemas by intent, deciding which of two conflicting records is right, summarizing a dataset in prose. DataShape reports types and fill rates, not semantics.

Nor is there a random sampler. SampleRecords offers head, tail, every-nth and evenly-spread, because a tool that returned a different subset on each call could not be cached, retried, or compared between runs.

Where a deterministic first pass exists, it belongs first and the model is the escalation path.

A caller's mistake is a result, not an exception

Malformed JSON, an unparseable path, an unterminated CSV quote, a mismatched XML tag: each comes back as a readable string naming the format and, where the format has lines, the line number. Tools throw only on a bug in the tool. JsonPatch goes further and is transactional — if operation 4 of 6 fails, it says so and the document is unchanged. That includes a pointer that is not a pointer: "a" instead of "/a" names the operation it broke on, rather than raising out of the tool.

src/index.test.ts drives every tool with malformed input and asserts a readable result, and src/integration.test.ts does the same through executeTool.

Layout

src/lib/ holds the pure functions and is where the behaviour is tested; src/index.ts wraps them as tools. A bug in the CSV quote handling reads better as a failing unit than as a failing tool call.

Safety flags

All twenty-three are readOnly, non-destructive, scope: "internal", and declare no io capability, because none of them crosses a process or network boundary. packages/tool-data/src/index.test.ts asserts that for every tool, so a future addition that reaches outside has to change the assertion deliberately.