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

folio-db-next

v0.2.3

Published

Document-centric data persistence for Next.js apps, backed by markdown files in Vercel Blob.

Readme

folio-db-next

Document-centric data persistence for Next.js apps, backed by markdown files in Vercel Blob.

Every entity is a markdown file with YAML frontmatter. Every collection is a directory. The blob store is the database. See the root folio-design-doc.md for rationale and non-goals.

Install

pnpm add folio-db-next
# optional — only needed for the blob adapter
pnpm add @vercel/blob

Quick start

import { createFolio } from 'folio-db-next';
import { MemoryAdapter } from 'folio-db-next/adapters/memory';
import { z } from 'zod';

const folio = createFolio({ adapter: new MemoryAdapter() });

const customers = folio.volume('customers', {
  schema: z.object({
    title: z.string(),
    status: z.enum(['active', 'churned', 'prospect']),
    tags: z.array(z.string()).default([]),
  }),
});

await customers.set('acme-corp', {
  frontmatter: { title: 'Acme Corp', status: 'active', tags: ['fintech'] },
  body: '## Notes\n\nKey account. Renewal Q2 2026.',
});

const page = await customers.get('acme-corp');
const all = await customers.list();
const active = all.filter((p) => p.frontmatter.status === 'active');
const hits = await customers.search('renewal');

Volume API

All methods live on Volume<T>:

| Method | Purpose | |---|---| | get(slug) | Fetch a single page, or null | | set(slug, { frontmatter, body }, opts?) | Create or replace. opts.ifMatch for optimistic locking | | patch(slug, { frontmatter?, body? }, opts?) | Partial update; missing fields preserved. Uses current etag unless ifMatch supplied | | delete(slug) | Unconditional idempotent delete — no ifMatch; see issue #32 | | list() | All pages in the volume, with bodies. Filter, sort, and paginate in the caller — folio deliberately doesn't ship a query DSL | | list({ fields: 'frontmatter', limit?, offset?, orderBy?, order? }) | Frontmatter-only list view. Skips bodies; uses the runtime cache if configured. See "List cache" below | | count() | Number of pages in the volume (metadata-only; no bodies fetched) | | stats() | { count, bytesApprox, lastUpdated } from adapter listKeys | | search(query, opts?) | Orama-backed full-text search across frontmatter + body | | reindex() | Rebuild the search index from current pages |

Writes throw ConflictError when ifMatch doesn't match. Reads on missing slugs return null; patch throws NotFoundError.

Adapters

A StorageAdapter is anything implementing get / put / delete / list. Three are shipped:

  • folio-db-next/adapters/memory — in-process, for tests and examples.
  • folio-db-next/adapters/blob — Vercel Blob. Requires the @vercel/blob peer dep.
  • folio-db-next/adapters/http — talks to a folio-server instance over HTTP.

Custom adapters should pass the conformance suite exercised by adapters/memory.test.ts.

List cache

volume.list({ fields: 'frontmatter' }) returns { slug, frontmatter, etag, updatedAt } for every page — bodies are skipped, which is the hot path for dashboards and index views. On a cold read it does listKeys + parallel get + parse; subsequent reads can be served from a pluggable ListCache.

The cache is never the source of truth. Writers invalidate it on every set/patch/delete/setIfAbsent. If the cache is unavailable, list views degrade to the uncached path — slower, never wrong.

Folio ships three implementations:

  • NoopListCache (default; always misses, no-op invalidate).
  • MemoryListCache (in-process, optional TTL, good for dev and single-process deployments).
  • RuntimeListCache (Vercel Runtime Cache; tag-based invalidation, shared across isolates).
import { createFolio } from 'folio-db-next';
import { RuntimeListCache } from 'folio-db-next/caches/runtime';

const folio = createFolio({ adapter });
const posts = folio.volume('posts', {
  listCache: new RuntimeListCache({ ttlSeconds: 300 }),
});
const summary = await posts.list({
  fields: 'frontmatter',
  orderBy: 'updatedAt',
  order: 'desc',
  limit: 20,
});

RuntimeListCache dynamically imports @vercel/functions — install it alongside folio-db-next in environments where it's available. Outside a Vercel Function the cache degrades to "always miss", which is correct but unaccelerated.

Rendering MDX

Folio stores raw markdown — the SDK deliberately doesn't parse the body. That leaves you free to render it however you like, including with MDX and your own React components. There's nothing to configure in folio itself; it's a pattern in the host app.

The repo ships a runnable example at examples/mdx-blog — a Next.js 16 blog that seeds a posts volume into the memory adapter and renders each body with next-mdx-remote/rsc and a small Callout / Metric / MetricGrid component registry. Start there for the full pattern, including generateStaticParams, the component map, and sample posts that mix plain markdown with MDX tags.

A few things to keep in mind when going down this path:

  • Frontmatter stays structured. Use frontmatter for data (titles, tags, dates) and Volume query / search for retrieval. MDX is for presentation flourish in the body — callouts, embeds, charts — not a back door for data.
  • Search indexes raw body. posts.search(...) tokenises the markdown string as-is, so component tags like <Callout> end up in the index. Keep the author-facing text as children, not attributes, so it remains searchable.
  • Portability tradeoff. A .md file with JSX tags needs your component registry to render correctly elsewhere. Weigh that against the editorial benefit before adopting MDX broadly.
  • The Desk editor is markdown-aware, not MDX-aware. Pages with embedded components still round-trip through folio-desk as text, but the editor won't validate or preview them.

Development

pnpm --filter folio-db-next build
pnpm --filter folio-db-next test
pnpm --filter folio-db-next typecheck

License

MIT.