npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@classytic/clockin

v2.2.0

Published

Modern attendance management with TypeScript - Plugin-based, event-driven, multi-tenant ready. Analytics, engagement metrics, streaks, check-in/check-out management

Readme

🎯 ClockIn — Attendance Framework (Mongoose + TypeScript)

Test npm version License: MIT

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.

🌟 Features

  • Multi-tenant ready: everything is scoped by organizationId for SaaS apps
  • Single-tenant friendly: just call .forSingleTenant() — no organizationId needed anywhere (see docs/SINGLE_TENANT.md)
  • Storage optimized: monthly aggregation (1 document per member per month)
  • Fast analytics: embedded attendanceStats + aggregation helpers
  • Event-driven: type-safe EventBus + plugin hooks
  • Clean architecture: Builder API, services, shared schemas & utilities

📦 Installation

npm install @classytic/clockin

Requirements

  • Node.js >= 18
  • Mongoose >= 8

🚀 Quick Start (v2)

1) Create the Attendance model (monthly aggregation)

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)
  })
);

2) Add ClockIn fields to your target schema (e.g. 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);

3) Build ClockIn

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();

4) Record a check-in

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);
}

5) Analytics

const dashboard = await clockin.analytics.dashboard({
  MemberModel: Membership,
  organizationId,
});

if (dashboard.ok) {
  console.log(dashboard.value.summary.totalCheckIns);
}

🎯 Custom Target Models (v2.0)

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 },
});

Configuring Target Models with Deep Merge

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:

  • Employee: Uses schedule-aware detection with percentage-based thresholds
  • Other models: Use time-based detection with absolute hour thresholds

Restricting Target Models (Optional)

For 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', ... });

🛡️ Error Handling

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.

🔄 Transactions

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 },
  });
});

🧠 Important Notes

  • Target model naming matters: services use the models you register via .withModels(...). That means your targetModel string must match the key you passed in .withModels({ ... }) (e.g. 'Membership', 'Employee').
  • Check-out requires a check-in id: checkOut.record needs a checkInId (tests should pass it explicitly).
  • Half-day types: schedule-aware detection can return half_day_morning or half_day_afternoon for employee check-outs.
  • Occupancy location: use clockin.checkOut.getOccupancy, not clockin.analytics.

🧩 Type Exports

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';

⏱️ Auto-checkout (batch helper)

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,
});

📈 Indexing for scale

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.

🔌 Plugins & Events

// 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()

Plugin Fail-Fast Mode

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();

Cleanup

Always destroy the instance when shutting down to prevent memory leaks:

await clockin.destroy();

See: docs/PLUGINS_AND_EVENTS.md

📚 Documentation

  • INTEGRATION.md — full integration guide (schemas, models, and best practices)
  • docs/SINGLE_TENANT.md — single-tenant setup
  • docs/SCHEMAS_AND_MODELS.md — schema details + indexing
  • docs/PLUGINS_AND_EVENTS.md — plugin hooks + EventBus
  • docs/CORRECTIONS.md — correction requests API

📝 License

MIT