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

@nats-ms/core

v0.2.0

Published

Type-safe NATS wrapper for microservices with messaging patterns

Readme

@nats-ms/core

⚡ Type-safe NATS wrapper for microservices with automatic type inference and built-in messaging patterns.

Features

  • 🎯 Full Type Safety - Automatic type inference from ServiceMap (no manual type annotations needed!)
  • 📘 TypeScript-First - Built with TypeScript, for TypeScript
  • 🔄 Messaging Patterns - Request-Reply, Pub-Sub, Scatter-Gather, Sharding
  • 🛡️ Resilience - Circuit Breaker, Retry strategies, Timeouts
  • 🔍 Auto-Discovery - Services announce themselves automatically
  • 📊 Built-in Metrics - Prometheus-compatible metrics
  • 🎭 Middleware - Request/Response interceptors
  • 📝 Schema Validation - Built-in Zod validation

Installation

npm install @nats-ms/core nats zod

Quick Start

1. Define ServiceMap

import { ServiceMap } from '@nats-ms/core';
import { z } from 'zod';

export class UserServiceMap extends ServiceMap {
  services = {
    'user.create': {
      request: z.object({
        username: z.string(),
        email: z.string().email(),
      }),
      response: z.object({
        userId: z.string(),
        username: z.string(),
      }),
    },
    'user.get': {
      request: z.object({
        userId: z.string(),
      }),
      response: z.object({
        userId: z.string(),
        username: z.string(),
        email: z.string(),
      }),
    },
  } as const;
}

2. Create Service

import { NATSInstanceFactory, createLogger } from '@nats-ms/core';
import { UserServiceMap } from './service-map';

const logger = createLogger({ serviceName: 'user-service' });

// Create factory
const factory = NATSInstanceFactory.create(
  {
    servers: 'nats://localhost:4222',
    name: 'user-service',
    maxReconnectAttempts: -1,
    reconnectTimeWait: 2000,
  },
  logger
);

await factory.connect();

// Create instance with ServiceMap
const instance = factory.createInstance(new UserServiceMap(), {
  discovery: {
    version: '1.0.0',
  },
});

3. Subscribe to Topics

// ✅ Types are inferred automatically from ServiceMap!
instance.subscribe('user.create', async (req) => {
  const { username, email } = req.data; // ✅ Fully typed!
  
  const userId = await createUser(username, email);
  
  // ✅ TypeScript validates response type
  return {
    userId,
    username,
  };
});

4. Request from Another Service

// Make request
const response = await instance.request('user.create', {
  username: 'john',
  email: '[email protected]',
});

// ✅ Use type guard for type narrowing
if (isSuccessResponse(response)) {
  console.log(response.data.userId); // ✅ string (not undefined!)
  console.log(response.data.username); // ✅ string
}

Type System

Automatic Type Inference

❌ DON'T add explicit type annotations:

// ❌ WRONG - types will be 'any'
instance.subscribe('user.create', async (req: MSRequest) => {
  req.data // ❌ type is 'any'
});

✅ DO let TypeScript infer types automatically:

// ✅ CORRECT - types inferred from ServiceMap
instance.subscribe('user.create', async (req) => {
  req.data.username // ✅ string
  req.data.email    // ✅ string
});

Utility Types

import { InferRequest, InferResponse, InferTopics } from '@nats-ms/core';

type Services = typeof UserServiceMap.prototype.services;

// Extract all topic names
type Topics = InferTopics<Services>; // 'user.create' | 'user.get'

// Extract request type for specific topic
type CreateRequest = InferRequest<Services, 'user.create'>;
// { username: string; email: string; }

// Extract response type
type CreateResponse = InferResponse<Services, 'user.create'>;
// { userId: string; username: string; }

Type Guards

import { isSuccessResponse, isErrorResponse } from '@nats-ms/core';

const response = await instance.request('user.create', data);

// Narrow to success type
if (isSuccessResponse(response)) {
  response.data.userId // ✅ string (NOT undefined)
}

// Narrow to error type
if (isErrorResponse(response)) {
  console.log(response.errorCode);    // ✅ number
  console.log(response.errorMessage); // ✅ string
}

Messaging Patterns

Request-Reply

const response = await instance.request('user.get', { userId: '123' });

Pub-Sub (Fire and Forget)

instance.send('user.updated', { userId: '123', username: 'john' });

Broadcast

instance.send('notification.alert', { message: 'Alert!' }, { broadcast: true });

Scatter-Gather (Request All)

const responses = await instance.requestAll('inventory.check', {
  productId: 'p1',
});

// Aggregate results from all instances
const totalStock = responses
  .filter(isSuccessResponse)
  .reduce((sum, r) => sum + r.data.quantity, 0);

First Responder

const response = await instance.requestFirst('cache.get', { key: 'user:123' });

Sharding

instance.sendSharded(
  'analytics.track',
  { userId: '123', event: 'click' },
  'user:123', // shard key
  10 // total shards
);

Resilience Patterns

Circuit Breaker

const response = await instance.request(
  'external-api.call',
  { data },
  {
    circuitBreaker: {
      failureThreshold: 5,
      resetTimeout: 30000,
    },
  }
);

Retry Strategy

const response = await instance.request(
  'unstable-service.call',
  { data },
  {
    retry: {
      maxRetries: 3,
      retryDelay: 1000,
      backoffMultiplier: 2,
    },
  }
);

Timeout

const response = await instance.request(
  'slow-service.call',
  { data },
  {
    timeout: 5000, // 5 seconds
  }
);

Middleware

// Logging middleware
instance.use(async (req, next) => {
  console.log(`Received: ${req.topic}`);
  const response = await next();
  console.log(`Responded: ${response.isSuccess}`);
  return response;
});

// Auth middleware
instance.use(async (req, next) => {
  const token = req.headers['authorization'];
  if (!token) {
    return {
      isSuccess: false,
      errorCode: 401,
      errorMessage: 'Unauthorized',
    };
  }
  return next();
});

Auto-Discovery

Services automatically announce themselves:

const instance = factory.createInstance(new UserServiceMap(), {
  discovery: {
    enabled: true,        // default: true
    version: '1.0.0',
    announceInterval: 30000, // 30 seconds
    // topics auto-detected from subscriptions
  },
});

Metrics

Built-in Prometheus-compatible metrics:

const metrics = factory.getMetrics();

// Available metrics:
// - messages_sent_total
// - messages_received_total
// - request_duration_seconds
// - errors_total
// - active_connections

Error Handling

import { MSError } from '@nats-ms/core';

instance.subscribe('user.create', async (req) => {
  if (await userExists(req.data.email)) {
    throw new MSError(409, 'User already exists');
  }
  
  return createUser(req.data);
});

Lifecycle Events

import { LifecycleEvent } from '@nats-ms/core';

instance.on(LifecycleEvent.REQUEST_START, (data) => {
  console.log(`Request started: ${data.topic}`);
});

instance.on(LifecycleEvent.REQUEST_END, (data) => {
  console.log(`Request completed in ${data.duration}ms`);
});

instance.on(LifecycleEvent.REQUEST_ERROR, (data) => {
  console.error(`Request error: ${data.error.message}`);
});

Graceful Shutdown

process.on('SIGINT', async () => {
  await instance.shutdown();
  await factory.close();
  process.exit(0);
});

API Reference

NATSInstanceFactory

  • create(config, logger?) - Create factory instance
  • connect() - Connect to NATS server
  • createInstance(serviceMap, options?) - Create typed instance
  • createUnmappedInstance<T>(options?) - Create instance without ServiceMap
  • close() - Close connection
  • getMetrics() - Get metrics instance

NATSInstance

  • subscribe(topic, handler, options?) - Subscribe to topic
  • request(topic, data?, options?) - Request-reply pattern
  • requestAll(topic, data?, options?) - Scatter-gather pattern
  • requestFirst(topic, data?, options?) - First responder pattern
  • send(topic, data?, options?) - Pub-sub pattern
  • sendSharded(topic, data, shardKey, totalShards?) - Sharded messaging
  • use(middleware) - Add middleware
  • unsubscribe(topic) - Unsubscribe from topic
  • shutdown(options?) - Graceful shutdown
  • getSubscriptions() - Get active subscriptions

CLI Tool

For quick project scaffolding, use the CLI:

npm install -g @nats-ms/cli
nats-ms init my-service

See @nats-ms/cli for details.

Requirements

  • Node.js >= 18.0.0
  • NATS Server 2.x

Links

License

MIT © 2025 TechnoFrog LLC