@cullet/erp-core 1.3.0 → 1.5.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 (197) hide show
  1. package/KIT_CONTEXT.md +11 -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 +201 -0
  8. package/dist/app-error.cjs.map +1 -0
  9. package/dist/app-error.js +178 -0
  10. package/dist/app-error.js.map +1 -0
  11. package/dist/application/index.cjs +2 -1
  12. package/dist/application/index.d.cts +2 -1
  13. package/dist/application/index.d.ts +2 -1
  14. package/dist/application/index.js +2 -1
  15. package/dist/authorization-error.cjs +157 -0
  16. package/dist/authorization-error.cjs.map +1 -0
  17. package/dist/authorization-error.d.cts +113 -0
  18. package/dist/authorization-error.d.ts +113 -0
  19. package/dist/authorization-error.js +152 -0
  20. package/dist/authorization-error.js.map +1 -0
  21. package/dist/authorizer.port.d.cts +130 -0
  22. package/dist/authorizer.port.d.ts +130 -0
  23. package/dist/composite-authorizer.d.cts +216 -0
  24. package/dist/composite-authorizer.d.ts +216 -0
  25. package/dist/condition-evaluator.cjs +579 -0
  26. package/dist/condition-evaluator.cjs.map +1 -0
  27. package/dist/condition-evaluator.js +550 -0
  28. package/dist/condition-evaluator.js.map +1 -0
  29. package/dist/{gate-types.d.ts → core-config.d.cts} +78 -77
  30. package/dist/{gate-types.d.cts → core-config.d.ts} +78 -77
  31. package/dist/domain/index.cjs +2 -1
  32. package/dist/domain/index.d.cts +2 -1
  33. package/dist/domain/index.d.ts +2 -1
  34. package/dist/domain/index.js +2 -1
  35. package/dist/domain-event-contracts.cjs +3 -2
  36. package/dist/domain-event-contracts.cjs.map +1 -1
  37. package/dist/domain-event-contracts.js +2 -1
  38. package/dist/domain-event-contracts.js.map +1 -1
  39. package/dist/entity.cjs +122 -0
  40. package/dist/entity.cjs.map +1 -0
  41. package/dist/entity.js +111 -0
  42. package/dist/entity.js.map +1 -0
  43. package/dist/errors/index.cjs +10 -7
  44. package/dist/errors/index.d.cts +2 -1
  45. package/dist/errors/index.d.ts +2 -1
  46. package/dist/errors/index.js +6 -3
  47. package/dist/gate-engine-registry.cjs +5 -4
  48. package/dist/gate-engine-registry.cjs.map +1 -1
  49. package/dist/gate-engine-registry.d.cts +3 -2
  50. package/dist/gate-engine-registry.d.ts +3 -2
  51. package/dist/gate-engine-registry.js +2 -1
  52. package/dist/gate-engine-registry.js.map +1 -1
  53. package/dist/gate-v1-payload.schema.cjs +0 -562
  54. package/dist/gate-v1-payload.schema.cjs.map +1 -1
  55. package/dist/gate-v1-payload.schema.js +1 -533
  56. package/dist/gate-v1-payload.schema.js.map +1 -1
  57. package/dist/hashing.cjs +2 -44
  58. package/dist/hashing.cjs.map +1 -1
  59. package/dist/hashing.js +2 -38
  60. package/dist/hashing.js.map +1 -1
  61. package/dist/index.cjs +36 -17
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/index.d.cts +19 -12
  64. package/dist/index.d.ts +19 -12
  65. package/dist/index.js +17 -10
  66. package/dist/index.js.map +1 -1
  67. package/dist/not-found-error.cjs +5 -5
  68. package/dist/not-found-error.cjs.map +1 -1
  69. package/dist/not-found-error.js +1 -1
  70. package/dist/outcome.d.cts +1 -83
  71. package/dist/outcome.d.ts +1 -83
  72. package/dist/parse-gate-payload.d.cts +2 -2
  73. package/dist/parse-gate-payload.d.ts +2 -2
  74. package/dist/path.d.cts +2 -2
  75. package/dist/path.d.ts +2 -2
  76. package/dist/policies/engines/index.d.cts +1 -1
  77. package/dist/policies/engines/index.d.ts +1 -1
  78. package/dist/policies/engines/v1/gate/index.cjs +5 -4
  79. package/dist/policies/engines/v1/gate/index.cjs.map +1 -1
  80. package/dist/policies/engines/v1/gate/index.d.cts +4 -2
  81. package/dist/policies/engines/v1/gate/index.d.ts +4 -2
  82. package/dist/policies/engines/v1/gate/index.js +2 -1
  83. package/dist/policies/engines/v1/gate/index.js.map +1 -1
  84. package/dist/policies/index.cjs +5 -5
  85. package/dist/policies/index.d.cts +3 -2
  86. package/dist/policies/index.d.ts +3 -2
  87. package/dist/policies/index.js +2 -2
  88. package/dist/policy-bridge.cjs +437 -0
  89. package/dist/policy-bridge.cjs.map +1 -0
  90. package/dist/policy-bridge.d.cts +185 -0
  91. package/dist/policy-bridge.d.ts +185 -0
  92. package/dist/policy-bridge.js +396 -0
  93. package/dist/policy-bridge.js.map +1 -0
  94. package/dist/policy-service.cjs +241 -240
  95. package/dist/policy-service.cjs.map +1 -1
  96. package/dist/policy-service.d.cts +2 -2
  97. package/dist/policy-service.d.ts +2 -2
  98. package/dist/policy-service.js +240 -239
  99. package/dist/policy-service.js.map +1 -1
  100. package/dist/rbac/index.cjs +9 -0
  101. package/dist/rbac/index.d.cts +3 -0
  102. package/dist/rbac/index.d.ts +3 -0
  103. package/dist/rbac/index.js +2 -0
  104. package/dist/requested-by.cjs +54 -0
  105. package/dist/requested-by.cjs.map +1 -0
  106. package/dist/requested-by.d.cts +25 -0
  107. package/dist/requested-by.d.ts +25 -0
  108. package/dist/requested-by.js +49 -0
  109. package/dist/requested-by.js.map +1 -0
  110. package/dist/result/index.d.cts +2 -1
  111. package/dist/result/index.d.ts +2 -1
  112. package/dist/result.d.cts +84 -0
  113. package/dist/result.d.ts +84 -0
  114. package/dist/rule.cjs +327 -0
  115. package/dist/rule.cjs.map +1 -0
  116. package/dist/rule.js +298 -0
  117. package/dist/rule.js.map +1 -0
  118. package/dist/stable-stringify.cjs +47 -0
  119. package/dist/stable-stringify.cjs.map +1 -0
  120. package/dist/stable-stringify.js +42 -0
  121. package/dist/stable-stringify.js.map +1 -0
  122. package/dist/temporal-use-case.cjs +6 -53
  123. package/dist/temporal-use-case.cjs.map +1 -1
  124. package/dist/temporal-use-case.d.cts +11 -28
  125. package/dist/temporal-use-case.d.ts +11 -28
  126. package/dist/temporal-use-case.js +7 -48
  127. package/dist/temporal-use-case.js.map +1 -1
  128. package/dist/unexpected-error.cjs +3 -192
  129. package/dist/unexpected-error.cjs.map +1 -1
  130. package/dist/unexpected-error.js +2 -167
  131. package/dist/unexpected-error.js.map +1 -1
  132. package/dist/use-case.cjs +2 -6
  133. package/dist/use-case.cjs.map +1 -1
  134. package/dist/use-case.js +2 -6
  135. package/dist/use-case.js.map +1 -1
  136. package/dist/uuid-identifier.cjs +2 -7
  137. package/dist/uuid-identifier.cjs.map +1 -1
  138. package/dist/uuid-identifier.d.cts +2 -85
  139. package/dist/uuid-identifier.d.ts +2 -85
  140. package/dist/uuid-identifier.js +1 -6
  141. package/dist/uuid-identifier.js.map +1 -1
  142. package/dist/uuid.cjs +18 -0
  143. package/dist/uuid.cjs.map +1 -0
  144. package/dist/uuid.js +13 -0
  145. package/dist/uuid.js.map +1 -0
  146. package/dist/validation-error.cjs +33 -166
  147. package/dist/validation-error.cjs.map +1 -1
  148. package/dist/validation-error.d.cts +12 -103
  149. package/dist/validation-error.d.ts +12 -103
  150. package/dist/validation-error.js +7 -134
  151. package/dist/validation-error.js.map +1 -1
  152. package/dist/value-object.cjs +12 -126
  153. package/dist/value-object.cjs.map +1 -1
  154. package/dist/value-object.d.cts +97 -0
  155. package/dist/value-object.d.ts +97 -0
  156. package/dist/value-object.js +13 -115
  157. package/dist/value-object.js.map +1 -1
  158. package/meta.json +20 -4
  159. package/package.json +27 -1
  160. package/src/abac/index.ts +1 -0
  161. package/src/core/abac/abac-request.ts +29 -0
  162. package/src/core/abac/attributes.ts +39 -0
  163. package/src/core/abac/authorizer.ts +108 -0
  164. package/src/core/abac/combining.ts +63 -0
  165. package/src/core/abac/composite-authorizer.ts +45 -0
  166. package/src/core/abac/domain/policy-set.ts +52 -0
  167. package/src/core/abac/domain/rule.ts +197 -0
  168. package/src/core/abac/index.ts +21 -0
  169. package/src/core/application/commands/requested-by.ts +3 -4
  170. package/src/core/application/ports/abac-authorizer.port.ts +20 -0
  171. package/src/core/application/ports/authorizer.port.ts +22 -0
  172. package/src/core/application/queries/query.ts +6 -1
  173. package/src/core/application/use-case.ts +1 -1
  174. package/src/core/domain/entity.ts +1 -1
  175. package/src/core/domain/uuid-identifier.ts +1 -8
  176. package/src/core/domain/value-object.ts +12 -3
  177. package/src/core/errors/authorization-error.ts +26 -0
  178. package/src/core/errors/error-codes.ts +1 -0
  179. package/src/core/errors/utils/json-safe.ts +20 -11
  180. package/src/core/index.ts +7 -0
  181. package/src/core/policies/engines/v1/condition-evaluator.ts +56 -1
  182. package/src/core/rbac/access-request.ts +32 -0
  183. package/src/core/rbac/authorizer.ts +91 -0
  184. package/src/core/rbac/domain/grant.ts +96 -0
  185. package/src/core/rbac/domain/permission-set.ts +67 -0
  186. package/src/core/rbac/domain/permission.ts +119 -0
  187. package/src/core/rbac/domain/role.ts +101 -0
  188. package/src/core/rbac/domain/scope.ts +84 -0
  189. package/src/core/rbac/index.ts +15 -0
  190. package/src/core/rbac/policy-bridge.ts +44 -0
  191. package/src/core/shared/hashing.ts +1 -55
  192. package/src/core/shared/stable-stringify.ts +57 -0
  193. package/src/core/shared/uuid.ts +11 -0
  194. package/src/examples/application/authorize-cancel-order-abac.example.ts +107 -0
  195. package/src/examples/application/authorize-cancel-order.example.ts +101 -0
  196. package/src/rbac/index.ts +1 -0
  197. 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
+ //#region src/core/shared/stable-stringify.ts
2
+ const CIRCULAR_STRUCTURE_MESSAGE = "Cannot stably stringify a circular structure";
3
+ function sortKeysDeep(value, seen) {
4
+ if (Array.isArray(value)) {
5
+ if (seen.has(value)) throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);
6
+ seen.add(value);
7
+ const mapped = value.map((item) => sortKeysDeep(item, seen));
8
+ seen.delete(value);
9
+ return mapped;
10
+ }
11
+ if (value instanceof Date) return value;
12
+ if (value && typeof value === "object") {
13
+ if (seen.has(value)) throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);
14
+ seen.add(value);
15
+ const record = value;
16
+ const ordered = Object.create(null);
17
+ for (const key of Object.keys(record).sort()) ordered[key] = sortKeysDeep(record[key], seen);
18
+ seen.delete(value);
19
+ return ordered;
20
+ }
21
+ return value;
22
+ }
23
+ /**
24
+ * Deterministic JSON string with object keys sorted recursively, so key order
25
+ * does not affect the output.
26
+ *
27
+ * Follows `JSON.stringify` semantics for the values it serializes: `undefined`,
28
+ * functions and symbols are dropped, non-finite numbers become `null`, `bigint`
29
+ * throws, and `Map`/`Set` serialize as `{}`. Distinct payloads can therefore
30
+ * collapse to the same string — for collision-resistant hashing use
31
+ * `PolicyHashing.canonicalJson` (policies/utils), which rejects those values
32
+ * up front.
33
+ *
34
+ * @throws {TypeError} On circular references (mirroring `JSON.stringify`).
35
+ */
36
+ function stableStringify(value) {
37
+ return JSON.stringify(sortKeysDeep(value, /* @__PURE__ */ new WeakSet()));
38
+ }
39
+ //#endregion
40
+ Object.defineProperty(exports, "stableStringify", {
41
+ enumerable: true,
42
+ get: function() {
43
+ return stableStringify;
44
+ }
45
+ });
46
+
47
+ //# sourceMappingURL=stable-stringify.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-stringify.cjs","names":[],"sources":["../src/core/shared/stable-stringify.ts"],"sourcesContent":["const CIRCULAR_STRUCTURE_MESSAGE =\n \"Cannot stably stringify a circular structure\";\n\n// `seen` holds the current traversal branch so true cycles are rejected while\n// repeated (acyclic) references are still serialized — matching JSON.stringify.\nfunction sortKeysDeep(value: unknown, seen: WeakSet<object>): unknown {\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);\n }\n seen.add(value);\n const mapped = value.map((item) => sortKeysDeep(item, seen));\n seen.delete(value);\n return mapped;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (value && typeof value === \"object\") {\n if (seen.has(value)) {\n throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);\n }\n seen.add(value);\n const record = value as Record<string, unknown>;\n const ordered = Object.create(null) as Record<string, unknown>;\n\n for (const key of Object.keys(record).sort()) {\n ordered[key] = sortKeysDeep(record[key], seen);\n }\n seen.delete(value);\n\n return ordered;\n }\n\n return value;\n}\n\n/**\n * Deterministic JSON string with object keys sorted recursively, so key order\n * does not affect the output.\n *\n * Follows `JSON.stringify` semantics for the values it serializes: `undefined`,\n * functions and symbols are dropped, non-finite numbers become `null`, `bigint`\n * throws, and `Map`/`Set` serialize as `{}`. Distinct payloads can therefore\n * collapse to the same string — for collision-resistant hashing use\n * `PolicyHashing.canonicalJson` (policies/utils), which rejects those values\n * up front.\n *\n * @throws {TypeError} On circular references (mirroring `JSON.stringify`).\n */\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortKeysDeep(value, new WeakSet<object>()));\n}\n\nexport { stableStringify };\n"],"mappings":";AAAA,MAAM,6BACF;AAIJ,SAAS,aAAa,OAAgB,MAAgC;CAClE,IAAI,MAAM,QAAQ,KAAK,GAAG;EACtB,IAAI,KAAK,IAAI,KAAK,GACd,MAAM,IAAI,UAAU,0BAA0B;EAElD,KAAK,IAAI,KAAK;EACd,MAAM,SAAS,MAAM,KAAK,SAAS,aAAa,MAAM,IAAI,CAAC;EAC3D,KAAK,OAAO,KAAK;EACjB,OAAO;CACX;CAEA,IAAI,iBAAiB,MACjB,OAAO;CAGX,IAAI,SAAS,OAAO,UAAU,UAAU;EACpC,IAAI,KAAK,IAAI,KAAK,GACd,MAAM,IAAI,UAAU,0BAA0B;EAElD,KAAK,IAAI,KAAK;EACd,MAAM,SAAS;EACf,MAAM,UAAU,OAAO,OAAO,IAAI;EAElC,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK,GACvC,QAAQ,OAAO,aAAa,OAAO,MAAM,IAAI;EAEjD,KAAK,OAAO,KAAK;EAEjB,OAAO;CACX;CAEA,OAAO;AACX;;;;;;;;;;;;;;AAeA,SAAS,gBAAgB,OAAwB;CAC7C,OAAO,KAAK,UAAU,aAAa,uBAAO,IAAI,QAAgB,CAAC,CAAC;AACpE"}
@@ -0,0 +1,42 @@
1
+ //#region src/core/shared/stable-stringify.ts
2
+ const CIRCULAR_STRUCTURE_MESSAGE = "Cannot stably stringify a circular structure";
3
+ function sortKeysDeep(value, seen) {
4
+ if (Array.isArray(value)) {
5
+ if (seen.has(value)) throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);
6
+ seen.add(value);
7
+ const mapped = value.map((item) => sortKeysDeep(item, seen));
8
+ seen.delete(value);
9
+ return mapped;
10
+ }
11
+ if (value instanceof Date) return value;
12
+ if (value && typeof value === "object") {
13
+ if (seen.has(value)) throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);
14
+ seen.add(value);
15
+ const record = value;
16
+ const ordered = Object.create(null);
17
+ for (const key of Object.keys(record).sort()) ordered[key] = sortKeysDeep(record[key], seen);
18
+ seen.delete(value);
19
+ return ordered;
20
+ }
21
+ return value;
22
+ }
23
+ /**
24
+ * Deterministic JSON string with object keys sorted recursively, so key order
25
+ * does not affect the output.
26
+ *
27
+ * Follows `JSON.stringify` semantics for the values it serializes: `undefined`,
28
+ * functions and symbols are dropped, non-finite numbers become `null`, `bigint`
29
+ * throws, and `Map`/`Set` serialize as `{}`. Distinct payloads can therefore
30
+ * collapse to the same string — for collision-resistant hashing use
31
+ * `PolicyHashing.canonicalJson` (policies/utils), which rejects those values
32
+ * up front.
33
+ *
34
+ * @throws {TypeError} On circular references (mirroring `JSON.stringify`).
35
+ */
36
+ function stableStringify(value) {
37
+ return JSON.stringify(sortKeysDeep(value, /* @__PURE__ */ new WeakSet()));
38
+ }
39
+ //#endregion
40
+ export { stableStringify as t };
41
+
42
+ //# sourceMappingURL=stable-stringify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-stringify.js","names":[],"sources":["../src/core/shared/stable-stringify.ts"],"sourcesContent":["const CIRCULAR_STRUCTURE_MESSAGE =\n \"Cannot stably stringify a circular structure\";\n\n// `seen` holds the current traversal branch so true cycles are rejected while\n// repeated (acyclic) references are still serialized — matching JSON.stringify.\nfunction sortKeysDeep(value: unknown, seen: WeakSet<object>): unknown {\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);\n }\n seen.add(value);\n const mapped = value.map((item) => sortKeysDeep(item, seen));\n seen.delete(value);\n return mapped;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (value && typeof value === \"object\") {\n if (seen.has(value)) {\n throw new TypeError(CIRCULAR_STRUCTURE_MESSAGE);\n }\n seen.add(value);\n const record = value as Record<string, unknown>;\n const ordered = Object.create(null) as Record<string, unknown>;\n\n for (const key of Object.keys(record).sort()) {\n ordered[key] = sortKeysDeep(record[key], seen);\n }\n seen.delete(value);\n\n return ordered;\n }\n\n return value;\n}\n\n/**\n * Deterministic JSON string with object keys sorted recursively, so key order\n * does not affect the output.\n *\n * Follows `JSON.stringify` semantics for the values it serializes: `undefined`,\n * functions and symbols are dropped, non-finite numbers become `null`, `bigint`\n * throws, and `Map`/`Set` serialize as `{}`. Distinct payloads can therefore\n * collapse to the same string — for collision-resistant hashing use\n * `PolicyHashing.canonicalJson` (policies/utils), which rejects those values\n * up front.\n *\n * @throws {TypeError} On circular references (mirroring `JSON.stringify`).\n */\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortKeysDeep(value, new WeakSet<object>()));\n}\n\nexport { stableStringify };\n"],"mappings":";AAAA,MAAM,6BACF;AAIJ,SAAS,aAAa,OAAgB,MAAgC;CAClE,IAAI,MAAM,QAAQ,KAAK,GAAG;EACtB,IAAI,KAAK,IAAI,KAAK,GACd,MAAM,IAAI,UAAU,0BAA0B;EAElD,KAAK,IAAI,KAAK;EACd,MAAM,SAAS,MAAM,KAAK,SAAS,aAAa,MAAM,IAAI,CAAC;EAC3D,KAAK,OAAO,KAAK;EACjB,OAAO;CACX;CAEA,IAAI,iBAAiB,MACjB,OAAO;CAGX,IAAI,SAAS,OAAO,UAAU,UAAU;EACpC,IAAI,KAAK,IAAI,KAAK,GACd,MAAM,IAAI,UAAU,0BAA0B;EAElD,KAAK,IAAI,KAAK;EACd,MAAM,SAAS;EACf,MAAM,UAAU,OAAO,OAAO,IAAI;EAElC,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK,GACvC,QAAQ,OAAO,aAAa,OAAO,MAAM,IAAI;EAEjD,KAAK,OAAO,KAAK;EAEjB,OAAO;CACX;CAEA,OAAO;AACX;;;;;;;;;;;;;;AAeA,SAAS,gBAAgB,OAAwB;CAC7C,OAAO,KAAK,UAAU,aAAa,uBAAO,IAAI,QAAgB,CAAC,CAAC;AACpE"}
@@ -1,54 +1,8 @@
1
- const require_unexpected_error = require("./unexpected-error.cjs");
2
1
  const require_not_found_error = require("./not-found-error.cjs");
3
- const require_validation_code = require("./validation-code.cjs");
4
- const require_validation_field = require("./validation-field.cjs");
5
- const require_validation_exception = require("./validation-exception.cjs");
2
+ const require_unexpected_error = require("./unexpected-error.cjs");
6
3
  const require_immutable = require("./immutable.cjs");
7
4
  const require_use_case = require("./use-case.cjs");
8
5
  const require_temporal_guards = require("./temporal-guards.cjs");
9
- //#region src/core/application/commands/requested-by.ts
10
- const REQUESTED_BY_FIELD = require_validation_field.ValidationField.of("requestedBy");
11
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
- const SYSTEM_IDENTITY_PATTERN = /^system:[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
13
- var RequestedBy = class RequestedBy {
14
- constructor(kind, raw) {
15
- this.kind = kind;
16
- this.raw = raw;
17
- Object.freeze(this);
18
- }
19
- /** Builds from a human user ID (must be a UUID). */
20
- static fromUser(userId) {
21
- if (!UUID_PATTERN.test(userId)) throw new require_validation_exception.InvalidValueException(REQUESTED_BY_FIELD, require_validation_code.ValidationCode.INVALID_FORMAT, `requestedBy: user identity must be a UUID, got "${userId}"`);
22
- return new RequestedBy("user", userId);
23
- }
24
- /**
25
- * Builds from a system identity.
26
- * Only accepts the format "system:<job>", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.
27
- */
28
- static fromSystem(systemIdentity) {
29
- if (!SYSTEM_IDENTITY_PATTERN.test(systemIdentity)) throw new require_validation_exception.InvalidValueException(REQUESTED_BY_FIELD, require_validation_code.ValidationCode.INVALID_FORMAT, `requestedBy: system identity must match "system:<job>" (e.g. "system:late-fee-job"), got "${systemIdentity}"`);
30
- return new RequestedBy("system", systemIdentity);
31
- }
32
- /**
33
- * Infers the kind from the raw value.
34
- * Use when the origin (user vs system) is not known at the call-site.
35
- */
36
- static parse(raw) {
37
- if (UUID_PATTERN.test(raw)) return new RequestedBy("user", raw);
38
- if (SYSTEM_IDENTITY_PATTERN.test(raw)) return new RequestedBy("system", raw);
39
- throw new require_validation_exception.InvalidValueException(REQUESTED_BY_FIELD, require_validation_code.ValidationCode.INVALID_FORMAT, `requestedBy must be a UUID (user) or "system:<job>" (system), got "${raw}"`);
40
- }
41
- get isUser() {
42
- return this.kind === "user";
43
- }
44
- get isSystem() {
45
- return this.kind === "system";
46
- }
47
- toString() {
48
- return this.raw;
49
- }
50
- };
51
- //#endregion
52
6
  //#region src/core/application/commands/command.ts
53
7
  /**
54
8
  * Base for use cases that **mutate state** (writes), following CQS.
@@ -105,6 +59,11 @@ function mapPolicyEvaluationError(err) {
105
59
  * and safe to call multiple times with the same input.
106
60
  */
107
61
  let Query = class Query extends require_use_case.UseCase {
62
+ /**
63
+ * Declares how infrastructure should cache this query's result. Public so
64
+ * a caching adapter can actually read it off the instance; overrides must
65
+ * stay public.
66
+ */
108
67
  cacheStrategy() {
109
68
  return { kind: "NO_CACHE" };
110
69
  }
@@ -157,12 +116,6 @@ Object.defineProperty(exports, "Query", {
157
116
  return Query;
158
117
  }
159
118
  });
160
- Object.defineProperty(exports, "RequestedBy", {
161
- enumerable: true,
162
- get: function() {
163
- return RequestedBy;
164
- }
165
- });
166
119
  Object.defineProperty(exports, "TemporalUseCase", {
167
120
  enumerable: true,
168
121
  get: function() {
@@ -1 +1 @@
1
- {"version":3,"file":"temporal-use-case.cjs","names":["ValidationField","InvalidValueException","ValidationCode","UseCase","version","BusinessRuleViolationError","NotFoundError","UnexpectedError","UseCase","version","makeImmutable","UseCase"],"sources":["../src/core/application/commands/requested-by.ts","../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 { ValidationCode } from \"../../exceptions/validation-code.js\";\nimport { InvalidValueException } from \"../../exceptions/validation-exception.js\";\nimport { ValidationField } from \"../../exceptions/validation-field.js\";\n\n// Identifies who triggered a Command.\n// Two valid formats:\n// - Human user: UUID v4 (e.g.: \"550e8400-e29b-41d4-a716-446655440000\")\n// - System identity: \"system:<job>\" where <job> is [a-z][a-z0-9]*(-[a-z0-9]+)*\n// (e.g.: \"system:late-fee-job\", \"system:email-sender\")\n//\n// The split between `fromUser` / `fromSystem` / `parse` exists to make intent\n// explicit at the call-site: whoever builds the value knows where it came from.\n\ntype RequestedByKind = \"user\" | \"system\";\n\nconst REQUESTED_BY_FIELD = ValidationField.of(\"requestedBy\");\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n// system:<job> where <job> starts with a lowercase letter and may have hyphens between segments\nconst SYSTEM_IDENTITY_PATTERN = /^system:[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;\n\nclass RequestedBy {\n readonly kind: RequestedByKind;\n readonly raw: string;\n\n private constructor(kind: RequestedByKind, raw: string) {\n this.kind = kind;\n this.raw = raw;\n Object.freeze(this);\n }\n\n /** Builds from a human user ID (must be a UUID). */\n static fromUser(userId: string): RequestedBy {\n if (!UUID_PATTERN.test(userId)) {\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy: user identity must be a UUID, got \"${userId}\"`,\n );\n }\n\n return new RequestedBy(\"user\", userId);\n }\n\n /**\n * Builds from a system identity.\n * Only accepts the format \"system:<job>\", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.\n */\n static fromSystem(systemIdentity: string): RequestedBy {\n if (!SYSTEM_IDENTITY_PATTERN.test(systemIdentity)) {\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy: system identity must match \"system:<job>\" (e.g. \"system:late-fee-job\"), got \"${systemIdentity}\"`,\n );\n }\n\n return new RequestedBy(\"system\", systemIdentity);\n }\n\n /**\n * Infers the kind from the raw value.\n * Use when the origin (user vs system) is not known at the call-site.\n */\n static parse(raw: string): RequestedBy {\n if (UUID_PATTERN.test(raw)) {\n return new RequestedBy(\"user\", raw);\n }\n\n if (SYSTEM_IDENTITY_PATTERN.test(raw)) {\n return new RequestedBy(\"system\", raw);\n }\n\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy must be a UUID (user) or \"system:<job>\" (system), got \"${raw}\"`,\n );\n }\n\n get isUser(): boolean {\n return this.kind === \"user\";\n }\n\n get isSystem(): boolean {\n return this.kind === \"system\";\n }\n\n toString(): string {\n return this.raw;\n }\n}\n\nexport type { RequestedByKind };\nexport { RequestedBy };\n","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":"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"}
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 /**\n * Declares how infrastructure should cache this query's result. Public so\n * a caching adapter can actually read it off the instance; overrides must\n * stay public.\n */\n public 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;;;;;;CAM5C,gBAAsC;EAClC,OAAO,EAAE,MAAM,WAAW;CAC9B;AACJ;sCAdCC,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"}
@@ -1,8 +1,9 @@
1
- import { n as ContractVersion } from "./version.cjs";
2
- import { a as Result } from "./outcome.cjs";
1
+ import { t as RequestedBy } from "./requested-by.cjs";
3
2
  import { t as AppError } from "./app-error.cjs";
4
- import { b as ContextSeed, r as PolicyEvaluationResult, s as PolicyEvaluationError, t as EvaluateInput } from "./policy-service.cjs";
3
+ import { r as Result } from "./result.cjs";
5
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";
6
7
 
7
8
  //#region src/core/application/ports/logger.port.d.ts
8
9
  type LogPayload = Record<string, unknown>;
@@ -79,29 +80,6 @@ declare abstract class UseCase<Input = void, Output extends Result<unknown, unkn
79
80
  private runInstrumented;
80
81
  }
81
82
  //#endregion
82
- //#region src/core/application/commands/requested-by.d.ts
83
- type RequestedByKind = "user" | "system";
84
- declare class RequestedBy {
85
- readonly kind: RequestedByKind;
86
- readonly raw: string;
87
- private constructor();
88
- /** Builds from a human user ID (must be a UUID). */
89
- static fromUser(userId: string): RequestedBy;
90
- /**
91
- * Builds from a system identity.
92
- * Only accepts the format "system:<job>", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.
93
- */
94
- static fromSystem(systemIdentity: string): RequestedBy;
95
- /**
96
- * Infers the kind from the raw value.
97
- * Use when the origin (user vs system) is not known at the call-site.
98
- */
99
- static parse(raw: string): RequestedBy;
100
- get isUser(): boolean;
101
- get isSystem(): boolean;
102
- toString(): string;
103
- }
104
- //#endregion
105
83
  //#region src/core/application/commands/command.d.ts
106
84
  interface CommandInput {
107
85
  readonly requestedBy: RequestedBy;
@@ -264,7 +242,12 @@ type QueryOutput = object | string | number | boolean | bigint | symbol | null;
264
242
  * and safe to call multiple times with the same input.
265
243
  */
266
244
  declare abstract class Query<Input = void, Data extends QueryOutput = never, Failure = AppError> extends UseCase<Input, Result<Data, Failure>> {
267
- protected cacheStrategy(): CacheStrategy;
245
+ /**
246
+ * Declares how infrastructure should cache this query's result. Public so
247
+ * a caching adapter can actually read it off the instance; overrides must
248
+ * stay public.
249
+ */
250
+ cacheStrategy(): CacheStrategy;
268
251
  }
269
252
  //#endregion
270
253
  //#region src/core/application/temporal/temporal-context.d.ts
@@ -300,5 +283,5 @@ declare abstract class TemporalUseCase<Input extends object, Output extends Resu
300
283
  protected buildPolicySeed<TSeed extends ContextSeed>(seed: TSeed, temporalContext: TemporalContext): TemporalizedContextSeed<TSeed>;
301
284
  }
302
285
  //#endregion
303
- export { MetricLabels as A, RequestedByKind as C, TraceAttributeValue as D, UseCaseObservability as E, LogPayload as M, LoggerPort as N, TraceSpan as O, RequestedBy as S, UseCase 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, MetricsPort as j, TracerPort 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, MaybePromise as w, CommandInput as x, PolicyPortError as y };
286
+ 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 };
304
287
  //# sourceMappingURL=temporal-use-case.d.cts.map
@@ -1,8 +1,9 @@
1
- import { n as ContractVersion } from "./version.js";
2
- import { a as Result } from "./outcome.js";
1
+ import { t as RequestedBy } from "./requested-by.js";
3
2
  import { t as AppError } from "./app-error.js";
4
- import { b as ContextSeed, r as PolicyEvaluationResult, s as PolicyEvaluationError, t as EvaluateInput } from "./policy-service.js";
3
+ import { r as Result } from "./result.js";
5
4
  import { t as DeepReadonly } from "./immutable.js";
5
+ import { n as ContractVersion } from "./version.js";
6
+ import { b as ContextSeed, r as PolicyEvaluationResult, s as PolicyEvaluationError, t as EvaluateInput } from "./policy-service.js";
6
7
 
7
8
  //#region src/core/application/ports/logger.port.d.ts
8
9
  type LogPayload = Record<string, unknown>;
@@ -79,29 +80,6 @@ declare abstract class UseCase<Input = void, Output extends Result<unknown, unkn
79
80
  private runInstrumented;
80
81
  }
81
82
  //#endregion
82
- //#region src/core/application/commands/requested-by.d.ts
83
- type RequestedByKind = "user" | "system";
84
- declare class RequestedBy {
85
- readonly kind: RequestedByKind;
86
- readonly raw: string;
87
- private constructor();
88
- /** Builds from a human user ID (must be a UUID). */
89
- static fromUser(userId: string): RequestedBy;
90
- /**
91
- * Builds from a system identity.
92
- * Only accepts the format "system:<job>", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.
93
- */
94
- static fromSystem(systemIdentity: string): RequestedBy;
95
- /**
96
- * Infers the kind from the raw value.
97
- * Use when the origin (user vs system) is not known at the call-site.
98
- */
99
- static parse(raw: string): RequestedBy;
100
- get isUser(): boolean;
101
- get isSystem(): boolean;
102
- toString(): string;
103
- }
104
- //#endregion
105
83
  //#region src/core/application/commands/command.d.ts
106
84
  interface CommandInput {
107
85
  readonly requestedBy: RequestedBy;
@@ -264,7 +242,12 @@ type QueryOutput = object | string | number | boolean | bigint | symbol | null;
264
242
  * and safe to call multiple times with the same input.
265
243
  */
266
244
  declare abstract class Query<Input = void, Data extends QueryOutput = never, Failure = AppError> extends UseCase<Input, Result<Data, Failure>> {
267
- protected cacheStrategy(): CacheStrategy;
245
+ /**
246
+ * Declares how infrastructure should cache this query's result. Public so
247
+ * a caching adapter can actually read it off the instance; overrides must
248
+ * stay public.
249
+ */
250
+ cacheStrategy(): CacheStrategy;
268
251
  }
269
252
  //#endregion
270
253
  //#region src/core/application/temporal/temporal-context.d.ts
@@ -300,5 +283,5 @@ declare abstract class TemporalUseCase<Input extends object, Output extends Resu
300
283
  protected buildPolicySeed<TSeed extends ContextSeed>(seed: TSeed, temporalContext: TemporalContext): TemporalizedContextSeed<TSeed>;
301
284
  }
302
285
  //#endregion
303
- export { MetricLabels as A, RequestedByKind as C, TraceAttributeValue as D, UseCaseObservability as E, LogPayload as M, LoggerPort as N, TraceSpan as O, RequestedBy as S, UseCase 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, MetricsPort as j, TracerPort 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, MaybePromise as w, CommandInput as x, PolicyPortError as y };
286
+ 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 };
304
287
  //# sourceMappingURL=temporal-use-case.d.ts.map
@@ -1,54 +1,8 @@
1
- import { t as UnexpectedError } from "./unexpected-error.js";
2
1
  import { n as BusinessRuleViolationError, t as NotFoundError } from "./not-found-error.js";
3
- import { t as ValidationCode } from "./validation-code.js";
4
- import { t as ValidationField } from "./validation-field.js";
5
- import { t as InvalidValueException } from "./validation-exception.js";
2
+ import { t as UnexpectedError } from "./unexpected-error.js";
6
3
  import { i as version, n as __decorate, t as makeImmutable } from "./immutable.js";
7
4
  import { t as UseCase } from "./use-case.js";
8
5
  import { t as assertValidDate } from "./temporal-guards.js";
9
- //#region src/core/application/commands/requested-by.ts
10
- const REQUESTED_BY_FIELD = ValidationField.of("requestedBy");
11
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
- const SYSTEM_IDENTITY_PATTERN = /^system:[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
13
- var RequestedBy = class RequestedBy {
14
- constructor(kind, raw) {
15
- this.kind = kind;
16
- this.raw = raw;
17
- Object.freeze(this);
18
- }
19
- /** Builds from a human user ID (must be a UUID). */
20
- static fromUser(userId) {
21
- if (!UUID_PATTERN.test(userId)) throw new InvalidValueException(REQUESTED_BY_FIELD, ValidationCode.INVALID_FORMAT, `requestedBy: user identity must be a UUID, got "${userId}"`);
22
- return new RequestedBy("user", userId);
23
- }
24
- /**
25
- * Builds from a system identity.
26
- * Only accepts the format "system:<job>", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.
27
- */
28
- static fromSystem(systemIdentity) {
29
- if (!SYSTEM_IDENTITY_PATTERN.test(systemIdentity)) throw new InvalidValueException(REQUESTED_BY_FIELD, ValidationCode.INVALID_FORMAT, `requestedBy: system identity must match "system:<job>" (e.g. "system:late-fee-job"), got "${systemIdentity}"`);
30
- return new RequestedBy("system", systemIdentity);
31
- }
32
- /**
33
- * Infers the kind from the raw value.
34
- * Use when the origin (user vs system) is not known at the call-site.
35
- */
36
- static parse(raw) {
37
- if (UUID_PATTERN.test(raw)) return new RequestedBy("user", raw);
38
- if (SYSTEM_IDENTITY_PATTERN.test(raw)) return new RequestedBy("system", raw);
39
- throw new InvalidValueException(REQUESTED_BY_FIELD, ValidationCode.INVALID_FORMAT, `requestedBy must be a UUID (user) or "system:<job>" (system), got "${raw}"`);
40
- }
41
- get isUser() {
42
- return this.kind === "user";
43
- }
44
- get isSystem() {
45
- return this.kind === "system";
46
- }
47
- toString() {
48
- return this.raw;
49
- }
50
- };
51
- //#endregion
52
6
  //#region src/core/application/commands/command.ts
53
7
  /**
54
8
  * Base for use cases that **mutate state** (writes), following CQS.
@@ -105,6 +59,11 @@ function mapPolicyEvaluationError(err) {
105
59
  * and safe to call multiple times with the same input.
106
60
  */
107
61
  let Query = class Query extends UseCase {
62
+ /**
63
+ * Declares how infrastructure should cache this query's result. Public so
64
+ * a caching adapter can actually read it off the instance; overrides must
65
+ * stay public.
66
+ */
108
67
  cacheStrategy() {
109
68
  return { kind: "NO_CACHE" };
110
69
  }
@@ -145,6 +104,6 @@ var TemporalUseCase = class extends UseCase {
145
104
  }
146
105
  };
147
106
  //#endregion
148
- export { mapPolicyEvaluationError as a, Query as i, assertTemporalContext as n, Command as o, createTemporalContext as r, RequestedBy as s, TemporalUseCase as t };
107
+ export { mapPolicyEvaluationError as a, Query as i, assertTemporalContext as n, Command as o, createTemporalContext as r, TemporalUseCase as t };
149
108
 
150
109
  //# sourceMappingURL=temporal-use-case.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"temporal-use-case.js","names":[],"sources":["../src/core/application/commands/requested-by.ts","../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 { ValidationCode } from \"../../exceptions/validation-code.js\";\nimport { InvalidValueException } from \"../../exceptions/validation-exception.js\";\nimport { ValidationField } from \"../../exceptions/validation-field.js\";\n\n// Identifies who triggered a Command.\n// Two valid formats:\n// - Human user: UUID v4 (e.g.: \"550e8400-e29b-41d4-a716-446655440000\")\n// - System identity: \"system:<job>\" where <job> is [a-z][a-z0-9]*(-[a-z0-9]+)*\n// (e.g.: \"system:late-fee-job\", \"system:email-sender\")\n//\n// The split between `fromUser` / `fromSystem` / `parse` exists to make intent\n// explicit at the call-site: whoever builds the value knows where it came from.\n\ntype RequestedByKind = \"user\" | \"system\";\n\nconst REQUESTED_BY_FIELD = ValidationField.of(\"requestedBy\");\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n// system:<job> where <job> starts with a lowercase letter and may have hyphens between segments\nconst SYSTEM_IDENTITY_PATTERN = /^system:[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;\n\nclass RequestedBy {\n readonly kind: RequestedByKind;\n readonly raw: string;\n\n private constructor(kind: RequestedByKind, raw: string) {\n this.kind = kind;\n this.raw = raw;\n Object.freeze(this);\n }\n\n /** Builds from a human user ID (must be a UUID). */\n static fromUser(userId: string): RequestedBy {\n if (!UUID_PATTERN.test(userId)) {\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy: user identity must be a UUID, got \"${userId}\"`,\n );\n }\n\n return new RequestedBy(\"user\", userId);\n }\n\n /**\n * Builds from a system identity.\n * Only accepts the format \"system:<job>\", where <job> matches [a-z][a-z0-9]*(-[a-z0-9]+)*.\n */\n static fromSystem(systemIdentity: string): RequestedBy {\n if (!SYSTEM_IDENTITY_PATTERN.test(systemIdentity)) {\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy: system identity must match \"system:<job>\" (e.g. \"system:late-fee-job\"), got \"${systemIdentity}\"`,\n );\n }\n\n return new RequestedBy(\"system\", systemIdentity);\n }\n\n /**\n * Infers the kind from the raw value.\n * Use when the origin (user vs system) is not known at the call-site.\n */\n static parse(raw: string): RequestedBy {\n if (UUID_PATTERN.test(raw)) {\n return new RequestedBy(\"user\", raw);\n }\n\n if (SYSTEM_IDENTITY_PATTERN.test(raw)) {\n return new RequestedBy(\"system\", raw);\n }\n\n throw new InvalidValueException(\n REQUESTED_BY_FIELD,\n ValidationCode.INVALID_FORMAT,\n `requestedBy must be a UUID (user) or \"system:<job>\" (system), got \"${raw}\"`,\n );\n }\n\n get isUser(): boolean {\n return this.kind === \"user\";\n }\n\n get isSystem(): boolean {\n return this.kind === \"system\";\n }\n\n toString(): string {\n return this.raw;\n }\n}\n\nexport type { RequestedByKind };\nexport { RequestedBy };\n","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":"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"}
1
+ {"version":3,"file":"temporal-use-case.js","names":[],"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 /**\n * Declares how infrastructure should cache this query's result. Public so\n * a caching adapter can actually read it off the instance; overrides must\n * stay public.\n */\n public 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,gBAGL,QAAuB,CAAC;sBAJjC,QAAQ,KAAK,CAAA,GAAA,OAAA;;;ACZd,SAAgB,yBAAyB,KAAsC;CAC3E,QAAQ,IAAI,MAAZ;EACI,KAAK,mBACD,OAAO,IAAI,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,IAAI,2BACP,sBACA,IAAI,SACJ,EAAE,cAAc,IAAI,aAAa,GACjC,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,oBACD,OAAO,IAAI,cACP,UACA,EAAE,WAAW,IAAI,UAAU,GAC3B,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,+BACD,OAAO,IAAI,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,IAAI,cACP,iBACA;GACI,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,sBAAsB,IAAI;EAC9B,GACA,EAAE,OAAO,IAAI,MAAM,CACvB;EACJ,KAAK,kBACD,OAAO,IAAI,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,cAIL,QAAsC;;;;;;CAM5C,gBAAsC;EAClC,OAAO,EAAE,MAAM,WAAW;CAC9B;AACJ;oBAdC,QAAQ,KAAK,CAAA,GAAA,KAAA;;;AC3Bd,SAAS,sBACL,iBACA,YAAoB,mBAChB;CACJ,gBAAgB,GAAG,UAAU,QAAQ,gBAAgB,IAAI;CACzD,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,OAAO,cAAc;EACjB;EACA;CACJ,CAAC;AACL;;;ACNA,IAAe,kBAAf,cAGU,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"}