@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
@@ -1,30 +1,45 @@
1
1
  import { t as ValidationCode } from "./validation-code.cjs";
2
2
  import { t as ValidationField } from "./validation-field.cjs";
3
- import { t as DomainException } from "./domain-exception.cjs";
4
3
 
4
+ //#region src/core/exceptions/domain-exception.d.ts
5
+ declare abstract class DomainException extends Error {
6
+ readonly cause?: unknown;
7
+ constructor(message: string, options?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ //#endregion
5
12
  //#region src/core/exceptions/business-rule-violation-exception.d.ts
6
13
  declare class BusinessRuleViolationException extends DomainException {
7
14
  readonly rule: string;
8
- constructor(rule: string, message: string);
15
+ constructor(rule: string, message: string, options?: {
16
+ cause?: unknown;
17
+ });
9
18
  }
10
19
  //#endregion
11
20
  //#region src/core/exceptions/entity-not-found-exception.d.ts
12
21
  declare class EntityNotFoundException extends DomainException {
13
22
  readonly entityName: string;
14
23
  readonly identifier: string;
15
- constructor(entityName: string, identifier: string);
24
+ constructor(entityName: string, identifier: string, options?: {
25
+ cause?: unknown;
26
+ });
16
27
  }
17
28
  //#endregion
18
29
  //#region src/core/exceptions/invalid-state-transition-exception.d.ts
19
30
  declare class InvalidStateTransitionException<TState extends string = string> extends DomainException {
20
31
  readonly from: TState;
21
32
  readonly to: TState;
22
- constructor(from: TState, to: TState, message: string);
33
+ constructor(from: TState, to: TState, message?: string, options?: {
34
+ cause?: unknown;
35
+ });
23
36
  }
24
37
  //#endregion
25
38
  //#region src/core/exceptions/invariant-violation-exception.d.ts
26
39
  declare class InvariantViolationException extends DomainException {
27
- constructor(message: string);
40
+ constructor(message: string, options?: {
41
+ cause?: unknown;
42
+ });
28
43
  }
29
44
  //#endregion
30
45
  //#region src/core/exceptions/validation-exception.d.ts
@@ -36,15 +51,24 @@ interface ValidationViolation {
36
51
  declare class ValidationException extends DomainException {
37
52
  readonly field: ValidationField;
38
53
  readonly code: ValidationCode;
39
- constructor(field: ValidationField, code: ValidationCode, message: string);
54
+ constructor(field: ValidationField, code: ValidationCode, message: string, options?: {
55
+ cause?: unknown;
56
+ });
40
57
  }
41
58
  declare class MultipleValidationException extends DomainException {
42
59
  readonly violations: readonly ValidationViolation[];
43
- constructor(violations: readonly ValidationViolation[]);
60
+ constructor(violations: readonly ValidationViolation[], options?: {
61
+ cause?: unknown;
62
+ });
63
+ get count(): number;
64
+ get first(): ValidationViolation;
65
+ byField(field: ValidationField): readonly ValidationViolation[];
44
66
  }
45
67
  declare class InvalidValueException extends ValidationException {
46
- constructor(field: ValidationField, code: ValidationCode, message: string);
68
+ constructor(field: ValidationField, code: ValidationCode, message: string, options?: {
69
+ cause?: unknown;
70
+ });
47
71
  }
48
72
  //#endregion
49
- export { InvariantViolationException as a, BusinessRuleViolationException as c, ValidationViolation as i, MultipleValidationException as n, InvalidStateTransitionException as o, ValidationException as r, EntityNotFoundException as s, InvalidValueException as t };
73
+ export { InvariantViolationException as a, BusinessRuleViolationException as c, ValidationViolation as i, DomainException as l, MultipleValidationException as n, InvalidStateTransitionException as o, ValidationException as r, EntityNotFoundException as s, InvalidValueException as t };
50
74
  //# sourceMappingURL=validation-exception.d.cts.map
@@ -1,30 +1,45 @@
1
1
  import { t as ValidationCode } from "./validation-code.js";
2
2
  import { t as ValidationField } from "./validation-field.js";
3
- import { t as DomainException } from "./domain-exception.js";
4
3
 
4
+ //#region src/core/exceptions/domain-exception.d.ts
5
+ declare abstract class DomainException extends Error {
6
+ readonly cause?: unknown;
7
+ constructor(message: string, options?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ //#endregion
5
12
  //#region src/core/exceptions/business-rule-violation-exception.d.ts
6
13
  declare class BusinessRuleViolationException extends DomainException {
7
14
  readonly rule: string;
8
- constructor(rule: string, message: string);
15
+ constructor(rule: string, message: string, options?: {
16
+ cause?: unknown;
17
+ });
9
18
  }
10
19
  //#endregion
11
20
  //#region src/core/exceptions/entity-not-found-exception.d.ts
12
21
  declare class EntityNotFoundException extends DomainException {
13
22
  readonly entityName: string;
14
23
  readonly identifier: string;
15
- constructor(entityName: string, identifier: string);
24
+ constructor(entityName: string, identifier: string, options?: {
25
+ cause?: unknown;
26
+ });
16
27
  }
17
28
  //#endregion
18
29
  //#region src/core/exceptions/invalid-state-transition-exception.d.ts
19
30
  declare class InvalidStateTransitionException<TState extends string = string> extends DomainException {
20
31
  readonly from: TState;
21
32
  readonly to: TState;
22
- constructor(from: TState, to: TState, message: string);
33
+ constructor(from: TState, to: TState, message?: string, options?: {
34
+ cause?: unknown;
35
+ });
23
36
  }
24
37
  //#endregion
25
38
  //#region src/core/exceptions/invariant-violation-exception.d.ts
26
39
  declare class InvariantViolationException extends DomainException {
27
- constructor(message: string);
40
+ constructor(message: string, options?: {
41
+ cause?: unknown;
42
+ });
28
43
  }
29
44
  //#endregion
30
45
  //#region src/core/exceptions/validation-exception.d.ts
@@ -36,15 +51,24 @@ interface ValidationViolation {
36
51
  declare class ValidationException extends DomainException {
37
52
  readonly field: ValidationField;
38
53
  readonly code: ValidationCode;
39
- constructor(field: ValidationField, code: ValidationCode, message: string);
54
+ constructor(field: ValidationField, code: ValidationCode, message: string, options?: {
55
+ cause?: unknown;
56
+ });
40
57
  }
41
58
  declare class MultipleValidationException extends DomainException {
42
59
  readonly violations: readonly ValidationViolation[];
43
- constructor(violations: readonly ValidationViolation[]);
60
+ constructor(violations: readonly ValidationViolation[], options?: {
61
+ cause?: unknown;
62
+ });
63
+ get count(): number;
64
+ get first(): ValidationViolation;
65
+ byField(field: ValidationField): readonly ValidationViolation[];
44
66
  }
45
67
  declare class InvalidValueException extends ValidationException {
46
- constructor(field: ValidationField, code: ValidationCode, message: string);
68
+ constructor(field: ValidationField, code: ValidationCode, message: string, options?: {
69
+ cause?: unknown;
70
+ });
47
71
  }
48
72
  //#endregion
49
- export { InvariantViolationException as a, BusinessRuleViolationException as c, ValidationViolation as i, MultipleValidationException as n, InvalidStateTransitionException as o, ValidationException as r, EntityNotFoundException as s, InvalidValueException as t };
73
+ export { InvariantViolationException as a, BusinessRuleViolationException as c, ValidationViolation as i, DomainException as l, MultipleValidationException as n, InvalidStateTransitionException as o, ValidationException as r, EntityNotFoundException as s, InvalidValueException as t };
50
74
  //# sourceMappingURL=validation-exception.d.ts.map
@@ -1,21 +1,32 @@
1
1
  import { t as DomainException } from "./domain-exception.js";
2
+ import { t as InvariantViolationException } from "./invariant-violation-exception.js";
2
3
  //#region src/core/exceptions/validation-exception.ts
3
4
  var ValidationException = class extends DomainException {
4
- constructor(field, code, message) {
5
- super(message);
5
+ constructor(field, code, message, options) {
6
+ super(message, options);
6
7
  this.field = field;
7
8
  this.code = code;
8
9
  }
9
10
  };
10
11
  var MultipleValidationException = class extends DomainException {
11
- constructor(violations) {
12
- super(violations.map((v) => v.message).join("; "));
12
+ constructor(violations, options) {
13
+ if (violations.length === 0) throw new InvariantViolationException("MultipleValidationException requires at least one violation.");
14
+ super(violations.map((v) => v.message).join("; "), options);
13
15
  this.violations = violations;
14
16
  }
17
+ get count() {
18
+ return this.violations.length;
19
+ }
20
+ get first() {
21
+ return this.violations[0];
22
+ }
23
+ byField(field) {
24
+ return this.violations.filter((v) => v.field.equals(field));
25
+ }
15
26
  };
16
27
  var InvalidValueException = class extends ValidationException {
17
- constructor(field, code, message) {
18
- super(field, code, message);
28
+ constructor(field, code, message, options) {
29
+ super(field, code, message, options);
19
30
  }
20
31
  };
21
32
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"validation-exception.js","names":[],"sources":["../src/core/exceptions/validation-exception.ts"],"sourcesContent":["import { DomainException } from \"./domain-exception.js\";\nimport { ValidationCode } from \"./validation-code.js\";\nimport { ValidationField } from \"./validation-field.js\";\n\nexport interface ValidationViolation {\n readonly field: ValidationField;\n readonly code: ValidationCode;\n readonly message: string;\n}\n\nclass ValidationException extends DomainException {\n constructor(\n public readonly field: ValidationField,\n public readonly code: ValidationCode,\n message: string,\n ) {\n super(message);\n }\n}\n\n// Carries multiple validation violations from a single operation (e.g. bulk validation).\n// Use when you need to report all field errors at once rather than failing on the first.\nclass MultipleValidationException extends DomainException {\n constructor(public readonly violations: readonly ValidationViolation[]) {\n super(violations.map((v) => v.message).join(\"; \"));\n }\n}\n\nclass InvalidValueException extends ValidationException {\n constructor(field: ValidationField, code: ValidationCode, message: string) {\n super(field, code, message);\n }\n}\n\nexport {\n InvalidValueException,\n MultipleValidationException,\n ValidationException,\n};\n"],"mappings":";;AAUA,IAAM,sBAAN,cAAkC,gBAAgB;CAC9C,YACI,OACA,MACA,SACF;EACE,MAAM,OAAO;EAJG,KAAA,QAAA;EACA,KAAA,OAAA;CAIpB;AACJ;AAIA,IAAM,8BAAN,cAA0C,gBAAgB;CACtD,YAAY,YAA4D;EACpE,MAAM,WAAW,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;EADzB,KAAA,aAAA;CAE5B;AACJ;AAEA,IAAM,wBAAN,cAAoC,oBAAoB;CACpD,YAAY,OAAwB,MAAsB,SAAiB;EACvE,MAAM,OAAO,MAAM,OAAO;CAC9B;AACJ"}
1
+ {"version":3,"file":"validation-exception.js","names":[],"sources":["../src/core/exceptions/validation-exception.ts"],"sourcesContent":["import { DomainException } from \"./domain-exception.js\";\nimport { InvariantViolationException } from \"./invariant-violation-exception.js\";\nimport { ValidationCode } from \"./validation-code.js\";\nimport { ValidationField } from \"./validation-field.js\";\n\nexport interface ValidationViolation {\n readonly field: ValidationField;\n readonly code: ValidationCode;\n readonly message: string;\n}\n\n// Extensible base for a single-field validation failure. Prefer throwing the\n// concrete InvalidValueException below so callers can `instanceof` the common\n// case; extend this directly only when you need a distinct validation subtype.\nclass ValidationException extends DomainException {\n constructor(\n public readonly field: ValidationField,\n public readonly code: ValidationCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\n// Carries multiple validation violations from a single operation (e.g. bulk validation).\n// Use when you need to report all field errors at once rather than failing on the first.\nclass MultipleValidationException extends DomainException {\n constructor(\n public readonly violations: readonly ValidationViolation[],\n options?: { cause?: unknown },\n ) {\n // An aggregate with no violations is a caller bug: it produces a\n // messageless, contentless exception. Fail fast as a broken invariant.\n if (violations.length === 0) {\n throw new InvariantViolationException(\n \"MultipleValidationException requires at least one violation.\",\n );\n }\n super(violations.map((v) => v.message).join(\"; \"), options);\n }\n\n // Number of accumulated violations (always >= 1).\n get count(): number {\n return this.violations.length;\n }\n\n // The first accumulated violation.\n get first(): ValidationViolation {\n return this.violations[0];\n }\n\n // Violations targeting the given field.\n byField(field: ValidationField): readonly ValidationViolation[] {\n return this.violations.filter((v) => v.field.equals(field));\n }\n}\n\n// The canonical concrete validation exception, thrown across the domain\n// (rbac/abac/policies/commands) when a single input value is invalid. Its\n// identity is exactly that: the named, catchable \"a value was invalid\" case,\n// versus ValidationException as the open base.\nclass InvalidValueException extends ValidationException {\n constructor(\n field: ValidationField,\n code: ValidationCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n super(field, code, message, options);\n }\n}\n\nexport {\n InvalidValueException,\n MultipleValidationException,\n ValidationException,\n};\n"],"mappings":";;;AAcA,IAAM,sBAAN,cAAkC,gBAAgB;CAC9C,YACI,OACA,MACA,SACA,SACF;EACE,MAAM,SAAS,OAAO;EALN,KAAA,QAAA;EACA,KAAA,OAAA;CAKpB;AACJ;AAIA,IAAM,8BAAN,cAA0C,gBAAgB;CACtD,YACI,YACA,SACF;EAGE,IAAI,WAAW,WAAW,GACtB,MAAM,IAAI,4BACN,8DACJ;EAEJ,MAAM,WAAW,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO;EAV1C,KAAA,aAAA;CAWpB;CAGA,IAAI,QAAgB;EAChB,OAAO,KAAK,WAAW;CAC3B;CAGA,IAAI,QAA6B;EAC7B,OAAO,KAAK,WAAW;CAC3B;CAGA,QAAQ,OAAwD;EAC5D,OAAO,KAAK,WAAW,QAAQ,MAAM,EAAE,MAAM,OAAO,KAAK,CAAC;CAC9D;AACJ;AAMA,IAAM,wBAAN,cAAoC,oBAAoB;CACpD,YACI,OACA,MACA,SACA,SACF;EACE,MAAM,OAAO,MAAM,SAAS,OAAO;CACvC;AACJ"}
@@ -13,6 +13,9 @@ var ValidationField = class ValidationField {
13
13
  toString() {
14
14
  return this.value;
15
15
  }
16
+ toJSON() {
17
+ return this.value;
18
+ }
16
19
  equals(other) {
17
20
  return this.value === other.value;
18
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validation-field.cjs","names":[],"sources":["../src/core/exceptions/validation-field.ts"],"sourcesContent":["// Object-oriented representation of validation fields.\n// Indicates WHERE a validation error occurred.\n// This is a minimal base class — field catalogs are defined per domain/module.\n\nclass ValidationField {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom or domain-specific fields\n public static of(value: string): ValidationField {\n return new ValidationField(value);\n }\n\n // Creates a nested field path: parent.nested('child') -> 'parent.child'\n public nested(child: string | ValidationField): ValidationField {\n const childValue =\n child instanceof ValidationField ? child.value : child;\n return new ValidationField(`${this.value}.${childValue}`);\n }\n\n public toString(): string {\n return this.value;\n }\n\n public equals(other: ValidationField): boolean {\n return this.value === other.value;\n }\n}\n\nexport { ValidationField };\n"],"mappings":";AAIA,IAAM,kBAAN,MAAM,gBAAgB;CAGlB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAAgC;EAC7C,OAAO,IAAI,gBAAgB,KAAK;CACpC;CAGA,OAAc,OAAkD;EAC5D,MAAM,aACF,iBAAiB,kBAAkB,MAAM,QAAQ;EACrD,OAAO,IAAI,gBAAgB,GAAG,KAAK,MAAM,GAAG,YAAY;CAC5D;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAiC;EAC3C,OAAO,KAAK,UAAU,MAAM;CAChC;AACJ"}
1
+ {"version":3,"file":"validation-field.cjs","names":[],"sources":["../src/core/exceptions/validation-field.ts"],"sourcesContent":["// Object-oriented representation of validation fields.\n// Indicates WHERE a validation error occurred.\n// This is a minimal base class — field catalogs are defined per domain/module.\n\nclass ValidationField {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom or domain-specific fields\n public static of(value: string): ValidationField {\n return new ValidationField(value);\n }\n\n // Creates a nested field path: parent.nested('child') -> 'parent.child'.\n // The separator is a literal '.' and segments are NOT escaped, so\n // of('a.b') and of('a').nested('b') collapse to the same path and compare\n // equal — a field name that itself contains '.' is indistinguishable from\n // a nested path. Keep segment names dot-free if that distinction matters.\n public nested(child: string | ValidationField): ValidationField {\n const childValue =\n child instanceof ValidationField ? child.value : child;\n return new ValidationField(`${this.value}.${childValue}`);\n }\n\n public toString(): string {\n return this.value;\n }\n\n // Serialize to the bare field path, so JSON.stringify of a violation yields\n // \"email\" rather than { \"value\": \"email\" }. Mirrors ValidationCode.\n public toJSON(): string {\n return this.value;\n }\n\n public equals(other: ValidationField): boolean {\n return this.value === other.value;\n }\n}\n\nexport { ValidationField };\n"],"mappings":";AAIA,IAAM,kBAAN,MAAM,gBAAgB;CAGlB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAAgC;EAC7C,OAAO,IAAI,gBAAgB,KAAK;CACpC;CAOA,OAAc,OAAkD;EAC5D,MAAM,aACF,iBAAiB,kBAAkB,MAAM,QAAQ;EACrD,OAAO,IAAI,gBAAgB,GAAG,KAAK,MAAM,GAAG,YAAY;CAC5D;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAIA,SAAwB;EACpB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAiC;EAC3C,OAAO,KAAK,UAAU,MAAM;CAChC;AACJ"}
@@ -5,6 +5,7 @@ declare class ValidationField {
5
5
  static of(value: string): ValidationField;
6
6
  nested(child: string | ValidationField): ValidationField;
7
7
  toString(): string;
8
+ toJSON(): string;
8
9
  equals(other: ValidationField): boolean;
9
10
  }
10
11
  //#endregion
@@ -5,6 +5,7 @@ declare class ValidationField {
5
5
  static of(value: string): ValidationField;
6
6
  nested(child: string | ValidationField): ValidationField;
7
7
  toString(): string;
8
+ toJSON(): string;
8
9
  equals(other: ValidationField): boolean;
9
10
  }
10
11
  //#endregion
@@ -13,6 +13,9 @@ var ValidationField = class ValidationField {
13
13
  toString() {
14
14
  return this.value;
15
15
  }
16
+ toJSON() {
17
+ return this.value;
18
+ }
16
19
  equals(other) {
17
20
  return this.value === other.value;
18
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validation-field.js","names":[],"sources":["../src/core/exceptions/validation-field.ts"],"sourcesContent":["// Object-oriented representation of validation fields.\n// Indicates WHERE a validation error occurred.\n// This is a minimal base class — field catalogs are defined per domain/module.\n\nclass ValidationField {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom or domain-specific fields\n public static of(value: string): ValidationField {\n return new ValidationField(value);\n }\n\n // Creates a nested field path: parent.nested('child') -> 'parent.child'\n public nested(child: string | ValidationField): ValidationField {\n const childValue =\n child instanceof ValidationField ? child.value : child;\n return new ValidationField(`${this.value}.${childValue}`);\n }\n\n public toString(): string {\n return this.value;\n }\n\n public equals(other: ValidationField): boolean {\n return this.value === other.value;\n }\n}\n\nexport { ValidationField };\n"],"mappings":";AAIA,IAAM,kBAAN,MAAM,gBAAgB;CAGlB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAAgC;EAC7C,OAAO,IAAI,gBAAgB,KAAK;CACpC;CAGA,OAAc,OAAkD;EAC5D,MAAM,aACF,iBAAiB,kBAAkB,MAAM,QAAQ;EACrD,OAAO,IAAI,gBAAgB,GAAG,KAAK,MAAM,GAAG,YAAY;CAC5D;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAiC;EAC3C,OAAO,KAAK,UAAU,MAAM;CAChC;AACJ"}
1
+ {"version":3,"file":"validation-field.js","names":[],"sources":["../src/core/exceptions/validation-field.ts"],"sourcesContent":["// Object-oriented representation of validation fields.\n// Indicates WHERE a validation error occurred.\n// This is a minimal base class — field catalogs are defined per domain/module.\n\nclass ValidationField {\n public readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n // Factory for custom or domain-specific fields\n public static of(value: string): ValidationField {\n return new ValidationField(value);\n }\n\n // Creates a nested field path: parent.nested('child') -> 'parent.child'.\n // The separator is a literal '.' and segments are NOT escaped, so\n // of('a.b') and of('a').nested('b') collapse to the same path and compare\n // equal — a field name that itself contains '.' is indistinguishable from\n // a nested path. Keep segment names dot-free if that distinction matters.\n public nested(child: string | ValidationField): ValidationField {\n const childValue =\n child instanceof ValidationField ? child.value : child;\n return new ValidationField(`${this.value}.${childValue}`);\n }\n\n public toString(): string {\n return this.value;\n }\n\n // Serialize to the bare field path, so JSON.stringify of a violation yields\n // \"email\" rather than { \"value\": \"email\" }. Mirrors ValidationCode.\n public toJSON(): string {\n return this.value;\n }\n\n public equals(other: ValidationField): boolean {\n return this.value === other.value;\n }\n}\n\nexport { ValidationField };\n"],"mappings":";AAIA,IAAM,kBAAN,MAAM,gBAAgB;CAGlB,YAAoB,OAAe;EAC/B,KAAK,QAAQ;CACjB;CAGA,OAAc,GAAG,OAAgC;EAC7C,OAAO,IAAI,gBAAgB,KAAK;CACpC;CAOA,OAAc,OAAkD;EAC5D,MAAM,aACF,iBAAiB,kBAAkB,MAAM,QAAQ;EACrD,OAAO,IAAI,gBAAgB,GAAG,KAAK,MAAM,GAAG,YAAY;CAC5D;CAEA,WAA0B;EACtB,OAAO,KAAK;CAChB;CAIA,SAAwB;EACpB,OAAO,KAAK;CAChB;CAEA,OAAc,OAAiC;EAC3C,OAAO,KAAK,UAAU,MAAM;CAChC;AACJ"}
@@ -23,7 +23,17 @@ declare class RulesetRegistry implements RulesetRegistry$1 {
23
23
  private _sealed;
24
24
  register(ruleset: Ruleset): void;
25
25
  seal(): void;
26
+ /**
27
+ * Looks up a ruleset by exact id. The `T` parameter is a caller-asserted
28
+ * cast, not a runtime check: asking for the wrong `T` compiles and returns
29
+ * the object mistyped — the standard registry trade-off. Keep the id and
30
+ * the expected type paired at the call site.
31
+ */
26
32
  get<T extends Ruleset>(id: string): T;
33
+ /**
34
+ * Returns the highest `major.minor` version registered under `prefix`.
35
+ * The same caller-asserted `T` cast as {@link get} applies.
36
+ */
27
37
  getCurrent<T extends Ruleset>(prefix: string): T;
28
38
  }
29
39
  //#endregion
@@ -23,7 +23,17 @@ declare class RulesetRegistry implements RulesetRegistry$1 {
23
23
  private _sealed;
24
24
  register(ruleset: Ruleset): void;
25
25
  seal(): void;
26
+ /**
27
+ * Looks up a ruleset by exact id. The `T` parameter is a caller-asserted
28
+ * cast, not a runtime check: asking for the wrong `T` compiles and returns
29
+ * the object mistyped — the standard registry trade-off. Keep the id and
30
+ * the expected type paired at the call site.
31
+ */
26
32
  get<T extends Ruleset>(id: string): T;
33
+ /**
34
+ * Returns the highest `major.minor` version registered under `prefix`.
35
+ * The same caller-asserted `T` cast as {@link get} applies.
36
+ */
27
37
  getCurrent<T extends Ruleset>(prefix: string): T;
28
38
  }
29
39
  //#endregion
@@ -1,4 +1,6 @@
1
+ const require_stable_stringify = require("./stable-stringify.cjs");
1
2
  const require_immutable = require("./immutable.cjs");
3
+ const require_decorate = require("./decorate.cjs");
2
4
  const require_plugin = require("./plugin.cjs");
3
5
  //#region src/core/domain/value-object.ts
4
6
  var _ValueObject;
@@ -14,6 +16,12 @@ var _ValueObject;
14
16
  * any risk of a consumer mutating shared state. Subclasses seal the instance
15
17
  * itself with {@link finalize} once their own fields are set.
16
18
  *
19
+ * One exception: nested `Date` instances are cloned but NOT frozen —
20
+ * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of
21
+ * `value` can still mutate an inner `Date` in place. Store instants as ISO
22
+ * strings or epoch timestamps in the wrapped state (converting to `Date` only
23
+ * in an accessor) when that guarantee matters.
24
+ *
17
25
  * @typeParam T - The shape of the wrapped data.
18
26
  * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.
19
27
  */
@@ -37,7 +45,7 @@ let ValueObject = class ValueObject {
37
45
  * `@version` decorator — used to detect state persisted under an older shape.
38
46
  */
39
47
  get contractVersion() {
40
- return _ValueObject.CONTRACT_VERSION;
48
+ return this.constructor.CONTRACT_VERSION;
41
49
  }
42
50
  /**
43
51
  * Freezes the instance shell, blocking reassignment of any own field.
@@ -66,14 +74,25 @@ let ValueObject = class ValueObject {
66
74
  * same data are considered equal.
67
75
  *
68
76
  * Delegates to the registered equality {@link plugins}; with no plugin
69
- * registered it falls back to comparing the serialized wrapped `value`.
77
+ * registered it falls back to comparing the serialized wrapped `value`,
78
+ * with object keys sorted so insertion order (e.g. code-built vs
79
+ * JSON-rehydrated) never affects the result.
70
80
  * Subclasses may still override for a faster or domain-specific comparison.
81
+ *
82
+ * The fallback also requires both sides to be the same concrete class, so
83
+ * an `OrderId` never equals a `CustomerId` that wraps the same string —
84
+ * `other: this` only guards at compile time, and a cast would otherwise
85
+ * slip through. And because the fallback serializes via `stableStringify`,
86
+ * it inherits `JSON.stringify` semantics: `undefined` properties are
87
+ * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse
88
+ * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override
89
+ * `equals` when the wrapped value relies on such shapes.
71
90
  */
72
91
  equals(other) {
73
- return _ValueObject.plugins.invoke("equals", [this, other], { fallback: (a, b) => JSON.stringify(a.value) === JSON.stringify(b.value) });
92
+ return _ValueObject.plugins.invoke("equals", [this, other], { fallback: (a, b) => a.constructor === b.constructor && require_stable_stringify.stableStringify(a.value) === require_stable_stringify.stableStringify(b.value) });
74
93
  }
75
94
  };
76
- ValueObject = _ValueObject = require_immutable.__decorate([require_immutable.version("1.0")], ValueObject);
95
+ ValueObject = _ValueObject = require_decorate.__decorate([require_immutable.version("1.0")], ValueObject);
77
96
  //#endregion
78
97
  Object.defineProperty(exports, "ValueObject", {
79
98
  enumerable: true,
@@ -1 +1 @@
1
- {"version":3,"file":"value-object.cjs","names":["PluginManager","makeImmutable","version"],"sources":["../src/core/domain/value-object.ts"],"sourcesContent":["import { PluginManager } from \"../plugins/index.js\";\nimport { type DeepReadonly, makeImmutable } from \"../shared/immutable.js\";\nimport { type ContractVersion, version } from \"../versioning/version.js\";\n\n/**\n * Extension point for value-object equality. A plugin implementing this\n * contract overrides how any two value objects are compared, letting the host\n * application swap in a structural comparator (e.g. `lodash.isEqual` over the\n * wrapped `value`) without every value object having to implement `equals` by\n * hand.\n */\ntype ValueObjectPluginContract = {\n equals: (\n a: ValueObject<unknown, unknown>,\n b: ValueObject<unknown, unknown>,\n ) => boolean;\n};\n\n/**\n * Base class for value objects — domain concepts defined entirely by their\n * contents, with no identity of their own. Two value objects are\n * interchangeable when they hold equal data, which is the opposite of an\n * {@link Entity} (compared by id). Money, a CPF, a date range: replacing one\n * instance with an equal one changes nothing about the model.\n *\n * Immutability is the defining guarantee here. The wrapped `value` is\n * deep-frozen on construction, so a value object can be shared freely without\n * any risk of a consumer mutating shared state. Subclasses seal the instance\n * itself with {@link finalize} once their own fields are set.\n *\n * @typeParam T - The shape of the wrapped data.\n * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.\n */\n@version(\"1.0\")\nabstract class ValueObject<T, P> {\n declare public static readonly CONTRACT_VERSION: ContractVersion;\n\n /**\n * Registry of equality plugins shared by every value object. Empty by\n * default — when nothing is registered, {@link equals} falls back to a\n * structural comparison of the wrapped `value`. Hosts register a plugin\n * (e.g. `lodash.isEqual`) once at startup to customise equality globally.\n */\n public static readonly plugins =\n new PluginManager<ValueObjectPluginContract>();\n\n /** The wrapped data, deep-frozen so it can never be mutated after construction. */\n public readonly value: DeepReadonly<T>;\n\n /**\n * Wraps `value`, deep-freezing it so the value object is immutable from the\n * moment it exists. Declared `protected` because value objects are built\n * through a validating subclass factory, never instantiated directly.\n */\n protected constructor(value: T) {\n this.value = makeImmutable(value);\n }\n\n /**\n * The schema/contract version stamped on this value-object type by the\n * `@version` decorator — used to detect state persisted under an older shape.\n */\n public get contractVersion(): ContractVersion {\n return ValueObject.CONTRACT_VERSION;\n }\n\n /**\n * Freezes the instance shell, blocking reassignment of any own field.\n *\n * `value` is already deep-frozen by the constructor, so the wrapped data is\n * immutable regardless. The instance itself is NOT frozen automatically\n * because a subclass may still need to assign its own fields after\n * `super(value)` runs. Call `finalize()` at the very end of the subclass\n * constructor (after all fields are set) to make the whole value object\n * immutable.\n */\n protected finalize(): void {\n Object.freeze(this);\n }\n\n /**\n * Hook for `JSON.stringify`. Delegates to {@link toPrimitive} so a value\n * object serializes to its primitive form rather than exposing the internal\n * `value` wrapper.\n */\n public toJSON(): P {\n return this.toPrimitive();\n }\n\n /**\n * Compares this value object with another by content. Because value objects\n * carry no identity, two independently constructed instances holding the\n * same data are considered equal.\n *\n * Delegates to the registered equality {@link plugins}; with no plugin\n * registered it falls back to comparing the serialized wrapped `value`.\n * Subclasses may still override for a faster or domain-specific comparison.\n */\n public equals(other: this): boolean {\n return ValueObject.plugins.invoke(\"equals\", [this, other], {\n fallback: (a, b) =>\n JSON.stringify(a.value) === JSON.stringify(b.value),\n });\n }\n\n /**\n * Projects the value object down to a plain, serializable primitive form —\n * the representation suitable for persistence, transport, or comparison.\n */\n public abstract toPrimitive(): P;\n}\n\nexport { type DeepReadonly, ValueObject, type ValueObjectPluginContract };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,IAAA,cAAA,MACe,YAAkB;;;;;iBAUzB,IAAIA,eAAAA,cAAyC;;;;;;;CAUjD,YAAsB,OAAU;EAC5B,KAAK,QAAQC,kBAAAA,cAAc,KAAK;CACpC;;;;;CAMA,IAAW,kBAAmC;EAC1C,OAAA,aAAmB;CACvB;;;;;;;;;;;CAYA,WAA2B;EACvB,OAAO,OAAO,IAAI;CACtB;;;;;;CAOA,SAAmB;EACf,OAAO,KAAK,YAAY;CAC5B;;;;;;;;;;CAWA,OAAc,OAAsB;EAChC,OAAA,aAAmB,QAAQ,OAAO,UAAU,CAAC,MAAM,KAAK,GAAG,EACvD,WAAW,GAAG,MACV,KAAK,UAAU,EAAE,KAAK,MAAM,KAAK,UAAU,EAAE,KAAK,EAC1D,CAAC;CACL;AAOJ;2DA7ECC,kBAAAA,QAAQ,KAAK,CAAA,GAAA,WAAA"}
1
+ {"version":3,"file":"value-object.cjs","names":["PluginManager","makeImmutable","stableStringify","version"],"sources":["../src/core/domain/value-object.ts"],"sourcesContent":["import { PluginManager } from \"../plugins/index.js\";\nimport { type DeepReadonly, makeImmutable } from \"../shared/immutable.js\";\nimport { stableStringify } from \"../shared/stable-stringify.js\";\nimport { type ContractVersion, version } from \"../versioning/version.js\";\n\n/**\n * Extension point for value-object equality. A plugin implementing this\n * contract overrides how any two value objects are compared, letting the host\n * application swap in a structural comparator (e.g. `lodash.isEqual` over the\n * wrapped `value`) without every value object having to implement `equals` by\n * hand.\n */\ntype ValueObjectPluginContract = {\n equals: (\n a: ValueObject<unknown, unknown>,\n b: ValueObject<unknown, unknown>,\n ) => boolean;\n};\n\n/**\n * Base class for value objects — domain concepts defined entirely by their\n * contents, with no identity of their own. Two value objects are\n * interchangeable when they hold equal data, which is the opposite of an\n * {@link Entity} (compared by id). Money, a CPF, a date range: replacing one\n * instance with an equal one changes nothing about the model.\n *\n * Immutability is the defining guarantee here. The wrapped `value` is\n * deep-frozen on construction, so a value object can be shared freely without\n * any risk of a consumer mutating shared state. Subclasses seal the instance\n * itself with {@link finalize} once their own fields are set.\n *\n * One exception: nested `Date` instances are cloned but NOT frozen —\n * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of\n * `value` can still mutate an inner `Date` in place. Store instants as ISO\n * strings or epoch timestamps in the wrapped state (converting to `Date` only\n * in an accessor) when that guarantee matters.\n *\n * @typeParam T - The shape of the wrapped data.\n * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.\n */\n@version(\"1.0\")\nabstract class ValueObject<T, P> {\n declare public static readonly CONTRACT_VERSION: ContractVersion;\n\n /**\n * Registry of equality plugins shared by every value object. Empty by\n * default — when nothing is registered, {@link equals} falls back to a\n * structural comparison of the wrapped `value`. Hosts register a plugin\n * (e.g. `lodash.isEqual`) once at startup to customise equality globally.\n *\n * \"Globally\" is literal: this static registry is process-wide and shared\n * by **every** `ValueObject` subclass, including the value objects the kit\n * itself ships (RBAC's `Permission`, `Scope`, …). A registered plugin\n * therefore redefines equality for those too — it must honour generic\n * value-object semantics, not the quirks of one host type. For a\n * type-specific comparison, override `equals` on that subclass instead.\n */\n public static readonly plugins =\n new PluginManager<ValueObjectPluginContract>();\n\n /** The wrapped data, deep-frozen so it can never be mutated after construction. */\n public readonly value: DeepReadonly<T>;\n\n /**\n * Wraps `value`, deep-freezing it so the value object is immutable from the\n * moment it exists. Declared `protected` because value objects are built\n * through a validating subclass factory, never instantiated directly.\n */\n protected constructor(value: T) {\n this.value = makeImmutable(value);\n }\n\n /**\n * The schema/contract version stamped on this value-object type by the\n * `@version` decorator — used to detect state persisted under an older shape.\n */\n public get contractVersion(): ContractVersion {\n return (this.constructor as typeof ValueObject).CONTRACT_VERSION;\n }\n\n /**\n * Freezes the instance shell, blocking reassignment of any own field.\n *\n * `value` is already deep-frozen by the constructor, so the wrapped data is\n * immutable regardless. The instance itself is NOT frozen automatically\n * because a subclass may still need to assign its own fields after\n * `super(value)` runs. Call `finalize()` at the very end of the subclass\n * constructor (after all fields are set) to make the whole value object\n * immutable.\n */\n protected finalize(): void {\n Object.freeze(this);\n }\n\n /**\n * Hook for `JSON.stringify`. Delegates to {@link toPrimitive} so a value\n * object serializes to its primitive form rather than exposing the internal\n * `value` wrapper.\n */\n public toJSON(): P {\n return this.toPrimitive();\n }\n\n /**\n * Compares this value object with another by content. Because value objects\n * carry no identity, two independently constructed instances holding the\n * same data are considered equal.\n *\n * Delegates to the registered equality {@link plugins}; with no plugin\n * registered it falls back to comparing the serialized wrapped `value`,\n * with object keys sorted so insertion order (e.g. code-built vs\n * JSON-rehydrated) never affects the result.\n * Subclasses may still override for a faster or domain-specific comparison.\n *\n * The fallback also requires both sides to be the same concrete class, so\n * an `OrderId` never equals a `CustomerId` that wraps the same string —\n * `other: this` only guards at compile time, and a cast would otherwise\n * slip through. And because the fallback serializes via `stableStringify`,\n * it inherits `JSON.stringify` semantics: `undefined` properties are\n * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse\n * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override\n * `equals` when the wrapped value relies on such shapes.\n */\n public equals(other: this): boolean {\n return ValueObject.plugins.invoke(\"equals\", [this, other], {\n fallback: (a, b) =>\n a.constructor === b.constructor &&\n stableStringify(a.value) === stableStringify(b.value),\n });\n }\n\n /**\n * Projects the value object down to a plain, serializable primitive form —\n * the representation suitable for persistence, transport, or comparison.\n */\n public abstract toPrimitive(): P;\n}\n\nexport { type DeepReadonly, ValueObject, type ValueObjectPluginContract };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAA,cAAA,MACe,YAAkB;;;;;iBAiBzB,IAAIA,eAAAA,cAAyC;;;;;;;CAUjD,YAAsB,OAAU;EAC5B,KAAK,QAAQC,kBAAAA,cAAc,KAAK;CACpC;;;;;CAMA,IAAW,kBAAmC;EAC1C,OAAQ,KAAK,YAAmC;CACpD;;;;;;;;;;;CAYA,WAA2B;EACvB,OAAO,OAAO,IAAI;CACtB;;;;;;CAOA,SAAmB;EACf,OAAO,KAAK,YAAY;CAC5B;;;;;;;;;;;;;;;;;;;;;CAsBA,OAAc,OAAsB;EAChC,OAAA,aAAmB,QAAQ,OAAO,UAAU,CAAC,MAAM,KAAK,GAAG,EACvD,WAAW,GAAG,MACV,EAAE,gBAAgB,EAAE,eACpBC,yBAAAA,gBAAgB,EAAE,KAAK,MAAMA,yBAAAA,gBAAgB,EAAE,KAAK,EAC5D,CAAC;CACL;AAOJ;0DAhGCC,kBAAAA,QAAQ,KAAK,CAAA,GAAA,WAAA"}
@@ -26,6 +26,12 @@ type ValueObjectPluginContract = {
26
26
  * any risk of a consumer mutating shared state. Subclasses seal the instance
27
27
  * itself with {@link finalize} once their own fields are set.
28
28
  *
29
+ * One exception: nested `Date` instances are cloned but NOT frozen —
30
+ * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of
31
+ * `value` can still mutate an inner `Date` in place. Store instants as ISO
32
+ * strings or epoch timestamps in the wrapped state (converting to `Date` only
33
+ * in an accessor) when that guarantee matters.
34
+ *
29
35
  * @typeParam T - The shape of the wrapped data.
30
36
  * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.
31
37
  */
@@ -36,6 +42,13 @@ declare abstract class ValueObject<T, P> {
36
42
  * default — when nothing is registered, {@link equals} falls back to a
37
43
  * structural comparison of the wrapped `value`. Hosts register a plugin
38
44
  * (e.g. `lodash.isEqual`) once at startup to customise equality globally.
45
+ *
46
+ * "Globally" is literal: this static registry is process-wide and shared
47
+ * by **every** `ValueObject` subclass, including the value objects the kit
48
+ * itself ships (RBAC's `Permission`, `Scope`, …). A registered plugin
49
+ * therefore redefines equality for those too — it must honour generic
50
+ * value-object semantics, not the quirks of one host type. For a
51
+ * type-specific comparison, override `equals` on that subclass instead.
39
52
  */
40
53
  static readonly plugins: PluginManager<ValueObjectPluginContract>;
41
54
  /** The wrapped data, deep-frozen so it can never be mutated after construction. */
@@ -74,8 +87,19 @@ declare abstract class ValueObject<T, P> {
74
87
  * same data are considered equal.
75
88
  *
76
89
  * Delegates to the registered equality {@link plugins}; with no plugin
77
- * registered it falls back to comparing the serialized wrapped `value`.
90
+ * registered it falls back to comparing the serialized wrapped `value`,
91
+ * with object keys sorted so insertion order (e.g. code-built vs
92
+ * JSON-rehydrated) never affects the result.
78
93
  * Subclasses may still override for a faster or domain-specific comparison.
94
+ *
95
+ * The fallback also requires both sides to be the same concrete class, so
96
+ * an `OrderId` never equals a `CustomerId` that wraps the same string —
97
+ * `other: this` only guards at compile time, and a cast would otherwise
98
+ * slip through. And because the fallback serializes via `stableStringify`,
99
+ * it inherits `JSON.stringify` semantics: `undefined` properties are
100
+ * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse
101
+ * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override
102
+ * `equals` when the wrapped value relies on such shapes.
79
103
  */
80
104
  equals(other: this): boolean;
81
105
  /**
@@ -26,6 +26,12 @@ type ValueObjectPluginContract = {
26
26
  * any risk of a consumer mutating shared state. Subclasses seal the instance
27
27
  * itself with {@link finalize} once their own fields are set.
28
28
  *
29
+ * One exception: nested `Date` instances are cloned but NOT frozen —
30
+ * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of
31
+ * `value` can still mutate an inner `Date` in place. Store instants as ISO
32
+ * strings or epoch timestamps in the wrapped state (converting to `Date` only
33
+ * in an accessor) when that guarantee matters.
34
+ *
29
35
  * @typeParam T - The shape of the wrapped data.
30
36
  * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.
31
37
  */
@@ -36,6 +42,13 @@ declare abstract class ValueObject<T, P> {
36
42
  * default — when nothing is registered, {@link equals} falls back to a
37
43
  * structural comparison of the wrapped `value`. Hosts register a plugin
38
44
  * (e.g. `lodash.isEqual`) once at startup to customise equality globally.
45
+ *
46
+ * "Globally" is literal: this static registry is process-wide and shared
47
+ * by **every** `ValueObject` subclass, including the value objects the kit
48
+ * itself ships (RBAC's `Permission`, `Scope`, …). A registered plugin
49
+ * therefore redefines equality for those too — it must honour generic
50
+ * value-object semantics, not the quirks of one host type. For a
51
+ * type-specific comparison, override `equals` on that subclass instead.
39
52
  */
40
53
  static readonly plugins: PluginManager<ValueObjectPluginContract>;
41
54
  /** The wrapped data, deep-frozen so it can never be mutated after construction. */
@@ -74,8 +87,19 @@ declare abstract class ValueObject<T, P> {
74
87
  * same data are considered equal.
75
88
  *
76
89
  * Delegates to the registered equality {@link plugins}; with no plugin
77
- * registered it falls back to comparing the serialized wrapped `value`.
90
+ * registered it falls back to comparing the serialized wrapped `value`,
91
+ * with object keys sorted so insertion order (e.g. code-built vs
92
+ * JSON-rehydrated) never affects the result.
78
93
  * Subclasses may still override for a faster or domain-specific comparison.
94
+ *
95
+ * The fallback also requires both sides to be the same concrete class, so
96
+ * an `OrderId` never equals a `CustomerId` that wraps the same string —
97
+ * `other: this` only guards at compile time, and a cast would otherwise
98
+ * slip through. And because the fallback serializes via `stableStringify`,
99
+ * it inherits `JSON.stringify` semantics: `undefined` properties are
100
+ * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse
101
+ * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override
102
+ * `equals` when the wrapped value relies on such shapes.
79
103
  */
80
104
  equals(other: this): boolean;
81
105
  /**
@@ -1,4 +1,6 @@
1
- import { i as version, n as __decorate, t as makeImmutable } from "./immutable.js";
1
+ import { n as stableStringify } from "./stable-stringify.js";
2
+ import { i as version, n as makeImmutable } from "./immutable.js";
3
+ import { t as __decorate } from "./decorate.js";
2
4
  import { t as PluginManager } from "./plugin.js";
3
5
  //#region src/core/domain/value-object.ts
4
6
  var _ValueObject;
@@ -14,6 +16,12 @@ var _ValueObject;
14
16
  * any risk of a consumer mutating shared state. Subclasses seal the instance
15
17
  * itself with {@link finalize} once their own fields are set.
16
18
  *
19
+ * One exception: nested `Date` instances are cloned but NOT frozen —
20
+ * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of
21
+ * `value` can still mutate an inner `Date` in place. Store instants as ISO
22
+ * strings or epoch timestamps in the wrapped state (converting to `Date` only
23
+ * in an accessor) when that guarantee matters.
24
+ *
17
25
  * @typeParam T - The shape of the wrapped data.
18
26
  * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.
19
27
  */
@@ -37,7 +45,7 @@ let ValueObject = class ValueObject {
37
45
  * `@version` decorator — used to detect state persisted under an older shape.
38
46
  */
39
47
  get contractVersion() {
40
- return _ValueObject.CONTRACT_VERSION;
48
+ return this.constructor.CONTRACT_VERSION;
41
49
  }
42
50
  /**
43
51
  * Freezes the instance shell, blocking reassignment of any own field.
@@ -66,11 +74,22 @@ let ValueObject = class ValueObject {
66
74
  * same data are considered equal.
67
75
  *
68
76
  * Delegates to the registered equality {@link plugins}; with no plugin
69
- * registered it falls back to comparing the serialized wrapped `value`.
77
+ * registered it falls back to comparing the serialized wrapped `value`,
78
+ * with object keys sorted so insertion order (e.g. code-built vs
79
+ * JSON-rehydrated) never affects the result.
70
80
  * Subclasses may still override for a faster or domain-specific comparison.
81
+ *
82
+ * The fallback also requires both sides to be the same concrete class, so
83
+ * an `OrderId` never equals a `CustomerId` that wraps the same string —
84
+ * `other: this` only guards at compile time, and a cast would otherwise
85
+ * slip through. And because the fallback serializes via `stableStringify`,
86
+ * it inherits `JSON.stringify` semantics: `undefined` properties are
87
+ * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse
88
+ * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override
89
+ * `equals` when the wrapped value relies on such shapes.
71
90
  */
72
91
  equals(other) {
73
- return _ValueObject.plugins.invoke("equals", [this, other], { fallback: (a, b) => JSON.stringify(a.value) === JSON.stringify(b.value) });
92
+ return _ValueObject.plugins.invoke("equals", [this, other], { fallback: (a, b) => a.constructor === b.constructor && stableStringify(a.value) === stableStringify(b.value) });
74
93
  }
75
94
  };
76
95
  ValueObject = _ValueObject = __decorate([version("1.0")], ValueObject);
@@ -1 +1 @@
1
- {"version":3,"file":"value-object.js","names":[],"sources":["../src/core/domain/value-object.ts"],"sourcesContent":["import { PluginManager } from \"../plugins/index.js\";\nimport { type DeepReadonly, makeImmutable } from \"../shared/immutable.js\";\nimport { type ContractVersion, version } from \"../versioning/version.js\";\n\n/**\n * Extension point for value-object equality. A plugin implementing this\n * contract overrides how any two value objects are compared, letting the host\n * application swap in a structural comparator (e.g. `lodash.isEqual` over the\n * wrapped `value`) without every value object having to implement `equals` by\n * hand.\n */\ntype ValueObjectPluginContract = {\n equals: (\n a: ValueObject<unknown, unknown>,\n b: ValueObject<unknown, unknown>,\n ) => boolean;\n};\n\n/**\n * Base class for value objects — domain concepts defined entirely by their\n * contents, with no identity of their own. Two value objects are\n * interchangeable when they hold equal data, which is the opposite of an\n * {@link Entity} (compared by id). Money, a CPF, a date range: replacing one\n * instance with an equal one changes nothing about the model.\n *\n * Immutability is the defining guarantee here. The wrapped `value` is\n * deep-frozen on construction, so a value object can be shared freely without\n * any risk of a consumer mutating shared state. Subclasses seal the instance\n * itself with {@link finalize} once their own fields are set.\n *\n * @typeParam T - The shape of the wrapped data.\n * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.\n */\n@version(\"1.0\")\nabstract class ValueObject<T, P> {\n declare public static readonly CONTRACT_VERSION: ContractVersion;\n\n /**\n * Registry of equality plugins shared by every value object. Empty by\n * default — when nothing is registered, {@link equals} falls back to a\n * structural comparison of the wrapped `value`. Hosts register a plugin\n * (e.g. `lodash.isEqual`) once at startup to customise equality globally.\n */\n public static readonly plugins =\n new PluginManager<ValueObjectPluginContract>();\n\n /** The wrapped data, deep-frozen so it can never be mutated after construction. */\n public readonly value: DeepReadonly<T>;\n\n /**\n * Wraps `value`, deep-freezing it so the value object is immutable from the\n * moment it exists. Declared `protected` because value objects are built\n * through a validating subclass factory, never instantiated directly.\n */\n protected constructor(value: T) {\n this.value = makeImmutable(value);\n }\n\n /**\n * The schema/contract version stamped on this value-object type by the\n * `@version` decorator — used to detect state persisted under an older shape.\n */\n public get contractVersion(): ContractVersion {\n return ValueObject.CONTRACT_VERSION;\n }\n\n /**\n * Freezes the instance shell, blocking reassignment of any own field.\n *\n * `value` is already deep-frozen by the constructor, so the wrapped data is\n * immutable regardless. The instance itself is NOT frozen automatically\n * because a subclass may still need to assign its own fields after\n * `super(value)` runs. Call `finalize()` at the very end of the subclass\n * constructor (after all fields are set) to make the whole value object\n * immutable.\n */\n protected finalize(): void {\n Object.freeze(this);\n }\n\n /**\n * Hook for `JSON.stringify`. Delegates to {@link toPrimitive} so a value\n * object serializes to its primitive form rather than exposing the internal\n * `value` wrapper.\n */\n public toJSON(): P {\n return this.toPrimitive();\n }\n\n /**\n * Compares this value object with another by content. Because value objects\n * carry no identity, two independently constructed instances holding the\n * same data are considered equal.\n *\n * Delegates to the registered equality {@link plugins}; with no plugin\n * registered it falls back to comparing the serialized wrapped `value`.\n * Subclasses may still override for a faster or domain-specific comparison.\n */\n public equals(other: this): boolean {\n return ValueObject.plugins.invoke(\"equals\", [this, other], {\n fallback: (a, b) =>\n JSON.stringify(a.value) === JSON.stringify(b.value),\n });\n }\n\n /**\n * Projects the value object down to a plain, serializable primitive form —\n * the representation suitable for persistence, transport, or comparison.\n */\n public abstract toPrimitive(): P;\n}\n\nexport { type DeepReadonly, ValueObject, type ValueObjectPluginContract };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,IAAA,cAAA,MACe,YAAkB;;;;;iBAUzB,IAAI,cAAyC;;;;;;;CAUjD,YAAsB,OAAU;EAC5B,KAAK,QAAQ,cAAc,KAAK;CACpC;;;;;CAMA,IAAW,kBAAmC;EAC1C,OAAA,aAAmB;CACvB;;;;;;;;;;;CAYA,WAA2B;EACvB,OAAO,OAAO,IAAI;CACtB;;;;;;CAOA,SAAmB;EACf,OAAO,KAAK,YAAY;CAC5B;;;;;;;;;;CAWA,OAAc,OAAsB;EAChC,OAAA,aAAmB,QAAQ,OAAO,UAAU,CAAC,MAAM,KAAK,GAAG,EACvD,WAAW,GAAG,MACV,KAAK,UAAU,EAAE,KAAK,MAAM,KAAK,UAAU,EAAE,KAAK,EAC1D,CAAC;CACL;AAOJ;yCA7EC,QAAQ,KAAK,CAAA,GAAA,WAAA"}
1
+ {"version":3,"file":"value-object.js","names":[],"sources":["../src/core/domain/value-object.ts"],"sourcesContent":["import { PluginManager } from \"../plugins/index.js\";\nimport { type DeepReadonly, makeImmutable } from \"../shared/immutable.js\";\nimport { stableStringify } from \"../shared/stable-stringify.js\";\nimport { type ContractVersion, version } from \"../versioning/version.js\";\n\n/**\n * Extension point for value-object equality. A plugin implementing this\n * contract overrides how any two value objects are compared, letting the host\n * application swap in a structural comparator (e.g. `lodash.isEqual` over the\n * wrapped `value`) without every value object having to implement `equals` by\n * hand.\n */\ntype ValueObjectPluginContract = {\n equals: (\n a: ValueObject<unknown, unknown>,\n b: ValueObject<unknown, unknown>,\n ) => boolean;\n};\n\n/**\n * Base class for value objects — domain concepts defined entirely by their\n * contents, with no identity of their own. Two value objects are\n * interchangeable when they hold equal data, which is the opposite of an\n * {@link Entity} (compared by id). Money, a CPF, a date range: replacing one\n * instance with an equal one changes nothing about the model.\n *\n * Immutability is the defining guarantee here. The wrapped `value` is\n * deep-frozen on construction, so a value object can be shared freely without\n * any risk of a consumer mutating shared state. Subclasses seal the instance\n * itself with {@link finalize} once their own fields are set.\n *\n * One exception: nested `Date` instances are cloned but NOT frozen —\n * `Object.freeze` does not block `setTime`/`setFullYear`, so a consumer of\n * `value` can still mutate an inner `Date` in place. Store instants as ISO\n * strings or epoch timestamps in the wrapped state (converting to `Date` only\n * in an accessor) when that guarantee matters.\n *\n * @typeParam T - The shape of the wrapped data.\n * @typeParam P - The primitive form produced by {@link toPrimitive} / {@link toJSON}.\n */\n@version(\"1.0\")\nabstract class ValueObject<T, P> {\n declare public static readonly CONTRACT_VERSION: ContractVersion;\n\n /**\n * Registry of equality plugins shared by every value object. Empty by\n * default — when nothing is registered, {@link equals} falls back to a\n * structural comparison of the wrapped `value`. Hosts register a plugin\n * (e.g. `lodash.isEqual`) once at startup to customise equality globally.\n *\n * \"Globally\" is literal: this static registry is process-wide and shared\n * by **every** `ValueObject` subclass, including the value objects the kit\n * itself ships (RBAC's `Permission`, `Scope`, …). A registered plugin\n * therefore redefines equality for those too — it must honour generic\n * value-object semantics, not the quirks of one host type. For a\n * type-specific comparison, override `equals` on that subclass instead.\n */\n public static readonly plugins =\n new PluginManager<ValueObjectPluginContract>();\n\n /** The wrapped data, deep-frozen so it can never be mutated after construction. */\n public readonly value: DeepReadonly<T>;\n\n /**\n * Wraps `value`, deep-freezing it so the value object is immutable from the\n * moment it exists. Declared `protected` because value objects are built\n * through a validating subclass factory, never instantiated directly.\n */\n protected constructor(value: T) {\n this.value = makeImmutable(value);\n }\n\n /**\n * The schema/contract version stamped on this value-object type by the\n * `@version` decorator — used to detect state persisted under an older shape.\n */\n public get contractVersion(): ContractVersion {\n return (this.constructor as typeof ValueObject).CONTRACT_VERSION;\n }\n\n /**\n * Freezes the instance shell, blocking reassignment of any own field.\n *\n * `value` is already deep-frozen by the constructor, so the wrapped data is\n * immutable regardless. The instance itself is NOT frozen automatically\n * because a subclass may still need to assign its own fields after\n * `super(value)` runs. Call `finalize()` at the very end of the subclass\n * constructor (after all fields are set) to make the whole value object\n * immutable.\n */\n protected finalize(): void {\n Object.freeze(this);\n }\n\n /**\n * Hook for `JSON.stringify`. Delegates to {@link toPrimitive} so a value\n * object serializes to its primitive form rather than exposing the internal\n * `value` wrapper.\n */\n public toJSON(): P {\n return this.toPrimitive();\n }\n\n /**\n * Compares this value object with another by content. Because value objects\n * carry no identity, two independently constructed instances holding the\n * same data are considered equal.\n *\n * Delegates to the registered equality {@link plugins}; with no plugin\n * registered it falls back to comparing the serialized wrapped `value`,\n * with object keys sorted so insertion order (e.g. code-built vs\n * JSON-rehydrated) never affects the result.\n * Subclasses may still override for a faster or domain-specific comparison.\n *\n * The fallback also requires both sides to be the same concrete class, so\n * an `OrderId` never equals a `CustomerId` that wraps the same string —\n * `other: this` only guards at compile time, and a cast would otherwise\n * slip through. And because the fallback serializes via `stableStringify`,\n * it inherits `JSON.stringify` semantics: `undefined` properties are\n * dropped (`{a: 1, b: undefined}` equals `{a: 1}`) and `Map`/`Set` collapse\n * to `{}` — see the caveats in `shared/stable-stringify.ts`. Override\n * `equals` when the wrapped value relies on such shapes.\n */\n public equals(other: this): boolean {\n return ValueObject.plugins.invoke(\"equals\", [this, other], {\n fallback: (a, b) =>\n a.constructor === b.constructor &&\n stableStringify(a.value) === stableStringify(b.value),\n });\n }\n\n /**\n * Projects the value object down to a plain, serializable primitive form —\n * the representation suitable for persistence, transport, or comparison.\n */\n public abstract toPrimitive(): P;\n}\n\nexport { type DeepReadonly, ValueObject, type ValueObjectPluginContract };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAA,cAAA,MACe,YAAkB;;;;;iBAiBzB,IAAI,cAAyC;;;;;;;CAUjD,YAAsB,OAAU;EAC5B,KAAK,QAAQ,cAAc,KAAK;CACpC;;;;;CAMA,IAAW,kBAAmC;EAC1C,OAAQ,KAAK,YAAmC;CACpD;;;;;;;;;;;CAYA,WAA2B;EACvB,OAAO,OAAO,IAAI;CACtB;;;;;;CAOA,SAAmB;EACf,OAAO,KAAK,YAAY;CAC5B;;;;;;;;;;;;;;;;;;;;;CAsBA,OAAc,OAAsB;EAChC,OAAA,aAAmB,QAAQ,OAAO,UAAU,CAAC,MAAM,KAAK,GAAG,EACvD,WAAW,GAAG,MACV,EAAE,gBAAgB,EAAE,eACpB,gBAAgB,EAAE,KAAK,MAAM,gBAAgB,EAAE,KAAK,EAC5D,CAAC;CACL;AAOJ;yCAhGC,QAAQ,KAAK,CAAA,GAAA,WAAA"}
@@ -1,9 +1,36 @@
1
1
  //#region src/core/versioning/version.d.ts
2
+ /**
3
+ * A structural contract version in `MAJOR.MINOR` form (e.g. `"1.0"`, `"2.5"`).
4
+ *
5
+ * The template-literal type is intentionally loose — it also accepts strings
6
+ * the module rejects at runtime (e.g. `"1.0.0"` matches as `1` · `.` · `0.0`).
7
+ * The runtime regex in {@link version} is the real guard; the type is only a
8
+ * light hint.
9
+ */
2
10
  type ContractVersion = `${number}.${number}`;
11
+ /** Anything a class decorator can stamp a static property onto. */
3
12
  type VersionedTarget = {
4
13
  readonly prototype: object;
5
14
  };
6
15
  declare const CONTRACT_VERSION_PROPERTY: "CONTRACT_VERSION";
16
+ /**
17
+ * Class decorator that stamps a `MAJOR.MINOR` structural contract version onto
18
+ * the class as a static `CONTRACT_VERSION` property.
19
+ *
20
+ * The property is defined **non-writable, non-configurable and non-enumerable**
21
+ * on purpose: it must not be reassigned or redefined (tampering with a contract
22
+ * version silently corrupts migration decisions), and keeping it off enumeration
23
+ * excludes it from `JSON.stringify`/`for..in`. A consequence of
24
+ * `configurable: false` is that decorating the same class twice throws
25
+ * `TypeError: Cannot redefine property` — a deliberate fail-fast.
26
+ *
27
+ * Note on inheritance: a subclass that is not re-decorated inherits its base's
28
+ * `CONTRACT_VERSION` through the static prototype chain. Re-decorate any
29
+ * subclass whose persisted shape diverges from its base.
30
+ *
31
+ * @throws {InvariantViolationException} When `contractVersion` is not
32
+ * `MAJOR.MINOR` (validated at module-load time, when the decorator runs).
33
+ */
7
34
  declare function version<const TVersion extends ContractVersion>(contractVersion: TVersion): (target: VersionedTarget) => void;
8
35
  //#endregion
9
36
  export { version as i, ContractVersion as n, VersionedTarget as r, CONTRACT_VERSION_PROPERTY as t };