RecurrenceService
Expand recurrence rules and manage recurring booking series
Overview
RecurrenceService handles recurrence rule expansion and full lifecycle management of recurring booking series. It supports weekly, biweekly, and custom (ISO calendar week) patterns, and provides methods for modifying single instances, future instances, or entire series.
import { RecurrenceService } from '@roomkit/nestjs';Methods
expandSeries
Expand a recurrence rule into a sorted array of dates. Supports WEEKLY (every week), BIWEEKLY (every other week), and CUSTOM (specific ISO calendar weeks) frequencies. Exception dates are excluded from the result.
expandSeries(rule: RecurrenceRule): Date[]| Parameter | Type | Description |
|---|---|---|
rule | RecurrenceRule | The recurrence rule to expand |
Returns a sorted array of Date objects, each set to 00:00:00 on the occurrence day.
const dates = recurrenceService.expandSeries({
id: 'rule-1',
frequency: 'WEEKLY',
startsAt: new Date('2026-03-02'),
endsAt: new Date('2026-03-30'),
dayOfWeek: 1, // Monday
exceptions: [new Date('2026-03-16')],
});
// Returns: [2026-03-02, 2026-03-09, 2026-03-23, 2026-03-30]createRecurringBooking
Create a full recurring booking series. Creates the recurrence rule, expands it into individual dates, and creates a booking for each occurrence. Conflicts on individual dates do not abort the entire operation — they are collected and returned.
async createRecurringBooking(
bookingTemplate: CreateBookingDto,
rule: CreateRecurrenceRuleDto,
): Promise<{
rule: RecurrenceRule;
bookings: Booking[];
conflicts: { date: Date; error: string }[];
}>| Parameter | Type | Description |
|---|---|---|
bookingTemplate | CreateBookingDto | Template used for each individual booking |
rule | CreateRecurrenceRuleDto | Recurrence pattern definition |
const result = await recurrenceService.createRecurringBooking(
{
roomId: 'room-a1',
personId: 'user-42',
title: 'Weekly Standup',
startsAt: new Date('2026-03-02T09:00:00'),
endsAt: new Date('2026-03-02T09:30:00'),
priorityName: 'standard',
},
{
frequency: 'WEEKLY',
startsAt: new Date('2026-03-02'),
endsAt: new Date('2026-06-29'),
dayOfWeek: 1,
},
);
console.log(`Created ${result.bookings.length} bookings`);
if (result.conflicts.length > 0) {
console.log('Skipped dates:', result.conflicts);
}modifySingle
Modify a single instance within a recurring series. The instance is marked as MODIFIED and detached from automatic series updates. Re-checks conflicts if the time range or room changes.
async modifySingle(
bookingId: string,
changes: UpdateBookingDto,
expectedVersion: number,
): Promise<Booking>| Parameter | Type | Description |
|---|---|---|
bookingId | string | ID of the individual booking instance to modify |
changes | UpdateBookingDto | Fields to update |
expectedVersion | number | Expected version for optimistic concurrency |
const updated = await recurrenceService.modifySingle(
'booking-7',
{
roomId: 'room-b2',
startsAt: new Date('2026-03-16T10:00:00'),
endsAt: new Date('2026-03-16T10:30:00'),
},
1,
);Throws BookingNotFoundError if the booking does not exist.
Throws BookingConflictError if the updated time/room has an overlapping booking.
modifyThisAndFuture
Modify a booking instance and all future instances in the series. This splits the series: the original rule is truncated to end before the target date, a new rule is created from the target date onward, future ORIGINAL instances are deleted, and the new rule is expanded into fresh bookings.
async modifyThisAndFuture(
bookingId: string,
changes: UpdateBookingDto,
): Promise<{
newRule: RecurrenceRule;
bookings: Booking[];
}>| Parameter | Type | Description |
|---|---|---|
bookingId | string | ID of the booking instance from which to apply changes |
changes | UpdateBookingDto | Fields to apply to the new series segment |
const result = await recurrenceService.modifyThisAndFuture(
'booking-12',
{
roomId: 'room-c3',
startsAt: new Date('2026-04-06T11:00:00'),
endsAt: new Date('2026-04-06T11:30:00'),
},
);
console.log('New rule:', result.newRule.id);
console.log('New bookings:', result.bookings.length);Throws BookingNotFoundError if the booking does not exist.
modifyAll
Modify all instances in a recurring series. Deletes all ORIGINAL instances and re-creates them with the applied changes. Instances that have been individually modified (MODIFIED) or detached (DETACHED) are not affected and are returned as skipped.
async modifyAll(
ruleId: string,
changes: UpdateBookingDto,
): Promise<{
bookings: Booking[];
skipped: Booking[];
}>| Parameter | Type | Description |
|---|---|---|
ruleId | string | ID of the recurrence rule to modify |
changes | UpdateBookingDto | Fields to apply to all re-created instances |
const result = await recurrenceService.modifyAll(
'rule-1',
{
roomId: 'room-d4',
title: 'Updated Weekly Standup',
},
);
console.log(`Re-created ${result.bookings.length} bookings`);
console.log(`Skipped ${result.skipped.length} individually modified bookings`);RecurrenceRule
interface RecurrenceRule {
id: string;
frequency: 'WEEKLY' | 'BIWEEKLY' | 'CUSTOM';
startsAt: Date;
endsAt: Date;
dayOfWeek: number;
isoWeeks?: number[]; // Only for CUSTOM frequency
exceptions: Date[];
}| Field | Type | Description |
|---|---|---|
frequency | 'WEEKLY' | 'BIWEEKLY' | 'CUSTOM' | Recurrence pattern |
startsAt | Date | Series start date |
endsAt | Date | Series end date |
dayOfWeek | number | Day of the week (0 = Sunday, 6 = Saturday) |
isoWeeks | number[] | Specific ISO calendar weeks (only for CUSTOM) |
exceptions | Date[] | Dates to exclude from the series |