import { parseAuth } from './auth.ts'
import { intercomRequest } from './client.ts'
import { compactDefined, extractListItems, mapContact } from './models.ts'
import type {
DryRunResult,
IntercomAuthInput,
IntercomContact,
IntercomPageInfo,
IntercomSearchQuery,
JsonRecord,
MutationInput,
} from './types.ts'
import {
clampInt,
optionalBoolean,
optionalString,
requireRecord,
requireString,
} from './types.ts'
export type ListContactsInput = IntercomAuthInput & {
perPage?: number
startingAfter?: string
}
export type GetContactInput = IntercomAuthInput & {
id: string
}
export type SearchContactsInput = IntercomAuthInput & {
query?: IntercomSearchQuery | JsonRecord
email?: string
perPage?: number
startingAfter?: string
}
export type CreateContactInput = IntercomAuthInput &
MutationInput & {
email?: string
externalId?: string
role?: string
name?: string
phone?: string
signedUpAt?: number
customAttributes?: JsonRecord
body?: JsonRecord
}
export type UpdateContactInput = IntercomAuthInput &
MutationInput & {
id: string
email?: string
externalId?: string
name?: string
phone?: string
customAttributes?: JsonRecord
body?: JsonRecord
}
export type ContactListResult = {
items: Array<IntercomContact>
pageInfo: IntercomPageInfo
}
function searchBody(input: SearchContactsInput): JsonRecord {
if (input.query) return { query: input.query }
if (input.email) {
return {
query: {
field: 'email',
operator: '=',
value: input.email,
},
}
}
throw new Error('searchContacts requires query or email.')
}
function contactWriteBody(input: CreateContactInput | UpdateContactInput): JsonRecord {
if (input.body) return input.body
return compactDefined({
email: input.email,
external_id: 'externalId' in input ? input.externalId : undefined,
role: 'role' in input ? input.role : undefined,
name: input.name,
phone: input.phone,
signed_up_at: 'signedUpAt' in input ? input.signedUpAt : undefined,
custom_attributes: input.customAttributes,
})
}
/**
* List Intercom contacts.
* @example
* import { listContacts } from 'kody:@kody/intercom/contacts'
* const { items } = await listContacts({ perPage: 10 })
*/
export async function listContacts(input: ListContactsInput = {}): Promise<ContactListResult> {
const result = await intercomRequest<unknown>({
...input,
operation: 'contacts.read',
path: '/contacts',
query: compactDefined({
per_page: clampInt(input.perPage, 1, 150, 20),
starting_after: input.startingAfter,
}),
})
if ('dryRun' in result) {
throw new Error('listContacts is read-only.')
}
return {
items: extractListItems(result.data)
.map(mapContact)
.filter((item): item is IntercomContact => Boolean(item)),
pageInfo: result.pageInfo,
}
}
/**
* Get one Intercom contact by id.
* @example
* import { getContact } from 'kody:@kody/intercom/contacts'
* const contact = await getContact({ id })
*/
export async function getContact(input: GetContactInput): Promise<IntercomContact> {
const id = requireString(input.id, 'id')
const result = await intercomRequest<unknown>({
...input,
operation: 'contacts.read',
path: `/contacts/${encodeURIComponent(id)}`,
})
if ('dryRun' in result) {
throw new Error('getContact is read-only.')
}
const mapped = mapContact(result.data)
if (!mapped) throw new Error('Intercom did not return a contact id.')
return mapped
}
/**
* Search Intercom contacts. POST `/contacts/search` is a read.
* @example
* import { searchContacts } from 'kody:@kody/intercom/contacts'
* const { items } = await searchContacts({ email: 'pat@example.com' })
*/
export async function searchContacts(input: SearchContactsInput): Promise<ContactListResult> {
const result = await intercomRequest<unknown>({
...input,
operation: 'contacts.read',
method: 'POST',
path: '/contacts/search',
body: compactDefined({
...searchBody(input),
pagination: compactDefined({
per_page: input.perPage === undefined ? undefined : clampInt(input.perPage, 1, 150, 20),
starting_after: input.startingAfter,
}),
}),
})
if ('dryRun' in result) {
throw new Error('searchContacts is read-only.')
}
return {
items: extractListItems(result.data)
.map(mapContact)
.filter((item): item is IntercomContact => Boolean(item)),
pageInfo: result.pageInfo,
}
}
/**
* Create an Intercom contact. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import { createContact } from 'kody:@kody/intercom/contacts'
* const preview = await createContact({ email: 'pat@example.com', dryRun: true })
*/
export async function createContact(
input: CreateContactInput,
): Promise<IntercomContact | DryRunResult> {
const body = contactWriteBody(input)
if (!body.email && !body.external_id && !input.body) {
throw new Error('createContact requires email, externalId, or body.')
}
const result = await intercomRequest<unknown>({
...input,
operation: 'contacts.write',
method: 'POST',
path: '/contacts',
body,
})
if ('dryRun' in result) return result
const mapped = mapContact(result.data)
if (!mapped) throw new Error('Intercom did not return a created contact id.')
return mapped
}
/**
* Update an Intercom contact. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import { updateContact } from 'kody:@kody/intercom/contacts'
* const preview = await updateContact({ id, name: 'Pat', dryRun: true })
*/
export async function updateContact(
input: UpdateContactInput,
): Promise<IntercomContact | DryRunResult> {
const id = requireString(input.id, 'id')
const result = await intercomRequest<unknown>({
...input,
operation: 'contacts.write',
method: 'PUT',
path: `/contacts/${encodeURIComponent(id)}`,
body: contactWriteBody(input),
})
if ('dryRun' in result) return result
const mapped = mapContact(result.data)
if (!mapped) throw new Error('Intercom did not return an updated contact id.')
return mapped
}
export function parseListContacts(params: Record<string, unknown>): ListContactsInput {
const input = requireRecord(params, 'list-contacts')
return {
...parseAuth(input),
perPage: input.perPage as number | undefined,
startingAfter: optionalString(input.startingAfter, 'startingAfter'),
}
}
export function parseGetContact(params: Record<string, unknown>): GetContactInput {
const input = requireRecord(params, 'get-contact')
return { ...parseAuth(input), id: requireString(input.id, 'id') }
}
export function parseSearchContacts(params: Record<string, unknown>): SearchContactsInput {
const input = requireRecord(params, 'search-contacts')
return {
...parseAuth(input),
query: input.query as SearchContactsInput['query'],
email: optionalString(input.email, 'email'),
perPage: input.perPage as number | undefined,
startingAfter: optionalString(input.startingAfter, 'startingAfter'),
}
}
export function parseCreateContact(params: Record<string, unknown>): CreateContactInput {
const input = requireRecord(params, 'create-contact')
return {
...parseAuth(input),
email: optionalString(input.email, 'email'),
externalId: optionalString(input.externalId, 'externalId'),
role: optionalString(input.role, 'role'),
name: optionalString(input.name, 'name'),
phone: optionalString(input.phone, 'phone'),
signedUpAt: input.signedUpAt as number | undefined,
customAttributes: input.customAttributes as JsonRecord | undefined,
body: input.body as JsonRecord | undefined,
confirm: optionalBoolean(input.confirm, 'confirm'),
dryRun: optionalBoolean(input.dryRun, 'dryRun'),
}
}
export function parseUpdateContact(params: Record<string, unknown>): UpdateContactInput {
const input = requireRecord(params, 'update-contact')
return {
...parseAuth(input),
id: requireString(input.id, 'id'),
email: optionalString(input.email, 'email'),
externalId: optionalString(input.externalId, 'externalId'),
name: optionalString(input.name, 'name'),
phone: optionalString(input.phone, 'phone'),
customAttributes: input.customAttributes as JsonRecord | undefined,
body: input.body as JsonRecord | undefined,
confirm: optionalBoolean(input.confirm, 'confirm'),
dryRun: optionalBoolean(input.dryRun, 'dryRun'),
}
}
/**
* Intercom contact helpers. Writes require `confirm: true` or `dryRun: true`.
* @example
* import { listContacts } from 'kody:@kody/intercom/contacts'
* const { items } = await listContacts({ perPage: 10 })
*/
export default async function contactsEntrypoint(params: Record<string, unknown> = {}) {
return listContacts(parseListContacts(params))
}