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

@arcaelas/whatsapp

v1.2.3

Published

A small box of tools, which are implemented in different factions of the library.

Readme

Arcaelas Insiders Arcaelas Insiders

@arcaelas/whatsapp

A multi‑device, storage‑agnostic WhatsApp client for Node.js.

Typed end‑to‑end · Sends any media · Zero‑boilerplate API · Written in TypeScript only


Contents


Install

# core package
yarn add @arcaelas/whatsapp

Node 18+ required. Works in ESM & TypeScript projects out of the box.


Quick Start

import WhatsApp from '@arcaelas/whatsapp';
import qrcode from 'qrcode-terminal';

const socket = new WhatsApp({
    phone: '+01000000000',
    loginType: 'qr',
    qr: (buffer) => qrcode.generate(buffer.toString('base64'), { small: true }),
});

await socket.ready(); // blocks until authenticated

const [chat] = await socket.chats();
await chat.send('Hello from Arcaelas 🤖');

Zero‑to‑Hero Guide

1. Create a Store

The library is storage‑agnostic. Implement the minimal Store contract once and reuse everywhere.

/** Minimal in‑memory store for demos */
const MemoryStore: Store = {
    map: new Map<string, string>(),

    has(key) {
        return this.map.has(key);
    },
    get(key) {
        return JSON.parse(this.map.get(key) ?? 'null');
    },
    set(key, value) {
        if (value == null) return this.delete(key);
        this.map.set(key, JSON.stringify(value));
        return true;
    },
    delete(key) {
        return this.map.delete(key);
    },
    async *keys() {
        for (const k of this.map.keys()) yield k;
    },
    async *values() {
        for (const v of this.map.values()) yield JSON.parse(v);
    },
    async *entries() {
        for (const [k, v] of this.map.entries()) yield [k, JSON.parse(v)];
    },
    clear() {
        this.map.clear();
        return true;
    },
};

Storage Layer

The client is completely storage-agnostic. You may use Redis, filesystem or in-memory persistence. The storage system must implement the Store interface.

Key structure (logical)

account:{phone}:index                           → Account
account:{phone}:chat:{id}:index                 → Chat
account:{phone}:chat:{id}:message:{id}:index    → Message

Directory-style translation

account/
└── {phone}/
    ├── index
    └── chat/
        └── {id}/
            ├── index
            └── message/
                └── {id}/
                    └── index

✅ Esto permite separar metadatos de contenido, aplicar TTLs, y reducir lecturas innecesarias.

2. Initialise the client

const socket = new WhatsApp({
    phone: '+584100000000',
    loginType: 'code',
    code: (pairCode) => console.log('Pair with:', pairCode),
    store: MemoryStore,
});

3. Read chats & messages

const chats = await socket.chats();
for (const chat of chats) {
    console.log(`📨 ${chat.id} has ${await chat.messages().then((m) => m.length)} messages`);
}

4. Send your first message

const [target] = chats;
await target.send('¡Hola Mundo!', { once: true });

Interfaces & Types

IWhatsApp

interface IWhatsApp<T extends 'qr' | 'code'> {
    phone: string;
    store?: Store;
    loginType: T;
    code: T extends 'code' ? (code: string) => void : never;
    qr: T extends 'qr' ? (buffer: Buffer) => void : never;
}

| Field | Required | Description | | ----------- | ------------- | ---------------------------------------------------------------------------------- | | phone | ✔ | International format (+5841…). | | store | ✖ | Backend persistence (defaults to in‑memory volatile store). | | loginType | ✔ | 'qr' or 'code'. Determines which callback is required. | | code | Conditional | Fired once with the pairing numeric code when loginType === 'code'. | | qr | Conditional | Fired with a Buffer JPG/PNG containing the QR image when loginType === 'qr'. |

Store

Contract used everywhere the SDK needs persistence: creds, chats, media pointers… Full JSDoc in src/types/Store.ts.

interface Store {
    has(key: string): boolean | Promise<boolean>;
    get(key: string): any | Promise<any>;
    set(key: string, value: any): boolean | Promise<boolean>;
    delete(key: string): boolean | Promise<boolean>;
    keys(): AsyncGenerator<string>;
    values(): AsyncGenerator<any>;
    entries(): AsyncGenerator<[string, any]>;
    clear(): boolean | Promise<boolean>;
    scan?(pattern: string): string[] | Promise<string[]>;
}

API Reference

Chats

socket.chats(): Promise<Chat[]>;

| Method | Description | | --------------------- | ---------------------------------------------------------------------- | | pin() | Pin chat to top. | | mute() / unmute() | Toggle notifications. | | seen() | Mark as read. | | presence(state) | Update own presence (available, composing, recording, paused). | | delete() | Remove chat locally. | | messages() | Fetch cached messages (lazy‑loaded). |

Messages

| Method | Description | | ------------------- | ------------------------------------------ | | content() | Returns payload: string or Buffer. | | reply(body, opts) | Reply in thread. Supports all media types. | | seen() | Mark as read. | | delete() | Delete for everyone when possible. | | like(emoji) | Simple reaction helper. | | forward(chatid) | Forward to another chat. |

Return type fields:

type MessageBase = {
    id: string;
    type: 'text' | 'image' | 'audio' | 'video' | 'location';
    caption?: string;
    once?: boolean;
    ptt?: boolean; // push‑to‑talk
    ptv?: boolean; // video‑note
};

Presence

await chat.presence('composing'); // typing…

Media Helpers

All send()/reply() share the same overload signature:

send(body: string | Buffer | { lat: number; lon: number }, opts?: SendOptions): Promise<Message>;

interface SendOptions {
  type?: "audio" | "video" | "image" | "location";
  caption?: string; // images
  ptt?: boolean; // audio
  ptv?: boolean; // video‑note
  once?: boolean; // view‑once
}

Storage Back‑ends

In‑memory

Use the demo MemoryStore from the Zero‑to‑Hero section. Volatile.

File‑system

import fs from 'node:fs/promises';

function FSStore(dir: string): Store {
    /* … */
}

Stores everything under .cache/ exactly like the suggested tree.

Redis

import { createClient } from 'redis';

function RedisStore(client = createClient()): Store {
    /* … */
}

Use SCAN for iteration and implement scan(pattern) via KEYS/SCAN glob.


Recipes

Auto‑responder bot

socket.on('message', async (msg) => {
    if (msg.type === 'text' && msg.content().includes('ping')) {
        await msg.reply('pong 🏓');
    }
});

Send location every hour

setInterval(async () => {
    await chat.send({ lat: 8.3014, lon: -62.7166 }, { type: 'location' });
}, 3.6e6);

Troubleshooting

| Error | Cause & Fix | | ------------------------------- | -------------------------------------------------------------------------------------- | | 401 – Session invalid | Credentials expired → re‑authenticate (clear store keys for auth: prefix). | | ERR_PACKAGE_PATH_NOT_EXPORTED | Make sure you import ESM build (import …). | | BaileysBoomError 428 | Connection closed by server – client will auto‑retry; ensure network clock is in sync. |


Contributing

  1. Fork → branch → PR (conventional commits).
  2. yarn lint && yarn test must pass.
  3. Document new features in this README.

License

MIT — © 2025 Miguel Alejandro / Arcaelas Insiders.