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/sales-analytics

v0.1.1

Published

SALES binding over the @classytic/facts fact-projection kernel — the salesFact/salesDaily projection spec (dimensions, the recognized-revenue measure model: qty + basis-explicit booked/fulfilled/returned/cancelled money, quality markers, 7-field rollup gr

Readme

@classytic/sales-analytics

The SALES binding over @classytic/facts — the erp-plan §17.3 reference implementation of a fact-projection domain binding. The kernel owns the machinery (generation manifest + verified cutover, the idempotent §4.3 projection transaction, rollup rebuild, resumable keyset backfill, reconcile framework, freshness, purge/anonymize); this package owns ONLY the sales semantics:

| This binding ships | Moved from be-prod | |---|---| | The salesFact/salesDaily projection spec — dimensions, the recognized-revenue measure model (qty + basis-explicit booked/fulfilled/returned/cancelled money), quality markers, 7-field rollup grain, live-collection index parity | sales-fact.model.ts, sales-daily.model.ts | | Event mappersorder:created / order:fulfillment.transition+.completed (coverage-commit only) / order:change.confirmed / order:canceled → fact rows with business dedupe keys, authoritative payload occurredAt, aggregated same-line returns | sales-fact.projector.ts (pure half) | | registerSalesProjector — relay-facing subscription wiring; THROWS on failure (ack contract); generation dual-write; partial-failure subscription rollback | sales-fact.projector.ts (IO half) | | Document backfill mappers + DocumentSource bindings — placed/fulfilled/returned/cancelled, row-equivalent to live (money + skuRef + orderId) | sales-fact-backfill.ts | | Reconcile source aggregations — every measure recomputed source-side; cell = civilDate × organizationId | sales-fact-reconcile.ts | | Lens catalog (./lens subpath) — salesDaily/salesFact semantic models + curated branch/HQ named queries | analytics/semantic-models.ts, named-queries.ts |

The recognized-revenue measure model (v1 contract)

Money is basis-explicit — every money measure names WHEN it is recognized, so a reader never mistakes booked-at-placement demand for recognized-at-ship revenue. The LEDGER remains the authoritative money system of record; these projections are an analytical view (proportional, integer-minor, per-unit × qty), never the books.

Raw additive measures (on the fact row and summed into the daily rollup):

| Measure | Fact kind | Meaning | |---|---|---| | orderedQty / fulfilledQty / returnedQty / cancelledQty | each | whole units placed / shipped / returned / cancelled-remainder | | bookedGrossMinor bookedDiscountMinor bookedTaxMinor bookedNetMinor bookedCogsMinor | ordered | booked at placementorder:created.lines[] money (no read). bookedNet = lineTotal = (unitPrice − unitDiscount + unitTax) × qty | | fulfilledNetMinor fulfilledCogsMinor | fulfilled | recognized at ship — order-line unit net/cogs × the shipped delta qty | | returnedNetMinor returnedCogsMinor | returned | reversal — unit net/cogs × returned qty | | cancelledNetMinor cancelledCogsMinor | cancelled | reversal — unit net/cogs × cancelled remainder qty |

unitNet = unitPrice − unitDiscount + unitTax, unitCogs = snapshot.costPrice — the single recognition basis (money.ts) shared by the live projector, the backfill, and the reconcile so the three can never drift. Reversal/recognized money is computed from the immutable order-line snapshot × qty via the kernel's overflow-checked safeMulMinor.

Derived lens measures (never stored; ratio-of-sums — computed from each group's summed operands, NEVER a per-row average of ratios):

| Measure | Definition | Meaning | |---|---|---| | netAcceptedQty | orderedQty − cancelledQty | net accepted demand | | bookedRevenue | bookedNet | booked at placement | | netRevenue | bookedNet − cancelledNet − returnedNet | booked, net of reversals | | recognizedRevenue | fulfilledNet − returnedNet | recognized at ship, net of returns | | bookedMargin | bookedNet − bookedCogs | | | netMargin | netRevenue − (bookedCogs − cancelledCogs − returnedCogs) | | | recognizedMargin | recognizedRevenue − (fulfilledCogs − returnedCogs) | |

Host ports

| Port | Where from | Role | |---|---|---| | CivilCalendarPort | @classytic/facts (host binds e.g. Asia/Dhaka) | instant → civil date/week/month labels | | OrderSourcePort | this package (ports.ts) | loadByNumber (skuRef + recognition money + org fallback for fulfilled/returned/cancelled) + optional loadChangeOrderRef (change → orderNumber) | | EventTransport (subscribe) | @classytic/primitives | the dedicated, strict sales-projection lane (see below) | | OutboxStore (FreshnessSource) | @classytic/primitives | getFreshness diagnostics over the projection's own outbox |

Boot

import { createSalesAnalytics, registerSalesProjector } from '@classytic/sales-analytics';

const sales = createSalesAnalytics({
  connection: mongoose.connection,
  civil: { dateOf, weekOf, monthOf },   // business zone (e.g. Asia/Dhaka)
  orderSource: {
    // ONE lean query — supplies skuRef, per-unit money (unitPrice/unitDiscount/
    // unitTax + snapshot.costPrice) AND the organizationId fallback.
    loadByNumber: (n) => order.models.Order.findOne({ orderNumber: n }).lean(),
    loadChangeOrderRef: async (c) => { /* OrderChange → {orderId, orderNumber} */ },
  },
  autoIndex: false,                     // production: syncIndexes() at deploy
  logger,
});
await sales.ensureReady({ skipIndexes: true });   // capability gate + collections

// Live projection — subscribe on the DEDICATED outbox-relay transport lane.
const registration = await registerSalesProjector({
  engine: sales,
  transport: salesProjectionTransport,
  logger,
});
// on shutdown: await registration.dispose();

The dedicated strict-transport requirement

transport MUST be the projection's OWN single-consumer lane, fed by the outbox relay, whose publish propagates handler errors (primitives InProcessEventBus propagateHandlerErrors: true, or equivalent). Handlers THROW on any failure so the relay retries/dead-letters — a swallowed error would ack an unprojected row (forbidden). On a partial subscribe failure the registration unsubscribes everything already subscribed before rethrowing, so a rejected subscribe never leaks the ones that succeeded.

sales is the kernel FactProjectionsales.manifest.*, sales.projectFacts, sales.rebuildRollup, sales.runBackfill, sales.reconcile, sales.getFreshness, sales.purgeGeneration, sales.purgeOccurrence, sales.anonymizeFactWindow, sales.syncIndexes all come straight from @classytic/facts. Import kernel types/helpers from @classytic/facts directly — this package never re-exports them (P4).

Sequence: index → backfill → reconcile → cutover

  1. ensureReady({ skipIndexes: true }) then syncIndexes() at deploy (a no-op against the live be-prod collections by the index-parity contract).
  2. manifest.beginRebuild() → a shadow generation.
  3. runBackfill({ sources, generation, checkpointId }) (below).
  4. reconcile(...) — ops refuse cutover unless report.summary.clean.
  5. manifest.markBackfillCompletemarkVerifiedcutover.
  6. purgeGeneration(retired) to reclaim the old generation.

Live-collection parity (the swap contract)

Collections sales_fact / sales_daily / sales_projection_manifest / sales_projection_checkpoints; unique indexes gen_dedupeKey_unique and the 7-field daily_grain_unique (exact key order); the six unnamed secondaries in be-prod's exact field orders. syncIndexes() is a no-op — pinned by the spec-index test. The recognized-revenue measures are additive columns on the same grain; the GRAIN and its unique index are unchanged.

Dedupe keys (business occurrences, never eventId): placed:{orderNumber}:{lineId} · fulfilled:{fulfillmentNumber}:{orderLineId} · returned:{changeNumber}:{orderLineId} · cancelled:{orderNumber}:{lineId}. A change carrying two return_item actions for one line aggregates to ONE returned fact (summed qty + money) — never a colliding second row.

Backfill

import { buildSalesBackfillSources } from '@classytic/sales-analytics';

const sources = buildSalesBackfillSources({
  civil,
  orders:       { read: (q, n) => db.collection('orders').find(q).sort({ _id: 1 }).limit(n).toArray(),
                  estimateTotal: (f) => db.collection('orders').countDocuments(f) },
  fulfillments: { read: (q, n) => db.collection('order_fulfillments').find(q).sort({ _id: 1 }).limit(n).toArray() },
  changes:      { read: (q, n) => db.collection('order_changes').find(q).sort({ _id: 1 }).limit(n).toArray() },
});
await sales.runBackfill({ sources, generation, checkpointId: 'rebuild-gen2' });

Source names (placed/fulfilled/returned/cancelled) match be-prod's historical checkpoints. The fulfilled/returned sources batch-resolve parent-order line info (skuRef + unit net/cogs — the recognition basis) through the same orders reader inside mapBatch, so a backfilled generation is row-equivalent to live projection on orderId, skuRef AND money.

Reconcile (cutover gate)

import { reconcileSales } from '@classytic/sales-analytics';

const report = await reconcileSales(sales, {
  generation, from: '2026-07-01', to: '2026-07-21',
  reads: {
    civil,
    dayStartUtc: businessDayStartUtc,
    dayEndUtc: businessDayEndUtc,
    scanOrders: (q, p) => db.collection('orders').find(q, { projection: p }),
    scanFulfillments: (q, p) => db.collection('order_fulfillments').find(q, { projection: p }),
    scanChanges: (q, p) => db.collection('order_changes').find(q, { projection: p }),
  },
});
// ops refuse manifest cutover unless report.summary.clean

Cell = civilDate × organizationId. EVERY measure is recomputed independently from the source docs (fulfilled/returned join the parent order via scanOrders for the unit net/cogs) — so markVerified() can't pass with wrong money, only wrong qty. Source timestamps convert to civil days through the SAME injected civil port as the projector/backfill — one timezone implementation.

Lens catalog — @classytic/sales-analytics/lens

import {
  buildSalesAnalyticsQueries,
  salesAnalyticsSemanticModel,
} from '@classytic/sales-analytics/lens';

const queries = buildSalesAnalyticsQueries({
  getGeneration: () => sales.manifest.getActiveGeneration(),  // never client-supplied
});
// mount with @classytic/arc-lens: BRANCH surface ← queries.branch (organizationId
// force-injected; branch grouping rejected), HQ surface ← queries.hq (adds the
// sales-by-branch rollup grouped by organizationId).

Own subpath on purpose: only this entry peers on @classytic/lens + @classytic/arc-lens (optional peers) — the core binding never drags them in.

Purge / anonymize (retention & erasure)

The kernel surface is exposed verbatim on the engine:

  • sales.purgeGeneration(gen) — hard-purge a RETIRED generation (cutover cleanup).
  • sales.purgeOccurrence(dedupeKey, { rebuild: true }) — erase one business occurrence's fact rows and re-derive the affected rollup window.
  • sales.anonymizeFactWindow({ generation, from, to, fields }) — redact PII dimensions across a civilDate range while RETAINING measures.

The current sales fact carries no PII dimension (no customerId — it was never captured), so anonymizeFactWindow is available for a FUTURE PII dimension; nothing needs redacting today.

Order payload contract

@classytic/order is a type-only devDependency (floor >=0.12.0, erased at build — zero runtime coupling). The consumed payload slices — lines[] / shippedLines[] / returnedLines[] plus the now-consumed orderId and occurredAt (the authoritative business instant: placedAt / coverageCommittedAt / confirmedAt / canceledAt) — are pinned against OrderEventPayloads by a compile-time conformance test. Upstream payload drift fails this package's typecheck, not production.

Peers

@classytic/facts (the kernel, >=0.2.0 — the purge/anonymize surface), mongokit / primitives / repo-core / mongoose / zod floors per PACKAGE_RULES; @classytic/lens + @classytic/arc-lens as OPTIONAL peers (only the ./lens subpath needs them).