hierarchical-approval 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +41 -0
- package/dist/{ApprovalEngine-BGfrrFxl.d.ts → ApprovalEngine-DFY53zRK.d.ts} +25 -7
- package/dist/{ApprovalEngine-BET-UqQo.d.cts → ApprovalEngine-DVMMtiSm.d.cts} +25 -7
- package/dist/{IAuditAdapter-B_DhuPsU.d.cts → IAuditAdapter-BODIlw4h.d.cts} +1 -1
- package/dist/{IAuditAdapter-B3vvU09m.d.ts → IAuditAdapter-CaM3A2Kt.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-CZESF3CJ.d.ts → IAuthorizationPolicy-DFD0ELtO.d.ts} +1 -1
- package/dist/{IAuthorizationPolicy-B6JzRNUk.d.cts → IAuthorizationPolicy-cT7LSHtl.d.cts} +1 -1
- package/dist/{INotificationAdapter-DVVmXU6a.d.cts → INotificationAdapter-Ci0gf6ac.d.cts} +1 -1
- package/dist/{INotificationAdapter-BdfVjYa8.d.ts → INotificationAdapter-Hn70VShV.d.ts} +1 -1
- package/dist/{IOperationMiddleware-KGAwT9f-.d.ts → IOperationMiddleware-BCcAqSzT.d.ts} +1 -1
- package/dist/{IOperationMiddleware-CXgXmGUF.d.cts → IOperationMiddleware-DOlOraCX.d.cts} +1 -1
- package/dist/{IStorageAdapter-D-oPxxun.d.ts → IStorageAdapter-D7jsZT6O.d.ts} +1 -1
- package/dist/{IStorageAdapter-BibHlgPw.d.cts → IStorageAdapter-egPQD6Ry.d.cts} +1 -1
- package/dist/adapters/MemoryAdapter.d.cts +2 -2
- package/dist/adapters/MemoryAdapter.d.ts +2 -2
- package/dist/adapters/PostgresAdapter.d.cts +2 -2
- package/dist/adapters/PostgresAdapter.d.ts +2 -2
- package/dist/index.cjs +81 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +81 -6
- package/dist/index.js.map +1 -1
- package/dist/{instance-D8D7b07N.d.cts → instance-CtkmEkYa.d.cts} +34 -2
- package/dist/{instance-D8D7b07N.d.ts → instance-CtkmEkYa.d.ts} +34 -2
- package/dist/nestjs.cjs +80 -5
- package/dist/nestjs.cjs.map +1 -1
- package/dist/nestjs.d.cts +7 -7
- package/dist/nestjs.d.ts +7 -7
- package/dist/nestjs.js +80 -5
- package/dist/nestjs.js.map +1 -1
- package/dist/plugins/audit.d.cts +2 -2
- package/dist/plugins/audit.d.ts +2 -2
- package/dist/plugins/notify.d.cts +2 -2
- package/dist/plugins/notify.d.ts +2 -2
- package/dist/plugins/resilience.d.cts +3 -3
- package/dist/plugins/resilience.d.ts +3 -3
- package/dist/plugins/tracing.d.cts +2 -2
- package/dist/plugins/tracing.d.ts +2 -2
- package/dist/plugins/webhook.d.cts +2 -2
- package/dist/plugins/webhook.d.ts +2 -2
- package/dist/testing.cjs +80 -5
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +7 -7
- package/dist/testing.d.ts +7 -7
- package/dist/testing.js +80 -5
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,51 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
_Nothing yet._
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Added — boolean condition expressions
|
|
13
|
+
|
|
14
|
+
- **A rule's `when` now accepts `all`, `any` and `not` groups, nestable to any
|
|
15
|
+
depth.** Conditions previously supported a single test or an array meaning
|
|
16
|
+
AND, so "escalate when the amount is large **or** the vendor is high-risk"
|
|
17
|
+
could not be written as one rule — it took two rules with duplicated
|
|
18
|
+
`addLevels`, and anything involving negation or a mix of AND and OR had no
|
|
19
|
+
expression at all.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
when: {
|
|
23
|
+
any: [
|
|
24
|
+
{ all: [
|
|
25
|
+
{ field: 'amount', operator: '>', value: 1000 },
|
|
26
|
+
{ field: 'dept', operator: '==', value: 'engineering' },
|
|
27
|
+
] },
|
|
28
|
+
{ field: 'override', operator: '==', value: true },
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A group sets exactly one combinator. `any` short-circuits on the first child
|
|
34
|
+
that holds. New `ConditionExpression` and `ConditionGroup` types are exported
|
|
35
|
+
from the package root.
|
|
36
|
+
|
|
37
|
+
**Fully backward compatible.** A bare condition still works, and an array is
|
|
38
|
+
shorthand for `all` — exactly what `when: [...]` already meant.
|
|
39
|
+
|
|
40
|
+
- **Condition trees are validated when the template is defined.** `validateTemplate()`
|
|
41
|
+
and `defineTemplate()` now reject an empty `all`/`any`, a group setting more
|
|
42
|
+
than one combinator, a non-array `all`/`any`, and a leaf missing its `field`
|
|
43
|
+
or `operator` — reporting the offending path, e.g.
|
|
44
|
+
`conditions[0].when.any[1].operator`. Previously a malformed condition was
|
|
45
|
+
only discovered at submit time, on a real document.
|
|
46
|
+
|
|
47
|
+
Operator *names* are deliberately not checked at definition time, because
|
|
48
|
+
custom operators can be registered after a template is defined; an unknown
|
|
49
|
+
operator still throws when the condition is evaluated.
|
|
50
|
+
|
|
51
|
+
- **`validateConditionExpression(expression, path)` is exported** for callers
|
|
52
|
+
that build condition trees dynamically and want to check one before handing it
|
|
53
|
+
to a template. It collects every problem rather than throwing on the first.
|
|
54
|
+
|
|
10
55
|
## [0.7.0] - 2026-09-04
|
|
11
56
|
|
|
12
57
|
### Fixed — two condition-evaluation bypasses
|
package/README.md
CHANGED
|
@@ -305,6 +305,47 @@ conditions: [
|
|
|
305
305
|
|
|
306
306
|
**Built-in operators:** `>`, `<`, `>=`, `<=`, `==`, `!=`, `in`, `not_in`
|
|
307
307
|
|
|
308
|
+
#### Combining conditions
|
|
309
|
+
|
|
310
|
+
A `when` can be a single condition, an array (every element must hold), or a
|
|
311
|
+
boolean group — `all`, `any`, `not` — which nest to any depth:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
conditions: [
|
|
315
|
+
{
|
|
316
|
+
// (amount > 1000 AND dept is engineering) OR an explicit override flag
|
|
317
|
+
when: {
|
|
318
|
+
any: [
|
|
319
|
+
{
|
|
320
|
+
all: [
|
|
321
|
+
{ field: 'amount', operator: '>', value: 1000 },
|
|
322
|
+
{ field: 'dept', operator: '==', value: 'engineering' },
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
{ field: 'override', operator: '==', value: true },
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
addLevels: [{ level: 3, name: 'CFO', approvers: [{ type: 'user', userId: 'cfo' }], mode: 'any' }],
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
// Everything outside the US skips the domestic finance review
|
|
332
|
+
when: { not: { field: 'region', operator: '==', value: 'US' } },
|
|
333
|
+
skipLevels: [2],
|
|
334
|
+
},
|
|
335
|
+
]
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
A group sets exactly one of `all` / `any` / `not`. `any` short-circuits on the
|
|
339
|
+
first child that holds. An array is shorthand for `all`, so every template
|
|
340
|
+
written before groups existed keeps working unchanged.
|
|
341
|
+
|
|
342
|
+
Groups are checked when the template is defined, not when a document is
|
|
343
|
+
submitted — `validateTemplate()` and `defineTemplate()` reject an empty `all`,
|
|
344
|
+
a group setting two combinators, or a leaf missing its `field`/`operator`, and
|
|
345
|
+
report the offending path (`conditions[0].when.any[1].operator`). Operator
|
|
346
|
+
*names* are deliberately not checked there, since custom operators may be
|
|
347
|
+
registered after the template is defined.
|
|
348
|
+
|
|
308
349
|
#### How values are compared
|
|
309
350
|
|
|
310
351
|
`==` and `!=` compare **strictly** — `'100'` does not equal `100`.
|
|
@@ -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-
|
|
2
|
-
import {
|
|
3
|
-
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-
|
|
1
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-D7jsZT6O.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-CtkmEkYa.js';
|
|
3
|
+
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Hn70VShV.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-
|
|
8
|
-
import { I as IAuditAdapter } from './IAuditAdapter-
|
|
7
|
+
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BCcAqSzT.js';
|
|
8
|
+
import { I as IAuditAdapter } from './IAuditAdapter-CaM3A2Kt.js';
|
|
9
9
|
import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.js';
|
|
10
10
|
import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
|
|
11
|
-
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
11
|
+
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DFD0ELtO.js';
|
|
12
12
|
|
|
13
13
|
declare const SubmitOptionsSchema: z.ZodObject<{
|
|
14
14
|
templateName: z.ZodString;
|
|
@@ -150,6 +150,24 @@ type ConditionOperatorFn = (actual: unknown, expected: unknown) => boolean;
|
|
|
150
150
|
* @returns The numeric value, or `null` when the value is not comparable.
|
|
151
151
|
*/
|
|
152
152
|
declare function toComparableNumber(value: unknown): number | null;
|
|
153
|
+
/**
|
|
154
|
+
* Statically check a condition expression tree, collecting every problem rather
|
|
155
|
+
* than throwing on the first.
|
|
156
|
+
*
|
|
157
|
+
* Runs at template-definition time so a malformed group is caught while the
|
|
158
|
+
* author is looking at it, instead of at submit time on a real document. The
|
|
159
|
+
* operator check is deliberately deferred: custom operators can be registered
|
|
160
|
+
* after a template is defined, so an unknown name is only an error once the
|
|
161
|
+
* condition is actually evaluated.
|
|
162
|
+
*
|
|
163
|
+
* @param expression - The `when` expression to check.
|
|
164
|
+
* @param path - Field path prefix used in reported errors.
|
|
165
|
+
* @returns One entry per problem found; empty when the tree is well formed.
|
|
166
|
+
*/
|
|
167
|
+
declare function validateConditionExpression(expression: ConditionExpression, path: string): Array<{
|
|
168
|
+
field: string;
|
|
169
|
+
message: string;
|
|
170
|
+
}>;
|
|
153
171
|
|
|
154
172
|
interface ValidationResult {
|
|
155
173
|
valid: boolean;
|
|
@@ -435,4 +453,4 @@ declare class ApprovalEngine {
|
|
|
435
453
|
private runExternalAudit;
|
|
436
454
|
}
|
|
437
455
|
|
|
438
|
-
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 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, weekendCalendar as w };
|
|
456
|
+
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 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
|
|
@@ -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-
|
|
2
|
-
import {
|
|
3
|
-
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-
|
|
1
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-egPQD6Ry.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-CtkmEkYa.cjs';
|
|
3
|
+
import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Ci0gf6ac.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-
|
|
8
|
-
import { I as IAuditAdapter } from './IAuditAdapter-
|
|
7
|
+
import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-DOlOraCX.cjs';
|
|
8
|
+
import { I as IAuditAdapter } from './IAuditAdapter-BODIlw4h.cjs';
|
|
9
9
|
import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.cjs';
|
|
10
10
|
import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
|
|
11
|
-
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-
|
|
11
|
+
import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-cT7LSHtl.cjs';
|
|
12
12
|
|
|
13
13
|
declare const SubmitOptionsSchema: z.ZodObject<{
|
|
14
14
|
templateName: z.ZodString;
|
|
@@ -150,6 +150,24 @@ type ConditionOperatorFn = (actual: unknown, expected: unknown) => boolean;
|
|
|
150
150
|
* @returns The numeric value, or `null` when the value is not comparable.
|
|
151
151
|
*/
|
|
152
152
|
declare function toComparableNumber(value: unknown): number | null;
|
|
153
|
+
/**
|
|
154
|
+
* Statically check a condition expression tree, collecting every problem rather
|
|
155
|
+
* than throwing on the first.
|
|
156
|
+
*
|
|
157
|
+
* Runs at template-definition time so a malformed group is caught while the
|
|
158
|
+
* author is looking at it, instead of at submit time on a real document. The
|
|
159
|
+
* operator check is deliberately deferred: custom operators can be registered
|
|
160
|
+
* after a template is defined, so an unknown name is only an error once the
|
|
161
|
+
* condition is actually evaluated.
|
|
162
|
+
*
|
|
163
|
+
* @param expression - The `when` expression to check.
|
|
164
|
+
* @param path - Field path prefix used in reported errors.
|
|
165
|
+
* @returns One entry per problem found; empty when the tree is well formed.
|
|
166
|
+
*/
|
|
167
|
+
declare function validateConditionExpression(expression: ConditionExpression, path: string): Array<{
|
|
168
|
+
field: string;
|
|
169
|
+
message: string;
|
|
170
|
+
}>;
|
|
153
171
|
|
|
154
172
|
interface ValidationResult {
|
|
155
173
|
valid: boolean;
|
|
@@ -435,4 +453,4 @@ declare class ApprovalEngine {
|
|
|
435
453
|
private runExternalAudit;
|
|
436
454
|
}
|
|
437
455
|
|
|
438
|
-
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 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, weekendCalendar as w };
|
|
456
|
+
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 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CtkmEkYa.js';
|
|
2
2
|
|
|
3
3
|
interface AuthorizationContext {
|
|
4
4
|
operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-
|
|
1
|
+
import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CtkmEkYa.cjs';
|
|
2
2
|
|
|
3
3
|
interface AuthorizationContext {
|
|
4
4
|
operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-
|
|
1
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-CtkmEkYa.js';
|
|
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-
|
|
1
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-CtkmEkYa.cjs';
|
|
2
2
|
|
|
3
3
|
interface PaginationOpts {
|
|
4
4
|
limit: number;
|
|
@@ -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-
|
|
2
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-
|
|
1
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-egPQD6Ry.cjs';
|
|
2
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-CtkmEkYa.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-
|
|
2
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-
|
|
1
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D7jsZT6O.js';
|
|
2
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-CtkmEkYa.js';
|
|
3
3
|
|
|
4
4
|
declare class MemoryAdapter implements IStorageAdapter {
|
|
5
5
|
private templates;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as tls from 'tls';
|
|
2
2
|
import * as pg from 'pg';
|
|
3
|
-
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-
|
|
4
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-
|
|
3
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-egPQD6Ry.cjs';
|
|
4
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-CtkmEkYa.cjs';
|
|
5
5
|
|
|
6
6
|
interface PostgresAdapterOptions {
|
|
7
7
|
connectionString?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as tls from 'tls';
|
|
2
2
|
import * as pg from 'pg';
|
|
3
|
-
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-
|
|
4
|
-
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-
|
|
3
|
+
import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D7jsZT6O.js';
|
|
4
|
+
import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-CtkmEkYa.js';
|
|
5
5
|
|
|
6
6
|
interface PostgresAdapterOptions {
|
|
7
7
|
connectionString?: string;
|
package/dist/index.cjs
CHANGED
|
@@ -498,16 +498,88 @@ function evaluateCondition(condition, data) {
|
|
|
498
498
|
const actual = getField(data, condition.field);
|
|
499
499
|
return fn(actual, condition.value);
|
|
500
500
|
}
|
|
501
|
-
function
|
|
502
|
-
if (Array.isArray(
|
|
503
|
-
return
|
|
501
|
+
function asGroup(expression) {
|
|
502
|
+
if (expression === null || typeof expression !== "object" || Array.isArray(expression)) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
const candidate = expression;
|
|
506
|
+
const keys = ["all", "any", "not"].filter((k) => candidate[k] !== void 0);
|
|
507
|
+
if (keys.length === 0) return null;
|
|
508
|
+
if (keys.length > 1) {
|
|
509
|
+
throw new ApprovalValidationError(
|
|
510
|
+
`Condition group must set exactly one of "all", "any" or "not" (got ${keys.join(", ")}).`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
const key = keys[0];
|
|
514
|
+
if (key !== "not" && !Array.isArray(candidate[key])) {
|
|
515
|
+
throw new ApprovalValidationError(`Condition group "${key}" must be an array of expressions.`);
|
|
516
|
+
}
|
|
517
|
+
if (key !== "not" && candidate[key].length === 0) {
|
|
518
|
+
throw new ApprovalValidationError(`Condition group "${key}" must not be empty.`);
|
|
519
|
+
}
|
|
520
|
+
return expression;
|
|
521
|
+
}
|
|
522
|
+
function evaluateExpression(expression, data) {
|
|
523
|
+
if (Array.isArray(expression)) {
|
|
524
|
+
return expression.every((child) => evaluateExpression(child, data));
|
|
525
|
+
}
|
|
526
|
+
const group = asGroup(expression);
|
|
527
|
+
if (group === null) {
|
|
528
|
+
return evaluateCondition(expression, data);
|
|
529
|
+
}
|
|
530
|
+
if (group.all !== void 0) {
|
|
531
|
+
return group.all.every((child) => evaluateExpression(child, data));
|
|
532
|
+
}
|
|
533
|
+
if (group.any !== void 0) {
|
|
534
|
+
return group.any.some((child) => evaluateExpression(child, data));
|
|
504
535
|
}
|
|
505
|
-
return
|
|
536
|
+
return !evaluateExpression(group.not, data);
|
|
537
|
+
}
|
|
538
|
+
function validateConditionExpression(expression, path) {
|
|
539
|
+
const errors = [];
|
|
540
|
+
const walk = (node, at) => {
|
|
541
|
+
if (Array.isArray(node)) {
|
|
542
|
+
if (node.length === 0) {
|
|
543
|
+
errors.push({ field: at, message: "Condition list must not be empty." });
|
|
544
|
+
}
|
|
545
|
+
node.forEach((child, i) => walk(child, `${at}[${i}]`));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
let group;
|
|
549
|
+
try {
|
|
550
|
+
group = asGroup(node);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
errors.push({ field: at, message: err.message });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (group === null) {
|
|
556
|
+
const leaf = node;
|
|
557
|
+
if (typeof leaf?.field !== "string" || leaf.field.length === 0) {
|
|
558
|
+
errors.push({
|
|
559
|
+
field: `${at}.field`,
|
|
560
|
+
message: "Condition requires a non-empty field path."
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
if (typeof leaf?.operator !== "string" || leaf.operator.length === 0) {
|
|
564
|
+
errors.push({ field: `${at}.operator`, message: "Condition requires an operator." });
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (group.all !== void 0) {
|
|
569
|
+
group.all.forEach((child, i) => walk(child, `${at}.all[${i}]`));
|
|
570
|
+
} else if (group.any !== void 0) {
|
|
571
|
+
group.any.forEach((child, i) => walk(child, `${at}.any[${i}]`));
|
|
572
|
+
} else {
|
|
573
|
+
walk(group.not, `${at}.not`);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
walk(expression, path);
|
|
577
|
+
return errors;
|
|
506
578
|
}
|
|
507
579
|
function evaluateConditions(conditions, data) {
|
|
508
580
|
const mutations = { addLevels: [], skipLevels: /* @__PURE__ */ new Set() };
|
|
509
581
|
for (const rule of conditions) {
|
|
510
|
-
if (
|
|
582
|
+
if (evaluateExpression(rule.when, data)) {
|
|
511
583
|
if (rule.addLevels) mutations.addLevels.push(...rule.addLevels);
|
|
512
584
|
if (rule.skipLevels) rule.skipLevels.forEach((l) => mutations.skipLevels.add(l));
|
|
513
585
|
}
|
|
@@ -805,6 +877,9 @@ var ApprovalEngine = class {
|
|
|
805
877
|
}
|
|
806
878
|
if (config.conditions) {
|
|
807
879
|
config.conditions.forEach((rule, ruleIdx) => {
|
|
880
|
+
errors.push(
|
|
881
|
+
...validateConditionExpression(rule.when, `conditions[${ruleIdx}].when`)
|
|
882
|
+
);
|
|
808
883
|
if (rule.addLevels) {
|
|
809
884
|
rule.addLevels.forEach((al, alIdx) => {
|
|
810
885
|
const conflictsWithStatic = config.levels.some((l) => l.level === al.level);
|
|
@@ -2619,6 +2694,7 @@ exports.defaultIdGenerator = defaultIdGenerator;
|
|
|
2619
2694
|
exports.noopLogger = noopLogger;
|
|
2620
2695
|
exports.systemClock = systemClock;
|
|
2621
2696
|
exports.toComparableNumber = toComparableNumber;
|
|
2697
|
+
exports.validateConditionExpression = validateConditionExpression;
|
|
2622
2698
|
exports.weekendCalendar = weekendCalendar;
|
|
2623
2699
|
//# sourceMappingURL=index.cjs.map
|
|
2624
2700
|
//# sourceMappingURL=index.cjs.map
|