@tailor-platform/erp-kit 0.1.2 → 0.2.1

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 (330) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +80 -12
  3. package/dist/cli.js +1070 -450
  4. package/package.json +11 -8
  5. package/schemas/app-compose/business-flow.yml +3 -0
  6. package/schemas/app-compose/story.yml +1 -1
  7. package/schemas/module/model.yml +5 -0
  8. package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +8 -14
  9. package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +6 -13
  10. package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -6
  11. package/skills/{app-compose-6-implementation-spec → erp-kit-app-4-impl-spec}/SKILL.md +11 -22
  12. package/skills/erp-kit-app-5-implementation/SKILL.md +149 -0
  13. package/skills/erp-kit-app-5-implementation/references/backend.md +232 -0
  14. package/skills/erp-kit-app-5-implementation/references/frontend.md +242 -0
  15. package/skills/{mock-scenario → erp-kit-mock-scenario}/SKILL.md +1 -1
  16. package/skills/{1-module-docs → erp-kit-module-1-docs}/SKILL.md +2 -2
  17. package/skills/{2-module-feature-breakdown → erp-kit-module-2-feature-breakdown}/SKILL.md +13 -9
  18. package/skills/erp-kit-module-2-feature-breakdown/references/naming.md +59 -0
  19. package/skills/{3-module-doc-review → erp-kit-module-3-doc-review}/SKILL.md +83 -25
  20. package/skills/erp-kit-module-4-tdd/SKILL.md +94 -0
  21. package/skills/erp-kit-module-4-tdd/references/cross-module-dependency.md +133 -0
  22. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/db-relations.md +5 -1
  23. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/exports.md +1 -1
  24. package/skills/erp-kit-module-4-tdd/references/generated-code.md +32 -0
  25. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/SKILL.md +46 -44
  26. package/skills/erp-kit-module-5-impl-review/references/commands.md +62 -0
  27. package/skills/erp-kit-module-5-impl-review/references/errors.md +10 -0
  28. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/references/testing.md +1 -1
  29. package/skills/erp-kit-module-shared/SKILL.md +16 -0
  30. package/skills/erp-kit-module-shared/references/commands.md +203 -0
  31. package/skills/erp-kit-module-shared/references/errors.md +35 -0
  32. package/skills/erp-kit-module-shared/references/queries.md +168 -0
  33. package/skills/erp-kit-module-shared/references/structure.md +36 -0
  34. package/skills/{3-module-doc-review → erp-kit-module-shared}/references/testing.md +4 -3
  35. package/skills/erp-kit-update/SKILL.md +64 -0
  36. package/src/cli.doc.test.ts +65 -0
  37. package/src/cli.ts +3 -35
  38. package/src/commands/app/index.ts +3 -3
  39. package/src/commands/check.test.ts +1 -1
  40. package/src/commands/check.ts +2 -2
  41. package/src/commands/index.ts +73 -0
  42. package/src/commands/init.test.ts +22 -5
  43. package/src/commands/init.ts +25 -16
  44. package/src/commands/license.ts +193 -0
  45. package/src/commands/mock/index.ts +2 -2
  46. package/src/commands/mock/start.ts +1 -1
  47. package/src/commands/mock/validate.test.ts +1 -1
  48. package/src/commands/module/generate.ts +35 -0
  49. package/src/commands/module/index.ts +6 -4
  50. package/src/commands/module/list.test.ts +7 -12
  51. package/src/commands/module/list.ts +1 -1
  52. package/src/commands/scaffold-templates.ts +65 -0
  53. package/src/commands/scaffold.test.ts +92 -2
  54. package/src/commands/scaffold.ts +22 -2
  55. package/src/commands/sync-check.test.ts +60 -1
  56. package/src/commands/sync-check.ts +35 -2
  57. package/src/generator/generate-code.test.ts +200 -0
  58. package/src/generator/generate-code.ts +260 -0
  59. package/src/generator/parse-command-doc.test.ts +159 -0
  60. package/src/generator/parse-command-doc.ts +116 -0
  61. package/src/integration.test.ts +2 -2
  62. package/src/module.ts +44 -6
  63. package/src/modules/item-management/README.md +38 -0
  64. package/src/modules/item-management/command/activateItem.generated.ts +6 -0
  65. package/src/modules/item-management/command/activateItem.test.ts +76 -0
  66. package/src/modules/item-management/command/activateItem.ts +42 -0
  67. package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +6 -0
  68. package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +88 -0
  69. package/src/modules/item-management/command/assignItemToTaxonomy.ts +63 -0
  70. package/src/modules/item-management/command/createItem.generated.ts +6 -0
  71. package/src/modules/item-management/command/createItem.test.ts +152 -0
  72. package/src/modules/item-management/command/createItem.ts +72 -0
  73. package/src/modules/item-management/command/createTaxonomyNode.generated.ts +6 -0
  74. package/src/modules/item-management/command/createTaxonomyNode.test.ts +126 -0
  75. package/src/modules/item-management/command/createTaxonomyNode.ts +70 -0
  76. package/src/modules/item-management/command/deactivateItem.generated.ts +6 -0
  77. package/src/modules/item-management/command/deactivateItem.test.ts +76 -0
  78. package/src/modules/item-management/command/deactivateItem.ts +42 -0
  79. package/src/modules/item-management/command/deleteItem.generated.ts +6 -0
  80. package/src/modules/item-management/command/deleteItem.test.ts +61 -0
  81. package/src/modules/item-management/command/deleteItem.ts +38 -0
  82. package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +6 -0
  83. package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +73 -0
  84. package/src/modules/item-management/command/deleteTaxonomyNode.ts +50 -0
  85. package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +6 -0
  86. package/src/modules/item-management/command/moveTaxonomyNode.test.ts +136 -0
  87. package/src/modules/item-management/command/moveTaxonomyNode.ts +85 -0
  88. package/src/modules/item-management/command/reactivateItem.generated.ts +6 -0
  89. package/src/modules/item-management/command/reactivateItem.test.ts +76 -0
  90. package/src/modules/item-management/command/reactivateItem.ts +42 -0
  91. package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +6 -0
  92. package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +43 -0
  93. package/src/modules/item-management/command/removeItemFromTaxonomy.ts +30 -0
  94. package/src/modules/item-management/command/updateItem.generated.ts +6 -0
  95. package/src/modules/item-management/command/updateItem.test.ts +178 -0
  96. package/src/modules/item-management/command/updateItem.ts +103 -0
  97. package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +6 -0
  98. package/src/modules/item-management/command/updateTaxonomyNode.test.ts +88 -0
  99. package/src/modules/item-management/command/updateTaxonomyNode.ts +62 -0
  100. package/src/modules/item-management/db/item.ts +47 -0
  101. package/src/modules/item-management/db/itemTaxonomyAssignment.ts +49 -0
  102. package/src/modules/item-management/db/taxonomyNode.ts +34 -0
  103. package/src/modules/item-management/docs/commands/ActivateItem.md +32 -0
  104. package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +38 -0
  105. package/src/modules/item-management/docs/commands/CreateItem.md +44 -0
  106. package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +44 -0
  107. package/src/modules/item-management/docs/commands/DeactivateItem.md +34 -0
  108. package/src/modules/item-management/docs/commands/DeleteItem.md +35 -0
  109. package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +39 -0
  110. package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +45 -0
  111. package/src/modules/item-management/docs/commands/ReactivateItem.md +34 -0
  112. package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +30 -0
  113. package/src/modules/item-management/docs/commands/UpdateItem.md +55 -0
  114. package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +36 -0
  115. package/src/modules/item-management/docs/features/item-lifecycle.md +60 -0
  116. package/src/modules/item-management/docs/features/item-taxonomy.md +65 -0
  117. package/src/modules/item-management/docs/models/ItemTaxonomyAssignment.md +36 -0
  118. package/src/modules/item-management/docs/models/TaxonomyNode.md +47 -0
  119. package/src/modules/item-management/docs/models/item.md +59 -0
  120. package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +36 -0
  121. package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +40 -0
  122. package/src/modules/item-management/docs/queries/DetectCircularReference.md +41 -0
  123. package/src/modules/item-management/docs/queries/GetItem.md +38 -0
  124. package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +29 -0
  125. package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +35 -0
  126. package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +29 -0
  127. package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +29 -0
  128. package/src/modules/item-management/generated/enums.ts +9 -0
  129. package/src/modules/item-management/generated/kysely-tailordb.ts +62 -0
  130. package/src/modules/item-management/index.ts +53 -0
  131. package/src/modules/item-management/lib/_db_deps.ts +13 -0
  132. package/src/modules/item-management/lib/errors.generated.ts +117 -0
  133. package/src/modules/item-management/lib/permissions.generated.ts +17 -0
  134. package/src/modules/item-management/lib/types.ts +19 -0
  135. package/src/modules/item-management/module.ts +97 -0
  136. package/src/modules/item-management/query/calculateNodeDepth.generated.ts +5 -0
  137. package/src/modules/item-management/query/calculateNodeDepth.test.ts +56 -0
  138. package/src/modules/item-management/query/calculateNodeDepth.ts +28 -0
  139. package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +5 -0
  140. package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +75 -0
  141. package/src/modules/item-management/query/calculateSubtreeDepth.ts +29 -0
  142. package/src/modules/item-management/query/detectCircularReference.generated.ts +5 -0
  143. package/src/modules/item-management/query/detectCircularReference.test.ts +61 -0
  144. package/src/modules/item-management/query/detectCircularReference.ts +32 -0
  145. package/src/modules/item-management/query/getItem.generated.ts +5 -0
  146. package/src/modules/item-management/query/getItem.test.ts +67 -0
  147. package/src/modules/item-management/query/getItem.ts +20 -0
  148. package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +5 -0
  149. package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +25 -0
  150. package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +18 -0
  151. package/src/modules/item-management/query/getTaxonomyNode.generated.ts +5 -0
  152. package/src/modules/item-management/query/getTaxonomyNode.test.ts +47 -0
  153. package/src/modules/item-management/query/getTaxonomyNode.ts +18 -0
  154. package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +5 -0
  155. package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +25 -0
  156. package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +16 -0
  157. package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +5 -0
  158. package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +34 -0
  159. package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +16 -0
  160. package/src/modules/item-management/tailor.config.ts +11 -0
  161. package/src/modules/item-management/testing/fixtures.ts +81 -0
  162. package/src/modules/primitives/command/activateCategory.generated.ts +6 -0
  163. package/src/modules/primitives/command/activateCategory.test.ts +11 -29
  164. package/src/modules/primitives/command/activateCategory.ts +27 -34
  165. package/src/modules/primitives/command/activateCurrency.generated.ts +6 -0
  166. package/src/modules/primitives/command/activateCurrency.test.ts +11 -29
  167. package/src/modules/primitives/command/activateCurrency.ts +27 -34
  168. package/src/modules/primitives/command/activateUnit.generated.ts +6 -0
  169. package/src/modules/primitives/command/activateUnit.test.ts +11 -15
  170. package/src/modules/primitives/command/activateUnit.ts +27 -34
  171. package/src/modules/primitives/command/createCategory.generated.ts +6 -0
  172. package/src/modules/primitives/command/createCategory.test.ts +27 -39
  173. package/src/modules/primitives/command/createCategory.ts +53 -62
  174. package/src/modules/primitives/command/createCurrency.generated.ts +6 -0
  175. package/src/modules/primitives/command/createCurrency.test.ts +78 -71
  176. package/src/modules/primitives/command/createCurrency.ts +43 -48
  177. package/src/modules/primitives/command/createExchangeRate.generated.ts +6 -0
  178. package/src/modules/primitives/command/createExchangeRate.test.ts +101 -100
  179. package/src/modules/primitives/command/createExchangeRate.ts +50 -59
  180. package/src/modules/primitives/command/createUnit.generated.ts +6 -0
  181. package/src/modules/primitives/command/createUnit.test.ts +92 -95
  182. package/src/modules/primitives/command/createUnit.ts +54 -57
  183. package/src/modules/primitives/command/deactivateCategory.generated.ts +6 -0
  184. package/src/modules/primitives/command/deactivateCategory.test.ts +27 -28
  185. package/src/modules/primitives/command/deactivateCategory.ts +43 -50
  186. package/src/modules/primitives/command/deactivateCurrency.generated.ts +6 -0
  187. package/src/modules/primitives/command/deactivateCurrency.test.ts +23 -38
  188. package/src/modules/primitives/command/deactivateCurrency.ts +31 -38
  189. package/src/modules/primitives/command/deactivateUnit.generated.ts +6 -0
  190. package/src/modules/primitives/command/deactivateUnit.test.ts +27 -23
  191. package/src/modules/primitives/command/deactivateUnit.ts +39 -49
  192. package/src/modules/primitives/command/setBaseCurrency.generated.ts +6 -0
  193. package/src/modules/primitives/command/setBaseCurrency.test.ts +40 -33
  194. package/src/modules/primitives/command/setBaseCurrency.ts +43 -50
  195. package/src/modules/primitives/command/setReferenceUnit.generated.ts +6 -0
  196. package/src/modules/primitives/command/setReferenceUnit.test.ts +39 -35
  197. package/src/modules/primitives/command/setReferenceUnit.ts +46 -59
  198. package/src/modules/primitives/db/unit.ts +13 -3
  199. package/src/modules/primitives/docs/commands/ActivateCategory.md +1 -2
  200. package/src/modules/primitives/docs/commands/ActivateCurrency.md +1 -2
  201. package/src/modules/primitives/docs/commands/ActivateUnit.md +1 -2
  202. package/src/modules/primitives/docs/commands/CreateCategory.md +1 -4
  203. package/src/modules/primitives/docs/commands/CreateCurrency.md +3 -4
  204. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +4 -5
  205. package/src/modules/primitives/docs/commands/CreateUnit.md +5 -5
  206. package/src/modules/primitives/docs/commands/DeactivateCategory.md +2 -3
  207. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +2 -3
  208. package/src/modules/primitives/docs/commands/DeactivateUnit.md +2 -3
  209. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +2 -3
  210. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +2 -3
  211. package/src/modules/primitives/docs/queries/ConvertAmount.md +3 -5
  212. package/src/modules/primitives/docs/queries/ConvertQuantity.md +3 -5
  213. package/src/modules/primitives/docs/queries/GetBaseCurrency.md +32 -0
  214. package/src/modules/primitives/docs/queries/GetCurrency.md +36 -0
  215. package/src/modules/primitives/docs/queries/GetUnit.md +36 -0
  216. package/src/modules/primitives/docs/queries/GetUoMCategory.md +36 -0
  217. package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +26 -0
  218. package/src/modules/primitives/generated/kysely-tailordb.ts +24 -45
  219. package/src/modules/primitives/index.ts +15 -4
  220. package/src/modules/primitives/lib/errors.generated.ts +112 -0
  221. package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -8
  222. package/src/modules/primitives/module.ts +37 -27
  223. package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
  224. package/src/modules/primitives/query/convertAmount.test.ts +2 -2
  225. package/src/modules/primitives/query/convertAmount.ts +27 -28
  226. package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
  227. package/src/modules/primitives/query/convertQuantity.test.ts +6 -2
  228. package/src/modules/primitives/query/convertQuantity.ts +49 -57
  229. package/src/modules/primitives/query/getBaseCurrency.generated.ts +5 -0
  230. package/src/modules/primitives/query/getBaseCurrency.test.ts +28 -0
  231. package/src/modules/primitives/query/getBaseCurrency.ts +16 -0
  232. package/src/modules/primitives/query/getCurrency.generated.ts +5 -0
  233. package/src/modules/primitives/query/getCurrency.test.ts +47 -0
  234. package/src/modules/primitives/query/getCurrency.ts +18 -0
  235. package/src/modules/primitives/query/getUnit.generated.ts +5 -0
  236. package/src/modules/primitives/query/getUnit.test.ts +47 -0
  237. package/src/modules/primitives/query/getUnit.ts +18 -0
  238. package/src/modules/primitives/query/getUoMCategory.generated.ts +5 -0
  239. package/src/modules/primitives/query/getUoMCategory.test.ts +47 -0
  240. package/src/modules/primitives/query/getUoMCategory.ts +18 -0
  241. package/src/modules/primitives/query/listUnitsByCategory.generated.ts +5 -0
  242. package/src/modules/primitives/query/listUnitsByCategory.ts +16 -0
  243. package/src/modules/primitives/tailor.config.ts +3 -3
  244. package/src/modules/shared/defineCommand.test.ts +23 -10
  245. package/src/modules/shared/defineCommand.ts +23 -10
  246. package/src/modules/shared/internal.ts +1 -0
  247. package/src/modules/shared/requirePermission.test.ts +22 -21
  248. package/src/modules/shared/requirePermission.ts +8 -2
  249. package/src/modules/shared/result.ts +12 -0
  250. package/src/modules/testing/index.ts +36 -11
  251. package/src/modules/user-management/command/activateUser.generated.ts +6 -0
  252. package/src/modules/user-management/command/activateUser.test.ts +27 -27
  253. package/src/modules/user-management/command/activateUser.ts +40 -48
  254. package/src/modules/user-management/command/assignPermissionToRole.generated.ts +6 -0
  255. package/src/modules/user-management/command/assignPermissionToRole.test.ts +42 -43
  256. package/src/modules/user-management/command/assignPermissionToRole.ts +59 -62
  257. package/src/modules/user-management/command/assignRoleToUser.generated.ts +6 -0
  258. package/src/modules/user-management/command/assignRoleToUser.test.ts +70 -63
  259. package/src/modules/user-management/command/assignRoleToUser.ts +63 -66
  260. package/src/modules/user-management/command/createPermission.generated.ts +6 -0
  261. package/src/modules/user-management/command/createPermission.test.ts +45 -38
  262. package/src/modules/user-management/command/createPermission.ts +42 -46
  263. package/src/modules/user-management/command/createRole.generated.ts +6 -0
  264. package/src/modules/user-management/command/createRole.test.ts +30 -29
  265. package/src/modules/user-management/command/createRole.ts +33 -33
  266. package/src/modules/user-management/command/createUser.generated.ts +6 -0
  267. package/src/modules/user-management/command/createUser.test.ts +64 -42
  268. package/src/modules/user-management/command/createUser.ts +54 -56
  269. package/src/modules/user-management/command/deactivateUser.generated.ts +6 -0
  270. package/src/modules/user-management/command/deactivateUser.test.ts +27 -27
  271. package/src/modules/user-management/command/deactivateUser.ts +40 -48
  272. package/src/modules/user-management/command/logAuditEvent.generated.ts +6 -0
  273. package/src/modules/user-management/command/logAuditEvent.test.ts +50 -42
  274. package/src/modules/user-management/command/logAuditEvent.ts +25 -28
  275. package/src/modules/user-management/command/reactivateUser.generated.ts +6 -0
  276. package/src/modules/user-management/command/reactivateUser.test.ts +31 -27
  277. package/src/modules/user-management/command/reactivateUser.ts +40 -48
  278. package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +6 -0
  279. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +52 -51
  280. package/src/modules/user-management/command/revokePermissionFromRole.ts +60 -57
  281. package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +6 -0
  282. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +53 -48
  283. package/src/modules/user-management/command/revokeRoleFromUser.ts +58 -57
  284. package/src/modules/user-management/docs/commands/CreatePermission.md +2 -2
  285. package/src/modules/user-management/docs/commands/CreateRole.md +1 -1
  286. package/src/modules/user-management/generated/enums.ts +11 -11
  287. package/src/modules/user-management/generated/kysely-tailordb.ts +27 -56
  288. package/src/modules/user-management/index.ts +2 -2
  289. package/src/modules/user-management/lib/errors.generated.ts +67 -0
  290. package/src/modules/user-management/{permissions.ts → lib/permissions.generated.ts} +8 -7
  291. package/src/modules/user-management/module.ts +22 -22
  292. package/src/modules/user-management/tailor.config.ts +3 -3
  293. package/src/schemas.ts +1 -1
  294. package/skills/1-module-docs/references/structure.md +0 -22
  295. package/skills/2-module-feature-breakdown/references/commands.md +0 -48
  296. package/skills/2-module-feature-breakdown/references/structure.md +0 -22
  297. package/skills/3-module-doc-review/references/commands.md +0 -54
  298. package/skills/3-module-doc-review/references/models.md +0 -29
  299. package/skills/4-module-tdd-implementation/SKILL.md +0 -74
  300. package/skills/4-module-tdd-implementation/references/commands.md +0 -45
  301. package/skills/4-module-tdd-implementation/references/errors.md +0 -7
  302. package/skills/4-module-tdd-implementation/references/models.md +0 -30
  303. package/skills/4-module-tdd-implementation/references/structure.md +0 -22
  304. package/skills/4-module-tdd-implementation/references/testing.md +0 -37
  305. package/skills/5-module-implementation-review/references/commands.md +0 -45
  306. package/skills/5-module-implementation-review/references/errors.md +0 -7
  307. package/skills/5-module-implementation-review/references/exports.md +0 -8
  308. package/skills/5-module-implementation-review/references/models.md +0 -30
  309. package/skills/app-compose-1-requirement-analysis/references/structure.md +0 -27
  310. package/skills/app-compose-2-requirements-breakdown/references/screen-detailview.md +0 -106
  311. package/skills/app-compose-2-requirements-breakdown/references/screen-form.md +0 -139
  312. package/skills/app-compose-2-requirements-breakdown/references/screen-listview.md +0 -153
  313. package/skills/app-compose-2-requirements-breakdown/references/structure.md +0 -27
  314. package/skills/app-compose-3-doc-review/references/structure.md +0 -27
  315. package/skills/app-compose-4-design-mock/SKILL.md +0 -256
  316. package/skills/app-compose-4-design-mock/references/component.md +0 -50
  317. package/skills/app-compose-4-design-mock/references/screen-detailview.md +0 -106
  318. package/skills/app-compose-4-design-mock/references/screen-form.md +0 -139
  319. package/skills/app-compose-4-design-mock/references/screen-listview.md +0 -153
  320. package/skills/app-compose-4-design-mock/references/structure.md +0 -27
  321. package/skills/app-compose-5-design-mock-review/SKILL.md +0 -290
  322. package/skills/app-compose-5-design-mock-review/references/component.md +0 -50
  323. package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +0 -106
  324. package/skills/app-compose-5-design-mock-review/references/screen-form.md +0 -139
  325. package/skills/app-compose-5-design-mock-review/references/screen-listview.md +0 -153
  326. package/skills/app-compose-6-implementation-spec/references/auth.md +0 -72
  327. package/skills/app-compose-6-implementation-spec/references/structure.md +0 -27
  328. package/src/modules/primitives/lib/errors.ts +0 -138
  329. package/src/modules/user-management/lib/errors.ts +0 -81
  330. /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
@@ -1,6 +1,11 @@
1
- import { defineQuery, type ReadonlyDB } from "../../shared/internal";
1
+ import type { ReadonlyDB, QueryContext } from "../../shared/internal";
2
2
  import type { DB } from "../generated/kysely-tailordb";
3
- import { InactiveUnitError, IncompatibleUnitsError, UnitNotFoundError } from "../lib/errors";
3
+ import {
4
+ InactiveUnitError,
5
+ IncompatibleUnitsError,
6
+ UnitNotFoundError,
7
+ } from "../lib/errors.generated";
8
+ import { getUnit } from "./getUnit.generated";
4
9
 
5
10
  export interface ConvertQuantityInput {
6
11
  quantity: number;
@@ -9,63 +14,50 @@ export interface ConvertQuantityInput {
9
14
  }
10
15
 
11
16
  /**
12
- * Function: convertQuantity
13
- *
14
17
  * Converts a quantity from one unit of measure to another within the same category.
15
18
  * Units are identified by their symbol (e.g., "kg", "lb", "g").
16
19
  * The conversion uses each unit's conversion factor relative to the category's reference unit.
17
20
  * Result is rounded to the target unit's precision setting.
18
21
  */
19
- export const convertQuantity = defineQuery(
20
- async (db: ReadonlyDB<DB>, input: ConvertQuantityInput) => {
21
- // 1. Validate source unit exists
22
- const sourceUnit = await db
23
- .selectFrom("Unit")
24
- .selectAll()
25
- .where("symbol", "=", input.sourceUnitSymbol)
26
- .executeTakeFirst();
27
-
28
- if (!sourceUnit) {
29
- throw new UnitNotFoundError(input.sourceUnitSymbol);
30
- }
31
-
32
- // 2. Validate target unit exists
33
- const targetUnit = await db
34
- .selectFrom("Unit")
35
- .selectAll()
36
- .where("symbol", "=", input.targetUnitSymbol)
37
- .executeTakeFirst();
38
-
39
- if (!targetUnit) {
40
- throw new UnitNotFoundError(input.targetUnitSymbol);
41
- }
42
-
43
- // 3. Validate both units are active
44
- if (!sourceUnit.isActive) {
45
- throw new InactiveUnitError(input.sourceUnitSymbol);
46
- }
47
-
48
- if (!targetUnit.isActive) {
49
- throw new InactiveUnitError(input.targetUnitSymbol);
50
- }
51
-
52
- // 4. Validate units belong to the same category
53
- if (sourceUnit.categoryId !== targetUnit.categoryId) {
54
- throw new IncompatibleUnitsError(input.sourceUnitSymbol, input.targetUnitSymbol);
55
- }
56
-
57
- // 5. Perform conversion
58
- // Formula: result = quantity * sourceConversionFactor / targetConversionFactor
59
- const rawResult = (input.quantity * sourceUnit.conversionFactor) / targetUnit.conversionFactor;
60
-
61
- // 6. Apply rounding to target unit's precision
62
- const roundingFactor = Math.pow(10, targetUnit.roundingPrecision);
63
- const convertedQuantity = Math.round(rawResult * roundingFactor) / roundingFactor;
64
-
65
- return {
66
- convertedQuantity,
67
- sourceUnit,
68
- targetUnit,
69
- };
70
- },
71
- );
22
+ export async function run(db: ReadonlyDB<DB>, input: ConvertQuantityInput, ctx: QueryContext) {
23
+ // Validate source unit exists
24
+ const { unit: sourceUnit } = await getUnit(db, { symbol: input.sourceUnitSymbol }, ctx);
25
+
26
+ if (!sourceUnit) {
27
+ throw new UnitNotFoundError(input.sourceUnitSymbol);
28
+ }
29
+
30
+ // Validate target unit exists
31
+ const { unit: targetUnit } = await getUnit(db, { symbol: input.targetUnitSymbol }, ctx);
32
+
33
+ if (!targetUnit) {
34
+ throw new UnitNotFoundError(input.targetUnitSymbol);
35
+ }
36
+
37
+ // Validate both units are active
38
+ if (!sourceUnit.isActive) {
39
+ throw new InactiveUnitError(input.sourceUnitSymbol);
40
+ }
41
+
42
+ if (!targetUnit.isActive) {
43
+ throw new InactiveUnitError(input.targetUnitSymbol);
44
+ }
45
+
46
+ // Validate units belong to the same category
47
+ if (sourceUnit.categoryId !== targetUnit.categoryId) {
48
+ throw new IncompatibleUnitsError(`${input.sourceUnitSymbol} and ${input.targetUnitSymbol}`);
49
+ }
50
+
51
+ // Perform conversion: result = quantity * sourceConversionFactor / targetConversionFactor
52
+ const rawResult = (input.quantity * sourceUnit.conversionFactor) / targetUnit.conversionFactor;
53
+
54
+ // Apply rounding to target unit's precision
55
+ const roundingFactor = Math.pow(10, targetUnit.roundingPrecision);
56
+ const convertedQuantity = Math.round(rawResult * roundingFactor) / roundingFactor;
57
+
58
+ return {
59
+ convertedQuantity,
60
+ sourceUnit,
61
+ targetUnit,
62
+ };
63
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./getBaseCurrency";
4
+
5
+ export const getBaseCurrency = defineQuery(run);
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import type { DB } from "../generated/kysely-tailordb";
4
+ import type { QueryContext } from "../../shared/internal";
5
+ import { baseCurrencyUSD } from "../testing/fixtures";
6
+ import { getBaseCurrency } from "./getBaseCurrency.generated";
7
+
8
+ describe("getBaseCurrency", () => {
9
+ const ctx: QueryContext = { actorId: "test-actor" };
10
+
11
+ it("returns base currency when set", async () => {
12
+ const { db, spies } = createMockDb<DB>();
13
+ spies.select.mockReturnValue(baseCurrencyUSD);
14
+
15
+ const result = await getBaseCurrency(db, undefined, ctx);
16
+
17
+ expect(result.currency).toEqual(baseCurrencyUSD);
18
+ });
19
+
20
+ it("returns null when no base currency is set", async () => {
21
+ const { db, spies } = createMockDb<DB>();
22
+ spies.select.mockReturnValue(undefined);
23
+
24
+ const result = await getBaseCurrency(db, undefined, ctx);
25
+
26
+ expect(result.currency).toBeNull();
27
+ });
28
+ });
@@ -0,0 +1,16 @@
1
+ import type { ReadonlyDB } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+
4
+ /**
5
+ * Retrieves the currency designated as the base currency.
6
+ * Returns null if no base currency has been set.
7
+ */
8
+ export async function run(db: ReadonlyDB<DB>) {
9
+ const currency = await db
10
+ .selectFrom("Currency")
11
+ .selectAll()
12
+ .where("isBaseCurrency", "=", true)
13
+ .executeTakeFirst();
14
+
15
+ return { currency: currency ?? null };
16
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./getCurrency";
4
+
5
+ export const getCurrency = defineQuery(run);
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import type { DB } from "../generated/kysely-tailordb";
4
+ import { baseCurrencyUSD } from "../testing/fixtures";
5
+ import { run } from "./getCurrency";
6
+
7
+ describe("getCurrency", () => {
8
+ describe("by id", () => {
9
+ it("returns currency when found", async () => {
10
+ const { db, spies } = createMockDb<DB>();
11
+ spies.select.mockReturnValue(baseCurrencyUSD);
12
+
13
+ const result = await run(db, { id: "currency-usd" });
14
+
15
+ expect(result.currency).toEqual(baseCurrencyUSD);
16
+ });
17
+
18
+ it("returns null when currency not found", async () => {
19
+ const { db, spies } = createMockDb<DB>();
20
+ spies.select.mockReturnValue(undefined);
21
+
22
+ const result = await run(db, { id: "nonexistent" });
23
+
24
+ expect(result.currency).toBeNull();
25
+ });
26
+ });
27
+
28
+ describe("by code", () => {
29
+ it("returns currency when found", async () => {
30
+ const { db, spies } = createMockDb<DB>();
31
+ spies.select.mockReturnValue(baseCurrencyUSD);
32
+
33
+ const result = await run(db, { code: "USD" });
34
+
35
+ expect(result.currency).toEqual(baseCurrencyUSD);
36
+ });
37
+
38
+ it("returns null when currency not found", async () => {
39
+ const { db, spies } = createMockDb<DB>();
40
+ spies.select.mockReturnValue(undefined);
41
+
42
+ const result = await run(db, { code: "NONEXISTENT" });
43
+
44
+ expect(result.currency).toBeNull();
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,18 @@
1
+ import type { ReadonlyDB } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+
4
+ export type GetCurrencyInput = { id: string } | { code: string };
5
+
6
+ export async function run(db: ReadonlyDB<DB>, input: GetCurrencyInput) {
7
+ let query = db.selectFrom("Currency").selectAll();
8
+
9
+ if ("id" in input) {
10
+ query = query.where("id", "=", input.id);
11
+ } else {
12
+ query = query.where("code", "=", input.code);
13
+ }
14
+
15
+ const currency = await query.executeTakeFirst();
16
+
17
+ return { currency: currency ?? null };
18
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./getUnit";
4
+
5
+ export const getUnit = defineQuery(run);
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import type { DB } from "../generated/kysely-tailordb";
4
+ import { baseUnitKg } from "../testing/fixtures";
5
+ import { run } from "./getUnit";
6
+
7
+ describe("getUnit", () => {
8
+ describe("by id", () => {
9
+ it("returns unit when found", async () => {
10
+ const { db, spies } = createMockDb<DB>();
11
+ spies.select.mockReturnValue(baseUnitKg);
12
+
13
+ const result = await run(db, { id: "unit-kg" });
14
+
15
+ expect(result.unit).toEqual(baseUnitKg);
16
+ });
17
+
18
+ it("returns null when unit not found", async () => {
19
+ const { db, spies } = createMockDb<DB>();
20
+ spies.select.mockReturnValue(undefined);
21
+
22
+ const result = await run(db, { id: "nonexistent" });
23
+
24
+ expect(result.unit).toBeNull();
25
+ });
26
+ });
27
+
28
+ describe("by symbol", () => {
29
+ it("returns unit when found", async () => {
30
+ const { db, spies } = createMockDb<DB>();
31
+ spies.select.mockReturnValue(baseUnitKg);
32
+
33
+ const result = await run(db, { symbol: "kg" });
34
+
35
+ expect(result.unit).toEqual(baseUnitKg);
36
+ });
37
+
38
+ it("returns null when unit not found", async () => {
39
+ const { db, spies } = createMockDb<DB>();
40
+ spies.select.mockReturnValue(undefined);
41
+
42
+ const result = await run(db, { symbol: "nonexistent" });
43
+
44
+ expect(result.unit).toBeNull();
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,18 @@
1
+ import type { ReadonlyDB } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+
4
+ export type GetUnitInput = { id: string } | { symbol: string };
5
+
6
+ export async function run(db: ReadonlyDB<DB>, input: GetUnitInput) {
7
+ let query = db.selectFrom("Unit").selectAll();
8
+
9
+ if ("id" in input) {
10
+ query = query.where("id", "=", input.id);
11
+ } else {
12
+ query = query.where("symbol", "=", input.symbol);
13
+ }
14
+
15
+ const unit = await query.executeTakeFirst();
16
+
17
+ return { unit: unit ?? null };
18
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./getUoMCategory";
4
+
5
+ export const getUoMCategory = defineQuery(run);
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import type { DB } from "../generated/kysely-tailordb";
4
+ import { baseUoMCategory } from "../testing/fixtures";
5
+ import { run } from "./getUoMCategory";
6
+
7
+ describe("getUoMCategory", () => {
8
+ describe("by id", () => {
9
+ it("returns category when found", async () => {
10
+ const { db, spies } = createMockDb<DB>();
11
+ spies.select.mockReturnValue(baseUoMCategory);
12
+
13
+ const result = await run(db, { id: "category-1" });
14
+
15
+ expect(result.uoMCategory).toEqual(baseUoMCategory);
16
+ });
17
+
18
+ it("returns null when category not found", async () => {
19
+ const { db, spies } = createMockDb<DB>();
20
+ spies.select.mockReturnValue(undefined);
21
+
22
+ const result = await run(db, { id: "nonexistent" });
23
+
24
+ expect(result.uoMCategory).toBeNull();
25
+ });
26
+ });
27
+
28
+ describe("by name", () => {
29
+ it("returns category when found", async () => {
30
+ const { db, spies } = createMockDb<DB>();
31
+ spies.select.mockReturnValue(baseUoMCategory);
32
+
33
+ const result = await run(db, { name: "Weight" });
34
+
35
+ expect(result.uoMCategory).toEqual(baseUoMCategory);
36
+ });
37
+
38
+ it("returns null when category not found", async () => {
39
+ const { db, spies } = createMockDb<DB>();
40
+ spies.select.mockReturnValue(undefined);
41
+
42
+ const result = await run(db, { name: "Nonexistent" });
43
+
44
+ expect(result.uoMCategory).toBeNull();
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,18 @@
1
+ import type { ReadonlyDB } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+
4
+ export type GetUoMCategoryInput = { id: string } | { name: string };
5
+
6
+ export async function run(db: ReadonlyDB<DB>, input: GetUoMCategoryInput) {
7
+ let query = db.selectFrom("UoMCategory").selectAll();
8
+
9
+ if ("id" in input) {
10
+ query = query.where("id", "=", input.id);
11
+ } else {
12
+ query = query.where("name", "=", input.name);
13
+ }
14
+
15
+ const uoMCategory = await query.executeTakeFirst();
16
+
17
+ return { uoMCategory: uoMCategory ?? null };
18
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./listUnitsByCategory";
4
+
5
+ export const listUnitsByCategory = defineQuery(run);
@@ -0,0 +1,16 @@
1
+ import type { ReadonlyDB } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+
4
+ export interface ListUnitsByCategoryInput {
5
+ categoryId: string;
6
+ }
7
+
8
+ export async function run(db: ReadonlyDB<DB>, input: ListUnitsByCategoryInput) {
9
+ const units = await db
10
+ .selectFrom("Unit")
11
+ .selectAll()
12
+ .where("categoryId", "=", input.categoryId)
13
+ .execute();
14
+
15
+ return { units };
16
+ }
@@ -2,10 +2,10 @@ import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
2
2
 
3
3
  export default defineConfig({
4
4
  name: "primitives",
5
- db: { "main-db": { files: [`./src/db/*.ts`] } },
5
+ db: { "main-db": { files: [`./db/*.ts`] } },
6
6
  });
7
7
 
8
8
  export const generators = defineGenerators(
9
- ["@tailor-platform/kysely-type", { distPath: `./src/generated/kysely-tailordb.ts` }],
10
- ["@tailor-platform/enum-constants", { distPath: "./src/generated/enums.ts" }],
9
+ ["@tailor-platform/kysely-type", { distPath: `./generated/kysely-tailordb.ts` }],
10
+ ["@tailor-platform/enum-constants", { distPath: "./generated/enums.ts" }],
11
11
  );
@@ -11,32 +11,45 @@ describe("defineCommand", () => {
11
11
 
12
12
  it("calls impl with db and input when permission is present", async () => {
13
13
  const impl = vi.fn().mockResolvedValue({ result: "ok" });
14
- const command = defineCommand("mod:doThing", impl);
14
+ const command = defineCommand("mod:doThing", impl)();
15
15
 
16
16
  const result = await command("fake-db", { foo: "bar" }, ctx);
17
17
 
18
18
  expect(result).toEqual({ result: "ok" });
19
- expect(impl).toHaveBeenCalledWith("fake-db", { foo: "bar" });
19
+ expect(impl).toHaveBeenCalledWith("fake-db", { foo: "bar" }, ctx, undefined);
20
20
  });
21
21
 
22
- it("throws InsufficientPermissionError when permission is missing", async () => {
22
+ it("returns InsufficientPermissionError when permission is missing", async () => {
23
23
  const impl = vi.fn();
24
- const command = defineCommand("mod:doThing", impl);
24
+ const command = defineCommand("mod:doThing", impl)();
25
25
  const denied: CommandContext = { actorId: "user-1", permissions: [] };
26
26
 
27
- await expect(command("fake-db", {}, denied)).rejects.toBeInstanceOf(
28
- InsufficientPermissionError,
29
- );
27
+ const result = await command("fake-db", {}, denied);
28
+ expect(result.ok).toBe(false);
29
+ if (!result.ok) {
30
+ expect(result.error).toBeInstanceOf(InsufficientPermissionError);
31
+ }
30
32
  expect(impl).not.toHaveBeenCalled();
31
33
  });
32
34
 
33
- it("does not pass ctx to impl", async () => {
35
+ it("passes ctx to impl", async () => {
34
36
  const impl = vi.fn().mockResolvedValue({});
35
- const command = defineCommand("mod:doThing", impl);
37
+ const command = defineCommand("mod:doThing", impl)();
36
38
 
37
39
  await command("fake-db", { x: 1 }, ctx);
38
40
 
39
41
  expect(impl).toHaveBeenCalledTimes(1);
40
- expect(impl.mock.calls[0]).toHaveLength(2);
42
+ expect(impl.mock.calls[0]).toHaveLength(4);
43
+ expect(impl.mock.calls[0][2]).toEqual(ctx);
44
+ });
45
+
46
+ it("passes deps to impl when provided", async () => {
47
+ const impl = vi.fn().mockResolvedValue({ result: "ok" });
48
+ const deps = { maxDepth: 10 };
49
+ const command = defineCommand("mod:doThing", impl)(deps);
50
+
51
+ await command("fake-db", { x: 1 }, ctx);
52
+
53
+ expect(impl).toHaveBeenCalledWith("fake-db", { x: 1 }, ctx, deps);
41
54
  });
42
55
  });
@@ -1,19 +1,32 @@
1
+ import type { Result } from "./result";
1
2
  import type { CommandContext } from "./types";
2
3
  import { requirePermission } from "./requirePermission";
4
+ import type { InsufficientPermissionError } from "./errors";
3
5
 
4
- export type Command<TInput, TResult> = (
5
- db: unknown,
6
+ export type Command<TInput, TReturn extends Result<unknown, Error>> = (
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ db: any,
6
9
  input: TInput,
7
10
  ctx: CommandContext,
8
- ) => Promise<TResult>;
11
+ ) => Promise<TReturn>;
12
+
13
+ type CommandResult<TReturn extends Result<unknown, Error>> =
14
+ | TReturn
15
+ | Result<never, InstanceType<typeof InsufficientPermissionError>>;
9
16
 
10
- export function defineCommand<TInput, TResult>(
17
+ export function defineCommand<TDeps, TInput, TReturn extends Result<unknown, Error>>(
11
18
  permission: string,
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- impl: (db: any, input: TInput) => Promise<TResult>,
14
- ): Command<TInput, TResult> {
15
- return async (db, input, ctx) => {
16
- requirePermission(ctx, permission);
17
- return impl(db, input);
19
+ impl: (
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ db: any,
22
+ input: TInput,
23
+ ctx: CommandContext,
24
+ deps?: TDeps,
25
+ ) => Promise<TReturn>,
26
+ ): (deps?: TDeps) => Command<TInput, CommandResult<TReturn>> {
27
+ return (deps) => async (db, input, ctx) => {
28
+ const check = requirePermission(ctx, permission);
29
+ if (!check.ok) return check;
30
+ return impl(db, input, ctx, deps);
18
31
  };
19
32
  }
@@ -1,4 +1,5 @@
1
1
  export { defineCommand, type Command } from "./defineCommand";
2
+ export { ok, err, type Result, type ValueOf, type ErrorOf } from "./result";
2
3
  export { defineQuery, type Query } from "./defineQuery";
3
4
  export { definePermissions } from "./definePermissions";
4
5
  export { requirePermission } from "./requirePermission";
@@ -13,35 +13,36 @@ describe("requirePermission", () => {
13
13
  ],
14
14
  };
15
15
 
16
- it("does not throw when permission is present", () => {
17
- expect(() => requirePermission(ctx, "primitives:createCategory")).not.toThrow();
18
- expect(() => requirePermission(ctx, "inventory:createItem")).not.toThrow();
16
+ it("returns ok when permission is present", () => {
17
+ expect(requirePermission(ctx, "primitives:createCategory").ok).toBe(true);
18
+ expect(requirePermission(ctx, "inventory:createItem").ok).toBe(true);
19
19
  });
20
20
 
21
- it("throws InsufficientPermissionError when permission is missing", () => {
22
- expect(() => requirePermission(ctx, "primitives:deleteCategory")).toThrow(
23
- InsufficientPermissionError,
24
- );
21
+ it("returns InsufficientPermissionError when permission is missing", () => {
22
+ const result = requirePermission(ctx, "primitives:deleteCategory");
23
+ expect(result.ok).toBe(false);
24
+ if (!result.ok) {
25
+ expect(result.error).toBeInstanceOf(InsufficientPermissionError);
26
+ }
25
27
  });
26
28
 
27
29
  it("includes actorId and permission key in error", () => {
28
- try {
29
- requirePermission(ctx, "orders:createOrder");
30
- expect.fail("Should have thrown");
31
- } catch (err) {
32
- const error = err as InstanceType<typeof InsufficientPermissionError>;
33
- expect(error.name).toBe("InsufficientPermissionError");
34
- expect(error.code).toBe("INSUFFICIENT_PERMISSION");
35
- expect(error.message).toContain("user-1");
36
- expect(error.message).toContain("orders:createOrder");
30
+ const result = requirePermission(ctx, "orders:createOrder");
31
+ expect(result.ok).toBe(false);
32
+ if (!result.ok) {
33
+ expect(result.error.name).toBe("InsufficientPermissionError");
34
+ expect(result.error.code).toBe("INSUFFICIENT_PERMISSION");
35
+ expect(result.error.message).toContain("user-1");
36
+ expect(result.error.message).toContain("orders:createOrder");
37
37
  }
38
38
  });
39
39
 
40
- it("throws when permissions array is empty", () => {
40
+ it("returns error when permissions array is empty", () => {
41
41
  const emptyCtx: CommandContext = { actorId: "user-2", permissions: [] };
42
-
43
- expect(() => requirePermission(emptyCtx, "primitives:createCategory")).toThrow(
44
- InsufficientPermissionError,
45
- );
42
+ const result = requirePermission(emptyCtx, "primitives:createCategory");
43
+ expect(result.ok).toBe(false);
44
+ if (!result.ok) {
45
+ expect(result.error).toBeInstanceOf(InsufficientPermissionError);
46
+ }
46
47
  });
47
48
  });
@@ -1,8 +1,14 @@
1
1
  import type { CommandContext } from "./types";
2
+ import type { Result } from "./result";
2
3
  import { InsufficientPermissionError } from "./errors";
4
+ import { ok, err } from "./result";
3
5
 
4
- export function requirePermission(ctx: CommandContext, key: string): void {
6
+ export function requirePermission(
7
+ ctx: CommandContext,
8
+ key: string,
9
+ ): Result<void, InstanceType<typeof InsufficientPermissionError>> {
5
10
  if (!ctx.permissions.includes(key)) {
6
- throw new InsufficientPermissionError(ctx.actorId, key);
11
+ return err(new InsufficientPermissionError(ctx.actorId, key));
7
12
  }
13
+ return ok(undefined);
8
14
  }
@@ -0,0 +1,12 @@
1
+ export type Result<T, E extends Error = never> = { ok: true; value: T } | { ok: false; error: E };
2
+
3
+ export type ValueOf<R> = R extends { ok: true; value: infer T } ? T : never;
4
+ export type ErrorOf<R> = R extends { ok: false; error: infer E } ? E : never;
5
+
6
+ export function ok<T>(value: T): { ok: true; value: T } {
7
+ return { ok: true, value };
8
+ }
9
+
10
+ export function err<E extends Error>(error: E): { ok: false; error: E } {
11
+ return { ok: false, error };
12
+ }