Events Overview
This guide explains how events work in Talawa, including standalone events and recurring event series.
Overview
- There are two kinds of events:
- Standalone events: single occurrence stored directly in
events. - Recurring events: defined by a template in
events+ a recurrence rule inrecurrence_rules, with materialized child instances inrecurring_event_instances.
- Standalone events: single occurrence stored directly in
- Users never edit the template rows directly. They work with instances or with high-level actions (entire series, this and following, this instance only).
- A background worker pre-creates instances within a 12‑month "hot window" and keeps cleaning old ones. Cron runs hourly (generation) and daily (cleanup).
- Updates support three scopes: this instance only, entire series (name/description), and this and following (split the series).
- Deletes support three scopes: this instance only, this and following, and entire series.
Data Model
Tables Overview
Tables in talawa-api (Drizzle ORM):
Events Table
events- Stores both standalone events and recurring templates.
is_recurring_template(akaisRecurringEventTemplate) tells if a row is a recurring template.- Standalone events:
is_recurring_template = falseand their ownstart_at/end_atare final. - Recurring templates:
is_recurring_template = trueand act as the parent/defaults for instances.
Recurrence Rules Table
recurrence_rules- One per recurring template.
- Holds the RRULE-like data (
frequency,interval,byDay,byMonth,byMonthDay,recurrenceStartDate, optionalrecurrenceEndDateorcount). base_recurring_event_idlinks back to the template inevents.original_series_idties together all splits of the same logical series across time.
Recurring Event Instances Table
recurring_event_instances- Materialized occurrences for recurring series inside the hot window.
- Key fields:
base_recurring_event_id→ templaterecurrence_rule_id→ source ruleoriginal_series_id→ logical series identity across splitsoriginal_instance_start_time→ when the RRULE said it should startactual_start_time/actual_end_time→ after applying exceptions and durationsequence_number,total_count(if finite),is_cancelled
Event Exceptions Table
event_exceptions- Stores per-instance overrides when a user edits a single occurrence (e.g., rename just one date, shift time, etc.).
- Exception JSON holds only the fields that differ from the template.
Event Generation Windows Table
event_generation_windows- Per-organization config for the hot window and retention:
hot_window_months_aheaddefault 12 (1 year forward)history_retention_monthsdefault 3 (cleanup window)- Tracks window boundaries and processing metadata.
- Per-organization config for the hot window and retention:
Event Types
-
Standalone event
- Single row in
events, shown directly in calendars and lists. - Users edit/delete the event itself.
- Single row in
-
Recurring template (parent)
- Row in
eventswithis_recurring_template = true. - Paired with a
recurrence_rulesrow. - Not directly shown or edited by users as an event occurrence.
- Row in
-
Recurring instance (child)
- Row in
recurring_event_instancescreated by the background worker. - Inherits default fields (name, description, location, visibility, registerable, etc.) from the template.
- May have a matching
event_exceptionsrow to override specific fields for that one occurrence. - What users usually see and interact with on the calendar.
- Row in
Background Processing
Instance Creation
Talawa pre-creates occurrences for recurring events in a forward window to make queries fast and simple.
- Window: The default hot window is 12 months ahead; retention keeps ~3 months of history. Config lives in
event_generation_windows. - Cron: A background worker runs on a schedule (node-cron). Defaults:
- Generation: hourly (
EVENT_GENERATION_CRON_SCHEDULE, default0 * * * *). - Cleanup: daily at 02:00 UTC (
CLEANUP_CRON_SCHEDULE, default0 2 * * *).
- Generation: hourly (
- Pipeline (high level):
- Discover work: find orgs whose window needs extension and their recurring templates.
- Normalize recurrence rules (convert COUNT to a derived end date for consistent window math).
- Calculate all occurrences between window start and window end.
- Insert only missing instances (dedupe by
original_instance_start_time). - Update housekeeping (window end, last processed counts, etc.).
- Key behavior:
- If a rule is never-ending (no end date and no count), we only generate up to window end.
- If a rule has COUNT, the engine estimates/derives completion and stops accordingly.
- Instances store both the original schedule and the final start/end — so exceptions and re-calculations remain consistent.
Operations
Updating Recurring Series
Three user-facing options map to distinct backend behaviors.
Update This Event Only
- Mutation:
updateSingleRecurringEventInstance - What happens:
- We write an
event_exceptionsrow for that instance with only changed fields (e.g.,name,description,location,allDay,isPublic,isRegisterable,startAt,endAt). - If times are changed, we also update
actual_start_time/actual_end_timeon the instance. - Other instances are unaffected.
- We write an
Update Entire Series
- Mutation:
updateEntireRecurringEventSeries - What happens:
- We resolve the logical series via
original_series_idwhich is template. - We update only safe, series-wide fields on all base templates across the whole logical series (even if the series was split earlier):
- Supported:
name,description - Not changed here: timing/recurrence/location/visibility/registration flags
- Supported:
- We bump
last_updated_aton instances so caches and clients know something changed. - Because instances inherit name/description, the change is reflected everywhere (past/present/future), unless an instance has its own exception for those fields.
- We resolve the logical series via
Update This and Following
- Mutation:
updateThisAndFollowingEvents - What happens:
- We "cut" the current template's recurrence by setting its
recurrence_end_dateto just before the targeted instance. - We delete materialized instances from this point forward for the current template.
- We create a brand-new template event, apply provided changes (name, desc, location, timing, flags), and create a new
recurrence_rulesrow for it. - We set the new rule's
original_series_id(used as the logical series identifier going forward). - The background worker immediately generates future instances for the new template within the hot window.
- Net effect in the UI: everything before the split stays as-is under the old template; the targeted instance becomes the first occurrence of a new series with your updates; everything after follows the new pattern.
- We "cut" the current template's recurrence by setting its
Notes on original_series_id
- This field links templates and instances that belong to the same logical series across splits.
- Actions like "update entire series" and "delete this and following" consult
original_series_idto operate across all templates created from earlier splits.
Deleting Recurring Events
Three user-facing options:
Delete This Event Only
- Mutation:
deleteSingleEventInstance - What happens:
- We delete action items linked to this instance.
- We set
is_cancelled = trueon the instance (kept for history/reporting), and updatelast_updated_at. - Other instances/series are unaffected.
Delete This and Following
- Mutation:
deleteThisAndFollowingEvents - What happens:
- We set the current rule's
recurrence_end_dateto just before this occurrence (effectively trimming the series). - We find all instances with the same
original_series_idwhoseactual_start_time >=this instance and delete them. - We also delete their exceptions and action items.
- Past instances remain.
- We set the current rule's
Delete Entire Series
- Mutation:
deleteEntireRecurringEventSeries - Input is the template (base event) ID.
- What happens:
- Using the template's rule, we get its
original_series_id. - We delete, in order: action items (for all templates and instances), instance exceptions, all instances in the series, all recurrence rules in the series, and finally all template rows in
eventsthat belong to this logical series. - Attachments for the requested template are cleaned up from storage.
- Using the template's rule, we get its
Data Access
Reading and Merging
- The GraphQL
Eventtype represents both standalone events and recurring instances with a unified shape. - For recurring instances, "resolved" fields come from: template defaults + per-instance exceptions + instance timing.
- Useful instance metadata:
sequenceNumber(e.g., "5 of 12"),totalCountwhen finite.hasExceptionsandappliedExceptionDataflags to indicate customizations.
Scheduling and Cleanup
- Generation cron (hourly by default) grows the forward window and materializes missing instances.
- Cleanup cron (daily by default) removes old instances beyond
history_retention_monthsfor each org and advancesretention_start_date. - Defaults live in
event_generation_windowsand can be tuned per organization.
Edge Cases
- Never-ending series: only materialized up to the current hot window; cron extends as time moves on.
- Count-based rules: internally normalized to derive an end date for consistent generation; the original count semantics are preserved when calculating occurrences.
- Instance de-duplication: before inserting, generation checks for existing
original_instance_start_timewithin the window to avoid duplicates.
Code References
- Tables:
src/drizzle/tables/events.ts,recurrenceRules.ts,recurringEventInstances.ts,recurringEventExceptions.ts,eventGenerationWindows.ts - Generation service:
src/services/eventGeneration/* - Background workers and cron:
src/workers/backgroundWorkerService.ts,src/workers/eventGeneration/*,src/workers/eventCleanupWorker.ts - GraphQL mutations:
src/graphql/types/Mutation/*RecurringEvent*and related inputs.