@apiary-dev/postgres Reference
@apiary-dev/postgres implements the core BookingRepository
port using node-postgres. See the PostgreSQL guide for
setup, the schema, and behavior notes.
PostgresBookingRepository
Section titled “PostgresBookingRepository”class PostgresBookingRepository implements BookingRepository { constructor(pool: Pool, options?: PostgresBookingRepositoryOptions);}Takes a pg.Pool instance — you own its lifecycle (create, configure, and
pool.end() it yourself).
getAvailableWindows
Section titled “getAvailableWindows”getAvailableWindows(params: { startDate: Date; endDate: Date; providerId?: string; mode: "dynamic" | "explicit"; timeZone: string;}): Promise<AvailableWindow[]>;mode: "dynamic"— readsscheduling_business_hoursand expands them with core’sexpandWeeklyRules, using the suppliedtimeZone.provider_id IS NULLrows act as global fallback rules; a non-nullproviderIdmatches both the provider’s rules and the global ones.mode: "explicit"— readsscheduling_shiftswhereis_published = true, overlapping the requested range, clipped to it, and sorted.
getBookingsInRange
Section titled “getBookingsInRange”getBookingsInRange( startDate: Date, endDate: Date, providerId?: string,): Promise<TimeRange[]>;Returns confirmed bookings and active holds overlapping the range. Expired
holds (hold_expires_at <= now()) are excluded.
createHold
Section titled “createHold”createHold(params: { serviceId: string; providerId: string; range: TimeRange; customerId?: string;}): Promise<{ holdToken: string; expiresAt: Date }>;Atomically inserts a hold for range (half-open '[)'). Overlapping holds or
confirmations for the same provider are rejected by the exclusion constraint
and surfaced as BookingConflictError. Before
inserting, expired holds are marked 'expired' so stale rows never block
slots. holdToken is the booking row’s UUID.
confirmBooking
Section titled “confirmBooking”confirmBooking(params: { holdToken: string; customerDetails: Record<string, unknown>;}): Promise<{ bookingId: string }>;Transitions a hold to confirmed, storing customerDetails in the
customer_details JSONB column. Fails with BookingConflictError when the
hold is missing or expired. bookingId equals the original holdToken.
PostgresBookingRepositoryOptions
Section titled “PostgresBookingRepositoryOptions”interface PostgresBookingRepositoryOptions { holdDurationMinutes?: number; // default 10}Controls how long createHold reserves a slot before it is considered
expired.
BookingConflictError
Section titled “BookingConflictError”class BookingConflictError extends Error;Thrown when an operation conflicts with an existing booking or hold:
createHoldoverlapping an existing hold/confirmation (PostgreSQL exclusion violation, SQLSTATE23P01)confirmBookingwith a missing or expired hold
It is the single, typed signal for “that slot is no longer available” and maps
naturally to a 409 Conflict in an HTTP layer. Unrelated database errors are
rethrown unchanged.
Schema
Section titled “Schema”The migration (src/migrations/001_init.sql) creates:
| Table | Purpose | Notable constraints |
|---|---|---|
scheduling_business_hours |
Weekly rules (dynamic mode) | day_of_week 0–6, end_time > start_time, nullable provider_id (global fallback) |
scheduling_shifts |
Admin shifts (explicit mode) | shift_window TSTZRANGE, is_published gates availability |
scheduling_bookings |
Holds + confirmed bookings | EXCLUDE USING GIST (provider_id WITH =, booking_window WITH &&) WHERE status IN ('hold','confirmed') |
Requires the btree_gist extension, created by the migration itself.