Skip to main content

@tuturuuu/trigger

The @tuturuuu/trigger package contains legacy and manual Trigger.dev task wrappers plus reusable Calendar sync helpers. Production Calendar provider sync and smart scheduling are owned by apps/web cron, with definitions in apps/web/cron.config.json. Do not add recurring Calendar schedules.task jobs here. Calendar provider sync runs every 15 minutes through /api/cron/calendar/provider-sync, which calls the central workspace sync API with cron auth.
The package uses the Trigger.dev v4 task() API (@trigger.dev/sdk ^4.4.5). The v4 SDK is imported from the @trigger.dev/sdk/v3 subpath, as in packages/trigger/src. The legacy v2 API (client.defineJob, eventTrigger, io.runTask, io.sendEvent) is not used anywhere in this repo.
apps/web (Next.js, port 7803) is being migrated to apps/tanstack-web (TanStack Start) plus apps/backend (Rust, port 7820). See TanStack + Rust migration. Calendar provider sync, the cron wrapper, and the apps/web workspace sync API remain on apps/web for now and have not been ported to the Rust backend. Only narrow calendar read endpoints (for example the legacy /api/v1/calendar/mock route) have moved so far; recurring sync stays on apps/web cron until the migration covers it.

Installation

# Already included in monorepo workspace
import { googleCalendarSync } from '@tuturuuu/trigger';

Available Jobs

The package’s public surface (packages/trigger/src/index.ts) currently exports googleCalendarFullSync, googleCalendarFullSyncOrchestrator, performFullSyncForWorkspace, unifiedScheduleTask, unifiedScheduleManualTrigger, and unifiedScheduleHelper. The incremental, batch, and standalone task-scheduling tasks below are illustrative patterns for how a task() is structured; confirm the exact export before importing one in product code.

Google Calendar Sync

The platform keeps reusable Calendar synchronization helpers here, but recurring production scheduling is handled by apps/web cron.

1. Full Sync

Complete synchronization of all calendar events.
// packages/trigger/google-calendar-full-sync.ts
import { task } from "@trigger.dev/sdk/v3";

export const googleCalendarFullSync = task({
  id: "google-calendar-full-sync",
  run: async (payload: { wsId: string; userId: string }) => {
    // Implementation
  },
});
Use Cases:
  • Initial calendar setup
  • Recovery from sync errors
  • Manual refresh requested by user
Trigger:
import { googleCalendarFullSync } from "@tuturuuu/trigger";

await googleCalendarFullSync.trigger({
  wsId: "workspace-id",
  userId: "user-id",
});

2. Incremental Sync

Efficient delta synchronization using Google’s sync tokens.
// packages/trigger/google-calendar-incremental-sync.ts
import { task } from "@trigger.dev/sdk/v3";

export const googleCalendarIncrementalSync = task({
  id: "google-calendar-incremental-sync",
  run: async (payload: { wsId: string; userId: string }) => {
    // Implementation using sync tokens
  },
});
How It Works:
  1. Retrieves last sync token from calendar_sync_states
  2. Fetches only changed events since last sync
  3. Updates database with changes
  4. Stores new sync token for next incremental sync
Use Cases:
  • Manual or debug delta sync
  • Reusable sync-token helper coverage
  • Webhook-triggered updates
Trigger:
import { googleCalendarIncrementalSync } from "@tuturuuu/trigger";

await googleCalendarIncrementalSync.trigger({
  wsId: "workspace-id",
  userId: "user-id",
});

3. Batched Sync

Batch synchronization for multiple workspaces.
// packages/trigger/google-calendar-sync.ts
import { task } from "@trigger.dev/sdk/v3";

export const googleCalendarBatchSync = task({
  id: "google-calendar-batch-sync",
  run: async (payload: {
    workspaces: Array<{ wsId: string; userId: string }>;
  }) => {
    // Batch processing implementation
  },
});
Use Cases:
  • Admin-initiated bulk sync
  • System-wide calendar refresh
Trigger:
import { googleCalendarBatchSync } from "@tuturuuu/trigger";

await googleCalendarBatchSync.trigger({
  workspaces: [
    { wsId: "ws-1", userId: "user-1" },
    { wsId: "ws-2", userId: "user-2" },
  ],
});

Task Scheduling

Schedule tasks based on due dates and priorities.
// packages/trigger/schedule-tasks.ts
import { task } from "@trigger.dev/sdk/v3";

export const scheduleTasks = task({
  id: "schedule-tasks",
  run: async (payload: { wsId: string }) => {
    // Auto-schedule tasks into calendar
  },
});
Features:
  • Analyzes task due dates and priorities
  • Finds available time slots in calendar
  • Creates calendar events for high-priority tasks
  • Respects work hours and existing commitments
Trigger:
import { scheduleTasks } from "@tuturuuu/trigger";

await scheduleTasks.trigger({
  wsId: "workspace-id",
});

Calendar Sync Implementation

Sync Coordination

The package uses atomic sync state management to prevent concurrent syncs: Background jobs run without a user session, so use the admin client with noCookie: true (the same pattern as packages/trigger/src):
import { createAdminClient } from "@tuturuuu/supabase/next/server";

async function acquireSyncLock(wsId: string): Promise<boolean> {
  const sbAdmin = await createAdminClient({ noCookie: true });

  // Check if already syncing
  const { data: state } = await sbAdmin
    .from("calendar_sync_states")
    .select("is_syncing")
    .eq("ws_id", wsId)
    .single();

  if (state?.is_syncing) {
    return false; // Another sync in progress
  }

  // Acquire lock
  const { error } = await sbAdmin
    .from("calendar_sync_states")
    .update({ is_syncing: true })
    .eq("ws_id", wsId);

  return !error;
}

async function releaseSyncLock(wsId: string) {
  const sbAdmin = await createAdminClient({ noCookie: true });

  await sbAdmin
    .from("calendar_sync_states")
    .update({ is_syncing: false })
    .eq("ws_id", wsId);
}

Full Sync Flow

import { task } from "@trigger.dev/sdk/v3";
import { google } from "googleapis";
import { createAdminClient } from "@tuturuuu/supabase/next/server";

export const googleCalendarFullSync = task({
  id: "google-calendar-full-sync",
  maxDuration: 300, // 5 minutes
  run: async (payload: { wsId: string; userId: string }) => {
    const sbAdmin = await createAdminClient({ noCookie: true });

    // Acquire sync lock
    const lockAcquired = await acquireSyncLock(payload.wsId);
    if (!lockAcquired) {
      throw new Error("Sync already in progress");
    }

    try {
      // Get OAuth tokens
      const { data: tokens } = await sbAdmin
        .from("calendar_auth_tokens")
        .select("*")
        .eq("user_id", payload.userId)
        .single();

      if (!tokens) throw new Error("No calendar tokens found");

      // Initialize Google Calendar API
      const oauth2Client = new google.auth.OAuth2();
      oauth2Client.setCredentials({
        access_token: tokens.access_token,
        refresh_token: tokens.refresh_token,
      });

      const calendar = google.calendar({ version: "v3", auth: oauth2Client });

      // Fetch all events
      const response = await calendar.events.list({
        calendarId: "primary",
        maxResults: 2500,
        singleEvents: true,
        orderBy: "startTime",
      });

      const events = response.data.items || [];

      // Log sync start
      const { data: syncLog } = await sbAdmin
        .from("workspace_calendar_sync_log")
        .insert({
          ws_id: payload.wsId,
          sync_type: "full",
          started_at: new Date().toISOString(),
        })
        .select()
        .single();

      // Batch upsert events
      for (let i = 0; i < events.length; i += 100) {
        const batch = events.slice(i, i + 100);

        await sbAdmin.from("workspace_calendar_events").upsert(
          batch.map((event) => ({
            ws_id: payload.wsId,
            google_event_id: event.id,
            title: event.summary,
            description: event.description,
            start_at: event.start?.dateTime || event.start?.date,
            end_at: event.end?.dateTime || event.end?.date,
            location: event.location,
            creator_id: payload.userId,
          })),
          {
            onConflict: "google_event_id",
          },
        );
      }

      // Update sync token
      await sbAdmin.from("calendar_sync_states").upsert({
        ws_id: payload.wsId,
        sync_token: response.data.nextSyncToken,
        last_synced_at: new Date().toISOString(),
      });

      // Log sync completion
      await sbAdmin
        .from("workspace_calendar_sync_log")
        .update({
          completed_at: new Date().toISOString(),
        })
        .eq("id", syncLog?.id);

      return {
        success: true,
        eventsProcessed: events.length,
      };
    } catch (error) {
      // Log error
      await sbAdmin.from("workspace_calendar_sync_log").update({
        completed_at: new Date().toISOString(),
        error: (error as Error).message,
      });

      throw error;
    } finally {
      // Always release lock
      await releaseSyncLock(payload.wsId);
    }
  },
});

Incremental Sync Flow

import { task } from "@trigger.dev/sdk/v3";
import { google } from "googleapis";
import { createAdminClient } from "@tuturuuu/supabase/next/server";

export const googleCalendarIncrementalSync = task({
  id: "google-calendar-incremental-sync",
  maxDuration: 60, // 1 minute
  run: async (payload: { wsId: string; userId: string }) => {
    const sbAdmin = await createAdminClient({ noCookie: true });

    const lockAcquired = await acquireSyncLock(payload.wsId);
    if (!lockAcquired) return { skipped: true };

    try {
      // Get sync token
      const { data: syncState } = await sbAdmin
        .from("calendar_sync_states")
        .select("sync_token")
        .eq("ws_id", payload.wsId)
        .single();

      if (!syncState?.sync_token) {
        // No sync token - trigger full sync instead
        await googleCalendarFullSync.trigger(payload);
        return { redirectedToFullSync: true };
      }

      // Get OAuth tokens
      const { data: tokens } = await sbAdmin
        .from("calendar_auth_tokens")
        .select("*")
        .eq("user_id", payload.userId)
        .single();

      if (!tokens) throw new Error("No calendar tokens found");

      const oauth2Client = new google.auth.OAuth2();
      oauth2Client.setCredentials({
        access_token: tokens.access_token,
        refresh_token: tokens.refresh_token,
      });

      const calendar = google.calendar({ version: "v3", auth: oauth2Client });

      // Fetch changes since last sync
      const response = await calendar.events.list({
        calendarId: "primary",
        syncToken: syncState.sync_token,
      });

      const events = response.data.items || [];

      // Process changes
      for (const event of events) {
        if (event.status === "cancelled") {
          // Delete event
          await sbAdmin
            .from("workspace_calendar_events")
            .delete()
            .eq("google_event_id", event.id);
        } else {
          // Upsert event
          await sbAdmin.from("workspace_calendar_events").upsert({
            ws_id: payload.wsId,
            google_event_id: event.id,
            title: event.summary,
            description: event.description,
            start_at: event.start?.dateTime || event.start?.date,
            end_at: event.end?.dateTime || event.end?.date,
            location: event.location,
            creator_id: payload.userId,
          });
        }
      }

      // Update sync token
      await sbAdmin
        .from("calendar_sync_states")
        .update({
          sync_token: response.data.nextSyncToken,
          last_synced_at: new Date().toISOString(),
        })
        .eq("ws_id", payload.wsId);

      return {
        success: true,
        changesProcessed: events.length,
      };
    } catch (error) {
      if ((error as any).code === 410) {
        // Sync token expired - trigger full sync
        await googleCalendarFullSync.trigger(payload);
        return { syncTokenExpired: true };
      }
      throw error;
    } finally {
      await releaseSyncLock(payload.wsId);
    }
  },
});

Scheduled Jobs

Production Calendar Scheduling

Recurring Calendar work runs from apps/web cron, not Trigger.dev. The canonical definitions live in apps/web/cron.config.json:
{
  "id": "calendar-provider-sync",
  "path": "/api/cron/calendar/provider-sync",
  "schedule": "*/15 * * * *"
},
{
  "id": "calendar-smart-schedule",
  "path": "/api/cron/calendar/smart-schedule",
  "schedule": "0 */6 * * *"
}
When cron definitions change, update apps/web/cron.config.json first and then sync the generated apps/web/vercel.json cron block:
# Fail if vercel.json is out of sync with cron.config.json (used in CI)
node scripts/sync-web-crons.js --check

# Regenerate vercel.json crons from cron.config.json
node scripts/sync-web-crons.js
The cron wrapper calls /api/v1/workspaces/:wsId/calendar/sync with cron auth and source: "cron". That workspace route owns provider fan-out, locks, and sync dashboard audit rows. This calendar cron path stays on apps/web until the TanStack/Rust migration covers recurring sync (see the warning above).

Development

Local Development

# Start Trigger.dev dev server
bun trigger:dev
This starts a local Trigger.dev instance for manual or legacy task testing. It is not required for production Calendar provider sync.

Testing Jobs Locally

import { googleCalendarFullSync } from "@tuturuuu/trigger";

// Trigger a test run
const run = await googleCalendarFullSync.trigger({
  wsId: "test-workspace",
  userId: "test-user",
});

console.log("Run ID:", run.id);

Deployment

Deploy Jobs to Production

bun trigger:deploy
This deploys Trigger.dev task wrappers for workflows that still use Trigger.dev. Do not use it to deploy recurring Calendar provider sync or smart scheduling.

Environment Variables

Required in .env:
TRIGGER_SECRET_KEY=your_trigger_secret_key

Monitoring

View Job Runs

Access the Trigger.dev dashboard to monitor:
  • Job execution history
  • Success/failure rates
  • Execution duration
  • Error logs
  • Retry attempts

Error Handling

export const myJob = task({
  id: "my-job",
  retry: {
    maxAttempts: 3,
    minTimeoutInMs: 1000,
    maxTimeoutInMs: 10000,
    factor: 2,
  },
  run: async (payload) => {
    // Job implementation
  },
});

Best Practices

✅ DO

  1. Use appropriate sync types
    // Initial setup: Full sync
    await googleCalendarFullSync.trigger({ wsId, userId });
    
    // Regular updates: Incremental sync
    await googleCalendarIncrementalSync.trigger({ wsId, userId });
    
  2. Implement sync locks
    const lockAcquired = await acquireSyncLock(wsId);
    if (!lockAcquired) return { skipped: true };
    
  3. Log sync operations
    await sbAdmin.from("workspace_calendar_sync_log").insert({
      ws_id: wsId,
      sync_type: "incremental",
      started_at: new Date().toISOString(),
    });
    
  4. Set appropriate max durations
    maxDuration: 300, // 5 minutes for full sync
    maxDuration: 60,  // 1 minute for incremental sync
    
  5. Handle token expiration
    if (error.code === 410) {
      // Trigger full sync to get new token
    }
    

❌ DON’T

  1. Don’t skip lock acquisition
    // ❌ Bad: Concurrent syncs can corrupt data
    
  2. Don’t use the user (cookie) client for background jobs
    // ❌ Bad: cookie-bound client has no session in a background task
    const supabase = await createClient();
    
    // ✅ Good: admin client with noCookie for server-side jobs
    const sbAdmin = await createAdminClient({ noCookie: true });
    
  3. Don’t ignore sync errors
    // ❌ Bad: Silent failure
    // ✅ Good: Log to sync_log table
    

Future Jobs

Potential background jobs to implement:
  • Email processing and AI summarization
  • Batch task creation from templates
  • Automated report generation
  • Data export and backup
  • Workspace analytics calculation