export type JsonRecord = Record<string, unknown>
export type ZendeskAuthMode = 'oauth' | 'apiToken'
export type ZendeskAuthInput = {
/** Saved OAuth integration name. Defaults to `zendesk`. */
integrationName?: string
/** Alias of `integrationName`. */
integration?: string
/**
* Short account alias. `work` → `zendesk-work`. Values that already start
* with `zendesk-` are used as-is. `default` / `zendesk` / omitted → `zendesk`.
*/
account?: string
/** User secret for the API token. Defaults to `zendeskApiToken`. */
secretName?: string
/** User secret for `{email}/token`. Defaults to `zendeskApiUsername`. */
usernameSecretName?: string
/** Force OAuth or API-token auth when both exist. */
auth?: ZendeskAuthMode
/**
* Zendesk Support subdomain (`acme` or `acme.zendesk.com`). Required unless
* already stored in this package's `packageStorage` `config.subdomain`.
*/
subdomain?: string
/**
* Agent email for the API-token lane. Stored in packageStorage when you
* call `./configure`. Not a default for another account's subdomain.
*/
email?: string
}
export type MutationInput = {
/** Preview the REST call without contacting Zendesk. */
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 ZendeskPageInfo = {
hasNextPage: boolean
nextPage: string | null
previousPage: string | null
count: number | null
}
export type ZendeskMe = {
id: number
name: string | null
role: string | null
active: boolean | null
}
export type ZendeskTicket = {
id: number
subject: string | null
status: string | null
priority: string | null
type: string | null
requesterId: number | null
assigneeId: number | null
createdAt: string | null
updatedAt: string | null
}
export type ZendeskTicketComment = {
id: number
authorId: number | null
public: boolean | null
body: string | null
createdAt: string | null
}
export type ZendeskUser = {
id: number
name: string | null
email: string | null
role: string | null
active: boolean | null
createdAt: string | null
updatedAt: string | null
}
export type ZendeskArticle = {
id: number
title: string | null
locale: string | null
sectionId: number | null
draft: boolean | null
htmlUrl: string | null
createdAt: string | null
updatedAt: string | null
}
export type ZendeskStoredConfig = {
subdomain?: string
email?: string
}
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 optionalNumber(value: unknown, label: string): number | undefined {
if (value === undefined || value === null) return undefined
if (typeof value !== 'number' || !Number.isFinite(value)) {
throw new Error(`${label} must be a number.`)
}
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 Zendesk types for this package.
* @example
* import types from 'kody:@kody/zendesk/types'
* const catalog = await types()
*/
export default async function typesOverview() {
return {
auth: ['ZendeskAuthInput', 'ZendeskAuthMode'],
models: ['ZendeskTicket', 'ZendeskUser', 'ZendeskArticle', 'ZendeskMe'],
}
}