hierarchical-approval 2.0.0 → 2.2.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 +78 -0
- package/README.md +60 -0
- package/dist/{ApprovalEngine-B8bPZq01.d.ts → ApprovalEngine-B3ChZxS7.d.ts} +109 -8
- package/dist/{ApprovalEngine-C7p9cOA3.d.cts → ApprovalEngine-CbFBAMlO.d.cts} +109 -8
- package/dist/{IAuditAdapter-UJNdJanq.d.cts → IAuditAdapter-BHVKMMv6.d.cts} +1 -1
- package/dist/{IAuditAdapter-DDV4Rf9F.d.ts → IAuditAdapter-C4OASS6u.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-CFJ-gXKl.d.ts → IAuthorizationPolicy-BA_m2Dg9.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-CN6LAaKg.d.cts → IAuthorizationPolicy-CtLkwQ8I.d.cts} +1 -1
- package/dist/{INotificationAdapter-Dv_MuYwS.d.ts → INotificationAdapter-BdgfUrIn.d.ts} +12 -2
- package/dist/{INotificationAdapter-Dy2d0JKy.d.cts → INotificationAdapter-JJAiMyMd.d.cts} +12 -2
- package/dist/{IOperationMiddleware-DWYXVuJD.d.ts → IOperationMiddleware-BGYrAqep.d.ts} +1 -1
- package/dist/{IOperationMiddleware-DGG-guxK.d.cts → IOperationMiddleware-D0ksiFM3.d.cts} +1 -1
- package/dist/{IStorageAdapter-Bgd6p8XL.d.cts → IStorageAdapter-Bk7ybd3z.d.cts} +1 -1
- package/dist/{IStorageAdapter-B8aRYeGI.d.ts → IStorageAdapter-DjRvHUF0.d.ts} +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 +385 -13
- 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 +385 -14
- package/dist/index.js.map +1 -1
- package/dist/{instance-BE0uJmg3.d.cts → instance-DUJY_Axf.d.cts} +41 -3
- package/dist/{instance-BE0uJmg3.d.ts → instance-DUJY_Axf.d.ts} +41 -3
- package/dist/nestjs.cjs +384 -13
- 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 +384 -13
- 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 +384 -13
- 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 +384 -13
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export { A as AddCommentOptions, a as ApprovalEngine, b as ApprovalEngineOptions, c as ApprovalStatistics, d as ApproveOptions, e as ApproverResolverFn, f as ApproverWorkload, B as BulkResult, g as BusinessCalendar, C as CanApproveResult, h as CancelOptions, i as ConditionOperatorFn, j as CycleTimeStats, D as DelegateOptions, E as EscalateOptions, H as HealthResult, I as IdGeneratorFn, k as IdempotencyKeyFn, O as OrgProvider,
|
|
2
|
-
import { I as IStorageAdapter } from './IStorageAdapter-
|
|
3
|
-
export { c as CursorPaginatedResult, C as CursorPaginationOpts, b as InstanceFilter, a as PaginatedResult, P as PaginationOpts } from './IStorageAdapter-
|
|
1
|
+
export { A as AddCommentOptions, a as ApprovalEngine, b as ApprovalEngineOptions, c as ApprovalStatistics, d as ApproveOptions, e as ApproverResolverFn, f as ApproverWorkload, B as BulkResult, g as BusinessCalendar, C as CanApproveResult, h as CancelOptions, i as ConditionOperatorFn, j as CycleTimeStats, D as DelegateOptions, E as EscalateOptions, H as HealthResult, I as IdGeneratorFn, k as IdempotencyKeyFn, l as ImportResult, O as OrgProvider, m as OutOfOfficeProvider, n as OverrideOptions, P as PreviewChainLevel, o as PreviewResult, p as ProvideInfoOptions, R as ReassignOptions, q as RejectOptions, r as RequestInfoOptions, s as ResubmitOptions, t as RetryPolicy, S as SubmitOptions, T as TEMPLATE_BUNDLE_VERSION, u as TemplateBundle, v as TransferResult, U as UpdateDataOptions, V as ValidationResult, W as WeekendCalendarOptions, w as defaultIdGenerator, x as toComparableNumber, y as validateConditionExpression, z as weekendCalendar } from './ApprovalEngine-CbFBAMlO.cjs';
|
|
2
|
+
import { I as IStorageAdapter } from './IStorageAdapter-Bk7ybd3z.cjs';
|
|
3
|
+
export { c as CursorPaginatedResult, C as CursorPaginationOpts, b as InstanceFilter, a as PaginatedResult, P as PaginationOpts } from './IStorageAdapter-Bk7ybd3z.cjs';
|
|
4
4
|
import { L as Logger } from './Logger-BplhlU7l.cjs';
|
|
5
5
|
export { n as noopLogger } from './Logger-BplhlU7l.cjs';
|
|
6
6
|
import { C as Clock } from './Clock-3FnOczFJ.cjs';
|
|
7
7
|
export { s as systemClock } from './Clock-3FnOczFJ.cjs';
|
|
8
|
-
export { A as AuthorizationContext, I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
9
|
-
export { A as ApprovalConflictError, a as ApprovalError, b as ApprovalForbiddenError, c as ApprovalNotFoundError, d as ApprovalTemplateNotFoundError, e as ApprovalValidationError, I as IOperationMiddleware, O as OperationContext } from './IOperationMiddleware-
|
|
8
|
+
export { A as AuthorizationContext, I as IAuthorizationPolicy } from './IAuthorizationPolicy-CtLkwQ8I.cjs';
|
|
9
|
+
export { A as ApprovalConflictError, a as ApprovalError, b as ApprovalForbiddenError, c as ApprovalNotFoundError, d as ApprovalTemplateNotFoundError, e as ApprovalValidationError, I as IOperationMiddleware, O as OperationContext } from './IOperationMiddleware-D0ksiFM3.cjs';
|
|
10
10
|
export { MemoryAdapter } from './adapters/MemoryAdapter.cjs';
|
|
11
|
-
export { A as ApprovalEvent, a as ApprovalEventMap, b as ApprovalEventName, c as ApprovedEvent, d as AttachmentEvent, C as CancelledEvent, D as DataUpdatedEvent, e as DelegatedEvent, E as EscalatedEvent, f as ExpiredEvent, H as HistoryEntry, I as INotificationAdapter, g as InfoProvidedEvent, h as InfoRequestedEvent, L as LevelAdvancedEvent, N as NotificationEvent, O as OverriddenEvent, R as ReassignedEvent, i as RejectedEvent, j as ReminderEvent, k as ResubmittedEvent, S as SlaBreachedEvent, l as SubmittedEvent } from './INotificationAdapter-
|
|
12
|
-
export { I as IAuditAdapter } from './IAuditAdapter-
|
|
11
|
+
export { A as ApprovalEvent, a as ApprovalEventMap, b as ApprovalEventName, c as ApprovedEvent, d as AttachmentEvent, C as CancelledEvent, D as DataUpdatedEvent, e as DelegatedEvent, E as EscalatedEvent, f as ExpiredEvent, H as HistoryEntry, I as INotificationAdapter, g as InfoProvidedEvent, h as InfoRequestedEvent, L as LevelAdvancedEvent, N as NotificationEvent, O as OverriddenEvent, R as ReassignedEvent, i as RejectedEvent, j as ReminderEvent, k as ResubmittedEvent, S as SlaBreachedEvent, l as SubWorkflowEvent, m as SubmittedEvent } from './INotificationAdapter-JJAiMyMd.cjs';
|
|
12
|
+
export { I as IAuditAdapter } from './IAuditAdapter-BHVKMMv6.cjs';
|
|
13
13
|
export { I as IMetricsAdapter, M as MetricName } from './IMetricsAdapter-DWq8IFaf.cjs';
|
|
14
14
|
export { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
|
|
15
|
-
export { a as ApprovalInstance, c as ApprovalLevelConfig, d as ApprovalLevelInstance, e as ApprovalMode, f as ApprovalStatus, A as ApprovalTemplate, g as ApprovalTemplateConfig, h as ApproverConfig, i as Attachment, j as AuditAction, k as AuditContext, b as AuditEntry, C as Condition, l as ConditionExpression, m as ConditionGroup, n as ConditionOperator, o as ConditionRule, E as EscalationConfig, I as InfoRequest, L as LevelStatus, R as ResolvedApprover, p as ResolverFn, T as TemplateSnapshot } from './instance-
|
|
15
|
+
export { a as ApprovalInstance, c as ApprovalLevelConfig, d as ApprovalLevelInstance, e as ApprovalMode, f as ApprovalStatus, A as ApprovalTemplate, g as ApprovalTemplateConfig, h as ApproverConfig, i as Attachment, j as AuditAction, k as AuditContext, b as AuditEntry, C as Condition, l as ConditionExpression, m as ConditionGroup, n as ConditionOperator, o as ConditionRule, E as EscalationConfig, I as InfoRequest, L as LevelStatus, R as ResolvedApprover, p as ResolverFn, S as SubWorkflowConfig, T as TemplateSnapshot } from './instance-DUJY_Axf.cjs';
|
|
16
16
|
import 'zod';
|
|
17
17
|
|
|
18
18
|
interface EscalationSchedulerOpts {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export { A as AddCommentOptions, a as ApprovalEngine, b as ApprovalEngineOptions, c as ApprovalStatistics, d as ApproveOptions, e as ApproverResolverFn, f as ApproverWorkload, B as BulkResult, g as BusinessCalendar, C as CanApproveResult, h as CancelOptions, i as ConditionOperatorFn, j as CycleTimeStats, D as DelegateOptions, E as EscalateOptions, H as HealthResult, I as IdGeneratorFn, k as IdempotencyKeyFn, O as OrgProvider,
|
|
2
|
-
import { I as IStorageAdapter } from './IStorageAdapter-
|
|
3
|
-
export { c as CursorPaginatedResult, C as CursorPaginationOpts, b as InstanceFilter, a as PaginatedResult, P as PaginationOpts } from './IStorageAdapter-
|
|
1
|
+
export { A as AddCommentOptions, a as ApprovalEngine, b as ApprovalEngineOptions, c as ApprovalStatistics, d as ApproveOptions, e as ApproverResolverFn, f as ApproverWorkload, B as BulkResult, g as BusinessCalendar, C as CanApproveResult, h as CancelOptions, i as ConditionOperatorFn, j as CycleTimeStats, D as DelegateOptions, E as EscalateOptions, H as HealthResult, I as IdGeneratorFn, k as IdempotencyKeyFn, l as ImportResult, O as OrgProvider, m as OutOfOfficeProvider, n as OverrideOptions, P as PreviewChainLevel, o as PreviewResult, p as ProvideInfoOptions, R as ReassignOptions, q as RejectOptions, r as RequestInfoOptions, s as ResubmitOptions, t as RetryPolicy, S as SubmitOptions, T as TEMPLATE_BUNDLE_VERSION, u as TemplateBundle, v as TransferResult, U as UpdateDataOptions, V as ValidationResult, W as WeekendCalendarOptions, w as defaultIdGenerator, x as toComparableNumber, y as validateConditionExpression, z as weekendCalendar } from './ApprovalEngine-B3ChZxS7.js';
|
|
2
|
+
import { I as IStorageAdapter } from './IStorageAdapter-DjRvHUF0.js';
|
|
3
|
+
export { c as CursorPaginatedResult, C as CursorPaginationOpts, b as InstanceFilter, a as PaginatedResult, P as PaginationOpts } from './IStorageAdapter-DjRvHUF0.js';
|
|
4
4
|
import { L as Logger } from './Logger-BplhlU7l.js';
|
|
5
5
|
export { n as noopLogger } from './Logger-BplhlU7l.js';
|
|
6
6
|
import { C as Clock } from './Clock-3FnOczFJ.js';
|
|
7
7
|
export { s as systemClock } from './Clock-3FnOczFJ.js';
|
|
8
|
-
export { A as AuthorizationContext, I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
9
|
-
export { A as ApprovalConflictError, a as ApprovalError, b as ApprovalForbiddenError, c as ApprovalNotFoundError, d as ApprovalTemplateNotFoundError, e as ApprovalValidationError, I as IOperationMiddleware, O as OperationContext } from './IOperationMiddleware-
|
|
8
|
+
export { A as AuthorizationContext, I as IAuthorizationPolicy } from './IAuthorizationPolicy-BA_m2Dg9.js';
|
|
9
|
+
export { A as ApprovalConflictError, a as ApprovalError, b as ApprovalForbiddenError, c as ApprovalNotFoundError, d as ApprovalTemplateNotFoundError, e as ApprovalValidationError, I as IOperationMiddleware, O as OperationContext } from './IOperationMiddleware-BGYrAqep.js';
|
|
10
10
|
export { MemoryAdapter } from './adapters/MemoryAdapter.js';
|
|
11
|
-
export { A as ApprovalEvent, a as ApprovalEventMap, b as ApprovalEventName, c as ApprovedEvent, d as AttachmentEvent, C as CancelledEvent, D as DataUpdatedEvent, e as DelegatedEvent, E as EscalatedEvent, f as ExpiredEvent, H as HistoryEntry, I as INotificationAdapter, g as InfoProvidedEvent, h as InfoRequestedEvent, L as LevelAdvancedEvent, N as NotificationEvent, O as OverriddenEvent, R as ReassignedEvent, i as RejectedEvent, j as ReminderEvent, k as ResubmittedEvent, S as SlaBreachedEvent, l as SubmittedEvent } from './INotificationAdapter-
|
|
12
|
-
export { I as IAuditAdapter } from './IAuditAdapter-
|
|
11
|
+
export { A as ApprovalEvent, a as ApprovalEventMap, b as ApprovalEventName, c as ApprovedEvent, d as AttachmentEvent, C as CancelledEvent, D as DataUpdatedEvent, e as DelegatedEvent, E as EscalatedEvent, f as ExpiredEvent, H as HistoryEntry, I as INotificationAdapter, g as InfoProvidedEvent, h as InfoRequestedEvent, L as LevelAdvancedEvent, N as NotificationEvent, O as OverriddenEvent, R as ReassignedEvent, i as RejectedEvent, j as ReminderEvent, k as ResubmittedEvent, S as SlaBreachedEvent, l as SubWorkflowEvent, m as SubmittedEvent } from './INotificationAdapter-BdgfUrIn.js';
|
|
12
|
+
export { I as IAuditAdapter } from './IAuditAdapter-C4OASS6u.js';
|
|
13
13
|
export { I as IMetricsAdapter, M as MetricName } from './IMetricsAdapter-DWq8IFaf.js';
|
|
14
14
|
export { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
|
|
15
|
-
export { a as ApprovalInstance, c as ApprovalLevelConfig, d as ApprovalLevelInstance, e as ApprovalMode, f as ApprovalStatus, A as ApprovalTemplate, g as ApprovalTemplateConfig, h as ApproverConfig, i as Attachment, j as AuditAction, k as AuditContext, b as AuditEntry, C as Condition, l as ConditionExpression, m as ConditionGroup, n as ConditionOperator, o as ConditionRule, E as EscalationConfig, I as InfoRequest, L as LevelStatus, R as ResolvedApprover, p as ResolverFn, T as TemplateSnapshot } from './instance-
|
|
15
|
+
export { a as ApprovalInstance, c as ApprovalLevelConfig, d as ApprovalLevelInstance, e as ApprovalMode, f as ApprovalStatus, A as ApprovalTemplate, g as ApprovalTemplateConfig, h as ApproverConfig, i as Attachment, j as AuditAction, k as AuditContext, b as AuditEntry, C as Condition, l as ConditionExpression, m as ConditionGroup, n as ConditionOperator, o as ConditionRule, E as EscalationConfig, I as InfoRequest, L as LevelStatus, R as ResolvedApprover, p as ResolverFn, S as SubWorkflowConfig, T as TemplateSnapshot } from './instance-DUJY_Axf.js';
|
|
16
16
|
import 'zod';
|
|
17
17
|
|
|
18
18
|
interface EscalationSchedulerOpts {
|
package/dist/index.js
CHANGED
|
@@ -908,6 +908,7 @@ function computeTimingStats(samples) {
|
|
|
908
908
|
}
|
|
909
909
|
|
|
910
910
|
// src/engine/ApprovalEngine.ts
|
|
911
|
+
var MAX_SUBWORKFLOW_DEPTH = 5;
|
|
911
912
|
var DEFAULT_MAX_REMINDERS = 3;
|
|
912
913
|
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
913
914
|
var DEFAULT_BASE_DELAY_MS = 50;
|
|
@@ -919,6 +920,7 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
|
919
920
|
]);
|
|
920
921
|
var CYCLE_TIME_STATUSES = ["approved", "rejected", "cancelled"];
|
|
921
922
|
var CYCLE_TIME_FETCH_BATCH_SIZE = 500;
|
|
923
|
+
var TEMPLATE_BUNDLE_VERSION = 1;
|
|
922
924
|
var ApprovalEngine = class _ApprovalEngine {
|
|
923
925
|
constructor(opts) {
|
|
924
926
|
this.opts = opts;
|
|
@@ -1012,12 +1014,32 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1012
1014
|
});
|
|
1013
1015
|
}
|
|
1014
1016
|
levelNums.add(l.level);
|
|
1015
|
-
if (!l.approvers || l.approvers.length === 0) {
|
|
1017
|
+
if (!l.subWorkflow && (!l.approvers || l.approvers.length === 0)) {
|
|
1016
1018
|
errors.push({
|
|
1017
1019
|
field: `levels[${i}].approvers`,
|
|
1018
1020
|
message: `Level ${l.level} must have at least one approver.`
|
|
1019
1021
|
});
|
|
1020
1022
|
}
|
|
1023
|
+
if (l.subWorkflow) {
|
|
1024
|
+
if (!l.subWorkflow.templateName) {
|
|
1025
|
+
errors.push({
|
|
1026
|
+
field: `levels[${i}].subWorkflow.templateName`,
|
|
1027
|
+
message: `Level ${l.level} declares a subWorkflow without a templateName.`
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
if (l.subWorkflow.templateName === config.name) {
|
|
1031
|
+
errors.push({
|
|
1032
|
+
field: `levels[${i}].subWorkflow.templateName`,
|
|
1033
|
+
message: `Level ${l.level} would spawn a sub-workflow of its own template ("${config.name}"), which cannot terminate.`
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (l.approvers && l.approvers.length > 0) {
|
|
1037
|
+
errors.push({
|
|
1038
|
+
field: `levels[${i}].approvers`,
|
|
1039
|
+
message: `Level ${l.level} sets both approvers and subWorkflow; a sub-workflow level is decided by its child approval, so its approvers would never be asked.`
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1021
1043
|
if (l.reminderAfterDays !== void 0 && l.reminderAfterDays <= 0) {
|
|
1022
1044
|
errors.push({
|
|
1023
1045
|
field: `levels[${i}].reminderAfterDays`,
|
|
@@ -1160,7 +1182,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1160
1182
|
return this.registry.list();
|
|
1161
1183
|
}
|
|
1162
1184
|
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
1163
|
-
async submit(raw, auditCtx) {
|
|
1185
|
+
async submit(raw, auditCtx, link) {
|
|
1164
1186
|
const opts = parseOrThrow(() => SubmitOptionsSchema.parse(raw));
|
|
1165
1187
|
const startMs = this.clock.now().getTime();
|
|
1166
1188
|
const template = await this.registry.get(opts.templateName);
|
|
@@ -1220,6 +1242,7 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1220
1242
|
weights: cfg.weights,
|
|
1221
1243
|
escalationAfterDays: cfg.escalationAfterDays,
|
|
1222
1244
|
escalationDueAt: inFirstGroup && cfg.escalationAfterDays ? this.deadlineFrom(now, cfg.escalationAfterDays) : void 0,
|
|
1245
|
+
subWorkflowTemplate: cfg.subWorkflow?.templateName,
|
|
1223
1246
|
reminderAfterDays: cfg.reminderAfterDays,
|
|
1224
1247
|
reminderEveryDays: cfg.reminderEveryDays,
|
|
1225
1248
|
maxReminders: cfg.maxReminders,
|
|
@@ -1228,6 +1251,10 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1228
1251
|
};
|
|
1229
1252
|
});
|
|
1230
1253
|
for (const lvl of levels.filter((l) => l.status === "pending")) {
|
|
1254
|
+
if (lvl.subWorkflowTemplate) {
|
|
1255
|
+
lvl.approverIds = [];
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1231
1258
|
lvl.approverIds = await this.resolver.resolveApprovers(
|
|
1232
1259
|
lvl.approverConfigs,
|
|
1233
1260
|
opts.submittedBy,
|
|
@@ -1248,6 +1275,9 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1248
1275
|
const instance = {
|
|
1249
1276
|
id: instanceId,
|
|
1250
1277
|
tenantId: this.tenantId,
|
|
1278
|
+
parentInstanceId: link?.parentInstanceId,
|
|
1279
|
+
parentLevel: link?.parentLevel,
|
|
1280
|
+
subWorkflowDepth: link?.depth,
|
|
1251
1281
|
templateId: template.id,
|
|
1252
1282
|
templateName: template.name,
|
|
1253
1283
|
documentId: opts.documentId,
|
|
@@ -1312,12 +1342,13 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1312
1342
|
{ operation: "submit", actorId: opts.submittedBy, tenantId: this.tenantId, input: opts },
|
|
1313
1343
|
instance
|
|
1314
1344
|
);
|
|
1345
|
+
await this.startSubWorkflows(instance);
|
|
1315
1346
|
return instance;
|
|
1316
1347
|
}
|
|
1317
1348
|
async approve(instanceId, raw, auditCtx) {
|
|
1318
1349
|
const opts = parseOrThrow(() => ApproveOptionsSchema.parse(raw));
|
|
1319
1350
|
const startMs = this.clock.now().getTime();
|
|
1320
|
-
|
|
1351
|
+
const decided = await this.withOptimisticRetry(instanceId, async (instance) => {
|
|
1321
1352
|
assertStatus(instance, "pending");
|
|
1322
1353
|
if (opts.approverId === instance.submittedBy) {
|
|
1323
1354
|
throw new ApprovalForbiddenError(
|
|
@@ -1530,10 +1561,12 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1530
1561
|
}
|
|
1531
1562
|
return instance;
|
|
1532
1563
|
});
|
|
1564
|
+
await this.afterDecision(decided);
|
|
1565
|
+
return decided;
|
|
1533
1566
|
}
|
|
1534
1567
|
async reject(instanceId, raw, auditCtx) {
|
|
1535
1568
|
const opts = parseOrThrow(() => RejectOptionsSchema.parse(raw));
|
|
1536
|
-
|
|
1569
|
+
const decided = await this.withOptimisticRetry(instanceId, async (instance) => {
|
|
1537
1570
|
assertStatus(instance, "pending");
|
|
1538
1571
|
if (opts.approverId === instance.submittedBy) {
|
|
1539
1572
|
throw new ApprovalForbiddenError(
|
|
@@ -1652,6 +1685,8 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1652
1685
|
}
|
|
1653
1686
|
return instance;
|
|
1654
1687
|
});
|
|
1688
|
+
await this.afterDecision(decided);
|
|
1689
|
+
return decided;
|
|
1655
1690
|
}
|
|
1656
1691
|
async delegate(instanceId, raw, auditCtx) {
|
|
1657
1692
|
const opts = parseOrThrow(() => DelegateOptionsSchema.parse(raw));
|
|
@@ -1829,7 +1864,8 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1829
1864
|
}
|
|
1830
1865
|
async cancel(instanceId, raw, auditCtx) {
|
|
1831
1866
|
const opts = parseOrThrow(() => CancelOptionsSchema.parse(raw));
|
|
1832
|
-
|
|
1867
|
+
let cancelled = null;
|
|
1868
|
+
const result = await this.withOptimisticRetry(instanceId, async (instance) => {
|
|
1833
1869
|
if (instance.status === "approved" || instance.status === "rejected") {
|
|
1834
1870
|
throw new ApprovalError(`Cannot cancel a "${instance.status}" approval.`, "CANNOT_CANCEL");
|
|
1835
1871
|
}
|
|
@@ -1883,8 +1919,11 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
1883
1919
|
},
|
|
1884
1920
|
instance
|
|
1885
1921
|
);
|
|
1922
|
+
cancelled = instance;
|
|
1886
1923
|
return instance;
|
|
1887
1924
|
});
|
|
1925
|
+
if (cancelled) await this.propagateToParent(cancelled);
|
|
1926
|
+
return result;
|
|
1888
1927
|
}
|
|
1889
1928
|
async escalate(instanceId, raw, auditCtx) {
|
|
1890
1929
|
parseOrThrow(() => EscalateOptionsSchema.parse(raw));
|
|
@@ -2963,6 +3002,118 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
2963
3002
|
oldestAgeMs: Number.isFinite(row.oldest) ? now.getTime() - row.oldest : 0
|
|
2964
3003
|
})).sort((a, b) => b.pending - a.pending || a.approverId.localeCompare(b.approverId));
|
|
2965
3004
|
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Export templates as a portable bundle.
|
|
3007
|
+
*
|
|
3008
|
+
* Approval configuration is written once and then has to travel — authored in
|
|
3009
|
+
* a sandbox, reviewed, promoted to production. Reading `listTemplates()` and
|
|
3010
|
+
* re-posting the rows carried each environment's own `id`, `tenantId` and
|
|
3011
|
+
* version lineage with it, which either collided on arrival or silently
|
|
3012
|
+
* claimed a history the target never had. This strips all of it.
|
|
3013
|
+
*
|
|
3014
|
+
* @param names - Templates to include; omit for all of them.
|
|
3015
|
+
*/
|
|
3016
|
+
async exportTemplates(names) {
|
|
3017
|
+
const all = await this.registry.list();
|
|
3018
|
+
const wanted = names ? all.filter((t) => names.includes(t.name)) : all;
|
|
3019
|
+
if (names) {
|
|
3020
|
+
const missing = names.filter((n) => !all.some((t) => t.name === n));
|
|
3021
|
+
if (missing.length > 0) {
|
|
3022
|
+
throw new ApprovalTemplateNotFoundError(missing.join(", "));
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return {
|
|
3026
|
+
bundleVersion: TEMPLATE_BUNDLE_VERSION,
|
|
3027
|
+
exportedAt: this.clock.now(),
|
|
3028
|
+
templates: wanted.map((t) => {
|
|
3029
|
+
const {
|
|
3030
|
+
id: _id,
|
|
3031
|
+
tenantId: _tenantId,
|
|
3032
|
+
createdAt: _createdAt,
|
|
3033
|
+
version: _version,
|
|
3034
|
+
previousVersionId: _previousVersionId,
|
|
3035
|
+
...config
|
|
3036
|
+
} = t;
|
|
3037
|
+
return config;
|
|
3038
|
+
})
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Import a bundle produced by {@link exportTemplates}.
|
|
3043
|
+
*
|
|
3044
|
+
* **Every template is validated before any is written.** A bundle that is
|
|
3045
|
+
* half-applied is worse than one rejected outright: the tenant is left in a
|
|
3046
|
+
* state matching neither environment, and the operator has no way to tell
|
|
3047
|
+
* which half landed. Per-template failures during the write phase are still
|
|
3048
|
+
* reported individually, since a storage error can occur after validation
|
|
3049
|
+
* passes.
|
|
3050
|
+
*
|
|
3051
|
+
* @param bundle - The bundle to apply.
|
|
3052
|
+
* @param opts - `mode: 'create'` (default) refuses to touch existing
|
|
3053
|
+
* templates; `'upsert'` updates them. `dryRun` reports without writing.
|
|
3054
|
+
*/
|
|
3055
|
+
async importTemplates(bundle, opts = {}) {
|
|
3056
|
+
const mode = opts.mode ?? "create";
|
|
3057
|
+
const dryRun = opts.dryRun ?? false;
|
|
3058
|
+
if (bundle.bundleVersion !== TEMPLATE_BUNDLE_VERSION) {
|
|
3059
|
+
throw new ApprovalValidationError(
|
|
3060
|
+
`Unsupported template bundle version ${bundle.bundleVersion}; this engine reads version ${TEMPLATE_BUNDLE_VERSION}.`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
if (!Array.isArray(bundle.templates) || bundle.templates.length === 0) {
|
|
3064
|
+
throw new ApprovalValidationError("Template bundle contains no templates.");
|
|
3065
|
+
}
|
|
3066
|
+
const duplicates = bundle.templates.map((t) => t.name).filter((name, i, all) => all.indexOf(name) !== i);
|
|
3067
|
+
if (duplicates.length > 0) {
|
|
3068
|
+
throw new ApprovalValidationError(
|
|
3069
|
+
`Template bundle contains duplicate names: ${[...new Set(duplicates)].join(", ")}.`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
const invalid = [];
|
|
3073
|
+
for (const config of bundle.templates) {
|
|
3074
|
+
const result2 = this.validateTemplate(config);
|
|
3075
|
+
if (!result2.valid) {
|
|
3076
|
+
invalid.push({
|
|
3077
|
+
name: config.name,
|
|
3078
|
+
message: result2.errors[0]?.message ?? "unknown validation error"
|
|
3079
|
+
});
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
if (invalid.length > 0) {
|
|
3083
|
+
throw new ApprovalValidationError(
|
|
3084
|
+
`Template bundle failed validation and was not applied: ${invalid.map((e) => `${e.name}: ${e.message}`).join("; ")}`
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
const result = { created: [], updated: [], skipped: [], errors: [], dryRun };
|
|
3088
|
+
for (const config of bundle.templates) {
|
|
3089
|
+
const existing = await this.opts.adapter.getTemplate(this.tenantId, config.name);
|
|
3090
|
+
try {
|
|
3091
|
+
if (existing && mode === "create") {
|
|
3092
|
+
result.skipped.push(config.name);
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
3095
|
+
if (existing) {
|
|
3096
|
+
if (!dryRun) await this.registry.update(config);
|
|
3097
|
+
result.updated.push(config.name);
|
|
3098
|
+
} else {
|
|
3099
|
+
if (!dryRun) await this.registry.define(config);
|
|
3100
|
+
result.created.push(config.name);
|
|
3101
|
+
}
|
|
3102
|
+
} catch (err) {
|
|
3103
|
+
result.errors.push({ name: config.name, message: err.message });
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
this.logger.info("importTemplates: bundle applied", {
|
|
3107
|
+
tenantId: this.tenantId,
|
|
3108
|
+
mode,
|
|
3109
|
+
dryRun,
|
|
3110
|
+
created: result.created.length,
|
|
3111
|
+
updated: result.updated.length,
|
|
3112
|
+
skipped: result.skipped.length,
|
|
3113
|
+
errors: result.errors.length
|
|
3114
|
+
});
|
|
3115
|
+
return result;
|
|
3116
|
+
}
|
|
2966
3117
|
async getStatistics(filter = {}) {
|
|
2967
3118
|
const statuses = [
|
|
2968
3119
|
"pending",
|
|
@@ -3401,14 +3552,18 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3401
3552
|
/** Resolve approvers for a group, set its deadlines, and mark it pending. */
|
|
3402
3553
|
async activateGroup(instance, group, now) {
|
|
3403
3554
|
for (const lvl of group) {
|
|
3404
|
-
lvl.
|
|
3405
|
-
lvl.
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3555
|
+
if (lvl.subWorkflowTemplate) {
|
|
3556
|
+
lvl.approverIds = [];
|
|
3557
|
+
} else {
|
|
3558
|
+
lvl.approverIds = await this.resolver.resolveApprovers(
|
|
3559
|
+
lvl.approverConfigs,
|
|
3560
|
+
instance.submittedBy,
|
|
3561
|
+
instance.data,
|
|
3562
|
+
this.opts.orgProvider,
|
|
3563
|
+
this.opts.outOfOfficeProvider,
|
|
3564
|
+
now
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3412
3567
|
if (lvl.escalationAfterDays) {
|
|
3413
3568
|
lvl.escalationDueAt = this.deadlineFrom(now, lvl.escalationAfterDays);
|
|
3414
3569
|
}
|
|
@@ -3495,6 +3650,222 @@ var ApprovalEngine = class _ApprovalEngine {
|
|
|
3495
3650
|
this.logger.error("reminder: failed to send", err, { tenantId: this.tenantId, instanceId });
|
|
3496
3651
|
}
|
|
3497
3652
|
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Start child approvals for any open level that delegates to a sub-workflow.
|
|
3655
|
+
*
|
|
3656
|
+
* Run after the parent has been persisted, never inside the same optimistic
|
|
3657
|
+
* write: the child's own `submit()` performs its own reads and writes, and
|
|
3658
|
+
* nesting them under the parent's compare-and-set would make a slow child
|
|
3659
|
+
* template a source of spurious version conflicts on the parent.
|
|
3660
|
+
*/
|
|
3661
|
+
async startSubWorkflows(instance) {
|
|
3662
|
+
const pendingSubs = instance.levels.filter(
|
|
3663
|
+
(l) => l.status === "pending" && l.subWorkflowTemplate && !l.childInstanceId
|
|
3664
|
+
);
|
|
3665
|
+
if (pendingSubs.length === 0) return;
|
|
3666
|
+
const depth = (instance.subWorkflowDepth ?? 0) + 1;
|
|
3667
|
+
if (depth > MAX_SUBWORKFLOW_DEPTH) {
|
|
3668
|
+
throw new ApprovalValidationError(
|
|
3669
|
+
`Sub-workflow nesting exceeded ${MAX_SUBWORKFLOW_DEPTH} levels at template "${instance.templateName}". Check for a template that reaches itself.`
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
for (const level of pendingSubs) {
|
|
3673
|
+
const templateName = level.subWorkflowTemplate;
|
|
3674
|
+
const childTemplate = await this.registry.get(templateName);
|
|
3675
|
+
const child = await this.submit(
|
|
3676
|
+
{
|
|
3677
|
+
templateName,
|
|
3678
|
+
// Unique per parent level, so a resubmitted parent does not collide
|
|
3679
|
+
// with the child it spawned last time.
|
|
3680
|
+
documentId: `${instance.documentId}#L${level.level}`,
|
|
3681
|
+
documentType: childTemplate.documentType,
|
|
3682
|
+
submittedBy: instance.submittedBy,
|
|
3683
|
+
data: instance.data,
|
|
3684
|
+
metadata: { ...instance.metadata, parentInstanceId: instance.id }
|
|
3685
|
+
},
|
|
3686
|
+
void 0,
|
|
3687
|
+
{ parentInstanceId: instance.id, parentLevel: level.level, depth }
|
|
3688
|
+
);
|
|
3689
|
+
await this.withOptimisticRetry(instance.id, async (parent) => {
|
|
3690
|
+
const lvl = parent.levels.find((l) => l.level === level.level);
|
|
3691
|
+
if (!lvl || lvl.childInstanceId) return parent;
|
|
3692
|
+
lvl.childInstanceId = child.id;
|
|
3693
|
+
parent.updatedAt = this.clock.now();
|
|
3694
|
+
await this.opts.adapter.updateInstance(parent, parent.version);
|
|
3695
|
+
return parent;
|
|
3696
|
+
});
|
|
3697
|
+
const payload = {
|
|
3698
|
+
instanceId: instance.id,
|
|
3699
|
+
documentId: instance.documentId,
|
|
3700
|
+
documentType: instance.documentType,
|
|
3701
|
+
timestamp: this.clock.now(),
|
|
3702
|
+
level: level.level,
|
|
3703
|
+
childInstanceId: child.id,
|
|
3704
|
+
childTemplateName: templateName
|
|
3705
|
+
};
|
|
3706
|
+
this.bus.emit("approval:subworkflow_started", payload);
|
|
3707
|
+
await this.notifyAdapters("approval:subworkflow_started", instance, payload);
|
|
3708
|
+
this.logger.info("subWorkflow: child approval started", {
|
|
3709
|
+
tenantId: this.tenantId,
|
|
3710
|
+
instanceId: instance.id,
|
|
3711
|
+
level: level.level,
|
|
3712
|
+
childInstanceId: child.id
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
/**
|
|
3717
|
+
* Return a finished child's outcome to the parent level that is waiting on it.
|
|
3718
|
+
*
|
|
3719
|
+
* An approved child approves its parent level and lets the chain advance; any
|
|
3720
|
+
* other terminal outcome — rejected, cancelled, expired — rejects the parent,
|
|
3721
|
+
* because the approval the parent was waiting for did not happen. Collapsing
|
|
3722
|
+
* those into one rejection is deliberate: a parent that treated a cancelled
|
|
3723
|
+
* child as "carry on" would advance past a gate nobody cleared.
|
|
3724
|
+
*/
|
|
3725
|
+
async propagateToParent(child) {
|
|
3726
|
+
if (!child.parentInstanceId || child.parentLevel === void 0) return;
|
|
3727
|
+
const outcome = child.status;
|
|
3728
|
+
const parentId = child.parentInstanceId;
|
|
3729
|
+
const parentLevelNumber = child.parentLevel;
|
|
3730
|
+
try {
|
|
3731
|
+
const parent = await this.opts.adapter.getInstance(this.tenantId, parentId);
|
|
3732
|
+
if (!parent || parent.status !== "pending") return;
|
|
3733
|
+
const payload = {
|
|
3734
|
+
instanceId: parentId,
|
|
3735
|
+
documentId: parent.documentId,
|
|
3736
|
+
documentType: parent.documentType,
|
|
3737
|
+
timestamp: this.clock.now(),
|
|
3738
|
+
level: parentLevelNumber,
|
|
3739
|
+
childInstanceId: child.id,
|
|
3740
|
+
childTemplateName: child.templateName,
|
|
3741
|
+
outcome
|
|
3742
|
+
};
|
|
3743
|
+
if (outcome === "approved") {
|
|
3744
|
+
await this.completeSubWorkflowLevel(parentId, parentLevelNumber, child);
|
|
3745
|
+
} else {
|
|
3746
|
+
await this.rejectFromSubWorkflow(parentId, parentLevelNumber, child);
|
|
3747
|
+
}
|
|
3748
|
+
const refreshed = await this.opts.adapter.getInstance(this.tenantId, parentId);
|
|
3749
|
+
this.bus.emit("approval:subworkflow_completed", payload);
|
|
3750
|
+
if (refreshed) {
|
|
3751
|
+
await this.notifyAdapters("approval:subworkflow_completed", refreshed, payload);
|
|
3752
|
+
}
|
|
3753
|
+
} catch (err) {
|
|
3754
|
+
this.logger.error("subWorkflow: failed to propagate outcome to parent", err, {
|
|
3755
|
+
tenantId: this.tenantId,
|
|
3756
|
+
childInstanceId: child.id,
|
|
3757
|
+
parentInstanceId: parentId
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
/** Mark a sub-workflow level approved and advance the parent chain. */
|
|
3762
|
+
async completeSubWorkflowLevel(parentId, levelNumber, child) {
|
|
3763
|
+
let advanced = null;
|
|
3764
|
+
await this.withOptimisticRetry(parentId, async (parent) => {
|
|
3765
|
+
const level = parent.levels.find((l) => l.level === levelNumber);
|
|
3766
|
+
if (!level || level.status !== "pending") return parent;
|
|
3767
|
+
const now = this.clock.now();
|
|
3768
|
+
level.status = "approved";
|
|
3769
|
+
level.reminderDueAt = void 0;
|
|
3770
|
+
level.escalationDueAt = void 0;
|
|
3771
|
+
const auditEntry = {
|
|
3772
|
+
action: "subworkflow_completed",
|
|
3773
|
+
actorId: "system",
|
|
3774
|
+
level: levelNumber,
|
|
3775
|
+
timestamp: now,
|
|
3776
|
+
newValue: { childInstanceId: child.id, outcome: "approved" }
|
|
3777
|
+
};
|
|
3778
|
+
parent.auditLog.push(auditEntry);
|
|
3779
|
+
parent.updatedAt = now;
|
|
3780
|
+
const siblingsOpen = this.groupMembers(parent, level).some(
|
|
3781
|
+
(l) => l.status === "pending" || l.status === "waiting"
|
|
3782
|
+
);
|
|
3783
|
+
if (!siblingsOpen) {
|
|
3784
|
+
const nextGroup = this.findNextGroup(parent);
|
|
3785
|
+
if (nextGroup.length === 0) {
|
|
3786
|
+
parent.status = "approved";
|
|
3787
|
+
} else {
|
|
3788
|
+
await this.activateGroup(parent, nextGroup, now);
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
await this.opts.adapter.updateInstance(parent, parent.version);
|
|
3792
|
+
await this.opts.adapter.appendAuditEntry(this.tenantId, parentId, auditEntry);
|
|
3793
|
+
await this.runExternalAudit(parent, auditEntry);
|
|
3794
|
+
advanced = parent;
|
|
3795
|
+
return parent;
|
|
3796
|
+
});
|
|
3797
|
+
if (advanced) {
|
|
3798
|
+
const parent = advanced;
|
|
3799
|
+
if (parent.status === "approved") {
|
|
3800
|
+
this.bus.emit("approval:completed", parent);
|
|
3801
|
+
await this.notifyAdapters("approval:completed", parent, parent);
|
|
3802
|
+
await this.propagateToParent(parent);
|
|
3803
|
+
} else {
|
|
3804
|
+
await this.startSubWorkflows(parent);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
/** Reject a parent because the child approval it was waiting on did not succeed. */
|
|
3809
|
+
async rejectFromSubWorkflow(parentId, levelNumber, child) {
|
|
3810
|
+
let rejected = null;
|
|
3811
|
+
await this.withOptimisticRetry(parentId, async (parent) => {
|
|
3812
|
+
const level = parent.levels.find((l) => l.level === levelNumber);
|
|
3813
|
+
if (!level || level.status !== "pending") return parent;
|
|
3814
|
+
const now = this.clock.now();
|
|
3815
|
+
level.status = "rejected";
|
|
3816
|
+
level.reminderDueAt = void 0;
|
|
3817
|
+
level.escalationDueAt = void 0;
|
|
3818
|
+
parent.status = "rejected";
|
|
3819
|
+
parent.updatedAt = now;
|
|
3820
|
+
const auditEntry = {
|
|
3821
|
+
action: "subworkflow_completed",
|
|
3822
|
+
actorId: "system",
|
|
3823
|
+
level: levelNumber,
|
|
3824
|
+
timestamp: now,
|
|
3825
|
+
reason: `Child approval ${child.id} ended as "${child.status}".`,
|
|
3826
|
+
newValue: { childInstanceId: child.id, outcome: child.status }
|
|
3827
|
+
};
|
|
3828
|
+
parent.auditLog.push(auditEntry);
|
|
3829
|
+
await this.opts.adapter.updateInstance(parent, parent.version);
|
|
3830
|
+
await this.opts.adapter.appendAuditEntry(this.tenantId, parentId, auditEntry);
|
|
3831
|
+
await this.runExternalAudit(parent, auditEntry);
|
|
3832
|
+
rejected = parent;
|
|
3833
|
+
return parent;
|
|
3834
|
+
});
|
|
3835
|
+
if (rejected) {
|
|
3836
|
+
const parent = rejected;
|
|
3837
|
+
const payload = {
|
|
3838
|
+
instanceId: parent.id,
|
|
3839
|
+
documentId: parent.documentId,
|
|
3840
|
+
documentType: parent.documentType,
|
|
3841
|
+
timestamp: this.clock.now(),
|
|
3842
|
+
approverId: "system",
|
|
3843
|
+
level: levelNumber,
|
|
3844
|
+
reason: `Child approval ${child.id} ended as "${child.status}".`,
|
|
3845
|
+
returnTo: null
|
|
3846
|
+
};
|
|
3847
|
+
this.bus.emit("approval:rejected", payload);
|
|
3848
|
+
await this.notifyAdapters("approval:rejected", parent, payload);
|
|
3849
|
+
await this.propagateToParent(parent);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
/**
|
|
3853
|
+
* Work that must happen after a decision is durably recorded, not inside it.
|
|
3854
|
+
*
|
|
3855
|
+
* Both branches touch other instances — a newly opened level may spawn a
|
|
3856
|
+
* child approval, and a finished instance may be a child that owes its
|
|
3857
|
+
* outcome to a parent. Doing either inside the deciding instance's
|
|
3858
|
+
* compare-and-set would nest writes under a version guard that knows nothing
|
|
3859
|
+
* about them, so a slow child template would surface as a spurious conflict
|
|
3860
|
+
* on the decision the user just made.
|
|
3861
|
+
*/
|
|
3862
|
+
async afterDecision(instance) {
|
|
3863
|
+
if (TERMINAL_STATUSES.has(instance.status)) {
|
|
3864
|
+
await this.propagateToParent(instance);
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
await this.startSubWorkflows(instance);
|
|
3868
|
+
}
|
|
3498
3869
|
findNextLevel(instance) {
|
|
3499
3870
|
return instance.levels.find((l) => l.level > instance.currentLevel && l.status === "waiting") ?? null;
|
|
3500
3871
|
}
|
|
@@ -3833,6 +4204,6 @@ function weekendCalendar(options = {}) {
|
|
|
3833
4204
|
};
|
|
3834
4205
|
}
|
|
3835
4206
|
|
|
3836
|
-
export { ApprovalConflictError, ApprovalEngine, ApprovalError, ApprovalForbiddenError, ApprovalNotFoundError, ApprovalTemplateNotFoundError, ApprovalValidationError, EscalationScheduler, MemoryAdapter, defaultIdGenerator, noopLogger, systemClock, toComparableNumber, validateConditionExpression, weekendCalendar };
|
|
4207
|
+
export { ApprovalConflictError, ApprovalEngine, ApprovalError, ApprovalForbiddenError, ApprovalNotFoundError, ApprovalTemplateNotFoundError, ApprovalValidationError, EscalationScheduler, MemoryAdapter, TEMPLATE_BUNDLE_VERSION, defaultIdGenerator, noopLogger, systemClock, toComparableNumber, validateConditionExpression, weekendCalendar };
|
|
3837
4208
|
//# sourceMappingURL=index.js.map
|
|
3838
4209
|
//# sourceMappingURL=index.js.map
|