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

@munesoft/api-normalizer

v1.0.0

Published

Universal utility to normalize inconsistent API responses into a consistent, predictable structure.

Readme

@munesoft/api-normalizer

Universal API response normalizer. Stop writing one-off mapping code for every integration. Define a schema once, get consistent output everywhere.

npm version zero dependencies TypeScript Tests


The Problem

Every API speaks a slightly different language:

// Stripe user
{ "id": "cus_abc", "name": "Acme Corp", "email": "[email protected]" }

// Legacy internal API
{ "user_id": 42, "full_name": "Acme Corp", "email_address": "[email protected]" }

// GitHub
{ "login": "octocat", "id": 583231, "avatar_url": "..." }

Without a tool like this, you write fragile, repetitive glue code in every service, every handler, every component. A field gets renamed upstream and your app silently breaks.

@munesoft/api-normalizer solves this by letting you define a mapping schema once and normalizing any response into a predictable shape.


Quick Start

npm install @munesoft/api-normalizer
import { normalize } from "@munesoft/api-normalizer";

const raw = { user_id: 42, full_name: "Jane Doe", email_address: "[email protected]" };

const result = normalize(raw, {
  id:    ["id", "user_id", "uuid"],
  name:  ["name", "full_name", "username"],
  email: ["email", "email_address", "user_email"],
});

// result.success === true
// result.data  === { id: 42, name: "Jane Doe", email: "[email protected]" }

Before / After

Before — scattered glue code

// user-service.ts
const user = {
  id:    response.id ?? response.user_id ?? response.uuid,
  name:  response.name ?? response.full_name ?? response.username,
  email: response.email ?? response.email_address ?? response.user_email,
};

// order-service.ts — different shape, same pain
const order = {
  id:     payload.order_id ?? payload.id ?? payload.orderId,
  total:  Number(payload.total_amount ?? payload.amount ?? payload.price),
  status: payload.status ?? payload.order_status ?? payload.state,
};

After — one schema, zero surprises

import { normalize } from "@munesoft/api-normalizer";

const USER_SCHEMA = {
  id:    ["id", "user_id", "uuid"],
  name:  ["name", "full_name", "username"],
  email: ["email", "email_address", "user_email"],
};

const ORDER_SCHEMA = {
  id:     ["id", "order_id", "orderId"],
  total:  { keys: ["total_amount", "amount", "price"], type: "number" as const },
  status: ["status", "order_status", "state"],
};

const user  = normalize(userPayload,  USER_SCHEMA);
const order = normalize(orderPayload, ORDER_SCHEMA);

Schema Mapping Guide

Shorthand: string array

The simplest form. Candidates are tried left-to-right; the first non-empty value wins.

const schema = {
  id:   ["id", "user_id", "uuid"],
  name: ["name", "full_name"],
};

Full FieldMapping descriptor

import type { Schema } from "@munesoft/api-normalizer";

const schema: Schema = {
  id: {
    keys:      ["id", "user_id"],   // candidates, tried in order
    default:   null,                // fallback when nothing resolves
    type:      "number",            // coerce to number
    required:  true,                // throw in strict mode if missing
    transform: (v) => String(v).padStart(6, "0"), // custom transform
  },
};

Dot-notation for nested data

const schema: Schema = {
  city: { keys: ["address.city", "location.city", "city"] },
  lat:  { keys: ["address.coordinates.lat", "lat"] },
};

normalize({ address: { city: "Nairobi", coordinates: { lat: -1.286 } } }, schema);
// → { city: "Nairobi", lat: -1.286 }

API Reference

normalize(data, schema, options?)

| Parameter | Type | Description | |-----------|------|-------------| | data | unknown | Raw API response — object, array, null, or undefined | | schema | Schema | Field mapping definition | | options | NormalizerOptions | Optional flags (see below) |

Returns NormalizeResult<T>:

// Success
{ success: true; data: T; _schema?: Schema; _diff?: DiffEntry[] }

// Failure (strict mode)
{ success: false; error: string; missingFields?: string[] }

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | strict | boolean | false | Throw on missing required fields | | coerceTypes | boolean | false | Enable global auto-coercion (type hints still work without this) | | inferSchema | boolean | false | Attach auto-inferred schema to result as _schema | | logDiff | boolean | false | Attach mapping diff to result as _diff | | plugins | Plugin[] | [] | Post-processing plugin pipeline |


Type Coercion

Set type on any FieldMapping to coerce the resolved value:

| Type | Behaviour | |------|-----------| | "number" | "42"42, true1 | | "string" | 42"42" | | "boolean" | "true" / "1" / "yes"true | | "date" | Any parseable date → ISO 8601 string | | "auto" | Detects numeric strings, booleans, and ISO dates |

const schema: Schema = {
  age:       { keys: ["age"],        type: "number"  },
  active:    { keys: ["active"],     type: "boolean" },
  createdAt: { keys: ["created_at"], type: "date"    },
};

Array Normalization

Pass an array and every element is normalized automatically:

const users = [
  { user_id: 1, full_name: "Alice" },
  { id: 2,      name: "Bob"        },
  { uuid: 3,    username: "Carol"  },
];

const result = normalize(users, USER_SCHEMA);
// result.data → [{ id:1, name:"Alice" }, { id:2, name:"Bob" }, { id:3, name:"Carol" }]

Strict Mode

Enable strict: true to catch missing required fields in production:

const result = normalize(payload, schema, { strict: true });

if (!result.success) {
  console.error(result.error);          // "Missing required fields: id, email"
  console.error(result.missingFields);  // ["id", "email"]
}

Diff Logging

See exactly what changed during normalization — great for debugging:

const result = normalize(raw, schema, { logDiff: true });

result._diff?.forEach(entry => {
  console.log(`${entry.sourceKey} → ${entry.outputKey}`, {
    raw:        entry.rawValue,
    normalized: entry.normalizedValue,
    coerced:    entry.coerced,
    usedDefault: entry.usedDefault,
  });
});

Plugin System

Plugins run after all field mappings and receive the fully normalized object:

import { normalize, Plugin } from "@munesoft/api-normalizer";

const auditPlugin: Plugin = {
  name: "audit",
  transform: (data, raw) => ({
    ...data,
    _normalizedAt: new Date().toISOString(),
    _source: (raw as any).__source ?? "unknown",
  }),
};

normalize(payload, schema, { plugins: [auditPlugin] });

Schema Inference

Quickly generate a starter schema from a sample response:

import { inferSchema } from "@munesoft/api-normalizer";

const sample = { user_id: 1, full_name: "Jane", email_address: "[email protected]" };
const schema = inferSchema(sample);
// Refine the output, then use it in normalize()

TypeScript Support

Full generics support for typed output:

interface User {
  id: number;
  name: string;
  email: string;
}

const result = normalize<User>(raw, schema);

if (result.success) {
  const user = result.data; // typed as User
}

Real-World Examples

Stripe + Internal API → unified User

const CUSTOMER_SCHEMA: Schema = {
  id:        ["id"],
  name:      ["name", "company_name", "full_name"],
  email:     ["email", "email_address"],
  createdAt: { keys: ["created", "created_at", "createdAt"], type: "date" },
  plan:      { keys: ["plan", "subscription_plan"], default: "free" },
};

// Works with Stripe, your own DB, or any other source
const stripeResult   = normalize(stripeCustomer,   CUSTOMER_SCHEMA);
const internalResult = normalize(internalDbRecord, CUSTOMER_SCHEMA);
// Both produce the same shape ✓

Normalizing paginated list responses

const ORDER_SCHEMA: Schema = {
  id:     ["id", "order_id", "orderId"],
  total:  { keys: ["total", "amount", "total_price"], type: "number" },
  status: ["status", "state", "order_status"],
  items:  { keys: ["items", "line_items", "products"], default: [] },
};

const result = normalize(apiResponse.orders, ORDER_SCHEMA);

Performance

  • Zero dependencies — no bundle bloat
  • Field resolution is a simple loop over candidate keys using Object.prototype lookups — O(k) per field where k is the number of candidates
  • Array normalization is O(n × f) where n = items and f = schema fields
  • Suitable for normalizing thousands of records synchronously in a single request

Browser Support

Works in any environment that supports ES2017 (async/await, Object.entries). No window, document, or Node.js-specific APIs are used.


License

MIT © munesoft