hierarchical-approval 2.1.0 → 2.3.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 (37) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +51 -0
  3. package/dist/{ApprovalEngine-DqRIadYg.d.cts → ApprovalEngine-C1dUPnLM.d.cts} +101 -2
  4. package/dist/{ApprovalEngine-CJwnpnpL.d.ts → ApprovalEngine-Ci9urTDI.d.ts} +101 -2
  5. package/dist/{IStorageAdapter-DjRvHUF0.d.ts → IStorageAdapter-BU3sau5W.d.ts} +14 -0
  6. package/dist/{IStorageAdapter-Bk7ybd3z.d.cts → IStorageAdapter-DdHO4Rf1.d.cts} +14 -0
  7. package/dist/adapters/MemoryAdapter.cjs +4 -0
  8. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  9. package/dist/adapters/MemoryAdapter.d.cts +2 -1
  10. package/dist/adapters/MemoryAdapter.d.ts +2 -1
  11. package/dist/adapters/MemoryAdapter.js +4 -0
  12. package/dist/adapters/MemoryAdapter.js.map +1 -1
  13. package/dist/adapters/PostgresAdapter.cjs +12 -0
  14. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  15. package/dist/adapters/PostgresAdapter.d.cts +2 -1
  16. package/dist/adapters/PostgresAdapter.d.ts +2 -1
  17. package/dist/adapters/PostgresAdapter.js +12 -0
  18. package/dist/adapters/PostgresAdapter.js.map +1 -1
  19. package/dist/index.cjs +195 -0
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +3 -3
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +195 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/nestjs.cjs +190 -0
  26. package/dist/nestjs.cjs.map +1 -1
  27. package/dist/nestjs.d.cts +2 -2
  28. package/dist/nestjs.d.ts +2 -2
  29. package/dist/nestjs.js +190 -0
  30. package/dist/nestjs.js.map +1 -1
  31. package/dist/testing.cjs +194 -0
  32. package/dist/testing.cjs.map +1 -1
  33. package/dist/testing.d.cts +2 -2
  34. package/dist/testing.d.ts +2 -2
  35. package/dist/testing.js +194 -0
  36. package/dist/testing.js.map +1 -1
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,79 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  _Nothing yet._
9
9
 
10
+ ## [2.3.0] - 2026-09-04
11
+
12
+ ### Added — retention
13
+
14
+ - **`purgeInstances()` removes finished approvals older than a cut-off.**
15
+ Approval tables only grow, and data-minimisation rules eventually require old
16
+ records to go. There was no way to remove one, so operators reached around the
17
+ library and deleted rows directly — which is exactly where orphaned audit rows
18
+ and half-deleted instances come from.
19
+
20
+ ```ts
21
+ await engine.purgeInstances({
22
+ olderThan: new Date('2024-01-01'),
23
+ statuses: ['approved', 'rejected'],
24
+ dryRun: true,
25
+ });
26
+ ```
27
+
28
+ - **Only terminal instances are eligible.** A pending approval is live work, and
29
+ deleting one would strand a document with no way to finish and no record of
30
+ why. A non-terminal status is rejected rather than quietly ignored, because a
31
+ caller who asked to purge pending work has misunderstood something and should
32
+ hear about it. The engine re-checks each instance's status and age before
33
+ deleting, so a custom adapter with a loose filter still cannot remove live
34
+ work.
35
+
36
+ - **`IStorageAdapter.deleteInstance` is optional.** For many deployments the
37
+ approval trail *is* the compliance record and the right answer is that nothing
38
+ is ever deleted — an adapter expresses that by not implementing the method,
39
+ and `purgeInstances()` then throws rather than reporting a successful purge of
40
+ nothing. Both bundled adapters implement it; `PostgresAdapter` removes audit
41
+ rows before the instance row, since orphaned audit rows are a better failure
42
+ mode than audit rows outliving nothing.
43
+
44
+ New export: `PurgeResult`.
45
+
46
+ ## [2.2.0] - 2026-09-04
47
+
48
+ ### Added — template export / import
49
+
50
+ - **`exportTemplates()` and `importTemplates()` move approval configuration
51
+ between environments.** Templates are authored in a sandbox, reviewed, then
52
+ promoted — but the only way to carry them across was to read `listTemplates()`
53
+ and re-post the rows, which dragged each environment's own `id`, `tenantId`
54
+ and version lineage along. Those either collided on arrival or silently
55
+ claimed a history the target never had.
56
+
57
+ ```ts
58
+ const bundle = await sandbox.exportTemplates(['PO', 'INV']);
59
+ await production.importTemplates(bundle, { mode: 'upsert', dryRun: true });
60
+ ```
61
+
62
+ A bundle is plain JSON, version-stamped, and carries no environment-specific
63
+ fields — they are stripped, not blanked, so a round trip cannot reintroduce a
64
+ stale id. The target assigns its own identity.
65
+
66
+ - **`mode: 'create'`** (default) skips templates that already exist;
67
+ **`'upsert'`** updates them, bumping the version and recording
68
+ `previousVersionId` exactly as `updateTemplate()` does. `dryRun` reports
69
+ without writing.
70
+
71
+ - **Every template is validated before any is written.** A half-applied bundle
72
+ is worse than one rejected outright: the tenant ends up matching neither
73
+ environment and the operator cannot tell which half landed. Validation
74
+ failures reject the whole bundle and name the offending template; storage
75
+ errors during the write phase are still reported per template, since those can
76
+ occur after validation passes.
77
+
78
+ Import also rejects an unsupported `bundleVersion`, an empty bundle, and
79
+ duplicate names within one bundle.
80
+
81
+ New exports: `TemplateBundle`, `ImportResult`, `TEMPLATE_BUNDLE_VERSION`.
82
+
10
83
  ## [2.1.0] - 2026-09-04
11
84
 
12
85
  ### Added — sub-workflows
package/README.md CHANGED
@@ -468,6 +468,57 @@ The sweep is **not atomic**: it reports per-instance failures rather than rollin
468
468
  back. A partial transfer is the useful outcome — the approvals that can move
469
469
  should move, and the ones that cannot are named so a human can look at them.
470
470
 
471
+ ### Retention
472
+
473
+ Approval tables only grow, and data-minimisation rules eventually require old
474
+ records to go:
475
+
476
+ ```ts
477
+ const result = await engine.purgeInstances({
478
+ olderThan: new Date('2024-01-01'),
479
+ statuses: ['approved', 'rejected'], // terminal statuses only
480
+ documentType: 'purchase_order', // optional
481
+ limit: 1000, // default 1000
482
+ dryRun: true, // see what would go first
483
+ });
484
+ ```
485
+
486
+ **Only terminal instances are eligible.** A pending approval is live work, and
487
+ deleting one would strand a document with no way to finish and no record of why.
488
+ Passing a non-terminal status is rejected rather than quietly ignored.
489
+
490
+ This is irreversible and removes the audit trail with the instance. In many
491
+ deployments that trail *is* the compliance record — which is why the underlying
492
+ `deleteInstance` is an **optional** adapter method. An adapter that does not
493
+ implement it makes purging unavailable, and the call throws rather than
494
+ reporting a successful purge of nothing.
495
+
496
+ ### Promoting templates between environments
497
+
498
+ Approval configuration is authored somewhere safe, reviewed, then promoted.
499
+ `exportTemplates()` produces a portable bundle and `importTemplates()` applies
500
+ it:
501
+
502
+ ```ts
503
+ const bundle = await sandbox.exportTemplates(['PO', 'INV']); // omit names for all
504
+ const result = await production.importTemplates(bundle, { mode: 'upsert', dryRun: true });
505
+ // { created: [], updated: ['PO', 'INV'], skipped: [], errors: [], dryRun: true }
506
+ ```
507
+
508
+ A bundle is plain JSON and carries **no** `id`, `tenantId`, `createdAt`,
509
+ `version` or `previousVersionId` — those describe one row in one database, and
510
+ importing them would either collide with the target's ids or claim a lineage the
511
+ target never had. The target assigns its own.
512
+
513
+ `mode: 'create'` (the default) skips templates that already exist; `'upsert'`
514
+ updates them, bumping the version and recording `previousVersionId` as a normal
515
+ `updateTemplate()` would.
516
+
517
+ **Every template is validated before any is written.** A half-applied bundle
518
+ leaves the tenant matching neither environment with no way to tell which half
519
+ landed, so a bundle that fails validation is rejected whole, naming the
520
+ offending template.
521
+
471
522
  ### Sub-workflows
472
523
 
473
524
  A level can delegate to a whole separate approval instead of to a list of
@@ -1,4 +1,4 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-Bk7ybd3z.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DdHO4Rf1.cjs';
2
2
  import { l as ConditionExpression, p as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-DUJY_Axf.cjs';
3
3
  import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-JJAiMyMd.cjs';
4
4
  import { z } from 'zod';
@@ -305,6 +305,45 @@ interface ApproverWorkload {
305
305
  /** Age of that oldest item. `0` when they hold nothing. */
306
306
  oldestAgeMs: number;
307
307
  }
308
+ /** Version stamp on an exported bundle, so an importer can reject a shape it does not understand. */
309
+ declare const TEMPLATE_BUNDLE_VERSION = 1;
310
+ /**
311
+ * A portable set of templates, safe to move between environments.
312
+ *
313
+ * Deliberately carries no `id`, `tenantId`, `createdAt`, `version` or
314
+ * `previousVersionId`: those describe one row in one database, and importing
315
+ * them would either collide with the target's own ids or silently claim a
316
+ * lineage the target never had.
317
+ */
318
+ interface TemplateBundle {
319
+ bundleVersion: number;
320
+ exportedAt: Date;
321
+ templates: ApprovalTemplateConfig[];
322
+ }
323
+ /** Outcome of {@link ApprovalEngine.importTemplates}. */
324
+ interface ImportResult {
325
+ created: string[];
326
+ updated: string[];
327
+ skipped: string[];
328
+ errors: Array<{
329
+ name: string;
330
+ message: string;
331
+ }>;
332
+ dryRun: boolean;
333
+ }
334
+ /** Outcome of a {@link ApprovalEngine.purgeInstances} sweep. */
335
+ interface PurgeResult {
336
+ /** Instances actually removed (or that would be, under `dryRun`). */
337
+ purged: Array<{
338
+ instanceId: string;
339
+ documentId: string;
340
+ status: ApprovalInstance['status'];
341
+ }>;
342
+ /** Instances examined. */
343
+ scanned: number;
344
+ /** True when nothing was written. */
345
+ dryRun: boolean;
346
+ }
308
347
  interface ApprovalStatistics {
309
348
  /** Total instances matching the filter (across all statuses). */
310
349
  total: number;
@@ -660,6 +699,66 @@ declare class ApprovalEngine {
660
699
  * @returns One row per approver holding at least one open level.
661
700
  */
662
701
  getWorkload(filter?: Omit<InstanceFilter, 'status'>): Promise<ApproverWorkload[]>;
702
+ /**
703
+ * Export templates as a portable bundle.
704
+ *
705
+ * Approval configuration is written once and then has to travel — authored in
706
+ * a sandbox, reviewed, promoted to production. Reading `listTemplates()` and
707
+ * re-posting the rows carried each environment's own `id`, `tenantId` and
708
+ * version lineage with it, which either collided on arrival or silently
709
+ * claimed a history the target never had. This strips all of it.
710
+ *
711
+ * @param names - Templates to include; omit for all of them.
712
+ */
713
+ exportTemplates(names?: string[]): Promise<TemplateBundle>;
714
+ /**
715
+ * Import a bundle produced by {@link exportTemplates}.
716
+ *
717
+ * **Every template is validated before any is written.** A bundle that is
718
+ * half-applied is worse than one rejected outright: the tenant is left in a
719
+ * state matching neither environment, and the operator has no way to tell
720
+ * which half landed. Per-template failures during the write phase are still
721
+ * reported individually, since a storage error can occur after validation
722
+ * passes.
723
+ *
724
+ * @param bundle - The bundle to apply.
725
+ * @param opts - `mode: 'create'` (default) refuses to touch existing
726
+ * templates; `'upsert'` updates them. `dryRun` reports without writing.
727
+ */
728
+ importTemplates(bundle: TemplateBundle, opts?: {
729
+ mode?: 'create' | 'upsert';
730
+ dryRun?: boolean;
731
+ }): Promise<ImportResult>;
732
+ /**
733
+ * Permanently remove finished approvals older than a cut-off.
734
+ *
735
+ * Approval tables only grow, and data-minimisation rules eventually require
736
+ * old records to go. There was no way to remove one, so operators reached
737
+ * around the library and deleted rows directly — which is exactly where
738
+ * orphaned audit rows and half-deleted instances come from.
739
+ *
740
+ * **Only terminal instances are eligible.** A pending approval is live work;
741
+ * deleting one would strand a document with no way to finish and no record of
742
+ * why. Passing a non-terminal status is rejected rather than quietly ignored,
743
+ * because a caller who asked to purge pending work has misunderstood
744
+ * something and should hear about it.
745
+ *
746
+ * This is irreversible and removes the audit trail with the instance. In many
747
+ * deployments that trail *is* the compliance record, which is why the
748
+ * underlying `deleteInstance` is an optional adapter method: an adapter that
749
+ * does not implement it makes the whole operation unavailable, and this
750
+ * throws rather than reporting a successful purge of nothing.
751
+ *
752
+ * @param opts - Cut-off, optional status/type scoping, safety limit, dry run.
753
+ * @returns What was removed, or would be under `dryRun`.
754
+ */
755
+ purgeInstances(opts: {
756
+ olderThan: Date;
757
+ statuses?: ApprovalInstance['status'][];
758
+ documentType?: string;
759
+ limit?: number;
760
+ dryRun?: boolean;
761
+ }): Promise<PurgeResult>;
663
762
  getStatistics(filter?: Omit<InstanceFilter, 'status'>): Promise<ApprovalStatistics>;
664
763
  shutdown(): Promise<void>;
665
764
  /**
@@ -791,4 +890,4 @@ declare class ApprovalEngine {
791
890
  private runExternalAudit;
792
891
  }
793
892
 
794
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, type TransferResult as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type CancelOptions as h, type ConditionOperatorFn as i, type CycleTimeStats as j, type IdempotencyKeyFn as k, type OutOfOfficeProvider as l, type OverrideOptions as m, type PreviewResult as n, type ProvideInfoOptions as o, type RejectOptions as p, type RequestInfoOptions as q, type ResubmitOptions as r, type RetryPolicy as s, defaultIdGenerator as t, toComparableNumber as u, validateConditionExpression as v, weekendCalendar as w };
893
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, weekendCalendar as F, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type CancelOptions as h, type ConditionOperatorFn as i, type CycleTimeStats as j, type IdempotencyKeyFn as k, type ImportResult as l, type OutOfOfficeProvider as m, type OverrideOptions as n, type PreviewResult as o, type ProvideInfoOptions as p, type PurgeResult as q, type RejectOptions as r, type RequestInfoOptions as s, type ResubmitOptions as t, type RetryPolicy as u, type TemplateBundle as v, type TransferResult as w, defaultIdGenerator as x, toComparableNumber as y, validateConditionExpression as z };
@@ -1,4 +1,4 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DjRvHUF0.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-BU3sau5W.js';
2
2
  import { l as ConditionExpression, p as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-DUJY_Axf.js';
3
3
  import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BdgfUrIn.js';
4
4
  import { z } from 'zod';
@@ -305,6 +305,45 @@ interface ApproverWorkload {
305
305
  /** Age of that oldest item. `0` when they hold nothing. */
306
306
  oldestAgeMs: number;
307
307
  }
308
+ /** Version stamp on an exported bundle, so an importer can reject a shape it does not understand. */
309
+ declare const TEMPLATE_BUNDLE_VERSION = 1;
310
+ /**
311
+ * A portable set of templates, safe to move between environments.
312
+ *
313
+ * Deliberately carries no `id`, `tenantId`, `createdAt`, `version` or
314
+ * `previousVersionId`: those describe one row in one database, and importing
315
+ * them would either collide with the target's own ids or silently claim a
316
+ * lineage the target never had.
317
+ */
318
+ interface TemplateBundle {
319
+ bundleVersion: number;
320
+ exportedAt: Date;
321
+ templates: ApprovalTemplateConfig[];
322
+ }
323
+ /** Outcome of {@link ApprovalEngine.importTemplates}. */
324
+ interface ImportResult {
325
+ created: string[];
326
+ updated: string[];
327
+ skipped: string[];
328
+ errors: Array<{
329
+ name: string;
330
+ message: string;
331
+ }>;
332
+ dryRun: boolean;
333
+ }
334
+ /** Outcome of a {@link ApprovalEngine.purgeInstances} sweep. */
335
+ interface PurgeResult {
336
+ /** Instances actually removed (or that would be, under `dryRun`). */
337
+ purged: Array<{
338
+ instanceId: string;
339
+ documentId: string;
340
+ status: ApprovalInstance['status'];
341
+ }>;
342
+ /** Instances examined. */
343
+ scanned: number;
344
+ /** True when nothing was written. */
345
+ dryRun: boolean;
346
+ }
308
347
  interface ApprovalStatistics {
309
348
  /** Total instances matching the filter (across all statuses). */
310
349
  total: number;
@@ -660,6 +699,66 @@ declare class ApprovalEngine {
660
699
  * @returns One row per approver holding at least one open level.
661
700
  */
662
701
  getWorkload(filter?: Omit<InstanceFilter, 'status'>): Promise<ApproverWorkload[]>;
702
+ /**
703
+ * Export templates as a portable bundle.
704
+ *
705
+ * Approval configuration is written once and then has to travel — authored in
706
+ * a sandbox, reviewed, promoted to production. Reading `listTemplates()` and
707
+ * re-posting the rows carried each environment's own `id`, `tenantId` and
708
+ * version lineage with it, which either collided on arrival or silently
709
+ * claimed a history the target never had. This strips all of it.
710
+ *
711
+ * @param names - Templates to include; omit for all of them.
712
+ */
713
+ exportTemplates(names?: string[]): Promise<TemplateBundle>;
714
+ /**
715
+ * Import a bundle produced by {@link exportTemplates}.
716
+ *
717
+ * **Every template is validated before any is written.** A bundle that is
718
+ * half-applied is worse than one rejected outright: the tenant is left in a
719
+ * state matching neither environment, and the operator has no way to tell
720
+ * which half landed. Per-template failures during the write phase are still
721
+ * reported individually, since a storage error can occur after validation
722
+ * passes.
723
+ *
724
+ * @param bundle - The bundle to apply.
725
+ * @param opts - `mode: 'create'` (default) refuses to touch existing
726
+ * templates; `'upsert'` updates them. `dryRun` reports without writing.
727
+ */
728
+ importTemplates(bundle: TemplateBundle, opts?: {
729
+ mode?: 'create' | 'upsert';
730
+ dryRun?: boolean;
731
+ }): Promise<ImportResult>;
732
+ /**
733
+ * Permanently remove finished approvals older than a cut-off.
734
+ *
735
+ * Approval tables only grow, and data-minimisation rules eventually require
736
+ * old records to go. There was no way to remove one, so operators reached
737
+ * around the library and deleted rows directly — which is exactly where
738
+ * orphaned audit rows and half-deleted instances come from.
739
+ *
740
+ * **Only terminal instances are eligible.** A pending approval is live work;
741
+ * deleting one would strand a document with no way to finish and no record of
742
+ * why. Passing a non-terminal status is rejected rather than quietly ignored,
743
+ * because a caller who asked to purge pending work has misunderstood
744
+ * something and should hear about it.
745
+ *
746
+ * This is irreversible and removes the audit trail with the instance. In many
747
+ * deployments that trail *is* the compliance record, which is why the
748
+ * underlying `deleteInstance` is an optional adapter method: an adapter that
749
+ * does not implement it makes the whole operation unavailable, and this
750
+ * throws rather than reporting a successful purge of nothing.
751
+ *
752
+ * @param opts - Cut-off, optional status/type scoping, safety limit, dry run.
753
+ * @returns What was removed, or would be under `dryRun`.
754
+ */
755
+ purgeInstances(opts: {
756
+ olderThan: Date;
757
+ statuses?: ApprovalInstance['status'][];
758
+ documentType?: string;
759
+ limit?: number;
760
+ dryRun?: boolean;
761
+ }): Promise<PurgeResult>;
663
762
  getStatistics(filter?: Omit<InstanceFilter, 'status'>): Promise<ApprovalStatistics>;
664
763
  shutdown(): Promise<void>;
665
764
  /**
@@ -791,4 +890,4 @@ declare class ApprovalEngine {
791
890
  private runExternalAudit;
792
891
  }
793
892
 
794
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, type TransferResult as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type CancelOptions as h, type ConditionOperatorFn as i, type CycleTimeStats as j, type IdempotencyKeyFn as k, type OutOfOfficeProvider as l, type OverrideOptions as m, type PreviewResult as n, type ProvideInfoOptions as o, type RejectOptions as p, type RequestInfoOptions as q, type ResubmitOptions as r, type RetryPolicy as s, defaultIdGenerator as t, toComparableNumber as u, validateConditionExpression as v, weekendCalendar as w };
893
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, weekendCalendar as F, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type CancelOptions as h, type ConditionOperatorFn as i, type CycleTimeStats as j, type IdempotencyKeyFn as k, type ImportResult as l, type OutOfOfficeProvider as m, type OverrideOptions as n, type PreviewResult as o, type ProvideInfoOptions as p, type PurgeResult as q, type RejectOptions as r, type RequestInfoOptions as s, type ResubmitOptions as t, type RetryPolicy as u, type TemplateBundle as v, type TransferResult as w, defaultIdGenerator as x, toComparableNumber as y, validateConditionExpression as z };
@@ -71,6 +71,20 @@ interface IStorageAdapter {
71
71
  * @since 2.0.0 — required. See the release notes for the migration.
72
72
  */
73
73
  countInstances(tenantId: string, filter: InstanceFilter): Promise<number>;
74
+ /**
75
+ * Permanently remove one instance and its audit rows.
76
+ *
77
+ * **Optional.** An adapter that omits it simply cannot be purged, and
78
+ * {@link ApprovalEngine.purgeInstances} says so rather than pretending to
79
+ * have deleted anything. Left optional deliberately: for many deployments the
80
+ * approval trail is the compliance record and the right answer is that
81
+ * nothing is ever deleted, which an adapter expresses by not implementing
82
+ * this at all.
83
+ *
84
+ * @returns true if a row was removed, false if there was nothing to remove.
85
+ * @since 2.3.0
86
+ */
87
+ deleteInstance?(tenantId: string, id: string): Promise<boolean>;
74
88
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
75
89
  appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void>;
76
90
  }
@@ -71,6 +71,20 @@ interface IStorageAdapter {
71
71
  * @since 2.0.0 — required. See the release notes for the migration.
72
72
  */
73
73
  countInstances(tenantId: string, filter: InstanceFilter): Promise<number>;
74
+ /**
75
+ * Permanently remove one instance and its audit rows.
76
+ *
77
+ * **Optional.** An adapter that omits it simply cannot be purged, and
78
+ * {@link ApprovalEngine.purgeInstances} says so rather than pretending to
79
+ * have deleted anything. Left optional deliberately: for many deployments the
80
+ * approval trail is the compliance record and the right answer is that
81
+ * nothing is ever deleted, which an adapter expresses by not implementing
82
+ * this at all.
83
+ *
84
+ * @returns true if a row was removed, false if there was nothing to remove.
85
+ * @since 2.3.0
86
+ */
87
+ deleteInstance?(tenantId: string, id: string): Promise<boolean>;
74
88
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
75
89
  appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void>;
76
90
  }
@@ -167,6 +167,10 @@ var MemoryAdapter = class {
167
167
  return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry || hasDueReminder;
168
168
  }).map((i) => reviveDates(deepClone(i)));
169
169
  }
170
+ async deleteInstance(tenantId, id) {
171
+ const key = `${tenantId}:${id}`;
172
+ return this.instances.delete(key);
173
+ }
170
174
  async countInstances(tenantId, filter) {
171
175
  let count = 0;
172
176
  for (const instance of this.instances.values()) {
@@ -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\n/**\n * Read a dot-path from a document, over **own** properties only.\n *\n * Mirrors how conditions resolve field paths, so a filter and a condition\n * written against the same path agree about what that path means — and an\n * inherited prototype member can never make an instance match a query.\n */\nfunction readPath(data: Record<string, unknown>, path: string): unknown {\n return path.split('.').reduce<unknown>((obj, key) => {\n if (obj !== null && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, key)) {\n return (obj as Record<string, unknown>)[key];\n }\n return undefined;\n }, data);\n}\n\n/** Structural equality, so a filter can match an object or array value. */\nfunction deepEquals(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(\n (k) =>\n Object.prototype.hasOwnProperty.call(b, k) &&\n deepEquals((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\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 if (filter.data) {\n for (const [path, expected] of Object.entries(filter.data)) {\n if (!deepEquals(readPath(instance.data ?? {}, path), expected)) return false;\n }\n }\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 countInstances(tenantId: string, filter: InstanceFilter): Promise<number> {\n let count = 0;\n for (const instance of this.instances.values()) {\n if (instance.tenantId === tenantId && applyFilter(instance, filter)) count++;\n }\n return count;\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\n/**\n * Read a dot-path from a document, over **own** properties only.\n *\n * Mirrors how conditions resolve field paths, so a filter and a condition\n * written against the same path agree about what that path means — and an\n * inherited prototype member can never make an instance match a query.\n */\nfunction readPath(data: Record<string, unknown>, path: string): unknown {\n return path.split('.').reduce<unknown>((obj, key) => {\n if (obj !== null && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, key)) {\n return (obj as Record<string, unknown>)[key];\n }\n return undefined;\n }, data);\n}\n\n/** Structural equality, so a filter can match an object or array value. */\nfunction deepEquals(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(\n (k) =>\n Object.prototype.hasOwnProperty.call(b, k) &&\n deepEquals((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\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 if (filter.data) {\n for (const [path, expected] of Object.entries(filter.data)) {\n if (!deepEquals(readPath(instance.data ?? {}, path), expected)) return false;\n }\n }\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 deleteInstance(tenantId: string, id: string): Promise<boolean> {\n const key = `${tenantId}:${id}`;\n // The audit trail lives on the instance in this adapter, so removing the\n // instance removes it too — matching PostgresAdapter, which cascades.\n return this.instances.delete(key);\n }\n\n async countInstances(tenantId: string, filter: InstanceFilter): Promise<number> {\n let count = 0;\n for (const instance of this.instances.values()) {\n if (instance.tenantId === tenantId && applyFilter(instance, filter)) count++;\n }\n return count;\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,4 +1,4 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-Bk7ybd3z.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-DdHO4Rf1.cjs';
2
2
  import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-DUJY_Axf.cjs';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
@@ -13,6 +13,7 @@ declare class MemoryAdapter implements IStorageAdapter {
13
13
  getInstancesByApprover(tenantId: string, approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
14
14
  getInstancesByFilter(tenantId: string, filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
15
15
  getOverdueInstances(tenantId: string, asOf: Date, filter?: InstanceFilter): Promise<ApprovalInstance[]>;
16
+ deleteInstance(tenantId: string, id: string): Promise<boolean>;
16
17
  countInstances(tenantId: string, filter: InstanceFilter): Promise<number>;
17
18
  getInstancesByCursor(tenantId: string, filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
18
19
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
@@ -1,4 +1,4 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-DjRvHUF0.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-BU3sau5W.js';
2
2
  import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-DUJY_Axf.js';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
@@ -13,6 +13,7 @@ declare class MemoryAdapter implements IStorageAdapter {
13
13
  getInstancesByApprover(tenantId: string, approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
14
14
  getInstancesByFilter(tenantId: string, filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
15
15
  getOverdueInstances(tenantId: string, asOf: Date, filter?: InstanceFilter): Promise<ApprovalInstance[]>;
16
+ deleteInstance(tenantId: string, id: string): Promise<boolean>;
16
17
  countInstances(tenantId: string, filter: InstanceFilter): Promise<number>;
17
18
  getInstancesByCursor(tenantId: string, filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
18
19
  getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
@@ -165,6 +165,10 @@ var MemoryAdapter = class {
165
165
  return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry || hasDueReminder;
166
166
  }).map((i) => reviveDates(deepClone(i)));
167
167
  }
168
+ async deleteInstance(tenantId, id) {
169
+ const key = `${tenantId}:${id}`;
170
+ return this.instances.delete(key);
171
+ }
168
172
  async countInstances(tenantId, filter) {
169
173
  let count = 0;
170
174
  for (const instance of this.instances.values()) {