@cullet/erp-core 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +24 -24
  2. package/dist/gate-engine-registry.js.map +1 -1
  3. package/dist/gate-v1-payload.schema.js +5 -0
  4. package/dist/gate-v1-payload.schema.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/outcome.js.map +1 -1
  9. package/dist/policies/engines/v1/gate/index.d.ts +1 -0
  10. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  11. package/dist/policy-service.js.map +1 -1
  12. package/dist/validation-code.js.map +1 -1
  13. package/dist/validation-error.js.map +1 -1
  14. package/dist/validation-field.js.map +1 -1
  15. package/meta.json +67 -67
  16. package/package.json +56 -56
  17. package/src/core/application/commands/command.ts +3 -3
  18. package/src/core/application/commands/requested-by.ts +60 -60
  19. package/src/core/application/index.ts +21 -21
  20. package/src/core/application/policy-error-mapper.ts +54 -54
  21. package/src/core/application/ports/index.ts +6 -6
  22. package/src/core/application/ports/logger.port.ts +4 -4
  23. package/src/core/application/ports/metrics.port.ts +3 -3
  24. package/src/core/application/ports/policy-port.ts +7 -7
  25. package/src/core/application/ports/repository.port.ts +3 -3
  26. package/src/core/application/ports/temporal-repository.port.ts +4 -4
  27. package/src/core/application/ports/tracer.port.ts +7 -7
  28. package/src/core/application/queries/query.ts +13 -13
  29. package/src/core/application/temporal/index.ts +7 -7
  30. package/src/core/application/temporal/temporal-context.ts +20 -20
  31. package/src/core/application/temporal/temporal-use-case.ts +22 -22
  32. package/src/core/application/use-case.ts +10 -10
  33. package/src/core/config/core-config.instance.ts +1 -1
  34. package/src/core/config/core-config.ts +59 -59
  35. package/src/core/config/index.ts +6 -6
  36. package/src/core/config/policy-reporter.ts +31 -31
  37. package/src/core/config/silent-policy-reporter.ts +1 -1
  38. package/src/core/domain/entity.ts +44 -44
  39. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +6 -6
  40. package/src/core/domain/rulesets/ruleset-registry.ts +49 -49
  41. package/src/core/domain/rulesets/ruleset.contracts.ts +6 -6
  42. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +5 -5
  43. package/src/core/domain/temporal/index.ts +11 -11
  44. package/src/core/domain/temporal/temporal-range.ts +16 -16
  45. package/src/core/domain/temporal/temporal-snapshot.ts +24 -24
  46. package/src/core/domain/temporal/transaction-time.ts +30 -30
  47. package/src/core/domain/temporal/valid-time.ts +29 -29
  48. package/src/core/domain/value-object.ts +16 -16
  49. package/src/core/errors/app-error.ts +64 -64
  50. package/src/core/errors/authentication-error.ts +134 -134
  51. package/src/core/errors/authorization-error.ts +154 -154
  52. package/src/core/errors/business-rule-violation-error.ts +15 -15
  53. package/src/core/errors/conflict-error.ts +202 -201
  54. package/src/core/errors/error-codes.ts +48 -48
  55. package/src/core/errors/idempotency-error.ts +324 -320
  56. package/src/core/errors/index.ts +50 -50
  57. package/src/core/errors/integration-error.ts +120 -120
  58. package/src/core/errors/legacy-incompatible-error.ts +13 -13
  59. package/src/core/errors/not-found-error.ts +14 -14
  60. package/src/core/errors/serialization-error.ts +167 -167
  61. package/src/core/errors/temporal-error.ts +109 -107
  62. package/src/core/errors/types.ts +35 -35
  63. package/src/core/errors/unexpected-error.ts +10 -10
  64. package/src/core/errors/utils/index.ts +2 -2
  65. package/src/core/errors/utils/json-safe.ts +53 -53
  66. package/src/core/errors/validation-error.ts +18 -18
  67. package/src/core/exceptions/business-rule-violation-exception.ts +6 -6
  68. package/src/core/exceptions/domain-exception.ts +5 -5
  69. package/src/core/exceptions/entity-not-found-exception.ts +6 -6
  70. package/src/core/exceptions/invalid-state-transition-exception.ts +8 -8
  71. package/src/core/exceptions/invariant-violation-exception.ts +3 -3
  72. package/src/core/exceptions/validation-code.ts +35 -29
  73. package/src/core/exceptions/validation-exception.ts +19 -19
  74. package/src/core/exceptions/validation-field.ts +20 -19
  75. package/src/core/index.ts +7 -7
  76. package/src/core/policies/asof/asof.ts +47 -47
  77. package/src/core/policies/catalog/catalog.instance.ts +8 -6
  78. package/src/core/policies/catalog/index.ts +4 -4
  79. package/src/core/policies/catalog/policy-catalog-entry.ts +199 -197
  80. package/src/core/policies/catalog/policy-catalog.ts +101 -101
  81. package/src/core/policies/catalog/policy-key.ts +51 -51
  82. package/src/core/policies/context/context-builder.ts +335 -313
  83. package/src/core/policies/context/context-registry.instance.ts +9 -9
  84. package/src/core/policies/context/context-registry.ts +61 -61
  85. package/src/core/policies/context/context-resolver.ts +12 -12
  86. package/src/core/policies/context/context-seed.ts +19 -15
  87. package/src/core/policies/context/index.ts +9 -9
  88. package/src/core/policies/context/path.ts +135 -128
  89. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +56 -55
  90. package/src/core/policies/defs/index.ts +10 -10
  91. package/src/core/policies/defs/policy-definition-repository.ts +3 -3
  92. package/src/core/policies/defs/policy-definition.ts +143 -142
  93. package/src/core/policies/defs/policy-scope.ts +21 -19
  94. package/src/core/policies/defs/policy-semver.ts +51 -51
  95. package/src/core/policies/engines/compute-engine-registry.ts +43 -43
  96. package/src/core/policies/engines/compute-evaluator-registry.ts +20 -20
  97. package/src/core/policies/engines/compute-registry.ts +45 -45
  98. package/src/core/policies/engines/compute-types.ts +15 -15
  99. package/src/core/policies/engines/condition-evaluator-reporter.ts +17 -17
  100. package/src/core/policies/engines/gate-engine-registry.ts +27 -25
  101. package/src/core/policies/engines/gate-types.ts +28 -28
  102. package/src/core/policies/engines/index.ts +28 -28
  103. package/src/core/policies/engines/parse-compute-payload.ts +51 -45
  104. package/src/core/policies/engines/parse-gate-payload.ts +42 -42
  105. package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +39 -37
  106. package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +43 -41
  107. package/src/core/policies/engines/v1/compute/index.ts +6 -6
  108. package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +41 -39
  109. package/src/core/policies/engines/v1/condition-evaluator.ts +557 -504
  110. package/src/core/policies/engines/v1/condition-schema.ts +35 -35
  111. package/src/core/policies/engines/v1/condition-types.ts +21 -21
  112. package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +134 -130
  113. package/src/core/policies/engines/v1/gate/gate-types-v1.ts +5 -5
  114. package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +32 -30
  115. package/src/core/policies/engines/v1/gate/index.ts +4 -4
  116. package/src/core/policies/engines/v1/index.ts +8 -8
  117. package/src/core/policies/index.ts +72 -72
  118. package/src/core/policies/package/policy-package.ts +3 -3
  119. package/src/core/policies/policy-ids.ts +25 -25
  120. package/src/core/policies/resolver/policy-resolver.ts +34 -34
  121. package/src/core/policies/service/index.ts +6 -6
  122. package/src/core/policies/service/policy-evaluation-error.ts +141 -133
  123. package/src/core/policies/service/policy-service.ts +432 -419
  124. package/src/core/policies/utils/date.ts +3 -3
  125. package/src/core/policies/utils/hash.ts +61 -61
  126. package/src/core/result/outcome.ts +131 -126
  127. package/src/core/result/result.ts +130 -127
  128. package/src/core/shared/aggregate-version.ts +5 -5
  129. package/src/core/shared/hashing.ts +18 -18
  130. package/src/core/shared/immutable.ts +29 -29
  131. package/src/core/shared/temporal-guards.ts +9 -9
  132. package/src/core/versioning/domain-event-contracts.ts +40 -40
  133. package/src/core/versioning/version.ts +19 -19
  134. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +18 -18
  135. package/src/examples/rulesets/entity/order-invariants-v1.ts +31 -29
  136. package/src/examples/rulesets/entity/order-invariants-v2.ts +12 -12
  137. package/src/examples/rulesets/entity/order.ts +127 -127
  138. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +41 -39
  139. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +14 -12
  140. package/src/examples/rulesets/value-object/cpf.ts +24 -24
  141. package/src/examples/rulesets/value-object/customer.ts +42 -39
  142. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +27 -27
  143. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +13 -13
  144. package/src/examples/rulesets/value-object/person-name.ts +29 -26
  145. package/src/index.ts +2 -2
  146. package/src/version.ts +1 -1
@@ -3,590 +3,643 @@ import { PolicyDateUtils } from "../../utils";
3
3
  import { Result } from "../../../result/result";
4
4
 
5
5
  import type {
6
- ConditionAndNode,
7
- ConditionLeafNode,
8
- ConditionNode,
9
- ConditionNotNode,
10
- ConditionOp,
11
- ConditionOrNode,
6
+ ConditionAndNode,
7
+ ConditionLeafNode,
8
+ ConditionNode,
9
+ ConditionNotNode,
10
+ ConditionOp,
11
+ ConditionOrNode,
12
12
  } from "./condition-types";
13
13
  import type {
14
- ConditionEvaluationOptions,
15
- ConditionEvaluationReport,
14
+ ConditionEvaluationOptions,
15
+ ConditionEvaluationReport,
16
16
  } from "../condition-evaluator-reporter";
17
17
  import type { PolicyContext } from "../gate-types";
18
18
 
19
19
  const CONDITION_EVAL_THROWN_TAG = "CONDITION_EVAL_THREW";
20
20
  const NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG =
21
- "NULLISH_NUMERIC_OPERAND_NOT_ALLOWED";
21
+ "NULLISH_NUMERIC_OPERAND_NOT_ALLOWED";
22
22
  const NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG = "NULLISH_DATE_OPERAND_NOT_ALLOWED";
23
23
  const INVALID_DATE_OPERAND_TAG = "INVALID_DATE_OPERAND";
24
24
  const EMPTY_OR_CONDITION_TAG = "EMPTY_OR_CONDITION";
25
+ const EMPTY_AND_CONDITION_TAG = "EMPTY_AND_CONDITION";
25
26
  const ISO_8601_UTC_DATE_PATTERN =
26
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
27
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
27
28
 
28
29
  export interface ConditionEvaluationTrace {
29
- readonly conditionPath: string;
30
- readonly decisiveNode: ConditionNode;
31
- readonly lastEvaluatedLeaf: ConditionLeafNode;
32
- readonly lastEvaluatedLeafPath: string;
33
- readonly lastEvaluatedLeafResult: boolean;
30
+ readonly conditionPath: string;
31
+ readonly decisiveNode: ConditionNode;
32
+ readonly lastEvaluatedLeaf: ConditionLeafNode;
33
+ readonly lastEvaluatedLeafPath: string;
34
+ readonly lastEvaluatedLeafResult: boolean;
34
35
  }
35
36
 
36
37
  export interface TracedConditionEvaluation {
37
- readonly matched: boolean;
38
- readonly trace: ConditionEvaluationTrace;
38
+ readonly matched: boolean;
39
+ readonly trace: ConditionEvaluationTrace;
39
40
  }
40
41
 
41
42
  type RelationalOperator = Extract<ConditionOp, "gt" | "gte" | "lt" | "lte">;
42
43
 
43
44
  export class ConditionEvaluatorV1 {
44
- constructor(
45
- private readonly context: PolicyContext,
46
- private readonly options: ConditionEvaluationOptions,
47
- ) {}
48
-
49
- private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
50
- return "field" in node && "op" in node;
51
- }
52
-
53
- private static isAndNode(node: ConditionNode): node is ConditionAndNode {
54
- return "and" in node;
55
- }
56
-
57
- private static isOrNode(node: ConditionNode): node is ConditionOrNode {
58
- return "or" in node;
59
- }
60
-
61
- private static isNotNode(node: ConditionNode): node is ConditionNotNode {
62
- return "not" in node;
63
- }
64
-
65
- private static isRelationalOperator(
66
- op: ConditionOp,
67
- ): op is RelationalOperator {
68
- return op === "gt" || op === "gte" || op === "lt" || op === "lte";
69
- }
70
-
71
- private static describeError(error: unknown): {
72
- name: string;
73
- message: string;
74
- } {
75
- if (error instanceof Error) {
76
- return {
77
- name: error.name,
78
- message: error.message,
79
- };
80
- }
45
+ constructor(
46
+ private readonly context: PolicyContext,
47
+ private readonly options: ConditionEvaluationOptions,
48
+ ) {}
81
49
 
82
- return {
83
- name: "NonErrorThrown",
84
- message: String(error),
85
- };
86
- }
87
-
88
- private static buildNullishNumericOperandMessage(
89
- node: ConditionLeafNode,
90
- actual: null | undefined,
91
- ): string {
92
- const resolvedValue = actual === null ? "null" : "undefined";
93
-
94
- return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
95
- }
96
-
97
- private static buildNullishDateOperandMessage(
98
- node: ConditionLeafNode,
99
- actual: null | undefined,
100
- ): string {
101
- const resolvedValue = actual === null ? "null" : "undefined";
102
-
103
- return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
104
- }
105
-
106
- private static buildInvalidDateOperandMessage(
107
- node: ConditionLeafNode,
108
- ): string {
109
- return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
110
- }
111
-
112
- private static buildEmptyOrConditionMessage(): string {
113
- return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
114
- }
115
-
116
- private static parseComparableDate(
117
- value: unknown,
118
- ): Result<Date, string> | null {
119
- if (value instanceof Date) {
120
- if (!PolicyDateUtils.isValid(value)) {
121
- return Result.err("Invalid Date instance");
122
- }
123
-
124
- return Result.ok(value);
50
+ private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
51
+ return "field" in node && "op" in node;
125
52
  }
126
53
 
127
- if (typeof value !== "string" || !ISO_8601_UTC_DATE_PATTERN.test(value)) {
128
- return null;
54
+ private static isAndNode(node: ConditionNode): node is ConditionAndNode {
55
+ return "and" in node;
129
56
  }
130
57
 
131
- const parsed = new Date(value);
132
- if (!PolicyDateUtils.isValid(parsed) || parsed.toISOString() !== value) {
133
- return Result.err("Invalid ISO 8601 UTC date string");
58
+ private static isOrNode(node: ConditionNode): node is ConditionOrNode {
59
+ return "or" in node;
134
60
  }
135
61
 
136
- return Result.ok(parsed);
137
- }
138
-
139
- private static evaluateRelationalNumbers(
140
- actual: number,
141
- op: RelationalOperator,
142
- expected: number,
143
- ): boolean {
144
- switch (op) {
145
- case "gt":
146
- return actual > expected;
147
- case "gte":
148
- return actual >= expected;
149
- case "lt":
150
- return actual < expected;
151
- case "lte":
152
- return actual <= expected;
62
+ private static isNotNode(node: ConditionNode): node is ConditionNotNode {
63
+ return "not" in node;
153
64
  }
154
- }
155
-
156
- private buildReport(params: {
157
- readonly level: ConditionEvaluationReport["level"];
158
- readonly tag: ConditionEvaluationReport["tag"];
159
- readonly message: string;
160
- readonly details: Readonly<Record<string, unknown>>;
161
- }): ConditionEvaluationReport {
162
- return {
163
- occurredAt: new Date(),
164
- level: params.level,
165
- tag: params.tag,
166
- message: params.message,
167
- engineVersion: this.options.engineVersion,
168
- details: params.details,
169
- };
170
- }
171
-
172
- private conditionEvalErr<TValue>(
173
- node: ConditionNode,
174
- error: unknown,
175
- ): Result<TValue, string> {
176
- const cause = ConditionEvaluatorV1.describeError(error);
177
- const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
178
-
179
- this.options.reporter.error(
180
- this.buildReport({
181
- level: "error",
182
- tag: CONDITION_EVAL_THROWN_TAG,
183
- message,
184
- details: { node, cause },
185
- }),
186
- );
187
-
188
- return Result.err(message);
189
- }
190
-
191
- private reportDateOperandError(
192
- node: ConditionLeafNode,
193
- actual: unknown,
194
- ): Result<boolean, string> {
195
- const message = ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
196
-
197
- this.options.reporter.error(
198
- this.buildReport({
199
- level: "error",
200
- tag: INVALID_DATE_OPERAND_TAG,
201
- message,
202
- details: {
203
- field: node.field,
204
- op: node.op,
205
- actual,
206
- expected: node.value,
207
- node,
208
- },
209
- }),
210
- );
211
-
212
- return Result.err(message);
213
- }
214
-
215
- private evaluateDateRelationalNode(
216
- node: ConditionLeafNode,
217
- actual: unknown,
218
- ): Result<boolean, string> | null {
219
- if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
220
- return null;
65
+
66
+ private static isRelationalOperator(
67
+ op: ConditionOp,
68
+ ): op is RelationalOperator {
69
+ return op === "gt" || op === "gte" || op === "lt" || op === "lte";
221
70
  }
222
71
 
223
- const actualDateResult = ConditionEvaluatorV1.parseComparableDate(actual);
224
- const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
225
- node.value,
226
- );
72
+ private static describeError(error: unknown): {
73
+ name: string;
74
+ message: string;
75
+ } {
76
+ if (error instanceof Error) {
77
+ return {
78
+ name: error.name,
79
+ message: error.message,
80
+ };
81
+ }
227
82
 
228
- if (actualDateResult === null && expectedDateResult === null) {
229
- return null;
83
+ return {
84
+ name: "NonErrorThrown",
85
+ message: String(error),
86
+ };
230
87
  }
231
88
 
232
- const allowsNull = node.allowNull === true;
233
-
234
- if (actual === null) {
235
- if (allowsNull) {
236
- return Result.ok(false);
237
- }
238
-
239
- const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
240
- node,
241
- actual,
242
- );
243
- this.options.reporter.error(
244
- this.buildReport({
245
- level: "error",
246
- tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
247
- message,
248
- details: {
249
- field: node.field,
250
- op: node.op,
251
- actual,
252
- expected: node.value,
253
- allowNull: allowsNull,
254
- node,
255
- },
256
- }),
257
- );
89
+ private static buildNullishNumericOperandMessage(
90
+ node: ConditionLeafNode,
91
+ actual: null | undefined,
92
+ ): string {
93
+ const resolvedValue = actual === null ? "null" : "undefined";
258
94
 
259
- return Result.err(message);
95
+ return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
260
96
  }
261
97
 
262
- if (actual === undefined) {
263
- const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
264
- node,
265
- actual,
266
- );
267
- this.options.reporter.error(
268
- this.buildReport({
269
- level: "error",
270
- tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
271
- message,
272
- details: {
273
- field: node.field,
274
- op: node.op,
275
- actual,
276
- expected: node.value,
277
- allowNull: allowsNull,
278
- node,
279
- },
280
- }),
281
- );
98
+ private static buildNullishDateOperandMessage(
99
+ node: ConditionLeafNode,
100
+ actual: null | undefined,
101
+ ): string {
102
+ const resolvedValue = actual === null ? "null" : "undefined";
282
103
 
283
- return Result.err(message);
104
+ return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
284
105
  }
285
106
 
286
- if (
287
- actualDateResult === null ||
288
- actualDateResult.isErr() ||
289
- expectedDateResult === null ||
290
- expectedDateResult.isErr()
291
- ) {
292
- return this.reportDateOperandError(node, actual);
107
+ private static buildInvalidDateOperandMessage(
108
+ node: ConditionLeafNode,
109
+ ): string {
110
+ return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
293
111
  }
294
112
 
295
- return Result.ok(
296
- ConditionEvaluatorV1.evaluateRelationalNumbers(
297
- actualDateResult.getOrNull()!.getTime(),
298
- node.op,
299
- expectedDateResult.getOrNull()!.getTime(),
300
- ),
301
- );
302
- }
303
-
304
- private evaluateOperator(
305
- actual: unknown,
306
- op: ConditionOp,
307
- expected: unknown,
308
- ): boolean {
309
- switch (op) {
310
- case "eq":
311
- return actual === expected;
312
- case "neq":
313
- return actual !== expected;
314
- case "gt":
315
- return (
316
- typeof actual === "number" &&
317
- typeof expected === "number" &&
318
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
319
- );
320
- case "gte":
321
- return (
322
- typeof actual === "number" &&
323
- typeof expected === "number" &&
324
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
325
- );
326
- case "lt":
327
- return (
328
- typeof actual === "number" &&
329
- typeof expected === "number" &&
330
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
331
- );
332
- case "lte":
333
- return (
334
- typeof actual === "number" &&
335
- typeof expected === "number" &&
336
- ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
337
- );
338
- case "in":
339
- return Array.isArray(expected) && expected.includes(actual);
340
- case "notIn":
341
- return Array.isArray(expected) && !expected.includes(actual);
342
- case "isNull":
343
- return actual === null;
344
- case "isNotNull":
345
- return actual !== null && actual !== undefined;
113
+ private static buildEmptyOrConditionMessage(): string {
114
+ return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
346
115
  }
347
- }
348
-
349
- private evaluateNumericRelationalNode(
350
- node: ConditionLeafNode,
351
- actual: unknown,
352
- ): Result<boolean, string> {
353
- const allowsNull = node.allowNull === true;
354
- if (actual === undefined || (actual === null && !allowsNull)) {
355
- const message = ConditionEvaluatorV1.buildNullishNumericOperandMessage(
356
- node,
357
- actual as null | undefined,
358
- );
359
-
360
- this.options.reporter.error(
361
- this.buildReport({
362
- level: "error",
363
- tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
364
- message,
365
- details: {
366
- field: node.field,
367
- op: node.op,
368
- actual,
369
- expected: node.value,
370
- allowNull: allowsNull,
371
- node,
372
- },
373
- }),
374
- );
375
116
 
376
- return Result.err(message);
117
+ private static buildEmptyAndConditionMessage(): string {
118
+ return `${EMPTY_AND_CONDITION_TAG}: AND nodes must contain at least one child condition`;
377
119
  }
378
120
 
379
- return Result.ok(this.evaluateOperator(actual, node.op, node.value));
380
- }
381
-
382
- private evaluateLeafNode(node: ConditionLeafNode): Result<boolean, string> {
383
- const actualResult = PolicyContextPath.getOrAbsent(
384
- this.context,
385
- node.field,
386
- );
387
- if (actualResult.isErr()) {
388
- this.options.reporter.warn(
389
- this.buildReport({
390
- level: "warn",
391
- tag: "MISSING_CONTEXT_FIELD",
392
- message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
393
- details: { field: node.field, node },
394
- }),
395
- );
396
-
397
- return Result.err(
398
- `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
399
- );
400
- }
121
+ private static parseComparableDate(
122
+ value: unknown,
123
+ ): Result<Date, string> | null {
124
+ if (value instanceof Date) {
125
+ if (!PolicyDateUtils.isValid(value)) {
126
+ return Result.err("Invalid Date instance");
127
+ }
401
128
 
402
- try {
403
- const actual = actualResult.getOrThrow();
404
- if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
405
- const dateResult = this.evaluateDateRelationalNode(node, actual);
406
- if (dateResult !== null) {
407
- return dateResult;
129
+ return Result.ok(value);
408
130
  }
409
131
 
410
- return this.evaluateNumericRelationalNode(node, actual);
411
- }
132
+ if (
133
+ typeof value !== "string" ||
134
+ !ISO_8601_UTC_DATE_PATTERN.test(value)
135
+ ) {
136
+ return null;
137
+ }
412
138
 
413
- return Result.ok(this.evaluateOperator(actual, node.op, node.value));
414
- } catch (error) {
415
- return this.conditionEvalErr(node, error);
139
+ const parsed = new Date(value);
140
+ if (
141
+ !PolicyDateUtils.isValid(parsed) ||
142
+ parsed.toISOString() !== value
143
+ ) {
144
+ return Result.err("Invalid ISO 8601 UTC date string");
145
+ }
146
+
147
+ return Result.ok(parsed);
416
148
  }
417
- }
418
149
 
419
- private buildTrace(params: {
420
- readonly conditionPath: string;
421
- readonly decisiveNode: ConditionNode;
422
- readonly lastEvaluatedLeaf: ConditionLeafNode;
423
- readonly lastEvaluatedLeafPath: string;
424
- readonly lastEvaluatedLeafResult: boolean;
425
- }): ConditionEvaluationTrace {
426
- return {
427
- conditionPath: params.conditionPath,
428
- decisiveNode: params.decisiveNode,
429
- lastEvaluatedLeaf: params.lastEvaluatedLeaf,
430
- lastEvaluatedLeafPath: params.lastEvaluatedLeafPath,
431
- lastEvaluatedLeafResult: params.lastEvaluatedLeafResult,
432
- };
433
- }
434
-
435
- private evaluateWithTraceInternal(
436
- node: ConditionNode,
437
- path: string,
438
- ): Result<TracedConditionEvaluation, string> {
439
- if (ConditionEvaluatorV1.isLeafNode(node)) {
440
- const leafResult = this.evaluateLeafNode(node);
441
- if (leafResult.isErr()) {
442
- return Result.err(leafResult.errorOrNull()!);
443
- }
444
-
445
- const matched = leafResult.getOrNull()!;
446
- return Result.ok({
447
- matched,
448
- trace: this.buildTrace({
449
- conditionPath: path,
450
- decisiveNode: node,
451
- lastEvaluatedLeaf: node,
452
- lastEvaluatedLeafPath: path,
453
- lastEvaluatedLeafResult: matched,
454
- }),
455
- });
150
+ private static evaluateRelationalNumbers(
151
+ actual: number,
152
+ op: RelationalOperator,
153
+ expected: number,
154
+ ): boolean {
155
+ switch (op) {
156
+ case "gt":
157
+ return actual > expected;
158
+ case "gte":
159
+ return actual >= expected;
160
+ case "lt":
161
+ return actual < expected;
162
+ case "lte":
163
+ return actual <= expected;
164
+ }
456
165
  }
457
166
 
458
- if (ConditionEvaluatorV1.isAndNode(node)) {
459
- let lastTracedChild: TracedConditionEvaluation | null = null;
167
+ private buildReport(params: {
168
+ readonly level: ConditionEvaluationReport["level"];
169
+ readonly tag: ConditionEvaluationReport["tag"];
170
+ readonly message: string;
171
+ readonly details: Readonly<Record<string, unknown>>;
172
+ }): ConditionEvaluationReport {
173
+ return {
174
+ occurredAt: new Date(),
175
+ level: params.level,
176
+ tag: params.tag,
177
+ message: params.message,
178
+ engineVersion: this.options.engineVersion,
179
+ details: params.details,
180
+ };
181
+ }
460
182
 
461
- for (const [index, child] of node.and.entries()) {
462
- const childPath = `${path}.and[${index}]`;
463
- const childResult = this.evaluateWithTraceInternal(child, childPath);
464
- if (childResult.isErr()) {
465
- return Result.err(childResult.errorOrNull()!);
466
- }
183
+ private conditionEvalErr<TValue>(
184
+ node: ConditionNode,
185
+ error: unknown,
186
+ ): Result<TValue, string> {
187
+ const cause = ConditionEvaluatorV1.describeError(error);
188
+ const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
189
+
190
+ this.options.reporter.error(
191
+ this.buildReport({
192
+ level: "error",
193
+ tag: CONDITION_EVAL_THROWN_TAG,
194
+ message,
195
+ details: { node, cause },
196
+ }),
197
+ );
198
+
199
+ return Result.err(message);
200
+ }
467
201
 
468
- const tracedChild = childResult.getOrNull()!;
469
- lastTracedChild = tracedChild;
470
- if (!tracedChild.matched) {
471
- return Result.ok({
472
- matched: false,
473
- trace: this.buildTrace({
474
- conditionPath: childPath,
475
- decisiveNode: child,
476
- lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
477
- lastEvaluatedLeafPath: tracedChild.trace.lastEvaluatedLeafPath,
478
- lastEvaluatedLeafResult:
479
- tracedChild.trace.lastEvaluatedLeafResult,
202
+ private reportDateOperandError(
203
+ node: ConditionLeafNode,
204
+ actual: unknown,
205
+ ): Result<boolean, string> {
206
+ const message =
207
+ ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
208
+
209
+ this.options.reporter.error(
210
+ this.buildReport({
211
+ level: "error",
212
+ tag: INVALID_DATE_OPERAND_TAG,
213
+ message,
214
+ details: {
215
+ field: node.field,
216
+ op: node.op,
217
+ actual,
218
+ expected: node.value,
219
+ node,
220
+ },
480
221
  }),
481
- });
482
- }
483
- }
222
+ );
484
223
 
485
- return Result.ok({
486
- matched: true,
487
- trace: lastTracedChild!.trace,
488
- });
224
+ return Result.err(message);
489
225
  }
490
226
 
491
- if (ConditionEvaluatorV1.isOrNode(node)) {
492
- let lastTrace: ConditionEvaluationTrace | null = null;
227
+ private evaluateDateRelationalNode(
228
+ node: ConditionLeafNode,
229
+ actual: unknown,
230
+ ): Result<boolean, string> | null {
231
+ if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
232
+ return null;
233
+ }
493
234
 
494
- for (const [index, child] of node.or.entries()) {
495
- const childResult = this.evaluateWithTraceInternal(
496
- child,
497
- `${path}.or[${index}]`,
235
+ const actualDateResult =
236
+ ConditionEvaluatorV1.parseComparableDate(actual);
237
+ const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
238
+ node.value,
498
239
  );
499
- if (childResult.isErr()) {
500
- return Result.err(childResult.errorOrNull()!);
240
+
241
+ if (actualDateResult === null && expectedDateResult === null) {
242
+ return null;
501
243
  }
502
244
 
503
- const tracedChild = childResult.getOrNull()!;
504
- lastTrace = tracedChild.trace;
505
- if (tracedChild.matched) {
506
- return Result.ok({
507
- matched: true,
508
- trace: tracedChild.trace,
509
- });
245
+ const allowsNull = node.allowNull === true;
246
+
247
+ if (actual === null) {
248
+ if (allowsNull) {
249
+ return Result.ok(false);
250
+ }
251
+
252
+ const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
253
+ node,
254
+ actual,
255
+ );
256
+ this.options.reporter.error(
257
+ this.buildReport({
258
+ level: "error",
259
+ tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
260
+ message,
261
+ details: {
262
+ field: node.field,
263
+ op: node.op,
264
+ actual,
265
+ expected: node.value,
266
+ allowNull: allowsNull,
267
+ node,
268
+ },
269
+ }),
270
+ );
271
+
272
+ return Result.err(message);
510
273
  }
511
- }
512
274
 
513
- if (lastTrace === null) {
514
- return Result.err(ConditionEvaluatorV1.buildEmptyOrConditionMessage());
515
- }
275
+ if (actual === undefined) {
276
+ const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
277
+ node,
278
+ actual,
279
+ );
280
+ this.options.reporter.error(
281
+ this.buildReport({
282
+ level: "error",
283
+ tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
284
+ message,
285
+ details: {
286
+ field: node.field,
287
+ op: node.op,
288
+ actual,
289
+ expected: node.value,
290
+ allowNull: allowsNull,
291
+ node,
292
+ },
293
+ }),
294
+ );
295
+
296
+ return Result.err(message);
297
+ }
298
+
299
+ if (
300
+ actualDateResult === null ||
301
+ actualDateResult.isErr() ||
302
+ expectedDateResult === null ||
303
+ expectedDateResult.isErr()
304
+ ) {
305
+ return this.reportDateOperandError(node, actual);
306
+ }
516
307
 
517
- return Result.ok({ matched: false, trace: lastTrace! });
308
+ return Result.ok(
309
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
310
+ actualDateResult.getOrNull()!.getTime(),
311
+ node.op,
312
+ expectedDateResult.getOrNull()!.getTime(),
313
+ ),
314
+ );
518
315
  }
519
316
 
520
- if (ConditionEvaluatorV1.isNotNode(node)) {
521
- const childPath = `${path}.not`;
522
- const childResult = this.evaluateWithTraceInternal(node.not, childPath);
523
- if (childResult.isErr()) {
524
- return Result.err(childResult.errorOrNull()!);
525
- }
526
-
527
- const tracedChild = childResult.getOrNull()!;
528
- return Result.ok({
529
- matched: !tracedChild.matched,
530
- trace: this.buildTrace({
531
- conditionPath: path,
532
- decisiveNode: node,
533
- lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
534
- lastEvaluatedLeafPath: tracedChild.trace.lastEvaluatedLeafPath,
535
- lastEvaluatedLeafResult: tracedChild.trace.lastEvaluatedLeafResult,
536
- }),
537
- });
317
+ private evaluateOperator(
318
+ actual: unknown,
319
+ op: ConditionOp,
320
+ expected: unknown,
321
+ ): boolean {
322
+ switch (op) {
323
+ case "eq":
324
+ return actual === expected;
325
+ case "neq":
326
+ return actual !== expected;
327
+ case "gt":
328
+ return (
329
+ typeof actual === "number" &&
330
+ typeof expected === "number" &&
331
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
332
+ actual,
333
+ op,
334
+ expected,
335
+ )
336
+ );
337
+ case "gte":
338
+ return (
339
+ typeof actual === "number" &&
340
+ typeof expected === "number" &&
341
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
342
+ actual,
343
+ op,
344
+ expected,
345
+ )
346
+ );
347
+ case "lt":
348
+ return (
349
+ typeof actual === "number" &&
350
+ typeof expected === "number" &&
351
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
352
+ actual,
353
+ op,
354
+ expected,
355
+ )
356
+ );
357
+ case "lte":
358
+ return (
359
+ typeof actual === "number" &&
360
+ typeof expected === "number" &&
361
+ ConditionEvaluatorV1.evaluateRelationalNumbers(
362
+ actual,
363
+ op,
364
+ expected,
365
+ )
366
+ );
367
+ case "in":
368
+ return Array.isArray(expected) && expected.includes(actual);
369
+ case "notIn":
370
+ return Array.isArray(expected) && !expected.includes(actual);
371
+ case "isNull":
372
+ return actual === null;
373
+ case "isNotNull":
374
+ return actual !== null && actual !== undefined;
375
+ }
538
376
  }
539
377
 
540
- return this.conditionEvalErr(
541
- node,
542
- new TypeError("Invalid condition node shape"),
543
- );
544
- }
545
-
546
- evaluate(node: ConditionNode): Result<boolean, string> {
547
- try {
548
- const result = this.evaluateWithTraceInternal(node, "$");
549
- if (result.isErr()) {
550
- return Result.err(result.errorOrNull()!);
551
- }
552
-
553
- return Result.ok(result.getOrNull()!.matched);
554
- } catch (error) {
555
- return this.conditionEvalErr(node, error);
378
+ private evaluateNumericRelationalNode(
379
+ node: ConditionLeafNode,
380
+ actual: unknown,
381
+ ): Result<boolean, string> {
382
+ const allowsNull = node.allowNull === true;
383
+ if (actual === undefined || (actual === null && !allowsNull)) {
384
+ const message =
385
+ ConditionEvaluatorV1.buildNullishNumericOperandMessage(
386
+ node,
387
+ actual as null | undefined,
388
+ );
389
+
390
+ this.options.reporter.error(
391
+ this.buildReport({
392
+ level: "error",
393
+ tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
394
+ message,
395
+ details: {
396
+ field: node.field,
397
+ op: node.op,
398
+ actual,
399
+ expected: node.value,
400
+ allowNull: allowsNull,
401
+ node,
402
+ },
403
+ }),
404
+ );
405
+
406
+ return Result.err(message);
407
+ }
408
+
409
+ return Result.ok(this.evaluateOperator(actual, node.op, node.value));
556
410
  }
557
- }
558
-
559
- evaluateWithTrace(
560
- node: ConditionNode,
561
- ): Result<TracedConditionEvaluation, string> {
562
- try {
563
- return this.evaluateWithTraceInternal(node, "$");
564
- } catch (error) {
565
- return this.conditionEvalErr(node, error);
411
+
412
+ private evaluateLeafNode(node: ConditionLeafNode): Result<boolean, string> {
413
+ const actualResult = PolicyContextPath.getOrAbsent(
414
+ this.context,
415
+ node.field,
416
+ );
417
+ if (actualResult.isErr()) {
418
+ this.options.reporter.warn(
419
+ this.buildReport({
420
+ level: "warn",
421
+ tag: "MISSING_CONTEXT_FIELD",
422
+ message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
423
+ details: { field: node.field, node },
424
+ }),
425
+ );
426
+
427
+ return Result.err(
428
+ `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
429
+ );
430
+ }
431
+
432
+ try {
433
+ const actual = actualResult.getOrThrow();
434
+ if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
435
+ const dateResult = this.evaluateDateRelationalNode(
436
+ node,
437
+ actual,
438
+ );
439
+ if (dateResult !== null) {
440
+ return dateResult;
441
+ }
442
+
443
+ return this.evaluateNumericRelationalNode(node, actual);
444
+ }
445
+
446
+ return Result.ok(
447
+ this.evaluateOperator(actual, node.op, node.value),
448
+ );
449
+ } catch (error) {
450
+ return this.conditionEvalErr(node, error);
451
+ }
566
452
  }
567
- }
568
453
 
569
- static extractFields(node: ConditionNode): string[] {
570
- if (ConditionEvaluatorV1.isLeafNode(node)) {
571
- return [node.field];
454
+ private buildTrace(params: {
455
+ readonly conditionPath: string;
456
+ readonly decisiveNode: ConditionNode;
457
+ readonly lastEvaluatedLeaf: ConditionLeafNode;
458
+ readonly lastEvaluatedLeafPath: string;
459
+ readonly lastEvaluatedLeafResult: boolean;
460
+ }): ConditionEvaluationTrace {
461
+ return {
462
+ conditionPath: params.conditionPath,
463
+ decisiveNode: params.decisiveNode,
464
+ lastEvaluatedLeaf: params.lastEvaluatedLeaf,
465
+ lastEvaluatedLeafPath: params.lastEvaluatedLeafPath,
466
+ lastEvaluatedLeafResult: params.lastEvaluatedLeafResult,
467
+ };
572
468
  }
573
469
 
574
- if (ConditionEvaluatorV1.isAndNode(node)) {
575
- return node.and.flatMap((child) =>
576
- ConditionEvaluatorV1.extractFields(child),
577
- );
470
+ private evaluateWithTraceInternal(
471
+ node: ConditionNode,
472
+ path: string,
473
+ ): Result<TracedConditionEvaluation, string> {
474
+ if (ConditionEvaluatorV1.isLeafNode(node)) {
475
+ const leafResult = this.evaluateLeafNode(node);
476
+ if (leafResult.isErr()) {
477
+ return Result.err(leafResult.errorOrNull()!);
478
+ }
479
+
480
+ const matched = leafResult.getOrNull()!;
481
+ return Result.ok({
482
+ matched,
483
+ trace: this.buildTrace({
484
+ conditionPath: path,
485
+ decisiveNode: node,
486
+ lastEvaluatedLeaf: node,
487
+ lastEvaluatedLeafPath: path,
488
+ lastEvaluatedLeafResult: matched,
489
+ }),
490
+ });
491
+ }
492
+
493
+ if (ConditionEvaluatorV1.isAndNode(node)) {
494
+ if (node.and.length === 0) {
495
+ return Result.err(
496
+ ConditionEvaluatorV1.buildEmptyAndConditionMessage(),
497
+ );
498
+ }
499
+
500
+ let lastTracedChild: TracedConditionEvaluation | null = null;
501
+
502
+ for (const [index, child] of node.and.entries()) {
503
+ const childPath = `${path}.and[${index}]`;
504
+ const childResult = this.evaluateWithTraceInternal(
505
+ child,
506
+ childPath,
507
+ );
508
+ if (childResult.isErr()) {
509
+ return Result.err(childResult.errorOrNull()!);
510
+ }
511
+
512
+ const tracedChild = childResult.getOrNull()!;
513
+ lastTracedChild = tracedChild;
514
+ if (!tracedChild.matched) {
515
+ return Result.ok({
516
+ matched: false,
517
+ trace: this.buildTrace({
518
+ conditionPath: childPath,
519
+ decisiveNode: child,
520
+ lastEvaluatedLeaf:
521
+ tracedChild.trace.lastEvaluatedLeaf,
522
+ lastEvaluatedLeafPath:
523
+ tracedChild.trace.lastEvaluatedLeafPath,
524
+ lastEvaluatedLeafResult:
525
+ tracedChild.trace.lastEvaluatedLeafResult,
526
+ }),
527
+ });
528
+ }
529
+ }
530
+
531
+ return Result.ok({
532
+ matched: true,
533
+ trace: lastTracedChild!.trace,
534
+ });
535
+ }
536
+
537
+ if (ConditionEvaluatorV1.isOrNode(node)) {
538
+ let lastTrace: ConditionEvaluationTrace | null = null;
539
+
540
+ for (const [index, child] of node.or.entries()) {
541
+ const childResult = this.evaluateWithTraceInternal(
542
+ child,
543
+ `${path}.or[${index}]`,
544
+ );
545
+ if (childResult.isErr()) {
546
+ return Result.err(childResult.errorOrNull()!);
547
+ }
548
+
549
+ const tracedChild = childResult.getOrNull()!;
550
+ lastTrace = tracedChild.trace;
551
+ if (tracedChild.matched) {
552
+ return Result.ok({
553
+ matched: true,
554
+ trace: tracedChild.trace,
555
+ });
556
+ }
557
+ }
558
+
559
+ if (lastTrace === null) {
560
+ return Result.err(
561
+ ConditionEvaluatorV1.buildEmptyOrConditionMessage(),
562
+ );
563
+ }
564
+
565
+ return Result.ok({ matched: false, trace: lastTrace! });
566
+ }
567
+
568
+ if (ConditionEvaluatorV1.isNotNode(node)) {
569
+ const childPath = `${path}.not`;
570
+ const childResult = this.evaluateWithTraceInternal(
571
+ node.not,
572
+ childPath,
573
+ );
574
+ if (childResult.isErr()) {
575
+ return Result.err(childResult.errorOrNull()!);
576
+ }
577
+
578
+ const tracedChild = childResult.getOrNull()!;
579
+ return Result.ok({
580
+ matched: !tracedChild.matched,
581
+ trace: this.buildTrace({
582
+ conditionPath: path,
583
+ decisiveNode: node,
584
+ lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
585
+ lastEvaluatedLeafPath:
586
+ tracedChild.trace.lastEvaluatedLeafPath,
587
+ lastEvaluatedLeafResult:
588
+ tracedChild.trace.lastEvaluatedLeafResult,
589
+ }),
590
+ });
591
+ }
592
+
593
+ return this.conditionEvalErr(
594
+ node,
595
+ new TypeError("Invalid condition node shape"),
596
+ );
578
597
  }
579
598
 
580
- if (ConditionEvaluatorV1.isOrNode(node)) {
581
- return node.or.flatMap((child) =>
582
- ConditionEvaluatorV1.extractFields(child),
583
- );
599
+ evaluate(node: ConditionNode): Result<boolean, string> {
600
+ try {
601
+ const result = this.evaluateWithTraceInternal(node, "$");
602
+ if (result.isErr()) {
603
+ return Result.err(result.errorOrNull()!);
604
+ }
605
+
606
+ return Result.ok(result.getOrNull()!.matched);
607
+ } catch (error) {
608
+ return this.conditionEvalErr(node, error);
609
+ }
584
610
  }
585
611
 
586
- if (ConditionEvaluatorV1.isNotNode(node)) {
587
- return ConditionEvaluatorV1.extractFields(node.not);
612
+ evaluateWithTrace(
613
+ node: ConditionNode,
614
+ ): Result<TracedConditionEvaluation, string> {
615
+ try {
616
+ return this.evaluateWithTraceInternal(node, "$");
617
+ } catch (error) {
618
+ return this.conditionEvalErr(node, error);
619
+ }
588
620
  }
589
621
 
590
- return [];
591
- }
622
+ static extractFields(node: ConditionNode): string[] {
623
+ if (ConditionEvaluatorV1.isLeafNode(node)) {
624
+ return [node.field];
625
+ }
626
+
627
+ if (ConditionEvaluatorV1.isAndNode(node)) {
628
+ return node.and.flatMap((child) =>
629
+ ConditionEvaluatorV1.extractFields(child),
630
+ );
631
+ }
632
+
633
+ if (ConditionEvaluatorV1.isOrNode(node)) {
634
+ return node.or.flatMap((child) =>
635
+ ConditionEvaluatorV1.extractFields(child),
636
+ );
637
+ }
638
+
639
+ if (ConditionEvaluatorV1.isNotNode(node)) {
640
+ return ConditionEvaluatorV1.extractFields(node.not);
641
+ }
642
+
643
+ return [];
644
+ }
592
645
  }