hierarchical-approval 4.0.1 → 4.0.3

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +34 -5
  2. package/README.md +1 -1
  3. package/dist/{ApprovalEngine-BfKL6iGQ.d.ts → ApprovalEngine-BtgQDr_8.d.ts} +201 -10
  4. package/dist/{ApprovalEngine-DcD6s_jB.d.cts → ApprovalEngine-Djdw-JzA.d.cts} +201 -10
  5. package/dist/Clock-D8Qp39q_.d.cts +8 -0
  6. package/dist/Clock-D8Qp39q_.d.ts +8 -0
  7. package/dist/{IAuditAdapter-DkbyFFjb.d.ts → IAuditAdapter-C-04ar9A.d.ts} +1 -1
  8. package/dist/{IAuditAdapter-Dt8qjkQL.d.cts → IAuditAdapter-TT2Xhtu6.d.cts} +1 -1
  9. package/dist/{IAuthorizationPolicy-eo6glbpE.d.ts → IAuthorizationPolicy-D6tTT3gv.d.cts} +6 -1
  10. package/dist/{IAuthorizationPolicy-YJ_08Mof.d.cts → IAuthorizationPolicy-wIE1mpTy.d.ts} +6 -1
  11. package/dist/{IMetricsAdapter-DWq8IFaf.d.cts → IMetricsAdapter-WROxYSXd.d.cts} +1 -0
  12. package/dist/{IMetricsAdapter-DWq8IFaf.d.ts → IMetricsAdapter-WROxYSXd.d.ts} +1 -0
  13. package/dist/{INotificationAdapter-BTO9FSKy.d.ts → INotificationAdapter-BcUskJ6z.d.ts} +9 -1
  14. package/dist/{INotificationAdapter-XbD69gss.d.cts → INotificationAdapter-CBsJDk-Y.d.cts} +9 -1
  15. package/dist/{IOperationMiddleware-CX7lo4lp.d.ts → IOperationMiddleware-C7ciNnJp.d.ts} +3 -2
  16. package/dist/{IOperationMiddleware-BBLk8MIz.d.cts → IOperationMiddleware-D3Yelw9R.d.cts} +3 -2
  17. package/dist/{IStorageAdapter-CdXMggPK.d.ts → IStorageAdapter-CVvNsnnD.d.ts} +7 -1
  18. package/dist/{IStorageAdapter-Cjw5zDwQ.d.cts → IStorageAdapter-D7cy20uD.d.cts} +7 -1
  19. package/dist/{Logger-BplhlU7l.d.cts → Logger-hqFbBCtB.d.cts} +7 -0
  20. package/dist/{Logger-BplhlU7l.d.ts → Logger-hqFbBCtB.d.ts} +7 -0
  21. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  22. package/dist/adapters/MemoryAdapter.d.cts +20 -2
  23. package/dist/adapters/MemoryAdapter.d.ts +20 -2
  24. package/dist/adapters/MemoryAdapter.js.map +1 -1
  25. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  26. package/dist/adapters/PostgresAdapter.d.cts +18 -2
  27. package/dist/adapters/PostgresAdapter.d.ts +18 -2
  28. package/dist/adapters/PostgresAdapter.js.map +1 -1
  29. package/dist/index.cjs +270 -21
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +13 -13
  32. package/dist/index.d.ts +13 -13
  33. package/dist/index.js +270 -21
  34. package/dist/index.js.map +1 -1
  35. package/dist/{instance-uUQh5XW3.d.cts → instance-Dd1jQizG.d.cts} +19 -0
  36. package/dist/{instance-uUQh5XW3.d.ts → instance-Dd1jQizG.d.ts} +19 -0
  37. package/dist/nestjs.cjs +269 -20
  38. package/dist/nestjs.cjs.map +1 -1
  39. package/dist/nestjs.d.cts +10 -10
  40. package/dist/nestjs.d.ts +10 -10
  41. package/dist/nestjs.js +269 -20
  42. package/dist/nestjs.js.map +1 -1
  43. package/dist/plugins/audit.cjs.map +1 -1
  44. package/dist/plugins/audit.d.cts +3 -3
  45. package/dist/plugins/audit.d.ts +3 -3
  46. package/dist/plugins/audit.js.map +1 -1
  47. package/dist/plugins/metrics.cjs.map +1 -1
  48. package/dist/plugins/metrics.d.cts +2 -2
  49. package/dist/plugins/metrics.d.ts +2 -2
  50. package/dist/plugins/metrics.js.map +1 -1
  51. package/dist/plugins/notify.cjs +3 -1
  52. package/dist/plugins/notify.cjs.map +1 -1
  53. package/dist/plugins/notify.d.cts +6 -4
  54. package/dist/plugins/notify.d.ts +6 -4
  55. package/dist/plugins/notify.js +3 -1
  56. package/dist/plugins/notify.js.map +1 -1
  57. package/dist/plugins/resilience.cjs.map +1 -1
  58. package/dist/plugins/resilience.d.cts +5 -5
  59. package/dist/plugins/resilience.d.ts +5 -5
  60. package/dist/plugins/resilience.js.map +1 -1
  61. package/dist/plugins/scheduler.cjs.map +1 -1
  62. package/dist/plugins/scheduler.d.cts +2 -2
  63. package/dist/plugins/scheduler.d.ts +2 -2
  64. package/dist/plugins/scheduler.js.map +1 -1
  65. package/dist/plugins/tracing.d.cts +2 -2
  66. package/dist/plugins/tracing.d.ts +2 -2
  67. package/dist/plugins/webhook.cjs.map +1 -1
  68. package/dist/plugins/webhook.d.cts +4 -4
  69. package/dist/plugins/webhook.d.ts +4 -4
  70. package/dist/plugins/webhook.js.map +1 -1
  71. package/dist/testing.cjs +281 -20
  72. package/dist/testing.cjs.map +1 -1
  73. package/dist/testing.d.cts +31 -10
  74. package/dist/testing.d.ts +31 -10
  75. package/dist/testing.js +281 -20
  76. package/dist/testing.js.map +1 -1
  77. package/package.json +1 -1
  78. package/dist/Clock-3FnOczFJ.d.cts +0 -6
  79. package/dist/Clock-3FnOczFJ.d.ts +0 -6
package/CHANGELOG.md CHANGED
@@ -1,9 +1,8 @@
1
- ## 4.0.1 - 2026-09-14
1
+ ## 4.0.3 - 2026-09-22
2
2
 
3
- - fix(resilience): include the offending value in RateLimitMiddleware errors
4
- - fix(scheduler): include the job id in InMemorySchedulerAdapter's post-shutdown error
5
- - fix(postgres): test the null-rowCount fallback via a dedicated fake pool
6
- - fix(postgres): let FakePool pass an explicit null rowCount through
3
+ - fix(engine): name the level in assertApproverOnLevel's error
4
+ - fix(utils): name the offending start date in businessHoursCalendar's error
5
+ - fix(engine): name the offending instance id in assertStatus's error
7
6
 
8
7
  # Changelog
9
8
 
@@ -14,9 +13,39 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
14
13
 
15
14
  ### Fixed
16
15
 
16
+ - `assertStatus`'s `INVALID_STATUS` error now names the instance that failed
17
+ the check, instead of a message that gave no clue which instance it was.
18
+ - `businessHoursCalendar`'s "no working day found" error now names the start
19
+ date that triggered it, instead of a message that gave no clue which call
20
+ misconfigured the calendar.
21
+ - `assertApproverOnLevel`'s forbidden-approver error now names the level,
22
+ matching every sibling guard in `StateMachine.ts`, instead of a bare level
23
+ number with no human-readable name.
24
+
25
+ ## [4.0.2] - 2026-09-20
26
+
27
+ ### Fixed
28
+
29
+ - `EventBus` (and therefore `engine.on()`/`off()`) no longer leaks a listener
30
+ when the same function is registered twice with `on()`: each registration
31
+ now needs its own `off()` call to undo, instead of the second registration
32
+ silently overwriting the bookkeeping for the first and leaving it permanently
33
+ attached.
34
+ - `DigestNotificationAdapter`'s `intervalMs` validation error now names the
35
+ offending value, instead of a message that gave no clue which value failed.
36
+ - The default-sleep regression test for `WebhookNotificationAdapter` no longer
37
+ flakes under CI's timer coalescing, which could report elapsed time a
38
+ millisecond or two short of the asserted floor.
39
+
40
+ ## [4.0.1] - 2026-09-14
41
+
42
+ ### Fixed
43
+
17
44
  - `InMemorySchedulerAdapter.scheduleAt()`'s post-shutdown error now names the
18
45
  rejected job id, so a caller can tell which scheduled job was dropped
19
46
  without adding their own logging around every call.
47
+ - `RateLimitMiddleware` errors now include the offending value that tripped
48
+ the limit, instead of a message that named the limit but not the input.
20
49
 
21
50
  ## [4.0.0] - 2026-09-04
22
51
 
package/README.md CHANGED
@@ -11,7 +11,7 @@ Multi-tenant · audit-ready · fully pluggable · zero runtime dependencies you
11
11
  [![types](https://img.shields.io/npm/types/hierarchical-approval.svg?logo=typescript&logoColor=white&color=3178c6)](https://www.typescriptlang.org/)
12
12
  [![minzipped size](https://img.shields.io/bundlephobia/minzip/hierarchical-approval?color=44cc11)](https://bundlephobia.com/package/hierarchical-approval)
13
13
  [![license](https://img.shields.io/npm/l/hierarchical-approval.svg?color=blue)](./LICENSE)
14
- [![tests](https://img.shields.io/badge/tests-1204%20passing-44cc11.svg?logo=vitest&logoColor=white)](./tests)
14
+ [![tests](https://img.shields.io/badge/tests-1256%20passing-44cc11.svg?logo=vitest&logoColor=white)](./tests)
15
15
 
16
16
  [**Documentation**](https://hierarchical-approval.matthewswong.com) ·
17
17
  [**npm**](https://www.npmjs.com/package/hierarchical-approval) ·
@@ -1,14 +1,14 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-CdXMggPK.js';
2
- import { m as ConditionExpression, r as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, C as Comment, e as ApprovalMode, f as ApprovalStatus, L as LevelStatus, b as AuditEntry } from './instance-uUQh5XW3.js';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BTO9FSKy.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-CVvNsnnD.js';
2
+ import { m as ConditionExpression, r as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, C as Comment, e as ApprovalMode, f as ApprovalStatus, L as LevelStatus, b as AuditEntry } from './instance-Dd1jQizG.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BcUskJ6z.js';
4
4
  import { z } from 'zod';
5
- import { L as Logger } from './Logger-BplhlU7l.js';
6
- import { C as Clock } from './Clock-3FnOczFJ.js';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-CX7lo4lp.js';
8
- import { I as IAuditAdapter } from './IAuditAdapter-DkbyFFjb.js';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.js';
5
+ import { L as Logger } from './Logger-hqFbBCtB.js';
6
+ import { C as Clock } from './Clock-D8Qp39q_.js';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-C7ciNnJp.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-C-04ar9A.js';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-WROxYSXd.js';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-eo6glbpE.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-wIE1mpTy.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -201,11 +201,15 @@ interface BusinessHoursCalendarOptions extends WeekendCalendarOptions {
201
201
  */
202
202
  declare function businessHoursCalendar(options?: BusinessHoursCalendarOptions): Required<Pick<BusinessCalendar, 'addBusinessDays' | 'addBusinessHours'>>;
203
203
 
204
+ /** Entity kind an id is generated for: instance, template, attachment, or comment. */
204
205
  type IdGeneratorPrefix = 'inst' | 'tpl' | 'att' | 'cmt';
206
+ /** Produces a unique id for the given entity kind. */
205
207
  type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
208
+ /** {@link IdGeneratorFn} used when no custom generator is configured. */
206
209
  declare const defaultIdGenerator: IdGeneratorFn;
207
210
 
208
211
  interface OrgProvider {
212
+ /** Required: resolve users holding a named role. */
209
213
  getUsersByRole(role: string, tenantId?: string): Promise<string[]> | string[];
210
214
  /** Optional: resolve users by department name. */
211
215
  getUsersByDepartment?(dept: string, tenantId?: string): Promise<string[]> | string[];
@@ -625,18 +629,71 @@ declare class ApprovalEngine {
625
629
  /** Set by {@link shutdown}; stops the self-rescheduling loop from scheduling another tick. */
626
630
  private schedulerStopped;
627
631
  constructor(opts: ApprovalEngineOptions);
632
+ /**
633
+ * Subscribe to a lifecycle event (see {@link ApprovalEventMap} for the full
634
+ * catalog and each payload's shape). A listener that throws, or returns a
635
+ * rejecting promise, cannot abort the operation that emitted the event or
636
+ * block the remaining listeners — the failure is reported via the engine's
637
+ * logger instead.
638
+ *
639
+ * @returns `this`, for chaining multiple subscriptions.
640
+ */
628
641
  on<K extends ApprovalEventName>(event: K, listener: (payload: ApprovalEventMap[K]) => void): this;
642
+ /**
643
+ * Unsubscribe a listener previously registered with {@link on}. A no-op if
644
+ * `listener` was never registered for `event`.
645
+ *
646
+ * @returns `this`, for chaining.
647
+ */
629
648
  off<K extends ApprovalEventName>(event: K, listener: (payload: ApprovalEventMap[K]) => void): this;
649
+ /**
650
+ * Register a dynamic-approver resolver under `name`, for use by a level whose
651
+ * approver config is `{ type: 'dynamic', resolver: name }`. Overwrites any
652
+ * resolver already registered under the same name.
653
+ */
630
654
  registerResolver(name: string, fn: ResolverFn): void;
655
+ /**
656
+ * Register a custom approver type under `typeName`, for use by a level whose
657
+ * approver config carries `{ type: typeName, ... }`. Overwrites any approver
658
+ * type already registered under the same type name.
659
+ */
631
660
  registerApproverType(typeName: string, fn: ApproverResolverFn): void;
661
+ /**
662
+ * Register a custom condition operator under `name`, for use by template
663
+ * condition rules whose `operator` is `name`. Operators are shared process-wide
664
+ * across every engine instance, not scoped to this tenant.
665
+ *
666
+ * @throws {@link ApprovalValidationError} if `name` is already taken by a
667
+ * built-in operator (`>`, `<`, `>=`, `<=`, `==`, `!=`, `in`, `not_in`).
668
+ */
632
669
  registerConditionOperator(name: string, fn: ConditionOperatorFn): void;
633
670
  /** Validate a template config without persisting. Synchronous; never throws. */
634
671
  validateTemplate(config: ApprovalTemplateConfig): ValidationResult;
635
672
  defineTemplate(config: ApprovalTemplateConfig): Promise<string>;
636
673
  /** Update an existing template, incrementing its version. In-flight instances are protected by their templateSnapshot. */
637
674
  updateTemplate(config: ApprovalTemplateConfig): Promise<string>;
675
+ /**
676
+ * Fetch the current version of a defined template.
677
+ *
678
+ * @throws {@link ApprovalTemplateNotFoundError} if no template is registered under `name`.
679
+ */
638
680
  getTemplate(name: string): Promise<ApprovalTemplate>;
681
+ /** List every template currently defined for this tenant. */
639
682
  listTemplates(): Promise<ApprovalTemplate[]>;
683
+ /**
684
+ * Start a new approval instance from a defined template.
685
+ *
686
+ * Evaluates the template's conditions against `raw.data` first, so the
687
+ * levels an instance opens with can differ from the template's static
688
+ * declaration (added, skipped, or reordered). If a prior call with the
689
+ * same tenant, document, template and data produced a non-terminal
690
+ * instance, that instance is returned unchanged instead of creating a
691
+ * duplicate — safe to retry on network failure.
692
+ *
693
+ * @throws {@link ApprovalTemplateNotFoundError} if `raw.templateName` is not defined.
694
+ * @throws {@link ApprovalValidationError} if `raw` fails schema validation, or if
695
+ * condition evaluation leaves no levels (or a duplicate level number) to open.
696
+ */
640
697
  submit(raw: SubmitOptions, auditCtx?: AuditContext,
641
698
  /**
642
699
  * @internal Set only when the engine spawns a sub-workflow child. Passed at
@@ -649,8 +706,63 @@ declare class ApprovalEngine {
649
706
  parentLevel: number;
650
707
  depth: number;
651
708
  }): Promise<ApprovalInstance>;
709
+ /**
710
+ * Record `opts.approverId`'s approval of their open level on `instanceId`.
711
+ *
712
+ * When more than one level is open in parallel and the approver sits on
713
+ * exactly one of them, that level is inferred; pass `opts.level` explicitly
714
+ * when it must be disambiguated (e.g. the approver sits on several).
715
+ *
716
+ * @throws {@link ApprovalNotFoundError} if `instanceId` does not exist.
717
+ * @throws {@link ApprovalError} (code `INVALID_STATUS`) if the instance is not `pending`,
718
+ * (code `INVALID_LEVEL`) if no level (or the explicit `opts.level`) is awaiting a decision,
719
+ * or (code `ALREADY_ACTED`) if this approver already approved or rejected that level.
720
+ * @throws {@link ApprovalForbiddenError} if `opts.approverId` submitted the instance
721
+ * (self-approval), is not an approver on the resolved level, or is denied by the
722
+ * configured {@link IAuthorizationPolicy}.
723
+ * @throws {@link ApprovalValidationError} if `opts.approverId` sits on more than one
724
+ * open parallel level and `opts.level` was not supplied to disambiguate.
725
+ */
652
726
  approve(instanceId: string, raw: ApproveOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
727
+ /**
728
+ * Record `opts.approverId`'s rejection of their open level on `instanceId`.
729
+ *
730
+ * A single rejection does not necessarily fail the level — under `'quorum'`
731
+ * or `'weighted'` mode it only fails once the remaining approvers can no
732
+ * longer reach threshold. When the level does fail, `opts.returnTo`
733
+ * controls what happens next: `'originator'` (default) fails the whole
734
+ * instance; `'previous'` resets every level after the prior one and sends
735
+ * the instance back there to replay.
736
+ *
737
+ * @throws {@link ApprovalNotFoundError} if `instanceId` does not exist.
738
+ * @throws {@link ApprovalError} (code `INVALID_STATUS`) if the instance is not `pending`,
739
+ * (code `INVALID_LEVEL`) if no level (or the explicit `opts.level`) is awaiting a decision,
740
+ * or (code `ALREADY_ACTED`) if this approver already approved or rejected that level.
741
+ * @throws {@link ApprovalForbiddenError} if `opts.approverId` submitted the instance
742
+ * (self-rejection), is not an approver on the resolved level, or is denied by the
743
+ * configured {@link IAuthorizationPolicy}.
744
+ * @throws {@link ApprovalValidationError} if `opts.approverId` sits on more than one
745
+ * open parallel level with `opts.level` unset, or if `opts.returnTo === 'previous'`
746
+ * but the failing level is already the first level in the chain.
747
+ */
653
748
  reject(instanceId: string, raw: RejectOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
749
+ /**
750
+ * Reassign `opts.fromApprover`'s seat on their open level to `opts.toApprover`,
751
+ * carrying across any 'weighted'-mode vote weight. When `opts.until` is set,
752
+ * the level records `delegatedFrom`/`delegatedTo`/`delegatedUntil`, and the
753
+ * scheduled escalation tick reassigns the seat back to `opts.fromApprover`
754
+ * once that time passes (still pending); omit `opts.until` for a permanent
755
+ * handoff.
756
+ *
757
+ * @throws {@link ApprovalNotFoundError} if `instanceId` does not exist.
758
+ * @throws {@link ApprovalError} (code `INVALID_STATUS`) if the instance is not `pending`,
759
+ * or (code `INVALID_LEVEL`) if no level (or the explicit `opts.level`) is awaiting a decision.
760
+ * @throws {@link ApprovalForbiddenError} if `opts.fromApprover` equals `opts.toApprover`,
761
+ * is not an approver on the resolved level, already acted on it, or is denied by the
762
+ * configured {@link IAuthorizationPolicy}.
763
+ * @throws {@link ApprovalValidationError} if `opts.fromApprover` sits on more than one
764
+ * open parallel level and `opts.level` was not supplied to disambiguate.
765
+ */
654
766
  delegate(instanceId: string, raw: DelegateOptions, auditCtx?: AuditContext): Promise<void>;
655
767
  /**
656
768
  * Administratively replace an approver on the current level. Unlike delegate(),
@@ -660,7 +772,36 @@ declare class ApprovalEngine {
660
772
  * cannot be reassigned.
661
773
  */
662
774
  reassign(instanceId: string, raw: ReassignOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
775
+ /**
776
+ * Withdraw an approval before it reaches a final decision.
777
+ *
778
+ * Cancelling cascades: any sub-workflow children spawned by this instance
779
+ * are cancelled in turn (an orphaned child left running would eventually
780
+ * report back to a parent that already moved on), and if this instance is
781
+ * itself a sub-workflow child, its own parent is unblocked as if this
782
+ * approval had been rejected.
783
+ *
784
+ * @param instanceId - The instance to cancel.
785
+ * @param raw - Who is cancelling and why.
786
+ * @param auditCtx - Optional compliance context recorded on the audit entry.
787
+ * @returns The cancelled instance.
788
+ * @throws ApprovalError if the instance is already `approved` or `rejected`.
789
+ */
663
790
  cancel(instanceId: string, raw: CancelOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
791
+ /**
792
+ * Manually escalate the instance's current (lowest pending) level to the
793
+ * next rung of its escalation ladder, adding whatever the rung's
794
+ * `escalateTo` resolves to as extra approvers on that level. This is the
795
+ * same path the scheduled tick uses for a deadline-driven escalation — call
796
+ * it directly to escalate on demand instead of waiting for the ladder timer.
797
+ *
798
+ * A no-op (returns the instance unchanged, does not throw) when: the
799
+ * instance is not `pending`; the ladder has no rung configured for the
800
+ * current step; or every resolved approver turns out to be the submitter
801
+ * (nobody new to add).
802
+ *
803
+ * @throws {@link ApprovalNotFoundError} if `instanceId` does not exist.
804
+ */
664
805
  escalate(instanceId: string, raw: EscalateOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
665
806
  /**
666
807
  * Change an instance's document data while it is still pending, and
@@ -822,6 +963,17 @@ declare class ApprovalEngine {
822
963
  submittedBy: string;
823
964
  decisions?: SimulatedDecision[];
824
965
  }): Promise<SimulationResult>;
966
+ /**
967
+ * Check whether a user could approve an instance right now, without acting.
968
+ *
969
+ * The read-only counterpart to calling {@link approve} and handling the
970
+ * rejection — useful for deciding whether to show an approve button at all.
971
+ * `eligible: false` never throws; the reason names why: the instance is not
972
+ * `pending`, the user submitted it, they hold no open level (or delegated
973
+ * theirs away), or they already voted on it.
974
+ *
975
+ * @returns `{ eligible: true }`, or `{ eligible: false, reason }`.
976
+ */
825
977
  canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
826
978
  /** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
827
979
  override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
@@ -848,13 +1000,34 @@ declare class ApprovalEngine {
848
1000
  * @returns What moved, what did not, and why. With `dryRun` nothing is written.
849
1001
  */
850
1002
  transferApprovals(raw: TransferApprovalsOptions, auditCtx?: AuditContext): Promise<TransferResult>;
1003
+ /** Approve multiple instances in one call. Never throws — failures collected in result.failed. */
851
1004
  bulkApprove(instanceIds: string[], raw: ApproveOptions, auditCtx?: AuditContext): Promise<BulkResult>;
852
1005
  /** Reject multiple instances in one call. Never throws — failures collected in result.failed. */
853
1006
  bulkReject(instanceIds: string[], raw: RejectOptions, auditCtx?: AuditContext): Promise<BulkResult>;
1007
+ /**
1008
+ * Fetch a single instance by id.
1009
+ *
1010
+ * @throws {@link ApprovalNotFoundError} if no instance exists with that id.
1011
+ */
854
1012
  getInstance(instanceId: string): Promise<ApprovalInstance>;
1013
+ /** Page through every instance with an open level assigned to one approver. */
855
1014
  getPendingFor(approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
1015
+ /** Page through instances matching an arbitrary filter (status, document type, date range, ...). */
856
1016
  queryInstances(filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
1017
+ /**
1018
+ * Cursor-paginated variant of {@link queryInstances}, for stable paging
1019
+ * through large or actively-changing result sets that offset pagination
1020
+ * would skip or duplicate rows in.
1021
+ *
1022
+ * @throws {@link ApprovalError} (`NOT_SUPPORTED`) if the configured storage
1023
+ * adapter does not implement `getInstancesByCursor`.
1024
+ */
857
1025
  queryInstancesByCursor(filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
1026
+ /**
1027
+ * The full audit trail for an instance, in the order it was recorded.
1028
+ *
1029
+ * @throws {@link ApprovalNotFoundError} if no instance exists with that id.
1030
+ */
858
1031
  getHistory(instanceId: string): Promise<AuditEntry[]>;
859
1032
  /**
860
1033
  * The levels currently collecting decisions, ascending.
@@ -867,6 +1040,16 @@ declare class ApprovalEngine {
867
1040
  * Empty once the instance is terminal.
868
1041
  */
869
1042
  getOpenLevels(instanceId: string): Promise<number[]>;
1043
+ /**
1044
+ * Everyone who currently owes a decision on an instance.
1045
+ *
1046
+ * A union across every open branch, so a parallel group with more than one
1047
+ * level collecting decisions at once still names every approver, not just
1048
+ * the one at {@link ApprovalInstance.currentLevel}. Empty once the instance
1049
+ * is no longer `pending`.
1050
+ *
1051
+ * @throws {@link ApprovalNotFoundError} if no instance exists with that id.
1052
+ */
870
1053
  getCurrentApprovers(instanceId: string): Promise<string[]>;
871
1054
  /** Check adapter connectivity and escalation scheduler health. */
872
1055
  healthCheck(): Promise<HealthResult>;
@@ -956,6 +1139,15 @@ declare class ApprovalEngine {
956
1139
  limit?: number;
957
1140
  dryRun?: boolean;
958
1141
  }): Promise<PurgeResult>;
1142
+ /**
1143
+ * Aggregate reporting numbers for a tenant: counts by status, approval
1144
+ * rate, cycle-time stats, and a per-template breakdown.
1145
+ *
1146
+ * `filter.status` is ignored — the breakdown covers every status by
1147
+ * design, so scoping it to one would make the counts misleading.
1148
+ *
1149
+ * @param filter - Optional scoping by document type, submitter, or date range.
1150
+ */
959
1151
  getStatistics(filter?: Omit<InstanceFilter, 'status'>): Promise<ApprovalStatistics>;
960
1152
  /** Stops the escalation scheduler and any scheduler-adapter background loop; call once when tearing the engine down. */
961
1153
  shutdown(): Promise<void>;
@@ -1206,7 +1398,6 @@ declare class ApprovalEngine {
1206
1398
  * approved document that one branch had rejected and another had never seen.
1207
1399
  */
1208
1400
  private assertFullyApproved;
1209
- private findNextLevel;
1210
1401
  /**
1211
1402
  * The level to send an instance back to, given the level being rejected.
1212
1403
  *