hierarchical-approval 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +232 -0
- package/dist/{ApprovalEngine-DICBgdIp.d.ts → ApprovalEngine-Bmg1bbrd.d.cts} +9 -190
- package/dist/{ApprovalEngine-CSxLWZ5h.d.cts → ApprovalEngine-DLyhejR-.d.ts} +9 -190
- package/dist/Clock-3FnOczFJ.d.cts +6 -0
- package/dist/Clock-3FnOczFJ.d.ts +6 -0
- package/dist/IAuditAdapter-B3vvU09m.d.ts +12 -0
- package/dist/IAuditAdapter-B_DhuPsU.d.cts +12 -0
- package/dist/IMetricsAdapter-D9PUz4tM.d.cts +9 -0
- package/dist/IMetricsAdapter-D9PUz4tM.d.ts +9 -0
- package/dist/INotificationAdapter-BdfVjYa8.d.ts +102 -0
- package/dist/INotificationAdapter-DVVmXU6a.d.cts +102 -0
- package/dist/IOperationMiddleware-CkRh93Wf.d.cts +62 -0
- package/dist/IOperationMiddleware-Dk_xOKxR.d.ts +62 -0
- package/dist/IStorageAdapter-DbeWhtCG.d.cts +47 -0
- package/dist/IStorageAdapter-EdwtErKD.d.ts +47 -0
- package/dist/Logger-CIzvalFc.d.cts +9 -0
- package/dist/Logger-CIzvalFc.d.ts +9 -0
- package/dist/adapters/MemoryAdapter.cjs +1 -6
- package/dist/adapters/MemoryAdapter.cjs.map +1 -1
- package/dist/adapters/MemoryAdapter.d.cts +3 -2
- package/dist/adapters/MemoryAdapter.d.ts +3 -2
- package/dist/adapters/MemoryAdapter.js +1 -6
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgresAdapter.d.cts +2 -1
- package/dist/adapters/PostgresAdapter.d.ts +2 -1
- package/dist/index.cjs +1 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/{IStorageAdapter-B2xGuaKi.d.cts → instance-D8D7b07N.d.cts} +1 -45
- package/dist/{IStorageAdapter-B2xGuaKi.d.ts → instance-D8D7b07N.d.ts} +1 -45
- package/dist/plugins/audit.cjs +370 -0
- package/dist/plugins/audit.cjs.map +1 -0
- package/dist/plugins/audit.d.cts +292 -0
- package/dist/plugins/audit.d.ts +292 -0
- package/dist/plugins/audit.js +362 -0
- package/dist/plugins/audit.js.map +1 -0
- package/dist/plugins/metrics.cjs +406 -0
- package/dist/plugins/metrics.cjs.map +1 -0
- package/dist/plugins/metrics.d.cts +282 -0
- package/dist/plugins/metrics.d.ts +282 -0
- package/dist/plugins/metrics.js +399 -0
- package/dist/plugins/metrics.js.map +1 -0
- package/dist/plugins/notify.cjs +373 -0
- package/dist/plugins/notify.cjs.map +1 -0
- package/dist/plugins/notify.d.cts +349 -0
- package/dist/plugins/notify.d.ts +349 -0
- package/dist/plugins/notify.js +368 -0
- package/dist/plugins/notify.js.map +1 -0
- package/dist/plugins/resilience.cjs +328 -0
- package/dist/plugins/resilience.cjs.map +1 -0
- package/dist/plugins/resilience.d.cts +310 -0
- package/dist/plugins/resilience.d.ts +310 -0
- package/dist/plugins/resilience.js +321 -0
- package/dist/plugins/resilience.js.map +1 -0
- package/dist/testing.cjs +1 -6
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +9 -2
- package/dist/testing.d.ts +9 -2
- package/dist/testing.js +1 -6
- package/dist/testing.js.map +1 -1
- package/package.json +22 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `hierarchical-approval` are documented here. This project
|
|
4
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## [0.3.0] - 2026-06-26
|
|
7
|
+
|
|
8
|
+
### Added — enterprise plug-in layer
|
|
9
|
+
|
|
10
|
+
Production-grade implementations of the existing engine extension points, each
|
|
11
|
+
published on its own tree-shakeable import subpath with **zero runtime
|
|
12
|
+
dependencies** (Node.js built-ins only). The core engine is unchanged.
|
|
13
|
+
|
|
14
|
+
- **`hierarchical-approval/plugins/audit`**
|
|
15
|
+
- `HashChainAuditAdapter` — SHA-256 hash-chained, tamper-evident audit log.
|
|
16
|
+
`verify()` detects content tampering, deletion, reordering, **and tail
|
|
17
|
+
truncation** (via an in-process high-water mark or an explicit
|
|
18
|
+
`expectedLength` anchor). Pluggable writer/reader; in-memory by default.
|
|
19
|
+
- `RedactingAuditAdapter` — redacts configured PII field paths and free-text
|
|
20
|
+
fields before forwarding to a wrapped adapter; original entries are never
|
|
21
|
+
mutated.
|
|
22
|
+
- `CompositeAuditAdapter` — fan out to multiple audit sinks with per-child
|
|
23
|
+
fault isolation. Never throws.
|
|
24
|
+
- **`hierarchical-approval/plugins/metrics`**
|
|
25
|
+
- `PrometheusMetricsAdapter` — accumulates counters/histograms and renders
|
|
26
|
+
`scrape()` in Prometheus text exposition format.
|
|
27
|
+
- `InMemoryMetricsAdapter` — `snapshot()` with count/sum/min/max/avg/p50/p95;
|
|
28
|
+
O(1) ring-buffer sample retention.
|
|
29
|
+
- `CompositeMetricsAdapter` — fan out to multiple metrics backends.
|
|
30
|
+
- **`hierarchical-approval/plugins/resilience`**
|
|
31
|
+
- `RbacAuthorizationPolicy` — per-operation role rules with a default-deny
|
|
32
|
+
posture and a pluggable role provider.
|
|
33
|
+
- `CompositeAuthorizationPolicy` — combine policies with AND/OR semantics.
|
|
34
|
+
- `RateLimitMiddleware` — token-bucket throttling keyed per actor + operation,
|
|
35
|
+
with an injectable clock.
|
|
36
|
+
- `LoggingMiddleware` — structured before/after/onError logging with timings.
|
|
37
|
+
- **`hierarchical-approval/plugins/notify`**
|
|
38
|
+
- `OutboxNotificationAdapter` — transactional outbox with retry, exponential
|
|
39
|
+
backoff, and a dead-letter queue for at-least-once delivery. Exposes
|
|
40
|
+
`drain()`, `pending()`, and `deadLettered()`.
|
|
41
|
+
- `TemplatedNotificationAdapter` — render a human-readable message per event
|
|
42
|
+
type via a configurable template map.
|
|
43
|
+
- `CompositeNotificationAdapter` — multi-channel fan-out with fault isolation.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- `package.json` `exports` and the build now expose the four `plugins/*`
|
|
48
|
+
subpaths in ESM, CJS, and `.d.ts`.
|
|
49
|
+
|
|
50
|
+
### Tests
|
|
51
|
+
|
|
52
|
+
- Test suite grew from 196 to 380 passing tests covering the new plug-ins
|
|
53
|
+
(tamper detection, backoff/dead-letter, rate-limit math, RBAC, PII redaction,
|
|
54
|
+
Prometheus exposition, and the public-export surface).
|
|
55
|
+
|
|
56
|
+
## [0.2.1] - 2026-06-23
|
|
57
|
+
|
|
58
|
+
- Documentation and tooling fixes; audit-log de-duplication in `MemoryAdapter`.
|
|
59
|
+
|
|
60
|
+
## [0.2.0] - 2026-06-23
|
|
61
|
+
|
|
62
|
+
- Advanced decision modes (`quorum`, `weighted`), `reassign()`, `getStatistics()`,
|
|
63
|
+
and an injectable `BusinessCalendar`.
|
|
64
|
+
|
|
65
|
+
## [0.1.1] - 2026-06-21
|
|
66
|
+
|
|
67
|
+
- Initial published release line.
|
package/README.md
CHANGED
|
@@ -28,6 +28,32 @@ npm install pg @types/pg
|
|
|
28
28
|
|
|
29
29
|
> 📖 Full docs & live guides: **[hierarchical-approval.matthewswong.com](https://hierarchical-approval.matthewswong.com)**
|
|
30
30
|
|
|
31
|
+
<details>
|
|
32
|
+
<summary><strong>Table of contents</strong></summary>
|
|
33
|
+
|
|
34
|
+
- [Why another approval library?](#why-another-approval-library)
|
|
35
|
+
- [How it works](#how-it-works) — the 30-second mental model + diagrams
|
|
36
|
+
- [Quick start](#quick-start)
|
|
37
|
+
- [Core concepts](#core-concepts)
|
|
38
|
+
- [Installation and setup](#installation-and-setup)
|
|
39
|
+
- [`new ApprovalEngine(options)`](#new-approvalengineoptions)
|
|
40
|
+
- [Approver types](#approver-types)
|
|
41
|
+
- [Lifecycle operations](#lifecycle-operations)
|
|
42
|
+
- [Queries](#queries)
|
|
43
|
+
- [Template management](#template-management)
|
|
44
|
+
- [Utility methods](#utility-methods)
|
|
45
|
+
- [Events](#events)
|
|
46
|
+
- [Enterprise adapters](#enterprise-adapters)
|
|
47
|
+
- [Custom storage adapter](#custom-storage-adapter)
|
|
48
|
+
- [Testing](#testing)
|
|
49
|
+
- [Error handling](#error-handling)
|
|
50
|
+
- [Multi-tenancy](#multi-tenancy)
|
|
51
|
+
- [Template reference](#template-reference)
|
|
52
|
+
- [FAQ](#faq)
|
|
53
|
+
- [License](#license)
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
31
57
|
---
|
|
32
58
|
|
|
33
59
|
## Why another approval library?
|
|
@@ -57,6 +83,75 @@ Approval workflows are deceptively simple until they aren't. Most teams start wi
|
|
|
57
83
|
|
|
58
84
|
---
|
|
59
85
|
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
**The 30-second mental model:** you define a reusable **template** (the approval blueprint), then **submit** documents against it. Each submission becomes an **instance** that walks through the template's **levels** one at a time. At each level the configured **approvers** act, and the level's **mode** decides when that level is satisfied. When the last level passes, the instance is `approved`.
|
|
89
|
+
|
|
90
|
+
| Term | What it is |
|
|
91
|
+
|---|---|
|
|
92
|
+
| **Template** | A named, reusable blueprint — ordered levels, approvers, modes, conditions, escalation, SLA. Define once, reuse for every document of that type. |
|
|
93
|
+
| **Instance** | One running approval: a specific document moving through a template. Holds the live state, the audit log, and a snapshot of the template at submit time. |
|
|
94
|
+
| **Level** | A single step in the chain (e.g. "Manager", then "Finance"). Levels run **sequentially**. |
|
|
95
|
+
| **Approver** | Who may act on a level — a fixed `user`, a `role`, or someone resolved dynamically at runtime. |
|
|
96
|
+
| **Mode** | How a level passes: `any`, `all`, `majority`, `quorum` (N-of-M), or `weighted`. |
|
|
97
|
+
|
|
98
|
+
### The status lifecycle
|
|
99
|
+
|
|
100
|
+
```mermaid
|
|
101
|
+
stateDiagram-v2
|
|
102
|
+
[*] --> pending
|
|
103
|
+
pending --> pending: approve advances a level
|
|
104
|
+
pending --> approved: final level or override
|
|
105
|
+
pending --> rejected: reject
|
|
106
|
+
pending --> cancelled: cancel
|
|
107
|
+
pending --> expired: deadline reached
|
|
108
|
+
approved --> [*]
|
|
109
|
+
rejected --> [*]
|
|
110
|
+
cancelled --> [*]
|
|
111
|
+
expired --> [*]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
> `submit()` creates the instance in `pending`; `resubmit()` on a rejected instance spawns a new linked instance starting again at level 1.
|
|
115
|
+
|
|
116
|
+
### How an approval flows
|
|
117
|
+
|
|
118
|
+
A level is satisfied according to its mode (`any`, `all`, `majority`, `quorum`, or `weighted`):
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
flowchart TD
|
|
122
|
+
S[Submit] --> R[Resolve approvers for current level]
|
|
123
|
+
R --> ACT{An approver acts}
|
|
124
|
+
ACT -->|approve| MET{Level threshold met}
|
|
125
|
+
ACT -->|reject| REJ{Level rejected}
|
|
126
|
+
MET -->|not yet| ACT
|
|
127
|
+
MET -->|yes| MORE{More levels}
|
|
128
|
+
MORE -->|yes| ADV[Advance to next level]
|
|
129
|
+
ADV --> R
|
|
130
|
+
MORE -->|no| DONE[Status approved]
|
|
131
|
+
REJ -->|yes| OUT[Status rejected]
|
|
132
|
+
REJ -->|no| ACT
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Architecture — a small core with pluggable ports
|
|
136
|
+
|
|
137
|
+
The engine never talks to your database, queue, or notification service directly — it talks to **interfaces you implement** (or use the built-ins). Only storage is required; everything else is opt-in.
|
|
138
|
+
|
|
139
|
+
Solid arrow = required (storage). Dotted arrows = optional ports you can plug in:
|
|
140
|
+
|
|
141
|
+
```mermaid
|
|
142
|
+
flowchart LR
|
|
143
|
+
APP[Your application] --> ENG[ApprovalEngine]
|
|
144
|
+
ENG --> ST[IStorageAdapter - Memory, Postgres or custom]
|
|
145
|
+
ENG -.-> NO[INotificationAdapter]
|
|
146
|
+
ENG -.-> AU[IAuditAdapter]
|
|
147
|
+
ENG -.-> ME[IMetricsAdapter]
|
|
148
|
+
ENG -.-> SC[ISchedulerAdapter]
|
|
149
|
+
ENG -.-> AZ[IAuthorizationPolicy]
|
|
150
|
+
ENG -.-> MW[IOperationMiddleware]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
60
155
|
## Quick start
|
|
61
156
|
|
|
62
157
|
```ts
|
|
@@ -837,6 +932,109 @@ Middleware errors are caught and logged — they never propagate to callers.
|
|
|
837
932
|
|
|
838
933
|
---
|
|
839
934
|
|
|
935
|
+
## Built-in enterprise plug-ins
|
|
936
|
+
|
|
937
|
+
The interfaces above are ports. The library also ships production-grade implementations of them, each on its own import subpath so you only pull in what you use (fully tree-shakeable, zero extra dependencies — `node:crypto` only).
|
|
938
|
+
|
|
939
|
+
### `plugins/audit` — tamper-evident & PII-safe audit
|
|
940
|
+
|
|
941
|
+
```ts
|
|
942
|
+
import {
|
|
943
|
+
HashChainAuditAdapter,
|
|
944
|
+
RedactingAuditAdapter,
|
|
945
|
+
CompositeAuditAdapter,
|
|
946
|
+
} from 'hierarchical-approval/plugins/audit';
|
|
947
|
+
|
|
948
|
+
// Each entry is SHA-256-chained to the previous one (per tenant+instance).
|
|
949
|
+
const chain = new HashChainAuditAdapter();
|
|
950
|
+
|
|
951
|
+
// Redact PII before it reaches external sinks (originals are never mutated).
|
|
952
|
+
const redacted = new RedactingAuditAdapter({
|
|
953
|
+
inner: chain,
|
|
954
|
+
fieldPaths: ['newValue.applicant.ssn', 'newValue.card.*'],
|
|
955
|
+
freeTextFields: ['comment', 'reason'],
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const engine = new ApprovalEngine({ adapter, auditAdapter: redacted });
|
|
959
|
+
|
|
960
|
+
// Later — prove the log was not altered, deleted, or reordered:
|
|
961
|
+
const result = chain.verify(tenantId, instanceId);
|
|
962
|
+
// { ok: true } | { ok: false, brokenAt: <seq> }
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
`verify()` detects content tampering, deletion, reordering, **and tail truncation** (via an in-process high-water mark, or an explicit `expectedLength` anchor that survives restarts). Use `CompositeAuditAdapter` to fan out to several sinks (Kafka, S3, CloudTrail) with per-child fault isolation.
|
|
966
|
+
|
|
967
|
+
### `plugins/metrics` — Prometheus & dashboards
|
|
968
|
+
|
|
969
|
+
```ts
|
|
970
|
+
import {
|
|
971
|
+
PrometheusMetricsAdapter,
|
|
972
|
+
InMemoryMetricsAdapter,
|
|
973
|
+
} from 'hierarchical-approval/plugins/metrics';
|
|
974
|
+
|
|
975
|
+
const metrics = new PrometheusMetricsAdapter();
|
|
976
|
+
const engine = new ApprovalEngine({ adapter, metricsAdapter: metrics });
|
|
977
|
+
|
|
978
|
+
// Expose on /metrics:
|
|
979
|
+
res.type('text/plain').send(metrics.scrape()); // valid Prometheus exposition format
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
`InMemoryMetricsAdapter.snapshot()` returns counters and timing stats (count/sum/min/max/avg/p50/p95) for dashboards and tests; `CompositeMetricsAdapter` feeds both at once.
|
|
983
|
+
|
|
984
|
+
### `plugins/resilience` — RBAC, rate limiting, structured logging
|
|
985
|
+
|
|
986
|
+
```ts
|
|
987
|
+
import {
|
|
988
|
+
RbacAuthorizationPolicy,
|
|
989
|
+
CompositeAuthorizationPolicy,
|
|
990
|
+
RateLimitMiddleware,
|
|
991
|
+
LoggingMiddleware,
|
|
992
|
+
} from 'hierarchical-approval/plugins/resilience';
|
|
993
|
+
|
|
994
|
+
const engine = new ApprovalEngine({
|
|
995
|
+
adapter,
|
|
996
|
+
authorizationPolicy: new RbacAuthorizationPolicy({
|
|
997
|
+
defaultMode: 'deny', // closed by default — unlisted operations are denied
|
|
998
|
+
rules: {
|
|
999
|
+
approve: { roles: ['manager', 'director'] }, // match: 'any' by default
|
|
1000
|
+
override: { roles: ['admin'] },
|
|
1001
|
+
},
|
|
1002
|
+
roleProvider: (actorId, tenantId) => roleStore.rolesFor(actorId, tenantId),
|
|
1003
|
+
}),
|
|
1004
|
+
middleware: [
|
|
1005
|
+
new RateLimitMiddleware({ capacity: 20, refillTokensPerSecond: 5 }), // token bucket per actor+op
|
|
1006
|
+
new LoggingMiddleware({ logger }),
|
|
1007
|
+
],
|
|
1008
|
+
});
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
`CompositeAuthorizationPolicy` combines policies with AND (first denial wins) or OR (any allow) semantics.
|
|
1012
|
+
|
|
1013
|
+
### `plugins/notify` — reliable, multi-channel delivery
|
|
1014
|
+
|
|
1015
|
+
```ts
|
|
1016
|
+
import {
|
|
1017
|
+
OutboxNotificationAdapter,
|
|
1018
|
+
TemplatedNotificationAdapter,
|
|
1019
|
+
CompositeNotificationAdapter,
|
|
1020
|
+
} from 'hierarchical-approval/plugins/notify';
|
|
1021
|
+
|
|
1022
|
+
// At-least-once delivery: transactional outbox + exponential backoff + dead-letter.
|
|
1023
|
+
const outbox = new OutboxNotificationAdapter({
|
|
1024
|
+
transport: async (event) => emailGateway.send(event),
|
|
1025
|
+
maxAttempts: 5,
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
const engine = new ApprovalEngine({ adapter, notificationAdapter: outbox });
|
|
1029
|
+
|
|
1030
|
+
await outbox.drain(); // deliver due items (call from a worker/cron)
|
|
1031
|
+
await outbox.deadLettered(); // inspect permanently-failed events
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
`TemplatedNotificationAdapter` renders a human-readable message per event type; `CompositeNotificationAdapter` fans out to multiple channels with fault isolation.
|
|
1035
|
+
|
|
1036
|
+
---
|
|
1037
|
+
|
|
840
1038
|
## Custom storage adapter
|
|
841
1039
|
|
|
842
1040
|
Implement `IStorageAdapter` to use any database:
|
|
@@ -1078,6 +1276,40 @@ Posts by the author on real-world approval workflow patterns:
|
|
|
1078
1276
|
|
|
1079
1277
|
---
|
|
1080
1278
|
|
|
1279
|
+
## FAQ
|
|
1280
|
+
|
|
1281
|
+
**Is this a BPMN / workflow engine?**
|
|
1282
|
+
No. It does one thing well — **hierarchical, multi-level approvals**. There are no arbitrary task graphs, timers-as-nodes, or scripting steps. If you need a general business-process engine, reach for Camunda/Temporal; if you need approvals, this is far simpler.
|
|
1283
|
+
|
|
1284
|
+
**Does it come with a UI?**
|
|
1285
|
+
No — it's a headless engine. You own the UI and notifications; the library gives you the state machine, events (`approval:*`), and queries (`getPendingFor`, `queryInstances`) to build them.
|
|
1286
|
+
|
|
1287
|
+
**When should I *not* use it?**
|
|
1288
|
+
Single-approver, single-step sign-offs (a boolean column is enough); free-form DAG workflows; or anything needing a visual process designer.
|
|
1289
|
+
|
|
1290
|
+
**How are concurrent approvals handled?**
|
|
1291
|
+
Every mutation uses **optimistic locking** on a `version` field with a configurable retry policy, so two approvers acting at once can't corrupt state. See [Error handling](#error-handling).
|
|
1292
|
+
|
|
1293
|
+
**Does it need a database?**
|
|
1294
|
+
No — `MemoryAdapter` runs everything in-process (great for tests and small apps). For production, use the built-in `PostgresAdapter` or implement the 8-method [`IStorageAdapter`](#custom-storage-adapter) for any store.
|
|
1295
|
+
|
|
1296
|
+
**Can approvers change while a request is in flight?**
|
|
1297
|
+
Yes. Approvers are resolved per-level when that level becomes active, so role membership and dynamic resolvers reflect the current org. In-flight instances are insulated from **template** edits via a snapshot taken at submit time.
|
|
1298
|
+
|
|
1299
|
+
**How do escalation and SLA timers fire?**
|
|
1300
|
+
A built-in poller advances them; for distributed/production setups plug in an [`ISchedulerAdapter`](#scheduler) (BullMQ, Temporal, EventBridge). Deadlines can count calendar days or **business days** via an injectable calendar.
|
|
1301
|
+
|
|
1302
|
+
**Is it multi-tenant?**
|
|
1303
|
+
Yes — every record is scoped by `tenantId`. See [Multi-tenancy](#multi-tenancy).
|
|
1304
|
+
|
|
1305
|
+
**ESM or CommonJS?**
|
|
1306
|
+
Both — the package ships dual ESM + CJS builds with full TypeScript declarations.
|
|
1307
|
+
|
|
1308
|
+
**Runnable examples?**
|
|
1309
|
+
See the [`examples/`](./examples) directory — purchase orders, conditional chains, quorum/weighted voting, and delegation + escalation, each runnable with `node`.
|
|
1310
|
+
|
|
1311
|
+
---
|
|
1312
|
+
|
|
1081
1313
|
## License
|
|
1082
1314
|
|
|
1083
1315
|
MIT
|
|
@@ -1,87 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DbeWhtCG.cjs';
|
|
2
|
+
import { m as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-D8D7b07N.cjs';
|
|
3
|
+
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DVVmXU6a.cjs';
|
|
2
4
|
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
timestamp: Date;
|
|
9
|
-
}
|
|
10
|
-
interface SubmittedEvent extends ApprovalEvent {
|
|
11
|
-
submittedBy: string;
|
|
12
|
-
currentApprovers: string[];
|
|
13
|
-
}
|
|
14
|
-
interface ApprovedEvent extends ApprovalEvent {
|
|
15
|
-
approverId: string;
|
|
16
|
-
level: number;
|
|
17
|
-
comment?: string;
|
|
18
|
-
isFinal: boolean;
|
|
19
|
-
}
|
|
20
|
-
interface RejectedEvent extends ApprovalEvent {
|
|
21
|
-
approverId: string;
|
|
22
|
-
level: number;
|
|
23
|
-
reason: string;
|
|
24
|
-
returnTo: 'originator' | 'previous' | null;
|
|
25
|
-
}
|
|
26
|
-
interface DelegatedEvent extends ApprovalEvent {
|
|
27
|
-
fromApprover: string;
|
|
28
|
-
toApprover: string;
|
|
29
|
-
level: number;
|
|
30
|
-
reason: string;
|
|
31
|
-
}
|
|
32
|
-
interface ReassignedEvent extends ApprovalEvent {
|
|
33
|
-
reassignedBy: string;
|
|
34
|
-
fromApprover: string;
|
|
35
|
-
toApprover: string;
|
|
36
|
-
level: number;
|
|
37
|
-
reason: string;
|
|
38
|
-
}
|
|
39
|
-
interface EscalatedEvent extends ApprovalEvent {
|
|
40
|
-
level: number;
|
|
41
|
-
escalatedTo: string;
|
|
42
|
-
}
|
|
43
|
-
interface CancelledEvent extends ApprovalEvent {
|
|
44
|
-
cancelledBy: string;
|
|
45
|
-
reason: string;
|
|
46
|
-
}
|
|
47
|
-
interface LevelAdvancedEvent extends ApprovalEvent {
|
|
48
|
-
fromLevel: number;
|
|
49
|
-
toLevel: number;
|
|
50
|
-
newApprovers: string[];
|
|
51
|
-
}
|
|
52
|
-
interface ResubmittedEvent extends ApprovalEvent {
|
|
53
|
-
resubmittedBy: string;
|
|
54
|
-
originalInstanceId: string;
|
|
55
|
-
}
|
|
56
|
-
interface OverriddenEvent extends ApprovalEvent {
|
|
57
|
-
overriddenBy: string;
|
|
58
|
-
justification: string;
|
|
59
|
-
}
|
|
60
|
-
interface ExpiredEvent extends ApprovalEvent {
|
|
61
|
-
deadlineAction: 'cancel' | 'reject';
|
|
62
|
-
}
|
|
63
|
-
interface SlaBreachedEvent extends ApprovalEvent {
|
|
64
|
-
slaDeadlineAt: Date;
|
|
65
|
-
}
|
|
66
|
-
interface ApprovalEventMap {
|
|
67
|
-
'approval:submitted': SubmittedEvent;
|
|
68
|
-
'approval:approved': ApprovedEvent;
|
|
69
|
-
'approval:rejected': RejectedEvent;
|
|
70
|
-
'approval:delegated': DelegatedEvent;
|
|
71
|
-
'approval:reassigned': ReassignedEvent;
|
|
72
|
-
'approval:escalated': EscalatedEvent;
|
|
73
|
-
'approval:cancelled': CancelledEvent;
|
|
74
|
-
'approval:completed': ApprovalInstance;
|
|
75
|
-
'approval:level_advanced': LevelAdvancedEvent;
|
|
76
|
-
'approval:resubmitted': ResubmittedEvent;
|
|
77
|
-
'approval:overridden': OverriddenEvent;
|
|
78
|
-
'approval:expired': ExpiredEvent;
|
|
79
|
-
'approval:sla_breached': SlaBreachedEvent;
|
|
80
|
-
}
|
|
81
|
-
type ApprovalEventName = keyof ApprovalEventMap;
|
|
82
|
-
interface HistoryEntry extends AuditEntry {
|
|
83
|
-
instanceId: string;
|
|
84
|
-
}
|
|
5
|
+
import { L as Logger } from './Logger-CIzvalFc.cjs';
|
|
6
|
+
import { C as Clock } from './Clock-3FnOczFJ.cjs';
|
|
7
|
+
import { I as IAuthorizationPolicy, g as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-CkRh93Wf.cjs';
|
|
8
|
+
import { I as IAuditAdapter } from './IAuditAdapter-B_DhuPsU.cjs';
|
|
9
|
+
import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.cjs';
|
|
85
10
|
|
|
86
11
|
declare const SubmitOptionsSchema: z.ZodObject<{
|
|
87
12
|
templateName: z.ZodString;
|
|
@@ -151,19 +76,6 @@ type ResubmitOptions = z.infer<typeof ResubmitOptionsSchema>;
|
|
|
151
76
|
type AddCommentOptions = z.infer<typeof AddCommentOptionsSchema>;
|
|
152
77
|
type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
|
|
153
78
|
|
|
154
|
-
interface Logger {
|
|
155
|
-
info(msg: string, context?: Record<string, unknown>): void;
|
|
156
|
-
warn(msg: string, context?: Record<string, unknown>): void;
|
|
157
|
-
error(msg: string, err?: unknown, context?: Record<string, unknown>): void;
|
|
158
|
-
debug(msg: string, context?: Record<string, unknown>): void;
|
|
159
|
-
}
|
|
160
|
-
declare const noopLogger: Logger;
|
|
161
|
-
|
|
162
|
-
interface Clock {
|
|
163
|
-
now(): Date;
|
|
164
|
-
}
|
|
165
|
-
declare const systemClock: Clock;
|
|
166
|
-
|
|
167
79
|
/**
|
|
168
80
|
* Computes deadline dates from a number of days. The default engine behaviour
|
|
169
81
|
* treats day offsets (escalationAfterDays, slaDeadlineDays) as plain calendar
|
|
@@ -216,67 +128,6 @@ type ApproverResolverFn = (config: Record<string, unknown>, ctx: {
|
|
|
216
128
|
|
|
217
129
|
type ConditionOperatorFn = (actual: unknown, expected: unknown) => boolean;
|
|
218
130
|
|
|
219
|
-
declare class ApprovalError extends Error {
|
|
220
|
-
readonly code: string;
|
|
221
|
-
constructor(message: string, code: string);
|
|
222
|
-
toJSON(): {
|
|
223
|
-
code: string;
|
|
224
|
-
message: string;
|
|
225
|
-
name: string;
|
|
226
|
-
};
|
|
227
|
-
toHttpStatus(): number;
|
|
228
|
-
}
|
|
229
|
-
declare class ApprovalNotFoundError extends ApprovalError {
|
|
230
|
-
constructor(resource: string, id: string);
|
|
231
|
-
}
|
|
232
|
-
declare class ApprovalConflictError extends ApprovalError {
|
|
233
|
-
constructor(instanceId: string);
|
|
234
|
-
}
|
|
235
|
-
declare class ApprovalForbiddenError extends ApprovalError {
|
|
236
|
-
constructor(message: string);
|
|
237
|
-
}
|
|
238
|
-
declare class ApprovalValidationError extends ApprovalError {
|
|
239
|
-
readonly cause?: unknown | undefined;
|
|
240
|
-
constructor(message: string, cause?: unknown | undefined);
|
|
241
|
-
}
|
|
242
|
-
declare class ApprovalTemplateNotFoundError extends ApprovalError {
|
|
243
|
-
constructor(name: string);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
interface NotificationEvent {
|
|
247
|
-
type: ApprovalEventName;
|
|
248
|
-
instanceId: string;
|
|
249
|
-
documentId: string;
|
|
250
|
-
documentType: string;
|
|
251
|
-
timestamp: Date;
|
|
252
|
-
/** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
|
|
253
|
-
recipients: string[];
|
|
254
|
-
templateName: string;
|
|
255
|
-
tenantId: string;
|
|
256
|
-
payload: ApprovalEventMap[ApprovalEventName];
|
|
257
|
-
}
|
|
258
|
-
interface INotificationAdapter {
|
|
259
|
-
/** Called after every emitted approval event. Must not throw — errors are logged and swallowed. */
|
|
260
|
-
notify(event: NotificationEvent): Promise<void>;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
interface IAuditAdapter {
|
|
264
|
-
/**
|
|
265
|
-
* Called after every state-mutating operation, in addition to the primary storage adapter.
|
|
266
|
-
* Intended for write-once sinks: Kafka, S3, CloudTrail, WORM stores.
|
|
267
|
-
* Must not throw — errors are logged and swallowed.
|
|
268
|
-
*/
|
|
269
|
-
append(tenantId: string, instanceId: string, entry: AuditEntry, instance: Readonly<ApprovalInstance>): Promise<void>;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
|
|
273
|
-
interface IMetricsAdapter {
|
|
274
|
-
/** Increment a counter. Synchronous — never awaited. */
|
|
275
|
-
increment(metric: MetricName, labels?: Record<string, string>): void;
|
|
276
|
-
/** Record a timing measurement in milliseconds. Synchronous — never awaited. */
|
|
277
|
-
timing(metric: MetricName, durationMs: number, labels?: Record<string, string>): void;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
131
|
interface ISchedulerAdapter {
|
|
281
132
|
/**
|
|
282
133
|
* Schedule a one-shot callback to run at the given date.
|
|
@@ -289,38 +140,6 @@ interface ISchedulerAdapter {
|
|
|
289
140
|
shutdown(): Promise<void>;
|
|
290
141
|
}
|
|
291
142
|
|
|
292
|
-
interface AuthorizationContext {
|
|
293
|
-
operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
|
|
294
|
-
actorId: string;
|
|
295
|
-
instance: Readonly<ApprovalInstance>;
|
|
296
|
-
level?: Readonly<ApprovalLevelInstance>;
|
|
297
|
-
opts: Record<string, unknown>;
|
|
298
|
-
}
|
|
299
|
-
interface IAuthorizationPolicy {
|
|
300
|
-
/**
|
|
301
|
-
* Return undefined to allow the operation.
|
|
302
|
-
* Return a non-empty string to deny — the engine throws ApprovalForbiddenError(message).
|
|
303
|
-
* Throwing ApprovalForbiddenError directly is also permitted.
|
|
304
|
-
*/
|
|
305
|
-
authorize(ctx: AuthorizationContext): Promise<string | undefined> | string | undefined;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
interface OperationContext<T = unknown> {
|
|
309
|
-
operation: string;
|
|
310
|
-
instanceId?: string;
|
|
311
|
-
actorId?: string;
|
|
312
|
-
tenantId: string;
|
|
313
|
-
input: T;
|
|
314
|
-
}
|
|
315
|
-
interface IOperationMiddleware {
|
|
316
|
-
/** Runs after authorization and input validation, before state mutations. */
|
|
317
|
-
before?(ctx: OperationContext): Promise<void> | void;
|
|
318
|
-
/** Runs after successful completion of the operation. */
|
|
319
|
-
after?(ctx: OperationContext, result: ApprovalInstance | void): Promise<void> | void;
|
|
320
|
-
/** Runs when an ApprovalError is thrown. Does not suppress the error. */
|
|
321
|
-
onError?(ctx: OperationContext, error: ApprovalError): Promise<void> | void;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
143
|
interface ValidationResult {
|
|
325
144
|
valid: boolean;
|
|
326
145
|
errors: Array<{
|
|
@@ -501,4 +320,4 @@ declare class ApprovalEngine {
|
|
|
501
320
|
private runExternalAudit;
|
|
502
321
|
}
|
|
503
322
|
|
|
504
|
-
export { type
|
|
323
|
+
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type ISchedulerAdapter as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type IdGeneratorFn as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, weekendCalendar as w };
|