hierarchical-approval 2.8.0 → 3.0.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 +84 -0
- package/README.md +36 -0
- package/dist/{ApprovalEngine-D6BmYI8B.d.cts → ApprovalEngine-Bfs5BnMm.d.cts} +102 -2
- package/dist/{ApprovalEngine-DKn7KymS.d.ts → ApprovalEngine-CCOwhMrs.d.ts} +102 -2
- package/dist/index.cjs +511 -315
- 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 +511 -315
- package/dist/index.js.map +1 -1
- package/dist/nestjs.cjs +504 -104
- 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 +504 -104
- package/dist/nestjs.js.map +1 -1
- package/dist/testing.cjs +511 -315
- 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 +511 -315
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,90 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
_Nothing yet._
|
|
9
9
|
|
|
10
|
+
## [3.0.0] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
Three defects found by auditing the interactions between features added across
|
|
13
|
+
2.x, rather than by adding anything new.
|
|
14
|
+
|
|
15
|
+
### Fixed — `updateData()` built condition-added levels wrong
|
|
16
|
+
|
|
17
|
+
- **A level a condition added during `updateData()` silently lost its `group`,
|
|
18
|
+
`subWorkflow`, `escalationAfterHours` and reminder configuration.** Levels
|
|
19
|
+
were constructed in two places — `submit()` and `recomputeFutureChain()` —
|
|
20
|
+
and the second copy had never been updated as fields were added across 1.0.0
|
|
21
|
+
to 2.6.0. The consequences were quiet and serious:
|
|
22
|
+
|
|
23
|
+
- a condition-added **parallel group ran sequentially**, one branch at a time,
|
|
24
|
+
because the levels came back without their `group`;
|
|
25
|
+
- a condition-added **sub-workflow level lost its binding**, then failed with
|
|
26
|
+
"No approvers resolved for this level" when reached — leaving an approval
|
|
27
|
+
that could never advance;
|
|
28
|
+
- hour-based escalation and reminders simply never fired.
|
|
29
|
+
|
|
30
|
+
A level whose configuration was unchanged was carried over intact, so this
|
|
31
|
+
only bit templates whose conditions *add* levels — and it bit them silently.
|
|
32
|
+
|
|
33
|
+
Both paths now go through one `buildLevelInstance()`, so a field cannot be
|
|
34
|
+
added to a level in one place and forgotten in the other. This is the same
|
|
35
|
+
failure the 1.6.0 Postgres column list had, in a different file.
|
|
36
|
+
|
|
37
|
+
### BREAKING — a finished parent no longer leaves its sub-workflow children running
|
|
38
|
+
|
|
39
|
+
- **Cancelling or rejecting a parent left its child approval pending forever.**
|
|
40
|
+
The child kept notifying, kept escalating, and kept appearing in
|
|
41
|
+
`getWorkload()` — asking people to decide something whose outcome nobody would
|
|
42
|
+
ever read, since `propagateToParent()` ignores a parent that is no longer
|
|
43
|
+
pending. Children of a terminal parent are now cancelled, with the reason
|
|
44
|
+
naming the parent, and the child's own audit trail is left intact rather than
|
|
45
|
+
deleted.
|
|
46
|
+
|
|
47
|
+
**Behaviour change:** a child that used to stay open now reaches `cancelled`.
|
|
48
|
+
Anything counting open approvals, or waiting on a child whose parent has
|
|
49
|
+
ended, will see different numbers — correct ones.
|
|
50
|
+
|
|
51
|
+
- **`purgeInstances()` orphaned sub-workflow children.** It removed the parent
|
|
52
|
+
and left the child behind, holding a `parentInstanceId` pointing at a row that
|
|
53
|
+
no longer existed — unreachable, and invisible to a purge scoped by document
|
|
54
|
+
type, since a child usually has a different one. A purge now takes the whole
|
|
55
|
+
sub-workflow family together, parents first, deduplicated so a parent and
|
|
56
|
+
child sharing a terminal status are each reported once.
|
|
57
|
+
|
|
58
|
+
## [2.9.0] - 2026-09-04
|
|
59
|
+
|
|
60
|
+
### Added — `simulate()`
|
|
61
|
+
|
|
62
|
+
- **Dry-runs a document through a template against scripted decisions.**
|
|
63
|
+
`explainChain()` (2.7.0) says what the chain will be; nothing said what
|
|
64
|
+
happens *to* it. Answering "if the CFO rejects at level 3, does it go back to
|
|
65
|
+
the submitter or die?" meant submitting a real approval into real storage and
|
|
66
|
+
cleaning it up afterwards, or reasoning about the state machine by hand.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
await engine.simulate({
|
|
70
|
+
templateName: 'purchase-order',
|
|
71
|
+
data: { amount: 20000 },
|
|
72
|
+
submittedBy: 'buyer-1',
|
|
73
|
+
decisions: [{ approve: 'mgr-1' }, { reject: 'cfo', reason: 'over budget' }],
|
|
74
|
+
});
|
|
75
|
+
// { finalStatus, levels, transcript, unreachedLevels, incomplete }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **Nothing escapes the simulation.** It runs against a private in-memory store
|
|
79
|
+
seeded with a copy of the template, with the notification, audit and metrics
|
|
80
|
+
adapters and the authorization policy detached — so a dry run cannot page an
|
|
81
|
+
approver, write somebody's audit log, or move a counter.
|
|
82
|
+
|
|
83
|
+
- **Custom resolvers and approver types are copied across.** A simulation that
|
|
84
|
+
could not resolve the caller's own `dynamic` approvers would answer a
|
|
85
|
+
different question from the one asked.
|
|
86
|
+
|
|
87
|
+
- **A refused decision stops the run and is reported, not thrown** — wrong
|
|
88
|
+
approver, wrong level, already acted. The refusal is usually the answer the
|
|
89
|
+
caller was looking for, and throwing would discard the transcript that
|
|
90
|
+
explains how the run got there.
|
|
91
|
+
|
|
92
|
+
New exports: `SimulationResult`, `SimulationStep`, `SimulatedDecision`.
|
|
93
|
+
|
|
10
94
|
## [2.8.0] - 2026-09-04
|
|
11
95
|
|
|
12
96
|
### 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()`
|
|
@@ -660,6 +689,13 @@ Collapsing the non-approved outcomes into one rejection is deliberate: a parent
|
|
|
660
689
|
that treated a cancelled child as "carry on" would advance past a gate nobody
|
|
661
690
|
cleared.
|
|
662
691
|
|
|
692
|
+
**A finished parent ends its children.** If a parent is cancelled or rejected
|
|
693
|
+
while a child is still running, the child is cancelled too, with the reason
|
|
694
|
+
naming the parent — otherwise it would keep asking people to decide something
|
|
695
|
+
whose outcome nobody will read. A child that already finished is left alone.
|
|
696
|
+
`purgeInstances()` likewise removes a whole sub-workflow family together, so a
|
|
697
|
+
child is never orphaned behind a deleted parent.
|
|
698
|
+
|
|
663
699
|
Children link back via `parentInstanceId` and `parentLevel`, and the level
|
|
664
700
|
records `childInstanceId`. Nesting is allowed up to five levels deep, and
|
|
665
701
|
`validateTemplate()` rejects a template that would spawn itself. Emits
|
|
@@ -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>;
|
|
@@ -929,6 +995,19 @@ declare class ApprovalEngine {
|
|
|
929
995
|
* `businessHoursCalendar` to have hours skip evenings and weekends.
|
|
930
996
|
*/
|
|
931
997
|
private deadlineFromHours;
|
|
998
|
+
/**
|
|
999
|
+
* Build a level instance from its template config.
|
|
1000
|
+
*
|
|
1001
|
+
* The single place a level is constructed. It previously happened twice — in
|
|
1002
|
+
* `submit()` and again in `recomputeFutureChain()` — and the second copy was
|
|
1003
|
+
* missing `group`, `subWorkflowTemplate`, `escalationAfterHours` and the
|
|
1004
|
+
* reminder fields, so a level added by a condition during `updateData()` came
|
|
1005
|
+
* out silently different from the same level created at submit.
|
|
1006
|
+
*
|
|
1007
|
+
* @param cfg - The template's configuration for this level.
|
|
1008
|
+
* @param opts - `open` activates the level now, computing deadlines from `now`.
|
|
1009
|
+
*/
|
|
1010
|
+
private buildLevelInstance;
|
|
932
1011
|
/** Level deadline from whichever of days/hours the template configured. */
|
|
933
1012
|
private levelEscalationDue;
|
|
934
1013
|
/** First rung of a ladder, sorted by delay, or undefined when there is none. */
|
|
@@ -1032,6 +1111,27 @@ declare class ApprovalEngine {
|
|
|
1032
1111
|
* about them, so a slow child template would surface as a spurious conflict
|
|
1033
1112
|
* on the decision the user just made.
|
|
1034
1113
|
*/
|
|
1114
|
+
/**
|
|
1115
|
+
* An instance and every sub-workflow descendant beneath it, parents first.
|
|
1116
|
+
*
|
|
1117
|
+
* A child is only reachable through its parent's `childInstanceId`, so a
|
|
1118
|
+
* purge that removed the parent alone would strand the rest of the tree.
|
|
1119
|
+
* Depth is bounded by the same cap that limits spawning, and an already-seen
|
|
1120
|
+
* id is skipped so a corrupted link cannot loop.
|
|
1121
|
+
*/
|
|
1122
|
+
private collectSubWorkflowFamily;
|
|
1123
|
+
/**
|
|
1124
|
+
* Cancel sub-workflow children whose parent has finished.
|
|
1125
|
+
*
|
|
1126
|
+
* A child outlives its parent otherwise: it stays pending, keeps notifying
|
|
1127
|
+
* and escalating, and keeps appearing in {@link getWorkload} — asking people
|
|
1128
|
+
* to decide something whose outcome nobody will ever read, because
|
|
1129
|
+
* {@link propagateToParent} ignores a parent that is no longer pending.
|
|
1130
|
+
*
|
|
1131
|
+
* Cancelling rather than deleting keeps the child's own audit trail intact:
|
|
1132
|
+
* the people who were asked, and why the request stopped, stay on the record.
|
|
1133
|
+
*/
|
|
1134
|
+
private cancelOrphanedChildren;
|
|
1035
1135
|
private afterDecision;
|
|
1036
1136
|
private findNextLevel;
|
|
1037
1137
|
private findPreviousLevel;
|
|
@@ -1043,4 +1143,4 @@ declare class ApprovalEngine {
|
|
|
1043
1143
|
private runExternalAudit;
|
|
1044
1144
|
}
|
|
1045
1145
|
|
|
1046
|
-
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type
|
|
1146
|
+
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-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>;
|
|
@@ -929,6 +995,19 @@ declare class ApprovalEngine {
|
|
|
929
995
|
* `businessHoursCalendar` to have hours skip evenings and weekends.
|
|
930
996
|
*/
|
|
931
997
|
private deadlineFromHours;
|
|
998
|
+
/**
|
|
999
|
+
* Build a level instance from its template config.
|
|
1000
|
+
*
|
|
1001
|
+
* The single place a level is constructed. It previously happened twice — in
|
|
1002
|
+
* `submit()` and again in `recomputeFutureChain()` — and the second copy was
|
|
1003
|
+
* missing `group`, `subWorkflowTemplate`, `escalationAfterHours` and the
|
|
1004
|
+
* reminder fields, so a level added by a condition during `updateData()` came
|
|
1005
|
+
* out silently different from the same level created at submit.
|
|
1006
|
+
*
|
|
1007
|
+
* @param cfg - The template's configuration for this level.
|
|
1008
|
+
* @param opts - `open` activates the level now, computing deadlines from `now`.
|
|
1009
|
+
*/
|
|
1010
|
+
private buildLevelInstance;
|
|
932
1011
|
/** Level deadline from whichever of days/hours the template configured. */
|
|
933
1012
|
private levelEscalationDue;
|
|
934
1013
|
/** First rung of a ladder, sorted by delay, or undefined when there is none. */
|
|
@@ -1032,6 +1111,27 @@ declare class ApprovalEngine {
|
|
|
1032
1111
|
* about them, so a slow child template would surface as a spurious conflict
|
|
1033
1112
|
* on the decision the user just made.
|
|
1034
1113
|
*/
|
|
1114
|
+
/**
|
|
1115
|
+
* An instance and every sub-workflow descendant beneath it, parents first.
|
|
1116
|
+
*
|
|
1117
|
+
* A child is only reachable through its parent's `childInstanceId`, so a
|
|
1118
|
+
* purge that removed the parent alone would strand the rest of the tree.
|
|
1119
|
+
* Depth is bounded by the same cap that limits spawning, and an already-seen
|
|
1120
|
+
* id is skipped so a corrupted link cannot loop.
|
|
1121
|
+
*/
|
|
1122
|
+
private collectSubWorkflowFamily;
|
|
1123
|
+
/**
|
|
1124
|
+
* Cancel sub-workflow children whose parent has finished.
|
|
1125
|
+
*
|
|
1126
|
+
* A child outlives its parent otherwise: it stays pending, keeps notifying
|
|
1127
|
+
* and escalating, and keeps appearing in {@link getWorkload} — asking people
|
|
1128
|
+
* to decide something whose outcome nobody will ever read, because
|
|
1129
|
+
* {@link propagateToParent} ignores a parent that is no longer pending.
|
|
1130
|
+
*
|
|
1131
|
+
* Cancelling rather than deleting keeps the child's own audit trail intact:
|
|
1132
|
+
* the people who were asked, and why the request stopped, stay on the record.
|
|
1133
|
+
*/
|
|
1134
|
+
private cancelOrphanedChildren;
|
|
1035
1135
|
private afterDecision;
|
|
1036
1136
|
private findNextLevel;
|
|
1037
1137
|
private findPreviousLevel;
|
|
@@ -1043,4 +1143,4 @@ declare class ApprovalEngine {
|
|
|
1043
1143
|
private runExternalAudit;
|
|
1044
1144
|
}
|
|
1045
1145
|
|
|
1046
|
-
export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type
|
|
1146
|
+
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 };
|