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

@caretcms/core

v0.1.2

Published

Platform-neutral core for CaretCMS: attribute-first inline editing, mutation engine, studio admin, StorageAdapter interface.

Readme

@caretcms/core

Inline editing and live content collections for Astro. Add one HTML attribute to make any element editable, and use Astro's live loaders to query CMS data with getLiveEntry / getLiveCollection (stable on Astro 6, experimental on Astro 5.10+).

CaretCMS inline editor demo

Install

npm install @caretcms/core

Static Astro site? Use static delivery — no SSR adapter:

integrations: [caret({ delivery: 'static' })],

Server-rendered site? Add an adapter and server output:

npm install @astrojs/node
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [caret()],

See Static delivery and Rendering & output below.

Setup

Static delivery (default path for static Astro sites)

// astro.config.mjs
import { defineConfig } from 'astro/config';
import caret from '@caretcms/core';

export default defineConfig({
  integrations: [caret({ delivery: 'static' })],
});
  • astro dev — full authoring: /admin, /api/cms, inline editor.
  • astro build — bakes stored overrides into generated HTML; authoring routes stay out of production output.
  • Public updates — after Publish, run CI/build/deploy (optionally via delivery.publish.webhookUrl).

Server delivery (per-request rewriting)

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import caret from '@caretcms/core';

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
  integrations: [caret()],
});

Edits are visible to visitors immediately — middleware rewrites HTML on each request.

Already have Astro content collections? If your project has collections under src/content/ and you haven't set storage, CaretCMS auto-selects markdownStorage() so the Studio lists those collections immediately (instead of "No collections yet") and edits write back to your .md frontmatter. Pass an explicit storage to override — storage: filesystemStorage() opts back out.

Choose your path

CaretCMS gives you two ways to make content editable. They share one storage layer and one login — you can mix them on the same site — but they answer different questions. Start at the top; reach for the next row only when you need it.

| Start here if… | Use | What you write | |---|---|---| | You have static markup (a hero, an about page) and just want to click words/images and change them | Inline editing | data-caret attributes on the elements | | The same fields repeat or you want short attribute names | Scoped inline editing | a data-caret-scope wrapper + short data-caret names | | Your content is dynamic data you query in frontmatter (a blog index, a list of products) | Live collections | caretLoader in src/caret.config.ts, then getLiveEntry / getLiveCollection |

The binding model in one line: every edit is addressed as collection::id::field. Inline editing lets you spell that out in pieces — data-caret-scope="pages::home" sets collection::id, and data-caret="headline" fills in the field, so the element above resolves to pages::home::headline. Live collections address the same collection + id from frontmatter instead. Same content, same storage — two ways to reach it.

Adding data-caret to an existing site by hand? npx @caretcms/caretize scans your Astro project and interactively annotates your templates for you. The attributes below are all you need either way — caretize just writes them.

Inline editing

Add data-caret attributes to your templates:

<main data-caret-scope="pages::home">
  <h1 data-caret="headline">Welcome to my site</h1>
  <p data-caret="intro">This text is editable.</p>
  <img data-caret="hero_image" src="/default.jpg" alt="Hero" />
</main>

Start the dev server:

npm run dev

With no password configured, a temporary dev password is printed in the terminal (dev only — production stays locked). To set a permanent one, add CARET_EDIT_PASSWORD=<your-password> to a .env file.

Log in at /admin, then click any annotated element on the page to edit it. The content Studio lives at /admin/cms.

The inline editor only bootstraps on pages that contain data-caret bindings and only after GET /api/cms/auth/session confirms an authenticated editor session. Session cookies are issued as HttpOnly, SameSite=Lax, and automatically add Secure on HTTPS requests.

When you're signed in and land on a live page that has no data-caret bindings yet, CaretCMS shows a small "signed in · no editable fields on this page" hint pointing you at the next step — so a page that isn't annotated yet reads as "nothing to edit here" rather than "is this broken?". The hint is editor-only (anonymous visitors never see it) and never appears inside the Studio.

Live content collections

You can also wire collections into Astro's native content layer using caretLoader. This lets you query CMS data with getLiveEntry and getLiveCollection from astro:content — the same API you use for any Astro live collection.

Astro 6: stable, no flag required. Astro 5.10+: available behind experimental.liveContentCollections: true in astro.config.*. Astro 5.0–5.9: the live-loader API (defineLiveCollection, getLiveEntry, getLiveCollection) doesn't exist; use data-caret inline editing, bindEntry(), and loadEntry() instead.

1. Define collections

Create src/caret.config.ts:

import { defineLiveCollection } from 'astro:content';
import { caretLoader } from '@caretcms/core';

const pages = defineLiveCollection({
  loader: caretLoader('pages'),
});

const site = defineLiveCollection({
  loader: caretLoader('site'),
});

export const collections = { pages, site };

2. Query in pages

---
import { getLiveEntry } from 'astro:content';

const { entry: home } = await getLiveEntry('pages', 'home');
---
<h1>{home?.data.headline}</h1>

3. Both approaches work together

Inline editing (data-caret) and live collections read from the same storage adapter. Edits made inline are immediately visible through getLiveEntry / getLiveCollection, and vice versa. Use whichever approach fits the context:

  • data-caret for visual, in-place editing of rendered pages
  • getLiveEntry / getLiveCollection for programmatic data access in frontmatter

Explicit schemas (optional)

By default, Studio infers field types from your stored data. For better field labels, validation, and widget hints, pass JSON Schemas via the integration config:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import { z } from 'astro/zod';
import caret from '@caretcms/core';

const PageSchema = z.object({ headline: z.string(), intro: z.string() });

export default defineConfig({
  output: 'server',
  integrations: [
    caret({
      schemas: {
        pages: z.toJSONSchema(PageSchema),
      },
    }),
  ],
});

When provided, Studio uses these for field names, types, and editor widgets instead of guessing from the first entry.

Already describe your collections with Zod? If you have a content.config.ts Zod schema, don't write it twice — @caretcms/zod derives the Studio schema from that single source:

// astro.config.mjs
import { schemaFromZod } from '@caretcms/zod';
import { blogSchema } from './src/schemas.mjs'; // the same object content.config.ts uses

caret({ schemas: { blog: schemaFromZod(blogSchema) } });

(Keep your Zod schemas in a plain module that imports only zod — not astro:content — so both content.config.ts and astro.config can import them.) Schemas remain optional: with auto-detected markdownStorage the Studio already shows each collection with fields inferred from existing entries; deriving from Zod just adds proper labels, types, and widget hints.

What you get

  • Inline editing on any data-caret element (text and images)
  • Live content collections via caretLoader for getLiveEntry / getLiveCollection (Astro 6 stable, Astro 5.10+ behind experimental.liveContentCollections)
  • Content Studio at /admin/cms for structured CRUD
  • Section composer for reordering and spacing page sections
  • Response rewriting middleware (server delivery) or build-time HTML bake (static delivery)
  • Scoped bindings via data-caret-scope to reduce repetition
  • Dev Toolbar app — in astro dev, inspect and highlight every binding on the page (no login required), grouped by entry with deep-links into Studio
  • Revision safety with optimistic locking and restore from history
  • Storage adapters — filesystem (default), in-memory, or custom via StorageAdapter interface

Cloudflare deployment

npm install @caretcms/cloudflare
import caret from '@caretcms/core';
import { cloudflareStorage, r2Uploads } from '@caretcms/cloudflare';

export default defineConfig({
  output: 'server',
  integrations: [
    caret({
      storage: cloudflareStorage({ binding: 'CMS_KV' }),
      uploads: r2Uploads({ binding: 'CMS_R2' }),
    }),
  ],
});

API

All routes are under /api/cms by default (configurable via apiBasePath):

| Route | Method | Purpose | |---|---|---| | /api/cms/entries | GET | List entries by collection | | /api/cms/schema | GET | Collection schema (explicit or inferred) | | /api/cms/mutate | POST | Save content mutations | | /api/cms/history | GET | Entry revision history | | /api/cms/upload | POST | File uploads | | /api/cms/auth/login | POST | Editor login | | /api/cms/auth/session | GET | Editor session status | | /api/cms/auth/logout | POST | Editor logout |

/api/cms/auth/session returns { authenticated: boolean } and is what the inline editor uses to decide whether it should mount on the current page.

Configuration

caret({
  mountPath: '/admin',          // Admin UI base path (default: /admin)
  apiBasePath: '/api/cms',      // API route prefix (default: /api/cms)
  enableAdmin: true,            // Inject admin pages (default: true)
  enableInlineEditor: true,     // Inject inline editor (default: true)
  delivery: 'static',           // 'static' | 'server' | { mode, bake, publish }
  storage: filesystemStorage(), // Storage adapter (default: markdownStorage when
                                //   src/content collections exist, else filesystem)
  uploads: localUploads(),      // Upload handler (default: local filesystem)
  schemas: {},                  // Optional JSON Schema map for Studio (default: inferred)
})

Rendering & output

CaretCMS supports two delivery modes via delivery:

| | Static delivery | Server delivery (default) | |---|---|---| | Config | caret({ delivery: 'static' }) | caret() on output: 'server' + adapter | | Public HTML | Baked at astro build | Rewritten per request in middleware | | Production CMS routes | Not shipped in static output | /admin, /api/cms live on the server | | Visitor sees edits | After publish + rebuild | Immediately after save | | SSR adapter | Not required | Required |

Static delivery

Use when your site stays output: 'static'. Local authoring works in astro dev; production is plain static files with content baked in.

caret({
  delivery: {
    mode: 'static',
    bake: true,
    publish: {
      webhookUrl: 'https://ci.example.com/hooks/rebuild',
    },
  },
})

Full guide: docs/static-delivery.md.

Server delivery

Middleware injects stored edits at request time. Requires output: 'server' (or compatible adapter host). Mostly-static site? Under server output you can keep individual pages static with export const prerender = true; pages with editable content should stay server-rendered.

Migrating a static site to server delivery: switching to output: 'server' changes how getStaticPaths pages receive props. Add export const prerender = true to keep a page static, or fetch at request time. This is standard Astro behavior, not CaretCMS-specific.

What gets written to disk

The default (embedded) providers write inside your project:

| Path | What | Written by | |---|---|---| | .caret/data/ | entry JSON | filesystemStorage() (default) | | .caret/drafts/ | per-editor draft overlays | filesystemStorage() | | .caretcms/ | revisions + history sidecar | both filesystem and markdown storage | | src/content/**.md | frontmatter edits | markdownStorage() (auto-selected when you have content collections) | | public/uploads/ | uploaded images | localUploads() (default) |

Recommended .gitignore for the transient state (keep .caret/data/ or your src/content edits if git IS your content store — see the commit-on-publish workflow in docs/deployment.md):

.caretcms/
.caret/drafts/
public/uploads/

Production checklist

  • CARET_EDIT_PASSWORD — the editor password. Without it, production is locked (no dev fallback).
  • CARET_SESSION_SECRETrequired in production when a password is set; sessions are HMAC-signed with it. Generate one with openssl rand -base64 32. If it's missing, logins return a configuration error and existing sessions are treated as signed out.
  • markdownStorage() edits src/content/*.md at request time — a dev/git workflow. In production, pair it with commit-on-publish + a CI rebuild (fields rendered through getCollection() are baked at build time and only refresh on rebuild), or use a server-side adapter like @caretcms/cloudflare.
  • localUploads() writes to public/uploads, which built sites serve from dist/client — files uploaded after the build won't be served. Treat it as dev-only and use R2 (or your own UploadHandler) in production.
  • Defaults resolve paths from the server process's working directory; run the built server from your project root, or pass explicit paths (filesystemStorage({ dataRoot }), markdownStorage({ contentRoot }), localUploads({ uploadsDir })).

Requirements

  • Astro 5 or 6
  • Node 20.19.1+ or 22.12.0+
  • Static delivery: default Astro static output (no adapter)
  • Server delivery: output: 'server' plus an SSR adapter

License

MIT