Skip to content

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

Package listing

@kody/intercom

src/types.ts

181 lines · 4.6 KB · TypeScript
export type JsonRecord = Record<string, unknown>

export type IntercomAuthMode = 'oauth' | 'accessToken'

export type IntercomRegion = 'us' | 'eu' | 'au'

export type IntercomAuthInput = {
	/** Saved OAuth integration name. Defaults to `intercom`. */
	integrationName?: string
	/** Alias of `integrationName`. */
	integration?: string
	/**
	 * Short account alias. `work` → `intercom-work`. Values that already start
	 * with `intercom-` are used as-is. `default` / `intercom` / omitted → `intercom`.
	 */
	account?: string
	/** User secret name for a workspace access token. Defaults to `intercomAccessToken`. */
	secretName?: string
	/** Force OAuth or access-token auth when both exist. */
	auth?: IntercomAuthMode
	/** Regional Intercom host. Defaults to `us`. Ignored when `apiBaseUrl` is set. */
	region?: IntercomRegion
	/** Absolute Intercom API origin, for example `https://api.eu.intercom.io`. */
	apiBaseUrl?: string
	/** Override the `Intercom-Version` header. Defaults to `2.14`. */
	apiVersion?: string
}

export type MutationInput = {
	/** Preview the REST call without contacting Intercom. */
	dryRun?: boolean
	/** Required for live writes after explicit user approval. */
	confirm?: boolean
}

export type DryRunResult<T extends JsonRecord = JsonRecord> = {
	dryRun: true
	method: string
	path: string
	url: string
	body?: T
}

export type IntercomPageInfo = {
	hasNextPage: boolean
	startingAfter: string | null
	page: number | null
	perPage: number | null
	totalPages: number | null
	totalCount: number | null
}

export type IntercomSearchQuery = {
	field?: string
	operator?: string
	value?: unknown
}

export type IntercomMe = {
	id: string
	type: string
	hasInboxSeat: boolean | null
	app: {
		idCode: string | null
		region: string | null
	}
}

export type IntercomContact = {
	id: string
	role: string | null
	email: string | null
	name: string | null
	externalId: string | null
	createdAt: number | null
	updatedAt: number | null
}

export type IntercomConversation = {
	id: string
	title: string | null
	state: string | null
	open: boolean | null
	createdAt: number | null
	updatedAt: number | null
	contactIds: Array<string>
}

export type IntercomArticle = {
	id: string
	title: string | null
	state: string | null
	url: string | null
	authorId: string | null
	createdAt: number | null
	updatedAt: number | null
}

export function requireRecord(value: unknown, label: string): JsonRecord {
	if (!value || typeof value !== 'object' || Array.isArray(value)) {
		throw new Error(`${label} must be an object.`)
	}
	return value as JsonRecord
}

export function requireString(
	value: unknown,
	label: string,
	options: { allowEmpty?: boolean } = {},
): string {
	if (typeof value !== 'string' || (!options.allowEmpty && value.trim().length === 0)) {
		throw new Error(
			`${label} must be ${options.allowEmpty ? 'a string' : 'a non-empty string'}.`,
		)
	}
	return options.allowEmpty ? value : value.trim()
}

export function optionalString(value: unknown, label: string): string | undefined {
	if (value === undefined || value === null) return undefined
	return requireString(value, label)
}

export function optionalBoolean(value: unknown, label: string): boolean | undefined {
	if (value === undefined) return undefined
	if (typeof value !== 'boolean') throw new Error(`${label} must be a boolean.`)
	return value
}

export function clampInt(
	value: unknown,
	min: number,
	max: number,
	fallback: number,
	label = 'perPage',
): number {
	if (value === undefined || value === null) return fallback
	if (typeof value !== 'number' || !Number.isInteger(value)) {
		throw new Error(`${label} must be an integer.`)
	}
	if (value < min || value > max) {
		throw new Error(`${label} must be between ${min} and ${max}.`)
	}
	return value
}

export function compactRecord<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
}

export function parseAction<T extends string>(
	value: unknown,
	allowed: Array<T>,
	fallback: T,
	label: string,
): T {
	if (value === undefined || value === null || value === '') return fallback
	const action = requireString(value, 'action')
	if (!allowed.includes(action as T)) {
		throw new Error(`${label} action must be one of: ${allowed.join(', ')}.`)
	}
	return action as T
}

/**
 * Shared Intercom types for this package.
 * @example
 * import types from 'kody:@kody/intercom/types'
 * const catalog = await types()
 */
export default async function typesOverview() {
	return {
		auth: ['IntercomAuthInput', 'IntercomAuthMode', 'IntercomRegion'],
		models: ['IntercomContact', 'IntercomConversation', 'IntercomArticle', 'IntercomMe'],
	}
}