npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@luanxdd/meowfly

v0.6.0

Published

Cliente em **TypeScript/Node.js** para a API **Meowfly 0.6**.

Readme

🐱 @luanxdd/meowfly

Cliente em TypeScript/Node.js para a API Meowfly 0.6.

Use o SDK para pesquisar faixas, resolver links do Spotify, criar e acompanhar downloads e obter o artefato final — por caminho local, quando sua aplicação roda no mesmo servidor, ou por URL assinada para clientes remotos.

Node.js 20+ · ESM · fetch nativo · sem dependências de runtime

Instalação

npm install @luanxdd/meowfly

Início rápido

import { Meowfly } from "@luanxdd/meowfly";

const meowfly = new Meowfly({
  baseUrl: process.env.MEOWFLY_BASE_URL!,
  token: process.env.MEOWFLY_TOKEN!,
});

const result = await meowfly.search.tracks(
  "Savage Funk Anitta",
  5,
);

const [track] = result.items;

if (!track) {
  throw new Error("Nenhuma faixa encontrada");
}

const created = await meowfly.downloads.create(
  track.id,
  { quality: "best" },
);

const job =
  created.status === "completed"
    ? created
    : await meowfly.downloads.wait(created.id);

console.log(job.artifact_id);

Lyrics persistidas

Depois que uma faixa teve lyrics persistidas por um download de áudio, elas podem ser lidas sem nova consulta externa:

const lyrics = await meowfly.lyrics.get(track.id);

console.log(lyrics.source); // "spotify"
console.log(lyrics.synced);
console.log(lyrics.plain);
console.log(lyrics.lines); // [{ start_ms, text }, ...]

Quando disponíveis, as mesmas lyrics também chegam em job.lyrics no Download concluído. O campo público source representa a identidade da Track e é sempre spotify.

Configuração

import { Meowfly } from "@luanxdd/meowfly";

const meowfly = new Meowfly({
  baseUrl: "https://meowfly.example.com",
  token: process.env.MEOWFLY_TOKEN!,
});

O construtor recebe:

| Opção | Tipo | Obrigatória | Descrição | | --- | --- | :---: | --- | | baseUrl | string | Sim | URL base da instância Meowfly. | | token | string | Sim | Token Bearer usado nas requisições. | | fetch | typeof globalThis.fetch | Não | Implementação customizada de fetch. |

Exemplo com fetch customizado:

const meowfly = new Meowfly({
  baseUrl: "https://meowfly.example.com",
  token: process.env.MEOWFLY_TOKEN!,
  fetch: customFetch,
});

API do SDK

search.tracks()

Pesquisa faixas usando o endpoint POST /v1/search.

const result = await meowfly.search.tracks(
  "Savage Funk Anitta",
  5,
  "auto",
);

Assinatura:

meowfly.search.tracks(
  query: string,
  limit?: number,
  strategy?: "auto" | "fresh" | "local",
): Promise<SearchResult>

O limit padrão é 10. A API aceita valores de 1 a 10.

Estratégias de busca

| Estratégia | Comportamento | | --- | --- | | auto | Usa cache em memória, depois busca local e consulta o Spotify quando necessário. | | fresh | Consulta o Spotify diretamente. | | local | Pesquisa somente no PostgreSQL local. |

A resposta também informa a origem e métricas da busca:

const result = await meowfly.search.tracks("Anitta");

console.log(result.source);        // "memory" | "local" | "spotify"
console.log(result.cache_hit);     // boolean
console.log(result.local_hits);    // number
console.log(result.external_hits); // number
console.log(result.took_ms);       // number

tracks.resolve()

Resolve uma referência do Spotify para um Track normalizado pelo Meowfly.

Aceita:

  • URL do Spotify;
  • URI spotify:track:...;
  • ID puro da faixa no Spotify.
const track = await meowfly.tracks.resolve(
  "https://open.spotify.com/track/7fvR6AqV3IEj1ZKzyV0k0f",
);

console.log(track.id);      // trk_...
console.log(track.title);
console.log(track.artists);

Assinatura:

meowfly.tracks.resolve(
  input: string,
): Promise<Track>

downloads.create()

Cria um download para um track_id retornado por search.tracks() ou tracks.resolve().

const job = await meowfly.downloads.create(
  track.id,
  {
    format: "m4a",
    quality: "best",
  },
);

Assinatura:

meowfly.downloads.create(
  trackId: string,
  options?: {
    mediaType?: "audio" | "video";
    format?: "m4a" | "mp4";
    quality?: AudioQuality | VideoQuality;
  },
): Promise<DownloadJob>

Qualidade

| Valor | Descrição | | --- | --- | | best | Perfil preferencial disponível. É o padrão. | | high | Perfil AAC/M4A de alta qualidade. | | low | Perfil AAC/M4A de menor largura de banda. | | 320k | Alias legado, normalizado pela API para high. | | 128k | Alias legado, normalizado pela API para low. |

Para código novo, prefira os nomes canônicos best, high e low.

A API pode retornar imediatamente um job completed quando existe um artefato válido em cache. Caso contrário, o download normalmente entra na fila e deve ser acompanhado.


clips.create()

Cria um download de clipe oficial/compatível para o mesmo Track que veio da busca/resolve do Spotify. A API pesquisa vídeos no Spotify e só aceita um candidato quando título, artista e duração atingem o limiar de confiança.

const clip = await meowfly.clips.create(track.id, {
  quality: "best",
});

const completed =
  clip.status === "completed"
    ? clip
    : await meowfly.clips.wait(clip.id);

console.log(completed.media_type); // "video"
console.log(completed.format);     // "mp4"
console.log(completed.artifact_id);

Qualidades disponíveis:

| Valor | Descrição | | --- | --- | | best | Melhor resolução disponível, preferindo 1080p. | | 1080p | Solicita Full HD; o backend Spotify pode escolher a melhor alternativa disponível. | | 720p | Solicita HD. | | 480p | Solicita SD 480p. | | 360p | Solicita SD 360p. |

Assinatura:

meowfly.clips.create(
  trackId: string,
  options?: { quality?: "best" | "1080p" | "720p" | "480p" | "360p" },
): Promise<DownloadJob>

clips.get() e clips.wait() usam a mesma state machine de downloads.


downloads.get()

Obtém o estado atual de um download.

const job = await meowfly.downloads.get("dwl_...");

console.log(job.status);
console.log(job.progress);

Assinatura:

meowfly.downloads.get(
  id: string,
): Promise<DownloadJob>

Os estados possíveis são:

"queued"
| "resolving"
| "downloading"
| "finalizing"
| "completed"
| "failed"
| "expired"

downloads.wait()

Aguarda um download terminar usando polling sobre downloads.get().

const job = await meowfly.downloads.wait(
  created.id,
  {
    intervalMs: 500,
    timeoutMs: 120_000,
  },
);

Assinatura:

meowfly.downloads.wait(
  id: string,
  options?: {
    intervalMs?: number;
    timeoutMs?: number;
  },
): Promise<DownloadJob>

Valores padrão:

| Opção | Padrão | | --- | ---: | | intervalMs | 250 ms | | timeoutMs | 60_000 ms |

O método retorna quando o status chega a completed.

Se o job terminar como failed ou expired, o SDK lança MeowflyError. Um timeout também gera MeowflyError, com código timeout.

Uma forma prática de evitar polling desnecessário quando a API já respondeu com cache hit é:

const job =
  created.status === "completed"
    ? created
    : await meowfly.downloads.wait(created.id);

artifacts.link()

Cria uma URL assinada e temporária para um artefato concluído.

const link = await meowfly.artifacts.link(
  job.artifact_id!,
);

console.log(link.url);
console.log(link.expires_at);

Também é possível solicitar um TTL específico, em segundos:

const link = await meowfly.artifacts.link(
  job.artifact_id!,
  300,
);

Assinatura:

meowfly.artifacts.link(
  artifactId: string,
  ttlSeconds?: number,
): Promise<ArtifactLink>

O TTL informado pode ser limitado pelo máximo configurado no servidor.

A resposta contém:

interface ArtifactLink {
  artifact_id: string;
  url: string;
  filename: string;
  content_type: string;
  size_bytes: number;
  etag: string;
  expires_at: string;
}

A URL gerada já contém a assinatura necessária para o acesso, portanto não exige o Bearer token. A rota de entrega também suporta HTTP Range.

Aplicações no mesmo VPS

Se seu bot ou aplicação roda no mesmo host do Meowfly, não é necessário baixar o arquivo novamente via HTTP.

Importe o helper específico para Node.js:

import { localArtifactPath } from "@luanxdd/meowfly/node";

const path = localArtifactPath(job);

console.log(path);
// /srv/meowfly/artifacts/art_....m4a ou art_....mp4

O diretório padrão é:

/srv/meowfly/artifacts

Você pode sobrescrevê-lo:

const path = localArtifactPath(
  job,
  "/data/artifacts",
);

O helper só aceita jobs concluídos que possuam artifact_id. O caminho final segue o formato:

<root>/<artifact_id>.<format>

Fluxo completo no mesmo servidor

import { Meowfly } from "@luanxdd/meowfly";
import { localArtifactPath } from "@luanxdd/meowfly/node";

const meowfly = new Meowfly({
  baseUrl: process.env.MEOWFLY_BASE_URL!,
  token: process.env.MEOWFLY_TOKEN!,
});

const track = await meowfly.tracks.resolve(
  "spotify:track:7fvR6AqV3IEj1ZKzyV0k0f",
);

const created = await meowfly.downloads.create(
  track.id,
  { quality: "best" },
);

const job =
  created.status === "completed"
    ? created
    : await meowfly.downloads.wait(created.id);

const path = localArtifactPath(job);

Aplicações remotas

Para clientes que não compartilham o filesystem com o Meowfly, use uma URL assinada:

const created = await meowfly.downloads.create(
  track.id,
  { quality: "best" },
);

const job =
  created.status === "completed"
    ? created
    : await meowfly.downloads.wait(created.id);

if (!job.artifact_id) {
  throw new Error("Download concluído sem artifact_id");
}

const artifact = await meowfly.artifacts.link(
  job.artifact_id,
);

console.log(artifact.url);

Tratamento de erros

Erros HTTP retornados pela API são convertidos em MeowflyError.

import {
  Meowfly,
  MeowflyError,
} from "@luanxdd/meowfly";

try {
  await meowfly.downloads.get("dwl_inexistente");
} catch (error) {
  if (error instanceof MeowflyError) {
    console.error({
      message: error.message,
      status: error.status,
      code: error.code,
      requestId: error.requestId,
    });
  }
}

Propriedades disponíveis:

| Propriedade | Tipo | Descrição | | --- | --- | --- | | message | string | Mensagem do erro. | | status | number | Status HTTP associado. | | code | string \| undefined | Código de erro retornado pela API. | | requestId | string \| undefined | ID de correlação da requisição, quando disponível. |

Tipos exportados

O pacote exporta os tipos principais diretamente pela entrada principal:

import type {
  ArtifactLink,
  DownloadJob,
  DownloadStatus,
  MediaSnapshot,
  SearchResult,
  SearchStrategy,
  Track,
} from "@luanxdd/meowfly";

Também estão disponíveis as interfaces de configuração do cliente:

import type {
  DownloadOptions,
  DownloadWaitOptions,
  MeowflyOptions,
} from "@luanxdd/meowfly";

Escopos do token

A API Meowfly usa tokens com escopos.

| Operação do SDK | Escopo necessário | | --- | --- | | search.tracks() | search | | tracks.resolve() | search | | downloads.create() | downloads:write | | downloads.get() | downloads:read | | downloads.wait() | downloads:read | | artifacts.link() | artifacts:read |

Um fluxo completo de pesquisa, criação, espera e entrega remota normalmente precisa de:

search
downloads:write
downloads:read
artifacts:read

Referência rápida

| Método | Retorno | | --- | --- | | search.tracks(q, limit?, strategy?) | Promise<SearchResult> | | tracks.resolve(input) | Promise<Track> | | downloads.create(trackId, options?) | Promise<DownloadJob> | | downloads.get(id) | Promise<DownloadJob> | | downloads.wait(id, options?) | Promise<DownloadJob> | | artifacts.link(id, ttlSeconds?) | Promise<ArtifactLink> | | localArtifactPath(job, root?) | string |

Limites atuais do SDK

O SDK TypeScript cobre o fluxo principal de busca, resolução, download e entrega de artefatos.

A API HTTP do Meowfly também possui endpoints que ainda não têm wrappers dedicados neste pacote, incluindo:

  • stream de eventos de download via SSE;
  • health checks;
  • bootstrap e gerenciamento de tokens;
  • consulta de uso diário.

Quando necessário, esses endpoints podem ser consumidos diretamente pela API HTTP.