hierarchical-approval 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +29 -0
- package/dist/{ApprovalEngine-DKn7KymS.d.ts → ApprovalEngine-B3PVWw05.d.ts} +68 -2
- package/dist/{ApprovalEngine-D6BmYI8B.d.cts → ApprovalEngine-DMM55f5R.d.cts} +68 -2
- package/dist/index.cjs +399 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +399 -282
- package/dist/index.js.map +1 -1
- package/dist/nestjs.cjs +377 -56
- package/dist/nestjs.cjs.map +1 -1
- package/dist/nestjs.d.cts +1 -1
- package/dist/nestjs.d.ts +1 -1
- package/dist/nestjs.js +377 -56
- package/dist/nestjs.js.map +1 -1
- package/dist/testing.cjs +399 -282
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +399 -282
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
_Nothing yet._
|
|
9
9
|
|
|
10
|
+
## [2.9.0] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Added — `simulate()`
|
|
13
|
+
|
|
14
|
+
- **Dry-runs a document through a template against scripted decisions.**
|
|
15
|
+
`explainChain()` (2.7.0) says what the chain will be; nothing said what
|
|
16
|
+
happens *to* it. Answering "if the CFO rejects at level 3, does it go back to
|
|
17
|
+
the submitter or die?" meant submitting a real approval into real storage and
|
|
18
|
+
cleaning it up afterwards, or reasoning about the state machine by hand.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
await engine.simulate({
|
|
22
|
+
templateName: 'purchase-order',
|
|
23
|
+
data: { amount: 20000 },
|
|
24
|
+
submittedBy: 'buyer-1',
|
|
25
|
+
decisions: [{ approve: 'mgr-1' }, { reject: 'cfo', reason: 'over budget' }],
|
|
26
|
+
});
|
|
27
|
+
// { finalStatus, levels, transcript, unreachedLevels, incomplete }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **Nothing escapes the simulation.** It runs against a private in-memory store
|
|
31
|
+
seeded with a copy of the template, with the notification, audit and metrics
|
|
32
|
+
adapters and the authorization policy detached — so a dry run cannot page an
|
|
33
|
+
approver, write somebody's audit log, or move a counter.
|
|
34
|
+
|
|
35
|
+
- **Custom resolvers and approver types are copied across.** A simulation that
|
|
36
|
+
could not resolve the caller's own `dynamic` approvers would answer a
|
|
37
|
+
different question from the one asked.
|
|
38
|
+
|
|
39
|
+
- **A refused decision stops the run and is reported, not thrown** — wrong
|
|
40
|
+
approver, wrong level, already acted. The refusal is usually the answer the
|
|
41
|
+
caller was looking for, and throwing would discard the transcript that
|
|
42
|
+
explains how the run got there.
|
|
43
|
+
|
|
44
|
+
New exports: `SimulationResult`, `SimulationStep`, `SimulatedDecision`.
|
|
45
|
+
|
|
10
46
|
## [2.8.0] - 2026-09-04
|
|
11
47
|
|
|
12
48
|
### Added — comment threads
|
package/README.md
CHANGED
|
@@ -495,6 +495,35 @@ the current approvers: a remark aimed at somebody should reach them, and one
|
|
|
495
495
|
aimed at nobody should not page the whole level. Comments are still written to
|
|
496
496
|
the audit trail, since the record of who said what belongs there.
|
|
497
497
|
|
|
498
|
+
### Dry-running a workflow
|
|
499
|
+
|
|
500
|
+
`explainChain()` says what the chain will be; `simulate()` says what happens to
|
|
501
|
+
it — "if the CFO rejects at level 3, does it go back to the submitter or die?":
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
const result = await engine.simulate({
|
|
505
|
+
templateName: 'purchase-order',
|
|
506
|
+
data: { amount: 20000 },
|
|
507
|
+
submittedBy: 'buyer-1',
|
|
508
|
+
decisions: [
|
|
509
|
+
{ approve: 'mgr-1' },
|
|
510
|
+
{ approve: 'fin-1' },
|
|
511
|
+
{ reject: 'cfo', reason: 'over budget' },
|
|
512
|
+
],
|
|
513
|
+
});
|
|
514
|
+
// { finalStatus: 'rejected', levels: [...], transcript: [...], unreachedLevels: [], incomplete: false }
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The run executes against a **private in-memory store** seeded with a copy of the
|
|
518
|
+
template, so your storage is untouched and no events reach your notification
|
|
519
|
+
adapters. Custom resolvers and approver types are copied across — a simulation
|
|
520
|
+
that couldn't resolve your own `dynamic` approvers would answer a different
|
|
521
|
+
question from the one you asked.
|
|
522
|
+
|
|
523
|
+
A refused decision (wrong approver, wrong level, already acted) stops the run
|
|
524
|
+
and appears in the transcript with its reason rather than throwing: the refusal
|
|
525
|
+
is usually the answer you were looking for.
|
|
526
|
+
|
|
498
527
|
### Why does this chain look like this?
|
|
499
528
|
|
|
500
529
|
`previewApprovalChain()` answers *what* the chain will be. `explainChain()`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-C2QxUwbX.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, b as AuditEntry } from './instance-BvOyT00S.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-BvOyT00S.js';
|
|
3
3
|
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DMvA4R-c.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { L as Logger } from './Logger-BplhlU7l.js';
|
|
@@ -347,6 +347,45 @@ interface ChainExplanation {
|
|
|
347
347
|
skipped: ExplainedSkip[];
|
|
348
348
|
rules: ExplainedRule[];
|
|
349
349
|
}
|
|
350
|
+
/** One scripted decision in a {@link ApprovalEngine.simulate} run. */
|
|
351
|
+
type SimulatedDecision = {
|
|
352
|
+
approve: string;
|
|
353
|
+
level?: number;
|
|
354
|
+
comment?: string;
|
|
355
|
+
} | {
|
|
356
|
+
reject: string;
|
|
357
|
+
level?: number;
|
|
358
|
+
reason?: string;
|
|
359
|
+
};
|
|
360
|
+
/** What one scripted decision did. */
|
|
361
|
+
interface SimulationStep {
|
|
362
|
+
/** 1-based position in the script. */
|
|
363
|
+
step: number;
|
|
364
|
+
action: 'approve' | 'reject';
|
|
365
|
+
actorId: string;
|
|
366
|
+
/** The level the decision landed on, when it was accepted. */
|
|
367
|
+
level?: number;
|
|
368
|
+
/** Instance status after the decision. */
|
|
369
|
+
status: ApprovalStatus;
|
|
370
|
+
/** Why the decision was refused, when it was. The run stops at the first refusal. */
|
|
371
|
+
error?: string;
|
|
372
|
+
}
|
|
373
|
+
/** Outcome of a {@link ApprovalEngine.simulate} run. */
|
|
374
|
+
interface SimulationResult {
|
|
375
|
+
finalStatus: ApprovalStatus;
|
|
376
|
+
/** The chain the document would get, in order. */
|
|
377
|
+
levels: Array<{
|
|
378
|
+
level: number;
|
|
379
|
+
name: string;
|
|
380
|
+
status: LevelStatus;
|
|
381
|
+
approvers: string[];
|
|
382
|
+
}>;
|
|
383
|
+
transcript: SimulationStep[];
|
|
384
|
+
/** Levels never reached because the run ended first. */
|
|
385
|
+
unreachedLevels: number[];
|
|
386
|
+
/** True when the script ran out before the approval finished. */
|
|
387
|
+
incomplete: boolean;
|
|
388
|
+
}
|
|
350
389
|
interface BulkResult {
|
|
351
390
|
succeeded: ApprovalInstance[];
|
|
352
391
|
failed: Array<{
|
|
@@ -756,6 +795,33 @@ declare class ApprovalEngine {
|
|
|
756
795
|
* @param submittedBy - Submitter, used for approver resolution.
|
|
757
796
|
*/
|
|
758
797
|
explainChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<ChainExplanation>;
|
|
798
|
+
/**
|
|
799
|
+
* Run a document through a template against scripted decisions, without
|
|
800
|
+
* persisting anything.
|
|
801
|
+
*
|
|
802
|
+
* `explainChain()` says what the chain will be; this says what happens to it —
|
|
803
|
+
* "if the CFO rejects at level 3, does it go back to the submitter or die?" —
|
|
804
|
+
* which previously meant submitting a real approval into a real store and
|
|
805
|
+
* cleaning it up afterwards, or reasoning about the state machine by hand.
|
|
806
|
+
*
|
|
807
|
+
* The run executes against a private in-memory store seeded with a copy of
|
|
808
|
+
* the template, so the caller's storage is untouched and no events reach the
|
|
809
|
+
* caller's notification adapters. Custom resolvers and approver types are
|
|
810
|
+
* copied across, because a simulation that could not resolve the caller's own
|
|
811
|
+
* `dynamic` approvers would answer a different question from the one asked.
|
|
812
|
+
*
|
|
813
|
+
* A refused decision — wrong approver, wrong level, already acted — stops the
|
|
814
|
+
* run and is reported in the transcript rather than thrown: the refusal is
|
|
815
|
+
* usually the answer the caller was looking for.
|
|
816
|
+
*
|
|
817
|
+
* @param opts - Template, document data, submitter and the decisions to play.
|
|
818
|
+
*/
|
|
819
|
+
simulate(opts: {
|
|
820
|
+
templateName: string;
|
|
821
|
+
data: Record<string, unknown>;
|
|
822
|
+
submittedBy: string;
|
|
823
|
+
decisions?: SimulatedDecision[];
|
|
824
|
+
}): Promise<SimulationResult>;
|
|
759
825
|
canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
|
|
760
826
|
/** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
|
|
761
827
|
override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
@@ -1043,4 +1109,4 @@ declare class ApprovalEngine {
|
|
|
1043
1109
|
private runExternalAudit;
|
|
1044
1110
|
}
|
|
1045
1111
|
|
|
1046
|
-
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type
|
|
1112
|
+
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type SimulationResult as F, type SimulationStep as G, type HealthResult as H, type IdGeneratorFn as I, type SubmitOptions as J, type TemplateBundle as K, type TransferResult as L, businessHoursCalendar as M, defaultIdGenerator as N, type OrgProvider as O, type PreviewChainLevel as P, toComparableNumber as Q, type ReassignOptions as R, type SimulatedDecision as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, validateConditionExpression as X, weekendCalendar as Y, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ChainExplanation as j, type ConditionOperatorFn as k, type CycleTimeStats as l, type ExplainedLevel as m, type ExplainedRule as n, type ExplainedSkip as o, type IdempotencyKeyFn as p, type ImportResult as q, type OutOfOfficeProvider as r, type OverrideOptions as s, type PreviewResult as t, type ProvideInfoOptions as u, type PurgeResult as v, type RejectOptions as w, type RequestInfoOptions as x, type ResubmitOptions as y, type RetryPolicy as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-C0qeZgs4.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, b as AuditEntry } from './instance-BvOyT00S.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-BvOyT00S.cjs';
|
|
3
3
|
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DFKvGpOp.cjs';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { L as Logger } from './Logger-BplhlU7l.cjs';
|
|
@@ -347,6 +347,45 @@ interface ChainExplanation {
|
|
|
347
347
|
skipped: ExplainedSkip[];
|
|
348
348
|
rules: ExplainedRule[];
|
|
349
349
|
}
|
|
350
|
+
/** One scripted decision in a {@link ApprovalEngine.simulate} run. */
|
|
351
|
+
type SimulatedDecision = {
|
|
352
|
+
approve: string;
|
|
353
|
+
level?: number;
|
|
354
|
+
comment?: string;
|
|
355
|
+
} | {
|
|
356
|
+
reject: string;
|
|
357
|
+
level?: number;
|
|
358
|
+
reason?: string;
|
|
359
|
+
};
|
|
360
|
+
/** What one scripted decision did. */
|
|
361
|
+
interface SimulationStep {
|
|
362
|
+
/** 1-based position in the script. */
|
|
363
|
+
step: number;
|
|
364
|
+
action: 'approve' | 'reject';
|
|
365
|
+
actorId: string;
|
|
366
|
+
/** The level the decision landed on, when it was accepted. */
|
|
367
|
+
level?: number;
|
|
368
|
+
/** Instance status after the decision. */
|
|
369
|
+
status: ApprovalStatus;
|
|
370
|
+
/** Why the decision was refused, when it was. The run stops at the first refusal. */
|
|
371
|
+
error?: string;
|
|
372
|
+
}
|
|
373
|
+
/** Outcome of a {@link ApprovalEngine.simulate} run. */
|
|
374
|
+
interface SimulationResult {
|
|
375
|
+
finalStatus: ApprovalStatus;
|
|
376
|
+
/** The chain the document would get, in order. */
|
|
377
|
+
levels: Array<{
|
|
378
|
+
level: number;
|
|
379
|
+
name: string;
|
|
380
|
+
status: LevelStatus;
|
|
381
|
+
approvers: string[];
|
|
382
|
+
}>;
|
|
383
|
+
transcript: SimulationStep[];
|
|
384
|
+
/** Levels never reached because the run ended first. */
|
|
385
|
+
unreachedLevels: number[];
|
|
386
|
+
/** True when the script ran out before the approval finished. */
|
|
387
|
+
incomplete: boolean;
|
|
388
|
+
}
|
|
350
389
|
interface BulkResult {
|
|
351
390
|
succeeded: ApprovalInstance[];
|
|
352
391
|
failed: Array<{
|
|
@@ -756,6 +795,33 @@ declare class ApprovalEngine {
|
|
|
756
795
|
* @param submittedBy - Submitter, used for approver resolution.
|
|
757
796
|
*/
|
|
758
797
|
explainChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<ChainExplanation>;
|
|
798
|
+
/**
|
|
799
|
+
* Run a document through a template against scripted decisions, without
|
|
800
|
+
* persisting anything.
|
|
801
|
+
*
|
|
802
|
+
* `explainChain()` says what the chain will be; this says what happens to it —
|
|
803
|
+
* "if the CFO rejects at level 3, does it go back to the submitter or die?" —
|
|
804
|
+
* which previously meant submitting a real approval into a real store and
|
|
805
|
+
* cleaning it up afterwards, or reasoning about the state machine by hand.
|
|
806
|
+
*
|
|
807
|
+
* The run executes against a private in-memory store seeded with a copy of
|
|
808
|
+
* the template, so the caller's storage is untouched and no events reach the
|
|
809
|
+
* caller's notification adapters. Custom resolvers and approver types are
|
|
810
|
+
* copied across, because a simulation that could not resolve the caller's own
|
|
811
|
+
* `dynamic` approvers would answer a different question from the one asked.
|
|
812
|
+
*
|
|
813
|
+
* A refused decision — wrong approver, wrong level, already acted — stops the
|
|
814
|
+
* run and is reported in the transcript rather than thrown: the refusal is
|
|
815
|
+
* usually the answer the caller was looking for.
|
|
816
|
+
*
|
|
817
|
+
* @param opts - Template, document data, submitter and the decisions to play.
|
|
818
|
+
*/
|
|
819
|
+
simulate(opts: {
|
|
820
|
+
templateName: string;
|
|
821
|
+
data: Record<string, unknown>;
|
|
822
|
+
submittedBy: string;
|
|
823
|
+
decisions?: SimulatedDecision[];
|
|
824
|
+
}): Promise<SimulationResult>;
|
|
759
825
|
canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
|
|
760
826
|
/** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
|
|
761
827
|
override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
@@ -1043,4 +1109,4 @@ declare class ApprovalEngine {
|
|
|
1043
1109
|
private runExternalAudit;
|
|
1044
1110
|
}
|
|
1045
1111
|
|
|
1046
|
-
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type
|
|
1112
|
+
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type SimulationResult as F, type SimulationStep as G, type HealthResult as H, type IdGeneratorFn as I, type SubmitOptions as J, type TemplateBundle as K, type TransferResult as L, businessHoursCalendar as M, defaultIdGenerator as N, type OrgProvider as O, type PreviewChainLevel as P, toComparableNumber as Q, type ReassignOptions as R, type SimulatedDecision as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, validateConditionExpression as X, weekendCalendar as Y, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ChainExplanation as j, type ConditionOperatorFn as k, type CycleTimeStats as l, type ExplainedLevel as m, type ExplainedRule as n, type ExplainedSkip as o, type IdempotencyKeyFn as p, type ImportResult as q, type OutOfOfficeProvider as r, type OverrideOptions as s, type PreviewResult as t, type ProvideInfoOptions as u, type PurgeResult as v, type RejectOptions as w, type RequestInfoOptions as x, type ResubmitOptions as y, type RetryPolicy as z };
|