@azlib/cms 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,7 @@ A framework-agnostic, modular Content Management System (CMS) runtime inspired b
12
12
  - 🗂️ **Hierarchical Taxonomies**: Nested category trees, flat tags, and custom taxonomy binding.
13
13
  - 🖼️ **Media & Asset Management**: MIME validation, file sanitization, metadata, and asset queries.
14
14
  - 🔒 **RBAC & Capability Matrix**: Built-in Administrator, Editor, Author, Contributor, and Subscriber roles with content ownership checks.
15
+ - 🔌 **Extensible Plugin System**: Package and distribute reusable plugins (`definePlugin`) with schema extensions, custom API routes, lifecycle hooks, and filter pipelines.
15
16
  - 🌐 **Universal Web Standard Router**: `Request` -> `Response` HTTP router ready for Next.js App Router, Remix, Vite, Astro, Express, or Cloudflare Workers.
16
17
  - 🚀 **Type-Safe Headless Client SDK**: Query content in-process or over HTTP with zero boilerplate (`createCmsClient`).
17
18
 
@@ -109,6 +110,198 @@ export async function POST(request: Request) {
109
110
 
110
111
  ---
111
112
 
113
+ ## Plugin System
114
+
115
+ Create modular, decoupled plugins that extend collections, inject custom fields, register custom Web Standard routes, and hook into lifecycle pipelines:
116
+
117
+ ```typescript
118
+ import { definePlugin, fields } from "@azlib/cms";
119
+
120
+ export const seoPlugin = definePlugin<{ defaultTitleSuffix?: string }>((options = {}) => ({
121
+ name: "seo-plugin",
122
+ version: "1.0.0",
123
+
124
+ // 1. Inject custom fields into existing collections
125
+ extendCollections: {
126
+ posts: [
127
+ fields.text({ name: "metaTitle", label: "Meta Title" }),
128
+ fields.text({ name: "metaDescription", label: "Meta Description" }),
129
+ ],
130
+ },
131
+
132
+ // 2. Programmatic setup for hooks and routes
133
+ setup({ hooks, registerRoute, engine }) {
134
+ // Intercept content before saving
135
+ hooks.addFilter("cms.before_create_input", (input: any) => {
136
+ if (input.title && options.defaultTitleSuffix && !input.data?.metaTitle) {
137
+ input.data = {
138
+ ...input.data,
139
+ metaTitle: `${input.title} | ${options.defaultTitleSuffix}`,
140
+ };
141
+ }
142
+ return input;
143
+ });
144
+
145
+ // Expose custom Web Standard API route
146
+ registerRoute("GET", "/api/seo/sitemap", async () => {
147
+ const posts = await engine.collection("posts").find({ status: "published" });
148
+ const urls = posts.items.map((p) => `https://example.com/posts/${p.slug}`);
149
+ return new Response(JSON.stringify({ urls }), {
150
+ headers: { "Content-Type": "application/json" },
151
+ });
152
+ });
153
+ },
154
+
155
+ // 3. Lifecycle callbacks
156
+ async onInit(engine) {
157
+ console.log("[SEO Plugin] Initialized");
158
+ },
159
+ }));
160
+ ```
161
+
162
+ Register plugins declaratively in `defineConfig` or dynamically via `cms.use()`:
163
+
164
+ ```typescript
165
+ // Declarative registration
166
+ export default defineConfig({
167
+ plugins: [seoPlugin({ defaultTitleSuffix: "Alex's Journal" })],
168
+ });
169
+
170
+ // Or dynamic registration
171
+ cms.use(seoPlugin());
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Built-in E-commerce Plugin
177
+
178
+ Turn `@azlib/cms` into a complete commerce backend with pre-defined schemas, catalog taxonomies, image uploading, coupon validation, cart calculation, inventory tracking, and orders:
179
+
180
+ ```typescript
181
+ import { createCMSEngine, defineConfig, ecommercePlugin, getEcommerceService } from "@azlib/cms";
182
+
183
+ // 1. Enable the plugin in your config
184
+ export default defineConfig({
185
+ plugins: [
186
+ ecommercePlugin({
187
+ defaultCurrency: "USD",
188
+ inventoryManagement: true,
189
+ defaultShippingCost: 15,
190
+ defaultTaxRate: 0.08, // 8%
191
+ }),
192
+ ],
193
+ });
194
+ ```
195
+
196
+ ### 1. Catalog & Product Management
197
+
198
+ ```typescript
199
+ const cms = createCMSEngine();
200
+ cms.use(ecommercePlugin());
201
+ await cms.init();
202
+
203
+ const commerce = getEcommerceService(cms);
204
+
205
+ // Create hierarchical catalog category
206
+ const apparel = await commerce.createCategory({ name: "Apparel", slug: "apparel" });
207
+ const shoes = await commerce.createCategory({ name: "Shoes", slug: "shoes", parentId: apparel.id });
208
+
209
+ // Create product with SKU, pricing, inventory, variants
210
+ const product = await commerce.createProduct({
211
+ title: "Aero Running Shoes",
212
+ price: 139.99,
213
+ compareAtPrice: 169.99,
214
+ sku: "RUN-AERO-01",
215
+ stock: 25,
216
+ status: "published",
217
+ categoryIds: [shoes.id],
218
+ variants: [
219
+ { id: "v-42", title: "Size 42", price: 139.99, stock: 10 },
220
+ { id: "v-43", title: "Size 43", price: 139.99, stock: 15 },
221
+ ],
222
+ });
223
+
224
+ // Upload and attach product images (auto-updates featured image and gallery)
225
+ await commerce.uploadProductImage(product.id, {
226
+ filename: "aero-shoes.jpg",
227
+ mimeType: "image/jpeg",
228
+ sizeBytes: 1048576,
229
+ url: "https://example.com/uploads/aero-shoes.jpg",
230
+ isFeatured: true,
231
+ });
232
+ ```
233
+
234
+ ### 2. Discounts & Coupon Engine
235
+
236
+ ```typescript
237
+ // Create promotional coupon
238
+ await commerce.createDiscount({
239
+ title: "Summer 20%",
240
+ code: "SUMMER20",
241
+ discountType: "percentage",
242
+ value: 20,
243
+ minOrderAmount: 50,
244
+ maxDiscountAmount: 30,
245
+ });
246
+
247
+ // Validate discount for a cart
248
+ const validation = await commerce.validateDiscount("SUMMER20", 100);
249
+ // => { valid: true, discountAmount: 20, code: "SUMMER20" }
250
+ ```
251
+
252
+ ### 3. Cart Calculation & Orders
253
+
254
+ ```typescript
255
+ // Calculate totals (items, discounts, shipping, and tax)
256
+ const cart = await commerce.calculateCart({
257
+ items: [{ productId: product.id, quantity: 2 }],
258
+ discountCode: "SUMMER20",
259
+ });
260
+
261
+ // Place an order (automatically decrements inventory and increments coupon counter)
262
+ const order = await commerce.createOrder({
263
+ customerEmail: "customer@example.com",
264
+ customerName: "Jane Doe",
265
+ items: [{ productId: product.id, quantity: 2 }],
266
+ discountCode: "SUMMER20",
267
+ shippingAddress: {
268
+ address1: "456 Market St",
269
+ city: "San Francisco",
270
+ country: "US",
271
+ postalCode: "94103",
272
+ },
273
+ });
274
+
275
+ console.log(`Order placed: #${order.data.orderNumber} (Total: $${order.data.total})`);
276
+ ```
277
+
278
+ ### 4. REST API & Headless Client
279
+
280
+ Mount the universal `createCMSRouter(cms)` to automatically expose REST endpoints:
281
+ - `GET /api/ecommerce/products` (supports `?category=...&minPrice=...&maxPrice=...&inStock=true&search=...`)
282
+ - `GET /api/ecommerce/products/:idOrSlug`
283
+ - `POST /api/ecommerce/products`
284
+ - `POST /api/ecommerce/products/:id/images`
285
+ - `GET /api/ecommerce/categories` (supports `?tree=true`)
286
+ - `POST /api/ecommerce/discounts/validate`
287
+ - `POST /api/ecommerce/cart/calculate`
288
+ - `POST /api/ecommerce/orders`
289
+ - `PATCH /api/ecommerce/orders/:id/status`
290
+
291
+ Or query via the Headless Client SDK:
292
+
293
+ ```typescript
294
+ import { createCmsClient, getEcommerceClient } from "@azlib/cms";
295
+
296
+ const client = createCmsClient({ baseUrl: "https://api.my-shop.com" });
297
+ const shop = getEcommerceClient(client);
298
+
299
+ const { items: products } = await shop.products.find({ minPrice: 50 });
300
+ const order = await shop.orders.create({ ... });
301
+ ```
302
+
303
+ ---
304
+
112
305
  ## License
113
306
 
114
307
  MIT © Google / azlib