@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.
Files changed (306) hide show
  1. package/KIT_CONTEXT.md +7 -1
  2. package/dist/abac/index.d.cts +2 -2
  3. package/dist/abac/index.d.ts +2 -2
  4. package/dist/aggregate-version.cjs +14 -0
  5. package/dist/aggregate-version.cjs.map +1 -0
  6. package/dist/aggregate-version.js +9 -0
  7. package/dist/aggregate-version.js.map +1 -0
  8. package/dist/app-error.cjs +21 -6
  9. package/dist/app-error.cjs.map +1 -1
  10. package/dist/app-error.js +21 -6
  11. package/dist/app-error.js.map +1 -1
  12. package/dist/application/index.cjs +8 -4
  13. package/dist/application/index.d.cts +2 -2
  14. package/dist/application/index.d.ts +2 -2
  15. package/dist/application/index.js +1 -2
  16. package/dist/authorization-error.cjs +67 -17
  17. package/dist/authorization-error.cjs.map +1 -1
  18. package/dist/authorization-error.d.cts +6 -2
  19. package/dist/authorization-error.d.ts +6 -2
  20. package/dist/authorization-error.js +50 -18
  21. package/dist/authorization-error.js.map +1 -1
  22. package/dist/authorizer.port.d.cts +18 -3
  23. package/dist/authorizer.port.d.ts +18 -3
  24. package/dist/composite-authorizer.d.cts +63 -10
  25. package/dist/composite-authorizer.d.ts +63 -10
  26. package/dist/condition-evaluator.cjs +117 -172
  27. package/dist/condition-evaluator.cjs.map +1 -1
  28. package/dist/condition-evaluator.js +118 -167
  29. package/dist/condition-evaluator.js.map +1 -1
  30. package/dist/core-config.d.cts +53 -6
  31. package/dist/core-config.d.ts +53 -6
  32. package/dist/decorate.cjs +14 -0
  33. package/dist/decorate.js +9 -0
  34. package/dist/domain/index.cjs +107 -2
  35. package/dist/domain/index.cjs.map +1 -0
  36. package/dist/domain/index.d.cts +25 -1
  37. package/dist/domain/index.d.ts +25 -1
  38. package/dist/domain/index.js +99 -3
  39. package/dist/domain/index.js.map +1 -0
  40. package/dist/domain-event-contracts.cjs +12 -8
  41. package/dist/domain-event-contracts.cjs.map +1 -1
  42. package/dist/domain-event-contracts.d.cts +29 -4
  43. package/dist/domain-event-contracts.d.ts +29 -4
  44. package/dist/domain-event-contracts.js +11 -7
  45. package/dist/domain-event-contracts.js.map +1 -1
  46. package/dist/domain-exception.cjs +2 -1
  47. package/dist/domain-exception.cjs.map +1 -1
  48. package/dist/domain-exception.js +2 -1
  49. package/dist/domain-exception.js.map +1 -1
  50. package/dist/errors/index.cjs +3 -2
  51. package/dist/errors/index.d.cts +1 -1
  52. package/dist/errors/index.d.ts +1 -1
  53. package/dist/errors/index.js +3 -2
  54. package/dist/exceptions/index.cjs +2 -2
  55. package/dist/exceptions/index.d.cts +1 -2
  56. package/dist/exceptions/index.d.ts +1 -2
  57. package/dist/exceptions/index.js +2 -2
  58. package/dist/gate-engine-registry.cjs.map +1 -1
  59. package/dist/gate-engine-registry.d.cts +1 -1
  60. package/dist/gate-engine-registry.d.ts +1 -1
  61. package/dist/gate-engine-registry.js.map +1 -1
  62. package/dist/gate-v1-payload.schema.cjs +11 -6
  63. package/dist/gate-v1-payload.schema.cjs.map +1 -1
  64. package/dist/gate-v1-payload.schema.js +11 -6
  65. package/dist/gate-v1-payload.schema.js.map +1 -1
  66. package/dist/hashing.cjs +2 -44
  67. package/dist/hashing.cjs.map +1 -1
  68. package/dist/hashing.js +3 -39
  69. package/dist/hashing.js.map +1 -1
  70. package/dist/immutable.cjs +25 -12
  71. package/dist/immutable.cjs.map +1 -1
  72. package/dist/immutable.js +24 -11
  73. package/dist/immutable.js.map +1 -1
  74. package/dist/index.cjs +15 -11
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +9 -10
  77. package/dist/index.d.ts +9 -10
  78. package/dist/index.js +8 -9
  79. package/dist/index.js.map +1 -1
  80. package/dist/invalid-state-transition-exception.cjs +6 -6
  81. package/dist/invalid-state-transition-exception.cjs.map +1 -1
  82. package/dist/invalid-state-transition-exception.js +6 -6
  83. package/dist/invalid-state-transition-exception.js.map +1 -1
  84. package/dist/invariant-violation-exception.cjs +2 -2
  85. package/dist/invariant-violation-exception.cjs.map +1 -1
  86. package/dist/invariant-violation-exception.js +2 -2
  87. package/dist/invariant-violation-exception.js.map +1 -1
  88. package/dist/not-found-error.cjs +6 -4
  89. package/dist/not-found-error.cjs.map +1 -1
  90. package/dist/not-found-error.js +6 -4
  91. package/dist/not-found-error.js.map +1 -1
  92. package/dist/outcome.cjs +5 -5
  93. package/dist/outcome.cjs.map +1 -1
  94. package/dist/outcome.js +5 -5
  95. package/dist/outcome.js.map +1 -1
  96. package/dist/parse-gate-payload.d.cts +1 -1
  97. package/dist/parse-gate-payload.d.ts +1 -1
  98. package/dist/path.d.cts +2 -2
  99. package/dist/path.d.ts +2 -2
  100. package/dist/plugin.cjs +23 -13
  101. package/dist/plugin.cjs.map +1 -1
  102. package/dist/plugin.d.cts +22 -8
  103. package/dist/plugin.d.ts +22 -8
  104. package/dist/plugin.js +23 -13
  105. package/dist/plugin.js.map +1 -1
  106. package/dist/policies/engines/index.d.cts +1 -1
  107. package/dist/policies/engines/index.d.ts +1 -1
  108. package/dist/policies/engines/v1/gate/index.d.cts +3 -13
  109. package/dist/policies/engines/v1/gate/index.d.ts +3 -13
  110. package/dist/policies/index.d.cts +1 -1
  111. package/dist/policies/index.d.ts +1 -1
  112. package/dist/policy-bridge.cjs +30 -2
  113. package/dist/policy-bridge.cjs.map +1 -1
  114. package/dist/policy-bridge.d.cts +18 -0
  115. package/dist/policy-bridge.d.ts +18 -0
  116. package/dist/policy-bridge.js +30 -2
  117. package/dist/policy-bridge.js.map +1 -1
  118. package/dist/policy-service.cjs +41 -46
  119. package/dist/policy-service.cjs.map +1 -1
  120. package/dist/policy-service.d.cts +72 -9
  121. package/dist/policy-service.d.ts +72 -9
  122. package/dist/policy-service.js +43 -48
  123. package/dist/policy-service.js.map +1 -1
  124. package/dist/requested-by.cjs +4 -4
  125. package/dist/requested-by.cjs.map +1 -1
  126. package/dist/requested-by.js +2 -2
  127. package/dist/requested-by.js.map +1 -1
  128. package/dist/result.cjs +29 -1
  129. package/dist/result.cjs.map +1 -1
  130. package/dist/result.d.cts +12 -0
  131. package/dist/result.d.ts +12 -0
  132. package/dist/result.js +29 -1
  133. package/dist/result.js.map +1 -1
  134. package/dist/rule.cjs +94 -24
  135. package/dist/rule.cjs.map +1 -1
  136. package/dist/rule.js +94 -24
  137. package/dist/rule.js.map +1 -1
  138. package/dist/ruleset-registry.cjs +19 -0
  139. package/dist/ruleset-registry.cjs.map +1 -1
  140. package/dist/ruleset-registry.js +19 -0
  141. package/dist/ruleset-registry.js.map +1 -1
  142. package/dist/stable-stringify.cjs +87 -0
  143. package/dist/stable-stringify.cjs.map +1 -0
  144. package/dist/stable-stringify.js +76 -0
  145. package/dist/stable-stringify.js.map +1 -0
  146. package/dist/temporal-snapshot.d.cts +87 -0
  147. package/dist/temporal-snapshot.d.ts +87 -0
  148. package/dist/temporal-use-case.cjs +174 -16
  149. package/dist/temporal-use-case.cjs.map +1 -1
  150. package/dist/temporal-use-case.d.cts +82 -44
  151. package/dist/temporal-use-case.d.ts +82 -44
  152. package/dist/temporal-use-case.js +167 -15
  153. package/dist/temporal-use-case.js.map +1 -1
  154. package/dist/unexpected-error.cjs +4 -2
  155. package/dist/unexpected-error.cjs.map +1 -1
  156. package/dist/unexpected-error.js +4 -2
  157. package/dist/unexpected-error.js.map +1 -1
  158. package/dist/uuid-identifier.cjs +141 -8
  159. package/dist/uuid-identifier.cjs.map +1 -1
  160. package/dist/uuid-identifier.d.cts +26 -7
  161. package/dist/uuid-identifier.d.ts +26 -7
  162. package/dist/uuid-identifier.js +135 -8
  163. package/dist/uuid-identifier.js.map +1 -1
  164. package/dist/uuid.cjs +23 -0
  165. package/dist/uuid.cjs.map +1 -0
  166. package/dist/uuid.js +18 -0
  167. package/dist/uuid.js.map +1 -0
  168. package/dist/validation-code.cjs +9 -0
  169. package/dist/validation-code.cjs.map +1 -1
  170. package/dist/validation-code.d.cts +3 -0
  171. package/dist/validation-code.d.ts +3 -0
  172. package/dist/validation-code.js +9 -0
  173. package/dist/validation-code.js.map +1 -1
  174. package/dist/validation-error.cjs +42 -67
  175. package/dist/validation-error.cjs.map +1 -1
  176. package/dist/validation-error.d.cts +29 -8
  177. package/dist/validation-error.d.ts +29 -8
  178. package/dist/validation-error.js +41 -66
  179. package/dist/validation-error.js.map +1 -1
  180. package/dist/validation-exception.cjs +17 -6
  181. package/dist/validation-exception.cjs.map +1 -1
  182. package/dist/validation-exception.d.cts +33 -9
  183. package/dist/validation-exception.d.ts +33 -9
  184. package/dist/validation-exception.js +17 -6
  185. package/dist/validation-exception.js.map +1 -1
  186. package/dist/validation-field.cjs +3 -0
  187. package/dist/validation-field.cjs.map +1 -1
  188. package/dist/validation-field.d.cts +1 -0
  189. package/dist/validation-field.d.ts +1 -0
  190. package/dist/validation-field.js +3 -0
  191. package/dist/validation-field.js.map +1 -1
  192. package/dist/value-object-ruleset.contracts.d.cts +10 -0
  193. package/dist/value-object-ruleset.contracts.d.ts +10 -0
  194. package/dist/value-object.cjs +23 -4
  195. package/dist/value-object.cjs.map +1 -1
  196. package/dist/value-object.d.cts +25 -1
  197. package/dist/value-object.d.ts +25 -1
  198. package/dist/value-object.js +23 -4
  199. package/dist/value-object.js.map +1 -1
  200. package/dist/version.d.cts +27 -0
  201. package/dist/version.d.ts +27 -0
  202. package/dist/versioning/index.d.cts +2 -2
  203. package/dist/versioning/index.d.ts +2 -2
  204. package/meta.json +5 -2
  205. package/package.json +1 -1
  206. package/src/core/abac/authorizer.ts +60 -10
  207. package/src/core/abac/domain/policy-set.ts +52 -5
  208. package/src/core/abac/domain/rule.ts +28 -16
  209. package/src/core/abac/index.ts +7 -1
  210. package/src/core/application/commands/command.ts +14 -1
  211. package/src/core/application/commands/requested-by.ts +13 -5
  212. package/src/core/application/index.ts +2 -1
  213. package/src/core/application/policy-error-mapper.ts +6 -0
  214. package/src/core/application/ports/index.ts +7 -0
  215. package/src/core/application/ports/temporal-repository.port.ts +35 -2
  216. package/src/core/application/queries/index.ts +1 -1
  217. package/src/core/application/queries/query.ts +25 -3
  218. package/src/core/application/temporal/temporal-use-case.ts +31 -4
  219. package/src/core/application/use-case.ts +45 -8
  220. package/src/core/config/core-config.ts +46 -25
  221. package/src/core/config/index.ts +1 -0
  222. package/src/core/config/policy-reporter.ts +32 -1
  223. package/src/core/domain/entity.ts +51 -8
  224. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +0 -2
  225. package/src/core/domain/rulesets/ruleset-registry.ts +24 -0
  226. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +1 -7
  227. package/src/core/domain/temporal/half-open-interval.ts +34 -0
  228. package/src/core/domain/temporal/temporal-snapshot.ts +13 -2
  229. package/src/core/domain/temporal/transaction-time.ts +19 -15
  230. package/src/core/domain/temporal/valid-time.ts +20 -15
  231. package/src/core/domain/uuid-identifier.ts +6 -11
  232. package/src/core/domain/value-object.ts +29 -3
  233. package/src/core/errors/authentication-error.ts +5 -31
  234. package/src/core/errors/authorization-error.ts +12 -20
  235. package/src/core/errors/business-rule-violation-error.ts +4 -1
  236. package/src/core/errors/idempotency-error.ts +5 -2
  237. package/src/core/errors/index.ts +4 -0
  238. package/src/core/errors/integration-error.ts +4 -24
  239. package/src/core/errors/legacy-incompatible-error.ts +1 -0
  240. package/src/core/errors/not-found-error.ts +4 -1
  241. package/src/core/errors/temporal-error.ts +22 -20
  242. package/src/core/errors/unexpected-error.ts +5 -1
  243. package/src/core/errors/utils/factory-helpers.ts +70 -0
  244. package/src/core/errors/utils/index.ts +5 -0
  245. package/src/core/errors/utils/json-safe.ts +35 -12
  246. package/src/core/errors/validation-error.ts +4 -1
  247. package/src/core/exceptions/business-rule-violation-exception.ts +2 -1
  248. package/src/core/exceptions/domain-exception.ts +9 -1
  249. package/src/core/exceptions/entity-not-found-exception.ts +5 -1
  250. package/src/core/exceptions/invalid-state-transition-exception.ts +5 -2
  251. package/src/core/exceptions/invariant-violation-exception.ts +2 -2
  252. package/src/core/exceptions/validation-code.ts +14 -0
  253. package/src/core/exceptions/validation-exception.ts +44 -5
  254. package/src/core/exceptions/validation-field.ts +11 -1
  255. package/src/core/plugins/plugin.ts +25 -15
  256. package/src/core/plugins/types.ts +4 -2
  257. package/src/core/policies/asof/asof.ts +12 -0
  258. package/src/core/policies/catalog/policy-catalog-entry.ts +3 -1
  259. package/src/core/policies/catalog/policy-catalog.ts +5 -1
  260. package/src/core/policies/context/context-builder.ts +20 -0
  261. package/src/core/policies/context/context-resolver.ts +5 -0
  262. package/src/core/policies/context/context-seed.ts +11 -0
  263. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +0 -2
  264. package/src/core/policies/engines/parse-gate-payload.ts +9 -0
  265. package/src/core/policies/engines/v1/condition-date-operands.ts +112 -0
  266. package/src/core/policies/engines/v1/condition-evaluator.ts +120 -291
  267. package/src/core/policies/engines/v1/condition-schema.ts +23 -19
  268. package/src/core/policies/engines/v1/condition-types.ts +18 -11
  269. package/src/core/policies/index.ts +4 -6
  270. package/src/core/policies/service/policy-service.ts +46 -35
  271. package/src/core/policies/utils/hash.ts +5 -69
  272. package/src/core/policies/utils/result.ts +1 -1
  273. package/src/core/rbac/access-request.ts +12 -2
  274. package/src/core/rbac/authorizer.ts +8 -1
  275. package/src/core/rbac/domain/role.ts +20 -0
  276. package/src/core/rbac/domain/scope.ts +6 -1
  277. package/src/core/result/index.ts +5 -0
  278. package/src/core/result/outcome.ts +5 -7
  279. package/src/core/result/result.ts +34 -1
  280. package/src/core/shared/hashing.ts +6 -57
  281. package/src/core/shared/immutable.ts +22 -4
  282. package/src/core/shared/stable-stringify.ts +144 -0
  283. package/src/core/shared/uuid.ts +16 -0
  284. package/src/core/versioning/domain-event-contracts.ts +43 -15
  285. package/src/core/versioning/index.ts +1 -0
  286. package/src/core/versioning/version.ts +30 -1
  287. package/src/domain/index.ts +5 -0
  288. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +1 -1
  289. package/src/examples/rulesets/entity/order-invariants-v1.ts +2 -4
  290. package/src/examples/rulesets/entity/order-invariants-v2.ts +2 -4
  291. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +2 -4
  292. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +2 -4
  293. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +2 -4
  294. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +2 -4
  295. package/src/result/index.ts +7 -2
  296. package/src/version.ts +1 -1
  297. package/dist/domain-exception.d.cts +0 -7
  298. package/dist/domain-exception.d.ts +0 -7
  299. package/dist/entity.cjs +0 -126
  300. package/dist/entity.cjs.map +0 -1
  301. package/dist/entity.js +0 -115
  302. package/dist/entity.js.map +0 -1
  303. package/dist/use-case.cjs +0 -96
  304. package/dist/use-case.cjs.map +0 -1
  305. package/dist/use-case.js +0 -91
  306. package/dist/use-case.js.map +0 -1
@@ -49,7 +49,8 @@ declare abstract class Entity<TIdentifier> {
49
49
  * Declared `protected` because entities are reconstituted through a
50
50
  * subclass factory, never instantiated directly by application code.
51
51
  *
52
- * @throws {InvariantViolationException} When `updatedAt` is earlier than `createdAt`.
52
+ * @throws {InvariantViolationException} When `id` is absent, or when
53
+ * `updatedAt` is earlier than `createdAt`.
53
54
  */
54
55
  protected constructor(state: EntityState<TIdentifier>);
55
56
  /** The entity's stable identity — the basis for equality between entities. */
@@ -86,13 +87,29 @@ declare abstract class Entity<TIdentifier> {
86
87
  * call this from every state-changing method so the version counter stays
87
88
  * an accurate optimistic-lock token.
88
89
  *
89
- * @param updatedAt - The modification instant; defaults to now. Validated
90
- * and required not to predate `createdAt`, preserving the same invariant
91
- * the constructor enforces.
90
+ * `updatedAt` is monotonic: each call must be at or after the current
91
+ * `updatedAt` (equal is fine two mutations can share a millisecond).
92
+ * Without this, the version counter would advance while the timestamp
93
+ * walked backwards, corrupting any audit trail or ordering that reads
94
+ * `updatedAt`. A caller replaying out-of-order events must sort them first.
95
+ *
96
+ * @param updatedAt - The modification instant; defaults to now.
92
97
  * @returns The new aggregate version after the increment.
93
- * @throws {InvariantViolationException} When `updatedAt` is earlier than `createdAt`.
98
+ * @throws {InvariantViolationException} When `updatedAt` is earlier than
99
+ * the current `updatedAt`.
94
100
  */
95
101
  protected markAsModified(updatedAt?: Date): number;
102
+ /**
103
+ * Identity-based equality — the semantics that define an entity. Two
104
+ * entities are equal when they are the same concrete class and their ids
105
+ * match, regardless of any other field.
106
+ *
107
+ * The class check keeps a `CustomerId`-bearing entity from equalling an
108
+ * unrelated entity that happens to reuse the same raw id. Value-object ids
109
+ * are compared through their own `equals`, so two independently
110
+ * reconstituted id instances still match; primitive ids use `===`.
111
+ */
112
+ equals(other: Entity<TIdentifier> | null | undefined): boolean;
96
113
  }
97
114
  //#endregion
98
115
  //#region src/core/domain/uuid-identifier.d.ts
@@ -137,8 +154,10 @@ declare abstract class UuidIdentifier<TBrand extends string = string> extends Va
137
154
  /** The wrapped UUID string — convenient for logging and interpolation. */
138
155
  toString(): string;
139
156
  /**
140
- * Whether `candidate` is a canonical UUID (versions 1–5). The single source
141
- * of truth for the format; concrete identifiers call this from `create`.
157
+ * Whether `candidate` is a canonical UUID (versions 1–8, including the
158
+ * time-ordered v7; nil and max UUIDs are rejected as sentinels). The single
159
+ * source of truth for the format — see `shared/uuid.ts`; concrete
160
+ * identifiers call this from `create`.
142
161
  */
143
162
  static isValid(candidate: string): boolean;
144
163
  }
@@ -1,11 +1,136 @@
1
+ import { t as InvariantViolationException } from "./invariant-violation-exception.js";
2
+ import { n as cloneDate, t as assertValidDate } from "./temporal-guards.js";
3
+ import { i as version } from "./immutable.js";
4
+ import { t as __decorate } from "./decorate.js";
5
+ import { t as UUID_PATTERN } from "./uuid.js";
6
+ import { t as assertValidAggregateVersion } from "./aggregate-version.js";
1
7
  import { t as ValueObject } from "./value-object.js";
2
- //#region src/core/domain/uuid-identifier.ts
8
+ //#region src/core/domain/entity.ts
3
9
  /**
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.
10
+ * Base class for domain entities objects defined by a stable identity rather
11
+ * than by their attributes. Two entities are "the same" when their `id`
12
+ * matches, even if every other field differs; this is the opposite of a
13
+ * {@link ValueObject}, which is defined entirely by its contents.
14
+ *
15
+ * The class owns the bookkeeping common to every aggregate root: a creation
16
+ * timestamp that never changes, a last-modified timestamp, and an
17
+ * `aggregateVersion` counter used for optimistic concurrency control. All three
18
+ * are validated on construction and the dates are defensively cloned, so an
19
+ * entity can never be built into — or leak — an inconsistent temporal state.
20
+ *
21
+ * @typeParam TIdentifier - The identity type (e.g. a branded string id or a VO).
7
22
  */
8
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
23
+ let Entity = class Entity {
24
+ /**
25
+ * Reconstitutes an entity from its persisted {@link EntityState}.
26
+ *
27
+ * Validates the temporal invariants up front so an invalid entity is
28
+ * impossible to construct: both dates must be valid, the aggregate version
29
+ * must be a non-negative integer, and `updatedAt` may not predate
30
+ * `createdAt`. Both dates are cloned on the way in so a later mutation of
31
+ * the caller's `Date` objects cannot reach into the entity's internal state.
32
+ *
33
+ * Declared `protected` because entities are reconstituted through a
34
+ * subclass factory, never instantiated directly by application code.
35
+ *
36
+ * @throws {InvariantViolationException} When `id` is absent, or when
37
+ * `updatedAt` is earlier than `createdAt`.
38
+ */
39
+ constructor(state) {
40
+ if (state.id == null) throw new InvariantViolationException("id is required: an entity cannot exist without an identity");
41
+ assertValidDate("createdAt", state.createdAt);
42
+ assertValidDate("updatedAt", state.updatedAt);
43
+ assertValidAggregateVersion(state.aggregateVersion);
44
+ if (state.updatedAt.getTime() < state.createdAt.getTime()) throw new InvariantViolationException("updatedAt cannot be earlier than createdAt");
45
+ this._id = state.id;
46
+ this._createdAt = cloneDate(state.createdAt);
47
+ this._updatedAt = cloneDate(state.updatedAt);
48
+ this._aggregateVersion = state.aggregateVersion;
49
+ }
50
+ /** The entity's stable identity — the basis for equality between entities. */
51
+ get id() {
52
+ return this._id;
53
+ }
54
+ /**
55
+ * When the entity was first created.
56
+ *
57
+ * Returns a clone so callers cannot mutate the entity's internal `Date`;
58
+ * the value is fixed at construction and never changes thereafter.
59
+ */
60
+ get createdAt() {
61
+ return cloneDate(this._createdAt);
62
+ }
63
+ /**
64
+ * When the entity was last modified.
65
+ *
66
+ * Returns a clone for the same encapsulation reason as {@link createdAt};
67
+ * the underlying value advances only through {@link markAsModified}.
68
+ */
69
+ get updatedAt() {
70
+ return cloneDate(this._updatedAt);
71
+ }
72
+ /**
73
+ * Monotonic counter incremented on every mutation, used for optimistic
74
+ * concurrency control: a writer reads this value, and the persistence layer
75
+ * rejects the write if the stored version has moved on in the meantime.
76
+ */
77
+ get aggregateVersion() {
78
+ return this._aggregateVersion;
79
+ }
80
+ /**
81
+ * The schema/contract version stamped on this entity type by the
82
+ * `@version` decorator — used to detect and migrate state persisted under
83
+ * an older shape.
84
+ */
85
+ get contractVersion() {
86
+ return this.constructor.CONTRACT_VERSION;
87
+ }
88
+ /**
89
+ * The single seam through which an entity records a mutation: it advances
90
+ * `updatedAt` and bumps {@link aggregateVersion} by one. Subclasses must
91
+ * call this from every state-changing method so the version counter stays
92
+ * an accurate optimistic-lock token.
93
+ *
94
+ * `updatedAt` is monotonic: each call must be at or after the current
95
+ * `updatedAt` (equal is fine — two mutations can share a millisecond).
96
+ * Without this, the version counter would advance while the timestamp
97
+ * walked backwards, corrupting any audit trail or ordering that reads
98
+ * `updatedAt`. A caller replaying out-of-order events must sort them first.
99
+ *
100
+ * @param updatedAt - The modification instant; defaults to now.
101
+ * @returns The new aggregate version after the increment.
102
+ * @throws {InvariantViolationException} When `updatedAt` is earlier than
103
+ * the current `updatedAt`.
104
+ */
105
+ markAsModified(updatedAt = /* @__PURE__ */ new Date()) {
106
+ assertValidDate("updatedAt", updatedAt);
107
+ if (updatedAt.getTime() < this._updatedAt.getTime()) throw new InvariantViolationException("updatedAt cannot move backwards: it must be at or after the current updatedAt");
108
+ this._updatedAt = cloneDate(updatedAt);
109
+ this._aggregateVersion += 1;
110
+ return this._aggregateVersion;
111
+ }
112
+ /**
113
+ * Identity-based equality — the semantics that define an entity. Two
114
+ * entities are equal when they are the same concrete class and their ids
115
+ * match, regardless of any other field.
116
+ *
117
+ * The class check keeps a `CustomerId`-bearing entity from equalling an
118
+ * unrelated entity that happens to reuse the same raw id. Value-object ids
119
+ * are compared through their own `equals`, so two independently
120
+ * reconstituted id instances still match; primitive ids use `===`.
121
+ */
122
+ equals(other) {
123
+ if (other == null) return false;
124
+ if (other === this) return true;
125
+ if (other.constructor !== this.constructor) return false;
126
+ const id = this._id;
127
+ if (id instanceof ValueObject) return id.equals(other._id);
128
+ return this._id === other._id;
129
+ }
130
+ };
131
+ Entity = __decorate([version("1.0")], Entity);
132
+ //#endregion
133
+ //#region src/core/domain/uuid-identifier.ts
9
134
  /**
10
135
  * Base class for UUID-backed identity value objects.
11
136
  *
@@ -47,14 +172,16 @@ var UuidIdentifier = class extends ValueObject {
47
172
  return this.value;
48
173
  }
49
174
  /**
50
- * Whether `candidate` is a canonical UUID (versions 1–5). The single source
51
- * of truth for the format; concrete identifiers call this from `create`.
175
+ * Whether `candidate` is a canonical UUID (versions 1–8, including the
176
+ * time-ordered v7; nil and max UUIDs are rejected as sentinels). The single
177
+ * source of truth for the format — see `shared/uuid.ts`; concrete
178
+ * identifiers call this from `create`.
52
179
  */
53
180
  static isValid(candidate) {
54
181
  return UUID_PATTERN.test(candidate);
55
182
  }
56
183
  };
57
184
  //#endregion
58
- export { UuidIdentifier as t };
185
+ export { Entity as n, UuidIdentifier as t };
59
186
 
60
187
  //# sourceMappingURL=uuid-identifier.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"uuid-identifier.js","names":[],"sources":["../src/core/domain/uuid-identifier.ts"],"sourcesContent":["import { ValueObject } from \"./value-object.js\";\n\n/**\n * Canonical RFC-4122 UUID (versions 1–5), matched case-insensitively. Declared\n * once here so the dozens of typed identifiers a domain accumulates never have\n * to re-state the pattern.\n */\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Base class for UUID-backed identity value objects.\n *\n * A domain typically has many of these — `OrderId`, `CustomerId`, `InvoiceId` —\n * all wrapping the same `string` shape. Two problems follow: the UUID format\n * check gets copy-pasted into every one, and because the wrapped shape is\n * identical, TypeScript's structural typing would happily accept an `OrderId`\n * where a `CustomerId` is expected. `UuidIdentifier` solves both: the format\n * lives here once ({@link isValid}), and the `TBrand` phantom tag makes each\n * subtype nominally distinct so the ids cannot be swapped for one another.\n *\n * It deliberately does not impose a factory. Identifiers vary in how they\n * report an invalid value (their own exception type, their own message), so a\n * concrete id adds a validating `create` that calls {@link isValid} plus a\n * `reconstitute` that trusts an already-persisted value:\n *\n * ```ts\n * class OrderId extends UuidIdentifier<\"OrderId\"> {\n * private constructor(value: string) { super(value); }\n * static create(raw: string): OrderId {\n * if (!UuidIdentifier.isValid(raw)) throw new InvalidOrderIdError(raw);\n * return new OrderId(raw);\n * }\n * static reconstitute(value: string): OrderId { return new OrderId(value); }\n * }\n * ```\n *\n * @typeParam TBrand - A unique string literal that nominally tags the subtype.\n */\nabstract class UuidIdentifier<\n TBrand extends string = string,\n> extends ValueObject<string, string> {\n /**\n * Phantom brand. Never assigned at runtime (`declare`), it exists only so\n * two identifiers with different brands are not interchangeable at the type\n * level despite sharing the same `string` value.\n */\n protected declare readonly __brand: TBrand;\n\n protected constructor(value: string) {\n super(value);\n }\n\n public toPrimitive(): string {\n return this.value;\n }\n\n /** The wrapped UUID string — convenient for logging and interpolation. */\n public toString(): string {\n return this.value;\n }\n\n /**\n * Whether `candidate` is a canonical UUID (versions 1–5). The single source\n * of truth for the format; concrete identifiers call this from `create`.\n */\n public static isValid(candidate: string): boolean {\n return UUID_PATTERN.test(candidate);\n }\n}\n\nexport { UuidIdentifier };\n"],"mappings":";;;;;;;AAOA,MAAM,eACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BJ,IAAe,iBAAf,cAEU,YAA4B;CAQlC,YAAsB,OAAe;EACjC,MAAM,KAAK;CACf;CAEA,cAA6B;EACzB,OAAO,KAAK;CAChB;;CAGA,WAA0B;EACtB,OAAO,KAAK;CAChB;;;;;CAMA,OAAc,QAAQ,WAA4B;EAC9C,OAAO,aAAa,KAAK,SAAS;CACtC;AACJ"}
1
+ {"version":3,"file":"uuid-identifier.js","names":[],"sources":["../src/core/domain/entity.ts","../src/core/domain/uuid-identifier.ts"],"sourcesContent":["import { InvariantViolationException } from \"../exceptions/invariant-violation-exception.js\";\nimport { assertValidAggregateVersion } from \"../shared/aggregate-version.js\";\nimport { assertValidDate, cloneDate } from \"../shared/temporal-guards.js\";\nimport { type ContractVersion, version } from \"../versioning/version.js\";\n\nimport { ValueObject } from \"./value-object.js\";\n\n/**\n * The minimal persisted shape needed to reconstitute an {@link Entity}.\n *\n * This is the contract between storage and the domain: a repository maps a row\n * (or document) onto these four fields and hands them to a subclass constructor.\n * `aggregateVersion` travels with the state so optimistic-concurrency checks\n * survive a round-trip through the database.\n */\ninterface EntityState<TIdentifier> {\n readonly id: TIdentifier;\n readonly createdAt: Date;\n readonly updatedAt: Date;\n readonly aggregateVersion: number;\n}\n\n/**\n * Base class for domain entities — objects defined by a stable identity rather\n * than by their attributes. Two entities are \"the same\" when their `id`\n * matches, even if every other field differs; this is the opposite of a\n * {@link ValueObject}, which is defined entirely by its contents.\n *\n * The class owns the bookkeeping common to every aggregate root: a creation\n * timestamp that never changes, a last-modified timestamp, and an\n * `aggregateVersion` counter used for optimistic concurrency control. All three\n * are validated on construction and the dates are defensively cloned, so an\n * entity can never be built into — or leak — an inconsistent temporal state.\n *\n * @typeParam TIdentifier - The identity type (e.g. a branded string id or a VO).\n */\n@version(\"1.0\")\nabstract class Entity<TIdentifier> {\n declare public static readonly CONTRACT_VERSION: ContractVersion;\n\n private readonly _id: TIdentifier;\n private readonly _createdAt: Date;\n private _updatedAt: Date;\n private _aggregateVersion: number;\n\n /**\n * Reconstitutes an entity from its persisted {@link EntityState}.\n *\n * Validates the temporal invariants up front so an invalid entity is\n * impossible to construct: both dates must be valid, the aggregate version\n * must be a non-negative integer, and `updatedAt` may not predate\n * `createdAt`. Both dates are cloned on the way in so a later mutation of\n * the caller's `Date` objects cannot reach into the entity's internal state.\n *\n * Declared `protected` because entities are reconstituted through a\n * subclass factory, never instantiated directly by application code.\n *\n * @throws {InvariantViolationException} When `id` is absent, or when\n * `updatedAt` is earlier than `createdAt`.\n */\n protected constructor(state: EntityState<TIdentifier>) {\n if (state.id == null) {\n throw new InvariantViolationException(\n \"id is required: an entity cannot exist without an identity\",\n );\n }\n\n assertValidDate(\"createdAt\", state.createdAt);\n assertValidDate(\"updatedAt\", state.updatedAt);\n assertValidAggregateVersion(state.aggregateVersion);\n\n if (state.updatedAt.getTime() < state.createdAt.getTime()) {\n throw new InvariantViolationException(\n \"updatedAt cannot be earlier than createdAt\",\n );\n }\n\n this._id = state.id;\n this._createdAt = cloneDate(state.createdAt);\n this._updatedAt = cloneDate(state.updatedAt);\n this._aggregateVersion = state.aggregateVersion;\n }\n\n /** The entity's stable identity — the basis for equality between entities. */\n public get id(): TIdentifier {\n return this._id;\n }\n\n /**\n * When the entity was first created.\n *\n * Returns a clone so callers cannot mutate the entity's internal `Date`;\n * the value is fixed at construction and never changes thereafter.\n */\n public get createdAt(): Date {\n return cloneDate(this._createdAt);\n }\n\n /**\n * When the entity was last modified.\n *\n * Returns a clone for the same encapsulation reason as {@link createdAt};\n * the underlying value advances only through {@link markAsModified}.\n */\n public get updatedAt(): Date {\n return cloneDate(this._updatedAt);\n }\n\n /**\n * Monotonic counter incremented on every mutation, used for optimistic\n * concurrency control: a writer reads this value, and the persistence layer\n * rejects the write if the stored version has moved on in the meantime.\n */\n public get aggregateVersion(): number {\n return this._aggregateVersion;\n }\n\n /**\n * The schema/contract version stamped on this entity type by the\n * `@version` decorator — used to detect and migrate state persisted under\n * an older shape.\n */\n public get contractVersion(): ContractVersion {\n return (this.constructor as typeof Entity).CONTRACT_VERSION;\n }\n\n /**\n * The single seam through which an entity records a mutation: it advances\n * `updatedAt` and bumps {@link aggregateVersion} by one. Subclasses must\n * call this from every state-changing method so the version counter stays\n * an accurate optimistic-lock token.\n *\n * `updatedAt` is monotonic: each call must be at or after the current\n * `updatedAt` (equal is fine — two mutations can share a millisecond).\n * Without this, the version counter would advance while the timestamp\n * walked backwards, corrupting any audit trail or ordering that reads\n * `updatedAt`. A caller replaying out-of-order events must sort them first.\n *\n * @param updatedAt - The modification instant; defaults to now.\n * @returns The new aggregate version after the increment.\n * @throws {InvariantViolationException} When `updatedAt` is earlier than\n * the current `updatedAt`.\n */\n protected markAsModified(updatedAt: Date = new Date()): number {\n assertValidDate(\"updatedAt\", updatedAt);\n\n if (updatedAt.getTime() < this._updatedAt.getTime()) {\n throw new InvariantViolationException(\n \"updatedAt cannot move backwards: it must be at or after the current updatedAt\",\n );\n }\n\n this._updatedAt = cloneDate(updatedAt);\n this._aggregateVersion += 1;\n\n return this._aggregateVersion;\n }\n\n /**\n * Identity-based equality — the semantics that define an entity. Two\n * entities are equal when they are the same concrete class and their ids\n * match, regardless of any other field.\n *\n * The class check keeps a `CustomerId`-bearing entity from equalling an\n * unrelated entity that happens to reuse the same raw id. Value-object ids\n * are compared through their own `equals`, so two independently\n * reconstituted id instances still match; primitive ids use `===`.\n */\n public equals(other: Entity<TIdentifier> | null | undefined): boolean {\n if (other == null) {\n return false;\n }\n if (other === this) {\n return true;\n }\n if (other.constructor !== this.constructor) {\n return false;\n }\n\n const id: unknown = this._id;\n if (id instanceof ValueObject) {\n return id.equals(other._id as typeof id);\n }\n\n return this._id === other._id;\n }\n}\n\nexport { Entity, type EntityState };\n","import { UUID_PATTERN } from \"../shared/uuid.js\";\nimport { ValueObject } from \"./value-object.js\";\n\n/**\n * Base class for UUID-backed identity value objects.\n *\n * A domain typically has many of these — `OrderId`, `CustomerId`, `InvoiceId` —\n * all wrapping the same `string` shape. Two problems follow: the UUID format\n * check gets copy-pasted into every one, and because the wrapped shape is\n * identical, TypeScript's structural typing would happily accept an `OrderId`\n * where a `CustomerId` is expected. `UuidIdentifier` solves both: the format\n * lives here once ({@link isValid}), and the `TBrand` phantom tag makes each\n * subtype nominally distinct so the ids cannot be swapped for one another.\n *\n * It deliberately does not impose a factory. Identifiers vary in how they\n * report an invalid value (their own exception type, their own message), so a\n * concrete id adds a validating `create` that calls {@link isValid} plus a\n * `reconstitute` that trusts an already-persisted value:\n *\n * ```ts\n * class OrderId extends UuidIdentifier<\"OrderId\"> {\n * private constructor(value: string) { super(value); }\n * static create(raw: string): OrderId {\n * if (!UuidIdentifier.isValid(raw)) throw new InvalidOrderIdError(raw);\n * return new OrderId(raw);\n * }\n * static reconstitute(value: string): OrderId { return new OrderId(value); }\n * }\n * ```\n *\n * @typeParam TBrand - A unique string literal that nominally tags the subtype.\n */\nabstract class UuidIdentifier<\n TBrand extends string = string,\n> extends ValueObject<string, string> {\n /**\n * Phantom brand. Never assigned at runtime (`declare`), it exists only so\n * two identifiers with different brands are not interchangeable at the type\n * level despite sharing the same `string` value.\n */\n declare protected readonly __brand: TBrand;\n\n protected constructor(value: string) {\n super(value);\n }\n\n public toPrimitive(): string {\n return this.value;\n }\n\n /** The wrapped UUID string — convenient for logging and interpolation. */\n public toString(): string {\n return this.value;\n }\n\n /**\n * Whether `candidate` is a canonical UUID (versions 1–8, including the\n * time-ordered v7; nil and max UUIDs are rejected as sentinels). The single\n * source of truth for the format — see `shared/uuid.ts`; concrete\n * identifiers call this from `create`.\n */\n public static isValid(candidate: string): boolean {\n return UUID_PATTERN.test(candidate);\n }\n}\n\nexport { UuidIdentifier };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAA,SAAA,MACe,OAAoB;;;;;;;;;;;;;;;;CAuB/B,YAAsB,OAAiC;EACnD,IAAI,MAAM,MAAM,MACZ,MAAM,IAAI,4BACN,4DACJ;EAGJ,gBAAgB,aAAa,MAAM,SAAS;EAC5C,gBAAgB,aAAa,MAAM,SAAS;EAC5C,4BAA4B,MAAM,gBAAgB;EAElD,IAAI,MAAM,UAAU,QAAQ,IAAI,MAAM,UAAU,QAAQ,GACpD,MAAM,IAAI,4BACN,4CACJ;EAGJ,KAAK,MAAM,MAAM;EACjB,KAAK,aAAa,UAAU,MAAM,SAAS;EAC3C,KAAK,aAAa,UAAU,MAAM,SAAS;EAC3C,KAAK,oBAAoB,MAAM;CACnC;;CAGA,IAAW,KAAkB;EACzB,OAAO,KAAK;CAChB;;;;;;;CAQA,IAAW,YAAkB;EACzB,OAAO,UAAU,KAAK,UAAU;CACpC;;;;;;;CAQA,IAAW,YAAkB;EACzB,OAAO,UAAU,KAAK,UAAU;CACpC;;;;;;CAOA,IAAW,mBAA2B;EAClC,OAAO,KAAK;CAChB;;;;;;CAOA,IAAW,kBAAmC;EAC1C,OAAQ,KAAK,YAA8B;CAC/C;;;;;;;;;;;;;;;;;;CAmBA,eAAyB,4BAAkB,IAAI,KAAK,GAAW;EAC3D,gBAAgB,aAAa,SAAS;EAEtC,IAAI,UAAU,QAAQ,IAAI,KAAK,WAAW,QAAQ,GAC9C,MAAM,IAAI,4BACN,+EACJ;EAGJ,KAAK,aAAa,UAAU,SAAS;EACrC,KAAK,qBAAqB;EAE1B,OAAO,KAAK;CAChB;;;;;;;;;;;CAYA,OAAc,OAAwD;EAClE,IAAI,SAAS,MACT,OAAO;EAEX,IAAI,UAAU,MACV,OAAO;EAEX,IAAI,MAAM,gBAAgB,KAAK,aAC3B,OAAO;EAGX,MAAM,KAAc,KAAK;EACzB,IAAI,cAAc,aACd,OAAO,GAAG,OAAO,MAAM,GAAgB;EAG3C,OAAO,KAAK,QAAQ,MAAM;CAC9B;AACJ;qBAtJC,QAAQ,KAAK,CAAA,GAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACJd,IAAe,iBAAf,cAEU,YAA4B;CAQlC,YAAsB,OAAe;EACjC,MAAM,KAAK;CACf;CAEA,cAA6B;EACzB,OAAO,KAAK;CAChB;;CAGA,WAA0B;EACtB,OAAO,KAAK;CAChB;;;;;;;CAQA,OAAc,QAAQ,WAA4B;EAC9C,OAAO,aAAa,KAAK,SAAS;CACtC;AACJ"}
package/dist/uuid.cjs ADDED
@@ -0,0 +1,23 @@
1
+ //#region src/core/shared/uuid.ts
2
+ /**
3
+ * Canonical RFC-4122 / RFC-9562 UUID, matched case-insensitively. Accepts
4
+ * versions 1–8 (variant 8/9/a/b) — notably UUIDv7, the time-ordered default for
5
+ * sortable primary keys in modern schemas. The nil (`0000…`) and max (`ffff…`)
6
+ * UUIDs are intentionally rejected: they are sentinels, not real identities, and
7
+ * a domain id should never carry one.
8
+ *
9
+ * The single source of truth for the UUID format — `UuidIdentifier` (domain ids)
10
+ * and `RequestedBy` (command actor) both validate against this pattern, so an
11
+ * identity accepted at the application boundary is always one the domain also
12
+ * considers valid.
13
+ */
14
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
15
+ //#endregion
16
+ Object.defineProperty(exports, "UUID_PATTERN", {
17
+ enumerable: true,
18
+ get: function() {
19
+ return UUID_PATTERN;
20
+ }
21
+ });
22
+
23
+ //# sourceMappingURL=uuid.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.cjs","names":[],"sources":["../src/core/shared/uuid.ts"],"sourcesContent":["/**\n * Canonical RFC-4122 / RFC-9562 UUID, matched case-insensitively. Accepts\n * versions 1–8 (variant 8/9/a/b) — notably UUIDv7, the time-ordered default for\n * sortable primary keys in modern schemas. The nil (`0000…`) and max (`ffff…`)\n * UUIDs are intentionally rejected: they are sentinels, not real identities, and\n * a domain id should never carry one.\n *\n * The single source of truth for the UUID format — `UuidIdentifier` (domain ids)\n * and `RequestedBy` (command actor) both validate against this pattern, so an\n * identity accepted at the application boundary is always one the domain also\n * considers valid.\n */\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nexport { UUID_PATTERN };\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,eACF"}
package/dist/uuid.js ADDED
@@ -0,0 +1,18 @@
1
+ //#region src/core/shared/uuid.ts
2
+ /**
3
+ * Canonical RFC-4122 / RFC-9562 UUID, matched case-insensitively. Accepts
4
+ * versions 1–8 (variant 8/9/a/b) — notably UUIDv7, the time-ordered default for
5
+ * sortable primary keys in modern schemas. The nil (`0000…`) and max (`ffff…`)
6
+ * UUIDs are intentionally rejected: they are sentinels, not real identities, and
7
+ * a domain id should never carry one.
8
+ *
9
+ * The single source of truth for the UUID format — `UuidIdentifier` (domain ids)
10
+ * and `RequestedBy` (command actor) both validate against this pattern, so an
11
+ * identity accepted at the application boundary is always one the domain also
12
+ * considers valid.
13
+ */
14
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
15
+ //#endregion
16
+ export { UUID_PATTERN as t };
17
+
18
+ //# sourceMappingURL=uuid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.js","names":[],"sources":["../src/core/shared/uuid.ts"],"sourcesContent":["/**\n * Canonical RFC-4122 / RFC-9562 UUID, matched case-insensitively. Accepts\n * versions 1–8 (variant 8/9/a/b) — notably UUIDv7, the time-ordered default for\n * sortable primary keys in modern schemas. The nil (`0000…`) and max (`ffff…`)\n * UUIDs are intentionally rejected: they are sentinels, not real identities, and\n * a domain id should never carry one.\n *\n * The single source of truth for the UUID format — `UuidIdentifier` (domain ids)\n * and `RequestedBy` (command actor) both validate against this pattern, so an\n * identity accepted at the application boundary is always one the domain also\n * considers valid.\n */\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nexport { UUID_PATTERN };\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,eACF"}
@@ -6,6 +6,15 @@ var ValidationCode = class ValidationCode {
6
6
  static of(value) {
7
7
  return new ValidationCode(value);
8
8
  }
9
+ toString() {
10
+ return this.value;
11
+ }
12
+ equals(other) {
13
+ return this.value === other.value;
14
+ }
15
+ toJSON() {
16
+ return this.value;
17
+ }
9
18
  static {
10
19
  this.BLANK = new ValidationCode("blank");
11
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validation-code.cjs","names":[],"sources":["../src/core/exceptions/validation-code.ts"],"sourcesContent":["// Object-oriented representation of validation codes (similar to Kotlin inline class).\n// Provides common reusable codes while allowing custom codes via `of`.\n\nclass ValidationCode {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom codes\n public static of(value: string): ValidationCode {\n return new ValidationCode(value);\n }\n\n // Common, domain-friendly codes\n public static readonly BLANK = new ValidationCode(\"blank\");\n public static readonly REQUIRED = new ValidationCode(\"required\");\n public static readonly INVALID_FORMAT = new ValidationCode(\n \"invalid_format\",\n );\n public static readonly INVALID_CHARACTERS = new ValidationCode(\n \"invalid_characters\",\n );\n public static readonly INVALID_LENGTH = new ValidationCode(\n \"invalid_length\",\n );\n public static readonly TOO_SHORT = new ValidationCode(\"too_short\");\n public static readonly TOO_LONG = new ValidationCode(\"too_long\");\n public static readonly OUT_OF_RANGE = new ValidationCode(\"out_of_range\");\n public static readonly INVALID_CHECKSUM = new ValidationCode(\n \"invalid_checksum\",\n );\n public static readonly INVALID_CHECKING_DIGIT = new ValidationCode(\n \"invalid_checking_digit\",\n );\n public static readonly ALREADY_EXISTS = new ValidationCode(\n \"already_exists\",\n );\n public static readonly NOT_FOUND = new ValidationCode(\"not_found\");\n public static readonly NOT_ALLOWED = new ValidationCode(\"not_allowed\");\n public static readonly UNEXPECTED = new ValidationCode(\"unexpected\");\n}\n\nexport { ValidationCode };\n"],"mappings":";AAGA,IAAM,iBAAN,MAAM,eAAe;CAGjB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAA+B;EAC5C,OAAO,IAAI,eAAe,KAAK;CACnC;;eAG+B,IAAI,eAAe,OAAO;;;kBACvB,IAAI,eAAe,UAAU;;;wBACvB,IAAI,eACxC,gBACJ;;;4BAC4C,IAAI,eAC5C,oBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;kBAC/B,IAAI,eAAe,UAAU;;;sBACzB,IAAI,eAAe,cAAc;;;0BAC7B,IAAI,eAC1C,kBACJ;;;gCACgD,IAAI,eAChD,wBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;qBAC5B,IAAI,eAAe,aAAa;;;oBACjC,IAAI,eAAe,YAAY;;AACvE"}
1
+ {"version":3,"file":"validation-code.cjs","names":[],"sources":["../src/core/exceptions/validation-code.ts"],"sourcesContent":["// Object-oriented representation of validation codes (similar to Kotlin inline class).\n// Provides common reusable codes while allowing custom codes via `of`.\n\nclass ValidationCode {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom codes\n public static of(value: string): ValidationCode {\n return new ValidationCode(value);\n }\n\n public toString(): string {\n return this.value;\n }\n\n public equals(other: ValidationCode): boolean {\n return this.value === other.value;\n }\n\n // Serialize to the bare code string, so JSON.stringify of a violation\n // yields \"blank\" rather than { \"value\": \"blank\" }. Mirrors ValidationField.\n public toJSON(): string {\n return this.value;\n }\n\n // Common, domain-friendly codes\n public static readonly BLANK = new ValidationCode(\"blank\");\n public static readonly REQUIRED = new ValidationCode(\"required\");\n public static readonly INVALID_FORMAT = new ValidationCode(\n \"invalid_format\",\n );\n public static readonly INVALID_CHARACTERS = new ValidationCode(\n \"invalid_characters\",\n );\n public static readonly INVALID_LENGTH = new ValidationCode(\n \"invalid_length\",\n );\n public static readonly TOO_SHORT = new ValidationCode(\"too_short\");\n public static readonly TOO_LONG = new ValidationCode(\"too_long\");\n public static readonly OUT_OF_RANGE = new ValidationCode(\"out_of_range\");\n public static readonly INVALID_CHECKSUM = new ValidationCode(\n \"invalid_checksum\",\n );\n public static readonly INVALID_CHECKING_DIGIT = new ValidationCode(\n \"invalid_checking_digit\",\n );\n public static readonly ALREADY_EXISTS = new ValidationCode(\n \"already_exists\",\n );\n public static readonly NOT_FOUND = new ValidationCode(\"not_found\");\n public static readonly NOT_ALLOWED = new ValidationCode(\"not_allowed\");\n public static readonly UNEXPECTED = new ValidationCode(\"unexpected\");\n}\n\nexport { ValidationCode };\n"],"mappings":";AAGA,IAAM,iBAAN,MAAM,eAAe;CAGjB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAA+B;EAC5C,OAAO,IAAI,eAAe,KAAK;CACnC;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAgC;EAC1C,OAAO,KAAK,UAAU,MAAM;CAChC;CAIA,SAAwB;EACpB,OAAO,KAAK;CAChB;;eAG+B,IAAI,eAAe,OAAO;;;kBACvB,IAAI,eAAe,UAAU;;;wBACvB,IAAI,eACxC,gBACJ;;;4BAC4C,IAAI,eAC5C,oBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;kBAC/B,IAAI,eAAe,UAAU;;;sBACzB,IAAI,eAAe,cAAc;;;0BAC7B,IAAI,eAC1C,kBACJ;;;gCACgD,IAAI,eAChD,wBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;qBAC5B,IAAI,eAAe,aAAa;;;oBACjC,IAAI,eAAe,YAAY;;AACvE"}
@@ -3,6 +3,9 @@ declare class ValidationCode {
3
3
  readonly value: string;
4
4
  private constructor();
5
5
  static of(value: string): ValidationCode;
6
+ toString(): string;
7
+ equals(other: ValidationCode): boolean;
8
+ toJSON(): string;
6
9
  static readonly BLANK: ValidationCode;
7
10
  static readonly REQUIRED: ValidationCode;
8
11
  static readonly INVALID_FORMAT: ValidationCode;
@@ -3,6 +3,9 @@ declare class ValidationCode {
3
3
  readonly value: string;
4
4
  private constructor();
5
5
  static of(value: string): ValidationCode;
6
+ toString(): string;
7
+ equals(other: ValidationCode): boolean;
8
+ toJSON(): string;
6
9
  static readonly BLANK: ValidationCode;
7
10
  static readonly REQUIRED: ValidationCode;
8
11
  static readonly INVALID_FORMAT: ValidationCode;
@@ -6,6 +6,15 @@ var ValidationCode = class ValidationCode {
6
6
  static of(value) {
7
7
  return new ValidationCode(value);
8
8
  }
9
+ toString() {
10
+ return this.value;
11
+ }
12
+ equals(other) {
13
+ return this.value === other.value;
14
+ }
15
+ toJSON() {
16
+ return this.value;
17
+ }
9
18
  static {
10
19
  this.BLANK = new ValidationCode("blank");
11
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validation-code.js","names":[],"sources":["../src/core/exceptions/validation-code.ts"],"sourcesContent":["// Object-oriented representation of validation codes (similar to Kotlin inline class).\n// Provides common reusable codes while allowing custom codes via `of`.\n\nclass ValidationCode {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom codes\n public static of(value: string): ValidationCode {\n return new ValidationCode(value);\n }\n\n // Common, domain-friendly codes\n public static readonly BLANK = new ValidationCode(\"blank\");\n public static readonly REQUIRED = new ValidationCode(\"required\");\n public static readonly INVALID_FORMAT = new ValidationCode(\n \"invalid_format\",\n );\n public static readonly INVALID_CHARACTERS = new ValidationCode(\n \"invalid_characters\",\n );\n public static readonly INVALID_LENGTH = new ValidationCode(\n \"invalid_length\",\n );\n public static readonly TOO_SHORT = new ValidationCode(\"too_short\");\n public static readonly TOO_LONG = new ValidationCode(\"too_long\");\n public static readonly OUT_OF_RANGE = new ValidationCode(\"out_of_range\");\n public static readonly INVALID_CHECKSUM = new ValidationCode(\n \"invalid_checksum\",\n );\n public static readonly INVALID_CHECKING_DIGIT = new ValidationCode(\n \"invalid_checking_digit\",\n );\n public static readonly ALREADY_EXISTS = new ValidationCode(\n \"already_exists\",\n );\n public static readonly NOT_FOUND = new ValidationCode(\"not_found\");\n public static readonly NOT_ALLOWED = new ValidationCode(\"not_allowed\");\n public static readonly UNEXPECTED = new ValidationCode(\"unexpected\");\n}\n\nexport { ValidationCode };\n"],"mappings":";AAGA,IAAM,iBAAN,MAAM,eAAe;CAGjB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAA+B;EAC5C,OAAO,IAAI,eAAe,KAAK;CACnC;;eAG+B,IAAI,eAAe,OAAO;;;kBACvB,IAAI,eAAe,UAAU;;;wBACvB,IAAI,eACxC,gBACJ;;;4BAC4C,IAAI,eAC5C,oBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;kBAC/B,IAAI,eAAe,UAAU;;;sBACzB,IAAI,eAAe,cAAc;;;0BAC7B,IAAI,eAC1C,kBACJ;;;gCACgD,IAAI,eAChD,wBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;qBAC5B,IAAI,eAAe,aAAa;;;oBACjC,IAAI,eAAe,YAAY;;AACvE"}
1
+ {"version":3,"file":"validation-code.js","names":[],"sources":["../src/core/exceptions/validation-code.ts"],"sourcesContent":["// Object-oriented representation of validation codes (similar to Kotlin inline class).\n// Provides common reusable codes while allowing custom codes via `of`.\n\nclass ValidationCode {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom codes\n public static of(value: string): ValidationCode {\n return new ValidationCode(value);\n }\n\n public toString(): string {\n return this.value;\n }\n\n public equals(other: ValidationCode): boolean {\n return this.value === other.value;\n }\n\n // Serialize to the bare code string, so JSON.stringify of a violation\n // yields \"blank\" rather than { \"value\": \"blank\" }. Mirrors ValidationField.\n public toJSON(): string {\n return this.value;\n }\n\n // Common, domain-friendly codes\n public static readonly BLANK = new ValidationCode(\"blank\");\n public static readonly REQUIRED = new ValidationCode(\"required\");\n public static readonly INVALID_FORMAT = new ValidationCode(\n \"invalid_format\",\n );\n public static readonly INVALID_CHARACTERS = new ValidationCode(\n \"invalid_characters\",\n );\n public static readonly INVALID_LENGTH = new ValidationCode(\n \"invalid_length\",\n );\n public static readonly TOO_SHORT = new ValidationCode(\"too_short\");\n public static readonly TOO_LONG = new ValidationCode(\"too_long\");\n public static readonly OUT_OF_RANGE = new ValidationCode(\"out_of_range\");\n public static readonly INVALID_CHECKSUM = new ValidationCode(\n \"invalid_checksum\",\n );\n public static readonly INVALID_CHECKING_DIGIT = new ValidationCode(\n \"invalid_checking_digit\",\n );\n public static readonly ALREADY_EXISTS = new ValidationCode(\n \"already_exists\",\n );\n public static readonly NOT_FOUND = new ValidationCode(\"not_found\");\n public static readonly NOT_ALLOWED = new ValidationCode(\"not_allowed\");\n public static readonly UNEXPECTED = new ValidationCode(\"unexpected\");\n}\n\nexport { ValidationCode };\n"],"mappings":";AAGA,IAAM,iBAAN,MAAM,eAAe;CAGjB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAA+B;EAC5C,OAAO,IAAI,eAAe,KAAK;CACnC;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAgC;EAC1C,OAAO,KAAK,UAAU,MAAM;CAChC;CAIA,SAAwB;EACpB,OAAO,KAAK;CAChB;;eAG+B,IAAI,eAAe,OAAO;;;kBACvB,IAAI,eAAe,UAAU;;;wBACvB,IAAI,eACxC,gBACJ;;;4BAC4C,IAAI,eAC5C,oBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;kBAC/B,IAAI,eAAe,UAAU;;;sBACzB,IAAI,eAAe,cAAc;;;0BAC7B,IAAI,eAC1C,kBACJ;;;gCACgD,IAAI,eAChD,wBACJ;;;wBACwC,IAAI,eACxC,gBACJ;;;mBACmC,IAAI,eAAe,WAAW;;;qBAC5B,IAAI,eAAe,aAAa;;;oBACjC,IAAI,eAAe,YAAY;;AACvE"}
@@ -1,4 +1,6 @@
1
1
  const require_app_error = require("./app-error.cjs");
2
+ const require_authorization_error = require("./authorization-error.cjs");
3
+ const require_hashing = require("./hashing.cjs");
2
4
  //#region src/core/errors/authentication-error.ts
3
5
  var AuthenticationError = class AuthenticationError extends require_app_error.AppError {
4
6
  constructor(params) {
@@ -24,8 +26,8 @@ var AuthenticationError = class AuthenticationError extends require_app_error.Ap
24
26
  message: "Missing credentials",
25
27
  code: require_app_error.ErrorCodes.authentication.missingToken,
26
28
  reason: "missing_token",
27
- metadata: compactMetadata$1(options),
28
- ...extractAppErrorOptions(options)
29
+ metadata: require_authorization_error.compactMetadata(options),
30
+ ...require_authorization_error.pickAppErrorOptions(options)
29
31
  });
30
32
  }
31
33
  static invalidToken(options) {
@@ -33,14 +35,14 @@ var AuthenticationError = class AuthenticationError extends require_app_error.Ap
33
35
  message: "Invalid credentials",
34
36
  code: require_app_error.ErrorCodes.authentication.invalidToken,
35
37
  reason: "invalid_token",
36
- metadata: compactMetadata$1({
38
+ metadata: require_authorization_error.compactMetadata({
37
39
  authScheme: options?.authScheme,
38
40
  tokenLocation: options?.tokenLocation,
39
41
  details: options?.details,
40
42
  correlationId: options?.correlationId,
41
43
  requestId: options?.requestId
42
44
  }),
43
- ...extractAppErrorOptions(options)
45
+ ...require_authorization_error.pickAppErrorOptions(options)
44
46
  });
45
47
  }
46
48
  static expiredToken(options) {
@@ -48,8 +50,8 @@ var AuthenticationError = class AuthenticationError extends require_app_error.Ap
48
50
  message: "Expired credentials",
49
51
  code: require_app_error.ErrorCodes.authentication.expiredToken,
50
52
  reason: "expired_token",
51
- metadata: compactMetadata$1(options),
52
- ...extractAppErrorOptions(options)
53
+ metadata: require_authorization_error.compactMetadata(options),
54
+ ...require_authorization_error.pickAppErrorOptions(options)
53
55
  });
54
56
  }
55
57
  static invalidCredentials(options) {
@@ -57,27 +59,11 @@ var AuthenticationError = class AuthenticationError extends require_app_error.Ap
57
59
  message: "Invalid username or password",
58
60
  code: require_app_error.ErrorCodes.authentication.invalidCredentials,
59
61
  reason: "invalid_credentials",
60
- metadata: compactMetadata$1(options),
61
- ...extractAppErrorOptions(options)
62
+ metadata: require_authorization_error.compactMetadata(options),
63
+ ...require_authorization_error.pickAppErrorOptions(options)
62
64
  });
63
65
  }
64
66
  };
65
- function extractAppErrorOptions(options) {
66
- return {
67
- cause: options?.cause,
68
- type: options?.type,
69
- severity: options?.severity,
70
- correlationId: options?.correlationId,
71
- requestId: options?.requestId,
72
- commandId: options?.commandId,
73
- createdAtIso: options?.createdAtIso,
74
- publicMessage: options?.publicMessage
75
- };
76
- }
77
- function compactMetadata$1(input) {
78
- if (!input) return void 0;
79
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
80
- }
81
67
  //#endregion
82
68
  //#region src/core/errors/conflict-error.ts
83
69
  /**
@@ -408,9 +394,10 @@ var IdempotencyReplayNotSupportedError = class IdempotencyReplayNotSupportedErro
408
394
  }
409
395
  };
410
396
  function resolveKeyIdentity(key, keyHash, keyPreview) {
397
+ const preview = keyPreview ?? (key ? key.length <= 8 ? key : key.slice(0, 8) : void 0);
411
398
  return {
412
- keyHash,
413
- keyPreview: keyPreview ?? (key ? key.length <= 8 ? key : key.slice(0, 8) : void 0)
399
+ keyHash: keyHash ?? (key ? require_hashing.sha256Hex(key) : void 0),
400
+ keyPreview: preview
414
401
  };
415
402
  }
416
403
  //#endregion
@@ -450,7 +437,7 @@ var IntegrationError = class IntegrationError extends require_app_error.AppError
450
437
  reason: "timeout",
451
438
  ...options
452
439
  }),
453
- ...pickAppErrorFields(options)
440
+ ...require_authorization_error.pickAppErrorOptions(options)
454
441
  });
455
442
  }
456
443
  /**
@@ -466,7 +453,7 @@ var IntegrationError = class IntegrationError extends require_app_error.AppError
466
453
  reason: "unreachable",
467
454
  ...options
468
455
  }),
469
- ...pickAppErrorFields(options)
456
+ ...require_authorization_error.pickAppErrorOptions(options)
470
457
  });
471
458
  }
472
459
  /**
@@ -483,12 +470,12 @@ var IntegrationError = class IntegrationError extends require_app_error.AppError
483
470
  reason: "bad_response",
484
471
  ...options
485
472
  }),
486
- ...pickAppErrorFields(options)
473
+ ...require_authorization_error.pickAppErrorOptions(options)
487
474
  });
488
475
  }
489
476
  };
490
477
  function buildMetadata(input) {
491
- return compactMetadata({
478
+ return require_authorization_error.compactMetadata({
492
479
  reason: input.reason,
493
480
  provider: input.provider,
494
481
  operation: input.operation,
@@ -503,21 +490,6 @@ function buildMetadata(input) {
503
490
  responseCode: input.responseCode
504
491
  });
505
492
  }
506
- function pickAppErrorFields(options) {
507
- return {
508
- cause: options.cause,
509
- type: options.type,
510
- severity: options.severity,
511
- correlationId: options.correlationId,
512
- requestId: options.requestId,
513
- commandId: options.commandId,
514
- createdAtIso: options.createdAtIso,
515
- publicMessage: options.publicMessage
516
- };
517
- }
518
- function compactMetadata(input) {
519
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
520
- }
521
493
  //#endregion
522
494
  //#region src/core/errors/legacy-incompatible-error.ts
523
495
  var LegacyIncompatibleError = class extends require_app_error.AppError {
@@ -530,6 +502,7 @@ var LegacyIncompatibleError = class extends require_app_error.AppError {
530
502
  ...options,
531
503
  metadata: mergedMetadata
532
504
  });
505
+ Object.freeze(this);
533
506
  }
534
507
  };
535
508
  //#endregion
@@ -644,17 +617,10 @@ var ExpiredError = class ExpiredError extends TemporalError {
644
617
  code: require_app_error.ErrorCodes.temporal.expired,
645
618
  kind: "expired",
646
619
  metadata: {
647
- ...input,
620
+ ...require_authorization_error.stripAppErrorOptions(input),
648
621
  hint: input.hint ?? "The window has expired. Request a new link or try again within the valid period."
649
622
  },
650
- cause: input.cause,
651
- type: input.type,
652
- severity: input.severity,
653
- correlationId: input.correlationId,
654
- requestId: input.requestId,
655
- commandId: input.commandId,
656
- createdAtIso: input.createdAtIso,
657
- publicMessage: input.publicMessage
623
+ ...require_authorization_error.pickAppErrorOptions(input)
658
624
  });
659
625
  }
660
626
  };
@@ -668,31 +634,39 @@ var NotYetValidError = class NotYetValidError extends TemporalError {
668
634
  code: require_app_error.ErrorCodes.temporal.notYetValid,
669
635
  kind: "not_yet_valid",
670
636
  metadata: {
671
- ...input,
637
+ ...require_authorization_error.stripAppErrorOptions(input),
672
638
  hint: input.hint ?? "Not yet within the valid period. Try again later."
673
639
  },
674
- cause: input.cause,
675
- type: input.type,
676
- severity: input.severity,
677
- correlationId: input.correlationId,
678
- requestId: input.requestId,
679
- commandId: input.commandId,
680
- createdAtIso: input.createdAtIso,
681
- publicMessage: input.publicMessage
640
+ ...require_authorization_error.pickAppErrorOptions(input)
682
641
  });
683
642
  }
684
643
  };
644
+ /**
645
+ * Evaluates whether `evaluatedAtIso` falls inside the [`validFromIso`,
646
+ * `validUntilIso`] window.
647
+ *
648
+ * Fails **closed**: a boundary that is present but unparseable is treated as if
649
+ * the window were closed on that side (a bad `validUntilIso` → expired, a bad
650
+ * `validFromIso` → not-yet-valid), never silently ignored — these checks guard
651
+ * access (invites, links, tokens), so a typo in an expiry must not disable
652
+ * expiry. A `null`/absent boundary means "no bound on that side", which is
653
+ * distinct from a malformed one. Returns `null` only when `evaluatedAtIso`
654
+ * itself is unparseable (nothing can be decided).
655
+ *
656
+ * For an inverted window (`validFrom` > `validUntil`) `expired` wins, since the
657
+ * upper-bound check short-circuits.
658
+ */
685
659
  function evaluateTemporalWindow(input) {
686
660
  const evaluatedMs = Date.parse(input.evaluatedAtIso);
687
661
  if (Number.isNaN(evaluatedMs)) return null;
688
662
  let notYetValid = false;
689
663
  if (input.validFromIso) {
690
664
  const fromMs = Date.parse(input.validFromIso);
691
- if (!Number.isNaN(fromMs) && evaluatedMs < fromMs) notYetValid = true;
665
+ if (Number.isNaN(fromMs) || evaluatedMs < fromMs) notYetValid = true;
692
666
  }
693
667
  if (input.validUntilIso) {
694
668
  const untilMs = Date.parse(input.validUntilIso);
695
- if (!Number.isNaN(untilMs) && evaluatedMs > untilMs) return {
669
+ if (Number.isNaN(untilMs) || evaluatedMs > untilMs) return {
696
670
  expired: true,
697
671
  notYetValid: false
698
672
  };
@@ -729,13 +703,14 @@ var ValidationError = class extends require_app_error.AppError {
729
703
  validationCode: code.value
730
704
  };
731
705
  const mergedMetadata = options?.metadata ? {
732
- ...baseMetadata,
733
- ...options.metadata
706
+ ...options.metadata,
707
+ ...baseMetadata
734
708
  } : baseMetadata;
735
709
  super(message, require_app_error.ErrorCodes.validation, {
736
710
  ...options,
737
711
  metadata: mergedMetadata
738
712
  });
713
+ Object.freeze(this);
739
714
  }
740
715
  };
741
716
  //#endregion