hierarchical-approval 2.7.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 +80 -0
- package/README.md +56 -0
- package/dist/{ApprovalEngine-DkQcLaeT.d.ts → ApprovalEngine-B3PVWw05.d.ts} +85 -8
- package/dist/{ApprovalEngine-BH9GvzMI.d.cts → ApprovalEngine-DMM55f5R.d.cts} +85 -8
- package/dist/{IAuditAdapter-DSor54iY.d.ts → IAuditAdapter-B33n15YP.d.ts} +1 -1
- package/dist/{IAuditAdapter-2UU5edKG.d.cts → IAuditAdapter-DxdK4UVt.d.cts} +1 -1
- package/dist/{IAuthorizationPolicy-DPpiKzyh.d.cts → IAuthorizationPolicy-BzGVzkLJ.d.cts} +1 -1
- package/dist/{IAuthorizationPolicy-BOWg-3AH.d.ts → IAuthorizationPolicy-DOm6KCwI.d.ts} +1 -1
- package/dist/{INotificationAdapter-C6HPrzh6.d.cts → INotificationAdapter-DFKvGpOp.d.cts} +13 -2
- package/dist/{INotificationAdapter-BNGivLgU.d.ts → INotificationAdapter-DMvA4R-c.d.ts} +13 -2
- package/dist/{IOperationMiddleware-CxnPzpqL.d.cts → IOperationMiddleware-C8i06wq4.d.cts} +1 -1
- package/dist/{IOperationMiddleware-CXtg2wzF.d.ts → IOperationMiddleware-Nk8Q1Z8o.d.ts} +1 -1
- package/dist/{IStorageAdapter-yX9ERfQE.d.cts → IStorageAdapter-C0qeZgs4.d.cts} +1 -1
- package/dist/{IStorageAdapter-ChPT7ZDp.d.ts → IStorageAdapter-C2QxUwbX.d.ts} +1 -1
- package/dist/adapters/MemoryAdapter.cjs +4 -0
- package/dist/adapters/MemoryAdapter.cjs.map +1 -1
- package/dist/adapters/MemoryAdapter.d.cts +2 -2
- package/dist/adapters/MemoryAdapter.d.ts +2 -2
- package/dist/adapters/MemoryAdapter.js +4 -0
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgresAdapter.cjs +10 -4
- package/dist/adapters/PostgresAdapter.cjs.map +1 -1
- package/dist/adapters/PostgresAdapter.d.cts +2 -2
- package/dist/adapters/PostgresAdapter.d.ts +2 -2
- package/dist/adapters/PostgresAdapter.js +10 -4
- package/dist/adapters/PostgresAdapter.js.map +1 -1
- package/dist/index.cjs +442 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +442 -269
- package/dist/index.js.map +1 -1
- package/dist/{instance-weh2w7Ji.d.cts → instance-BvOyT00S.d.cts} +23 -1
- package/dist/{instance-weh2w7Ji.d.ts → instance-BvOyT00S.d.ts} +23 -1
- package/dist/nestjs.cjs +430 -57
- package/dist/nestjs.cjs.map +1 -1
- package/dist/nestjs.d.cts +7 -7
- package/dist/nestjs.d.ts +7 -7
- package/dist/nestjs.js +430 -57
- package/dist/nestjs.js.map +1 -1
- package/dist/plugins/audit.d.cts +2 -2
- package/dist/plugins/audit.d.ts +2 -2
- package/dist/plugins/notify.d.cts +2 -2
- package/dist/plugins/notify.d.ts +2 -2
- package/dist/plugins/resilience.d.cts +3 -3
- package/dist/plugins/resilience.d.ts +3 -3
- package/dist/plugins/tracing.d.cts +2 -2
- package/dist/plugins/tracing.d.ts +2 -2
- package/dist/plugins/webhook.d.cts +2 -2
- package/dist/plugins/webhook.d.ts +2 -2
- package/dist/testing.cjs +442 -269
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +7 -7
- package/dist/testing.d.ts +7 -7
- package/dist/testing.js +442 -269
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,86 @@ 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
|
+
|
|
46
|
+
## [2.8.0] - 2026-09-04
|
|
47
|
+
|
|
48
|
+
### Added — comment threads
|
|
49
|
+
|
|
50
|
+
- **Comments are addressable objects rather than audit entries alone.** There
|
|
51
|
+
was no id to point at, so no way to reply to a comment, and no way to tell
|
|
52
|
+
somebody a remark was aimed at them — discussion moved to email, where the
|
|
53
|
+
approval record could not see it.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
await engine.addComment(id, { actorId: 'mgr-1', comment: 'Need the quote.' });
|
|
57
|
+
const [question] = await engine.getComments(id);
|
|
58
|
+
await engine.addComment(id, {
|
|
59
|
+
actorId: 'buyer-1',
|
|
60
|
+
comment: 'Attached now.',
|
|
61
|
+
parentCommentId: question.id,
|
|
62
|
+
mentions: ['mgr-1'],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **`approval:commented` is addressed to the people the comment mentions**, not
|
|
67
|
+
to the current approvers. A remark aimed at somebody should reach them, and
|
|
68
|
+
one aimed at nobody should not page the whole level.
|
|
69
|
+
|
|
70
|
+
- **`getComments()` returns a flat list carrying `parentCommentId`**, oldest
|
|
71
|
+
first, rather than a nested tree: a UI that wants threads can build them, and
|
|
72
|
+
one that wants a chronological feed does not have to flatten a structure it
|
|
73
|
+
never wanted. Replying to a comment that is not on the approval is rejected.
|
|
74
|
+
|
|
75
|
+
Comments are still written to the audit trail — the record of who said what
|
|
76
|
+
belongs there. `addComment()` keeps its `Promise<void>` signature, so nothing
|
|
77
|
+
calling it needs to change.
|
|
78
|
+
|
|
79
|
+
New exports: `Comment`, `CommentedEvent`. `ApprovalInstance` gains `comments`.
|
|
80
|
+
|
|
81
|
+
### Fixed — MemoryAdapter left some timestamps as strings
|
|
82
|
+
|
|
83
|
+
- **`attachments[].addedAt`, `infoRequest.askedAt` and `levels[].reminderDueAt`
|
|
84
|
+
read back as strings, not `Date`s.** `MemoryAdapter` clones through JSON and
|
|
85
|
+
revives date fields by an explicit list, which these were missing from —
|
|
86
|
+
while `PostgresAdapter` revived them correctly. The two adapters therefore
|
|
87
|
+
disagreed, and any code trusting the declared `Date` type broke under one of
|
|
88
|
+
them only. Affected `addedAt` since 1.7.0 and `askedAt` since 1.5.0.
|
|
89
|
+
|
|
10
90
|
## [2.7.0] - 2026-09-04
|
|
11
91
|
|
|
12
92
|
### Added — `explainChain()`
|
package/README.md
CHANGED
|
@@ -468,6 +468,62 @@ The sweep is **not atomic**: it reports per-instance failures rather than rollin
|
|
|
468
468
|
back. A partial transfer is the useful outcome — the approvals that can move
|
|
469
469
|
should move, and the ones that cannot are named so a human can look at them.
|
|
470
470
|
|
|
471
|
+
### Comment threads
|
|
472
|
+
|
|
473
|
+
Comments are addressable objects, so approvers can reply to each other and name
|
|
474
|
+
the person a remark is aimed at:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
await engine.addComment(id, { actorId: 'mgr-1', comment: 'Need the quote.' });
|
|
478
|
+
const [question] = await engine.getComments(id);
|
|
479
|
+
|
|
480
|
+
await engine.addComment(id, {
|
|
481
|
+
actorId: 'buyer-1',
|
|
482
|
+
comment: 'Attached now, @mgr-1.',
|
|
483
|
+
parentCommentId: question.id,
|
|
484
|
+
mentions: ['mgr-1'],
|
|
485
|
+
});
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
`getComments()` returns a flat list carrying `parentCommentId`, oldest first — a
|
|
489
|
+
UI that wants threads can build them, and one that wants a chronological feed
|
|
490
|
+
(what most approval screens show) does not have to flatten a structure it never
|
|
491
|
+
wanted.
|
|
492
|
+
|
|
493
|
+
`approval:commented` is addressed to the people the comment **mentions**, not to
|
|
494
|
+
the current approvers: a remark aimed at somebody should reach them, and one
|
|
495
|
+
aimed at nobody should not page the whole level. Comments are still written to
|
|
496
|
+
the audit trail, since the record of who said what belongs there.
|
|
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
|
+
|
|
471
527
|
### Why does this chain look like this?
|
|
472
528
|
|
|
473
529
|
`previewApprovalChain()` answers *what* the chain will be. `explainChain()`
|
|
@@ -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-
|
|
2
|
-
import {
|
|
3
|
-
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-
|
|
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, f as ApprovalStatus, L as LevelStatus, b as AuditEntry } from './instance-BvOyT00S.js';
|
|
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';
|
|
6
6
|
import { C as Clock } from './Clock-3FnOczFJ.js';
|
|
7
|
-
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-
|
|
8
|
-
import { I as IAuditAdapter } from './IAuditAdapter-
|
|
7
|
+
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-Nk8Q1Z8o.js';
|
|
8
|
+
import { I as IAuditAdapter } from './IAuditAdapter-B33n15YP.js';
|
|
9
9
|
import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.js';
|
|
10
10
|
import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
|
|
11
|
-
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
11
|
+
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DOm6KCwI.js';
|
|
12
12
|
|
|
13
13
|
declare const SubmitOptionsSchema: z.ZodObject<{
|
|
14
14
|
templateName: z.ZodString;
|
|
@@ -66,6 +66,8 @@ declare const ResubmitOptionsSchema: z.ZodObject<{
|
|
|
66
66
|
declare const AddCommentOptionsSchema: z.ZodObject<{
|
|
67
67
|
actorId: z.ZodString;
|
|
68
68
|
comment: z.ZodString;
|
|
69
|
+
parentCommentId: z.ZodOptional<z.ZodString>;
|
|
70
|
+
mentions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
69
71
|
}, z.core.$strip>;
|
|
70
72
|
declare const OverrideOptionsSchema: z.ZodObject<{
|
|
71
73
|
overriddenBy: z.ZodString;
|
|
@@ -199,7 +201,7 @@ interface BusinessHoursCalendarOptions extends WeekendCalendarOptions {
|
|
|
199
201
|
*/
|
|
200
202
|
declare function businessHoursCalendar(options?: BusinessHoursCalendarOptions): Required<Pick<BusinessCalendar, 'addBusinessDays' | 'addBusinessHours'>>;
|
|
201
203
|
|
|
202
|
-
type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
|
|
204
|
+
type IdGeneratorPrefix = 'inst' | 'tpl' | 'att' | 'cmt';
|
|
203
205
|
type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
|
|
204
206
|
declare const defaultIdGenerator: IdGeneratorFn;
|
|
205
207
|
|
|
@@ -345,6 +347,45 @@ interface ChainExplanation {
|
|
|
345
347
|
skipped: ExplainedSkip[];
|
|
346
348
|
rules: ExplainedRule[];
|
|
347
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
|
+
}
|
|
348
389
|
interface BulkResult {
|
|
349
390
|
succeeded: ApprovalInstance[];
|
|
350
391
|
failed: Array<{
|
|
@@ -718,6 +759,15 @@ declare class ApprovalEngine {
|
|
|
718
759
|
removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
719
760
|
/** Add a comment to an instance without approving or rejecting. */
|
|
720
761
|
addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
|
|
762
|
+
/**
|
|
763
|
+
* Comments on an approval, oldest first.
|
|
764
|
+
*
|
|
765
|
+
* Returns a flat list carrying `parentCommentId`, rather than a nested tree:
|
|
766
|
+
* a UI that wants threads can build them, and one that wants a chronological
|
|
767
|
+
* feed — which is what most approval screens actually show — does not have to
|
|
768
|
+
* flatten a structure it never wanted.
|
|
769
|
+
*/
|
|
770
|
+
getComments(instanceId: string): Promise<Comment[]>;
|
|
721
771
|
/** Resubmit a rejected instance, creating a new linked instance from level 1. */
|
|
722
772
|
resubmit(instanceId: string, raw: ResubmitOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
723
773
|
/** Preview the resolved approval chain for a template and document data, without creating an instance. */
|
|
@@ -745,6 +795,33 @@ declare class ApprovalEngine {
|
|
|
745
795
|
* @param submittedBy - Submitter, used for approver resolution.
|
|
746
796
|
*/
|
|
747
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>;
|
|
748
825
|
canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
|
|
749
826
|
/** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
|
|
750
827
|
override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
@@ -1032,4 +1109,4 @@ declare class ApprovalEngine {
|
|
|
1032
1109
|
private runExternalAudit;
|
|
1033
1110
|
}
|
|
1034
1111
|
|
|
1035
|
-
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,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-
|
|
2
|
-
import {
|
|
3
|
-
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-
|
|
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, f as ApprovalStatus, L as LevelStatus, b as AuditEntry } from './instance-BvOyT00S.cjs';
|
|
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';
|
|
6
6
|
import { C as Clock } from './Clock-3FnOczFJ.cjs';
|
|
7
|
-
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-
|
|
8
|
-
import { I as IAuditAdapter } from './IAuditAdapter-
|
|
7
|
+
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-C8i06wq4.cjs';
|
|
8
|
+
import { I as IAuditAdapter } from './IAuditAdapter-DxdK4UVt.cjs';
|
|
9
9
|
import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.cjs';
|
|
10
10
|
import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
|
|
11
|
-
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
11
|
+
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-BzGVzkLJ.cjs';
|
|
12
12
|
|
|
13
13
|
declare const SubmitOptionsSchema: z.ZodObject<{
|
|
14
14
|
templateName: z.ZodString;
|
|
@@ -66,6 +66,8 @@ declare const ResubmitOptionsSchema: z.ZodObject<{
|
|
|
66
66
|
declare const AddCommentOptionsSchema: z.ZodObject<{
|
|
67
67
|
actorId: z.ZodString;
|
|
68
68
|
comment: z.ZodString;
|
|
69
|
+
parentCommentId: z.ZodOptional<z.ZodString>;
|
|
70
|
+
mentions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
69
71
|
}, z.core.$strip>;
|
|
70
72
|
declare const OverrideOptionsSchema: z.ZodObject<{
|
|
71
73
|
overriddenBy: z.ZodString;
|
|
@@ -199,7 +201,7 @@ interface BusinessHoursCalendarOptions extends WeekendCalendarOptions {
|
|
|
199
201
|
*/
|
|
200
202
|
declare function businessHoursCalendar(options?: BusinessHoursCalendarOptions): Required<Pick<BusinessCalendar, 'addBusinessDays' | 'addBusinessHours'>>;
|
|
201
203
|
|
|
202
|
-
type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
|
|
204
|
+
type IdGeneratorPrefix = 'inst' | 'tpl' | 'att' | 'cmt';
|
|
203
205
|
type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
|
|
204
206
|
declare const defaultIdGenerator: IdGeneratorFn;
|
|
205
207
|
|
|
@@ -345,6 +347,45 @@ interface ChainExplanation {
|
|
|
345
347
|
skipped: ExplainedSkip[];
|
|
346
348
|
rules: ExplainedRule[];
|
|
347
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
|
+
}
|
|
348
389
|
interface BulkResult {
|
|
349
390
|
succeeded: ApprovalInstance[];
|
|
350
391
|
failed: Array<{
|
|
@@ -718,6 +759,15 @@ declare class ApprovalEngine {
|
|
|
718
759
|
removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
719
760
|
/** Add a comment to an instance without approving or rejecting. */
|
|
720
761
|
addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
|
|
762
|
+
/**
|
|
763
|
+
* Comments on an approval, oldest first.
|
|
764
|
+
*
|
|
765
|
+
* Returns a flat list carrying `parentCommentId`, rather than a nested tree:
|
|
766
|
+
* a UI that wants threads can build them, and one that wants a chronological
|
|
767
|
+
* feed — which is what most approval screens actually show — does not have to
|
|
768
|
+
* flatten a structure it never wanted.
|
|
769
|
+
*/
|
|
770
|
+
getComments(instanceId: string): Promise<Comment[]>;
|
|
721
771
|
/** Resubmit a rejected instance, creating a new linked instance from level 1. */
|
|
722
772
|
resubmit(instanceId: string, raw: ResubmitOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
723
773
|
/** Preview the resolved approval chain for a template and document data, without creating an instance. */
|
|
@@ -745,6 +795,33 @@ declare class ApprovalEngine {
|
|
|
745
795
|
* @param submittedBy - Submitter, used for approver resolution.
|
|
746
796
|
*/
|
|
747
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>;
|
|
748
825
|
canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
|
|
749
826
|
/** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
|
|
750
827
|
override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
|
|
@@ -1032,4 +1109,4 @@ declare class ApprovalEngine {
|
|
|
1032
1109
|
private runExternalAudit;
|
|
1033
1110
|
}
|
|
1034
1111
|
|
|
1035
|
-
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,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BvOyT00S.cjs';
|
|
2
2
|
|
|
3
3
|
interface AuthorizationContext {
|
|
4
4
|
operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BvOyT00S.js';
|
|
2
2
|
|
|
3
3
|
interface AuthorizationContext {
|
|
4
4
|
operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, b as AuditEntry } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, b as AuditEntry } from './instance-BvOyT00S.cjs';
|
|
2
2
|
|
|
3
3
|
interface ApprovalEvent {
|
|
4
4
|
instanceId: string;
|
|
@@ -120,6 +120,16 @@ interface SubWorkflowEvent extends ApprovalEvent {
|
|
|
120
120
|
/** Set on completion: the outcome the child returned. */
|
|
121
121
|
outcome?: 'approved' | 'rejected' | 'cancelled' | 'expired';
|
|
122
122
|
}
|
|
123
|
+
/** Emitted when a comment is posted. Recipients are the users it mentions. */
|
|
124
|
+
interface CommentedEvent extends ApprovalEvent {
|
|
125
|
+
commentId: string;
|
|
126
|
+
authorId: string;
|
|
127
|
+
body: string;
|
|
128
|
+
level?: number;
|
|
129
|
+
parentCommentId?: string;
|
|
130
|
+
/** Users named in the comment. */
|
|
131
|
+
recipients: string[];
|
|
132
|
+
}
|
|
123
133
|
interface ApprovalEventMap {
|
|
124
134
|
'approval:submitted': SubmittedEvent;
|
|
125
135
|
'approval:approved': ApprovedEvent;
|
|
@@ -142,6 +152,7 @@ interface ApprovalEventMap {
|
|
|
142
152
|
'approval:attachment_removed': AttachmentEvent;
|
|
143
153
|
'approval:subworkflow_started': SubWorkflowEvent;
|
|
144
154
|
'approval:subworkflow_completed': SubWorkflowEvent;
|
|
155
|
+
'approval:commented': CommentedEvent;
|
|
145
156
|
}
|
|
146
157
|
type ApprovalEventName = keyof ApprovalEventMap;
|
|
147
158
|
interface HistoryEntry extends AuditEntry {
|
|
@@ -165,4 +176,4 @@ interface INotificationAdapter {
|
|
|
165
176
|
notify(event: NotificationEvent): Promise<void>;
|
|
166
177
|
}
|
|
167
178
|
|
|
168
|
-
export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d,
|
|
179
|
+
export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, CommentedEvent as e, DelegatedEvent as f, ExpiredEvent as g, InfoProvidedEvent as h, InfoRequestedEvent as i, RejectedEvent as j, ReminderEvent as k, ResubmittedEvent as l, SubWorkflowEvent as m, SubmittedEvent as n };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, b as AuditEntry } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, b as AuditEntry } from './instance-BvOyT00S.js';
|
|
2
2
|
|
|
3
3
|
interface ApprovalEvent {
|
|
4
4
|
instanceId: string;
|
|
@@ -120,6 +120,16 @@ interface SubWorkflowEvent extends ApprovalEvent {
|
|
|
120
120
|
/** Set on completion: the outcome the child returned. */
|
|
121
121
|
outcome?: 'approved' | 'rejected' | 'cancelled' | 'expired';
|
|
122
122
|
}
|
|
123
|
+
/** Emitted when a comment is posted. Recipients are the users it mentions. */
|
|
124
|
+
interface CommentedEvent extends ApprovalEvent {
|
|
125
|
+
commentId: string;
|
|
126
|
+
authorId: string;
|
|
127
|
+
body: string;
|
|
128
|
+
level?: number;
|
|
129
|
+
parentCommentId?: string;
|
|
130
|
+
/** Users named in the comment. */
|
|
131
|
+
recipients: string[];
|
|
132
|
+
}
|
|
123
133
|
interface ApprovalEventMap {
|
|
124
134
|
'approval:submitted': SubmittedEvent;
|
|
125
135
|
'approval:approved': ApprovedEvent;
|
|
@@ -142,6 +152,7 @@ interface ApprovalEventMap {
|
|
|
142
152
|
'approval:attachment_removed': AttachmentEvent;
|
|
143
153
|
'approval:subworkflow_started': SubWorkflowEvent;
|
|
144
154
|
'approval:subworkflow_completed': SubWorkflowEvent;
|
|
155
|
+
'approval:commented': CommentedEvent;
|
|
145
156
|
}
|
|
146
157
|
type ApprovalEventName = keyof ApprovalEventMap;
|
|
147
158
|
interface HistoryEntry extends AuditEntry {
|
|
@@ -165,4 +176,4 @@ interface INotificationAdapter {
|
|
|
165
176
|
notify(event: NotificationEvent): Promise<void>;
|
|
166
177
|
}
|
|
167
178
|
|
|
168
|
-
export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d,
|
|
179
|
+
export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, CommentedEvent as e, DelegatedEvent as f, ExpiredEvent as g, InfoProvidedEvent as h, InfoRequestedEvent as i, RejectedEvent as j, ReminderEvent as k, ResubmittedEvent as l, SubWorkflowEvent as m, SubmittedEvent as n };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-
|
|
1
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BvOyT00S.cjs';
|
|
2
2
|
|
|
3
3
|
interface PaginationOpts {
|
|
4
4
|
limit: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-
|
|
1
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BvOyT00S.js';
|
|
2
2
|
|
|
3
3
|
interface PaginationOpts {
|
|
4
4
|
limit: number;
|
|
@@ -44,10 +44,14 @@ function reviveDates(instance) {
|
|
|
44
44
|
slaDeadlineAt: instance.slaDeadlineAt ? new Date(instance.slaDeadlineAt) : void 0,
|
|
45
45
|
slaBreachedAt: instance.slaBreachedAt ? new Date(instance.slaBreachedAt) : void 0,
|
|
46
46
|
auditLog: instance.auditLog.map((e) => ({ ...e, timestamp: new Date(e.timestamp) })),
|
|
47
|
+
infoRequest: instance.infoRequest ? { ...instance.infoRequest, askedAt: new Date(instance.infoRequest.askedAt) } : void 0,
|
|
48
|
+
attachments: instance.attachments ? instance.attachments.map((a) => ({ ...a, addedAt: new Date(a.addedAt) })) : void 0,
|
|
49
|
+
comments: instance.comments ? instance.comments.map((c) => ({ ...c, createdAt: new Date(c.createdAt) })) : void 0,
|
|
47
50
|
levels: instance.levels.map((l) => {
|
|
48
51
|
const level = { ...l };
|
|
49
52
|
if (l.escalationDueAt) level.escalationDueAt = new Date(l.escalationDueAt);
|
|
50
53
|
if (l.delegatedUntil) level.delegatedUntil = new Date(l.delegatedUntil);
|
|
54
|
+
if (l.reminderDueAt) level.reminderDueAt = new Date(l.reminderDueAt);
|
|
51
55
|
return level;
|
|
52
56
|
})
|
|
53
57
|
};
|