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/supplier-performance

v0.1.1

Published

Framework-agnostic supplier performance engine — on-time-delivery, defect, and price-variance tracking with append-only event log + cached scorecards. Powered by MongoDB via @classytic/mongokit. Zero framework deps — usable from Arc, Express, Nest, Ne

Readme

@classytic/supplier-performance

Framework-agnostic supplier performance engine — on-time-delivery, defect, and price-variance tracking with an append-only event log and cached scorecards. Powered by MongoDB via @classytic/mongokit.

flow.procurement.received
       │
       ▼
recordEvent(supplierId, type, metrics)   ──▶  performance_event (append-only log)
                                                       │
                                                       ▼
                                 computeScore(supplierId, period)
                                                       │
                                                       ▼
                                  supplier_score (cached, upsert per period)

Zero framework dependencies — usable from Arc, Express, Nest, Next.js, or raw Node.

Install

npm install @classytic/supplier-performance @classytic/mongokit @classytic/primitives mongoose

Peer deps:

| | | |---|---| | @classytic/mongokit | >=3.11.0 | | @classytic/primitives | >=0.1.0 | | mongoose | >=9.4.1 |

Quick start

import mongoose from 'mongoose';
import { createSupplierPerformance } from '@classytic/supplier-performance';

await mongoose.connect('mongodb://localhost/myapp');

const engine = await createSupplierPerformance({ connection: mongoose.connection });
await engine.syncIndexes();

// Host bridges sibling events. Example: turn `flow.procurement.received`
// into a delivery_received event on the supplier's record.
const ctx = {
  organizationId: 'org_42',
  actorRef: 'system:cron',
  actorKind: 'system' as const,
  correlationId: 'req_xyz',
};

await engine.services.score.recordEvent(
  {
    supplierId: 'SUP-001',
    type: 'delivery_received',
    occurredAt: new Date(),
    metrics: { quantity: 100 },
    sourceRef: 'PO-2026-0042',
    sourceType: 'procurement_order',
  },
  ctx,
);

// Compute (and cache) a scorecard for a window
const scorecard = await engine.services.score.computeScore(
  {
    supplierId: 'SUP-001',
    period: {
      start: new Date('2026-01-01'),
      end: new Date('2026-04-01'),
      label: '2026-Q1',
    },
  },
  ctx,
);

console.log(scorecard.metrics);
// {
//   deliveryCount: 10,
//   onTimeCount: 8,
//   onTimeRate: 0.8,
//   avgDelayDays: 0.6,
//   unitsReceived: 1000,
//   defectiveUnits: 5,
//   defectRate: 0.005,
//   priceVarianceCount: 2,
//   avgPriceVariance: 0.04,
//   compositeScore: 89.5
// }

Architecture

Event types

The catalog ships 4 event types — extend by recording additional metadata fields, not new types (kernel-level enum changes break consumers).

| Type | Drives | Required metrics | |---|---|---| | delivery_received | on-time rate, units received | quantity | | delivery_late | on-time rate, average delay | quantity, delayDays | | defect_reported | defect rate, defective units | quantity | | price_variance | average variance % | expectedUnitCost, actualUnitCost, variancePct |

Score formula

The default composite score is bounded 0..100 (higher = better):

score = onTimeRate          × 50
      + (1 − defectRate)    × 40
      + (1 − |variance|)    × 10

Hosts that need different weights re-rank events directly via the scoreFromEvents pure helper or write their own roll-up.

Repositories

| Repo | Verbs | |---|---| | performanceEvent | inherited mongokit CRUD + findInWindow(supplierId, start, end, ctx) | | supplierScore | inherited CRUD + upsertForPeriod(input, ctx), findLatest(supplierId, ctx) |

Service

| Verb | Behavior | |---|---| | recordEvent(input, ctx) | Append one row to the event log; emit supplier_performance:event.recorded. | | computeScore({ supplierId, period }, ctx) | Load events in window → run scoreFromEvents → upsert score. Idempotent. Emits supplier_performance:score.computed. | | getScorecard(supplierId, ctx, period?) | When period is supplied, recompute. Otherwise return the latest persisted snapshot. |

Events

supplier_performance:event.recorded
supplier_performance:score.computed

Use any EventTransport-shaped bus (Arc's MemoryEventTransport, Redis, Kafka, …). The bundled InProcessSupplierPerformanceBus is a zero-dep default.

Multi-tenant

Drives off @classytic/primitives/tenant. Default field type is 'string' (matches the legacy convention shared by sibling packages):

const engine = await createSupplierPerformance({
  connection,
  tenant: { fieldType: 'objectId' }, // when org IDs are stored as ObjectId
});

Indexes

| Collection | Index | Purpose | |---|---|---| | supplier_performance_events | { supplierId, occurredAt } | Range query for scoring window | | supplier_performance_events | { sourceRef, type } (partial) | Drill-down from PO / RMA into the events that fired | | supplier_scores | { supplierId, period.start, period.end } (unique) | One snapshot per (supplier, period) | | supplier_scores | { period.end ↓, metrics.compositeScore ↓ } | Dashboard "top suppliers this month" |

The tenant field is prepended to compound indexes via injectTenantField when scoping is enabled.

Soft delete

Every collection wires softDeletePlugin with a default 7-year retention. Hosts override via retentionDays on the engine config.

Why a service (and not just repos)?

Per PACKAGE_RULES §2, a service is justified when it does ≥1 of:

  • multi-repo orchestration ✅ (event log + score cache)
  • pure domain algorithm ✅ (scoreFromEvents is in calc/, called by the service)
  • event emission ✅

So ScoreService exists. Pure scoring is testable in isolation via scoreFromEvents.

Bridges (host-side)

This package never imports @classytic/flow, @classytic/order, or any sibling — supplier-performance is downstream. The host wires whatever signals it cares about:

import { subscribe } from '#lib/events/arcEvents.js';

subscribe('flow.procurement.received', async (event) => {
  const order = await flow.repositories.procurement.getByQuery(/* … */);
  const onTime = order.expectedAt && new Date() <= order.expectedAt;
  await engine.services.score.recordEvent(
    {
      supplierId: order.vendorRef,
      type: onTime ? 'delivery_received' : 'delivery_late',
      occurredAt: new Date(),
      metrics: {
        quantity: totalReceived,
        ...(order.expectedAt ? { expectedAt: order.expectedAt, actualAt: new Date() } : {}),
      },
      sourceRef: order.orderNumber,
      sourceType: 'procurement_order',
    },
    ctx,
  );
});

License

MIT