@onairtattoo/booking
v0.6.0
Published
Generic booking engine for tattoo studios, dental clinics, vets, repair shops, salons. Framework-agnostic schema + pure engine (slot calc, recurrence, conflicts, status) + (WIP) Hono routes + email templates.
Maintainers
Readme
@onairtattoo/booking
Generic booking engine for service businesses (tattoo studios, vets, repair shops, salons, clinics). Framework-agnostic.
Status
v0.5.0 — engine + validators + cancel tokens + email templates + reminders shipped, routes / iCal still WIP.
What works today:
- Pure slot availability calculator (
calculateAvailableSlots,calculateUnavailableDays) - Recurrence expansion (
expandRecurrence) — single / range / weekly / monthly with month-end clamping - Interval overlap detection (
isOverlap,findConflicts) - Booking status state machine (
canTransition,isTerminal) - Drizzle schema for
provider,service,booking,scheduleBlock - Zod validators for HTTP APIs (booking / provider / service / scheduleBlock create+update, slot query, id param) — flat
createScheduleBlockSchemafor HTTP plus discriminatedrecurrencePatternSchemafor direct engine input - Cancel-token helpers (
generateCancelToken,buildCancelTokenExpiry,isCancelTokenValid,canCancelBooking) +computeRefundEligibility. 256-bit URL-safe tokens with timing-safe comparison. - HTML email templates.
EmailThemeinterface +DARK_THEME/LIGHT_THEMEpresets, 8 building-block functions (emailWrapper,emailHeader,emailDivider,emailParagraph,emailGreeting,emailTable,emailButton,emailFooter), and 4 opinionated builders (buildBookingConfirmationEmail,buildStatusUpdateEmail,buildReminderEmail,buildReviewRequestEmail) returning{ subject, html }. All user-controlled input is HTML-escaped automatically. No copy embedded — the consumer passes every user-facing string. - NEW v0.5.0: reminder scheduling.
getDueReminders({ candidates, schedule, now })returns the list of reminders due to fire right now, sorted by booking time ascending. Idempotent (skips reminders already inremindersSent), catch-up friendly (fires up untilbookingAt), skips terminal bookings.markReminderSent(remindersSent, minutesBefore, now)returns a new immutable array. PlusbuildReminderEmailas a semantic alias for the confirmation template.
What's coming:
- Hono router factory (configurable per consumer)
- iCal feed generator
- Duration-aware slot calculator (extend
calculateAvailableSlotsto handle variable booking durations)
Install
npm install @onairtattoo/booking drizzle-ormdrizzle-orm >= 0.45.2 is a required peer. hono and zod are optional peers (only needed once you consume the (future) routes / validators).
Usage
import { calculateAvailableSlots, expandRecurrence } from "@onairtattoo/booking";
// Pure — fetch your own data from your own DB
const slots = calculateAvailableSlots({
candidateSlots: ["10:00", "11:00", "12:00", "17:00", "18:00", "19:00"],
bookedRanges: [{ start: "11:00", end: "12:00" }],
blockedRanges: [{ startTime: "17:00", endTime: "18:00" }],
hasAllDayBlock: false,
// Optional duration-aware mode (default duration is 60 min):
slotDurationMin: 60,
sessionWindows: [
{ start: "10:00", end: "13:00" },
{ start: "17:00", end: "20:00" },
],
});
// → ["10:00", "12:00", "18:00", "19:00"]
const dates = expandRecurrence({
type: "weekly",
startDate: "2026-06-01",
endDate: "2026-06-30",
weekdays: [1, 3], // Mon, Wed
});
// → 9 datesFor server-only imports (schema), use:
import { schema } from "@onairtattoo/booking/server";Validators are exposed from the root and from the /validators sub-path:
import { createBookingSchema, createScheduleBlockSchema } from "@onairtattoo/booking";
// or, tree-shake-friendly:
import { createBookingSchema } from "@onairtattoo/booking/validators";
const parsed = createBookingSchema.parse(req.body);Field naming is camelCase (matches the Drizzle schema's TS shape). Error messages are in English — translate or wrap as needed for your locale.
License
MIT
