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

@loanscope/program-rules

v0.1.1

Published

Canonical ProgramRules resolver primitives (toProgramRules, mergeRules, resolveVariant, findApplicableTier) shared by the LoanScope engine and product packages.

Readme

@loanscope/program-rules

npm version License: MIT

Canonical resolver primitives for ProgramRules: toProgramRules, mergeRules, resolveVariant, findApplicableTier.

Why a separate package

These four primitives flatten, merge, and dispatch over the ProgramRules shape from @loanscope/domain. They are consumed by both @loanscope/engine (for runtime tier / variant / occupancy resolution) and @loanscope/products (for catalog-flattening via extends chains).

Until this package existed, each consumer had its own private copy of the three primitives — byte-identical except for trivial parameter-name differences. That duplication was a documented drift risk: any new ProgramRules field had to be threaded through four places (two toProgramRules implementations, two mergeRules implementations), and every bug fix had to be replicated.

Factoring the primitives into this leaf package:

  • Eliminates the drift risk. One canonical toProgramRules, one mergeRules, one resolveVariant, one findApplicableTier.
  • Preserves the correct dependency direction. Neither @loanscope/engine nor @loanscope/products can depend on the other (would cycle). Both can depend on this package, which in turn depends only on @loanscope/domain for type shapes.
  • Is pinned by invariant tests. 10 tests live in the package's __tests__/ asserting the merge / normalize / override-wins semantics that every consumer relies on.

What it does

  • toProgramRules(rules, context) — normalize a Partial<ProgramRules> into a fully-typed ProgramRules by asserting the two strictly-required fields (allowedPurposes, allowedOccupancies) and copying every optional field through only when defined. context is threaded into the error message so catalog authors can locate offending products.
  • mergeRules(base, override) — layer override onto base field-by-field with override-wins semantics. maxLtvByOccupancy and maxLtvByPurpose are merged key-by-key via mergeRecord so partial overrides refine individual occupancies / purposes without clobbering siblings. Nested-object rules (borrower, appraisal, cash-out, property, AUS, asset-eligibility, buydown, MI) are merged shallowly via mergeOptional.
  • resolveVariant(product, term, occupancy, amortizationType, programKind?, armFixedPeriod?) — resolve the single ProductVariant that matches. The resolver is total: exactly-one match is required; zero matches and more-than-one matches both throw so ambiguous catalog shapes surface at runtime instead of producing silently-wrong evaluations.
  • findApplicableTier(tiers, loanAmount) — find the first LoanAmountTier whose range contains loanAmount. Missing bounds are treated as open (min ?? 0, max ?? +Infinity).

Install

pnpm add @loanscope/program-rules @loanscope/domain

Usage

import {
  mergeRules,
  resolveVariant,
  findApplicableTier,
  toProgramRules,
} from "@loanscope/program-rules";
import { AmortizationType, Occupancy, ProgramKind } from "@loanscope/domain";

// Flatten a partial rules object (throws if required fields are missing)
const normalized = toProgramRules(product.baseConstraints, `product ${product.id}`);

// Layer an override onto a base set of rules
const merged = mergeRules(normalized, {
  maxLTVRatio: 0.9,
  maxLtvByOccupancy: { [Occupancy.Investment]: 0.75 },
});

// Dispatch to the matching variant for a given transaction shape
const variant = resolveVariant(
  product,
  360,
  Occupancy.Primary,
  AmortizationType.FullyAmortizing,
  ProgramKind.Fixed,
);

// Walk tier overrides for loan-amount-dependent rules
const tier = findApplicableTier(product.tiers, loanAmount);

Part of the LoanScope monorepo

See the repository root for the full list of @loanscope/* packages and the architecture reference.

License

MIT