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

@taulell/cms

v0.13.0

Published

A content system for a site on Cloudflare Workers: schema in code, a content API, an MCP server and rendering helpers.

Readme

@taulell/cms

A content system for a site on Cloudflare Workers. A site declares its content model in code, and its own Worker serves:

  • a content API under /_cms/api, with its OpenAPI spec
  • an MCP server for an agent at /_cms/mcp
  • uploaded images under /_cms/media
  • the reader its pages render from
  • a revision for every write, which can be restored

Content lives in the site's own D1, KV and R2. Edits are live: there are no drafts and no publish step. The CMS assumes no language, country, currency or business, and the site decides who may write.

Use it in a site

Declare the content model in cms.config.ts. It runs in Node as well as a Worker.

import { defineConfig, field } from '@taulell/cms';

export default defineConfig({
  locales: ['en', 'es'],
  defaultLocale: 'en',
  singletons: {
    shop: { fields: { name: field.text({ required: true }), hours: field.hours() } },
  },
  collections: {
    products: {
      path: { en: 'products', es: 'productos' },
      fields: {
        name: field.localized({ required: true }),
        slug: field.slug(),
        price: field.price({ currency: 'EUR' }),
        photo: field.image(),
      },
    },
  },
});

Serve it from the site's Worker, and say who may call it. authorize has no default, so no site is open by omission.

import { cachedPage, createCms } from '@taulell/cms/worker';
import config from './cms.config.ts';

const cms = createCms(config, {
  // Turn a request's bearer token into a caller, or refuse it.
  authorize: async (token, env: Env) => {
    const caller = await verify(token, env); // the site's own check: { scope: 'read' | 'write', actor }
    return caller ? { ok: true, caller } : { ok: false, status: 401, error: 'The token is not valid.' };
  },
});

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    if (cms.handles(new URL(request.url).pathname)) return cms.fetch(request, env, ctx);
    // A page reads its content through `await cms.reader(env)`.
    return cachedPage(request, env.CMS_KV, ctx, () => renderPage(request, env), env.VERSION.id);
  },
};

The Worker needs these bindings and settings:

  • CMS_DB (D1), CMS_KV (KV), CMS_MEDIA (R2) and CMS_LOADER (a Worker Loader). The tables are made on first use, so there is no migration.
  • CMS_SITE_URL, a var holding the site's public origin, which its pages name in canonical URLs and a sitemap. The CMS itself doesn't read it.
  • The nodejs_compat compatibility flag. The MCP server runs the agent's code in a Dynamic Worker, which needs Workers Paid.
  • A version_metadata binding, whose id goes to cachedPage so a deploy never leaves cached pages linking assets it removed.

cachedPage never caches a page asked for at localhost, so a dev server shows each change to the code at once.

Before a deploy, validateSite(config, production?) returns a sentence for each problem with the config, and, given production's API and a read token, for each live document the new config would refuse. Run it in CI so a change that would break live content fails first.

To work on a site locally with its live content, importContent(target, exported, file) writes what the live site's GET /_cms/api/export answers into local storage: target holds the D1, KV and R2 bindings, such as wrangler's getPlatformProxy() gives in Node on the storage wrangler dev reads, and file(key) fetches an image file the storage lacks, which the live site serves at /_cms/media/<key>. The documents replace the local ones, the pages' snapshot is published from them, and no revision is written.

Exports

| Import | Runs in | Holds | | --- | --- | --- | | @taulell/cms | Node, Workers, browsers | defineConfig and field for the config; validateSite, configProblems and contentProblems; createReader; the formatters formatPrice, formatDate, dayName and renderMarkdown; the routing functions resolvePath, alternates, collectionPath, entryPath and pagePath; importContent; and their types | | @taulell/cms/worker | Workers | createCms and cachedPage | | @taulell/cms/client | Node, Workers, browsers | createClient({ url, token }), a typed method for each API route, and CmsRequestError, for code outside the site; variantWidths(width) and MEDIA_WIDTHS, the widths to resize an image to before uploading it |

The field kinds are text, localized, markdown, slug, number, boolean, select, url, email, phone, price, image, images, hours, date, timezone, geo, reference and identifiers. A field, singleton or collection may carry a label per language.

Commands

None. validateSite is a function; @taulell/site runs it from its taulell validate.

Versions

A site pins one exact version, and @taulell/site peers on one. CHANGELOG.md lists each release, newest first, with what changed for a site and what the site does to move to it.