@cullet/erp-core 1.5.0 → 2.0.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/KIT_CONTEXT.md +2 -2
- package/dist/abac/index.d.cts +2 -2
- package/dist/abac/index.d.ts +2 -2
- package/dist/aggregate-version.cjs +14 -0
- package/dist/aggregate-version.cjs.map +1 -0
- package/dist/aggregate-version.js +9 -0
- package/dist/aggregate-version.js.map +1 -0
- package/dist/app-error.cjs +8 -1
- package/dist/app-error.cjs.map +1 -1
- package/dist/app-error.js +8 -1
- package/dist/app-error.js.map +1 -1
- package/dist/application/index.cjs +8 -4
- package/dist/application/index.d.cts +2 -2
- package/dist/application/index.d.ts +2 -2
- package/dist/application/index.js +1 -2
- package/dist/authorization-error.cjs +67 -17
- package/dist/authorization-error.cjs.map +1 -1
- package/dist/authorization-error.d.cts +6 -2
- package/dist/authorization-error.d.ts +6 -2
- package/dist/authorization-error.js +50 -18
- package/dist/authorization-error.js.map +1 -1
- package/dist/authorizer.port.d.cts +18 -3
- package/dist/authorizer.port.d.ts +18 -3
- package/dist/composite-authorizer.d.cts +63 -10
- package/dist/composite-authorizer.d.ts +63 -10
- package/dist/condition-evaluator.cjs +115 -184
- package/dist/condition-evaluator.cjs.map +1 -1
- package/dist/condition-evaluator.js +116 -179
- package/dist/condition-evaluator.js.map +1 -1
- package/dist/core-config.d.cts +53 -6
- package/dist/core-config.d.ts +53 -6
- package/dist/decorate.cjs +14 -0
- package/dist/decorate.js +9 -0
- package/dist/domain/index.cjs +107 -2
- package/dist/domain/index.cjs.map +1 -0
- package/dist/domain/index.d.cts +25 -1
- package/dist/domain/index.d.ts +25 -1
- package/dist/domain/index.js +99 -3
- package/dist/domain/index.js.map +1 -0
- package/dist/domain-event-contracts.cjs +12 -8
- package/dist/domain-event-contracts.cjs.map +1 -1
- package/dist/domain-event-contracts.d.cts +29 -4
- package/dist/domain-event-contracts.d.ts +29 -4
- package/dist/domain-event-contracts.js +11 -7
- package/dist/domain-event-contracts.js.map +1 -1
- package/dist/domain-exception.cjs +2 -1
- package/dist/domain-exception.cjs.map +1 -1
- package/dist/domain-exception.js +2 -1
- package/dist/domain-exception.js.map +1 -1
- package/dist/errors/index.cjs +1 -1
- package/dist/errors/index.d.cts +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/exceptions/index.cjs +2 -2
- package/dist/exceptions/index.d.cts +1 -2
- package/dist/exceptions/index.d.ts +1 -2
- package/dist/exceptions/index.js +2 -2
- package/dist/gate-engine-registry.cjs.map +1 -1
- package/dist/gate-engine-registry.d.cts +1 -1
- package/dist/gate-engine-registry.d.ts +1 -1
- package/dist/gate-engine-registry.js.map +1 -1
- package/dist/gate-v1-payload.schema.cjs +11 -6
- package/dist/gate-v1-payload.schema.cjs.map +1 -1
- package/dist/gate-v1-payload.schema.js +11 -6
- package/dist/gate-v1-payload.schema.js.map +1 -1
- package/dist/hashing.cjs +1 -1
- package/dist/hashing.cjs.map +1 -1
- package/dist/hashing.js +2 -2
- package/dist/hashing.js.map +1 -1
- package/dist/immutable.cjs +25 -12
- package/dist/immutable.cjs.map +1 -1
- package/dist/immutable.js +24 -11
- package/dist/immutable.js.map +1 -1
- package/dist/index.cjs +13 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -10
- package/dist/index.d.ts +9 -10
- package/dist/index.js +7 -9
- package/dist/index.js.map +1 -1
- package/dist/invalid-state-transition-exception.cjs +6 -6
- package/dist/invalid-state-transition-exception.cjs.map +1 -1
- package/dist/invalid-state-transition-exception.js +6 -6
- package/dist/invalid-state-transition-exception.js.map +1 -1
- package/dist/invariant-violation-exception.cjs +2 -2
- package/dist/invariant-violation-exception.cjs.map +1 -1
- package/dist/invariant-violation-exception.js +2 -2
- package/dist/invariant-violation-exception.js.map +1 -1
- package/dist/not-found-error.cjs +6 -4
- package/dist/not-found-error.cjs.map +1 -1
- package/dist/not-found-error.js +6 -4
- package/dist/not-found-error.js.map +1 -1
- package/dist/outcome.cjs +5 -5
- package/dist/outcome.cjs.map +1 -1
- package/dist/outcome.js +5 -5
- package/dist/outcome.js.map +1 -1
- package/dist/parse-gate-payload.d.cts +1 -1
- package/dist/parse-gate-payload.d.ts +1 -1
- package/dist/path.d.cts +2 -2
- package/dist/path.d.ts +2 -2
- package/dist/plugin.cjs +23 -13
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +22 -8
- package/dist/plugin.d.ts +22 -8
- package/dist/plugin.js +23 -13
- package/dist/plugin.js.map +1 -1
- package/dist/policies/engines/index.d.cts +1 -1
- package/dist/policies/engines/index.d.ts +1 -1
- package/dist/policies/engines/v1/gate/index.d.cts +3 -15
- package/dist/policies/engines/v1/gate/index.d.ts +3 -15
- package/dist/policies/index.d.cts +1 -1
- package/dist/policies/index.d.ts +1 -1
- package/dist/policy-bridge.cjs +30 -2
- package/dist/policy-bridge.cjs.map +1 -1
- package/dist/policy-bridge.d.cts +18 -0
- package/dist/policy-bridge.d.ts +18 -0
- package/dist/policy-bridge.js +30 -2
- package/dist/policy-bridge.js.map +1 -1
- package/dist/policy-service.cjs +40 -46
- package/dist/policy-service.cjs.map +1 -1
- package/dist/policy-service.d.cts +72 -9
- package/dist/policy-service.d.ts +72 -9
- package/dist/policy-service.js +42 -48
- package/dist/policy-service.js.map +1 -1
- package/dist/requested-by.cjs +1 -1
- package/dist/requested-by.cjs.map +1 -1
- package/dist/requested-by.js +1 -1
- package/dist/requested-by.js.map +1 -1
- package/dist/result.cjs +29 -1
- package/dist/result.cjs.map +1 -1
- package/dist/result.d.cts +12 -0
- package/dist/result.d.ts +12 -0
- package/dist/result.js +29 -1
- package/dist/result.js.map +1 -1
- package/dist/rule.cjs +94 -24
- package/dist/rule.cjs.map +1 -1
- package/dist/rule.js +94 -24
- package/dist/rule.js.map +1 -1
- package/dist/ruleset-registry.cjs +19 -0
- package/dist/ruleset-registry.cjs.map +1 -1
- package/dist/ruleset-registry.js +19 -0
- package/dist/ruleset-registry.js.map +1 -1
- package/dist/stable-stringify.cjs +43 -3
- package/dist/stable-stringify.cjs.map +1 -1
- package/dist/stable-stringify.js +38 -4
- package/dist/stable-stringify.js.map +1 -1
- package/dist/temporal-snapshot.d.cts +87 -0
- package/dist/temporal-snapshot.d.ts +87 -0
- package/dist/temporal-use-case.cjs +169 -16
- package/dist/temporal-use-case.cjs.map +1 -1
- package/dist/temporal-use-case.d.cts +76 -43
- package/dist/temporal-use-case.d.ts +76 -43
- package/dist/temporal-use-case.js +162 -15
- package/dist/temporal-use-case.js.map +1 -1
- package/dist/unexpected-error.cjs +4 -2
- package/dist/unexpected-error.cjs.map +1 -1
- package/dist/unexpected-error.js +4 -2
- package/dist/unexpected-error.js.map +1 -1
- package/dist/uuid-identifier.cjs +140 -2
- package/dist/uuid-identifier.cjs.map +1 -1
- package/dist/uuid-identifier.d.cts +26 -7
- package/dist/uuid-identifier.d.ts +26 -7
- package/dist/uuid-identifier.js +135 -3
- package/dist/uuid-identifier.js.map +1 -1
- package/dist/uuid.cjs +11 -6
- package/dist/uuid.cjs.map +1 -1
- package/dist/uuid.js +11 -6
- package/dist/uuid.js.map +1 -1
- package/dist/validation-code.cjs +9 -0
- package/dist/validation-code.cjs.map +1 -1
- package/dist/validation-code.d.cts +3 -0
- package/dist/validation-code.d.ts +3 -0
- package/dist/validation-code.js +9 -0
- package/dist/validation-code.js.map +1 -1
- package/dist/validation-error.cjs +42 -67
- package/dist/validation-error.cjs.map +1 -1
- package/dist/validation-error.d.cts +24 -8
- package/dist/validation-error.d.ts +24 -8
- package/dist/validation-error.js +41 -66
- package/dist/validation-error.js.map +1 -1
- package/dist/validation-exception.cjs +17 -6
- package/dist/validation-exception.cjs.map +1 -1
- package/dist/validation-exception.d.cts +33 -9
- package/dist/validation-exception.d.ts +33 -9
- package/dist/validation-exception.js +17 -6
- package/dist/validation-exception.js.map +1 -1
- package/dist/validation-field.cjs +3 -0
- package/dist/validation-field.cjs.map +1 -1
- package/dist/validation-field.d.cts +1 -0
- package/dist/validation-field.d.ts +1 -0
- package/dist/validation-field.js +3 -0
- package/dist/validation-field.js.map +1 -1
- package/dist/value-object-ruleset.contracts.d.cts +10 -0
- package/dist/value-object-ruleset.contracts.d.ts +10 -0
- package/dist/value-object.cjs +12 -2
- package/dist/value-object.cjs.map +1 -1
- package/dist/value-object.d.cts +16 -0
- package/dist/value-object.d.ts +16 -0
- package/dist/value-object.js +13 -3
- package/dist/value-object.js.map +1 -1
- package/dist/version.d.cts +27 -0
- package/dist/version.d.ts +27 -0
- package/dist/versioning/index.d.cts +2 -2
- package/dist/versioning/index.d.ts +2 -2
- package/meta.json +3 -2
- package/package.json +1 -1
- package/src/core/abac/authorizer.ts +60 -10
- package/src/core/abac/domain/policy-set.ts +52 -5
- package/src/core/abac/domain/rule.ts +28 -16
- package/src/core/abac/index.ts +7 -1
- package/src/core/application/commands/command.ts +14 -1
- package/src/core/application/commands/requested-by.ts +12 -3
- package/src/core/application/index.ts +2 -1
- package/src/core/application/policy-error-mapper.ts +6 -0
- package/src/core/application/ports/index.ts +7 -0
- package/src/core/application/ports/temporal-repository.port.ts +35 -2
- package/src/core/application/queries/index.ts +1 -1
- package/src/core/application/queries/query.ts +19 -2
- package/src/core/application/temporal/temporal-use-case.ts +31 -4
- package/src/core/application/use-case.ts +44 -7
- package/src/core/config/core-config.ts +46 -25
- package/src/core/config/index.ts +1 -0
- package/src/core/config/policy-reporter.ts +32 -1
- package/src/core/domain/entity.ts +50 -7
- package/src/core/domain/rulesets/entity-ruleset.contracts.ts +0 -2
- package/src/core/domain/rulesets/ruleset-registry.ts +24 -0
- package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +1 -7
- package/src/core/domain/temporal/half-open-interval.ts +34 -0
- package/src/core/domain/temporal/temporal-snapshot.ts +13 -2
- package/src/core/domain/temporal/transaction-time.ts +19 -15
- package/src/core/domain/temporal/valid-time.ts +20 -15
- package/src/core/domain/uuid-identifier.ts +5 -3
- package/src/core/domain/value-object.ts +17 -0
- package/src/core/errors/authentication-error.ts +5 -31
- package/src/core/errors/authorization-error.ts +12 -20
- package/src/core/errors/business-rule-violation-error.ts +4 -1
- package/src/core/errors/idempotency-error.ts +5 -2
- package/src/core/errors/index.ts +4 -0
- package/src/core/errors/integration-error.ts +4 -24
- package/src/core/errors/legacy-incompatible-error.ts +1 -0
- package/src/core/errors/not-found-error.ts +4 -1
- package/src/core/errors/temporal-error.ts +22 -20
- package/src/core/errors/unexpected-error.ts +5 -1
- package/src/core/errors/utils/factory-helpers.ts +70 -0
- package/src/core/errors/utils/index.ts +5 -0
- package/src/core/errors/utils/json-safe.ts +15 -1
- package/src/core/errors/validation-error.ts +4 -1
- package/src/core/exceptions/business-rule-violation-exception.ts +2 -1
- package/src/core/exceptions/domain-exception.ts +9 -1
- package/src/core/exceptions/entity-not-found-exception.ts +5 -1
- package/src/core/exceptions/invalid-state-transition-exception.ts +5 -2
- package/src/core/exceptions/invariant-violation-exception.ts +2 -2
- package/src/core/exceptions/validation-code.ts +14 -0
- package/src/core/exceptions/validation-exception.ts +44 -5
- package/src/core/exceptions/validation-field.ts +11 -1
- package/src/core/plugins/plugin.ts +25 -15
- package/src/core/plugins/types.ts +4 -2
- package/src/core/policies/asof/asof.ts +12 -0
- package/src/core/policies/catalog/policy-catalog-entry.ts +3 -1
- package/src/core/policies/catalog/policy-catalog.ts +5 -1
- package/src/core/policies/context/context-builder.ts +20 -0
- package/src/core/policies/context/context-resolver.ts +5 -0
- package/src/core/policies/context/context-seed.ts +11 -0
- package/src/core/policies/defs/in-memory-policy-definition-repo.ts +0 -2
- package/src/core/policies/engines/parse-gate-payload.ts +9 -0
- package/src/core/policies/engines/v1/condition-date-operands.ts +112 -0
- package/src/core/policies/engines/v1/condition-evaluator.ts +99 -325
- package/src/core/policies/engines/v1/condition-schema.ts +23 -19
- package/src/core/policies/engines/v1/condition-types.ts +18 -11
- package/src/core/policies/index.ts +4 -6
- package/src/core/policies/service/policy-service.ts +46 -35
- package/src/core/policies/utils/hash.ts +5 -69
- package/src/core/policies/utils/result.ts +1 -1
- package/src/core/rbac/access-request.ts +12 -2
- package/src/core/rbac/authorizer.ts +8 -1
- package/src/core/rbac/domain/role.ts +20 -0
- package/src/core/rbac/domain/scope.ts +6 -1
- package/src/core/result/index.ts +5 -0
- package/src/core/result/outcome.ts +5 -7
- package/src/core/result/result.ts +34 -1
- package/src/core/shared/hashing.ts +6 -3
- package/src/core/shared/immutable.ts +22 -4
- package/src/core/shared/stable-stringify.ts +91 -4
- package/src/core/shared/uuid.ts +11 -6
- package/src/core/versioning/domain-event-contracts.ts +43 -15
- package/src/core/versioning/index.ts +1 -0
- package/src/core/versioning/version.ts +30 -1
- package/src/domain/index.ts +5 -0
- package/src/examples/rulesets/entity/order-creation-rules-v1.ts +1 -1
- package/src/examples/rulesets/entity/order-invariants-v1.ts +2 -4
- package/src/examples/rulesets/entity/order-invariants-v2.ts +2 -4
- package/src/examples/rulesets/value-object/cpf-rules-v1.ts +2 -4
- package/src/examples/rulesets/value-object/cpf-rules-v2.ts +2 -4
- package/src/examples/rulesets/value-object/person-name-rules-v1.ts +2 -4
- package/src/examples/rulesets/value-object/person-name-rules-v2.ts +2 -4
- package/src/result/index.ts +7 -2
- package/src/version.ts +1 -1
- package/dist/domain-exception.d.cts +0 -7
- package/dist/domain-exception.d.ts +0 -7
- package/dist/entity.cjs +0 -122
- package/dist/entity.cjs.map +0 -1
- package/dist/entity.js +0 -111
- package/dist/entity.js.map +0 -1
- package/dist/use-case.cjs +0 -92
- package/dist/use-case.cjs.map +0 -1
- package/dist/use-case.js +0 -87
- package/dist/use-case.js.map +0 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { PolicyContextPath } from "../../context/index.js";
|
|
2
|
-
// Import the date util directly (not the utils barrel): the barrel re-exports
|
|
3
|
-
// PolicyHashing, whose node:crypto import would needlessly drag a Node-only
|
|
4
|
-
// API into the zod-free ./abac subpath.
|
|
5
|
-
import { PolicyDateUtils } from "../../utils/date.js";
|
|
6
2
|
import { Result } from "../../../result/result.js";
|
|
7
3
|
|
|
4
|
+
import {
|
|
5
|
+
CANONICAL_UTC_DATE_FORMAT,
|
|
6
|
+
containsInvalidDate,
|
|
7
|
+
evaluateRelationalNumbers,
|
|
8
|
+
normalizeDateOperand,
|
|
9
|
+
parseComparableDate,
|
|
10
|
+
type RelationalOperator,
|
|
11
|
+
} from "./condition-date-operands.js";
|
|
8
12
|
import type {
|
|
9
13
|
ConditionAndNode,
|
|
10
14
|
ConditionLeafNode,
|
|
@@ -28,8 +32,6 @@ const INVALID_NUMERIC_OPERAND_TAG = "INVALID_NUMERIC_OPERAND";
|
|
|
28
32
|
const INVALID_SET_OPERAND_TAG = "INVALID_SET_OPERAND";
|
|
29
33
|
const EMPTY_OR_CONDITION_TAG = "EMPTY_OR_CONDITION";
|
|
30
34
|
const EMPTY_AND_CONDITION_TAG = "EMPTY_AND_CONDITION";
|
|
31
|
-
const ISO_8601_UTC_DATE_PATTERN =
|
|
32
|
-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
33
35
|
|
|
34
36
|
export interface ConditionEvaluationTrace {
|
|
35
37
|
readonly conditionPath: string;
|
|
@@ -44,8 +46,6 @@ export interface TracedConditionEvaluation {
|
|
|
44
46
|
readonly trace: ConditionEvaluationTrace;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
type RelationalOperator = Extract<ConditionOp, "gt" | "gte" | "lt" | "lte">;
|
|
48
|
-
|
|
49
49
|
export class ConditionEvaluatorV1 {
|
|
50
50
|
constructor(
|
|
51
51
|
private readonly context: PolicyContext,
|
|
@@ -79,106 +79,14 @@ export class ConditionEvaluatorV1 {
|
|
|
79
79
|
message: string;
|
|
80
80
|
} {
|
|
81
81
|
if (error instanceof Error) {
|
|
82
|
-
return {
|
|
83
|
-
name: error.name,
|
|
84
|
-
message: error.message,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
name: "NonErrorThrown",
|
|
90
|
-
message: String(error),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private static buildNullishNumericOperandMessage(
|
|
95
|
-
node: ConditionLeafNode,
|
|
96
|
-
actual: null | undefined,
|
|
97
|
-
): string {
|
|
98
|
-
const resolvedValue = actual === null ? "null" : "undefined";
|
|
99
|
-
|
|
100
|
-
return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private static buildNullishDateOperandMessage(
|
|
104
|
-
node: ConditionLeafNode,
|
|
105
|
-
actual: null | undefined,
|
|
106
|
-
): string {
|
|
107
|
-
const resolvedValue = actual === null ? "null" : "undefined";
|
|
108
|
-
|
|
109
|
-
return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private static buildInvalidDateOperandMessage(
|
|
113
|
-
node: ConditionLeafNode,
|
|
114
|
-
): string {
|
|
115
|
-
return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private static buildInvalidNumericOperandMessage(
|
|
119
|
-
node: ConditionLeafNode,
|
|
120
|
-
): string {
|
|
121
|
-
return `${INVALID_NUMERIC_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires numeric operands`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private static buildInvalidSetOperandMessage(
|
|
125
|
-
node: ConditionLeafNode,
|
|
126
|
-
): string {
|
|
127
|
-
return `${INVALID_SET_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires an array operand`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private static buildEmptyOrConditionMessage(): string {
|
|
131
|
-
return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private static buildEmptyAndConditionMessage(): string {
|
|
135
|
-
return `${EMPTY_AND_CONDITION_TAG}: AND nodes must contain at least one child condition`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private static parseComparableDate(
|
|
139
|
-
value: unknown,
|
|
140
|
-
): Result<Date, string> | null {
|
|
141
|
-
if (value instanceof Date) {
|
|
142
|
-
if (!PolicyDateUtils.isValid(value)) {
|
|
143
|
-
return Result.err("Invalid Date instance");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return Result.ok(value);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
typeof value !== "string" ||
|
|
151
|
-
!ISO_8601_UTC_DATE_PATTERN.test(value)
|
|
152
|
-
) {
|
|
153
|
-
return null;
|
|
82
|
+
return { name: error.name, message: error.message };
|
|
154
83
|
}
|
|
155
84
|
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
!PolicyDateUtils.isValid(parsed) ||
|
|
159
|
-
parsed.toISOString() !== value
|
|
160
|
-
) {
|
|
161
|
-
return Result.err("Invalid ISO 8601 UTC date string");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return Result.ok(parsed);
|
|
85
|
+
return { name: "NonErrorThrown", message: String(error) };
|
|
165
86
|
}
|
|
166
87
|
|
|
167
|
-
private static
|
|
168
|
-
|
|
169
|
-
op: RelationalOperator,
|
|
170
|
-
expected: number,
|
|
171
|
-
): boolean {
|
|
172
|
-
switch (op) {
|
|
173
|
-
case "gt":
|
|
174
|
-
return actual > expected;
|
|
175
|
-
case "gte":
|
|
176
|
-
return actual >= expected;
|
|
177
|
-
case "lt":
|
|
178
|
-
return actual < expected;
|
|
179
|
-
case "lte":
|
|
180
|
-
return actual <= expected;
|
|
181
|
-
}
|
|
88
|
+
private static invalidDateOperandMessage(node: ConditionLeafNode): string {
|
|
89
|
+
return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC (${CANONICAL_UTC_DATE_FORMAT}) string operands`;
|
|
182
90
|
}
|
|
183
91
|
|
|
184
92
|
private buildReport(params: {
|
|
@@ -197,93 +105,27 @@ export class ConditionEvaluatorV1 {
|
|
|
197
105
|
};
|
|
198
106
|
}
|
|
199
107
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.options.reporter.error(
|
|
208
|
-
this.buildReport({
|
|
209
|
-
level: "error",
|
|
210
|
-
tag: CONDITION_EVAL_THROWN_TAG,
|
|
211
|
-
message,
|
|
212
|
-
details: { node, cause },
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
return Result.err(message);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private static containsInvalidDate(value: unknown): boolean {
|
|
220
|
-
if (value instanceof Date) {
|
|
221
|
-
return !PolicyDateUtils.isValid(value);
|
|
222
|
-
}
|
|
223
|
-
if (Array.isArray(value)) {
|
|
224
|
-
return value.some((item) =>
|
|
225
|
-
ConditionEvaluatorV1.containsInvalidDate(item),
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Valid Dates become their ISO string so equality/set operators compare
|
|
232
|
-
// instants instead of references; every other value passes through.
|
|
233
|
-
private static normalizeDateOperand(value: unknown): unknown {
|
|
234
|
-
if (value instanceof Date) {
|
|
235
|
-
return value.toISOString();
|
|
236
|
-
}
|
|
237
|
-
if (Array.isArray(value)) {
|
|
238
|
-
return value.map((item) =>
|
|
239
|
-
ConditionEvaluatorV1.normalizeDateOperand(item),
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
return value;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private reportDateOperandError(
|
|
246
|
-
node: ConditionLeafNode,
|
|
247
|
-
actual: unknown,
|
|
248
|
-
): Result<boolean, string> {
|
|
249
|
-
const message =
|
|
250
|
-
ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
|
|
251
|
-
|
|
252
|
-
this.options.reporter.error(
|
|
253
|
-
this.buildReport({
|
|
254
|
-
level: "error",
|
|
255
|
-
tag: INVALID_DATE_OPERAND_TAG,
|
|
256
|
-
message,
|
|
257
|
-
details: {
|
|
258
|
-
field: node.field,
|
|
259
|
-
op: node.op,
|
|
260
|
-
actual,
|
|
261
|
-
expected: node.value,
|
|
262
|
-
node,
|
|
263
|
-
},
|
|
264
|
-
}),
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
return Result.err(message);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private reportInvalidNumericOperand(
|
|
108
|
+
// Single reporting seam for every leaf-level operand error. Collapses the
|
|
109
|
+
// formerly per-tag report methods, whose only real difference was the tag,
|
|
110
|
+
// message, and (for the nullish cases) an `allowNull` detail.
|
|
111
|
+
private reportLeafError(
|
|
112
|
+
tag: ConditionEvaluationReport["tag"],
|
|
113
|
+
message: string,
|
|
271
114
|
node: ConditionLeafNode,
|
|
272
115
|
actual: unknown,
|
|
116
|
+
extra?: Readonly<Record<string, unknown>>,
|
|
273
117
|
): Result<boolean, string> {
|
|
274
|
-
const message =
|
|
275
|
-
ConditionEvaluatorV1.buildInvalidNumericOperandMessage(node);
|
|
276
|
-
|
|
277
118
|
this.options.reporter.error(
|
|
278
119
|
this.buildReport({
|
|
279
120
|
level: "error",
|
|
280
|
-
tag
|
|
121
|
+
tag,
|
|
281
122
|
message,
|
|
282
123
|
details: {
|
|
283
124
|
field: node.field,
|
|
284
125
|
op: node.op,
|
|
285
126
|
actual,
|
|
286
127
|
expected: node.value,
|
|
128
|
+
...extra,
|
|
287
129
|
node,
|
|
288
130
|
},
|
|
289
131
|
}),
|
|
@@ -292,25 +134,19 @@ export class ConditionEvaluatorV1 {
|
|
|
292
134
|
return Result.err(message);
|
|
293
135
|
}
|
|
294
136
|
|
|
295
|
-
private
|
|
296
|
-
node:
|
|
297
|
-
|
|
298
|
-
): Result<
|
|
299
|
-
const
|
|
300
|
-
|
|
137
|
+
private conditionEvalErr<TValue>(
|
|
138
|
+
node: ConditionNode,
|
|
139
|
+
error: unknown,
|
|
140
|
+
): Result<TValue, string> {
|
|
141
|
+
const cause = ConditionEvaluatorV1.describeError(error);
|
|
142
|
+
const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
|
|
301
143
|
|
|
302
144
|
this.options.reporter.error(
|
|
303
145
|
this.buildReport({
|
|
304
146
|
level: "error",
|
|
305
|
-
tag:
|
|
147
|
+
tag: CONDITION_EVAL_THROWN_TAG,
|
|
306
148
|
message,
|
|
307
|
-
details: {
|
|
308
|
-
field: node.field,
|
|
309
|
-
op: node.op,
|
|
310
|
-
actual,
|
|
311
|
-
expected: node.value,
|
|
312
|
-
node,
|
|
313
|
-
},
|
|
149
|
+
details: { node, cause },
|
|
314
150
|
}),
|
|
315
151
|
);
|
|
316
152
|
|
|
@@ -325,84 +161,49 @@ export class ConditionEvaluatorV1 {
|
|
|
325
161
|
return null;
|
|
326
162
|
}
|
|
327
163
|
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
|
|
331
|
-
node.value,
|
|
332
|
-
);
|
|
164
|
+
const actualDate = parseComparableDate(actual);
|
|
165
|
+
const expectedDate = parseComparableDate(node.value);
|
|
333
166
|
|
|
334
|
-
|
|
167
|
+
// Neither operand is date-shaped: let the numeric path handle it.
|
|
168
|
+
if (actualDate === null && expectedDate === null) {
|
|
335
169
|
return null;
|
|
336
170
|
}
|
|
337
171
|
|
|
338
172
|
const allowsNull = node.allowNull === true;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (allowsNull) {
|
|
342
|
-
return Result.ok(false);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
|
|
346
|
-
node,
|
|
347
|
-
actual,
|
|
348
|
-
);
|
|
349
|
-
this.options.reporter.error(
|
|
350
|
-
this.buildReport({
|
|
351
|
-
level: "error",
|
|
352
|
-
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
353
|
-
message,
|
|
354
|
-
details: {
|
|
355
|
-
field: node.field,
|
|
356
|
-
op: node.op,
|
|
357
|
-
actual,
|
|
358
|
-
expected: node.value,
|
|
359
|
-
allowNull: allowsNull,
|
|
360
|
-
node,
|
|
361
|
-
},
|
|
362
|
-
}),
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
return Result.err(message);
|
|
173
|
+
if (actual === null && allowsNull) {
|
|
174
|
+
return Result.ok(false);
|
|
366
175
|
}
|
|
367
176
|
|
|
368
|
-
if (actual === undefined) {
|
|
369
|
-
const
|
|
177
|
+
if (actual === null || actual === undefined) {
|
|
178
|
+
const resolved = actual === null ? "null" : "undefined";
|
|
179
|
+
return this.reportLeafError(
|
|
180
|
+
NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
181
|
+
`${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolved} for date comparison operator "${node.op}"`,
|
|
370
182
|
node,
|
|
371
183
|
actual,
|
|
184
|
+
{ allowNull: allowsNull },
|
|
372
185
|
);
|
|
373
|
-
this.options.reporter.error(
|
|
374
|
-
this.buildReport({
|
|
375
|
-
level: "error",
|
|
376
|
-
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
377
|
-
message,
|
|
378
|
-
details: {
|
|
379
|
-
field: node.field,
|
|
380
|
-
op: node.op,
|
|
381
|
-
actual,
|
|
382
|
-
expected: node.value,
|
|
383
|
-
allowNull: allowsNull,
|
|
384
|
-
node,
|
|
385
|
-
},
|
|
386
|
-
}),
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
return Result.err(message);
|
|
390
186
|
}
|
|
391
187
|
|
|
392
188
|
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
189
|
+
actualDate === null ||
|
|
190
|
+
actualDate.isErr() ||
|
|
191
|
+
expectedDate === null ||
|
|
192
|
+
expectedDate.isErr()
|
|
397
193
|
) {
|
|
398
|
-
return this.
|
|
194
|
+
return this.reportLeafError(
|
|
195
|
+
INVALID_DATE_OPERAND_TAG,
|
|
196
|
+
ConditionEvaluatorV1.invalidDateOperandMessage(node),
|
|
197
|
+
node,
|
|
198
|
+
actual,
|
|
199
|
+
);
|
|
399
200
|
}
|
|
400
201
|
|
|
401
202
|
return Result.ok(
|
|
402
|
-
|
|
403
|
-
|
|
203
|
+
evaluateRelationalNumbers(
|
|
204
|
+
actualDate.getOrNull()!.getTime(),
|
|
404
205
|
node.op,
|
|
405
|
-
|
|
206
|
+
expectedDate.getOrNull()!.getTime(),
|
|
406
207
|
),
|
|
407
208
|
);
|
|
408
209
|
}
|
|
@@ -418,51 +219,20 @@ export class ConditionEvaluatorV1 {
|
|
|
418
219
|
case "neq":
|
|
419
220
|
return actual !== expected;
|
|
420
221
|
case "gt":
|
|
421
|
-
return (
|
|
422
|
-
typeof actual === "number" &&
|
|
423
|
-
typeof expected === "number" &&
|
|
424
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
425
|
-
actual,
|
|
426
|
-
op,
|
|
427
|
-
expected,
|
|
428
|
-
)
|
|
429
|
-
);
|
|
430
222
|
case "gte":
|
|
431
|
-
return (
|
|
432
|
-
typeof actual === "number" &&
|
|
433
|
-
typeof expected === "number" &&
|
|
434
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
435
|
-
actual,
|
|
436
|
-
op,
|
|
437
|
-
expected,
|
|
438
|
-
)
|
|
439
|
-
);
|
|
440
223
|
case "lt":
|
|
441
|
-
return (
|
|
442
|
-
typeof actual === "number" &&
|
|
443
|
-
typeof expected === "number" &&
|
|
444
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
445
|
-
actual,
|
|
446
|
-
op,
|
|
447
|
-
expected,
|
|
448
|
-
)
|
|
449
|
-
);
|
|
450
224
|
case "lte":
|
|
451
225
|
return (
|
|
452
226
|
typeof actual === "number" &&
|
|
453
227
|
typeof expected === "number" &&
|
|
454
|
-
|
|
455
|
-
actual,
|
|
456
|
-
op,
|
|
457
|
-
expected,
|
|
458
|
-
)
|
|
228
|
+
evaluateRelationalNumbers(actual, op, expected)
|
|
459
229
|
);
|
|
460
230
|
case "in":
|
|
461
231
|
return Array.isArray(expected) && expected.includes(actual);
|
|
462
232
|
case "notIn":
|
|
463
233
|
return Array.isArray(expected) && !expected.includes(actual);
|
|
464
234
|
case "isNull":
|
|
465
|
-
return actual === null;
|
|
235
|
+
return actual === null || actual === undefined;
|
|
466
236
|
case "isNotNull":
|
|
467
237
|
return actual !== null && actual !== undefined;
|
|
468
238
|
}
|
|
@@ -474,29 +244,14 @@ export class ConditionEvaluatorV1 {
|
|
|
474
244
|
): Result<boolean, string> {
|
|
475
245
|
const allowsNull = node.allowNull === true;
|
|
476
246
|
if (actual === undefined || (actual === null && !allowsNull)) {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
this.buildReport({
|
|
485
|
-
level: "error",
|
|
486
|
-
tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
|
|
487
|
-
message,
|
|
488
|
-
details: {
|
|
489
|
-
field: node.field,
|
|
490
|
-
op: node.op,
|
|
491
|
-
actual,
|
|
492
|
-
expected: node.value,
|
|
493
|
-
allowNull: allowsNull,
|
|
494
|
-
node,
|
|
495
|
-
},
|
|
496
|
-
}),
|
|
247
|
+
const resolved = actual === null ? "null" : "undefined";
|
|
248
|
+
return this.reportLeafError(
|
|
249
|
+
NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
|
|
250
|
+
`${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolved} for numeric operator "${node.op}"`,
|
|
251
|
+
node,
|
|
252
|
+
actual,
|
|
253
|
+
{ allowNull: allowsNull },
|
|
497
254
|
);
|
|
498
|
-
|
|
499
|
-
return Result.err(message);
|
|
500
255
|
}
|
|
501
256
|
|
|
502
257
|
// null + allowNull: a relational comparison against a missing value
|
|
@@ -505,11 +260,21 @@ export class ConditionEvaluatorV1 {
|
|
|
505
260
|
return Result.ok(false);
|
|
506
261
|
}
|
|
507
262
|
|
|
508
|
-
// Both operands must be numbers. A present but wrong-typed
|
|
509
|
-
// context/configuration error, surfaced like the date
|
|
510
|
-
// silently collapsing to "no match".
|
|
511
|
-
if (
|
|
512
|
-
|
|
263
|
+
// Both operands must be finite numbers. A present but wrong-typed or
|
|
264
|
+
// NaN operand is a context/configuration error, surfaced like the date
|
|
265
|
+
// path instead of silently collapsing to "no match".
|
|
266
|
+
if (
|
|
267
|
+
typeof actual !== "number" ||
|
|
268
|
+
Number.isNaN(actual) ||
|
|
269
|
+
typeof node.value !== "number" ||
|
|
270
|
+
Number.isNaN(node.value)
|
|
271
|
+
) {
|
|
272
|
+
return this.reportLeafError(
|
|
273
|
+
INVALID_NUMERIC_OPERAND_TAG,
|
|
274
|
+
`${INVALID_NUMERIC_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires numeric operands`,
|
|
275
|
+
node,
|
|
276
|
+
actual,
|
|
277
|
+
);
|
|
513
278
|
}
|
|
514
279
|
|
|
515
280
|
return Result.ok(this.evaluateOperator(actual, node.op, node.value));
|
|
@@ -521,18 +286,17 @@ export class ConditionEvaluatorV1 {
|
|
|
521
286
|
node.field,
|
|
522
287
|
);
|
|
523
288
|
if (actualResult.isErr()) {
|
|
289
|
+
const message = `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`;
|
|
524
290
|
this.options.reporter.warn(
|
|
525
291
|
this.buildReport({
|
|
526
292
|
level: "warn",
|
|
527
293
|
tag: "MISSING_CONTEXT_FIELD",
|
|
528
|
-
message
|
|
294
|
+
message,
|
|
529
295
|
details: { field: node.field, node },
|
|
530
296
|
}),
|
|
531
297
|
);
|
|
532
298
|
|
|
533
|
-
return Result.err(
|
|
534
|
-
`MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
|
|
535
|
-
);
|
|
299
|
+
return Result.err(message);
|
|
536
300
|
}
|
|
537
301
|
|
|
538
302
|
try {
|
|
@@ -553,7 +317,12 @@ export class ConditionEvaluatorV1 {
|
|
|
553
317
|
(node.op === "in" || node.op === "notIn") &&
|
|
554
318
|
!Array.isArray(node.value)
|
|
555
319
|
) {
|
|
556
|
-
return this.
|
|
320
|
+
return this.reportLeafError(
|
|
321
|
+
INVALID_SET_OPERAND_TAG,
|
|
322
|
+
`${INVALID_SET_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires an array operand`,
|
|
323
|
+
node,
|
|
324
|
+
actual,
|
|
325
|
+
);
|
|
557
326
|
}
|
|
558
327
|
|
|
559
328
|
if (
|
|
@@ -567,17 +336,22 @@ export class ConditionEvaluatorV1 {
|
|
|
567
336
|
// including set items) to their ISO string; an invalid Date is
|
|
568
337
|
// a context/configuration error, like the relational path.
|
|
569
338
|
if (
|
|
570
|
-
|
|
571
|
-
|
|
339
|
+
containsInvalidDate(actual) ||
|
|
340
|
+
containsInvalidDate(node.value)
|
|
572
341
|
) {
|
|
573
|
-
return this.
|
|
342
|
+
return this.reportLeafError(
|
|
343
|
+
INVALID_DATE_OPERAND_TAG,
|
|
344
|
+
ConditionEvaluatorV1.invalidDateOperandMessage(node),
|
|
345
|
+
node,
|
|
346
|
+
actual,
|
|
347
|
+
);
|
|
574
348
|
}
|
|
575
349
|
|
|
576
350
|
return Result.ok(
|
|
577
351
|
this.evaluateOperator(
|
|
578
|
-
|
|
352
|
+
normalizeDateOperand(actual),
|
|
579
353
|
node.op,
|
|
580
|
-
|
|
354
|
+
normalizeDateOperand(node.value),
|
|
581
355
|
),
|
|
582
356
|
);
|
|
583
357
|
}
|
|
@@ -632,7 +406,7 @@ export class ConditionEvaluatorV1 {
|
|
|
632
406
|
if (ConditionEvaluatorV1.isAndNode(node)) {
|
|
633
407
|
if (node.and.length === 0) {
|
|
634
408
|
return Result.err(
|
|
635
|
-
|
|
409
|
+
`${EMPTY_AND_CONDITION_TAG}: AND nodes must contain at least one child condition`,
|
|
636
410
|
);
|
|
637
411
|
}
|
|
638
412
|
|
|
@@ -697,7 +471,7 @@ export class ConditionEvaluatorV1 {
|
|
|
697
471
|
|
|
698
472
|
if (lastTrace === null) {
|
|
699
473
|
return Result.err(
|
|
700
|
-
|
|
474
|
+
`${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`,
|
|
701
475
|
);
|
|
702
476
|
}
|
|
703
477
|
|
|
@@ -28,23 +28,27 @@ export const conditionLeafNodeSchema: z.ZodType<ConditionLeafNode> = z
|
|
|
28
28
|
})
|
|
29
29
|
.strict() as z.ZodType<ConditionLeafNode>;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Cap nesting depth so an adversarially deep payload fails validation instead
|
|
32
|
+
// of overflowing the stack during parse: zod recurses once per level, and the
|
|
33
|
+
// old `z.lazy` schema was unbounded. The schema is built to exactly this depth
|
|
34
|
+
// (leaf-only past it), so a too-deep tree is rejected at the boundary without
|
|
35
|
+
// deep recursion. 32 is far beyond any hand-written policy — raise it only with
|
|
36
|
+
// a matching stack-safety review.
|
|
37
|
+
export const MAX_CONDITION_DEPTH = 32;
|
|
38
|
+
|
|
39
|
+
function boundedConditionNodeSchema(depth: number): z.ZodType<ConditionNode> {
|
|
40
|
+
if (depth <= 1) {
|
|
41
|
+
return conditionLeafNodeSchema;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const child = boundedConditionNodeSchema(depth - 1);
|
|
45
|
+
return z.union([
|
|
33
46
|
conditionLeafNodeSchema,
|
|
34
|
-
z
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
43
|
-
.strict(),
|
|
44
|
-
z
|
|
45
|
-
.object({
|
|
46
|
-
not: conditionNodeSchema,
|
|
47
|
-
})
|
|
48
|
-
.strict(),
|
|
49
|
-
]),
|
|
50
|
-
);
|
|
47
|
+
z.object({ and: z.array(child).min(1) }).strict(),
|
|
48
|
+
z.object({ or: z.array(child).min(1) }).strict(),
|
|
49
|
+
z.object({ not: child }).strict(),
|
|
50
|
+
]) as z.ZodType<ConditionNode>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const conditionNodeSchema: z.ZodType<ConditionNode> =
|
|
54
|
+
boundedConditionNodeSchema(MAX_CONDITION_DEPTH);
|
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
// share the same condition-tree DSL. Keeping them here avoids making compute/v1
|
|
4
4
|
// structurally dependent on gate/v1.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
// Single source of truth for the operator vocabulary: the union is derived from
|
|
7
|
+
// this array so a new operator is added in exactly one place, and consumers that
|
|
8
|
+
// need the runtime list (e.g. AbacRule's structural check) reuse it instead of
|
|
9
|
+
// hand-maintaining a parallel set that can drift out of sync.
|
|
10
|
+
export const CONDITION_OPS = [
|
|
11
|
+
"eq",
|
|
12
|
+
"neq",
|
|
13
|
+
"gt",
|
|
14
|
+
"gte",
|
|
15
|
+
"lt",
|
|
16
|
+
"lte",
|
|
17
|
+
"in",
|
|
18
|
+
"notIn",
|
|
19
|
+
"isNull",
|
|
20
|
+
"isNotNull",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type ConditionOp = (typeof CONDITION_OPS)[number];
|
|
17
24
|
|
|
18
25
|
export interface ConditionLeafNode {
|
|
19
26
|
readonly field: string;
|