Skip to content

Changelog

All notable changes to The Booking Kit will be documented in this file.

Dependency and correctness release. All packages move to 0.5.0 together.

  • withSerializableRetry (@thebookingkit/server) now finds the Postgres SQLSTATE anywhere down the error’s cause chain. On drizzle-orm ≥ 0.44 (which wraps driver errors) a double-booking used to surface as a raw “Failed query” error instead of BookingConflictError, and serialization failures were not retried.
  • Stripe Connect cancels — StripePaymentAdapter.cancelPaymentIntent works with stripe-node 22+ (request options moved to the third argument).
  • createSetupIntent no longer sends receipt_email, which Stripe rejects for SetupIntents.
  • Plain Node ESM — the packages now load outside a bundler (previously rrule’s packaging made import throw under bare Node).
  • stripe peer range: >=17.0.0 <24.0.0. Pass new Stripe(...) straight to StripePaymentAdapter — no cast needed.
  • date-fns: ^3.6.0 || ^4.0.0 in core and d1.
  • @thebookingkit/db requires drizzle-orm ^0.45.4 (SQL-injection advisory fixed in 0.45.2).
  • @thebookingkit/cli requires Node ≥ 22.12.

Atomic double-booking guard for D1 / SQLite

Section titled “Atomic double-booking guard for D1 / SQLite”
  • New: insertBookingIfFree, insertBookingOrThrow, buildInsertIfFree and insertResourceBookingIfFree check for overlap and insert in a single INSERT … SELECT … WHERE NOT EXISTS statement, so concurrent requests can’t interleave. Supports buffers (conflictWindow), rescheduling (excludeId), custom schemas (columns) and resource scoping. Overlap is half-open, so back-to-back bookings don’t conflict.
  • New: opt-in BOOKINGS_UNIQUE_SLOT_DDL as a schema-level backstop, plus findLegacyRows() / migrateRowDates() to make existing rows canonical UTC first.
  • A holder whose lease had expired could release a lock another request now held. Every acquisition now writes a holder fencing token and release is scoped to it.
  • A lease expiring mid-callback is no longer silent: withLock throws LockLeaseExpiredError, carrying the callback’s return value as .result.

Consistent slot occupancy across engine, Postgres and D1

Section titled “Consistent slot occupancy across engine, Postgres and D1”
  • cancelled, rejected, no_show and rescheduled now free a slot everywhere; completed still blocks. Previously the slot engine, Postgres and D1 disagreed on no_show and rescheduled.
  • Postgres action required: run runCustomMigrations() (or apply migration 0007) — it only ever makes the overlap constraints more permissive.
  • Lockstep versioning: all published packages now share one version number.
  • Fixed the repository URL in package metadata.
  • New: per-event-type deposits (deposit_cents or deposit_percentage) with computeDepositAmount() / requiresDeposit() in core.
  • New: StripePaymentAdapter (with Stripe Connect), handleStripeWebhook (signature verification + idempotency on event.id), and initiateDeposit / refundDeposit in @thebookingkit/server.
  • New: card-on-file and off-session charging — customerId, setupFutureUsage, paymentMethodId + offSession + confirm, and idempotencyKey on createPaymentIntent; authorize-only deposits via captureMethod: "manual".
  • New: edge-runtime webhooks — handleStripeWebhook uses constructEventAsync, so it works on Cloudflare Workers and Deno.
  • Schema: "deposit" payment type; Postgres migration 0006_deposits.sql, D1 MIGRATION_0002_DEPOSITS_DDL.
  • New: getResourceAvailableSlots, assignResource, isResourceSlotAvailable, getResourcePoolSummary — tables, yoga mats, rooms, courts. See Resource booking.

Control when time slots become visible to customers — fill earlier gaps first, use rolling release windows, or incentivize early bookings with discounts.

  • New: applySlotRelease() — three slot release strategies:
    • fill_earlier_first: Hide afternoon slots until morning reaches a fill threshold (barbers, salons)
    • rolling_window: Only show slots within N hours/days from now (restaurants releasing dinner day-of)
    • discount_incentive: Annotate harder-to-fill slots with releaseMetadata.discountPercent (no filtering)
  • New: computeWindowFillRates() — exported helper for custom fill rate computation
  • Extended: SlotComputeOptions with optional slotRelease field
  • Extended: Slot with optional releaseMetadata for discount annotations
  • New: Feature guide: Slot Release Strategies

Book restaurant tables, yoga mats, meeting rooms, tennis courts, and any physical bookable unit with capacity-aware scheduling.

  • New: getResourceAvailableSlots() — capacity-aware slot computation across resource pools (per-resource pipeline, then merge)
  • New: assignResource() — auto-assign best resource with 4 strategies: best_fit, first_available, round_robin, largest_first
  • New: isResourceSlotAvailable() — quick availability check for a specific resource or any-in-pool
  • New: getResourcePoolSummary() — admin utilization metrics with per-type breakdown
  • New: ResourceUnavailableError with typed reasons (no_capacity, no_matching_type, all_booked)
  • New: 9 resource types: ResourceInput, ResourceSlot, AvailableResource, ResourceAssignmentStrategy, and more
  • New: Shared pipeline extraction (slot-pipeline.ts) — RRULE expansion and override masking shared between provider and resource engines
  • Extended: BookingInput with optional resourceId and guestCount (backward-compatible)
  • Performance: 50-resource / 30-day computation in ~45ms

Extracted reusable conflict detection primitives from the kiosk module for use across any booking flow.

  • New: findConflicts() — generic booking overlap detection. Checks half-open intervals, excludes inactive statuses, supports self-exclusion via excludeId
  • New: canReschedule() — status check for reschedulable bookings (only confirmed and pending)
  • New: describeConflicts() — human-readable conflict descriptions with custom time formatter support
  • New: Types: ConflictCheckBooking, ConflictDetail
  • Refactored: validateReschedule and validateBreakBlock now delegate to findConflicts() — eliminates duplicated overlap logic
  • New: resources table — bookable physical units with type, capacity, and location
  • New: resource_availability_rules and resource_availability_overrides tables
  • New: bookings.resource_id nullable FK with EXCLUDE USING gist constraint (scoped to resource_id IS NOT NULL)
  • New: Updated create_booking() function with optional p_resource_id parameter
  • New: Migration 0005_resources.sql (idempotent)
  • New: d1ResourceAvailabilityRowsToInputs() and d1ResourceOverrideRowsToInputs() converters
  • New: D1ResourceBookingLock for resource-scoped advisory locking
  • New: 14 domain-scoped DDL constants for all core tables (ORGANIZATIONS_DDL, TEAMS_DDL, PROVIDERS_DDL, etc.)
  • New: ALL_DDL — convenience aggregate for initializing a complete D1 database in one call
  • New: d1AvailabilityRuleRowsToInputs() — converter for provider availability rules (was missing)
  • New: 12 webhook triggers: BOOKING_COMPLETED, RESOURCE_BOOKED, RESOURCE_RELEASED, WALK_IN_ADDED, WALK_IN_STARTED, WALK_IN_COMPLETED, WALK_IN_CANCELLED, SLOT_RELEASED, RECURRING_SERIES_CREATED, OCCURRENCE_CANCELLED, OCCURRENCE_RESCHEDULED, PAYMENT_REFUNDED
  • New: 5 background job names for walk-in, resource, recurring, and slot release workflows
  • Security: Replaced manual XOR with crypto.timingSafeEqual, added error logging in withAuth, JSON/HTML escaping in templates
  • Branding: Webhook headers renamed from X-SlotKit-* to X-BookingKit-*
  • New: 42 integration tests validating end-to-end booking flows across all modules (barber shop, restaurant, team scheduling, walk-in queue, cross-feature composition, seats/groups)
  • New: 101 D1 DDL structure tests
  • Total test count: 1,213 across the monorepo
  • Updated: Introduction now explicitly mentions resource booking and all booking types (restaurants, hotels, yoga, coworking, courts)
  • Updated: Quick-start with correct v0.2.0 API signatures + resource booking quick start section
  • Updated: Examples page with 3 new resource-based use cases (restaurant, hotel, coworking)
  • Updated: Schema docs with resource tables in main grid
  • New: Feature guide: Resource & Capacity Booking
  • New: Feature guide: Slot Release Strategies
  • New: API reference: Resource Engine API
  • New: Interactive restaurant reservation demo (Olive & Vine Bistro)
  • All “SlotKit” references renamed to “BookingKit” / “The Booking Kit” across the monorepo
  • CLI types: SlotKitManifest → BookingKitManifest, SlotKitConfig → BookingKitConfig (deprecated aliases retained)

QA Audit — 49 Critical & High Priority Bug Fixes

Section titled “QA Audit — 49 Critical & High Priority Bug Fixes”

This release includes a comprehensive QA audit across all packages, fixing 49 bugs across scheduling logic, database constraints, server security, D1 adapter edge cases, and CLI stability.

  • Critical: Midnight-crossing availability windows now produce correct slots; single now reference in slot filter prevents time drift; RRULE dtstart preserved when BYDAY is present
  • High: Booking limit counters increment after validation; overlapping booking intervals merged before wait time calculation; array responses handled element-wise in condition evaluation
  • Medium: Multi-select validation rejects invalid options; break-to-override returns specific time window; HTML escaping in embed snippets
  • Low: Slug generation handles special characters; cancellation fee validation throws on invalid timing; queue reordering preserves unmentioned entries; auto-reject deadlines clamped to minimum 1 hour
  • Critical: EXCLUDE constraint now excludes rescheduled status; audit trigger uses updated fallback; metadata initialization fixed
  • High: teams.slug and eventTypes.slug now have unique constraints
  • High: Audit trigger status→enum cast replaced with explicit CASE expression to prevent silent cast failures on future enum additions; bookingEvents.bookingId uses onDelete: restrict to protect audit integrity
  • Medium: WalkInQueue type exports added; pgcrypto extension added to setup migration; integration test ANY(array) queries fixed to use inArray() and sql.join
  • Low: Added vitest test script; drizzle.config.ts schema path corrected to compiled output
  • Critical: SQL identifier validation prevents injection; table name validation in migrations
  • High: Lock acquisition only retries on UNIQUE constraint errors; other errors re-thrown immediately
  • Medium: DST-aware day boundary computation; 1ms adjustment for midnight exclusion; hour normalization and validation; stricter time range validation; stale lock cleanup timing; date codec refactored to reject ambiguous formats
  • Low: Out-of-range time rejection; unused import removed
  • Critical: Full 256-bit HMAC signatures in booking tokens; constant-time comparison for token verification
  • High: Error handling sanitizes stack traces; role hierarchy enforces admin > provider > member; SSRF validation on webhook subscriptions and fire_webhook action; UUID validation in slot query params
  • Medium: Multi-tenancy scope assertions throw on null resource; recursive template injection prevented; XSS prevention in email templates; 90-day date range limit for DoS prevention; slug validation prevents path traversal; timezone-aware time/date formatting in workflows
  • Low: ICS carriage return stripping prevents line structure injection
  • Critical: Registry HTTPS validation; adapter input validation prevents shell injection
  • High: Manifest JSON parse errors wrapped with helpful messages; partial install state preserved; null-check on registry responses; directory escape prevention; circular dependency detection
  • Medium: Dynamic version from package.json; default manifest factory creates fresh instances; migration filename warnings; path traversal prevention
  • Low: Config template shape fixed; quote/backslash injection prevention; unused import removed; adapter config validation

KioskCalendar — Drag-and-Drop & Schedule Map

Section titled “KioskCalendar — Drag-and-Drop & Schedule Map”
  • Fixed: onEventDrop and onEventResize callbacks were defined as props but never wired to the underlying calendar. Events can now be dragged to reschedule and (optionally) reassign resources.
  • Added: resizable prop (default false) — opt-in to allow event duration changes by dragging the bottom edge.
  • Added: scheduleMap prop and KioskScheduleEntry interface — pass per-resource working hours to automatically dim off-hours slots with the tbk-slot-off CSS class.
  • Added: draggableAccessor — events with terminal statuses (completed, cancelled, no_show, rejected) are automatically non-draggable.
  • BreakBlockForm: “Block rest of day” quick action now flows through react-hook-form validation instead of bypassing it.
  • WalkInEntryForm: Fixed missing dependencies in useEffect for onServiceChange callback.
  • PaymentGate: Clarified misleading comment about Stripe confirmation flow.
  • kiosk-calendar now correctly lists react-big-calendar and date-fns as npm dependencies and booking-status-badge as a component dependency.
  • Updated kiosk-calendar documentation with new props, schedule map usage example, and CSS class reference.
  • Comprehensive documentation added for all booking, kiosk, and admin components.
  • Renamed CLI config generation function for branding consistency.
  • Updated package metadata, repository URLs, and GitHub links across all packages.
  • Swapped domains — demo app becomes main site, docs becomes subdomain.
  • Added publishing instructions and launch preparation documents.

  • Bumped all packages to v0.1.2.
  • Redesigned demo as single-page scroll showcase.
  • Wired registry build into docs deploy pipeline.
  • Removed client references from examples, fixed package scope.
  • Added npm registry READMEs for all packages.
  • Added comparison and examples pages, fixed card alignment in docs.
  • Renamed scope from slotkit to thebookingkit across entire codebase.

We are excited to announce the first public release of The Booking Kit!

  • @thebookingkit/core@0.1.1
  • @thebookingkit/server@0.1.1
  • @thebookingkit/cli@0.1.1
  • @thebookingkit/d1@0.1.1
  • Headless Core Engine: Timezone-aware slot computation and RRULE expansion.
  • D1 Adapter: Specialized support for Cloudflare D1 / SQLite with canonical date encoding.
  • Server Utilities: Multi-tenancy, JWT-based management tokens, and workflow automation.
  • UI Components: copy-paste source code for Calendar, Slot Picker, and Walk-in Entry.
  • CLI: init and add commands for rapid project setup.

For detailed changes per package, visit the GitHub repository.