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

@minipim/sdk

v0.4.0

Published

Typed TypeScript client for the MiniPim API.

Readme

@minipim/sdk

Typed TypeScript client for the MiniPim API. Generated from the OpenAPI spec at /docs/json and wrapped with openapi-fetch plus a webhook-signature helper.

Install

pnpm add @minipim/sdk

Requires Node 18+ (for native fetch) or a fetch polyfill. Ships both ESM and CommonJS builds (v0.3.0+) — import and require both work, including in CJS test tooling. The root entry imports no node: modules, so createMinipimClient and the attribute/pagination helpers are safe in Edge runtimes.

baseUrl is the origin (https://api.minipim.com); every endpoint path already includes /v1/..., so you pass pim.GET('/v1/products'), not a pre-joined URL.

Quick start

import { createMinipimClient } from '@minipim/sdk';

const pim = createMinipimClient({
  baseUrl: 'https://api.minipim.com',
  organizationId: '00000000-0000-0000-0000-000000000001',
  apiKey: process.env.MINIPIM_API_KEY!,
});

// List products
const { data, error } = await pim.GET('/v1/products', {
  params: { query: { limit: 50, sortBy: 'name', sortDir: 'asc' } },
});
if (error) throw new Error(error.error.message);
console.log(`got ${data.data.length} products, hasMore: ${data.hasMore}`);

// Single product with locale + channel resolution
const product = await pim.GET('/v1/products/{id}', {
  params: {
    path: { id: '0b8c5d3a-…' },
    query: { locale: 'en_US', channel: 'headless-main' },
  },
});
console.log(product.data?.resolvedAttributes);

Authentication

API keys (recommended for service-to-service):

const pim = createMinipimClient({
  baseUrl: 'https://api.minipim.com',
  organizationId: '<your-org-uuid>',
  apiKey: 'pim_xxxxxxxxxxxx',
});

Issue keys in the admin UI at /api-keys. x-organization-id is required separately — API keys identify the principal, not the tenant.

Dev mode (header auth) — only when the deployment is started with PIM_AUTH=header:

const pim = createMinipimClient({
  baseUrl: 'http://localhost:4100',
  organizationId: '00000000-0000-0000-0000-000000000001',
  userId: 'dev-test',
});

Webhooks

The headless connector POSTs events to your URL with an HMAC-SHA256 signature in X-MiniPim-Signature-256. Verify against the raw body — re-stringified JSON breaks the signature.

Next.js App Router

import { verifyMinipimWebhook } from '@minipim/sdk/webhook';

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get('x-minipim-signature-256') ?? '';
  // Node verifier is synchronous — no await.
  const ok = verifyMinipimWebhook({
    rawBody: raw,
    signature: sig,
    secret: process.env.MINIPIM_WEBHOOK_SECRET!,
  });
  if (!ok) return new Response('invalid signature', { status: 401 });

  const event = JSON.parse(raw);
  // event.name = 'product.updated' | 'category.deleted' | ...
  // event.entity_id, event.payload, etc.
  // Handle + return 2xx — non-2xx is silently dropped in v1 (no retries yet).
  return Response.json({ ok: true });
}

Edge / Cloudflare Workers / Vercel Edge

import { verifyMinipimWebhookEdge } from '@minipim/sdk/webhook-edge';

const ok = await verifyMinipimWebhookEdge({ rawBody, signature, secret });
if (!ok) return new Response('invalid signature', { status: 401 });

Both functions:

  • Constant-time comparison (safe against timing attacks).
  • Return false on malformed signatures (don't throw).

Node verifier is synchronous; Edge verifier is async. The Node one (@minipim/sdk/webhook) returns boolean — use it directly in if (!verifyMinipimWebhook(...)). The Edge one (@minipim/sdk/webhook-edge) returns Promise<boolean> — you must await it. They live on separate entry points so the Node node:crypto import never lands in an Edge bundle.

Pagination

Don't hand-roll the hasMore loop — the SDK ships paginate, a generic async iterator over any list endpoint:

import { paginate, collectAll } from '@minipim/sdk';
import type { components } from '@minipim/sdk';

type Product = components['schemas']['ProductSelect'];

// stream
for await (const p of paginate<Product>(pim, '/v1/products', { query: { status: 'active' } })) {
  await ingest(p);
}

// or collect everything
const all = await collectAll<Product>(pim, '/v1/products');

paginate manages limit/offset, honors the server's hasMore, and defaults to the 200-row max page size (override with pageSize). Pass withTotal: true in query if you need total — it adds one COUNT(*), so leave it off on hot reads.

Endpoints that return a plain array instead of the envelope (/v1/categories without ?limit=, /v1/attributes, /v1/products/{id}/variants) are handled too (v0.3.0+): the array is treated as the one-and-only page, so collectAll works uniformly across every list endpoint.

Attribute helpers

Attribute values are unknown and keyed by (locale, channel). List responses return the raw { code: [{ locale, channel, value }] } shape (only product detail with ?locale=&channel= returns a flat resolvedAttributes). The SDK ships the flatten + coercion helpers so you don't reimplement them:

import {
  flattenAttributes, getAttribute, asMoney, asMeasurement, formatMoney,
} from '@minipim/sdk';

// resolve one code for a (locale, channel), with server-matching fallback
const desc = getAttribute(product.attributes, 'description', { locale: 'en_US', channel: 'headless-main' });

// flatten the whole bag (client-side equivalent of resolvedAttributes, for lists)
const flat = flattenAttributes(product.attributes, { locale: 'en_US', channel: 'headless-main' });

// coerce money / measurement shapes
const price = asMoney(getAttribute(product.attributes, 'price'));      // { amount_cents, currency } | null
if (price) console.log(formatMoney(price));                            // "$18.99"
const weight = asMeasurement(getAttribute(product.attributes, 'weight')); // { amount, unit } | null

Also available at the @minipim/sdk/attributes subpath.

Modifier helpers

Modifiers are order-line options that don't create a SKU (engraving, add-ons, print/hardware choices). They live at GET /v1/products/{id}/modifiers and GET /v1/modifiers/{id} — product detail only flags their presence via modifierCount / hasRequiredModifiers, so a PDP with required options must fetch the list. Price deltas are in config.priceAdjusters, keyed by choice value slug; money deltas are integer cents + ISO currency (not dollars) and percentage deltas are integer basis points. These helpers resolve and price them:

import {
  getModifierChoices, getModifierPriceAdjuster, applyPriceAdjuster, formatPriceAdjuster,
  getAttribute, asMoney, formatMoney,
} from '@minipim/sdk';

const [mod] = await pim.GET('/v1/products/{id}/modifiers', { params: { path: { id } } }).then((r) => r.data);

// choices joined with their structured delta
for (const c of getModifierChoices(mod)) {
  // c.priceAdjuster is null for free choices (and product_list modifiers, whose
  // price lives on a referenced product — see DEVELOPERS.md)
  const suffix = c.priceAdjuster ? ` (${formatPriceAdjuster(c.priceAdjuster)})` : '';
  console.log(`${c.label}${suffix}`);   // "Pair of LED Lights (+$170.00)"
}

// price a selected choice against the base price
const base = asMoney(getAttribute(product.attributes, 'price'));  // { amount_cents, currency }
const chosen = getModifierPriceAdjuster(mod, 'pair_of_led_lights');
if (base && chosen) formatMoney(applyPriceAdjuster(base, chosen)); // "$869.00"

asPriceAdjuster / asModifierConfig narrow the opaque config; they reject the pre-2026-07 legacy shape (bare value in dollars) so the SDK never misreads dollars as cents. Also available at the @minipim/sdk/modifiers subpath.

Error handling

res.error is a per-path union that's awkward to narrow. Use the shared guard:

import { isMinipimError, getErrorMessage } from '@minipim/sdk';

const { data, error } = await pim.GET('/v1/products/{id}', { params: { path: { id } } });
if (error) {
  throw new Error(getErrorMessage(error, 'fetch failed'));   // pulls error.error.message
}

Reconciliation after downtime

?updatedSince=<iso8601> filters the product list to rows modified after a timestamp. Pair with hasMore to walk the changeset back to current state:

async function catchUp(since: string) {
  for await (const p of allProducts({ updatedSince: since })) {
    await ingest(p);
  }
}

TypeScript reference

import type { paths, components, operations } from '@minipim/sdk';
type Product = components['schemas']['ProductSelect'];

The generated paths, components, and operations cover the full surface at /docs/json. Use them to type DTOs, request handlers, etc.

Related

  • Narrative integration guide — auth, pagination, locale/channel resolution, webhook contract, common integration shapes.
  • Swagger UI — interactive endpoint reference, try-it-out enabled.
  • OpenAPI spec — raw JSON, what this SDK is generated from.

License

Apache-2.0.