---
title: "Partner Search Sync Pipeline"
description: "This page documents the end-to-end execution flow when a partner deletion cron request (POST) triggers side-effect cleanups, unlinking, and eventually interacts with the Turbopuffer search provider..."
last_updated: "2026-10-05T05:07:35.179478+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/how-it-works/partner-search-sync-pipeline"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/web/app/(ee)/api/cron/partners/delete/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/partners/delete/route.ts)
- [apps/web/lib/api/links/bulk-delete-links.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/links/bulk-delete-links.ts)
- [apps/web/lib/api/partners/queue-partner-search-sync.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.co.ts)
- [apps/web/lib/api/partners/search/provider.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/provider.ts)
- [apps/web/lib/api/partners/search/providers/turbopuffer.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts)
</details>

## Overview

### Overview

This page documents the end-to-end execution flow when a partner deletion cron request (`POST`) triggers side-effect cleanups, unlinking, and eventually interacts with the Turbopuffer search provider via `createNamespace`. 

When a partner is permanently deleted from a program, the system must clean up associated database rows, delete related short links in bulk, and queue an index synchronization task with the external vector and search database (Turbopuffer). This flow ensures that search indices remain consistent and reflect deleted enrollments without blocking the primary mutation.

Sources: [apps/web/app/(ee)/api/cron/partners/delete/route.ts:20-184](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/partners/delete/route.ts#L20-L184)

---

### Step 1: POST /api/cron/partners/delete

The entry point is the cron route handler which receives a JSON body containing `workspaceId`, `programId`, `partnerId`, and `userId`. After validating the input schema and ensuring the partner enrollment and its associated links and tags exist, the handler verifies deletion constraints (such as checking for zero active commissions or payouts). It clears related relational records (submitted leads, fraud events, messages) and invokes bulk link deletion if any links are attached to the enrollment.

Sources: [apps/web/app/(ee)/api/cron/partners/delete/route.ts:20-140](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/partners/delete/route.ts#L20-L140)

---

### Step 2: bulkDeleteLinks

Called by the delete route when `links.length > 0`, the `bulkDeleteLinks` function processes links in batches of 100. For each batch, it identifies and removes related discount codes, executes a database transaction to delete the link records, and decrements the project's total link counter. Finally, it schedules asynchronous cleanups for Redis cache entries, Tinybird analytics records, R2 storage images, and queues partner search synchronization if any deleted links were associated with a partner.

Sources: [apps/web/lib/api/links/bulk-delete-links.ts:23-78](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/links/bulk-delete-links.ts#L23-L78)

---

### Step 3: queuePartnerSearchSyncForLinks

As part of the asynchronous cleanup following link deletion, `queuePartnerSearchSyncForLinks` maps over the deleted links that carry a `partnerId` and `programId`. It aggregates partner IDs by their respective program IDs to prevent redundant job creation, subsequently delegating each grouped program batch to the search sync queue.

Sources: [apps/web/lib/api/partners/queue-partner-search-sync.ts:100-125](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.ts#L100-L125)

---

### Step 4: queuePartnerSearchSync

The core queuing function, `queuePartnerSearchSync`, validates that a search provider is actively configured. It chunks enrollment IDs and partner IDs into batch payloads and attempts to dispatch them to the `partnerSearchSyncJob` queue with a 5-second default delay. This delay ensures database mutations have fully committed before background workers attempt to read the rows back.

Sources: [apps/web/lib/api/partners/queue-partner-search-sync.ts:44-83](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.ts#L44-L83)

---

### Step 5: getPartnerSearchProvider

To interact with the search service, `getPartnerSearchProvider` checks for the presence of the `TURBOPUFFER_API_KEY` environment variable. If configured, it initializes and caches a singleton instance of the Turbopuffer search provider, avoiding repeated TLS handshakes and connection pool overhead across requests.

Sources: [apps/web/lib/api/partners/search/provider.ts:12-20](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/provider.ts#L12-L20)

---

### Step 6: createTurbopufferPartnerSearchProvider

The provider factory initializes the Turbopuffer integration wrapper. It exposes methods for searching candidates, counting results, upserting documents, and removing document IDs. Behind the scenes, these methods reference a shared vector namespace (defaults to `partner-search-v4`) where partner program data is indexed.

Sources: [apps/web/lib/api/partners/search/providers/turbopuffer.ts:34-36](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L34-L36), [apps/web/lib/api/partners/search/providers/turbopuffer.ts:345-421](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L345-L421)

---

### Step 7: createNamespace

When provider operations require access to Turbopuffer, `createNamespace` instantiates the official `@turbopuffer/turbopuffer` client configured with the API key and the `aws-us-east-1` region. It returns the requested namespace object (e.g., `partner-search-v4`), allowing write, query, and delete operations to execute against the remote index.

Sources: [apps/web/lib/api/partners/search/providers/turbopuffer.ts:123-136](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L123-L136)

---

## Sequence Diagram

```mermaid
sequenceDiagram
    participant Cron as POST /api/cron/partners/delete
    participant Links as bulkDeleteLinks
    participant QueueLinks as queuePartnerSearchSyncForLinks
    participant Queue as queuePartnerSearchSync
    participant Provider as getPartnerSearchProvider
    participant Turbopuffer as createTurbopufferPartnerSearchProvider
    participant Namespace as createNamespace

    Cron->>Cron: Parse input, validate constraints & delete DB records
    alt Links exist
        Cron->>Links: bulkDeleteLinks(links)
        Links->>Links: Delete discount codes & execute transaction
        Links->>QueueLinks: queuePartnerSearchSyncForLinks(links)
        QueueLinks->>Queue: queuePartnerSearchSync({ partnerIds, programId })
    end
    Cron->>Queue: queuePartnerSearchSync({ enrollmentIds })
    Queue->>Provider: getPartnerSearchProvider()
    Provider->>Turbopuffer: createTurbopufferPartnerSearchProvider()
    Turbopuffer->>Namespace: createNamespace(resolvedNamespaceName)
    Namespace-->>Turbopuffer: TurbopufferNamespace instance
    Turbopuffer-->>Provider: SearchProvider methods
    Provider-->>Queue: Provider ready for sync/write
```

Sources: [apps/web/app/(ee)/api/cron/partners/delete/route.ts:20-163](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/partners/delete/route.ts#L20-L163), [apps/web/lib/api/links/bulk-delete-links.ts:23-73](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/links/bulk-delete-links.ts#L23-L73), [apps/web/lib/api/partners/queue-partner-search-sync.ts:44-125](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.ts#L44-L125), [apps/web/lib/api/partners/search/provider.ts:12-20](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/provider.ts#L12-L20), [apps/web/lib/api/partners/search/providers/turbopuffer.ts:123-136](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L123-L136), [apps/web/lib/api/partners/search/providers/turbopuffer.ts:345-351](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L345-L351)

---

## Flowchart

```mermaid
flowchart TD
    A[POST Cron Request] --> B{Enrollment Exists?}
    B -- No --> C[Skip Delete & Respond]
    B -- Yes --> D{Can Delete Partner?}
    D -- No --> E[Reject & Respond]
    D -- Yes --> F[Delete Relational Records]
    F --> G{Links Exist?}
    G -- Yes --> H[bulkDeleteLinks]
    H --> I[Queue Link Search Sync]
    G -- No --> J[Delete Enrollment & Project Usage]
    I --> J
    J --> K[queuePartnerSearchSync]
    K --> L{Provider Configured?}
    L -- No --> M[No-op / Skip Indexing]
    L -- Yes --> N[getPartnerSearchProvider]
    N --> O[createTurbopufferPartnerSearchProvider]
    O --> P[createNamespace]
    P --> Q[Dispatch Sync Job to QStash]
```

Sources: [apps/web/app/(ee)/api/cron/partners/delete/route.ts:49-163](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/partners/delete/route.ts#L49-L163), [apps/web/lib/api/links/bulk-delete-links.ts:31-73](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/links/bulk-delete-links.ts#L31-L73), [apps/web/lib/api/partners/queue-partner-search-sync.ts:52-83](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.ts#L52-L83), [apps/web/lib/api/partners/search/provider.ts:12-20](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/provider.ts#L12-L20), [apps/web/lib/api/partners/search/providers/turbopuffer.ts:123-136](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/providers/turbopuffer.ts#L123-L136)

---

## Key Observations

- **Cross-Module Boundaries:** This execution flow bridges administrative cron jobs, relational database persistence (Prisma), bulk link deletion side effects (Redis, Tinybird, R2 storage), and external vector search index synchronization (Turbopuffer).
- **Resilience and Non-blocking Queues:** The partner search queue wrapper (`queuePartnerSearchSync`) is designed to catch dispatch failures without failing the primary source mutation. If QStash dispatch throws, errors are logged gracefully while allowing the core HTTP response to proceed.
- **Singleton Caching:** The search provider is initialized as a process-level singleton (`cachedSearchProvider`), which prevents connection overhead and TLS handshake degradation during frequent search and synchronization updates.

Sources: [apps/web/lib/api/links/bulk-delete-links.ts:48-72](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/links/bulk-delete-links.ts#L48-L72), [apps/web/lib/api/partners/queue-partner-search-sync.ts:40-83](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/queue-partner-search-sync.ts#L40-L83), [apps/web/lib/api/partners/search/provider.ts:4-20](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/search/provider.ts#L4-L20)

## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
