Skip to content

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

Package listing

@kody/trello

src/lists.ts

198 lines · 5.9 KB · TypeScript
import { isDryRunResult, trelloRequest } from './client.ts'
import { parseAuthInput } from './auth.ts'
import { LIST_FIELDS, listSummary, mapMany, requireMapped } from './models.ts'
import type {
	DryRunResult,
	MutationInput,
	TrelloAuthInput,
	TrelloListSummary,
} from './types.ts'
import {
	clampInt,
	compactRecord,
	optionalBoolean,
	optionalString,
	requireRecord,
	requireString,
} from './types.ts'

export type ListListsInput = TrelloAuthInput & {
	boardId: string
	filter?: 'open' | 'closed' | 'all'
	limit?: number
}

export type GetListInput = TrelloAuthInput & {
	listId: string
}

export type CreateListInput = TrelloAuthInput &
	MutationInput & {
		boardId: string
		name: string
		pos?: string | number
	}

export type UpdateListInput = TrelloAuthInput &
	MutationInput & {
		listId: string
		name?: string
		closed?: boolean
		pos?: string | number
		boardId?: string
	}

function listFilter(value: unknown): 'open' | 'closed' | 'all' {
	if (value === undefined || value === null || value === '') return 'open'
	const filter = requireString(value, 'filter')
	if (filter !== 'open' && filter !== 'closed' && filter !== 'all') {
		throw new Error('filter must be "open", "closed", or "all".')
	}
	return filter
}

function optionalPos(value: unknown, label: string): string | number | undefined {
	if (value === undefined || value === null) return undefined
	if (typeof value === 'number' && Number.isFinite(value)) return value
	return requireString(value, label)
}

/**
 * List lists on a caller-supplied board id.
 * @example
 * import { listLists } from 'kody:@kody/trello/lists'
 * const lists = await listLists({ boardId })
 */
export async function listLists(input: ListListsInput): Promise<Array<TrelloListSummary>> {
	const boardId = requireString(input.boardId, 'boardId')
	const limit = clampInt(input.limit, 1, 200, 50)
	const result = await trelloRequest<unknown>({
		...input,
		method: 'GET',
		path: `/boards/${boardId}/lists`,
		query: compactRecord({
			filter: listFilter(input.filter),
			fields: LIST_FIELDS,
		}),
	})
	if (isDryRunResult(result)) return []
	return mapMany(result.data, listSummary).slice(0, limit)
}

/**
 * Get one Trello list by id supplied by the caller.
 * @example
 * import { getList } from 'kody:@kody/trello/lists'
 * const list = await getList({ listId })
 */
export async function getList(input: GetListInput): Promise<TrelloListSummary> {
	const listId = requireString(input.listId, 'listId')
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'GET',
		path: `/lists/${listId}`,
		query: { fields: LIST_FIELDS },
	})
	if (isDryRunResult(result)) {
		throw new Error('getList is read-only and does not support dryRun.')
	}
	return requireMapped(listSummary(result.data), 'getList')
}

/**
 * Create a Trello list. Requires `confirm: true`, or use `dryRun: true`.
 * @example
 * import { createList } from 'kody:@kody/trello/lists'
 * const preview = await createList({ boardId, name: 'In review', dryRun: true })
 */
export async function createList(input: CreateListInput): Promise<TrelloListSummary | DryRunResult> {
	const body = compactRecord({
		idBoard: requireString(input.boardId, 'boardId'),
		name: requireString(input.name, 'name'),
		pos: optionalPos(input.pos, 'pos'),
	})
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'POST',
		path: '/lists',
		query: body,
		body,
	})
	if (isDryRunResult(result)) return result
	return requireMapped(listSummary(result.data), 'createList')
}

/**
 * Update a Trello list. Requires `confirm: true`, or use `dryRun: true`.
 * @example
 * import { updateList } from 'kody:@kody/trello/lists'
 * const preview = await updateList({ listId, name: 'Done', dryRun: true })
 */
export async function updateList(input: UpdateListInput): Promise<TrelloListSummary | DryRunResult> {
	const listId = requireString(input.listId, 'listId')
	const body = compactRecord({
		name: optionalString(input.name, 'name'),
		closed: optionalBoolean(input.closed, 'closed'),
		pos: optionalPos(input.pos, 'pos'),
		idBoard: optionalString(input.boardId, 'boardId'),
	})
	if (Object.keys(body).length === 0) {
		throw new Error('updateList requires at least one of name, closed, pos, or boardId.')
	}
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'PUT',
		path: `/lists/${listId}`,
		query: body,
		body,
	})
	if (isDryRunResult(result)) return result
	return requireMapped(listSummary(result.data), 'updateList')
}

export { listSummary }

/**
 * List helpers dispatcher.
 * @example
 * import lists from 'kody:@kody/trello/lists'
 * const items = await lists({ action: 'list', boardId })
 */
export default async function listsEntrypoint(params: Record<string, unknown> = {}) {
	const input = requireRecord(params, 'lists')
	const auth = parseAuthInput(input)
	const action = optionalString(input.action, 'action') ?? 'list'
	switch (action) {
		case 'list':
			return listLists({
				...auth,
				boardId: requireString(input.boardId, 'boardId'),
				filter: listFilter(input.filter),
				limit: input.limit as number | undefined,
			})
		case 'get':
			return getList({ ...auth, listId: requireString(input.listId, 'listId') })
		case 'create':
			return createList({
				...auth,
				boardId: requireString(input.boardId, 'boardId'),
				name: requireString(input.name, 'name'),
				pos: optionalPos(input.pos, 'pos'),
				confirm: optionalBoolean(input.confirm, 'confirm'),
				dryRun: optionalBoolean(input.dryRun, 'dryRun'),
			})
		case 'update':
			return updateList({
				...auth,
				listId: requireString(input.listId, 'listId'),
				name: optionalString(input.name, 'name'),
				closed: optionalBoolean(input.closed, 'closed'),
				pos: optionalPos(input.pos, 'pos'),
				boardId: optionalString(input.boardId, 'boardId'),
				confirm: optionalBoolean(input.confirm, 'confirm'),
				dryRun: optionalBoolean(input.dryRun, 'dryRun'),
			})
		default:
			throw new Error('lists action must be one of: list, get, create, update.')
	}
}