@cullet/erp-core 1.0.10 → 1.1.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 (62) hide show
  1. package/KIT_CONTEXT.md +67 -0
  2. package/README.md +128 -1
  3. package/dist/app-error.d.ts +108 -0
  4. package/dist/application/index.d.ts +2 -0
  5. package/dist/application/index.js +2 -0
  6. package/dist/errors/index.d.ts +2 -1
  7. package/dist/errors/index.js +4 -2
  8. package/dist/gate-engine-registry.d.ts +80 -0
  9. package/dist/gate-v1-payload.schema.js +2 -28
  10. package/dist/gate-v1-payload.schema.js.map +1 -1
  11. package/dist/hashing.js +49 -0
  12. package/dist/hashing.js.map +1 -0
  13. package/dist/index.d.ts +122 -45
  14. package/dist/index.js +104 -85
  15. package/dist/index.js.map +1 -1
  16. package/dist/not-found-error.js +54 -0
  17. package/dist/not-found-error.js.map +1 -0
  18. package/dist/outcome.js +1 -1
  19. package/dist/parse-gate-payload.d.ts +3 -78
  20. package/dist/path.d.ts +89 -0
  21. package/dist/policies/engines/index.d.ts +2 -1
  22. package/dist/policies/index.d.ts +4 -2
  23. package/dist/policy-service.d.ts +102 -86
  24. package/dist/policy-service.js +78 -16
  25. package/dist/policy-service.js.map +1 -1
  26. package/dist/temporal-guards.js +30 -0
  27. package/dist/temporal-guards.js.map +1 -0
  28. package/dist/temporal-use-case.d.ts +309 -0
  29. package/dist/temporal-use-case.js +284 -0
  30. package/dist/temporal-use-case.js.map +1 -0
  31. package/dist/validation-code.js +34 -47
  32. package/dist/validation-code.js.map +1 -1
  33. package/dist/validation-error.d.ts +171 -74
  34. package/dist/validation-error.js +152 -38
  35. package/dist/validation-error.js.map +1 -1
  36. package/dist/validation-exception.js +18 -0
  37. package/dist/validation-exception.js.map +1 -0
  38. package/meta.json +4 -2
  39. package/package.json +5 -1
  40. package/src/application/index.ts +1 -0
  41. package/src/core/application/commands/index.ts +1 -0
  42. package/src/core/application/index.ts +12 -3
  43. package/src/core/application/ports/index.ts +2 -2
  44. package/src/core/application/ports/policy-port.ts +13 -3
  45. package/src/core/application/ports/repository.port.ts +37 -1
  46. package/src/core/application/temporal/temporal-use-case.ts +14 -2
  47. package/src/core/application/use-case.ts +133 -2
  48. package/src/core/domain/entity.ts +71 -0
  49. package/src/core/domain/value-object.ts +41 -0
  50. package/src/core/errors/app-error.ts +33 -0
  51. package/src/core/errors/authorization-error.ts +43 -0
  52. package/src/core/errors/conflict-error.ts +69 -0
  53. package/src/core/errors/integration-error.ts +25 -0
  54. package/src/core/errors/not-found-error.ts +14 -0
  55. package/src/core/errors/validation-error.ts +18 -0
  56. package/src/core/index.ts +1 -10
  57. package/src/core/policies/catalog/policy-catalog.ts +36 -0
  58. package/src/core/policies/resolver/policy-resolver.ts +10 -0
  59. package/src/core/policies/service/policy-service.ts +53 -0
  60. package/src/examples/application/cancel-order.example.ts +124 -0
  61. package/src/examples/application/in-memory-account-repository.example.ts +73 -0
  62. package/src/version.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { c as ErrorCodes, l as serializationErrorCode, o as AppError } from "./validation-code.js";
1
+ import { a as ErrorCodes, o as serializationErrorCode, r as AppError } from "./validation-code.js";
2
2
  //#region src/core/errors/authentication-error.ts
3
3
  var AuthenticationError = class AuthenticationError extends AppError {
4
4
  constructor(params) {
@@ -80,6 +80,19 @@ function compactMetadata$1(input) {
80
80
  }
81
81
  //#endregion
82
82
  //#region src/core/errors/authorization-error.ts
83
+ /**
84
+ * Raised when an authenticated actor is not allowed to perform a business
85
+ * action — the "you may not" error, distinct from authentication ("who are
86
+ * you"). Maps to an HTTP 403 at the edge.
87
+ *
88
+ * The discriminating {@link reason} records *why* access was denied (a flat
89
+ * forbid, a missing role/capability, an out-of-scope target, or a policy
90
+ * decision) so the boundary can shape the response without re-deriving it. The
91
+ * metadata is deliberately built around a stable business `action` and a
92
+ * type/id resource reference rather than an HTTP route or full payload, keeping
93
+ * sensitive data out of logs. Instances are frozen. Construct through the
94
+ * static factories, never directly.
95
+ */
83
96
  var AuthorizationError = class AuthorizationError extends AppError {
84
97
  constructor(params) {
85
98
  super(params.message, params.code, {
@@ -96,6 +109,12 @@ var AuthorizationError = class AuthorizationError extends AppError {
96
109
  this.reason = params.reason;
97
110
  Object.freeze(this);
98
111
  }
112
+ /**
113
+ * A flat denial with no more specific reason — the actor simply may not
114
+ * perform this action. Reach for a more precise factory
115
+ * ({@link AuthorizationError.missingCapability}, {@link AuthorizationError.outOfScope},
116
+ * {@link AuthorizationError.policyDenied}) when the cause is known.
117
+ */
99
118
  static forbidden(input) {
100
119
  return new AuthorizationError({
101
120
  message: "Action not allowed",
@@ -108,6 +127,15 @@ var AuthorizationError = class AuthorizationError extends AppError {
108
127
  ...extractAppErrorOptions(input)
109
128
  });
110
129
  }
130
+ /**
131
+ * The action was denied by an evaluated policy. Captures the deciding
132
+ * policy's id, version, and evaluation instant into the metadata (with
133
+ * `decision: "deny"`) so the denial is auditable back to the exact policy
134
+ * that produced it.
135
+ *
136
+ * @param input - Factory options plus the required `policyId`,
137
+ * `policyVersion`, and `evaluatedAtIso` of the deciding policy.
138
+ */
111
139
  static policyDenied(input) {
112
140
  return new AuthorizationError({
113
141
  message: "Action denied by policy",
@@ -121,6 +149,13 @@ var AuthorizationError = class AuthorizationError extends AppError {
121
149
  ...extractAppErrorOptions(input)
122
150
  });
123
151
  }
152
+ /**
153
+ * The actor lacks a required role or capability. The expected
154
+ * {@link AuthorizationRequirement} is recorded so the boundary can tell the
155
+ * caller precisely what grant is missing.
156
+ *
157
+ * @param input - Factory options plus the `required` role/capability/scope.
158
+ */
124
159
  static missingCapability(input) {
125
160
  return new AuthorizationError({
126
161
  message: "Insufficient capability to perform the action",
@@ -133,6 +168,14 @@ var AuthorizationError = class AuthorizationError extends AppError {
133
168
  ...extractAppErrorOptions(input)
134
169
  });
135
170
  }
171
+ /**
172
+ * The actor may perform the action in general, but not on *this* target —
173
+ * the resource falls outside the actor's permitted scope (e.g. a different
174
+ * school or tenant than the one they are bound to).
175
+ *
176
+ * @param input - Factory options plus the optional `required` scope that the
177
+ * target failed to satisfy.
178
+ */
136
179
  static outOfScope(input) {
137
180
  return new AuthorizationError({
138
181
  message: "Action outside the allowed scope",
@@ -163,25 +206,18 @@ function extractMetadataOnly(input) {
163
206
  return metadata;
164
207
  }
165
208
  //#endregion
166
- //#region src/core/errors/business-rule-violation-error.ts
167
- var BusinessRuleViolationError = class extends AppError {
168
- constructor(rule, message, detail, options) {
169
- const baseMetadata = detail ? {
170
- rule,
171
- detail
172
- } : { rule };
173
- const mergedMetadata = options?.metadata ? {
174
- ...baseMetadata,
175
- ...options.metadata
176
- } : baseMetadata;
177
- super(message, ErrorCodes.businessRuleViolation, {
178
- ...options,
179
- metadata: mergedMetadata
180
- });
181
- }
182
- };
183
- //#endregion
184
209
  //#region src/core/errors/conflict-error.ts
210
+ /**
211
+ * Base for errors that signal a state conflict — the request collides with data
212
+ * that already exists. Common at an HTTP 409 boundary.
213
+ *
214
+ * The discriminating {@link kind} lets a handler branch on *why* it conflicted
215
+ * (an already-existing record, a domain duplicate, or a raw storage uniqueness
216
+ * breach) without `instanceof` chains. Abstract: construct one of the concrete
217
+ * subclasses through its `detected(...)` factory, which fills in the right code,
218
+ * message, and metadata. Instances are frozen, so a conflict error can be shared
219
+ * and rethrown without risk of tampering.
220
+ */
185
221
  var ConflictError = class extends AppError {
186
222
  constructor(params) {
187
223
  super(params.message, params.code, {
@@ -195,6 +231,11 @@ var ConflictError = class extends AppError {
195
231
  Object.freeze(this);
196
232
  }
197
233
  };
234
+ /**
235
+ * The operation cannot proceed because a matching record already exists —
236
+ * e.g. creating something whose natural key is already taken. Construct via
237
+ * {@link AlreadyExistsError.detected}.
238
+ */
198
239
  var AlreadyExistsError = class AlreadyExistsError extends ConflictError {
199
240
  constructor(input) {
200
241
  super({
@@ -204,6 +245,14 @@ var AlreadyExistsError = class AlreadyExistsError extends ConflictError {
204
245
  ...input
205
246
  });
206
247
  }
248
+ /**
249
+ * Builds an {@link AlreadyExistsError} for a detected collision. `operation`
250
+ * defaults to `"create"` and a generic retry `hint` is supplied when none is
251
+ * given, so the error is actionable even from a minimal call site.
252
+ *
253
+ * @param input - The conflicting entity plus optional non-sensitive context
254
+ * (operation, field, existing id, value hash/preview, correlation ids).
255
+ */
207
256
  static detected(input) {
208
257
  return new AlreadyExistsError({
209
258
  metadata: {
@@ -223,6 +272,13 @@ var AlreadyExistsError = class AlreadyExistsError extends ConflictError {
223
272
  });
224
273
  }
225
274
  };
275
+ /**
276
+ * A domain-level duplicate: the data being written would duplicate an existing
277
+ * record under the model's own uniqueness rules. Use this when the duplication
278
+ * is recognized in the domain, as opposed to a raw DB constraint
279
+ * ({@link UniqueConstraintViolationError}). Construct via
280
+ * {@link DuplicateError.detected}.
281
+ */
226
282
  var DuplicateError = class DuplicateError extends ConflictError {
227
283
  constructor(input) {
228
284
  super({
@@ -232,6 +288,13 @@ var DuplicateError = class DuplicateError extends ConflictError {
232
288
  ...input
233
289
  });
234
290
  }
291
+ /**
292
+ * Builds a {@link DuplicateError} for a detected duplicate, defaulting to a
293
+ * "adjust the data so it does not duplicate" hint when none is provided.
294
+ *
295
+ * @param input - The conflicting entity plus optional non-sensitive context
296
+ * (field, constraint name, existing id, value hash/preview, correlation ids).
297
+ */
235
298
  static detected(input) {
236
299
  return new DuplicateError({
237
300
  metadata: {
@@ -252,6 +315,14 @@ var DuplicateError = class DuplicateError extends ConflictError {
252
315
  });
253
316
  }
254
317
  };
318
+ /**
319
+ * A uniqueness breach surfaced by the storage layer itself (a database unique
320
+ * index), carrying the raw {@link UniqueConstraintViolation} (constraint, table,
321
+ * columns). Keep this distinct from {@link DuplicateError}: this one is the
322
+ * infrastructure signal; translate it into a domain duplicate at the boundary
323
+ * with {@link translateUniqueViolationToDuplicate} when the model should own the
324
+ * message. Construct via {@link UniqueConstraintViolationError.detected}.
325
+ */
255
326
  var UniqueConstraintViolationError = class UniqueConstraintViolationError extends ConflictError {
256
327
  constructor(input) {
257
328
  super({
@@ -261,6 +332,13 @@ var UniqueConstraintViolationError = class UniqueConstraintViolationError extend
261
332
  ...input
262
333
  });
263
334
  }
335
+ /**
336
+ * Builds a {@link UniqueConstraintViolationError} from the constraint details
337
+ * a driver reports, packaging them into a nested `violation` payload.
338
+ *
339
+ * @param input - The entity plus the offending constraint name, table, and
340
+ * columns, and optional correlation ids.
341
+ */
264
342
  static detected(input) {
265
343
  return new UniqueConstraintViolationError({
266
344
  metadata: {
@@ -283,6 +361,17 @@ var UniqueConstraintViolationError = class UniqueConstraintViolationError extend
283
361
  });
284
362
  }
285
363
  };
364
+ /**
365
+ * Translates a raw storage {@link UniqueConstraintViolation} into a domain-level
366
+ * {@link DuplicateError}. This is the seam where an infrastructure concern (a DB
367
+ * unique index firing) is re-expressed in the language of the model, so callers
368
+ * upstream catch a `DuplicateError` and never have to know a database was
369
+ * involved.
370
+ *
371
+ * @param input - The entity, the driver-reported `violation`, and optional
372
+ * request context (`ctx`) carrying correlation ids and a clock instant.
373
+ * @returns A {@link DuplicateError} carrying the constraint name as its field hint.
374
+ */
286
375
  function translateUniqueViolationToDuplicate(input) {
287
376
  return DuplicateError.detected({
288
377
  entity: input.entity,
@@ -453,6 +542,18 @@ function resolveKeyIdentity(key, keyHash, keyPreview) {
453
542
  }
454
543
  //#endregion
455
544
  //#region src/core/errors/integration-error.ts
545
+ /**
546
+ * Raised when a call to an external provider fails — a timeout, an unreachable
547
+ * endpoint, or a malformed response. This is the boundary error that separates
548
+ * "our code is fine, the outside world misbehaved" from internal faults, which
549
+ * matters for retry and alerting decisions.
550
+ *
551
+ * Every instance records the `provider`, the `operation`, and timing
552
+ * (`startedAtIso` / `durationMs`) so failures are correlatable across services
553
+ * and a slow dependency is visible in the metadata. The discriminating
554
+ * {@link reason} lets callers decide whether a failure is worth retrying.
555
+ * Instances are frozen; construct through the static factories.
556
+ */
456
557
  var IntegrationError = class IntegrationError extends AppError {
457
558
  constructor(params) {
458
559
  const { message, code, metadata, ...rest } = params;
@@ -463,6 +564,10 @@ var IntegrationError = class IntegrationError extends AppError {
463
564
  this.reason = params.reason;
464
565
  Object.freeze(this);
465
566
  }
567
+ /**
568
+ * The provider did not respond within the allotted time. Usually retryable,
569
+ * since a timeout leaves the outcome unknown rather than known-failed.
570
+ */
466
571
  static timeout(options) {
467
572
  return new IntegrationError({
468
573
  message: "Timeout while integrating with external provider",
@@ -475,6 +580,10 @@ var IntegrationError = class IntegrationError extends AppError {
475
580
  ...pickAppErrorFields(options)
476
581
  });
477
582
  }
583
+ /**
584
+ * The provider could not be reached at all (connection refused, DNS
585
+ * failure, network partition) — the request never landed.
586
+ */
478
587
  static unreachable(options) {
479
588
  return new IntegrationError({
480
589
  message: "Provider unavailable",
@@ -487,6 +596,11 @@ var IntegrationError = class IntegrationError extends AppError {
487
596
  ...pickAppErrorFields(options)
488
597
  });
489
598
  }
599
+ /**
600
+ * The provider answered, but with something the system cannot use — an
601
+ * unexpected status, an unparsable body, or a contract mismatch. Unlike a
602
+ * timeout this is a definite failure, so blind retries rarely help.
603
+ */
490
604
  static badResponse(options) {
491
605
  return new IntegrationError({
492
606
  message: "Unexpected response from provider",
@@ -546,24 +660,6 @@ var LegacyIncompatibleError = class extends AppError {
546
660
  }
547
661
  };
548
662
  //#endregion
549
- //#region src/core/errors/not-found-error.ts
550
- var NotFoundError = class extends AppError {
551
- constructor(resource, criteria, options) {
552
- const baseMetadata = criteria ? {
553
- resource,
554
- criteria
555
- } : { resource };
556
- const mergedMetadata = options?.metadata ? {
557
- ...baseMetadata,
558
- ...options.metadata
559
- } : baseMetadata;
560
- super(`${resource} not found`, ErrorCodes.notFound, {
561
- ...options,
562
- metadata: mergedMetadata
563
- });
564
- }
565
- };
566
- //#endregion
567
663
  //#region src/core/errors/serialization-error.ts
568
664
  var SerializationError = class extends AppError {
569
665
  constructor(input) {
@@ -735,7 +831,25 @@ function evaluateTemporalWindow(input) {
735
831
  }
736
832
  //#endregion
737
833
  //#region src/core/errors/validation-error.ts
834
+ /**
835
+ * Raised when a single input fails a validation rule — the canonical
836
+ * "this field is wrong, and here is why" error.
837
+ *
838
+ * It pins the offending `field` and a machine-readable `validationCode` into
839
+ * the metadata (merged ahead of any caller-supplied metadata) so an API layer
840
+ * can map the failure straight onto a form field without parsing the message.
841
+ * The fixed {@link ErrorCodes.validation} code lets callers catch all
842
+ * validation failures uniformly while still distinguishing the specific rule
843
+ * through `validationCode`.
844
+ */
738
845
  var ValidationError = class extends AppError {
846
+ /**
847
+ * @param field - The input that failed validation; surfaced as `metadata.field`.
848
+ * @param code - The specific rule that was violated; surfaced as `metadata.validationCode`.
849
+ * @param message - The developer-facing description of the failure.
850
+ * @param options - Optional cause, correlation ids, and extra metadata
851
+ * (merged over the field/code metadata, never overwriting it by accident).
852
+ */
739
853
  constructor(field, code, message, options) {
740
854
  const baseMetadata = {
741
855
  field: field.value,
@@ -752,6 +866,6 @@ var ValidationError = class extends AppError {
752
866
  }
753
867
  };
754
868
  //#endregion
755
- export { UniqueConstraintViolationError as C, AuthenticationError as D, AuthorizationError as E, DuplicateError as S, BusinessRuleViolationError as T, IdempotencyKeyMissingError as _, evaluateTemporalWindow as a, AlreadyExistsError as b, SerializationInError as c, safePreview as d, NotFoundError as f, IdempotencyInProgressError as g, IdempotencyError as h, TemporalError as i, SerializationMessages as l, IntegrationError as m, ExpiredError as n, SerializationCodes as o, LegacyIncompatibleError as p, NotYetValidError as r, SerializationError as s, ValidationError as t, SerializationOutError as u, IdempotencyPayloadMismatchError as v, translateUniqueViolationToDuplicate as w, ConflictError as x, IdempotencyReplayNotSupportedError as y };
869
+ export { translateUniqueViolationToDuplicate as C, UniqueConstraintViolationError as S, AuthenticationError as T, IdempotencyPayloadMismatchError as _, evaluateTemporalWindow as a, ConflictError as b, SerializationInError as c, safePreview as d, LegacyIncompatibleError as f, IdempotencyKeyMissingError as g, IdempotencyInProgressError as h, TemporalError as i, SerializationMessages as l, IdempotencyError as m, ExpiredError as n, SerializationCodes as o, IntegrationError as p, NotYetValidError as r, SerializationError as s, ValidationError as t, SerializationOutError as u, IdempotencyReplayNotSupportedError as v, AuthorizationError as w, DuplicateError as x, AlreadyExistsError as y };
756
870
 
757
871
  //# sourceMappingURL=validation-error.js.map