hierarchical-approval 2.4.0 → 2.6.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 +71 -0
- package/README.md +54 -0
- package/dist/{ApprovalEngine-C1dUPnLM.d.cts → ApprovalEngine-BWrblNQ0.d.cts} +81 -7
- package/dist/{ApprovalEngine-Ci9urTDI.d.ts → ApprovalEngine-D4DdSx-M.d.ts} +81 -7
- package/dist/{IAuditAdapter-BHVKMMv6.d.cts → IAuditAdapter-2UU5edKG.d.cts} +1 -1
- package/dist/{IAuditAdapter-C4OASS6u.d.ts → IAuditAdapter-DSor54iY.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-BA_m2Dg9.d.ts → IAuthorizationPolicy-BOWg-3AH.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-CtLkwQ8I.d.cts → IAuthorizationPolicy-DPpiKzyh.d.cts} +1 -1
- package/dist/{INotificationAdapter-BdgfUrIn.d.ts → INotificationAdapter-BNGivLgU.d.ts} +1 -1
- package/dist/{INotificationAdapter-JJAiMyMd.d.cts → INotificationAdapter-C6HPrzh6.d.cts} +1 -1
- package/dist/{IOperationMiddleware-BGYrAqep.d.ts → IOperationMiddleware-CXtg2wzF.d.ts} +1 -1
- package/dist/{IOperationMiddleware-D0ksiFM3.d.cts → IOperationMiddleware-CxnPzpqL.d.cts} +1 -1
- package/dist/{IStorageAdapter-BU3sau5W.d.ts → IStorageAdapter-ChPT7ZDp.d.ts} +1 -1
- package/dist/{IStorageAdapter-DdHO4Rf1.d.cts → IStorageAdapter-yX9ERfQE.d.cts} +1 -1
- package/dist/adapters/MemoryAdapter.d.cts +2 -2
- package/dist/adapters/MemoryAdapter.d.ts +2 -2
- package/dist/adapters/PostgresAdapter.d.cts +2 -2
- package/dist/adapters/PostgresAdapter.d.ts +2 -2
- package/dist/index.cjs +179 -11
- 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 +179 -12
- package/dist/index.js.map +1 -1
- package/dist/{instance-DUJY_Axf.d.cts → instance-weh2w7Ji.d.cts} +39 -1
- package/dist/{instance-DUJY_Axf.d.ts → instance-weh2w7Ji.d.ts} +39 -1
- package/dist/nestjs.cjs +111 -11
- 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 +111 -11
- 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 +111 -11
- 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 +111 -11
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/nestjs.d.cts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { OnModuleDestroy, DynamicModule, ModuleMetadata, InjectionToken, OptionalFactoryDependency, Inject } from '@nestjs/common';
|
|
2
|
-
import { a as ApprovalEngine, b as ApprovalEngineOptions } from './ApprovalEngine-
|
|
3
|
-
import './IStorageAdapter-
|
|
4
|
-
import './instance-
|
|
5
|
-
import './INotificationAdapter-
|
|
2
|
+
import { a as ApprovalEngine, b as ApprovalEngineOptions } from './ApprovalEngine-BWrblNQ0.cjs';
|
|
3
|
+
import './IStorageAdapter-yX9ERfQE.cjs';
|
|
4
|
+
import './instance-weh2w7Ji.cjs';
|
|
5
|
+
import './INotificationAdapter-C6HPrzh6.cjs';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import './Logger-BplhlU7l.cjs';
|
|
8
8
|
import './Clock-3FnOczFJ.cjs';
|
|
9
|
-
import './IOperationMiddleware-
|
|
10
|
-
import './IAuditAdapter-
|
|
9
|
+
import './IOperationMiddleware-CxnPzpqL.cjs';
|
|
10
|
+
import './IAuditAdapter-2UU5edKG.cjs';
|
|
11
11
|
import './IMetricsAdapter-DWq8IFaf.cjs';
|
|
12
12
|
import './ISchedulerAdapter-DKv_QjVN.cjs';
|
|
13
|
-
import './IAuthorizationPolicy-
|
|
13
|
+
import './IAuthorizationPolicy-DPpiKzyh.cjs';
|
|
14
14
|
|
|
15
15
|
/** Options for {@link HierarchicalApprovalModule.forRoot}. */
|
|
16
16
|
interface HierarchicalApprovalModuleOptions extends ApprovalEngineOptions {
|
package/dist/nestjs.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { OnModuleDestroy, DynamicModule, ModuleMetadata, InjectionToken, OptionalFactoryDependency, Inject } from '@nestjs/common';
|
|
2
|
-
import { a as ApprovalEngine, b as ApprovalEngineOptions } from './ApprovalEngine-
|
|
3
|
-
import './IStorageAdapter-
|
|
4
|
-
import './instance-
|
|
5
|
-
import './INotificationAdapter-
|
|
2
|
+
import { a as ApprovalEngine, b as ApprovalEngineOptions } from './ApprovalEngine-D4DdSx-M.js';
|
|
3
|
+
import './IStorageAdapter-ChPT7ZDp.js';
|
|
4
|
+
import './instance-weh2w7Ji.js';
|
|
5
|
+
import './INotificationAdapter-BNGivLgU.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import './Logger-BplhlU7l.js';
|
|
8
8
|
import './Clock-3FnOczFJ.js';
|
|
9
|
-
import './IOperationMiddleware-
|
|
10
|
-
import './IAuditAdapter-
|
|
9
|
+
import './IOperationMiddleware-CXtg2wzF.js';
|
|
10
|
+
import './IAuditAdapter-DSor54iY.js';
|
|
11
11
|
import './IMetricsAdapter-DWq8IFaf.js';
|
|
12
12
|
import './ISchedulerAdapter-DKv_QjVN.js';
|
|
13
|
-
import './IAuthorizationPolicy-
|
|
13
|
+
import './IAuthorizationPolicy-BOWg-3AH.js';
|
|
14
14
|
|
|
15
15
|
/** Options for {@link HierarchicalApprovalModule.forRoot}. */
|
|
16
16
|
interface HierarchicalApprovalModuleOptions extends ApprovalEngineOptions {
|
package/dist/nestjs.js
CHANGED
|
@@ -1066,6 +1066,18 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1066
1066
|
message: `Level ${l.level} sets reminderEveryDays without reminderAfterDays, so no reminder would ever be sent.`
|
|
1067
1067
|
});
|
|
1068
1068
|
}
|
|
1069
|
+
if (l.escalationAfterHours !== void 0 && l.escalationAfterHours <= 0) {
|
|
1070
|
+
errors.push({
|
|
1071
|
+
field: `levels[${i}].escalationAfterHours`,
|
|
1072
|
+
message: `Level ${l.level} escalationAfterHours must be a positive number.`
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
if (l.escalationAfterDays !== void 0 && l.escalationAfterHours !== void 0) {
|
|
1076
|
+
errors.push({
|
|
1077
|
+
field: `levels[${i}].escalationAfterHours`,
|
|
1078
|
+
message: `Level ${l.level} sets both escalationAfterDays and escalationAfterHours; pick one so the deadline is unambiguous.`
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1069
1081
|
if (l.escalationAfterDays !== void 0 && l.escalationAfterDays <= 0) {
|
|
1070
1082
|
errors.push({
|
|
1071
1083
|
field: `levels[${i}].escalationAfterDays`,
|
|
@@ -1130,6 +1142,18 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1130
1142
|
}
|
|
1131
1143
|
});
|
|
1132
1144
|
}
|
|
1145
|
+
if (config.slaDeadlineDays !== void 0 && config.slaDeadlineHours !== void 0) {
|
|
1146
|
+
errors.push({
|
|
1147
|
+
field: "slaDeadlineHours",
|
|
1148
|
+
message: "Template sets both slaDeadlineDays and slaDeadlineHours; pick one so the SLA is unambiguous."
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
if (config.slaDeadlineHours !== void 0 && config.slaDeadlineHours <= 0) {
|
|
1152
|
+
errors.push({
|
|
1153
|
+
field: "slaDeadlineHours",
|
|
1154
|
+
message: "slaDeadlineHours must be a positive number."
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1133
1157
|
if (config.conditions) {
|
|
1134
1158
|
config.conditions.forEach((rule, ruleIdx) => {
|
|
1135
1159
|
errors.push(...validateConditionExpression(rule.when, `conditions[${ruleIdx}].when`));
|
|
@@ -1243,7 +1267,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1243
1267
|
threshold: cfg.threshold,
|
|
1244
1268
|
weights: cfg.weights,
|
|
1245
1269
|
escalationAfterDays: cfg.escalationAfterDays,
|
|
1246
|
-
|
|
1270
|
+
escalationAfterHours: cfg.escalationAfterHours,
|
|
1271
|
+
escalationStep: 0,
|
|
1272
|
+
escalationDueAt: inFirstGroup ? this.levelEscalationDue(now, cfg, this.firstRungOf(template.escalationSteps)) : void 0,
|
|
1247
1273
|
subWorkflowTemplate: cfg.subWorkflow?.templateName,
|
|
1248
1274
|
reminderAfterDays: cfg.reminderAfterDays,
|
|
1249
1275
|
reminderEveryDays: cfg.reminderEveryDays,
|
|
@@ -1273,7 +1299,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1273
1299
|
timestamp: now,
|
|
1274
1300
|
...auditCtx
|
|
1275
1301
|
};
|
|
1276
|
-
const slaDeadlineAt = template.slaDeadlineDays ? this.deadlineFrom(now, template.slaDeadlineDays) : void 0;
|
|
1302
|
+
const slaDeadlineAt = template.slaDeadlineHours ? this.deadlineFromHours(now, template.slaDeadlineHours) : template.slaDeadlineDays ? this.deadlineFrom(now, template.slaDeadlineDays) : void 0;
|
|
1277
1303
|
const instance = {
|
|
1278
1304
|
id: instanceId,
|
|
1279
1305
|
tenantId: this.tenantId,
|
|
@@ -1300,7 +1326,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1300
1326
|
slaDeadlineAt,
|
|
1301
1327
|
templateSnapshot: {
|
|
1302
1328
|
escalation: template.escalation,
|
|
1329
|
+
escalationSteps: template.escalationSteps,
|
|
1303
1330
|
slaDeadlineDays: template.slaDeadlineDays,
|
|
1331
|
+
slaDeadlineHours: template.slaDeadlineHours,
|
|
1304
1332
|
allowOverride: template.allowOverride
|
|
1305
1333
|
}
|
|
1306
1334
|
};
|
|
@@ -2572,7 +2600,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
2572
2600
|
reason: opts.reason,
|
|
2573
2601
|
...auditCtx
|
|
2574
2602
|
};
|
|
2575
|
-
const slaDeadlineAt = template.slaDeadlineDays ? this.deadlineFrom(now, template.slaDeadlineDays) : void 0;
|
|
2603
|
+
const slaDeadlineAt = template.slaDeadlineHours ? this.deadlineFromHours(now, template.slaDeadlineHours) : template.slaDeadlineDays ? this.deadlineFrom(now, template.slaDeadlineDays) : void 0;
|
|
2576
2604
|
const newInstance = {
|
|
2577
2605
|
id: newInstanceId,
|
|
2578
2606
|
tenantId: this.tenantId,
|
|
@@ -2594,7 +2622,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
2594
2622
|
slaDeadlineAt,
|
|
2595
2623
|
templateSnapshot: {
|
|
2596
2624
|
escalation: template.escalation,
|
|
2625
|
+
escalationSteps: template.escalationSteps,
|
|
2597
2626
|
slaDeadlineDays: template.slaDeadlineDays,
|
|
2627
|
+
slaDeadlineHours: template.slaDeadlineHours,
|
|
2598
2628
|
allowOverride: template.allowOverride
|
|
2599
2629
|
}
|
|
2600
2630
|
};
|
|
@@ -3341,10 +3371,14 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3341
3371
|
async escalateInternal(instanceId, escalatedBy = "system", auditCtx, levelNumber) {
|
|
3342
3372
|
return this.withOptimisticRetry(instanceId, async (instance) => {
|
|
3343
3373
|
if (instance.status !== "pending") return instance;
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3374
|
+
const ladder = await this.escalationLadder(instance);
|
|
3375
|
+
const targetLevel = levelNumber === void 0 ? this.currentLevelInstance(instance) : instance.levels.find((l) => l.level === levelNumber) ?? this.currentLevelInstance(instance);
|
|
3376
|
+
const rungIndex = targetLevel.escalationStep ?? 0;
|
|
3377
|
+
const rung = ladder[rungIndex];
|
|
3378
|
+
const escalateTo = rung?.escalateTo ?? (instance.templateSnapshot?.escalation ?? (await this.registry.get(instance.templateName)).escalation)?.escalateTo;
|
|
3379
|
+
if (!escalateTo) return instance;
|
|
3346
3380
|
const newApprovers = await this.resolver.resolveApprovers(
|
|
3347
|
-
[
|
|
3381
|
+
[escalateTo],
|
|
3348
3382
|
instance.submittedBy,
|
|
3349
3383
|
instance.data,
|
|
3350
3384
|
this.opts.orgProvider,
|
|
@@ -3362,10 +3396,13 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3362
3396
|
);
|
|
3363
3397
|
return instance;
|
|
3364
3398
|
}
|
|
3365
|
-
const level =
|
|
3399
|
+
const level = targetLevel;
|
|
3366
3400
|
level.approverIds = [.../* @__PURE__ */ new Set([...level.approverIds, ...filteredApprovers])];
|
|
3367
|
-
level.escalationDueAt = void 0;
|
|
3368
3401
|
const now = this.clock.now();
|
|
3402
|
+
level.escalationStep = rungIndex + 1;
|
|
3403
|
+
const nextRung = ladder[rungIndex + 1];
|
|
3404
|
+
const levelOpenedAt = this.levelOpenedAt(instance, level, now);
|
|
3405
|
+
level.escalationDueAt = nextRung ? this.stepDueAt(levelOpenedAt, nextRung) : void 0;
|
|
3369
3406
|
const escalatedTo = filteredApprovers[0] ?? "unknown";
|
|
3370
3407
|
const auditEntry = {
|
|
3371
3408
|
action: "escalated",
|
|
@@ -3537,6 +3574,69 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3537
3574
|
deadlineFrom(from, days) {
|
|
3538
3575
|
return this.calendar ? this.calendar.addBusinessDays(from, days) : new Date(from.getTime() + days * 864e5);
|
|
3539
3576
|
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Resolve an hour-based deadline.
|
|
3579
|
+
*
|
|
3580
|
+
* A calendar that only knows whole days — `weekendCalendar`, say — has no
|
|
3581
|
+
* `addBusinessHours`, and this falls back to elapsed clock time rather than
|
|
3582
|
+
* quietly pretending the calendar was applied. Configure
|
|
3583
|
+
* `businessHoursCalendar` to have hours skip evenings and weekends.
|
|
3584
|
+
*/
|
|
3585
|
+
deadlineFromHours(from, hours) {
|
|
3586
|
+
const addHours = this.calendar?.addBusinessHours?.bind(this.calendar);
|
|
3587
|
+
return addHours ? addHours(from, hours) : new Date(from.getTime() + hours * 36e5);
|
|
3588
|
+
}
|
|
3589
|
+
/** Level deadline from whichever of days/hours the template configured. */
|
|
3590
|
+
levelEscalationDue(from, level, firstRung) {
|
|
3591
|
+
if (level.escalationAfterHours) return this.deadlineFromHours(from, level.escalationAfterHours);
|
|
3592
|
+
if (level.escalationAfterDays) return this.deadlineFrom(from, level.escalationAfterDays);
|
|
3593
|
+
return firstRung ? this.stepDueAt(from, firstRung) : void 0;
|
|
3594
|
+
}
|
|
3595
|
+
/** First rung of a ladder, sorted by delay, or undefined when there is none. */
|
|
3596
|
+
firstRungOf(steps) {
|
|
3597
|
+
if (!steps || steps.length === 0) return void 0;
|
|
3598
|
+
return [...steps].sort(
|
|
3599
|
+
(a, b) => (a.afterHours ?? (a.afterDays ?? 0) * 24) - (b.afterHours ?? (b.afterDays ?? 0) * 24)
|
|
3600
|
+
)[0];
|
|
3601
|
+
}
|
|
3602
|
+
/**
|
|
3603
|
+
* The escalation ladder for an instance, sorted by delay.
|
|
3604
|
+
*
|
|
3605
|
+
* Read from the instance's template snapshot so an in-flight approval keeps
|
|
3606
|
+
* the ladder it was submitted under, exactly as the single-step
|
|
3607
|
+
* {@link EscalationConfig} already did.
|
|
3608
|
+
*/
|
|
3609
|
+
async escalationLadder(instance) {
|
|
3610
|
+
const snapshot = instance.templateSnapshot;
|
|
3611
|
+
const steps = snapshot?.escalationSteps ?? (await this.registry.get(instance.templateName)).escalationSteps ?? [];
|
|
3612
|
+
return [...steps].sort(
|
|
3613
|
+
(a, b) => (a.afterHours ?? (a.afterDays ?? 0) * 24) - (b.afterHours ?? (b.afterDays ?? 0) * 24)
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* When a level started collecting decisions.
|
|
3618
|
+
*
|
|
3619
|
+
* Escalation rungs are measured from this, not from the previous rung, so a
|
|
3620
|
+
* ladder reads the way it is written. Recovered from the audit trail — the
|
|
3621
|
+
* `submitted` entry for the opening level, the `level_advanced` entry
|
|
3622
|
+
* otherwise — and falls back to `now` when no entry exists, which only leaves
|
|
3623
|
+
* the ladder no worse off than the single-step behaviour it replaces.
|
|
3624
|
+
*/
|
|
3625
|
+
levelOpenedAt(instance, level, fallback) {
|
|
3626
|
+
for (let i = instance.auditLog.length - 1; i >= 0; i--) {
|
|
3627
|
+
const entry = instance.auditLog[i];
|
|
3628
|
+
if (!entry) continue;
|
|
3629
|
+
const opensThisLevel = (entry.action === "level_advanced" || entry.action === "submitted") && entry.level === level.level;
|
|
3630
|
+
if (opensThisLevel) return new Date(entry.timestamp);
|
|
3631
|
+
}
|
|
3632
|
+
return fallback;
|
|
3633
|
+
}
|
|
3634
|
+
/** Deadline for one rung, measured from when the level opened. */
|
|
3635
|
+
stepDueAt(from, step) {
|
|
3636
|
+
if (step.afterHours) return this.deadlineFromHours(from, step.afterHours);
|
|
3637
|
+
if (step.afterDays) return this.deadlineFrom(from, step.afterDays);
|
|
3638
|
+
return void 0;
|
|
3639
|
+
}
|
|
3540
3640
|
async requireInstance(id) {
|
|
3541
3641
|
const instance = await this.opts.adapter.getInstance(this.tenantId, id);
|
|
3542
3642
|
if (!instance) throw new ApprovalNotFoundError("Instance", id);
|
|
@@ -3629,6 +3729,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3629
3729
|
}
|
|
3630
3730
|
/** Resolve approvers for a group, set its deadlines, and mark it pending. */
|
|
3631
3731
|
async activateGroup(instance, group, now) {
|
|
3732
|
+
const firstRung = this.firstRungOf(instance.templateSnapshot?.escalationSteps);
|
|
3632
3733
|
for (const lvl of group) {
|
|
3633
3734
|
if (lvl.subWorkflowTemplate) {
|
|
3634
3735
|
lvl.approverIds = [];
|
|
@@ -3642,9 +3743,8 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3642
3743
|
now
|
|
3643
3744
|
);
|
|
3644
3745
|
}
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
}
|
|
3746
|
+
lvl.escalationDueAt = this.levelEscalationDue(now, lvl, firstRung);
|
|
3747
|
+
lvl.escalationStep = 0;
|
|
3648
3748
|
this.scheduleReminder(lvl, now);
|
|
3649
3749
|
lvl.status = "pending";
|
|
3650
3750
|
}
|