Before the submit: how the Booker knows which times are open
Before the Booker can let a visitor submit the booking form traced in module 1, it has to know which times are even clickable. This module follows useSchedule as it calls the slots.getSchedule tRPC procedure — a second client/server transport alongside Pages API routes — into getScheduleHandler and the AvailableSlotsService, which computes open slots on every request from a host's schedule, existing bookings, and buffer settings rather than reading them off a stored table.
export const slotsRouter = router({ getSchedule: publicProcedure.input(ZGetScheduleInputSchema).query(async ({ input, ctx }) => { const { getScheduleHandler } = await import("./getSchedule.handler"); return getScheduleHandler({ ctx, input, }); }),
packages/trpc/server/routers/viewer/slots/_router.tsx · lines 17–25getSchedule is declared as a publicProcedure — no session required, matching the public booking page — with a Zod input schema (ZGetScheduleInputSchema) and a .query(...) resolver. Contrast this with module 1's createBooking, which is a handler function exported from a Next.js Pages API route file and matched by URL path. Same client-to-server boundary, second transport: tRPC procedures are named and typed end-to-end (viewer.slots.getSchedule) instead of parsed out of a request body by hand.import { getAvailableSlotsService } from "@calcom/features/di/containers/AvailableSlots";import type { GetScheduleOptions } from "./types";export const getScheduleHandler = async ({ ctx, input }: GetScheduleOptions) => { const availableSlotsService = getAvailableSlotsService(); return await availableSlotsService.getAvailableSlots({ ctx, input });};
packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts · lines 1–8getScheduleHandler does none of that — it resolves an AvailableSlotsService via getAvailableSlotsService() (a dependency-injection container, not a direct class import) and immediately returns availableSlotsService.getAvailableSlots({ ctx, input }). All the real logic for computing openings lives in that service, not in the handler.function _enrichUsersWithData() { return usersWithCredentials.map((currentUser) => { return { ...currentUser, currentBookings: currentBookingsAllUsers .filter( (b) => b.userId === currentUser.id || b.attendees?.some((a) => a.email === currentUser.email) ) .map((bookings) => { const { attendees: _attendees, ...bookingWithoutAttendees } = bookings; return bookingWithoutAttendees; }), outOfOfficeDays: outOfOfficeDaysAllUsers.filter((o) => o.user.id === currentUser.id), }; }); } const enrichUsersWithData = withReporting(_enrichUsersWithData.bind(this), "enrichUsersWithData"); const users = enrichUsersWithData(); const premappedUsersAvailability = await this.dependencies.userAvailabilityService.getUsersAvailability({ users, query: { dateFrom: startTime.format(), dateTo: endTime.format(), eventTypeId: eventType.id, afterEventBuffer: eventType.afterEventBuffer, beforeEventBuffer: eventType.beforeEventBuffer, duration: input.duration || 0, returnDateOverrides: false, bypassBusyCalendarTimes, silentlyHandleCalendarFailures: silentCalendarFailures, mode, },
packages/trpc/server/routers/viewer/slots/util.ts · lines 788–820currentBookings is sliced per host out of currentBookingsAllUsers — bookings already sitting in the database, matched by userId or attendee email — while eventType.afterEventBuffer/beforeEventBuffer are handed straight into getUsersAvailability alongside the date range. The host's own schedule (schedule.availability) is read further inside that same service call. Nothing here is a precomputed 'availability' record; it's schedule, bookings, and buffers combined fresh for this one request.aggregate.push({ start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(), end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(), title, source: `eventType-${eventType?.id}-booking-${id}`, }); return aggregate;
packages/features/busyTimes/services/getBusyTimes.ts · lines 176–182minutesToBlockBeforeEvent earlier and ends minutesToBlockAfterEvent later than the booking's own start/end. That widened interval — not the raw booking time — is what candidate slots get checked against, so a buffer never needs to be written anywhere; it's applied at read time, on top of whatever bookings already exist.Checkpoint: fetching available slots
Answered in place — nothing is graded, everything is explained. 0 / 3 passed
Which pairing plays the same client/server-boundary role for reading slots that `createBooking`'s POST handler played for submitting a booking?
When does a host's `beforeEventBuffer`/`afterEventBuffer` actually get turned into blocked time?
How do the Booker's slot-fetching and booking-submission requests relate to each other?