@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
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import { type CommandContext } from "../../shared/internal";
4
+ import { DB } from "../generated/kysely-tailordb";
5
+ import { InvalidStateTransitionError, ItemNotFoundError } from "../lib/errors.generated";
6
+ import { baseActiveItem, baseDraftItem, baseInactiveItem } from "../testing/fixtures";
7
+ import { run } from "./reactivateItem";
8
+
9
+ describe("reactivateItem", () => {
10
+ const ctx: CommandContext = {
11
+ actorId: "test-actor",
12
+ permissions: ["item-management:reactivateItem"],
13
+ };
14
+
15
+ it("returns error when item does not exist", async () => {
16
+ const { db, spies } = createMockDb<DB>();
17
+ spies.select.mockReturnValue(undefined);
18
+
19
+ const result = await run(db, { id: "nonexistent" }, ctx);
20
+ expect(result.ok).toBe(false);
21
+ if (!result.ok) {
22
+ expect(result.error).toBeInstanceOf(ItemNotFoundError);
23
+ }
24
+ });
25
+
26
+ it("returns error when item is not INACTIVE", async () => {
27
+ const { db, spies } = createMockDb<DB>();
28
+ spies.select.mockReturnValueOnce(baseActiveItem);
29
+
30
+ const result = await run(db, { id: baseActiveItem.id }, ctx);
31
+ expect(result.ok).toBe(false);
32
+ if (!result.ok) {
33
+ expect(result.error).toBeInstanceOf(InvalidStateTransitionError);
34
+ }
35
+ });
36
+
37
+ it("returns error when item is DRAFT", async () => {
38
+ const { db, spies } = createMockDb<DB>();
39
+ spies.select.mockReturnValueOnce(baseDraftItem);
40
+
41
+ const result = await run(db, { id: baseDraftItem.id }, ctx);
42
+ expect(result.ok).toBe(false);
43
+ if (!result.ok) {
44
+ expect(result.error).toBeInstanceOf(InvalidStateTransitionError);
45
+ }
46
+ });
47
+
48
+ it("reactivates an INACTIVE item", async () => {
49
+ const { db, spies } = createMockDb<DB>();
50
+ const reactivatedItem = { ...baseInactiveItem, status: "ACTIVE" };
51
+ spies.select.mockReturnValueOnce(baseInactiveItem);
52
+ spies.update.mockReturnValue(reactivatedItem);
53
+
54
+ const result = await run(db, { id: baseInactiveItem.id }, ctx);
55
+
56
+ expect(result.ok).toBe(true);
57
+ if (result.ok) {
58
+ expect(result.value.item.status).toBe("ACTIVE");
59
+ }
60
+ expect(spies.update).toHaveBeenCalled();
61
+ });
62
+
63
+ it("reactivates from custom status when from is overridden", async () => {
64
+ const { db, spies } = createMockDb<DB>();
65
+ const reactivatedItem = { ...baseDraftItem, status: "ACTIVE" };
66
+ spies.select.mockReturnValueOnce(baseDraftItem);
67
+ spies.update.mockReturnValue(reactivatedItem);
68
+
69
+ const result = await run(db, { id: baseDraftItem.id, from: ["INACTIVE", "DRAFT"] }, ctx);
70
+
71
+ expect(result.ok).toBe(true);
72
+ if (result.ok) {
73
+ expect(result.value.item.status).toBe("ACTIVE");
74
+ }
75
+ });
76
+ });
@@ -0,0 +1,42 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { InvalidStateTransitionError, ItemNotFoundError } from "../lib/errors.generated";
4
+ import { getItem } from "../query/getItem.generated";
5
+
6
+ export interface ReactivateItemInput {
7
+ id: string;
8
+ from?: string[];
9
+ }
10
+
11
+ /**
12
+ * Function: reactivateItem
13
+ *
14
+ * Transitions an item from INACTIVE back to ACTIVE status,
15
+ * restoring a previously discontinued SKU for transactions.
16
+ */
17
+ export async function run(db: DB, input: ReactivateItemInput, ctx: CommandContext) {
18
+ const { id, from } = input;
19
+
20
+ // 1. Check item exists
21
+ const { item } = await getItem(db, { id }, ctx);
22
+
23
+ if (!item) {
24
+ return err(new ItemNotFoundError(id));
25
+ }
26
+
27
+ // 2. Check status is valid for reactivation
28
+ const validFromStatuses = from ?? ["INACTIVE"];
29
+ if (!validFromStatuses.includes(item.status)) {
30
+ return err(new InvalidStateTransitionError(`${item.status} to ACTIVE`));
31
+ }
32
+
33
+ // 3. Update status to ACTIVE
34
+ const reactivatedItem = await db
35
+ .updateTable("Item")
36
+ .set({ status: "ACTIVE", updatedAt: new Date() })
37
+ .where("id", "=", id)
38
+ .returningAll()
39
+ .executeTakeFirst();
40
+
41
+ return ok({ item: reactivatedItem! });
42
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { defineCommand } from "../../shared/internal";
3
+ import { permissions } from "../lib/permissions.generated";
4
+ import { run } from "./removeItemFromTaxonomy";
5
+
6
+ export const removeItemFromTaxonomy = defineCommand(permissions.removeItemFromTaxonomy, run);
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import { type CommandContext } from "../../shared/internal";
4
+ import { DB } from "../generated/kysely-tailordb";
5
+ import { AssignmentNotFoundError } from "../lib/errors.generated";
6
+ import { baseAssignment } from "../testing/fixtures";
7
+ import { run } from "./removeItemFromTaxonomy";
8
+
9
+ describe("removeItemFromTaxonomy", () => {
10
+ const ctx: CommandContext = {
11
+ actorId: "test-actor",
12
+ permissions: ["item-management:removeItemFromTaxonomy"],
13
+ };
14
+
15
+ it("returns error when assignment does not exist", async () => {
16
+ const { db, spies } = createMockDb<DB>();
17
+ spies.select.mockReturnValueOnce(undefined);
18
+
19
+ const result = await run(db, { itemId: "item-99", taxonomyNodeId: "node-99" }, ctx);
20
+ expect(result.ok).toBe(false);
21
+ if (!result.ok) {
22
+ expect(result.error).toBeInstanceOf(AssignmentNotFoundError);
23
+ }
24
+ });
25
+
26
+ it("removes assignment successfully", async () => {
27
+ const { db, spies } = createMockDb<DB>();
28
+ spies.select.mockReturnValueOnce(baseAssignment);
29
+ spies.delete.mockReturnValue(undefined);
30
+
31
+ const result = await run(
32
+ db,
33
+ { itemId: baseAssignment.itemId, taxonomyNodeId: baseAssignment.taxonomyNodeId },
34
+ ctx,
35
+ );
36
+
37
+ expect(result.ok).toBe(true);
38
+ if (result.ok) {
39
+ expect(result.value.success).toBe(true);
40
+ }
41
+ expect(spies.delete).toHaveBeenCalled();
42
+ });
43
+ });
@@ -0,0 +1,30 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { AssignmentNotFoundError } from "../lib/errors.generated";
4
+ import { getItemTaxonomyAssignment } from "../query/getItemTaxonomyAssignment.generated";
5
+
6
+ export interface RemoveItemFromTaxonomyInput {
7
+ itemId: string;
8
+ taxonomyNodeId: string;
9
+ }
10
+
11
+ /**
12
+ * Function: removeItemFromTaxonomy
13
+ *
14
+ * Removes the link between an item and a taxonomy node.
15
+ */
16
+ export async function run(db: DB, input: RemoveItemFromTaxonomyInput, ctx: CommandContext) {
17
+ const { itemId, taxonomyNodeId } = input;
18
+
19
+ // 1. Check assignment exists
20
+ const { assignment } = await getItemTaxonomyAssignment(db, { itemId, taxonomyNodeId }, ctx);
21
+
22
+ if (!assignment) {
23
+ return err(new AssignmentNotFoundError(`${itemId} in ${taxonomyNodeId}`));
24
+ }
25
+
26
+ // 2. Delete assignment
27
+ await db.deleteFrom("ItemTaxonomyAssignment").where("id", "=", assignment.id).execute();
28
+
29
+ return ok({ success: true as const });
30
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { defineCommand } from "../../shared/internal";
3
+ import { permissions } from "../lib/permissions.generated";
4
+ import { run } from "./updateItem";
5
+
6
+ export const updateItem = defineCommand(permissions.updateItem, run);
@@ -0,0 +1,178 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import { type CommandContext } from "../../shared/internal";
4
+ import { DB } from "../generated/kysely-tailordb";
5
+ import {
6
+ DuplicateBarcodeError,
7
+ UnitNotFoundError,
8
+ ItemNotFoundError,
9
+ NoFieldsToUpdateError,
10
+ SkuImmutableError,
11
+ UomLockedError,
12
+ } from "../lib/errors.generated";
13
+ import { baseActiveItem, baseDraftItem, baseInactiveItem } from "../testing/fixtures";
14
+ import { getUnit } from "../../primitives";
15
+ import { run } from "./updateItem";
16
+
17
+ describe("updateItem", () => {
18
+ const ctx: CommandContext = {
19
+ actorId: "test-actor",
20
+ permissions: ["item-management:updateItem"],
21
+ };
22
+
23
+ it("returns error when item does not exist", async () => {
24
+ const { db, spies } = createMockDb<DB>();
25
+ spies.select.mockReturnValue(undefined);
26
+
27
+ const result = await run(db, { id: "nonexistent", name: "New Name" }, ctx, { getUnit });
28
+ expect(result.ok).toBe(false);
29
+ if (!result.ok) {
30
+ expect(result.error).toBeInstanceOf(ItemNotFoundError);
31
+ }
32
+ });
33
+
34
+ it("returns error when attempting to change SKU", async () => {
35
+ const { db, spies } = createMockDb<DB>();
36
+ spies.select.mockReturnValueOnce(baseDraftItem);
37
+
38
+ const result = await run(db, { id: baseDraftItem.id, sku: "NEW-SKU" }, ctx, { getUnit });
39
+ expect(result.ok).toBe(false);
40
+ if (!result.ok) {
41
+ expect(result.error).toBeInstanceOf(SkuImmutableError);
42
+ }
43
+ });
44
+
45
+ it("returns error when no fields to update", async () => {
46
+ const { db, spies } = createMockDb<DB>();
47
+ spies.select.mockReturnValueOnce(baseDraftItem);
48
+
49
+ const result = await run(db, { id: baseDraftItem.id }, ctx, { getUnit });
50
+ expect(result.ok).toBe(false);
51
+ if (!result.ok) {
52
+ expect(result.error).toBeInstanceOf(NoFieldsToUpdateError);
53
+ }
54
+ });
55
+
56
+ it("returns error when barcode is duplicate", async () => {
57
+ const { db, spies } = createMockDb<DB>();
58
+ spies.select
59
+ .mockReturnValueOnce(baseDraftItem) // Item lookup
60
+ .mockReturnValueOnce(baseActiveItem); // Barcode already taken
61
+
62
+ const result = await run(db, { id: baseDraftItem.id, barcode: "BAR-002" }, ctx, { getUnit });
63
+ expect(result.ok).toBe(false);
64
+ if (!result.ok) {
65
+ expect(result.error).toBeInstanceOf(DuplicateBarcodeError);
66
+ }
67
+ });
68
+
69
+ it("returns error when changing UoM on non-DRAFT item", async () => {
70
+ const { db, spies } = createMockDb<DB>();
71
+ spies.select.mockReturnValueOnce(baseActiveItem);
72
+
73
+ const result = await run(db, { id: baseActiveItem.id, unitId: "unit-g" }, ctx, { getUnit });
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toBeInstanceOf(UomLockedError);
77
+ }
78
+ });
79
+
80
+ it("returns error when changing UoM on INACTIVE item", async () => {
81
+ const { db, spies } = createMockDb<DB>();
82
+ spies.select.mockReturnValueOnce(baseInactiveItem);
83
+
84
+ const result = await run(db, { id: baseInactiveItem.id, unitId: "unit-g" }, ctx, { getUnit });
85
+ expect(result.ok).toBe(false);
86
+ if (!result.ok) {
87
+ expect(result.error).toBeInstanceOf(UomLockedError);
88
+ }
89
+ });
90
+
91
+ it("returns error when new UoM does not exist", async () => {
92
+ const { db, spies } = createMockDb<DB>();
93
+ spies.select
94
+ .mockReturnValueOnce(baseDraftItem) // Item lookup
95
+ .mockReturnValueOnce(undefined); // UoM not found
96
+
97
+ const result = await run(db, { id: baseDraftItem.id, unitId: "nonexistent-unit" }, ctx, {
98
+ getUnit,
99
+ });
100
+ expect(result.ok).toBe(false);
101
+ if (!result.ok) {
102
+ expect(result.error).toBeInstanceOf(UnitNotFoundError);
103
+ }
104
+ });
105
+
106
+ it("updates name in any status", async () => {
107
+ const { db, spies } = createMockDb<DB>();
108
+ const updatedItem = { ...baseActiveItem, name: "Updated Name" };
109
+ spies.select.mockReturnValueOnce(baseActiveItem);
110
+ spies.update.mockReturnValue(updatedItem);
111
+
112
+ const result = await run(db, { id: baseActiveItem.id, name: "Updated Name" }, ctx, { getUnit });
113
+
114
+ expect(result.ok).toBe(true);
115
+ if (result.ok) {
116
+ expect(result.value.item.name).toBe("Updated Name");
117
+ }
118
+ expect(spies.update).toHaveBeenCalled();
119
+ });
120
+
121
+ it("updates barcode in any status", async () => {
122
+ const { db, spies } = createMockDb<DB>();
123
+ const updatedItem = { ...baseActiveItem, barcode: "NEW-BAR" };
124
+ spies.select
125
+ .mockReturnValueOnce(baseActiveItem) // Item lookup
126
+ .mockReturnValueOnce(undefined); // Barcode unique
127
+ spies.update.mockReturnValue(updatedItem);
128
+
129
+ const result = await run(db, { id: baseActiveItem.id, barcode: "NEW-BAR" }, ctx, { getUnit });
130
+
131
+ expect(result.ok).toBe(true);
132
+ if (result.ok) {
133
+ expect(result.value.item.barcode).toBe("NEW-BAR");
134
+ }
135
+ });
136
+
137
+ it("clears barcode by setting to null", async () => {
138
+ const { db, spies } = createMockDb<DB>();
139
+ const updatedItem = { ...baseActiveItem, barcode: null };
140
+ spies.select.mockReturnValueOnce(baseActiveItem);
141
+ spies.update.mockReturnValue(updatedItem);
142
+
143
+ const result = await run(db, { id: baseActiveItem.id, barcode: null }, ctx, { getUnit });
144
+
145
+ expect(result.ok).toBe(true);
146
+ if (result.ok) {
147
+ expect(result.value.item.barcode).toBeNull();
148
+ }
149
+ });
150
+
151
+ it("updates UoM in DRAFT status", async () => {
152
+ const { db, spies } = createMockDb<DB>();
153
+ const activeUnit = { id: "unit-g", isActive: true };
154
+ const updatedItem = { ...baseDraftItem, unitId: "unit-g" };
155
+ spies.select
156
+ .mockReturnValueOnce(baseDraftItem) // Item lookup
157
+ .mockReturnValueOnce(activeUnit); // UoM valid
158
+ spies.update.mockReturnValue(updatedItem);
159
+
160
+ const result = await run(db, { id: baseDraftItem.id, unitId: "unit-g" }, ctx, { getUnit });
161
+
162
+ expect(result.ok).toBe(true);
163
+ if (result.ok) {
164
+ expect(result.value.item.unitId).toBe("unit-g");
165
+ }
166
+ });
167
+
168
+ it("passes custom fields through to set", async () => {
169
+ const { db, spies } = createMockDb<DB>();
170
+ const updatedItem = { ...baseDraftItem, priority: 10 };
171
+ spies.select.mockReturnValueOnce(baseDraftItem);
172
+ spies.update.mockReturnValue(updatedItem);
173
+
174
+ await run(db, { id: baseDraftItem.id, priority: 10 }, ctx, { getUnit });
175
+
176
+ expect(spies.set).toHaveBeenNthCalledWith(1, expect.objectContaining({ priority: 10 }));
177
+ });
178
+ });
@@ -0,0 +1,103 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ DuplicateBarcodeError,
5
+ UnitNotFoundError,
6
+ ItemNotFoundError,
7
+ NoFieldsToUpdateError,
8
+ SkuImmutableError,
9
+ UomLockedError,
10
+ } from "../lib/errors.generated";
11
+ import { getItem } from "../query/getItem.generated";
12
+ import { type PrimitivesQueries } from "../module";
13
+
14
+ export interface UpdateItemInput {
15
+ id: string;
16
+ sku?: string;
17
+ name?: string;
18
+ barcode?: string | null;
19
+ unitId?: string;
20
+ }
21
+
22
+ /**
23
+ * Function: updateItem
24
+ *
25
+ * Updates mutable fields of an existing item. SKU is immutable.
26
+ * UoM can only be changed in DRAFT status.
27
+ */
28
+ export async function run<CF extends Record<string, unknown>>(
29
+ db: DB,
30
+ input: UpdateItemInput & Partial<CF>,
31
+ ctx: CommandContext,
32
+ primitivesQueries?: Pick<PrimitivesQueries, "getUnit">,
33
+ ) {
34
+ const { id, sku, name, barcode, unitId, ...customFields } = input;
35
+
36
+ // 1. Check item exists
37
+ const { item } = await getItem(db, { id }, ctx);
38
+
39
+ if (!item) {
40
+ return err(new ItemNotFoundError(id));
41
+ }
42
+
43
+ // 2. SKU is immutable
44
+ if (sku !== undefined) {
45
+ return err(new SkuImmutableError(id));
46
+ }
47
+
48
+ // 3. Check at least one field provided
49
+ const hasName = name !== undefined;
50
+ const hasBarcode = barcode !== undefined;
51
+ const hasUnitId = unitId !== undefined;
52
+ const hasCustomFields = Object.keys(customFields).length > 0;
53
+
54
+ if (!hasName && !hasBarcode && !hasUnitId && !hasCustomFields) {
55
+ return err(new NoFieldsToUpdateError(id));
56
+ }
57
+
58
+ // 4. Check barcode uniqueness when provided (non-null)
59
+ if (hasBarcode && barcode !== null) {
60
+ const existingBarcode = await db
61
+ .selectFrom("Item")
62
+ .selectAll()
63
+ .where("barcode", "=", barcode)
64
+ .where("id", "!=", id)
65
+ .executeTakeFirst();
66
+
67
+ if (existingBarcode) {
68
+ return err(new DuplicateBarcodeError(barcode));
69
+ }
70
+ }
71
+
72
+ // 5. UoM can only be changed in DRAFT status
73
+ if (hasUnitId) {
74
+ if (item.status !== "DRAFT") {
75
+ return err(new UomLockedError(id));
76
+ }
77
+
78
+ // Validate UoM exists and is active
79
+ const { unit } = await primitivesQueries!.getUnit(db, { id: unitId }, ctx);
80
+
81
+ if (!unit?.isActive) {
82
+ return err(new UnitNotFoundError(unitId));
83
+ }
84
+ }
85
+
86
+ // 6. Apply updates
87
+ const updates: Record<string, unknown> = {
88
+ ...(customFields as Record<string, unknown>),
89
+ updatedAt: new Date(),
90
+ };
91
+ if (hasName) updates.name = name;
92
+ if (hasBarcode) updates.barcode = barcode;
93
+ if (hasUnitId) updates.unitId = unitId;
94
+
95
+ const updatedItem = await db
96
+ .updateTable("Item")
97
+ .set(updates)
98
+ .where("id", "=", id)
99
+ .returningAll()
100
+ .executeTakeFirstOrThrow();
101
+
102
+ return ok({ item: updatedItem });
103
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { defineCommand } from "../../shared/internal";
3
+ import { permissions } from "../lib/permissions.generated";
4
+ import { run } from "./updateTaxonomyNode";
5
+
6
+ export const updateTaxonomyNode = defineCommand(permissions.updateTaxonomyNode, run);
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../testing/index";
3
+ import { type CommandContext } from "../../shared/internal";
4
+ import { DB } from "../generated/kysely-tailordb";
5
+ import {
6
+ CodeImmutableError,
7
+ MissingRequiredFieldsError,
8
+ NodeNotFoundError,
9
+ } from "../lib/errors.generated";
10
+ import { baseRootNode } from "../testing/fixtures";
11
+ import { run } from "./updateTaxonomyNode";
12
+
13
+ describe("updateTaxonomyNode", () => {
14
+ const ctx: CommandContext = {
15
+ actorId: "test-actor",
16
+ permissions: ["item-management:updateTaxonomyNode"],
17
+ };
18
+
19
+ it("returns error when node does not exist", async () => {
20
+ const { db, spies } = createMockDb<DB>();
21
+ spies.select.mockReturnValue(undefined);
22
+
23
+ const result = await run(db, { id: "nonexistent", name: "New Name" }, ctx);
24
+ expect(result.ok).toBe(false);
25
+ if (!result.ok) {
26
+ expect(result.error).toBeInstanceOf(NodeNotFoundError);
27
+ }
28
+ });
29
+
30
+ it("returns error when attempting to change code", async () => {
31
+ const { db, spies } = createMockDb<DB>();
32
+ spies.select.mockReturnValueOnce(baseRootNode);
33
+
34
+ const result = await run(db, { id: baseRootNode.id, code: "NEW-CODE" }, ctx);
35
+ expect(result.ok).toBe(false);
36
+ if (!result.ok) {
37
+ expect(result.error).toBeInstanceOf(CodeImmutableError);
38
+ }
39
+ });
40
+
41
+ it("returns error when name is empty", async () => {
42
+ const { db, spies } = createMockDb<DB>();
43
+ spies.select.mockReturnValueOnce(baseRootNode);
44
+
45
+ const result = await run(db, { id: baseRootNode.id, name: "" }, ctx);
46
+ expect(result.ok).toBe(false);
47
+ if (!result.ok) {
48
+ expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
49
+ }
50
+ });
51
+
52
+ it("returns error when name is not provided", async () => {
53
+ const { db, spies } = createMockDb<DB>();
54
+ spies.select.mockReturnValueOnce(baseRootNode);
55
+
56
+ const result = await run(db, { id: baseRootNode.id }, ctx);
57
+ expect(result.ok).toBe(false);
58
+ if (!result.ok) {
59
+ expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
60
+ }
61
+ });
62
+
63
+ it("updates node name", async () => {
64
+ const { db, spies } = createMockDb<DB>();
65
+ const updatedNode = { ...baseRootNode, name: "Consumer Electronics" };
66
+ spies.select.mockReturnValueOnce(baseRootNode);
67
+ spies.update.mockReturnValue(updatedNode);
68
+
69
+ const result = await run(db, { id: baseRootNode.id, name: "Consumer Electronics" }, ctx);
70
+
71
+ expect(result.ok).toBe(true);
72
+ if (result.ok) {
73
+ expect(result.value.node.name).toBe("Consumer Electronics");
74
+ }
75
+ expect(spies.update).toHaveBeenCalled();
76
+ });
77
+
78
+ it("passes custom fields through to set", async () => {
79
+ const { db, spies } = createMockDb<DB>();
80
+ const updatedNode = { ...baseRootNode, sortOrder: 5 };
81
+ spies.select.mockReturnValueOnce(baseRootNode);
82
+ spies.update.mockReturnValue(updatedNode);
83
+
84
+ await run(db, { id: baseRootNode.id, name: "Electronics", sortOrder: 5 }, ctx);
85
+
86
+ expect(spies.set).toHaveBeenNthCalledWith(1, expect.objectContaining({ sortOrder: 5 }));
87
+ });
88
+ });
@@ -0,0 +1,62 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ CodeImmutableError,
5
+ MissingRequiredFieldsError,
6
+ NodeNotFoundError,
7
+ } from "../lib/errors.generated";
8
+ import { getTaxonomyNode } from "../query/getTaxonomyNode.generated";
9
+
10
+ export interface UpdateTaxonomyNodeInput {
11
+ id: string;
12
+ code?: string;
13
+ name?: string;
14
+ }
15
+
16
+ /**
17
+ * Function: updateTaxonomyNode
18
+ *
19
+ * Updates the display name of an existing taxonomy node.
20
+ * Node code is immutable.
21
+ */
22
+ export async function run<CF extends Record<string, unknown>>(
23
+ db: DB,
24
+ input: UpdateTaxonomyNodeInput & Partial<CF>,
25
+ ctx: CommandContext,
26
+ ) {
27
+ const { id, code, name, ...customFields } = input;
28
+
29
+ // 1. Check node exists
30
+ const { node } = await getTaxonomyNode(db, { id }, ctx);
31
+
32
+ if (!node) {
33
+ return err(new NodeNotFoundError(id));
34
+ }
35
+
36
+ // 2. Code is immutable
37
+ if (code !== undefined) {
38
+ return err(new CodeImmutableError(id));
39
+ }
40
+
41
+ // 3. Name must be provided and non-empty (unless custom fields provided)
42
+ const hasCustomFields = Object.keys(customFields).length > 0;
43
+ if (!name && !hasCustomFields) {
44
+ return err(new MissingRequiredFieldsError("name"));
45
+ }
46
+
47
+ // 4. Update fields
48
+ const updates: Record<string, unknown> = {
49
+ ...(customFields as Record<string, unknown>),
50
+ updatedAt: new Date(),
51
+ };
52
+ if (name) updates.name = name;
53
+
54
+ const updatedNode = await db
55
+ .updateTable("TaxonomyNode")
56
+ .set(updates)
57
+ .where("id", "=", id)
58
+ .returningAll()
59
+ .executeTakeFirstOrThrow();
60
+
61
+ return ok({ node: updatedNode });
62
+ }