hierarchical-approval 4.0.2 → 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 (72) hide show
  1. package/CHANGELOG.md +20 -4
  2. package/dist/{ApprovalEngine-Dqe-rC6K.d.ts → ApprovalEngine-BtgQDr_8.d.ts} +13 -9
  3. package/dist/{ApprovalEngine-XgGBQLC_.d.cts → ApprovalEngine-Djdw-JzA.d.cts} +13 -9
  4. package/dist/Clock-D8Qp39q_.d.cts +8 -0
  5. package/dist/Clock-D8Qp39q_.d.ts +8 -0
  6. package/dist/{IAuditAdapter-DkbyFFjb.d.ts → IAuditAdapter-C-04ar9A.d.ts} +1 -1
  7. package/dist/{IAuditAdapter-Dt8qjkQL.d.cts → IAuditAdapter-TT2Xhtu6.d.cts} +1 -1
  8. package/dist/{IAuthorizationPolicy-eo6glbpE.d.ts → IAuthorizationPolicy-D6tTT3gv.d.cts} +6 -1
  9. package/dist/{IAuthorizationPolicy-YJ_08Mof.d.cts → IAuthorizationPolicy-wIE1mpTy.d.ts} +6 -1
  10. package/dist/{IMetricsAdapter-DWq8IFaf.d.cts → IMetricsAdapter-WROxYSXd.d.cts} +1 -0
  11. package/dist/{IMetricsAdapter-DWq8IFaf.d.ts → IMetricsAdapter-WROxYSXd.d.ts} +1 -0
  12. package/dist/{INotificationAdapter-BTO9FSKy.d.ts → INotificationAdapter-BcUskJ6z.d.ts} +9 -1
  13. package/dist/{INotificationAdapter-XbD69gss.d.cts → INotificationAdapter-CBsJDk-Y.d.cts} +9 -1
  14. package/dist/{IOperationMiddleware-CX7lo4lp.d.ts → IOperationMiddleware-C7ciNnJp.d.ts} +3 -2
  15. package/dist/{IOperationMiddleware-BBLk8MIz.d.cts → IOperationMiddleware-D3Yelw9R.d.cts} +3 -2
  16. package/dist/{IStorageAdapter-CdXMggPK.d.ts → IStorageAdapter-CVvNsnnD.d.ts} +7 -1
  17. package/dist/{IStorageAdapter-Cjw5zDwQ.d.cts → IStorageAdapter-D7cy20uD.d.cts} +7 -1
  18. package/dist/{Logger-BplhlU7l.d.cts → Logger-hqFbBCtB.d.cts} +7 -0
  19. package/dist/{Logger-BplhlU7l.d.ts → Logger-hqFbBCtB.d.ts} +7 -0
  20. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  21. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  22. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  23. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  24. package/dist/index.cjs +32 -3
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +13 -13
  27. package/dist/index.d.ts +13 -13
  28. package/dist/index.js +32 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/{instance-uUQh5XW3.d.cts → instance-Dd1jQizG.d.cts} +19 -0
  31. package/dist/{instance-uUQh5XW3.d.ts → instance-Dd1jQizG.d.ts} +19 -0
  32. package/dist/nestjs.cjs +31 -2
  33. package/dist/nestjs.cjs.map +1 -1
  34. package/dist/nestjs.d.cts +10 -10
  35. package/dist/nestjs.d.ts +10 -10
  36. package/dist/nestjs.js +31 -2
  37. package/dist/nestjs.js.map +1 -1
  38. package/dist/plugins/audit.cjs.map +1 -1
  39. package/dist/plugins/audit.d.cts +3 -3
  40. package/dist/plugins/audit.d.ts +3 -3
  41. package/dist/plugins/audit.js.map +1 -1
  42. package/dist/plugins/metrics.cjs.map +1 -1
  43. package/dist/plugins/metrics.d.cts +2 -2
  44. package/dist/plugins/metrics.d.ts +2 -2
  45. package/dist/plugins/metrics.js.map +1 -1
  46. package/dist/plugins/notify.cjs.map +1 -1
  47. package/dist/plugins/notify.d.cts +4 -4
  48. package/dist/plugins/notify.d.ts +4 -4
  49. package/dist/plugins/notify.js.map +1 -1
  50. package/dist/plugins/resilience.cjs.map +1 -1
  51. package/dist/plugins/resilience.d.cts +5 -5
  52. package/dist/plugins/resilience.d.ts +5 -5
  53. package/dist/plugins/resilience.js.map +1 -1
  54. package/dist/plugins/scheduler.cjs.map +1 -1
  55. package/dist/plugins/scheduler.d.cts +2 -2
  56. package/dist/plugins/scheduler.d.ts +2 -2
  57. package/dist/plugins/scheduler.js.map +1 -1
  58. package/dist/plugins/tracing.d.cts +2 -2
  59. package/dist/plugins/tracing.d.ts +2 -2
  60. package/dist/plugins/webhook.cjs.map +1 -1
  61. package/dist/plugins/webhook.d.cts +4 -4
  62. package/dist/plugins/webhook.d.ts +4 -4
  63. package/dist/plugins/webhook.js.map +1 -1
  64. package/dist/testing.cjs +31 -2
  65. package/dist/testing.cjs.map +1 -1
  66. package/dist/testing.d.cts +10 -10
  67. package/dist/testing.d.ts +10 -10
  68. package/dist/testing.js +31 -2
  69. package/dist/testing.js.map +1 -1
  70. package/package.json +1 -1
  71. package/dist/Clock-3FnOczFJ.d.cts +0 -6
  72. package/dist/Clock-3FnOczFJ.d.ts +0 -6
package/CHANGELOG.md CHANGED
@@ -1,8 +1,8 @@
1
- ## 4.0.2 - 2026-09-20
1
+ ## 4.0.3 - 2026-09-22
2
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
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
6
6
 
7
7
  # Changelog
8
8
 
@@ -13,6 +13,19 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
13
13
 
14
14
  ### Fixed
15
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
+
16
29
  - `EventBus` (and therefore `engine.on()`/`off()`) no longer leaks a listener
17
30
  when the same function is registered twice with `on()`: each registration
18
31
  now needs its own `off()` call to undo, instead of the second registration
@@ -20,6 +33,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
20
33
  attached.
21
34
  - `DigestNotificationAdapter`'s `intervalMs` validation error now names the
22
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.
23
39
 
24
40
  ## [4.0.1] - 2026-09-14
25
41
 
@@ -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[];
@@ -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-D7cy20uD.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-Dd1jQizG.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-CBsJDk-Y.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';
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-D3Yelw9R.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-TT2Xhtu6.cjs';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-WROxYSXd.cjs';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-YJ_08Mof.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-D6tTT3gv.cjs';
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[];
@@ -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,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-uUQh5XW3.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-Dd1jQizG.js';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-uUQh5XW3.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-Dd1jQizG.cjs';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -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-Dd1jQizG.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,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-Dd1jQizG.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,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,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-uUQh5XW3.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-Dd1jQizG.js';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -160,15 +160,23 @@ interface HistoryEntry extends AuditEntry {
160
160
  }
161
161
 
162
162
  interface NotificationEvent {
163
+ /** The event that fired, e.g. `'submitted'`, `'approved'`, `'escalated'`. */
163
164
  type: ApprovalEventName;
165
+ /** Id of the approval instance the event fired for. */
164
166
  instanceId: string;
167
+ /** Id of the document under approval, as submitted by the caller. */
165
168
  documentId: string;
169
+ /** Caller-defined document category (e.g. `'purchase_order'`). */
166
170
  documentType: string;
171
+ /** When the underlying transition happened. */
167
172
  timestamp: Date;
168
173
  /** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
169
174
  recipients: string[];
175
+ /** Name of the template governing the instance. */
170
176
  templateName: string;
177
+ /** Tenant the instance belongs to, for multi-tenant deployments. */
171
178
  tenantId: string;
179
+ /** Event-specific detail, shaped by {@link type}. */
172
180
  payload: ApprovalEventMap[ApprovalEventName];
173
181
  }
174
182
  interface INotificationAdapter {
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-uUQh5XW3.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-Dd1jQizG.cjs';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -160,15 +160,23 @@ interface HistoryEntry extends AuditEntry {
160
160
  }
161
161
 
162
162
  interface NotificationEvent {
163
+ /** The event that fired, e.g. `'submitted'`, `'approved'`, `'escalated'`. */
163
164
  type: ApprovalEventName;
165
+ /** Id of the approval instance the event fired for. */
164
166
  instanceId: string;
167
+ /** Id of the document under approval, as submitted by the caller. */
165
168
  documentId: string;
169
+ /** Caller-defined document category (e.g. `'purchase_order'`). */
166
170
  documentType: string;
171
+ /** When the underlying transition happened. */
167
172
  timestamp: Date;
168
173
  /** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
169
174
  recipients: string[];
175
+ /** Name of the template governing the instance. */
170
176
  templateName: string;
177
+ /** Tenant the instance belongs to, for multi-tenant deployments. */
171
178
  tenantId: string;
179
+ /** Event-specific detail, shaped by {@link type}. */
172
180
  payload: ApprovalEventMap[ApprovalEventName];
173
181
  }
174
182
  interface INotificationAdapter {
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-uUQh5XW3.js';
1
+ import { a as ApprovalInstance } from './instance-Dd1jQizG.js';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -27,8 +27,9 @@ declare class ApprovalTemplateNotFoundError extends ApprovalError {
27
27
  constructor(name: string);
28
28
  }
29
29
 
30
+ type EngineOperation = 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment' | 'addComment' | 'resubmit' | 'override';
30
31
  interface OperationContext<T = unknown> {
31
- operation: string;
32
+ operation: EngineOperation;
32
33
  instanceId?: string;
33
34
  actorId?: string;
34
35
  tenantId: string;
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-uUQh5XW3.cjs';
1
+ import { a as ApprovalInstance } from './instance-Dd1jQizG.cjs';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -27,8 +27,9 @@ declare class ApprovalTemplateNotFoundError extends ApprovalError {
27
27
  constructor(name: string);
28
28
  }
29
29
 
30
+ type EngineOperation = 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment' | 'addComment' | 'resubmit' | 'override';
30
31
  interface OperationContext<T = unknown> {
31
- operation: string;
32
+ operation: EngineOperation;
32
33
  instanceId?: string;
33
34
  actorId?: string;
34
35
  tenantId: string;
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-uUQh5XW3.js';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-Dd1jQizG.js';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -42,14 +42,19 @@ interface IStorageAdapter {
42
42
  saveTemplate(template: ApprovalTemplate): Promise<void>;
43
43
  getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null>;
44
44
  listTemplates(tenantId: string): Promise<ApprovalTemplate[]>;
45
+ /** Create a new instance. Callers own uniqueness of `instance.id`. */
45
46
  saveInstance(instance: ApprovalInstance): Promise<void>;
46
47
  /** Conditional update — throws ApprovalConflictError if stored version !== expectedVersion. */
47
48
  updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void>;
49
+ /** Fetch one instance by id, scoped to a tenant. Returns null if absent. */
48
50
  getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null>;
51
+ /** Instances where `approverId` is a current approver on any pending level. */
49
52
  getInstancesByApprover(tenantId: string, approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
53
+ /** Instances matching an {@link InstanceFilter}. Ordering is adapter-specific. */
50
54
  getInstancesByFilter(tenantId: string, filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
51
55
  /** Optional cursor-based pagination — more efficient than offset at scale. */
52
56
  getInstancesByCursor?(tenantId: string, filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
57
+ /** Pending instances overdue as of `asOf` on any open level, not just the current one. */
53
58
  getOverdueInstances(tenantId: string, asOf: Date, filter?: InstanceFilter): Promise<ApprovalInstance[]>;
54
59
  /**
55
60
  * Count instances matching a filter, without fetching any of them.
@@ -85,6 +90,7 @@ interface IStorageAdapter {
85
90
  * @since 2.3.0
86
91
  */
87
92
  deleteInstance?(tenantId: string, id: string): Promise<boolean>;
93
+ /** Look up an instance by `idempotencyKey`, unique within a tenant, not globally. */
88
94
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
89
95
  appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void>;
90
96
  }
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-uUQh5XW3.cjs';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-Dd1jQizG.cjs';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -42,14 +42,19 @@ interface IStorageAdapter {
42
42
  saveTemplate(template: ApprovalTemplate): Promise<void>;
43
43
  getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null>;
44
44
  listTemplates(tenantId: string): Promise<ApprovalTemplate[]>;
45
+ /** Create a new instance. Callers own uniqueness of `instance.id`. */
45
46
  saveInstance(instance: ApprovalInstance): Promise<void>;
46
47
  /** Conditional update — throws ApprovalConflictError if stored version !== expectedVersion. */
47
48
  updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void>;
49
+ /** Fetch one instance by id, scoped to a tenant. Returns null if absent. */
48
50
  getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null>;
51
+ /** Instances where `approverId` is a current approver on any pending level. */
49
52
  getInstancesByApprover(tenantId: string, approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
53
+ /** Instances matching an {@link InstanceFilter}. Ordering is adapter-specific. */
50
54
  getInstancesByFilter(tenantId: string, filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
51
55
  /** Optional cursor-based pagination — more efficient than offset at scale. */
52
56
  getInstancesByCursor?(tenantId: string, filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
57
+ /** Pending instances overdue as of `asOf` on any open level, not just the current one. */
53
58
  getOverdueInstances(tenantId: string, asOf: Date, filter?: InstanceFilter): Promise<ApprovalInstance[]>;
54
59
  /**
55
60
  * Count instances matching a filter, without fetching any of them.
@@ -85,6 +90,7 @@ interface IStorageAdapter {
85
90
  * @since 2.3.0
86
91
  */
87
92
  deleteInstance?(tenantId: string, id: string): Promise<boolean>;
93
+ /** Look up an instance by `idempotencyKey`, unique within a tenant, not globally. */
88
94
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
89
95
  appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void>;
90
96
  }
@@ -1,10 +1,17 @@
1
+ /** Structured logger injected into engine and plugin components. */
1
2
  interface Logger {
3
+ /** Routine, expected activity. */
2
4
  info(msg: string, context?: Record<string, unknown>): void;
5
+ /** Unexpected but recoverable condition. */
3
6
  warn(msg: string, context?: Record<string, unknown>): void;
7
+ /** A caught error that did not stop the calling operation. */
4
8
  error(msg: string, err?: unknown, context?: Record<string, unknown>): void;
9
+ /** A caught error severe enough that the calling operation could not continue. */
5
10
  fatal(msg: string, err?: unknown, context?: Record<string, unknown>): void;
11
+ /** Verbose, developer-facing detail. */
6
12
  debug(msg: string, context?: Record<string, unknown>): void;
7
13
  }
14
+ /** {@link Logger} that discards every call — the default when no logger is configured. */
8
15
  declare const noopLogger: Logger;
9
16
 
10
17
  export { type Logger as L, noopLogger as n };
@@ -1,10 +1,17 @@
1
+ /** Structured logger injected into engine and plugin components. */
1
2
  interface Logger {
3
+ /** Routine, expected activity. */
2
4
  info(msg: string, context?: Record<string, unknown>): void;
5
+ /** Unexpected but recoverable condition. */
3
6
  warn(msg: string, context?: Record<string, unknown>): void;
7
+ /** A caught error that did not stop the calling operation. */
4
8
  error(msg: string, err?: unknown, context?: Record<string, unknown>): void;
9
+ /** A caught error severe enough that the calling operation could not continue. */
5
10
  fatal(msg: string, err?: unknown, context?: Record<string, unknown>): void;
11
+ /** Verbose, developer-facing detail. */
6
12
  debug(msg: string, context?: Record<string, unknown>): void;
7
13
  }
14
+ /** {@link Logger} that discards every call — the default when no logger is configured. */
8
15
  declare const noopLogger: Logger;
9
16
 
10
17
  export { type Logger as L, noopLogger as n };
@@ -1,5 +1,5 @@
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 { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-uUQh5XW3.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D7cy20uD.cjs';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-Dd1jQizG.cjs';
3
3
 
4
4
  /**
5
5
  * In-process {@link IStorageAdapter} backed by two `Map`s, keyed by
@@ -1,5 +1,5 @@
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 { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-uUQh5XW3.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 { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-Dd1jQizG.js';
3
3
 
4
4
  /**
5
5
  * In-process {@link IStorageAdapter} backed by two `Map`s, keyed by
@@ -1,7 +1,7 @@
1
1
  import * as tls from 'tls';
2
2
  import * as pg from 'pg';
3
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-Cjw5zDwQ.cjs';
4
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-uUQh5XW3.cjs';
3
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D7cy20uD.cjs';
4
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-Dd1jQizG.cjs';
5
5
 
6
6
  interface PostgresAdapterOptions {
7
7
  connectionString?: string;
@@ -1,7 +1,7 @@
1
1
  import * as tls from 'tls';
2
2
  import * as pg from 'pg';
3
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-CdXMggPK.js';
4
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-uUQh5XW3.js';
3
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-CVvNsnnD.js';
4
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-Dd1jQizG.js';
5
5
 
6
6
  interface PostgresAdapterOptions {
7
7
  connectionString?: string;
package/dist/index.cjs CHANGED
@@ -556,6 +556,7 @@ var TemplateRegistry = class {
556
556
  delete resolved.removeLevels;
557
557
  return resolved;
558
558
  }
559
+ /** Register a new template. Throws if a template with this name already exists for the tenant. */
559
560
  async define(config) {
560
561
  const existing = await this.adapter.getTemplate(this.tenantId, config.name);
561
562
  if (existing) {
@@ -592,11 +593,13 @@ var TemplateRegistry = class {
592
593
  await this.adapter.saveTemplate(updated);
593
594
  return newId;
594
595
  }
596
+ /** Fetch a template by name. Throws if no template with this name exists for the tenant. */
595
597
  async get(name) {
596
598
  const template = await this.adapter.getTemplate(this.tenantId, name);
597
599
  if (!template) throw new ApprovalTemplateNotFoundError(name);
598
600
  return template;
599
601
  }
602
+ /** List every template registered for the tenant. */
600
603
  async list() {
601
604
  return this.adapter.listTemplates(this.tenantId);
602
605
  }
@@ -636,6 +639,32 @@ var LevelResolver = class {
636
639
  for (const [name, fn] of this.resolvers) target.register(name, fn);
637
640
  for (const [name, fn] of this.approverTypes) target.registerApproverType(name, fn);
638
641
  }
642
+ /**
643
+ * Resolve a level's `approvers` config down to a deduplicated list of user
644
+ * ids, applying out-of-office cover last so a substitute never sees a stale
645
+ * approver id.
646
+ *
647
+ * @param approvers - The level's approver configs (`user`, `role`,
648
+ * `dynamic`, or a custom type registered via {@link registerApproverType}).
649
+ * @param submittedBy - Passed through to `dynamic` resolvers and custom
650
+ * approver-type functions as routing context.
651
+ * @param data - The instance's document data, passed through the same way.
652
+ * @param orgProvider - Required to resolve `role` approvers; omit only when
653
+ * the template uses no `role` approvers.
654
+ * @param outOfOffice - Optional cover lookup, consulted after every other
655
+ * approver type has resolved.
656
+ * @param at - The moment out-of-office cover is resolved for. Defaults to
657
+ * now.
658
+ * @param substitutions - Filled with `original -> stand-in` for each
659
+ * approver replaced by out-of-office cover, so the caller can carry
660
+ * anything keyed by the original id (such as a weighted level's vote
661
+ * weight) across to the substitute.
662
+ * @returns The resolved, deduplicated approver ids (post-substitution).
663
+ * @throws {ApprovalValidationError} If no approvers resolve at all, a
664
+ * `role` approver is configured without an `orgProvider`, a `dynamic`
665
+ * approver names a resolver that was never registered, or a custom
666
+ * approver type was never registered.
667
+ */
639
668
  async resolveApprovers(approvers, submittedBy, data, orgProvider, outOfOffice, at, substitutions) {
640
669
  const resolved = [];
641
670
  for (const approver of approvers) {
@@ -1047,7 +1076,7 @@ function evaluateConditions(conditions, data) {
1047
1076
  function assertStatus(instance, expected) {
1048
1077
  if (instance.status !== expected) {
1049
1078
  throw new ApprovalError(
1050
- `Expected instance status "${expected}" but got "${instance.status}".`,
1079
+ `Instance "${instance.id}": expected status "${expected}" but got "${instance.status}".`,
1051
1080
  "INVALID_STATUS"
1052
1081
  );
1053
1082
  }
@@ -1055,7 +1084,7 @@ function assertStatus(instance, expected) {
1055
1084
  function assertApproverOnLevel(level, approverId) {
1056
1085
  if (!level.approverIds.includes(approverId)) {
1057
1086
  throw new ApprovalForbiddenError(
1058
- `User "${approverId}" is not an approver for level ${level.level}.`
1087
+ `User "${approverId}" is not an approver for level ${level.level} ("${level.name}").`
1059
1088
  );
1060
1089
  }
1061
1090
  }
@@ -5170,7 +5199,7 @@ function businessHoursCalendar(options = {}) {
5170
5199
  return cursor;
5171
5200
  }
5172
5201
  throw new Error(
5173
- "businessHoursCalendar: no working day found within 10 years of the start date."
5202
+ `businessHoursCalendar: no working day found within 10 years of ${from.toISOString()} \u2014 check weekendDays and holidays.`
5174
5203
  );
5175
5204
  };
5176
5205
  const base = weekendCalendar(options);