/**
* Utility to fetch parent record names for notes
* Handles different object types: people, companies, funds, and list entries
*/
import type { AttioClient } from '../attio-client.js';
interface PersonRecord {
data: {
id: { record_id: string };
values: {
name?: Array<{
full_name?: string;
first_name?: string;
last_name?: string;
}>;
};
};
}
interface CompanyRecord {
data: {
id: { record_id: string };
values: {
name?: Array<{ value: string }>;
};
};
}
interface FundRecord {
data: {
id: { record_id: string };
values: {
name?: Array<{ value: string }>;
};
};
}
interface ListEntryRecord {
data: {
id: { entry_id: string };
entry_values: {
name?: Array<{ value: string }>;
display_name?: Array<{ value: string }>;
};
};
}
/**
* Mapping of object slugs to their name attribute
*/
const LIST_NAME_ATTRIBUTES: Record<string, string> = {
investment_opportunities: 'display_name',
lp_opportunities: 'name',
};
/**
* Fetch a single parent record name based on object type
*/
async function fetchSingleRecordName(
client: AttioClient,
parentObject: string,
parentRecordId: string
): Promise<string | null> {
try {
// Handle standard objects
if (parentObject === 'people') {
const response = await client.get<PersonRecord>(
`/objects/people/records/${parentRecordId}`
);
const nameObj = response.data.values.name?.[0];
return (
nameObj?.full_name ||
[nameObj?.first_name, nameObj?.last_name].filter(Boolean).join(' ') ||
null
);
}
if (parentObject === 'companies') {
const response = await client.get<CompanyRecord>(
`/objects/companies/records/${parentRecordId}`
);
return response.data.values.name?.[0]?.value || null;
}
if (parentObject === 'funds') {
const response = await client.get<FundRecord>(
`/objects/funds/records/${parentRecordId}`
);
return response.data.values.name?.[0]?.value || null;
}
// Handle list entries (investment_opportunities, lp_opportunities)
const nameAttr = LIST_NAME_ATTRIBUTES[parentObject];
if (nameAttr) {
const response = await client.get<ListEntryRecord>(
`/lists/${parentObject}/entries/${parentRecordId}`
);
const values = response.data.entry_values as Record<
string,
Array<{ value: string }>
>;
return values[nameAttr]?.[0]?.value || null;
}
// Unknown object type - can't fetch name
return null;
} catch {
// Silently skip records that can't be fetched
return null;
}
}
/**
* Fetch parent record names for multiple notes in parallel
*
* @param client - AttioClient instance
* @param notes - Array of notes with parent_object and parent_record_id
* @returns Map of "parent_object:parent_record_id" -> name
*/
export async function fetchParentRecordNames(
client: AttioClient,
notes: Array<{ parent_object: string; parent_record_id: string }>
): Promise<Map<string, string>> {
const result = new Map<string, string>();
// Create unique keys to avoid duplicate fetches
const uniqueKeys = new Set(
notes.map((n) => `${n.parent_object}:${n.parent_record_id}`)
);
// Fetch all names in parallel
const fetchPromises = Array.from(uniqueKeys).map(async (key) => {
const parts = key.split(':');
const parentObject = parts[0];
const parentRecordId = parts[1];
if (!parentObject || !parentRecordId) return;
const name = await fetchSingleRecordName(
client,
parentObject,
parentRecordId
);
if (name) {
result.set(key, name);
}
});
await Promise.all(fetchPromises);
return result;
}