Repository: SwitchbackTech/compass (ts)

Issue:
Optimize event import performance
### Feature Description

Update backend code to reduce how long it takes for calendar events to finish being imported into Compass.


### Use Case

This'll improve the onboarding UX by lettings users see and use their existing calendar events quicker.

### Additional Context

This should be worked on after #543 is finished. 

### Implementation Notes
Here are some ideas on how to improve performance:

Instead of a general `fetchAndProcessEventsPageByPage` call with `singleEvents: true` (which fetches all instances and re-fetches regular events), iterate over the `recurringEventIds` collected in Pass 1.

For each `recurringEventId`, make a specific API call to the Google Calendar API to fetch only the instances for that particular master event (e.g., using `calendar.events.instances({ calendarId, eventId: masterEventId, ... }))`.

These individual API calls for each master event's instances can be executed in parallel (e.g., using `Promise.allSettled` for robustness against individual failures).

The processing logic (`shouldProcessDuringPass2`) would then handle these fetched instances.

After all parallel instance fetching and processing is complete, make a separate, lightweight `events.list` call (e.g., with maxResults: 1 and singleEvents: true) to obtain the `nextSyncToken` for the calendar.

Pull request:
Feat optimize event import performance
# What does this PR do?

This PR optimizes the backend calendar import/full sync process to reduce how long it takes for calendar events to finish being imported into Compass.

## Description

Closes #544 

## Manual Test

- Create a test `GCAL` account with more than 50,000 events.
- Signup with the test account
- Check that the calendar import takes less than (25 secs)


Patch:
diff --git a/packages/backend/src/auth/controllers/auth.controller.ts b/packages/backend/src/auth/controllers/auth.controller.ts
index 35c99f00..a5d0c65a 100644
--- a/packages/backend/src/auth/controllers/auth.controller.ts
+++ b/packages/backend/src/auth/controllers/auth.controller.ts
@@ -27,10 +27,7 @@ import {
   SReqBody,
 } from "@backend/common/types/express.types";
 import syncService from "@backend/sync/services/sync.service";
-import {
-  findCompassUserBy,
-  updateGoogleRefreshToken,
-} from "@backend/user/queries/user.queries";
+import { updateGoogleRefreshToken } from "@backend/user/queries/user.queries";
 import userService from "@backend/user/services/user.service";
 import { initGoogleClient } from "../services/auth.utils";
 
diff --git a/packages/backend/src/common/services/gcal/gcal.service.ts b/packages/backend/src/common/services/gcal/gcal.service.ts
index 05b87b10..f534cade 100644
--- a/packages/backend/src/common/services/gcal/gcal.service.ts
+++ b/packages/backend/src/common/services/gcal/gcal.service.ts
@@ -1,12 +1,31 @@
+import type { GaxiosResponse } from "gaxios";
 import { GCAL_NOTIFICATION_ENDPOINT } from "@core/constants/core.constants";
-import { gCalendar, gParamsEventsList, gSchema$Event } from "@core/types/gcal";
-import { Params_WatchEvents } from "@core/types/sync.types";
+import type {
+  gCalendar,
+  gParamsEventsList,
+  gSchema$Event,
+  gSchema$Events,
+} from "@core/types/gcal";
+import type { Params_WatchEvents } from "@core/types/sync.types";
 import { GCAL_PRIMARY } from "@backend/common/constants/backend.constants";
 import { ENV } from "@backend/common/constants/env.constants";
 import { error } from "@backend/common/errors/handlers/error.handler";
 import { GcalError } from "@backend/common/errors/integration/gcal/gcal.errors";
 
 class GCalService {
+  private validateGCalResponse<T>(
+    response: GaxiosResponse<T>,
+    message = "Gcal request failed.",
+  ) {
+    const { status, statusText } = response;
+
+    if (status !== 200 || statusText !== "OK") {
+      throw new Error(message);
+    }
+
+    return response;
+  }
+
   async createEvent(gcal: gCalendar, event: gSchema$Event) {
     const response = await gcal.events.insert({
       calendarId: "primary",
@@ -31,12 +50,16 @@ class GCalService {
     eventId: string,
     timeMin?: string,
     timeMax?: string,
+    pageToken?: string,
+    maxResults?: number,
   ) {
     const response = await gcal.events.instances({
       calendarId,
       eventId,
       timeMin,
       timeMax,
+      pageToken,
+      maxResults,
     });
     return response;
   }
@@ -46,6 +69,104 @@ class GCalService {
     return response;
   }
 
+  async *getBaseRecurringEventInstances({
+    gCal,
+    calendarId,
+    eventId,
+    maxResults = 1000,
+    timeMin,
+    timeMax,
+    pageToken,
+  }: {
+    gCal: gCalendar;
+    calendarId: string;
+    eventId: string;
+    maxResults?: number;
+    timeMin?: string;
+    timeMax?: string;
+    pageToken?: string;
+  }): AsyncGenerator<
+    Pick<gSchema$Events, "nextPageToken" | "nextSyncToken" | "items">
+  > {
+    let hasNextPage = false;
+
+    do {
+      const { data = {} } = await this.getEventInstances(
+        gCal,
+        calendarId,
+        eventId,
+        timeMin,
+        timeMax,
+        pageToken,
+        maxResults,
+      ).then((res) =>
+        this.validateGCalResponse(
+          res,
+          `Failed to fetch gcal instances for base event ${eventId}`,
+        ),
+      );
+
+      const { nextPageToken, nextSyncToken, items = [] } = data;
+
+      pageToken = nextPageToken === null ? undefined : nextPageToken;
+
+      hasNextPage =
+        typeof nextPageToken === "string" && nextPageToken.length > 0;
+
+      yield { nextPageToken, nextSyncToken, items };
+    } while (hasNextPage);
+  }
+
+  /**
+   * getAllEvents
+   * generator function to list all google calendar events
+   */
+  async *getAllEvents({
+    gCal,
+    calendarId,
+    maxResults = 1000,
+    singleEvents = false,
+    pageToken,
+    syncToken,
+  }: {
+    gCal: gCalendar;
+    calendarId: string;
+    maxResults?: number;
+    singleEvents?: boolean;
+    pageToken?: string;
+    syncToken?: string;
+  }): AsyncGenerator<
+    Pick<gSchema$Events, "nextPageToken" | "nextSyncToken" | "items">
+  > {
+    let hasNextPage = false;
+    let isLastPage = true;
+
+    do {
+      const { data = {} } = await this.getEvents(gCal, {
+        calendarId,
+        singleEvents,
+        maxResults,
+        pageToken,
+        syncToken,
+      }).then((res) =>
+        this.validateGCalResponse(res, `Failed to fetch gcal events`),
+      );
+
+      const { nextPageToken, nextSyncToken, items = [] } = data;
+
+      pageToken = nextPageToken === null ? undefined : nextPageToken;
+      syncToken = nextSyncToken === null ? undefined : nextSyncToken;
+
+      hasNextPage =
+        typeof nextPageToken === "string" && nextPageToken.length > 0;
+
+      isLastPage =
+        typeof nextSyncToken === "string" && nextSyncToken.length > 0;
+
+      yield { nextPageToken, nextSyncToken, items };
+    } while (hasNextPage || !isLastPage);
+  }
+
   async getCalendarlist(gcal: gCalendar) {
     const response = await gcal.calendarList.list();
 
diff --git a/packages/backend/src/sync/services/import/all/import.all.gcal.ts b/packages/backend/src/sync/services/import/all/import.all.gcal.ts
deleted file mode 100644
index 24e4126e..00000000
--- a/packages/backend/src/sync/services/import/all/import.all.gcal.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { ObjectId } from "mongodb";
-import { Origin } from "@core/constants/core.constants";
-import { Logger } from "@core/logger/winston.logger";
-import { MapEvent } from "@core/mappers/map.event";
-import {
-  gCalendar,
-  gParamsImportAllEvents,
-  gSchema$Event,
-} from "@core/types/gcal";
-import { EventError } from "@backend/common/errors/event/event.errors";
-import { error } from "@backend/common/errors/handlers/error.handler";
-import gcalService from "@backend/common/services/gcal/gcal.service";
-import eventService from "@backend/event/services/event.service";
-import { Callback_EventProcessor, Map_Recurrences } from "../sync.import.types";
-import { assignIds } from "./import.all.util";
-
-const logger = Logger("sync.import.all.gcal");
-
-/**
- * Generic helper: Fetches events page by page, processes using a callback, and saves.
- */
-export const fetchAndProcessEventsPageByPage = async (
-  userId: string,
-  gcal: gCalendar,
-  calendarId: string,
-  gcalApiParams: gParamsImportAllEvents,
-  // Callback function to process each event and decide if it should be saved
-  shouldSave: Callback_EventProcessor,
-  // Shared state accessible/modifiable by the processor
-  sharedState: Map_Recurrences,
-  captureSyncToken: boolean,
-): Promise<{
-  savedCount: number;
-  processedCount: number;
-  nextSyncToken: string | undefined;
-  baseEventMap: Map<string, ObjectId>;
-}> => {
-  const start = Date.now();
-
-  try {
-    let nextPageToken: string | undefined = undefined;
-    let finalNextSyncToken: string | undefined = undefined;
-    let totalProcessedApi = 0;
-    let totalSaved = 0;
-    const passIdentifier = gcalApiParams.singleEvents ? "Pass 2" : "Pass 1"; // For logging
-    let baseEventMap = sharedState.baseEventMap;
-
-    logger.info(
-      `${passIdentifier}: Fetching events for ${calendarId}. Params: ${JSON.stringify(gcalApiParams)}`,
-    );
-
-    const maxResults = gcalApiParams.maxResults || 1000;
-    do {
-      const params: gParamsImportAllEvents = {
-        calendarId,
-        pageToken: nextPageToken,
-        maxResults,
-        ...gcalApiParams, // Spread specific parameters like singleEvents
-      };
-
-      const gEventsResponse = await gcalService.getEvents(gcal, params);
-
-      // Handle cases where the API call might fail or return unexpected structure
-      if (!gEventsResponse?.data) {
-        // Check for data property
-        logger.warn(
-          `${passIdentifier}: Invalid response structure or error fetching page for ${calendarId}. PageToken: ${nextPageToken}`,
-          gEventsResponse,
-        );
-        if (!nextPageToken && totalProcessedApi === 0) {
-          // Error on first fetch is critical
-          throw error(
-            EventError.NoGevents, // Or a more specific Gcal API error
-            `${passIdentifier}: Initial fetch failed or returned invalid data for ${calendarId}`,
-          );
-        }
-        break;
-      }
-
-      const gEvents = gEventsResponse.data.items || [];
-      totalProcessedApi += gEvents.length;
-
-      if (gEvents.length > 0) {
-        const eventsToSave: gSchema$Event[] = [];
-        gEvents.forEach((event) => {
-          // Call the provided processor function
-          const _shouldSave = shouldSave(event, sharedState);
-          if (_shouldSave) {
-            eventsToSave.push(event);
-          }
-        });
-
-        if (eventsToSave.length > 0) {
-          const cEvents = MapEvent.toCompass(
-            userId,
-            eventsToSave,
-            Origin.GOOGLE_IMPORT,
-          );
-          baseEventMap = assignIds(cEvents, baseEventMap);
-          if (cEvents.length > 0) {
-            const result = await eventService.createMany(cEvents, {
-              stripIds: false, // Preserve our generated ids in order to match the gcal ids
-            });
-            const savedCount =
-              result && typeof result.insertedCount === "number"
-                ? result.insertedCount
-                : 0;
-            totalSaved += savedCount;
-          }
-        }
-      }
-
-      nextPageToken = gEventsResponse.data.nextPageToken ?? undefined;
-      // Capture nextSyncToken ONLY on the last page AND if requested
-      if (captureSyncToken && !nextPageToken) {
-        finalNextSyncToken = gEventsResponse.data.nextSyncToken ?? undefined;
-        logger.info(
-          `${passIdentifier}: Reached last page. Got nextSyncToken: ${finalNextSyncToken ? "..." : "null"}`,
-        );
-      }
-    } while (nextPageToken !== undefined);
-
-    logSummary(
-      passIdentifier,
-      calendarId,
-      maxResults,
-      totalProcessedApi,
-      totalSaved,
-      start,
-    );
-
-    return {
-      savedCount: totalSaved,
-      processedCount: totalProcessedApi,
-      nextSyncToken: finalNextSyncToken, // Will be undefined if captureSyncToken was false
-      baseEventMap,
-    };
-  } catch (err) {
-    logger.error(
-      `Unhandled error in fetchAndProcessEventsPageByPage for ${calendarId}: ${err}`,
-    );
-    throw err;
-  }
-};
-
-const logSummary = (
-  passIdentifier: string,
-  calendarId: string,
-  maxResults: number,
-  totalProcessedApi: number,
-  totalSaved: number,
-  start: number,
-) => {
-  const end = Date.now();
-  const seconds = (end - start) / 1000;
-  logger.info(
-    `${passIdentifier} completed for ${calendarId}.
-    Max results / page: ${maxResults}
-    Processed Gcal events: ${totalProcessedApi}
-    Saved Compass events: ${totalSaved}
-    Duration: ${seconds.toFixed(2)}s 
-    `,
-  );
-};
diff --git a/packages/backend/src/sync/services/import/all/import.all.util.ts b/packages/backend/src/sync/services/import/all/import.all.util.ts
deleted file mode 100644
index 20907974..00000000
--- a/packages/backend/src/sync/services/import/all/import.all.util.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import dayjs from "dayjs";
-import { ObjectId } from "mongodb";
-import { Logger } from "@core/logger/winston.logger";
-import { Event_Core } from "@core/types/event.types";
-import { gSchema$Event } from "@core/types/gcal";
-import { Event_Core_WithObjectId } from "@backend/sync/sync.types";
-import { Callback_EventProcessor } from "../sync.import.types";
-
-const logger = Logger("sync.import.all.util");
-
-const getStartTimeString = (event: gSchema$Event): string | null => {
-  if (event.start?.dateTime) {
-    return event.start.dateTime;
-  }
-  if (event.start?.date) {
-    // For all-day events, represent the start as the beginning of that day in UTC
-    // This assumes MapEvent.toCompass handles all-day events similarly. Adjust if needed.
-    return dayjs(event.start.date).startOf("day").toISOString();
-  }
-  return null;
-};
-
-// Processor for Pass 1: Identifies base/single events
-export const shouldProcessDuringPass1: Callback_EventProcessor = (
-  gEvent,
-  state,
-) => {
-  if (gEvent.id) {
-    const isRecurringBase = gEvent.recurrence;
-    if (isRecurringBase) {
-      const startTime = getStartTimeString(gEvent);
-      state.baseEventStartTimes.set(gEvent.id, startTime);
-      state.processedEventIdsPass1.add(gEvent.id);
-      return true; // Save base event
-    }
-  }
-  return false; // Don't save (e.g., instances encountered in Pass 1)
-};
-
-// Processor for Pass 2: Filters events based on shared state
-export const shouldProcessDuringPass2: Callback_EventProcessor = (
-  gEvent,
-  state,
-) => {
-  // Filter 1: Skip event if already processed in Pass 1
-  if (gEvent.id && state.processedEventIdsPass1.has(gEvent.id || "")) {
-    logger.verbose(
-      `Pass 2: Skipping  base event: ${gEvent.summary} (processed in Pass 2).`,
-    );
-    return false; // Don't save
-  }
-  return true; // Save this event
-};
-
-/**
- * Assigns Mongo ObjectIds to events (in-place) and links instances to their base events
- * @param events - The events to assign ids to
- */
-export const assignIds = (
-  events: Event_Core[] | Event_Core_WithObjectId[], // union type to allow for in-place id mutation in this function
-  existingBaseEventMap: Map<string, ObjectId> = new Map(),
-): Map<string, ObjectId> => {
-  // First pass: identify base events and assign their IDs
-  const baseEventMap = new Map(existingBaseEventMap);
-
-  // Handle base events first
-  events.forEach((event) => {
-    const id = new ObjectId();
-    if (event.recurrence?.rule && !event.gRecurringEventId) {
-      // This is a base event
-      event._id = id;
-      if (event.gEventId) {
-        baseEventMap.set(event.gEventId, id);
-      }
-    } else {
-      // This is a regular event or instance
-      event._id = id;
-    }
-  });
-
-  // Second pass: assign IDs to instances and link them to their base events
-  events.forEach((event) => {
-    if (event.gRecurringEventId) {
-      // This is an instance
-      const baseEventId = baseEventMap.get(event.gRecurringEventId);
-      if (baseEventId) {
-        event.recurrence = {
-          eventId: baseEventId.toString(),
-        };
-      }
-    }
-  });
-
-  return baseEventMap;
-};
diff --git a/packages/backend/src/sync/services/import/sync.import.ts b/packages/backend/src/sync/services/import/sync.import.ts
index 8534576c..9fa8a9b8 100644
--- a/packages/backend/src/sync/services/import/sync.import.ts
+++ b/packages/backend/src/sync/services/import/sync.import.ts
@@ -11,7 +11,7 @@ import {
 } from "@core/types/event.types";
 import { gCalendar, gSchema$Event, gSchema$EventBase } from "@core/types/gcal";
 import { Schema_Sync } from "@core/types/sync.types";
-import { categorizeRecurringEvents } from "@core/util/event/event.util";
+import { isBaseGCalEvent } from "@core/util/event/gcal.event.util";
 import { getGcalClient } from "@backend/auth/services/google.auth.service";
 import { Collections } from "@backend/common/constants/collections";
 import { ENV } from "@backend/common/constants/env.constants";
@@ -21,6 +21,12 @@ import { GcalError } from "@backend/common/errors/integration/gcal/gcal.errors";
 import { SyncError } from "@backend/common/errors/sync/sync.errors";
 import gcalService from "@backend/common/services/gcal/gcal.service";
 import mongoService from "@backend/common/services/mongo.service";
+import eventService from "@backend/event/services/event.service";
+import { EventsToModify } from "@backend/sync/services/import/sync.import.types";
+import {
+  assignIdsToEvents,
+  organizeGcalEventsByType,
+} from "@backend/sync/services/import/sync.import.util";
 import {
   getSync,
   updateSync,
@@ -33,13 +39,6 @@ import {
 import { getCalendarsToSync } from "../init/sync.init";
 import syncService from "../sync.service";
 import { assembleEventWatchPayloads } from "../watch/sync.watch";
-import { fetchAndProcessEventsPageByPage } from "./all/import.all.gcal";
-import {
-  shouldProcessDuringPass1,
-  shouldProcessDuringPass2,
-} from "./all/import.all.util";
-import { EventsToModify, Map_Recurrences } from "./sync.import.types";
-import { organizeGcalEventsByType } from "./sync.import.util";
 
 const logger = Logger("app:sync.import");
 
@@ -75,34 +74,6 @@ export class SyncImport {
     return syncEvents;
   }
 
-  /**
-   * Assigns IDs to recurring events
-   * @param events - The events to assign IDs to (** assumes each event belongs to the same series **)
-   * @returns The events with IDs assigned
-   */
-  private assignIdsToRecurringEvents(
-    events: RecurrenceWithoutId[],
-  ): RecurrenceWithObjectId[] {
-    const { baseEvent, instances } = categorizeRecurringEvents(events);
-
-    const baseId = new ObjectId();
-    const newBase: RecurrenceWithObjectId = {
-      ...baseEvent,
-      _id: baseId,
-    };
-
-    const instancesWithIds = instances.map((instance) => ({
-      ...instance,
-      _id: new ObjectId(),
-      recurrence: {
-        ...instance.recurrence,
-        eventId: baseId.toString(),
-      },
-    }));
-
-    return [newBase, ...instancesWithIds];
-  }
-
   private async fetchAndCategorizeEventsToModify(
     userId: string,
     gCalendarId: string,
@@ -276,70 +247,139 @@ export class SyncImport {
     return response.data;
   }
 
+  async importEventInstances(
+    userId: string,
+    calendarId: string,
+    baseEvent: gSchema$Event,
+    perPage = 1000,
+  ): Promise<{
+    totalProcessed: number;
+    totalSaved: number;
+    totalInstancesSaved: number;
+  }> {
+    const isBaseEvent = isBaseGCalEvent(baseEvent);
+    const instances: gSchema$Event[] = [baseEvent];
+
+    if (isBaseEvent) {
+      const gCalResponse = gcalService.getBaseRecurringEventInstances({
+        gCal: this.gcal,
+        calendarId,
+        eventId: baseEvent.id!,
+        maxResults: perPage,
+      });
+
+      for await (const { items = [] } of gCalResponse) {
+        instances.push(...items);
+      }
+    }
+
+    const cEvents = MapEvent.toCompass(userId, instances, Origin.GOOGLE_IMPORT);
+
+    const compassSeries = assignIdsToEvents(cEvents);
+
+    let insertedCount = 0;
+
+    if (compassSeries.length > 0) {
+      const createResponse = await eventService.createMany(compassSeries, {
+        stripIds: false, // Preserve our generated ids in order to match the gcal ids
+      });
+
+      insertedCount = createResponse.insertedCount;
+    }
+
+    return {
+      totalProcessed: instances.length,
+      totalSaved: insertedCount,
+      totalInstancesSaved: Math.max(insertedCount - 1, 0),
+    };
+  }
+
   /**
-   * Imports ALL events for a calendar (Full Sync).
+   * importAllEvents
+   * Import ALL events for a calendar (Full Sync).
    */
-  public async importAllEvents(userId: string, calendarId: string) {
+  async importAllEvents(
+    userId: string,
+    calendarId: string,
+    perPage = 1000,
+  ): Promise<{
+    totalProcessed: number;
+    totalBaseEventsChanged: number;
+    totalChanged: number;
+    nextSyncToken: string;
+  }> {
     logger.info(
       `Starting importAllEvents for user ${userId}, calendar ${calendarId}.`,
     );
 
-    // Shared state needed by both passes
-    const recurrencesMap: Map_Recurrences = {
-      baseEventStartTimes: new Map<string, string | null>(),
-      processedEventIdsPass1: new Set<string>(),
-      baseEventMap: new Map<string, ObjectId>(),
+    const startTime = performance.now();
+
+    let syncToken: string | undefined = undefined;
+
+    const stats: {
+      totalProcessed: number;
+      totalBaseEventsChanged: number;
+      totalChanged: number;
+    } = {
+      totalProcessed: 0,
+      totalBaseEventsChanged: 0,
+      totalChanged: 0,
     };
 
-    // Execute Pass 1
-    const pass1Result = await fetchAndProcessEventsPageByPage(
-      userId,
-      this.gcal,
+    const gCalResponse = gcalService.getAllEvents({
+      gCal: this.gcal,
       calendarId,
-      { singleEvents: false }, // regular and base events (no instances)
-      shouldProcessDuringPass1,
-      recurrencesMap,
-      false, // Don't capture sync token in Pass 1
-    );
+      maxResults: perPage,
+      syncToken,
+    });
 
-    // Update the shared state with the map from Pass 1
-    recurrencesMap.baseEventMap = pass1Result.baseEventMap;
+    for await (const { items = [], nextSyncToken } of gCalResponse) {
+      await Promise.allSettled(
+        items.map(async (baseEvent) => {
+          const instanceStats = await this.importEventInstances(
+            userId,
+            calendarId,
+            baseEvent,
+            perPage,
+          );
+
+          const totalBaseEventsSaved =
+            instanceStats.totalSaved - instanceStats.totalInstancesSaved;
+
+          stats.totalChanged += instanceStats.totalSaved;
+          stats.totalProcessed += instanceStats.totalProcessed;
+          stats.totalBaseEventsChanged += totalBaseEventsSaved;
+        }),
+      );
 
-    // Execute Pass 2
-    const pass2Result = await fetchAndProcessEventsPageByPage(
-      userId,
-      this.gcal,
-      calendarId,
-      { singleEvents: true }, // regular and instance events (no base)
-      shouldProcessDuringPass2,
-      recurrencesMap,
-      true, // Capture sync token in Pass 2
-    );
+      if (nextSyncToken) syncToken = nextSyncToken;
+    }
 
-    const nextSyncToken = pass2Result.nextSyncToken;
-    if (!nextSyncToken) {
-      // If Pass 2 ran but yielded no sync token (e.g., empty calendar or API issue on last page)
+    if (!syncToken) {
+      // If no sync token (e.g., empty calendar or sync did not reach last page)
       throw error(
         GcalError.NoSyncToken,
         `Failed to finalize full import because nextSyncToken was not found for ${calendarId}. Incremental sync may not work correctly.`,
       );
     }
 
-    const baseEventsSavedCount = pass1Result.savedCount;
-    const instanceEventsSavedCount = pass2Result.savedCount;
-    const totalChanged = baseEventsSavedCount + instanceEventsSavedCount;
-    const totalProcessed =
-      pass1Result.processedCount + pass2Result.processedCount;
+    const baseEventsSavedCount = stats.totalBaseEventsChanged;
+    const instanceEventsSavedCount = stats.totalChanged - baseEventsSavedCount;
+    const endTime = performance.now();
+    const duration = (endTime - startTime) / 1000;
 
     logger.info(
-      `importAllEvents completed for ${calendarId}. Total API events processed: ${totalProcessed}. Total base/single saved in Pass 1: ${baseEventsSavedCount}, Total new instances/single saved in Pass 2: ${instanceEventsSavedCount}. Total Saved/Changed: ${totalChanged}. Final nextSyncToken acquired.`,
+      `importAllEvents completed for ${calendarId}.
+      Max results / page: ${perPage}
+      Total GCal events processed: ${stats.totalProcessed}.
+      Total base/single saved: ${baseEventsSavedCount},
+      Total instances saved: ${instanceEventsSavedCount}.
+      Total Saved/Changed Compass Events: ${stats.totalChanged}.
+      Duration: ${duration.toFixed(2)}s
+      Final nextSyncToken acquired.`,
     );
 
-    return {
-      totalProcessed,
-      totalChanged,
-      nextSyncToken,
-    };
+    return { ...stats, nextSyncToken: syncToken };
   }
 
   /**
@@ -385,7 +425,7 @@ export class SyncImport {
     const series = [cBase, ...cInstances];
 
     // assign ids and link instances to base
-    const compassSeries = this.assignIdsToRecurringEvents(series);
+    const compassSeries = assignIdsToEvents(series);
 
     const result = await mongoService.db
       .collection(Collections.EVENT)
diff --git a/packages/backend/src/sync/services/import/sync.import.util.ts b/packages/backend/src/sync/services/import/sync.import.util.ts
index 0c64283f..4152f21f 100644
--- a/packages/backend/src/sync/services/import/sync.import.util.ts
+++ b/packages/backend/src/sync/services/import/sync.import.util.ts
@@ -1,7 +1,15 @@
-import { AnyBulkWriteOperation } from "mongodb";
-import { Schema_Event_Core } from "@core/types/event.types";
+import { AnyBulkWriteOperation, ObjectId } from "mongodb";
+import {
+  Event_Core,
+  RecurrenceWithoutId,
+  Schema_Event_Core,
+  Schema_Event_Regular,
+  WithoutCompassId,
+} from "@core/types/event.types";
 import { gSchema$Event } from "@core/types/gcal";
+import { isBase, isInstanceWithoutId } from "@core/util/event/event.util";
 import { cancelledEventsIds } from "@backend/common/services/gcal/gcal.utils";
+import { Event_Core_WithObjectId } from "@backend/sync/sync.types";
 
 export const assembleEventOperations = (
   userId: string,
@@ -69,3 +77,37 @@ export const organizeGcalEventsByType = (events: gSchema$Event[]) => {
   };
   return categorized;
 };
+
+/**
+ * Assigns IDs to events
+ * will map instance events to their base events
+ * @param events - The events to assign IDs
+ * @returns The events with IDs assigned
+ */
+export const assignIdsToEvents = (
+  events: Array<RecurrenceWithoutId | WithoutCompassId<Schema_Event_Regular>>,
+): Array<Event_Core_WithObjectId> => {
+  const idMaps = new Map<string, ObjectId>();
+
+  return events.map((e) => {
+    const event = e as Event_Core;
+    const isBaseEvent = isBase(event);
+    const isInstance = isInstanceWithoutId(event);
+    const isRegularEvent = !isBaseEvent && !isInstance;
+    const baseEventId = event?.gRecurringEventId ?? event.gEventId!;
+
+    if (!idMaps.get(baseEventId)) idMaps.set(baseEventId, new ObjectId());
+
+    const baseEventObjectId = idMaps.get(baseEventId)!;
+
+    return {
+      ...event,
+      _id: isBaseEvent ? baseEventObjectId : new ObjectId(),
+      recurrence: (isRegularEvent
+        ? undefined
+        : isInstance
+          ? { eventId: baseEventObjectId.toString() }
+          : event.recurrence) as { rule: string[] },
+    };
+  });
+};
diff --git a/packages/backend/src/sync/services/sync.service.ts b/packages/backend/src/sync/services/sync.service.ts
index a7a5179b..f78b336a 100644
--- a/packages/backend/src/sync/services/sync.service.ts
+++ b/packages/backend/src/sync/services/sync.service.ts
@@ -102,6 +102,7 @@ class SyncService {
       const { nextSyncToken } = await syncImport.importAllEvents(
         userId,
         gCalId,
+        2500,
       );
       if (isUsingHttps()) {
         await updateSyncTokenFor("events", userId, nextSyncToken, gCalId);
@@ -161,7 +162,7 @@ class SyncService {
     const resynced = refreshResult.filter((r) => r.resynced);
 
     logger.debug(`Sync Maintenance Results:
-      IGNORED: ${ignored.length} 
+      IGNORED: ${ignored.length}
       PRUNED: ${pruned.map((p) => p.user).toString()}
       REFRESHED: ${refreshed.map((r) => r.user).toString()}
 
diff --git a/packages/core/src/types/event.types.ts b/packages/core/src/types/event.types.ts
index 09414aa3..b74838a8 100644
--- a/packages/core/src/types/event.types.ts
+++ b/packages/core/src/types/event.types.ts
@@ -49,7 +49,7 @@ export interface Payload_Order {
 
 export interface Result_DeleteMany {
   deletedCount: number;
-  errors: any[];
+  errors: unknown[];
 }
 
 export interface Schema_Event {
@@ -74,6 +74,11 @@ export interface Schema_Event {
   user?: string;
 }
 
+export type Schema_Event_Regular = Omit<
+  Schema_Event,
+  "recurrence" | "gRecurringEventId"
+>;
+
 export interface Schema_Event_Recur_Base
   extends Omit<Schema_Event, "recurrence"> {
   recurrence: {
diff --git a/packages/core/src/util/event/event.util.ts b/packages/core/src/util/event/event.util.ts
index e05d7eee..5fa55a2e 100644
--- a/packages/core/src/util/event/event.util.ts
+++ b/packages/core/src/util/event/event.util.ts
@@ -33,7 +33,7 @@ export const isAllDay = (event: Schema_Event) =>
   event.endDate?.length === 10;
 
 /**
- * Base events have an `eventId` and an empty `rule`
+ * Base compass events have no `eventId` and an non-empty `rule` within their `recurrence` field
  * @param event
  * @returns
  */
@@ -45,7 +45,20 @@ export const isBase = (event: Schema_Event) => {
 };
 
 /**
- * Instances have an `eventId` and an empty `rule`
+ * Instance compass events have an `eventId` and an empty `rule` within their `recurrence` field
+ * @param event
+ * @returns
+ */
+export const isInstanceWithoutId = (event: Schema_Event) => {
+  return (
+    event?.recurrence?.rule === undefined &&
+    event?.recurrence?.eventId === undefined &&
+    typeof event.gRecurringEventId === "string"
+  );
+};
+
+/**
+ * Instances with mongo Ids have an `eventId` and an empty `rule` within their `recurrence` field
  * @param event
  * @returns
  */
diff --git a/packages/core/src/util/event/gcal.event.util.ts b/packages/core/src/util/event/gcal.event.util.ts
index a2fc369e..88ab2654 100644
--- a/packages/core/src/util/event/gcal.event.util.ts
+++ b/packages/core/src/util/event/gcal.event.util.ts
@@ -4,3 +4,40 @@ import { gSchema$Event } from "@core/types/gcal";
 export const notCancelled = (e: gSchema$Event) => {
   return e.status && e.status !== "cancelled";
 };
+
+/**
+ * isBaseGCalEvent
+ *
+ * Base gCal events have an `id` field and a non-empty `recurrence` array field
+ * https://developers.google.com/workspace/calendar/api/v3/reference/events#resource
+ */
+export const isBaseGCalEvent = ({
+  id,
+  recurrence,
+  recurringEventId,
+}: gSchema$Event) =>
+  typeof id === "string" && Array.isArray(recurrence) && !recurringEventId;
+
+/**
+ * isInstanceGCalEvent
+ *
+ * Recurring Instances of gCal events have an undefined `recurrence` field
+ * and a specified `recurringEventId` field
+ * https://developers.google.com/workspace/calendar/api/v3/reference/events#resource
+ */
+export const isInstanceGCalEvent = ({
+  recurrence,
+  recurringEventId,
+}: gSchema$Event) => !recurrence && typeof recurringEventId === "string";
+
+/**
+ * isRegularGCalEvent
+ *
+ * Regular standalone gCal events have an undefined `recurrence` field
+ * and an undefined `recurringEventId` field
+ * https://developers.google.com/workspace/calendar/api/v3/reference/events#resource
+ */
+export const isRegularGCalEvent = ({
+  recurrence,
+  recurringEventId,
+}: gSchema$Event) => !recurrence && !recurringEventId;


Test patch:
diff --git a/packages/backend/src/__tests__/mocks.gcal/factories/gcal.factory.ts b/packages/backend/src/__tests__/mocks.gcal/factories/gcal.factory.ts
index 6e3d2678..744d4625 100644
--- a/packages/backend/src/__tests__/mocks.gcal/factories/gcal.factory.ts
+++ b/packages/backend/src/__tests__/mocks.gcal/factories/gcal.factory.ts
@@ -1,8 +1,15 @@
 import {
   gSchema$CalendarListEntry,
   gSchema$Event,
+  gSchema$EventBase,
   gSchema$Events,
 } from "@core/types/gcal";
+import {
+  isBaseGCalEvent,
+  isInstanceGCalEvent,
+  isRegularGCalEvent,
+} from "@core/util/event/gcal.event.util";
+import { mockRecurringGcalEvents } from "./gcal.event.factory";
 
 /**
  * Generates a paginated events object for the Google Calendar API.
@@ -78,9 +85,12 @@ export const mockGcal = ({
                 pageToken?: string;
                 singleEvents?: boolean;
               }) => {
-                // When singleEvents is false, only return base events
-                if (params.singleEvents === false) {
-                  const baseEvents = events.filter((event) => event.recurrence);
+                // When singleEvents is false, only return base events and regular events - without instance events
+                if (!params.singleEvents) {
+                  const baseEvents = events.filter(
+                    (e) => isBaseGCalEvent(e) || isRegularGCalEvent(e),
+                  );
+
                   const eventsPage = generatePaginatedEvents(
                     baseEvents,
                     nextSyncToken,
@@ -88,21 +98,26 @@ export const mockGcal = ({
                     params.pageToken,
                   );
                   return {
+                    statusText: "OK",
+                    status: 200,
                     data: eventsPage,
                   };
                 }
 
-                // When singleEvents is true, return instances and non-recurring events
-                const instancesAndNonRecurring = events.filter(
-                  (event) => !event.recurrence || event.recurringEventId,
+                // When singleEvents is true, return instance events and regular events - without base events
+                const instanceAndRegularEvents = events.filter(
+                  (e) => isRegularGCalEvent(e) || isInstanceGCalEvent(e),
                 );
+
                 const eventsPage = generatePaginatedEvents(
-                  instancesAndNonRecurring,
+                  instanceAndRegularEvents,
                   nextSyncToken,
                   pageSize,
                   params.pageToken,
                 );
                 return {
+                  statusText: "OK",
+                  status: 200,
                   data: eventsPage,
                 };
               },
@@ -110,17 +125,19 @@ export const mockGcal = ({
           instances: jest
             .fn()
             .mockImplementation(async (params: { eventId: string }) => {
-              const { eventId } = params;
-              const instances = Array(3)
-                .fill(null)
-                .map(() => ({
-                  ...events[0],
-                  recurringEventId: eventId,
-                }));
+              const { eventId: id } = params;
+
+              const baseEvent = events.find(
+                isBaseGCalEvent,
+              ) as gSchema$EventBase;
+
+              const data = mockRecurringGcalEvents({ ...baseEvent, id }, 3, 7);
 
               return {
+                statusText: "OK",
+                status: 200,
                 data: {
-                  items: instances,
+                  items: data.instances,
                 },
               };
             }),
diff --git a/packages/backend/src/sync/services/import/all/import.all.util.test.ts b/packages/backend/src/sync/services/import/all/import.all.util.test.ts
deleted file mode 100644
index a002db92..00000000
--- a/packages/backend/src/sync/services/import/all/import.all.util.test.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { ObjectId } from "mongodb";
-import { Event_Core } from "@core/types/event.types";
-import { gSchema$EventInstance } from "@core/types/gcal";
-import {
-  createMockBaseEvent,
-  createMockInstance,
-} from "@core/util/test/ccal.event.factory";
-import {
-  generateGcalId,
-  mockRecurringGcalBaseEvent,
-  mockRecurringGcalInstances,
-  mockRegularGcalEvent,
-} from "@backend/__tests__/mocks.gcal/factories/gcal.event.factory";
-import { assignIds, shouldProcessDuringPass1 } from "./import.all.util";
-
-const getEventsWithoutIds = () => {
-  // Create a base event with a Google ID
-  const baseEvent = createMockBaseEvent();
-  const baseGEventId = generateGcalId();
-  baseEvent.gEventId = baseGEventId;
-  delete baseEvent._id;
-
-  const gEventId = baseEvent.gEventId as string;
-
-  // Create instances that reference the base event's Google ID
-  const instance1 = createMockInstance(baseGEventId, gEventId, {});
-  const instance2 = createMockInstance(baseGEventId, gEventId, {});
-  // delete _ids to simulate a newly mapped event
-  delete instance1._id;
-  delete instance2._id;
-
-  const eventsWithoutIds = [baseEvent, instance1, instance2];
-  return eventsWithoutIds;
-};
-
-describe("assignIds", () => {
-  it("assigns Mongo ObjectIds to events", () => {
-    const events = getEventsWithoutIds() as Event_Core[];
-    assignIds(events);
-
-    // Verify all events have ObjectIds
-    for (const event of events) {
-      expect(event?._id).toBeDefined();
-      expect(event._id).toBeInstanceOf(ObjectId);
-    }
-  });
-  it("links instances to base events", () => {
-    const events = getEventsWithoutIds() as Event_Core[]; // no ids originally
-    assignIds(events); // adds ids
-
-    const baseEvent = events.filter((event) => event.recurrence?.rule);
-    expect(baseEvent).toHaveLength(1);
-
-    const instances = events.filter((event) => event.recurrence?.eventId);
-    expect(instances).toHaveLength(2);
-
-    for (const instance of instances) {
-      const baseEventId = baseEvent[0]!._id?.toString();
-      expect(instance.recurrence?.eventId).toBe(baseEventId);
-    }
-  });
-});
-
-describe("shouldProcessDuringPass1", () => {
-  const state = {
-    processedEventIdsPass1: new Set<string>(),
-    baseEventStartTimes: new Map<string, string>(),
-    baseEventMap: new Map<string, ObjectId>(),
-  };
-  it("returns true for base events", () => {
-    const baseEvent = mockRecurringGcalBaseEvent();
-    expect(shouldProcessDuringPass1(baseEvent, state)).toBe(true);
-  });
-  it("returns false for instances", () => {
-    const instance = mockRecurringGcalInstances(
-      mockRecurringGcalBaseEvent(),
-      1,
-      1,
-    )[0] as gSchema$EventInstance;
-    expect(shouldProcessDuringPass1(instance, state)).toBe(false);
-  });
-  it("returns false for regular events", () => {
-    const regularEvent = mockRegularGcalEvent();
-    expect(shouldProcessDuringPass1(regularEvent, state)).toBe(false);
-  });
-});
diff --git a/packages/backend/src/sync/services/import/sync.import.full.test.ts b/packages/backend/src/sync/services/import/sync.import.full.test.ts
index a168aa54..f34299cb 100644
--- a/packages/backend/src/sync/services/import/sync.import.full.test.ts
+++ b/packages/backend/src/sync/services/import/sync.import.full.test.ts
@@ -1,3 +1,4 @@
+import { isBase, isExistingInstance } from "@core/util/event/event.util";
 import { getCategorizedEventsInDb } from "@backend/__tests__/helpers/mock.db.queries";
 import {
   cleanupCollections,
@@ -39,55 +40,56 @@ describe("SyncImport: Full", () => {
     await syncImport.importAllEvents(setup.userId, "test-calendar");
 
     const currentEventsInDb = await mongoService.event.find().toArray();
+    const baseEvent = currentEventsInDb.find(isBase)!;
+    const firstInstance = currentEventsInDb.find(isExistingInstance)!;
 
-    const recurringEvents = currentEventsInDb.filter(
-      (e) => e.recurrence !== undefined,
-    );
+    expect(baseEvent).toBeDefined();
+    expect(firstInstance).toBeDefined();
 
-    const baseStart = recurringEvents[0]?.startDate;
-    const firstInstanceStart = recurringEvents[1]?.startDate;
-    expect(baseStart).toEqual(firstInstanceStart);
+    expect(baseEvent.startDate).toEqual(firstInstance.startDate);
   });
 
   it("should connect instances to their base events", async () => {
     await syncImport.importAllEvents(setup.userId, "test-calendar");
     const { baseEvents, instanceEvents } = await getCategorizedEventsInDb();
 
-    expect(instanceEvents).toHaveLength(2);
+    expect(instanceEvents).toHaveLength(3);
     instanceEvents.forEach((instance) => {
       expect(instance.recurrence?.eventId).toBe(baseEvents[0]?._id?.toString());
     });
   });
+
   it("should include regular and recurring events and skip cancelled events", async () => {
     const { totalProcessed, totalChanged, nextSyncToken } =
       await syncImport.importAllEvents(setup.userId, "test-calendar");
 
     const currentEventsInDb = await mongoService.event.find().toArray();
 
-    expect(totalProcessed).toBe(5); // base + 2 instances + regular + cancelled
-    expect(totalChanged).toBe(4); // base + 2 instances + regular - cancelled
-    expect(currentEventsInDb).toHaveLength(4); // base + 2 instances + regular - cancelled
-    // Verify we have the base recurring event
-    const baseEvents = currentEventsInDb.filter(
-      (e) => e.recurrence?.rule !== undefined,
-    );
+    expect(totalProcessed).toBe(6); // base + 3 instances + regular + cancelled
+    expect(totalChanged).toBe(5); // base + 3 instances + regular
+    expect(currentEventsInDb).toHaveLength(5); // base + 3 instances + regular
+    // Verify we have the base event
+    const baseEvents = currentEventsInDb.filter(isBase);
+
     expect(baseEvents).toHaveLength(1);
     expect(baseEvents[0]?.title).toBe("Recurrence");
 
     // Verify we have the correct instance
-    const instanceEvents = currentEventsInDb.filter(
-      (e) => e.recurrence?.eventId !== undefined,
-    );
-    expect(instanceEvents).toHaveLength(2);
+    const instanceEvents = currentEventsInDb.filter(isExistingInstance);
+
+    expect(instanceEvents).toHaveLength(3);
+
     const baseGevId = baseEvents[0]?.gEventId as string;
+
     expect(instanceEvents.map((e) => e.gEventId)).toEqual(
       expect.arrayContaining([expect.stringMatching(baseGevId)]),
     );
 
     // Verify we have the regular event
     const regularEvents = currentEventsInDb.filter(
-      (e) => e.recurrence === undefined,
+      ({ recurrence }) => recurrence === undefined || recurrence === null,
     );
+
     expect(regularEvents).toHaveLength(1);
     expect(regularEvents[0]?.gEventId).toBe("regular-1");
 
@@ -100,14 +102,12 @@ describe("SyncImport: Full", () => {
 
     const currentEventsInDb = await mongoService.event.find().toArray();
 
-    // Get all recurring events
-    const recurringEvents = currentEventsInDb.filter(
-      (e) => e.recurrence !== undefined,
-    );
+    // Get all instance events
+    const instances = currentEventsInDb.filter(isExistingInstance);
 
-    // For each recurring event, verify there are no duplicates
+    // For each instance event, verify there are no duplicates
     const eventIds = new Set<string>();
-    const duplicateEvents = recurringEvents.filter((event) => {
+    const duplicateEvents = instances.filter((event) => {
       if (!event.gEventId) return false; // Skip events without IDs
       if (eventIds.has(event.gEventId)) {
         return true;
@@ -123,9 +123,11 @@ describe("SyncImport: Full", () => {
     await syncImport.importAllEvents(setup.userId, "test-calendar");
 
     const currentEventsInDb = await mongoService.event.find().toArray();
+
     const regularEvents = currentEventsInDb.filter(
-      (e) => e.recurrence === undefined,
+      ({ recurrence }) => recurrence === undefined || recurrence === null,
     );
+
     expect(regularEvents).toHaveLength(1);
     expect(regularEvents[0]?.gEventId).toBe("regular-1");
   });
diff --git a/packages/backend/src/sync/services/import/sync.import.util.test.ts b/packages/backend/src/sync/services/import/sync.import.util.test.ts
index f12d10de..05673f22 100644
--- a/packages/backend/src/sync/services/import/sync.import.util.test.ts
+++ b/packages/backend/src/sync/services/import/sync.import.util.test.ts
@@ -1,8 +1,19 @@
 import dayjs from "dayjs";
-import { gcalEvents } from "../../../../../core/src/__mocks__/v1/events/gcal/gcal.event";
-import { cancelledEventsIds } from "../../../common/services/gcal/gcal.utils";
-import { syncExpired, syncExpiresSoon } from "../../util/sync.util";
-import { organizeGcalEventsByType } from "./sync.import.util";
+import { ObjectId } from "mongodb";
+import { gcalEvents } from "@core/__mocks__/v1/events/gcal/gcal.event";
+import { Event_Core } from "@core/types/event.types";
+import {
+  createMockBaseEvent,
+  createMockInstance,
+} from "@core/util/test/ccal.event.factory";
+import { generateGcalId } from "@backend/__tests__/mocks.gcal/factories/gcal.event.factory";
+import { cancelledEventsIds } from "@backend/common/services/gcal/gcal.utils";
+import { syncExpired, syncExpiresSoon } from "@backend/sync/util/sync.util";
+import { gSchema$Event } from "../../../../../core/src/types/gcal";
+import {
+  assignIdsToEvents,
+  organizeGcalEventsByType,
+} from "./sync.import.util";
 
 describe("categorizeGcalEvents", () => {
   const { toDelete, toUpdate } = organizeGcalEventsByType(gcalEvents.items);
@@ -13,19 +24,23 @@ describe("categorizeGcalEvents", () => {
 
       // should be array of string numbers
       expect(typeof toDelete[1]).toBe("string");
-      const parsedToInt = parseInt(toDelete[1]);
+      const parsedToInt = parseInt(toDelete[1] ?? "");
       expect(typeof parsedToInt).toBe("number");
     });
     it("finds deleted/cancelled events", () => {
       const cancelledIds = cancelledEventsIds(gcalEvents.items);
       gcalEvents.items.forEach((e) => {
-        if (e.status === "cancelled") {
-          cancelledIds.push(e.id);
+        const event = e as gSchema$Event;
+
+        if (event.status === "cancelled") {
+          cancelledIds.push(event.id!);
         }
       });
 
       toDelete.forEach((e) => {
-        if (cancelledIds.includes(e.id)) {
+        const event = e as gSchema$Event;
+
+        if (cancelledIds.includes(event.id!)) {
           throw new Error("a cancelled event was missed");
         }
       });
@@ -38,7 +53,7 @@ describe("categorizeGcalEvents", () => {
     const allIds = [...recurringIds, ...nonRecurringIds];
 
     allIds.forEach((e) => {
-      if (toDelete.includes(e)) {
+      if (toDelete.includes(e!)) {
         throw new Error("An event was found in the delete and update category");
       }
     });
@@ -81,3 +96,63 @@ describe("Sync Expiry Checks", () => {
     expect(expiresSoon).toBe(false);
   });
 });
+
+const getEventsWithoutIds = () => {
+  // Create a base event with a Google ID
+  const baseEvent = createMockBaseEvent();
+  const baseGEventId = generateGcalId();
+
+  baseEvent.gEventId = baseGEventId;
+
+  delete (baseEvent as Partial<typeof baseEvent>)._id;
+
+  const gEventId = baseEvent.gEventId as string;
+
+  // Create instances that reference the base event's Google ID
+  const instance1 = createMockInstance(baseGEventId, gEventId, {});
+  const instance2 = createMockInstance(baseGEventId, gEventId, {});
+
+  // delete _ids to simulate a newly mapped event
+  delete (instance1 as Partial<typeof instance1>)._id;
+  delete (instance1 as Partial<typeof instance1>).recurrence;
+  delete (instance2 as Partial<typeof instance2>)._id;
+  delete (instance2 as Partial<typeof instance2>).recurrence;
+
+  const eventsWithoutIds = [baseEvent, instance1, instance2];
+
+  return eventsWithoutIds;
+};
+
+describe("assignIdsToEvents", () => {
+  it("assigns Mongo ObjectIds to events", () => {
+    const eventsWithoutIds = getEventsWithoutIds() as Event_Core[];
+
+    const events = assignIdsToEvents(eventsWithoutIds);
+
+    // Verify all events have ObjectIds
+    for (const event of events) {
+      expect(event?._id).toBeDefined();
+      expect(event._id).toBeInstanceOf(ObjectId);
+    }
+  });
+
+  it("links instances to base events", () => {
+    const eventsWithoutIds = getEventsWithoutIds() as Event_Core[];
+
+    const events = assignIdsToEvents(eventsWithoutIds);
+
+    const baseEvent = events.find((event) => event.recurrence?.rule);
+
+    expect(baseEvent).toBeDefined();
+
+    console.log(events);
+
+    const instances = events.filter((event) => event.recurrence?.eventId);
+
+    expect(instances).toHaveLength(2);
+
+    for (const instance of instances) {
+      expect(instance.recurrence?.eventId).toBe(baseEvent?._id?.toString());
+    }
+  });
+});