hierarchical-approval 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -6
- package/README.md +1 -1
- package/dist/{ApprovalEngine-XgGBQLC_.d.cts → ApprovalEngine-D0_F5OPZ.d.cts} +35 -10
- package/dist/{ApprovalEngine-Dqe-rC6K.d.ts → ApprovalEngine-D4bXQrKD.d.ts} +35 -10
- package/dist/Clock-D8Qp39q_.d.cts +8 -0
- package/dist/Clock-D8Qp39q_.d.ts +8 -0
- package/dist/{IAuditAdapter-Dt8qjkQL.d.cts → IAuditAdapter-D9WE0t7A.d.cts} +2 -1
- package/dist/{IAuditAdapter-DkbyFFjb.d.ts → IAuditAdapter-FI_RF_XU.d.ts} +2 -1
- package/dist/{IAuthorizationPolicy-YJ_08Mof.d.cts → IAuthorizationPolicy-BKKw7jGN.d.ts} +6 -1
- package/dist/{IAuthorizationPolicy-eo6glbpE.d.ts → IAuthorizationPolicy-DDmjeYV-.d.cts} +6 -1
- package/dist/{IMetricsAdapter-DWq8IFaf.d.cts → IMetricsAdapter-WROxYSXd.d.cts} +1 -0
- package/dist/{IMetricsAdapter-DWq8IFaf.d.ts → IMetricsAdapter-WROxYSXd.d.ts} +1 -0
- package/dist/{INotificationAdapter-BTO9FSKy.d.ts → INotificationAdapter-CLROEfRd.d.ts} +26 -1
- package/dist/{INotificationAdapter-XbD69gss.d.cts → INotificationAdapter-J0wSPLIC.d.cts} +26 -1
- package/dist/{IOperationMiddleware-CX7lo4lp.d.ts → IOperationMiddleware-B32a2dwP.d.ts} +15 -2
- package/dist/{IOperationMiddleware-BBLk8MIz.d.cts → IOperationMiddleware-UUKqztxM.d.cts} +15 -2
- package/dist/{ISchedulerAdapter-DKv_QjVN.d.cts → ISchedulerAdapter-CLMdBA-5.d.cts} +1 -0
- package/dist/{ISchedulerAdapter-DKv_QjVN.d.ts → ISchedulerAdapter-CLMdBA-5.d.ts} +1 -0
- package/dist/{IStorageAdapter-CdXMggPK.d.ts → IStorageAdapter-DSKJ1onF.d.ts} +14 -1
- package/dist/{IStorageAdapter-Cjw5zDwQ.d.cts → IStorageAdapter-Dhrc4ES6.d.cts} +14 -1
- package/dist/{Logger-BplhlU7l.d.cts → Logger-hqFbBCtB.d.cts} +7 -0
- package/dist/{Logger-BplhlU7l.d.ts → Logger-hqFbBCtB.d.ts} +7 -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.map +1 -1
- 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.map +1 -1
- package/dist/index.cjs +51 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +51 -8
- package/dist/index.js.map +1 -1
- package/dist/{instance-uUQh5XW3.d.cts → instance-CUZcHWK2.d.cts} +28 -0
- package/dist/{instance-uUQh5XW3.d.ts → instance-CUZcHWK2.d.ts} +28 -0
- package/dist/nestjs.cjs +61 -7
- package/dist/nestjs.cjs.map +1 -1
- package/dist/nestjs.d.cts +22 -11
- package/dist/nestjs.d.ts +22 -11
- package/dist/nestjs.js +61 -7
- package/dist/nestjs.js.map +1 -1
- package/dist/plugins/audit.cjs.map +1 -1
- package/dist/plugins/audit.d.cts +3 -3
- package/dist/plugins/audit.d.ts +3 -3
- package/dist/plugins/audit.js.map +1 -1
- package/dist/plugins/metrics.cjs.map +1 -1
- package/dist/plugins/metrics.d.cts +2 -2
- package/dist/plugins/metrics.d.ts +2 -2
- package/dist/plugins/metrics.js.map +1 -1
- package/dist/plugins/notify.cjs.map +1 -1
- package/dist/plugins/notify.d.cts +4 -4
- package/dist/plugins/notify.d.ts +4 -4
- package/dist/plugins/notify.js.map +1 -1
- package/dist/plugins/resilience.cjs.map +1 -1
- package/dist/plugins/resilience.d.cts +5 -5
- package/dist/plugins/resilience.d.ts +5 -5
- package/dist/plugins/resilience.js.map +1 -1
- package/dist/plugins/scheduler.cjs.map +1 -1
- package/dist/plugins/scheduler.d.cts +3 -3
- package/dist/plugins/scheduler.d.ts +3 -3
- package/dist/plugins/scheduler.js.map +1 -1
- package/dist/plugins/tracing.cjs.map +1 -1
- package/dist/plugins/tracing.d.cts +4 -2
- package/dist/plugins/tracing.d.ts +4 -2
- package/dist/plugins/tracing.js.map +1 -1
- package/dist/plugins/webhook.cjs.map +1 -1
- package/dist/plugins/webhook.d.cts +4 -4
- package/dist/plugins/webhook.d.ts +4 -4
- package/dist/plugins/webhook.js.map +1 -1
- package/dist/testing.cjs +50 -7
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +11 -11
- package/dist/testing.d.ts +11 -11
- package/dist/testing.js +50 -7
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/Clock-3FnOczFJ.d.cts +0 -6
- package/dist/Clock-3FnOczFJ.d.ts +0 -6
|
@@ -13,18 +13,28 @@ type ApproverConfig = {
|
|
|
13
13
|
type: string;
|
|
14
14
|
[key: string]: unknown;
|
|
15
15
|
};
|
|
16
|
+
/** An {@link ApproverConfig} resolved down to a concrete user. */
|
|
16
17
|
interface ResolvedApprover {
|
|
17
18
|
userId: string;
|
|
19
|
+
/** The config entry this approver was resolved from. */
|
|
18
20
|
source: ApproverConfig;
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves a `{ type: 'dynamic' }` approver to a concrete user id, given the
|
|
24
|
+
* submitter and the document's data. Registered via
|
|
25
|
+
* {@link ApprovalEngine.registerResolver}.
|
|
26
|
+
*/
|
|
20
27
|
type ResolverFn = (submittedBy: string, data: Record<string, unknown>) => Promise<string> | string;
|
|
21
28
|
|
|
22
29
|
type ApprovalMode = 'all' | 'any' | 'majority' | 'quorum' | 'weighted';
|
|
23
30
|
/** Built-in operators. Use engine.registerConditionOperator() to add custom ones. */
|
|
24
31
|
type ConditionOperator = '>' | '<' | '>=' | '<=' | '==' | '!=' | 'in' | 'not_in' | (string & {});
|
|
32
|
+
/** A single test comparing a field on the evaluated document against a value. */
|
|
25
33
|
interface Condition {
|
|
34
|
+
/** Dot path into the document being evaluated, e.g. `"amount"` or `"header.total"`. */
|
|
26
35
|
field: string;
|
|
27
36
|
operator: ConditionOperator;
|
|
37
|
+
/** The value `field` is compared against; its shape depends on `operator`. */
|
|
28
38
|
value: unknown;
|
|
29
39
|
}
|
|
30
40
|
interface ApprovalLevelConfig {
|
|
@@ -145,8 +155,11 @@ interface ConditionRule {
|
|
|
145
155
|
addLevels?: ApprovalLevelConfig[];
|
|
146
156
|
skipLevels?: number[];
|
|
147
157
|
}
|
|
158
|
+
/** A single-rung escalation. See {@link EscalationStep} for a multi-rung ladder. */
|
|
148
159
|
interface EscalationConfig {
|
|
160
|
+
/** Delay from the level opening, in calendar or business days. */
|
|
149
161
|
afterDays: number;
|
|
162
|
+
/** Who is added to the level once `afterDays` elapses. */
|
|
150
163
|
escalateTo: ApproverConfig;
|
|
151
164
|
}
|
|
152
165
|
/** One rung of an escalation ladder. See {@link ApprovalTemplateConfig.escalationSteps}. */
|
|
@@ -215,19 +228,33 @@ interface ApprovalTemplate extends ApprovalTemplateConfig {
|
|
|
215
228
|
type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'cancelled' | 'expired';
|
|
216
229
|
type LevelStatus = 'waiting' | 'pending' | 'approved' | 'rejected' | 'skipped';
|
|
217
230
|
type AuditAction = 'submitted' | 'approved' | 'rejected' | 'delegated' | 'reassigned' | 'escalated' | 'cancelled' | 'level_advanced' | 'commented' | 'resubmitted' | 'overridden' | 'data_updated' | 'reminded' | 'info_requested' | 'info_provided' | 'attachment_added' | 'attachment_removed' | 'subworkflow_started' | 'subworkflow_completed' | 'expired';
|
|
231
|
+
/** One row of an {@link ApprovalInstance}'s audit log, recording a single state change. */
|
|
218
232
|
interface AuditEntry {
|
|
233
|
+
/** What happened. */
|
|
219
234
|
action: AuditAction;
|
|
235
|
+
/** Id of the user or system that performed the action. */
|
|
220
236
|
actorId: string;
|
|
237
|
+
/** Role the actor held at the time, when supplied via {@link AuditContext}. */
|
|
221
238
|
actorRole?: string;
|
|
239
|
+
/** IP address the action originated from, when supplied via {@link AuditContext}. */
|
|
222
240
|
actorIp?: string;
|
|
241
|
+
/** User agent the action originated from, when supplied via {@link AuditContext}. */
|
|
223
242
|
actorUserAgent?: string;
|
|
243
|
+
/** Level the action applied to, or the current level for instance-wide actions. */
|
|
224
244
|
level: number;
|
|
245
|
+
/** When the action happened. */
|
|
225
246
|
timestamp: Date;
|
|
247
|
+
/** Caller-supplied trace id for correlating this entry with external logs. */
|
|
226
248
|
traceId?: string;
|
|
249
|
+
/** Free-text comment attached to the action, if any. */
|
|
227
250
|
comment?: string;
|
|
251
|
+
/** Reason given for the action (e.g. a rejection or override reason), if any. */
|
|
228
252
|
reason?: string;
|
|
253
|
+
/** Id of the user this action routed to, for `'delegated'`/`'reassigned'`/`'escalated'` actions. */
|
|
229
254
|
delegateTo?: string;
|
|
255
|
+
/** State the action changed from, shaped differently per {@link action}. */
|
|
230
256
|
oldValue?: Record<string, unknown>;
|
|
257
|
+
/** State the action changed to, shaped differently per {@link action}. */
|
|
231
258
|
newValue?: Record<string, unknown>;
|
|
232
259
|
}
|
|
233
260
|
/** Context injected by the caller on each mutating operation (for SOX/SOC2 compliance). */
|
|
@@ -237,6 +264,7 @@ interface AuditContext {
|
|
|
237
264
|
actorUserAgent?: string;
|
|
238
265
|
traceId?: string;
|
|
239
266
|
}
|
|
267
|
+
/** Runtime state of one level within an {@link ApprovalInstance} — its resolved approvers and how far it has progressed. */
|
|
240
268
|
interface ApprovalLevelInstance {
|
|
241
269
|
level: number;
|
|
242
270
|
name: string;
|
|
@@ -13,18 +13,28 @@ type ApproverConfig = {
|
|
|
13
13
|
type: string;
|
|
14
14
|
[key: string]: unknown;
|
|
15
15
|
};
|
|
16
|
+
/** An {@link ApproverConfig} resolved down to a concrete user. */
|
|
16
17
|
interface ResolvedApprover {
|
|
17
18
|
userId: string;
|
|
19
|
+
/** The config entry this approver was resolved from. */
|
|
18
20
|
source: ApproverConfig;
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves a `{ type: 'dynamic' }` approver to a concrete user id, given the
|
|
24
|
+
* submitter and the document's data. Registered via
|
|
25
|
+
* {@link ApprovalEngine.registerResolver}.
|
|
26
|
+
*/
|
|
20
27
|
type ResolverFn = (submittedBy: string, data: Record<string, unknown>) => Promise<string> | string;
|
|
21
28
|
|
|
22
29
|
type ApprovalMode = 'all' | 'any' | 'majority' | 'quorum' | 'weighted';
|
|
23
30
|
/** Built-in operators. Use engine.registerConditionOperator() to add custom ones. */
|
|
24
31
|
type ConditionOperator = '>' | '<' | '>=' | '<=' | '==' | '!=' | 'in' | 'not_in' | (string & {});
|
|
32
|
+
/** A single test comparing a field on the evaluated document against a value. */
|
|
25
33
|
interface Condition {
|
|
34
|
+
/** Dot path into the document being evaluated, e.g. `"amount"` or `"header.total"`. */
|
|
26
35
|
field: string;
|
|
27
36
|
operator: ConditionOperator;
|
|
37
|
+
/** The value `field` is compared against; its shape depends on `operator`. */
|
|
28
38
|
value: unknown;
|
|
29
39
|
}
|
|
30
40
|
interface ApprovalLevelConfig {
|
|
@@ -145,8 +155,11 @@ interface ConditionRule {
|
|
|
145
155
|
addLevels?: ApprovalLevelConfig[];
|
|
146
156
|
skipLevels?: number[];
|
|
147
157
|
}
|
|
158
|
+
/** A single-rung escalation. See {@link EscalationStep} for a multi-rung ladder. */
|
|
148
159
|
interface EscalationConfig {
|
|
160
|
+
/** Delay from the level opening, in calendar or business days. */
|
|
149
161
|
afterDays: number;
|
|
162
|
+
/** Who is added to the level once `afterDays` elapses. */
|
|
150
163
|
escalateTo: ApproverConfig;
|
|
151
164
|
}
|
|
152
165
|
/** One rung of an escalation ladder. See {@link ApprovalTemplateConfig.escalationSteps}. */
|
|
@@ -215,19 +228,33 @@ interface ApprovalTemplate extends ApprovalTemplateConfig {
|
|
|
215
228
|
type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'cancelled' | 'expired';
|
|
216
229
|
type LevelStatus = 'waiting' | 'pending' | 'approved' | 'rejected' | 'skipped';
|
|
217
230
|
type AuditAction = 'submitted' | 'approved' | 'rejected' | 'delegated' | 'reassigned' | 'escalated' | 'cancelled' | 'level_advanced' | 'commented' | 'resubmitted' | 'overridden' | 'data_updated' | 'reminded' | 'info_requested' | 'info_provided' | 'attachment_added' | 'attachment_removed' | 'subworkflow_started' | 'subworkflow_completed' | 'expired';
|
|
231
|
+
/** One row of an {@link ApprovalInstance}'s audit log, recording a single state change. */
|
|
218
232
|
interface AuditEntry {
|
|
233
|
+
/** What happened. */
|
|
219
234
|
action: AuditAction;
|
|
235
|
+
/** Id of the user or system that performed the action. */
|
|
220
236
|
actorId: string;
|
|
237
|
+
/** Role the actor held at the time, when supplied via {@link AuditContext}. */
|
|
221
238
|
actorRole?: string;
|
|
239
|
+
/** IP address the action originated from, when supplied via {@link AuditContext}. */
|
|
222
240
|
actorIp?: string;
|
|
241
|
+
/** User agent the action originated from, when supplied via {@link AuditContext}. */
|
|
223
242
|
actorUserAgent?: string;
|
|
243
|
+
/** Level the action applied to, or the current level for instance-wide actions. */
|
|
224
244
|
level: number;
|
|
245
|
+
/** When the action happened. */
|
|
225
246
|
timestamp: Date;
|
|
247
|
+
/** Caller-supplied trace id for correlating this entry with external logs. */
|
|
226
248
|
traceId?: string;
|
|
249
|
+
/** Free-text comment attached to the action, if any. */
|
|
227
250
|
comment?: string;
|
|
251
|
+
/** Reason given for the action (e.g. a rejection or override reason), if any. */
|
|
228
252
|
reason?: string;
|
|
253
|
+
/** Id of the user this action routed to, for `'delegated'`/`'reassigned'`/`'escalated'` actions. */
|
|
229
254
|
delegateTo?: string;
|
|
255
|
+
/** State the action changed from, shaped differently per {@link action}. */
|
|
230
256
|
oldValue?: Record<string, unknown>;
|
|
257
|
+
/** State the action changed to, shaped differently per {@link action}. */
|
|
231
258
|
newValue?: Record<string, unknown>;
|
|
232
259
|
}
|
|
233
260
|
/** Context injected by the caller on each mutating operation (for SOX/SOC2 compliance). */
|
|
@@ -237,6 +264,7 @@ interface AuditContext {
|
|
|
237
264
|
actorUserAgent?: string;
|
|
238
265
|
traceId?: string;
|
|
239
266
|
}
|
|
267
|
+
/** Runtime state of one level within an {@link ApprovalInstance} — its resolved approvers and how far it has progressed. */
|
|
240
268
|
interface ApprovalLevelInstance {
|
|
241
269
|
level: number;
|
|
242
270
|
name: string;
|
package/dist/nestjs.cjs
CHANGED
|
@@ -557,6 +557,7 @@ var TemplateRegistry = class {
|
|
|
557
557
|
delete resolved.removeLevels;
|
|
558
558
|
return resolved;
|
|
559
559
|
}
|
|
560
|
+
/** Register a new template. Throws if a template with this name already exists for the tenant. */
|
|
560
561
|
async define(config) {
|
|
561
562
|
const existing = await this.adapter.getTemplate(this.tenantId, config.name);
|
|
562
563
|
if (existing) {
|
|
@@ -593,11 +594,13 @@ var TemplateRegistry = class {
|
|
|
593
594
|
await this.adapter.saveTemplate(updated);
|
|
594
595
|
return newId;
|
|
595
596
|
}
|
|
597
|
+
/** Fetch a template by name. Throws if no template with this name exists for the tenant. */
|
|
596
598
|
async get(name) {
|
|
597
599
|
const template = await this.adapter.getTemplate(this.tenantId, name);
|
|
598
600
|
if (!template) throw new ApprovalTemplateNotFoundError(name);
|
|
599
601
|
return template;
|
|
600
602
|
}
|
|
603
|
+
/** List every template registered for the tenant. */
|
|
601
604
|
async list() {
|
|
602
605
|
return this.adapter.listTemplates(this.tenantId);
|
|
603
606
|
}
|
|
@@ -637,6 +640,32 @@ var LevelResolver = class {
|
|
|
637
640
|
for (const [name, fn] of this.resolvers) target.register(name, fn);
|
|
638
641
|
for (const [name, fn] of this.approverTypes) target.registerApproverType(name, fn);
|
|
639
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Resolve a level's `approvers` config down to a deduplicated list of user
|
|
645
|
+
* ids, applying out-of-office cover last so a substitute never sees a stale
|
|
646
|
+
* approver id.
|
|
647
|
+
*
|
|
648
|
+
* @param approvers - The level's approver configs (`user`, `role`,
|
|
649
|
+
* `dynamic`, or a custom type registered via {@link registerApproverType}).
|
|
650
|
+
* @param submittedBy - Passed through to `dynamic` resolvers and custom
|
|
651
|
+
* approver-type functions as routing context.
|
|
652
|
+
* @param data - The instance's document data, passed through the same way.
|
|
653
|
+
* @param orgProvider - Required to resolve `role` approvers; omit only when
|
|
654
|
+
* the template uses no `role` approvers.
|
|
655
|
+
* @param outOfOffice - Optional cover lookup, consulted after every other
|
|
656
|
+
* approver type has resolved.
|
|
657
|
+
* @param at - The moment out-of-office cover is resolved for. Defaults to
|
|
658
|
+
* now.
|
|
659
|
+
* @param substitutions - Filled with `original -> stand-in` for each
|
|
660
|
+
* approver replaced by out-of-office cover, so the caller can carry
|
|
661
|
+
* anything keyed by the original id (such as a weighted level's vote
|
|
662
|
+
* weight) across to the substitute.
|
|
663
|
+
* @returns The resolved, deduplicated approver ids (post-substitution).
|
|
664
|
+
* @throws {ApprovalValidationError} If no approvers resolve at all, a
|
|
665
|
+
* `role` approver is configured without an `orgProvider`, a `dynamic`
|
|
666
|
+
* approver names a resolver that was never registered, or a custom
|
|
667
|
+
* approver type was never registered.
|
|
668
|
+
*/
|
|
640
669
|
async resolveApprovers(approvers, submittedBy, data, orgProvider, outOfOffice, at, substitutions) {
|
|
641
670
|
const resolved = [];
|
|
642
671
|
for (const approver of approvers) {
|
|
@@ -1048,7 +1077,7 @@ function evaluateConditions(conditions, data) {
|
|
|
1048
1077
|
function assertStatus(instance, expected) {
|
|
1049
1078
|
if (instance.status !== expected) {
|
|
1050
1079
|
throw new ApprovalError(
|
|
1051
|
-
`
|
|
1080
|
+
`Instance "${instance.id}": expected status "${expected}" but got "${instance.status}".`,
|
|
1052
1081
|
"INVALID_STATUS"
|
|
1053
1082
|
);
|
|
1054
1083
|
}
|
|
@@ -1056,7 +1085,7 @@ function assertStatus(instance, expected) {
|
|
|
1056
1085
|
function assertApproverOnLevel(level, approverId) {
|
|
1057
1086
|
if (!level.approverIds.includes(approverId)) {
|
|
1058
1087
|
throw new ApprovalForbiddenError(
|
|
1059
|
-
`User "${approverId}" is not an approver for level ${level.level}.`
|
|
1088
|
+
`User "${approverId}" is not an approver for level ${level.level} ("${level.name}").`
|
|
1060
1089
|
);
|
|
1061
1090
|
}
|
|
1062
1091
|
}
|
|
@@ -2100,7 +2129,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
2100
2129
|
await this.withOptimisticRetry(instanceId, async (instance) => {
|
|
2101
2130
|
assertStatus(instance, "pending");
|
|
2102
2131
|
if (opts.fromApprover === opts.toApprover) {
|
|
2103
|
-
throw new ApprovalForbiddenError(
|
|
2132
|
+
throw new ApprovalForbiddenError(`Cannot delegate to yourself: "${opts.fromApprover}".`);
|
|
2104
2133
|
}
|
|
2105
2134
|
const level = this.resolveActorLevel(instance, opts.fromApprover, opts.level);
|
|
2106
2135
|
this.inheritWeight(level, opts.fromApprover, opts.toApprover);
|
|
@@ -2183,7 +2212,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
2183
2212
|
return this.withOptimisticRetry(instanceId, async (instance) => {
|
|
2184
2213
|
assertStatus(instance, "pending");
|
|
2185
2214
|
if (opts.fromApprover === opts.toApprover) {
|
|
2186
|
-
throw new ApprovalForbiddenError(
|
|
2215
|
+
throw new ApprovalForbiddenError(
|
|
2216
|
+
`Cannot reassign an approver to themselves: "${opts.fromApprover}".`
|
|
2217
|
+
);
|
|
2187
2218
|
}
|
|
2188
2219
|
const level = this.resolveActorLevel(instance, opts.fromApprover, opts.level);
|
|
2189
2220
|
this.inheritWeight(level, opts.fromApprover, opts.toApprover);
|
|
@@ -3009,7 +3040,15 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3009
3040
|
}
|
|
3010
3041
|
const levelNums = new Set(allLevelCfgs.map((l) => l.level));
|
|
3011
3042
|
if (levelNums.size !== allLevelCfgs.length) {
|
|
3012
|
-
|
|
3043
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3044
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
3045
|
+
for (const l of allLevelCfgs) {
|
|
3046
|
+
if (seen.has(l.level)) duplicates.add(l.level);
|
|
3047
|
+
seen.add(l.level);
|
|
3048
|
+
}
|
|
3049
|
+
throw new ApprovalValidationError(
|
|
3050
|
+
`Duplicate level numbers after condition evaluation: ${[...duplicates].join(", ")}.`
|
|
3051
|
+
);
|
|
3013
3052
|
}
|
|
3014
3053
|
const now = this.clock.now();
|
|
3015
3054
|
const newInstanceId = this.generateId("inst");
|
|
@@ -3381,7 +3420,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3381
3420
|
);
|
|
3382
3421
|
}
|
|
3383
3422
|
if (opts.overriddenBy === instance.submittedBy) {
|
|
3384
|
-
throw new ApprovalForbiddenError(
|
|
3423
|
+
throw new ApprovalForbiddenError(
|
|
3424
|
+
`Override cannot be performed by the original submitter: "${opts.overriddenBy}".`
|
|
3425
|
+
);
|
|
3385
3426
|
}
|
|
3386
3427
|
await this.runAuthorizationPolicy({
|
|
3387
3428
|
operation: "override",
|
|
@@ -3875,7 +3916,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3875
3916
|
const dryRun = opts.dryRun ?? false;
|
|
3876
3917
|
const limit = opts.limit ?? DEFAULT_PURGE_LIMIT;
|
|
3877
3918
|
if (!(opts.olderThan instanceof Date) || Number.isNaN(opts.olderThan.getTime())) {
|
|
3878
|
-
throw new ApprovalValidationError(
|
|
3919
|
+
throw new ApprovalValidationError(
|
|
3920
|
+
`purgeInstances requires a valid olderThan date, got ${String(opts.olderThan)}.`
|
|
3921
|
+
);
|
|
3879
3922
|
}
|
|
3880
3923
|
const requested = opts.statuses ?? [...TERMINAL_STATUSES];
|
|
3881
3924
|
const nonTerminal = requested.filter((st) => !TERMINAL_STATUSES.has(st));
|
|
@@ -5114,6 +5157,11 @@ var HierarchicalApprovalModule = class _HierarchicalApprovalModule {
|
|
|
5114
5157
|
constructor(engine) {
|
|
5115
5158
|
this.engine = engine;
|
|
5116
5159
|
}
|
|
5160
|
+
/**
|
|
5161
|
+
* Build the module synchronously from a fixed set of engine options.
|
|
5162
|
+
* @param options - Engine configuration plus {@link HierarchicalApprovalModuleOptions.isGlobal}.
|
|
5163
|
+
* @returns A Nest `DynamicModule` exporting the engine under {@link APPROVAL_ENGINE}.
|
|
5164
|
+
*/
|
|
5117
5165
|
static forRoot(options) {
|
|
5118
5166
|
const { isGlobal, ...engineOptions } = options;
|
|
5119
5167
|
const engineProvider = {
|
|
@@ -5127,6 +5175,12 @@ var HierarchicalApprovalModule = class _HierarchicalApprovalModule {
|
|
|
5127
5175
|
exports: [APPROVAL_ENGINE]
|
|
5128
5176
|
};
|
|
5129
5177
|
}
|
|
5178
|
+
/**
|
|
5179
|
+
* Build the module with engine options resolved asynchronously, e.g. from a
|
|
5180
|
+
* `ConfigService` injected via {@link HierarchicalApprovalModuleAsyncOptions.inject}.
|
|
5181
|
+
* @param options - Async factory and its imports/injected providers.
|
|
5182
|
+
* @returns A Nest `DynamicModule` exporting the engine under {@link APPROVAL_ENGINE}.
|
|
5183
|
+
*/
|
|
5130
5184
|
static forRootAsync(options) {
|
|
5131
5185
|
const engineProvider = {
|
|
5132
5186
|
provide: APPROVAL_ENGINE,
|