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

@frontierjs/toolbelt

v0.1.8

Published

FrontierJS pure-function kits — zero dependencies, no I/O, importable from any package

Readme

@frontierjs/toolbelt

Pure functions, shared across every FrontierJS package and app, so the same string/date/object logic is not written a fourth time.

One package, one kit per subpath. Each kit stands on its own — import @frontierjs/toolbelt/glow and nothing else comes with it — and they ship together because a project that wants one usually ends up wanting two.

import { glow } from '@frontierjs/toolbelt/glow'

| Subpath | Kit | State | | --- | --- | --- | | /glow | source code → highlighted HTML | shipping | | /cron | what a five-field cron expression admits | shipping | | /datetime | an instant read as a place's wall clock, formatted, and written back | shipping | | /inflect | how a name is spelled — number and shape | shipping | | /directives | the $ convention — filters vs directives | shipping | | /gate | the access ladder — levels, levelPasses, gradeStanding, canAtLevel | shipping | | /aria | what a css VOCAB term owes the accessibility tree — role, required relationships, tracked state | shipping | | /history | the key naming one occurrence of change | shipping | | /frontier | a Frontier member's chain of machines, and the post's signed row for it | shipping | | /hooks | the four-phase resource pipeline | shipping | | /json | reading and editing a JSON document nothing describes | shipping | | /jsonschema | follow a $ref; what a blank record looks like | shipping | | /match | does this record belong in that query's results | shipping | | /predicate | does this record satisfy a declared .lite expression | shipping | | /query | what a query string MEANS — types, structure, both directions | shipping | | /redact | is this key a credential, and redacting a value by that answer | shipping | | /seal | a value only one key can open — HPKE base mode over X25519, in WebCrypto | shipping | | /search | ranking a corpus nobody indexed | shipping | | /signature | what a signed machine-to-machine request is, and a short signed token for an address | shipping | | /tty | @frontierjs/css's tones as a terminal's own 16 colors | shipping | | /units | a magnitude with a unit, as a person reads it | shipping |

The whole package holds to one rule:

Every export is a pure function. Same input, same output. No I/O, no clock, no filesystem, no network, no globals, no framework imports, no mutation of its arguments.

That rule is not a style preference — it is what buys the package its standing (below). A helper that needs the current time is handed it — as an argument, or as a clock function the app passes in (FJS-D268). One that needs an env var or a Junction ctx does not go in a new subpath here; it goes in the package that needs it.


Realm

Cross-cutting. Not a realm noun — toolbelt introduces no Model, Service, or Resource.


Dependency direction

Zero dependencies, ever — workspace or otherwise. Toolbelt is substrate: it sits below the dependency graph rather than being a member of it, so importing it can never create a cycle or route a package around Litestone ← Junction ← Sierra. Any package may import it, including litestone and mesa.

Ruled FJS-D26, DECISIONS.md § Dependencies & the ecosystem. The purity rule above is what the ruling rests on: a single Date.now() in src/ costs the exemption, not just the style.


query — what a query string means

import { parseQueryString, encodeQueryString } from '@frontierjs/toolbelt/query'

parseQueryString('?qty=5&live=true&sku=007&id[in][]=1&id[in][]=2')
// { qty: 5, live: true, sku: '007', id: { in: [1, 2] } }

A URL carries text, so something has to decide whether ?qty=5 is a number. Three boundaries decide it — Junction's transport, Junction's client writing one, Sierra's router — and they used to give three answers (FJS-D125).

A string is a number only if it round-trips: String(Number(v)) === v. One test, and the traps of the usual parseFloat version fall out of it — '007' stays a SKU, '+1' stays a phone number, '1.50' keeps its cents, and '9007199254740993' stays a string because the round trip loses its last digit.

true, false and null are themselves. Structure is bracket notation, never a sigil. A repeated key is an array. ?code="5" is the one escape, for text that would otherwise read back as something else — and it is exactly what encodeQueryString emits for such a string, which is what makes the two halves inverses rather than approximately so.

It is not validation and not schema coercion. It answers what did the caller type, with no model in the room. Where a model exists it has the last word: id String filtered by ?id=5 reads 5 here and Litestone converts it back.

cron — what a five-field expression admits

import { parseCron, cronMatches } from '@frontierjs/toolbelt/cron'

const fields = parseCron('0 9-17/2 * * mon-fri')   // → Sets, one per field
cronMatches(fields, { minutes: 0, hours: 11, date: 4, month: 3, day: 3 })  // true

parseCron answers the set of values each field admits and throws on anything it cannot mean, naming the field and the bound — 0 25 * * * is hour value is 25, outside 0-23 rather than a schedule that registers and matches no minute for the life of the process. 0 9 31 2 * is refused the same way: every field is in range and no month it admits is long enough.

cronMatches takes clock parts rather than a Date, with month 1-12 and day 0-6. Which clock, and when to look, is the caller's — Caravan reads a named zone and walks a wall clock across daylight boundaries, junction's in-process scheduler reads the host clock, and neither is a fact about the grammar.

Day of month and day of week are OR'd where BOTH are restricted, and AND'd with the other three: 0 0 1 * mon is the first of the month AND every Monday, which is what it means in a crontab. The asymmetry reads as a bug and is the grammar — under a uniform AND the same line fires only when the 1st happens to BE a Monday, about one month in seven, which is a schedule that runs and looks alive (FJS-1008). Restricted means not written as a star — cron's own test is the field's FIRST CHARACTER, so 0-6 names every day and is still restricted, and 0 0 1 * 0-6 fires daily. Reading set COMPLETENESS instead looks tidier and diverges on exactly that line, silently.

A name is resolved in its own field's table, never over the line: sat is a day and jan is a month, and each is refused in the other's field rather than becoming a number (FJS-1009). Any prefix of two letters or more naming exactly one entry, so jun resolves and ju is refused naming what it could be.

cronMatches throws where parts is missing one of the five keys, because false there is a schedule that never fires and never says so.

Sunday is 0 and 7. A term may combine a list, a range and a step (0,2-4,9-15/3).

datetime — an instant, a place, and the words between them

import { createDatetime } from '@frontierjs/toolbelt/datetime'

// once, in the app — the kit reads no clock and keeps no settings of its own
export const dt = createDatetime({ timeZone: 'America/Denver', now: Date.now })

dt.format(order.createdAt, 'DDDD, MMMM D [at] h:mm aa')   // 'Saturday, July 4 at 10:05 AM'
dt.relativeToNow(order.createdAt)                          // '2 hours ago'
dt.dayHeaderNow(message.sentAt)                            // 'Today' · 'Yesterday' · 'Thursday' · 'Jun 27' · 'Dec 31, 2025'
dt.fromWall({ year: 2026, month: 11, day: 1, hour: 1, minute: 30 }, { disambiguation: 'reject' })
// RangeError: datetime: 2026-11-01T01:30:00 in America/Denver happens twice — the clock went back. …

Every function also stands alone with its arguments explicit: format(instant, pattern, { timeZone, locale }), relative(instant, now, { style }), partsIn(instant, timeZone), resolveWall(fields, timeZone) and fromWall(fields, timeZone, { disambiguation }). An instant is epoch milliseconds, a Date, or an ISO string with Z or an offset — a string without one is refused, because it names a different moment wherever it is read.

Tokens. Uppercase is the date, lowercase is the time. Literal text goes in brackets, and a run of letters that is not made of tokens is an error that names it.

| Token | | Token | | | --- | --- | --- | --- | | YYYY YY | year | hh h | hour — 12-hour only when a/aa/aaa is in the pattern | | GGGG | the ISO week's year | mm m | minute | | QQQQ QQQ QQ Q | 3rd quarter Q3 03 3 | ss s | second | | MMMM MMM MM M | July Jul 07 7 | aaa aa a | in the morning AM A | | WWWW WWW | week of the month: 1st week W1 | ttt tt | Mountain Daylight Time MDT | | WW W | ISO week | t | offset, -06:00 | | DDDD DDD DD D | Saturday Sat 04 4 | | |

Names follow locale; digits are always Latin. QQQQ and WWWW are English and refuse another locale. Zone rules are the runtime's own ICU table, and node and bun can disagree where a country changed its rules recently (FJS-D268).

Plain dates — a day in no zone, which is what a String @date column holds and what a billing period, a due date or a birthday is (FJS-D288).

plainDateIn('2026-01-31T01:00:00Z', 'America/New_York')  // '2026-01-30' — an evening there
addToDate('2026-01-31', { months: 1 })                   // '2026-02-28' — clamped, Temporal's 'constrain'
daysBetween('2026-08-30', '2026-09-29')                  // 30
startOfDay('2026-09-06', 'America/Santiago')             // the instant that day begins — 01:00, not midnight
dt.today()                                               // the day it is now, in the instance's zone

A zone is asked for at exactly two crossings — an instant to a day, and a day to the instant it starts — and the calendar arithmetic between them has none to disagree with. A day is [startOfDay(d), startOfDay(addToDate(d, { days: 1 }))), 23 or 25 hours long on a transition day, so it is never start + 24h. addToDate's clamp does not remember the day it came from: '2026-01-31' plus a month twice is 28 February and then 28 March, so a recurring date is computed from its anchor rather than by stepping.

units — a magnitude with a unit

import { formatBytes } from '@frontierjs/toolbelt/units'

formatBytes(5 * 1024 ** 2)              // '5.0 MB'
formatBytes(45 * 1024 ** 2)             // '45 MB'
formatBytes(500 * 1024 ** 3)            // '500 GB'
formatBytes(45 * 1024 ** 2, { decimals: 2 })   // '45.00 MB'
formatBytes(undefined)                  // ''  — absent is not zero

Binary steps with the familiar labels: 1024 to the step, and the step is called MB rather than MiB, which is what almost every tool a reader has used shows them. Precision is adaptive — one decimal below ten of a unit, none above, and never on bytes — because a long list is scanned for magnitudes, and 503.2 GB carries a digit nobody is reading. decimals fixes it for a column that must not jitter.

'' for undefined, null, NaN, [] and a non-numeric string: answering 0 B for a missing size is how we do not know reads as an empty file. Number(null) and Number('') are both 0, so every function here asks before it divides.

What it does about that answer depends on the surface, and the three differ on purpose. A formatter says '' — display can say nothing. fromMinor answers NaN, which formatMoney turns into '', so a nullable money column reaches the cell as blank rather than as $0.00, and one missing amount does not take a screen down. toMinor and roundMinor throw: those are the write and the arithmetic, where a silent 0 is a free order or a receipt that balances and is wrong (FJS-1011). The currency CODE is strict on all of them.

Four copies of this function existed before it did, and two of them disagreed — @frontierjs/ui said 5.0 MB where three basecamp screens said 5 MB, so one application showed one disk two ways (FJS-408).

inflect — how a name is spelled

Three axes of one question. NUMBER is post ⇄ posts; SHAPE is product_variants ⇄ ProductVariant ⇄ productVariant; READER is postal_code → Postal Code.

import { pluralize, singularize, modelName, camel, snake, slug, toolName } from '@frontierjs/toolbelt/inflect'

pluralize('category')            // 'categories'
singularize('statuses')          // 'status'

modelName('product-variants')    // 'ProductVariant'  — Invariant 2, composed once
camel('HTTPStatus')              // 'httpStatus'      — a leading initialism lowers WHOLE
snake('ProductVariant')          // 'product_variant'
slug("It's a C++ thing")         // 'its-a-c-thing'
slug('add product variants', { sep: '_' })   // 'add_product_variants'
toolName('orders', 'refund')     // 'orders_refund'   — the MCP spelling; a dot is illegal there

modelName is exported rather than left to the caller. Six sites composed pascal(singularize(t)) by hand with four different splitters, and every one of them already imported singularize from here — so half of Invariant 2's derivation was shared and the other half of the same expression was not. order-item was OrderItem to one and Order-item to two others.

humanize is the third axis and it answers to somebody else. postal_code and postalCode are both Postal Code: @frontierjs/ui labels a control whose field declared no @label, and sierra falls back to it when a stored value has no row to read a label from (FJS-D225). It is words() plus exactly one added rule — a digit starts a word of its own — and that rule may not be lifted upward, because kebab('address1') becoming address-1 renames a column. What NUMBER and SHAPE answer must not change when a reader changes; what humanize answers must (FJS-D236). Only _, - and whitespace separate in either half, so partman.template_x comes back visibly wrong rather than as a plausible PartmanTemplateX.

slug is one rule and one stated exception: every run of non-alphanumerics becomes one separator, and an apostrophe is deleted, because it sits inside a word where every other mark sits between two. sep is a parameter (a migration filename wants _), max truncates and re-trims (a .slice() at the call site is what stores strategy-and-), and accents fold rather than drop — five copies of this answered caf for Café.

One definition, five callers. FrontierJS names one thing three ways — model Post in the schema, posts for the service and the URL, db.post for the accessor — and a table name and the model name derived back from it have to be the same rules run twice. They were five copies that disagreed: two knew twenty irregulars, one knew none but had the guards that stop status becoming statu, and one was endsWith('s').

English's regular rules plus a fixed irregular table (person/people, index/indices, datum/data, 21 in all). Not a dictionary, and it cannot become one — the gaps are structural: bases is basis and never base, houses singularizes to hous, lens to len, because pens is a real plural with the same ending. A word the rules cannot reach is said by hand: @@map in the Data realm, createResource('lenses', { model: 'Lens' }) in the UI.


directives — the $ convention

import { splitParams, RESERVED_PARAMS } from '@frontierjs/toolbelt/directives'

splitParams({ status: 'active', $limit: '20', $orderBy: '-createdAt', $first: '1', $limitt: '5' })
// { query:      { status: 'active' },
//   directives: { limit: 20, orderBy: '-createdAt' },
//   flags:      { first: true },
//   unknown: ['$limitt'], listed: [], invalid: [] }

FrontierJS carries two different kinds of thing in one bag of parameters: the filters (status=active — columns, values, a WHERE) and the directives ($limit=20 — how much, in what order, which fields). The $ is what tells them apart, and it is transport syntax: nothing past the boundary that reads it should ever see one.

Two boundaries read it, not one — Junction's bridge, off an HTTP query string or a WebSocket frame, and Sierra's router, off a URL's search string. Same grammar, two realms, which is why the table is here rather than in either. A $ key the table does not name never reaches the filters: splitParams drops every $ key but COMBINATORS ($or, $and, $not — filters written with a $, left in query for the where grammar), and its unknown names the ones it did not recognize, so a boundary can refuse them by name (FJS-D237, FJS-D910). listed and invalid are the other two refusals, and flags is $first / $wrap, so a request is graded off one call; a bag with no $ key answers all of them empty without walking the table.

Both directions: parseDirectives reads the $ names into the structured form, and directiveParams writes that form back out as $ names, so a client building a URL uses the same table the boundary reads it with.

Value shapes are deliberately not fixed: over HTTP everything is a string, and a caller that has already parsed a URL passes numbers and booleans. Both are read, and absent stays absent — nothing was asked is not the defaults.


docfences — the fenced blocks of a markdown page

import { docFences, isRunnable } from '@frontierjs/toolbelt/docfences'

docFences('```lite\nmodel A { id Int }\n```\n```js throws\ndb.a.create()\n```\n')
// → [{ lang: 'lite', line: 1, body: 'model A { id Int }', schema: null, … },
//    { lang: 'js', line: 4, words: Set { 'throws' }, schema: 'model A { id Int }', … }]

One reader for litestone's doc-example runner, fli check's doc-examples-run and the doc audit. schema is the nearest preceding non-fragment ```lite block; fragment, throws and failing on the info string are the contract, and isRunnable is a runnable tag not marked fragment. A package supplies its own prelude, since what a block runs under is the package's.


frontmatter — what a --- block means

import { parseFrontmatter } from '@frontierjs/toolbelt/frontmatter'

parseFrontmatter('---\ntitle: About\nitems:\n- name: Home\n  link: /\n---\nBody')
// → { frontmatter: { title: 'About', items: [{ name: 'Home', link: '/' }] }, body: 'Body' }

Sierra's route table, mesa's .md compiler and fli's command files all read it, so a page's page.meta and its module's frontmatter are the same object, and a command means to fli what it means to any other reader. It reads a declared subset of YAML (FJS-D549):

| Reads | Refuses, naming the line | | --- | --- | | block maps and lists, nested up to 100 deep, including a list at its key's own column | anchors &, aliases *, tags !, merge keys << | | \| and > blocks, with -/+ and an indent digit | complex keys ?, directives %, a ... line | | flow [ ] and { }, over several lines | a tab in the indentation | | 'single' and "double" quotes with YAML's escapes | anything YAML itself refuses, such as an unquoted : in a value or a duplicate key |

Scalars resolve by YAML 1.2's core schema: 2024-01-05, yes and on stay strings. A refusal throws an Error whose message starts line N: and whose .line is N, counted from the top of the file. parseFrontmatterBlock(text) reads the text between the fences. splitFrontmatter(source) answers { block, body } without reading the block, so a caller that reports a refusal can still show the body.

A generator writing a person's words into a block uses frontmatterValue(value) — the text to put after key: that this reader reads back as exactly that string. It is plain where plain already means it and double-quoted otherwise, so Fix: login becomes "Fix: login" and @scope/app stops being refused.


glow — source code to highlighted HTML

import { glow } from '@frontierjs/toolbelt/glow'

glow('.btn { color: red }', { language: 'css', prefix: false })
// → <code language="css">…</code>

| Option | Default | What it does | | --- | --- | --- | | language | inferred from the first character | css, html, js, bash, md, json, yaml, … | | prefix | true | Treat a leading +, - or > as a line marker. See the trap below. | | mark | true | Treat •text• as a highlight and ••text•• as an error | | numbered | false | Wrap each line in a <span> so a CSS counter can number it |

The output carries no CSS classes. A token is marked with the HTML element that already means it, so the theme is element selectors and one attribute:

| Element | Marks | | --- | --- | | <sup> | comment | | <i> | punctuation | | <b> | identifier — property, function, key | | <em> | value — string, number, CSS custom property | | <strong> | keyword, tag name, hex color | | <label> | @rule, decorator, !important | | <ins> <del> <dfn> | a whole line: added, removed, noted | | <mark> <u> | an author's highlight, an author's error |

@frontierjs/css ships that theme in components/code.css and needs nothing from this package at runtime to do it.

The line-prefix trap

With prefix on, a line beginning +, - or > is a marker and the character is removed from the output. In CSS all three are legal first characters — --custom-prop, > .child, + .sibling — so highlighting a stylesheet with prefixes on silently eats one character per line.

-- is handled for you: two dashes are never a diff marker. The combinators are not, and cannot be — + .sibling and a diff-added line are the same three characters. A caller highlighting CSS wants prefix: false.


Install

bun add @frontierjs/toolbelt

Testing

bun run test           # all specs
bun run test inflect   # only specs whose filename matches

Zero dependencies — test/run.js is the whole harness, and it runs under node as well as bun.

The corpus in test/fixtures/guide-samples.json is 137 real code samples lifted from the @frontierjs/css guide — CSS, HTML, JS, shell. Every one is round-tripped through every language and must come back byte-identical, because the one way a highlighter can be catastrophically wrong is silently: it drops a character, the output still looks like code, and the reader copies a sample that does not work. Refresh it with node test/fixtures/extract.mjs.


Candidate contents

Nothing here is committed to. Listed so the boundary is legible:

  • string: case conversion, slugify, truncate
  • object: pick, omit, deep equal, deep clone, dot-path get/set
  • array: groupBy, chunk, uniqueBy, partition
  • type guards and small predicates
  • result/option helpers, if the framework settles on a shape

Date and time helpers are not on that list: they are the /datetime kit.


Open questions

  • ~~Is purity enforced, or only documented?~~ Enforced — scripts/ci.mjs § hygiene fails the build on a dependency in the manifest, or a clock, a global, a network call or any non-relative import under src/ (FJS-258).
  • Whether there is ever a root . entry. Today every kit is a subpath, so an app that imports one pulls in one.
  • Does toolbelt duplicate anything already living inside litestone or sierra? If so, the copy there moves here and the original re-exports — never two implementations. The two that were open closed this way: FJS-191 (mesa's forked glow) and FJS-192 (five inflection rule sets).
  • glow was written elsewhere and adopted; docs/glow/ keeps the Svelte editor and SCSS theme it arrived with, as reference only. Neither is shipped, and neither is FrontierJS code — the repo has no Svelte, and the SCSS uses UnoCSS's @apply.