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

@molecule/api-resource-invoice

v1.0.1

Published

Invoice CRUD + line items + draft/sent/paid/overdue status. Extracted from invoice-billing flagship.

Readme

@molecule/api-resource-invoice

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

@molecule/api-resource-invoice — line-item-based invoice CRUD with auto-computed totals (subtotal + tax), draft/sent/partial/paid/overdue/void status, and a recordPayment(invoiceId, userId, amount) helper that advances status automatically.

Extracted from the invoice-billing flagship.

Quick Start

import { createInvoiceRouter } from '@molecule/api-resource-invoice'
app.use('/invoices', createInvoiceRouter())
import { createInvoiceForUser, recordPayment } from '@molecule/api-resource-invoice'

const inv = await createInvoiceForUser(userId, {
  client_id: 'acme-co',
  items: [{ description: 'Consulting', quantity: 10, unit_price: 250 }],
  tax_rate: 8.5,
})
await recordPayment(inv.id, userId, 2710.0) // marks paid

Type

resource

Installation

npm install @molecule/api-resource-invoice @molecule/api-bonds-default-express @molecule/api-database @molecule/api-i18n @molecule/api-middleware-validation express zod
npm install -D @types/express

API

Interfaces

Invoice

Normalized invoice with all date fields serialized to ISO strings for API responses.

interface Invoice extends Omit<
  InvoiceRow,
  'issue_date' | 'due_date' | 'paid_at' | 'created_at' | 'updated_at'
> {
  issue_date: string
  due_date: string | null
  paid_at: string | null
  created_at: string
  updated_at: string
}

InvoiceRow

Raw database row shape for an invoice, with date fields typed as string or Date.

interface InvoiceRow {
  id: string
  user_id: string
  client_id: string
  number: string
  status: InvoiceStatus
  items: LineItem[]
  subtotal: number
  tax_rate: number
  tax_amount: number
  total: number
  amount_paid: number
  currency: string
  issue_date: string | Date
  due_date: string | Date | null
  paid_at: string | Date | null
  notes: string | null
  created_at: string | Date
  updated_at: string | Date
}

LineItem

A single billable line on an invoice, with description, quantity, and unit price.

interface LineItem {
  description: string
  quantity: number
  unit_price: number
}

Types

InvoiceStatus

Lifecycle states an invoice can be in, from initial draft through payment or cancellation.

type InvoiceStatus = 'draft' | 'sent' | 'partial' | 'paid' | 'overdue' | 'void'

Functions

computeTotals(items, taxRate)

Compute subtotal/tax/total from line items + tax rate (percent 0-100).

function computeTotals(
  items: LineItem[],
  taxRate: number,
): { subtotal: number; tax_amount: number; total: number }

createInvoiceForUser(userId, data)

Create a new draft invoice for a user, computing totals from line items and tax rate.

function createInvoiceForUser(
  userId: string,
  data: {
    client_id: string
    items: LineItem[]
    due_date?: string
    notes?: string
    tax_rate?: number
    currency?: string
  },
): Promise<Invoice>

createInvoiceRouter()

Creates and returns an Express router with all CRUD + payment routes for invoices.

function createInvoiceRouter(): Router

deleteInvoiceForUser(invoiceId, userId)

Delete a user-owned invoice by ID, returning false if not found or not owned by the user.

function deleteInvoiceForUser(invoiceId: string, userId: string): Promise<boolean>

getInvoiceForUser(invoiceId, userId)

Fetch a single invoice by ID, returning null if not found or not owned by the user.

function getInvoiceForUser(invoiceId: string, userId: string): Promise<Invoice | null>

listInvoicesForUser(userId, opts?)

List all invoices for a user, with optional client/status filters and pagination.

function listInvoicesForUser(
  userId: string,
  opts?: { client_id?: string; status?: InvoiceStatus; page?: number; limit?: number },
): Promise<{ data: Invoice[]; total: number; page: number; limit: number }>

recordPayment(invoiceId, userId, amount)

Record a payment amount against an invoice, updating amount_paid and transitioning status to partial or paid.

function recordPayment(invoiceId: string, userId: string, amount: number): Promise<Invoice | null>

toInvoice(row)

Map a raw database InvoiceRow to a normalized Invoice with ISO date strings.

function toInvoice(row: InvoiceRow): Invoice

updateInvoiceForUser(invoiceId, userId, patch)

Apply a partial update to a user-owned invoice, recomputing totals and marking paid_at when status becomes paid.

function updateInvoiceForUser(
  invoiceId: string,
  userId: string,
  patch: Partial<{
    items: LineItem[]
    due_date: string
    notes: string
    tax_rate: number
    currency: string
    status: InvoiceStatus
  }>,
): Promise<Invoice | null>

Constants

INVOICE_STATUSES

All valid lifecycle statuses an invoice can hold.

const INVOICE_STATUSES: readonly ['draft', 'sent', 'partial', 'paid', 'overdue', 'void']

invoiceCreateSchema

Zod schema for creating a new invoice (client, items, optional due date / notes / tax / currency).

const invoiceCreateSchema: z.ZodObject<
  {
    client_id: z.ZodString
    items: z.ZodArray<
      z.ZodObject<
        { description: z.ZodString; quantity: z.ZodNumber; unit_price: z.ZodNumber },
        z.core.$strip
      >
    >
    due_date: z.ZodOptional<z.ZodString>
    notes: z.ZodOptional<z.ZodString>
    tax_rate: z.ZodOptional<z.ZodNumber>
    currency: z.ZodOptional<z.ZodString>
  },
  z.core.$strip
>

invoiceUpdateSchema

Zod schema for partially updating an existing invoice (all fields optional).

const invoiceUpdateSchema: z.ZodObject<
  {
    items: z.ZodOptional<
      z.ZodArray<
        z.ZodObject<
          { description: z.ZodString; quantity: z.ZodNumber; unit_price: z.ZodNumber },
          z.core.$strip
        >
      >
    >
    due_date: z.ZodOptional<z.ZodString>
    notes: z.ZodOptional<z.ZodString>
    tax_rate: z.ZodOptional<z.ZodNumber>
    currency: z.ZodOptional<z.ZodString>
    status: z.ZodOptional<
      z.ZodEnum<{
        draft: 'draft'
        sent: 'sent'
        partial: 'partial'
        paid: 'paid'
        overdue: 'overdue'
        void: 'void'
      }>
    >
  },
  z.core.$strip
>

lineItemSchema

Zod schema for a single invoice line item (description, quantity, unit price).

const lineItemSchema: z.ZodObject<
  { description: z.ZodString; quantity: z.ZodNumber; unit_price: z.ZodNumber },
  z.core.$strip
>

recordPaymentSchema

Zod schema for recording a payment against an invoice (positive amount required).

const recordPaymentSchema: z.ZodObject<{ amount: z.ZodNumber }, z.core.$strip>

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bonds-default-express ^1.0.1
  • @molecule/api-database ^1.0.1
  • @molecule/api-i18n ^1.0.1
  • @molecule/api-middleware-validation ^1.0.1
  • express ^5.0.0
  • zod ^4.0.0

Runtime Dependencies

  • @molecule/api-bonds-default-express
  • @molecule/api-database
  • @molecule/api-i18n
  • @molecule/api-middleware-validation
  • express
  • zod

Table: src/__setup__/invoices.sql creates the single invoices table. An mlcl-scaffolded API replays __setup__/*.sql automatically on migrate; anywhere else run it once. The bundled computeTotals(items, taxRate) helper is exported for client-side total previews.

Everything is owner-scoped: service functions take the authenticated userId and return null/false for rows the caller doesn't own, and the router reads the caller from res.locals.session (mount behind your global auth middleware; 401 otherwise). recordPayment is bookkeeping — "record a payment I received" against my own invoice — it never talks to a payment provider; wire actual charging separately (see @molecule/api-payments).

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • [ ] Creating an invoice with line items auto-computes the money: add a line (quantity × unit_price) and the subtotal updates, the tax_rate is applied (tax_amount = subtotal × rate / 100), and total = subtotal + tax_amount — matching computeTotals down to the rounded cents shown in the UI. Change a quantity or add another line and every figure re-computes.
  • [ ] The status lifecycle advances correctly through INVOICE_STATUSES: a freshly created invoice reads draft; sending it moves it to sent; a recordPayment for LESS than the balance flips it to partial; paying the remaining balance flips it to paid and stamps a paid date. The balance-due shown to the user (total - amount_paid) is correct after each step.
  • [ ] Terminal/edge states read right: an unpaid invoice past its due_date shows overdue, a cancelled one shows void, and once an invoice is paid or void the UI blocks further line-item/total edits as designed.
  • [ ] Overpayment and dead-invoice payments are rejected, not silently over-applied: recording an amount greater than the outstanding balance, or any payment against an already-paid or void invoice, fails with a visible error and leaves amount_paid + status unchanged.
  • [ ] AUTHORIZATION: a signed-in user sees and mutates only THEIR OWN invoices — every path is *ForUser-scoped (listInvoicesForUser / getInvoiceForUser / updateInvoiceForUser / recordPayment). Guessing or tampering another user's invoice id on GET/PUT/DELETE/:id/payment returns 403/404 — never that invoice's data and never a cross-user payment.