@cullet/erp-core 1.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 (182) hide show
  1. package/KIT_CONTEXT.md +39 -0
  2. package/README.md +110 -0
  3. package/dist/errors/index.d.ts +2 -0
  4. package/dist/errors/index.js +3 -0
  5. package/dist/exceptions/validation-field.d.ts +2 -0
  6. package/dist/exceptions/validation-field.js +2 -0
  7. package/dist/gate-engine-registry.js +260 -0
  8. package/dist/gate-engine-registry.js.map +1 -0
  9. package/dist/gate-types.d.ts +307 -0
  10. package/dist/gate-v1-payload.schema.js +677 -0
  11. package/dist/gate-v1-payload.schema.js.map +1 -0
  12. package/dist/index.d.ts +88 -0
  13. package/dist/index.js +153 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/outcome.js +92 -0
  16. package/dist/outcome.js.map +1 -0
  17. package/dist/parse-gate-payload.d.ts +136 -0
  18. package/dist/policies/engines/index.d.ts +3 -0
  19. package/dist/policies/engines/index.js +2 -0
  20. package/dist/policies/engines/v1/gate/index.d.ts +115 -0
  21. package/dist/policies/engines/v1/gate/index.js +84 -0
  22. package/dist/policies/engines/v1/gate/index.js.map +1 -0
  23. package/dist/policies/index.d.ts +4 -0
  24. package/dist/policies/index.js +5 -0
  25. package/dist/policy-service.d.ts +541 -0
  26. package/dist/policy-service.js +992 -0
  27. package/dist/policy-service.js.map +1 -0
  28. package/dist/validation-code.js +210 -0
  29. package/dist/validation-code.js.map +1 -0
  30. package/dist/validation-error.d.ts +686 -0
  31. package/dist/validation-error.js +757 -0
  32. package/dist/validation-error.js.map +1 -0
  33. package/dist/validation-field.d.ts +12 -0
  34. package/dist/validation-field.js +23 -0
  35. package/dist/validation-field.js.map +1 -0
  36. package/meta.json +66 -0
  37. package/package.json +59 -0
  38. package/src/core/application/commands/command.ts +28 -0
  39. package/src/core/application/commands/index.ts +2 -0
  40. package/src/core/application/commands/requested-by.ts +97 -0
  41. package/src/core/application/index.ts +31 -0
  42. package/src/core/application/policy-error-mapper.ts +60 -0
  43. package/src/core/application/ports/index.ts +14 -0
  44. package/src/core/application/ports/logger.port.ts +10 -0
  45. package/src/core/application/ports/metrics.port.ts +9 -0
  46. package/src/core/application/ports/policy-port.ts +20 -0
  47. package/src/core/application/ports/repository.port.ts +7 -0
  48. package/src/core/application/ports/temporal-repository.port.ts +14 -0
  49. package/src/core/application/ports/tracer.port.ts +16 -0
  50. package/src/core/application/queries/index.ts +2 -0
  51. package/src/core/application/queries/query.ts +53 -0
  52. package/src/core/application/temporal/index.ts +11 -0
  53. package/src/core/application/temporal/temporal-context.ts +41 -0
  54. package/src/core/application/temporal/temporal-use-case.ts +41 -0
  55. package/src/core/application/use-case.ts +24 -0
  56. package/src/core/config/core-config.instance.ts +14 -0
  57. package/src/core/config/core-config.ts +87 -0
  58. package/src/core/config/index.ts +12 -0
  59. package/src/core/config/policy-reporter.ts +60 -0
  60. package/src/core/config/silent-policy-reporter.ts +5 -0
  61. package/src/core/domain/entity.ts +69 -0
  62. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +16 -0
  63. package/src/core/domain/rulesets/ruleset-registry.ts +71 -0
  64. package/src/core/domain/rulesets/ruleset.contracts.ts +15 -0
  65. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +13 -0
  66. package/src/core/domain/temporal/index.ts +18 -0
  67. package/src/core/domain/temporal/temporal-range.ts +53 -0
  68. package/src/core/domain/temporal/temporal-snapshot.ts +47 -0
  69. package/src/core/domain/temporal/transaction-time.ts +60 -0
  70. package/src/core/domain/temporal/valid-time.ts +57 -0
  71. package/src/core/domain/value-object.ts +30 -0
  72. package/src/core/errors/app-error.ts +85 -0
  73. package/src/core/errors/authentication-error.ts +171 -0
  74. package/src/core/errors/authorization-error.ts +204 -0
  75. package/src/core/errors/business-rule-violation-error.ts +28 -0
  76. package/src/core/errors/conflict-error.ts +246 -0
  77. package/src/core/errors/error-codes.ts +60 -0
  78. package/src/core/errors/idempotency-error.ts +372 -0
  79. package/src/core/errors/index.ts +99 -0
  80. package/src/core/errors/integration-error.ts +157 -0
  81. package/src/core/errors/legacy-incompatible-error.ts +26 -0
  82. package/src/core/errors/not-found-error.ts +27 -0
  83. package/src/core/errors/serialization-error.ts +233 -0
  84. package/src/core/errors/temporal-error.ts +145 -0
  85. package/src/core/errors/types.ts +74 -0
  86. package/src/core/errors/unexpected-error.ts +22 -0
  87. package/src/core/errors/utils/index.ts +4 -0
  88. package/src/core/errors/utils/json-safe.ts +93 -0
  89. package/src/core/errors/validation-error.ts +34 -0
  90. package/src/core/exceptions/business-rule-violation-exception.ts +12 -0
  91. package/src/core/exceptions/domain-exception.ts +9 -0
  92. package/src/core/exceptions/entity-not-found-exception.ts +12 -0
  93. package/src/core/exceptions/invalid-state-transition-exception.ts +15 -0
  94. package/src/core/exceptions/invariant-violation-exception.ts +9 -0
  95. package/src/core/exceptions/validation-code.ts +39 -0
  96. package/src/core/exceptions/validation-exception.ts +39 -0
  97. package/src/core/exceptions/validation-field.ts +32 -0
  98. package/src/core/index.ts +15 -0
  99. package/src/core/policies/asof/asof.ts +68 -0
  100. package/src/core/policies/asof/index.ts +2 -0
  101. package/src/core/policies/catalog/catalog-types.ts +12 -0
  102. package/src/core/policies/catalog/catalog.instance.ts +21 -0
  103. package/src/core/policies/catalog/index.ts +11 -0
  104. package/src/core/policies/catalog/policy-catalog-entry.ts +220 -0
  105. package/src/core/policies/catalog/policy-catalog.ts +124 -0
  106. package/src/core/policies/catalog/policy-key.ts +78 -0
  107. package/src/core/policies/context/context-builder.ts +391 -0
  108. package/src/core/policies/context/context-registry.instance.ts +26 -0
  109. package/src/core/policies/context/context-registry.ts +95 -0
  110. package/src/core/policies/context/context-resolver.ts +31 -0
  111. package/src/core/policies/context/context-seed.ts +32 -0
  112. package/src/core/policies/context/index.ts +21 -0
  113. package/src/core/policies/context/path.ts +152 -0
  114. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +73 -0
  115. package/src/core/policies/defs/index.ts +20 -0
  116. package/src/core/policies/defs/policy-definition-repository.ts +8 -0
  117. package/src/core/policies/defs/policy-definition.ts +192 -0
  118. package/src/core/policies/defs/policy-payload.contracts.ts +13 -0
  119. package/src/core/policies/defs/policy-scope.ts +53 -0
  120. package/src/core/policies/defs/policy-semver.ts +83 -0
  121. package/src/core/policies/engines/compute-engine-registry.ts +57 -0
  122. package/src/core/policies/engines/compute-evaluator-registry.ts +28 -0
  123. package/src/core/policies/engines/compute-payload.ts +5 -0
  124. package/src/core/policies/engines/compute-registry.ts +67 -0
  125. package/src/core/policies/engines/compute-types.ts +51 -0
  126. package/src/core/policies/engines/condition-evaluator-reporter.ts +34 -0
  127. package/src/core/policies/engines/gate-engine-registry.ts +38 -0
  128. package/src/core/policies/engines/gate-payload.ts +5 -0
  129. package/src/core/policies/engines/gate-types.ts +63 -0
  130. package/src/core/policies/engines/index.ts +48 -0
  131. package/src/core/policies/engines/parse-compute-payload.ts +71 -0
  132. package/src/core/policies/engines/parse-gate-payload.ts +68 -0
  133. package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +55 -0
  134. package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +75 -0
  135. package/src/core/policies/engines/v1/compute/index.ts +12 -0
  136. package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +54 -0
  137. package/src/core/policies/engines/v1/condition-evaluator.ts +592 -0
  138. package/src/core/policies/engines/v1/condition-schema.ts +50 -0
  139. package/src/core/policies/engines/v1/condition-types.ts +41 -0
  140. package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +157 -0
  141. package/src/core/policies/engines/v1/gate/gate-types-v1.ts +12 -0
  142. package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +46 -0
  143. package/src/core/policies/engines/v1/gate/index.ts +9 -0
  144. package/src/core/policies/engines/v1/index.ts +13 -0
  145. package/src/core/policies/index.ts +132 -0
  146. package/src/core/policies/package/policy-package.ts +17 -0
  147. package/src/core/policies/policy-ids.ts +60 -0
  148. package/src/core/policies/resolver/index.ts +1 -0
  149. package/src/core/policies/resolver/policy-resolver.ts +57 -0
  150. package/src/core/policies/service/index.ts +10 -0
  151. package/src/core/policies/service/policy-evaluation-error.ts +148 -0
  152. package/src/core/policies/service/policy-service.ts +493 -0
  153. package/src/core/policies/utils/date.ts +7 -0
  154. package/src/core/policies/utils/hash.ts +90 -0
  155. package/src/core/policies/utils/index.ts +3 -0
  156. package/src/core/policies/utils/result.ts +4 -0
  157. package/src/core/result/outcome.ts +158 -0
  158. package/src/core/result/result.ts +153 -0
  159. package/src/core/shared/aggregate-version.ts +11 -0
  160. package/src/core/shared/hashing.ts +38 -0
  161. package/src/core/shared/immutable.ts +46 -0
  162. package/src/core/shared/temporal-guards.ts +22 -0
  163. package/src/core/versioning/domain-event-contracts.ts +73 -0
  164. package/src/core/versioning/version.ts +34 -0
  165. package/src/errors/index.ts +1 -0
  166. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +31 -0
  167. package/src/examples/rulesets/entity/order-invariants-v1.ts +48 -0
  168. package/src/examples/rulesets/entity/order-invariants-v2.ts +28 -0
  169. package/src/examples/rulesets/entity/order.ts +162 -0
  170. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +52 -0
  171. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +27 -0
  172. package/src/examples/rulesets/value-object/cpf.ts +37 -0
  173. package/src/examples/rulesets/value-object/customer.ts +67 -0
  174. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +37 -0
  175. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +27 -0
  176. package/src/examples/rulesets/value-object/person-name.ts +39 -0
  177. package/src/exceptions/validation-field.ts +1 -0
  178. package/src/index.ts +11 -0
  179. package/src/policies/engines/index.ts +1 -0
  180. package/src/policies/engines/v1/gate/index.ts +1 -0
  181. package/src/policies/index.ts +1 -0
  182. package/src/version.ts +7 -0
@@ -0,0 +1,246 @@
1
+ import { AppError } from "./app-error";
2
+ import { ErrorCodes } from "./error-codes";
3
+ import type { AppErrorOptions } from "./types";
4
+
5
+ /**
6
+ * Conflict kinds we want to distinguish semantically.
7
+ */
8
+ type ConflictKind = "already_exists" | "duplicate" | "unique_violation";
9
+
10
+ /**
11
+ * Metadata attached to conflicts without exposing sensitive values.
12
+ */
13
+ type ConflictErrorMetadata = {
14
+ kind: ConflictKind;
15
+ entity: string;
16
+ operation?: string;
17
+ field?: string;
18
+ constraintName?: string;
19
+ existingId?: string;
20
+ valueHash?: string;
21
+ valuePreview?: string;
22
+ detectedAtIso?: string;
23
+ correlationId?: string;
24
+ requestId?: string;
25
+ commandId?: string;
26
+ hint?: string;
27
+ };
28
+
29
+ type UniqueConstraintViolation = {
30
+ kind: "unique_violation";
31
+ constraintName?: string;
32
+ table?: string;
33
+ columns?: string[];
34
+ };
35
+
36
+ abstract class ConflictError extends AppError {
37
+ public readonly kind: ConflictKind;
38
+
39
+ protected constructor(
40
+ params: {
41
+ message: string;
42
+ code: string;
43
+ kind: ConflictKind;
44
+ metadata: Omit<ConflictErrorMetadata, "kind">;
45
+ cause?: unknown;
46
+ } & AppErrorOptions,
47
+ ) {
48
+ super(params.message, params.code, {
49
+ ...params,
50
+ metadata: {
51
+ kind: params.kind,
52
+ ...params.metadata,
53
+ },
54
+ });
55
+
56
+ this.kind = params.kind;
57
+ Object.freeze(this);
58
+ }
59
+ }
60
+
61
+ class AlreadyExistsError extends ConflictError {
62
+ private constructor(
63
+ input: {
64
+ metadata: Omit<ConflictErrorMetadata, "kind">;
65
+ cause?: unknown;
66
+ } & AppErrorOptions,
67
+ ) {
68
+ super({
69
+ message: "A matching record already exists for the requested operation",
70
+ code: ErrorCodes.conflict.alreadyExists,
71
+ kind: "already_exists",
72
+ ...input,
73
+ });
74
+ }
75
+
76
+ static detected(input: {
77
+ entity: string;
78
+ operation?: string;
79
+ field?: string;
80
+ existingId?: string;
81
+ valueHash?: string;
82
+ valuePreview?: string;
83
+ detectedAtIso?: string;
84
+ correlationId?: string;
85
+ requestId?: string;
86
+ commandId?: string;
87
+ hint?: string;
88
+ cause?: unknown;
89
+ }): AlreadyExistsError {
90
+ return new AlreadyExistsError({
91
+ metadata: {
92
+ entity: input.entity,
93
+ operation: input.operation ?? "create",
94
+ field: input.field,
95
+ existingId: input.existingId,
96
+ valueHash: input.valueHash,
97
+ valuePreview: input.valuePreview,
98
+ detectedAtIso: input.detectedAtIso,
99
+ correlationId: input.correlationId,
100
+ requestId: input.requestId,
101
+ commandId: input.commandId,
102
+ hint: input.hint ?? "Verify existing records before retrying.",
103
+ },
104
+ cause: input.cause,
105
+ });
106
+ }
107
+ }
108
+
109
+ class DuplicateError extends ConflictError {
110
+ private constructor(
111
+ input: {
112
+ metadata: Omit<ConflictErrorMetadata, "kind">;
113
+ cause?: unknown;
114
+ } & AppErrorOptions,
115
+ ) {
116
+ super({
117
+ message: "Conflict: an existing record violates uniqueness",
118
+ code: ErrorCodes.conflict.duplicate,
119
+ kind: "duplicate",
120
+ ...input,
121
+ });
122
+ }
123
+
124
+ static detected(input: {
125
+ entity: string;
126
+ operation?: string;
127
+ field?: string;
128
+ constraintName?: string;
129
+ existingId?: string;
130
+ valueHash?: string;
131
+ valuePreview?: string;
132
+ detectedAtIso?: string;
133
+ correlationId?: string;
134
+ requestId?: string;
135
+ commandId?: string;
136
+ hint?: string;
137
+ cause?: unknown;
138
+ }): DuplicateError {
139
+ return new DuplicateError({
140
+ metadata: {
141
+ entity: input.entity,
142
+ operation: input.operation,
143
+ field: input.field,
144
+ constraintName: input.constraintName,
145
+ existingId: input.existingId,
146
+ valueHash: input.valueHash,
147
+ valuePreview: input.valuePreview,
148
+ detectedAtIso: input.detectedAtIso,
149
+ correlationId: input.correlationId,
150
+ requestId: input.requestId,
151
+ commandId: input.commandId,
152
+ hint:
153
+ input.hint ??
154
+ "Adjust the data so it does not duplicate existing records.",
155
+ },
156
+ cause: input.cause,
157
+ });
158
+ }
159
+ }
160
+
161
+ class UniqueConstraintViolationError extends ConflictError {
162
+ private constructor(
163
+ input: {
164
+ metadata: Omit<ConflictErrorMetadata, "kind"> & {
165
+ violation: UniqueConstraintViolation;
166
+ };
167
+ cause?: unknown;
168
+ } & AppErrorOptions,
169
+ ) {
170
+ super({
171
+ message: "Uniqueness violation in storage",
172
+ code: ErrorCodes.conflict.uniqueViolation,
173
+ kind: "unique_violation",
174
+ ...input,
175
+ });
176
+ }
177
+
178
+ static detected(input: {
179
+ entity: string;
180
+ operation?: string;
181
+ constraintName?: string;
182
+ table?: string;
183
+ columns?: string[];
184
+ detectedAtIso?: string;
185
+ correlationId?: string;
186
+ requestId?: string;
187
+ commandId?: string;
188
+ cause?: unknown;
189
+ }): UniqueConstraintViolationError {
190
+ return new UniqueConstraintViolationError({
191
+ metadata: {
192
+ entity: input.entity,
193
+ operation: input.operation,
194
+ constraintName: input.constraintName,
195
+ detectedAtIso: input.detectedAtIso,
196
+ correlationId: input.correlationId,
197
+ requestId: input.requestId,
198
+ commandId: input.commandId,
199
+ violation: {
200
+ kind: "unique_violation",
201
+ constraintName: input.constraintName,
202
+ table: input.table,
203
+ columns: input.columns,
204
+ },
205
+ hint: "Uniqueness conflict detected in the database.",
206
+ },
207
+ cause: input.cause,
208
+ });
209
+ }
210
+ }
211
+
212
+ function translateUniqueViolationToDuplicate(input: {
213
+ entity: string;
214
+ operation?: string;
215
+ violation: UniqueConstraintViolation;
216
+ field?: string;
217
+ ctx?: {
218
+ correlationId?: string;
219
+ requestId?: string;
220
+ commandId?: string;
221
+ nowIso?: string;
222
+ };
223
+ cause?: unknown;
224
+ }): DuplicateError {
225
+ return DuplicateError.detected({
226
+ entity: input.entity,
227
+ operation: input.operation,
228
+ field: input.field,
229
+ constraintName: input.violation.constraintName,
230
+ detectedAtIso: input.ctx?.nowIso,
231
+ correlationId: input.ctx?.correlationId,
232
+ requestId: input.ctx?.requestId,
233
+ commandId: input.ctx?.commandId,
234
+ cause: input.cause,
235
+ });
236
+ }
237
+
238
+ export {
239
+ AlreadyExistsError,
240
+ ConflictError,
241
+ DuplicateError,
242
+ translateUniqueViolationToDuplicate,
243
+ UniqueConstraintViolationError,
244
+ };
245
+
246
+ export type { ConflictErrorMetadata, ConflictKind, UniqueConstraintViolation };
@@ -0,0 +1,60 @@
1
+ const ErrorCodes = {
2
+ authentication: {
3
+ missingToken: "sec.authn.missing_token",
4
+ invalidToken: "sec.authn.invalid_token",
5
+ expiredToken: "sec.authn.expired_token",
6
+ invalidCredentials: "sec.authn.invalid_credentials",
7
+ },
8
+ authorization: {
9
+ forbidden: "sec.authz.forbidden",
10
+ policyDenied: "sec.authz.policy_denied",
11
+ missingCapability: "sec.authz.missing_capability",
12
+ outOfScope: "sec.authz.out_of_scope",
13
+ },
14
+ businessRuleViolation: "business_rule_violation",
15
+ conflict: {
16
+ alreadyExists: "conf.already_exists",
17
+ duplicate: "conf.duplicate",
18
+ uniqueViolation: "conf.unique_violation",
19
+ },
20
+ idempotency: {
21
+ keyMissing: "idemp.key_missing",
22
+ inProgress: "idemp.in_progress",
23
+ payloadMismatch: "idemp.payload_mismatch",
24
+ replayNotSupported: "idemp.replay_not_supported",
25
+ },
26
+ integration: {
27
+ timeout: "int.timeout",
28
+ unreachable: "int.unreachable",
29
+ badResponse: "int.bad_response",
30
+ },
31
+ legacyIncompatible: "legacy_incompatible",
32
+ notFound: "not_found",
33
+ serialization: {
34
+ deserializePrefix: "ser.des",
35
+ serializePrefix: "ser.ser",
36
+ },
37
+ temporal: {
38
+ expired: "tmp.expired",
39
+ notYetValid: "tmp.not_yet_valid",
40
+ },
41
+ unexpected: "unexpected",
42
+ validation: "validation_error",
43
+ } as const;
44
+
45
+ type SerializationCodeDirection = "deserialize" | "serialize";
46
+
47
+ function serializationErrorCode(
48
+ direction: SerializationCodeDirection,
49
+ category: string,
50
+ ): string {
51
+ const prefix =
52
+ direction === "deserialize"
53
+ ? ErrorCodes.serialization.deserializePrefix
54
+ : ErrorCodes.serialization.serializePrefix;
55
+
56
+ return `${prefix}.${category}`;
57
+ }
58
+
59
+ export { ErrorCodes, serializationErrorCode };
60
+ export type { SerializationCodeDirection };
@@ -0,0 +1,372 @@
1
+ import { payloadHash, sha256Hex, stableStringify } from "../shared/hashing";
2
+
3
+ import { AppError } from "./app-error";
4
+ import { ErrorCodes } from "./error-codes";
5
+
6
+ export type IdempotencyFailureKind =
7
+ | "key_missing"
8
+ | "in_progress"
9
+ | "payload_mismatch"
10
+ | "replay_not_supported"
11
+ | "unknown";
12
+
13
+ export type IdempotencyErrorMetadata = {
14
+ kind: IdempotencyFailureKind;
15
+ operation: string;
16
+ scope?: string;
17
+ entity?: string;
18
+ keyHash?: string;
19
+ keyPreview?: string;
20
+ incomingPayloadHash?: string;
21
+ existingPayloadHash?: string;
22
+ idempotencyRecordId?: string;
23
+ correlationId?: string;
24
+ requestId?: string;
25
+ commandId?: string;
26
+ detectedAtIso?: string;
27
+ hint?: string;
28
+ details?: string;
29
+ };
30
+
31
+ type IdempotencyErrorConstructorInput = {
32
+ message: string;
33
+ code: string;
34
+ kind: IdempotencyFailureKind;
35
+ metadata: Omit<IdempotencyErrorMetadata, "kind">;
36
+ cause?: unknown;
37
+ };
38
+
39
+ abstract class IdempotencyError extends AppError {
40
+ public readonly kind: IdempotencyFailureKind;
41
+
42
+ protected constructor(input: IdempotencyErrorConstructorInput) {
43
+ super(input.message, input.code, {
44
+ cause: input.cause,
45
+ metadata: { kind: input.kind, ...input.metadata },
46
+ });
47
+
48
+ this.kind = input.kind;
49
+ Object.freeze(this);
50
+ }
51
+
52
+ static keyMissing(input: {
53
+ operation: string;
54
+ scope?: string;
55
+ entity?: string;
56
+ correlationId?: string;
57
+ requestId?: string;
58
+ commandId?: string;
59
+ detectedAtIso?: string;
60
+ hint?: string;
61
+ details?: string;
62
+ cause?: unknown;
63
+ }): IdempotencyKeyMissingError {
64
+ return IdempotencyKeyMissingError.detected(input);
65
+ }
66
+
67
+ static inProgress(input: {
68
+ operation: string;
69
+ scope?: string;
70
+ entity?: string;
71
+ key?: string;
72
+ keyHash?: string;
73
+ keyPreview?: string;
74
+ idempotencyRecordId?: string;
75
+ correlationId?: string;
76
+ requestId?: string;
77
+ commandId?: string;
78
+ detectedAtIso?: string;
79
+ hint?: string;
80
+ details?: string;
81
+ cause?: unknown;
82
+ }): IdempotencyInProgressError {
83
+ return IdempotencyInProgressError.detected(input);
84
+ }
85
+
86
+ static payloadMismatch(input: {
87
+ operation: string;
88
+ scope?: string;
89
+ entity?: string;
90
+ key?: string;
91
+ keyHash?: string;
92
+ keyPreview?: string;
93
+ incomingPayloadHash?: string;
94
+ existingPayloadHash?: string;
95
+ idempotencyRecordId?: string;
96
+ correlationId?: string;
97
+ requestId?: string;
98
+ commandId?: string;
99
+ detectedAtIso?: string;
100
+ hint?: string;
101
+ details?: string;
102
+ cause?: unknown;
103
+ }): IdempotencyPayloadMismatchError {
104
+ return IdempotencyPayloadMismatchError.detected(input);
105
+ }
106
+
107
+ static replayNotSupported(input: {
108
+ operation: string;
109
+ scope?: string;
110
+ entity?: string;
111
+ key?: string;
112
+ keyHash?: string;
113
+ keyPreview?: string;
114
+ idempotencyRecordId?: string;
115
+ correlationId?: string;
116
+ requestId?: string;
117
+ commandId?: string;
118
+ detectedAtIso?: string;
119
+ hint?: string;
120
+ details?: string;
121
+ cause?: unknown;
122
+ }): IdempotencyReplayNotSupportedError {
123
+ return IdempotencyReplayNotSupportedError.detected(input);
124
+ }
125
+ }
126
+
127
+ class IdempotencyKeyMissingError extends IdempotencyError {
128
+ private constructor(input: {
129
+ metadata: Omit<IdempotencyErrorMetadata, "kind">;
130
+ cause?: unknown;
131
+ }) {
132
+ super({
133
+ message: "Idempotency key/commandId missing for the requested operation",
134
+ code: ErrorCodes.idempotency.keyMissing,
135
+ kind: "key_missing",
136
+ metadata: input.metadata,
137
+ cause: input.cause,
138
+ });
139
+ }
140
+
141
+ static detected(input: {
142
+ operation: string;
143
+ scope?: string;
144
+ entity?: string;
145
+ correlationId?: string;
146
+ requestId?: string;
147
+ commandId?: string;
148
+ detectedAtIso?: string;
149
+ hint?: string;
150
+ details?: string;
151
+ cause?: unknown;
152
+ }): IdempotencyKeyMissingError {
153
+ return new IdempotencyKeyMissingError({
154
+ cause: input.cause,
155
+ metadata: {
156
+ operation: input.operation,
157
+ scope: input.scope,
158
+ entity: input.entity,
159
+ correlationId: input.correlationId,
160
+ requestId: input.requestId,
161
+ commandId: input.commandId,
162
+ detectedAtIso: input.detectedAtIso,
163
+ hint:
164
+ input.hint ??
165
+ "Send a commandId/idempotency key to prevent duplicate processing.",
166
+ details: input.details,
167
+ },
168
+ });
169
+ }
170
+ }
171
+
172
+ class IdempotencyInProgressError extends IdempotencyError {
173
+ private constructor(input: {
174
+ metadata: Omit<IdempotencyErrorMetadata, "kind">;
175
+ cause?: unknown;
176
+ }) {
177
+ super({
178
+ message: "The same business intent is already being processed",
179
+ code: ErrorCodes.idempotency.inProgress,
180
+ kind: "in_progress",
181
+ metadata: input.metadata,
182
+ cause: input.cause,
183
+ });
184
+ }
185
+
186
+ static detected(input: {
187
+ operation: string;
188
+ scope?: string;
189
+ entity?: string;
190
+ key?: string;
191
+ keyHash?: string;
192
+ keyPreview?: string;
193
+ idempotencyRecordId?: string;
194
+ correlationId?: string;
195
+ requestId?: string;
196
+ commandId?: string;
197
+ detectedAtIso?: string;
198
+ hint?: string;
199
+ details?: string;
200
+ cause?: unknown;
201
+ }): IdempotencyInProgressError {
202
+ const resolved = resolveKeyIdentity(
203
+ input.key,
204
+ input.keyHash,
205
+ input.keyPreview,
206
+ );
207
+
208
+ return new IdempotencyInProgressError({
209
+ cause: input.cause,
210
+ metadata: {
211
+ operation: input.operation,
212
+ scope: input.scope,
213
+ entity: input.entity,
214
+ keyHash: resolved.keyHash,
215
+ keyPreview: resolved.keyPreview,
216
+ idempotencyRecordId: input.idempotencyRecordId,
217
+ correlationId: input.correlationId,
218
+ requestId: input.requestId,
219
+ commandId: input.commandId,
220
+ detectedAtIso: input.detectedAtIso,
221
+ hint: input.hint ?? "Wait for completion and retry using the same key.",
222
+ details: input.details,
223
+ },
224
+ });
225
+ }
226
+ }
227
+
228
+ class IdempotencyPayloadMismatchError extends IdempotencyError {
229
+ private constructor(input: {
230
+ metadata: Omit<IdempotencyErrorMetadata, "kind">;
231
+ cause?: unknown;
232
+ }) {
233
+ super({
234
+ message: "The idempotency key was reused with a different payload",
235
+ code: ErrorCodes.idempotency.payloadMismatch,
236
+ kind: "payload_mismatch",
237
+ metadata: input.metadata,
238
+ cause: input.cause,
239
+ });
240
+ }
241
+
242
+ static detected(input: {
243
+ operation: string;
244
+ scope?: string;
245
+ entity?: string;
246
+ key?: string;
247
+ keyHash?: string;
248
+ keyPreview?: string;
249
+ incomingPayloadHash?: string;
250
+ existingPayloadHash?: string;
251
+ idempotencyRecordId?: string;
252
+ correlationId?: string;
253
+ requestId?: string;
254
+ commandId?: string;
255
+ detectedAtIso?: string;
256
+ hint?: string;
257
+ details?: string;
258
+ cause?: unknown;
259
+ }): IdempotencyPayloadMismatchError {
260
+ const resolved = resolveKeyIdentity(
261
+ input.key,
262
+ input.keyHash,
263
+ input.keyPreview,
264
+ );
265
+
266
+ return new IdempotencyPayloadMismatchError({
267
+ cause: input.cause,
268
+ metadata: {
269
+ operation: input.operation,
270
+ scope: input.scope,
271
+ entity: input.entity,
272
+ keyHash: resolved.keyHash,
273
+ keyPreview: resolved.keyPreview,
274
+ incomingPayloadHash: input.incomingPayloadHash,
275
+ existingPayloadHash: input.existingPayloadHash,
276
+ idempotencyRecordId: input.idempotencyRecordId,
277
+ correlationId: input.correlationId,
278
+ requestId: input.requestId,
279
+ commandId: input.commandId,
280
+ detectedAtIso: input.detectedAtIso,
281
+ hint:
282
+ input.hint ??
283
+ "Use a new idempotency key for a new intent. The same key should only be reused for retries of the same payload.",
284
+ details: input.details,
285
+ },
286
+ });
287
+ }
288
+ }
289
+
290
+ class IdempotencyReplayNotSupportedError extends IdempotencyError {
291
+ private constructor(input: {
292
+ metadata: Omit<IdempotencyErrorMetadata, "kind">;
293
+ cause?: unknown;
294
+ }) {
295
+ super({
296
+ message:
297
+ "Re-execution detected, but replay of the previous result is not implemented",
298
+ code: ErrorCodes.idempotency.replayNotSupported,
299
+ kind: "replay_not_supported",
300
+ metadata: input.metadata,
301
+ cause: input.cause,
302
+ });
303
+ }
304
+
305
+ static detected(input: {
306
+ operation: string;
307
+ scope?: string;
308
+ entity?: string;
309
+ key?: string;
310
+ keyHash?: string;
311
+ keyPreview?: string;
312
+ idempotencyRecordId?: string;
313
+ correlationId?: string;
314
+ requestId?: string;
315
+ commandId?: string;
316
+ detectedAtIso?: string;
317
+ hint?: string;
318
+ details?: string;
319
+ cause?: unknown;
320
+ }): IdempotencyReplayNotSupportedError {
321
+ const resolved = resolveKeyIdentity(
322
+ input.key,
323
+ input.keyHash,
324
+ input.keyPreview,
325
+ );
326
+
327
+ return new IdempotencyReplayNotSupportedError({
328
+ cause: input.cause,
329
+ metadata: {
330
+ operation: input.operation,
331
+ scope: input.scope,
332
+ entity: input.entity,
333
+ keyHash: resolved.keyHash,
334
+ keyPreview: resolved.keyPreview,
335
+ idempotencyRecordId: input.idempotencyRecordId,
336
+ correlationId: input.correlationId,
337
+ requestId: input.requestId,
338
+ commandId: input.commandId,
339
+ detectedAtIso: input.detectedAtIso,
340
+ hint:
341
+ input.hint ??
342
+ "Try again later, or enable result replay for full idempotency.",
343
+ details: input.details,
344
+ },
345
+ });
346
+ }
347
+ }
348
+
349
+ function resolveKeyIdentity(
350
+ key?: string,
351
+ keyHash?: string,
352
+ keyPreview?: string,
353
+ ): { keyHash?: string; keyPreview?: string } {
354
+ const preview =
355
+ keyPreview ?? (key ? (key.length <= 8 ? key : key.slice(0, 8)) : undefined);
356
+
357
+ return {
358
+ keyHash: keyHash,
359
+ keyPreview: preview,
360
+ };
361
+ }
362
+
363
+ export {
364
+ IdempotencyError,
365
+ IdempotencyKeyMissingError,
366
+ IdempotencyInProgressError,
367
+ IdempotencyPayloadMismatchError,
368
+ IdempotencyReplayNotSupportedError,
369
+ sha256Hex,
370
+ stableStringify,
371
+ payloadHash,
372
+ };