@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.
- package/KIT_CONTEXT.md +67 -0
- package/README.md +128 -1
- package/dist/app-error.d.ts +108 -0
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.js +2 -0
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +4 -2
- package/dist/gate-engine-registry.d.ts +80 -0
- package/dist/gate-v1-payload.schema.js +2 -28
- package/dist/gate-v1-payload.schema.js.map +1 -1
- package/dist/hashing.js +49 -0
- package/dist/hashing.js.map +1 -0
- package/dist/index.d.ts +122 -45
- package/dist/index.js +104 -85
- package/dist/index.js.map +1 -1
- package/dist/not-found-error.js +54 -0
- package/dist/not-found-error.js.map +1 -0
- package/dist/outcome.js +1 -1
- package/dist/parse-gate-payload.d.ts +3 -78
- package/dist/path.d.ts +89 -0
- package/dist/policies/engines/index.d.ts +2 -1
- package/dist/policies/index.d.ts +4 -2
- package/dist/policy-service.d.ts +102 -86
- package/dist/policy-service.js +78 -16
- package/dist/policy-service.js.map +1 -1
- package/dist/temporal-guards.js +30 -0
- package/dist/temporal-guards.js.map +1 -0
- package/dist/temporal-use-case.d.ts +309 -0
- package/dist/temporal-use-case.js +284 -0
- package/dist/temporal-use-case.js.map +1 -0
- package/dist/validation-code.js +34 -47
- package/dist/validation-code.js.map +1 -1
- package/dist/validation-error.d.ts +171 -74
- package/dist/validation-error.js +152 -38
- package/dist/validation-error.js.map +1 -1
- package/dist/validation-exception.js +18 -0
- package/dist/validation-exception.js.map +1 -0
- package/meta.json +4 -2
- package/package.json +5 -1
- package/src/application/index.ts +1 -0
- package/src/core/application/commands/index.ts +1 -0
- package/src/core/application/index.ts +12 -3
- package/src/core/application/ports/index.ts +2 -2
- package/src/core/application/ports/policy-port.ts +13 -3
- package/src/core/application/ports/repository.port.ts +37 -1
- package/src/core/application/temporal/temporal-use-case.ts +14 -2
- package/src/core/application/use-case.ts +133 -2
- package/src/core/domain/entity.ts +71 -0
- package/src/core/domain/value-object.ts +41 -0
- package/src/core/errors/app-error.ts +33 -0
- package/src/core/errors/authorization-error.ts +43 -0
- package/src/core/errors/conflict-error.ts +69 -0
- package/src/core/errors/integration-error.ts +25 -0
- package/src/core/errors/not-found-error.ts +14 -0
- package/src/core/errors/validation-error.ts +18 -0
- package/src/core/index.ts +1 -10
- package/src/core/policies/catalog/policy-catalog.ts +36 -0
- package/src/core/policies/resolver/policy-resolver.ts +10 -0
- package/src/core/policies/service/policy-service.ts +53 -0
- package/src/examples/application/cancel-order.example.ts +124 -0
- package/src/examples/application/in-memory-account-repository.example.ts +73 -0
- package/src/version.ts +1 -1
package/dist/validation-error.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|