@azlib/cms 0.3.0 → 0.5.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 +257 -38
- package/dist/index.cjs +2905 -6
- package/dist/index.d.cts +933 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +933 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2886 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,46 +117,56 @@ Create modular, decoupled plugins that extend collections, inject custom fields,
|
|
|
117
117
|
```typescript
|
|
118
118
|
import { definePlugin, fields } from "@azlib/cms";
|
|
119
119
|
|
|
120
|
-
export const seoPlugin = definePlugin<{ defaultTitleSuffix?: string }>(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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" },
|
|
120
|
+
export const seoPlugin = definePlugin<{ defaultTitleSuffix?: string }>(
|
|
121
|
+
(options = {}) => ({
|
|
122
|
+
name: "seo-plugin",
|
|
123
|
+
version: "1.0.0",
|
|
124
|
+
|
|
125
|
+
// 1. Inject custom fields into existing collections
|
|
126
|
+
extendCollections: {
|
|
127
|
+
posts: [
|
|
128
|
+
fields.text({ name: "metaTitle", label: "Meta Title" }),
|
|
129
|
+
fields.text({ name: "metaDescription", label: "Meta Description" }),
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// 2. Programmatic setup for hooks and routes
|
|
134
|
+
setup({ hooks, registerRoute, engine }) {
|
|
135
|
+
// Intercept content before saving
|
|
136
|
+
hooks.addFilter("cms.before_create_input", (input: any) => {
|
|
137
|
+
if (
|
|
138
|
+
input.title &&
|
|
139
|
+
options.defaultTitleSuffix &&
|
|
140
|
+
!input.data?.metaTitle
|
|
141
|
+
) {
|
|
142
|
+
input.data = {
|
|
143
|
+
...input.data,
|
|
144
|
+
metaTitle: `${input.title} | ${options.defaultTitleSuffix}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return input;
|
|
151
148
|
});
|
|
152
|
-
});
|
|
153
|
-
},
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
})
|
|
150
|
+
// Expose custom Web Standard API route
|
|
151
|
+
registerRoute("GET", "/api/seo/sitemap", async () => {
|
|
152
|
+
const posts = await engine
|
|
153
|
+
.collection("posts")
|
|
154
|
+
.find({ status: "published" });
|
|
155
|
+
const urls = posts.items.map(
|
|
156
|
+
(p) => `https://example.com/posts/${p.slug}`,
|
|
157
|
+
);
|
|
158
|
+
return new Response(JSON.stringify({ urls }), {
|
|
159
|
+
headers: { "Content-Type": "application/json" },
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// 3. Lifecycle callbacks
|
|
165
|
+
async onInit(engine) {
|
|
166
|
+
console.log("[SEO Plugin] Initialized");
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
160
170
|
```
|
|
161
171
|
|
|
162
172
|
Register plugins declaratively in `defineConfig` or dynamically via `cms.use()`:
|
|
@@ -173,6 +183,215 @@ cms.use(seoPlugin());
|
|
|
173
183
|
|
|
174
184
|
---
|
|
175
185
|
|
|
186
|
+
## Built-in E-commerce Plugin
|
|
187
|
+
|
|
188
|
+
Turn `@azlib/cms` into a complete commerce backend with pre-defined schemas, catalog taxonomies, image uploading, coupon validation, cart calculation, inventory tracking, and orders:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import {
|
|
192
|
+
createCMSEngine,
|
|
193
|
+
defineConfig,
|
|
194
|
+
ecommercePlugin,
|
|
195
|
+
getEcommerceService,
|
|
196
|
+
} from "@azlib/cms";
|
|
197
|
+
|
|
198
|
+
// 1. Enable the plugin in your config
|
|
199
|
+
export default defineConfig({
|
|
200
|
+
plugins: [
|
|
201
|
+
ecommercePlugin({
|
|
202
|
+
defaultCurrency: "USD",
|
|
203
|
+
inventoryManagement: true,
|
|
204
|
+
defaultShippingCost: 15,
|
|
205
|
+
defaultTaxRate: 0.08, // 8%
|
|
206
|
+
}),
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 1. Catalog & Product Management
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const cms = createCMSEngine();
|
|
215
|
+
cms.use(ecommercePlugin());
|
|
216
|
+
await cms.init();
|
|
217
|
+
|
|
218
|
+
const commerce = getEcommerceService(cms);
|
|
219
|
+
|
|
220
|
+
// Create hierarchical catalog category
|
|
221
|
+
const apparel = await commerce.createCategory({
|
|
222
|
+
name: "Apparel",
|
|
223
|
+
slug: "apparel",
|
|
224
|
+
});
|
|
225
|
+
const shoes = await commerce.createCategory({
|
|
226
|
+
name: "Shoes",
|
|
227
|
+
slug: "shoes",
|
|
228
|
+
parentId: apparel.id,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Create product with SKU, pricing, inventory, variants
|
|
232
|
+
const product = await commerce.createProduct({
|
|
233
|
+
title: "Aero Running Shoes",
|
|
234
|
+
price: 139.99,
|
|
235
|
+
compareAtPrice: 169.99,
|
|
236
|
+
sku: "RUN-AERO-01",
|
|
237
|
+
stock: 25,
|
|
238
|
+
status: "published",
|
|
239
|
+
categoryIds: [shoes.id],
|
|
240
|
+
variants: [
|
|
241
|
+
{ id: "v-42", title: "Size 42", price: 139.99, stock: 10 },
|
|
242
|
+
{ id: "v-43", title: "Size 43", price: 139.99, stock: 15 },
|
|
243
|
+
],
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Upload and attach product images (auto-updates featured image and gallery)
|
|
247
|
+
await commerce.uploadProductImage(product.id, {
|
|
248
|
+
filename: "aero-shoes.jpg",
|
|
249
|
+
mimeType: "image/jpeg",
|
|
250
|
+
sizeBytes: 1048576,
|
|
251
|
+
url: "https://example.com/uploads/aero-shoes.jpg",
|
|
252
|
+
isFeatured: true,
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 2. Discounts & Coupon Engine
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// Create promotional coupon
|
|
260
|
+
await commerce.createDiscount({
|
|
261
|
+
title: "Summer 20%",
|
|
262
|
+
code: "SUMMER20",
|
|
263
|
+
discountType: "percentage",
|
|
264
|
+
value: 20,
|
|
265
|
+
minOrderAmount: 50,
|
|
266
|
+
maxDiscountAmount: 30,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Validate discount for a cart
|
|
270
|
+
const validation = await commerce.validateDiscount("SUMMER20", 100);
|
|
271
|
+
// => { valid: true, discountAmount: 20, code: "SUMMER20" }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 3. Cart Calculation & Orders
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Calculate totals (items, discounts, shipping, and tax)
|
|
278
|
+
const cart = await commerce.calculateCart({
|
|
279
|
+
items: [{ productId: product.id, quantity: 2 }],
|
|
280
|
+
discountCode: "SUMMER20",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Place an order (automatically decrements inventory and increments coupon counter)
|
|
284
|
+
const order = await commerce.createOrder({
|
|
285
|
+
customerEmail: "customer@example.com",
|
|
286
|
+
customerName: "Jane Doe",
|
|
287
|
+
items: [{ productId: product.id, quantity: 2 }],
|
|
288
|
+
discountCode: "SUMMER20",
|
|
289
|
+
shippingAddress: {
|
|
290
|
+
address1: "456 Market St",
|
|
291
|
+
city: "San Francisco",
|
|
292
|
+
country: "US",
|
|
293
|
+
postalCode: "94103",
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
console.log(
|
|
298
|
+
`Order placed: #${order.data.orderNumber} (Total: $${order.data.total})`,
|
|
299
|
+
);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 4. REST API & Headless Client
|
|
303
|
+
|
|
304
|
+
Mount the universal `createCMSRouter(cms)` to automatically expose REST endpoints:
|
|
305
|
+
|
|
306
|
+
- `GET /api/ecommerce/products` (supports `?category=...&minPrice=...&maxPrice=...&inStock=true&search=...`)
|
|
307
|
+
- `GET /api/ecommerce/products/:idOrSlug`
|
|
308
|
+
- `POST /api/ecommerce/products`
|
|
309
|
+
- `POST /api/ecommerce/products/:id/images`
|
|
310
|
+
- `GET /api/ecommerce/categories` (supports `?tree=true`)
|
|
311
|
+
- `POST /api/ecommerce/discounts/validate`
|
|
312
|
+
- `POST /api/ecommerce/cart/calculate`
|
|
313
|
+
- `POST /api/ecommerce/orders`
|
|
314
|
+
- `PATCH /api/ecommerce/orders/:id/status`
|
|
315
|
+
|
|
316
|
+
Or query via the Headless Client SDK:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { createCmsClient, getEcommerceClient } from "@azlib/cms";
|
|
320
|
+
|
|
321
|
+
const client = createCmsClient({ baseUrl: "https://api.my-shop.com" });
|
|
322
|
+
const shop = getEcommerceClient(client);
|
|
323
|
+
|
|
324
|
+
const { items: products } = await shop.products.find({ minPrice: 50 });
|
|
325
|
+
const order = await shop.orders.create({ ... });
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Built-in HRMS Plugin (`hrmsPlugin`)
|
|
331
|
+
|
|
332
|
+
`@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.
|
|
333
|
+
|
|
334
|
+
### Features
|
|
335
|
+
|
|
336
|
+
- 🏢 **Multi-Tenant Employers**: Manage distinct companies/organizations with custom work schedules, timezones, and grace periods.
|
|
337
|
+
- 👤 **Employee Directory**: Rich employee profiles, contract/document attachments, emergency contacts, manager hierarchies, and department taxonomies.
|
|
338
|
+
- ⏱️ **Daily Attendance Tracking**: Clock-in and clock-out with automated duration calculation, overtime hours, and late-arrival detection against work schedules.
|
|
339
|
+
- 🏖️ **Leave Quota & Approval Workflow**: Custom leave types (Annual, Sick, Unpaid), balance ledger reports, and manager approval/rejection pipelines.
|
|
340
|
+
- 🌐 **Web Standard REST APIs & Client SDK**: Pre-mounted routes under `/api/hrms/*` and typed `HRMSClient` SDK.
|
|
341
|
+
|
|
342
|
+
### Usage
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import {
|
|
346
|
+
createCMSEngine,
|
|
347
|
+
defineConfig,
|
|
348
|
+
hrmsPlugin,
|
|
349
|
+
getHRMSService,
|
|
350
|
+
} from "@azlib/cms";
|
|
351
|
+
|
|
352
|
+
const config = defineConfig({
|
|
353
|
+
plugins: [
|
|
354
|
+
hrmsPlugin({
|
|
355
|
+
workScheduleStart: "09:00",
|
|
356
|
+
workScheduleEnd: "17:00",
|
|
357
|
+
standardWorkDayHours: 8,
|
|
358
|
+
gracePeriodMinutes: 15,
|
|
359
|
+
}),
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const cms = createCMSEngine(config);
|
|
364
|
+
await cms.init();
|
|
365
|
+
|
|
366
|
+
const hrms = getHRMSService(cms);
|
|
367
|
+
|
|
368
|
+
// 1. Create an Employer
|
|
369
|
+
const employer = await hrms.createEmployer({
|
|
370
|
+
companyName: "Acme Corp",
|
|
371
|
+
timezone: "America/New_York",
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// 2. Add an Employee
|
|
375
|
+
const employee = await hrms.createEmployee({
|
|
376
|
+
employerId: employer.id,
|
|
377
|
+
employeeNumber: "ACME-001",
|
|
378
|
+
firstName: "Jane",
|
|
379
|
+
lastName: "Doe",
|
|
380
|
+
email: "jane.doe@acme.example",
|
|
381
|
+
hireDate: "2024-01-15",
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// 3. Employee Check-In & Check-Out
|
|
385
|
+
await hrms.checkIn({ employeeId: employee.id });
|
|
386
|
+
await hrms.checkOut({ employeeId: employee.id });
|
|
387
|
+
|
|
388
|
+
// 4. Request Leave & Check Balance
|
|
389
|
+
const leaveReport = await hrms.calculateLeaveBalance(employee.id, 2026);
|
|
390
|
+
console.log(`Remaining days: ${leaveReport.totalRemaining}`);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
176
395
|
## License
|
|
177
396
|
|
|
178
397
|
MIT © Google / azlib
|