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

complyrail

v0.1.1

Published

Open-source compliance agent. It reads a buyer's documents, applies a rule set, drafts grounded text and renders a filing pack.

Readme

ComplyRail

ComplyRail is an open-source compliance agent. It reads a buyer's documents, applies a rule set, drafts grounded text and renders a filing pack. You describe one product as a pack. The engine runs every paid order for that pack, from the Stripe payment to the delivered files.

npm install complyrail

What makes it different

Every drafted sentence is grounded. The model drafts text only from a closed list of facts that the pack builds for each order. The engine then checks the draft. Every digit run, every number word, every written date and every capitalised multi-word phrase must appear in the facts or in the pack's vocabulary. A number word counts as its value: "Three" needs a 3 in the facts, in digits or in words. One ungrounded token rejects the whole draft. After three rejected drafts the engine applies the pack's declared fallback: it omits the text, prints the buyer's own words, or asks the buyer for more detail. The rule is fixed in the engine, and a pack cannot change it.

Rules decide the outcome, not a model. A pack's determine() is a plain function from facts to a result. The model has two jobs only. It reads each uploaded document into typed fields, and it drafts text from facts. Pack code compares every field the model read with what the buyer typed, and a disagreement goes back to the buyer as one sentence.

A paid order is never dropped. Every stage returns done, retry(reason) or needsInput(sentence). A retry waits 2, 4, then 8 minutes, doubling up to a ceiling, and it never gives up. A crash inside a stage is a retry. A problem only the buyer can fix waits on the order page with one sentence. A refusal by the rules refunds the payment through Stripe. No order is delivered short while a retry can still succeed.

A pack is data plus small functions. The pack holds the intake fields, the document schemas, the rules, the narrative slots, the templates and the email copy. The engine owns payments, intake, retries, document reading, drafting, rendering, delivery, actions and monitoring.

You bring your own model. One provider interface covers Gemini, OpenAI, Anthropic and any OpenAI-compatible base URL, including a local model. The examples use Gemini Flash. Tests use a stub provider that never touches the network.

The stages

Each order moves through these stages. Each stage reads and writes one order row.

| Stage | What it does | |---|---| | verify_payment | Confirms the Stripe Checkout session is complete, paid and matched by the pack. Creates the order once. | | request_intake | Emails the order page link. Sends one reminder after the pack's reminder delay. | | validate_intake | Runs the pack's intake validator over the buyer's form. | | read_documents | Reads each upload into the pack's schema, then runs the pack's validator against the typed answers. | | fetch_sources | Calls the pack's source connectors. An outage is a retry. | | determine | Runs the pack's rules. A refusal refunds the payment. | | draft | Drafts each narrative slot from its facts and checks it against the grounding rule. | | render | Fills the templates, prints PDFs, CSVs or HTML, and runs the pack's output checks. | | deliver | Zips the files, stores them, and emails the buyer the archive and the order page link. | | act | Optional. Runs the pack's external actions, each with its own state and retry. | | monitor | Optional. Re-checks on a schedule while the subscription is active and emails a change report. |

The Pack interface

interface Pack {
  id: string;                       // "goodstanding"
  matches(session: StripeSession): Tier | null;
  intake: { fields: Field[]; validate(form): Result<Intake> };
  documents?: DocSpec[];            // { slot, schema, mime, minConfidence, privacy, validate(read, intake) }
  sources?: Source[];               // { name, fetch(order) -> facts }, throws Retry on outage
  determine(ctx: Facts): Determination | Refusal | NeedsInput;
  narratives?: NarrativeSpec[];     // { slot, system, facts(ctx), vocabulary, fallback }
  render(ctx): Doc[];               // { name, template, format: "pdf" | "csv" | "html" }
  checks?: (files) => Problem[];
  actions?: ActionSpec[];           // { state, run(order) -> done | retry | needsInput }
  monitor?: { everyDays: number; run(order) -> ChangeReport | null };
  mail: { from: string; intake: Template; delivery: Template; reminderDays: number };
}

The engine gives every pack these services:

| Service | What it does | |---|---| | readDocument(file, schema) | One model read of one file into the schema's typed fields, with a confidence. | | draftGrounded(system, facts, vocabulary) | One draft, checked against the grounding rule. | | renderPdf(html) | Prints HTML to PDF with a local headless Chrome or Chromium. | | upload(bucket, path, bytes) | Stores a file through the storage adapter. | | send(template, fields, attach) | Sends one email through the mail adapter. | | stripe.refund(session) | Refunds the payment behind a Checkout session, once. |

Full TypeScript declarations ship in src/index.d.ts. determine(), render() and the other pack functions may also return a promise.

A small pack

import { definePack, validateFields, determination, refusal } from 'complyrail';

const FIELDS = [
  { name: 'business', label: 'the business name', required: true },
  { name: 'staff', label: 'the number of staff', type: 'integer', required: true },
  { name: 'licence', label: 'your business licence', type: 'file', slot: 'licence', required: true },
];

export default definePack({
  id: 'smallbiz',
  matches: (s) => (s.metadata?.product === 'smallbiz' ? { id: 'standard', amount: 1900, mode: 'payment' } : null),
  intake: { fields: FIELDS, validate: (form, ctx) => validateFields(FIELDS, form, ctx) },
  documents: [{
    slot: 'licence',
    schema: { fields: { holder: 'string', number: 'string', expires: 'date' } },
    mime: ['application/pdf', 'image/png', 'image/jpeg'],
    minConfidence: 0.7,
    privacy: 'full',
    validate: (read, intake) => (read.value.holder === intake.business ? [] : [`The licence names ${read.value.holder}. Upload the licence for ${intake.business}.`]),
  }],
  determine: (ctx) => (ctx.intake.staff > 100 ? refusal('This product covers businesses with 100 staff or fewer.') : determination({ summary: `${ctx.intake.business} is in scope.` })),
  narratives: [{
    slot: 'summary',
    system: 'Write two plain sentences for the owner.',
    facts: (ctx) => ({ Business: ctx.intake.business, Staff: ctx.intake.staff }),
    vocabulary: [],
    fallback: 'omit',
  }],
  render: (ctx) => [{ name: 'record', format: 'pdf', template: '# {{business}}\n\n{{summary}}', fields: { business: ctx.intake.business, summary: ctx.drafts.summary ?? '' } }],
  mail: {
    from: 'Small Biz Records <[email protected]>',
    intake: 'Subject: Fill in your form\n\nOpen {{order_url}} and fill in the form.',
    delivery: 'Subject: Your record is ready\n\nYour files are attached. They are also at {{order_url}}.',
    reminderDays: 3,
  },
});

Run it with an engine:

import { createEngine, gemini, stripePayments, supabaseOrderStore, supabaseStorage, smtpMailer } from 'complyrail';
import pack from './pack.mjs';

const engine = createEngine({
  pack,
  provider: gemini({ apiKey: process.env.GEMINI_API_KEY }),
  payments: stripePayments({ secretKey: process.env.STRIPE_SECRET_KEY }),
  store: supabaseOrderStore({ url: process.env.SUPABASE_URL, serviceKey: process.env.SUPABASE_SERVICE_ROLE_KEY }),
  storage: supabaseStorage({ url: process.env.SUPABASE_URL, serviceKey: process.env.SUPABASE_SERVICE_ROLE_KEY }),
  mailer: smtpMailer({ host: process.env.SMTP_HOST, user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }),
  siteUrl: 'https://your-site.example',
});

await engine.syncPayments(); // new paid sessions become orders
await engine.tick();         // every due order moves as far as it can

Your site calls three more methods. engine.openOrder(sessionId) runs on the Stripe success redirect. engine.submitIntake(token, form, files) takes the buyer's form. engine.view(token) and engine.download(token) serve the order page.

The DoseTrace example

examples/dosetrace is a complete pack built from DoseTrace, a product that makes a DSCSA readiness binder for one pharmacy. The buyer answers a form about the pharmacy and uploads one licence or authorized trading partner letter per wholesaler. The model reads each letter. Pack code compares the holder name and licence number with the typed answers, applies the 25-employee rule and the sunset date, and fills nine document templates. The model drafts one readiness paragraph from a closed fact list, and the binder ships without it after three rejected drafts.

Every name, number and document in the example is synthetic. Both licence PDFs in the sample folder are marked on their face as not real.

CHROME_PATH=/path/to/chrome node examples/dosetrace/run.mjs                    # stub model, no network
CHROME_PATH=/path/to/chrome GEMINI_API_KEY=... node examples/dosetrace/run.mjs --provider gemini

On macOS the Chrome binary is /Applications/Google Chrome.app/Contents/MacOS/Google Chrome. A stub run prints this:

2. The buyer sends the form with a mistyped licence number.
  read_documents   needs_input  You typed licence number WA-WD-0007741 for Torvald Pharmaceutical ...
3. The buyer corrects the number and keeps both uploaded licences.
  read_documents   done
  determine        done
  draft            done
  render           done
  deliver          done
   determination: Small dispenser under the FDA exemption. 9 licensed pharmacists and technicians
   across one location, at or under the 25-employee threshold.
   9 files in examples/dosetrace/out/.../dosetrace-2026-10-02.zip
4. The grounding check rejects an invented number.
   checkGrounded on the delivered brief plus "You also employ 14 delivery drivers.": rejected, ungrounded number "14"

With --provider gemini, Gemini Flash reads the two PDF licences and drafts the paragraph. The output files and a report.json land in examples/dosetrace/out.

Adapters

| Concern | Adapters | |---|---| | Payments | stripePayments({ secretKey }), and memoryPayments() for development | | Order store | supabaseOrderStore({ url, serviceKey }), fileOrderStore({ dir }) | | File storage | supabaseStorage({ url, serviceKey }), fileStorage({ dir }) | | Mail | smtpMailer({ host, port, user, pass }), fileOutbox({ dir }) |

The file adapters keep everything on one machine, so a pack runs end to end with no accounts. SUPABASE_SCHEMA holds the SQL for the order table. Turn on row level security and add no policies, so only the service role reads orders. An adapter is a plain object, so you can write your own for another database, store or mail service.

Models

import { gemini, openai, anthropic, openaiCompatible } from 'complyrail';

gemini({ apiKey });                                              // Gemini Flash, with a fallback chain on 429
openai({ apiKey, model });
anthropic({ apiKey, model });
openaiCompatible({ baseURL: 'http://localhost:11434/v1', model: 'llama3.1' }); // a local model

gemini() walks a chain of Flash models when one answers 429, 404 or 503. A failed call never throws. It comes back as { ok: false, transient }, and the engine turns it into a retry. modelIntervalMs on the engine spaces calls apart for a free tier. A document schema's privacy setting controls what reaches the model: the whole file, only the text a function returns, or nothing.

Scaffold a site

npx complyrail new-app my-site --app both     # or --app console, or --app simple
cd my-site && npm install && npm run dev      # the site
npm run worker                                # the order worker, in a second terminal

--app console gives one dense working view: the stages, the form and the order record side by side. --app simple gives one roomy view: the outcome, one primary action, and details behind disclosures. --app both gives both views, a first-visit welcome that explains the product and offers the choice, and footer controls to switch. The app is a Next.js tree with checkout, the order page, the form, the order status, the download and a worker script. It runs with no accounts in development: the start button opens an order without a payment, and files and email stay in .complyrail/.

Command line

complyrail new-app [dir] --app console|simple|both [--name "Product name"]
complyrail tick   --config complyrail.config.mjs    # advance every due order once
complyrail sync   --config complyrail.config.mjs    # turn new paid sessions into orders
complyrail worker --config complyrail.config.mjs --every 120

Limits

The grounding rule checks digit runs, number words from two upward, written dates and capitalised phrases. It does not check "one" or "a", because both are more often a pronoun or an article than a count. It does not check ordinals such as "third". It does not judge whether a grounded sentence is true: "Three of your staff signed" passes when the facts hold 3 staff, whether or not they signed. PDFs need a local Chrome or Chromium. The engine processes orders one at a time per worker, and a lease stops two workers from running the same order.

Development

npm test                     # node --test, with a stub model and local servers only
node scripts/scrub-gate.mjs  # fails on personal data, private ids, keys and bot-detection bypass code

CI runs both on every push.

License

MIT. Copyright Compound Labs. ComplyRail is built by Compound Labs.