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

@git-cms/client

v0.1.8

Published

TypeScript SDK for GitCMS

Downloads

79

Readme

@git-cms/client

TypeScript SDK for GitCMS - Universal GitHub-Based Content Management System

npm version License: MIT TypeScript

GitCMS transforms your GitHub repository into a powerful, type-safe content management system. Perfect for blogs, documentation sites, portfolios, and any content-driven application.

✨ Features

  • 🚀 Zero Backend Required - Works with public GitHub repos client-side
  • 🔐 Secure Authentication - Private repo support with token-based auth
  • 📝 Markdown & JSON - Native support for both content formats
  • 🖼️ Rich Media - Images, videos, audio, documents, 3D models
  • 🎯 Type-Safe - Full TypeScript support with comprehensive types
  • 🔍 Powerful Queries - Filter, sort, and paginate content with nested field access
  • 🎨 Two Transport Modes - Public or authenticated (proxy mode removed)
  • Progressive Enhancement - Fast thumbnails, async full resolution
  • 🌐 Framework Agnostic - Works with React, Vue, Next.js, vanilla JS

📦 Installation

npm install @git-cms/client

🚀 Quick Start

Public Repository (No Token Required)

For public GitHub repositories, you can use GitCMS without any authentication:

import { GitCMS } from '@git-cms/client';

// Initialize for public repository
const cms = new GitCMS({
  repository: 'username/my-blog',
});

// Fetch all blog posts
const posts = await cms.from('posts').get();

// Fetch a single post
const post = await cms.from('posts').doc('my-first-post').get();

Private Repository (Authenticated)

For private repositories or higher rate limits, provide a GitHub token (server-side only):

import { GitCMS } from '@git-cms/client';

// Initialize with authentication (server-side only!)
const cms = new GitCMS({
  repository: 'username/my-private-blog',
  token: process.env.GITHUB_TOKEN, // Never expose tokens client-side!
});

// Same API as public mode
const posts = await cms.from('posts').get();

Authenticated Mode with Media Proxying

For secure media handling with private repositories, use the API endpoint:

import { GitCMS } from '@git-cms/client';

// Initialize with media API endpoint
const cms = new GitCMS({
  repository: 'username/my-blog',
  token: process.env.GITHUB_TOKEN, // Server-side only
  apiEndpoint: '/api/media', // Your Express/Next.js API endpoint
});

// Media URLs will automatically use the API endpoint for secure proxying
const posts = await cms.from('posts').get();

Example Express endpoint for media proxying:

// server.js
import express from 'express';
import cors from 'cors';
import { getMediaMapping } from '@git-cms/client';

const app = express();
app.use(cors({ origin: 'http://localhost:3000' }));

app.get('/api/media/:mediaId', async (req, res) => {
  const { mediaId } = req.params;
  const token = process.env.GITHUB_TOKEN;

  // Get the actual GitHub URL from media ID
  const mapping = getMediaMapping(mediaId);
  if (!mapping) {
    return res.status(404).send('Media not found');
  }

  // Fetch from GitHub with authentication
  const response = await fetch(mapping.url, {
    headers: { Authorization: `Bearer ${token}` },
  });

  const buffer = await response.arrayBuffer();

  // Set proper headers
  res.setHeader('Content-Type', mapping.mimeType || 'application/octet-stream');
  res.setHeader('Content-Length', buffer.byteLength);
  res.setHeader('Cache-Control', 'public, max-age=31536000');

  // Send raw binary data
  res.send(Buffer.from(buffer));
});

app.listen(5000);

⚙️ Configuration

interface GitCMSConfig {
  repository: string; // GitHub repository in format 'owner/repo'
  branch?: string; // Git branch (default: 'main')
  token?: string; // GitHub personal access token (optional for public repos)
  apiEndpoint?: string; // API endpoint for media proxying (authenticated mode)
  transport?: 'public' | 'authenticated'; // Force specific transport mode
}

Transport Modes

GitCMS automatically selects the transport mode based on your configuration:

1. Public Mode (Default for public repos)

  • When: No token provided
  • Best for: Public repositories, client-side applications
  • Rate limits: 60 requests/hour per IP (generous on raw URLs)
  • Security: No credentials exposed
  • Media: Direct GitHub URLs (public access)
const cms = new GitCMS({
  repository: 'username/public-blog',
  // No token needed!
});

2. Authenticated Mode (For private repos or higher limits)

  • When: token provided
  • Best for: Private repositories, server-side apps, higher rate limits
  • Rate limits: 5,000 requests/hour (authenticated)
  • Security: Keep token server-side only
  • Media: Use apiEndpoint for secure proxying
const cms = new GitCMS({
  repository: 'username/private-blog',
  token: process.env.GITHUB_TOKEN, // Server-side only!
  apiEndpoint: '/api/media', // Optional: for media proxying
});

Checking Current Transport Mode

const cms = new GitCMS({
  repository: 'username/my-blog',
});

console.log(cms.getTransportMode()); // 'public' or 'authenticated'
console.log(cms.isPublicMode()); // true if in public mode

Rate Limit Information

Monitor your GitHub API rate limits (public and authenticated modes):

const cms = new GitCMS({
  repository: 'username/my-blog',
  token: 'ghp_xxx',
});

const rateLimit = await cms.getRateLimit();
if (rateLimit) {
  console.log(`Remaining: ${rateLimit.remaining}/${rateLimit.limit}`);
  console.log(`Resets at: ${rateLimit.reset}`);
}

🔍 Querying Content

Query content by schema type using the SQL-like from() method.

// Get all items from a schema
const products = await cms.from('products').get();

// Simple field filters
const electronics = await cms
  .from('products')
  .where('category', '==', 'electronics')
  .where('inStock', true)
  .get();

// Nested field access with dot notation
const published = await cms
  .from('posts')
  .where('metadata.status', '==', 'published')
  .where('author.verified', true)
  .get();

// Order by nested fields
const latestPosts = await cms
  .from('posts')
  .orderBy('metadata.publishedAt', 'desc')
  .get();

// Multiple order criteria (tiebreakers)
const prioritizedPosts = await cms
  .from('posts')
  .where('metadata.status', '==', 'published')
  .orderBy('metadata.priority', 'desc') // Primary sort
  .orderBy('metadata.publishedAt', 'desc') // Tiebreaker
  .get();

// Limit results
const featured = await cms
  .from('products')
  .where('featured', true)
  .limit(5)
  .get();

Nested Field Access

GitCMS supports dot notation for accessing nested fields:

// Access nested fields
await cms.from('posts').where('metadata.status', '==', 'published').get();
await cms.from('posts').where('author.profile.verified', true).get();
await cms.from('products').where('pricing.retail', '<', 100).get();

// Works with orderBy too
await cms.from('products').orderBy('ratings.average', 'desc').get();
await cms.from('posts').orderBy('stats.views', 'desc').get();

How it works:

  • Fields are checked in the exact path specified: item.metadata.status
  • If not found, tries item.data.metadata.status (backward compatibility)
  • Supports any nesting depth: a.b.c.d.e...

Supported Operators

'==' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'contains';

Examples:

// Equality
.where('status', '==', 'published')
.where('verified', true)

// Comparison
.where('views', '>', 1000)
.where('price', '<=', 99.99)

// In array
.where('category', 'in', ['tech', 'tutorial', 'guide'])

// Contains (array field contains value)
.where('tags', 'contains', 'javascript')

📄 Documents

Access individual content items by their ID:

// Get a single document from a schema
const post = await cms.from('posts').doc('my-post-id').get();

// Get a standalone document
const config = await cms.doc('site-config').get();

∑ LaTeX In Rich Text

GitCMS rich text equations are stored as <gitcms-math> tags.

The client SDK now includes automatic math resolution when you use resolveToolcalls or resolveToolcallsAsync.

import { resolveToolcalls } from '@git-cms/client';

const html = post.data.content || '';

// Even with no toolcall renderers, this now resolves <gitcms-math> by default.
const enhanced = resolveToolcalls(html, {});

If you only need math conversion (without toolcalls), use resolveMath:

import { resolveMath } from '@git-cms/client';

const enhanced = resolveMath(post.data.content || '');

Optional: KaTeX visual style

By default, GitCMS uses MathML output so equations are visible without CSS. If you want KaTeX HTML styling instead, use:

import { resolveMath } from '@git-cms/client';
import 'katex/dist/katex.min.css';

const enhanced = resolveMath(post.data.content || '', {
  output: 'htmlAndMathml',
});

🖼️ Media Management

GitCMS provides a powerful media API for working with embedded media in your content. It supports fast thumbnail loading with async full-resolution fetching for optimal performance.

Quick Example

import { GitCMS } from '@git-cms/client';

const cms = new GitCMS({
  repository: 'owner/repo',
  token: 'your-token',
});

// Get a post with embedded media
const post = await cms.from('posts').doc('my-post').get();

// Extract media references (fast, synchronous)
const mediaRefs = cms.media.extractFromHTML(post.content);

// Get thumbnails immediately (fast)
const thumbnail = cms.media.getThumbnail(mediaRefs[0]);

// Fetch full resolution asynchronously
const fullMedia = await cms.media.fetchFull(mediaRefs[0]);

Progressive Enhancement Pattern

// 1. Render with thumbnails immediately
const fastHtml = cms.media.renderFast(post.content);
document.getElementById('content').innerHTML = fastHtml;

// 2. Load full resolution in background
const fullHtml = await cms.media.renderFull(post.content, {
  onProgress: (current, total, ref) => {
    console.log(`Loading ${current}/${total}: ${ref.filename}`);
  },
});
document.getElementById('content').innerHTML = fullHtml;

Content Helper

For convenient media operations on entire content items:

// Extract ALL media (rich-text + fields)
const allMedia = cms.contentMedia.extractAll(post);

// Get all thumbnails
const thumbnails = cms.contentMedia.getThumbnails(post);

// Preload all media
const fullMediaMap = await cms.contentMedia.preloadAll(post);

// Render entire content item
const fastPost = cms.contentMedia.renderFast(post);
const fullPost = await cms.contentMedia.renderFull(post);

Supported Media Types

  • Images: jpg, png, gif, webp, svg, etc.
  • Videos: mp4, webm, mov, etc.
  • Audio: mp3, wav, ogg, etc.
  • 3D Models: glb, gltf, obj, fbx
  • Documents: pdf, doc, docx, txt

Video & Document Embedding

import {
  injectMediaStyles,
  enableProgressiveMediaLoading,
} from '@git-cms/client';

// 1. Inject styles (once)
injectMediaStyles();

// 2. Render content with media
const html = cms.media.renderFast(content); // Shows thumbnails/placeholders
container.innerHTML = html;

// 3. Enable click-to-load for videos/documents
enableProgressiveMediaLoading(container, cms.media);

How it works:

  • Videos: Fast render shows poster image with play button → Full render embeds actual <video> element
  • Documents: Fast render shows thumbnail preview → Full render provides download link
  • Images: Fast render shows thumbnail → Full render shows high-resolution image
  • Audio: Fast render shows icon → Full render embeds <audio> element

Media API Features

  • 🚀 Fast thumbnails: Instant display with embedded base64 data
  • 🔄 Async loading: Progressive enhancement for full resolution
  • 💾 Smart caching: Automatic caching of fetched media
  • 🎯 Type-safe: Full TypeScript support
  • 🎨 Multiple formats: Images, videos, audio, 3D models, documents
  • LFS support: Handles Git LFS files automatically

🔒 Security Best Practices

⚠️ NEVER Expose Tokens Client-Side

// ❌ BAD: Token in client-side code
const cms = new GitCMS({
  repository: 'username/blog',
  token: 'ghp_xxxxxxxxxxxxx', // NEVER DO THIS!
});

// ✅ GOOD: Use public mode for client-side
const cms = new GitCMS({
  repository: 'username/blog',
  // No token - safe for browsers
});

// ✅ GOOD: Token in server environment
const cms = new GitCMS({
  repository: 'username/blog',
  token: process.env.GITHUB_TOKEN, // Server-only
});

Rate Limit Considerations

Public Mode: 60 requests/hour per IP

  • Best for: Low-traffic sites, development
  • Consider: Static generation or caching for high traffic

Authenticated Mode: 5,000 requests/hour

  • Best for: Server-side applications, high-traffic sites
  • Consider: Only use server-side, never expose token

📚 Recommended Usage Patterns

Client-Side React/Vue/Next.js Application (Public Repository)

Best Practice: Use public mode - no backend needed!

// app/lib/cms.ts
import { GitCMS } from '@git-cms/client';

export const cms = new GitCMS({
  repository: 'username/blog-content',
  // No token - completely safe for client-side
});

// app/page.tsx (Next.js App Router)
import { cms } from '@/lib/cms';

export default async function BlogPage() {
  const posts = await cms
    .from('posts')
    .where('metadata.status', '==', 'published')
    .orderBy('metadata.publishedAt', 'desc')
    .get();

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

Server-Side Application (Private Repository)

Best Practice: Use authenticated mode with environment variables

// lib/cms.ts (server-side only)
import { GitCMS } from '@git-cms/client';

if (!process.env.GITHUB_TOKEN) {
  throw new Error('GITHUB_TOKEN is required for private repositories');
}

export const cms = new GitCMS({
  repository: 'company/private-content',
  token: process.env.GITHUB_TOKEN,
});

// NEVER expose this client to the browser!

Next.js with Server Actions (Hybrid Approach)

Best Practice: Keep token server-side, expose public API

// app/actions/content.ts (Server Action)
'use server';

import { GitCMS } from '@git-cms/client';

const cms = new GitCMS({
  repository: 'username/blog',
  token: process.env.GITHUB_TOKEN, // Server-side only
});

export async function getPosts() {
  return await cms
    .from('posts')
    .where('metadata.status', '==', 'published')
    .get();
}

// app/page.tsx (Client Component)
import { getPosts } from './actions/content';

export default async function Page() {
  const posts = await getPosts();
  return <PostList posts={posts} />;
}

Mobile App or Static Site Generator

Best Practice: Fetch at build time, no runtime API calls

// build-time script or static site generator
import { GitCMS } from '@git-cms/client';

const cms = new GitCMS({
  repository: 'username/app-content',
  // Public repo - no token needed
});

async function generateStaticData() {
  const posts = await cms.from('posts').get();
  const pages = await cms.from('pages').get();

  // Write to static JSON files
  await fs.writeFile('data/posts.json', JSON.stringify(posts));
  await fs.writeFile('data/pages.json', JSON.stringify(pages));
}

generateStaticData();

📐 Repository Structure

Your GitHub repository should follow this structure:

repository/
├── .gitcms/
│   ├── config.json
│   └── schemas/
│       ├── blog-post.json
│       └── product.json
├── content/
│   ├── posts/
│   │   ├── my-first-post.md
│   │   └── another-post.json
│   ├── products/
│   │   └── awesome-product.json
│   └── about.json
└── media/
    └── images/

📝 Content Formats

GitCMS supports both JSON and Markdown files:

JSON Content

{
  "id": "my-post",
  "title": "My Blog Post",
  "content": "This is the content...",
  "published": true,
  "publishedAt": "2024-01-15T10:00:00Z"
}

Markdown with Frontmatter

---
title: My Blog Post
published: true
publishedAt: 2024-01-15T10:00:00Z
---

This is the content of my blog post written in **Markdown**.

🎯 TypeScript Support

The SDK is fully typed and provides excellent TypeScript support:

interface BlogPost {
  id: string;
  title: string;
  content: string;
  metadata: {
    status: 'draft' | 'published' | 'archived';
    publishedAt: string;
    featured: boolean;
  };
  author: {
    name: string;
    verified: boolean;
  };
}

const posts = (await cms.from('posts').get()) as BlogPost[];

// Type-safe queries
const published = (await cms
  .from('posts')
  .where('metadata.status', '==', 'published')
  .get()) as BlogPost[];

⚠️ Error Handling

try {
  const posts = await cms.from('posts').get();
} catch (error) {
  console.error('Failed to fetch posts:', error);
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT License - see the LICENSE file for details.

🔗 Links

🌟 What's New

Version 0.1.0

  • ✨ Two transport modes: public and authenticated
  • ✨ Nested field access with dot notation
  • ✨ Media API with progressive enhancement
  • ✨ Video and document embedding support
  • ✨ Full TypeScript support
  • ✨ Secure media proxying with API endpoints
  • 🔧 Rate limit monitoring
  • 📝 Comprehensive documentation

Made with ❤️ by Manuel Maiuolo