hierarchical-approval 1.0.0 → 1.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +59 -0
  3. package/dist/{ApprovalEngine-DzHUWix1.d.cts → ApprovalEngine-CdtOJOsu.d.cts} +22 -7
  4. package/dist/{ApprovalEngine-DB62pkEk.d.ts → ApprovalEngine-DRC-o30z.d.ts} +22 -7
  5. package/dist/{IAuditAdapter-zVO7XpWm.d.cts → IAuditAdapter-B2I3CjaJ.d.cts} +1 -1
  6. package/dist/{IAuditAdapter-RKagmrfi.d.ts → IAuditAdapter-GixC9zS1.d.ts} +1 -1
  7. package/dist/{IAuthorizationPolicy-DEDQnNzq.d.ts → IAuthorizationPolicy-4ge9Gnxj.d.ts} +1 -1
  8. package/dist/{IAuthorizationPolicy-Dx9NzWx8.d.cts → IAuthorizationPolicy-BHDZK9QL.d.cts} +1 -1
  9. package/dist/{IMetricsAdapter-t7QpfA1I.d.cts → IMetricsAdapter-QvyaKQCc.d.cts} +1 -1
  10. package/dist/{IMetricsAdapter-t7QpfA1I.d.ts → IMetricsAdapter-QvyaKQCc.d.ts} +1 -1
  11. package/dist/{INotificationAdapter-u5ZE7wS6.d.ts → INotificationAdapter-BKMeGmaG.d.cts} +11 -2
  12. package/dist/{INotificationAdapter-Dld84ihs.d.cts → INotificationAdapter-BmSeB7FZ.d.ts} +11 -2
  13. package/dist/{IOperationMiddleware-BSfjsnTe.d.cts → IOperationMiddleware-BLw9Oc-S.d.cts} +1 -1
  14. package/dist/{IOperationMiddleware-DndObWfg.d.ts → IOperationMiddleware-BW9t8wPn.d.ts} +1 -1
  15. package/dist/{IStorageAdapter-D-yIRiET.d.cts → IStorageAdapter-CKV_FZFV.d.cts} +1 -1
  16. package/dist/{IStorageAdapter-BYlHnm7_.d.ts → IStorageAdapter-DQJmR6Mt.d.ts} +1 -1
  17. package/dist/adapters/MemoryAdapter.cjs +7 -3
  18. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  19. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  20. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  21. package/dist/adapters/MemoryAdapter.js +7 -3
  22. package/dist/adapters/MemoryAdapter.js.map +1 -1
  23. package/dist/adapters/PostgresAdapter.cjs +6 -0
  24. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  25. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  26. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  27. package/dist/adapters/PostgresAdapter.js +6 -0
  28. package/dist/adapters/PostgresAdapter.js.map +1 -1
  29. package/dist/index.cjs +223 -22
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +13 -10
  32. package/dist/index.d.ts +13 -10
  33. package/dist/index.js +223 -22
  34. package/dist/index.js.map +1 -1
  35. package/dist/{instance-iaAIdMbs.d.cts → instance-BsM0DUPf.d.cts} +36 -1
  36. package/dist/{instance-iaAIdMbs.d.ts → instance-BsM0DUPf.d.ts} +36 -1
  37. package/dist/nestjs.cjs +216 -19
  38. package/dist/nestjs.cjs.map +1 -1
  39. package/dist/nestjs.d.cts +8 -8
  40. package/dist/nestjs.d.ts +8 -8
  41. package/dist/nestjs.js +216 -19
  42. package/dist/nestjs.js.map +1 -1
  43. package/dist/plugins/audit.d.cts +2 -2
  44. package/dist/plugins/audit.d.ts +2 -2
  45. package/dist/plugins/metrics.cjs +4 -3
  46. package/dist/plugins/metrics.cjs.map +1 -1
  47. package/dist/plugins/metrics.d.cts +1 -1
  48. package/dist/plugins/metrics.d.ts +1 -1
  49. package/dist/plugins/metrics.js +4 -3
  50. package/dist/plugins/metrics.js.map +1 -1
  51. package/dist/plugins/notify.d.cts +2 -2
  52. package/dist/plugins/notify.d.ts +2 -2
  53. package/dist/plugins/resilience.d.cts +3 -3
  54. package/dist/plugins/resilience.d.ts +3 -3
  55. package/dist/plugins/tracing.d.cts +2 -2
  56. package/dist/plugins/tracing.d.ts +2 -2
  57. package/dist/plugins/webhook.d.cts +2 -2
  58. package/dist/plugins/webhook.d.ts +2 -2
  59. package/dist/testing.cjs +223 -22
  60. package/dist/testing.cjs.map +1 -1
  61. package/dist/testing.d.cts +8 -8
  62. package/dist/testing.d.ts +8 -8
  63. package/dist/testing.js +223 -22
  64. package/dist/testing.js.map +1 -1
  65. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,93 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  _Nothing yet._
9
9
 
10
+ ## [1.2.0] - 2026-09-04
11
+
12
+ ### Added — template inheritance
13
+
14
+ - **`extends` derives one template from another.** ERP tenants run many
15
+ near-identical workflows — one per region, legal entity or document class —
16
+ that share a spine and differ in a level or two. Each had to be written out in
17
+ full, so a change to the shared part meant editing every copy and hoping none
18
+ were missed.
19
+
20
+ ```ts
21
+ await engine.defineTemplate({
22
+ name: 'PO-EU',
23
+ extends: 'PO-base',
24
+ documentType: 'purchase_order',
25
+ removeLevels: [2],
26
+ levels: [{ level: 3, approvers: [{ type: 'user', userId: 'eu-fin' }] }],
27
+ });
28
+ ```
29
+
30
+ Levels are keyed by number: a child level overrides the base level *field by
31
+ field* (so a derived template can swap just the approvers and inherit name,
32
+ mode and deadlines), child-only levels are appended, and `removeLevels` drops
33
+ inherited ones. `conditions` are replaced wholesale when supplied — merging
34
+ two rule lists would produce a chain neither author intended. `escalation`,
35
+ `slaDeadlineDays` and `allowOverride` are inherited unless the child sets them.
36
+
37
+ - **Resolution happens once, at define time, and the flattened result is what
38
+ gets stored.** Resolving lazily on read would mean editing a base silently
39
+ reshapes every derived template — and every in-flight instance running against
40
+ one — which is exactly the surprise `templateSnapshot` exists to prevent
41
+ elsewhere in the engine. A base that itself extends something is already
42
+ flattened, so chains resolve naturally and cycles cannot form.
43
+
44
+ - **Validation runs on the flattened template**, not the fragment: a derived
45
+ config carrying no levels of its own is valid (it inherits the chain), while
46
+ one whose `removeLevels` strips the chain entirely is rejected at definition
47
+ time. `extends` and `removeLevels` are directives for the call and are not
48
+ persisted.
49
+
50
+ ## [1.1.0] - 2026-09-04
51
+
52
+ ### Added — approval reminders
53
+
54
+ - **Levels can nudge their pending approvers before escalating.** Escalation
55
+ reassigns work, which is the wrong instrument for an approval that is merely
56
+ late; the only alternative was an out-of-band cron job reading the database.
57
+
58
+ ```ts
59
+ { level: 1, name: 'Manager', ...,
60
+ reminderAfterDays: 2, // first nudge two days after the level opens
61
+ reminderEveryDays: 1, // then daily
62
+ maxReminders: 3 } // default 3
63
+ ```
64
+
65
+ Emits `approval:reminder` (`level`, `recipients`, `reminderNumber`), records a
66
+ `reminded` audit entry, and increments `approval.reminded`. Recipients exclude
67
+ anyone who has already voted, so a half-decided quorum level stops pestering
68
+ the approvers who did their part. Reminders stop when the level closes or the
69
+ cap is reached, and never change who can approve or when the level escalates.
70
+
71
+ `validateTemplate()` rejects non-positive intervals, a non-integer
72
+ `maxReminders`, and `reminderEveryDays` without `reminderAfterDays` — which
73
+ would configure a repeat for a reminder that never fires.
74
+
75
+ New export: `ReminderEvent`. `EscalationSchedulerOpts` gains `onRemind`.
76
+
77
+ ### Fixed — escalation could not reach every branch of a parallel group
78
+
79
+ - **Only the lowest-numbered branch of a parallel group could escalate.** Both
80
+ the scheduler and `MemoryAdapter.getOverdueInstances` matched levels against
81
+ `instance.currentLevel`, which names just one level of a group. An overdue
82
+ upper branch was never fetched and never escalated — it would wait forever.
83
+ Introduced with parallel groups in 1.0.0.
84
+
85
+ Escalation now considers every open branch, and `escalateInternal` takes the
86
+ level to escalate rather than assuming the current one.
87
+
88
+ This also settles a pre-existing disagreement between the adapters:
89
+ `PostgresAdapter` already scanned *all* levels for `escalationDueAt` while
90
+ `MemoryAdapter` scanned one, so the same template escalated differently
91
+ depending on which adapter was in use. Both now scan every open branch.
92
+
93
+ - **`getOverdueInstances` also reports due reminders** in both adapters —
94
+ without which the scheduler would never see a reminder-due instance and no
95
+ reminder would ever be sent.
96
+
10
97
  ## [1.0.0] - 2026-09-04
11
98
 
12
99
  First stable release. The public API is now considered settled: breaking changes
package/README.md CHANGED
@@ -399,6 +399,65 @@ engine.registerConditionOperator('between', (actual, expected) => {
399
399
  });
400
400
  ```
401
401
 
402
+ ### Template inheritance
403
+
404
+ ERP tenants run many near-identical workflows — one per region, entity or
405
+ document class — that share a spine and differ in a level or two. `extends`
406
+ derives one template from another:
407
+
408
+ ```ts
409
+ await engine.defineTemplate({
410
+ name: 'PO-EU',
411
+ extends: 'PO-base',
412
+ documentType: 'purchase_order',
413
+ removeLevels: [2], // drop an inherited level
414
+ levels: [
415
+ { level: 3, approvers: [{ type: 'user', userId: 'eu-fin' }] }, // override just the approvers
416
+ { level: 4, name: 'EU Compliance', approvers: [...], mode: 'any' }, // add a new level
417
+ ],
418
+ });
419
+ ```
420
+
421
+ | Field | Merge rule |
422
+ |---|---|
423
+ | `levels` | Keyed by level number: a child level overrides the base level *field by field*, child-only levels are appended, `removeLevels` drops inherited ones |
424
+ | `conditions` | Replaced wholesale when the child supplies them |
425
+ | `escalation`, `slaDeadlineDays`, `allowOverride` | Child value if set, otherwise inherited |
426
+
427
+ **Resolution happens once, at define time, and the flattened result is stored.**
428
+ Editing a base later never silently reshapes a derived template or an instance
429
+ already running against one — the same insulation `templateSnapshot` gives
430
+ in-flight instances. A base that itself extends something is already flattened,
431
+ so chains resolve naturally and cycles cannot form.
432
+
433
+ Validation runs on the *flattened* template, so a derived config that strips its
434
+ whole chain is rejected at definition time.
435
+
436
+ ### Reminders
437
+
438
+ Escalation reassigns work; a reminder only nudges, which is usually what an
439
+ overdue-but-not-yet-critical approval needs. Configure it per level:
440
+
441
+ ```ts
442
+ {
443
+ level: 1,
444
+ name: 'Manager',
445
+ approvers: [{ type: 'user', userId: 'mgr-1' }],
446
+ mode: 'any',
447
+ reminderAfterDays: 2, // first nudge two days after the level opens
448
+ reminderEveryDays: 1, // then daily
449
+ maxReminders: 3, // default is 3
450
+ }
451
+ ```
452
+
453
+ Emits `approval:reminder` with `level`, `recipients` and `reminderNumber`, and
454
+ records a `reminded` audit entry. Recipients exclude anyone who has already
455
+ voted, so a half-decided quorum level stops pestering the approvers who did
456
+ their part. Reminders stop when the level closes or the cap is reached, and they
457
+ never change who can approve or when the level escalates.
458
+
459
+ Requires the escalation scheduler to be running (it is by default).
460
+
402
461
  ### Parallel branch groups
403
462
 
404
463
  Give levels the same `group` name and they activate together, joining before
@@ -1,14 +1,14 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-D-yIRiET.cjs';
2
- import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-iaAIdMbs.cjs';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Dld84ihs.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-CKV_FZFV.cjs';
2
+ import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-BsM0DUPf.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BKMeGmaG.cjs';
4
4
  import { z } from 'zod';
5
5
  import { L as Logger } from './Logger-BplhlU7l.cjs';
6
6
  import { C as Clock } from './Clock-3FnOczFJ.cjs';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BSfjsnTe.cjs';
8
- import { I as IAuditAdapter } from './IAuditAdapter-zVO7XpWm.cjs';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-t7QpfA1I.cjs';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BLw9Oc-S.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-B2I3CjaJ.cjs';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-QvyaKQCc.cjs';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-Dx9NzWx8.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-BHDZK9QL.cjs';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -521,6 +521,21 @@ declare class ApprovalEngine {
521
521
  private findNextGroup;
522
522
  /** Resolve approvers for a group, set its deadlines, and mark it pending. */
523
523
  private activateGroup;
524
+ /**
525
+ * Set (or clear) the next reminder deadline for a level that has just opened.
526
+ *
527
+ * Called wherever a level becomes pending, so a level activated by submit,
528
+ * by advancing, or by a group activation all schedule alike.
529
+ */
530
+ private scheduleReminder;
531
+ /**
532
+ * Nudge the approvers who still owe a decision on a pending level.
533
+ *
534
+ * Reminders never change who can approve or when the level escalates — they
535
+ * only notify. Recipients exclude anyone who has already voted, so a
536
+ * half-decided quorum level stops pestering the approvers who did their part.
537
+ */
538
+ private sendReminder;
524
539
  private findNextLevel;
525
540
  private findPreviousLevel;
526
541
  private guardBulkSize;
@@ -1,14 +1,14 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-BYlHnm7_.js';
2
- import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-iaAIdMbs.js';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-u5ZE7wS6.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DQJmR6Mt.js';
2
+ import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-BsM0DUPf.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BmSeB7FZ.js';
4
4
  import { z } from 'zod';
5
5
  import { L as Logger } from './Logger-BplhlU7l.js';
6
6
  import { C as Clock } from './Clock-3FnOczFJ.js';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-DndObWfg.js';
8
- import { I as IAuditAdapter } from './IAuditAdapter-RKagmrfi.js';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-t7QpfA1I.js';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BW9t8wPn.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-GixC9zS1.js';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-QvyaKQCc.js';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DEDQnNzq.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-4ge9Gnxj.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -521,6 +521,21 @@ declare class ApprovalEngine {
521
521
  private findNextGroup;
522
522
  /** Resolve approvers for a group, set its deadlines, and mark it pending. */
523
523
  private activateGroup;
524
+ /**
525
+ * Set (or clear) the next reminder deadline for a level that has just opened.
526
+ *
527
+ * Called wherever a level becomes pending, so a level activated by submit,
528
+ * by advancing, or by a group activation all schedule alike.
529
+ */
530
+ private scheduleReminder;
531
+ /**
532
+ * Nudge the approvers who still owe a decision on a pending level.
533
+ *
534
+ * Reminders never change who can approve or when the level escalates — they
535
+ * only notify. Recipients exclude anyone who has already voted, so a
536
+ * half-decided quorum level stops pestering the approvers who did their part.
537
+ */
538
+ private sendReminder;
524
539
  private findNextLevel;
525
540
  private findPreviousLevel;
526
541
  private guardBulkSize;
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-iaAIdMbs.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BsM0DUPf.cjs';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-iaAIdMbs.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BsM0DUPf.js';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-iaAIdMbs.js';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BsM0DUPf.js';
2
2
 
3
3
  interface AuthorizationContext {
4
4
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData';
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-iaAIdMbs.cjs';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BsM0DUPf.cjs';
2
2
 
3
3
  interface AuthorizationContext {
4
4
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData';
@@ -1,4 +1,4 @@
1
- type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
1
+ type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.reminded' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
2
  interface IMetricsAdapter {
3
3
  /** Increment a counter. Synchronous — never awaited. */
4
4
  increment(metric: MetricName, labels?: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
1
+ type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.reminded' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
2
  interface IMetricsAdapter {
3
3
  /** Increment a counter. Synchronous — never awaited. */
4
4
  increment(metric: MetricName, labels?: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-iaAIdMbs.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BsM0DUPf.cjs';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -78,6 +78,14 @@ interface DataUpdatedEvent extends ApprovalEvent {
78
78
  /** Level numbers removed from the future chain by re-evaluating conditions. */
79
79
  removedLevels: number[];
80
80
  }
81
+ /** Emitted when a pending level's approvers are nudged. */
82
+ interface ReminderEvent extends ApprovalEvent {
83
+ level: number;
84
+ /** Approvers who still owe a decision on this level. */
85
+ recipients: string[];
86
+ /** 1 for the first reminder on this level, 2 for the next, and so on. */
87
+ reminderNumber: number;
88
+ }
81
89
  interface ApprovalEventMap {
82
90
  'approval:submitted': SubmittedEvent;
83
91
  'approval:approved': ApprovedEvent;
@@ -93,6 +101,7 @@ interface ApprovalEventMap {
93
101
  'approval:expired': ExpiredEvent;
94
102
  'approval:sla_breached': SlaBreachedEvent;
95
103
  'approval:data_updated': DataUpdatedEvent;
104
+ 'approval:reminder': ReminderEvent;
96
105
  }
97
106
  type ApprovalEventName = keyof ApprovalEventMap;
98
107
  interface HistoryEntry extends AuditEntry {
@@ -116,4 +125,4 @@ interface INotificationAdapter {
116
125
  notify(event: NotificationEvent): Promise<void>;
117
126
  }
118
127
 
119
- export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ResubmittedEvent as g, SubmittedEvent as h };
128
+ export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ReminderEvent as g, ResubmittedEvent as h, SubmittedEvent as i };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-iaAIdMbs.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BsM0DUPf.js';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -78,6 +78,14 @@ interface DataUpdatedEvent extends ApprovalEvent {
78
78
  /** Level numbers removed from the future chain by re-evaluating conditions. */
79
79
  removedLevels: number[];
80
80
  }
81
+ /** Emitted when a pending level's approvers are nudged. */
82
+ interface ReminderEvent extends ApprovalEvent {
83
+ level: number;
84
+ /** Approvers who still owe a decision on this level. */
85
+ recipients: string[];
86
+ /** 1 for the first reminder on this level, 2 for the next, and so on. */
87
+ reminderNumber: number;
88
+ }
81
89
  interface ApprovalEventMap {
82
90
  'approval:submitted': SubmittedEvent;
83
91
  'approval:approved': ApprovedEvent;
@@ -93,6 +101,7 @@ interface ApprovalEventMap {
93
101
  'approval:expired': ExpiredEvent;
94
102
  'approval:sla_breached': SlaBreachedEvent;
95
103
  'approval:data_updated': DataUpdatedEvent;
104
+ 'approval:reminder': ReminderEvent;
96
105
  }
97
106
  type ApprovalEventName = keyof ApprovalEventMap;
98
107
  interface HistoryEntry extends AuditEntry {
@@ -116,4 +125,4 @@ interface INotificationAdapter {
116
125
  notify(event: NotificationEvent): Promise<void>;
117
126
  }
118
127
 
119
- export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ResubmittedEvent as g, SubmittedEvent as h };
128
+ export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ReminderEvent as g, ResubmittedEvent as h, SubmittedEvent as i };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-iaAIdMbs.cjs';
1
+ import { a as ApprovalInstance } from './instance-BsM0DUPf.cjs';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-iaAIdMbs.js';
1
+ import { a as ApprovalInstance } from './instance-BsM0DUPf.js';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-iaAIdMbs.cjs';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BsM0DUPf.cjs';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-iaAIdMbs.js';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BsM0DUPf.js';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -129,14 +129,18 @@ var MemoryAdapter = class {
129
129
  if (i.tenantId !== tenantId || i.status !== "pending") return false;
130
130
  if (filter.documentType && i.documentType !== filter.documentType) return false;
131
131
  if (filter.submittedBy && i.submittedBy !== filter.submittedBy) return false;
132
- const currentLevel = i.levels.find((l) => l.level === i.currentLevel);
133
- const hasOverdueEscalation = currentLevel?.escalationDueAt != null && new Date(currentLevel.escalationDueAt) <= asOf;
132
+ const hasOverdueEscalation = i.levels.some(
133
+ (l) => l.escalationDueAt != null && new Date(l.escalationDueAt) <= asOf
134
+ );
134
135
  const isExpired = i.expiresAt != null && new Date(i.expiresAt) <= asOf;
135
136
  const hasSLABreach = i.slaDeadlineAt != null && new Date(i.slaDeadlineAt) <= asOf && !i.slaBreachedAt;
136
137
  const hasDelegationExpiry = i.levels.some(
137
138
  (l) => l.status === "pending" && l.delegatedUntil != null && new Date(l.delegatedUntil) <= asOf && l.delegatedFrom != null
138
139
  );
139
- return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry;
140
+ const hasDueReminder = i.levels.some(
141
+ (l) => l.status === "pending" && l.reminderDueAt != null && new Date(l.reminderDueAt) <= asOf
142
+ );
143
+ return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry || hasDueReminder;
140
144
  }).map((i) => reviveDates(deepClone(i)));
141
145
  }
142
146
  async getInstancesByCursor(tenantId, filter, opts) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/adapters/MemoryAdapter.ts"],"names":[],"mappings":"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","file":"MemoryAdapter.cjs","sourcesContent":["export class ApprovalError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'ApprovalError';\n }\n\n toJSON(): { code: string; message: string; name: string } {\n return { code: this.code, message: this.message, name: this.name };\n }\n\n toHttpStatus(): number {\n const map: Record<string, number> = {\n NOT_FOUND: 404,\n CONFLICT: 409,\n FORBIDDEN: 403,\n VALIDATION: 422,\n TEMPLATE_NOT_FOUND: 404,\n };\n return map[this.code] ?? 500;\n }\n}\n\nexport class ApprovalNotFoundError extends ApprovalError {\n constructor(resource: string, id: string) {\n super(`${resource} \"${id}\" not found.`, 'NOT_FOUND');\n this.name = 'ApprovalNotFoundError';\n }\n}\n\nexport class ApprovalConflictError extends ApprovalError {\n constructor(instanceId: string) {\n super(\n `Concurrent modification detected on instance \"${instanceId}\". The record was updated by another process. Please retry.`,\n 'CONFLICT',\n );\n this.name = 'ApprovalConflictError';\n }\n}\n\nexport class ApprovalForbiddenError extends ApprovalError {\n constructor(message: string) {\n super(message, 'FORBIDDEN');\n this.name = 'ApprovalForbiddenError';\n }\n}\n\nexport class ApprovalValidationError extends ApprovalError {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message, 'VALIDATION');\n this.name = 'ApprovalValidationError';\n }\n}\n\nexport class ApprovalTemplateNotFoundError extends ApprovalError {\n constructor(name: string) {\n super(`Template \"${name}\" not found.`, 'TEMPLATE_NOT_FOUND');\n this.name = 'ApprovalTemplateNotFoundError';\n }\n}\n","import type {\n IStorageAdapter,\n PaginationOpts,\n PaginatedResult,\n InstanceFilter,\n CursorPaginationOpts,\n CursorPaginatedResult,\n} from './IStorageAdapter.js';\nimport type { ApprovalTemplate, ApprovalInstance, AuditEntry } from '../types/index.js';\nimport { ApprovalConflictError } from '../errors.js';\n\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction reviveDates(instance: ApprovalInstance): ApprovalInstance {\n return {\n ...instance,\n createdAt: new Date(instance.createdAt),\n updatedAt: new Date(instance.updatedAt),\n expiresAt: instance.expiresAt ? new Date(instance.expiresAt) : undefined,\n slaDeadlineAt: instance.slaDeadlineAt ? new Date(instance.slaDeadlineAt) : undefined,\n slaBreachedAt: instance.slaBreachedAt ? new Date(instance.slaBreachedAt) : undefined,\n auditLog: instance.auditLog.map((e) => ({ ...e, timestamp: new Date(e.timestamp) })),\n levels: instance.levels.map((l) => {\n const level: typeof l = { ...l };\n if (l.escalationDueAt) level.escalationDueAt = new Date(l.escalationDueAt);\n if (l.delegatedUntil) level.delegatedUntil = new Date(l.delegatedUntil);\n return level;\n }),\n };\n}\n\nfunction reviveTemplateDates(template: ApprovalTemplate): ApprovalTemplate {\n return { ...template, createdAt: new Date(template.createdAt) };\n}\n\nfunction applyFilter(instance: ApprovalInstance, filter: InstanceFilter): boolean {\n if (filter.status && instance.status !== filter.status) return false;\n if (filter.documentType && instance.documentType !== filter.documentType) return false;\n if (filter.submittedBy && instance.submittedBy !== filter.submittedBy) return false;\n if (filter.templateName && instance.templateName !== filter.templateName) return false;\n // Stored instances carry string dates (deepClone JSON round-trip), so wrap before comparing.\n if (filter.fromDate && new Date(instance.createdAt) < filter.fromDate) return false;\n if (filter.toDate && new Date(instance.createdAt) > filter.toDate) return false;\n return true;\n}\n\nexport class MemoryAdapter implements IStorageAdapter {\n // keyed by `${tenantId}:${template.name}`\n private templates = new Map<string, ApprovalTemplate>();\n // keyed by `${tenantId}:${instance.id}`\n private instances = new Map<string, ApprovalInstance>();\n\n async saveTemplate(template: ApprovalTemplate): Promise<void> {\n this.templates.set(`${template.tenantId}:${template.name}`, deepClone(template));\n }\n\n async getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null> {\n const template = this.templates.get(`${tenantId}:${name}`);\n return template ? reviveTemplateDates(deepClone(template)) : null;\n }\n\n async listTemplates(tenantId: string): Promise<ApprovalTemplate[]> {\n const result: ApprovalTemplate[] = [];\n for (const [key, template] of this.templates) {\n if (key.startsWith(`${tenantId}:`)) {\n result.push(reviveTemplateDates(deepClone(template)));\n }\n }\n return result;\n }\n\n async saveInstance(instance: ApprovalInstance): Promise<void> {\n this.instances.set(`${instance.tenantId}:${instance.id}`, deepClone(instance));\n }\n\n async updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void> {\n const key = `${instance.tenantId}:${instance.id}`;\n const stored = this.instances.get(key);\n if (!stored) throw new ApprovalConflictError(instance.id);\n if (stored.version !== expectedVersion) throw new ApprovalConflictError(instance.id);\n const updated = deepClone(instance);\n updated.version = expectedVersion + 1;\n this.instances.set(key, updated);\n }\n\n async getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null> {\n const raw = this.instances.get(`${tenantId}:${id}`);\n if (!raw) return null;\n return reviveDates(deepClone(raw));\n }\n\n async getInstancesByApprover(\n tenantId: string,\n approverId: string,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n // Use .find() by level number, not array index (level numbers may not be consecutive)\n const currentLevel = i.levels.find((l) => l.level === i.currentLevel);\n return currentLevel?.approverIds.includes(approverId) ?? false;\n });\n return paginate(\n all.map((i) => reviveDates(deepClone(i))),\n opts,\n );\n }\n\n async getInstancesByFilter(\n tenantId: string,\n filter: InstanceFilter,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter(\n (i) => i.tenantId === tenantId && applyFilter(i, filter),\n );\n return paginate(\n all.map((i) => reviveDates(deepClone(i))),\n opts,\n );\n }\n\n async getOverdueInstances(\n tenantId: string,\n asOf: Date,\n filter: InstanceFilter = {},\n ): Promise<ApprovalInstance[]> {\n return [...this.instances.values()]\n .filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n if (filter.documentType && i.documentType !== filter.documentType) return false;\n if (filter.submittedBy && i.submittedBy !== filter.submittedBy) return false;\n // Escalation overdue\n const currentLevel = i.levels.find((l) => l.level === i.currentLevel);\n const hasOverdueEscalation =\n currentLevel?.escalationDueAt != null && new Date(currentLevel.escalationDueAt) <= asOf;\n // Instance deadline expired\n const isExpired = i.expiresAt != null && new Date(i.expiresAt) <= asOf;\n // SLA breach (not yet recorded)\n const hasSLABreach =\n i.slaDeadlineAt != null && new Date(i.slaDeadlineAt) <= asOf && !i.slaBreachedAt;\n // Delegation expiry on any pending level\n const hasDelegationExpiry = i.levels.some(\n (l) =>\n l.status === 'pending' &&\n l.delegatedUntil != null &&\n new Date(l.delegatedUntil) <= asOf &&\n l.delegatedFrom != null,\n );\n return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry;\n })\n .map((i) => reviveDates(deepClone(i)));\n }\n\n async getInstancesByCursor(\n tenantId: string,\n filter: InstanceFilter,\n opts: CursorPaginationOpts,\n ): Promise<CursorPaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()]\n .filter((i) => i.tenantId === tenantId && applyFilter(i, filter))\n .map((i) => reviveDates(deepClone(i)))\n .sort((a, b) => {\n const ta = a.updatedAt.getTime();\n const tb = b.updatedAt.getTime();\n return ta !== tb ? ta - tb : a.id.localeCompare(b.id);\n });\n\n const { cursor, limit, direction = 'forward' } = opts;\n let startIdx = 0;\n\n if (cursor) {\n const [ts, id] = decodeCursor(cursor);\n const idx = all.findIndex(\n (i) => i.updatedAt.getTime() > ts || (i.updatedAt.getTime() === ts && i.id > id),\n );\n startIdx = idx === -1 ? all.length : idx;\n }\n\n if (direction === 'backward' && startIdx > 0) {\n startIdx = Math.max(0, startIdx - limit - 1);\n }\n\n const slice = all.slice(startIdx, startIdx + limit);\n const hasMore = startIdx + limit < all.length;\n const nextCursor = hasMore ? encodeCursor(slice[slice.length - 1]!) : undefined;\n const prevCursor = startIdx > 0 ? encodeCursor(all[startIdx - 1]!) : undefined;\n\n return { items: slice, nextCursor, prevCursor, hasMore };\n }\n\n async getIdempotentInstance(\n tenantId: string,\n idempotencyKey: string,\n ): Promise<ApprovalInstance | null> {\n for (const instance of this.instances.values()) {\n if (instance.tenantId === tenantId && instance.idempotencyKey === idempotencyKey) {\n return reviveDates(deepClone(instance));\n }\n }\n return null;\n }\n\n async appendAuditEntry(\n _tenantId: string,\n _instanceId: string,\n _entry: AuditEntry,\n ): Promise<void> {\n // No-op by design. The engine already pushes each entry onto instance.auditLog\n // and persists the whole instance via saveInstance()/updateInstance(), so the\n // embedded log is the source of truth for this adapter. Pushing here too would\n // duplicate every entry (as a dedicated, append-only audit table would in\n // PostgresAdapter, this method exists to satisfy that separate-sink contract).\n }\n\n /** Test helper — total stored instances across all tenants. */\n get size(): number {\n return this.instances.size;\n }\n}\n\nfunction paginate<T>(items: T[], opts?: PaginationOpts): PaginatedResult<T> {\n const total = items.length;\n if (!opts) return { items, total };\n return { items: items.slice(opts.offset, opts.offset + opts.limit), total };\n}\n\nfunction encodeCursor(instance: ApprovalInstance): string {\n return Buffer.from(`${instance.updatedAt.getTime()}|${instance.id}`).toString('base64');\n}\n\nfunction decodeCursor(cursor: string): [number, string] {\n const decoded = Buffer.from(cursor, 'base64').toString('utf8');\n const pipeIdx = decoded.indexOf('|');\n return [Number(decoded.slice(0, pipeIdx)), decoded.slice(pipeIdx + 1)];\n}\n"]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/adapters/MemoryAdapter.ts"],"names":[],"mappings":"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","file":"MemoryAdapter.cjs","sourcesContent":["export class ApprovalError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'ApprovalError';\n }\n\n toJSON(): { code: string; message: string; name: string } {\n return { code: this.code, message: this.message, name: this.name };\n }\n\n toHttpStatus(): number {\n const map: Record<string, number> = {\n NOT_FOUND: 404,\n CONFLICT: 409,\n FORBIDDEN: 403,\n VALIDATION: 422,\n TEMPLATE_NOT_FOUND: 404,\n };\n return map[this.code] ?? 500;\n }\n}\n\nexport class ApprovalNotFoundError extends ApprovalError {\n constructor(resource: string, id: string) {\n super(`${resource} \"${id}\" not found.`, 'NOT_FOUND');\n this.name = 'ApprovalNotFoundError';\n }\n}\n\nexport class ApprovalConflictError extends ApprovalError {\n constructor(instanceId: string) {\n super(\n `Concurrent modification detected on instance \"${instanceId}\". The record was updated by another process. Please retry.`,\n 'CONFLICT',\n );\n this.name = 'ApprovalConflictError';\n }\n}\n\nexport class ApprovalForbiddenError extends ApprovalError {\n constructor(message: string) {\n super(message, 'FORBIDDEN');\n this.name = 'ApprovalForbiddenError';\n }\n}\n\nexport class ApprovalValidationError extends ApprovalError {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message, 'VALIDATION');\n this.name = 'ApprovalValidationError';\n }\n}\n\nexport class ApprovalTemplateNotFoundError extends ApprovalError {\n constructor(name: string) {\n super(`Template \"${name}\" not found.`, 'TEMPLATE_NOT_FOUND');\n this.name = 'ApprovalTemplateNotFoundError';\n }\n}\n","import type {\n IStorageAdapter,\n PaginationOpts,\n PaginatedResult,\n InstanceFilter,\n CursorPaginationOpts,\n CursorPaginatedResult,\n} from './IStorageAdapter.js';\nimport type { ApprovalTemplate, ApprovalInstance, AuditEntry } from '../types/index.js';\nimport { ApprovalConflictError } from '../errors.js';\n\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction reviveDates(instance: ApprovalInstance): ApprovalInstance {\n return {\n ...instance,\n createdAt: new Date(instance.createdAt),\n updatedAt: new Date(instance.updatedAt),\n expiresAt: instance.expiresAt ? new Date(instance.expiresAt) : undefined,\n slaDeadlineAt: instance.slaDeadlineAt ? new Date(instance.slaDeadlineAt) : undefined,\n slaBreachedAt: instance.slaBreachedAt ? new Date(instance.slaBreachedAt) : undefined,\n auditLog: instance.auditLog.map((e) => ({ ...e, timestamp: new Date(e.timestamp) })),\n levels: instance.levels.map((l) => {\n const level: typeof l = { ...l };\n if (l.escalationDueAt) level.escalationDueAt = new Date(l.escalationDueAt);\n if (l.delegatedUntil) level.delegatedUntil = new Date(l.delegatedUntil);\n return level;\n }),\n };\n}\n\nfunction reviveTemplateDates(template: ApprovalTemplate): ApprovalTemplate {\n return { ...template, createdAt: new Date(template.createdAt) };\n}\n\nfunction applyFilter(instance: ApprovalInstance, filter: InstanceFilter): boolean {\n if (filter.status && instance.status !== filter.status) return false;\n if (filter.documentType && instance.documentType !== filter.documentType) return false;\n if (filter.submittedBy && instance.submittedBy !== filter.submittedBy) return false;\n if (filter.templateName && instance.templateName !== filter.templateName) return false;\n // Stored instances carry string dates (deepClone JSON round-trip), so wrap before comparing.\n if (filter.fromDate && new Date(instance.createdAt) < filter.fromDate) return false;\n if (filter.toDate && new Date(instance.createdAt) > filter.toDate) return false;\n return true;\n}\n\nexport class MemoryAdapter implements IStorageAdapter {\n // keyed by `${tenantId}:${template.name}`\n private templates = new Map<string, ApprovalTemplate>();\n // keyed by `${tenantId}:${instance.id}`\n private instances = new Map<string, ApprovalInstance>();\n\n async saveTemplate(template: ApprovalTemplate): Promise<void> {\n this.templates.set(`${template.tenantId}:${template.name}`, deepClone(template));\n }\n\n async getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null> {\n const template = this.templates.get(`${tenantId}:${name}`);\n return template ? reviveTemplateDates(deepClone(template)) : null;\n }\n\n async listTemplates(tenantId: string): Promise<ApprovalTemplate[]> {\n const result: ApprovalTemplate[] = [];\n for (const [key, template] of this.templates) {\n if (key.startsWith(`${tenantId}:`)) {\n result.push(reviveTemplateDates(deepClone(template)));\n }\n }\n return result;\n }\n\n async saveInstance(instance: ApprovalInstance): Promise<void> {\n this.instances.set(`${instance.tenantId}:${instance.id}`, deepClone(instance));\n }\n\n async updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void> {\n const key = `${instance.tenantId}:${instance.id}`;\n const stored = this.instances.get(key);\n if (!stored) throw new ApprovalConflictError(instance.id);\n if (stored.version !== expectedVersion) throw new ApprovalConflictError(instance.id);\n const updated = deepClone(instance);\n updated.version = expectedVersion + 1;\n this.instances.set(key, updated);\n }\n\n async getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null> {\n const raw = this.instances.get(`${tenantId}:${id}`);\n if (!raw) return null;\n return reviveDates(deepClone(raw));\n }\n\n async getInstancesByApprover(\n tenantId: string,\n approverId: string,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n // Use .find() by level number, not array index (level numbers may not be consecutive)\n const currentLevel = i.levels.find((l) => l.level === i.currentLevel);\n return currentLevel?.approverIds.includes(approverId) ?? false;\n });\n return paginate(\n all.map((i) => reviveDates(deepClone(i))),\n opts,\n );\n }\n\n async getInstancesByFilter(\n tenantId: string,\n filter: InstanceFilter,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter(\n (i) => i.tenantId === tenantId && applyFilter(i, filter),\n );\n return paginate(\n all.map((i) => reviveDates(deepClone(i))),\n opts,\n );\n }\n\n async getOverdueInstances(\n tenantId: string,\n asOf: Date,\n filter: InstanceFilter = {},\n ): Promise<ApprovalInstance[]> {\n return [...this.instances.values()]\n .filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n if (filter.documentType && i.documentType !== filter.documentType) return false;\n if (filter.submittedBy && i.submittedBy !== filter.submittedBy) return false;\n // Escalation overdue on ANY open branch. Filtering to i.currentLevel\n // would miss the upper branches of a parallel group entirely, and left\n // this adapter disagreeing with PostgresAdapter, which already scans\n // every level.\n const hasOverdueEscalation = i.levels.some(\n (l) => l.escalationDueAt != null && new Date(l.escalationDueAt) <= asOf,\n );\n // Instance deadline expired\n const isExpired = i.expiresAt != null && new Date(i.expiresAt) <= asOf;\n // SLA breach (not yet recorded)\n const hasSLABreach =\n i.slaDeadlineAt != null && new Date(i.slaDeadlineAt) <= asOf && !i.slaBreachedAt;\n // Delegation expiry on any pending level\n const hasDelegationExpiry = i.levels.some(\n (l) =>\n l.status === 'pending' &&\n l.delegatedUntil != null &&\n new Date(l.delegatedUntil) <= asOf &&\n l.delegatedFrom != null,\n );\n // Reminder due on any open branch. Without this the scheduler never\n // sees the instance and no reminder is ever sent.\n const hasDueReminder = i.levels.some(\n (l) =>\n l.status === 'pending' && l.reminderDueAt != null && new Date(l.reminderDueAt) <= asOf,\n );\n return (\n hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry || hasDueReminder\n );\n })\n .map((i) => reviveDates(deepClone(i)));\n }\n\n async getInstancesByCursor(\n tenantId: string,\n filter: InstanceFilter,\n opts: CursorPaginationOpts,\n ): Promise<CursorPaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()]\n .filter((i) => i.tenantId === tenantId && applyFilter(i, filter))\n .map((i) => reviveDates(deepClone(i)))\n .sort((a, b) => {\n const ta = a.updatedAt.getTime();\n const tb = b.updatedAt.getTime();\n return ta !== tb ? ta - tb : a.id.localeCompare(b.id);\n });\n\n const { cursor, limit, direction = 'forward' } = opts;\n let startIdx = 0;\n\n if (cursor) {\n const [ts, id] = decodeCursor(cursor);\n const idx = all.findIndex(\n (i) => i.updatedAt.getTime() > ts || (i.updatedAt.getTime() === ts && i.id > id),\n );\n startIdx = idx === -1 ? all.length : idx;\n }\n\n if (direction === 'backward' && startIdx > 0) {\n startIdx = Math.max(0, startIdx - limit - 1);\n }\n\n const slice = all.slice(startIdx, startIdx + limit);\n const hasMore = startIdx + limit < all.length;\n const nextCursor = hasMore ? encodeCursor(slice[slice.length - 1]!) : undefined;\n const prevCursor = startIdx > 0 ? encodeCursor(all[startIdx - 1]!) : undefined;\n\n return { items: slice, nextCursor, prevCursor, hasMore };\n }\n\n async getIdempotentInstance(\n tenantId: string,\n idempotencyKey: string,\n ): Promise<ApprovalInstance | null> {\n for (const instance of this.instances.values()) {\n if (instance.tenantId === tenantId && instance.idempotencyKey === idempotencyKey) {\n return reviveDates(deepClone(instance));\n }\n }\n return null;\n }\n\n async appendAuditEntry(\n _tenantId: string,\n _instanceId: string,\n _entry: AuditEntry,\n ): Promise<void> {\n // No-op by design. The engine already pushes each entry onto instance.auditLog\n // and persists the whole instance via saveInstance()/updateInstance(), so the\n // embedded log is the source of truth for this adapter. Pushing here too would\n // duplicate every entry (as a dedicated, append-only audit table would in\n // PostgresAdapter, this method exists to satisfy that separate-sink contract).\n }\n\n /** Test helper — total stored instances across all tenants. */\n get size(): number {\n return this.instances.size;\n }\n}\n\nfunction paginate<T>(items: T[], opts?: PaginationOpts): PaginatedResult<T> {\n const total = items.length;\n if (!opts) return { items, total };\n return { items: items.slice(opts.offset, opts.offset + opts.limit), total };\n}\n\nfunction encodeCursor(instance: ApprovalInstance): string {\n return Buffer.from(`${instance.updatedAt.getTime()}|${instance.id}`).toString('base64');\n}\n\nfunction decodeCursor(cursor: string): [number, string] {\n const decoded = Buffer.from(cursor, 'base64').toString('utf8');\n const pipeIdx = decoded.indexOf('|');\n return [Number(decoded.slice(0, pipeIdx)), decoded.slice(pipeIdx + 1)];\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D-yIRiET.cjs';
2
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-iaAIdMbs.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-CKV_FZFV.cjs';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-BsM0DUPf.cjs';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
5
5
  private templates;
@@ -1,5 +1,5 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-BYlHnm7_.js';
2
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-iaAIdMbs.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-DQJmR6Mt.js';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-BsM0DUPf.js';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
5
5
  private templates;
@@ -127,14 +127,18 @@ var MemoryAdapter = class {
127
127
  if (i.tenantId !== tenantId || i.status !== "pending") return false;
128
128
  if (filter.documentType && i.documentType !== filter.documentType) return false;
129
129
  if (filter.submittedBy && i.submittedBy !== filter.submittedBy) return false;
130
- const currentLevel = i.levels.find((l) => l.level === i.currentLevel);
131
- const hasOverdueEscalation = currentLevel?.escalationDueAt != null && new Date(currentLevel.escalationDueAt) <= asOf;
130
+ const hasOverdueEscalation = i.levels.some(
131
+ (l) => l.escalationDueAt != null && new Date(l.escalationDueAt) <= asOf
132
+ );
132
133
  const isExpired = i.expiresAt != null && new Date(i.expiresAt) <= asOf;
133
134
  const hasSLABreach = i.slaDeadlineAt != null && new Date(i.slaDeadlineAt) <= asOf && !i.slaBreachedAt;
134
135
  const hasDelegationExpiry = i.levels.some(
135
136
  (l) => l.status === "pending" && l.delegatedUntil != null && new Date(l.delegatedUntil) <= asOf && l.delegatedFrom != null
136
137
  );
137
- return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry;
138
+ const hasDueReminder = i.levels.some(
139
+ (l) => l.status === "pending" && l.reminderDueAt != null && new Date(l.reminderDueAt) <= asOf
140
+ );
141
+ return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry || hasDueReminder;
138
142
  }).map((i) => reviveDates(deepClone(i)));
139
143
  }
140
144
  async getInstancesByCursor(tenantId, filter, opts) {