@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 +193 -0
- package/dist/index.cjs +1496 -27
- package/dist/index.d.cts +829 -227
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +829 -227
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1487 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|