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

zoopse-crm-shared

v1.2.2

Published

Shared utilities for the Zoopse CRM backend. This is a small, dependency-light **internal library** (npm package `zoopse-crm-shared`) consumed by every microservice — the API Gateway, Auth, Sales, Subscription, and Notification services — to keep cross-cu

Readme

zoopse-crm-shared

Shared utilities for the Zoopse CRM backend. This is a small, dependency-light internal library (npm package zoopse-crm-shared) consumed by every microservice — the API Gateway, Auth, Sales, Subscription, and Notification services — to keep cross-cutting concerns (errors, responses, gateway auth, logging, Kafka, helpers) consistent across the platform.

It is not a service: there is no server, database, or routes here — just ES module exports.

Table of contents

Installation & usage

The package is ESM ("type": "module"). Each service depends on it ("zoopse-crm-shared": "^1.2.x") and imports named exports from the package root:

import {
  CustomError, BadRequestError, NotFoundError, NotAuthorizedError, FileTooLargeError, ServerError,
  verifyGatewayRequest,
  sendCommonResponse,
  winstonLogger,
  KafkaPublisher,
  firstLetterUppercase, lowerCase, toUpperCase, isEmail, isDataURL
} from 'zoopse-crm-shared';

Runtime dependencies: http-status-codes, jsonwebtoken, kafkajs, winston, winston-elasticsearch.

Several services install this from a local path / workspace and run a predev check (npm ls zoopse-crm-shared) to ensure it resolves before booting.

What's exported

| Export | Module | Kind | Purpose | |--------|--------|------|---------| | CustomError | src/errors.js | class | Base error with comingFrom + serializeErrors() | | BadRequestError | src/errors.js | class | 400 | | NotFoundError | src/errors.js | class | 404 | | NotAuthorizedError | src/errors.js | class | 401 | | FileTooLargeError | src/errors.js | class | 413 | | ServerError | src/errors.js | class | 503 | | verifyGatewayRequest | src/gateway-middleware.js | middleware factory | Validates the gatewaytoken header | | sendCommonResponse | src/commonResponse.js | function | Standard success-response envelope | | winstonLogger | src/logger.js | factory | Console + optional Elasticsearch logger | | KafkaPublisher | src/kafka.publisher.js | class | Lazy-connecting Kafka producer | | firstLetterUppercase, lowerCase, toUpperCase, isEmail, isDataURL | src/helper.js | functions | String/validation helpers |


Errors

A small hierarchy of typed HTTP errors. All extend CustomError, which captures a comingFrom source label and exposes serializeErrors() — the shape every service's error handler returns for a CustomError.

export class CustomError extends Error {
  constructor(message, comingFrom) { super(message); this.comingFrom = comingFrom; }
  serializeErrors() {
    return { message: this.message, statusCode: this.statusCode, status: this.status, comingFrom: this.comingFrom };
  }
}

| Class | statusCode | status | |-------|--------------|----------| | BadRequestError | 400 | error | | NotAuthorizedError | 401 | error | | NotFoundError | 404 | error | | FileTooLargeError | 413 (REQUEST_TOO_LONG) | error | | ServerError | 503 (SERVICE_UNAVAILABLE) | error |

Usage — throw with a message + a source label; the centralized error handler serializes it:

import { BadRequestError } from 'zoopse-crm-shared';
if (!email) throw new BadRequestError('email is required', 'SignUp Ctrl');

Each service's errorHandler checks err instanceof CustomError and returns err.serializeErrors() with err.statusCode. The gateway additionally maps upstream HTTP statuses back to these classes (400→BadRequestError, 401→NotAuthorizedError, 404→NotFoundError, 413→FileTooLargeError, else ServerError).

Note: a bare new CustomError(message, comingFrom) has no statusCode/status; handlers fall back to a default (usually 400 or 500). Prefer a concrete subclass.


Common response

sendCommonResponse(res, statusCode, message, data?, pagination?)

Writes the platform's standard success envelope and returns the Express response:

{ "success": true, "message": "...", "data": { }, "pagination": { } }
  • data is included only when not undefined.
  • pagination is included only when truthy (services pass { total, page, limit } or { total, skip, limit, hasMore }).
import { sendCommonResponse } from 'zoopse-crm-shared';
sendCommonResponse(res, 200, 'leads fetched', leads, { total, page, limit });

Gateway middleware

verifyGatewayRequest(gatewaySecret = process.env.GATEWAY_JWT_SECRET)

An Express middleware factory that protects downstream services so they only accept traffic from the API Gateway. It:

  1. Requires a gatewaytoken request header (else NotAuthorizedError).
  2. Verifies the token's JWT signature against gatewaySecret.
  3. Requires the decoded payload.id to be one of the known service identifiers: auth, sales, notification, subscription.
  4. Calls next() on success; otherwise throws NotAuthorizedError('Request coming from invalid source').

The gateway signs this token per outbound service (jwt.sign({ id: '<service>' }, GATEWAY_JWT_TOKEN)), so GATEWAY_JWT_TOKEN on the gateway must equal the gatewaySecret passed here by each service.

import { verifyGatewayRequest } from 'zoopse-crm-shared';
app.use('/api/v1/auth', verifyGatewayRequest(config.GATEWAY_JWT_SECRET), authRoutes());

The same secret is also used for service-to-service gRPC gatewaytoken metadata (e.g. Subscription → Auth GetTenantInvoiceProfile).


Logger

winstonLogger(elasticsearchNode, name, level)

Creates a Winston logger with a colorized console transport and an optional Elasticsearch transport. Elasticsearch is enabled only when:

  • elasticsearchNode (URL) is provided, and
  • NODE_ENV !== 'test', and
  • DISABLE_ELASTICSEARCH_LOGGER !== '1'.

defaultMeta.service is set to name, and exitOnError is false. winston-elasticsearch is loaded lazily (via createRequire) only when ES logging is active, so test/dev runs don't pull it in.

import { winstonLogger } from 'zoopse-crm-shared';
const log = winstonLogger(process.env.ELASTIC_SEARCH_URL, 'auth-service', 'debug');

Most services wrap this in a local getLogger() that falls back to a plain console logger when ES is unconfigured.


Kafka publisher

new KafkaPublisher(topic, { KAFKA_CLIENT_ID, KAFKA_BROKERS })
await publisher.publish(payload)   // payload must include a `key`

A thin KafkaJS producer wrapper:

  • Requires a topic (throws otherwise).
  • Parses KAFKA_BROKERS (comma-separated; defaults to localhost:9092) and uses KAFKA_CLIENT_ID (default default-client).
  • Lazily connects on first publish and reuses the connection.
  • Sends one message per call: { key: payload.key, value: JSON.stringify(payload) }.
import { KafkaPublisher } from 'zoopse-crm-shared';
const publisher = new KafkaPublisher('notification', {
  KAFKA_CLIENT_ID: config.KAFKA_CLIENT_ID,
  KAFKA_BROKERS: config.KAFKA_BROKERS
});
await publisher.publish({ key: 'create-notification', data: { /* ... */ } });

Consumers read message.value and JSON.parse it — so the full payload (including key) is serialized into the value. Used by: the gateway (lead webhooks, bulk upload), Sales (overdue-task notifications), and Subscription (webhook fan-out).

The wrapper logs via console, not the Winston logger, and has no built-in retry/backoff beyond KafkaJS defaults.


Helpers

src/helper.js — small pure functions:

| Function | Behaviour | |----------|-----------| | firstLetterUppercase(str) | Title-cases each space-separated word (lowercases the rest). | | lowerCase(str) | str.toLowerCase(). | | toUpperCase(str) | Upper-cases, but returns the input unchanged when falsy. | | isEmail(email) | Regex email validation. | | isDataURL(value) | True if the string is a data: URL. |

import { isEmail } from 'zoopse-crm-shared';
existingUser = isEmail(username)
  ? await getUserByEmail(username)
  : await getUserByUsername(username);

How services use it

| Export | Primary consumers | |--------|-------------------| | Error classes + CustomError | every service (thrown in controllers/services; serialized in each error handler) | | sendCommonResponse | every service's controllers | | verifyGatewayRequest | Auth, Sales, Subscription (route guards); Auth gRPC also reuses the secret | | winstonLogger | every service (usually behind a local getLogger) | | KafkaPublisher | Gateway, Sales, Subscription | | Helpers | Auth (isEmail), Sales, others as needed |

This single library is what keeps response shapes, error contracts, the gateway trust boundary, log formatting, and Kafka messaging identical across services — change it here and every service inherits it on the next version bump.

Versioning & publishing

  • Package: zoopse-crm-shared, current version 1.2.1, license ISC, "type": "module", entry index.js.
  • Tests: npm test (node --test) — see tests/.
  • All public API is re-exported from index.js; add new exports there.
  • Consumers pin ^1.2.x; bump the version when changing any exported contract (error shapes, response envelope, the gateway token's allowed ids, etc.) since all five services depend on it.