@azlib/cms 0.4.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 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 }>((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" },
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
- // 3. Lifecycle callbacks
156
- async onInit(engine) {
157
- console.log("[SEO Plugin] Initialized");
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()`:
@@ -178,7 +188,12 @@ cms.use(seoPlugin());
178
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:
179
189
 
180
190
  ```typescript
181
- import { createCMSEngine, defineConfig, ecommercePlugin, getEcommerceService } from "@azlib/cms";
191
+ import {
192
+ createCMSEngine,
193
+ defineConfig,
194
+ ecommercePlugin,
195
+ getEcommerceService,
196
+ } from "@azlib/cms";
182
197
 
183
198
  // 1. Enable the plugin in your config
184
199
  export default defineConfig({
@@ -203,8 +218,15 @@ await cms.init();
203
218
  const commerce = getEcommerceService(cms);
204
219
 
205
220
  // 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 });
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
+ });
208
230
 
209
231
  // Create product with SKU, pricing, inventory, variants
210
232
  const product = await commerce.createProduct({
@@ -272,12 +294,15 @@ const order = await commerce.createOrder({
272
294
  },
273
295
  });
274
296
 
275
- console.log(`Order placed: #${order.data.orderNumber} (Total: $${order.data.total})`);
297
+ console.log(
298
+ `Order placed: #${order.data.orderNumber} (Total: $${order.data.total})`,
299
+ );
276
300
  ```
277
301
 
278
302
  ### 4. REST API & Headless Client
279
303
 
280
304
  Mount the universal `createCMSRouter(cms)` to automatically expose REST endpoints:
305
+
281
306
  - `GET /api/ecommerce/products` (supports `?category=...&minPrice=...&maxPrice=...&inStock=true&search=...`)
282
307
  - `GET /api/ecommerce/products/:idOrSlug`
283
308
  - `POST /api/ecommerce/products`
@@ -302,6 +327,71 @@ const order = await shop.orders.create({ ... });
302
327
 
303
328
  ---
304
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
+
305
395
  ## License
306
396
 
307
397
  MIT © Google / azlib