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

@inowu/cashdro

v0.3.1

Published

TypeScript client for CashDro automated cash recyclers over the Web Service integration API.

Readme

CashDro TypeScript SDK

@inowu/cashdro proporciona acceso conveniente y tipado a los recicladores de efectivo automatizados CashDro desde TypeScript y JavaScript — ejecutando ventas, pagos, depósitos, retiros y operaciones de gestión de efectivo, además de consultas de niveles de efectivo y reportes.

Tabla de contenidos

Instalación

npm i @inowu/cashdro

Requisitos: Node.js 18+ (o cualquier runtime con fetch global).

Inicio rápido

import { CashDroClient } from "@inowu/cashdro";

const cashdro = new CashDroClient({
    baseUrl: "https://your-cashdro/",
    username: "manager",
    password: "••••",
});

// 1. Verificar conectividad + credenciales
const ping = await cashdro.doTest();
if (ping.code !== 1) throw new Error("CashDro unreachable or bad credentials");

// 2. Realizar una venta de 12.50 (los montos están en unidades menores — valor × 100)
const { operationId } = await cashdro.sale(1250, { posuser: "Ana" });

// 3. Esperar a que el cliente termine de pagar
const final = await cashdro.pollUntilFinished(operationId);
const op = final.response?.operation?.operation;
console.log(`paid=${op?.totalin} change=${op?.totalout}`); // el cambio es negativo

// 4. Confirmar que registraste la transacción
await cashdro.setOperationImported(operationId);

Autenticación

Cada llamada se autentica como un usuario de CashDro mediante HTTP Basic. El usuario debe existir en la máquina y tener permiso para la operación.

const cashdro = new CashDroClient({ baseUrl, username: "manager", password: "••••" });

Los campos opcionales posuser, posid y aliasid aceptados por las transacciones no son credenciales — solo etiquetan quién (cajero), qué terminal y un id de referencia del lado del host inició una operación (≤ 50 caracteres cada uno).

Configuración

CashDroClient acepta las siguientes opciones:

| Opción | Descripción | |--------|-------------| | baseUrl | URL de la unidad CashDro. Apunta esto a tu dispositivo / unidad de producción. | | username, password | Credenciales de usuario de CashDro (HTTP Basic). | | timeoutInSeconds | Timeout por solicitud (por defecto 60). | | maxRetries | Reintentos automáticos con backoff exponencial (por defecto 2). | | headers | Encabezados adicionales para enviar en cada solicitud. | | fetch | Implementación personalizada de fetch (ver Avanzado). | | debug | Traza los helpers de multi-operación — true para loguear, o una función handler (ver Modo debug). |

const cashdro = new CashDroClient({
    baseUrl: "https://cashdro.prod.example/",
    username: "manager",
    password: "••••",
    timeoutInSeconds: 30,
    maxRetries: 3,
});

Cómo funcionan las transacciones

Una transacción de movimiento de efectivo (venta, pago, depósito, retiro, …) corre de forma asíncrona en el hardware, por lo que se conduce mediante un ciclo de vida corto:

start ─▶ acknowledge ─▶ (poll status) ─▶ finish? ─▶ import
 id        launch          I → Q → E → F   finalize   registrarla
  • start abre la transacción y devuelve un operationId (el handle para todo lo demás). La unidad aún no hace nada.
  • acknowledge lanza la ejecución (los aceptadores se abren / comienza la dispensación).
  • poll status hasta que state === "F". Estados: I pendiente, Q en cola, E ejecutando, F finalizado.
  • finish finaliza o cancela (solo algunas transacciones necesitan un finish explícito).
  • import le indica a la unidad que el host ya registró el resultado.

Los métodos de conveniencia (sale, payment, …) y los helpers (runOperation, pollUntilFinished) colapsan este boilerplate por ti mientras siguen devolviendo el operationId y cada respuesta cruda.

Dinero: todos los montos son enteros en unidades menores (valor × 100 — 1250 = 12.50). Los montos dispensados (totalout) son negativos. La moneda es la que tenga configurada la unidad.

Una a la vez: un CashDro ejecuta una sola operación a la vez; iniciar otra mientras una está activa falla con code -2. Usa askOperationExecuting() para detectar y recuperar una operación atascada.

Uso no bloqueante

Una venta puede tardar — la unidad espera a que el cliente inserte efectivo. No hagas await de toda la cadena start → wait → import dentro de un request handler; en su lugar déjala correr en segundo plano y reacciona mediante callbacks. El callback onStart te entrega el operationId en el instante en que se conoce (antes de cualquier espera), para que la UI pueda mostrar una pantalla de pago y ofrecer un botón de Cancelar de inmediato.

import { CashDroClient, CashDroTimeoutError } from "@inowu/cashdro";

// Fire-and-forget: ejecuta start → acknowledge → poll → import en segundo plano.
void cashdro
    .sale(1250, {
        waitForFinish: true, // sondear hasta que el cliente termine de pagar
        autoImport: true, // registrarlo en la unidad cuando termine
        onStart: (operationId) => showPayScreen(operationId), // se devuelve de inmediato
        poll: { onPoll: (status) => updateUi(status), timeoutMs: 120_000 },
    })
    .then((r) => onPaid(r.final?.response?.operation?.operation)) // totalin / totalout
    .catch((err) => {
        if (err instanceof CashDroTimeoutError) return; // nadie pagó a tiempo
        onError(err);
    });

Para permitir que un botón de Cancelar detenga una venta en curso, cancélala en la unidad — el poll en segundo plano se resuelve entonces en el estado F en su siguiente tick:

cancelButton.onclick = () => cashdro.finishOperation(operationId, 2);

De forma equivalente puedes conducir los pasos tú mismo: await cashdro.sale(...) retorna después de start+acknowledge (rápido), luego llama a cashdro.pollUntilFinished(id) desde una tarea en segundo plano. Pasa un AbortSignal en las opciones de poll/pollUntilFinished si necesitas detener el sondeo sin cancelar la transacción, o cancelOnTimeout: true para auto-cancelar la venta si nadie paga a tiempo.

Modo debug

Los helpers de multi-operación (sale, runOperation, pollUntilFinished, …) pueden trazar exactamente lo que hacen — cada llamada subyacente, su resultado, y cada sondeo de estado. Pasa debug: true para loguear a la consola:

const cashdro = new CashDroClient({ baseUrl, username, password, debug: true });
await cashdro.sale(100, { waitForFinish: true, autoImport: true });
[cashdro] sale: startOperation(type=4, amount=100)
[cashdro] sale: startOperation → operationId=128 (code=1, 41ms)
[cashdro] sale: acknowledgeOperationId(128) → code=1
[cashdro] pollUntilFinished(128): polling every 1000ms, timeout 120000ms
[cashdro] pollUntilFinished(128): poll #1 → state=Q
[cashdro] pollUntilFinished(128): poll #2 → state=E
[cashdro] pollUntilFinished(128): finished after 3 poll(s)
[cashdro] sale: setOperationImported(128) → code=1
[cashdro] sale: done (operationId=128)

Para logging estructurado, pasa un handler en su lugar y enruta los CashDroDebugEvent a tu propio logger:

const cashdro = new CashDroClient({
    baseUrl,
    username,
    password,
    debug: (e) => logger.debug({ seq: e.seq, helper: e.helper, op: e.op, ...e.detail }, e.message),
});

Cada evento es { seq, at, helper, op, phase, detail, message }phase es "call", "result", "poll", o "info". Esto traza la orquestación del helper y es independiente de la capa de transporte.

Referencia del cliente

Los métodos que mapean 1:1 a una operación del dispositivo devuelven la respuesta cruda; los helpers de conveniencia y orquestación combinan varias llamadas pero nunca ocultan los datos subyacentes.

Conectividad y salud

| Método | Qué hace / cuándo usarlo | |--------|----------------------------| | doTest() | Ping autenticado. Verifica accesibilidad + credenciales antes de transaccionar. | | getDiagnosis() | Estado de error del dispositivo (WithError) + lista de dispositivos. Usa esto como gate antes de transaccionar para evitar hardware atascado. | | getAlerts() | Denominaciones en niveles críticos / de inhibición / por debajo del mínimo / por encima del máximo. Para avisos de reabastecimiento/vaciado. | | askOperationExecuting() | La operación que se está ejecutando actualmente (o OperationId: -1 cuando está inactiva). Recupera una operación huérfana al iniciar. |

Transacciones — ciclo de vida

Bloques de construcción de bajo nivel (normalmente usarás los métodos de conveniencia de abajo):

| Método | Qué hace / cuándo usarlo | |--------|----------------------------| | startOperation({ type, amount?, parameters?, … }) | Abre una transacción de un tipo dado; devuelve operationId. | | acknowledgeOperationId(id) | Lanza una operación iniciada. | | askOperation(id) | Sondea el estado completo (estado, total, totalin, totalout, niveles por denominación, mensajes). | | finishOperation(id, type) | type 1 finaliza/acepta, 2 cancela, 4 finaliza con una lista explícita de piezas. | | setOperationImported(id) | Marca la operación como registrada por el host (la elimina de la lista pendiente). |

Transacciones — métodos de conveniencia

Cada uno abre + confirma una transacción y devuelve { operationId, started, … }. Las opciones aceptan posid / posuser / aliasid y las opciones de orquestación.

| Método | Transacción | |--------|-------------| | sale(amount, opts?) | Venta — el cliente paga, se devuelve el cambio. | | payment(amount, opts?) | Pago / desembolso de un monto. | | paymentByDenomination(pieces, opts?) | Pagar piezas específicas (ver abajo). | | depositByAmount(amount, opts?) | Depositar hasta un monto objetivo. | | deposit(opts?) | Depósito abierto; finaliza con finishOperation(id, 1). | | change(opts?) | Cambio / romper un billete. | | load(opts?) | Cargar efectivo en los recicladores. | | withdraw(opts?) | Retirar efectivo (el operador selecciona en la unidad). | | transferToCassette(opts?) | Mover efectivo de los recicladores al cassette. | | emptyNoteCassette(opts?) / emptyCoinCassette(opts?) | Registrar un cassette como vaciado. | | finishWithPieces(id, { recycler?, cassette? }) | Finalizar un retiro/transferencia con una lista explícita de piezas. |

// Pagar exactamente 3×1.00 monedas y 1×20.00 billete
await cashdro.paymentByDenomination([
    { value: 100, level: 3, isBill: false },
    { value: 2000, level: 1, isBill: true },
]);

// Retirar 1×500 billete directamente de los recicladores (sin selección en el dispositivo)
const { operationId } = await cashdro.withdraw();
await cashdro.finishWithPieces(operationId, { recycler: [{ value: 500, level: 1, isBill: true }] });
await cashdro.setOperationImported(operationId);

Helpers de orquestación

| Método | Qué hace | |--------|--------------| | runOperation(params, opts?) | El motor detrás de los métodos de conveniencia: start → (acknowledge) → poll opcional → import opcional. Devuelve { operationId, started, final?, imported? }. | | pollUntilFinished(id, opts?) | Sondea askOperation hasta que state === "F"; devuelve la respuesta final completa. Lanza CashDroTimeoutError en caso de timeout. |

Opciones de runOperation: acknowledge (por defecto true), waitForFinish (por defecto false — una venta bloquea en espera del efectivo del cliente), autoImport, un callback onStart(id, started), y poll (reenviado a pollUntilFinished). Opciones de pollUntilFinished: intervalMs, timeoutMs, signal (AbortSignal), onPoll(status), y cancelOnTimeout — cuando es true, un timeout cancela e importa la operación antes de lanzar el error, para que no quede abierta bloqueando la siguiente transacción con code -2 (un error común de testing al probar ventas sin efectivo).

const { operationId } = await cashdro.sale(500, {
    onStart: (id) => showOnScreen(id),      // operationId en el instante en que se conoce
});
const final = await cashdro.pollUntilFinished(operationId, {
    timeoutMs: 120_000,
    onPoll: (s) => updateUi(s.response?.operation?.operation),
});

Consultas y reportes

| Método | Qué hace / cuándo usarlo | |--------|----------------------------| | getPiecesCurrency({ currencyId, includeLevels?, includeImages? }) | Niveles de efectivo por denominación. Para UI de inventario y para pre-verificar un retiro. | | askPendingOperations({ terminal?, importManualOperations? }) | Operaciones aún no importadas por el host. Reconciliar antes de un cierre de caja. | | askMovements({ fromDate, toDate, group?, operationType? }) | Operaciones entre dos fechas, detalladas o agregadas. Para reportes. |

const levels = await cashdro.getPiecesCurrency({ currencyId: "EUR", includeLevels: 1 });
const report = await cashdro.askMovements({
    fromDate: "2026-06-01_00:00:00",
    toDate: "2026-06-30_23:59:59",
    group: true,
    operationType: JSON.stringify([{ Id: 4 }]), // solo ventas
});

Tipos de transacción

startOperation (y los métodos de conveniencia) aceptan un type numérico:

| type | Transacción | Monto/piezas | |-------:|-------------|---------------| | 1 | carga | — | | 2 | retiro | en el dispositivo, o finishWithPieces | | 3 | pago | amount, o paymentByDenomination | | 4 | venta | amount | | 8 | transferencia al cassette | en el dispositivo, o finishWithPieces | | 10 | vaciar cassette de billetes | — | | 11 | vaciar cassette de monedas | — | | 16 | depósito | finaliza con finishOperation(id, 1) | | 17 | depósito por monto | amount | | 18 | cambio | — |

Una "pieza" (para paymentByDenomination / finishWithPieces) es { value: number, level: number, isBill: boolean }value en unidades menores, level la cantidad, isBill true para billetes.

Respuestas

Cada llamada resuelve a un envelope tipado:

{ code: number; response: { errorMessage: string; /* datos de la operación */ } }

code >= 1 significa éxito; un code negativo es un error (los métodos de conveniencia/helper lanzan una excepción en su lugar — ver abajo). Los tipos de solicitud y respuesta están disponibles bajo el namespace CashDroApi:

import { CashDroApi } from "@inowu/cashdro";
const res: CashDroApi.AskOperationResponse = await cashdro.askOperation(id);

Manejo de errores

import { CashDroError, CashDroTimeoutError } from "@inowu/cashdro";

try {
    const { operationId } = await cashdro.sale(1250);
    await cashdro.pollUntilFinished(operationId, { timeoutMs: 60_000 });
    await cashdro.setOperationImported(operationId);
} catch (err) {
    if (err instanceof CashDroTimeoutError) {
        // no se insertó efectivo a tiempo — la operación sigue abierta; cancélala
        await cashdro.finishOperation(err.operationId, 2);
    } else if (err instanceof CashDroError) {
        console.error("CashDro rejected the request:", err.response);
    } else {
        throw err; // error de transporte/HTTP
    }
}
  • CashDroError — una llamada devolvió un code de no-éxito (contiene la response cruda).
  • CashDroTimeoutErrorpollUntilFinished alcanzó el timeout (contiene operationId y el último poll).

Códigos de dispositivo comunes: -1 credenciales incorrectas, -2 ocupado / no encontrado, -3 monto incorrecto, -4 sin permiso, -998/-999 dispositivo no disponible (revisar diagnóstico).

Avanzado

Acceso a la respuesta cruda

Los métodos que mapean 1:1 a una operación del dispositivo devuelven un awaitable que también expone la respuesta HTTP cruda:

const { data, rawResponse } = await cashdro.askOperation(id).withRawResponse();
console.log(rawResponse.headers);

Fetch personalizado / TLS autofirmado

Proporciona tu propio fetch para plataformas sin uno incorporado, o para alcanzar una unidad que sirve un certificado autofirmado. Prefiere instalar la CA de la unidad en tu almacén de confianza; solo relaja la verificación en entornos confiables y que no sean de producción.

import { Agent, setGlobalDispatcher } from "undici";
setGlobalDispatcher(new Agent({ connect: { rejectUnauthorized: false } })); // solo no-prod

Opciones por solicitud

Cada método 1:1 acepta un segundo argumento opcional para overrides por llamada:

const controller = new AbortController();
await cashdro.askOperation(id, {
    timeoutInSeconds: 10,
    maxRetries: 0,
    abortSignal: controller.signal,
    headers: { "X-Trace-Id": "abc" },
});

Cliente de bajo nivel

Para endpoints aún no envueltos, cashdro.api expone el cliente subyacente usado para hacer las solicitudes.

Compatibilidad de runtime

Funciona en Node.js 18+, Deno, Bun, Cloudflare Workers, Vercel y React Native.