Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/intercom

src/models.ts

139 lines · 4.2 KB · TypeScript
import type {
	IntercomArticle,
	IntercomContact,
	IntercomConversation,
	IntercomMe,
	IntercomPageInfo,
	JsonRecord,
} from './types.ts'

function asRecord(value: unknown): JsonRecord {
	return value && typeof value === 'object' && !Array.isArray(value)
		? (value as JsonRecord)
		: {}
}

function asString(value: unknown): string | null {
	return typeof value === 'string' && value.length > 0 ? value : null
}

function asNumber(value: unknown): number | null {
	return typeof value === 'number' && Number.isFinite(value) ? value : null
}

function asBoolean(value: unknown): boolean | null {
	return typeof value === 'boolean' ? value : null
}

export function mapMe(value: unknown): IntercomMe {
	const record = asRecord(value)
	const app = asRecord(record.app)
	const id = asString(record.id)
	if (!id) throw new Error('Intercom /me did not return an admin id.')
	return {
		id,
		type: asString(record.type) ?? 'admin',
		hasInboxSeat: asBoolean(record.has_inbox_seat),
		app: {
			idCode: asString(app.id_code),
			region: asString(app.region),
		},
	}
}

export function mapContact(value: unknown): IntercomContact | null {
	const record = asRecord(value)
	const id = asString(record.id)
	if (!id) return null
	return {
		id,
		role: asString(record.role),
		email: asString(record.email),
		name: asString(record.name),
		externalId: asString(record.external_id),
		createdAt: asNumber(record.created_at),
		updatedAt: asNumber(record.updated_at),
	}
}

export function mapConversation(value: unknown): IntercomConversation | null {
	const record = asRecord(value)
	const id = asString(record.id)
	if (!id) return null
	const contacts = asRecord(record.contacts)
	const contactItems = Array.isArray(contacts.contacts) ? contacts.contacts : []
	const source = asRecord(record.source)
	return {
		id,
		title: asString(record.title) ?? asString(source.subject),
		state: asString(record.state),
		open: asBoolean(record.open),
		createdAt: asNumber(record.created_at),
		updatedAt: asNumber(record.updated_at),
		contactIds: contactItems
			.map((item) => asString(asRecord(item).id))
			.filter((item): item is string => Boolean(item)),
	}
}

export function mapArticle(value: unknown): IntercomArticle | null {
	const record = asRecord(value)
	const id = asString(record.id) ?? (record.id != null ? String(record.id) : null)
	if (!id) return null
	return {
		id,
		title: asString(record.title),
		state: asString(record.state),
		url: asString(record.url),
		authorId:
			asString(record.author_id) ??
			(record.author_id != null ? String(record.author_id) : null),
		createdAt: asNumber(record.created_at),
		updatedAt: asNumber(record.updated_at),
	}
}

export function extractPageInfo(payload: unknown): IntercomPageInfo {
	const record = asRecord(payload)
	const pages =
		record.pages && typeof record.pages === 'object' ? asRecord(record.pages) : null
	const next = pages?.next
	let startingAfter: string | null = null
	if (next && typeof next === 'object') {
		const cursor = (next as { starting_after?: unknown }).starting_after
		if (typeof cursor === 'string' && cursor.length > 0) startingAfter = cursor
	} else if (typeof next === 'string' && next.length > 0) {
		startingAfter = next
	}
	const page = typeof pages?.page === 'number' ? pages.page : null
	const perPage = typeof pages?.per_page === 'number' ? pages.per_page : null
	const totalPages = typeof pages?.total_pages === 'number' ? pages.total_pages : null
	const totalCount = typeof record.total_count === 'number' ? record.total_count : null
	return {
		hasNextPage: Boolean(startingAfter) || (totalPages !== null && page !== null && page < totalPages),
		startingAfter,
		page,
		perPage,
		totalPages,
		totalCount,
	}
}

export function extractListItems(payload: unknown): Array<unknown> {
	if (Array.isArray(payload)) return payload
	const record = asRecord(payload)
	if (Array.isArray(record.data)) return record.data
	if (Array.isArray(record.articles)) return record.articles
	if (Array.isArray(record.conversations)) return record.conversations
	if (Array.isArray(record.contacts)) return record.contacts
	return []
}

export function compactDefined<T extends JsonRecord>(value: T): T {
	const output: JsonRecord = {}
	for (const [key, item] of Object.entries(value)) {
		if (item === undefined) continue
		output[key] = item
	}
	return output as T
}