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

dabke

v0.81.1

Published

Scheduling library powered by constraint programming (CP-SAT)

Readme

dabke

npm version License: MIT CI

TypeScript scheduling library powered by constraint programming.

Define teams, shifts, coverage, and rules declaratively. dabke compiles them into a CP-SAT model and solves for an optimized schedule.

import {
  defineSchedule,
  t,
  time,
  cover,
  shift,
  maxHoursPerWeek,
  minRestBetweenShifts,
  timeOff,
  minimizeCost,
  weekdays,
  weekend,
} from "dabke";

const schedule = defineSchedule({
  roles: ["barista", "server"],

  // Times: WHEN you need people (named periods for coverage + rules)
  times: {
    breakfast: time({ startTime: t(7), endTime: t(10) }),
    lunch_rush: time(
      { startTime: t(11, 30), endTime: t(14) },
      { startTime: t(11), endTime: t(15), dayOfWeek: weekend },
    ),
    closing: time({ startTime: t(20), endTime: t(22) }),
  },

  // Coverage: HOW MANY people you need during each time
  coverage: [
    cover("breakfast", "barista", 1),
    cover("lunch_rush", "barista", 2, { dayOfWeek: weekdays }),
    cover("lunch_rush", "barista", 3, { dayOfWeek: weekend }),
    cover("lunch_rush", "server", 1),
    cover("closing", "server", 1),
  ],

  // Shifts: WHEN people CAN work (the actual time slots)
  // An opener covers breakfast + lunch; a closer covers lunch + closing
  shiftPatterns: [
    shift("opener", t(6), t(14)),
    shift("mid", t(10), t(18)),
    shift("closer", t(14), t(22)),
  ],

  rules: [
    maxHoursPerWeek(40),
    minRestBetweenShifts(11),
    timeOff({ appliesTo: "alice", dayOfWeek: weekend }),
    minimizeCost(),
  ],
});

Quick Start

npm install dabke

dabke compiles scheduling problems into a constraint model. You need a CP-SAT solver to find the solution:

docker pull christianklotz/dabke-solver
docker run -p 8080:8080 christianklotz/dabke-solver

Once you have a schedule definition, create a config with runtime data and solve:

import { ModelBuilder, HttpSolverClient, parseSolverResponse, resolveAssignments } from "dabke";

const config = schedule.createSchedulerConfig({
  schedulingPeriod: {
    dateRange: { start: "2026-02-09", end: "2026-02-15" },
  },
  members: [
    { id: "alice", roles: ["barista", "server"], pay: { hourlyRate: 1500 } },
    { id: "bob", roles: ["barista"], pay: { hourlyRate: 1200 } },
    { id: "carol", roles: ["barista", "server"], pay: { hourlyRate: 1400 } },
    { id: "dave", roles: ["barista", "server"], pay: { hourlyRate: 1200 } },
    { id: "eve", roles: ["barista", "server"], pay: { hourlyRate: 1300 } },
  ],
});

const builder = new ModelBuilder(config);
const { request, canSolve } = builder.compile();

const solver = new HttpSolverClient(fetch, "http://localhost:8080");
const response = await solver.solve(request);
const result = parseSolverResponse(response);
const shifts = resolveAssignments(result.assignments, config.shiftPatterns);

Key Concepts

Times vs shift patterns. These are two distinct things. Times are named periods you need coverage for ("breakfast", "lunch_rush"). Shift patterns are the time slots people actually work ("opener 6-14", "closer 14-22"). They don't need to match. The solver assigns members to shift patterns whose hours overlap with times to satisfy coverage.

Coverage. How many people with which roles you need during each time. Coverage can vary by day of week or specific dates using scoping options or the variant form of cover().

Rules. Business constraints expressed as function calls. Rules with priority: "MANDATORY" (the default) are hard constraints the solver will never violate. Rules with LOW, MEDIUM, or HIGH priority are soft preferences the solver optimizes across.

Scoping. Every rule accepts appliesTo (a role, skill, or member ID), plus time scoping (dayOfWeek, dateRange, dates, recurringPeriods) to target when and who:

maxHoursPerDay(8, { appliesTo: "barista", dayOfWeek: weekdays }),
timeOff({ appliesTo: "alice", dateRange: { start: "2026-02-09", end: "2026-02-13" } }),
maxHoursPerWeek(30, { appliesTo: "bob", priority: "MEDIUM" }),

Cost optimization. Members declare pay as hourly or salaried. Cost rules (minimizeCost, dayMultiplier, overtimeMultiplier, tieredOvertimeMultiplier, etc.) tell the solver to minimize total labor cost, factoring in overtime, day premiums, and time-based surcharges.

Validation. ModelBuilder reports coverage gaps and rule violations both pre-solve (via compile()) and post-solve (via builder.reporter.analyzeSolution()). Use summarizeValidation() for grouped reporting.


Rules

Scheduling

| Function | Description | | -------------------------- | ------------------------------------ | | maxHoursPerDay(hours) | Max hours per person per day | | maxHoursPerWeek(hours) | Max hours per person per week | | minHoursPerDay(hours) | Min hours per person per day | | minHoursPerWeek(hours) | Min hours per person per week | | maxShiftsPerDay(n) | Max shift assignments per day | | maxConsecutiveDays(n) | Max consecutive working days | | minConsecutiveDays(n) | Min consecutive working days | | minRestBetweenShifts(h) | Min rest hours between shifts | | timeOff(opts) | Block assignments during periods | | preference(level, opts) | Prefer or avoid assigning members | | preferLocation(id, opts) | Prefer members at specific locations | | assignTogether(opts) | Keep members on the same shifts |

Cost

| Function | Description | | ---------------------------------------- | ---------------------------------------- | | minimizeCost() | Minimize total labor cost | | dayMultiplier(factor, opts?) | Pay multiplier for specific days | | daySurcharge(amount, opts?) | Flat surcharge per hour on specific days | | timeSurcharge(amount, window, opts?) | Surcharge during a time-of-day window | | overtimeMultiplier(opts) | Weekly overtime pay multiplier | | overtimeSurcharge(opts) | Weekly overtime flat surcharge | | dailyOvertimeMultiplier(opts) | Daily overtime pay multiplier | | dailyOvertimeSurcharge(opts) | Daily overtime flat surcharge | | tieredOvertimeMultiplier(tiers, opts?) | Graduated overtime rates |


LLM Integration

dabke ships an llms.txt with complete API documentation for AI code generation:

import { apiDocs } from "dabke/llms";

Development

Unit tests (no dependencies): npm run test:unit

Integration tests (requires Docker): npm run test:integration

The test harness builds and starts a solver container from solver/Dockerfile automatically.

import { startSolverContainer } from "dabke/testing";
const solver = await startSolverContainer();

See CONTRIBUTING.md for how to add rules and contribute.

License

MIT