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.
Files changed (81) hide show
  1. package/CHANGELOG.md +20 -6
  2. package/README.md +1 -1
  3. package/dist/{ApprovalEngine-XgGBQLC_.d.cts → ApprovalEngine-D0_F5OPZ.d.cts} +35 -10
  4. package/dist/{ApprovalEngine-Dqe-rC6K.d.ts → ApprovalEngine-D4bXQrKD.d.ts} +35 -10
  5. package/dist/Clock-D8Qp39q_.d.cts +8 -0
  6. package/dist/Clock-D8Qp39q_.d.ts +8 -0
  7. package/dist/{IAuditAdapter-Dt8qjkQL.d.cts → IAuditAdapter-D9WE0t7A.d.cts} +2 -1
  8. package/dist/{IAuditAdapter-DkbyFFjb.d.ts → IAuditAdapter-FI_RF_XU.d.ts} +2 -1
  9. package/dist/{IAuthorizationPolicy-YJ_08Mof.d.cts → IAuthorizationPolicy-BKKw7jGN.d.ts} +6 -1
  10. package/dist/{IAuthorizationPolicy-eo6glbpE.d.ts → IAuthorizationPolicy-DDmjeYV-.d.cts} +6 -1
  11. package/dist/{IMetricsAdapter-DWq8IFaf.d.cts → IMetricsAdapter-WROxYSXd.d.cts} +1 -0
  12. package/dist/{IMetricsAdapter-DWq8IFaf.d.ts → IMetricsAdapter-WROxYSXd.d.ts} +1 -0
  13. package/dist/{INotificationAdapter-BTO9FSKy.d.ts → INotificationAdapter-CLROEfRd.d.ts} +26 -1
  14. package/dist/{INotificationAdapter-XbD69gss.d.cts → INotificationAdapter-J0wSPLIC.d.cts} +26 -1
  15. package/dist/{IOperationMiddleware-CX7lo4lp.d.ts → IOperationMiddleware-B32a2dwP.d.ts} +15 -2
  16. package/dist/{IOperationMiddleware-BBLk8MIz.d.cts → IOperationMiddleware-UUKqztxM.d.cts} +15 -2
  17. package/dist/{ISchedulerAdapter-DKv_QjVN.d.cts → ISchedulerAdapter-CLMdBA-5.d.cts} +1 -0
  18. package/dist/{ISchedulerAdapter-DKv_QjVN.d.ts → ISchedulerAdapter-CLMdBA-5.d.ts} +1 -0
  19. package/dist/{IStorageAdapter-CdXMggPK.d.ts → IStorageAdapter-DSKJ1onF.d.ts} +14 -1
  20. package/dist/{IStorageAdapter-Cjw5zDwQ.d.cts → IStorageAdapter-Dhrc4ES6.d.cts} +14 -1
  21. package/dist/{Logger-BplhlU7l.d.cts → Logger-hqFbBCtB.d.cts} +7 -0
  22. package/dist/{Logger-BplhlU7l.d.ts → Logger-hqFbBCtB.d.ts} +7 -0
  23. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  24. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  25. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  26. package/dist/adapters/MemoryAdapter.js.map +1 -1
  27. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  28. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  29. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  30. package/dist/adapters/PostgresAdapter.js.map +1 -1
  31. package/dist/index.cjs +51 -8
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +14 -14
  34. package/dist/index.d.ts +14 -14
  35. package/dist/index.js +51 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/{instance-uUQh5XW3.d.cts → instance-CUZcHWK2.d.cts} +28 -0
  38. package/dist/{instance-uUQh5XW3.d.ts → instance-CUZcHWK2.d.ts} +28 -0
  39. package/dist/nestjs.cjs +61 -7
  40. package/dist/nestjs.cjs.map +1 -1
  41. package/dist/nestjs.d.cts +22 -11
  42. package/dist/nestjs.d.ts +22 -11
  43. package/dist/nestjs.js +61 -7
  44. package/dist/nestjs.js.map +1 -1
  45. package/dist/plugins/audit.cjs.map +1 -1
  46. package/dist/plugins/audit.d.cts +3 -3
  47. package/dist/plugins/audit.d.ts +3 -3
  48. package/dist/plugins/audit.js.map +1 -1
  49. package/dist/plugins/metrics.cjs.map +1 -1
  50. package/dist/plugins/metrics.d.cts +2 -2
  51. package/dist/plugins/metrics.d.ts +2 -2
  52. package/dist/plugins/metrics.js.map +1 -1
  53. package/dist/plugins/notify.cjs.map +1 -1
  54. package/dist/plugins/notify.d.cts +4 -4
  55. package/dist/plugins/notify.d.ts +4 -4
  56. package/dist/plugins/notify.js.map +1 -1
  57. package/dist/plugins/resilience.cjs.map +1 -1
  58. package/dist/plugins/resilience.d.cts +5 -5
  59. package/dist/plugins/resilience.d.ts +5 -5
  60. package/dist/plugins/resilience.js.map +1 -1
  61. package/dist/plugins/scheduler.cjs.map +1 -1
  62. package/dist/plugins/scheduler.d.cts +3 -3
  63. package/dist/plugins/scheduler.d.ts +3 -3
  64. package/dist/plugins/scheduler.js.map +1 -1
  65. package/dist/plugins/tracing.cjs.map +1 -1
  66. package/dist/plugins/tracing.d.cts +4 -2
  67. package/dist/plugins/tracing.d.ts +4 -2
  68. package/dist/plugins/tracing.js.map +1 -1
  69. package/dist/plugins/webhook.cjs.map +1 -1
  70. package/dist/plugins/webhook.d.cts +4 -4
  71. package/dist/plugins/webhook.d.ts +4 -4
  72. package/dist/plugins/webhook.js.map +1 -1
  73. package/dist/testing.cjs +50 -7
  74. package/dist/testing.cjs.map +1 -1
  75. package/dist/testing.d.cts +11 -11
  76. package/dist/testing.d.ts +11 -11
  77. package/dist/testing.js +50 -7
  78. package/dist/testing.js.map +1 -1
  79. package/package.json +1 -1
  80. package/dist/Clock-3FnOczFJ.d.cts +0 -6
  81. package/dist/Clock-3FnOczFJ.d.ts +0 -6
package/dist/index.cjs CHANGED
@@ -556,6 +556,7 @@ var TemplateRegistry = class {
556
556
  delete resolved.removeLevels;
557
557
  return resolved;
558
558
  }
559
+ /** Register a new template. Throws if a template with this name already exists for the tenant. */
559
560
  async define(config) {
560
561
  const existing = await this.adapter.getTemplate(this.tenantId, config.name);
561
562
  if (existing) {
@@ -592,11 +593,13 @@ var TemplateRegistry = class {
592
593
  await this.adapter.saveTemplate(updated);
593
594
  return newId;
594
595
  }
596
+ /** Fetch a template by name. Throws if no template with this name exists for the tenant. */
595
597
  async get(name) {
596
598
  const template = await this.adapter.getTemplate(this.tenantId, name);
597
599
  if (!template) throw new ApprovalTemplateNotFoundError(name);
598
600
  return template;
599
601
  }
602
+ /** List every template registered for the tenant. */
600
603
  async list() {
601
604
  return this.adapter.listTemplates(this.tenantId);
602
605
  }
@@ -636,6 +639,32 @@ var LevelResolver = class {
636
639
  for (const [name, fn] of this.resolvers) target.register(name, fn);
637
640
  for (const [name, fn] of this.approverTypes) target.registerApproverType(name, fn);
638
641
  }
642
+ /**
643
+ * Resolve a level's `approvers` config down to a deduplicated list of user
644
+ * ids, applying out-of-office cover last so a substitute never sees a stale
645
+ * approver id.
646
+ *
647
+ * @param approvers - The level's approver configs (`user`, `role`,
648
+ * `dynamic`, or a custom type registered via {@link registerApproverType}).
649
+ * @param submittedBy - Passed through to `dynamic` resolvers and custom
650
+ * approver-type functions as routing context.
651
+ * @param data - The instance's document data, passed through the same way.
652
+ * @param orgProvider - Required to resolve `role` approvers; omit only when
653
+ * the template uses no `role` approvers.
654
+ * @param outOfOffice - Optional cover lookup, consulted after every other
655
+ * approver type has resolved.
656
+ * @param at - The moment out-of-office cover is resolved for. Defaults to
657
+ * now.
658
+ * @param substitutions - Filled with `original -> stand-in` for each
659
+ * approver replaced by out-of-office cover, so the caller can carry
660
+ * anything keyed by the original id (such as a weighted level's vote
661
+ * weight) across to the substitute.
662
+ * @returns The resolved, deduplicated approver ids (post-substitution).
663
+ * @throws {ApprovalValidationError} If no approvers resolve at all, a
664
+ * `role` approver is configured without an `orgProvider`, a `dynamic`
665
+ * approver names a resolver that was never registered, or a custom
666
+ * approver type was never registered.
667
+ */
639
668
  async resolveApprovers(approvers, submittedBy, data, orgProvider, outOfOffice, at, substitutions) {
640
669
  const resolved = [];
641
670
  for (const approver of approvers) {
@@ -1047,7 +1076,7 @@ function evaluateConditions(conditions, data) {
1047
1076
  function assertStatus(instance, expected) {
1048
1077
  if (instance.status !== expected) {
1049
1078
  throw new ApprovalError(
1050
- `Expected instance status "${expected}" but got "${instance.status}".`,
1079
+ `Instance "${instance.id}": expected status "${expected}" but got "${instance.status}".`,
1051
1080
  "INVALID_STATUS"
1052
1081
  );
1053
1082
  }
@@ -1055,7 +1084,7 @@ function assertStatus(instance, expected) {
1055
1084
  function assertApproverOnLevel(level, approverId) {
1056
1085
  if (!level.approverIds.includes(approverId)) {
1057
1086
  throw new ApprovalForbiddenError(
1058
- `User "${approverId}" is not an approver for level ${level.level}.`
1087
+ `User "${approverId}" is not an approver for level ${level.level} ("${level.name}").`
1059
1088
  );
1060
1089
  }
1061
1090
  }
@@ -2099,7 +2128,7 @@ var ApprovalEngine = class _ApprovalEngine {
2099
2128
  await this.withOptimisticRetry(instanceId, async (instance) => {
2100
2129
  assertStatus(instance, "pending");
2101
2130
  if (opts.fromApprover === opts.toApprover) {
2102
- throw new ApprovalForbiddenError("Cannot delegate to yourself.");
2131
+ throw new ApprovalForbiddenError(`Cannot delegate to yourself: "${opts.fromApprover}".`);
2103
2132
  }
2104
2133
  const level = this.resolveActorLevel(instance, opts.fromApprover, opts.level);
2105
2134
  this.inheritWeight(level, opts.fromApprover, opts.toApprover);
@@ -2182,7 +2211,9 @@ var ApprovalEngine = class _ApprovalEngine {
2182
2211
  return this.withOptimisticRetry(instanceId, async (instance) => {
2183
2212
  assertStatus(instance, "pending");
2184
2213
  if (opts.fromApprover === opts.toApprover) {
2185
- throw new ApprovalForbiddenError("Cannot reassign an approver to themselves.");
2214
+ throw new ApprovalForbiddenError(
2215
+ `Cannot reassign an approver to themselves: "${opts.fromApprover}".`
2216
+ );
2186
2217
  }
2187
2218
  const level = this.resolveActorLevel(instance, opts.fromApprover, opts.level);
2188
2219
  this.inheritWeight(level, opts.fromApprover, opts.toApprover);
@@ -3008,7 +3039,15 @@ var ApprovalEngine = class _ApprovalEngine {
3008
3039
  }
3009
3040
  const levelNums = new Set(allLevelCfgs.map((l) => l.level));
3010
3041
  if (levelNums.size !== allLevelCfgs.length) {
3011
- throw new ApprovalValidationError("Duplicate level numbers after condition evaluation.");
3042
+ const seen = /* @__PURE__ */ new Set();
3043
+ const duplicates = /* @__PURE__ */ new Set();
3044
+ for (const l of allLevelCfgs) {
3045
+ if (seen.has(l.level)) duplicates.add(l.level);
3046
+ seen.add(l.level);
3047
+ }
3048
+ throw new ApprovalValidationError(
3049
+ `Duplicate level numbers after condition evaluation: ${[...duplicates].join(", ")}.`
3050
+ );
3012
3051
  }
3013
3052
  const now = this.clock.now();
3014
3053
  const newInstanceId = this.generateId("inst");
@@ -3380,7 +3419,9 @@ var ApprovalEngine = class _ApprovalEngine {
3380
3419
  );
3381
3420
  }
3382
3421
  if (opts.overriddenBy === instance.submittedBy) {
3383
- throw new ApprovalForbiddenError("Override cannot be performed by the original submitter.");
3422
+ throw new ApprovalForbiddenError(
3423
+ `Override cannot be performed by the original submitter: "${opts.overriddenBy}".`
3424
+ );
3384
3425
  }
3385
3426
  await this.runAuthorizationPolicy({
3386
3427
  operation: "override",
@@ -3874,7 +3915,9 @@ var ApprovalEngine = class _ApprovalEngine {
3874
3915
  const dryRun = opts.dryRun ?? false;
3875
3916
  const limit = opts.limit ?? DEFAULT_PURGE_LIMIT;
3876
3917
  if (!(opts.olderThan instanceof Date) || Number.isNaN(opts.olderThan.getTime())) {
3877
- throw new ApprovalValidationError("purgeInstances requires a valid olderThan date.");
3918
+ throw new ApprovalValidationError(
3919
+ `purgeInstances requires a valid olderThan date, got ${String(opts.olderThan)}.`
3920
+ );
3878
3921
  }
3879
3922
  const requested = opts.statuses ?? [...TERMINAL_STATUSES];
3880
3923
  const nonTerminal = requested.filter((st) => !TERMINAL_STATUSES.has(st));
@@ -5170,7 +5213,7 @@ function businessHoursCalendar(options = {}) {
5170
5213
  return cursor;
5171
5214
  }
5172
5215
  throw new Error(
5173
- "businessHoursCalendar: no working day found within 10 years of the start date."
5216
+ `businessHoursCalendar: no working day found within 10 years of ${from.toISOString()} \u2014 check weekendDays and holidays.`
5174
5217
  );
5175
5218
  };
5176
5219
  const base = weekendCalendar(options);