@tailor-platform/erp-kit 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +81 -12
  3. package/dist/cli.js +1070 -450
  4. package/package.json +11 -8
  5. package/schemas/module/model.yml +5 -0
  6. package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +2 -2
  7. package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +3 -3
  8. package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -2
  9. package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/SKILL.md +3 -3
  10. package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/SKILL.md +4 -4
  11. package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/SKILL.md +3 -3
  12. package/skills/{mock-scenario → erp-kit-mock-scenario}/SKILL.md +1 -1
  13. package/skills/{1-module-docs → erp-kit-module-1-docs}/SKILL.md +2 -2
  14. package/skills/{2-module-feature-breakdown → erp-kit-module-2-feature-breakdown}/SKILL.md +13 -9
  15. package/skills/erp-kit-module-2-feature-breakdown/references/naming.md +59 -0
  16. package/skills/{3-module-doc-review → erp-kit-module-3-doc-review}/SKILL.md +83 -25
  17. package/skills/erp-kit-module-4-tdd/SKILL.md +94 -0
  18. package/skills/erp-kit-module-4-tdd/references/cross-module-dependency.md +133 -0
  19. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/db-relations.md +5 -1
  20. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/exports.md +1 -1
  21. package/skills/erp-kit-module-4-tdd/references/generated-code.md +32 -0
  22. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/SKILL.md +46 -44
  23. package/skills/erp-kit-module-5-impl-review/references/commands.md +62 -0
  24. package/skills/erp-kit-module-5-impl-review/references/errors.md +10 -0
  25. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/references/testing.md +1 -1
  26. package/skills/erp-kit-module-shared/SKILL.md +16 -0
  27. package/skills/erp-kit-module-shared/references/commands.md +203 -0
  28. package/skills/erp-kit-module-shared/references/errors.md +35 -0
  29. package/skills/erp-kit-module-shared/references/queries.md +168 -0
  30. package/skills/erp-kit-module-shared/references/structure.md +36 -0
  31. package/skills/{3-module-doc-review → erp-kit-module-shared}/references/testing.md +4 -3
  32. package/skills/erp-kit-update/SKILL.md +64 -0
  33. package/src/cli.doc.test.ts +65 -0
  34. package/src/cli.ts +3 -35
  35. package/src/commands/app/index.ts +3 -3
  36. package/src/commands/check.test.ts +1 -1
  37. package/src/commands/check.ts +2 -2
  38. package/src/commands/index.ts +73 -0
  39. package/src/commands/init.test.ts +22 -5
  40. package/src/commands/init.ts +25 -16
  41. package/src/commands/license.ts +193 -0
  42. package/src/commands/mock/index.ts +2 -2
  43. package/src/commands/mock/start.ts +1 -1
  44. package/src/commands/mock/validate.test.ts +1 -1
  45. package/src/commands/module/generate.ts +35 -0
  46. package/src/commands/module/index.ts +6 -4
  47. package/src/commands/module/list.test.ts +7 -12
  48. package/src/commands/module/list.ts +1 -1
  49. package/src/commands/scaffold-templates.ts +65 -0
  50. package/src/commands/scaffold.test.ts +92 -2
  51. package/src/commands/scaffold.ts +22 -2
  52. package/src/commands/sync-check.test.ts +60 -1
  53. package/src/commands/sync-check.ts +35 -2
  54. package/src/generator/generate-code.test.ts +200 -0
  55. package/src/generator/generate-code.ts +260 -0
  56. package/src/generator/parse-command-doc.test.ts +159 -0
  57. package/src/generator/parse-command-doc.ts +116 -0
  58. package/src/integration.test.ts +2 -2
  59. package/src/module.ts +6 -6
  60. package/src/modules/item-management/README.md +38 -0
  61. package/src/modules/item-management/command/activateItem.generated.ts +6 -0
  62. package/src/modules/item-management/command/activateItem.test.ts +76 -0
  63. package/src/modules/item-management/command/activateItem.ts +42 -0
  64. package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +6 -0
  65. package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +88 -0
  66. package/src/modules/item-management/command/assignItemToTaxonomy.ts +63 -0
  67. package/src/modules/item-management/command/createItem.generated.ts +6 -0
  68. package/src/modules/item-management/command/createItem.test.ts +152 -0
  69. package/src/modules/item-management/command/createItem.ts +72 -0
  70. package/src/modules/item-management/command/createTaxonomyNode.generated.ts +6 -0
  71. package/src/modules/item-management/command/createTaxonomyNode.test.ts +126 -0
  72. package/src/modules/item-management/command/createTaxonomyNode.ts +70 -0
  73. package/src/modules/item-management/command/deactivateItem.generated.ts +6 -0
  74. package/src/modules/item-management/command/deactivateItem.test.ts +76 -0
  75. package/src/modules/item-management/command/deactivateItem.ts +42 -0
  76. package/src/modules/item-management/command/deleteItem.generated.ts +6 -0
  77. package/src/modules/item-management/command/deleteItem.test.ts +61 -0
  78. package/src/modules/item-management/command/deleteItem.ts +38 -0
  79. package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +6 -0
  80. package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +73 -0
  81. package/src/modules/item-management/command/deleteTaxonomyNode.ts +50 -0
  82. package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +6 -0
  83. package/src/modules/item-management/command/moveTaxonomyNode.test.ts +136 -0
  84. package/src/modules/item-management/command/moveTaxonomyNode.ts +85 -0
  85. package/src/modules/item-management/command/reactivateItem.generated.ts +6 -0
  86. package/src/modules/item-management/command/reactivateItem.test.ts +76 -0
  87. package/src/modules/item-management/command/reactivateItem.ts +42 -0
  88. package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +6 -0
  89. package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +43 -0
  90. package/src/modules/item-management/command/removeItemFromTaxonomy.ts +30 -0
  91. package/src/modules/item-management/command/updateItem.generated.ts +6 -0
  92. package/src/modules/item-management/command/updateItem.test.ts +178 -0
  93. package/src/modules/item-management/command/updateItem.ts +103 -0
  94. package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +6 -0
  95. package/src/modules/item-management/command/updateTaxonomyNode.test.ts +88 -0
  96. package/src/modules/item-management/command/updateTaxonomyNode.ts +62 -0
  97. package/src/modules/item-management/db/item.ts +47 -0
  98. package/src/modules/item-management/db/itemTaxonomyAssignment.ts +49 -0
  99. package/src/modules/item-management/db/taxonomyNode.ts +34 -0
  100. package/src/modules/item-management/docs/commands/ActivateItem.md +32 -0
  101. package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +38 -0
  102. package/src/modules/item-management/docs/commands/CreateItem.md +44 -0
  103. package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +44 -0
  104. package/src/modules/item-management/docs/commands/DeactivateItem.md +34 -0
  105. package/src/modules/item-management/docs/commands/DeleteItem.md +35 -0
  106. package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +39 -0
  107. package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +45 -0
  108. package/src/modules/item-management/docs/commands/ReactivateItem.md +34 -0
  109. package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +30 -0
  110. package/src/modules/item-management/docs/commands/UpdateItem.md +55 -0
  111. package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +36 -0
  112. package/src/modules/item-management/docs/features/item-lifecycle.md +60 -0
  113. package/src/modules/item-management/docs/features/item-taxonomy.md +65 -0
  114. package/src/modules/item-management/docs/models/ItemTaxonomyAssignment.md +36 -0
  115. package/src/modules/item-management/docs/models/TaxonomyNode.md +47 -0
  116. package/src/modules/item-management/docs/models/item.md +59 -0
  117. package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +36 -0
  118. package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +40 -0
  119. package/src/modules/item-management/docs/queries/DetectCircularReference.md +41 -0
  120. package/src/modules/item-management/docs/queries/GetItem.md +38 -0
  121. package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +29 -0
  122. package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +35 -0
  123. package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +29 -0
  124. package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +29 -0
  125. package/src/modules/item-management/generated/enums.ts +9 -0
  126. package/src/modules/item-management/generated/kysely-tailordb.ts +62 -0
  127. package/src/modules/item-management/index.ts +53 -0
  128. package/src/modules/item-management/lib/_db_deps.ts +13 -0
  129. package/src/modules/item-management/lib/errors.generated.ts +117 -0
  130. package/src/modules/item-management/lib/permissions.generated.ts +17 -0
  131. package/src/modules/item-management/lib/types.ts +19 -0
  132. package/src/modules/item-management/module.ts +97 -0
  133. package/src/modules/item-management/query/calculateNodeDepth.generated.ts +5 -0
  134. package/src/modules/item-management/query/calculateNodeDepth.test.ts +56 -0
  135. package/src/modules/item-management/query/calculateNodeDepth.ts +28 -0
  136. package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +5 -0
  137. package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +75 -0
  138. package/src/modules/item-management/query/calculateSubtreeDepth.ts +29 -0
  139. package/src/modules/item-management/query/detectCircularReference.generated.ts +5 -0
  140. package/src/modules/item-management/query/detectCircularReference.test.ts +61 -0
  141. package/src/modules/item-management/query/detectCircularReference.ts +32 -0
  142. package/src/modules/item-management/query/getItem.generated.ts +5 -0
  143. package/src/modules/item-management/query/getItem.test.ts +67 -0
  144. package/src/modules/item-management/query/getItem.ts +20 -0
  145. package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +5 -0
  146. package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +25 -0
  147. package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +18 -0
  148. package/src/modules/item-management/query/getTaxonomyNode.generated.ts +5 -0
  149. package/src/modules/item-management/query/getTaxonomyNode.test.ts +47 -0
  150. package/src/modules/item-management/query/getTaxonomyNode.ts +18 -0
  151. package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +5 -0
  152. package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +25 -0
  153. package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +16 -0
  154. package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +5 -0
  155. package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +34 -0
  156. package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +16 -0
  157. package/src/modules/item-management/tailor.config.ts +11 -0
  158. package/src/modules/item-management/testing/fixtures.ts +81 -0
  159. package/src/modules/primitives/command/activateCategory.generated.ts +6 -0
  160. package/src/modules/primitives/command/activateCategory.test.ts +11 -29
  161. package/src/modules/primitives/command/activateCategory.ts +27 -34
  162. package/src/modules/primitives/command/activateCurrency.generated.ts +6 -0
  163. package/src/modules/primitives/command/activateCurrency.test.ts +11 -29
  164. package/src/modules/primitives/command/activateCurrency.ts +27 -34
  165. package/src/modules/primitives/command/activateUnit.generated.ts +6 -0
  166. package/src/modules/primitives/command/activateUnit.test.ts +11 -15
  167. package/src/modules/primitives/command/activateUnit.ts +27 -34
  168. package/src/modules/primitives/command/createCategory.generated.ts +6 -0
  169. package/src/modules/primitives/command/createCategory.test.ts +27 -39
  170. package/src/modules/primitives/command/createCategory.ts +53 -62
  171. package/src/modules/primitives/command/createCurrency.generated.ts +6 -0
  172. package/src/modules/primitives/command/createCurrency.test.ts +78 -71
  173. package/src/modules/primitives/command/createCurrency.ts +43 -48
  174. package/src/modules/primitives/command/createExchangeRate.generated.ts +6 -0
  175. package/src/modules/primitives/command/createExchangeRate.test.ts +101 -100
  176. package/src/modules/primitives/command/createExchangeRate.ts +50 -59
  177. package/src/modules/primitives/command/createUnit.generated.ts +6 -0
  178. package/src/modules/primitives/command/createUnit.test.ts +92 -95
  179. package/src/modules/primitives/command/createUnit.ts +54 -57
  180. package/src/modules/primitives/command/deactivateCategory.generated.ts +6 -0
  181. package/src/modules/primitives/command/deactivateCategory.test.ts +27 -28
  182. package/src/modules/primitives/command/deactivateCategory.ts +43 -50
  183. package/src/modules/primitives/command/deactivateCurrency.generated.ts +6 -0
  184. package/src/modules/primitives/command/deactivateCurrency.test.ts +23 -38
  185. package/src/modules/primitives/command/deactivateCurrency.ts +31 -38
  186. package/src/modules/primitives/command/deactivateUnit.generated.ts +6 -0
  187. package/src/modules/primitives/command/deactivateUnit.test.ts +27 -23
  188. package/src/modules/primitives/command/deactivateUnit.ts +39 -49
  189. package/src/modules/primitives/command/setBaseCurrency.generated.ts +6 -0
  190. package/src/modules/primitives/command/setBaseCurrency.test.ts +40 -33
  191. package/src/modules/primitives/command/setBaseCurrency.ts +43 -50
  192. package/src/modules/primitives/command/setReferenceUnit.generated.ts +6 -0
  193. package/src/modules/primitives/command/setReferenceUnit.test.ts +39 -35
  194. package/src/modules/primitives/command/setReferenceUnit.ts +46 -59
  195. package/src/modules/primitives/db/unit.ts +13 -3
  196. package/src/modules/primitives/docs/commands/ActivateCategory.md +1 -2
  197. package/src/modules/primitives/docs/commands/ActivateCurrency.md +1 -2
  198. package/src/modules/primitives/docs/commands/ActivateUnit.md +1 -2
  199. package/src/modules/primitives/docs/commands/CreateCategory.md +1 -4
  200. package/src/modules/primitives/docs/commands/CreateCurrency.md +3 -4
  201. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +4 -5
  202. package/src/modules/primitives/docs/commands/CreateUnit.md +5 -5
  203. package/src/modules/primitives/docs/commands/DeactivateCategory.md +2 -3
  204. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +2 -3
  205. package/src/modules/primitives/docs/commands/DeactivateUnit.md +2 -3
  206. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +2 -3
  207. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +2 -3
  208. package/src/modules/primitives/docs/queries/ConvertAmount.md +3 -5
  209. package/src/modules/primitives/docs/queries/ConvertQuantity.md +3 -5
  210. package/src/modules/primitives/docs/queries/GetBaseCurrency.md +32 -0
  211. package/src/modules/primitives/docs/queries/GetCurrency.md +36 -0
  212. package/src/modules/primitives/docs/queries/GetUnit.md +36 -0
  213. package/src/modules/primitives/docs/queries/GetUoMCategory.md +36 -0
  214. package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +26 -0
  215. package/src/modules/primitives/generated/kysely-tailordb.ts +24 -45
  216. package/src/modules/primitives/index.ts +15 -4
  217. package/src/modules/primitives/lib/errors.generated.ts +112 -0
  218. package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -8
  219. package/src/modules/primitives/module.ts +37 -27
  220. package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
  221. package/src/modules/primitives/query/convertAmount.test.ts +2 -2
  222. package/src/modules/primitives/query/convertAmount.ts +27 -28
  223. package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
  224. package/src/modules/primitives/query/convertQuantity.test.ts +6 -2
  225. package/src/modules/primitives/query/convertQuantity.ts +49 -57
  226. package/src/modules/primitives/query/getBaseCurrency.generated.ts +5 -0
  227. package/src/modules/primitives/query/getBaseCurrency.test.ts +28 -0
  228. package/src/modules/primitives/query/getBaseCurrency.ts +16 -0
  229. package/src/modules/primitives/query/getCurrency.generated.ts +5 -0
  230. package/src/modules/primitives/query/getCurrency.test.ts +47 -0
  231. package/src/modules/primitives/query/getCurrency.ts +18 -0
  232. package/src/modules/primitives/query/getUnit.generated.ts +5 -0
  233. package/src/modules/primitives/query/getUnit.test.ts +47 -0
  234. package/src/modules/primitives/query/getUnit.ts +18 -0
  235. package/src/modules/primitives/query/getUoMCategory.generated.ts +5 -0
  236. package/src/modules/primitives/query/getUoMCategory.test.ts +47 -0
  237. package/src/modules/primitives/query/getUoMCategory.ts +18 -0
  238. package/src/modules/primitives/query/listUnitsByCategory.generated.ts +5 -0
  239. package/src/modules/primitives/query/listUnitsByCategory.ts +16 -0
  240. package/src/modules/primitives/tailor.config.ts +3 -3
  241. package/src/modules/shared/defineCommand.test.ts +23 -10
  242. package/src/modules/shared/defineCommand.ts +23 -10
  243. package/src/modules/shared/internal.ts +1 -0
  244. package/src/modules/shared/requirePermission.test.ts +22 -21
  245. package/src/modules/shared/requirePermission.ts +8 -2
  246. package/src/modules/shared/result.ts +12 -0
  247. package/src/modules/testing/index.ts +36 -11
  248. package/src/modules/user-management/command/activateUser.generated.ts +6 -0
  249. package/src/modules/user-management/command/activateUser.test.ts +27 -27
  250. package/src/modules/user-management/command/activateUser.ts +40 -48
  251. package/src/modules/user-management/command/assignPermissionToRole.generated.ts +6 -0
  252. package/src/modules/user-management/command/assignPermissionToRole.test.ts +42 -43
  253. package/src/modules/user-management/command/assignPermissionToRole.ts +59 -62
  254. package/src/modules/user-management/command/assignRoleToUser.generated.ts +6 -0
  255. package/src/modules/user-management/command/assignRoleToUser.test.ts +70 -63
  256. package/src/modules/user-management/command/assignRoleToUser.ts +63 -66
  257. package/src/modules/user-management/command/createPermission.generated.ts +6 -0
  258. package/src/modules/user-management/command/createPermission.test.ts +45 -38
  259. package/src/modules/user-management/command/createPermission.ts +42 -46
  260. package/src/modules/user-management/command/createRole.generated.ts +6 -0
  261. package/src/modules/user-management/command/createRole.test.ts +30 -29
  262. package/src/modules/user-management/command/createRole.ts +33 -33
  263. package/src/modules/user-management/command/createUser.generated.ts +6 -0
  264. package/src/modules/user-management/command/createUser.test.ts +64 -42
  265. package/src/modules/user-management/command/createUser.ts +54 -56
  266. package/src/modules/user-management/command/deactivateUser.generated.ts +6 -0
  267. package/src/modules/user-management/command/deactivateUser.test.ts +27 -27
  268. package/src/modules/user-management/command/deactivateUser.ts +40 -48
  269. package/src/modules/user-management/command/logAuditEvent.generated.ts +6 -0
  270. package/src/modules/user-management/command/logAuditEvent.test.ts +50 -42
  271. package/src/modules/user-management/command/logAuditEvent.ts +25 -28
  272. package/src/modules/user-management/command/reactivateUser.generated.ts +6 -0
  273. package/src/modules/user-management/command/reactivateUser.test.ts +31 -27
  274. package/src/modules/user-management/command/reactivateUser.ts +40 -48
  275. package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +6 -0
  276. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +52 -51
  277. package/src/modules/user-management/command/revokePermissionFromRole.ts +60 -57
  278. package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +6 -0
  279. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +53 -48
  280. package/src/modules/user-management/command/revokeRoleFromUser.ts +58 -57
  281. package/src/modules/user-management/docs/commands/CreatePermission.md +2 -2
  282. package/src/modules/user-management/docs/commands/CreateRole.md +1 -1
  283. package/src/modules/user-management/generated/enums.ts +11 -11
  284. package/src/modules/user-management/generated/kysely-tailordb.ts +27 -56
  285. package/src/modules/user-management/index.ts +2 -2
  286. package/src/modules/user-management/lib/errors.generated.ts +67 -0
  287. package/src/modules/user-management/{permissions.ts → lib/permissions.generated.ts} +8 -7
  288. package/src/modules/user-management/module.ts +22 -22
  289. package/src/modules/user-management/tailor.config.ts +3 -3
  290. package/src/schemas.ts +1 -1
  291. package/skills/1-module-docs/references/structure.md +0 -22
  292. package/skills/2-module-feature-breakdown/references/commands.md +0 -48
  293. package/skills/2-module-feature-breakdown/references/structure.md +0 -22
  294. package/skills/3-module-doc-review/references/commands.md +0 -54
  295. package/skills/3-module-doc-review/references/models.md +0 -29
  296. package/skills/4-module-tdd-implementation/SKILL.md +0 -74
  297. package/skills/4-module-tdd-implementation/references/commands.md +0 -45
  298. package/skills/4-module-tdd-implementation/references/errors.md +0 -7
  299. package/skills/4-module-tdd-implementation/references/models.md +0 -30
  300. package/skills/4-module-tdd-implementation/references/structure.md +0 -22
  301. package/skills/4-module-tdd-implementation/references/testing.md +0 -37
  302. package/skills/5-module-implementation-review/references/commands.md +0 -45
  303. package/skills/5-module-implementation-review/references/errors.md +0 -7
  304. package/skills/5-module-implementation-review/references/exports.md +0 -8
  305. package/skills/5-module-implementation-review/references/models.md +0 -30
  306. package/src/modules/primitives/lib/errors.ts +0 -138
  307. package/src/modules/user-management/lib/errors.ts +0 -81
  308. /package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/references/structure.md +0 -0
  309. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-detailview.md +0 -0
  310. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-form.md +0 -0
  311. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-listview.md +0 -0
  312. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/structure.md +0 -0
  313. /package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/references/structure.md +0 -0
  314. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/component.md +0 -0
  315. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-detailview.md +0 -0
  316. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-form.md +0 -0
  317. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-listview.md +0 -0
  318. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/structure.md +0 -0
  319. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/component.md +0 -0
  320. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-detailview.md +0 -0
  321. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-form.md +0 -0
  322. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-listview.md +0 -0
  323. /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/auth.md +0 -0
  324. /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/structure.md +0 -0
  325. /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
@@ -0,0 +1,70 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ DuplicateNodeCodeError,
5
+ MaxDepthExceededError,
6
+ ParentNodeNotFoundError,
7
+ } from "../lib/errors.generated";
8
+ import { getTaxonomyNode } from "../query/getTaxonomyNode.generated";
9
+ import { calculateNodeDepth } from "../query/calculateNodeDepth.generated";
10
+
11
+ export interface CreateTaxonomyNodeInput {
12
+ code: string;
13
+ name: string;
14
+ parentId?: string;
15
+ }
16
+
17
+ /**
18
+ * Function: createTaxonomyNode
19
+ *
20
+ * Creates a new root or child taxonomy node with a unique code.
21
+ */
22
+ export async function run<CF extends Record<string, unknown>>(
23
+ db: DB,
24
+ input: CreateTaxonomyNodeInput & CF,
25
+ ctx: CommandContext,
26
+ deps?: { maxDepth: number },
27
+ ) {
28
+ const { maxDepth } = deps!;
29
+ const { code, name, parentId, ...customFields } = input;
30
+
31
+ // 1. Check code uniqueness
32
+ const { node: existingCode } = await getTaxonomyNode(db, { code }, ctx);
33
+
34
+ if (existingCode) {
35
+ return err(new DuplicateNodeCodeError(code));
36
+ }
37
+
38
+ // 2. If parent specified, check it exists and validate depth
39
+ if (parentId) {
40
+ const { node: parent } = await getTaxonomyNode(db, { id: parentId }, ctx);
41
+
42
+ if (!parent) {
43
+ return err(new ParentNodeNotFoundError(parentId));
44
+ }
45
+
46
+ // Calculate depth: parent depth + 1 for new node
47
+ const { depth: parentDepth } = await calculateNodeDepth(db, { nodeId: parentId }, ctx);
48
+ const depth = parentDepth + 1;
49
+
50
+ if (depth > maxDepth) {
51
+ return err(new MaxDepthExceededError(`${depth} exceeds ${maxDepth}`));
52
+ }
53
+ }
54
+
55
+ // 3. Create node
56
+ const node = await db
57
+ .insertInto("TaxonomyNode")
58
+ .values({
59
+ ...(customFields as Record<string, unknown>),
60
+ code,
61
+ name,
62
+ parentId: parentId ?? null,
63
+ createdAt: new Date(),
64
+ updatedAt: null,
65
+ })
66
+ .returningAll()
67
+ .executeTakeFirstOrThrow();
68
+
69
+ return ok({ node: node });
70
+ }
@@ -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 "./deactivateItem";
5
+
6
+ export const deactivateItem = defineCommand(permissions.deactivateItem, run);
@@ -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 "./deactivateItem";
8
+
9
+ describe("deactivateItem", () => {
10
+ const ctx: CommandContext = {
11
+ actorId: "test-actor",
12
+ permissions: ["item-management:deactivateItem"],
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 ACTIVE", async () => {
27
+ const { db, spies } = createMockDb<DB>();
28
+ spies.select.mockReturnValueOnce(baseDraftItem);
29
+
30
+ const result = await run(db, { id: baseDraftItem.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 already INACTIVE", async () => {
38
+ const { db, spies } = createMockDb<DB>();
39
+ spies.select.mockReturnValueOnce(baseInactiveItem);
40
+
41
+ const result = await run(db, { id: baseInactiveItem.id }, ctx);
42
+ expect(result.ok).toBe(false);
43
+ if (!result.ok) {
44
+ expect(result.error).toBeInstanceOf(InvalidStateTransitionError);
45
+ }
46
+ });
47
+
48
+ it("deactivates an ACTIVE item", async () => {
49
+ const { db, spies } = createMockDb<DB>();
50
+ const deactivatedItem = { ...baseActiveItem, status: "INACTIVE" };
51
+ spies.select.mockReturnValueOnce(baseActiveItem);
52
+ spies.update.mockReturnValue(deactivatedItem);
53
+
54
+ const result = await run(db, { id: baseActiveItem.id }, ctx);
55
+
56
+ expect(result.ok).toBe(true);
57
+ if (result.ok) {
58
+ expect(result.value.item.status).toBe("INACTIVE");
59
+ }
60
+ expect(spies.update).toHaveBeenCalled();
61
+ });
62
+
63
+ it("deactivates from custom status when from is overridden", async () => {
64
+ const { db, spies } = createMockDb<DB>();
65
+ const deactivatedItem = { ...baseDraftItem, status: "INACTIVE" };
66
+ spies.select.mockReturnValueOnce(baseDraftItem);
67
+ spies.update.mockReturnValue(deactivatedItem);
68
+
69
+ const result = await run(db, { id: baseDraftItem.id, from: ["ACTIVE", "DRAFT"] }, ctx);
70
+
71
+ expect(result.ok).toBe(true);
72
+ if (result.ok) {
73
+ expect(result.value.item.status).toBe("INACTIVE");
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 DeactivateItemInput {
7
+ id: string;
8
+ from?: string[];
9
+ }
10
+
11
+ /**
12
+ * Function: deactivateItem
13
+ *
14
+ * Transitions an item from ACTIVE to INACTIVE status,
15
+ * preserving it for audit trails while preventing new transactions.
16
+ */
17
+ export async function run(db: DB, input: DeactivateItemInput, 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 deactivation
28
+ const validFromStatuses = from ?? ["ACTIVE"];
29
+ if (!validFromStatuses.includes(item.status)) {
30
+ return err(new InvalidStateTransitionError(`${item.status} to INACTIVE`));
31
+ }
32
+
33
+ // 3. Update status to INACTIVE
34
+ const deactivatedItem = await db
35
+ .updateTable("Item")
36
+ .set({ status: "INACTIVE", updatedAt: new Date() })
37
+ .where("id", "=", id)
38
+ .returningAll()
39
+ .executeTakeFirstOrThrow();
40
+
41
+ return ok({ item: deactivatedItem });
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 "./deleteItem";
5
+
6
+ export const deleteItem = defineCommand(permissions.deleteItem, run);
@@ -0,0 +1,61 @@
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 { DeleteNonDraftError, ItemNotFoundError } from "../lib/errors.generated";
6
+ import { baseActiveItem, baseDraftItem, baseInactiveItem } from "../testing/fixtures";
7
+ import { run } from "./deleteItem";
8
+
9
+ describe("deleteItem", () => {
10
+ const ctx: CommandContext = {
11
+ actorId: "test-actor",
12
+ permissions: ["item-management:deleteItem"],
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 ACTIVE", 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(DeleteNonDraftError);
34
+ }
35
+ });
36
+
37
+ it("returns error when item is INACTIVE", async () => {
38
+ const { db, spies } = createMockDb<DB>();
39
+ spies.select.mockReturnValueOnce(baseInactiveItem);
40
+
41
+ const result = await run(db, { id: baseInactiveItem.id }, ctx);
42
+ expect(result.ok).toBe(false);
43
+ if (!result.ok) {
44
+ expect(result.error).toBeInstanceOf(DeleteNonDraftError);
45
+ }
46
+ });
47
+
48
+ it("deletes a DRAFT item and removes taxonomy assignments", async () => {
49
+ const { db, spies } = createMockDb<DB>();
50
+ spies.select.mockReturnValueOnce(baseDraftItem);
51
+ spies.delete.mockReturnValue(undefined); // Assignments delete + item delete
52
+
53
+ const result = await run(db, { id: baseDraftItem.id }, ctx);
54
+
55
+ expect(result.ok).toBe(true);
56
+ if (result.ok) {
57
+ expect(result.value.success).toBe(true);
58
+ }
59
+ expect(spies.delete).toHaveBeenCalled();
60
+ });
61
+ });
@@ -0,0 +1,38 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import { DeleteNonDraftError, ItemNotFoundError } from "../lib/errors.generated";
4
+ import { getItem } from "../query/getItem.generated";
5
+
6
+ export interface DeleteItemInput {
7
+ id: string;
8
+ }
9
+
10
+ /**
11
+ * Function: deleteItem
12
+ *
13
+ * Permanently removes a DRAFT item from the system.
14
+ * Only DRAFT items can be deleted; ACTIVE and INACTIVE are preserved for audit trails.
15
+ */
16
+ export async function run(db: DB, input: DeleteItemInput, ctx: CommandContext) {
17
+ const { id } = input;
18
+
19
+ // 1. Check item exists
20
+ const { item } = await getItem(db, { id }, ctx);
21
+
22
+ if (!item) {
23
+ return err(new ItemNotFoundError(id));
24
+ }
25
+
26
+ // 2. Check status is DRAFT
27
+ if (item.status !== "DRAFT") {
28
+ return err(new DeleteNonDraftError(`${id} (status: ${item.status})`));
29
+ }
30
+
31
+ // 3. Remove taxonomy assignments
32
+ await db.deleteFrom("ItemTaxonomyAssignment").where("itemId", "=", id).execute();
33
+
34
+ // 4. Delete item
35
+ await db.deleteFrom("Item").where("id", "=", id).execute();
36
+
37
+ return ok({ success: true as const });
38
+ }
@@ -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 "./deleteTaxonomyNode";
5
+
6
+ export const deleteTaxonomyNode = defineCommand(permissions.deleteTaxonomyNode, run);
@@ -0,0 +1,73 @@
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
+ NodeHasAssignmentsError,
7
+ NodeHasChildrenError,
8
+ NodeNotFoundError,
9
+ } from "../lib/errors.generated";
10
+ import { baseAssignment, baseChildNode, baseLeafNode, baseRootNode } from "../testing/fixtures";
11
+ import { run } from "./deleteTaxonomyNode";
12
+
13
+ describe("deleteTaxonomyNode", () => {
14
+ const ctx: CommandContext = {
15
+ actorId: "test-actor",
16
+ permissions: ["item-management:deleteTaxonomyNode"],
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" }, 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 node has children", async () => {
31
+ const { db, spies } = createMockDb<DB>();
32
+ spies.select
33
+ .mockReturnValueOnce(baseRootNode) // getTaxonomyNode
34
+ .mockReturnValueOnce([baseChildNode]); // getTaxonomyNodeChildren (execute returns array)
35
+
36
+ const result = await run(db, { id: baseRootNode.id }, ctx);
37
+ expect(result.ok).toBe(false);
38
+ if (!result.ok) {
39
+ expect(result.error).toBeInstanceOf(NodeHasChildrenError);
40
+ }
41
+ });
42
+
43
+ it("returns error when node has item assignments", async () => {
44
+ const { db, spies } = createMockDb<DB>();
45
+ spies.select
46
+ .mockReturnValueOnce(baseLeafNode) // getTaxonomyNode
47
+ .mockReturnValueOnce([]) // getTaxonomyNodeChildren (no children)
48
+ .mockReturnValueOnce([baseAssignment]); // getTaxonomyNodeAssignments (has assignment)
49
+
50
+ const result = await run(db, { id: baseLeafNode.id }, ctx);
51
+ expect(result.ok).toBe(false);
52
+ if (!result.ok) {
53
+ expect(result.error).toBeInstanceOf(NodeHasAssignmentsError);
54
+ }
55
+ });
56
+
57
+ it("deletes a leaf node with no assignments", async () => {
58
+ const { db, spies } = createMockDb<DB>();
59
+ spies.select
60
+ .mockReturnValueOnce(baseLeafNode) // getTaxonomyNode
61
+ .mockReturnValueOnce([]) // getTaxonomyNodeChildren (no children)
62
+ .mockReturnValueOnce([]); // getTaxonomyNodeAssignments (no assignments)
63
+ spies.delete.mockReturnValue(undefined);
64
+
65
+ const result = await run(db, { id: baseLeafNode.id }, ctx);
66
+
67
+ expect(result.ok).toBe(true);
68
+ if (result.ok) {
69
+ expect(result.value.success).toBe(true);
70
+ }
71
+ expect(spies.delete).toHaveBeenCalled();
72
+ });
73
+ });
@@ -0,0 +1,50 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ NodeHasAssignmentsError,
5
+ NodeHasChildrenError,
6
+ NodeNotFoundError,
7
+ } from "../lib/errors.generated";
8
+ import { getTaxonomyNode } from "../query/getTaxonomyNode.generated";
9
+ import { getTaxonomyNodeChildren } from "../query/getTaxonomyNodeChildren.generated";
10
+ import { getTaxonomyNodeAssignments } from "../query/getTaxonomyNodeAssignments.generated";
11
+
12
+ export interface DeleteTaxonomyNodeInput {
13
+ id: string;
14
+ }
15
+
16
+ /**
17
+ * Function: deleteTaxonomyNode
18
+ *
19
+ * Permanently removes a leaf taxonomy node with no item assignments.
20
+ * The node's code becomes available for reuse.
21
+ */
22
+ export async function run(db: DB, input: DeleteTaxonomyNodeInput, ctx: CommandContext) {
23
+ const { id } = input;
24
+
25
+ // 1. Check node exists
26
+ const { node } = await getTaxonomyNode(db, { id }, ctx);
27
+
28
+ if (!node) {
29
+ return err(new NodeNotFoundError(id));
30
+ }
31
+
32
+ // 2. Check no children
33
+ const { children } = await getTaxonomyNodeChildren(db, { parentId: id }, ctx);
34
+
35
+ if (children.length > 0) {
36
+ return err(new NodeHasChildrenError(id));
37
+ }
38
+
39
+ // 3. Check no item assignments
40
+ const { assignments } = await getTaxonomyNodeAssignments(db, { taxonomyNodeId: id }, ctx);
41
+
42
+ if (assignments.length > 0) {
43
+ return err(new NodeHasAssignmentsError(id));
44
+ }
45
+
46
+ // 4. Delete node
47
+ await db.deleteFrom("TaxonomyNode").where("id", "=", id).execute();
48
+
49
+ return ok({ success: true as const });
50
+ }
@@ -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 "./moveTaxonomyNode";
5
+
6
+ export const moveTaxonomyNode = defineCommand(permissions.moveTaxonomyNode, run);
@@ -0,0 +1,136 @@
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
+ CircularReferenceError,
7
+ MaxDepthExceededError,
8
+ NodeNotFoundError,
9
+ ParentNodeNotFoundError,
10
+ } from "../lib/errors.generated";
11
+ import { baseChildNode, baseLeafNode, baseRootNode, secondRootNode } from "../testing/fixtures";
12
+ import { run } from "./moveTaxonomyNode";
13
+
14
+ describe("moveTaxonomyNode", () => {
15
+ const ctx: CommandContext = {
16
+ actorId: "test-actor",
17
+ permissions: ["item-management:moveTaxonomyNode"],
18
+ };
19
+
20
+ it("returns error when node does not exist", async () => {
21
+ const { db, spies } = createMockDb<DB>();
22
+ spies.select.mockReturnValue(undefined);
23
+
24
+ const result = await run(db, { id: "nonexistent", newParentId: null }, ctx, { maxDepth: 10 });
25
+ expect(result.ok).toBe(false);
26
+ if (!result.ok) {
27
+ expect(result.error).toBeInstanceOf(NodeNotFoundError);
28
+ }
29
+ });
30
+
31
+ it("returns error when new parent does not exist", async () => {
32
+ const { db, spies } = createMockDb<DB>();
33
+ spies.select
34
+ .mockReturnValueOnce(baseChildNode) // getTaxonomyNode for node
35
+ .mockReturnValueOnce(undefined); // getTaxonomyNode for new parent
36
+
37
+ const result = await run(db, { id: baseChildNode.id, newParentId: "nonexistent" }, ctx, {
38
+ maxDepth: 10,
39
+ });
40
+ expect(result.ok).toBe(false);
41
+ if (!result.ok) {
42
+ expect(result.error).toBeInstanceOf(ParentNodeNotFoundError);
43
+ }
44
+ });
45
+
46
+ it("returns error when moving node under itself", async () => {
47
+ const { db, spies } = createMockDb<DB>();
48
+ spies.select
49
+ .mockReturnValueOnce(baseRootNode) // getTaxonomyNode for node
50
+ .mockReturnValueOnce(baseRootNode); // getTaxonomyNode for new parent
51
+
52
+ const result = await run(db, { id: baseRootNode.id, newParentId: baseRootNode.id }, ctx, {
53
+ maxDepth: 10,
54
+ });
55
+ expect(result.ok).toBe(false);
56
+ if (!result.ok) {
57
+ expect(result.error).toBeInstanceOf(CircularReferenceError);
58
+ }
59
+ });
60
+
61
+ it("returns error when moving node under its descendant", async () => {
62
+ const { db, spies } = createMockDb<DB>();
63
+ spies.select
64
+ .mockReturnValueOnce(baseRootNode) // getTaxonomyNode for node (node-1)
65
+ .mockReturnValueOnce(baseLeafNode) // getTaxonomyNode for new parent (node-3)
66
+ .mockReturnValueOnce(baseLeafNode) // detectCircularReference: lookup node-3
67
+ .mockReturnValueOnce(baseChildNode); // detectCircularReference: lookup node-2 → parentId=node-1=nodeId → circular!
68
+
69
+ const result = await run(db, { id: baseRootNode.id, newParentId: baseLeafNode.id }, ctx, {
70
+ maxDepth: 10,
71
+ });
72
+ expect(result.ok).toBe(false);
73
+ if (!result.ok) {
74
+ expect(result.error).toBeInstanceOf(CircularReferenceError);
75
+ }
76
+ });
77
+
78
+ it("promotes node to root by setting parent to null", async () => {
79
+ const { db, spies } = createMockDb<DB>();
80
+ const movedNode = { ...baseChildNode, parentId: null };
81
+ spies.select.mockReturnValueOnce(baseChildNode); // getTaxonomyNode for node
82
+ spies.update.mockReturnValue(movedNode);
83
+
84
+ const result = await run(db, { id: baseChildNode.id, newParentId: null }, ctx, {
85
+ maxDepth: 10,
86
+ });
87
+
88
+ expect(result.ok).toBe(true);
89
+ if (result.ok) {
90
+ expect(result.value.node.parentId).toBeNull();
91
+ }
92
+ expect(spies.update).toHaveBeenCalled();
93
+ });
94
+
95
+ it("moves node to different parent", async () => {
96
+ const { db, spies } = createMockDb<DB>();
97
+ const movedNode = { ...baseChildNode, parentId: secondRootNode.id };
98
+ spies.select
99
+ .mockReturnValueOnce(baseChildNode) // getTaxonomyNode for node (node-2)
100
+ .mockReturnValueOnce(secondRootNode) // getTaxonomyNode for new parent (node-4)
101
+ .mockReturnValueOnce(secondRootNode) // detectCircularReference: lookup node-4 → parentId=null → not circular
102
+ .mockReturnValueOnce(secondRootNode) // calculateNodeDepth: lookup node-4 → parentId=null → depth=1
103
+ .mockReturnValueOnce([baseLeafNode]) // calculateSubtreeDepth: children of node-2
104
+ .mockReturnValueOnce([]); // calculateSubtreeDepth: children of node-3 (leaf, no children)
105
+ spies.update.mockReturnValue(movedNode);
106
+
107
+ const result = await run(db, { id: baseChildNode.id, newParentId: secondRootNode.id }, ctx, {
108
+ maxDepth: 10,
109
+ });
110
+
111
+ expect(result.ok).toBe(true);
112
+ if (result.ok) {
113
+ expect(result.value.node.parentId).toBe(secondRootNode.id);
114
+ }
115
+ });
116
+
117
+ it("returns error when subtree depth + new parent depth exceeds maxDepth", async () => {
118
+ const { db, spies } = createMockDb<DB>();
119
+ spies.select
120
+ .mockReturnValueOnce(baseChildNode) // getTaxonomyNode for node (node-2)
121
+ .mockReturnValueOnce(secondRootNode) // getTaxonomyNode for new parent (node-4)
122
+ .mockReturnValueOnce(secondRootNode) // detectCircularReference: lookup node-4 → not circular
123
+ .mockReturnValueOnce(secondRootNode) // calculateNodeDepth: lookup node-4 → depth=1
124
+ .mockReturnValueOnce([baseLeafNode]) // calculateSubtreeDepth: children of node-2
125
+ .mockReturnValueOnce([]); // calculateSubtreeDepth: children of node-3 (leaf)
126
+
127
+ // newParentDepth=1 (root), subtreeDepth=2 (child+leaf), totalDepth=3 > maxDepth=2
128
+ const result = await run(db, { id: baseChildNode.id, newParentId: secondRootNode.id }, ctx, {
129
+ maxDepth: 2,
130
+ });
131
+ expect(result.ok).toBe(false);
132
+ if (!result.ok) {
133
+ expect(result.error).toBeInstanceOf(MaxDepthExceededError);
134
+ }
135
+ });
136
+ });
@@ -0,0 +1,85 @@
1
+ import { ok, err, type CommandContext } from "../../shared/internal";
2
+ import { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ CircularReferenceError,
5
+ MaxDepthExceededError,
6
+ NodeNotFoundError,
7
+ ParentNodeNotFoundError,
8
+ } from "../lib/errors.generated";
9
+ import { getTaxonomyNode } from "../query/getTaxonomyNode.generated";
10
+ import { detectCircularReference } from "../query/detectCircularReference.generated";
11
+ import { calculateNodeDepth } from "../query/calculateNodeDepth.generated";
12
+ import { calculateSubtreeDepth } from "../query/calculateSubtreeDepth.generated";
13
+
14
+ export interface MoveTaxonomyNodeInput {
15
+ id: string;
16
+ newParentId: string | null;
17
+ }
18
+
19
+ /**
20
+ * Function: moveTaxonomyNode
21
+ *
22
+ * Reparents a taxonomy node. Moving to null promotes to root.
23
+ * Validates circular references and depth limits.
24
+ */
25
+ export async function run(
26
+ db: DB,
27
+ input: MoveTaxonomyNodeInput,
28
+ ctx: CommandContext,
29
+ config?: { maxDepth: number },
30
+ ) {
31
+ const { maxDepth } = config!;
32
+ const { id, newParentId } = input;
33
+
34
+ // 1. Check node exists
35
+ const { node } = await getTaxonomyNode(db, { id }, ctx);
36
+
37
+ if (!node) {
38
+ return err(new NodeNotFoundError(id));
39
+ }
40
+
41
+ // 2. If promoting to root, just update
42
+ if (newParentId === null) {
43
+ const movedNode = await db
44
+ .updateTable("TaxonomyNode")
45
+ .set({ parentId: null, updatedAt: new Date() })
46
+ .where("id", "=", id)
47
+ .returningAll()
48
+ .executeTakeFirst();
49
+
50
+ return ok({ node: movedNode! });
51
+ }
52
+
53
+ // 3. Check new parent exists
54
+ const { node: newParent } = await getTaxonomyNode(db, { id: newParentId }, ctx);
55
+
56
+ if (!newParent) {
57
+ return err(new ParentNodeNotFoundError(newParentId));
58
+ }
59
+
60
+ // 4. Check circular reference
61
+ const { isCircular } = await detectCircularReference(db, { nodeId: id, newParentId }, ctx);
62
+
63
+ if (isCircular) {
64
+ return err(new CircularReferenceError(`${id} -> ${newParentId}`));
65
+ }
66
+
67
+ // 5. Check depth limit: new parent depth + subtree depth of moved node
68
+ const { depth: newParentDepth } = await calculateNodeDepth(db, { nodeId: newParentId }, ctx);
69
+ const { depth: subtreeDepth } = await calculateSubtreeDepth(db, { nodeId: id }, ctx);
70
+ const totalDepth = newParentDepth + subtreeDepth;
71
+
72
+ if (totalDepth > maxDepth) {
73
+ return err(new MaxDepthExceededError(`${totalDepth} exceeds ${maxDepth}`));
74
+ }
75
+
76
+ // 6. Update parent
77
+ const movedNode = await db
78
+ .updateTable("TaxonomyNode")
79
+ .set({ parentId: newParentId, updatedAt: new Date() })
80
+ .where("id", "=", id)
81
+ .returningAll()
82
+ .executeTakeFirst();
83
+
84
+ return ok({ node: movedNode! });
85
+ }
@@ -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 "./reactivateItem";
5
+
6
+ export const reactivateItem = defineCommand(permissions.reactivateItem, run);