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

@vagdur/loth

v0.6.10

Published

Liturgy of the Hours assembler

Readme

@vagdur/loth

Assembles the Liturgy of the Hours. Given a date and a calendar, it works out which celebration falls that day, resolves every slot of every hour against a tree of liturgical texts, and renders the result as HTML, LaTeX or plain text. The work happens in three layers: the calendar decides what is celebrated, the hours layer decides which texts each hour draws on and in what order — as references, not yet as text — and an assembler resolves those references and produces markup. Chant is carried as GABC and drawn in the browser by @vagdur/exsurge, which can also sing it back.

This package contains code only — no liturgical texts. The psalms, antiphons, hymns and melodies live in a separate data tree that you supply, and much of that material is under copyright that does not permit redistribution. @vagdur/loth/node reads such a tree from disk; hosts without a filesystem (a Cloudflare Worker, say) instead load a bundle produced by readRepoBundle at publish time. Everything reachable from the default entry point is free of Node built-ins, so it bundles for a browser or a Worker without polyfills; npm run check:worker-safe is what keeps that true.

The data/en/ tree in this repository is not an office anyone can pray. It is placeholder text — [Week 2 Sunday Lauds hymn, stanza 1] — with invented chant in data/en/melodies/sample.yaml, and it exists so the test suite can render every hour, in every output mode, with and without scores. Point the library at a real tree to get a real office; npm run generate:data -- --locale xx seeds the shape of a new one.

import { HtmlAssembler, buildDay, defaultContext, resolveDay, utcDate,
         withSanctoralRegistry } from "@vagdur/loth";
import { loadRepository, loadSanctoralRegistry } from "@vagdur/loth/node";

const repo     = await loadRepository("./data", "sv");
const registry = await loadSanctoralRegistry("./data", "sv");

const hour = withSanctoralRegistry(registry, () => {
  const day = buildDay(resolveDay(utcDate(2026, 5, 10), "general"),
                       defaultContext("general"));
  return new HtmlAssembler({ outputMode: "hybrid", fragmentOnly: true })
    .assembleLauds(day.lauds, repo);
});

assemble* returns the hour as a tree, the scores it contains, and html() for the markup. A host that walks the tree gets each score as data — id, GABC, language — rather than having to find it in markup afterwards, which is what lets a framework render an hour without the renderer and the framework fighting over the same DOM.

Then render the scores, client-side, so they play:

import { renderScore } from "@vagdur/loth/browser";

// `element` is yours: renderScore fills it and never looks outside it.
const handles = hour.scores.map((spec) => renderScore(elementFor(spec.id), spec));
await Promise.all(handles.map((h) => h.ready));

handles.forEach((h) => h.setPlayback({ volume: 0.5 }));  // safe before ready too
handles.forEach((h) => h.destroy());                     // releases audio + listeners

| Entry point | What it holds | | --- | --- | | @vagdur/loth | Calendar, hours, assemblers, the document tree. No Node built-ins. | | @vagdur/loth/node | Reading the data tree, exsurge's asset paths, LaTeX compilation. | | @vagdur/loth/browser | renderScore — the only DOM-touching code. | | @vagdur/loth/loth.css | Presentation for the emitted class names. Ships with ExsurgeChar.otf beside it, which the @font-face expects. |

withSanctoralRegistry scopes the ambient calendar around a synchronous render. A server handling more than one locale must use it rather than initSanctoralRegistry, whose global would otherwise let one request change the calendar out from under another.

MIT licensed.