Skip to content

Conversation

@jeffredodd
Copy link
Contributor

Add Generic Batching for API Mutations

Problem

The API limits payroll updates to 100 employee compensations per request. We currently can't handle larger payrolls without manually implementing batching logic in each component.

Solution

New useBatchedMutation hook - works exactly like TanStack Query mutations, but with automatic batching.

How It Works

  • ≤ 100 items: Single API call (no overhead)
  • > 100 items: Automatic batching (e.g., 150 items = 2 calls: 100 + 50)
  • Errors: Stops on first error
  • Loading: Single isPending for all batches

What's Included

  • src/hooks/useBatchedMutation.ts
  • src/helpers/batchProcessor.ts

Use Anywhere

Works with any tanstack query mutation that needs batching:

const { mutateAsync: updatePayments } = useContractorPaymentsMutation()

const { mutateAsync } = useBatchedMutation(
  payments => updatePayments({ request: { companyId, payments } }),
  { batchSize: 50 },
)

await mutateAsync(allPayments)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces generic batching utilities to handle API mutations that have size limits (e.g., 100 employee compensations per request). The implementation provides a reusable useBatchedMutation hook that wraps TanStack Query mutations with automatic batching capabilities.

Key Changes:

  • Adds useBatchedMutation hook for automatic batch processing of large datasets
  • Implements core batching logic in batchProcessor.ts with configurable batch sizes
  • Includes comprehensive test coverage for both the hook and helper utilities

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/hooks/useBatchedMutation.ts React hook providing batched mutation capabilities with loading state management
src/hooks/useBatchedMutation.test.ts Test suite for the batched mutation hook covering batching, loading states, and error handling
src/helpers/batchProcessor.ts Core utilities for splitting arrays into batches and sequential batch processing
src/helpers/batchProcessor.test.ts Comprehensive tests for batch splitting and processing logic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jeffredodd jeffredodd force-pushed the jdj/api-batch-processing branch from 9426948 to 120247c Compare November 6, 2025 04:43
@dryrunsecurity
Copy link

dryrunsecurity bot commented Nov 10, 2025

DryRun Security

This pull request introduces or includes a batched-update capability via a useBatchedMutation hook and processBatches function that can send up to 100 payroll updates per request (though current UI components send one at a time). If the backend API (baseUpdatePayroll from @gusto/embedded-api/mod) does not validate batch size or consider batch item counts in rate limiting, this could allow business-logic abuse such as mass payroll modifications or bypassing single-update rate limits.

Business Logic Abuse via Batched Updates in src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx
Vulnerability Business Logic Abuse via Batched Updates
Description The useBatchedMutation hook allows for sending up to 100 payroll updates in a single request. While the current client-side implementation in PayrollConfiguration.tsx and PayrollEditEmployee.tsx only sends one item at a time, the underlying processBatches function and the useBatchedMutation hook are designed to handle batches of up to 100 items. The baseUpdatePayroll function, which is called by the batched mutation, originates from an external API (@gusto/embedded-api/mod). Without access to the backend implementation of this API, it is impossible to determine if adequate rate limiting or payload size validation is in place on the server-side to prevent abuse. If the backend does not properly validate the batch size or implement rate limiting that considers the number of items in a batch, an attacker could potentially bypass existing rate limits designed for single updates or perform mass modifications of payroll data more efficiently than with individual requests.

const { mutateAsync: updatePayroll, isPending: isUpdatingPayroll } = useBatchedMutation(
async (batch: PayrollUpdateEmployeeCompensations[]) => {
const result = await baseUpdatePayroll({
request: {
companyId,
payrollId,
payrollUpdate: { employeeCompensations: batch },
},
})
return result.payrollPrepared!
},
{ batchSize: 100 },
)


All finding details can be found in the DryRun Security Dashboard.

jeffredodd and others added 3 commits November 12, 2025 09:15
- Add useBatchedMutation hook with TanStack-style API
- Add generic batchProcessor helpers (splitIntoBatches, processBatches)
- Hook wraps any mutation to transparently handle API batch limits
- Works by passing mutation function and calling with array
- Comprehensive test coverage (23 tests)
- Documentation with usage examples
feat: implement batching for payroll updates to support v2025-06-15 API changes

- Update PayrollConfiguration and PayrollEditEmployee to use useBatchedMutation
- Set batch size to 100 to align with API maximum for employee compensations
- Simplify implementation by passing inline async functions to useBatchedMutation
- Remove unnecessary useCallback wrappers as useBatchedMutation handles refs internally
- Future-proofs components for bulk payroll update operations
@jeffredodd jeffredodd force-pushed the jdj/api-batch-processing branch from 956b60c to 6172843 Compare November 12, 2025 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants