@cullet/erp-core 1.4.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 +7 -1
- 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 +21 -6
- package/dist/app-error.cjs.map +1 -1
- package/dist/app-error.js +21 -6
- 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 +117 -172
- package/dist/condition-evaluator.cjs.map +1 -1
- package/dist/condition-evaluator.js +118 -167
- 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 +3 -2
- package/dist/errors/index.d.cts +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +3 -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 +2 -44
- package/dist/hashing.cjs.map +1 -1
- package/dist/hashing.js +3 -39
- 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 +15 -11
- 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 +8 -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 -13
- package/dist/policies/engines/v1/gate/index.d.ts +3 -13
- 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 +41 -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 +43 -48
- package/dist/policy-service.js.map +1 -1
- package/dist/requested-by.cjs +4 -4
- package/dist/requested-by.cjs.map +1 -1
- package/dist/requested-by.js +2 -2
- 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 +87 -0
- package/dist/stable-stringify.cjs.map +1 -0
- package/dist/stable-stringify.js +76 -0
- package/dist/stable-stringify.js.map +1 -0
- package/dist/temporal-snapshot.d.cts +87 -0
- package/dist/temporal-snapshot.d.ts +87 -0
- package/dist/temporal-use-case.cjs +174 -16
- package/dist/temporal-use-case.cjs.map +1 -1
- package/dist/temporal-use-case.d.cts +82 -44
- package/dist/temporal-use-case.d.ts +82 -44
- package/dist/temporal-use-case.js +167 -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 +141 -8
- 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 -8
- package/dist/uuid-identifier.js.map +1 -1
- package/dist/uuid.cjs +23 -0
- package/dist/uuid.cjs.map +1 -0
- package/dist/uuid.js +18 -0
- package/dist/uuid.js.map +1 -0
- 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 +29 -8
- package/dist/validation-error.d.ts +29 -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 +23 -4
- package/dist/value-object.cjs.map +1 -1
- package/dist/value-object.d.cts +25 -1
- package/dist/value-object.d.ts +25 -1
- package/dist/value-object.js +23 -4
- 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 +5 -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 +13 -5
- 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 +25 -3
- package/src/core/application/temporal/temporal-use-case.ts +31 -4
- package/src/core/application/use-case.ts +45 -8
- 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 +51 -8
- 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 +6 -11
- package/src/core/domain/value-object.ts +29 -3
- 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 +35 -12
- 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 +120 -291
- 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 -57
- package/src/core/shared/immutable.ts +22 -4
- package/src/core/shared/stable-stringify.ts +144 -0
- package/src/core/shared/uuid.ts +16 -0
- 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 -126
- package/dist/entity.cjs.map +0 -1
- package/dist/entity.js +0 -115
- package/dist/entity.js.map +0 -1
- package/dist/use-case.cjs +0 -96
- package/dist/use-case.cjs.map +0 -1
- package/dist/use-case.js +0 -91
- package/dist/use-case.js.map +0 -1
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import { InvariantViolationException } from "../../exceptions/invariant-violation-exception.js";
|
|
2
1
|
import { makeImmutable } from "../../shared/immutable.js";
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
import { assertHalfOpenInterval } from "./half-open-interval.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Represents business time (Valid Time).
|
|
7
7
|
* Defines when a piece of information is considered true or in effect in the real world.
|
|
8
|
+
*
|
|
9
|
+
* The window is half-open — `from` inclusive, `to` exclusive — and strictly
|
|
10
|
+
* non-empty: `to` must be later than `from`, so a zero-duration range `[t, t)`
|
|
11
|
+
* is deliberately not representable. Model an instantaneous event as an open
|
|
12
|
+
* range closed by the next fact, not as an empty window.
|
|
13
|
+
*
|
|
14
|
+
* One caveat mirrors {@link ValueObject}: `createValidTime` clones the input
|
|
15
|
+
* dates (no aliasing) but the inner `Date`s are NOT frozen — `Object.freeze`
|
|
16
|
+
* cannot block `setTime`/`setFullYear`, so a caller can still mutate `from`
|
|
17
|
+
* in place and corrupt the range after creation. `readonly` only prevents
|
|
18
|
+
* reassignment. Treat the dates as read-only, or keep instants as ISO/epoch in
|
|
19
|
+
* your own state when that guarantee matters.
|
|
8
20
|
*/
|
|
9
21
|
interface ValidTime {
|
|
10
22
|
/** Start of the validity window (inclusive). */
|
|
@@ -19,19 +31,12 @@ function assertValidTime(
|
|
|
19
31
|
validTime: ValidTime,
|
|
20
32
|
fieldName: string = "validTime",
|
|
21
33
|
): void {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
assertValidDate(`${fieldName}.to`, validTime.to);
|
|
29
|
-
|
|
30
|
-
if (validTime.to.getTime() <= validTime.from.getTime()) {
|
|
31
|
-
throw new InvariantViolationException(
|
|
32
|
-
`${fieldName}.to must be later than ${fieldName}.from`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
34
|
+
assertHalfOpenInterval(
|
|
35
|
+
`${fieldName}.from`,
|
|
36
|
+
validTime.from,
|
|
37
|
+
`${fieldName}.to`,
|
|
38
|
+
validTime.to,
|
|
39
|
+
);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
function createValidTime(input: CreateValidTimeInput): ValidTime {
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
+
import { UUID_PATTERN } from "../shared/uuid.js";
|
|
1
2
|
import { ValueObject } from "./value-object.js";
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
-
* Canonical RFC-4122 UUID (versions 1–5), matched case-insensitively. Declared
|
|
5
|
-
* once here so the dozens of typed identifiers a domain accumulates never have
|
|
6
|
-
* to re-state the pattern.
|
|
7
|
-
*/
|
|
8
|
-
const UUID_PATTERN =
|
|
9
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
10
|
-
|
|
11
4
|
/**
|
|
12
5
|
* Base class for UUID-backed identity value objects.
|
|
13
6
|
*
|
|
@@ -45,7 +38,7 @@ abstract class UuidIdentifier<
|
|
|
45
38
|
* two identifiers with different brands are not interchangeable at the type
|
|
46
39
|
* level despite sharing the same `string` value.
|
|
47
40
|
*/
|
|
48
|
-
protected
|
|
41
|
+
declare protected readonly __brand: TBrand;
|
|
49
42
|
|
|
50
43
|
protected constructor(value: string) {
|
|
51
44
|
super(value);
|
|
@@ -61,8 +54,10 @@ abstract class UuidIdentifier<
|
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
/**
|
|
64
|
-
* Whether `candidate` is a canonical UUID (versions 1–
|
|
65
|
-
*
|
|
57
|
+
* Whether `candidate` is a canonical UUID (versions 1–8, including the
|
|
58
|
+
* time-ordered v7; nil and max UUIDs are rejected as sentinels). The single
|
|
59
|
+
* source of truth for the format — see `shared/uuid.ts`; concrete
|
|
60
|
+
* identifiers call this from `create`.
|
|
66
61
|
*/
|
|
67
62
|
public static isValid(candidate: string): boolean {
|
|
68
63
|
return UUID_PATTERN.test(candidate);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PluginManager } from "../plugins/index.js";
|
|
2
2
|
import { type DeepReadonly, makeImmutable } from "../shared/immutable.js";
|
|
3
|
+
import { stableStringify } from "../shared/stable-stringify.js";
|
|
3
4
|
import { type ContractVersion, version } from "../versioning/version.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -28,6 +29,12 @@ type ValueObjectPluginContract = {
|
|
|
28
29
|
* any risk of a consumer mutating shared state. Subclasses seal the instance
|
|
29
30
|
* itself with {@link finalize} once their own fields are set.
|
|
30
31
|
*
|
|
32
|
+
* One exception: nested `Date` instances are cloned but NOT frozen —
|
|
33
|
+
* `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of
|
|
34
|
+
* `value` can still mutate an inner `Date` in place. Store instants as ISO
|
|
35
|
+
* strings or epoch timestamps in the wrapped state (converting to `Date` only
|
|
36
|
+
* in an accessor) when that guarantee matters.
|
|
37
|
+
*
|
|
31
38
|
* @typeParam T - The shape of the wrapped data.
|
|
32
39
|
* @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.
|
|
33
40
|
*/
|
|
@@ -40,6 +47,13 @@ abstract class ValueObject<T, P> {
|
|
|
40
47
|
* default — when nothing is registered, {@link equals} falls back to a
|
|
41
48
|
* structural comparison of the wrapped `value`. Hosts register a plugin
|
|
42
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.
|
|
43
57
|
*/
|
|
44
58
|
public static readonly plugins =
|
|
45
59
|
new PluginManager<ValueObjectPluginContract>();
|
|
@@ -61,7 +75,7 @@ abstract class ValueObject<T, P> {
|
|
|
61
75
|
* `@version` decorator — used to detect state persisted under an older shape.
|
|
62
76
|
*/
|
|
63
77
|
public get contractVersion(): ContractVersion {
|
|
64
|
-
return ValueObject.CONTRACT_VERSION;
|
|
78
|
+
return (this.constructor as typeof ValueObject).CONTRACT_VERSION;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
/**
|
|
@@ -93,13 +107,25 @@ abstract class ValueObject<T, P> {
|
|
|
93
107
|
* same data are considered equal.
|
|
94
108
|
*
|
|
95
109
|
* Delegates to the registered equality {@link plugins}; with no plugin
|
|
96
|
-
* registered it falls back to comparing the serialized wrapped `value
|
|
110
|
+
* registered it falls back to comparing the serialized wrapped `value`,
|
|
111
|
+
* with object keys sorted so insertion order (e.g. code-built vs
|
|
112
|
+
* JSON-rehydrated) never affects the result.
|
|
97
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.
|
|
98
123
|
*/
|
|
99
124
|
public equals(other: this): boolean {
|
|
100
125
|
return ValueObject.plugins.invoke("equals", [this, other], {
|
|
101
126
|
fallback: (a, b) =>
|
|
102
|
-
|
|
127
|
+
a.constructor === b.constructor &&
|
|
128
|
+
stableStringify(a.value) === stableStringify(b.value),
|
|
103
129
|
});
|
|
104
130
|
}
|
|
105
131
|
|
|
@@ -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 };
|