@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
|
@@ -47,6 +47,13 @@ abstract class ValueObject<T, P> {
|
|
|
47
47
|
* default — when nothing is registered, {@link equals} falls back to a
|
|
48
48
|
* structural comparison of the wrapped `value`. Hosts register a plugin
|
|
49
49
|
* (e.g. `lodash.isEqual`) once at startup to customise equality globally.
|
|
50
|
+
*
|
|
51
|
+
* "Globally" is literal: this static registry is process-wide and shared
|
|
52
|
+
* by **every** `ValueObject` subclass, including the value objects the kit
|
|
53
|
+
* itself ships (RBAC's `Permission`, `Scope`, …). A registered plugin
|
|
54
|
+
* therefore redefines equality for those too — it must honour generic
|
|
55
|
+
* value-object semantics, not the quirks of one host type. For a
|
|
56
|
+
* type-specific comparison, override `equals` on that subclass instead.
|
|
50
57
|
*/
|
|
51
58
|
public static readonly plugins =
|
|
52
59
|
new PluginManager<ValueObjectPluginContract>();
|
|
@@ -104,10 +111,20 @@ abstract class ValueObject<T, P> {
|
|
|
104
111
|
* with object keys sorted so insertion order (e.g. code-built vs
|
|
105
112
|
* JSON-rehydrated) never affects the result.
|
|
106
113
|
* Subclasses may still override for a faster or domain-specific comparison.
|
|
114
|
+
*
|
|
115
|
+
* The fallback also requires both sides to be the same concrete class, so
|
|
116
|
+
* an `OrderId` never equals a `CustomerId` that wraps the same string —
|
|
117
|
+
* `other: this` only guards at compile time, and a cast would otherwise
|
|
118
|
+
* slip through. And because the fallback serializes via `stableStringify`,
|
|
119
|
+
* it inherits `JSON.stringify` semantics: `undefined` properties are
|
|
120
|
+
* dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse
|
|
121
|
+
* to `{}` — see the caveats in `shared/stable-stringify.ts`. Override
|
|
122
|
+
* `equals` when the wrapped value relies on such shapes.
|
|
107
123
|
*/
|
|
108
124
|
public equals(other: this): boolean {
|
|
109
125
|
return ValueObject.plugins.invoke("equals", [this, other], {
|
|
110
126
|
fallback: (a, b) =>
|
|
127
|
+
a.constructor === b.constructor &&
|
|
111
128
|
stableStringify(a.value) === stableStringify(b.value),
|
|
112
129
|
});
|
|
113
130
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError } from "./app-error.js";
|
|
2
2
|
import { ErrorCodes } from "./error-codes.js";
|
|
3
3
|
import type { AppErrorOptions, ErrorSeverity } from "./types.js";
|
|
4
|
+
import { compactMetadata, pickAppErrorOptions } from "./utils/index.js";
|
|
4
5
|
|
|
5
6
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
7
|
// Types
|
|
@@ -85,7 +86,7 @@ class AuthenticationError extends AppError {
|
|
|
85
86
|
code: ErrorCodes.authentication.missingToken,
|
|
86
87
|
reason: "missing_token",
|
|
87
88
|
metadata: compactMetadata(options),
|
|
88
|
-
...
|
|
89
|
+
...pickAppErrorOptions(options),
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -103,7 +104,7 @@ class AuthenticationError extends AppError {
|
|
|
103
104
|
correlationId: options?.correlationId,
|
|
104
105
|
requestId: options?.requestId,
|
|
105
106
|
}),
|
|
106
|
-
...
|
|
107
|
+
...pickAppErrorOptions(options),
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -115,7 +116,7 @@ class AuthenticationError extends AppError {
|
|
|
115
116
|
code: ErrorCodes.authentication.expiredToken,
|
|
116
117
|
reason: "expired_token",
|
|
117
118
|
metadata: compactMetadata(options),
|
|
118
|
-
...
|
|
119
|
+
...pickAppErrorOptions(options),
|
|
119
120
|
});
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -135,37 +136,10 @@ class AuthenticationError extends AppError {
|
|
|
135
136
|
code: ErrorCodes.authentication.invalidCredentials,
|
|
136
137
|
reason: "invalid_credentials",
|
|
137
138
|
metadata: compactMetadata(options),
|
|
138
|
-
...
|
|
139
|
+
...pickAppErrorOptions(options),
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
144
|
-
// Helpers
|
|
145
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
function extractAppErrorOptions(options?: Partial<AppErrorOptions>) {
|
|
148
|
-
return {
|
|
149
|
-
cause: options?.cause,
|
|
150
|
-
type: options?.type,
|
|
151
|
-
severity: options?.severity,
|
|
152
|
-
correlationId: options?.correlationId,
|
|
153
|
-
requestId: options?.requestId,
|
|
154
|
-
commandId: options?.commandId,
|
|
155
|
-
createdAtIso: options?.createdAtIso,
|
|
156
|
-
publicMessage: options?.publicMessage,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function compactMetadata<T extends Record<string, unknown>>(
|
|
161
|
-
input?: T,
|
|
162
|
-
): T | undefined {
|
|
163
|
-
if (!input) return undefined;
|
|
164
|
-
|
|
165
|
-
return Object.fromEntries(
|
|
166
|
-
Object.entries(input).filter(([, value]) => value !== undefined),
|
|
167
|
-
) as T;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
144
|
export { AuthenticationError };
|
|
171
145
|
export type { AuthenticationErrorMetadata, AuthenticationErrorReason };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError } from "./app-error.js";
|
|
2
2
|
import { ErrorCodes } from "./error-codes.js";
|
|
3
3
|
import type { AppErrorOptions, ErrorSeverity } from "./types.js";
|
|
4
|
+
import { pickAppErrorOptions } from "./utils/index.js";
|
|
4
5
|
|
|
5
6
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
7
|
// Types
|
|
@@ -30,8 +31,12 @@ type AuthorizationErrorMetadata = {
|
|
|
30
31
|
/** Resource identification (without leaking the full payload) */
|
|
31
32
|
resource?: { type: string; id?: string };
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Optional: the acting principal (do not include sensitive data). `actorId`
|
|
36
|
+
* is the raw `RequestedBy` value — a user UUID *or* a `"system:<job>"`
|
|
37
|
+
* identity — so it must not be assumed to be a human user.
|
|
38
|
+
*/
|
|
39
|
+
actor?: { actorId: string; role?: string };
|
|
35
40
|
|
|
36
41
|
/** Optional: expected requirement */
|
|
37
42
|
required?: AuthorizationRequirement;
|
|
@@ -125,7 +130,7 @@ class AuthorizationError extends AppError {
|
|
|
125
130
|
code: ErrorCodes.authorization.forbidden,
|
|
126
131
|
reason: "forbidden",
|
|
127
132
|
metadata: { reason: "forbidden", ...extractMetadataOnly(input) },
|
|
128
|
-
...
|
|
133
|
+
...pickAppErrorOptions(input),
|
|
129
134
|
});
|
|
130
135
|
}
|
|
131
136
|
|
|
@@ -154,7 +159,7 @@ class AuthorizationError extends AppError {
|
|
|
154
159
|
...extractMetadataOnly(input),
|
|
155
160
|
decision: "deny",
|
|
156
161
|
},
|
|
157
|
-
...
|
|
162
|
+
...pickAppErrorOptions(input),
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -180,7 +185,7 @@ class AuthorizationError extends AppError {
|
|
|
180
185
|
reason: "missing_role",
|
|
181
186
|
...extractMetadataOnly(input),
|
|
182
187
|
},
|
|
183
|
-
...
|
|
188
|
+
...pickAppErrorOptions(input),
|
|
184
189
|
});
|
|
185
190
|
}
|
|
186
191
|
|
|
@@ -204,7 +209,7 @@ class AuthorizationError extends AppError {
|
|
|
204
209
|
reason: "missing_capability",
|
|
205
210
|
...extractMetadataOnly(input),
|
|
206
211
|
},
|
|
207
|
-
...
|
|
212
|
+
...pickAppErrorOptions(input),
|
|
208
213
|
});
|
|
209
214
|
}
|
|
210
215
|
|
|
@@ -226,7 +231,7 @@ class AuthorizationError extends AppError {
|
|
|
226
231
|
code: ErrorCodes.authorization.outOfScope,
|
|
227
232
|
reason: "out_of_scope",
|
|
228
233
|
metadata: { reason: "out_of_scope", ...extractMetadataOnly(input) },
|
|
229
|
-
...
|
|
234
|
+
...pickAppErrorOptions(input),
|
|
230
235
|
});
|
|
231
236
|
}
|
|
232
237
|
}
|
|
@@ -235,19 +240,6 @@ class AuthorizationError extends AppError {
|
|
|
235
240
|
// Helpers
|
|
236
241
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
237
242
|
|
|
238
|
-
function extractAppErrorOptions(options?: Partial<AppErrorOptions>) {
|
|
239
|
-
return {
|
|
240
|
-
cause: options?.cause,
|
|
241
|
-
type: options?.type,
|
|
242
|
-
severity: options?.severity,
|
|
243
|
-
correlationId: options?.correlationId,
|
|
244
|
-
requestId: options?.requestId,
|
|
245
|
-
commandId: options?.commandId,
|
|
246
|
-
createdAtIso: options?.createdAtIso,
|
|
247
|
-
publicMessage: options?.publicMessage,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
243
|
function extractMetadataOnly(
|
|
252
244
|
input: AuthorizationErrorFactoryOptions,
|
|
253
245
|
): Omit<AuthorizationErrorMetadata, "reason"> {
|
|
@@ -14,14 +14,17 @@ class BusinessRuleViolationError extends AppError {
|
|
|
14
14
|
options?: AppErrorOptions,
|
|
15
15
|
) {
|
|
16
16
|
const baseMetadata = detail ? { rule, detail } : { rule };
|
|
17
|
+
// Reserved keys (`rule`/`detail`) spread last so caller metadata cannot
|
|
18
|
+
// clobber them.
|
|
17
19
|
const mergedMetadata = options?.metadata
|
|
18
|
-
? { ...
|
|
20
|
+
? { ...options.metadata, ...baseMetadata }
|
|
19
21
|
: baseMetadata;
|
|
20
22
|
|
|
21
23
|
super(message, ErrorCodes.businessRuleViolation, {
|
|
22
24
|
...options,
|
|
23
25
|
metadata: mergedMetadata,
|
|
24
26
|
});
|
|
27
|
+
Object.freeze(this);
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { payloadHash, sha256Hex
|
|
1
|
+
import { payloadHash, sha256Hex } from "../shared/hashing.js";
|
|
2
|
+
import { stableStringify } from "../shared/stable-stringify.js";
|
|
2
3
|
|
|
3
4
|
import { AppError } from "./app-error.js";
|
|
4
5
|
import { ErrorCodes } from "./error-codes.js";
|
|
@@ -359,7 +360,9 @@ function resolveKeyIdentity(
|
|
|
359
360
|
(key ? (key.length <= 8 ? key : key.slice(0, 8)) : undefined);
|
|
360
361
|
|
|
361
362
|
return {
|
|
362
|
-
|
|
363
|
+
// Derive the hash from the raw key when the caller didn't supply one —
|
|
364
|
+
// cheap traceability without leaking the key itself.
|
|
365
|
+
keyHash: keyHash ?? (key ? sha256Hex(key) : undefined),
|
|
363
366
|
keyPreview: preview,
|
|
364
367
|
};
|
|
365
368
|
}
|
package/src/core/errors/index.ts
CHANGED
|
@@ -74,6 +74,8 @@ export type {
|
|
|
74
74
|
SerializationFailureCategory,
|
|
75
75
|
} from "./serialization-error.js";
|
|
76
76
|
export {
|
|
77
|
+
// `DeserializationError` is a readability alias for the inbound direction
|
|
78
|
+
// (deserialization == SerializationInError); both names are public API.
|
|
77
79
|
SerializationInError as DeserializationError,
|
|
78
80
|
safePreview,
|
|
79
81
|
SerializationCodes,
|
|
@@ -83,6 +85,8 @@ export {
|
|
|
83
85
|
SerializationOutError,
|
|
84
86
|
} from "./serialization-error.js";
|
|
85
87
|
export type {
|
|
88
|
+
// `ExpiredError*` aliases are kept for callers that reach for the
|
|
89
|
+
// expiry-specific name; they are the same shapes as the generic Temporal*.
|
|
86
90
|
TemporalErrorMetadata as ExpiredErrorMetadata,
|
|
87
91
|
TemporalPrecision as ExpiredErrorPrecision,
|
|
88
92
|
TemporalErrorMetadata,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppErrorOptions, ErrorSeverity } from "./types.js";
|
|
2
2
|
import { AppError } from "./app-error.js";
|
|
3
3
|
import { ErrorCodes } from "./error-codes.js";
|
|
4
|
+
import { compactMetadata, pickAppErrorOptions } from "./utils/index.js";
|
|
4
5
|
|
|
5
6
|
type IntegrationErrorReason =
|
|
6
7
|
| "timeout"
|
|
@@ -78,7 +79,7 @@ class IntegrationError extends AppError {
|
|
|
78
79
|
code: ErrorCodes.integration.timeout,
|
|
79
80
|
reason: "timeout",
|
|
80
81
|
metadata: buildMetadata({ reason: "timeout", ...options }),
|
|
81
|
-
...
|
|
82
|
+
...pickAppErrorOptions(options),
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -94,7 +95,7 @@ class IntegrationError extends AppError {
|
|
|
94
95
|
code: ErrorCodes.integration.unreachable,
|
|
95
96
|
reason: "unreachable",
|
|
96
97
|
metadata: buildMetadata({ reason: "unreachable", ...options }),
|
|
97
|
-
...
|
|
98
|
+
...pickAppErrorOptions(options),
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -111,7 +112,7 @@ class IntegrationError extends AppError {
|
|
|
111
112
|
code: ErrorCodes.integration.badResponse,
|
|
112
113
|
reason: "bad_response",
|
|
113
114
|
metadata: buildMetadata({ reason: "bad_response", ...options }),
|
|
114
|
-
...
|
|
115
|
+
...pickAppErrorOptions(options),
|
|
115
116
|
});
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -157,26 +158,5 @@ function buildMetadata(
|
|
|
157
158
|
}) as IntegrationErrorMetadata;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
function pickAppErrorFields(
|
|
161
|
-
options: IntegrationErrorBaseOptions,
|
|
162
|
-
): IntegrationErrorAppOptions {
|
|
163
|
-
return {
|
|
164
|
-
cause: options.cause,
|
|
165
|
-
type: options.type,
|
|
166
|
-
severity: options.severity,
|
|
167
|
-
correlationId: options.correlationId,
|
|
168
|
-
requestId: options.requestId,
|
|
169
|
-
commandId: options.commandId,
|
|
170
|
-
createdAtIso: options.createdAtIso,
|
|
171
|
-
publicMessage: options.publicMessage,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function compactMetadata<T extends Record<string, unknown>>(input: T): T {
|
|
176
|
-
return Object.fromEntries(
|
|
177
|
-
Object.entries(input).filter(([, value]) => value !== undefined),
|
|
178
|
-
) as T;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
161
|
export { IntegrationError };
|
|
182
162
|
export type { IntegrationErrorReason, IntegrationErrorMetadata };
|
|
@@ -27,14 +27,17 @@ class NotFoundError extends AppError {
|
|
|
27
27
|
options?: AppErrorOptions,
|
|
28
28
|
) {
|
|
29
29
|
const baseMetadata = criteria ? { resource, criteria } : { resource };
|
|
30
|
+
// Reserved keys (`resource`/`criteria`) spread last so caller metadata
|
|
31
|
+
// can add to but never clobber them — as the docs promise.
|
|
30
32
|
const mergedMetadata = options?.metadata
|
|
31
|
-
? { ...
|
|
33
|
+
? { ...options.metadata, ...baseMetadata }
|
|
32
34
|
: baseMetadata;
|
|
33
35
|
|
|
34
36
|
super(`${resource} not found`, ErrorCodes.notFound, {
|
|
35
37
|
...options,
|
|
36
38
|
metadata: mergedMetadata,
|
|
37
39
|
});
|
|
40
|
+
Object.freeze(this);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError } from "./app-error.js";
|
|
2
2
|
import { ErrorCodes } from "./error-codes.js";
|
|
3
3
|
import type { AppErrorOptions } from "./types.js";
|
|
4
|
+
import { pickAppErrorOptions, stripAppErrorOptions } from "./utils/index.js";
|
|
4
5
|
|
|
5
6
|
type TemporalKind = "expired" | "not_yet_valid";
|
|
6
7
|
|
|
@@ -66,19 +67,12 @@ class ExpiredError extends TemporalError {
|
|
|
66
67
|
code: ErrorCodes.temporal.expired,
|
|
67
68
|
kind: "expired",
|
|
68
69
|
metadata: {
|
|
69
|
-
...input,
|
|
70
|
+
...stripAppErrorOptions(input),
|
|
70
71
|
hint:
|
|
71
72
|
input.hint ??
|
|
72
73
|
"The window has expired. Request a new link or try again within the valid period.",
|
|
73
74
|
},
|
|
74
|
-
|
|
75
|
-
type: input.type,
|
|
76
|
-
severity: input.severity,
|
|
77
|
-
correlationId: input.correlationId,
|
|
78
|
-
requestId: input.requestId,
|
|
79
|
-
commandId: input.commandId,
|
|
80
|
-
createdAtIso: input.createdAtIso,
|
|
81
|
-
publicMessage: input.publicMessage,
|
|
75
|
+
...pickAppErrorOptions(input),
|
|
82
76
|
});
|
|
83
77
|
}
|
|
84
78
|
}
|
|
@@ -94,23 +88,31 @@ class NotYetValidError extends TemporalError {
|
|
|
94
88
|
code: ErrorCodes.temporal.notYetValid,
|
|
95
89
|
kind: "not_yet_valid",
|
|
96
90
|
metadata: {
|
|
97
|
-
...input,
|
|
91
|
+
...stripAppErrorOptions(input),
|
|
98
92
|
hint:
|
|
99
93
|
input.hint ??
|
|
100
94
|
"Not yet within the valid period. Try again later.",
|
|
101
95
|
},
|
|
102
|
-
|
|
103
|
-
type: input.type,
|
|
104
|
-
severity: input.severity,
|
|
105
|
-
correlationId: input.correlationId,
|
|
106
|
-
requestId: input.requestId,
|
|
107
|
-
commandId: input.commandId,
|
|
108
|
-
createdAtIso: input.createdAtIso,
|
|
109
|
-
publicMessage: input.publicMessage,
|
|
96
|
+
...pickAppErrorOptions(input),
|
|
110
97
|
});
|
|
111
98
|
}
|
|
112
99
|
}
|
|
113
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Evaluates whether `evaluatedAtIso` falls inside the [`validFromIso`,
|
|
103
|
+
* `validUntilIso`] window.
|
|
104
|
+
*
|
|
105
|
+
* Fails **closed**: a boundary that is present but unparseable is treated as if
|
|
106
|
+
* the window were closed on that side (a bad `validUntilIso` → expired, a bad
|
|
107
|
+
* `validFromIso` → not-yet-valid), never silently ignored — these checks guard
|
|
108
|
+
* access (invites, links, tokens), so a typo in an expiry must not disable
|
|
109
|
+
* expiry. A `null`/absent boundary means "no bound on that side", which is
|
|
110
|
+
* distinct from a malformed one. Returns `null` only when `evaluatedAtIso`
|
|
111
|
+
* itself is unparseable (nothing can be decided).
|
|
112
|
+
*
|
|
113
|
+
* For an inverted window (`validFrom` > `validUntil`) `expired` wins, since the
|
|
114
|
+
* upper-bound check short-circuits.
|
|
115
|
+
*/
|
|
114
116
|
function evaluateTemporalWindow(input: {
|
|
115
117
|
validFromIso?: string | null;
|
|
116
118
|
validUntilIso?: string | null;
|
|
@@ -123,14 +125,14 @@ function evaluateTemporalWindow(input: {
|
|
|
123
125
|
|
|
124
126
|
if (input.validFromIso) {
|
|
125
127
|
const fromMs = Date.parse(input.validFromIso);
|
|
126
|
-
if (
|
|
128
|
+
if (Number.isNaN(fromMs) || evaluatedMs < fromMs) {
|
|
127
129
|
notYetValid = true;
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
if (input.validUntilIso) {
|
|
132
134
|
const untilMs = Date.parse(input.validUntilIso);
|
|
133
|
-
if (
|
|
135
|
+
if (Number.isNaN(untilMs) || evaluatedMs > untilMs) {
|
|
134
136
|
return { expired: true, notYetValid: false };
|
|
135
137
|
}
|
|
136
138
|
}
|
|
@@ -12,10 +12,14 @@ class UnexpectedError extends AppError {
|
|
|
12
12
|
cause?: unknown,
|
|
13
13
|
options?: Omit<AppErrorOptions, "cause">,
|
|
14
14
|
) {
|
|
15
|
+
// The error you most want alerted on defaults to "critical" — a caller
|
|
16
|
+
// can still lower it via options.severity.
|
|
15
17
|
super(message, ErrorCodes.unexpected, {
|
|
16
|
-
|
|
18
|
+
severity: "critical",
|
|
17
19
|
...options,
|
|
20
|
+
cause,
|
|
18
21
|
});
|
|
22
|
+
Object.freeze(this);
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Shared helpers for the error factories: separating the AppError envelope
|
|
2
|
+
// options from the free-form metadata, and dropping undefined-valued keys.
|
|
3
|
+
// Previously each of authentication/authorization/integration carried its own
|
|
4
|
+
// near-identical copy of these.
|
|
5
|
+
|
|
6
|
+
import type { AppErrorOptions } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/** The envelope fields AppError understands; everything else is metadata. */
|
|
9
|
+
const APP_ERROR_OPTION_KEYS = [
|
|
10
|
+
"cause",
|
|
11
|
+
"type",
|
|
12
|
+
"severity",
|
|
13
|
+
"correlationId",
|
|
14
|
+
"requestId",
|
|
15
|
+
"commandId",
|
|
16
|
+
"createdAtIso",
|
|
17
|
+
"publicMessage",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
type AppErrorEnvelope = Pick<
|
|
21
|
+
AppErrorOptions,
|
|
22
|
+
(typeof APP_ERROR_OPTION_KEYS)[number]
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Picks the AppError envelope fields out of a flat factory input, so they can
|
|
27
|
+
* be forwarded to the constructor as options instead of leaking into metadata.
|
|
28
|
+
*/
|
|
29
|
+
function pickAppErrorOptions(
|
|
30
|
+
input?: Partial<AppErrorOptions>,
|
|
31
|
+
): AppErrorEnvelope {
|
|
32
|
+
return {
|
|
33
|
+
cause: input?.cause,
|
|
34
|
+
type: input?.type,
|
|
35
|
+
severity: input?.severity,
|
|
36
|
+
correlationId: input?.correlationId,
|
|
37
|
+
requestId: input?.requestId,
|
|
38
|
+
commandId: input?.commandId,
|
|
39
|
+
createdAtIso: input?.createdAtIso,
|
|
40
|
+
publicMessage: input?.publicMessage,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The complement of {@link pickAppErrorOptions}: everything that is *not* an
|
|
46
|
+
* AppError envelope field, i.e. the caller-supplied metadata. Keeps the cause
|
|
47
|
+
* and the public message out of the internal data block.
|
|
48
|
+
*/
|
|
49
|
+
function stripAppErrorOptions<T extends Record<string, unknown>>(
|
|
50
|
+
input: T,
|
|
51
|
+
): Omit<T, (typeof APP_ERROR_OPTION_KEYS)[number]> {
|
|
52
|
+
const result = { ...input };
|
|
53
|
+
for (const key of APP_ERROR_OPTION_KEYS) {
|
|
54
|
+
delete result[key];
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Drops keys whose value is `undefined` (mirrors how JSON.stringify treats them). */
|
|
60
|
+
function compactMetadata<T extends Record<string, unknown>>(
|
|
61
|
+
input?: T,
|
|
62
|
+
): T | undefined {
|
|
63
|
+
if (!input) return undefined;
|
|
64
|
+
|
|
65
|
+
return Object.fromEntries(
|
|
66
|
+
Object.entries(input).filter(([, value]) => value !== undefined),
|
|
67
|
+
) as T;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { compactMetadata, pickAppErrorOptions, stripAppErrorOptions };
|
|
@@ -75,7 +75,21 @@ function sanitizeValue(value: unknown, seen: WeakSet<object>): JsonSafeValue {
|
|
|
75
75
|
const result: Record<string, JsonSafeValue> = {};
|
|
76
76
|
for (const [key, val] of Object.entries(value)) {
|
|
77
77
|
if (val === undefined) continue;
|
|
78
|
-
|
|
78
|
+
const sanitized = sanitizeValue(val, seen);
|
|
79
|
+
if (key === "__proto__") {
|
|
80
|
+
// Plain `result.__proto__ = x` would reparent `result` instead of
|
|
81
|
+
// creating an own data property, silently dropping the key. Define
|
|
82
|
+
// it explicitly so `__proto__` survives as data — matching how
|
|
83
|
+
// JSON.stringify/JSON.parse round-trip it.
|
|
84
|
+
Object.defineProperty(result, key, {
|
|
85
|
+
value: sanitized,
|
|
86
|
+
enumerable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
result[key] = sanitized;
|
|
92
|
+
}
|
|
79
93
|
}
|
|
80
94
|
seen.delete(value);
|
|
81
95
|
return result;
|
|
@@ -38,14 +38,17 @@ class ValidationError extends AppError {
|
|
|
38
38
|
field: field.value,
|
|
39
39
|
validationCode: code.value,
|
|
40
40
|
};
|
|
41
|
+
// `field`/`validationCode` spread last so caller metadata is merged
|
|
42
|
+
// over them without ever overwriting them by accident.
|
|
41
43
|
const mergedMetadata = options?.metadata
|
|
42
|
-
? { ...
|
|
44
|
+
? { ...options.metadata, ...baseMetadata }
|
|
43
45
|
: baseMetadata;
|
|
44
46
|
|
|
45
47
|
super(message, ErrorCodes.validation, {
|
|
46
48
|
...options,
|
|
47
49
|
metadata: mergedMetadata,
|
|
48
50
|
});
|
|
51
|
+
Object.freeze(this);
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
abstract class DomainException extends Error {
|
|
2
|
-
|
|
2
|
+
// ES2020's `Error` lib type does not declare `cause`; mirror the sibling
|
|
3
|
+
// `AppError` and carry it explicitly so a domain exception can wrap the
|
|
4
|
+
// lower-level failure that triggered it. Assigned rather than passed to
|
|
5
|
+
// `super` to stay independent of the ES2022 `Error(message, { cause })`
|
|
6
|
+
// overload the build target doesn't guarantee.
|
|
7
|
+
public readonly cause?: unknown;
|
|
8
|
+
|
|
9
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
3
10
|
super(message);
|
|
11
|
+
this.cause = options?.cause;
|
|
4
12
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
5
13
|
this.name = new.target.name;
|
|
6
14
|
}
|
|
@@ -4,8 +4,12 @@ class EntityNotFoundException extends DomainException {
|
|
|
4
4
|
constructor(
|
|
5
5
|
public readonly entityName: string,
|
|
6
6
|
public readonly identifier: string,
|
|
7
|
+
options?: { cause?: unknown },
|
|
7
8
|
) {
|
|
8
|
-
super(
|
|
9
|
+
super(
|
|
10
|
+
`${entityName} with identifier ${identifier} was not found.`,
|
|
11
|
+
options,
|
|
12
|
+
);
|
|
9
13
|
}
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -6,9 +6,12 @@ class InvalidStateTransitionException<
|
|
|
6
6
|
constructor(
|
|
7
7
|
public readonly from: TState,
|
|
8
8
|
public readonly to: TState,
|
|
9
|
-
message
|
|
9
|
+
message?: string,
|
|
10
|
+
options?: { cause?: unknown },
|
|
10
11
|
) {
|
|
11
|
-
|
|
12
|
+
// Compose a sensible default from `from`/`to` when no message is given,
|
|
13
|
+
// mirroring how EntityNotFoundException builds its own message.
|
|
14
|
+
super(message ?? `Cannot transition from ${from} to ${to}.`, options);
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DomainException } from "./domain-exception.js";
|
|
2
2
|
|
|
3
3
|
class InvariantViolationException extends DomainException {
|
|
4
|
-
constructor(message: string) {
|
|
5
|
-
super(message);
|
|
4
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
5
|
+
super(message, options);
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -13,6 +13,20 @@ class ValidationCode {
|
|
|
13
13
|
return new ValidationCode(value);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
public toString(): string {
|
|
17
|
+
return this.value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public equals(other: ValidationCode): boolean {
|
|
21
|
+
return this.value === other.value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Serialize to the bare code string, so JSON.stringify of a violation
|
|
25
|
+
// yields "blank" rather than { "value": "blank" }. Mirrors ValidationField.
|
|
26
|
+
public toJSON(): string {
|
|
27
|
+
return this.value;
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
// Common, domain-friendly codes
|
|
17
31
|
public static readonly BLANK = new ValidationCode("blank");
|
|
18
32
|
public static readonly REQUIRED = new ValidationCode("required");
|