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

@orion-studios/cms

v0.15.0

Published

Orion CMS v2 core engine: JSONB content model on Supabase primitives

Readme

@orion-studios/cms

Orion Studios' CMS engine: a single package that turns a custom Next.js site into a client-editable site. JSONB content on Supabase, a self-contained Studio admin, and a block system where one definition drives everything.

Frontend and Studio visual contract

The custom frontend owns the design. Orion CMS supplies typed content, permissions, versioning, and editing behavior. It does not substitute a generic CMS renderer for a project-owned page.

Studio always opens pages, collection entries, and site-wide globals through protected same-origin preview routes. Those routes must call the same project components, layouts, CSS, fonts, and responsive rules as the public routes. Studio adds inline editing to that rendered document and keeps links, forms, booking controls, and other effects inert. Inspector fields remain available when a value cannot be matched safely in the rendered document.

Every consuming project must provide the applicable /cms-preview/[id], /cms-collection-preview/[id], and /cms-global-preview/[key] routes. An integration is incomplete until its public and Studio output match at desktop and mobile sizes. Do not change frontend markup or styling to accommodate the CMS. Change the typed content model, preview adapter, or editing controls.

  • No schema migrations for content-model changes. Pages and collection entry data use JSONB, so adding or editing block and entry fields is a code change.
  • defineBlock() is the single source of truth. A Zod schema + a React component per section type produces: TypeScript types, validation, defaults, the builder palette, the editing panel, inline click-to-edit, and the public-site renderer.
  • Three runtime modes, chosen by env: supabase (production), memory (zero-setup dev/demo, in-process store), static (no CMS at all — renders from site-content.ts; how sites launch before a client buys the CMS).

Install

npm install @orion-studios/cms

Requires Next.js (App Router), React 19, Zod 3. Supabase only in supabase mode.

Entry points

| Import | Contents | | --- | --- | | @orion-studios/cms/blocks | Blocks, globals, typed collections, and connected application definitions and registries | | @orion-studios/cms/server | createCmsRoutes, host application handlers, previews, content sync, and the permission matrix | | @orion-studios/cms/studio | Studio, password recovery and account controls, users, globals, collections, connected applications, and createStudioApi | | @orion-studios/cms/content | Anonymous published reads for pages, globals, collection lists, and collection details | | @orion-studios/cms/forms | Form config types, processSubmission (validation, honeypot, rate limiting), shared SiteForm renderer | | @orion-studios/cms/sql/bootstrap.sql | The complete idempotent schema (tables, RPCs, RLS, grants) | | @orion-studios/cms/studio/styles.css | Studio styles (self-contained, ost- prefixed) |

Blog content helpers

@orion-studios/cms/blocks exports createBlogPostSchema, defineBlogBlockExport, and createPostMarkdownExporter. These opt-in helpers reuse typed collection storage and block validation, and require explicit content or omission rules when exporting an article. They do not enable a blog or authorize publication. See the blog content contract for usage, limits, and the remaining editor and review work.

The 5-minute wiring (already done in the site template)

// src/app/api/cms/[...path]/route.ts — the entire CMS API
import { createCmsRoutes } from '@orion-studios/cms/server'
import { collections, connectedApplications, registry } from '@/cms/registry'
export const { GET, POST, PATCH, DELETE } = createCmsRoutes({
  registry,
  collections,
  connectedApplications: {
    registry: connectedApplications,
    handlers: hostApplicationHandlers,
  },
  syncToken: process.env.CMS_SYNC_TOKEN,
  cronToken: process.env.CRON_SECRET,
  previewSecret: process.env.CMS_PREVIEW_SECRET,
  previewKeyId: process.env.CMS_PREVIEW_KEY_ID,
  analyticsSecret: process.env.CMS_ANALYTICS_HASH_SECRET,
  autoReplyHashSecret: process.env.CMS_AUTO_REPLY_HASH_SECRET,
  projectRef: process.env.CMS_EXPECTED_SUPABASE_PROJECT_REF,
})

// src/app/studio/page.tsx — the admin
import { Studio } from '@orion-studios/cms/studio'
import '@orion-studios/cms/studio/styles.css'
export default function Page() {
  return <Studio
    registry={registry}
    globals={globals}
    collections={collections}
    connectedApplications={connectedApplications}
    recoveryRedirectUrl="https://client.example/studio"
    siteName="Client Co"
    logoUrl="/logo.png"
  />
}

Don't start here for a new site — copy templates/site from the orion-cms-packages repo instead; it has all of this wired plus the scripts (bootstrap, create-admin, sync). See NEW-SITE-PLAYBOOK.md at the repo root for the full procedure.

Roles

Four tiers, enforced in the API (src/server/permissions.ts), UI adapts:

  • content — edit text/images on existing sections (no add/remove/reorder)
  • editor: full content control including structure, new pages, exact-version publish, submissions, detailed analytics, and replacement of existing media
  • admin — everything + user management (Users panel in the Studio)
  • developer — full access; the tier Orion Studios keeps for itself

Rank rules: you can only assign roles at or below your own, never change your own role, never delete yourself.

Environment

| Variable | Where | Purpose | | --- | --- | --- | | NEXT_PUBLIC_SUPABASE_URL | local + Vercel | project URL (also selects supabase mode) | | NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | local + Vercel | public reads (RLS: published content only) | | SUPABASE_SECRET_KEY | local + Vercel | server-only API access; the legacy service-role alias remains supported | | CMS_EXPECTED_SUPABASE_PROJECT_REF | local + Vercel | binds the project URL, server credential, previews, and operator database target | | NEXT_PUBLIC_CMS_RECOVERY_URL | local + Vercel | absolute Studio password recovery return URL, also allowlisted in Supabase Auth | | CMS_DATABASE_URL | local operator only | direct or port 5432 session connection for bootstrap and migrations | | CMS_EXPECTED_DATABASE_ROLE | local operator only | dedicated CMS migration role with no app schema access | | CMS_EXPECTED_DATABASE_NAME | local operator only | exact database name required by the target guard | | CMS_DATABASE_CONFIRMATION | local operator only | exact redacted target-receipt hash required before database writes | | CMS_PREVIEW_SECRET | local + Vercel | signs short-lived, revocable preview grants | | CMS_PREVIEW_KEY_ID | local + Vercel | non-secret identifier bound into preview grants | | CMS_PREVIEW_PREVIOUS_SECRET, CMS_PREVIEW_PREVIOUS_KEY_ID, CMS_PREVIEW_PREVIOUS_VALID_UNTIL | local + Vercel | optional bounded preview-key rotation overlap | | CMS_ANALYTICS_HASH_SECRET | local + Vercel | hashes analytics session and visitor identifiers | | CMS_AUTO_REPLY_HASH_SECRET | local + Vercel | hashes form auto-reply rate-limit keys | | CMS_SYNC_TOKEN | local + Vercel | authorizes POST /api/cms/sync | | CRON_SECRET | local + Vercel | authorizes scheduled publication only | | CMS_GOOGLE_CLIENT_ID, CMS_GOOGLE_CLIENT_SECRET | local + Vercel | site-held Google OAuth client credentials | | CMS_GOOGLE_REDIRECT_URI | local + Vercel | this site's fixed /api/cms/integrations/google/callback URL | | CMS_GOOGLE_RELAY_URL | Vercel | optional owner-enabled Orion relay URL used as Google's registered redirect URI | | CMS_GOOGLE_OAUTH_STATE_SECRET | local + Vercel | signs the site origin, state id, and capability returned through OAuth | | CMS_GOOGLE_TOKEN_KEY_ID, CMS_GOOGLE_TOKEN_KEYS | local + Vercel | active key id and JSON keyring for encrypted refresh tokens and PKCE verifiers | | CMS_STATIC=true | optional | static mode (no CMS) | | CMS_MEMORY=true | optional | force memory mode |

The memory backend is privileged only outside production. In production it can serve public ephemeral forms and events, but it rejects Studio and guarded routes, the fixed development token, preview fallback, sync, publish cron, notification email, and user management. NEXT_PUBLIC_CMS_MEMORY and other public flags cannot enable the development credential.

Studio holds one Google account connection per site. Search Console, Business Profile, and Calendar are enabled separately, and each enable request asks only for that capability's missing scopes. The site stores and exchanges every token. Relay adoption, Google Cloud redirect registration, and app verification remain owner actions. Calendar availability is provided here for later booking work; booking behavior is not part of this package phase.

Publication contract

Draft title, SEO, and layout are saved together as one immutable page version. Manual publish requires that version's current draft_version_id. Scheduling stores the selected version ID and later draft edits do not change what will go live. A missing, stale, foreign, or already-published version fails with 409. Content roles may create media and edit its metadata. Only editor, admin, and developer roles may replace an existing file or read submissions and detailed analytics.

Globals use the same draft, exact publish, schedule, and restore rules. The legacy cms_globals.data column remains the published value used by public clients. Studio edits write draft_data only until an editor publishes the exact draft version.

Collections provide versioned entries for articles, portfolio work, and other structured lists. defineCollection() supplies the data schema, path prefix, editor fields, and renderer. Anonymous list and detail reads receive published projections only, with a maximum page size of 100. Draft preview tokens are bound to one entry, collection, version, project, and ten-minute window. Slug, path, display date, and sort order are versioned with the entry. Draft changes remain private, and a redirect is created only when a changed published path goes live. The content sync manifest accepts typed collections entries and seeds them as private drafts.

Set the top-level sync input to publication: 'draft' for a staged cutover. Page and global manifests then update only their draft snapshots. Their public projections remain unchanged until an editor publishes an exact version. The default remains published for existing sites.

Connected applications keep operational records in the host system. Use defineConnectedApplication() for presentation, safe preview, ownership, and protected settings schemas. Mount host handlers through createCmsRoutes. The package validates bearer identity, trusted CMS role, schema, and exact settings revision before calling a handler. Studio preview disables all submission and provider actions.

Password recovery

Studio users can request recovery from the sign-in screen and change their password from Account. Administrators can reset another lower-ranked user from Users without a browser prompt. Add each production, preview, and local Studio URL to Supabase Auth redirect URLs, then pass the matching absolute URL through recoveryRedirectUrl.

Successful self-service and recovery password updates record account.password.change with the authenticated user ID. The activity record does not contain an email, password, recovery token, or request body.

Configure custom SMTP for production password recovery. Supabase's default mail service is suitable only for initial testing and may restrict recipients, templates, and delivery volume. Never place reset tokens, passwords, or server keys in logs, activity rows, query parameters, or client-visible errors.

Run the additive SQL migrations in filename order before deploying code that calls the exact-version RPCs. bootstrap.sql has the same final schema, functions, grants, and published-metadata backfill for new projects. If a release fails after the migration, keep the fail-closed RPCs and roll forward. Do not restore the old mutable publish signature.

Development

npm run typecheck && npm test   # 24 node:test suites
npm run build                    # tsup → dist/
npm pack                         # tarball for vendoring / inspection

The SQL bootstrap is idempotent and runs under restricted Postgres roles: it avoids auth-schema privileges and explicitly grants the PostgREST roles (service_role, anon, authenticated) table access — RLS policies filter rows but do not grant access, and Supabase's default privileges only cover postgres-created tables.