# My ERP Approval npm Library Passed 1,200 Weekly Downloads

> Eleven weeks after the first publish, hierarchical-approval crossed 1,200 weekly npm downloads. What shipped to get there: parallel branch groups, five approval modes, twelve entry points and 404 tests.

URL: https://www.matthewswong.com/en/blog/hierarchical-approval-1200-weekly-downloads/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2026-09-08
- Updated: 2026-09-08
- Category: Backend

> **Key takeaway:** hierarchical-approval, an MIT-licensed TypeScript engine for multi-level ERP approval chains, passed 1,204 weekly npm downloads eleven weeks after its first release. It now ships 37 versions, 404 tests, twelve entry points and six pluggable adapter interfaces, with 4.0.0 replacing a single current-level field with an explicit list of open levels.

I published version 0.1.0 of hierarchical-approval on 21 June, mostly to stop rebuilding the same approval chain by hand. I had written that code three times in eighteen months for three different ERP projects, and on the third time I extracted it instead. On 7 September the npm page read 1,204 weekly downloads and version 4.0.0. That is a good week, and it is worth writing down what got there.

This post is the eleven-week story: what the library does, the release that made the API stable, the release that fixed the data model rather than the sixth symptom of it, and an honest note on what an npm counter is and is not measuring. Every figure comes from the registry API or the changelog, both linked at the end.

## Eleven weeks, 37 versions, 2,620 downloads

The badge shows the last seven days. The range endpoint shows the whole arc, and the arc is a launch, a long quiet middle where I was building rather than shipping, and a September in which the library grew up. 2,620 downloads across the first eleven weeks, ending on the best week so far.

The quiet stretch in July and August is the part I would defend. Nothing was being installed because nothing new was being offered: the engine worked, but it could only do sequential chains, and I was writing the parallel-branch model that 1.0.0 shipped. A library that does one thing well and is honest about the rest is a better starting point than a wide one that is wrong in six places.

## What the library actually does

The premise has not changed since the first release. An approval chain is not business logic worth rewriting per project, it is infrastructure: a named template, a document moving through it, and a set of guarantees around concurrency and audit that everyone needs and nobody enjoys building. Each of these landed for a reason I hit in production first.

| The problem in every ERP I have worked on | What the library gives you | Since |
| --- | --- | --- |
| Approval chains hardcoded per document type | Named templates, defined once and reused anywhere | 0.1.0 |
| Two approvers clicking at the same moment | Optimistic locking on a version field, with a configurable retry policy | 0.1.0 |
| A double-click or a network retry creating two requests | Idempotency keyed by tenant, document and template | 0.1.0 |
| Tests that need a real database and a real clock | ApprovalTestKit and an injectable ManualClock, zero I/O | 0.4.0 |
| Finance and Legal reviewing one after the other for no reason | Parallel branch groups that open together and join before the chain advances | 1.0.0 |

Around those sit six pluggable adapter interfaces, for notifications, metrics, audit, scheduling, authorisation and middleware, so the engine can write to Kafka, Datadog, BullMQ or whatever your platform already runs without knowing any of them exist. The audit log is immutable and records an old and new state diff on every mutation, which is the part compliance actually asks for.

## 1.0.0 was the release that let branches run in parallel

Until 1.0.0 every chain was strictly sequential, so a genuinely concurrent step had to be faked. Finance and Legal review the same contract independently, and modelling that as Finance then Legal added days of cycle time to satisfy the data model rather than the business. Levels sharing a group name now activate together and join before the chain advances.

This was the last item on the roadmap, and the release that let me call the public API settled: breaking changes get a major version from here. The detail I was most pleased about is that all 673 tests written against the sequential engine passed unchanged, because a level without a group is simply its own group of one. Backward compatibility is easier to promise than to earn, and the test suite is where you find out which you did.

## 4.0.0 fixed the model instead of the sixth symptom

Six defects across the 3.x line shared one root cause. An instance tracked where it was with a single number, currentLevel, and that number simply cannot describe the approval frontier once a parallel group has several levels open at once. Each release corrected one more reader of it: a return that stepped back into the group it was leaving, an escalation that watched the wrong branch, a workload query that missed half the open work. Six symptoms, one bad field.

So 4.0.0 stopped patching readers and changed the model. Every instance write now goes through a single path that recomputes the frontier first, which means a future operation cannot persist levels without updating the open list. That is the same discipline applied to level construction in 3.0.0 and to the Postgres column list in 1.6.0, both of which had drifted for exactly the same reason: two places building the same thing, and only one of them kept current.

> If you are on 3.x and reading currentLevel to decide who may act, what to notify, or what is overdue, move those reads to openLevels before upgrading. On a sequential template the two always agree, so most upgrades need no change at all. Custom storage adapters must round-trip the new field, as they already must for levels; the bundled PostgresAdapter adds the column through its own migrate step.

## What 1,204 includes, and what I count instead

I checked the number before enjoying it, because npm has always been open about what it counts: HTTP 200 responses served for tarball files, which includes mirrors, CI runners and analysis robots alongside people. Publishing a version guarantees a burst, since every mirror fetches the new tarball, and an evening of releases is visible in the chart for exactly that reason. The registry's own rule of thumb puts confident signal above roughly 50 downloads a day.

That does not make the milestone less real, it makes it specific. The durable figures are the ones that do not move when I publish: 404 tests passing, twelve entry points, eleven open issues from people who read the docs closely enough to find edge cases, and a package that installs cleanly with two runtime dependencies. 1,204 is the headline; those are the reasons it is trending in the right direction, and they are what I will be watching next month.

## The release cadence I would defend, and the one I would not

Between 17:16 UTC on 3 September and 02:14 the next morning I published 28 versions. The content was real, every entry in the changelog is a defect with a reproduction and a named failure mode, and shipping fixes the same evening you find them is the right instinct for a library people build approvals on. The packaging of it was not right, and three things changed as a result:

- Four major bumps in nine hours makes a major bump meaningless. Semantic versioning treats it as a promise that something broke, and a promise made four times before breakfast is not one anybody can act on. A major now needs a written breaking change and a migration note before it is allowed out.
- Fixes accumulate on a branch and go out together. The 28 versions carried no more value than four releases would have, and four releases would have been far easier for anyone pinned to a range to follow.
- An audit is now a scheduled activity rather than an accident. 3.0.0 came from auditing the interactions between features added across 2.x rather than from adding anything new, and it found three defects that no individual feature test would ever have caught.

None of that is a reason to be sorry about the evening itself. A library that ships 4.0.0 eleven weeks after 0.1.0 is a library being used seriously enough to find its own edges, and I would rather have found those six frontier defects myself than have someone find them inside a purchase order.

## Try it without installing anything

The whole package is MIT-licensed and installs in one line, with pg needed only if you want the Postgres adapter. The entry points are split so that importing the engine does not drag in NestJS, the test kit or seven plugins you are not using.

> You can run the library in a browser before deciding anything. RunKit gives you an instant notebook with require('hierarchical-approval') already resolved, and the StackBlitz playground boots a complete purchase-order approval chain, conditions and all, with no local install. Both links are on the npm page, and they are the fastest way to find out whether the template model fits your documents.

Eleven weeks, 37 versions, 404 tests and a best week of 1,204 downloads. The number that made me open the page is not the number that will keep the library alive, but it is a real signal that a thing I built for my own ERP work is now useful outside it, and that is the whole reason to publish anything. Version 4.0.0 is the one I would tell you to install.

hierarchical-approval on npm

> MIT-licensed, TypeScript-first, multi-tenant and audit-ready. Zero runtime dependencies you do not opt into.

## Sources

- hierarchical-approval on npm
- hierarchical-approval — CHANGELOG on GitHub
- npm registry — package download counts API documentation
- Semantic Versioning 2.0.0

## Source links

- https://www.npmjs.com/package/hierarchical-approval
- https://github.com/matthews-wong/hierarchical-approval/blob/main/CHANGELOG.md
- https://github.com/npm/registry/blob/main/docs/download-counts.md
- https://semver.org/

## FAQ

### What is hierarchical-approval and who is it for?

It is an MIT-licensed, TypeScript-first engine for multi-level approval workflows in enterprise systems, aimed at developers building ERP-style flows such as purchase orders, expense claims and contract sign-off. You define a named template once and reuse it for every document of that type. It is multi-tenant, audit-ready and has only two runtime dependencies.

### Can approval levels run in parallel instead of one after another?

Yes, since version 1.0.0. Levels that share a group name open together and join before the chain advances, so Finance and Legal can review the same contract concurrently and the CEO level stays waiting until every branch approves. Rejecting any branch rejects the whole instance, and a group may also lead the chain, in which case it opens at submit.

### What changed in version 4.0.0, and do I need to migrate?

An instance now exposes openLevels, an ascending list of every level currently collecting decisions, because a single currentLevel number could not describe the frontier once a parallel group had several branches open. currentLevel remains as the lowest open level for display and audit. On a sequential template the two always agree, so most callers need no change; move to openLevels anywhere you decide who may act or what is overdue.

### How does the library prevent duplicate or conflicting approvals?

Two mechanisms. Optimistic locking on a version field with a configurable retry policy handles two approvers acting at the same moment, and built-in idempotency keyed by tenant, document and template means a double-click or a network retry returns the existing instance rather than creating a second one. Every mutation is also written to an immutable audit log with an old and new state diff.

### Do 1,204 weekly npm downloads mean 1,204 people are using it?

No, and npm says so openly. The counter records HTTP 200 responses served for tarball files, which includes mirrors, CI runners and analysis robots, and publishing a version guarantees a burst because every mirror fetches the new tarball. It is a directional indicator of activity rather than a user count, which is why the durable signals for this package are its 404 passing tests and its open issues.

---

- HTML version: https://www.matthewswong.com/en/blog/hierarchical-approval-1200-weekly-downloads/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/hierarchical-approval-1200-weekly-downloads/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
