@classytic/muster
v0.1.1
Published
Universal attendance primitive — framework-agnostic, subject-polymorphic, storage-agnostic. One engine for corporate HR, gym access, class rosters, appointment check-in, and RFID/biometric/facial gates. Powered by @classytic/mongokit.
Readme
@classytic/muster
Universal attendance primitive. One engine for corporate HR clock-in, gym turnstiles, university classrooms, doctor appointments, warehouse gates, and any other "was X present at Y during Z" use-case.
Subject-polymorphic. @classytic/muster has zero opinions about who is being tracked. ERPNext HR and Odoo HR hardcode Employee; muster uses subjectRef: string + subjectModel: string (SourceBridge pattern). The host maps sessions to any entity — Employee, Patient, Student, GymMember, Visitor.
Framework-agnostic. MongoKit repositories are the domain layer. Arc auto-CRUD via defineResource() with zero adapter code. Hosts without Arc can call repositories directly.
Install
npm install @classytic/muster @classytic/mongokit @classytic/primitives mongoose zodCore shape
AttendanceSession ──────┬── AttendanceRecord (append-only event log)
subjectRef │ check_in / check_out / break_start /
subjectModel │ break_end / correction_applied
scope │
scheduledStart/End │
actualStart/End │
status: open|closed| │
cancelled │
outcome: present|late| │
absent|... │
checkInMethod │
checkOutMethod │
│
AttendanceCorrection ──┘
proposedChanges
status: pending|
approved|rejected
reviewedByQuick start
import mongoose from 'mongoose';
import { createMuster } from '@classytic/muster';
const muster = await createMuster({
connection: mongoose.connection,
tenantFieldType: 'objectId',
});
// Corporate HR: employee clocking in via kiosk
const session = await muster.repositories.attendanceSession.checkIn(
{
subjectRef: String(employee._id),
subjectModel: 'Employee',
scope: String(branch._id),
method: 'kiosk',
scheduledStart: new Date('2026-04-18T09:00:00Z'),
scheduledEnd: new Date('2026-04-18T17:00:00Z'),
},
ctx,
);
// Gym access: member tapping an RFID card
await muster.repositories.attendanceSession.checkIn(
{
subjectRef: member.memberNumber,
subjectModel: 'GymMember',
scope: 'downtown-gym',
method: 'rfid',
device: { deviceId: 'turnstile-3', deviceType: 'turnstile' },
},
ctx,
);
// University class: student scanning a QR code
await muster.repositories.attendanceSession.checkIn(
{
subjectRef: String(student._id),
subjectModel: 'Student',
scope: 'classroom-101',
method: 'qr',
scheduledStart: new Date('2026-04-18T10:00:00Z'),
scheduledEnd: new Date('2026-04-18T11:30:00Z'),
},
ctx,
);
// Check out
await muster.repositories.attendanceSession.checkOut(
String(session._id),
{ method: 'kiosk' },
ctx,
);Events
Hosts subscribe via the Arc-compatible EventTransport. Any arc transport (Memory, Redis, Kafka) drops in.
await muster.events.subscribe('muster:session.*', async (event) => {
// handle muster:session.opened / .closed / .cancelled / .corrected
});
await muster.events.subscribe('muster:correction.approved', async (event) => {
// payroll / attendance reports get notified when a correction is applied
});Event names:
| Name | Fired on |
| ----------------------------- | ------------------------------------------------------ |
| muster:session.opened | checkIn() creates a new session |
| muster:session.closed | checkOut() closes an open session |
| muster:session.cancelled | cancel() voids an open session |
| muster:session.corrected | A correction is approved and applied |
| muster:record.appended | Any AttendanceRecord is written |
| muster:correction.requested | A correction request is filed |
| muster:correction.approved | Reviewer approves a correction |
| muster:correction.rejected | Reviewer rejects a correction |
Check-in methods
type CheckInMethod =
| 'manual' // admin entered the time manually
| 'kiosk' // on-premise touchscreen
| 'qr' // student/patient scanned a QR code
| 'rfid' // card/badge tap at a reader
| 'biometric' // fingerprint / palm vein
| 'facial' // face recognition camera
| 'mobile' // in-app check-in
| 'geofence'; // auto check-in from GPS fenceBiometric vendors own the template store. Muster records only the method + an opaque device.deviceId.
Arc integration
import { defineResource } from '@classytic/arc';
export const sessionResource = defineResource({
name: 'attendance-session',
prefix: '/attendance/sessions',
adapter: createAdapter(muster.models.AttendanceSession, muster.repositories.attendanceSession),
actions: {
checkIn: { handler: (_id, data, req) => muster.repositories.attendanceSession.checkIn(data, req.scope) },
checkOut: { handler: (id, data, req) => muster.repositories.attendanceSession.checkOut(id, data, req.scope) },
cancel: { handler: (id, data, req) => muster.repositories.attendanceSession.cancel(id, data, req.scope) },
},
});CRUD, pagination, filtering, soft-delete query support — all inherited from mongokit + Arc adapter.
Why "muster"?
Military tradition: a formal gathering of personnel for accountability. The word has no "employee" bias built in.
