@nby.ai/ucm
v1.1.0
Published
Universal Content Module - Supabase-based multilingual CMS
Downloads
188
Maintainers
Readme
@nby.ai/ucm
Universal Content Module - Supabase-based multilingual CMS
Features
- Multi-language content - JSONB storage, unlimited languages (zh, en, ja, ko, etc.)
- Multiple content types - news, blog, doc, event, thought, digest, briefing, case_study, faq
- Hierarchical categories - Multi-level category tree support
- Framework-agnostic core - Works with Node.js, scripts, any framework
- React integration - Provider + Hooks with TanStack React Query
- Full TypeScript support - Complete type definitions
Installation
# Core package
pnpm add @nby.ai/ucm @supabase/supabase-js
# If using React hooks
pnpm add @tanstack/react-query reactQuick Start
Core API (Node.js / Scripts)
import { createUCMClient } from '@nby.ai/ucm'
const ucm = createUCMClient({
url: process.env.SUPABASE_URL!,
anonKey: process.env.SUPABASE_ANON_KEY!,
})
// List blog posts
const posts = await ucm.contents.list({ type: 'blog', limit: 10 })
// Get single content by slug
const post = await ucm.contents.getBySlug('hello-world')
// Full-text search
const results = await ucm.contents.search('AI Agent')
// Get category tree
const categories = await ucm.categories.getTree()
// Create content (requires service key)
await ucm.contents.create({
slug: 'new-post',
type: 'blog',
title: { zh: '新文章', en: 'New Post' },
content: { zh: '正文...', en: 'Body...' },
})React Integration
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { UCMProvider, useContents, useContent, getLocalizedText } from '@nby.ai/ucm'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
<UCMProvider
config={{
url: import.meta.env.VITE_SUPABASE_URL,
anonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
}}
>
<BlogList />
</UCMProvider>
</QueryClientProvider>
)
}
function BlogList() {
const { contents, isLoading, error, loadMore, hasMore } = useContents({
type: 'blog',
limit: 10,
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<>
<ul>
{contents.map(post => (
<li key={post.id}>
{getLocalizedText(post.title, 'zh')}
</li>
))}
</ul>
{hasMore && <button onClick={loadMore}>Load More</button>}
</>
)
}API Reference
createUCMClient(config)
Creates a UCM client instance.
interface UCMClientConfig {
url: string // Supabase project URL
anonKey: string // Supabase anon key (or service key for admin ops)
options?: {
persistSession?: boolean // Default: false
autoRefreshToken?: boolean // Default: false
}
}UCMClient Methods
contents
| Method | Description |
|--------|-------------|
| list(params?) | List contents with filtering and pagination |
| getBySlug(slug) | Get content by slug |
| getById(id) | Get content by ID |
| count(params?) | Count contents matching params |
| search(query, type?) | Full-text search |
| getTags(type?) | Get all unique tags |
| create(input) | Create content (requires service key) |
| update(id, input) | Update content |
| delete(id) | Delete content |
| incrementView(id) | Increment view count |
categories
| Method | Description |
|--------|-------------|
| list(params?) | List categories |
| getTree() | Get hierarchical category tree |
| getBySlug(slug) | Get category by slug |
| getById(id) | Get category by ID |
| getChildren(parentId) | Get child categories |
| getRoots() | Get root categories |
| getBreadcrumb(categoryId) | Get category breadcrumb path |
React Hooks
| Hook | Description |
|------|-------------|
| useContents(options) | Fetch content list with infinite scroll |
| useContent(options) | Fetch single content by slug or ID |
| useCategories(options) | Fetch categories (flat or tree) |
| useSearchContents(options) | Search with debounce |
| useTags(options) | Fetch all tags |
| useAdjacentContents(options) | Get prev/next navigation |
| useThoughts(options) | Fetch agent thoughts |
Utility Functions
import { getLocalizedText, buildCategoryTree } from '@nby.ai/ucm'
// Get localized text with fallback
const title = getLocalizedText(content.title, 'zh', 'en')
// Build category tree from flat list
const tree = buildCategoryTree(flatCategories)Types
import type {
// Core types
Content,
ContentType,
ContentStatus,
ContentVisibility,
Category,
I18nText,
// Query params
ContentQueryParams,
CategoryQueryParams,
CreateContentInput,
UpdateContentInput,
// Metadata types
BlogMetadata,
NewsMetadata,
ThoughtMetadata,
ThoughtMood,
ThoughtType,
DigestMetadata,
BriefingMetadata,
CaseStudyMetadata,
EventMetadata,
DocMetadata,
} from '@nby.ai/ucm'Content Types
| Type | Description |
|------|-------------|
| blog | Blog posts, tutorials |
| news | Announcements, press releases |
| doc | Documentation, API docs |
| event | Events with time/location |
| thought | Agent thoughts (social feed style) |
| digest | Daily/weekly digests |
| briefing | Project briefings (daily/weekly/monthly) |
| case_study | Customer case studies |
| faq | Frequently asked questions |
I18nText
Multi-language text stored as JSONB:
interface I18nText {
zh?: string // Chinese
en?: string // English
ja?: string // Japanese
ko?: string // Korean
// ... more languages
}Database Schema
Requires Supabase with the following tables:
contents- Main content tablecategories- Hierarchical categories
See migrations for full schema.
Peer Dependencies
| Package | Version | Required |
|---------|---------|----------|
| @supabase/supabase-js | ^2.0.0 | Yes |
| @tanstack/react-query | ^5.0.0 | For React hooks |
| react | ^18.0.0 || ^19.0.0 | For React hooks |
License
MIT
Version 1.0.0 | Last updated: 2026-02-15
