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

@classytic/cms

v0.2.1

Published

Engine-factory CMS package: content types, content items, assets. Block-based (Slate AST), form-based (typed), and MDX content unified in one storage model. Multi-tenant, headless, visibility-aware. PACKAGE_RULES compliant; built on Mongoose + mongokit +

Readme

@classytic/cms

Engine-factory CMS package. Stores content as block-based (Slate AST), form-based (typed shape), or raw MDX in a single unified resource. Built on Mongoose + mongokit + arc primitives.

Why this exists

Most CMS packages force a single content model. A blog post needs paragraphs + headings + embedded blocks (Slate / MDX). A landing-page "About" section needs a typed shape (hero headline, story paragraphs, values list) authored via a form. WordPress, Sanity, Payload all solve this by shipping two parallel systems.

@classytic/cms solves it with one resource (ContentItem) and a per-org ContentType registry that declares the body shape:

  • displayMode: 'block' → body is a Slate AST array, edited in a Plate-based editor, optionally compiled to MDX on publish.
  • displayMode: 'form' → body is a typed object matching a form schema (e.g. @classytic/formkit), edited via form-system.
  • displayMode: 'mdx' → body is an MDX source string, edited as text or via MDX-aware editor.

The package owns storage + domain logic + events. The host owns HTTP/MCP wiring (via @classytic/arc) and the editor UI.

Resources

| Resource | Purpose | |---|---| | ContentType | Per-org content type registry: { key, label, displayMode, formSchema?, allowedBlocks?, routePattern? }. Each org defines its own types (blog, lesson, about-page, faq, etc.). | | ContentItem | The actual content document: { contentTypeKey, slug, status, content, bodyMdx?, metadata, publishedAt }. content is Schema.Types.Mixed (Slate AST for block mode, form data for form mode, MDX string for mdx mode). | | Asset | Media library entry: { type, url, storageKey, mime, width, height, duration, altText, sourceRef }. |

Quick start

import { createCms } from '@classytic/cms';

const cms = await createCms({
  connection: mongooseConnection,
  tenantFieldType: 'objectId',
  multiTenant: true,
  eventTransport: hostEventBus,
  logger: { error: console.error, info: console.log },
});

// Domain verbs on repositories
const item = await cms.repositories.contentItem.createItem(
  { contentTypeKey: 'blog', slug: 'hello-world', content: slateAst },
  { organizationId: orgId, actorId: userId },
);

await cms.repositories.contentItem.publish(
  String(item._id),
  {},
  { organizationId: orgId, actorId: userId },
);

Host integration (arc)

The host's server app wires arc resources around the engine. arc generates CRUD from the mongoose model + mongokit repository — no proxy layer in this package.

// apps/server/src/resources/cms/cms-engine.ts
import { createCms } from '@classytic/cms';

let cms: Awaited<ReturnType<typeof createCms>> | null = null;

export async function ensureCms(connection: Connection, eventTransport: EventTransport) {
  if (cms) return cms;
  cms = await createCms({ connection, eventTransport, multiTenant: true });
  return cms;
}
// apps/server/src/resources/cms/content-item.resource.ts
import { defineResource } from '@classytic/arc';
import { createMongooseAdapter } from '@classytic/mongokit/adapter';
import { ensureCms } from './cms-engine.js';

export async function registerContentItemResource(fastify, deps) {
  const cms = await ensureCms(deps.connection, deps.eventTransport);

  defineResource({
    fastify,
    name: 'content-item',
    prefix: '/content-items',
    adapter: createMongooseAdapter(cms.models.ContentItem, cms.repositories.contentItem),
    // disableDefaultRoutes: false → arc auto-CRUDs from the repo
  });
}

Content modes — author UX

| Mode | Stored shape | Editor (host-owned) | Renderer (host-owned) | |---|---|---|---| | block | [{ type, children, ...props }] (Slate AST) | Plate or any Slate-based editor wired to the block contract | Compile to MDX on publish, or render Slate directly with custom components | | form | { hero: {...}, story: {...}, values: [...] } (matches form schema) | @classytic/formkit <FormGenerator schema={contentType.formSchema} /> | Hand-rolled React page that reads the typed shape | | mdx | "# Heading\n\nSome **MDX** with <Custom />..." (string) | Plain textarea or MDX-aware editor (MDXEditor, etc.) | @mdx-js/mdx compile + render |

The package doesn't ship editors. Hosts pick what fits their domain.

Domain verbs on repositories

Every repository extends Repository<TDoc> from mongokit. Hosts get full CRUD from arc auto-generation. Domain verbs live on the repository:

  • ContentItemRepository.createItem(input, ctx) — validates against ContentType.displayMode, normalizes body, emits content.created.
  • ContentItemRepository.publish(id, extras, ctx) — atomic CAS via repo.claim(), sets publishedAt, emits content.published.
  • ContentItemRepository.unpublish(id, ctx) — back to draft, emits content.unpublished.
  • ContentItemRepository.archive(id, ctx) — emits content.archived.
  • ContentTypeRepository.upsertByKey(orgId, key, input, ctx) — register or update a content type.
  • AssetRepository.registerUpload(input, ctx) — record a completed upload, emits asset.created.

All CRUD primitives (getById, getAll, update, delete, claim, cursor) inherit from mongokit's Repository. No re-exports.

Events

import { CMS_EVENTS } from '@classytic/cms';

CMS_EVENTS.CONTENT_CREATED    // 'content.created'
CMS_EVENTS.CONTENT_PUBLISHED  // 'content.published'
CMS_EVENTS.CONTENT_UNPUBLISHED
CMS_EVENTS.CONTENT_ARCHIVED
CMS_EVENTS.CONTENT_TYPE_UPSERTED
CMS_EVENTS.ASSET_CREATED
CMS_EVENTS.ASSET_DELETED

Hosts subscribe via their own EventTransport (passed to createCms). The package ships an InProcessCmsBus fallback for dev.

Multi-tenancy

multiTenant: true injects organizationId (ObjectId) into every schema and adds the multi-tenant plugin. Repositories filter by ctx.organizationId on every operation. Soft-delete and audit-log plugins are NOT included by default — hosts opt in via plugins: [...].

What this package does NOT do

  • No HTTP routes (arc owns that).
  • No React, no editor UI (host owns that).
  • No storage adapter impl (host provides R2/S3/etc. presigner; package only stores Asset metadata).
  • No AI / Prism integration (host wires that into its editor).
  • No versions / drafts as sibling docs (v0.2; v0.1 uses status enum like commerce-cms does today).
  • No tags / taxonomy (v0.2).

License

MIT