# Saya Membangun Library npm untuk Workflow Approval Hierarkis ERP

> Setelah membangun ulang workflow approval multi-level tiga kali di proyek ERP, saya mengekstrak polanya menjadi library npm TypeScript open-source.

URL: https://www.matthewswong.com/id/blog/hierarchical-approval-npm-library/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-06-21
- Updated: 2026-06-21
- Category: Backend

Delapan belas bulan lalu saya membangun sistem approval purchase order multi-level untuk klien ERP. Enam bulan kemudian saya membangun yang lain untuk pengajuan cuti. Tiga bulan setelah itu, yang ketiga untuk onboarding vendor. Setiap kali logika bisnisnya hampir identik — level sekuensial, rantai kondisional berdasarkan data dokumen, jejak audit, penanganan race condition saat dua approver mengklik secara bersamaan — dan setiap kali saya menulis ulang dari awal. Pada rebuild ketiga saya berhenti, mengekstrak polanya, menambahkan jaminan kebenaran yang telah saya pelajari dengan susah payah, dan mempublikasikannya sebagai hierarchical-approval di npm.

## Kode yang sama, ditulis tiga kali

Setiap modul ERP yang melibatkan uang, headcount, atau kepatuhan memiliki langkah approval. Bentuknya bervariasi — purchase order diarahkan berdasarkan jumlah, permintaan cuti berdasarkan departemen, onboarding vendor berdasarkan tingkat risiko — tetapi mekanisme dasarnya sama. Anda membutuhkan: definisi rantai yang dapat digunakan kembali (template), instance yang melacak kemajuan setiap dokumen melalui rantai tersebut, kemajuan level sekuensial, penyisipan level kondisional saat submit, log audit yang tidak dapat diubah, dan penanganan kasus di mana dua approver bertindak pada level yang sama secara bersamaan. Menulis itu dengan benar setiap saat membutuhkan waktu, dan bagian yang halus — terutama penanganan konkurensi — mudah salah.

## Mengapa library yang ada tidak cocok

Saya melihat apa yang tersedia di npm sebelum menulis sendiri. Workflow engine serbaguna memang powerful tetapi mengharuskan Anda mengimplementasikan setiap guard, setiap kondisi, setiap entri audit sendiri — mereka memberi Anda state machine, bukan workflow approval. Package khusus approval yang saya temukan sudah tidak terawat, tidak memiliki dukungan TypeScript, dan sebagian besar hanya untuk single-level. Tidak ada yang memiliki idempotency, tidak ada yang memiliki optimistic locking, dan tidak ada yang memiliki cara untuk menyuntikkan test clock. Poin terakhir itulah faktor penentu: jika saya tidak dapat mengontrol waktu dalam pengujian, saya tidak dapat menguji logika SLA dan eskalasi secara andal tanpa penantian nyata.

### Keputusan desain utama: template dan instance

Abstraksi sentral adalah template — definisi rantai yang dapat digunakan kembali yang disimpan berdasarkan nama. Ketika sebuah dokumen disubmit, engine membuat instance dari template tersebut dan mengevaluasi aturan kondisional terhadap data dokumen untuk menentukan level mana yang berlaku. Kondisi dievaluasi sekali saat submit dan dibekukan ke dalam instance, sehingga perubahan template di tengah jalan tidak pernah mempengaruhi approval yang sedang berjalan. Instance membawa nomor versi untuk optimistic locking dan kunci idempotency sehingga dokumen yang disubmit dua kali mengembalikan instance yang sudah ada alih-alih membuat duplikat.

> Kunci idempotency secara default adalah hash SHA-256 dari tenantId + documentType + documentId + templateName. Anda dapat menggantinya dengan fungsi kustom jika logika bisnis Anda memerlukan kunci gabungan — misalnya, menyertakan periode fiskal sehingga dokumen dapat melewati template yang sama lagi di periode baru.

## Memulai: definisikan template, submit, setujui

API-nya sengaja dibuat minimal. Definisikan template sekali, lalu panggil submit, approve, reject, delegate, atau cancel pada instance mana pun. Library menangani kemajuan level, pelacakan SLA, penjadwalan eskalasi, dan emisi event secara otomatis. Berikut alur approval purchase order lengkap dengan level keuangan kondisional:

## Empat masalah yang memerlukan rekayasa nyata

Terlepas dari API yang bersih, sebagian besar kode library ada di empat masalah kebenaran yang muncul dalam produksi tetapi tidak terlihat dalam contoh sederhana.

### Konkurensi: optimistic locking dengan retry

Dalam ERP dengan ratusan pengguna aktif, dua manajer dapat menyetujui level yang sama secara bersamaan. Tanpa kontrol konkurensi, kedua pembacaan melihat versi 1, kedua penulisan berhasil, dan approval kedua secara diam-diam menimpa yang pertama — log audit kehilangan entri dan level mungkin melewati secara salah. Library menyelesaikan ini dengan field versi pada setiap instance. Setiap penulisan memanggil updateInstance dengan versi yang dibaca; adapter penyimpanan melempar ApprovalConflictError jika versi yang tersimpan telah berubah. Engine mencoba ulang secara otomatis dengan exponential backoff, dapat dikonfigurasi melalui retryPolicy.

### Idempotency: retry jaringan dan double-click

Retry jaringan dan double-click tombol adalah masalah nyata di UI approval. Mensubmit dokumen yang sama dua kali seharusnya tidak membuat dua instance. Library meng-hash konteks submission menjadi kunci idempotency, memeriksa instance yang sudah ada dengan kunci tersebut sebelum menulis, dan mengembalikan yang sudah ada jika ditemukan. Ini transparan bagi pemanggil — panggilan submit kedua mengembalikan instance yang sama seperti yang pertama tanpa penanganan khusus.

> Snapshot template melindungi instance yang sedang berjalan dari pembaruan template. Ketika Anda memanggil updateTemplate, engine membuat versi template baru dengan penghitung versi yang dinaikkan. Instance yang sudah ada mempertahankan konfigurasi eskalasi dan SLA asli mereka dalam templateSnapshot yang dibekukan. Perbarui template dengan bebas — Anda tidak akan merusak approval yang sedang berjalan.

## Adapter enterprise: sambungkan ke infrastruktur Anda

Library ini dirancang untuk hidup di dalam infrastruktur yang sudah Anda miliki. Enam antarmuka adapter memungkinkan Anda menghubungkan sistem notifikasi, backend metrik, audit sink, job scheduler, kebijakan otorisasi, dan middleware operasi tanpa mem-fork library. Semua adapter bersifat fire-and-forget — kegagalan pada adapter mana pun ditangkap, dicatat, dan diabaikan. Koneksi Kafka yang rusak tidak akan pernah mencegah approval selesai.

## Menguji logika approval tanpa kerumitan

Bagian tersulit dalam menguji workflow approval adalah waktu. Pengujian SLA breach, eskalasi, dan deadline semuanya memerlukan waktu untuk berlalu, dan timer nyata dalam pengujian itu lambat, tidak dapat diandalkan, dan mimpi buruk untuk di-debug. Library menyertakan ManualClock dan ApprovalTestKit di sub-path pengujian khusus. ManualClock memungkinkan Anda melompat beberapa hari seketika. ApprovalTestKit.create memberi Anda engine yang sudah terkonfigurasi dengan MemoryAdapter dan ManualClock. ApprovalTestKit.fullyApprove menjalankan instance melalui semua level dalam satu panggilan. Tidak ada timer nyata, tidak ada database, deterministik dalam semua kasus.

## Instal dan jelajahi

Library ini diterbitkan di npm sebagai hierarchical-approval, ditulis dalam TypeScript 5 dengan strict mode diaktifkan, output CJS dan ESM ganda, serta deklarasi tipe lengkap. Peer dependency pada pg bersifat opsional dan hanya diperlukan untuk adapter Postgres. Sub-path pengujian sengaja dipisahkan dari bundle utama agar tidak pernah dikirim ke produksi.

hierarchical-approval di npm

> TypeScript-first. CJS/ESM ganda. Tidak ada peer dep yang diperlukan untuk inti.

## Sumber dan bacaan lanjutan

- hierarchical-approval di npmjs.com
- Wikipedia — Optimistic Concurrency Control
- The Twelve-Factor App — Backing Services

## Source links

- https://www.npmjs.com/package/hierarchical-approval
- https://en.wikipedia.org/wiki/Optimistic_concurrency_control
- https://12factor.net/backing-services

## FAQ

### Mengapa membangun library npm khusus daripada menulis logika approval langsung di dalam aplikasi?

Pola approval multi-level yang sama — level sekuensial, rantai kondisional, log audit, dan penanganan konkurensi — muncul di hampir setiap modul ERP yang menyangkut uang atau kepatuhan: purchase order, pengajuan cuti, onboarding vendor, rilis anggaran. Menulis semuanya dengan benar setiap kali membutuhkan waktu, dan bagian yang halus seperti approval bersamaan dan idempotency pada retry jaringan mudah salah. Mengekstraknya ke dalam library yang sudah diuji dan diverifikasi berarti logikanya ditulis sekali dan jaminan kebenarannya ikut tersedia di mana pun library digunakan.

### Bagaimana hierarchical-approval mencegah dua manajer menyetujui step yang sama secara bersamaan?

Setiap instance menyimpan nomor versi. Saat approval disubmit, engine memanggil updateInstance dengan versi yang dibaca dari database. Adapter penyimpanan membandingkan versi yang diharapkan dengan yang tersimpan dan melempar ApprovalConflictError jika berbeda, artinya penulis lain sudah meng-commit perubahan. Engine mencoba ulang secara otomatis dengan exponential backoff dan jitter opsional, yang dapat dikonfigurasi melalui opsi retryPolicy. Ini adalah optimistic locking standar — tidak memerlukan row lock di level database.

### Apa arti idempotency dalam konteks library ini, dan mengapa penting untuk UI approval?

Idempotency berarti mensubmit dokumen yang sama dua kali mengembalikan instance approval yang sama, bukan membuat duplikat. Library ini meng-hash konteks pengajuan (tenantId, documentType, documentId, templateName) menjadi sebuah kunci, memeriksa instance yang sudah ada dengan kunci tersebut sebelum menulis, dan mengembalikannya jika ditemukan. Ini penting karena retry jaringan dan double-click tombol Submit adalah hal umum di UI nyata — tanpa idempotency, respons lambat diikuti retry akan membuat dua workflow approval terpisah untuk dokumen yang sama.

### Bisakah saya menguji SLA breach dan logika eskalasi tanpa menunggu timer sungguhan?

Bisa. Library ini menyertakan ManualClock di sub-path hierarchical-approval/testing. Anda meneruskannya ke engine melalui opsi clock dan ia menggantikan semua pemanggilan new Date() internal. Panggil clock.advanceDays(3) untuk melompat tiga hari seketika, lalu panggil engine.escalation.tick() untuk memicu pengecekan scheduler secara manual. Helper ApprovalTestKit.create menggabungkan ManualClock dan MemoryAdapter sehingga Anda bisa menulis pengujian deterministik sepenuhnya tanpa I/O nyata dan tanpa pemanggilan sleep.

### Backend penyimpanan apa yang didukung hierarchical-approval, dan bisakah saya membawa milik sendiri?

Library ini menyertakan MemoryAdapter untuk pengembangan dan pengujian, serta PostgresAdapter untuk produksi. Database apa pun dapat digunakan dengan mengimplementasikan antarmuka IStorageAdapter, yang memiliki metode untuk menyimpan dan membaca template serta instance, pagination offset, pagination kursor, pencarian kunci idempotency, dan penambahan entri audit. Satu kontrak yang tidak bisa ditawar ada di updateInstance: harus membandingkan argumen expectedVersion dengan versi yang tersimpan dan melempar ApprovalConflictError jika berbeda — itulah fondasi dari jaminan optimistic locking.

---

- HTML version: https://www.matthewswong.com/id/blog/hierarchical-approval-npm-library/
- English: https://www.matthewswong.com/en/blog/hierarchical-approval-npm-library/
- 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
