---
title: "Commission Export Batching"
description: "The execution flow tracing from POST down to DubApiError governs the processing of large background commission exports triggered via QStash. When an export cron request hits the API route, it verif..."
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/commission-export-batching"
---

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

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

- [apps/web/app/ee/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts)
- [apps/web/app/ee/api/cron/export/commissions/fetch-commissions-batch.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/fetch-commissions-batch.ts)
- [apps/web/lib/api/commissions/get-commissions.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/get-commissions.ts)
- [apps/web/lib/api/commissions/metadata-filters.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts)
- [apps/web/lib/api/errors.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/errors.ts)
</details>

## Overview

The execution flow tracing from `POST` down to `DubApiError` governs the processing of large background commission exports triggered via QStash. When an export cron request hits the API route, it verifies signatures, parses input payloads, retrieves batches of commissions with optional metadata filters, and generates downloadable CSV reports. If validation failures or invalid query cursors occur during this pipeline, errors are caught, logged, and structured into standard API error responses using `DubApiError`.

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts:22-114](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L22-L114)

---

### Step 1: POST

The execution begins at the `POST` route handler for commission export cron jobs. It reads the incoming request body, validates the QStash signature, and parses the payload using a Zod schema to extract filters, `programId`, `columns`, and `userId`. It verifies the existence of the target user and program in the database before initiating batch processing.

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts:23-67](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app/(ee)/api/cron/export/commissions/route.ts#L23-L67)

---

### Step 2: fetchCommissionsBatch

To handle large export datasets without memory exhaustion, the router invokes the `fetchCommissionsBatch` async generator. This function iterates through paginated database queries by requesting fixed-size batches (defaulting to 1,000 records per page) until all matching records have been retrieved.

Sources: [apps/web/app/(ee)/api/cron/export/commissions/fetch-commissions-batch.ts:12-34](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/fetch-commissions-batch.ts#L12-L34)

---

### Step 3: getCommissions

Inside the batch generator, `getCommissions` executes the underlying database queries via Prisma. It processes filtering parameters such as partner IDs, statuses, date ranges, and pagination cursors. It also validates pagination cursor IDs to guarantee that provided cursors belong to the correct program.

Sources: [apps/web/lib/api/commissions/get-commissions.ts:35-97](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/get-commissions.ts#L35-L97)

---

### Step 4: parseCommissionMetadataQuery

When commission queries include metadata filtering expressions, `parseCommissionMetadataQuery` validates and parses the raw query string. It normalizes quotes, verifies that logical operators (AND/OR) are not improperly mixed, checks condition limits, and splits the expression into distinct filter segments.

Sources: [apps/web/lib/api/commissions/metadata-filters.ts:109-170](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts#L109-L170)

---

### Step 5: parseCondition

Each individual segment of the metadata query is passed to `parseCondition`. This function uses regular expressions to isolate the metadata key, operator, and raw value, ensuring keys adhere to valid identifier rules and that empty or malformed conditions are rejected.

Sources: [apps/web/lib/api/commissions/metadata-filters.ts:34-83](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts#L34-L83)

---

### Step 6: mapOperator

The `mapOperator` helper translates raw operator tokens (such as `=`, `:`, or `!=`) into internal `CommissionMetadataFilterOp` representations (`equals` or `notEquals`). If an unsupported operator is supplied, it throws a structured API error.

Sources: [apps/web/lib/api/commissions/metadata-filters.ts:19-32](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts#L19-L32)

---

### Step 7: DubApiError

When validation failures occur—such as invalid metadata operators, keys containing forbidden characters, or invalid pagination cursors—the application throws a `DubApiError` instance. The top-level `POST` catch block captures this error, logs it via Axiom, and converts it into a standardized JSON error response with appropriate HTTP status codes.

Sources: [apps/web/lib/api/errors.ts:44-61](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/errors.ts#L44-L61), [apps/web/lib/api/errors.ts:106-131](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/errors.ts#L106-L131)

---

## Sequence Diagram

```mermaid
sequenceDiagram
    participant Route as POST (route.ts)
    participant Batch as fetchCommissionsBatch
    participant GetComm as getCommissions
    participant MetaQuery as parseCommissionMetadataQuery
    participant Cond as parseCondition
    participant Op as mapOperator
    participant Err as DubApiError

    Route->>Batch: fetchCommissionsBatch(filters)
    loop Paginated Batches
        Batch->>GetComm: getCommissions(filters + page)
        GetComm->>MetaQuery: parseCommissionMetadataQuery(query)
        alt Invalid Query Structure
            MetaQuery->>Err: throw DubApiError
        end
        MetaQuery->>Cond: parseCondition(trimmedCondition)
        alt Invalid Metadata Key/Value
            Cond->>Err: throw DubApiError
        end
        Cond->>Op: mapOperator(operator)
        alt Unsupported Operator
            Op->>Err: throw DubApiError
        end
        Op-->>Cond: return filter op
        Cond-->>MetaQuery: return parsed filter
        MetaQuery-->>GetComm: return parsed metadata where clause
        GetComm-->>Batch: return commissions array
    end
    Batch-->>Route: yield commissions batch
```

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts:75-79](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L75-L79), [apps/web/app/(ee)/api/cron/export/commissions/fetch-commissions-batch.ts:19-33](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/fetch-commissions-batch.ts#L19-L33), [apps/web/lib/api/commissions/get-commissions.ts:58-96](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/get-commissions.ts#L58-L96), [apps/web/lib/api/commissions/metadata-filters.ts:19-168](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts#L19-L168), [apps/web/lib/api/errors.ts:44-61](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/errors.ts#L44-L61)

---

## Flowchart

```mermaid
flowchart TD
    A[POST Request] --> B[fetchCommissionsBatch]
    B --> C[getCommissions]
    C --> D[parseCommissionMetadataQuery]
    D --> E{Valid Query?}
    E -- No --> Z[DubApiError]
    E -- Yes --> F[parseCondition]
    F --> G{Valid Condition?}
    G -- No --> Z
    G -- Yes --> H[mapOperator]
    H --> I{Supported Op?}
    I -- No --> Z
    I -- Yes --> J[Execute Prisma Query]
    J --> K[Return Commissions Batch]
```

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts:23-79](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app/(ee)/api/cron/export/commissions/route.ts#L23-L79), [apps/web/lib/api/commissions/get-commissions.ts:58-208](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/get-commissions.ts#L58-L208), [apps/web/lib/api/commissions/metadata-filters.ts:19-168](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/commissions/metadata-filters.ts#L19-L168), [apps/web/lib/api/errors.ts:44-61](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/errors.ts#L44-L61)

---

## Key Observations

- **Modular Boundaries:** The execution flow seamlessly crosses API route handlers, async generator utilities, query builders, metadata parsers, and centralized error management layers.
- **Robust Validation:** Metadata queries undergo multi-stage validation checking condition limits, mixing of logical operators, and structural syntax rules before ever reaching the database layer.
- **Error Handling:** Any validation or execution failure thrown as a `DubApiError` is caught at the root API handler, logged to Axiom, and returned with precise HTTP status mapping.

## Sitemap

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