@cullet/erp-core 1.2.0 → 1.4.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 (218) hide show
  1. package/KIT_CONTEXT.md +5 -3
  2. package/README.md +160 -0
  3. package/dist/abac/index.cjs +7 -0
  4. package/dist/abac/index.d.cts +2 -0
  5. package/dist/abac/index.d.ts +2 -0
  6. package/dist/abac/index.js +2 -0
  7. package/dist/app-error.cjs +193 -0
  8. package/dist/app-error.cjs.map +1 -0
  9. package/dist/app-error.d.cts +108 -0
  10. package/dist/app-error.js +170 -0
  11. package/dist/app-error.js.map +1 -0
  12. package/dist/application/index.cjs +27 -0
  13. package/dist/application/index.d.cts +3 -0
  14. package/dist/application/index.d.ts +2 -1
  15. package/dist/application/index.js +2 -1
  16. package/dist/authorization-error.cjs +157 -0
  17. package/dist/authorization-error.cjs.map +1 -0
  18. package/dist/authorization-error.d.cts +113 -0
  19. package/dist/authorization-error.d.ts +113 -0
  20. package/dist/authorization-error.js +152 -0
  21. package/dist/authorization-error.js.map +1 -0
  22. package/dist/authorizer.port.d.cts +130 -0
  23. package/dist/authorizer.port.d.ts +130 -0
  24. package/dist/composite-authorizer.d.cts +216 -0
  25. package/dist/composite-authorizer.d.ts +216 -0
  26. package/dist/condition-evaluator.cjs +565 -0
  27. package/dist/condition-evaluator.cjs.map +1 -0
  28. package/dist/condition-evaluator.js +536 -0
  29. package/dist/condition-evaluator.js.map +1 -0
  30. package/dist/core-config.d.cts +172 -0
  31. package/dist/{gate-types.d.ts → core-config.d.ts} +78 -77
  32. package/dist/domain/index.cjs +17 -0
  33. package/dist/domain/index.d.cts +4 -0
  34. package/dist/domain/index.d.ts +2 -1
  35. package/dist/domain/index.js +2 -1
  36. package/dist/domain-event-contracts.cjs +34 -0
  37. package/dist/domain-event-contracts.cjs.map +1 -0
  38. package/dist/domain-event-contracts.d.cts +27 -0
  39. package/dist/domain-event-contracts.js +2 -1
  40. package/dist/domain-event-contracts.js.map +1 -1
  41. package/dist/domain-exception.cjs +17 -0
  42. package/dist/domain-exception.cjs.map +1 -0
  43. package/dist/domain-exception.d.cts +7 -0
  44. package/dist/entity.cjs +126 -0
  45. package/dist/entity.cjs.map +1 -0
  46. package/dist/entity.js +115 -0
  47. package/dist/entity.js.map +1 -0
  48. package/dist/errors/index.cjs +43 -0
  49. package/dist/errors/index.d.cts +4 -0
  50. package/dist/errors/index.d.ts +2 -1
  51. package/dist/errors/index.js +4 -2
  52. package/dist/exceptions/index.cjs +17 -0
  53. package/dist/exceptions/index.d.cts +5 -0
  54. package/dist/exceptions/validation-field.cjs +3 -0
  55. package/dist/exceptions/validation-field.d.cts +2 -0
  56. package/dist/gate-engine-registry.cjs +309 -0
  57. package/dist/gate-engine-registry.cjs.map +1 -0
  58. package/dist/gate-engine-registry.d.cts +82 -0
  59. package/dist/gate-engine-registry.d.ts +3 -2
  60. package/dist/gate-engine-registry.js +2 -1
  61. package/dist/gate-engine-registry.js.map +1 -1
  62. package/dist/gate-v1-payload.schema.cjs +76 -0
  63. package/dist/gate-v1-payload.schema.cjs.map +1 -0
  64. package/dist/gate-v1-payload.schema.js +1 -533
  65. package/dist/gate-v1-payload.schema.js.map +1 -1
  66. package/dist/hashing.cjs +66 -0
  67. package/dist/hashing.cjs.map +1 -0
  68. package/dist/immutable.cjs +77 -0
  69. package/dist/immutable.cjs.map +1 -0
  70. package/dist/immutable.d.cts +6 -0
  71. package/dist/index.cjs +181 -0
  72. package/dist/index.cjs.map +1 -0
  73. package/dist/index.d.cts +37 -0
  74. package/dist/index.d.ts +19 -12
  75. package/dist/index.js +15 -9
  76. package/dist/index.js.map +1 -1
  77. package/dist/invalid-state-transition-exception.cjs +47 -0
  78. package/dist/invalid-state-transition-exception.cjs.map +1 -0
  79. package/dist/invariant-violation-exception.cjs +16 -0
  80. package/dist/invariant-violation-exception.cjs.map +1 -0
  81. package/dist/not-found-error.cjs +65 -0
  82. package/dist/not-found-error.cjs.map +1 -0
  83. package/dist/not-found-error.js +1 -1
  84. package/dist/outcome.cjs +97 -0
  85. package/dist/outcome.cjs.map +1 -0
  86. package/dist/outcome.d.cts +58 -0
  87. package/dist/outcome.d.ts +1 -83
  88. package/dist/parse-gate-payload.d.cts +62 -0
  89. package/dist/parse-gate-payload.d.ts +2 -2
  90. package/dist/path.d.cts +90 -0
  91. package/dist/path.d.ts +2 -2
  92. package/dist/plugin.cjs +79 -0
  93. package/dist/plugin.cjs.map +1 -0
  94. package/dist/plugin.d.cts +85 -0
  95. package/dist/plugins/index.cjs +3 -0
  96. package/dist/plugins/index.d.cts +2 -0
  97. package/dist/policies/engines/index.cjs +10 -0
  98. package/dist/policies/engines/index.d.cts +4 -0
  99. package/dist/policies/engines/index.d.ts +1 -1
  100. package/dist/policies/engines/v1/gate/index.cjs +92 -0
  101. package/dist/policies/engines/v1/gate/index.cjs.map +1 -0
  102. package/dist/policies/engines/v1/gate/index.d.cts +121 -0
  103. package/dist/policies/engines/v1/gate/index.d.ts +2 -2
  104. package/dist/policies/engines/v1/gate/index.js +2 -1
  105. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  106. package/dist/policies/index.cjs +41 -0
  107. package/dist/policies/index.d.cts +8 -0
  108. package/dist/policies/index.d.ts +3 -2
  109. package/dist/policies/index.js +2 -2
  110. package/dist/policy-bridge.cjs +437 -0
  111. package/dist/policy-bridge.cjs.map +1 -0
  112. package/dist/policy-bridge.d.cts +185 -0
  113. package/dist/policy-bridge.d.ts +185 -0
  114. package/dist/policy-bridge.js +396 -0
  115. package/dist/policy-bridge.js.map +1 -0
  116. package/dist/policy-service.cjs +1190 -0
  117. package/dist/policy-service.cjs.map +1 -0
  118. package/dist/policy-service.d.cts +559 -0
  119. package/dist/policy-service.d.ts +2 -2
  120. package/dist/policy-service.js +239 -239
  121. package/dist/policy-service.js.map +1 -1
  122. package/dist/rbac/index.cjs +9 -0
  123. package/dist/rbac/index.d.cts +3 -0
  124. package/dist/rbac/index.d.ts +3 -0
  125. package/dist/rbac/index.js +2 -0
  126. package/dist/requested-by.cjs +54 -0
  127. package/dist/requested-by.cjs.map +1 -0
  128. package/dist/requested-by.d.cts +25 -0
  129. package/dist/requested-by.d.ts +25 -0
  130. package/dist/requested-by.js +49 -0
  131. package/dist/requested-by.js.map +1 -0
  132. package/dist/result/index.cjs +7 -0
  133. package/dist/result/index.d.cts +3 -0
  134. package/dist/result/index.d.ts +2 -1
  135. package/dist/result.cjs +135 -0
  136. package/dist/result.cjs.map +1 -0
  137. package/dist/result.d.cts +84 -0
  138. package/dist/result.d.ts +84 -0
  139. package/dist/rule.cjs +327 -0
  140. package/dist/rule.cjs.map +1 -0
  141. package/dist/rule.js +298 -0
  142. package/dist/rule.js.map +1 -0
  143. package/dist/ruleset-registry.cjs +47 -0
  144. package/dist/ruleset-registry.cjs.map +1 -0
  145. package/dist/rulesets/index.cjs +3 -0
  146. package/dist/rulesets/index.d.cts +2 -0
  147. package/dist/temporal-guards.cjs +32 -0
  148. package/dist/temporal-guards.cjs.map +1 -0
  149. package/dist/temporal-use-case.cjs +139 -0
  150. package/dist/temporal-use-case.cjs.map +1 -0
  151. package/dist/temporal-use-case.d.cts +282 -0
  152. package/dist/temporal-use-case.d.ts +5 -27
  153. package/dist/temporal-use-case.js +2 -48
  154. package/dist/temporal-use-case.js.map +1 -1
  155. package/dist/unexpected-error.cjs +19 -0
  156. package/dist/unexpected-error.cjs.map +1 -0
  157. package/dist/unexpected-error.js +2 -167
  158. package/dist/unexpected-error.js.map +1 -1
  159. package/dist/use-case.cjs +96 -0
  160. package/dist/use-case.cjs.map +1 -0
  161. package/dist/uuid-identifier.cjs +65 -0
  162. package/dist/uuid-identifier.cjs.map +1 -0
  163. package/dist/uuid-identifier.d.cts +147 -0
  164. package/dist/uuid-identifier.d.ts +2 -85
  165. package/dist/validation-code.cjs +60 -0
  166. package/dist/validation-code.cjs.map +1 -0
  167. package/dist/validation-code.d.cts +23 -0
  168. package/dist/validation-error.cjs +887 -0
  169. package/dist/validation-error.cjs.map +1 -0
  170. package/dist/validation-error.d.cts +681 -0
  171. package/dist/validation-error.d.ts +2 -98
  172. package/dist/validation-error.js +7 -134
  173. package/dist/validation-error.js.map +1 -1
  174. package/dist/validation-exception.cjs +41 -0
  175. package/dist/validation-exception.cjs.map +1 -0
  176. package/dist/validation-exception.d.cts +50 -0
  177. package/dist/validation-field.cjs +28 -0
  178. package/dist/validation-field.cjs.map +1 -0
  179. package/dist/validation-field.d.cts +12 -0
  180. package/dist/value-object-ruleset.contracts.d.cts +36 -0
  181. package/dist/value-object.cjs +85 -0
  182. package/dist/value-object.cjs.map +1 -0
  183. package/dist/value-object.d.cts +89 -0
  184. package/dist/value-object.d.ts +89 -0
  185. package/dist/value-object.js +1 -112
  186. package/dist/value-object.js.map +1 -1
  187. package/dist/version.d.cts +10 -0
  188. package/dist/versioning/index.cjs +7 -0
  189. package/dist/versioning/index.d.cts +3 -0
  190. package/meta.json +19 -4
  191. package/package.json +173 -28
  192. package/src/abac/index.ts +1 -0
  193. package/src/core/abac/abac-request.ts +29 -0
  194. package/src/core/abac/attributes.ts +39 -0
  195. package/src/core/abac/authorizer.ts +108 -0
  196. package/src/core/abac/combining.ts +63 -0
  197. package/src/core/abac/composite-authorizer.ts +45 -0
  198. package/src/core/abac/domain/policy-set.ts +52 -0
  199. package/src/core/abac/domain/rule.ts +197 -0
  200. package/src/core/abac/index.ts +21 -0
  201. package/src/core/application/ports/abac-authorizer.port.ts +20 -0
  202. package/src/core/application/ports/authorizer.port.ts +22 -0
  203. package/src/core/errors/authorization-error.ts +26 -0
  204. package/src/core/errors/error-codes.ts +1 -0
  205. package/src/core/index.ts +7 -0
  206. package/src/core/rbac/access-request.ts +32 -0
  207. package/src/core/rbac/authorizer.ts +91 -0
  208. package/src/core/rbac/domain/grant.ts +96 -0
  209. package/src/core/rbac/domain/permission-set.ts +67 -0
  210. package/src/core/rbac/domain/permission.ts +119 -0
  211. package/src/core/rbac/domain/role.ts +101 -0
  212. package/src/core/rbac/domain/scope.ts +84 -0
  213. package/src/core/rbac/index.ts +15 -0
  214. package/src/core/rbac/policy-bridge.ts +44 -0
  215. package/src/examples/application/authorize-cancel-order-abac.example.ts +107 -0
  216. package/src/examples/application/authorize-cancel-order.example.ts +101 -0
  217. package/src/rbac/index.ts +1 -0
  218. package/src/version.ts +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.js","names":[],"sources":["../src/core/abac/attributes.ts","../src/core/abac/combining.ts","../src/core/abac/authorizer.ts","../src/core/abac/composite-authorizer.ts","../src/core/abac/domain/policy-set.ts","../src/core/abac/domain/rule.ts"],"sourcesContent":["import type { PolicyContext } from \"../policies/engines/gate-types.js\";\n\nimport type { AbacRequest } from \"./abac-request.js\";\n\n/**\n * Flattens an {@link AbacRequest} into the nested {@link PolicyContext} the\n * condition evaluator reads (it resolves dotted fields like `resource.status` by\n * walking nested objects). The actor lands as `subject.id`; the four attribute\n * bags nest under `subject` / `resource` / `action` / `env`; a `resource`\n * reference adds `resource.type` / `resource.id`. The actor id and the resource\n * reference win over any same-named keys in the attribute bags.\n *\n * Consumers fold dynamic facts (e.g. RBAC roles/permissions derived from grants,\n * or a computed `ownerIsActor` flag) into `attributes.subject` / `resource` so\n * rules can reference `subject.roles`, `resource.ownerIsActor`, and the like.\n */\nexport function abacContext(request: AbacRequest): PolicyContext {\n const attributes = request.attributes;\n\n const subject: Record<string, unknown> = {\n ...attributes.subject,\n id: request.actor.raw,\n };\n\n const resource: Record<string, unknown> = { ...attributes.resource };\n if (request.resource) {\n resource.type = request.resource.type;\n if (request.resource.id !== undefined) {\n resource.id = request.resource.id;\n }\n }\n\n return {\n subject,\n resource,\n action: { ...attributes.action },\n env: { ...attributes.environment },\n };\n}\n","import type { AbacRule, RuleEffect } from \"./domain/rule.js\";\n\n/**\n * How an {@link AbacPolicySet} resolves several applicable rules into one effect.\n *\n * - `deny-overrides` (default, secure): any applicable DENY wins; otherwise the\n * first PERMIT; otherwise the set's default effect.\n * - `permit-overrides`: any applicable PERMIT wins; otherwise the first DENY;\n * otherwise the default effect.\n * - `first-applicable`: the effect of the first rule that matches; otherwise the\n * default effect.\n */\nexport type CombiningAlgorithm =\n | \"deny-overrides\"\n | \"permit-overrides\"\n | \"first-applicable\";\n\n/** The resolved decision plus the rule (if any) that produced it. */\nexport interface CombineResult {\n readonly effect: RuleEffect;\n readonly decidingRule?: AbacRule;\n}\n\nfunction firstWithEffect(\n rules: readonly AbacRule[],\n effect: RuleEffect,\n): AbacRule | undefined {\n return rules.find((rule) => rule.effect === effect);\n}\n\n/**\n * Applies `algorithm` to the already-matched `applicable` rules, falling back to\n * `defaultEffect` when the algorithm reaches no rule-backed decision. Pure and\n * total; the returned `decidingRule` is absent exactly when `defaultEffect` was\n * used.\n */\nexport function combine(\n algorithm: CombiningAlgorithm,\n applicable: readonly AbacRule[],\n defaultEffect: RuleEffect,\n): CombineResult {\n switch (algorithm) {\n case \"deny-overrides\": {\n const deny = firstWithEffect(applicable, \"DENY\");\n if (deny) return { effect: \"DENY\", decidingRule: deny };\n const permit = firstWithEffect(applicable, \"PERMIT\");\n if (permit) return { effect: \"PERMIT\", decidingRule: permit };\n return { effect: defaultEffect };\n }\n case \"permit-overrides\": {\n const permit = firstWithEffect(applicable, \"PERMIT\");\n if (permit) return { effect: \"PERMIT\", decidingRule: permit };\n const deny = firstWithEffect(applicable, \"DENY\");\n if (deny) return { effect: \"DENY\", decidingRule: deny };\n return { effect: defaultEffect };\n }\n case \"first-applicable\": {\n const first = applicable[0];\n if (first) return { effect: first.effect, decidingRule: first };\n return { effect: defaultEffect };\n }\n }\n}\n","import { type CoreConfig, coreConfig } from \"../config/index.js\";\nimport { AuthorizationError } from \"../errors/authorization-error.js\";\nimport { ConditionEvaluatorV1 } from \"../policies/engines/v1/condition-evaluator.js\";\nimport { Result } from \"../result/result.js\";\n\nimport type { AbacRequest } from \"./abac-request.js\";\nimport { abacContext } from \"./attributes.js\";\nimport { combine, type CombiningAlgorithm } from \"./combining.js\";\nimport type { AbacPolicySet } from \"./domain/policy-set.js\";\nimport type { AbacRule } from \"./domain/rule.js\";\n\n// ABAC reuses the gate engine's v1 condition matcher; the version stamps the\n// evaluation reports the shared reporter emits.\nconst ENGINE_VERSION = 1;\n\n/** The successful decision returned by {@link AbacAuthorizer.authorize}. */\ninterface AbacDecision {\n /** The audited business action that was allowed. */\n readonly action: string;\n readonly effect: \"PERMIT\";\n /** The id of the rule that permitted, or `\"<default>\"` when the set's default did. */\n readonly matchedRule: string;\n readonly algorithm: CombiningAlgorithm;\n /** When the decision was made, ISO-8601. */\n readonly grantedAtIso: string;\n}\n\n/**\n * The pure ABAC decisor. Given an {@link AbacRequest} and an\n * {@link AbacPolicySet}, it flattens the request's attributes into a context,\n * matches each rule's condition with the gate engine's pure condition evaluator\n * (reused, not reimplemented), resolves the effects through the set's combining\n * algorithm, and returns a {@link Result} — never throwing, mirroring the\n * \"errors as values\" contract of `RbacAuthorizer`/`GateEngineV1`.\n *\n * A denial maps to {@link AuthorizationError.policyDenied}, attributed to the\n * deciding rule's `id`/`version`; a condition-evaluation failure (a missing\n * attribute, a wrong-typed operand) fails closed as\n * {@link AuthorizationError.forbidden}. Loading the dynamic attributes is the\n * consumer's job (behind an `AbacAuthorizerPort` adapter); this class only decides.\n */\nclass AbacAuthorizer {\n private readonly coreConfig: CoreConfig;\n\n constructor(params: { readonly coreConfig?: CoreConfig } = {}) {\n this.coreConfig = params.coreConfig ?? coreConfig;\n }\n\n authorize(\n request: AbacRequest,\n policies: AbacPolicySet,\n ): Result<AbacDecision, AuthorizationError> {\n const context = abacContext(request);\n const evaluator = new ConditionEvaluatorV1(\n context,\n this.coreConfig.getConditionEvaluationOptions(ENGINE_VERSION),\n );\n\n const applicable: AbacRule[] = [];\n for (const rule of policies.rules) {\n const matched = evaluator.evaluate(rule.condition);\n if (matched.isErr()) {\n // Fail closed: a technical evaluation error is never a silent PERMIT.\n return Result.err(\n AuthorizationError.forbidden({\n action: request.action,\n resource: request.resource,\n actor: { userId: request.actor.raw },\n details: matched.errorOrNull() ?? undefined,\n }),\n );\n }\n if (matched.getOrThrow()) {\n applicable.push(rule);\n }\n }\n\n const { effect, decidingRule } = combine(\n policies.algorithm,\n applicable,\n policies.defaultEffect,\n );\n\n if (effect === \"PERMIT\") {\n return Result.ok({\n action: request.action,\n effect: \"PERMIT\",\n matchedRule: decidingRule?.id ?? \"<default>\",\n algorithm: policies.algorithm,\n grantedAtIso: new Date().toISOString(),\n });\n }\n\n return Result.err(\n AuthorizationError.policyDenied({\n policyId: decidingRule?.id ?? \"<default-deny>\",\n policyVersion: decidingRule?.version ?? 0,\n evaluatedAtIso: new Date().toISOString(),\n action: request.action,\n resource: request.resource,\n actor: { userId: request.actor.raw },\n reasonCode: decidingRule?.id,\n }),\n );\n }\n}\n\nexport { AbacAuthorizer, type AbacDecision };\n","import { Result } from \"../result/result.js\";\nimport type { AbacAuthorizerPort } from \"../application/ports/abac-authorizer.port.js\";\nimport type { AuthorizerPort } from \"../application/ports/authorizer.port.js\";\nimport type { AuthorizationError } from \"../errors/authorization-error.js\";\nimport type { AccessRequest } from \"../rbac/access-request.js\";\n\nimport type { AbacAttributes } from \"./abac-request.js\";\n\n/**\n * The request a {@link CompositeAuthorizer} accepts — one object that satisfies\n * both the RBAC {@link AccessRequest} (its `required`/`scope`) and the ABAC\n * request (its `attributes`).\n */\ntype CompositeAccessRequest = AccessRequest & {\n readonly attributes: AbacAttributes;\n};\n\n/**\n * Runs a coarse RBAC check first, then refines with ABAC — the standard hybrid\n * \"does the actor hold the capability, *and* do the attributes allow it here?\"\n * flow. Short-circuits with the RBAC denial when the actor is not even capable,\n * so the boundary sees the most specific 403.\n *\n * Only the two port interfaces are referenced (both erased at runtime), so this\n * pulls in no RBAC/ABAC engine code of its own — it just sequences the seams the\n * consumer wires up.\n */\nclass CompositeAuthorizer {\n constructor(\n private readonly rbac: AuthorizerPort,\n private readonly abac: AbacAuthorizerPort,\n ) {}\n\n async authorize(\n request: CompositeAccessRequest,\n ): Promise<Result<void, AuthorizationError>> {\n const coarse = await this.rbac.authorize(request);\n if (coarse.isErr()) {\n return coarse;\n }\n return this.abac.authorize(request);\n }\n}\n\nexport { CompositeAuthorizer, type CompositeAccessRequest };\n","import type { CombiningAlgorithm } from \"../combining.js\";\n\nimport type { AbacRule, RuleEffect } from \"./rule.js\";\n\n/**\n * An ordered collection of {@link AbacRule}s plus how to combine them\n * ({@link CombiningAlgorithm}) and what to decide when no rule applies\n * ({@link defaultEffect}). This is where ABAC goes beyond a single gate\n * condition: several PERMIT/DENY rules resolved by an explicit algorithm.\n *\n * Closed by default — the combining algorithm defaults to `\"deny-overrides\"` and\n * the default effect to `\"DENY\"`, so a request matching nothing is denied. A\n * plain holder (not a value object); build through {@link of}.\n */\nclass AbacPolicySet {\n private constructor(\n private readonly _rules: readonly AbacRule[],\n private readonly _algorithm: CombiningAlgorithm,\n private readonly _defaultEffect: RuleEffect,\n ) {\n Object.freeze(this._rules);\n Object.freeze(this);\n }\n\n static of(\n rules: readonly AbacRule[],\n options: {\n readonly algorithm?: CombiningAlgorithm;\n readonly defaultEffect?: RuleEffect;\n } = {},\n ): AbacPolicySet {\n return new AbacPolicySet(\n [...rules],\n options.algorithm ?? \"deny-overrides\",\n options.defaultEffect ?? \"DENY\",\n );\n }\n\n get rules(): readonly AbacRule[] {\n return this._rules;\n }\n\n get algorithm(): CombiningAlgorithm {\n return this._algorithm;\n }\n\n get defaultEffect(): RuleEffect {\n return this._defaultEffect;\n }\n}\n\nexport { AbacPolicySet };\n","import { ValidationCode } from \"../../exceptions/validation-code.js\";\nimport { InvalidValueException } from \"../../exceptions/validation-exception.js\";\nimport { ValidationField } from \"../../exceptions/validation-field.js\";\nimport { ValueObject } from \"../../domain/value-object.js\";\nimport type {\n ConditionNode,\n ConditionOp,\n} from \"../../policies/engines/v1/condition-types.js\";\n\n/** Whether a matching {@link AbacRule} permits or denies the action. */\ntype RuleEffect = \"PERMIT\" | \"DENY\";\n\n/**\n * The serializable shape of a rule: a stable `id`/`version` (surfaced on a\n * denial as `policyId`/`policyVersion`), the {@link RuleEffect}, and the\n * {@link ConditionNode} that decides whether the rule applies to a request's\n * flattened attributes.\n */\ntype AbacRuleProps = {\n readonly id: string;\n readonly version: number;\n readonly effect: RuleEffect;\n readonly condition: ConditionNode;\n readonly description?: string;\n};\n\nconst RULE_FIELD = ValidationField.of(\"abacRule\");\n\nconst CONDITION_OPS = new Set<ConditionOp>([\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"in\",\n \"notIn\",\n \"isNull\",\n \"isNotNull\",\n]);\n\nconst RULE_EFFECTS = new Set<RuleEffect>([\"PERMIT\", \"DENY\"]);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * A single attribute-based rule: an {@link RuleEffect} that takes hold when its\n * {@link ConditionNode} matches the request's flattened attributes. The\n * condition reuses the gate/compute condition DSL (`{ field, op, value }` leaves\n * combined with `and`/`or`/`not`), so a consumer learns one condition language\n * across the whole kit.\n *\n * A zod-free value object: it validates its own shape on construction (throwing\n * {@link InvalidValueException}) and is deep-frozen thereafter. Because rules are\n * authored in TypeScript — trusted data, not untrusted JSON — the structural\n * check is a hand-rolled walk of the condition tree rather than a schema parse.\n * Build one through {@link of}; the constructor is private.\n */\nclass AbacRule extends ValueObject<AbacRuleProps, AbacRuleProps> {\n private constructor(props: AbacRuleProps) {\n super(props);\n this.finalize();\n }\n\n /**\n * Builds a rule, validating that `id` is non-blank, `version` is an integer\n * ≥ 1, `effect` is `\"PERMIT\"`/`\"DENY\"`, and `condition` is a well-formed\n * condition node. Throws {@link InvalidValueException} otherwise.\n */\n static of(props: AbacRuleProps): AbacRule {\n if (typeof props.id !== \"string\" || props.id.trim().length === 0) {\n throw new InvalidValueException(\n RULE_FIELD.nested(\"id\"),\n ValidationCode.BLANK,\n `abac rule id must be a non-empty string, got \"${props.id}\"`,\n );\n }\n\n if (!Number.isInteger(props.version) || props.version < 1) {\n throw new InvalidValueException(\n RULE_FIELD.nested(\"version\"),\n ValidationCode.OUT_OF_RANGE,\n `abac rule version must be an integer >= 1, got \"${props.version}\"`,\n );\n }\n\n if (!RULE_EFFECTS.has(props.effect)) {\n throw new InvalidValueException(\n RULE_FIELD.nested(\"effect\"),\n ValidationCode.INVALID_FORMAT,\n `abac rule effect must be \"PERMIT\" or \"DENY\", got \"${String(props.effect)}\"`,\n );\n }\n\n AbacRule.assertConditionShape(props.condition);\n\n return new AbacRule(props);\n }\n\n // Mirrors what the shared condition evaluator would reject at eval time\n // (empty and/or nodes, malformed leaves) but fails fast at construction.\n private static assertConditionShape(node: unknown): void {\n if (!isPlainObject(node)) {\n AbacRule.rejectCondition(\n \"abac rule condition must be a leaf { field, op } or an { and | or | not } node\",\n );\n }\n\n if (\"and\" in node || \"or\" in node) {\n const key = \"and\" in node ? \"and\" : \"or\";\n const children = node[key];\n if (!Array.isArray(children) || children.length === 0) {\n AbacRule.rejectCondition(\n `abac rule \"${key}\" node must hold a non-empty array of child conditions`,\n );\n }\n for (const child of children) {\n AbacRule.assertConditionShape(child);\n }\n return;\n }\n\n if (\"not\" in node) {\n AbacRule.assertConditionShape(node.not);\n return;\n }\n\n if (\"field\" in node && \"op\" in node) {\n if (\n typeof node.field !== \"string\" ||\n node.field.trim().length === 0\n ) {\n AbacRule.rejectCondition(\n `abac rule condition leaf \"field\" must be a non-empty string, got \"${String(node.field)}\"`,\n );\n }\n if (\n typeof node.op !== \"string\" ||\n !CONDITION_OPS.has(node.op as ConditionOp)\n ) {\n AbacRule.rejectCondition(\n `abac rule condition leaf \"op\" is not a supported operator, got \"${String(node.op)}\"`,\n );\n }\n return;\n }\n\n AbacRule.rejectCondition(\n \"abac rule condition must be a leaf { field, op } or an { and | or | not } node\",\n );\n }\n\n private static rejectCondition(message: string): never {\n throw new InvalidValueException(\n RULE_FIELD.nested(\"condition\"),\n ValidationCode.INVALID_FORMAT,\n message,\n );\n }\n\n get id(): string {\n return this.value.id;\n }\n\n get version(): number {\n return this.value.version;\n }\n\n get effect(): RuleEffect {\n return this.value.effect;\n }\n\n /** The condition tree evaluated against a request's flattened attributes. */\n get condition(): ConditionNode {\n return this.value.condition as ConditionNode;\n }\n\n get description(): string | undefined {\n return this.value.description;\n }\n\n toPrimitive(): AbacRuleProps {\n return {\n id: this.value.id,\n version: this.value.version,\n effect: this.value.effect,\n condition: this.value.condition as ConditionNode,\n ...(this.value.description !== undefined\n ? { description: this.value.description }\n : {}),\n };\n }\n}\n\nexport { AbacRule, type AbacRuleProps, type RuleEffect };\n"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ const require_domain_exception = require("./domain-exception.cjs");
2
+ //#region src/core/domain/rulesets/ruleset-registry.ts
3
+ var RulesetRegistryError = class extends require_domain_exception.DomainException {};
4
+ function parseVersion(id) {
5
+ const atIdx = id.lastIndexOf("@");
6
+ const [major, minor] = id.slice(atIdx + 1).split(".").map(Number);
7
+ return [major, minor];
8
+ }
9
+ var RulesetRegistry = class {
10
+ constructor() {
11
+ this._store = /* @__PURE__ */ new Map();
12
+ this._sealed = false;
13
+ }
14
+ register(ruleset) {
15
+ if (this._sealed) throw new RulesetRegistryError("Registry is sealed. No new rulesets can be registered.");
16
+ if (this._store.has(ruleset.id)) throw new RulesetRegistryError(`Ruleset with id "${ruleset.id}" is already registered.`);
17
+ this._store.set(ruleset.id, ruleset);
18
+ }
19
+ seal() {
20
+ this._sealed = true;
21
+ }
22
+ get(id) {
23
+ const ruleset = this._store.get(id);
24
+ if (!ruleset) throw new RulesetRegistryError(`Ruleset "${id}" not found. Available: [${Array.from(this._store.keys()).join(", ")}]`);
25
+ return ruleset;
26
+ }
27
+ getCurrent(prefix) {
28
+ const matchingEntries = Array.from(this._store.entries()).filter(([key]) => key.startsWith(prefix + "@"));
29
+ if (matchingEntries.length === 0) throw new RulesetRegistryError(`No rulesets found with prefix "${prefix}".`);
30
+ const sorted = matchingEntries.sort(([a], [b]) => {
31
+ const [aMajor, aMinor] = parseVersion(a);
32
+ const [bMajor, bMinor] = parseVersion(b);
33
+ if (aMajor !== bMajor) return aMajor - bMajor;
34
+ return aMinor - bMinor;
35
+ });
36
+ return sorted[sorted.length - 1][1];
37
+ }
38
+ };
39
+ //#endregion
40
+ Object.defineProperty(exports, "RulesetRegistry", {
41
+ enumerable: true,
42
+ get: function() {
43
+ return RulesetRegistry;
44
+ }
45
+ });
46
+
47
+ //# sourceMappingURL=ruleset-registry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleset-registry.cjs","names":["DomainException"],"sources":["../src/core/domain/rulesets/ruleset-registry.ts"],"sourcesContent":["import { DomainException } from \"../../exceptions/domain-exception.js\";\nimport {\n type Ruleset,\n type RulesetRegistry as RulesetRegistryContract,\n} from \"./ruleset.contracts.js\";\n\nclass RulesetRegistryError extends DomainException {}\n\nfunction parseVersion(id: string): [number, number] {\n const atIdx = id.lastIndexOf(\"@\");\n const versionStr = id.slice(atIdx + 1);\n const [major, minor] = versionStr.split(\".\").map(Number);\n return [major, minor];\n}\n\nclass RulesetRegistry implements RulesetRegistryContract {\n private readonly _store = new Map<string, Ruleset>();\n private _sealed = false;\n\n register(ruleset: Ruleset): void {\n if (this._sealed) {\n throw new RulesetRegistryError(\n \"Registry is sealed. No new rulesets can be registered.\",\n );\n }\n if (this._store.has(ruleset.id)) {\n throw new RulesetRegistryError(\n `Ruleset with id \"${ruleset.id}\" is already registered.`,\n );\n }\n this._store.set(ruleset.id, ruleset);\n }\n\n seal(): void {\n this._sealed = true;\n }\n\n get<T extends Ruleset>(id: string): T {\n const ruleset = this._store.get(id);\n if (!ruleset) {\n const available = Array.from(this._store.keys()).join(\", \");\n throw new RulesetRegistryError(\n `Ruleset \"${id}\" not found. Available: [${available}]`,\n );\n }\n return ruleset as T;\n }\n\n getCurrent<T extends Ruleset>(prefix: string): T {\n const matchingEntries = Array.from(this._store.entries()).filter(\n ([key]) => key.startsWith(prefix + \"@\"),\n );\n\n if (matchingEntries.length === 0) {\n throw new RulesetRegistryError(\n `No rulesets found with prefix \"${prefix}\".`,\n );\n }\n\n const sorted = matchingEntries.sort(([a], [b]) => {\n const [aMajor, aMinor] = parseVersion(a);\n const [bMajor, bMinor] = parseVersion(b);\n if (aMajor !== bMajor) return aMajor - bMajor;\n return aMinor - bMinor;\n });\n\n return sorted[sorted.length - 1][1] as T;\n }\n}\n\nexport { RulesetRegistry };\n"],"mappings":";;AAMA,IAAM,uBAAN,cAAmCA,yBAAAA,gBAAgB,CAAC;AAEpD,SAAS,aAAa,IAA8B;CAChD,MAAM,QAAQ,GAAG,YAAY,GAAG;CAEhC,MAAM,CAAC,OAAO,SADK,GAAG,MAAM,QAAQ,CACJ,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;CACvD,OAAO,CAAC,OAAO,KAAK;AACxB;AAEA,IAAM,kBAAN,MAAyD;;gCAC3B,IAAI,IAAqB;iBACjC;;CAElB,SAAS,SAAwB;EAC7B,IAAI,KAAK,SACL,MAAM,IAAI,qBACN,wDACJ;EAEJ,IAAI,KAAK,OAAO,IAAI,QAAQ,EAAE,GAC1B,MAAM,IAAI,qBACN,oBAAoB,QAAQ,GAAG,yBACnC;EAEJ,KAAK,OAAO,IAAI,QAAQ,IAAI,OAAO;CACvC;CAEA,OAAa;EACT,KAAK,UAAU;CACnB;CAEA,IAAuB,IAAe;EAClC,MAAM,UAAU,KAAK,OAAO,IAAI,EAAE;EAClC,IAAI,CAAC,SAED,MAAM,IAAI,qBACN,YAAY,GAAG,2BAFD,MAAM,KAAK,KAAK,OAAO,KAAK,CAAC,EAAE,KAAK,IAEA,EAAE,EACxD;EAEJ,OAAO;CACX;CAEA,WAA8B,QAAmB;EAC7C,MAAM,kBAAkB,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAC,EAAE,QACrD,CAAC,SAAS,IAAI,WAAW,SAAS,GAAG,CAC1C;EAEA,IAAI,gBAAgB,WAAW,GAC3B,MAAM,IAAI,qBACN,kCAAkC,OAAO,GAC7C;EAGJ,MAAM,SAAS,gBAAgB,MAAM,CAAC,IAAI,CAAC,OAAO;GAC9C,MAAM,CAAC,QAAQ,UAAU,aAAa,CAAC;GACvC,MAAM,CAAC,QAAQ,UAAU,aAAa,CAAC;GACvC,IAAI,WAAW,QAAQ,OAAO,SAAS;GACvC,OAAO,SAAS;EACpB,CAAC;EAED,OAAO,OAAO,OAAO,SAAS,GAAG;CACrC;AACJ"}
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_ruleset_registry = require("../ruleset-registry.cjs");
3
+ exports.RulesetRegistry = require_ruleset_registry.RulesetRegistry;
@@ -0,0 +1,2 @@
1
+ import { a as Ruleset, i as InvariantRuleset, n as RulesetRegistry, o as RulesetId, r as CreationRuleset, t as ValueObjectRuleset } from "../value-object-ruleset.contracts.cjs";
2
+ export { CreationRuleset, InvariantRuleset, Ruleset, RulesetId, RulesetRegistry, ValueObjectRuleset };
@@ -0,0 +1,32 @@
1
+ const require_invariant_violation_exception = require("./invariant-violation-exception.cjs");
2
+ //#region src/core/shared/temporal-guards.ts
3
+ function isValidDate(value) {
4
+ return value instanceof Date && !Number.isNaN(value.getTime());
5
+ }
6
+ function cloneDate(date) {
7
+ return new Date(date.getTime());
8
+ }
9
+ function assertValidDate(fieldName, value) {
10
+ if (!isValidDate(value)) throw new require_invariant_violation_exception.InvariantViolationException(`${fieldName} must be a valid Date instance`);
11
+ }
12
+ //#endregion
13
+ Object.defineProperty(exports, "assertValidDate", {
14
+ enumerable: true,
15
+ get: function() {
16
+ return assertValidDate;
17
+ }
18
+ });
19
+ Object.defineProperty(exports, "cloneDate", {
20
+ enumerable: true,
21
+ get: function() {
22
+ return cloneDate;
23
+ }
24
+ });
25
+ Object.defineProperty(exports, "isValidDate", {
26
+ enumerable: true,
27
+ get: function() {
28
+ return isValidDate;
29
+ }
30
+ });
31
+
32
+ //# sourceMappingURL=temporal-guards.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-guards.cjs","names":["InvariantViolationException"],"sources":["../src/core/shared/temporal-guards.ts"],"sourcesContent":["import { InvariantViolationException } from \"../exceptions/invariant-violation-exception.js\";\n\nfunction isValidDate(value: unknown): value is Date {\n return value instanceof Date && !Number.isNaN(value.getTime());\n}\n\nfunction cloneDate(date: Date): Date {\n return new Date(date.getTime());\n}\n\nfunction assertValidDate(\n fieldName: string,\n value: unknown,\n): asserts value is Date {\n if (!isValidDate(value)) {\n throw new InvariantViolationException(\n `${fieldName} must be a valid Date instance`,\n );\n }\n}\n\nexport { assertValidDate, cloneDate, isValidDate };\n"],"mappings":";;AAEA,SAAS,YAAY,OAA+B;CAChD,OAAO,iBAAiB,QAAQ,CAAC,OAAO,MAAM,MAAM,QAAQ,CAAC;AACjE;AAEA,SAAS,UAAU,MAAkB;CACjC,OAAO,IAAI,KAAK,KAAK,QAAQ,CAAC;AAClC;AAEA,SAAS,gBACL,WACA,OACqB;CACrB,IAAI,CAAC,YAAY,KAAK,GAClB,MAAM,IAAIA,sCAAAA,4BACN,GAAG,UAAU,+BACjB;AAER"}
@@ -0,0 +1,139 @@
1
+ const require_not_found_error = require("./not-found-error.cjs");
2
+ const require_unexpected_error = require("./unexpected-error.cjs");
3
+ const require_immutable = require("./immutable.cjs");
4
+ const require_use_case = require("./use-case.cjs");
5
+ const require_temporal_guards = require("./temporal-guards.cjs");
6
+ //#region src/core/application/commands/command.ts
7
+ /**
8
+ * Base for use cases that **mutate state** (writes), following CQS.
9
+ *
10
+ * - `Input extends CommandInput` ensures every mutation records who triggered it.
11
+ * - `Output extends Result<unknown, unknown>` ensures business errors are
12
+ * explicit values, never thrown exceptions.
13
+ *
14
+ * The Command/Query distinction is semantic: the type declares the intent
15
+ * before any implementation exists, guiding code review and API contracts.
16
+ */
17
+ let Command = class Command extends require_use_case.UseCase {};
18
+ Command = require_immutable.__decorate([require_immutable.version("1.0")], Command);
19
+ //#endregion
20
+ //#region src/core/application/policy-error-mapper.ts
21
+ function mapPolicyEvaluationError(err) {
22
+ switch (err.kind) {
23
+ case "INVALID_CONTEXT": return new require_not_found_error.BusinessRuleViolationError(`policy.${err.stage.toLowerCase()}`, err.message, {
24
+ policyKey: err.policyKey,
25
+ stage: err.stage
26
+ }, { cause: err.cause });
27
+ case "INVALID_POLICY_KEY": return new require_not_found_error.BusinessRuleViolationError("policy.invalid_key", err.message, { rawPolicyKey: err.rawPolicyKey }, { cause: err.cause });
28
+ case "POLICY_NOT_FOUND": return new require_not_found_error.NotFoundError("Policy", { policyKey: err.policyKey }, { cause: err.cause });
29
+ case "POLICY_DEFINITION_NOT_FOUND": return new require_not_found_error.NotFoundError("PolicyDefinition", {
30
+ policyKey: err.policyKey,
31
+ contextVersion: err.contextVersion,
32
+ asOf: err.asOf.toISOString()
33
+ }, { cause: err.cause });
34
+ case "POLICY_VARIANT_NOT_FOUND": return new require_not_found_error.NotFoundError("PolicyVariant", {
35
+ policyKey: err.policyKey,
36
+ policyKind: err.policyKind,
37
+ payloadSchemaVersion: err.payloadSchemaVersion
38
+ }, { cause: err.cause });
39
+ case "ENGINE_FAILURE": return new require_unexpected_error.UnexpectedError(err.message, err.cause, { metadata: {
40
+ policyKey: err.policyKey,
41
+ engine: err.engine,
42
+ engineVersion: err.engineVersion
43
+ } });
44
+ }
45
+ }
46
+ //#endregion
47
+ //#region src/core/application/queries/query.ts
48
+ /**
49
+ * Base for use cases that **only read state**, with no side effects, following CQS.
50
+ *
51
+ * - `Data extends QueryOutput` prevents a query from declaring a `void` or
52
+ * `undefined` success payload, while still allowing primitive projections
53
+ * and `null` when that makes sense for the read. Failures stay explicit in
54
+ * `Result<T, E>`.
55
+ * - Override `cacheStrategy()` to declare how infrastructure should cache the
56
+ * result. By default, no cache is applied.
57
+ *
58
+ * A Query must never mutate persisted data — its execution must be idempotent
59
+ * and safe to call multiple times with the same input.
60
+ */
61
+ let Query = class Query extends require_use_case.UseCase {
62
+ cacheStrategy() {
63
+ return { kind: "NO_CACHE" };
64
+ }
65
+ };
66
+ Query = require_immutable.__decorate([require_immutable.version("1.0")], Query);
67
+ //#endregion
68
+ //#region src/core/application/temporal/temporal-context.ts
69
+ function assertTemporalContext(temporalContext, fieldName = "temporalContext") {
70
+ require_temporal_guards.assertValidDate(`${fieldName}.asOf`, temporalContext.asOf);
71
+ require_temporal_guards.assertValidDate(`${fieldName}.requestedAt`, temporalContext.requestedAt);
72
+ }
73
+ function createTemporalContext(input = {}) {
74
+ const requestedAt = input.requestedAt ?? /* @__PURE__ */ new Date();
75
+ const asOf = input.asOf ?? requestedAt;
76
+ assertTemporalContext({
77
+ asOf,
78
+ requestedAt
79
+ });
80
+ return require_immutable.makeImmutable({
81
+ asOf,
82
+ requestedAt
83
+ });
84
+ }
85
+ //#endregion
86
+ //#region src/core/application/temporal/temporal-use-case.ts
87
+ var TemporalUseCase = class extends require_use_case.UseCase {
88
+ resolveTemporalContext(input) {
89
+ return createTemporalContext(input.temporalContext);
90
+ }
91
+ buildPolicySeed(seed, temporalContext) {
92
+ return Object.freeze({
93
+ ...seed,
94
+ fields: {
95
+ ...seed.fields,
96
+ now: new Date(temporalContext.requestedAt.getTime())
97
+ }
98
+ });
99
+ }
100
+ };
101
+ //#endregion
102
+ Object.defineProperty(exports, "Command", {
103
+ enumerable: true,
104
+ get: function() {
105
+ return Command;
106
+ }
107
+ });
108
+ Object.defineProperty(exports, "Query", {
109
+ enumerable: true,
110
+ get: function() {
111
+ return Query;
112
+ }
113
+ });
114
+ Object.defineProperty(exports, "TemporalUseCase", {
115
+ enumerable: true,
116
+ get: function() {
117
+ return TemporalUseCase;
118
+ }
119
+ });
120
+ Object.defineProperty(exports, "assertTemporalContext", {
121
+ enumerable: true,
122
+ get: function() {
123
+ return assertTemporalContext;
124
+ }
125
+ });
126
+ Object.defineProperty(exports, "createTemporalContext", {
127
+ enumerable: true,
128
+ get: function() {
129
+ return createTemporalContext;
130
+ }
131
+ });
132
+ Object.defineProperty(exports, "mapPolicyEvaluationError", {
133
+ enumerable: true,
134
+ get: function() {
135
+ return mapPolicyEvaluationError;
136
+ }
137
+ });
138
+
139
+ //# sourceMappingURL=temporal-use-case.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-use-case.cjs","names":["UseCase","version","BusinessRuleViolationError","NotFoundError","UnexpectedError","UseCase","version","makeImmutable","UseCase"],"sources":["../src/core/application/commands/command.ts","../src/core/application/policy-error-mapper.ts","../src/core/application/queries/query.ts","../src/core/application/temporal/temporal-context.ts","../src/core/application/temporal/temporal-use-case.ts"],"sourcesContent":["import { UseCase } from \"../use-case.js\";\nimport { version } from \"../../versioning/version.js\";\nimport type { Result } from \"../../result/result.js\";\n\nimport { RequestedBy } from \"./requested-by.js\";\n\ninterface CommandInput {\n readonly requestedBy: RequestedBy;\n}\n\n/**\n * Base for use cases that **mutate state** (writes), following CQS.\n *\n * - `Input extends CommandInput` ensures every mutation records who triggered it.\n * - `Output extends Result<unknown, unknown>` ensures business errors are\n * explicit values, never thrown exceptions.\n *\n * The Command/Query distinction is semantic: the type declares the intent\n * before any implementation exists, guiding code review and API contracts.\n */\n@version(\"1.0\")\nabstract class Command<\n Input extends CommandInput,\n Output extends Result<unknown, unknown> = Result<void, never>,\n> extends UseCase<Input, Output> {}\n\nexport type { CommandInput };\nexport { Command };\n","import {\n type AppError,\n BusinessRuleViolationError,\n NotFoundError,\n UnexpectedError,\n} from \"../errors/index.js\";\nimport type { PolicyEvaluationError } from \"../policies/index.js\";\n\nexport function mapPolicyEvaluationError(err: PolicyEvaluationError): AppError {\n switch (err.kind) {\n case \"INVALID_CONTEXT\":\n return new BusinessRuleViolationError(\n `policy.${err.stage.toLowerCase()}`,\n err.message,\n { policyKey: err.policyKey, stage: err.stage },\n { cause: err.cause },\n );\n case \"INVALID_POLICY_KEY\":\n return new BusinessRuleViolationError(\n \"policy.invalid_key\",\n err.message,\n { rawPolicyKey: err.rawPolicyKey },\n { cause: err.cause },\n );\n case \"POLICY_NOT_FOUND\":\n return new NotFoundError(\n \"Policy\",\n { policyKey: err.policyKey },\n { cause: err.cause },\n );\n case \"POLICY_DEFINITION_NOT_FOUND\":\n return new NotFoundError(\n \"PolicyDefinition\",\n {\n policyKey: err.policyKey,\n contextVersion: err.contextVersion,\n asOf: err.asOf.toISOString(),\n },\n { cause: err.cause },\n );\n case \"POLICY_VARIANT_NOT_FOUND\":\n return new NotFoundError(\n \"PolicyVariant\",\n {\n policyKey: err.policyKey,\n policyKind: err.policyKind,\n payloadSchemaVersion: err.payloadSchemaVersion,\n },\n { cause: err.cause },\n );\n case \"ENGINE_FAILURE\":\n return new UnexpectedError(err.message, err.cause, {\n metadata: {\n policyKey: err.policyKey,\n engine: err.engine,\n engineVersion: err.engineVersion,\n },\n });\n }\n}\n","import { UseCase } from \"../use-case.js\";\nimport { version } from \"../../versioning/version.js\";\nimport type { AppError } from \"../../errors/index.js\";\nimport type { Result } from \"../../result/result.js\";\n\n/**\n * Paginated result of a list query.\n * Use as `Output` when the query returns a collection with pagination metadata.\n */\ninterface Page<T> {\n readonly items: readonly T[];\n readonly total: number;\n readonly page: number;\n readonly pageSize: number;\n}\n\n/**\n * Cache strategy declared by the query type.\n * Infrastructure reads this value to decide whether and how to cache the result.\n */\ntype CacheStrategy =\n | { readonly kind: \"NO_CACHE\" }\n | { readonly kind: \"TIME_TO_LIVE\"; readonly ttlMs: number }\n | { readonly kind: \"STALE_WHILE_REVALIDATE\"; readonly ttlMs: number };\n\ntype QueryOutput = object | string | number | boolean | bigint | symbol | null;\n\n/**\n * Base for use cases that **only read state**, with no side effects, following CQS.\n *\n * - `Data extends QueryOutput` prevents a query from declaring a `void` or\n * `undefined` success payload, while still allowing primitive projections\n * and `null` when that makes sense for the read. Failures stay explicit in\n * `Result<T, E>`.\n * - Override `cacheStrategy()` to declare how infrastructure should cache the\n * result. By default, no cache is applied.\n *\n * A Query must never mutate persisted data — its execution must be idempotent\n * and safe to call multiple times with the same input.\n */\n@version(\"1.0\")\nabstract class Query<\n Input = void,\n Data extends QueryOutput = never,\n Failure = AppError,\n> extends UseCase<Input, Result<Data, Failure>> {\n protected cacheStrategy(): CacheStrategy {\n return { kind: \"NO_CACHE\" };\n }\n}\n\nexport type { CacheStrategy, Page, QueryOutput };\nexport { Query };\n","import { makeImmutable } from \"../../shared/immutable.js\";\nimport { assertValidDate } from \"../../shared/temporal-guards.js\";\n\ninterface TemporalContext {\n readonly asOf: Date;\n readonly requestedAt: Date;\n}\n\ninterface CreateTemporalContextInput {\n readonly asOf?: Date;\n readonly requestedAt?: Date;\n}\n\nfunction assertTemporalContext(\n temporalContext: TemporalContext,\n fieldName: string = \"temporalContext\",\n): void {\n assertValidDate(`${fieldName}.asOf`, temporalContext.asOf);\n assertValidDate(`${fieldName}.requestedAt`, temporalContext.requestedAt);\n}\n\nfunction createTemporalContext(\n input: CreateTemporalContextInput = {},\n): TemporalContext {\n const requestedAt = input.requestedAt ?? new Date();\n const asOf = input.asOf ?? requestedAt;\n\n assertTemporalContext({ asOf, requestedAt });\n\n return makeImmutable({\n asOf,\n requestedAt,\n });\n}\n\nexport {\n assertTemporalContext,\n createTemporalContext,\n type CreateTemporalContextInput,\n type TemporalContext,\n};\n","import { UseCase } from \"../use-case.js\";\nimport type { ContextSeed } from \"../../policies/index.js\";\nimport type { Result } from \"../../result/result.js\";\n\nimport {\n createTemporalContext,\n type TemporalContext,\n} from \"./temporal-context.js\";\n\ninterface TemporalUseCaseInput {\n readonly temporalContext?: TemporalContext;\n}\n\n/**\n * A {@link ContextSeed} after temporal enrichment by\n * {@link TemporalUseCase.buildPolicySeed}: structurally identical to `TSeed`,\n * except `fields.now` is now guaranteed present as a `Date` (injected from the\n * resolved {@link TemporalContext}). Downstream policies can therefore read\n * `seed.fields.now` without a presence/type guard.\n */\ntype TemporalizedContextSeed<TSeed extends ContextSeed> = Omit<\n TSeed,\n \"fields\"\n> & {\n readonly fields: TSeed[\"fields\"] & { readonly now: Date };\n};\n\nabstract class TemporalUseCase<\n Input extends object,\n Output extends Result<unknown, unknown>,\n> extends UseCase<Input & TemporalUseCaseInput, Output> {\n protected resolveTemporalContext(\n input: Input & TemporalUseCaseInput,\n ): TemporalContext {\n return createTemporalContext(input.temporalContext);\n }\n\n protected buildPolicySeed<TSeed extends ContextSeed>(\n seed: TSeed,\n temporalContext: TemporalContext,\n ): TemporalizedContextSeed<TSeed> {\n return Object.freeze({\n ...seed,\n fields: {\n ...seed.fields,\n now: new Date(temporalContext.requestedAt.getTime()),\n },\n }) as TemporalizedContextSeed<TSeed>;\n }\n}\n\nexport type { TemporalizedContextSeed, TemporalUseCaseInput };\nexport { TemporalUseCase };\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,IAAA,UAAA,MACe,gBAGLA,iBAAAA,QAAuB,CAAC;wCAJjCC,kBAAAA,QAAQ,KAAK,CAAA,GAAA,OAAA;;;ACZd,SAAgB,yBAAyB,KAAsC;CAC3E,QAAQ,IAAI,MAAZ;EACI,KAAK,mBACD,OAAO,IAAIC,wBAAAA,2BACP,UAAU,IAAI,MAAM,YAAY,KAChC,IAAI,SACJ;GAAE,WAAW,IAAI;GAAW,OAAO,IAAI;EAAM,GAC7C,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,sBACD,OAAO,IAAIA,wBAAAA,2BACP,sBACA,IAAI,SACJ,EAAE,cAAc,IAAI,aAAa,GACjC,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,oBACD,OAAO,IAAIC,wBAAAA,cACP,UACA,EAAE,WAAW,IAAI,UAAU,GAC3B,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,+BACD,OAAO,IAAIA,wBAAAA,cACP,oBACA;GACI,WAAW,IAAI;GACf,gBAAgB,IAAI;GACpB,MAAM,IAAI,KAAK,YAAY;EAC/B,GACA,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,4BACD,OAAO,IAAIA,wBAAAA,cACP,iBACA;GACI,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,sBAAsB,IAAI;EAC9B,GACA,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,kBACD,OAAO,IAAIC,yBAAAA,gBAAgB,IAAI,SAAS,IAAI,OAAO,EAC/C,UAAU;GACN,WAAW,IAAI;GACf,QAAQ,IAAI;GACZ,eAAe,IAAI;EACvB,EACJ,CAAC;CACT;AACJ;;;;;;;;;;;;;;;;ACnBA,IAAA,QAAA,MACe,cAILC,iBAAAA,QAAsC;CAC5C,gBAAyC;EACrC,OAAO,EAAE,MAAM,WAAW;CAC9B;AACJ;sCATCC,kBAAAA,QAAQ,KAAK,CAAA,GAAA,KAAA;;;AC3Bd,SAAS,sBACL,iBACA,YAAoB,mBAChB;CACJ,wBAAA,gBAAgB,GAAG,UAAU,QAAQ,gBAAgB,IAAI;CACzD,wBAAA,gBAAgB,GAAG,UAAU,eAAe,gBAAgB,WAAW;AAC3E;AAEA,SAAS,sBACL,QAAoC,CAAC,GACtB;CACf,MAAM,cAAc,MAAM,+BAAe,IAAI,KAAK;CAClD,MAAM,OAAO,MAAM,QAAQ;CAE3B,sBAAsB;EAAE;EAAM;CAAY,CAAC;CAE3C,OAAOC,kBAAAA,cAAc;EACjB;EACA;CACJ,CAAC;AACL;;;ACNA,IAAe,kBAAf,cAGUC,iBAAAA,QAA8C;CACpD,uBACI,OACe;EACf,OAAO,sBAAsB,MAAM,eAAe;CACtD;CAEA,gBACI,MACA,iBAC8B;EAC9B,OAAO,OAAO,OAAO;GACjB,GAAG;GACH,QAAQ;IACJ,GAAG,KAAK;IACR,KAAK,IAAI,KAAK,gBAAgB,YAAY,QAAQ,CAAC;GACvD;EACJ,CAAC;CACL;AACJ"}
@@ -0,0 +1,282 @@
1
+ import { t as RequestedBy } from "./requested-by.cjs";
2
+ import { t as AppError } from "./app-error.cjs";
3
+ import { r as Result } from "./result.cjs";
4
+ import { t as DeepReadonly } from "./immutable.cjs";
5
+ import { n as ContractVersion } from "./version.cjs";
6
+ import { b as ContextSeed, r as PolicyEvaluationResult, s as PolicyEvaluationError, t as EvaluateInput } from "./policy-service.cjs";
7
+
8
+ //#region src/core/application/ports/logger.port.d.ts
9
+ type LogPayload = Record<string, unknown>;
10
+ interface LoggerPort {
11
+ debug(message: string, payload?: LogPayload): void;
12
+ info(message: string, payload?: LogPayload): void;
13
+ warn(message: string, payload?: LogPayload): void;
14
+ error(message: string, payload?: LogPayload): void;
15
+ }
16
+ //#endregion
17
+ //#region src/core/application/ports/metrics.port.d.ts
18
+ type MetricLabels = Readonly<Record<string, string | number | boolean>>;
19
+ interface MetricsPort {
20
+ counter(name: string, value: number, labels?: MetricLabels): void;
21
+ gauge(name: string, value: number, labels?: MetricLabels): void;
22
+ histogram(name: string, value: number, labels?: MetricLabels): void;
23
+ }
24
+ //#endregion
25
+ //#region src/core/application/ports/tracer.port.d.ts
26
+ type TraceAttributeValue = string | number | boolean;
27
+ interface TraceSpan {
28
+ setAttribute(key: string, value: TraceAttributeValue): void;
29
+ recordException(error: unknown): void;
30
+ end(): void;
31
+ }
32
+ interface TracerPort {
33
+ startSpan(name: string, attributes?: Record<string, TraceAttributeValue>): TraceSpan;
34
+ }
35
+ //#endregion
36
+ //#region src/core/application/use-case.d.ts
37
+ type MaybePromise<T> = T | Promise<T>;
38
+ /**
39
+ * Observability adapters a use case may opt into.
40
+ *
41
+ * All fields are optional: a use case that provides none keeps the plain
42
+ * `input → Result` behavior with zero overhead. When provided, {@link UseCase.run}
43
+ * instruments every execution around `execute()` — opening a span, recording
44
+ * duration, counting outcomes, and logging business/unexpected failures.
45
+ */
46
+ interface UseCaseObservability {
47
+ readonly logger?: LoggerPort;
48
+ readonly metrics?: MetricsPort;
49
+ readonly tracer?: TracerPort;
50
+ }
51
+ declare abstract class UseCase<Input = void, Output extends Result<unknown, unknown> = Result<void, never>> {
52
+ static readonly CONTRACT_VERSION: ContractVersion;
53
+ get contractVersion(): ContractVersion;
54
+ /**
55
+ * Runs the use case.
56
+ *
57
+ * `run()` is the single seam every use case crosses, so it is where
58
+ * cross-cutting instrumentation lives. When {@link observability} exposes
59
+ * any adapter, the call to `execute()` is wrapped with tracing, metrics and
60
+ * failure logging; otherwise it delegates directly with no overhead.
61
+ *
62
+ * Observability is strictly side-effecting: a misbehaving adapter never
63
+ * changes the business result nor masks a thrown error — its own failures
64
+ * are swallowed.
65
+ */
66
+ run(input: Input): Promise<Output>;
67
+ /**
68
+ * Adapters used to instrument {@link run}. Override to opt a use case into
69
+ * tracing/metrics/logging. Returns an empty object by default, which keeps
70
+ * the plain delegating behavior.
71
+ */
72
+ protected observability(): UseCaseObservability;
73
+ /**
74
+ * Stable identity used for span names and metric labels. Defaults to the
75
+ * runtime class name; override when bundling/minification would otherwise
76
+ * erase a meaningful name.
77
+ */
78
+ protected get useCaseName(): string;
79
+ protected abstract execute(input: Input): MaybePromise<Output>;
80
+ private runInstrumented;
81
+ }
82
+ //#endregion
83
+ //#region src/core/application/commands/command.d.ts
84
+ interface CommandInput {
85
+ readonly requestedBy: RequestedBy;
86
+ }
87
+ /**
88
+ * Base for use cases that **mutate state** (writes), following CQS.
89
+ *
90
+ * - `Input extends CommandInput` ensures every mutation records who triggered it.
91
+ * - `Output extends Result<unknown, unknown>` ensures business errors are
92
+ * explicit values, never thrown exceptions.
93
+ *
94
+ * The Command/Query distinction is semantic: the type declares the intent
95
+ * before any implementation exists, guiding code review and API contracts.
96
+ */
97
+ declare abstract class Command<Input extends CommandInput, Output extends Result<unknown, unknown> = Result<void, never>> extends UseCase<Input, Output> {}
98
+ //#endregion
99
+ //#region src/core/application/ports/policy-port.d.ts
100
+ type PolicyEvaluationInput = EvaluateInput;
101
+ type PolicyEvaluationOutput = PolicyEvaluationResult;
102
+ /**
103
+ * Failure surfaced by {@link PolicyPort.evaluate}.
104
+ *
105
+ * This is the *post-mapping* boundary: the structured `PolicyEvaluationError`
106
+ * union produced inside the policies module is expected to have already been
107
+ * translated into an {@link AppError} (e.g. via `mapPolicyEvaluationError`)
108
+ * before it reaches a use case. Named distinctly from the policies-layer
109
+ * `PolicyEvaluationError` to avoid a collision on the public surface.
110
+ */
111
+ type PolicyPortError = AppError;
112
+ interface PolicyPort {
113
+ evaluate(input: PolicyEvaluationInput): Promise<Result<PolicyEvaluationOutput, PolicyPortError>>;
114
+ }
115
+ //#endregion
116
+ //#region src/core/application/ports/repository.port.d.ts
117
+ /**
118
+ * Persistence port for an aggregate in the imperative style: absence is
119
+ * modelled as `null`, and failures (missing row on delete, optimistic
120
+ * concurrency conflicts, infrastructure errors) surface as thrown exceptions.
121
+ *
122
+ * Prefer {@link ResultRepository} when you want those persistence outcomes to
123
+ * be explicit, recoverable values rather than exceptions.
124
+ */
125
+ interface Repository<TEntity, TId> {
126
+ findById(id: TId): Promise<TEntity | null>;
127
+ save(entity: TEntity): Promise<void>;
128
+ delete(id: TId): Promise<void>;
129
+ }
130
+ /**
131
+ * `Result`-returning counterpart of {@link Repository}, aligned with the
132
+ * "errors as values" philosophy used by `Command` / `UseCase`.
133
+ *
134
+ * Where {@link Repository} returns `Promise<void>` from `save`/`delete` and
135
+ * relies on thrown exceptions, this variant lets a repository *signal*
136
+ * recoverable persistence outcomes without breaking control flow — for
137
+ * example a `NotFoundError` when updating/deleting a missing aggregate, or a
138
+ * `ConflictError` on an optimistic-concurrency version mismatch. A successful
139
+ * write resolves to `Result.ok(undefined)`.
140
+ *
141
+ * `findById` keeps `null` as the "not present" answer to a lookup (not an
142
+ * error) while still wrapping the call in a `Result` so genuine failures
143
+ * (e.g. a connectivity error) stay in band.
144
+ *
145
+ * `TError` defaults to {@link AppError} to match the rest of the application
146
+ * boundary (see `PolicyPortError`); narrow it per repository when the failure
147
+ * set is known, e.g. `ResultRepository<Order, OrderId, NotFoundError | ConflictError>`.
148
+ */
149
+ interface ResultRepository<TEntity, TId, TError = AppError> {
150
+ findById(id: TId): Promise<Result<TEntity | null, TError>>;
151
+ save(entity: TEntity): Promise<Result<void, TError>>;
152
+ delete(id: TId): Promise<Result<void, TError>>;
153
+ }
154
+ //#endregion
155
+ //#region src/core/domain/temporal/transaction-time.d.ts
156
+ /**
157
+ * Represents transaction time (Transaction Time).
158
+ * Defines when the system learned about or recorded a piece of information.
159
+ * Useful for auditing and reconstructing system state at a specific past moment.
160
+ */
161
+ interface TransactionTime {
162
+ /** When the information was first recorded in the system. */
163
+ readonly recordedAt: Date;
164
+ /** When this information was superseded or invalidated by a newer version. */
165
+ readonly supersededAt?: Date;
166
+ }
167
+ //#endregion
168
+ //#region src/core/domain/temporal/valid-time.d.ts
169
+ /**
170
+ * Represents business time (Valid Time).
171
+ * Defines when a piece of information is considered true or in effect in the real world.
172
+ */
173
+ interface ValidTime {
174
+ /** Start of the validity window (inclusive). */
175
+ readonly from: Date;
176
+ /** End of the validity window (exclusive). When absent, the information is still in effect. */
177
+ readonly to?: Date;
178
+ }
179
+ //#endregion
180
+ //#region src/core/domain/temporal/temporal-snapshot.d.ts
181
+ /**
182
+ * Pairs a domain datum with its time dimensions (Business and Transaction).
183
+ * It is the representation of an entry in the append-only (historical) table.
184
+ */
185
+ interface TemporalSnapshot<T> {
186
+ /** The data or state captured by the snapshot. */
187
+ readonly data: DeepReadonly<T>;
188
+ /** Period during which this datum was valid in the real world. */
189
+ readonly validTime: ValidTime;
190
+ /** Period during which this record was the state known to the system. */
191
+ readonly txTime: TransactionTime;
192
+ }
193
+ //#endregion
194
+ //#region src/core/application/ports/temporal-repository.port.d.ts
195
+ type TemporalHistory<TEntity> = readonly TemporalSnapshot<TEntity>[];
196
+ interface TemporalRepository<TEntity, TId> extends Repository<TEntity, TId> {
197
+ findAsOf(id: TId, asOf: Date): Promise<TEntity | null>;
198
+ findAtTransaction(id: TId, txTime: Date): Promise<TEntity | null>;
199
+ findHistory(id: TId): Promise<TemporalHistory<TEntity>>;
200
+ save(entity: TEntity, validFrom?: Date): Promise<void>;
201
+ }
202
+ //#endregion
203
+ //#region src/core/application/policy-error-mapper.d.ts
204
+ declare function mapPolicyEvaluationError(err: PolicyEvaluationError): AppError;
205
+ //#endregion
206
+ //#region src/core/application/queries/query.d.ts
207
+ /**
208
+ * Paginated result of a list query.
209
+ * Use as `Output` when the query returns a collection with pagination metadata.
210
+ */
211
+ interface Page<T> {
212
+ readonly items: readonly T[];
213
+ readonly total: number;
214
+ readonly page: number;
215
+ readonly pageSize: number;
216
+ }
217
+ /**
218
+ * Cache strategy declared by the query type.
219
+ * Infrastructure reads this value to decide whether and how to cache the result.
220
+ */
221
+ type CacheStrategy = {
222
+ readonly kind: "NO_CACHE";
223
+ } | {
224
+ readonly kind: "TIME_TO_LIVE";
225
+ readonly ttlMs: number;
226
+ } | {
227
+ readonly kind: "STALE_WHILE_REVALIDATE";
228
+ readonly ttlMs: number;
229
+ };
230
+ type QueryOutput = object | string | number | boolean | bigint | symbol | null;
231
+ /**
232
+ * Base for use cases that **only read state**, with no side effects, following CQS.
233
+ *
234
+ * - `Data extends QueryOutput` prevents a query from declaring a `void` or
235
+ * `undefined` success payload, while still allowing primitive projections
236
+ * and `null` when that makes sense for the read. Failures stay explicit in
237
+ * `Result<T, E>`.
238
+ * - Override `cacheStrategy()` to declare how infrastructure should cache the
239
+ * result. By default, no cache is applied.
240
+ *
241
+ * A Query must never mutate persisted data — its execution must be idempotent
242
+ * and safe to call multiple times with the same input.
243
+ */
244
+ declare abstract class Query<Input = void, Data extends QueryOutput = never, Failure = AppError> extends UseCase<Input, Result<Data, Failure>> {
245
+ protected cacheStrategy(): CacheStrategy;
246
+ }
247
+ //#endregion
248
+ //#region src/core/application/temporal/temporal-context.d.ts
249
+ interface TemporalContext {
250
+ readonly asOf: Date;
251
+ readonly requestedAt: Date;
252
+ }
253
+ interface CreateTemporalContextInput {
254
+ readonly asOf?: Date;
255
+ readonly requestedAt?: Date;
256
+ }
257
+ declare function assertTemporalContext(temporalContext: TemporalContext, fieldName?: string): void;
258
+ declare function createTemporalContext(input?: CreateTemporalContextInput): TemporalContext;
259
+ //#endregion
260
+ //#region src/core/application/temporal/temporal-use-case.d.ts
261
+ interface TemporalUseCaseInput {
262
+ readonly temporalContext?: TemporalContext;
263
+ }
264
+ /**
265
+ * A {@link ContextSeed} after temporal enrichment by
266
+ * {@link TemporalUseCase.buildPolicySeed}: structurally identical to `TSeed`,
267
+ * except `fields.now` is now guaranteed present as a `Date` (injected from the
268
+ * resolved {@link TemporalContext}). Downstream policies can therefore read
269
+ * `seed.fields.now` without a presence/type guard.
270
+ */
271
+ type TemporalizedContextSeed<TSeed extends ContextSeed> = Omit<TSeed, "fields"> & {
272
+ readonly fields: TSeed["fields"] & {
273
+ readonly now: Date;
274
+ };
275
+ };
276
+ declare abstract class TemporalUseCase<Input extends object, Output extends Result<unknown, unknown>> extends UseCase<Input & TemporalUseCaseInput, Output> {
277
+ protected resolveTemporalContext(input: Input & TemporalUseCaseInput): TemporalContext;
278
+ protected buildPolicySeed<TSeed extends ContextSeed>(seed: TSeed, temporalContext: TemporalContext): TemporalizedContextSeed<TSeed>;
279
+ }
280
+ //#endregion
281
+ export { LogPayload as A, UseCase as C, TracerPort as D, TraceSpan as E, MetricLabels as O, MaybePromise as S, TraceAttributeValue as T, PolicyEvaluationOutput as _, TemporalContext as a, Command as b, CacheStrategy as c, mapPolicyEvaluationError as d, TemporalHistory as f, PolicyEvaluationInput as g, ResultRepository as h, CreateTemporalContextInput as i, LoggerPort as j, MetricsPort as k, Page as l, Repository as m, TemporalUseCaseInput as n, assertTemporalContext as o, TemporalRepository as p, TemporalizedContextSeed as r, createTemporalContext as s, TemporalUseCase as t, Query as u, PolicyPort as v, UseCaseObservability as w, CommandInput as x, PolicyPortError as y };
282
+ //# sourceMappingURL=temporal-use-case.d.cts.map