@chrono-os/clickup-client
v0.1.1
Published
Cliente REST do núcleo ClickUp (busca por custom field, criar task, tag, comentário, custom field) com backoff em 429 e timeout; zero dependências de runtime.
Maintainers
Readme
@chrono-os/clickup-client
Cliente REST do núcleo do ClickUp (API v2): busca de task por custom field, criar task, tag, comentário, custom field e descrição — com timeout, backoff em 429 e erro tipado sem vazar o token. Zero dependências de runtime (fetch nativo).
import { criarClickUp } from '@chrono-os/clickup-client'
const clickup = criarClickUp({
token: process.env.CLICKUP_API_TOKEN!,
listId: process.env.CLICKUP_LIST_ID, // default; cada método aceita listId próprio para sobrepor
logger, // opcional — warn(obj, msg) / error(obj, msg)
})
// Dedup por email, depois por telefone — a ordem/campos são do consumidor.
const existente =
(await clickup.searchTaskByCustomField(FIELD_EMAIL, email)) ??
(await clickup.searchTaskByCustomField(FIELD_WHATSAPP, telefoneE164))
if (existente) {
await clickup.addTag(existente.id, 'comprou')
await clickup.addComment(existente.id, 'Novo contato via LP X')
await clickup.updateCustomField(existente.id, FIELD_SCORE, 27)
} else {
await clickup.createTask({
name: nome,
markdownDescription: `**E-mail:** ${email}`,
tags: ['nairio-site'],
customFields: [{ id: FIELD_EMAIL, value: email }],
})
}O que fica fora do pacote (config do consumidor)
- IDs de custom field, tags e templates de comentário/descrição — cada app decide os seus.
- Normalização de telefone para E.164 — não é ClickUp, é formatação de entrada; estava duplicada (idêntica) em 3 apps do Naírio, mas fica no consumidor.
- Listagem de listas do workspace (
GET /team→/space→/folder, usada só pelo seletor admin do nairio-institucional) — específica o bastante para não entrar no núcleo. - Anexar arquivo na task (
POST /task/:id/attachment, multipart) — usada só pela calculadora-institucional-backend; fica no consumidor por enquanto (candidata a entrar se aparecer um 2º uso).
Rate limit (429)
Nenhuma das 5 cópias originais tratava 429 — todas deixavam estourar como erro HTTP qualquer. Aqui, em 429:
- Lê
X-RateLimit-Reset(epoch Unix em segundos) e espera até lá (+250ms de folga); sem header confiável, cai num backoff exponencial com jitter. - Nunca espera mais que
maxEsperaMs(default 30000ms) por tentativa. - Tenta de novo até
tentativasvezes no total (default 3); esgotado, propaga o429comoClickUpError.
Erros
Todo erro é uma ClickUpError (op, status?, body?) — body é o texto que o ClickUp devolveu, nunca o request que fizemos, então o token não aparece nele. O token só existe no header Authorization; não é logado, não vai para a URL nem para o corpo, e nenhuma mensagem de erro do pacote o inclui (coberto em teste, inclusive para falha de rede).
listId: fixo ou por chamada
criarClickUp({ listId }) define o default. Métodos que operam por lista (searchTaskByCustomField, createTask) aceitam um listId próprio que sobrepõe o default — para o caso do fan-out por-LP (mesma organização, listas diferentes por página). Se nenhum dos dois vier, lança ClickUpError antes de chamar a rede.
Paginação
searchTaskByCustomField lê só a primeira página (GET /list/:id/task). Nenhuma das 5 cópias originais paginava — mantido assim de propósito, não é uma lacuna nova deste pacote.
