My App
RoomKitBackend

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[]
ParameterTypeDescription
ruleRecurrenceRuleThe 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 }[];
}>
ParameterTypeDescription
bookingTemplateCreateBookingDtoTemplate used for each individual booking
ruleCreateRecurrenceRuleDtoRecurrence 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>
ParameterTypeDescription
bookingIdstringID of the individual booking instance to modify
changesUpdateBookingDtoFields to update
expectedVersionnumberExpected 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[];
}>
ParameterTypeDescription
bookingIdstringID of the booking instance from which to apply changes
changesUpdateBookingDtoFields 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[];
}>
ParameterTypeDescription
ruleIdstringID of the recurrence rule to modify
changesUpdateBookingDtoFields 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[];
}
FieldTypeDescription
frequency'WEEKLY' | 'BIWEEKLY' | 'CUSTOM'Recurrence pattern
startsAtDateSeries start date
endsAtDateSeries end date
dayOfWeeknumberDay of the week (0 = Sunday, 6 = Saturday)
isoWeeksnumber[]Specific ISO calendar weeks (only for CUSTOM)
exceptionsDate[]Dates to exclude from the series

On this page