Resource Engine API
For a conceptual overview of resource-based booking and practical examples, see the Resource Booking feature guide.
Slot Computation
Section titled “Slot Computation”import { getResourceAvailableSlots, isResourceSlotAvailable } from "@thebookingkit/core";
// Compute available slots across a resource poolconst slots = getResourceAvailableSlots( resources, { start: new Date("2026-03-09"), end: new Date("2026-03-13") }, "America/New_York", { duration: 60, minCapacity: 4 });
// Check if a specific slot is availableconst available = isResourceSlotAvailable( resources, "resource-123", new Date("2026-03-10T14:00:00.000Z"), new Date("2026-03-10T15:00:00.000Z"));getResourceAvailableSlots
Section titled “getResourceAvailableSlots”Compute capacity-aware available slots for a pool of bookable resources.
function getResourceAvailableSlots( resources: ResourceInput[], dateRange: DateRange, customerTimezone: string, options?: ResourceSlotOptions): ResourceSlot[]Description
Section titled “Description”The function runs the three-step pipeline (expand rules, apply overrides, generate slots) independently for each active resource, then merges results by time slot. A slot is included in the result only if at least one resource has remaining capacity of ≥ 1 at that time.
Filtering:
- Inactive resources (
isActive !== true) are excluded. options.resourceTypelimits computation to a specific type.options.minCapacityfilters out resources too small for the party.options.bufferBefore/bufferAfterapply per resource.- Slots whose end is before
options.now(default: current time) are discarded.
Merge semantics:
Each ResourceSlot carries availableResources — the list of resources still available at that time with per-resource remainingCapacity. Slots are sorted chronologically; ties are broken by total remaining capacity (highest first).
Parameters
Section titled “Parameters”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
resources | ResourceInput[] | Yes | — | The pool of bookable resources with their scheduling data |
dateRange | DateRange | Yes | — | UTC date range to compute slots within |
customerTimezone | string | Yes | — | IANA timezone for localStart/localEnd formatting |
options | ResourceSlotOptions | No | — | Duration, buffer, interval, type filter, and capacity filter |
Return Type
Section titled “Return Type”type Result = ResourceSlot[];
interface ResourceSlot { /** Start time in UTC ISO-8601 */ startTime: string; /** End time in UTC ISO-8601 */ endTime: string; /** Start time formatted in customer's local timezone */ localStart: string; /** End time formatted in customer's local timezone */ localEnd: string; /** Resources still available at this slot, with their remaining capacities */ availableResources: AvailableResource[];}
interface AvailableResource { /** Unique identifier of the resource */ resourceId: string; /** Display name of the resource */ resourceName: string; /** Free-form type string (e.g. "table", "room") */ resourceType: string; /** Remaining capacity after accounting for overlapping bookings' guest counts */ remainingCapacity: number;}Example
Section titled “Example”const resources = [ { id: "table-1", name: "Table 1", type: "dining_table", capacity: 4, isActive: true, rules: [ { rrule: "RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU", startTime: "11:00", endTime: "22:00", timezone: "America/New_York" } ], overrides: [], bookings: [ { startsAt: new Date("2026-03-10T12:00:00.000Z"), endsAt: new Date("2026-03-10T13:00:00.000Z"), status: "confirmed", guestCount: 2 } ] }, { id: "table-2", name: "Table 2", type: "dining_table", capacity: 6, isActive: true, rules: [ { rrule: "RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU", startTime: "11:00", endTime: "22:00", timezone: "America/New_York" } ], overrides: [], bookings: [] }];
const slots = getResourceAvailableSlots( resources, { start: new Date("2026-03-10T00:00:00.000Z"), end: new Date("2026-03-10T23:59:59.999Z") }, "America/New_York", { duration: 90, minCapacity: 4 });
// Result:// [// {// startTime: "2026-03-10T15:00:00.000Z",// endTime: "2026-03-10T16:30:00.000Z",// localStart: "3/10/2026, 11:00 AM",// localEnd: "3/10/2026, 12:30 PM",// availableResources: [// { resourceId: "table-1", resourceName: "Table 1", resourceType: "dining_table", remainingCapacity: 2 },// { resourceId: "table-2", resourceName: "Table 2", resourceType: "dining_table", remainingCapacity: 4 }// ]// },// // ... more slots// ]assignResource
Section titled “assignResource”Automatically select the best resource from a pool for a given time window.
function assignResource( resources: ResourceInput[], startTime: Date, endTime: Date, options?: ResourceSlotOptions): ResourceAssignmentResultDescription
Section titled “Description”The function applies the requested allocation strategy after narrowing the pool to resources that are:
- Active
- Match the optional
resourceType - Have sufficient capacity for the party (
requestedCapacity) - Are free at the requested time (no overlapping active bookings within buffer boundaries)
Assignment strategies:
"best_fit"(default) — smallest resource whosecapacity >= requestedCapacity"first_available"— first free resource in array order"round_robin"— resource with the lowestbookingCountinoptions.pastCounts"largest_first"— resource with the highestcapacity
Error escalation:
- No resource matches the requested
resourceType→ throwsResourceUnavailableError("no_matching_type") - No matching resource has sufficient
capacity→ throwsResourceUnavailableError("no_capacity") - All capacity-sufficient resources are booked → throws
ResourceUnavailableError("all_booked")
Parameters
Section titled “Parameters”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
resources | ResourceInput[] | Yes | — | The pool of bookable resources |
startTime | Date | Yes | — | Requested booking start time (UTC) |
endTime | Date | Yes | — | Requested booking end time (UTC) |
options | ResourceSlotOptions | No | — | Strategy, capacity, buffer, and round-robin counts |
Return Type
Section titled “Return Type”interface ResourceAssignmentResult { /** Unique identifier of the assigned resource */ resourceId: string; /** Display name of the assigned resource */ resourceName: string; /** Human-readable reason for the selection (e.g. "best_fit", "round_robin") */ reason: string;}Thrown Errors
Section titled “Thrown Errors”class ResourceUnavailableError extends Error { code: "RESOURCE_UNAVAILABLE"; reason: "no_capacity" | "no_matching_type" | "all_booked";}Example
Section titled “Example”const resources = [ { id: "desk-1", name: "Desk 1", type: "standing_desk", capacity: 1, isActive: true, rules: [/* ... */], overrides: [], bookings: [ { startsAt: new Date("2026-03-10T09:00:00.000Z"), endsAt: new Date("2026-03-10T10:00:00.000Z"), status: "confirmed" } ] }, { id: "desk-2", name: "Desk 2", type: "standing_desk", capacity: 1, isActive: true, rules: [/* ... */], overrides: [], bookings: [] }];
try { const assigned = assignResource( resources, new Date("2026-03-10T09:30:00.000Z"), new Date("2026-03-10T10:30:00.000Z"), { strategy: "first_available", requestedCapacity: 1, bufferAfter: 15 } );
// Result: { resourceId: "desk-2", resourceName: "Desk 2", reason: "first_available" }} catch (error) { if (error instanceof ResourceUnavailableError) { console.error(`Failed to assign: ${error.reason}`); }}isResourceSlotAvailable
Section titled “isResourceSlotAvailable”Quick availability check for a single time window, either for a specific resource or for any resource in the pool.
function isResourceSlotAvailable( resources: ResourceInput[], resourceId: string | undefined, startTime: Date, endTime: Date, bufferBefore?: number, bufferAfter?: number, options?: ResourceSlotOptions): ResourceSlotAvailabilityResultDescription
Section titled “Description”When resourceId is provided, the check is scoped to that one resource and returns:
- Not found or inactive →
{ available: false, reason: "resource_inactive" } - Override blocks the date →
{ available: false, reason: "blocked_date" } - Start/end falls outside all availability windows →
{ available: false, reason: "outside_availability" } - Overlapping booking (exact overlap) →
{ available: false, reason: "resource_booked" } - Overlapping booking (buffer-only) →
{ available: false, reason: "buffer_conflict" } - Otherwise →
{ available: true, remainingCapacity }
When resourceId is undefined, the check is a pool-level query: any active resource that passes all checks is sufficient; the best (highest) remainingCapacity across passing resources is returned.
Parameters
Section titled “Parameters”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
resources | ResourceInput[] | Yes | — | The resource pool to search |
resourceId | string | undefined | Yes | — | Resource to check, or undefined for a pool-level check |
startTime | Date | Yes | — | Proposed slot start time (UTC) |
endTime | Date | Yes | — | Proposed slot end time (UTC) |
bufferBefore | number | No | 0 | Buffer minutes before the slot |
bufferAfter | number | No | 0 | Buffer minutes after the slot |
options | ResourceSlotOptions | No | — | Optional options.now for test determinism |
Return Type
Section titled “Return Type”type ResourceSlotAvailabilityResult = | { available: true; remainingCapacity: number; } | { available: false; reason: | "outside_availability" | "resource_booked" | "blocked_date" | "buffer_conflict" | "resource_inactive"; };Example: Specific Resource Check
Section titled “Example: Specific Resource Check”const resources = [ { id: "room-301", name: "Conference Room 301", type: "conference_room", capacity: 12, isActive: true, rules: [ { rrule: "RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR", startTime: "08:00", endTime: "18:00", timezone: "America/New_York" } ], overrides: [ { date: new Date("2026-03-10T00:00:00.000Z"), isUnavailable: true } ], bookings: [ { startsAt: new Date("2026-03-11T14:00:00.000Z"), endsAt: new Date("2026-03-11T15:00:00.000Z"), status: "confirmed", guestCount: 6 } ] }];
const result = isResourceSlotAvailable( resources, "room-301", new Date("2026-03-11T15:30:00.000Z"), new Date("2026-03-11T16:30:00.000Z"), 15, // bufferBefore 15 // bufferAfter);
// Result: { available: true, remainingCapacity: 6 }Example: Pool-Level Check
Section titled “Example: Pool-Level Check”const result = isResourceSlotAvailable( resources, undefined, // Check any resource in the pool new Date("2026-03-11T15:30:00.000Z"), new Date("2026-03-11T16:30:00.000Z"), 15, 15);
// Result: { available: true, remainingCapacity: 12 }getResourcePoolSummary
Section titled “getResourcePoolSummary”Produce a utilization summary for each available time slot across the resource pool.
function getResourcePoolSummary( resources: ResourceInput[], dateRange: DateRange, customerTimezone: string, options?: ResourceSlotOptions): ResourcePoolSummary[]Description
Section titled “Description”The function runs the same per-resource pipeline as getResourceAvailableSlots but instead of returning slots with resource lists it returns aggregated metrics for each unique slot time:
- Total resource count
- Available count
- Utilization percentage
- Per-type breakdown
Designed for admin dashboards that need a live capacity overview (e.g. a restaurant host screen or gym manager board). The function is pure and deterministic when options.now is provided, making it safe for polling.
Parameters
Section titled “Parameters”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
resources | ResourceInput[] | Yes | — | The pool of bookable resources with their scheduling data |
dateRange | DateRange | Yes | — | UTC date range for the summary |
customerTimezone | string | Yes | — | IANA timezone for localStart/localEnd formatting |
options | ResourceSlotOptions | No | — | Duration, buffer, interval, type filter, and options.now |
Return Type
Section titled “Return Type”type Result = ResourcePoolSummary[];
interface ResourcePoolSummary { /** Slot start time in UTC ISO-8601 */ startTime: string; /** Slot end time in UTC ISO-8601 */ endTime: string; /** Slot start formatted in the customer's local timezone */ localStart: string; /** Slot end formatted in the customer's local timezone */ localEnd: string; /** Total number of resources in the pool */ totalResources: number; /** Number of resources still available at this slot */ availableResources: number; /** Percentage of resources currently booked, rounded to the nearest integer */ utilizationPercent: number; /** Per-type breakdown of total vs available resources */ byType: Record<string, { total: number; available: number }>;}Example
Section titled “Example”const resources = [ { id: "court-1", name: "Court 1", type: "tennis_court", capacity: 2, isActive: true, rules: [ { rrule: "RRULE:FREQ=DAILY", startTime: "08:00", endTime: "20:00", timezone: "America/Los_Angeles" } ], overrides: [], bookings: [ { startsAt: new Date("2026-03-10T16:00:00.000Z"), endsAt: new Date("2026-03-10T17:00:00.000Z"), status: "confirmed", guestCount: 2 } ] }, { id: "court-2", name: "Court 2", type: "tennis_court", capacity: 2, isActive: true, rules: [ { rrule: "RRULE:FREQ=DAILY", startTime: "08:00", endTime: "20:00", timezone: "America/Los_Angeles" } ], overrides: [], bookings: [] }];
const summary = getResourcePoolSummary( resources, { start: new Date("2026-03-10T00:00:00.000Z"), end: new Date("2026-03-10T23:59:59.999Z") }, "America/Los_Angeles", { duration: 60 });
// Result:// [// {// startTime: "2026-03-10T16:00:00.000Z",// endTime: "2026-03-10T17:00:00.000Z",// localStart: "3/10/2026, 8:00 AM",// localEnd: "3/10/2026, 9:00 AM",// totalResources: 2,// availableResources: 1,// utilizationPercent: 50,// byType: {// tennis_court: { total: 2, available: 1 }// }// },// {// startTime: "2026-03-10T17:00:00.000Z",// endTime: "2026-03-10T18:00:00.000Z",// localStart: "3/10/2026, 9:00 AM",// localEnd: "3/10/2026, 10:00 AM",// totalResources: 2,// availableResources: 2,// utilizationPercent: 0,// byType: {// tennis_court: { total: 2, available: 2 }// }// }// ]ResourceInput
Section titled “ResourceInput”A bookable resource (table, room, court, desk) with its scheduling data.
interface ResourceInput { /** Unique identifier for this resource */ id: string; /** Display name of the resource (e.g. "Table 5", "Studio A") */ name: string; /** Free-form category string (e.g. "table", "room", "court", "desk") */ type: string; /** Maximum party size this resource can accommodate */ capacity: number; /** Whether this resource participates in slot computation */ isActive: boolean; /** Resource availability rules (RRULE-based) */ rules: AvailabilityRuleInput[]; /** Resource availability overrides (date-specific exceptions) */ overrides: AvailabilityOverrideInput[]; /** Existing bookings on this resource for conflict checking */ bookings: BookingInput[];}ResourcePoolInput
Section titled “ResourcePoolInput”Alias for an array of ResourceInput objects passed to resource slot functions.
type ResourcePoolInput = ResourceInput[];AvailableResource
Section titled “AvailableResource”A single available resource within a time slot.
interface AvailableResource { /** Unique identifier of the resource */ resourceId: string; /** Display name of the resource */ resourceName: string; /** Free-form type string (e.g. "table", "room") */ resourceType: string; /** Remaining capacity after accounting for overlapping bookings' guest counts */ remainingCapacity: number;}ResourceSlot
Section titled “ResourceSlot”A computed resource slot with available resources attached.
interface ResourceSlot extends Slot { /** Resources still available at this slot, with their remaining capacities */ availableResources: AvailableResource[];}
interface Slot { /** Start time in UTC ISO-8601 */ startTime: string; /** End time in UTC ISO-8601 */ endTime: string; /** Start time formatted in the customer's local timezone */ localStart: string; /** End time formatted in the customer's local timezone */ localEnd: string;}ResourceAssignmentStrategy
Section titled “ResourceAssignmentStrategy”Strategy for auto-assigning a resource to a booking.
type ResourceAssignmentStrategy = | "best_fit" | "first_available" | "round_robin" | "largest_first";| Strategy | Description |
|---|---|
"best_fit" | Smallest resource whose capacity >= requestedCapacity (default) |
"first_available" | First free resource in array order |
"round_robin" | Resource with the lowest bookingCount in options.pastCounts |
"largest_first" | Resource with the highest capacity |
ResourceAssignmentResult
Section titled “ResourceAssignmentResult”Result of resource auto-assignment.
interface ResourceAssignmentResult { /** Unique identifier of the assigned resource */ resourceId: string; /** Display name of the assigned resource */ resourceName: string; /** Human-readable reason for the selection (e.g. "best_fit", "round_robin") */ reason: string;}ResourceSlotAvailabilityResult
Section titled “ResourceSlotAvailabilityResult”Resource slot availability check result.
type ResourceSlotAvailabilityResult = | { available: true; remainingCapacity: number; } | { available: false; reason: | "outside_availability" | "resource_booked" | "blocked_date" | "buffer_conflict" | "resource_inactive"; };| Reason | Description |
|---|---|
"outside_availability" | Slot falls outside all availability windows (rules + overrides) |
"resource_booked" | Slot overlaps with an existing booking’s exact time window |
"blocked_date" | Slot date is blocked by an availability override (isUnavailable: true) |
"buffer_conflict" | Slot overlaps with buffer time around an existing booking (not the booking itself) |
"resource_inactive" | Resource is not active or not found in the pool |
ResourcePoolSummary
Section titled “ResourcePoolSummary”Resource pool summary for a single time slot.
interface ResourcePoolSummary { /** Slot start time in UTC ISO-8601 */ startTime: string; /** Slot end time in UTC ISO-8601 */ endTime: string; /** Slot start formatted in the customer's local timezone */ localStart: string; /** Slot end formatted in the customer's local timezone */ localEnd: string; /** Total number of resources in the pool */ totalResources: number; /** Number of resources still available at this slot */ availableResources: number; /** Percentage of resources currently booked, rounded to the nearest integer */ utilizationPercent: number; /** Per-type breakdown of total vs available resources */ byType: Record<string, { total: number; available: number }>;}ResourceSlotOptions
Section titled “ResourceSlotOptions”Options for resource slot computation, extending the base SlotComputeOptions.
interface ResourceSlotOptions extends SlotComputeOptions { /** Slot duration in minutes (default: 30) */ duration?: number; /** Buffer time in minutes before each slot (default: 0) */ bufferBefore?: number; /** Buffer time in minutes after each slot (default: 0) */ bufferAfter?: number; /** Slot interval in minutes (default: same as duration) */ slotInterval?: number; /** The reference point for filtering out past slots (default: current time) */ now?: Date; /** Only include resources that can accommodate at least this many guests */ minCapacity?: number; /** Filter to resources of this type only */ resourceType?: string; /** Assignment strategy used by `assignResource()` (default: "best_fit") */ strategy?: ResourceAssignmentStrategy; /** * Past booking counts per resource, used for `round_robin` balancing. * Provide `{ resourceId, bookingCount }` entries for each resource. */ pastCounts?: Array<{ resourceId: string; bookingCount: number }>; /** Requested party size for `assignResource()` (default: 1) */ requestedCapacity?: number;}ResourceUnavailableError
Section titled “ResourceUnavailableError”Thrown when no resource can be assigned to a booking.
class ResourceUnavailableError extends Error { code: "RESOURCE_UNAVAILABLE"; reason: "no_capacity" | "no_matching_type" | "all_booked";
constructor( reason: "no_capacity" | "no_matching_type" | "all_booked", message?: string );}| Reason | Description |
|---|---|
"no_capacity" | No matching resource has sufficient capacity for the requested party size |
"no_matching_type" | No resource in the pool matches the requested resourceType |
"all_booked" | All capacity-sufficient resources are booked at the requested time |
Shared Types
Section titled “Shared Types”The resource engine uses these shared types from the core scheduling system:
/** Availability rule input */interface AvailabilityRuleInput { rrule: string; startTime: string; // "HH:mm" endTime: string; // "HH:mm" timezone: string; // IANA timezone validFrom?: Date | null; validUntil?: Date | null;}
/** Availability override input */interface AvailabilityOverrideInput { date: Date; startTime?: string | null; endTime?: string | null; isUnavailable: boolean;}
/** Existing booking input */interface BookingInput { startsAt: Date; endsAt: Date; status: string; resourceId?: string; // Resource ID for resource-based bookings guestCount?: number; // Number of guests/seats (defaults to 1)}
/** Date range for queries */interface DateRange { start: Date; // Must be UTC end: Date; // Must be UTC}