koala73/worldmonitor · error · Error
batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} row
Error message
batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} rows What it means
Thrown by normalizeCompanyImportBatch() in shared/company-monitoring-contract.ts when the import batch array has more rows than COMPANY_MONITORING_LIMITS.maxImportRows (100). This is the first batch-level guard: the company-monitoring import API is designed around small, atomic batches of at most 100 rows per request. Note the same limit is also enforced per-row on the ordinal field (line 290), so ordinals 0..99 are the valid range.
Source
Thrown at shared/company-monitoring-contract.ts:311
const normalized: NormalizedCompanyImportRow = {
contractVersion: COMPANY_MONITORING_IMPORT_VERSION,
clientImportId,
ordinal: input.ordinal,
...normalizeMonitoredCompanyInput(input),
};
if (utf8Bytes(JSON.stringify(normalized)) > COMPANY_MONITORING_LIMITS.maxImportRowBytes) {
throw new Error(`row exceeds ${COMPANY_MONITORING_LIMITS.maxImportRowBytes} bytes`);
}
return normalized;
}
export function normalizeCompanyImportBatch(inputs: CompanyImportRowInput[]): NormalizedCompanyImportRow[] {
if (!Array.isArray(inputs)) throw new Error('import batch must be a list');
if (inputs.length === 0) throw new Error('import batch requires at least one row');
if (inputs.length > COMPANY_MONITORING_LIMITS.maxImportRows) {
throw new Error(`batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} rows`);
}
const normalized = inputs.map(normalizeCompanyImportRow).sort((left, right) => left.ordinal - right.ordinal);
const importId = normalized[0]?.clientImportId;
for (let index = 0; index < normalized.length; index += 1) {
const row = normalized[index]!;
if (row.clientImportId !== importId) throw new Error('batch rows must share one clientImportId');
if (row.ordinal !== index) throw new Error('batch ordinals must be contiguous from 0');
}
if (utf8Bytes(JSON.stringify(normalized)) > COMPANY_MONITORING_LIMITS.maxImportBatchBytes) {
throw new Error(`batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchBytes} bytes`);
}
return normalized;
}
export function assertCompanyMonitoringPayloadSize(byteLength: number): void {
if (!Number.isSafeInteger(byteLength) || byteLength < 0) throw new Error('request byte length is invalid');
if (byteLength > COMPANY_MONITORING_LIMITS.maxRequestBytes) {View on GitHub (pinned to eeab0a219f)
Solutions
- Chunk the rows client-side into batches of at most COMPANY_MONITORING_LIMITS.maxImportRows (100) and submit them as separate import calls, each with its own contiguous 0-based ordinals
- Pre-validate before sending: if (rows.length > COMPANY_MONITORING_LIMITS.maxImportRows) split or reject with a UI message
- If bigger batches are a real product requirement, raise maxImportRows in shared/company-monitoring-contract.ts and ship client and server together, remembering ordinals must still be < the new limit
Example fix
// before
const normalized = normalizeCompanyImportBatch(allRows); // throws if allRows.length > 100
// after
const CHUNK = COMPANY_MONITORING_LIMITS.maxImportRows;
for (let i = 0; i < allRows.length; i += CHUNK) {
const slice = allRows.slice(i, i + CHUNK).map((row, j) => ({ ...row, ordinal: j }));
await submitImportBatch(slice); // each call: <=100 rows, ordinals 0..slice.length-1
} Defensive patterns
Strategy: validation
Validate before calling
const LIMITS = COMPANY_MONITORING_LIMITS; // maxImportRows = 100
function splitImportBatch(rows: CompanyImportRowInput[]): CompanyImportRowInput[][] {
const chunks: CompanyImportRowInput[][] = [];
for (let i = 0; i < rows.length; i += LIMITS.maxImportRows) {
chunks.push(rows.slice(i, i + LIMITS.maxImportRows));
}
return chunks;
}
// before every submit:
if (rows.length > LIMITS.maxImportRows) throw new RangeError(`split first: ${rows.length} > ${LIMITS.maxImportRows}`); Type guard
function isImportBatchSizeOk(rows: CompanyImportRowInput[]): boolean {
return Array.isArray(rows) && rows.length > 0 && rows.length <= COMPANY_MONITORING_LIMITS.maxImportRows;
} Try / catch
catch (e) { if (e instanceof Error && e.message.endsWith(' rows')) showBatchSplitPrompt(); else throw e; } Prevention
- Build chunking into the import UI so users never construct >100-row requests
- Import COMPANY_MONITORING_LIMITS from the shared contract instead of hard-coding 100
- Add a unit test at the boundary: exactly 100 rows passes, 101 throws
When it happens
Trigger: Calling normalizeCompanyImportBatch() (or the import RPC that wraps it) with a CompanyImportRowInput[] of 101 or more rows. Typical producers: a CSV/spreadsheet import where the user selected 150 companies and the client sends them in one call, or test fixtures that generate maxImportRows + 1 rows to probe the boundary.
Common situations: Bulk CSV import UIs without client-side chunking; merging several prepared batches into a single request before sending; a test fixture copy-pasted and doubled; raising maxImportRows in the shared contract without updating the client that still chunks at the old value (or vice versa).
Related errors
- batch rows must share one clientImportId
- batch ordinals must be contiguous from 0
- batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchByte
- invalid ${kind} logical ID
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/823241416c190460.
Report an issue: GitHub.