@wearedev-mgrr/sdk
v1.0.0
Published
Official TypeScript SDK for the MGRR (Motor de Generacion de Reportes Regulatorios) API
Maintainers
Readme
@wearedev-mgrr/sdk
Cliente TypeScript oficial para el Motor de Generación de Reportes Regulatorios (MGRR) de WeAre. Permite listar reportes, ejecutarlos, consultar su estado y descargar los archivos generados desde Node.js o browser.
Estado:
0.2.0— superficie admin completa + exchange de API key por JWT. Ver CHANGELOG.md.
Install
npm install @wearedev-mgrr/sdk
# o
pnpm add @wearedev-mgrr/sdk
# o
yarn add @wearedev-mgrr/sdkRequiere Node >= 20 (para fetch y AbortSignal globales) o un browser moderno (ES2022).
Quickstart
Node
import { MgrrClient } from '@wearedev-mgrr/sdk';
const mgrr = new MgrrClient({
apiKey: process.env.MGRR_API_KEY!,
baseUrl: 'https://api.mgrr.weare.co',
});
const { data: reports } = await mgrr.reports.list({ limit: 20 });
const exec = await mgrr.executions.run('UIAF_CAMBISTAS', {
params: { year: 2026, month: 3 },
});
await exec.waitUntilDone({ timeoutMs: 600_000 });
const dl = await exec.download();
await dl.pipeToFile('./out/uiaf_cambistas_2026_03.txt');Browser
import { MgrrClient } from '@wearedev-mgrr/sdk';
const mgrr = new MgrrClient({ apiKey: userKey, baseUrl: '/api' });
const exec = await mgrr.executions.run('DIAN_1100', { params: { year: 2026 } });
await exec.waitUntilDone();
const blob = await (await exec.download()).toBlob();Matriz de compatibilidad
| Entorno | Version minima | Notas |
|-----------------|----------------|---------------------------------------------------|
| Node.js | >= 20 | fetch, AbortSignal nativos. |
| Browser (ESM) | ES2022 | Chrome 94+, Firefox 93+, Safari 15.4+, Edge 94+. |
| TypeScript | >= 5.0 | Tipos incluidos (.d.ts). |
| SDK version | API / OpenAPI | Estado |
|-------------|---------------|--------|
| 0.x | v0 (pre-GA) | beta |
| 1.x | v1 | GA |
Configuración
new MgrrClient({
apiKey: 'mgrr_acme_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // requerido
baseUrl: 'https://api.mgrr.weare.co', // default: http://localhost:8080
timeoutMs: 30_000, // HTTP request timeout (default 30s)
userAgent: 'my-app/1.0', // opcional
fetch: customFetch, // override (tests, proxies)
logger: (level, msg, data) => console.log(level, msg, data),
defaultPoll: { pollIntervalMs: 1000, maxIntervalMs: 30_000 },
});| Opción | Tipo | Default |
|---------------|--------------------------------------|----------------------------|
| apiKey | string (requerido) | — |
| baseUrl | string | http://localhost:8080 |
| timeoutMs | number | 30000 |
| userAgent | string | mgrr-sdk/<version> |
| fetch | typeof fetch | globalThis.fetch |
| logger | (level, msg, data?) => void | undefined |
| defaultPoll | PollOptions | backoff 1s → 30s, 30 min |
Uso
Listar reportes
const page = await mgrr.reports.list({ limit: 50, offset: 0 });
page.data.forEach((r) => console.log(r.code, r.name));
const detail = await mgrr.reports.get('UIAF_CAMBISTAS');
console.log(detail.parameters);Ejecutar un reporte
const exec = await mgrr.executions.run('UIAF_CAMBISTAS', {
params: { year: 2026, month: 3 },
idempotencyKey: 'uiaf-2026-03-run-1', // opcional
});
console.log(exec.id, exec.status); // 'QUEUED'Esperar hasta finalizar (waitUntilDone)
Azúcar sobre el polling. Resuelve con DoneEvent o rechaza con un error tipado.
const done = await exec.waitUntilDone({
pollIntervalMs: 1000,
maxIntervalMs: 30_000,
timeoutMs: 600_000,
});
console.log(done.fileUrl, done.rowCount, done.durationSeconds);Eventos (EventEmitter)
Para UIs reactivas o logs en vivo.
exec.on('progress', (p) => console.log(`[${p.status}] ${p.progressPercentage}%`));
exec.on('done', (d) => console.log('Listo:', d.fileUrl));
exec.on('error', (e) => console.error(e.code, e.message));
await exec.waitUntilDone();Descargar el archivo
const dl = await exec.download();
// Node
await dl.pipeToFile('./out/report.txt');
// Isomórfico
const bytes: Uint8Array = await dl.toBuffer();
// Browser
const blob: Blob = await dl.toBlob();
const url = URL.createObjectURL(blob);Cancelación con AbortSignal
const controller = new AbortController();
setTimeout(() => controller.abort(), 60_000);
const exec = await mgrr.executions.run('DIAN_1099', {
params: { year: 2026 },
signal: controller.signal,
});
await exec.waitUntilDone({ signal: controller.signal });Execution.cancel() aborta el polling local (no cancela la ejecución en el servidor en v1).
Errores
Todos los errores heredan de MgrrError y exponen code, statusCode, requestId y details.
| Clase | HTTP | Cuando se lanza | Campos extras |
|--------------------------------|---------|---------------------------------------------------------------|-------------------------------|
| MgrrNetworkError | — | Fetch fail, DNS, connection reset. | — |
| MgrrTimeoutError | — | HTTP request o polling excedio timeoutMs. | — |
| MgrrAbortError | — | AbortSignal disparado por el consumidor. | — |
| MgrrValidationError | 400 | Parametros invalidos (error.code = VALIDATION_ERROR). | details: [{field, message}] |
| MgrrAuthError | 401/403 | INVALID_API_KEY, EXPIRED, FORBIDDEN. | — |
| MgrrNotFoundError | 404 | REPORT_NOT_FOUND, FILE_NOT_FOUND, execution no existe. | — |
| MgrrConflictError | 409 | REPORT_INACTIVE, NOT_READY (archivo aun no generado). | — |
| MgrrRateLimitError | 429 | RATE_LIMITED. Respeta header Retry-After. | details.retryAfterMs |
| MgrrServerError | 5xx | Error del servidor MGRR. | — |
| MgrrExecutionFailedError | — | waitUntilDone() detecta status = FAILED. | details.errorMessage |
| MgrrExecutionCancelledError | — | waitUntilDone() detecta status = CANCELLED. | — |
| MgrrUsageError | — | Mal uso del SDK (ej. apiKey faltante, pipeToFile en browser). | — |
| MgrrError (base) | * | Cualquier otro 400 o respuesta con shape desconocido. | details: <body crudo> |
Ejemplo try/catch tipado
import {
MgrrClient,
MgrrValidationError,
MgrrAuthError,
MgrrRateLimitError,
MgrrExecutionFailedError,
MgrrTimeoutError,
} from '@wearedev-mgrr/sdk';
try {
const exec = await mgrr.executions.run('UIAF_CAMBISTAS', {
params: { year: 2026, month: 3 },
});
await exec.waitUntilDone({ timeoutMs: 300_000 });
const dl = await exec.download();
await dl.pipeToFile('./out.txt');
} catch (e) {
if (e instanceof MgrrValidationError) {
console.error('Parámetros inválidos:', e.details);
} else if (e instanceof MgrrAuthError) {
console.error('API key inválida o sin permisos');
} else if (e instanceof MgrrRateLimitError) {
console.error('Rate limited, reintentar en', e.details);
} else if (e instanceof MgrrExecutionFailedError) {
console.error('Ejecución falló:', e.message, 'requestId:', e.requestId);
} else if (e instanceof MgrrTimeoutError) {
console.error('Timeout esperando al reporte');
} else {
throw e;
}
}Todo error incluye error.requestId (header X-Request-Id) para correlacionar con los logs del API.
Mapeo por status HTTP (0.2.0)
Desde 0.2.0 el mapeo es por status code, no por error.code:
| Status | Clase lanzada | Alias corto |
|--------|---------------------------|---------------------|
| 400 | MgrrValidationError | ValidationError |
| 401 | MgrrAuthError | — |
| 403 | MgrrForbiddenError | ForbiddenError |
| 404 | MgrrNotFoundError | NotFoundError |
| 409 | MgrrConflictError | ConflictError |
| 429 | MgrrRateLimitError | RateLimitedError |
| 5xx | MgrrApiError | ApiError |
MgrrForbiddenError extends MgrrAuthError, asi que instanceof MgrrAuthError sigue capturando 401 y 403 — back-compat con codigo pre-0.2.0. Para distinguir 403 puro usa instanceof MgrrForbiddenError.
Auth: exchange de API key por JWT
client.auth.exchangeApiKey() intercambia la API key configurada (o una pasada como argumento) por un JWT Bearer de 300 s. Pensado para apps browser (ej. sdk-visual) que no deben persistir la API key cruda en localStorage — el token corto se guarda en memoria y se refresca al vencer.
El MgrrClient soporta dos modos de auth mutuamente excluyentes:
- API key (default): emite
X-API-Key: <apiKey>en cada request. - Bearer JWT: tras
setAccessToken(jwt), emiteAuthorization: Bearer <jwt>y deja de enviarX-API-Key. Es el modo requerido paraclient.admin.*, ya que el middleware de admin rechaza la API key.
Quickstart recomendado (browser / sdk-visual)
const mgrr = new MgrrClient({ apiKey, baseUrl: '/api' });
// One-shot: intercambia la key, instala el Bearer internamente.
const { access_token, expires_in } = await mgrr.authenticateWithApiKey();
// a partir de aqui client.admin.* funciona:
const { data: reports } = await mgrr.admin.reports.list();
// Al vencer (expires_in = 300s) refresca con la misma llamada — el
// exchange usa siempre X-API-Key, incluso si ya habia un JWT instalado.
await mgrr.authenticateWithApiKey();
// Logout: vuelve al modo X-API-Key (util para catalogo publico).
mgrr.setAccessToken(null);API de auth
// Intercambio explicito — retorna el ExchangeResponse sin instalar nada.
const res = await mgrr.auth.exchangeApiKey();
// Instala un JWT obtenido por otro medio (ej. login UI).
mgrr.setAccessToken(res.access_token);
// Limpia el Bearer y vuelve a la API key original.
mgrr.setAccessToken(null);Cuando usar cada modo
| Modo | Setup | Cuando |
|----------------------|---------------------------------------------|-------------------------------------------------------------------------|
| API key directo | Default — ningun setup | Node / scripts CI / servicios backend. La key vive en env vars. |
| Bearer via exchange | authenticateWithApiKey() | Browser SPA. Convierte la key a un token corto, habilita admin.*. |
| Bearer JWT externo | setAccessToken(jwtFromLogin) | UI admin con login password + TOTP que emite su propio JWT. |
Admin namespace
Agrupa todas las operaciones de autoria + administracion. Require API key con role ADMIN (o el rol correspondiente server-side). Todas las escrituras son tenant-scoped automaticamente.
// Reportes — CRUD + clone
const { data: reports } = await mgrr.admin.reports.list({ limit: 20 });
const full = await mgrr.admin.reports.get(42); // ReportFull hidratado
const created = await mgrr.admin.reports.create({ code: 'UIAF_NEW', name: '...', output_format: 'FIXED_WIDTH' });
const cloned = await mgrr.admin.reports.clone(42, { code: 'UIAF_COPY' });
// Parametros
await mgrr.admin.parameters.create({ report_id: 42, code: 'year', data_type: 'INTEGER', required: true });
await mgrr.admin.parameters.update(parameterId, { default_value: '2026' });
await mgrr.admin.parameters.delete(parameterId);
// Secciones — CRUD + reorder
await mgrr.admin.sections.list(42);
await mgrr.admin.sections.reorder(42, [sec1Id, sec2Id, sec3Id]);
// Campos — CRUD + carga batch (util en el wizard)
await mgrr.admin.fields.batchAdd(sectionId, [
{ code: 'nit', data_type: 'STRING', position: 1, length: 11 },
{ code: 'razon_social', data_type: 'STRING', position: 2, length: 100 },
]);
// Validaciones — CRUD + batch
await mgrr.admin.validations.batchCreate(fieldId, [
{ rule_type: 'REQUIRED', rule_config: {} },
{ rule_type: 'LENGTH_MAX', rule_config: { max: 100 } },
]);
// Post-process (put idempotente)
await mgrr.admin.postProcess.set(42, { compression_type: 'GZIP', encryption_type: 'NONE' });
const pp = await mgrr.admin.postProcess.get(42); // null si no hay
// Credenciales — respuestas SIEMPRE enmascaradas
const { data: creds } = await mgrr.admin.credentials.list({ type: 'DB_POSTGRES' });
const testResult = await mgrr.admin.credentials.test(credId); // { ok: true/false, error? }
// Usuarios (tenant-scoped)
const user = await mgrr.admin.users.create({ email: '[email protected]', full_name: '...', role: 'AUTHOR' });
const { temporary_password } = await mgrr.admin.users.resetPassword(user.id);
await mgrr.admin.users.resetTotp(user.id);
// API Keys — `key` crudo mostrado UNA sola vez
const issued = await mgrr.admin.apiKeys.issue({ name: 'ci-prod', role: 'EXECUTOR' });
console.log(issued.key); // guardar ya, no se recupera despues
const rotated = await mgrr.admin.apiKeys.rotate(issued.id); // emite nueva, revoca vieja
await mgrr.admin.apiKeys.audit(issued.id, { limit: 100 });
// Tools — helpers de autoria
await mgrr.admin.tools.testSource({ source_type: 'DB_POSTGRES', credential_id, query: 'SELECT 1' });
await mgrr.admin.tools.testQuery({ credential_id, query: 'SELECT * FROM ventas LIMIT 10' });
await mgrr.admin.tools.previewJsonata({ expression: 'total * 1.19', sample_row: { total: 100 } });
await mgrr.admin.tools.previewFile({ report_id: 42, params: { year: 2026 }, sample_size: 5 });Streaming downloads
Para archivos grandes (>10 MB) usa downloadExecutionFile en lugar de exec.download().toBuffer() / toBlob() — evita cargar el archivo completo en heap.
import { downloadExecutionFile } from '@wearedev-mgrr/sdk';
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
// Node — Readable pipeable a fs
const { stream, filename, contentLength } = await downloadExecutionFile(
mgrr,
executionId,
{ asNodeStream: true },
);
await pipeline(stream, createWriteStream(`./out/${filename ?? 'report.bin'}`));
// Browser — web ReadableStream (default)
const { stream } = await downloadExecutionFile(mgrr, executionId);
const res = new Response(stream);
const blob = await res.blob(); // o pipe a otro ladoDesarrollo
Regenerar tipos desde OpenAPI
npm run dev --workspace=packages/api # 1. Levantar API
curl -s http://localhost:8080/docs/json > packages/sdk/openapi.json
npm run gen:types --workspace=packages/sdknpm run check:types-fresh falla si src/schema.d.ts quedo desactualizado respecto a openapi.json.
Build
npm run build # dual ESM + CJS + .d.ts via tsup
npm run typecheck
npm testEjemplos
examples/node-cli/— CLI Node que lista, ejecuta y descarga un reporte.examples/browser-vite/— SPA minima en Vite que ejecuta un reporte y descarga el Blob.
Licencia
Propietario — WeAre S.A.S. Uso interno.
