vault-video-processor / docs /inngest-workflows.md
dvijaykrishnan's picture
docs: Introduce comprehensive technical documentation and update planning artifacts to reflect MVP completion and detailed FR implementation status.
d03d74d
|
Raw
History Blame Contribute Delete
17.1 kB

Vault - Inngest Workflows

Generated: 2026-02-11
Framework: Inngest 3.49.1


Overview

Inngest provides durable workflow orchestration for long-running background tasks. All workflows are defined in src/inngest/.

Client Configuration

File: src/inngest/client.ts

import { Inngest } from 'inngest';

export const inngest = new Inngest({
  id: 'vault',
  name: 'Vault AI Discovery',
  eventKey: process.env.INNGEST_EVENT_KEY,
});

Handler Registration

File: src/app/api/inngest/route.ts

import { serve } from 'inngest/next';
import { inngest } from '@/inngest/client';
import { scanVideoArchive } from '@/inngest/functions/scan-video-archive';
import { detectObjects } from '@/inngest/functions/detect-objects';
import { matchMarketplace } from '@/inngest/functions/match-marketplace';
import { monitorLinkHealth, checkSingleLinkHealth } from '@/inngest/functions/monitor-link-health';
import { warmFeaturedCreatorsCache } from '@/inngest/functions/warm-featured-creators-cache';
import { healSocialMetadata } from '@/inngest/functions/social-healing';

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [
    scanVideoArchive,
    detectObjects,
    matchMarketplace,
    monitorLinkHealth,
    checkSingleLinkHealth,
    warmFeaturedCreatorsCache,
    healSocialMetadata,
  ],
  signingKey: process.env.INNGEST_SIGNING_KEY,
});

Workflow: Scan Video Archive

Function ID: scan-video-archive
File: src/inngest/functions/scan-video-archive.ts
Event: youtube/video-archive.scan

Purpose

Scans a YouTube channel's video archive, fetching metadata and queuing videos for object detection.

Event Payload

interface ScanVideoArchiveEvent {
  name: 'youtube/video-archive.scan';
  data: {
    channelId: string;  // Internal DB channel ID
    userId: string;     // User who initiated scan
    scanJobId: string;  // Progress tracking ID
  };
}

Workflow Steps

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    scan-video-archive                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  Step 1: validate-channel                                    β”‚
β”‚  β”œβ”€β”€ Verify channel ownership                                β”‚
β”‚  β”œβ”€β”€ Update syncStatus to 'syncing'                          β”‚
β”‚  └── Return channel data                                     β”‚
β”‚                                                              β”‚
β”‚  Step 2: fetch-refresh-token                                 β”‚
β”‚  └── Get OAuth refresh token from accounts table             β”‚
β”‚                                                              β”‚
β”‚  Step 3: refresh-access-token                                β”‚
β”‚  └── Exchange refresh token for new access token             β”‚
β”‚                                                              β”‚
β”‚  Step 4: get-uploads-playlist                                β”‚
β”‚  └── Fetch channel's uploads playlist ID                     β”‚
β”‚                                                              β”‚
β”‚  Step 5: fetch-video-ids (paginated)                         β”‚
β”‚  └── Get all video IDs from playlist                         β”‚
β”‚                                                              β”‚
β”‚  Step 6: fetch-video-metadata (batched)                      β”‚
β”‚  └── Get metadata for each video                             β”‚
β”‚                                                              β”‚
β”‚  Step 7: store-videos                                        β”‚
β”‚  └── Insert/update videos in database                        β”‚
β”‚                                                              β”‚
β”‚  Step 8: trigger-detection (per video)                       β”‚
β”‚  └── Send 'youtube/video.detect-objects' event               β”‚
β”‚                                                              β”‚
β”‚  Step 9: update-scan-job                                     β”‚
β”‚  └── Mark scan job as completed                              β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Error Handling

  • NonRetriableError: Channel not found, access denied
  • RetryAfterError: YouTube API rate limits
  • onFailure: Update scan job status to 'failed', set channel syncStatus to 'errored'

Workflow: Detect Objects

Function ID: detect-objects
File: src/inngest/functions/detect-objects.ts
Event: youtube/video.detect-objects

Purpose

Extracts frames from a video and runs AI object detection on each frame.

Event Payload

interface DetectObjectsEvent {
  name: 'youtube/video.detect-objects';
  data: {
    videoId: string;   // Internal DB video ID
    videoUrl: string;  // Video URL for processing
  };
}

Workflow Steps

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      detect-objects                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  Step 1: update-scan-status-started                          β”‚
β”‚  └── Set video scanStatus to 'in_progress'                   β”‚
β”‚                                                              β”‚
β”‚  Step 2: analyze-video-content                               β”‚
β”‚  β”œβ”€β”€ Calculate adaptive frame interval                       β”‚
β”‚  β”‚   β”œβ”€β”€ 0-2 min: every 5 seconds                            β”‚
β”‚  β”‚   β”œβ”€β”€ 2-5 min: every 10 seconds                           β”‚
β”‚  β”‚   β”œβ”€β”€ 5-10 min: every 15 seconds                          β”‚
β”‚  β”‚   └── 10+ min: every 30 seconds                           β”‚
β”‚  β”œβ”€β”€ Extract frames at intervals (FFmpeg)                    β”‚
β”‚  β”œβ”€β”€ Process frames in batches of 3                          β”‚
β”‚  β”œβ”€β”€ Run AI detection on each frame                          β”‚
β”‚  β”œβ”€β”€ Generate thumbnails for detections                      β”‚
β”‚  └── Return all detection results                            β”‚
β”‚                                                              β”‚
β”‚  Step 3: store-detections                                    β”‚
β”‚  └── Insert detected objects into database                   β”‚
β”‚                                                              β”‚
β”‚  Step 4: trigger-marketplace-matching                        β”‚
β”‚  └── Send 'discovery/objects.match-marketplace' event        β”‚
β”‚                                                              β”‚
β”‚  Step 5: update-scan-status-completed                        β”‚
β”‚  └── Set video scanStatus to 'awaiting_approval'             β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AI Vision Providers

Configured via VISION_PROVIDER environment variable:

Provider Model Best For
gemini Gemini Flash Complex scenes, good accuracy
huggingface DETR Specialized object detection, better free tier

Error Handling

  • VideoUnavailableError: Video is private or deleted
  • VisionRateLimitError: AI provider rate limits
  • NonRetriableError: Missing videoId or videoUrl

Workflow: Match Marketplace

Function ID: match-marketplace
File: src/inngest/functions/match-marketplace.ts
Event: discovery/objects.match-marketplace

Purpose

Searches Amazon, eBay, and Etsy for products matching detected objects.

Event Payload

interface MatchMarketplaceEvent {
  name: 'discovery/objects.match-marketplace';
  data: {
    detectedObjectId?: string;      // Single ID (legacy)
    detectedObjectIds?: string[];   // Batch IDs (preferred)
  };
}

Workflow Steps

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    match-marketplace                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  For each detected object:                                   β”‚
β”‚                                                              β”‚
β”‚  Step 1: fetch-object-{id}                                   β”‚
β”‚  └── Get detection details from database                     β”‚
β”‚                                                              β”‚
β”‚  Step 2: search-amazon-{id}                                  β”‚
β”‚  β”œβ”€β”€ Check cache for existing matches                        β”‚
β”‚  β”œβ”€β”€ If not cached, search Amazon PA-API                     β”‚
β”‚  β”œβ”€β”€ Cache results                                           β”‚
β”‚  └── Return matches                                          β”‚
β”‚                                                              β”‚
β”‚  Step 3: search-ebay-{id}                                    β”‚
β”‚  β”œβ”€β”€ Check cache for existing matches                        β”‚
β”‚  β”œβ”€β”€ If not cached, search eBay Finding API                  β”‚
β”‚  β”œβ”€β”€ Cache results                                           β”‚
β”‚  └── Return matches                                          β”‚
β”‚                                                              β”‚
β”‚  Step 4: search-etsy-{id}                                    β”‚
β”‚  β”œβ”€β”€ Check cache for existing matches                        β”‚
β”‚  β”œβ”€β”€ If not cached, search Etsy Open API                     β”‚
β”‚  β”œβ”€β”€ Cache results                                           β”‚
β”‚  └── Return matches                                          β”‚
β”‚                                                              β”‚
β”‚  Step 5: store-matches-{id}                                  β”‚
β”‚  └── Insert marketplace matches into database                β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Caching Strategy

  • Cache Key: {objectId}:{marketplace}
  • TTL: 24 hours
  • Storage: Upstash Redis

Rate Limit Handling

  • MarketplaceRateLimitError: Triggers RetryAfterError
  • Automatic retry after specified delay

Workflow: Monitor Link Health

Function ID: monitor-link-health
File: src/inngest/functions/monitor-link-health.ts
Event: marketplace/links.health-check

Purpose

Periodically checks affiliate links for availability and updates status.

Event Payload

interface MonitorLinkHealthEvent {
  name: 'marketplace/links.health-check';
  data: {
    matchId?: string;       // Single match check
    checkAll?: boolean;     // Check all active links
  };
}

Workflow Steps

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   monitor-link-health                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  Step 1: fetch-links-to-check                                β”‚
β”‚  └── Get links with status 'ACTIVE' or 'CHECKING'            β”‚
β”‚                                                              β”‚
β”‚  For each link:                                              β”‚
β”‚                                                              β”‚
β”‚  Step 2: check-link-{id}                                     β”‚
β”‚  β”œβ”€β”€ HTTP HEAD request to affiliate URL                      β”‚
β”‚  β”œβ”€β”€ Record HTTP status                                      β”‚
β”‚  └── Return status                                           β”‚
β”‚                                                              β”‚
β”‚  Step 3: update-link-status-{id}                             β”‚
β”‚  β”œβ”€β”€ If 200: Set status 'ACTIVE'                             β”‚
β”‚  β”œβ”€β”€ If 404: Set status 'BROKEN'                             β”‚
β”‚  └── Increment checkAttempts                                 β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Link Status Transitions

ACTIVE ──check──> CHECKING ──success──> ACTIVE
                     β”‚
                     └──failure──> BROKEN

Workflow: Warm Featured Creators Cache

Function ID: warm-featured-creators-cache
File: src/inngest/functions/warm-featured-creators-cache.ts
Event: cache/warm-featured-creators

Purpose

Pre-warms cache for featured creators on the homepage.

Workflow Steps

  1. Fetch featured creators from database
  2. For each creator, fetch their vault data
  3. Store in Redis cache with TTL

Workflow: Heal Social Metadata

Function ID: heal-social-metadata
File: src/inngest/functions/social-healing.ts
Event: social/metadata.heal

Purpose

Repairs inconsistent platform metadata for multi-platform content.

Workflow Steps

  1. Find videos with platform mismatches
  2. Update platform field based on video ID patterns
  3. Update channel platform associations

Error Handling Patterns

NonRetriableError

For errors that should not be retried:

import { NonRetriableError } from 'inngest';

throw new NonRetriableError('Channel not found or access denied');

RetryAfterError

For rate-limited resources:

import { RetryAfterError } from 'inngest';

throw new RetryAfterError('Amazon rate limit exceeded', '60s');

Sentry Integration

All functions capture errors with Sentry:

import * as Sentry from '@sentry/nextjs';

onFailure: async ({ event, error }) => {
  Sentry.captureException(error, {
    tags: {
      source: 'inngest',
      function: 'function-name',
      type: 'function_failure',
    },
    extra: { event: event.data },
  });
}

Development & Testing

Local Development

  1. Start Inngest Dev Server:

    npx inngest-cli dev
    
  2. Application connects to dev server automatically in development mode.

Manual Triggers

Use the manual trigger endpoint for testing:

curl -X POST http://localhost:3000/api/manual-trigger \
  -H "Content-Type: application/json" \
  -d '{"function": "scan-video-archive", "data": {...}}'

Monitoring

  • Inngest Cloud Dashboard: View function runs, retries, errors
  • Sentry: Error tracking with context
  • Logs: Console output captured in Inngest dashboard

Related Documentation