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

bridgex

v2.2.0

Published

a library for mazz app or a bridge for messaging that allow and automate the use of our service

Readme

SMS Client SDK

A fault-tolerant TypeScript SDK for sending SMS messages — usable as a library, a standalone microservice, or a background job processor.


Table of Contents

  1. Installation
  2. Quick Start
  3. Auto-Provisioning Credentials
  4. SendConfig — Fluent Parameter Builder
  5. Send Methods
  6. Result & Error Types
  7. SMSQueue — Background Job Queue
  8. SMSScheduler — Recurring & Scheduled Sends
  9. SMSService — Run as a Microservice
  10. Plugins & Hooks
  11. Fault Tolerance (Retry + Circuit Breaker)
  12. Full Configuration Reference
  13. File Structure

Installation

npm install bridgex

Quick Start

import { SMSClient, SendConfig, LoggerPlugin } from "bridgex";

const client = new SMSClient({
  baseUrl: "https://api.your-service.com",
  apiKey: "your-access-token",
  projectKey: "your-repo-token",
});

client.use(LoggerPlugin("my-app"));

const result = await client.send({
  to: "+15551234567",
  template: "Hello {name}, your order {orderId} is confirmed!",
  variables: { name: "Alice", orderId: "ORD-999" },
});

if (result.ok) {
  console.log("Sent!", result.data);
} else {
  console.error(result.error); // typed ErrorLog — never throws
}

Auto-Provisioning Credentials

Your service auto-generates a repo token, access token, and URL. Use CredentialProvisioner to fetch them automatically.

import { CredentialProvisioner, SMSClient } from "bridgex";

const credentials = await CredentialProvisioner.provision({
  provisionUrl: "https://api.your-service.com/provision",
  adminKey: "your-admin-key",
  projectName: "my-app",
});

// credentials = { baseUrl, apiKey, projectKey }
const client = new SMSClient(credentials);

Accepts camelCase or snake_case field names from your API (apiKey, api_key, accessToken, access_token, etc.).


SendConfig — Fluent Parameter Builder

SendConfig lets you define templates, defaults, tags, TTLs, and priority once and reuse them across many calls. No more copying the same template string everywhere.

import { SendConfig } from "bridgex";

// Build a reusable config
const otpConfig = SendConfig.otp(10) // preset: OTP with 10-min expiry
  .set("appName", "MyApp"); // add a shared default variable

// Use it
const result = await client.send(
  otpConfig.for("+15551234567", { code: "482910" }),
);

// Send to many recipients with per-recipient variables
const results = await client.sendMany(
  otpConfig.forMany([
    { to: "+15550000001", variables: { code: "111111" } },
    { to: "+15550000002", variables: { code: "222222" } },
  ]),
  otpConfig._template, // or pass template inline
);

Builder Methods

| Method | Description | | ------------------------------------ | ---------------------------------------------- | | .template(str) | Set the message template | | .defaults(vars) | Default variable values (overridable per-call) | | .set(key, value) | Add a single default variable | | .tag(...tags) | Label this config (used in logs and hooks) | | .ttl(ms) | Drop jobs older than this (queue only) | | .priority("high"\|"normal"\|"low") | Queue priority | | .dedupKey(key) | Suppress duplicate jobs in the queue | | .clone() | Copy without mutating | | .merge(other) | Combine two configs (other wins on conflicts) |

Static Presets

SendConfig.otp(expiryMinutes?)       // OTP — high priority, TTL-aware
SendConfig.orderNotification()       // Order status updates
SendConfig.reminder()                // Appointment / task reminders
SendConfig.promo()                   // Marketing messages — low priority

Materialise Methods

config.for(to, variables?)           // → SendParams (use with client.send)
config.forMany(recipients)           // → SendParams[] (use with client.sendMany)
config.forObject(to, object)         // → SendObjectParams (use with client.sendObject)

Send Methods

All methods return a Result and never throw.

client.send(params)Promise<Result>

const result = await client.send({
  to: "+15551234567",
  template: "Hi {user.firstName}, your balance is {account.balance}",
  variables: {
    user: { firstName: "Bob" },
    account: { balance: "$42.00" },
  },
});

Supports nested variable paths ({nested.key}).


client.sendMany(recipients, template, sharedVars?)Promise<BatchResult>

const result = await client.sendMany(
  [
    { to: "+15550000001", variables: { name: "Alice" } },
    { to: "+15550000002", variables: { name: "Bob" } },
  ],
  "Hi {name}, your appointment is tomorrow at {time}.",
  { time: "10:00 AM" },
);

console.log(`${result.successCount}/${result.total} sent`);
result.failed.forEach((f) => console.error(`[${f.to}]`, f.error.message));

client.sendObject(params)Promise<Result>

Auto-extracts the message from message, body, text, or content fields. Or pass a template to use any field as a variable.

// Auto-detection
await client.sendObject({
  to: "+15551234567",
  object: { message: "Your package has shipped!", trackingId: "TRK-123" },
});

// With template
await client.sendObject({
  to: "+15551234567",
  object: { firstName: "Alice", trackingId: "TRK-123" },
  template: "Hi {firstName}, your tracking ID is {trackingId}.",
});

client.sendObjectMany(items)Promise<BatchResult>

await client.sendObjectMany([
  {
    to: "+15550000001",
    object: { firstName: "Alice", orderId: "ORD-1" },
    template: "Hi {firstName}, order {orderId} is ready.",
  },
  {
    to: "+15550000002",
    object: { firstName: "Bob", orderId: "ORD-2" },
    template: "Hi {firstName}, order {orderId} is ready.",
  },
]);

Result & Error Types

type Result<T> =
  | { ok: true; data: T; error: null }
  | { ok: false; data: null; error: ErrorLog };

interface ErrorLog {
  name: string;
  message: string;
  code: string; // see Error Codes table
  isClientError: boolean; // your code caused it
  isServerError: boolean; // server returned an error
  details?: unknown;
  timestamp: string; // ISO 8601
}

BatchResult

interface BatchResult<T> {
  succeeded: Array<{ index: number; to: string; data: T }>;
  failed: Array<{ index: number; to: string; error: ErrorLog }>;
  total: number;
  successCount: number;
  failureCount: number;
}

Error Codes

| Code | Client? | Server? | Cause | | ------------------ | ------- | ------- | ------------------------------ | | VALIDATION_ERROR | ✅ | ❌ | Missing required param | | TEMPLATE_ERROR | ✅ | ❌ | Variable missing from template | | SERVER_ERROR | ❌ | ✅ | Non-2xx HTTP response | | RATE_LIMIT_ERROR | ❌ | ✅ | 429 — respects Retry-After | | NETWORK_ERROR | ❌ | ❌ | Timeout / no connectivity | | CIRCUIT_OPEN | ❌ | ❌ | Circuit breaker tripped |


SMSQueue — Background Job Queue

Fire-and-forget sending with priority lanes, deduplication, TTL, and delayed sends.

import { SMSQueue, SendConfig, LoggerPlugin, PluginManager } from "bridgex";

const plugins = new PluginManager().use(LoggerPlugin("worker"));
const queue = new SMSQueue(client, { concurrency: 5 }, plugins);
queue.start();

// Immediate
const otpCfg = SendConfig.otp().dedupKey(`otp:${userId}`);
queue.enqueue(otpCfg.for(phone, { code: "482910" }));

// Delayed (send in 5 minutes)
queue.enqueueAfter(reminderCfg.for(phone), 5 * 60 * 1000);

// At a specific time
queue.enqueueAt(promoCfg.for(phone), new Date("2025-12-25T09:00:00").getTime());

// Stats
console.log(queue.stats());
// { pending: 3, running: 1, completed: 47, dropped: 2 }

// Cancel a specific job
queue.cancel(jobId);

// Drain all remaining jobs then stop
await queue.drain();

Queue Options

| Option | Default | Description | | -------------- | ------- | -------------------------------------------- | | concurrency | 3 | Max parallel workers | | pollInterval | 1000 | How often (ms) to check for ready jobs | | autoStart | true | Start workers automatically on first enqueue |


SMSScheduler — Recurring & Scheduled Sends

import { SMSScheduler, SendConfig } from "bridgex";

const scheduler = new SMSScheduler(queue);
scheduler.start();

// Every 24 hours
scheduler.every(
  24 * 60 * 60 * 1000,
  "daily-report",
  reportConfig.for(adminPhone),
);

// Cron expression: weekdays at 9am
scheduler.cron("0 9 * * 1-5", "weekday-digest", digestConfig.for(adminPhone), {
  maxRuns: 50,
});

// One-shot future send
scheduler.once(
  new Date("2025-06-01T09:00:00"),
  "summer-launch",
  promoConfig.for(phone),
);

// Control
scheduler.pause("daily-report");
scheduler.resume("daily-report");
scheduler.remove("summer-launch");

// List all scheduled jobs
console.log(scheduler.list());

Cron format: minute hour day-of-month month day-of-week

  • * = every, , = list, - = range, / = step
  • Example: "30 8,12,18 * * *" = 8:30, 12:30, and 18:30 every day

SMSService — Run as a Microservice

Deploy the SDK as a standalone REST service so any other service (Python, Go, Ruby, etc.) can send SMS without bundling this SDK.

import {
  SMSClient,
  SMSQueue,
  SMSService,
  MetricsPlugin,
  PluginManager,
} from "bridgex";

const client = new SMSClient({ baseUrl, apiKey, projectKey });
const queue = new SMSQueue(client, { concurrency: 10 });
const metrics = MetricsPlugin();

client.use(metrics);
queue.start();

const service = new SMSService(client, {
  port: 4000,
  apiKey: "my-service-secret",
})
  .withQueue(queue)
  .withMetrics(metrics);

await service.start();
// [SMSService] Listening on http://0.0.0.0:4000

REST API

| Method | Path | Description | | -------- | ------------------- | ------------------------------------ | | POST | /send | Send a single message | | POST | /send/many | Send to many recipients | | POST | /send/object | Send from a plain object | | POST | /send/object/many | Send objects to many recipients | | POST | /queue/enqueue | Fire-and-forget via queue | | POST | /queue/schedule | Schedule a future send | | GET | /queue/stats | Queue stats | | DELETE | /queue/:id | Cancel a queued job | | GET | /health | Liveness + circuit state + uptime | | GET | /metrics | Send rate, success rate, avg latency |

All requests must include x-service-key: <your-service-secret> if apiKey is configured.

Example: POST /send

curl -X POST http://localhost:4000/send \
  -H "Content-Type: application/json" \
  -H "x-service-key: my-service-secret" \
  -d '{ "to": "+15551234567", "template": "Hello {name}", "variables": { "name": "Alice" } }'

Example: POST /queue/schedule

# Send at a specific time
curl -X POST http://localhost:4000/queue/schedule \
  -H "x-service-key: my-service-secret" \
  -H "Content-Type: application/json" \
  -d '{ "at": "2025-12-25T09:00:00Z", "to": "+1555...", "template": "Merry Christmas {name}!", "variables": { "name": "Bob" } }'

# Send after a delay
curl -X POST http://localhost:4000/queue/schedule \
  -H "x-service-key: my-service-secret" \
  -H "Content-Type: application/json" \
  -d '{ "delayMs": 300000, "to": "+1555...", "template": "Your reminder: {text}", "variables": { "text": "Call the dentist" } }'

GET /health response

{
  "status": "ok",
  "circuitState": "CLOSED",
  "uptime": 3600,
  "startedAt": "2025-01-01T09:00:00.000Z",
  "queue": { "pending": 0, "running": 2, "completed": 1024, "dropped": 3 }
}

Plugins & Hooks

Plugins run on every send lifecycle event. They're async and never crash the main flow.

import { SMSClient, LoggerPlugin, MetricsPlugin } from "bridgex";

// Built-in logger
client.use(LoggerPlugin("order-service"));

// Built-in metrics
const metrics = MetricsPlugin();
client.use(metrics);

// Later
console.log(metrics.snapshot());
// { sent: 100, succeeded: 97, failed: 3, retries: 5, successRate: 0.97, avgDurationMs: 212, ... }
metrics.reset();

Custom Plugin

client.use({
  name: "slack-alerts",
  onError: async ({ to, error }) => {
    await slackClient.send(
      `SMS failed to ${to}: [${error.code}] ${error.message}`,
    );
  },
  onRetry: async ({ to, attempt }) => {
    if (attempt >= 2) console.warn(`High retry count for ${to}`);
  },
});

Hook Reference

| Hook | When it fires | | ----------- | --------------------------------------------- | | onSend | Before every send attempt | | onSuccess | After a successful delivery | | onError | After all retries are exhausted | | onRetry | On each retry attempt | | onDrop | When a queued job is discarded (TTL or dedup) |


Fault Tolerance

Retry Handler

Automatically retries transient failures (network errors, 5xx, rate limits). Never retries client-side errors (VALIDATION_ERROR, TEMPLATE_ERROR). Respects Retry-After headers on 429s.

Circuit Breaker

After N consecutive failures the circuit opens — all calls immediately fail with CircuitOpenError instead of hammering a down service. After a timeout the circuit goes half-open and probes before closing again.

console.log(client.circuitState); // "CLOSED" | "OPEN" | "HALF_OPEN"
client.resetCircuit(); // manual reset after fixing downstream

Full Configuration Reference

const client = new SMSClient({
  baseUrl: "https://api.your-service.com",
  apiKey: "your-access-token",
  projectKey: "your-repo-token",

  retry: {
    maxAttempts: 4,
    delay: 300,
    strategy: "exponential", // "fixed" | "exponential"
    jitter: true, // ±20% randomness
  },

  circuitBreaker: {
    threshold: 5, // failures to open circuit
    timeout: 30_000, // ms before half-open probe
    successThreshold: 2, // probes to fully close
  },

  concurrency: 5,
  batchSize: 50,
  plugins: [LoggerPlugin("app")],
});