
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@classytic/clockin
Advanced tools
Modern attendance management with TypeScript - Plugin-based, event-driven, multi-tenant ready. Analytics, engagement metrics, streaks, check-in/check-out management
ClockIn is a Mongoose-first attendance framework for any kind of event check-in: gym members, employees, students, picnics, classes, IoT scans, QR/RFID/biometric—built as a clean, pluggable service.
organizationId for SaaS apps.forSingleTenant() — no organizationId needed anywhere (see docs/SINGLE_TENANT.md)attendanceStats + aggregation helpersEventBus + plugin hooksnpm install @classytic/clockin
Requirements
import mongoose from 'mongoose';
import { createAttendanceSchema } from '@classytic/clockin';
export const Attendance = mongoose.model(
'Attendance',
createAttendanceSchema({
ttlDays: 730, // 0 disables TTL
createIndexes: true, // opt-in to index creation (default: false)
})
);
Membership)import mongoose from 'mongoose';
import { commonAttendanceFields, applyAttendanceIndexes } from '@classytic/clockin';
const membershipSchema = new mongoose.Schema(
{
organizationId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
customer: { name: String, email: String },
membershipCode: String,
status: { type: String, default: 'active' },
// Adds: currentSession, attendanceStats, attendanceEnabled, attendanceNotes
...commonAttendanceFields,
},
{ timestamps: true }
);
// Opt-in to index creation (recommended for production)
applyAttendanceIndexes(membershipSchema, {
tenantField: 'organizationId',
createIndexes: true, // default: false
});
export const Membership = mongoose.model('Membership', membershipSchema);
import { ClockIn, loggingPlugin } from '@classytic/clockin';
import { Attendance } from './models/attendance.js';
import { Membership } from './models/membership.js';
export const clockin = await ClockIn
.create()
.withModels({ Attendance, Membership })
.withPlugin(loggingPlugin())
.build();
import { isOk } from '@classytic/clockin';
import { clockin } from './clockin.js';
const member = await mongoose.model('Membership').findOne({ _id: memberId, organizationId });
const result = await clockin.checkIn.record({
member,
targetModel: 'Membership',
data: { method: 'qr_code', notes: 'Front gate' },
context: { organizationId, userId, userName: 'Admin', userRole: 'admin' },
});
if (isOk(result)) {
console.log(result.value.stats.totalVisits);
}
const dashboard = await clockin.analytics.dashboard({
MemberModel: Membership,
organizationId,
});
if (dashboard.ok) {
console.log(dashboard.value.summary.totalCheckIns);
}
ClockIn accepts any target model by default. Track attendance for memberships, employees, events, workshops, or any custom entity:
// Track attendance for a custom "Workshop" model
const clockin = await ClockIn
.create()
.withModels({ Attendance, Workshop })
.build();
await clockin.checkIn.record({
member: workshop,
targetModel: 'Workshop', // Any string works
data: { method: 'api' },
context: { organizationId },
});
When you configure a target model with .withTargetModel(), your config is deep merged with smart defaults. This means you only need to specify the values you want to override—nested properties you don't specify are preserved from defaults:
const clockin = await ClockIn
.create()
.withModels({ Attendance, Membership })
.withTargetModel('Membership', {
detection: {
type: 'time-based', // Only override the type
// rules.thresholds, scheduleSource, timeHints are preserved from defaults
},
autoCheckout: {
afterHours: 4, // Only override afterHours
// enabled, maxSession are preserved from defaults
},
})
.build();
Default configurations are generated based on the target model name:
schedule-aware detection with percentage-based thresholdstime-based detection with absolute hour thresholdsFor stricter validation, restrict to a specific allowlist:
const clockin = await ClockIn
.create()
.withModels({ Attendance, Membership, Employee })
.restrictTargetModels(['Membership', 'Employee']) // Only these allowed
.build();
// This will throw TargetModelNotAllowedError:
await clockin.checkIn.record({ targetModel: 'Workshop', ... });
ClockIn uses a Result type (inspired by Rust) for explicit error handling—no try/catch needed:
import { isOk, isErr } from '@classytic/clockin';
const result = await clockin.checkIn.record({ ... });
if (isOk(result)) {
console.log(result.value.stats.totalVisits);
} else {
// result.error is a typed ClockInError
console.error(result.error.code, result.error.message);
}
Common error types: ValidationError, DuplicateCheckInError, AttendanceNotEnabledError, MemberNotFoundError, TargetModelNotAllowedError.
For atomic operations across multiple documents, pass a Mongoose session:
const session = await mongoose.startSession();
await session.withTransaction(async () => {
await clockin.checkIn.record({
member,
targetModel: 'Membership',
context: { organizationId, session },
});
});
.withModels(...).
That means your targetModel string must match the key you passed in .withModels({ ... }) (e.g. 'Membership', 'Employee').checkOut.record needs a checkInId (tests should pass it explicitly).half_day_morning or half_day_afternoon for employee check-outs.clockin.checkOut.getOccupancy, not clockin.analytics.ClockIn exports its full type surface from the main package entry. Import what you need from @classytic/clockin:
import type {
AttendanceTargetModel,
AttendanceRecord,
CheckInParams,
CheckOutParams,
OccupancyData,
ActiveSessionData,
CheckoutExpiredParams,
} from '@classytic/clockin';
For scheduled jobs, use the built-in batch helper to close expired sessions safely in chunks:
await clockin.checkOut.checkoutExpired({
organizationId,
targetModel: 'Employee', // optional: process all registered models
before: new Date(),
limit: 500,
});
Index creation is opt-in to give you full control over your database indexes. For production usage with bursty multi-tenant workloads, enable indexes explicitly:
// On your Attendance schema
createAttendanceSchema({
ttlDays: 730,
createIndexes: true, // Creates query + TTL indexes
});
// On your target schemas (Membership, Employee, etc.)
applyAttendanceIndexes(schema, {
tenantField: 'organizationId',
createIndexes: true, // Creates session + stats indexes
});
This includes real-time session indexes for currentSession.isActive and currentSession.expectedCheckOutAt.
// Subscribe to events (returns unsubscribe function)
const unsubscribe = clockin.on('checkIn:recorded', (event) => {
console.log(`${event.data.member.name} checked in!`);
});
// Clean up when done
unsubscribe();
Built-in plugins: loggingPlugin(), metricsPlugin(), notificationPlugin()
By default, plugin errors are logged but don't stop execution. Enable fail-fast to throw on first plugin error:
const clockin = await ClockIn.create()
.withModels({ Attendance })
.withPlugin(myPlugin)
.withPluginFailFast() // Throws PluginError on failure
.build();
Always destroy the instance when shutting down to prevent memory leaks:
await clockin.destroy();
See: docs/PLUGINS_AND_EVENTS.md
INTEGRATION.md — full integration guide (schemas, models, and best practices)docs/SINGLE_TENANT.md — single-tenant setupdocs/SCHEMAS_AND_MODELS.md — schema details + indexingdocs/PLUGINS_AND_EVENTS.md — plugin hooks + EventBusdocs/CORRECTIONS.md — correction requests APIMIT
FAQs
Modern attendance management with TypeScript - Plugin-based, event-driven, multi-tenant ready. Analytics, engagement metrics, streaks, check-in/check-out management
The npm package @classytic/clockin receives a total of 35 weekly downloads. As such, @classytic/clockin popularity was classified as not popular.
We found that @classytic/clockin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.