@cullet/erp-core 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/KIT_CONTEXT.md +7 -1
  2. package/dist/abac/index.d.cts +2 -2
  3. package/dist/abac/index.d.ts +2 -2
  4. package/dist/aggregate-version.cjs +14 -0
  5. package/dist/aggregate-version.cjs.map +1 -0
  6. package/dist/aggregate-version.js +9 -0
  7. package/dist/aggregate-version.js.map +1 -0
  8. package/dist/app-error.cjs +21 -6
  9. package/dist/app-error.cjs.map +1 -1
  10. package/dist/app-error.js +21 -6
  11. package/dist/app-error.js.map +1 -1
  12. package/dist/application/index.cjs +8 -4
  13. package/dist/application/index.d.cts +2 -2
  14. package/dist/application/index.d.ts +2 -2
  15. package/dist/application/index.js +1 -2
  16. package/dist/authorization-error.cjs +67 -17
  17. package/dist/authorization-error.cjs.map +1 -1
  18. package/dist/authorization-error.d.cts +6 -2
  19. package/dist/authorization-error.d.ts +6 -2
  20. package/dist/authorization-error.js +50 -18
  21. package/dist/authorization-error.js.map +1 -1
  22. package/dist/authorizer.port.d.cts +18 -3
  23. package/dist/authorizer.port.d.ts +18 -3
  24. package/dist/composite-authorizer.d.cts +63 -10
  25. package/dist/composite-authorizer.d.ts +63 -10
  26. package/dist/condition-evaluator.cjs +117 -172
  27. package/dist/condition-evaluator.cjs.map +1 -1
  28. package/dist/condition-evaluator.js +118 -167
  29. package/dist/condition-evaluator.js.map +1 -1
  30. package/dist/core-config.d.cts +53 -6
  31. package/dist/core-config.d.ts +53 -6
  32. package/dist/decorate.cjs +14 -0
  33. package/dist/decorate.js +9 -0
  34. package/dist/domain/index.cjs +107 -2
  35. package/dist/domain/index.cjs.map +1 -0
  36. package/dist/domain/index.d.cts +25 -1
  37. package/dist/domain/index.d.ts +25 -1
  38. package/dist/domain/index.js +99 -3
  39. package/dist/domain/index.js.map +1 -0
  40. package/dist/domain-event-contracts.cjs +12 -8
  41. package/dist/domain-event-contracts.cjs.map +1 -1
  42. package/dist/domain-event-contracts.d.cts +29 -4
  43. package/dist/domain-event-contracts.d.ts +29 -4
  44. package/dist/domain-event-contracts.js +11 -7
  45. package/dist/domain-event-contracts.js.map +1 -1
  46. package/dist/domain-exception.cjs +2 -1
  47. package/dist/domain-exception.cjs.map +1 -1
  48. package/dist/domain-exception.js +2 -1
  49. package/dist/domain-exception.js.map +1 -1
  50. package/dist/errors/index.cjs +3 -2
  51. package/dist/errors/index.d.cts +1 -1
  52. package/dist/errors/index.d.ts +1 -1
  53. package/dist/errors/index.js +3 -2
  54. package/dist/exceptions/index.cjs +2 -2
  55. package/dist/exceptions/index.d.cts +1 -2
  56. package/dist/exceptions/index.d.ts +1 -2
  57. package/dist/exceptions/index.js +2 -2
  58. package/dist/gate-engine-registry.cjs.map +1 -1
  59. package/dist/gate-engine-registry.d.cts +1 -1
  60. package/dist/gate-engine-registry.d.ts +1 -1
  61. package/dist/gate-engine-registry.js.map +1 -1
  62. package/dist/gate-v1-payload.schema.cjs +11 -6
  63. package/dist/gate-v1-payload.schema.cjs.map +1 -1
  64. package/dist/gate-v1-payload.schema.js +11 -6
  65. package/dist/gate-v1-payload.schema.js.map +1 -1
  66. package/dist/hashing.cjs +2 -44
  67. package/dist/hashing.cjs.map +1 -1
  68. package/dist/hashing.js +3 -39
  69. package/dist/hashing.js.map +1 -1
  70. package/dist/immutable.cjs +25 -12
  71. package/dist/immutable.cjs.map +1 -1
  72. package/dist/immutable.js +24 -11
  73. package/dist/immutable.js.map +1 -1
  74. package/dist/index.cjs +15 -11
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +9 -10
  77. package/dist/index.d.ts +9 -10
  78. package/dist/index.js +8 -9
  79. package/dist/index.js.map +1 -1
  80. package/dist/invalid-state-transition-exception.cjs +6 -6
  81. package/dist/invalid-state-transition-exception.cjs.map +1 -1
  82. package/dist/invalid-state-transition-exception.js +6 -6
  83. package/dist/invalid-state-transition-exception.js.map +1 -1
  84. package/dist/invariant-violation-exception.cjs +2 -2
  85. package/dist/invariant-violation-exception.cjs.map +1 -1
  86. package/dist/invariant-violation-exception.js +2 -2
  87. package/dist/invariant-violation-exception.js.map +1 -1
  88. package/dist/not-found-error.cjs +6 -4
  89. package/dist/not-found-error.cjs.map +1 -1
  90. package/dist/not-found-error.js +6 -4
  91. package/dist/not-found-error.js.map +1 -1
  92. package/dist/outcome.cjs +5 -5
  93. package/dist/outcome.cjs.map +1 -1
  94. package/dist/outcome.js +5 -5
  95. package/dist/outcome.js.map +1 -1
  96. package/dist/parse-gate-payload.d.cts +1 -1
  97. package/dist/parse-gate-payload.d.ts +1 -1
  98. package/dist/path.d.cts +2 -2
  99. package/dist/path.d.ts +2 -2
  100. package/dist/plugin.cjs +23 -13
  101. package/dist/plugin.cjs.map +1 -1
  102. package/dist/plugin.d.cts +22 -8
  103. package/dist/plugin.d.ts +22 -8
  104. package/dist/plugin.js +23 -13
  105. package/dist/plugin.js.map +1 -1
  106. package/dist/policies/engines/index.d.cts +1 -1
  107. package/dist/policies/engines/index.d.ts +1 -1
  108. package/dist/policies/engines/v1/gate/index.d.cts +3 -13
  109. package/dist/policies/engines/v1/gate/index.d.ts +3 -13
  110. package/dist/policies/index.d.cts +1 -1
  111. package/dist/policies/index.d.ts +1 -1
  112. package/dist/policy-bridge.cjs +30 -2
  113. package/dist/policy-bridge.cjs.map +1 -1
  114. package/dist/policy-bridge.d.cts +18 -0
  115. package/dist/policy-bridge.d.ts +18 -0
  116. package/dist/policy-bridge.js +30 -2
  117. package/dist/policy-bridge.js.map +1 -1
  118. package/dist/policy-service.cjs +41 -46
  119. package/dist/policy-service.cjs.map +1 -1
  120. package/dist/policy-service.d.cts +72 -9
  121. package/dist/policy-service.d.ts +72 -9
  122. package/dist/policy-service.js +43 -48
  123. package/dist/policy-service.js.map +1 -1
  124. package/dist/requested-by.cjs +4 -4
  125. package/dist/requested-by.cjs.map +1 -1
  126. package/dist/requested-by.js +2 -2
  127. package/dist/requested-by.js.map +1 -1
  128. package/dist/result.cjs +29 -1
  129. package/dist/result.cjs.map +1 -1
  130. package/dist/result.d.cts +12 -0
  131. package/dist/result.d.ts +12 -0
  132. package/dist/result.js +29 -1
  133. package/dist/result.js.map +1 -1
  134. package/dist/rule.cjs +94 -24
  135. package/dist/rule.cjs.map +1 -1
  136. package/dist/rule.js +94 -24
  137. package/dist/rule.js.map +1 -1
  138. package/dist/ruleset-registry.cjs +19 -0
  139. package/dist/ruleset-registry.cjs.map +1 -1
  140. package/dist/ruleset-registry.js +19 -0
  141. package/dist/ruleset-registry.js.map +1 -1
  142. package/dist/stable-stringify.cjs +87 -0
  143. package/dist/stable-stringify.cjs.map +1 -0
  144. package/dist/stable-stringify.js +76 -0
  145. package/dist/stable-stringify.js.map +1 -0
  146. package/dist/temporal-snapshot.d.cts +87 -0
  147. package/dist/temporal-snapshot.d.ts +87 -0
  148. package/dist/temporal-use-case.cjs +174 -16
  149. package/dist/temporal-use-case.cjs.map +1 -1
  150. package/dist/temporal-use-case.d.cts +82 -44
  151. package/dist/temporal-use-case.d.ts +82 -44
  152. package/dist/temporal-use-case.js +167 -15
  153. package/dist/temporal-use-case.js.map +1 -1
  154. package/dist/unexpected-error.cjs +4 -2
  155. package/dist/unexpected-error.cjs.map +1 -1
  156. package/dist/unexpected-error.js +4 -2
  157. package/dist/unexpected-error.js.map +1 -1
  158. package/dist/uuid-identifier.cjs +141 -8
  159. package/dist/uuid-identifier.cjs.map +1 -1
  160. package/dist/uuid-identifier.d.cts +26 -7
  161. package/dist/uuid-identifier.d.ts +26 -7
  162. package/dist/uuid-identifier.js +135 -8
  163. package/dist/uuid-identifier.js.map +1 -1
  164. package/dist/uuid.cjs +23 -0
  165. package/dist/uuid.cjs.map +1 -0
  166. package/dist/uuid.js +18 -0
  167. package/dist/uuid.js.map +1 -0
  168. package/dist/validation-code.cjs +9 -0
  169. package/dist/validation-code.cjs.map +1 -1
  170. package/dist/validation-code.d.cts +3 -0
  171. package/dist/validation-code.d.ts +3 -0
  172. package/dist/validation-code.js +9 -0
  173. package/dist/validation-code.js.map +1 -1
  174. package/dist/validation-error.cjs +42 -67
  175. package/dist/validation-error.cjs.map +1 -1
  176. package/dist/validation-error.d.cts +29 -8
  177. package/dist/validation-error.d.ts +29 -8
  178. package/dist/validation-error.js +41 -66
  179. package/dist/validation-error.js.map +1 -1
  180. package/dist/validation-exception.cjs +17 -6
  181. package/dist/validation-exception.cjs.map +1 -1
  182. package/dist/validation-exception.d.cts +33 -9
  183. package/dist/validation-exception.d.ts +33 -9
  184. package/dist/validation-exception.js +17 -6
  185. package/dist/validation-exception.js.map +1 -1
  186. package/dist/validation-field.cjs +3 -0
  187. package/dist/validation-field.cjs.map +1 -1
  188. package/dist/validation-field.d.cts +1 -0
  189. package/dist/validation-field.d.ts +1 -0
  190. package/dist/validation-field.js +3 -0
  191. package/dist/validation-field.js.map +1 -1
  192. package/dist/value-object-ruleset.contracts.d.cts +10 -0
  193. package/dist/value-object-ruleset.contracts.d.ts +10 -0
  194. package/dist/value-object.cjs +23 -4
  195. package/dist/value-object.cjs.map +1 -1
  196. package/dist/value-object.d.cts +25 -1
  197. package/dist/value-object.d.ts +25 -1
  198. package/dist/value-object.js +23 -4
  199. package/dist/value-object.js.map +1 -1
  200. package/dist/version.d.cts +27 -0
  201. package/dist/version.d.ts +27 -0
  202. package/dist/versioning/index.d.cts +2 -2
  203. package/dist/versioning/index.d.ts +2 -2
  204. package/meta.json +5 -2
  205. package/package.json +1 -1
  206. package/src/core/abac/authorizer.ts +60 -10
  207. package/src/core/abac/domain/policy-set.ts +52 -5
  208. package/src/core/abac/domain/rule.ts +28 -16
  209. package/src/core/abac/index.ts +7 -1
  210. package/src/core/application/commands/command.ts +14 -1
  211. package/src/core/application/commands/requested-by.ts +13 -5
  212. package/src/core/application/index.ts +2 -1
  213. package/src/core/application/policy-error-mapper.ts +6 -0
  214. package/src/core/application/ports/index.ts +7 -0
  215. package/src/core/application/ports/temporal-repository.port.ts +35 -2
  216. package/src/core/application/queries/index.ts +1 -1
  217. package/src/core/application/queries/query.ts +25 -3
  218. package/src/core/application/temporal/temporal-use-case.ts +31 -4
  219. package/src/core/application/use-case.ts +45 -8
  220. package/src/core/config/core-config.ts +46 -25
  221. package/src/core/config/index.ts +1 -0
  222. package/src/core/config/policy-reporter.ts +32 -1
  223. package/src/core/domain/entity.ts +51 -8
  224. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +0 -2
  225. package/src/core/domain/rulesets/ruleset-registry.ts +24 -0
  226. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +1 -7
  227. package/src/core/domain/temporal/half-open-interval.ts +34 -0
  228. package/src/core/domain/temporal/temporal-snapshot.ts +13 -2
  229. package/src/core/domain/temporal/transaction-time.ts +19 -15
  230. package/src/core/domain/temporal/valid-time.ts +20 -15
  231. package/src/core/domain/uuid-identifier.ts +6 -11
  232. package/src/core/domain/value-object.ts +29 -3
  233. package/src/core/errors/authentication-error.ts +5 -31
  234. package/src/core/errors/authorization-error.ts +12 -20
  235. package/src/core/errors/business-rule-violation-error.ts +4 -1
  236. package/src/core/errors/idempotency-error.ts +5 -2
  237. package/src/core/errors/index.ts +4 -0
  238. package/src/core/errors/integration-error.ts +4 -24
  239. package/src/core/errors/legacy-incompatible-error.ts +1 -0
  240. package/src/core/errors/not-found-error.ts +4 -1
  241. package/src/core/errors/temporal-error.ts +22 -20
  242. package/src/core/errors/unexpected-error.ts +5 -1
  243. package/src/core/errors/utils/factory-helpers.ts +70 -0
  244. package/src/core/errors/utils/index.ts +5 -0
  245. package/src/core/errors/utils/json-safe.ts +35 -12
  246. package/src/core/errors/validation-error.ts +4 -1
  247. package/src/core/exceptions/business-rule-violation-exception.ts +2 -1
  248. package/src/core/exceptions/domain-exception.ts +9 -1
  249. package/src/core/exceptions/entity-not-found-exception.ts +5 -1
  250. package/src/core/exceptions/invalid-state-transition-exception.ts +5 -2
  251. package/src/core/exceptions/invariant-violation-exception.ts +2 -2
  252. package/src/core/exceptions/validation-code.ts +14 -0
  253. package/src/core/exceptions/validation-exception.ts +44 -5
  254. package/src/core/exceptions/validation-field.ts +11 -1
  255. package/src/core/plugins/plugin.ts +25 -15
  256. package/src/core/plugins/types.ts +4 -2
  257. package/src/core/policies/asof/asof.ts +12 -0
  258. package/src/core/policies/catalog/policy-catalog-entry.ts +3 -1
  259. package/src/core/policies/catalog/policy-catalog.ts +5 -1
  260. package/src/core/policies/context/context-builder.ts +20 -0
  261. package/src/core/policies/context/context-resolver.ts +5 -0
  262. package/src/core/policies/context/context-seed.ts +11 -0
  263. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +0 -2
  264. package/src/core/policies/engines/parse-gate-payload.ts +9 -0
  265. package/src/core/policies/engines/v1/condition-date-operands.ts +112 -0
  266. package/src/core/policies/engines/v1/condition-evaluator.ts +120 -291
  267. package/src/core/policies/engines/v1/condition-schema.ts +23 -19
  268. package/src/core/policies/engines/v1/condition-types.ts +18 -11
  269. package/src/core/policies/index.ts +4 -6
  270. package/src/core/policies/service/policy-service.ts +46 -35
  271. package/src/core/policies/utils/hash.ts +5 -69
  272. package/src/core/policies/utils/result.ts +1 -1
  273. package/src/core/rbac/access-request.ts +12 -2
  274. package/src/core/rbac/authorizer.ts +8 -1
  275. package/src/core/rbac/domain/role.ts +20 -0
  276. package/src/core/rbac/domain/scope.ts +6 -1
  277. package/src/core/result/index.ts +5 -0
  278. package/src/core/result/outcome.ts +5 -7
  279. package/src/core/result/result.ts +34 -1
  280. package/src/core/shared/hashing.ts +6 -57
  281. package/src/core/shared/immutable.ts +22 -4
  282. package/src/core/shared/stable-stringify.ts +144 -0
  283. package/src/core/shared/uuid.ts +16 -0
  284. package/src/core/versioning/domain-event-contracts.ts +43 -15
  285. package/src/core/versioning/index.ts +1 -0
  286. package/src/core/versioning/version.ts +30 -1
  287. package/src/domain/index.ts +5 -0
  288. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +1 -1
  289. package/src/examples/rulesets/entity/order-invariants-v1.ts +2 -4
  290. package/src/examples/rulesets/entity/order-invariants-v2.ts +2 -4
  291. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +2 -4
  292. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +2 -4
  293. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +2 -4
  294. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +2 -4
  295. package/src/result/index.ts +7 -2
  296. package/src/version.ts +1 -1
  297. package/dist/domain-exception.d.cts +0 -7
  298. package/dist/domain-exception.d.ts +0 -7
  299. package/dist/entity.cjs +0 -126
  300. package/dist/entity.cjs.map +0 -1
  301. package/dist/entity.js +0 -115
  302. package/dist/entity.js.map +0 -1
  303. package/dist/use-case.cjs +0 -96
  304. package/dist/use-case.cjs.map +0 -1
  305. package/dist/use-case.js +0 -91
  306. package/dist/use-case.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"requested-by.cjs","names":["ValidationField","InvalidValueException","ValidationCode"],"sources":["../src/core/application/commands/requested-by.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"],"mappings":";;;;AAeA,MAAM,qBAAqBA,yBAAAA,gBAAgB,GAAG,aAAa;AAE3D,MAAM,eACF;AAGJ,MAAM,0BAA0B;AAEhC,IAAM,cAAN,MAAM,YAAY;CAId,YAAoB,MAAuB,KAAa;EACpD,KAAK,OAAO;EACZ,KAAK,MAAM;EACX,OAAO,OAAO,IAAI;CACtB;;CAGA,OAAO,SAAS,QAA6B;EACzC,IAAI,CAAC,aAAa,KAAK,MAAM,GACzB,MAAM,IAAIC,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,mDAAmD,OAAO,EAC9D;EAGJ,OAAO,IAAI,YAAY,QAAQ,MAAM;CACzC;;;;;CAMA,OAAO,WAAW,gBAAqC;EACnD,IAAI,CAAC,wBAAwB,KAAK,cAAc,GAC5C,MAAM,IAAID,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,6FAA6F,eAAe,EAChH;EAGJ,OAAO,IAAI,YAAY,UAAU,cAAc;CACnD;;;;;CAMA,OAAO,MAAM,KAA0B;EACnC,IAAI,aAAa,KAAK,GAAG,GACrB,OAAO,IAAI,YAAY,QAAQ,GAAG;EAGtC,IAAI,wBAAwB,KAAK,GAAG,GAChC,OAAO,IAAI,YAAY,UAAU,GAAG;EAGxC,MAAM,IAAID,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,sEAAsE,IAAI,EAC9E;CACJ;CAEA,IAAI,SAAkB;EAClB,OAAO,KAAK,SAAS;CACzB;CAEA,IAAI,WAAoB;EACpB,OAAO,KAAK,SAAS;CACzB;CAEA,WAAmB;EACf,OAAO,KAAK;CAChB;AACJ"}
1
+ {"version":3,"file":"requested-by.cjs","names":["ValidationField","UUID_PATTERN","InvalidValueException","ValidationCode"],"sources":["../src/core/application/commands/requested-by.ts"],"sourcesContent":["import { ValidationCode } from \"../../exceptions/validation-code.js\";\nimport { InvalidValueException } from \"../../exceptions/validation-exception.js\";\nimport { ValidationField } from \"../../exceptions/validation-field.js\";\nimport { UUID_PATTERN } from \"../../shared/uuid.js\";\n\n// Identifies who triggered a Command.\n// Two valid formats:\n// - Human user: canonical RFC-4122 UUID, versions 1–8 (including the\n// time-ordered v7) — the same pattern `UuidIdentifier` enforces\n// (e.g.: \"550e8400-e29b-41d4-a716-446655440000\"). The constraint is\n// single-sourced in `shared/uuid.ts`, so `UuidIdentifier` and this value\n// always accept the same set of versions.\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\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 // UUIDs are matched case-insensitively (see `shared/uuid.ts`), so the\n // same user id can arrive lowercased from Postgres and uppercased from a\n // JWT. Canonicalize user ids to lowercase here — the single choke point\n // every factory passes through — so exact-string comparators downstream\n // (`Grant.appliesTo`, `rbacContextFields`) never miss the right identity\n // on case alone. System identities are already lowercase by pattern.\n this.raw = kind === \"user\" ? raw.toLowerCase() : 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"],"mappings":";;;;;AAoBA,MAAM,qBAAqBA,yBAAAA,gBAAgB,GAAG,aAAa;AAG3D,MAAM,0BAA0B;AAEhC,IAAM,cAAN,MAAM,YAAY;CAId,YAAoB,MAAuB,KAAa;EACpD,KAAK,OAAO;EAOZ,KAAK,MAAM,SAAS,SAAS,IAAI,YAAY,IAAI;EACjD,OAAO,OAAO,IAAI;CACtB;;CAGA,OAAO,SAAS,QAA6B;EACzC,IAAI,CAACC,aAAAA,aAAa,KAAK,MAAM,GACzB,MAAM,IAAIC,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,mDAAmD,OAAO,EAC9D;EAGJ,OAAO,IAAI,YAAY,QAAQ,MAAM;CACzC;;;;;CAMA,OAAO,WAAW,gBAAqC;EACnD,IAAI,CAAC,wBAAwB,KAAK,cAAc,GAC5C,MAAM,IAAID,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,6FAA6F,eAAe,EAChH;EAGJ,OAAO,IAAI,YAAY,UAAU,cAAc;CACnD;;;;;CAMA,OAAO,MAAM,KAA0B;EACnC,IAAIF,aAAAA,aAAa,KAAK,GAAG,GACrB,OAAO,IAAI,YAAY,QAAQ,GAAG;EAGtC,IAAI,wBAAwB,KAAK,GAAG,GAChC,OAAO,IAAI,YAAY,UAAU,GAAG;EAGxC,MAAM,IAAIC,6BAAAA,sBACN,oBACAC,wBAAAA,eAAe,gBACf,sEAAsE,IAAI,EAC9E;CACJ;CAEA,IAAI,SAAkB;EAClB,OAAO,KAAK,SAAS;CACzB;CAEA,IAAI,WAAoB;EACpB,OAAO,KAAK,SAAS;CACzB;CAEA,WAAmB;EACf,OAAO,KAAK;CAChB;AACJ"}
@@ -1,14 +1,14 @@
1
1
  import { t as ValidationCode } from "./validation-code.js";
2
2
  import { t as ValidationField } from "./validation-field.js";
3
3
  import { t as InvalidValueException } from "./validation-exception.js";
4
+ import { t as UUID_PATTERN } from "./uuid.js";
4
5
  //#region src/core/application/commands/requested-by.ts
5
6
  const REQUESTED_BY_FIELD = ValidationField.of("requestedBy");
6
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7
7
  const SYSTEM_IDENTITY_PATTERN = /^system:[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
8
8
  var RequestedBy = class RequestedBy {
9
9
  constructor(kind, raw) {
10
10
  this.kind = kind;
11
- this.raw = raw;
11
+ this.raw = kind === "user" ? raw.toLowerCase() : raw;
12
12
  Object.freeze(this);
13
13
  }
14
14
  /** Builds from a human user ID (must be a UUID). */
@@ -1 +1 @@
1
- {"version":3,"file":"requested-by.js","names":[],"sources":["../src/core/application/commands/requested-by.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"],"mappings":";;;;AAeA,MAAM,qBAAqB,gBAAgB,GAAG,aAAa;AAE3D,MAAM,eACF;AAGJ,MAAM,0BAA0B;AAEhC,IAAM,cAAN,MAAM,YAAY;CAId,YAAoB,MAAuB,KAAa;EACpD,KAAK,OAAO;EACZ,KAAK,MAAM;EACX,OAAO,OAAO,IAAI;CACtB;;CAGA,OAAO,SAAS,QAA6B;EACzC,IAAI,CAAC,aAAa,KAAK,MAAM,GACzB,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,mDAAmD,OAAO,EAC9D;EAGJ,OAAO,IAAI,YAAY,QAAQ,MAAM;CACzC;;;;;CAMA,OAAO,WAAW,gBAAqC;EACnD,IAAI,CAAC,wBAAwB,KAAK,cAAc,GAC5C,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,6FAA6F,eAAe,EAChH;EAGJ,OAAO,IAAI,YAAY,UAAU,cAAc;CACnD;;;;;CAMA,OAAO,MAAM,KAA0B;EACnC,IAAI,aAAa,KAAK,GAAG,GACrB,OAAO,IAAI,YAAY,QAAQ,GAAG;EAGtC,IAAI,wBAAwB,KAAK,GAAG,GAChC,OAAO,IAAI,YAAY,UAAU,GAAG;EAGxC,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,sEAAsE,IAAI,EAC9E;CACJ;CAEA,IAAI,SAAkB;EAClB,OAAO,KAAK,SAAS;CACzB;CAEA,IAAI,WAAoB;EACpB,OAAO,KAAK,SAAS;CACzB;CAEA,WAAmB;EACf,OAAO,KAAK;CAChB;AACJ"}
1
+ {"version":3,"file":"requested-by.js","names":[],"sources":["../src/core/application/commands/requested-by.ts"],"sourcesContent":["import { ValidationCode } from \"../../exceptions/validation-code.js\";\nimport { InvalidValueException } from \"../../exceptions/validation-exception.js\";\nimport { ValidationField } from \"../../exceptions/validation-field.js\";\nimport { UUID_PATTERN } from \"../../shared/uuid.js\";\n\n// Identifies who triggered a Command.\n// Two valid formats:\n// - Human user: canonical RFC-4122 UUID, versions 1–8 (including the\n// time-ordered v7) — the same pattern `UuidIdentifier` enforces\n// (e.g.: \"550e8400-e29b-41d4-a716-446655440000\"). The constraint is\n// single-sourced in `shared/uuid.ts`, so `UuidIdentifier` and this value\n// always accept the same set of versions.\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\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 // UUIDs are matched case-insensitively (see `shared/uuid.ts`), so the\n // same user id can arrive lowercased from Postgres and uppercased from a\n // JWT. Canonicalize user ids to lowercase here — the single choke point\n // every factory passes through — so exact-string comparators downstream\n // (`Grant.appliesTo`, `rbacContextFields`) never miss the right identity\n // on case alone. System identities are already lowercase by pattern.\n this.raw = kind === \"user\" ? raw.toLowerCase() : 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"],"mappings":";;;;;AAoBA,MAAM,qBAAqB,gBAAgB,GAAG,aAAa;AAG3D,MAAM,0BAA0B;AAEhC,IAAM,cAAN,MAAM,YAAY;CAId,YAAoB,MAAuB,KAAa;EACpD,KAAK,OAAO;EAOZ,KAAK,MAAM,SAAS,SAAS,IAAI,YAAY,IAAI;EACjD,OAAO,OAAO,IAAI;CACtB;;CAGA,OAAO,SAAS,QAA6B;EACzC,IAAI,CAAC,aAAa,KAAK,MAAM,GACzB,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,mDAAmD,OAAO,EAC9D;EAGJ,OAAO,IAAI,YAAY,QAAQ,MAAM;CACzC;;;;;CAMA,OAAO,WAAW,gBAAqC;EACnD,IAAI,CAAC,wBAAwB,KAAK,cAAc,GAC5C,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,6FAA6F,eAAe,EAChH;EAGJ,OAAO,IAAI,YAAY,UAAU,cAAc;CACnD;;;;;CAMA,OAAO,MAAM,KAA0B;EACnC,IAAI,aAAa,KAAK,GAAG,GACrB,OAAO,IAAI,YAAY,QAAQ,GAAG;EAGtC,IAAI,wBAAwB,KAAK,GAAG,GAChC,OAAO,IAAI,YAAY,UAAU,GAAG;EAGxC,MAAM,IAAI,sBACN,oBACA,eAAe,gBACf,sEAAsE,IAAI,EAC9E;CACJ;CAEA,IAAI,SAAkB;EAClB,OAAO,KAAK,SAAS;CACzB;CAEA,IAAI,WAAoB;EACpB,OAAO,KAAK,SAAS;CACzB;CAEA,WAAmB;EACf,OAAO,KAAK;CAChB;AACJ"}
package/dist/result.cjs CHANGED
@@ -19,6 +19,9 @@ var Result = class Result {
19
19
  }
20
20
  /**
21
21
  * Returns the value when this is success, or null when this is an error.
22
+ *
23
+ * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.
24
+ * Use `match`/`isOk` when that distinction matters.
22
25
  */
23
26
  getOrNull() {
24
27
  return this.match({
@@ -42,12 +45,14 @@ var Result = class Result {
42
45
  return this.match({
43
46
  ok: (value) => value,
44
47
  err: (error) => {
45
- throw error instanceof Error ? error : new Error(String(error));
48
+ throw toError(error);
46
49
  }
47
50
  });
48
51
  }
49
52
  /**
50
53
  * Transforms the success value using the provided function. Keeps the error when this is a failure.
54
+ *
55
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
51
56
  */
52
57
  map(transform) {
53
58
  return this.match({
@@ -57,6 +62,8 @@ var Result = class Result {
57
62
  }
58
63
  /**
59
64
  * Transforms the error using the provided function. Keeps the value when this is a success.
65
+ *
66
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
60
67
  */
61
68
  mapError(transform) {
62
69
  return this.match({
@@ -66,6 +73,8 @@ var Result = class Result {
66
73
  }
67
74
  /**
68
75
  * Chains another operation that returns a Result when this result is a success.
76
+ *
77
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
69
78
  */
70
79
  flatMap(transform) {
71
80
  return this.match({
@@ -76,6 +85,9 @@ var Result = class Result {
76
85
  };
77
86
  /**
78
87
  * Success variant of Result.
88
+ *
89
+ * The instance is frozen, but the freeze is shallow: a contained mutable
90
+ * object is not frozen.
79
91
  */
80
92
  var Ok = class extends Result {
81
93
  constructor(value) {
@@ -112,6 +124,22 @@ var Err = class extends Result {
112
124
  return handlers.err(this.error);
113
125
  }
114
126
  };
127
+ /**
128
+ * Coerces an arbitrary error value into an Error for `getOrThrow`, preserving
129
+ * the original structured payload as `cause` so nothing is lost when `E` is a
130
+ * plain object/DTO rather than an Error.
131
+ */
132
+ function toError(error) {
133
+ if (error instanceof Error) return error;
134
+ if (typeof error === "string") return new Error(error);
135
+ let message;
136
+ try {
137
+ message = JSON.stringify(error);
138
+ } catch {}
139
+ const wrapped = new Error(message ?? String(error));
140
+ wrapped.cause = error;
141
+ return wrapped;
142
+ }
115
143
  //#endregion
116
144
  Object.defineProperty(exports, "Err", {
117
145
  enumerable: true,
@@ -1 +1 @@
1
- {"version":3,"file":"result.cjs","names":[],"sources":["../src/core/result/result.ts"],"sourcesContent":["/**\n * Represents the outcome of an operation that may succeed or fail.\n * Useful for avoiding excessive exceptions and making error flows explicit.\n */\nexport abstract class Result<T, E> {\n protected constructor() {\n // prevent direct instantiation\n }\n\n /**\n * Creates a success result containing a value.\n */\n static ok<T>(value: T): Result<T, never> {\n return new Ok(value);\n }\n\n /**\n * Creates an error result containing an error object.\n */\n static err<E>(error: E): Result<never, E> {\n return new Err(error);\n }\n\n /**\n * Returns true when this is a success result.\n */\n abstract isOk(): this is Ok<T>;\n\n /**\n * Returns true when this is an error result.\n */\n abstract isErr(): this is Err<E>;\n\n /**\n * Runs a handler based on the result state (similar to pattern matching).\n */\n abstract match<R>(handlers: {\n ok: (value: T) => R;\n err: (error: E) => R;\n }): R;\n\n /**\n * Returns the value when this is success, or null when this is an error.\n */\n getOrNull(): T | null {\n return this.match({\n ok: (value) => value,\n err: () => null,\n });\n }\n\n /**\n * Returns the error when this is a failure, or null when this is a success.\n */\n errorOrNull(): E | null {\n return this.match({\n ok: () => null,\n err: (error) => error,\n });\n }\n\n /**\n * Returns the value when this is a success, or throws the error when this is a failure.\n */\n getOrThrow(): T {\n return this.match({\n ok: (value) => value,\n err: (error) => {\n throw error instanceof Error ? error : new Error(String(error));\n },\n });\n }\n\n /**\n * Transforms the success value using the provided function. Keeps the error when this is a failure.\n */\n map<R>(transform: (value: T) => R): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => Result.ok(transform(value)),\n // TS does not \"see\" variance here; the cast is intentional and safe:\n // Err carries no T, so it can be widened to Result<R, E> safely.\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n\n /**\n * Transforms the error using the provided function. Keeps the value when this is a success.\n */\n mapError<F>(transform: (error: E) => F): Result<T, F> {\n return this.match<Result<T, F>>({\n // Ok carries no E, so it can be widened to Result<T, F>.\n ok: (value) => Result.ok(value) as unknown as Result<T, F>,\n err: (error) => Result.err(transform(error)),\n });\n }\n\n /**\n * Chains another operation that returns a Result when this result is a success.\n */\n flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => transform(value),\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n}\n\n/**\n * Success variant of Result.\n */\nexport class Ok<T> extends Result<T, never> {\n public readonly value: T;\n\n constructor(value: T) {\n super();\n this.value = value;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<T> {\n return true;\n }\n\n isErr(): this is Err<never> {\n return false;\n }\n\n match<R>(handlers: { ok: (value: T) => R; err: (error: never) => R }): R {\n return handlers.ok(this.value);\n }\n}\n\n/**\n * Error variant of Result.\n */\nexport class Err<E> extends Result<never, E> {\n public readonly error: E;\n\n constructor(error: E) {\n super();\n this.error = error;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<never> {\n return false;\n }\n\n isErr(): this is Err<E> {\n return true;\n }\n\n match<R>(handlers: { ok: (value: never) => R; err: (error: E) => R }): R {\n return handlers.err(this.error);\n }\n}\n"],"mappings":";;;;;AAIA,IAAsB,SAAtB,MAAsB,OAAa;CAC/B,cAAwB,CAExB;;;;CAKA,OAAO,GAAM,OAA4B;EACrC,OAAO,IAAI,GAAG,KAAK;CACvB;;;;CAKA,OAAO,IAAO,OAA4B;EACtC,OAAO,IAAI,IAAI,KAAK;CACxB;;;;CAuBA,YAAsB;EAClB,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,WAAW;EACf,CAAC;CACL;;;;CAKA,cAAwB;EACpB,OAAO,KAAK,MAAM;GACd,UAAU;GACV,MAAM,UAAU;EACpB,CAAC;CACL;;;;CAKA,aAAgB;EACZ,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,MAAM,UAAU;IACZ,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GAClE;EACJ,CAAC;CACL;;;;CAKA,IAAO,WAA0C;EAC7C,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,OAAO,GAAG,UAAU,KAAK,CAAC;GAGzC,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;;;;CAKA,SAAY,WAA0C;EAClD,OAAO,KAAK,MAAoB;GAE5B,KAAK,UAAU,OAAO,GAAG,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,UAAU,KAAK,CAAC;EAC/C,CAAC;CACL;;;;CAKA,QAAW,WAAqD;EAC5D,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,UAAU,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;AACJ;;;;AAKA,IAAa,KAAb,cAA2B,OAAiB;CAGxC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAAsB;EAClB,OAAO;CACX;CAEA,QAA4B;EACxB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,GAAG,KAAK,KAAK;CACjC;AACJ;;;;AAKA,IAAa,MAAb,cAA4B,OAAiB;CAGzC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAA0B;EACtB,OAAO;CACX;CAEA,QAAwB;EACpB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,IAAI,KAAK,KAAK;CAClC;AACJ"}
1
+ {"version":3,"file":"result.cjs","names":[],"sources":["../src/core/result/result.ts"],"sourcesContent":["/**\n * Represents the outcome of an operation that may succeed or fail.\n * Useful for avoiding excessive exceptions and making error flows explicit.\n */\nexport abstract class Result<T, E> {\n protected constructor() {\n // prevent direct instantiation\n }\n\n /**\n * Creates a success result containing a value.\n */\n static ok<T>(value: T): Result<T, never> {\n return new Ok(value);\n }\n\n /**\n * Creates an error result containing an error object.\n */\n static err<E>(error: E): Result<never, E> {\n return new Err(error);\n }\n\n /**\n * Returns true when this is a success result.\n */\n abstract isOk(): this is Ok<T>;\n\n /**\n * Returns true when this is an error result.\n */\n abstract isErr(): this is Err<E>;\n\n /**\n * Runs a handler based on the result state (similar to pattern matching).\n */\n abstract match<R>(handlers: {\n ok: (value: T) => R;\n err: (error: E) => R;\n }): R;\n\n /**\n * Returns the value when this is success, or null when this is an error.\n *\n * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.\n * Use `match`/`isOk` when that distinction matters.\n */\n getOrNull(): T | null {\n return this.match({\n ok: (value) => value,\n err: () => null,\n });\n }\n\n /**\n * Returns the error when this is a failure, or null when this is a success.\n */\n errorOrNull(): E | null {\n return this.match({\n ok: () => null,\n err: (error) => error,\n });\n }\n\n /**\n * Returns the value when this is a success, or throws the error when this is a failure.\n */\n getOrThrow(): T {\n return this.match({\n ok: (value) => value,\n err: (error) => {\n throw toError(error);\n },\n });\n }\n\n /**\n * Transforms the success value using the provided function. Keeps the error when this is a failure.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n map<R>(transform: (value: T) => R): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => Result.ok(transform(value)),\n // TS does not \"see\" variance here; the cast is intentional and safe:\n // Err carries no T, so it can be widened to Result<R, E> safely.\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n\n /**\n * Transforms the error using the provided function. Keeps the value when this is a success.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n mapError<F>(transform: (error: E) => F): Result<T, F> {\n return this.match<Result<T, F>>({\n // Ok carries no E, so it can be widened to Result<T, F>.\n ok: (value) => Result.ok(value) as unknown as Result<T, F>,\n err: (error) => Result.err(transform(error)),\n });\n }\n\n /**\n * Chains another operation that returns a Result when this result is a success.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => transform(value),\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n}\n\n/**\n * Success variant of Result.\n *\n * The instance is frozen, but the freeze is shallow: a contained mutable\n * object is not frozen.\n */\nexport class Ok<T> extends Result<T, never> {\n public readonly value: T;\n\n constructor(value: T) {\n super();\n this.value = value;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<T> {\n return true;\n }\n\n isErr(): this is Err<never> {\n return false;\n }\n\n match<R>(handlers: { ok: (value: T) => R; err: (error: never) => R }): R {\n return handlers.ok(this.value);\n }\n}\n\n/**\n * Error variant of Result.\n */\nexport class Err<E> extends Result<never, E> {\n public readonly error: E;\n\n constructor(error: E) {\n super();\n this.error = error;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<never> {\n return false;\n }\n\n isErr(): this is Err<E> {\n return true;\n }\n\n match<R>(handlers: { ok: (value: never) => R; err: (error: E) => R }): R {\n return handlers.err(this.error);\n }\n}\n\n/**\n * Coerces an arbitrary error value into an Error for `getOrThrow`, preserving\n * the original structured payload as `cause` so nothing is lost when `E` is a\n * plain object/DTO rather than an Error.\n */\nfunction toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (typeof error === \"string\") return new Error(error);\n let message: string | undefined;\n try {\n message = JSON.stringify(error);\n } catch {\n // circular / BigInt / etc.\n }\n const wrapped = new Error(message ?? String(error));\n // Attach the original payload as `cause` (assigned, not passed to the\n // ES2022 constructor option, to stay within the ES2020 lib target).\n (wrapped as Error & { cause?: unknown }).cause = error;\n return wrapped;\n}\n"],"mappings":";;;;;AAIA,IAAsB,SAAtB,MAAsB,OAAa;CAC/B,cAAwB,CAExB;;;;CAKA,OAAO,GAAM,OAA4B;EACrC,OAAO,IAAI,GAAG,KAAK;CACvB;;;;CAKA,OAAO,IAAO,OAA4B;EACtC,OAAO,IAAI,IAAI,KAAK;CACxB;;;;;;;CA0BA,YAAsB;EAClB,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,WAAW;EACf,CAAC;CACL;;;;CAKA,cAAwB;EACpB,OAAO,KAAK,MAAM;GACd,UAAU;GACV,MAAM,UAAU;EACpB,CAAC;CACL;;;;CAKA,aAAgB;EACZ,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,MAAM,UAAU;IACZ,MAAM,QAAQ,KAAK;GACvB;EACJ,CAAC;CACL;;;;;;CAOA,IAAO,WAA0C;EAC7C,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,OAAO,GAAG,UAAU,KAAK,CAAC;GAGzC,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;;;;;;CAOA,SAAY,WAA0C;EAClD,OAAO,KAAK,MAAoB;GAE5B,KAAK,UAAU,OAAO,GAAG,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,UAAU,KAAK,CAAC;EAC/C,CAAC;CACL;;;;;;CAOA,QAAW,WAAqD;EAC5D,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,UAAU,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;AACJ;;;;;;;AAQA,IAAa,KAAb,cAA2B,OAAiB;CAGxC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAAsB;EAClB,OAAO;CACX;CAEA,QAA4B;EACxB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,GAAG,KAAK,KAAK;CACjC;AACJ;;;;AAKA,IAAa,MAAb,cAA4B,OAAiB;CAGzC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAA0B;EACtB,OAAO;CACX;CAEA,QAAwB;EACpB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,IAAI,KAAK,KAAK;CAClC;AACJ;;;;;;AAOA,SAAS,QAAQ,OAAuB;CACpC,IAAI,iBAAiB,OAAO,OAAO;CACnC,IAAI,OAAO,UAAU,UAAU,OAAO,IAAI,MAAM,KAAK;CACrD,IAAI;CACJ,IAAI;EACA,UAAU,KAAK,UAAU,KAAK;CAClC,QAAQ,CAER;CACA,MAAM,UAAU,IAAI,MAAM,WAAW,OAAO,KAAK,CAAC;CAGlD,QAAyC,QAAQ;CACjD,OAAO;AACX"}
package/dist/result.d.cts CHANGED
@@ -30,6 +30,9 @@ declare abstract class Result<T, E> {
30
30
  }): R;
31
31
  /**
32
32
  * Returns the value when this is success, or null when this is an error.
33
+ *
34
+ * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.
35
+ * Use `match`/`isOk` when that distinction matters.
33
36
  */
34
37
  getOrNull(): T | null;
35
38
  /**
@@ -42,19 +45,28 @@ declare abstract class Result<T, E> {
42
45
  getOrThrow(): T;
43
46
  /**
44
47
  * Transforms the success value using the provided function. Keeps the error when this is a failure.
48
+ *
49
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
45
50
  */
46
51
  map<R>(transform: (value: T) => R): Result<R, E>;
47
52
  /**
48
53
  * Transforms the error using the provided function. Keeps the value when this is a success.
54
+ *
55
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
49
56
  */
50
57
  mapError<F>(transform: (error: E) => F): Result<T, F>;
51
58
  /**
52
59
  * Chains another operation that returns a Result when this result is a success.
60
+ *
61
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
53
62
  */
54
63
  flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E>;
55
64
  }
56
65
  /**
57
66
  * Success variant of Result.
67
+ *
68
+ * The instance is frozen, but the freeze is shallow: a contained mutable
69
+ * object is not frozen.
58
70
  */
59
71
  declare class Ok<T> extends Result<T, never> {
60
72
  readonly value: T;
package/dist/result.d.ts CHANGED
@@ -30,6 +30,9 @@ declare abstract class Result<T, E> {
30
30
  }): R;
31
31
  /**
32
32
  * Returns the value when this is success, or null when this is an error.
33
+ *
34
+ * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.
35
+ * Use `match`/`isOk` when that distinction matters.
33
36
  */
34
37
  getOrNull(): T | null;
35
38
  /**
@@ -42,19 +45,28 @@ declare abstract class Result<T, E> {
42
45
  getOrThrow(): T;
43
46
  /**
44
47
  * Transforms the success value using the provided function. Keeps the error when this is a failure.
48
+ *
49
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
45
50
  */
46
51
  map<R>(transform: (value: T) => R): Result<R, E>;
47
52
  /**
48
53
  * Transforms the error using the provided function. Keeps the value when this is a success.
54
+ *
55
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
49
56
  */
50
57
  mapError<F>(transform: (error: E) => F): Result<T, F>;
51
58
  /**
52
59
  * Chains another operation that returns a Result when this result is a success.
60
+ *
61
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
53
62
  */
54
63
  flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E>;
55
64
  }
56
65
  /**
57
66
  * Success variant of Result.
67
+ *
68
+ * The instance is frozen, but the freeze is shallow: a contained mutable
69
+ * object is not frozen.
58
70
  */
59
71
  declare class Ok<T> extends Result<T, never> {
60
72
  readonly value: T;
package/dist/result.js CHANGED
@@ -19,6 +19,9 @@ var Result = class Result {
19
19
  }
20
20
  /**
21
21
  * Returns the value when this is success, or null when this is an error.
22
+ *
23
+ * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.
24
+ * Use `match`/`isOk` when that distinction matters.
22
25
  */
23
26
  getOrNull() {
24
27
  return this.match({
@@ -42,12 +45,14 @@ var Result = class Result {
42
45
  return this.match({
43
46
  ok: (value) => value,
44
47
  err: (error) => {
45
- throw error instanceof Error ? error : new Error(String(error));
48
+ throw toError(error);
46
49
  }
47
50
  });
48
51
  }
49
52
  /**
50
53
  * Transforms the success value using the provided function. Keeps the error when this is a failure.
54
+ *
55
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
51
56
  */
52
57
  map(transform) {
53
58
  return this.match({
@@ -57,6 +62,8 @@ var Result = class Result {
57
62
  }
58
63
  /**
59
64
  * Transforms the error using the provided function. Keeps the value when this is a success.
65
+ *
66
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
60
67
  */
61
68
  mapError(transform) {
62
69
  return this.match({
@@ -66,6 +73,8 @@ var Result = class Result {
66
73
  }
67
74
  /**
68
75
  * Chains another operation that returns a Result when this result is a success.
76
+ *
77
+ * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.
69
78
  */
70
79
  flatMap(transform) {
71
80
  return this.match({
@@ -76,6 +85,9 @@ var Result = class Result {
76
85
  };
77
86
  /**
78
87
  * Success variant of Result.
88
+ *
89
+ * The instance is frozen, but the freeze is shallow: a contained mutable
90
+ * object is not frozen.
79
91
  */
80
92
  var Ok = class extends Result {
81
93
  constructor(value) {
@@ -112,6 +124,22 @@ var Err = class extends Result {
112
124
  return handlers.err(this.error);
113
125
  }
114
126
  };
127
+ /**
128
+ * Coerces an arbitrary error value into an Error for `getOrThrow`, preserving
129
+ * the original structured payload as `cause` so nothing is lost when `E` is a
130
+ * plain object/DTO rather than an Error.
131
+ */
132
+ function toError(error) {
133
+ if (error instanceof Error) return error;
134
+ if (typeof error === "string") return new Error(error);
135
+ let message;
136
+ try {
137
+ message = JSON.stringify(error);
138
+ } catch {}
139
+ const wrapped = new Error(message ?? String(error));
140
+ wrapped.cause = error;
141
+ return wrapped;
142
+ }
115
143
  //#endregion
116
144
  export { Ok as n, Result as r, Err as t };
117
145
 
@@ -1 +1 @@
1
- {"version":3,"file":"result.js","names":[],"sources":["../src/core/result/result.ts"],"sourcesContent":["/**\n * Represents the outcome of an operation that may succeed or fail.\n * Useful for avoiding excessive exceptions and making error flows explicit.\n */\nexport abstract class Result<T, E> {\n protected constructor() {\n // prevent direct instantiation\n }\n\n /**\n * Creates a success result containing a value.\n */\n static ok<T>(value: T): Result<T, never> {\n return new Ok(value);\n }\n\n /**\n * Creates an error result containing an error object.\n */\n static err<E>(error: E): Result<never, E> {\n return new Err(error);\n }\n\n /**\n * Returns true when this is a success result.\n */\n abstract isOk(): this is Ok<T>;\n\n /**\n * Returns true when this is an error result.\n */\n abstract isErr(): this is Err<E>;\n\n /**\n * Runs a handler based on the result state (similar to pattern matching).\n */\n abstract match<R>(handlers: {\n ok: (value: T) => R;\n err: (error: E) => R;\n }): R;\n\n /**\n * Returns the value when this is success, or null when this is an error.\n */\n getOrNull(): T | null {\n return this.match({\n ok: (value) => value,\n err: () => null,\n });\n }\n\n /**\n * Returns the error when this is a failure, or null when this is a success.\n */\n errorOrNull(): E | null {\n return this.match({\n ok: () => null,\n err: (error) => error,\n });\n }\n\n /**\n * Returns the value when this is a success, or throws the error when this is a failure.\n */\n getOrThrow(): T {\n return this.match({\n ok: (value) => value,\n err: (error) => {\n throw error instanceof Error ? error : new Error(String(error));\n },\n });\n }\n\n /**\n * Transforms the success value using the provided function. Keeps the error when this is a failure.\n */\n map<R>(transform: (value: T) => R): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => Result.ok(transform(value)),\n // TS does not \"see\" variance here; the cast is intentional and safe:\n // Err carries no T, so it can be widened to Result<R, E> safely.\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n\n /**\n * Transforms the error using the provided function. Keeps the value when this is a success.\n */\n mapError<F>(transform: (error: E) => F): Result<T, F> {\n return this.match<Result<T, F>>({\n // Ok carries no E, so it can be widened to Result<T, F>.\n ok: (value) => Result.ok(value) as unknown as Result<T, F>,\n err: (error) => Result.err(transform(error)),\n });\n }\n\n /**\n * Chains another operation that returns a Result when this result is a success.\n */\n flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => transform(value),\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n}\n\n/**\n * Success variant of Result.\n */\nexport class Ok<T> extends Result<T, never> {\n public readonly value: T;\n\n constructor(value: T) {\n super();\n this.value = value;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<T> {\n return true;\n }\n\n isErr(): this is Err<never> {\n return false;\n }\n\n match<R>(handlers: { ok: (value: T) => R; err: (error: never) => R }): R {\n return handlers.ok(this.value);\n }\n}\n\n/**\n * Error variant of Result.\n */\nexport class Err<E> extends Result<never, E> {\n public readonly error: E;\n\n constructor(error: E) {\n super();\n this.error = error;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<never> {\n return false;\n }\n\n isErr(): this is Err<E> {\n return true;\n }\n\n match<R>(handlers: { ok: (value: never) => R; err: (error: E) => R }): R {\n return handlers.err(this.error);\n }\n}\n"],"mappings":";;;;;AAIA,IAAsB,SAAtB,MAAsB,OAAa;CAC/B,cAAwB,CAExB;;;;CAKA,OAAO,GAAM,OAA4B;EACrC,OAAO,IAAI,GAAG,KAAK;CACvB;;;;CAKA,OAAO,IAAO,OAA4B;EACtC,OAAO,IAAI,IAAI,KAAK;CACxB;;;;CAuBA,YAAsB;EAClB,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,WAAW;EACf,CAAC;CACL;;;;CAKA,cAAwB;EACpB,OAAO,KAAK,MAAM;GACd,UAAU;GACV,MAAM,UAAU;EACpB,CAAC;CACL;;;;CAKA,aAAgB;EACZ,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,MAAM,UAAU;IACZ,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GAClE;EACJ,CAAC;CACL;;;;CAKA,IAAO,WAA0C;EAC7C,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,OAAO,GAAG,UAAU,KAAK,CAAC;GAGzC,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;;;;CAKA,SAAY,WAA0C;EAClD,OAAO,KAAK,MAAoB;GAE5B,KAAK,UAAU,OAAO,GAAG,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,UAAU,KAAK,CAAC;EAC/C,CAAC;CACL;;;;CAKA,QAAW,WAAqD;EAC5D,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,UAAU,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;AACJ;;;;AAKA,IAAa,KAAb,cAA2B,OAAiB;CAGxC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAAsB;EAClB,OAAO;CACX;CAEA,QAA4B;EACxB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,GAAG,KAAK,KAAK;CACjC;AACJ;;;;AAKA,IAAa,MAAb,cAA4B,OAAiB;CAGzC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAA0B;EACtB,OAAO;CACX;CAEA,QAAwB;EACpB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,IAAI,KAAK,KAAK;CAClC;AACJ"}
1
+ {"version":3,"file":"result.js","names":[],"sources":["../src/core/result/result.ts"],"sourcesContent":["/**\n * Represents the outcome of an operation that may succeed or fail.\n * Useful for avoiding excessive exceptions and making error flows explicit.\n */\nexport abstract class Result<T, E> {\n protected constructor() {\n // prevent direct instantiation\n }\n\n /**\n * Creates a success result containing a value.\n */\n static ok<T>(value: T): Result<T, never> {\n return new Ok(value);\n }\n\n /**\n * Creates an error result containing an error object.\n */\n static err<E>(error: E): Result<never, E> {\n return new Err(error);\n }\n\n /**\n * Returns true when this is a success result.\n */\n abstract isOk(): this is Ok<T>;\n\n /**\n * Returns true when this is an error result.\n */\n abstract isErr(): this is Err<E>;\n\n /**\n * Runs a handler based on the result state (similar to pattern matching).\n */\n abstract match<R>(handlers: {\n ok: (value: T) => R;\n err: (error: E) => R;\n }): R;\n\n /**\n * Returns the value when this is success, or null when this is an error.\n *\n * Note: an `Ok(null)` also returns `null`, indistinguishable from an `Err`.\n * Use `match`/`isOk` when that distinction matters.\n */\n getOrNull(): T | null {\n return this.match({\n ok: (value) => value,\n err: () => null,\n });\n }\n\n /**\n * Returns the error when this is a failure, or null when this is a success.\n */\n errorOrNull(): E | null {\n return this.match({\n ok: () => null,\n err: (error) => error,\n });\n }\n\n /**\n * Returns the value when this is a success, or throws the error when this is a failure.\n */\n getOrThrow(): T {\n return this.match({\n ok: (value) => value,\n err: (error) => {\n throw toError(error);\n },\n });\n }\n\n /**\n * Transforms the success value using the provided function. Keeps the error when this is a failure.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n map<R>(transform: (value: T) => R): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => Result.ok(transform(value)),\n // TS does not \"see\" variance here; the cast is intentional and safe:\n // Err carries no T, so it can be widened to Result<R, E> safely.\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n\n /**\n * Transforms the error using the provided function. Keeps the value when this is a success.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n mapError<F>(transform: (error: E) => F): Result<T, F> {\n return this.match<Result<T, F>>({\n // Ok carries no E, so it can be widened to Result<T, F>.\n ok: (value) => Result.ok(value) as unknown as Result<T, F>,\n err: (error) => Result.err(transform(error)),\n });\n }\n\n /**\n * Chains another operation that returns a Result when this result is a success.\n *\n * Exceptions thrown by `transform` propagate; they are not captured into an `Err`.\n */\n flatMap<R>(transform: (value: T) => Result<R, E>): Result<R, E> {\n return this.match<Result<R, E>>({\n ok: (value) => transform(value),\n err: (error) => Result.err(error) as unknown as Result<R, E>,\n });\n }\n}\n\n/**\n * Success variant of Result.\n *\n * The instance is frozen, but the freeze is shallow: a contained mutable\n * object is not frozen.\n */\nexport class Ok<T> extends Result<T, never> {\n public readonly value: T;\n\n constructor(value: T) {\n super();\n this.value = value;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<T> {\n return true;\n }\n\n isErr(): this is Err<never> {\n return false;\n }\n\n match<R>(handlers: { ok: (value: T) => R; err: (error: never) => R }): R {\n return handlers.ok(this.value);\n }\n}\n\n/**\n * Error variant of Result.\n */\nexport class Err<E> extends Result<never, E> {\n public readonly error: E;\n\n constructor(error: E) {\n super();\n this.error = error;\n Object.freeze(this);\n }\n\n isOk(): this is Ok<never> {\n return false;\n }\n\n isErr(): this is Err<E> {\n return true;\n }\n\n match<R>(handlers: { ok: (value: never) => R; err: (error: E) => R }): R {\n return handlers.err(this.error);\n }\n}\n\n/**\n * Coerces an arbitrary error value into an Error for `getOrThrow`, preserving\n * the original structured payload as `cause` so nothing is lost when `E` is a\n * plain object/DTO rather than an Error.\n */\nfunction toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (typeof error === \"string\") return new Error(error);\n let message: string | undefined;\n try {\n message = JSON.stringify(error);\n } catch {\n // circular / BigInt / etc.\n }\n const wrapped = new Error(message ?? String(error));\n // Attach the original payload as `cause` (assigned, not passed to the\n // ES2022 constructor option, to stay within the ES2020 lib target).\n (wrapped as Error & { cause?: unknown }).cause = error;\n return wrapped;\n}\n"],"mappings":";;;;;AAIA,IAAsB,SAAtB,MAAsB,OAAa;CAC/B,cAAwB,CAExB;;;;CAKA,OAAO,GAAM,OAA4B;EACrC,OAAO,IAAI,GAAG,KAAK;CACvB;;;;CAKA,OAAO,IAAO,OAA4B;EACtC,OAAO,IAAI,IAAI,KAAK;CACxB;;;;;;;CA0BA,YAAsB;EAClB,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,WAAW;EACf,CAAC;CACL;;;;CAKA,cAAwB;EACpB,OAAO,KAAK,MAAM;GACd,UAAU;GACV,MAAM,UAAU;EACpB,CAAC;CACL;;;;CAKA,aAAgB;EACZ,OAAO,KAAK,MAAM;GACd,KAAK,UAAU;GACf,MAAM,UAAU;IACZ,MAAM,QAAQ,KAAK;GACvB;EACJ,CAAC;CACL;;;;;;CAOA,IAAO,WAA0C;EAC7C,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,OAAO,GAAG,UAAU,KAAK,CAAC;GAGzC,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;;;;;;CAOA,SAAY,WAA0C;EAClD,OAAO,KAAK,MAAoB;GAE5B,KAAK,UAAU,OAAO,GAAG,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,UAAU,KAAK,CAAC;EAC/C,CAAC;CACL;;;;;;CAOA,QAAW,WAAqD;EAC5D,OAAO,KAAK,MAAoB;GAC5B,KAAK,UAAU,UAAU,KAAK;GAC9B,MAAM,UAAU,OAAO,IAAI,KAAK;EACpC,CAAC;CACL;AACJ;;;;;;;AAQA,IAAa,KAAb,cAA2B,OAAiB;CAGxC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAAsB;EAClB,OAAO;CACX;CAEA,QAA4B;EACxB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,GAAG,KAAK,KAAK;CACjC;AACJ;;;;AAKA,IAAa,MAAb,cAA4B,OAAiB;CAGzC,YAAY,OAAU;EAClB,MAAM;EACN,KAAK,QAAQ;EACb,OAAO,OAAO,IAAI;CACtB;CAEA,OAA0B;EACtB,OAAO;CACX;CAEA,QAAwB;EACpB,OAAO;CACX;CAEA,MAAS,UAAgE;EACrE,OAAO,SAAS,IAAI,KAAK,KAAK;CAClC;AACJ;;;;;;AAOA,SAAS,QAAQ,OAAuB;CACpC,IAAI,iBAAiB,OAAO,OAAO;CACnC,IAAI,OAAO,UAAU,UAAU,OAAO,IAAI,MAAM,KAAK;CACrD,IAAI;CACJ,IAAI;EACA,UAAU,KAAK,UAAU,KAAK;CAClC,QAAQ,CAER;CACA,MAAM,UAAU,IAAI,MAAM,WAAW,OAAO,KAAK,CAAC;CAGlD,QAAyC,QAAQ;CACjD,OAAO;AACX"}
package/dist/rule.cjs CHANGED
@@ -97,46 +97,73 @@ const ENGINE_VERSION = 1;
97
97
  * "errors as values" contract of `RbacAuthorizer`/`GateEngineV1`.
98
98
  *
99
99
  * A denial maps to {@link AuthorizationError.policyDenied}, attributed to the
100
- * deciding rule's `id`/`version`; a condition-evaluation failure (a missing
100
+ * deciding rule's `id`/`version`. A condition-evaluation failure (a missing
101
101
  * attribute, a wrong-typed operand) fails closed as
102
- * {@link AuthorizationError.forbidden}. Loading the dynamic attributes is the
103
- * consumer's job (behind an `AbacAuthorizerPort` adapter); this class only decides.
102
+ * {@link AuthorizationError.forbidden} also attributed to the culprit rule's
103
+ * `id`/`version` unless the set opts into `onEvaluationError: "skip-rule"`, in
104
+ * which case the unevaluable rule is skipped and the rest decide. Every resolved
105
+ * decision (PERMIT and DENY) is emitted best-effort through the configured
106
+ * reporter as an `abac-decision` event (silent by default). Timestamps come from
107
+ * an injectable `clock` (default `() => new Date()`), so the decisor is pure
108
+ * given one. Loading the dynamic attributes is the consumer's job (behind an
109
+ * `AbacAuthorizerPort` adapter); this class only decides.
104
110
  */
105
111
  var AbacAuthorizer = class {
106
112
  constructor(params = {}) {
107
113
  this.coreConfig = params.coreConfig ?? require_condition_evaluator.coreConfig;
114
+ this.clock = params.clock ?? (() => /* @__PURE__ */ new Date());
108
115
  }
109
116
  authorize(request, policies) {
110
117
  const evaluator = new require_condition_evaluator.ConditionEvaluatorV1(abacContext(request), this.coreConfig.getConditionEvaluationOptions(ENGINE_VERSION));
111
118
  const applicable = [];
112
119
  for (const rule of policies.rules) {
113
120
  const matched = evaluator.evaluate(rule.condition);
114
- if (matched.isErr()) return require_result.Result.err(require_authorization_error.AuthorizationError.forbidden({
115
- action: request.action,
116
- resource: request.resource,
117
- actor: { userId: request.actor.raw },
118
- details: matched.errorOrNull() ?? void 0
119
- }));
121
+ if (matched.isErr()) {
122
+ if (policies.onEvaluationError === "skip-rule") continue;
123
+ return require_result.Result.err(require_authorization_error.AuthorizationError.forbidden({
124
+ action: request.action,
125
+ resource: request.resource,
126
+ actor: { actorId: request.actor.raw },
127
+ policyId: rule.id,
128
+ policyVersion: rule.version,
129
+ details: matched.errorOrNull() ?? void 0
130
+ }));
131
+ }
120
132
  if (matched.getOrThrow()) applicable.push(rule);
121
133
  }
122
134
  const { effect, decidingRule } = combine(policies.algorithm, applicable, policies.defaultEffect);
135
+ this.reportDecision(request, policies, effect, decidingRule);
123
136
  if (effect === "PERMIT") return require_result.Result.ok({
124
137
  action: request.action,
125
138
  effect: "PERMIT",
126
139
  matchedRule: decidingRule?.id ?? "<default>",
127
140
  algorithm: policies.algorithm,
128
- grantedAtIso: (/* @__PURE__ */ new Date()).toISOString()
141
+ grantedAtIso: this.clock().toISOString()
129
142
  });
130
143
  return require_result.Result.err(require_authorization_error.AuthorizationError.policyDenied({
131
144
  policyId: decidingRule?.id ?? "<default-deny>",
132
145
  policyVersion: decidingRule?.version ?? 0,
133
- evaluatedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
146
+ evaluatedAtIso: this.clock().toISOString(),
134
147
  action: request.action,
135
148
  resource: request.resource,
136
- actor: { userId: request.actor.raw },
149
+ actor: { actorId: request.actor.raw },
137
150
  reasonCode: decidingRule?.id
138
151
  }));
139
152
  }
153
+ reportDecision(request, policies, effect, decidingRule) {
154
+ this.coreConfig.reportSafely({
155
+ kind: "abac-decision",
156
+ level: "info",
157
+ action: request.action,
158
+ resource: request.resource,
159
+ actorId: request.actor.raw,
160
+ effect,
161
+ decidingRuleId: decidingRule?.id ?? (effect === "PERMIT" ? "<default>" : "<default-deny>"),
162
+ decidingRuleVersion: decidingRule?.version,
163
+ algorithm: policies.algorithm,
164
+ occurredAt: this.clock()
165
+ });
166
+ }
140
167
  };
141
168
  //#endregion
142
169
  //#region src/core/abac/composite-authorizer.ts
@@ -163,26 +190,43 @@ var CompositeAuthorizer = class {
163
190
  };
164
191
  //#endregion
165
192
  //#region src/core/abac/domain/policy-set.ts
193
+ const POLICY_SET_FIELD = require_validation_field.ValidationField.of("abacPolicySet");
166
194
  /**
167
195
  * An ordered collection of {@link AbacRule}s plus how to combine them
168
- * ({@link CombiningAlgorithm}) and what to decide when no rule applies
169
- * ({@link defaultEffect}). This is where ABAC goes beyond a single gate
196
+ * ({@link CombiningAlgorithm}), what to decide when no rule applies
197
+ * ({@link defaultEffect}), and how to react to an unevaluable rule
198
+ * ({@link onEvaluationError}). This is where ABAC goes beyond a single gate
170
199
  * condition: several PERMIT/DENY rules resolved by an explicit algorithm.
171
200
  *
172
- * Closed by default — the combining algorithm defaults to `"deny-overrides"` and
173
- * the default effect to `"DENY"`, so a request matching nothing is denied. A
174
- * plain holder (not a value object); build through {@link of}.
201
+ * Closed by default — the combining algorithm defaults to `"deny-overrides"`,
202
+ * the default effect to `"DENY"`, and evaluation errors to `"fail-closed"`, so a
203
+ * request matching nothing (or hitting a technical error) is denied. A plain
204
+ * holder (not a value object); build through {@link of}.
175
205
  */
176
206
  var AbacPolicySet = class AbacPolicySet {
177
- constructor(_rules, _algorithm, _defaultEffect) {
207
+ constructor(_rules, _algorithm, _defaultEffect, _onEvaluationError) {
178
208
  this._rules = _rules;
179
209
  this._algorithm = _algorithm;
180
210
  this._defaultEffect = _defaultEffect;
211
+ this._onEvaluationError = _onEvaluationError;
181
212
  Object.freeze(this._rules);
182
213
  Object.freeze(this);
183
214
  }
215
+ /**
216
+ * Builds a policy set. Rejects duplicate rule ids with
217
+ * {@link InvalidValueException} so a denial's `policyId` attribution is
218
+ * unambiguous.
219
+ */
184
220
  static of(rules, options = {}) {
185
- return new AbacPolicySet([...rules], options.algorithm ?? "deny-overrides", options.defaultEffect ?? "DENY");
221
+ AbacPolicySet.assertUniqueIds(rules);
222
+ return new AbacPolicySet([...rules], options.algorithm ?? "deny-overrides", options.defaultEffect ?? "DENY", options.onEvaluationError ?? "fail-closed");
223
+ }
224
+ static assertUniqueIds(rules) {
225
+ const seen = /* @__PURE__ */ new Set();
226
+ for (const rule of rules) {
227
+ if (seen.has(rule.id)) throw new require_validation_exception.InvalidValueException(POLICY_SET_FIELD.nested("rules"), require_validation_code.ValidationCode.ALREADY_EXISTS, `abac policy set has duplicate rule id "${rule.id}"; ids must be unique for unambiguous audit attribution`);
228
+ seen.add(rule.id);
229
+ }
186
230
  }
187
231
  get rules() {
188
232
  return this._rules;
@@ -193,11 +237,13 @@ var AbacPolicySet = class AbacPolicySet {
193
237
  get defaultEffect() {
194
238
  return this._defaultEffect;
195
239
  }
240
+ get onEvaluationError() {
241
+ return this._onEvaluationError;
242
+ }
196
243
  };
197
244
  //#endregion
198
- //#region src/core/abac/domain/rule.ts
199
- const RULE_FIELD = require_validation_field.ValidationField.of("abacRule");
200
- const CONDITION_OPS = new Set([
245
+ //#region src/core/policies/engines/v1/condition-types.ts
246
+ const CONDITION_OPS = [
201
247
  "eq",
202
248
  "neq",
203
249
  "gt",
@@ -208,7 +254,11 @@ const CONDITION_OPS = new Set([
208
254
  "notIn",
209
255
  "isNull",
210
256
  "isNotNull"
211
- ]);
257
+ ];
258
+ //#endregion
259
+ //#region src/core/abac/domain/rule.ts
260
+ const RULE_FIELD = require_validation_field.ValidationField.of("abacRule");
261
+ const SUPPORTED_OPS = new Set(CONDITION_OPS);
212
262
  const RULE_EFFECTS = new Set(["PERMIT", "DENY"]);
213
263
  function isPlainObject(value) {
214
264
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -225,6 +275,26 @@ function isPlainObject(value) {
225
275
  * authored in TypeScript — trusted data, not untrusted JSON — the structural
226
276
  * check is a hand-rolled walk of the condition tree rather than a schema parse.
227
277
  * Build one through {@link of}; the constructor is private.
278
+ *
279
+ * **Attribute semantics a rule author must know (the condition is evaluated by
280
+ * the shared gate/compute engine, whose runtime rules apply here too):**
281
+ * - **A referenced attribute that is *absent* from the request is a technical
282
+ * evaluation error, not a non-match.** By default (`onEvaluationError:
283
+ * "fail-closed"` on the {@link AbacPolicySet}) that error fails the *entire*
284
+ * decision closed — so adding a rule that reads an attribute a given call site
285
+ * does not yet populate makes that call site start returning `forbidden`,
286
+ * *even for requests the older rules used to permit*. Choose
287
+ * `onEvaluationError: "skip-rule"` on the set to skip an unevaluable rule
288
+ * instead of failing the whole set.
289
+ * - **`isNull` / `isNotNull` test for an explicit `null`/`undefined` *value*, not
290
+ * for a missing key.** A key that is absent from the bag never reaches the
291
+ * operator — it short-circuits to the missing-attribute error above. To match
292
+ * "no deletedAt", the consumer must put `deletedAt: null` in the bag, not omit
293
+ * it. Pass `allowNull: true` on a relational leaf to treat a present `null` as
294
+ * a non-match instead of an error.
295
+ * - **Date comparison operands must be strict ISO-8601 UTC**
296
+ * (`YYYY-MM-DDTHH:mm:ss.sssZ`); a bare `"2026-07-09"` or an offset like
297
+ * `+00:00` is rejected as an invalid operand and fails closed.
228
298
  */
229
299
  var AbacRule = class AbacRule extends require_value_object.ValueObject {
230
300
  constructor(props) {
@@ -258,7 +328,7 @@ var AbacRule = class AbacRule extends require_value_object.ValueObject {
258
328
  }
259
329
  if ("field" in node && "op" in node) {
260
330
  if (typeof node.field !== "string" || node.field.trim().length === 0) AbacRule.rejectCondition(`abac rule condition leaf "field" must be a non-empty string, got "${String(node.field)}"`);
261
- if (typeof node.op !== "string" || !CONDITION_OPS.has(node.op)) AbacRule.rejectCondition(`abac rule condition leaf "op" is not a supported operator, got "${String(node.op)}"`);
331
+ if (typeof node.op !== "string" || !SUPPORTED_OPS.has(node.op)) AbacRule.rejectCondition(`abac rule condition leaf "op" is not a supported operator, got "${String(node.op)}"`);
262
332
  return;
263
333
  }
264
334
  AbacRule.rejectCondition("abac rule condition must be a leaf { field, op } or an { and | or | not } node");