import type { HubSpotAccountInfo, HubSpotCrmRecord, HubSpotFilterGroup, HubSpotSearchFilter, JsonRecord, } from './types.ts' import { requireRecord, requireString } from './types.ts' import type { HubSpotOperation } from './setup.ts' export const STANDARD_OBJECT_TYPES = ['contacts', 'companies', 'deals', 'tickets'] as const export type StandardObjectType = (typeof STANDARD_OBJECT_TYPES)[number] export type ObjectSpec = { objectType: string kind: 'standard' | 'custom' readOperation: HubSpotOperation writeOperation: HubSpotOperation defaultProperties: Array } export const DEFAULT_PROPERTIES: Record> = { contacts: ['email', 'firstname', 'lastname', 'phone', 'company', 'lifecyclestage', 'hs_object_id'], companies: ['name', 'domain', 'industry', 'city', 'phone', 'lifecyclestage', 'hs_object_id'], deals: ['dealname', 'amount', 'dealstage', 'pipeline', 'closedate', 'hs_object_id'], tickets: ['subject', 'content', 'hs_pipeline', 'hs_pipeline_stage', 'hs_ticket_priority', 'hs_object_id'], } export function isStandardObjectType(value: string): value is StandardObjectType { return (STANDARD_OBJECT_TYPES as readonly string[]).includes(value) } export function resolveObjectSpec(objectType: string): ObjectSpec { const slug = requireString(objectType, 'objectType') if (isStandardObjectType(slug)) { switch (slug) { case 'contacts': return { objectType: slug, kind: 'standard', readOperation: 'contacts.read', writeOperation: 'contacts.write', defaultProperties: DEFAULT_PROPERTIES.contacts, } case 'companies': return { objectType: slug, kind: 'standard', readOperation: 'companies.read', writeOperation: 'companies.write', defaultProperties: DEFAULT_PROPERTIES.companies, } case 'deals': return { objectType: slug, kind: 'standard', readOperation: 'deals.read', writeOperation: 'deals.write', defaultProperties: DEFAULT_PROPERTIES.deals, } case 'tickets': return { objectType: slug, kind: 'standard', readOperation: 'tickets.read', writeOperation: 'tickets.write', defaultProperties: DEFAULT_PROPERTIES.tickets, } default: { const exhaustive: never = slug throw new Error(`Unsupported standard HubSpot object: ${String(exhaustive)}`) } } } return { objectType: slug, kind: 'custom', readOperation: 'custom.read', writeOperation: 'unknownMutation', defaultProperties: ['hs_object_id'], } } export function objectPath(objectType: string, id?: string): string { const spec = resolveObjectSpec(objectType) return id ? `/crm/v3/objects/${spec.objectType}/${id}` : `/crm/v3/objects/${spec.objectType}` } export function mapProperties(value: unknown): Record { if (!value || typeof value !== 'object' || Array.isArray(value)) return {} const output: Record = {} for (const [key, item] of Object.entries(value as JsonRecord)) { if (item === null) { output[key] = null continue } if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { output[key] = String(item) } } return output } export function mapCrmRecord(value: unknown, objectType: string): HubSpotCrmRecord { const record = (value && typeof value === 'object' ? value : {}) as JsonRecord return { id: String(record.id || ''), objectType, properties: mapProperties(record.properties), createdAt: typeof record.createdAt === 'string' ? record.createdAt : null, updatedAt: typeof record.updatedAt === 'string' ? record.updatedAt : null, archived: typeof record.archived === 'boolean' ? record.archived : null, } } export function mapAccount(value: unknown): HubSpotAccountInfo { const record = (value && typeof value === 'object' ? value : {}) as JsonRecord const portalId = record.portalId ?? record.portal_id return { hasPortalId: portalId !== undefined && portalId !== null && String(portalId).length > 0, accountType: typeof record.accountType === 'string' ? record.accountType : null, timeZone: typeof record.timeZone === 'string' ? record.timeZone : null, uiDomain: typeof record.uiDomain === 'string' ? record.uiDomain : null, } } export function parseFilter(value: unknown, label: string): HubSpotSearchFilter { const record = requireRecord(value, label) const filter: HubSpotSearchFilter = { propertyName: requireString(record.propertyName, `${label}.propertyName`), operator: requireString(record.operator, `${label}.operator`), } if (record.value !== undefined && record.value !== null) { filter.value = String(record.value) } if (record.highValue !== undefined && record.highValue !== null) { filter.highValue = String(record.highValue) } if (record.values !== undefined) { if (!Array.isArray(record.values)) { throw new Error(`${label}.values must be an array.`) } filter.values = record.values.map((item, index) => String(item ?? `${label}.values[${index}]`)) } return filter } export function parseFilterGroups(value: unknown): Array | undefined { if (value === undefined || value === null) return undefined if (!Array.isArray(value)) throw new Error('filterGroups must be an array.') return value.map((group, groupIndex) => { const record = requireRecord(group, `filterGroups[${groupIndex}]`) if (!Array.isArray(record.filters)) { throw new Error(`filterGroups[${groupIndex}].filters must be an array.`) } return { filters: record.filters.map((filter, filterIndex) => parseFilter(filter, `filterGroups[${groupIndex}].filters[${filterIndex}]`), ), } }) } export function asRecordArray(value: unknown): Array { if (value === undefined || value === null) return [] return Array.isArray(value) ? value : [value] }