hierarchical-approval 4.0.2 → 4.0.4

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 (81) hide show
  1. package/CHANGELOG.md +20 -6
  2. package/README.md +1 -1
  3. package/dist/{ApprovalEngine-XgGBQLC_.d.cts → ApprovalEngine-D0_F5OPZ.d.cts} +35 -10
  4. package/dist/{ApprovalEngine-Dqe-rC6K.d.ts → ApprovalEngine-D4bXQrKD.d.ts} +35 -10
  5. package/dist/Clock-D8Qp39q_.d.cts +8 -0
  6. package/dist/Clock-D8Qp39q_.d.ts +8 -0
  7. package/dist/{IAuditAdapter-Dt8qjkQL.d.cts → IAuditAdapter-D9WE0t7A.d.cts} +2 -1
  8. package/dist/{IAuditAdapter-DkbyFFjb.d.ts → IAuditAdapter-FI_RF_XU.d.ts} +2 -1
  9. package/dist/{IAuthorizationPolicy-YJ_08Mof.d.cts → IAuthorizationPolicy-BKKw7jGN.d.ts} +6 -1
  10. package/dist/{IAuthorizationPolicy-eo6glbpE.d.ts → IAuthorizationPolicy-DDmjeYV-.d.cts} +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-CLROEfRd.d.ts} +26 -1
  14. package/dist/{INotificationAdapter-XbD69gss.d.cts → INotificationAdapter-J0wSPLIC.d.cts} +26 -1
  15. package/dist/{IOperationMiddleware-CX7lo4lp.d.ts → IOperationMiddleware-B32a2dwP.d.ts} +15 -2
  16. package/dist/{IOperationMiddleware-BBLk8MIz.d.cts → IOperationMiddleware-UUKqztxM.d.cts} +15 -2
  17. package/dist/{ISchedulerAdapter-DKv_QjVN.d.cts → ISchedulerAdapter-CLMdBA-5.d.cts} +1 -0
  18. package/dist/{ISchedulerAdapter-DKv_QjVN.d.ts → ISchedulerAdapter-CLMdBA-5.d.ts} +1 -0
  19. package/dist/{IStorageAdapter-CdXMggPK.d.ts → IStorageAdapter-DSKJ1onF.d.ts} +14 -1
  20. package/dist/{IStorageAdapter-Cjw5zDwQ.d.cts → IStorageAdapter-Dhrc4ES6.d.cts} +14 -1
  21. package/dist/{Logger-BplhlU7l.d.cts → Logger-hqFbBCtB.d.cts} +7 -0
  22. package/dist/{Logger-BplhlU7l.d.ts → Logger-hqFbBCtB.d.ts} +7 -0
  23. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  24. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  25. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  26. package/dist/adapters/MemoryAdapter.js.map +1 -1
  27. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  28. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  29. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  30. package/dist/adapters/PostgresAdapter.js.map +1 -1
  31. package/dist/index.cjs +51 -8
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +14 -14
  34. package/dist/index.d.ts +14 -14
  35. package/dist/index.js +51 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/{instance-uUQh5XW3.d.cts → instance-CUZcHWK2.d.cts} +28 -0
  38. package/dist/{instance-uUQh5XW3.d.ts → instance-CUZcHWK2.d.ts} +28 -0
  39. package/dist/nestjs.cjs +61 -7
  40. package/dist/nestjs.cjs.map +1 -1
  41. package/dist/nestjs.d.cts +22 -11
  42. package/dist/nestjs.d.ts +22 -11
  43. package/dist/nestjs.js +61 -7
  44. package/dist/nestjs.js.map +1 -1
  45. package/dist/plugins/audit.cjs.map +1 -1
  46. package/dist/plugins/audit.d.cts +3 -3
  47. package/dist/plugins/audit.d.ts +3 -3
  48. package/dist/plugins/audit.js.map +1 -1
  49. package/dist/plugins/metrics.cjs.map +1 -1
  50. package/dist/plugins/metrics.d.cts +2 -2
  51. package/dist/plugins/metrics.d.ts +2 -2
  52. package/dist/plugins/metrics.js.map +1 -1
  53. package/dist/plugins/notify.cjs.map +1 -1
  54. package/dist/plugins/notify.d.cts +4 -4
  55. package/dist/plugins/notify.d.ts +4 -4
  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 +3 -3
  63. package/dist/plugins/scheduler.d.ts +3 -3
  64. package/dist/plugins/scheduler.js.map +1 -1
  65. package/dist/plugins/tracing.cjs.map +1 -1
  66. package/dist/plugins/tracing.d.cts +4 -2
  67. package/dist/plugins/tracing.d.ts +4 -2
  68. package/dist/plugins/tracing.js.map +1 -1
  69. package/dist/plugins/webhook.cjs.map +1 -1
  70. package/dist/plugins/webhook.d.cts +4 -4
  71. package/dist/plugins/webhook.d.ts +4 -4
  72. package/dist/plugins/webhook.js.map +1 -1
  73. package/dist/testing.cjs +50 -7
  74. package/dist/testing.cjs.map +1 -1
  75. package/dist/testing.d.cts +11 -11
  76. package/dist/testing.d.ts +11 -11
  77. package/dist/testing.js +50 -7
  78. package/dist/testing.js.map +1 -1
  79. package/package.json +1 -1
  80. package/dist/Clock-3FnOczFJ.d.cts +0 -6
  81. package/dist/Clock-3FnOczFJ.d.ts +0 -6
package/CHANGELOG.md CHANGED
@@ -1,9 +1,3 @@
1
- ## 4.0.2 - 2026-09-20
2
-
3
- - fix(notify): name the offending value in DigestNotificationAdapter's error
4
- - fix(engine): stop leaking a listener when on() double-registers it
5
- - fix(webhook): give the real-timer sleep test slack for CI jitter
6
-
7
1
  # Changelog
8
2
 
9
3
  All notable changes to `hierarchical-approval` are documented here. This project
@@ -11,6 +5,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
5
 
12
6
  ## [Unreleased]
13
7
 
8
+ ## [4.0.4] - 2026-09-24
9
+
10
+ ## [4.0.3] - 2026-09-22
11
+
12
+ ### Fixed
13
+
14
+ - `assertStatus`'s `INVALID_STATUS` error now names the instance that failed
15
+ the check, instead of a message that gave no clue which instance it was.
16
+ - `businessHoursCalendar`'s "no working day found" error now names the start
17
+ date that triggered it, instead of a message that gave no clue which call
18
+ misconfigured the calendar.
19
+ - `assertApproverOnLevel`'s forbidden-approver error now names the level,
20
+ matching every sibling guard in `StateMachine.ts`, instead of a bare level
21
+ number with no human-readable name.
22
+
23
+ ## [4.0.2] - 2026-09-20
24
+
14
25
  ### Fixed
15
26
 
16
27
  - `EventBus` (and therefore `engine.on()`/`off()`) no longer leaks a listener
@@ -20,6 +31,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
20
31
  attached.
21
32
  - `DigestNotificationAdapter`'s `intervalMs` validation error now names the
22
33
  offending value, instead of a message that gave no clue which value failed.
34
+ - The default-sleep regression test for `WebhookNotificationAdapter` no longer
35
+ flakes under CI's timer coalescing, which could report elapsed time a
36
+ millisecond or two short of the asserted floor.
23
37
 
24
38
  ## [4.0.1] - 2026-09-14
25
39
 
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-1256%20passing-44cc11.svg?logo=vitest&logoColor=white)](./tests)
14
+ [![tests](https://img.shields.io/badge/tests-1271%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-Cjw5zDwQ.cjs';
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.cjs';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-XbD69gss.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-Dhrc4ES6.cjs';
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-CUZcHWK2.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-J0wSPLIC.cjs';
4
4
  import { z } from 'zod';
5
- import { L as Logger } from './Logger-BplhlU7l.cjs';
6
- import { C as Clock } from './Clock-3FnOczFJ.cjs';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BBLk8MIz.cjs';
8
- import { I as IAuditAdapter } from './IAuditAdapter-Dt8qjkQL.cjs';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.cjs';
10
- import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-YJ_08Mof.cjs';
5
+ import { L as Logger } from './Logger-hqFbBCtB.cjs';
6
+ import { C as Clock } from './Clock-D8Qp39q_.cjs';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-UUKqztxM.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-D9WE0t7A.cjs';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-WROxYSXd.cjs';
10
+ import { I as ISchedulerAdapter } from './ISchedulerAdapter-CLMdBA-5.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DDmjeYV-.cjs';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -201,11 +201,16 @@ 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
 
211
+ /** Backs `{ type: 'role' }` approvers, so a level can name an org role instead of a fixed user id. */
208
212
  interface OrgProvider {
213
+ /** Required: resolve users holding a named role. */
209
214
  getUsersByRole(role: string, tenantId?: string): Promise<string[]> | string[];
210
215
  /** Optional: resolve users by department name. */
211
216
  getUsersByDepartment?(dept: string, tenantId?: string): Promise<string[]> | string[];
@@ -233,6 +238,11 @@ interface OutOfOfficeProvider {
233
238
  */
234
239
  getDelegateFor(userId: string, at: Date): Promise<string | null | undefined> | string | null | undefined;
235
240
  }
241
+ /**
242
+ * Resolves a custom approver type's config to concrete user ids. Registered
243
+ * via {@link ApprovalEngine.registerApproverType} to back a level whose
244
+ * approver config carries an app-defined `type`.
245
+ */
236
246
  type ApproverResolverFn = (config: Record<string, unknown>, ctx: {
237
247
  submittedBy: string;
238
248
  data: Record<string, unknown>;
@@ -279,6 +289,7 @@ declare function validateConditionExpression(expression: ConditionExpression, pa
279
289
  message: string;
280
290
  }>;
281
291
 
292
+ /** Outcome of {@link ApprovalEngine.validateTemplate}. */
282
293
  interface ValidationResult {
283
294
  valid: boolean;
284
295
  errors: Array<{
@@ -286,13 +297,16 @@ interface ValidationResult {
286
297
  message: string;
287
298
  }>;
288
299
  }
300
+ /** Outcome of {@link ApprovalEngine.canApprove}. */
289
301
  interface CanApproveResult {
290
302
  eligible: boolean;
291
303
  reason?: 'not_an_approver' | 'already_acted' | 'self_approval' | 'wrong_status' | 'delegated_away';
292
304
  }
305
+ /** One entry in a {@link ApprovalEngine.preview} result, resolved against the given data without persisting anything. */
293
306
  interface PreviewChainLevel {
294
307
  level: number;
295
308
  name: string;
309
+ /** Approver ids resolved as of preview time; dynamic/role approvers may resolve differently at submit time. */
296
310
  resolvedApprovers: string[];
297
311
  mode: ApprovalMode;
298
312
  }
@@ -386,12 +400,16 @@ interface SimulationResult {
386
400
  /** True when the script ran out before the approval finished. */
387
401
  incomplete: boolean;
388
402
  }
403
+ /** Outcome of a {@link ApprovalEngine.bulkApprove}/{@link ApprovalEngine.bulkReject} sweep. */
389
404
  interface BulkResult {
405
+ /** Instances that reached the requested decision. */
390
406
  succeeded: ApprovalInstance[];
407
+ /** Instances that could not be decided, with the reason. */
391
408
  failed: Array<{
392
409
  instanceId: string;
393
410
  error: ApprovalError;
394
411
  }>;
412
+ /** Number of instance ids passed in, equal to `succeeded.length + failed.length`. */
395
413
  total: number;
396
414
  }
397
415
  /** Outcome of a {@link ApprovalEngine.transferApprovals} sweep. */
@@ -537,6 +555,7 @@ interface CycleTimeStats {
537
555
  /** Largest observed time-to-decision. `0` when {@link count} is `0`. */
538
556
  maxMs: number;
539
557
  }
558
+ /** Outcome of {@link ApprovalEngine.healthCheck}. */
540
559
  interface HealthResult {
541
560
  status: 'healthy' | 'degraded' | 'unhealthy';
542
561
  adapter: 'connected' | 'error';
@@ -545,12 +564,18 @@ interface HealthResult {
545
564
  escalationRunning: boolean;
546
565
  lastTickAt?: Date;
547
566
  }
567
+ /** Controls how {@link ApprovalEngine}'s optimistic-lock retry backs off on write conflicts. Defaults to 3 attempts with a 50ms base delay. */
548
568
  interface RetryPolicy {
569
+ /** Number of attempts before giving up and rethrowing the conflict. */
549
570
  maxAttempts: number;
571
+ /** Linear backoff unit; attempt N waits roughly `baseDelayMs * N` before retrying. */
550
572
  baseDelayMs: number;
573
+ /** Upper bound on the computed delay. Defaults to unbounded. */
551
574
  maxDelayMs?: number;
575
+ /** Adds up to one `baseDelayMs` of random jitter to each delay. Defaults to `true`. */
552
576
  jitter?: boolean;
553
577
  }
578
+ /** Derives the idempotency key used to dedupe a {@link ApprovalEngine.submit} call from its tenant and document identity. */
554
579
  type IdempotencyKeyFn = (tenantId: string, documentType: string, documentId: string, templateName: string, data: Record<string, unknown>) => string;
555
580
  interface ApprovalEngineOptions {
556
581
  adapter: IStorageAdapter;
@@ -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-DSKJ1onF.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-CUZcHWK2.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-CLROEfRd.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';
10
- import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-eo6glbpE.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-B32a2dwP.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-FI_RF_XU.js';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-WROxYSXd.js';
10
+ import { I as ISchedulerAdapter } from './ISchedulerAdapter-CLMdBA-5.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-BKKw7jGN.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -201,11 +201,16 @@ 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
 
211
+ /** Backs `{ type: 'role' }` approvers, so a level can name an org role instead of a fixed user id. */
208
212
  interface OrgProvider {
213
+ /** Required: resolve users holding a named role. */
209
214
  getUsersByRole(role: string, tenantId?: string): Promise<string[]> | string[];
210
215
  /** Optional: resolve users by department name. */
211
216
  getUsersByDepartment?(dept: string, tenantId?: string): Promise<string[]> | string[];
@@ -233,6 +238,11 @@ interface OutOfOfficeProvider {
233
238
  */
234
239
  getDelegateFor(userId: string, at: Date): Promise<string | null | undefined> | string | null | undefined;
235
240
  }
241
+ /**
242
+ * Resolves a custom approver type's config to concrete user ids. Registered
243
+ * via {@link ApprovalEngine.registerApproverType} to back a level whose
244
+ * approver config carries an app-defined `type`.
245
+ */
236
246
  type ApproverResolverFn = (config: Record<string, unknown>, ctx: {
237
247
  submittedBy: string;
238
248
  data: Record<string, unknown>;
@@ -279,6 +289,7 @@ declare function validateConditionExpression(expression: ConditionExpression, pa
279
289
  message: string;
280
290
  }>;
281
291
 
292
+ /** Outcome of {@link ApprovalEngine.validateTemplate}. */
282
293
  interface ValidationResult {
283
294
  valid: boolean;
284
295
  errors: Array<{
@@ -286,13 +297,16 @@ interface ValidationResult {
286
297
  message: string;
287
298
  }>;
288
299
  }
300
+ /** Outcome of {@link ApprovalEngine.canApprove}. */
289
301
  interface CanApproveResult {
290
302
  eligible: boolean;
291
303
  reason?: 'not_an_approver' | 'already_acted' | 'self_approval' | 'wrong_status' | 'delegated_away';
292
304
  }
305
+ /** One entry in a {@link ApprovalEngine.preview} result, resolved against the given data without persisting anything. */
293
306
  interface PreviewChainLevel {
294
307
  level: number;
295
308
  name: string;
309
+ /** Approver ids resolved as of preview time; dynamic/role approvers may resolve differently at submit time. */
296
310
  resolvedApprovers: string[];
297
311
  mode: ApprovalMode;
298
312
  }
@@ -386,12 +400,16 @@ interface SimulationResult {
386
400
  /** True when the script ran out before the approval finished. */
387
401
  incomplete: boolean;
388
402
  }
403
+ /** Outcome of a {@link ApprovalEngine.bulkApprove}/{@link ApprovalEngine.bulkReject} sweep. */
389
404
  interface BulkResult {
405
+ /** Instances that reached the requested decision. */
390
406
  succeeded: ApprovalInstance[];
407
+ /** Instances that could not be decided, with the reason. */
391
408
  failed: Array<{
392
409
  instanceId: string;
393
410
  error: ApprovalError;
394
411
  }>;
412
+ /** Number of instance ids passed in, equal to `succeeded.length + failed.length`. */
395
413
  total: number;
396
414
  }
397
415
  /** Outcome of a {@link ApprovalEngine.transferApprovals} sweep. */
@@ -537,6 +555,7 @@ interface CycleTimeStats {
537
555
  /** Largest observed time-to-decision. `0` when {@link count} is `0`. */
538
556
  maxMs: number;
539
557
  }
558
+ /** Outcome of {@link ApprovalEngine.healthCheck}. */
540
559
  interface HealthResult {
541
560
  status: 'healthy' | 'degraded' | 'unhealthy';
542
561
  adapter: 'connected' | 'error';
@@ -545,12 +564,18 @@ interface HealthResult {
545
564
  escalationRunning: boolean;
546
565
  lastTickAt?: Date;
547
566
  }
567
+ /** Controls how {@link ApprovalEngine}'s optimistic-lock retry backs off on write conflicts. Defaults to 3 attempts with a 50ms base delay. */
548
568
  interface RetryPolicy {
569
+ /** Number of attempts before giving up and rethrowing the conflict. */
549
570
  maxAttempts: number;
571
+ /** Linear backoff unit; attempt N waits roughly `baseDelayMs * N` before retrying. */
550
572
  baseDelayMs: number;
573
+ /** Upper bound on the computed delay. Defaults to unbounded. */
551
574
  maxDelayMs?: number;
575
+ /** Adds up to one `baseDelayMs` of random jitter to each delay. Defaults to `true`. */
552
576
  jitter?: boolean;
553
577
  }
578
+ /** Derives the idempotency key used to dedupe a {@link ApprovalEngine.submit} call from its tenant and document identity. */
554
579
  type IdempotencyKeyFn = (tenantId: string, documentType: string, documentId: string, templateName: string, data: Record<string, unknown>) => string;
555
580
  interface ApprovalEngineOptions {
556
581
  adapter: IStorageAdapter;
@@ -0,0 +1,8 @@
1
+ /** Time source injected into engine and plugin components, so tests can control the clock. */
2
+ interface Clock {
3
+ now(): Date;
4
+ }
5
+ /** {@link Clock} backed by the real wall clock — the default when no clock is configured. */
6
+ declare const systemClock: Clock;
7
+
8
+ export { type Clock as C, systemClock as s };
@@ -0,0 +1,8 @@
1
+ /** Time source injected into engine and plugin components, so tests can control the clock. */
2
+ interface Clock {
3
+ now(): Date;
4
+ }
5
+ /** {@link Clock} backed by the real wall clock — the default when no clock is configured. */
6
+ declare const systemClock: Clock;
7
+
8
+ export { type Clock as C, systemClock as s };
@@ -1,5 +1,6 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-uUQh5XW3.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-CUZcHWK2.cjs';
2
2
 
3
+ /** Secondary write sink for audit entries, alongside the primary storage adapter. */
3
4
  interface IAuditAdapter {
4
5
  /**
5
6
  * Called after every state-mutating operation, in addition to the primary storage adapter.
@@ -1,5 +1,6 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-uUQh5XW3.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-CUZcHWK2.js';
2
2
 
3
+ /** Secondary write sink for audit entries, alongside the primary storage adapter. */
3
4
  interface IAuditAdapter {
4
5
  /**
5
6
  * Called after every state-mutating operation, in addition to the primary storage adapter.
@@ -1,10 +1,15 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-uUQh5XW3.cjs';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CUZcHWK2.js';
2
2
 
3
3
  interface AuthorizationContext {
4
+ /** The engine method the actor is attempting to call. */
4
5
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
6
+ /** Id of the user or system attempting the operation. */
5
7
  actorId: string;
8
+ /** The instance the operation targets, as it stood before the operation ran. */
6
9
  instance: Readonly<ApprovalInstance>;
10
+ /** The current level, for level-scoped operations (approve, reject, delegate, etc.); absent otherwise. */
7
11
  level?: Readonly<ApprovalLevelInstance>;
12
+ /** The caller's own options object for this operation, passed through unmodified. */
8
13
  opts: Record<string, unknown>;
9
14
  }
10
15
  interface IAuthorizationPolicy {
@@ -1,10 +1,15 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-uUQh5XW3.js';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CUZcHWK2.cjs';
2
2
 
3
3
  interface AuthorizationContext {
4
+ /** The engine method the actor is attempting to call. */
4
5
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
6
+ /** Id of the user or system attempting the operation. */
5
7
  actorId: string;
8
+ /** The instance the operation targets, as it stood before the operation ran. */
6
9
  instance: Readonly<ApprovalInstance>;
10
+ /** The current level, for level-scoped operations (approve, reject, delegate, etc.); absent otherwise. */
7
11
  level?: Readonly<ApprovalLevelInstance>;
12
+ /** The caller's own options object for this operation, passed through unmodified. */
8
13
  opts: Record<string, unknown>;
9
14
  }
10
15
  interface IAuthorizationPolicy {
@@ -1,3 +1,4 @@
1
+ /** Closed set of metric names an {@link IMetricsAdapter} may be called with, namespaced `approval.<verb>`. */
1
2
  type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.reminded' | 'approval.info_requested' | 'approval.info_provided' | 'approval.attachment_added' | 'approval.attachment_removed' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
3
  interface IMetricsAdapter {
3
4
  /** Increment a counter. Synchronous — never awaited. */
@@ -1,3 +1,4 @@
1
+ /** Closed set of metric names an {@link IMetricsAdapter} may be called with, namespaced `approval.<verb>`. */
1
2
  type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.reminded' | 'approval.info_requested' | 'approval.info_provided' | 'approval.attachment_added' | 'approval.attachment_removed' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
3
  interface IMetricsAdapter {
3
4
  /** Increment a counter. Synchronous — never awaited. */
@@ -1,33 +1,43 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-uUQh5XW3.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-CUZcHWK2.js';
2
2
 
3
+ /** Common fields carried by every lifecycle event emitted for an approval instance. */
3
4
  interface ApprovalEvent {
5
+ /** The `ApprovalInstance.id` this event pertains to. */
4
6
  instanceId: string;
7
+ /** The id of the business document the instance was raised against. */
5
8
  documentId: string;
9
+ /** The document type, matching the template used to raise the instance. */
6
10
  documentType: string;
11
+ /** When the event occurred. */
7
12
  timestamp: Date;
8
13
  }
14
+ /** Emitted as `approval:submitted` when a new instance is raised. */
9
15
  interface SubmittedEvent extends ApprovalEvent {
10
16
  submittedBy: string;
11
17
  currentApprovers: string[];
12
18
  }
19
+ /** Emitted as `approval:approved` when an approver accepts a level. */
13
20
  interface ApprovedEvent extends ApprovalEvent {
14
21
  approverId: string;
15
22
  level: number;
16
23
  comment?: string;
17
24
  isFinal: boolean;
18
25
  }
26
+ /** Emitted as `approval:rejected` when an approver rejects a level. */
19
27
  interface RejectedEvent extends ApprovalEvent {
20
28
  approverId: string;
21
29
  level: number;
22
30
  reason: string;
23
31
  returnTo: 'originator' | 'previous' | null;
24
32
  }
33
+ /** Emitted as `approval:delegated` when an approver hands their decision to someone else. */
25
34
  interface DelegatedEvent extends ApprovalEvent {
26
35
  fromApprover: string;
27
36
  toApprover: string;
28
37
  level: number;
29
38
  reason: string;
30
39
  }
40
+ /** Emitted as `approval:reassigned` when an admin moves a level to a different approver. */
31
41
  interface ReassignedEvent extends ApprovalEvent {
32
42
  reassignedBy: string;
33
43
  fromApprover: string;
@@ -35,30 +45,37 @@ interface ReassignedEvent extends ApprovalEvent {
35
45
  level: number;
36
46
  reason: string;
37
47
  }
48
+ /** Emitted as `approval:escalated` when a level's SLA rung hands off to a new approver. */
38
49
  interface EscalatedEvent extends ApprovalEvent {
39
50
  level: number;
40
51
  escalatedTo: string;
41
52
  }
53
+ /** Emitted as `approval:cancelled` when the instance is withdrawn before completion. */
42
54
  interface CancelledEvent extends ApprovalEvent {
43
55
  cancelledBy: string;
44
56
  reason: string;
45
57
  }
58
+ /** Emitted as `approval:level_advanced` when the chain moves to its next open level. */
46
59
  interface LevelAdvancedEvent extends ApprovalEvent {
47
60
  fromLevel: number;
48
61
  toLevel: number;
49
62
  newApprovers: string[];
50
63
  }
64
+ /** Emitted as `approval:resubmitted` when a rejected instance is raised again. */
51
65
  interface ResubmittedEvent extends ApprovalEvent {
52
66
  resubmittedBy: string;
53
67
  originalInstanceId: string;
54
68
  }
69
+ /** Emitted as `approval:overridden` when an admin force-approves past the normal chain. */
55
70
  interface OverriddenEvent extends ApprovalEvent {
56
71
  overriddenBy: string;
57
72
  justification: string;
58
73
  }
74
+ /** Emitted as `approval:expired` when the instance's deadline passes unresolved. */
59
75
  interface ExpiredEvent extends ApprovalEvent {
60
76
  deadlineAction: 'cancel' | 'reject';
61
77
  }
78
+ /** Emitted as `approval:sla_breached` when a level's deadline passes without expiring the instance. */
62
79
  interface SlaBreachedEvent extends ApprovalEvent {
63
80
  slaDeadlineAt: Date;
64
81
  }
@@ -160,15 +177,23 @@ interface HistoryEntry extends AuditEntry {
160
177
  }
161
178
 
162
179
  interface NotificationEvent {
180
+ /** The event that fired, e.g. `'submitted'`, `'approved'`, `'escalated'`. */
163
181
  type: ApprovalEventName;
182
+ /** Id of the approval instance the event fired for. */
164
183
  instanceId: string;
184
+ /** Id of the document under approval, as submitted by the caller. */
165
185
  documentId: string;
186
+ /** Caller-defined document category (e.g. `'purchase_order'`). */
166
187
  documentType: string;
188
+ /** When the underlying transition happened. */
167
189
  timestamp: Date;
168
190
  /** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
169
191
  recipients: string[];
192
+ /** Name of the template governing the instance. */
170
193
  templateName: string;
194
+ /** Tenant the instance belongs to, for multi-tenant deployments. */
171
195
  tenantId: string;
196
+ /** Event-specific detail, shaped by {@link type}. */
172
197
  payload: ApprovalEventMap[ApprovalEventName];
173
198
  }
174
199
  interface INotificationAdapter {
@@ -1,33 +1,43 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-uUQh5XW3.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-CUZcHWK2.cjs';
2
2
 
3
+ /** Common fields carried by every lifecycle event emitted for an approval instance. */
3
4
  interface ApprovalEvent {
5
+ /** The `ApprovalInstance.id` this event pertains to. */
4
6
  instanceId: string;
7
+ /** The id of the business document the instance was raised against. */
5
8
  documentId: string;
9
+ /** The document type, matching the template used to raise the instance. */
6
10
  documentType: string;
11
+ /** When the event occurred. */
7
12
  timestamp: Date;
8
13
  }
14
+ /** Emitted as `approval:submitted` when a new instance is raised. */
9
15
  interface SubmittedEvent extends ApprovalEvent {
10
16
  submittedBy: string;
11
17
  currentApprovers: string[];
12
18
  }
19
+ /** Emitted as `approval:approved` when an approver accepts a level. */
13
20
  interface ApprovedEvent extends ApprovalEvent {
14
21
  approverId: string;
15
22
  level: number;
16
23
  comment?: string;
17
24
  isFinal: boolean;
18
25
  }
26
+ /** Emitted as `approval:rejected` when an approver rejects a level. */
19
27
  interface RejectedEvent extends ApprovalEvent {
20
28
  approverId: string;
21
29
  level: number;
22
30
  reason: string;
23
31
  returnTo: 'originator' | 'previous' | null;
24
32
  }
33
+ /** Emitted as `approval:delegated` when an approver hands their decision to someone else. */
25
34
  interface DelegatedEvent extends ApprovalEvent {
26
35
  fromApprover: string;
27
36
  toApprover: string;
28
37
  level: number;
29
38
  reason: string;
30
39
  }
40
+ /** Emitted as `approval:reassigned` when an admin moves a level to a different approver. */
31
41
  interface ReassignedEvent extends ApprovalEvent {
32
42
  reassignedBy: string;
33
43
  fromApprover: string;
@@ -35,30 +45,37 @@ interface ReassignedEvent extends ApprovalEvent {
35
45
  level: number;
36
46
  reason: string;
37
47
  }
48
+ /** Emitted as `approval:escalated` when a level's SLA rung hands off to a new approver. */
38
49
  interface EscalatedEvent extends ApprovalEvent {
39
50
  level: number;
40
51
  escalatedTo: string;
41
52
  }
53
+ /** Emitted as `approval:cancelled` when the instance is withdrawn before completion. */
42
54
  interface CancelledEvent extends ApprovalEvent {
43
55
  cancelledBy: string;
44
56
  reason: string;
45
57
  }
58
+ /** Emitted as `approval:level_advanced` when the chain moves to its next open level. */
46
59
  interface LevelAdvancedEvent extends ApprovalEvent {
47
60
  fromLevel: number;
48
61
  toLevel: number;
49
62
  newApprovers: string[];
50
63
  }
64
+ /** Emitted as `approval:resubmitted` when a rejected instance is raised again. */
51
65
  interface ResubmittedEvent extends ApprovalEvent {
52
66
  resubmittedBy: string;
53
67
  originalInstanceId: string;
54
68
  }
69
+ /** Emitted as `approval:overridden` when an admin force-approves past the normal chain. */
55
70
  interface OverriddenEvent extends ApprovalEvent {
56
71
  overriddenBy: string;
57
72
  justification: string;
58
73
  }
74
+ /** Emitted as `approval:expired` when the instance's deadline passes unresolved. */
59
75
  interface ExpiredEvent extends ApprovalEvent {
60
76
  deadlineAction: 'cancel' | 'reject';
61
77
  }
78
+ /** Emitted as `approval:sla_breached` when a level's deadline passes without expiring the instance. */
62
79
  interface SlaBreachedEvent extends ApprovalEvent {
63
80
  slaDeadlineAt: Date;
64
81
  }
@@ -160,15 +177,23 @@ interface HistoryEntry extends AuditEntry {
160
177
  }
161
178
 
162
179
  interface NotificationEvent {
180
+ /** The event that fired, e.g. `'submitted'`, `'approved'`, `'escalated'`. */
163
181
  type: ApprovalEventName;
182
+ /** Id of the approval instance the event fired for. */
164
183
  instanceId: string;
184
+ /** Id of the document under approval, as submitted by the caller. */
165
185
  documentId: string;
186
+ /** Caller-defined document category (e.g. `'purchase_order'`). */
166
187
  documentType: string;
188
+ /** When the underlying transition happened. */
167
189
  timestamp: Date;
168
190
  /** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
169
191
  recipients: string[];
192
+ /** Name of the template governing the instance. */
170
193
  templateName: string;
194
+ /** Tenant the instance belongs to, for multi-tenant deployments. */
171
195
  tenantId: string;
196
+ /** Event-specific detail, shaped by {@link type}. */
172
197
  payload: ApprovalEventMap[ApprovalEventName];
173
198
  }
174
199
  interface INotificationAdapter {
@@ -1,5 +1,6 @@
1
- import { a as ApprovalInstance } from './instance-uUQh5XW3.js';
1
+ import { a as ApprovalInstance } from './instance-CUZcHWK2.js';
2
2
 
3
+ /** Base class for all errors thrown by this library; carries a stable machine-readable `code`. */
3
4
  declare class ApprovalError extends Error {
4
5
  readonly code: string;
5
6
  constructor(message: string, code: string);
@@ -10,28 +11,40 @@ declare class ApprovalError extends Error {
10
11
  };
11
12
  toHttpStatus(): number;
12
13
  }
14
+ /** Thrown when a requested resource (instance, template, etc.) does not exist. */
13
15
  declare class ApprovalNotFoundError extends ApprovalError {
14
16
  constructor(resource: string, id: string);
15
17
  }
18
+ /** Thrown when an instance was modified by another process since it was last read. */
16
19
  declare class ApprovalConflictError extends ApprovalError {
17
20
  constructor(instanceId: string);
18
21
  }
22
+ /** Thrown when the caller is not authorized to perform the requested action. */
19
23
  declare class ApprovalForbiddenError extends ApprovalError {
20
24
  constructor(message: string);
21
25
  }
26
+ /** Thrown when input or configuration fails validation; `cause` may hold the originating error. */
22
27
  declare class ApprovalValidationError extends ApprovalError {
23
28
  readonly cause?: unknown | undefined;
24
29
  constructor(message: string, cause?: unknown | undefined);
25
30
  }
31
+ /** Thrown when a template lookup by name finds no registered template. */
26
32
  declare class ApprovalTemplateNotFoundError extends ApprovalError {
27
33
  constructor(name: string);
28
34
  }
29
35
 
36
+ /** The engine method a middleware hook is running around. */
37
+ type EngineOperation = 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment' | 'addComment' | 'resubmit' | 'override';
38
+ /** Context passed to an {@link IOperationMiddleware} hook for one engine call. */
30
39
  interface OperationContext<T = unknown> {
31
- operation: string;
40
+ operation: EngineOperation;
41
+ /** The target instance's id, for operations that act on an existing instance. */
32
42
  instanceId?: string;
43
+ /** Id of the user or system performing the operation, when known. */
33
44
  actorId?: string;
45
+ /** The engine's configured tenant id (defaults to `'default'`). */
34
46
  tenantId: string;
47
+ /** The caller's own input for this operation, e.g. the submit payload or decision comment. */
35
48
  input: T;
36
49
  }
37
50
  interface IOperationMiddleware {