@azlib/cms
v0.8.0
Published
A framework-agnostic, modular Content Management System (CMS) runtime inspired by WordPress, designed for modern TypeScript and headless web applications.
Readme
@azlib/cms
A framework-agnostic, modular Content Management System (CMS) runtime inspired by WordPress, designed for modern TypeScript and headless web applications.
Key Features
- ⚙️ Declarative Content Modeling: Define collections and custom fields using a clean, type-safe DSL (
defineConfig,collection,fields). - ⚡ Zero-Config Storage: In-memory storage for rapid prototyping and unit testing, with pluggable persistence adapters.
- 🪝 WordPress-Style Hook Pipeline: Full Action and Filter hook system (
addAction,doAction,addFilter,applyFilters) with priority ordering and async support. - 🕒 Publishing Workflow & Scheduler: Draft, Pending Review, Scheduled, Published, Private, and Trash lifecycle states with auto-publishing.
- 📜 Snapshots, Revisions & Rollback: Automatic version tracking, diff computation between revisions, and one-click rollback.
- 🗂️ Hierarchical Taxonomies: Nested category trees, flat tags, and custom taxonomy binding.
- 🖼️ Media & Asset Management: MIME validation, file sanitization, metadata, and asset queries.
- 🔒 RBAC & Capability Matrix: Built-in Administrator, Editor, Author, Contributor, and Subscriber roles with content ownership checks.
- 🔌 Extensible Plugin System: Package and distribute reusable plugins (
definePlugin) with schema extensions, custom API routes, lifecycle hooks, and filter pipelines. - 🌐 Universal Web Standard Router:
Request->ResponseHTTP router ready for Next.js App Router, Remix, Vite, Astro, Express, or Cloudflare Workers. - 🚀 Type-Safe Headless Client SDK: Query content in-process or over HTTP with zero boilerplate (
createCmsClient).
Quickstart
1. Define your CMS Configuration (azlib.config.ts)
import { defineConfig, collection, fields } from "@azlib/cms";
export default defineConfig({
site: {
name: "Alex's Tech Journal",
url: "http://localhost:3000",
},
collections: [
collection({
slug: "posts",
label: "Articles",
timestamps: true,
revisions: true,
taxonomies: ["categories", "tags"],
fields: [
fields.text({ name: "title", label: "Title", required: true }),
fields.slug({ from: "title" }),
fields.richText({ name: "content", label: "Content" }),
fields.image({ name: "featuredImage", label: "Featured Image" }),
fields.select({
name: "status",
options: ["draft", "published"],
defaultValue: "draft",
}),
],
}),
],
});2. Initialize the Engine
import { createCMSEngine } from "@azlib/cms";
import config from "./azlib.config.js";
const cms = createCMSEngine(config);
await cms.init();
// Create a post
const post = await cms.collection("posts").create({
title: "Hello World",
data: {
content: "Welcome to my new blog powered by @azlib/cms!",
},
});
console.log(`Created post with slug: ${post.slug}`);3. Query with the Headless Client SDK
import { createCmsClient } from "@azlib/cms";
const client = createCmsClient({ engine: cms });
// Find published articles
const { items: posts } = await client.collection("posts").find({
status: "published",
limit: 10,
});REST API Integration
Mount the universal router in any Web Standards or Node.js framework:
import { createCMSRouter } from "@azlib/cms";
const router = createCMSRouter(cms);
// In Next.js App Router (app/api/[...cms]/route.ts)
export async function GET(request: Request) {
return router.handle(request);
}
export async function POST(request: Request) {
return router.handle(request);
}Plugin System
Create modular, decoupled plugins that extend collections, inject custom fields, register custom Web Standard routes, and hook into lifecycle pipelines:
import { definePlugin, fields } from "@azlib/cms";
export const seoPlugin = definePlugin<{ defaultTitleSuffix?: string }>(
(options = {}) => ({
name: "seo-plugin",
version: "1.0.0",
// 1. Inject custom fields into existing collections
extendCollections: {
posts: [
fields.text({ name: "metaTitle", label: "Meta Title" }),
fields.text({ name: "metaDescription", label: "Meta Description" }),
],
},
// 2. Programmatic setup for hooks and routes
setup({ hooks, registerRoute, engine }) {
// Intercept content before saving
hooks.addFilter("cms.before_create_input", (input: any) => {
if (
input.title &&
options.defaultTitleSuffix &&
!input.data?.metaTitle
) {
input.data = {
...input.data,
metaTitle: `${input.title} | ${options.defaultTitleSuffix}`,
};
}
return input;
});
// Expose custom Web Standard API route
registerRoute("GET", "/api/seo/sitemap", async () => {
const posts = await engine
.collection("posts")
.find({ status: "published" });
const urls = posts.items.map(
(p) => `https://example.com/posts/${p.slug}`,
);
return new Response(JSON.stringify({ urls }), {
headers: { "Content-Type": "application/json" },
});
});
},
// 3. Lifecycle callbacks
async onInit(engine) {
console.log("[SEO Plugin] Initialized");
},
}),
);Register plugins declaratively in defineConfig or dynamically via cms.use():
// Declarative registration
export default defineConfig({
plugins: [seoPlugin({ defaultTitleSuffix: "Alex's Journal" })],
});
// Or dynamic registration
cms.use(seoPlugin());Built-in E-commerce Plugin
Turn @azlib/cms into a complete commerce backend with pre-defined schemas, catalog taxonomies, image uploading, coupon validation, cart calculation, inventory tracking, and orders:
import {
createCMSEngine,
defineConfig,
ecommercePlugin,
getEcommerceService,
} from "@azlib/cms";
// 1. Enable the plugin in your config
export default defineConfig({
plugins: [
ecommercePlugin({
defaultCurrency: "USD",
inventoryManagement: true,
defaultShippingCost: 15,
defaultTaxRate: 0.08, // 8%
}),
],
});1. Catalog & Product Management
const cms = createCMSEngine();
cms.use(ecommercePlugin());
await cms.init();
const commerce = getEcommerceService(cms);
// Create hierarchical catalog category
const apparel = await commerce.createCategory({
name: "Apparel",
slug: "apparel",
});
const shoes = await commerce.createCategory({
name: "Shoes",
slug: "shoes",
parentId: apparel.id,
});
// Create product with SKU, pricing, inventory, variants
const product = await commerce.createProduct({
title: "Aero Running Shoes",
price: 139.99,
compareAtPrice: 169.99,
sku: "RUN-AERO-01",
stock: 25,
status: "published",
categoryIds: [shoes.id],
variants: [
{ id: "v-42", title: "Size 42", price: 139.99, stock: 10 },
{ id: "v-43", title: "Size 43", price: 139.99, stock: 15 },
],
});
// Upload and attach product images (auto-updates featured image and gallery)
await commerce.uploadProductImage(product.id, {
filename: "aero-shoes.jpg",
mimeType: "image/jpeg",
sizeBytes: 1048576,
url: "https://example.com/uploads/aero-shoes.jpg",
isFeatured: true,
});2. Discounts & Coupon Engine
// Create promotional coupon
await commerce.createDiscount({
title: "Summer 20%",
code: "SUMMER20",
discountType: "percentage",
value: 20,
minOrderAmount: 50,
maxDiscountAmount: 30,
});
// Validate discount for a cart
const validation = await commerce.validateDiscount("SUMMER20", 100);
// => { valid: true, discountAmount: 20, code: "SUMMER20" }3. Cart Calculation & Orders
// Calculate totals (items, discounts, shipping, and tax)
const cart = await commerce.calculateCart({
items: [{ productId: product.id, quantity: 2 }],
discountCode: "SUMMER20",
});
// Place an order (automatically decrements inventory and increments coupon counter)
const order = await commerce.createOrder({
customerEmail: "[email protected]",
customerName: "Jane Doe",
items: [{ productId: product.id, quantity: 2 }],
discountCode: "SUMMER20",
shippingAddress: {
address1: "456 Market St",
city: "San Francisco",
country: "US",
postalCode: "94103",
},
});
console.log(
`Order placed: #${order.data.orderNumber} (Total: $${order.data.total})`,
);4. REST API & Headless Client
Mount the universal createCMSRouter(cms) to automatically expose REST endpoints:
GET /api/ecommerce/products(supports?category=...&minPrice=...&maxPrice=...&inStock=true&search=...)GET /api/ecommerce/products/:idOrSlugPOST /api/ecommerce/productsPOST /api/ecommerce/products/:id/imagesGET /api/ecommerce/categories(supports?tree=true)POST /api/ecommerce/discounts/validatePOST /api/ecommerce/cart/calculatePOST /api/ecommerce/ordersPATCH /api/ecommerce/orders/:id/status
Or query via the Headless Client SDK:
import { createCmsClient, getEcommerceClient } from "@azlib/cms";
const client = createCmsClient({ baseUrl: "https://api.my-shop.com" });
const shop = getEcommerceClient(client);
const { items: products } = await shop.products.find({ minPrice: 50 });
const order = await shop.orders.create({ ... });Built-in HRMS Plugin (hrmsPlugin)
@azlib/cms includes a full-featured Human Resource Management System (HRMS) plugin for multi-tenant companies, employee directory profiles, attendance tracking, and leave quota management.
Features
- 🏢 Multi-Tenant Employers: Manage distinct companies/organizations with custom work schedules, timezones, and grace periods.
- 👤 Employee Directory: Rich employee profiles, contract/document attachments, emergency contacts, manager hierarchies, and department taxonomies.
- ⏱️ Daily Attendance Tracking: Clock-in and clock-out with automated duration calculation, overtime hours, and late-arrival detection against work schedules.
- 🏖️ Leave Quota & Approval Workflow: Custom leave types (Annual, Sick, Unpaid), balance ledger reports, and manager approval/rejection pipelines.
- 🌐 Web Standard REST APIs & Client SDK: Pre-mounted routes under
/api/hrms/*and typedHRMSClientSDK.
Usage
import {
createCMSEngine,
defineConfig,
hrmsPlugin,
getHRMSService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [
hrmsPlugin({
workScheduleStart: "09:00",
workScheduleEnd: "17:00",
standardWorkDayHours: 8,
gracePeriodMinutes: 15,
}),
],
});
const cms = createCMSEngine(config);
await cms.init();
const hrms = getHRMSService(cms);
// 1. Create an Employer
const employer = await hrms.createEmployer({
companyName: "Acme Corp",
timezone: "America/New_York",
});
// 2. Add an Employee
const employee = await hrms.createEmployee({
employerId: employer.id,
employeeNumber: "ACME-001",
firstName: "Jane",
lastName: "Doe",
email: "[email protected]",
hireDate: "2024-01-15",
});
// 3. Employee Check-In & Check-Out
await hrms.checkIn({ employeeId: employee.id });
await hrms.checkOut({ employeeId: employee.id });
// 4. Request Leave & Check Balance
const leaveReport = await hrms.calculateLeaveBalance(employee.id, 2026);
console.log(`Remaining days: ${leaveReport.totalRemaining}`);Built-in SEO & Metadata Plugin (seoPlugin)
Automates dynamic XML sitemap generation, search engine robots.txt, OpenGraph/Twitter social cards, JSON-LD structured data, and content readability/SEO auditing.
import {
createCMSEngine,
defineConfig,
seoPlugin,
getSeoService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [
seoPlugin({
siteUrl: "https://example.com",
defaultTitle: "My Site",
titleTemplate: "%s | My Site",
autoExtendCollections: ["posts", "pages"], // Injects SEO fields into posts/pages
}),
],
});
const cms = createCMSEngine(config);
await cms.init();
// Exposes REST endpoints:
// GET /sitemap.xml
// GET /robots.txt
// GET /api/seo/metadata/:collection/:id
// POST /api/seo/analyzeBuilt-in Search Indexing Plugin (searchPlugin)
High-performance inverted-index full-text search with automatic real-time indexing across content create, update, publish, and delete hooks.
import {
createCMSEngine,
defineConfig,
searchPlugin,
getSearchService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [
searchPlugin({
enableAutoIndex: true, // Automatically updates search index on content mutations
}),
],
});
const cms = createCMSEngine(config);
await cms.init();
const searchService = getSearchService(cms);
const results = await searchService.search({ q: "microservices", limit: 10 });
// Exposes REST endpoints:
// GET /api/search?q=...&collections=...&limit=...
// POST /api/search/reindexBuilt-in Audit Log Plugin (auditLogPlugin)
Immutable enterprise compliance logging tracking all mutations across content, media, taxonomies, options, and auth with field-level diffs.
import {
createCMSEngine,
defineConfig,
auditLogPlugin,
getAuditLogService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [auditLogPlugin()],
});
const cms = createCMSEngine(config);
await cms.init();
const auditService = getAuditLogService(cms);
const history = await auditService.query({ action: "update", limit: 20 });
// Exposes REST endpoints:
// GET /api/audit-logs?action=...&actorId=...
// GET /api/audit-logs/:idBuilt-in Dynamic Forms Plugin (formsPlugin)
Dynamic form schema builder with honeypot anti-spam protection, submission validation, and storage.
import {
createCMSEngine,
defineConfig,
formsPlugin,
getFormsService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [formsPlugin()],
});
const cms = createCMSEngine(config);
await cms.init();
const formsService = getFormsService(cms);
await formsService.registerForm({
slug: "contact",
title: "Contact Us",
fields: [
{ name: "fullName", label: "Full Name", type: "text", required: true },
{ name: "email", label: "Email", type: "email", required: true },
{ name: "message", label: "Message", type: "textarea", required: true },
],
});
// Exposes REST endpoints:
// GET /api/forms
// GET /api/forms/:slug
// POST /api/forms/:slug/submit
// GET /api/forms/:slug/submissions
// PUT /api/forms/submissions/:id/statusBuilt-in Frontend Cache & Revalidation Plugin (revalidationPlugin)
On-demand cache invalidation and ISR revalidation (Next.js, Vercel, Cloudflare, Astro) dispatching HMAC-signed webhook requests.
import {
createCMSEngine,
defineConfig,
revalidationPlugin,
getRevalidationService,
} from "@azlib/cms";
const config = defineConfig({
plugins: [
revalidationPlugin({
endpoints: [
{
url: "https://my-frontend.com/api/revalidate",
secret: process.env.REVALIDATION_SECRET,
},
],
collectionPathMap: {
posts: (item) => [`/blog/${item.slug}`, "/blog"],
},
}),
],
});
const cms = createCMSEngine(config);
await cms.init();
// Automatically derives tags and paths and notifies frontends with HMAC signature.
// Exposes REST endpoints:
// POST /api/revalidate
// GET /api/revalidate/historyLicense
MIT © Google / azlib
