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

@classytic/catalog

v0.5.0

Published

Commerce catalog kernel for MongoDB — products, variants, offers, pricing layers, modifiers, attributes, categories, offer execution modules, channels, bridges

Readme

@classytic/catalog

Commerce catalog kernel for MongoDB. Products, variants, offers, pricing layers, modifiers, attributes, categories, offer execution modules, channels, and integration bridges.

Framework-agnostic by design. Catalog ships pure primitives — Mongoose models, mongokit repositories, pure-domain utilities, Zod schemas, and a typed event transport. There are no Arc / Fastify / Express / Next.js imports anywhere in the package, and no ./http adapter ships. Hosts wire catalog into their framework of choice with ~5 lines of glue code, calling catalog.repositories.* directly — see the integration recipes below.

Design principles

  1. Zero config to startcreateCatalog({ connection }) works.
  2. Progressive disclosure — four API levels: zero config, simple knobs, module opt-in, full config. Each is a natural step up, never a rewrite.
  3. Composition over configuration — modules opt in via modules: { offers: true, ... } rather than monolithic presets.
  4. Bounded contexts — Catalog Definition, Offers, Offer Execution, Channels, Projections, Integration. Each is an internal subdomain with a clear responsibility boundary.
  5. MongoDB-native via Mongoose + @classytic/mongokit as peer deps. Not DB-agnostic — that's a deliberate simplification for v1.
  6. Framework-agnostic — no framework imports, no HTTP adapter shipped. Host apps wire their own routes against catalog.repositories.*.
  7. Better Auth aligned — storage field is always organizationId.
  8. Zod as source of truth — every schema is a Zod schema, types derive via z.infer<>, runtime validation at service boundaries.
  9. No breaking changes post-1.0.

Package structure

src/
  catalog-core/          Product, Variant, Attribute, Category, Exclusion,
                         Modifier, Compliance, Relationship, RankingSignals
  offers/                Offer, PricingLayers, OfferAvailability,
                         CompositionPolicy, OfferBoost
  offer-execution/       OfferExecutionModule interface + 11 built-in modules
                         (standard, affiliate, preorder, reservation, voucher,
                          auction, rental, subscription, course_offering,
                          dropship, pod)
  channels/              ChannelPublication value object
  projections/           SearchProjection + flat denorm builder/plugin
  integration/           13 bridge interfaces (inventory, pricing, tax,
                         media, currency, recommendation, review, promotion,
                         loyalty, ranking, composition, compliance, uom)
  product-types/         ProductTypeHandler interface + physical / service
  value-objects/         Money, Identifiers, ProductRef, Translatable
  events/                41 typed event contracts
  engine/                createCatalog factory + CatalogEngine + tenant resolver
  validators/            Zod schemas (exported via @classytic/catalog/schemas)

Catalog ships only the pure primitives. There is no src/http/ and no @classytic/catalog/http subpath — HTTP integration is host-owned, see the recipes below.

Peer dependencies

  • mongoose >= 9.4.1 (required)
  • @classytic/mongokit >= 3.16.0 (required)
  • @classytic/repo-core >= 0.6.0 (required)
  • @classytic/primitives >= 0.5.0 (required)
  • zod >= 4.0.0 (required)

No framework peer deps. No optional plugins in the peer list.

Quick start

import mongoose from 'mongoose';
import { createCatalog } from '@classytic/catalog';

await mongoose.connect(process.env.MONGO_URI!);

const catalog = await createCatalog({
  connection: mongoose.connection,
  multiTenant: true, // enable per-tenant scoping (default field: organizationId)
  defaultCurrency: 'USD',
  modules: {
    offers: true, // enable marketplace-style offers + buy-box
  },
});

// Use repositories directly in any runtime (Node, Fastify, Express, Next.js, CLI)
const ctx = { organizationId: 'org_123', actorId: 'user_456' };
const product = await catalog.repositories.product.create(
  { name: 'T-Shirt', productType: 'physical' },
  ctx,
);

Integration recipes

Catalog has no HTTP adapter — the engine returns repositories that hosts call directly from their framework's request handler. Each recipe below is ~15 lines of host glue: parse the request → call catalog.repositories.<entity>.<verb>(...) → serialize the response.

The pattern (any framework)

The recipe is the same in every framework — read the request, build a CatalogContext, call a repository / utility method, return the result. No catalog-specific adapter is involved.

import { catalog } from './catalog.js';

// Inside any HTTP handler — Arc, Fastify, Express, Hono, Next.js Route
// Handler, tRPC, etc.
async function handlePostProduct(req, reply) {
  const ctx = {
    organizationId: req.user?.orgId,
    actorId: req.user?.id,
  };
  const product = await catalog.repositories.product.create(req.body, ctx);
  reply.send({ data: product });
}

async function handleGetProduct(req, reply) {
  const ctx = { organizationId: req.user?.orgId, actorId: req.user?.id };
  const doc = await catalog.repositories.product.getById(req.params.id, ctx);
  if (!doc) return reply.code(404).send({ error: 'Not Found' });
  reply.send({ data: doc });
}

The repositories surface is whatever mongokit gives you (getAll, getById, create, update, delete, count, aggregate) plus the domain methods catalog adds (product.delete(id, ctx, { cascade }), offer.commit(input, ctx), etc.). Pick the recipe for your framework's auth + req/res wiring; the catalog call is always the same.

CLI / cron / workers (no HTTP)

import { catalog } from './catalog.js';

const ctx = { organizationId: 'org_123', actorId: 'system' };

const product = await catalog.repositories.product.create(
  { name: 'Widget', productType: 'physical' },
  ctx,
);

const result = await catalog.repositories.offer.commit(
  { offerId: 'offer_abc', quantity: 1, idempotencyKey: 'job_run_xyz' },
  ctx,
);

Operational helpers

All on the repositories — no extra service surface. Full runbooks live in AGENTS.md.

import { SkuCollisionError } from '@classytic/catalog/catalog-core';

// Typed SKU/barcode collision contract on create — pre-flight check plus
// E11000 mapping; the unique index stays the authoritative guard.
try {
  await catalog.repositories.product.create(input, ctx);
} catch (err) {
  if (err instanceof SkuCollisionError) {
    // err.field: 'sku' | 'barcode', err.values, err.conflictingProductId?
  }
}

// Denormalized category counts — failures emit catalog:category.count_desync.
await catalog.repositories.category.validateCategoryCounts(ctx); // report drift
await catalog.repositories.category.repairCategoryCounts(ctx);   // re-aggregate + fix

// Remove variants that are both user-disabled AND auto-disabled (frees SKUs).
await catalog.repositories.product.compactVariants(productId, ctx);

// Batched create-or-update keyed by per-tenant-unique slug.
await catalog.repositories.product.bulkUpsertProducts(inputs, ctx, { errorMode: 'continue' });

// Scheduled publishing — call from the host's cron (no scheduler ships).
await catalog.repositories.product.runScheduledPublishing(ctx);

Zod schemas for frontends and SDKs

Catalog exports every Zod schema via the @classytic/catalog/schemas subpath. Frontends, SDKs, and test code can import them to get runtime validation + TypeScript types without pulling in the rest of the package:

import {
  productCreateSchema,
  offerCreateSchema,
  type OfferCreateInput,
} from '@classytic/catalog/schemas';

// Client-side form validation — same Zod schema the backend uses.
const result = productCreateSchema.safeParse(formValues);
if (!result.success) {
  // handle validation errors
}

Testing

# Run the fast unit suite (no DB)
npm run test:unit

# Run integration tests (MongoDB memory server)
npm run test:integration

# Run both in CI
npm test

# Typecheck only
npm run typecheck

License

MIT