@pattern-stack/frontend-patterns 0.1.3 → 0.2.0-alpha.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 (446) hide show
  1. package/CHANGELOG.md +69 -74
  2. package/README.md +17 -2
  3. package/cli/commands/generate-hooks.ts +15 -6
  4. package/cli/commands/scaffold.ts +1 -1
  5. package/cli/index.ts +1 -3
  6. package/cli/src/codegen/openapi/__tests__/naming-utils.test.js +367 -0
  7. package/cli/src/codegen/openapi/client-generator.js +90 -35
  8. package/cli/src/codegen/openapi/confidence-scorer.js +89 -200
  9. package/cli/src/codegen/openapi/hook-config.js +45 -63
  10. package/cli/src/codegen/openapi/hook-generator.js +253 -547
  11. package/cli/src/codegen/openapi/naming-constants.js +98 -0
  12. package/cli/src/codegen/openapi/naming-utils.js +149 -0
  13. package/cli/src/codegen/openapi/parser.js +9 -14
  14. package/cli/src/codegen/openapi/type-generator.js +6 -16
  15. package/dist/atoms/components/core/Avatar/Avatar.d.ts +6 -6
  16. package/dist/atoms/components/core/Avatar/Avatar.d.ts.map +1 -1
  17. package/dist/atoms/components/core/Avatar/index.d.ts +1 -1
  18. package/dist/atoms/components/core/Badge/Badge.d.ts +6 -6
  19. package/dist/atoms/components/core/Badge/Badge.d.ts.map +1 -1
  20. package/dist/atoms/components/core/Badge/index.d.ts +1 -1
  21. package/dist/atoms/components/core/Button/Button.d.ts +3 -3
  22. package/dist/atoms/components/core/Button/Button.d.ts.map +1 -1
  23. package/dist/atoms/components/core/Button/index.d.ts +2 -2
  24. package/dist/atoms/components/core/Card/Card.d.ts +2 -2
  25. package/dist/atoms/components/core/Card/Card.d.ts.map +1 -1
  26. package/dist/atoms/components/core/Card/index.d.ts +2 -2
  27. package/dist/atoms/components/core/Card/index.d.ts.map +1 -1
  28. package/dist/atoms/components/core/Checkbox/Checkbox.d.ts +4 -4
  29. package/dist/atoms/components/core/Checkbox/Checkbox.d.ts.map +1 -1
  30. package/dist/atoms/components/core/Checkbox/index.d.ts +2 -2
  31. package/dist/atoms/components/core/Input/Input.d.ts +2 -2
  32. package/dist/atoms/components/core/Input/Input.d.ts.map +1 -1
  33. package/dist/atoms/components/core/Input/index.d.ts +2 -2
  34. package/dist/atoms/components/core/Label/Label.d.ts +3 -4
  35. package/dist/atoms/components/core/Label/Label.d.ts.map +1 -1
  36. package/dist/atoms/components/core/Label/index.d.ts +2 -2
  37. package/dist/atoms/components/core/Select/Select.d.ts +5 -5
  38. package/dist/atoms/components/core/Select/Select.d.ts.map +1 -1
  39. package/dist/atoms/components/core/Select/index.d.ts +2 -2
  40. package/dist/atoms/components/core/Select/index.d.ts.map +1 -1
  41. package/dist/atoms/components/core/Spinner/Spinner.d.ts +4 -4
  42. package/dist/atoms/components/core/Spinner/Spinner.d.ts.map +1 -1
  43. package/dist/atoms/components/core/Spinner/index.d.ts +2 -2
  44. package/dist/atoms/components/core/Switch/Switch.d.ts +4 -4
  45. package/dist/atoms/components/core/Switch/Switch.d.ts.map +1 -1
  46. package/dist/atoms/components/core/Switch/index.d.ts +1 -1
  47. package/dist/atoms/components/core/index.d.ts +10 -10
  48. package/dist/atoms/components/core/index.d.ts.map +1 -1
  49. package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts +2 -2
  50. package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts.map +1 -1
  51. package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts +3 -3
  52. package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts.map +1 -1
  53. package/dist/atoms/components/data/ActivityFeed/index.d.ts +3 -3
  54. package/dist/atoms/components/data/ActivityFeed/index.d.ts.map +1 -1
  55. package/dist/atoms/components/data/ActivityFeed/types.d.ts +3 -3
  56. package/dist/atoms/components/data/ActivityFeed/types.d.ts.map +1 -1
  57. package/dist/atoms/components/data/ActivityFeed/utils.d.ts +2 -2
  58. package/dist/atoms/components/data/ActivityFeed/utils.d.ts.map +1 -1
  59. package/dist/atoms/components/data/Chart/Chart.d.ts +52 -5
  60. package/dist/atoms/components/data/Chart/Chart.d.ts.map +1 -1
  61. package/dist/atoms/components/data/Chart/index.d.ts +2 -2
  62. package/dist/atoms/components/data/Chart/index.d.ts.map +1 -1
  63. package/dist/atoms/components/data/DataBadge/DataBadge.d.ts +6 -6
  64. package/dist/atoms/components/data/DataBadge/DataBadge.d.ts.map +1 -1
  65. package/dist/atoms/components/data/DataBadge/index.d.ts +1 -1
  66. package/dist/atoms/components/data/DataTable/DataTable.d.ts +4 -4
  67. package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
  68. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +4 -4
  69. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
  70. package/dist/atoms/components/data/DataTable/TableCellWithTooltip.d.ts +1 -1
  71. package/dist/atoms/components/data/DataTable/index.d.ts +2 -2
  72. package/dist/atoms/components/data/DetailedCard/DetailedCard.d.ts +4 -4
  73. package/dist/atoms/components/data/DetailedCard/DetailedCard.d.ts.map +1 -1
  74. package/dist/atoms/components/data/DetailedCard/index.d.ts +2 -2
  75. package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts +3 -3
  76. package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts.map +1 -1
  77. package/dist/atoms/components/data/EntityIcon/index.d.ts +1 -1
  78. package/dist/atoms/components/data/IconBadge/IconBadge.d.ts +6 -6
  79. package/dist/atoms/components/data/IconBadge/IconBadge.d.ts.map +1 -1
  80. package/dist/atoms/components/data/IconBadge/index.d.ts +2 -2
  81. package/dist/atoms/components/data/ListCard/ListCard.d.ts +9 -9
  82. package/dist/atoms/components/data/ListCard/ListCard.d.ts.map +1 -1
  83. package/dist/atoms/components/data/ListCard/index.d.ts +1 -1
  84. package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts +5 -5
  85. package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts.map +1 -1
  86. package/dist/atoms/components/data/ProgressBar/index.d.ts +1 -1
  87. package/dist/atoms/components/data/StatCard/StatCard.d.ts +3 -3
  88. package/dist/atoms/components/data/StatCard/StatCard.d.ts.map +1 -1
  89. package/dist/atoms/components/data/StatCard/index.d.ts +1 -1
  90. package/dist/atoms/components/data/Table/Table.d.ts +2 -2
  91. package/dist/atoms/components/data/Table/Table.d.ts.map +1 -1
  92. package/dist/atoms/components/data/Table/index.d.ts +1 -1
  93. package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts +2 -2
  94. package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts.map +1 -1
  95. package/dist/atoms/components/data/TruncatedText/index.d.ts +1 -1
  96. package/dist/atoms/components/data/index.d.ts +12 -12
  97. package/dist/atoms/components/data/index.d.ts.map +1 -1
  98. package/dist/atoms/components/domain/SalesPanel/SalesPanel.d.ts +15 -15
  99. package/dist/atoms/components/domain/SalesPanel/SalesPanel.d.ts.map +1 -1
  100. package/dist/atoms/components/domain/SalesPanel/index.d.ts +1 -1
  101. package/dist/atoms/components/domain/SalesPanel/index.d.ts.map +1 -1
  102. package/dist/atoms/components/domain/index.d.ts +1 -1
  103. package/dist/atoms/components/feedback/Alert/Alert.d.ts +4 -4
  104. package/dist/atoms/components/feedback/Alert/Alert.d.ts.map +1 -1
  105. package/dist/atoms/components/feedback/Alert/index.d.ts +1 -1
  106. package/dist/atoms/components/feedback/EmptyState/EmptyState.d.ts +3 -3
  107. package/dist/atoms/components/feedback/EmptyState/EmptyState.d.ts.map +1 -1
  108. package/dist/atoms/components/feedback/EmptyState/index.d.ts +1 -1
  109. package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts +1 -1
  110. package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts.map +1 -1
  111. package/dist/atoms/components/feedback/ErrorBoundary/index.d.ts +1 -1
  112. package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts +30 -4
  113. package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts.map +1 -1
  114. package/dist/atoms/components/feedback/Skeleton/index.d.ts +1 -1
  115. package/dist/atoms/components/feedback/Toast/Toast.d.ts +4 -4
  116. package/dist/atoms/components/feedback/Toast/Toast.d.ts.map +1 -1
  117. package/dist/atoms/components/feedback/Toast/index.d.ts +1 -1
  118. package/dist/atoms/components/feedback/Toast/index.d.ts.map +1 -1
  119. package/dist/atoms/components/feedback/index.d.ts +5 -5
  120. package/dist/atoms/components/feedback/index.d.ts.map +1 -1
  121. package/dist/atoms/components/forms/DateTimePicker/DateTimePicker.d.ts +6 -6
  122. package/dist/atoms/components/forms/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  123. package/dist/atoms/components/forms/DateTimePicker/index.d.ts +2 -2
  124. package/dist/atoms/components/forms/FileUpload/FileUpload.d.ts +3 -3
  125. package/dist/atoms/components/forms/FileUpload/FileUpload.d.ts.map +1 -1
  126. package/dist/atoms/components/forms/FileUpload/index.d.ts +2 -2
  127. package/dist/atoms/components/forms/FormField/FormField.d.ts +2 -2
  128. package/dist/atoms/components/forms/FormField/FormField.d.ts.map +1 -1
  129. package/dist/atoms/components/forms/FormField/index.d.ts +1 -1
  130. package/dist/atoms/components/forms/index.d.ts +3 -3
  131. package/dist/atoms/components/index.d.ts +9 -9
  132. package/dist/atoms/components/layout/Accordion/Accordion.d.ts +3 -3
  133. package/dist/atoms/components/layout/Accordion/Accordion.d.ts.map +1 -1
  134. package/dist/atoms/components/layout/Accordion/index.d.ts +1 -1
  135. package/dist/atoms/components/layout/Accordion/index.d.ts.map +1 -1
  136. package/dist/atoms/components/layout/Breadcrumb/Breadcrumb.d.ts +2 -3
  137. package/dist/atoms/components/layout/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  138. package/dist/atoms/components/layout/Breadcrumb/index.d.ts +1 -1
  139. package/dist/atoms/components/layout/Breadcrumb/index.d.ts.map +1 -1
  140. package/dist/atoms/components/layout/Dialog/Dialog.d.ts +45 -0
  141. package/dist/atoms/components/layout/Dialog/Dialog.d.ts.map +1 -0
  142. package/dist/atoms/components/layout/Dialog/index.d.ts +2 -2
  143. package/dist/atoms/components/layout/Dialog/index.d.ts.map +1 -1
  144. package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts +9 -9
  145. package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts.map +1 -1
  146. package/dist/atoms/components/layout/Dropdown/index.d.ts +2 -2
  147. package/dist/atoms/components/layout/Dropdown/index.d.ts.map +1 -1
  148. package/dist/atoms/components/layout/Modal/Modal.d.ts +6 -6
  149. package/dist/atoms/components/layout/Modal/Modal.d.ts.map +1 -1
  150. package/dist/atoms/components/layout/Modal/index.d.ts +2 -2
  151. package/dist/atoms/components/layout/Tabs/Tabs.d.ts +46 -0
  152. package/dist/atoms/components/layout/Tabs/Tabs.d.ts.map +1 -0
  153. package/dist/atoms/components/layout/Tabs/index.d.ts +1 -1
  154. package/dist/atoms/components/layout/Tooltip/Tooltip.d.ts +6 -6
  155. package/dist/atoms/components/layout/Tooltip/Tooltip.d.ts.map +1 -1
  156. package/dist/atoms/components/layout/Tooltip/index.d.ts +1 -1
  157. package/dist/atoms/components/layout/index.d.ts +7 -7
  158. package/dist/atoms/components/layout/index.d.ts.map +1 -1
  159. package/dist/atoms/components/navigation/GlobalSearch/GlobalSearch.d.ts +2 -3
  160. package/dist/atoms/components/navigation/GlobalSearch/GlobalSearch.d.ts.map +1 -1
  161. package/dist/atoms/components/navigation/GlobalSearch/index.d.ts +1 -1
  162. package/dist/atoms/components/navigation/GlobalSearch/index.d.ts.map +1 -1
  163. package/dist/atoms/components/navigation/index.d.ts +1 -1
  164. package/dist/atoms/components/theme/ColorSwatch/ColorSwatch.d.ts +2 -2
  165. package/dist/atoms/components/theme/ColorSwatch/ColorSwatch.d.ts.map +1 -1
  166. package/dist/atoms/components/theme/ColorSwatch/index.d.ts +1 -1
  167. package/dist/atoms/components/theme/DarkModeToggle.d.ts.map +1 -1
  168. package/dist/atoms/components/theme/PaletteSwitcher.d.ts +1 -1
  169. package/dist/atoms/components/theme/PaletteSwitcher.d.ts.map +1 -1
  170. package/dist/atoms/components/theme/StyleGuide.d.ts +1 -1
  171. package/dist/atoms/components/theme/StyleGuide.d.ts.map +1 -1
  172. package/dist/atoms/components/theme/index.d.ts +4 -4
  173. package/dist/atoms/components/user/UserAvatar/UserAvatar.d.ts +2 -3
  174. package/dist/atoms/components/user/UserAvatar/UserAvatar.d.ts.map +1 -1
  175. package/dist/atoms/components/user/UserAvatar/index.d.ts +1 -1
  176. package/dist/atoms/components/user/UserAvatar/index.d.ts.map +1 -1
  177. package/dist/atoms/components/user/UserMenu/UserMenu.d.ts +2 -3
  178. package/dist/atoms/components/user/UserMenu/UserMenu.d.ts.map +1 -1
  179. package/dist/atoms/components/user/UserMenu/index.d.ts +1 -1
  180. package/dist/atoms/components/user/UserMenu/index.d.ts.map +1 -1
  181. package/dist/atoms/components/user/index.d.ts +2 -2
  182. package/dist/atoms/config/responsive.d.ts +16 -16
  183. package/dist/atoms/config/responsive.d.ts.map +1 -1
  184. package/dist/atoms/hooks/index.d.ts +4 -4
  185. package/dist/atoms/hooks/use-toast.d.ts +1 -1
  186. package/dist/atoms/hooks/use-toast.d.ts.map +1 -1
  187. package/dist/atoms/hooks/useApi.d.ts +2 -2
  188. package/dist/atoms/hooks/useApi.d.ts.map +1 -1
  189. package/dist/atoms/hooks/useResponsive.d.ts +1 -1
  190. package/dist/atoms/hooks/useResponsive.d.ts.map +1 -1
  191. package/dist/atoms/index.d.ts +6 -7
  192. package/dist/atoms/index.d.ts.map +1 -1
  193. package/dist/atoms/primitives/Badge.d.ts +6 -6
  194. package/dist/atoms/primitives/Badge.d.ts.map +1 -1
  195. package/dist/atoms/primitives/ErrorBoundary.d.ts +3 -3
  196. package/dist/atoms/primitives/ErrorBoundary.d.ts.map +1 -1
  197. package/dist/atoms/primitives/Select.d.ts +9 -7
  198. package/dist/atoms/primitives/Select.d.ts.map +1 -1
  199. package/dist/atoms/primitives/Switch.d.ts +4 -3
  200. package/dist/atoms/primitives/Switch.d.ts.map +1 -1
  201. package/dist/atoms/primitives/Tabs.d.ts +10 -10
  202. package/dist/atoms/primitives/Tabs.d.ts.map +1 -1
  203. package/dist/atoms/primitives/avatar.d.ts.map +1 -1
  204. package/dist/atoms/primitives/button.d.ts +2 -5
  205. package/dist/atoms/primitives/button.d.ts.map +1 -1
  206. package/dist/atoms/primitives/button.variants.d.ts +7 -0
  207. package/dist/atoms/primitives/button.variants.d.ts.map +1 -0
  208. package/dist/atoms/primitives/card.d.ts +3 -2
  209. package/dist/atoms/primitives/card.d.ts.map +1 -1
  210. package/dist/atoms/primitives/checkbox.d.ts +3 -3
  211. package/dist/atoms/primitives/checkbox.d.ts.map +1 -1
  212. package/dist/atoms/primitives/dialog.d.ts +4 -4
  213. package/dist/atoms/primitives/dialog.d.ts.map +1 -1
  214. package/dist/atoms/primitives/dropdown-menu.d.ts.map +1 -1
  215. package/dist/atoms/primitives/index.d.ts +16 -16
  216. package/dist/atoms/primitives/index.d.ts.map +1 -1
  217. package/dist/atoms/primitives/input.d.ts.map +1 -1
  218. package/dist/atoms/primitives/label.d.ts +4 -3
  219. package/dist/atoms/primitives/label.d.ts.map +1 -1
  220. package/dist/atoms/primitives/skeleton.d.ts.map +1 -1
  221. package/dist/atoms/primitives/spinner.d.ts +5 -5
  222. package/dist/atoms/primitives/spinner.d.ts.map +1 -1
  223. package/dist/atoms/primitives/table.d.ts.map +1 -1
  224. package/dist/atoms/services/api/client.d.ts +11 -2
  225. package/dist/atoms/services/api/client.d.ts.map +1 -1
  226. package/dist/atoms/services/auth-service.d.ts +1 -1
  227. package/dist/atoms/services/auth-service.d.ts.map +1 -1
  228. package/dist/atoms/services/health.d.ts +2 -2
  229. package/dist/atoms/services/index.d.ts +3 -3
  230. package/dist/atoms/shared/config/dashboard-sizes.d.ts +17 -17
  231. package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +1 -1
  232. package/dist/atoms/shared/config/environment.d.ts.map +1 -1
  233. package/dist/atoms/shared/index.d.ts +4 -4
  234. package/dist/atoms/shared/index.d.ts.map +1 -1
  235. package/dist/atoms/types/auth.d.ts +11 -1
  236. package/dist/atoms/types/auth.d.ts.map +1 -1
  237. package/dist/atoms/types/entity-config.d.ts +9 -9
  238. package/dist/atoms/types/entity-config.d.ts.map +1 -1
  239. package/dist/atoms/types/generated.d.ts.map +1 -1
  240. package/dist/atoms/types/index.d.ts +6 -6
  241. package/dist/atoms/types/loading.d.ts +1 -1
  242. package/dist/atoms/types/navigation.d.ts +1 -1
  243. package/dist/atoms/types/navigation.d.ts.map +1 -1
  244. package/dist/atoms/types/ui-config.d.ts +8 -8
  245. package/dist/atoms/types/ui-config.d.ts.map +1 -1
  246. package/dist/atoms/utils/animations.d.ts +7 -7
  247. package/dist/atoms/utils/animations.d.ts.map +1 -1
  248. package/dist/atoms/utils/color-manager.d.ts +1 -1
  249. package/dist/atoms/utils/debounce.d.ts +1 -1
  250. package/dist/atoms/utils/debounce.d.ts.map +1 -1
  251. package/dist/atoms/utils/field-detection.d.ts +2 -2
  252. package/dist/atoms/utils/field-detection.d.ts.map +1 -1
  253. package/dist/atoms/utils/icon-map.d.ts +68 -0
  254. package/dist/atoms/utils/icon-map.d.ts.map +1 -0
  255. package/dist/atoms/utils/icon-resolver.d.ts +7 -67
  256. package/dist/atoms/utils/icon-resolver.d.ts.map +1 -1
  257. package/dist/atoms/utils/index.d.ts +4 -4
  258. package/dist/atoms/utils/metric-engine.d.ts +2 -10
  259. package/dist/atoms/utils/metric-engine.d.ts.map +1 -1
  260. package/dist/atoms/utils/tooltip-helpers.d.ts +1 -1
  261. package/dist/atoms/utils/tooltip-helpers.d.ts.map +1 -1
  262. package/dist/atoms/utils/ui-mapping.d.ts +6 -4
  263. package/dist/atoms/utils/ui-mapping.d.ts.map +1 -1
  264. package/dist/atoms/utils/utils.d.ts +7 -6
  265. package/dist/atoms/utils/utils.d.ts.map +1 -1
  266. package/dist/codegen/openapi/bulk-types.d.ts +4 -4
  267. package/dist/codegen/openapi/bulk-types.d.ts.map +1 -1
  268. package/dist/features/auth/components/LoginForm.d.ts.map +1 -1
  269. package/dist/features/auth/components/LogoutButton.d.ts.map +1 -1
  270. package/dist/features/auth/components/ProtectedRoute.d.ts +2 -2
  271. package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
  272. package/dist/features/auth/components/index.d.ts +3 -3
  273. package/dist/features/auth/hooks/auth-context.d.ts +3 -0
  274. package/dist/features/auth/hooks/auth-context.d.ts.map +1 -0
  275. package/dist/features/auth/hooks/index.d.ts +4 -3
  276. package/dist/features/auth/hooks/index.d.ts.map +1 -1
  277. package/dist/features/auth/hooks/use-auth.d.ts +3 -0
  278. package/dist/features/auth/hooks/use-auth.d.ts.map +1 -0
  279. package/dist/features/auth/hooks/useAuth.d.ts +3 -4
  280. package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
  281. package/dist/features/auth/hooks/useAuthContext.d.ts +1 -1
  282. package/dist/features/auth/hooks/useAuthContext.d.ts.map +1 -1
  283. package/dist/features/auth/hooks/usePermissions.d.ts +3 -3
  284. package/dist/features/auth/index.d.ts +3 -3
  285. package/dist/features/auth/providers/MockAuthProvider.d.ts +3 -4
  286. package/dist/features/auth/providers/MockAuthProvider.d.ts.map +1 -1
  287. package/dist/features/auth/providers/index.d.ts +2 -1
  288. package/dist/features/auth/providers/index.d.ts.map +1 -1
  289. package/dist/features/auth/providers/mock-auth-context.d.ts +3 -0
  290. package/dist/features/auth/providers/mock-auth-context.d.ts.map +1 -0
  291. package/dist/features/auth/providers/use-mock-auth.d.ts +3 -0
  292. package/dist/features/auth/providers/use-mock-auth.d.ts.map +1 -0
  293. package/dist/features/auth/services/mock-auth-service.d.ts +3 -3
  294. package/dist/features/auth/services/mock-auth-service.d.ts.map +1 -1
  295. package/dist/features/index.d.ts +1 -1
  296. package/dist/frontend-patterns.css +713 -576
  297. package/dist/index.d.ts +17 -17
  298. package/dist/index.d.ts.map +1 -1
  299. package/dist/index.es.js +21400 -21788
  300. package/dist/index.es.js.map +1 -1
  301. package/dist/index.js +21404 -21792
  302. package/dist/index.js.map +1 -1
  303. package/dist/molecules/forms/FormGroup.d.ts +2 -2
  304. package/dist/molecules/forms/FormGroup.d.ts.map +1 -1
  305. package/dist/molecules/forms/SearchInput.d.ts +1 -1
  306. package/dist/molecules/forms/SearchInput.d.ts.map +1 -1
  307. package/dist/molecules/forms/index.d.ts +2 -2
  308. package/dist/molecules/forms/index.d.ts.map +1 -1
  309. package/dist/molecules/index.d.ts +3 -3
  310. package/dist/molecules/layout/AppHeader/AppHeader.d.ts +1 -1
  311. package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
  312. package/dist/molecules/layout/AppHeader/index.d.ts +1 -1
  313. package/dist/molecules/layout/BulkSelectionBar.d.ts +2 -2
  314. package/dist/molecules/layout/BulkSelectionBar.d.ts.map +1 -1
  315. package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts +1 -1
  316. package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts +1 -1
  317. package/dist/molecules/layout/NavigationContext.d.ts +3 -9
  318. package/dist/molecules/layout/NavigationContext.d.ts.map +1 -1
  319. package/dist/molecules/layout/PageTemplate.d.ts +1 -1
  320. package/dist/molecules/layout/PageTemplate.d.ts.map +1 -1
  321. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +4 -4
  322. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +1 -1
  323. package/dist/molecules/layout/SectionHeader/index.d.ts +1 -1
  324. package/dist/molecules/layout/ShowcaseSection.d.ts +4 -4
  325. package/dist/molecules/layout/ShowcaseSection.d.ts.map +1 -1
  326. package/dist/molecules/layout/Sidebar.d.ts.map +1 -1
  327. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +1 -1
  328. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -1
  329. package/dist/molecules/layout/SidebarButton/index.d.ts +1 -1
  330. package/dist/molecules/layout/SidebarContext.d.ts +1 -8
  331. package/dist/molecules/layout/SidebarContext.d.ts.map +1 -1
  332. package/dist/molecules/layout/index.d.ts +14 -11
  333. package/dist/molecules/layout/index.d.ts.map +1 -1
  334. package/dist/molecules/layout/navigation-context.d.ts +9 -0
  335. package/dist/molecules/layout/navigation-context.d.ts.map +1 -0
  336. package/dist/molecules/layout/sidebar-context.d.ts +7 -0
  337. package/dist/molecules/layout/sidebar-context.d.ts.map +1 -0
  338. package/dist/molecules/layout/use-navigation.d.ts +2 -0
  339. package/dist/molecules/layout/use-navigation.d.ts.map +1 -0
  340. package/dist/molecules/layout/use-sidebar.d.ts +2 -0
  341. package/dist/molecules/layout/use-sidebar.d.ts.map +1 -0
  342. package/dist/molecules/navigation/NavMenu.d.ts +2 -2
  343. package/dist/molecules/navigation/NavMenu.d.ts.map +1 -1
  344. package/dist/molecules/navigation/Pagination.d.ts +2 -2
  345. package/dist/molecules/navigation/index.d.ts +2 -2
  346. package/dist/organisms/index.d.ts +1 -1
  347. package/dist/organisms/showcase/ComponentShowcasePage.d.ts +1 -1
  348. package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +1 -1
  349. package/dist/templates/AuthTemplate.d.ts +4 -4
  350. package/dist/templates/AuthTemplate.d.ts.map +1 -1
  351. package/dist/templates/ComponentShowcaseTemplate.d.ts +7 -7
  352. package/dist/templates/ComponentShowcaseTemplate.d.ts.map +1 -1
  353. package/dist/templates/DashboardTemplate.d.ts +3 -3
  354. package/dist/templates/DashboardTemplate.d.ts.map +1 -1
  355. package/dist/templates/DataTemplate.d.ts +3 -3
  356. package/dist/templates/DataTemplate.d.ts.map +1 -1
  357. package/dist/templates/EnhancedDataTemplate.d.ts +11 -11
  358. package/dist/templates/EnhancedDataTemplate.d.ts.map +1 -1
  359. package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts +2 -2
  360. package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts.map +1 -1
  361. package/dist/templates/EnhancedDataTemplate.hooks.d.ts +4 -4
  362. package/dist/templates/EnhancedDataTemplate.hooks.d.ts.map +1 -1
  363. package/dist/templates/admin/AdminCRUDTemplate.d.ts +4 -4
  364. package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
  365. package/dist/templates/admin/AdminDashboardTemplate.d.ts +7 -7
  366. package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +1 -1
  367. package/dist/templates/admin/AdminDetailTemplate.d.ts +4 -4
  368. package/dist/templates/admin/AdminDetailTemplate.d.ts.map +1 -1
  369. package/dist/templates/admin/index.d.ts +3 -3
  370. package/dist/templates/admin/index.d.ts.map +1 -1
  371. package/dist/templates/api/APIDataTemplate.d.ts +14 -9
  372. package/dist/templates/api/APIDataTemplate.d.ts.map +1 -1
  373. package/dist/templates/api/create-api-data-template.d.ts +4 -0
  374. package/dist/templates/api/create-api-data-template.d.ts.map +1 -0
  375. package/dist/templates/api/index.d.ts +3 -2
  376. package/dist/templates/api/index.d.ts.map +1 -1
  377. package/dist/templates/factory.d.ts +3 -3
  378. package/dist/templates/factory.d.ts.map +1 -1
  379. package/dist/templates/index.d.ts +8 -8
  380. package/dist/templates/index.d.ts.map +1 -1
  381. package/package.json +10 -7
  382. package/LICENSE +0 -19
  383. package/cli/cli/commands/generate-hooks.js +0 -291
  384. package/cli/cli/commands/init.js +0 -25
  385. package/cli/cli/commands/scaffold.js +0 -201
  386. package/cli/cli/index.js +0 -113
  387. package/cli/commands/generate-hooks.js +0 -291
  388. package/cli/commands/init.js +0 -25
  389. package/cli/commands/scaffold.js +0 -201
  390. package/cli/index.js +0 -6665
  391. package/cli/src/codegen/openapi/bulk-hook-generator.js +0 -252
  392. package/cli/src/codegen/openapi/bulk-types.js +0 -89
  393. package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts +0 -38
  394. package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts.map +0 -1
  395. package/dist/codegen/index.d.ts +0 -7
  396. package/dist/codegen/index.d.ts.map +0 -1
  397. package/dist/codegen/openapi/bulk-hook-generator.d.ts +0 -40
  398. package/dist/codegen/openapi/bulk-hook-generator.d.ts.map +0 -1
  399. package/dist/codegen/openapi/client-generator.d.ts +0 -52
  400. package/dist/codegen/openapi/client-generator.d.ts.map +0 -1
  401. package/dist/codegen/openapi/confidence-scorer.d.ts +0 -30
  402. package/dist/codegen/openapi/confidence-scorer.d.ts.map +0 -1
  403. package/dist/codegen/openapi/hook-config.d.ts +0 -50
  404. package/dist/codegen/openapi/hook-config.d.ts.map +0 -1
  405. package/dist/codegen/openapi/hook-generator.d.ts +0 -108
  406. package/dist/codegen/openapi/hook-generator.d.ts.map +0 -1
  407. package/dist/codegen/openapi/index.d.ts +0 -27
  408. package/dist/codegen/openapi/index.d.ts.map +0 -1
  409. package/dist/codegen/openapi/parser.d.ts +0 -107
  410. package/dist/codegen/openapi/parser.d.ts.map +0 -1
  411. package/dist/codegen/openapi/type-generator.d.ts +0 -53
  412. package/dist/codegen/openapi/type-generator.d.ts.map +0 -1
  413. package/dist/generated/client/client.d.ts +0 -23
  414. package/dist/generated/client/client.d.ts.map +0 -1
  415. package/dist/generated/client/config.d.ts +0 -10
  416. package/dist/generated/client/config.d.ts.map +0 -1
  417. package/dist/generated/client/index.d.ts +0 -12
  418. package/dist/generated/client/index.d.ts.map +0 -1
  419. package/dist/generated/client/methods.d.ts +0 -591
  420. package/dist/generated/client/methods.d.ts.map +0 -1
  421. package/dist/generated/client/types.d.ts +0 -37
  422. package/dist/generated/client/types.d.ts.map +0 -1
  423. package/dist/generated/example.d.ts +0 -8
  424. package/dist/generated/example.d.ts.map +0 -1
  425. package/dist/generated/hooks/index.d.ts +0 -11
  426. package/dist/generated/hooks/index.d.ts.map +0 -1
  427. package/dist/generated/hooks/keys.d.ts +0 -59
  428. package/dist/generated/hooks/keys.d.ts.map +0 -1
  429. package/dist/generated/hooks/mutations.d.ts +0 -551
  430. package/dist/generated/hooks/mutations.d.ts.map +0 -1
  431. package/dist/generated/hooks/queries.d.ts +0 -426
  432. package/dist/generated/hooks/queries.d.ts.map +0 -1
  433. package/dist/generated/hooks/types.d.ts +0 -318
  434. package/dist/generated/hooks/types.d.ts.map +0 -1
  435. package/dist/generated/index.d.ts +0 -13
  436. package/dist/generated/index.d.ts.map +0 -1
  437. package/dist/generated/types/endpoints.d.ts +0 -1364
  438. package/dist/generated/types/endpoints.d.ts.map +0 -1
  439. package/dist/generated/types/index.d.ts +0 -11
  440. package/dist/generated/types/index.d.ts.map +0 -1
  441. package/dist/generated/types/parameters.d.ts +0 -8
  442. package/dist/generated/types/parameters.d.ts.map +0 -1
  443. package/dist/generated/types/responses.d.ts +0 -8
  444. package/dist/generated/types/responses.d.ts.map +0 -1
  445. package/dist/generated/types/schemas.d.ts +0 -652
  446. package/dist/generated/types/schemas.d.ts.map +0 -1
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * React Hook Generator
4
3
  *
@@ -7,17 +6,16 @@
7
6
  *
8
7
  * Part of FRO-3: React Hook Generator
9
8
  */
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.ReactHookGenerator = void 0;
12
- exports.generateHooks = generateHooks;
13
- const hook_config_1 = require("./hook-config");
14
- const confidence_scorer_1 = require("./confidence-scorer");
15
- const bulk_hook_generator_1 = require("./bulk-hook-generator");
16
- const bulk_types_1 = require("./bulk-types");
17
- class ReactHookGenerator {
9
+ import { HookConfigManager } from './hook-config.js';
10
+ import { ConfidenceScorer } from './confidence-scorer.js';
11
+ import { singularize, pluralize, isPlural } from './naming-utils.js';
12
+ import { AUTH_ACTIONS, HEALTH_ENDPOINTS, USER_PROFILE_ENDPOINTS, SINGLETON_RESOURCES, COLLECTION_ACTIONS, SPECIAL_ACTIONS } from './naming-constants.js';
13
+ export class ReactHookGenerator {
14
+ options;
15
+ configManager;
16
+ confidenceScorer;
17
+ scoredNames = [];
18
18
  constructor(options = {}) {
19
- this.scoredNames = [];
20
- this.resourceConfigs = new Map();
21
19
  this.options = {
22
20
  queryKeyPrefix: options.queryKeyPrefix || 'api',
23
21
  includeInfiniteQueries: options.includeInfiniteQueries !== false,
@@ -28,115 +26,13 @@ class ReactHookGenerator {
28
26
  configPath: options.configPath || './hooks.config.json',
29
27
  enableConfidenceScoring: options.enableConfidenceScoring ?? true
30
28
  };
31
- this.configManager = new hook_config_1.HookConfigManager(this.options.configPath);
32
- this.confidenceScorer = new confidence_scorer_1.ConfidenceScorer();
33
- this.bulkHookGenerator = new bulk_hook_generator_1.BulkHookGenerator();
34
- }
35
- /**
36
- * Detect resource operations from endpoints
37
- */
38
- detectResourceOperations(endpoints) {
39
- const resourceMap = new Map();
40
- endpoints.forEach(endpoint => {
41
- const resourceName = this.extractResourceName(endpoint.path);
42
- if (!resourceName)
43
- return;
44
- const config = resourceMap.get(resourceName) || this.createDefaultConfig(resourceName);
45
- // Check if this is a bulk operation
46
- const requestBody = endpoint.requestBody;
47
- if ((0, bulk_types_1.isBulkOperation)(endpoint.path, endpoint.method, requestBody)) {
48
- const bulkType = detectBulkOperationType(endpoint.path, endpoint.method);
49
- switch (bulkType) {
50
- case 'delete':
51
- config.operations.bulkDelete = {
52
- path: endpoint.path,
53
- method: endpoint.method
54
- };
55
- break;
56
- case 'update':
57
- config.operations.bulkUpdate = {
58
- path: endpoint.path,
59
- method: endpoint.method
60
- };
61
- break;
62
- case 'create':
63
- config.operations.bulkCreate = {
64
- path: endpoint.path,
65
- method: endpoint.method
66
- };
67
- break;
68
- case 'mixed':
69
- default:
70
- if (!config.operations.customBulk) {
71
- config.operations.customBulk = [];
72
- }
73
- config.operations.customBulk.push({
74
- name: this.getOperationName(endpoint),
75
- path: endpoint.path,
76
- method: endpoint.method,
77
- operationType: bulkType || 'mixed'
78
- });
79
- break;
80
- }
81
- }
82
- else {
83
- // Standard CRUD operations
84
- switch (endpoint.method.toLowerCase()) {
85
- case 'get':
86
- if (endpoint.path.includes('{')) {
87
- // Single resource fetch
88
- }
89
- else {
90
- config.operations.list = { path: endpoint.path, method: endpoint.method };
91
- }
92
- break;
93
- case 'post':
94
- config.operations.create = { path: endpoint.path, method: endpoint.method };
95
- break;
96
- case 'put':
97
- case 'patch':
98
- config.operations.update = { path: endpoint.path, method: endpoint.method };
99
- break;
100
- case 'delete':
101
- config.operations.delete = { path: endpoint.path, method: endpoint.method };
102
- break;
103
- }
104
- }
105
- resourceMap.set(resourceName, config);
106
- });
107
- return resourceMap;
108
- }
109
- /**
110
- * Extract resource name from path
111
- */
112
- extractResourceName(path) {
113
- // Remove API version prefix
114
- const cleanPath = path.replace(/^\/api\/v\d+\//, '/');
115
- const segments = cleanPath.split('/').filter(Boolean);
116
- // Find the first non-parameter segment
117
- for (const segment of segments) {
118
- if (!segment.startsWith('{')) {
119
- return segment.replace(/-/g, '_');
120
- }
121
- }
122
- return null;
123
- }
124
- /**
125
- * Create default API config for a resource
126
- */
127
- createDefaultConfig(resourceName) {
128
- return {
129
- baseURL: '',
130
- resourceName,
131
- operations: {}
132
- };
29
+ this.configManager = new HookConfigManager(this.options.configPath);
30
+ this.confidenceScorer = new ConfidenceScorer();
133
31
  }
134
32
  async generate(parsedAPI) {
135
33
  const endpoints = parsedAPI.endpoints;
136
34
  // Load configuration
137
35
  await this.configManager.load();
138
- // Detect resource operations
139
- this.resourceConfigs = this.detectResourceOperations(endpoints);
140
36
  // Reset scored names for this generation
141
37
  this.scoredNames = [];
142
38
  const result = {
@@ -169,50 +65,15 @@ class ReactHookGenerator {
169
65
  const hooks = [];
170
66
  hooks.push(this.generateFileHeader('Query Hooks'));
171
67
  hooks.push('');
172
- hooks.push("import { useQuery, useInfiniteQuery, type UseQueryOptions, type UseInfiniteQueryOptions } from '@tanstack/react-query'");
68
+ hooks.push("import { useQuery, useInfiniteQuery, QueryOptions, InfiniteQueryOptions } from '@tanstack/react-query'");
173
69
  hooks.push("import { apiClient } from '../client'");
174
70
  hooks.push("import { queryKeys } from './keys'");
175
71
  hooks.push("import * as Types from './types'");
176
72
  hooks.push('');
177
73
  // Filter GET endpoints for queries
178
74
  const queryEndpoints = endpoints.filter(endpoint => endpoint.method === 'get');
179
- // Track generated hook names to avoid duplicates
180
- const generatedHooks = new Map();
181
75
  for (const endpoint of queryEndpoints) {
182
- let hookName = this.generateQueryHookName(endpoint);
183
- // If we've already generated this hook, add suffix to differentiate
184
- if (generatedHooks.has(hookName)) {
185
- const existingEndpoint = generatedHooks.get(hookName);
186
- // Determine which endpoint should get a suffix based on the path
187
- const existingIsSimpler = existingEndpoint.path.split('/').filter(s => !s.startsWith('{')).length <
188
- endpoint.path.split('/').filter(s => !s.startsWith('{')).length;
189
- if (existingIsSimpler) {
190
- // Current endpoint needs a suffix
191
- const suffix = endpoint.path.includes('/{subcategory_id}') ? 'Single' : 'List';
192
- hookName = `${hookName}${suffix}`;
193
- }
194
- else {
195
- // Need to rename the existing hook that we already added
196
- const existingSuffix = existingEndpoint.path.includes('/{subcategory_id}') ? 'Single' : 'List';
197
- const existingHookName = hookName;
198
- const newExistingHookName = `${existingHookName}${existingSuffix}`;
199
- // Find and update the existing hook in our output
200
- for (let i = hooks.length - 1; i >= 0; i--) {
201
- if (hooks[i].includes(`function ${existingHookName}(`)) {
202
- hooks[i] = hooks[i].replace(`function ${existingHookName}(`, `function ${newExistingHookName}(`);
203
- // Also update the JSDoc if it references the hook name
204
- if (i > 0 && hooks[i - 1].includes(`${existingHookName}`)) {
205
- hooks[i - 1] = hooks[i - 1].replace(existingHookName, newExistingHookName);
206
- }
207
- break;
208
- }
209
- }
210
- // Update our tracking map
211
- generatedHooks.delete(existingHookName);
212
- generatedHooks.set(newExistingHookName, existingEndpoint);
213
- }
214
- }
215
- generatedHooks.set(hookName, endpoint);
76
+ const hookName = this.generateQueryHookName(endpoint);
216
77
  const hook = this.generateQueryHook(endpoint, hookName);
217
78
  hooks.push(hook);
218
79
  hooks.push('');
@@ -241,24 +102,21 @@ class ReactHookGenerator {
241
102
  lines.push(' */');
242
103
  // Generate hook signature
243
104
  const paramType = hasParams ? this.generateParamType(endpoint) : '';
244
- const params = hasParams ? `params: ${paramType}, options?: UseQueryOptions<any, any, any>` : 'options?: UseQueryOptions<any, any, any>';
105
+ const params = hasParams ? `params: ${paramType}, options?: QueryOptions` : 'options?: QueryOptions = {}';
245
106
  lines.push(`export function ${hookName}(${params}) {`);
246
107
  // Generate hook body
247
- // Extract the query key name from the hook name (remove 'use' prefix)
248
- const queryKeyName = hookName.replace(/^use/, '');
249
- const queryKeyNameCamelCase = queryKeyName.charAt(0).toLowerCase() + queryKeyName.slice(1);
250
108
  const queryKeyCall = hasParams ?
251
- `queryKeys.${queryKeyNameCamelCase}(params)` :
252
- `queryKeys.${queryKeyNameCamelCase}()`;
109
+ `queryKeys.${this.camelCase(operationName)}(params)` :
110
+ `queryKeys.${this.camelCase(operationName)}()`;
253
111
  const apiCall = hasParams ?
254
112
  `() => apiClient.${this.camelCase(operationName)}(params)` :
255
113
  `() => apiClient.${this.camelCase(operationName)}()`;
256
114
  lines.push(' return useQuery({');
257
115
  lines.push(` queryKey: ${queryKeyCall},`);
258
116
  lines.push(` queryFn: ${apiCall},`);
259
- // Note: Authentication is handled by the API client interceptors
260
- // If you need to disable queries when not authenticated, use:
261
- // enabled: !!user && (options?.enabled ?? true) in your component
117
+ if (this.options.authenticationRequired) {
118
+ lines.push(' enabled: !!authToken && (options?.enabled ?? true),');
119
+ }
262
120
  lines.push(' ...options');
263
121
  lines.push(' })');
264
122
  lines.push('}');
@@ -272,18 +130,14 @@ class ReactHookGenerator {
272
130
  lines.push(` * Infinite query version of ${baseHookName}`);
273
131
  lines.push(' */');
274
132
  const paramType = this.generateParamType(endpoint);
275
- // Extract the query key name from the hook name (remove 'use' prefix)
276
- const queryKeyName = baseHookName.replace(/^use/, '');
277
- const queryKeyNameCamelCase = queryKeyName.charAt(0).toLowerCase() + queryKeyName.slice(1);
278
- lines.push(`export function ${hookName}(params: ${paramType}, options?: UseInfiniteQueryOptions<any, any, any, any, any>) {`);
133
+ lines.push(`export function ${hookName}(params: ${paramType}, options?: InfiniteQueryOptions) {`);
279
134
  lines.push(' return useInfiniteQuery({');
280
- lines.push(` queryKey: [...queryKeys.${queryKeyNameCamelCase}(), params],`);
135
+ lines.push(` queryKey: queryKeys.${this.camelCase(operationName)}(params),`);
281
136
  lines.push(` queryFn: ({ pageParam = 1 }) => apiClient.${this.camelCase(operationName)}({ ...params, page: pageParam }),`);
282
137
  lines.push(' getNextPageParam: (lastPage, allPages) => {');
283
138
  lines.push(' // Implement pagination logic based on your API response structure');
284
139
  lines.push(' return lastPage?.hasNextPage ? allPages.length + 1 : undefined');
285
140
  lines.push(' },');
286
- lines.push(' initialPageParam: 1,');
287
141
  lines.push(' ...options');
288
142
  lines.push(' })');
289
143
  lines.push('}');
@@ -293,33 +147,16 @@ class ReactHookGenerator {
293
147
  const hooks = [];
294
148
  hooks.push(this.generateFileHeader('Mutation Hooks'));
295
149
  hooks.push('');
296
- hooks.push("import { useMutation, useQueryClient, type UseMutationOptions } from '@tanstack/react-query'");
150
+ hooks.push("import { useMutation, useQueryClient, MutationOptions } from '@tanstack/react-query'");
297
151
  hooks.push("import { apiClient } from '../client'");
298
152
  hooks.push("import { queryKeys } from './keys'");
299
153
  hooks.push("import * as Types from './types'");
300
- hooks.push("import type { BulkOperationRequest, BulkOperationResponse, BulkOperationProgress, BulkMutationOptions } from '../bulk-types'");
301
154
  hooks.push('');
302
155
  // Filter non-GET endpoints for mutations
303
156
  const mutationEndpoints = endpoints.filter(endpoint => endpoint.method !== 'get');
304
- // Track generated hook names to avoid duplicates
305
- const generatedHooks = new Map();
306
157
  for (const endpoint of mutationEndpoints) {
307
- // Check if this is a bulk operation
308
- const requestBody = endpoint.requestBody;
309
- const isBulk = (0, bulk_types_1.isBulkOperation)(endpoint.path, endpoint.method, requestBody);
310
- let hookName = this.generateMutationHookName(endpoint);
311
- // If we've already generated this hook, add suffix to differentiate
312
- if (generatedHooks.has(hookName)) {
313
- const existingEndpoint = generatedHooks.get(hookName);
314
- // Add method suffix to differentiate
315
- const methodSuffix = endpoint.method.charAt(0).toUpperCase() + endpoint.method.slice(1);
316
- hookName = `${hookName}${methodSuffix}`;
317
- }
318
- generatedHooks.set(hookName, endpoint);
319
- // Generate appropriate hook based on type
320
- const hook = isBulk
321
- ? this.bulkHookGenerator.generateBulkMutationHook(endpoint, hookName, this.getOperationName(endpoint))
322
- : this.generateMutationHook(endpoint, hookName);
158
+ const hookName = this.generateMutationHookName(endpoint);
159
+ const hook = this.generateMutationHook(endpoint, hookName);
323
160
  hooks.push(hook);
324
161
  hooks.push('');
325
162
  }
@@ -339,7 +176,7 @@ class ReactHookGenerator {
339
176
  lines.push(' */');
340
177
  // Generate hook signature
341
178
  const mutationType = this.generateMutationType(endpoint);
342
- lines.push(`export function ${hookName}(options?: UseMutationOptions<any, any, ${mutationType}>) {`);
179
+ lines.push(`export function ${hookName}(options?: MutationOptions<any, any, ${mutationType}>) {`);
343
180
  lines.push(' const queryClient = useQueryClient()');
344
181
  lines.push('');
345
182
  lines.push(' return useMutation({');
@@ -363,97 +200,56 @@ class ReactHookGenerator {
363
200
  }
364
201
  generateOptimisticUpdate(endpoint) {
365
202
  const method = endpoint.method.toLowerCase();
366
- const resource = endpoint.tags?.[0] || 'default';
367
- const hasPathParams = endpoint.parameters.some(p => p.in === 'path');
368
203
  switch (method) {
369
204
  case 'post':
370
- return ` onMutate: async (data: any) => {
205
+ return ` onMutate: async (newData) => {
371
206
  // Cancel outgoing refetches
372
- await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
373
-
207
+ await queryClient.cancelQueries({ queryKey: queryKeys.all })
208
+
374
209
  // Snapshot previous value
375
- const previousData = queryClient.getQueryData(queryKeys.${resource}())
376
-
210
+ const previousData = queryClient.getQueryData(queryKeys.all)
211
+
377
212
  // Optimistically update cache
378
- queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
379
- if (Array.isArray(old)) {
380
- return [...old, data]
381
- }
382
- return old
383
- })
384
-
213
+ queryClient.setQueryData(queryKeys.all, (old: any) => [...(old || []), newData])
214
+
385
215
  return { previousData }
386
216
  },
387
- onError: (err: any, data: any, context: any) => {
217
+ onError: (err, newData, context) => {
388
218
  // Rollback on error
389
- if (context?.previousData !== undefined) {
390
- queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
391
- }
219
+ queryClient.setQueryData(queryKeys.all, context?.previousData)
392
220
  },`;
393
221
  case 'put':
394
222
  case 'patch':
395
- if (hasPathParams) {
396
- return ` onMutate: async (data: any) => {
397
- await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
398
-
399
- const previousData = queryClient.getQueryData(queryKeys.${resource}())
400
-
223
+ return ` onMutate: async (updatedData) => {
224
+ await queryClient.cancelQueries({ queryKey: queryKeys.all })
225
+
226
+ const previousData = queryClient.getQueryData(queryKeys.all)
227
+
401
228
  // Update specific item in cache
402
- queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
403
- if (Array.isArray(old)) {
404
- return old.map((item: any) =>
405
- item.id === data.pathParams?.id || item.id === data.pathParams?.${resource.slice(0, -1)}_id
406
- ? { ...item, ...data }
407
- : item
408
- )
409
- }
410
- return old
411
- })
412
-
413
- return { previousData }
414
- },
415
- onError: (err: any, data: any, context: any) => {
416
- if (context?.previousData !== undefined) {
417
- queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
418
- }
419
- },`;
420
- }
421
- return ` onMutate: async (data: any) => {
422
- await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
423
-
424
- const previousData = queryClient.getQueryData(queryKeys.${resource}())
425
-
426
- // Update cache with new data
427
- queryClient.setQueryData(queryKeys.${resource}(), data)
428
-
229
+ queryClient.setQueryData(queryKeys.all, (old: any) =>
230
+ old?.map((item: any) => item.id === updatedData.id ? { ...item, ...updatedData } : item)
231
+ )
232
+
429
233
  return { previousData }
430
234
  },
431
- onError: (err: any, data: any, context: any) => {
432
- if (context?.previousData !== undefined) {
433
- queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
434
- }
235
+ onError: (err, updatedData, context) => {
236
+ queryClient.setQueryData(queryKeys.all, context?.previousData)
435
237
  },`;
436
238
  case 'delete':
437
- return ` onMutate: async (data: any) => {
438
- await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
439
-
440
- const previousData = queryClient.getQueryData(queryKeys.${resource}())
441
-
239
+ return ` onMutate: async (id) => {
240
+ await queryClient.cancelQueries({ queryKey: queryKeys.all })
241
+
242
+ const previousData = queryClient.getQueryData(queryKeys.all)
243
+
442
244
  // Remove item from cache
443
- queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
444
- if (Array.isArray(old)) {
445
- const idToDelete = data.pathParams?.id || data.pathParams?.${resource.slice(0, -1)}_id || data
446
- return old.filter((item: any) => item.id !== idToDelete)
447
- }
448
- return old
449
- })
450
-
245
+ queryClient.setQueryData(queryKeys.all, (old: any) =>
246
+ old?.filter((item: any) => item.id !== id)
247
+ )
248
+
451
249
  return { previousData }
452
250
  },
453
- onError: (err: any, data: any, context: any) => {
454
- if (context?.previousData !== undefined) {
455
- queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
456
- }
251
+ onError: (err, id, context) => {
252
+ queryClient.setQueryData(queryKeys.all, context?.previousData)
457
253
  },`;
458
254
  default:
459
255
  return '';
@@ -463,7 +259,7 @@ class ReactHookGenerator {
463
259
  const relatedTags = this.getRelatedQueryTags(endpoint);
464
260
  return ` onSettled: () => {
465
261
  // Invalidate related queries
466
- ${relatedTags.map(tag => `queryClient.invalidateQueries({ queryKey: queryKeys.${tag}() })`).join('\n ')}
262
+ ${relatedTags.map(tag => `queryClient.invalidateQueries({ queryKey: queryKeys.${tag} })`).join('\n ')}
467
263
  },`;
468
264
  }
469
265
  generateQueryKeys(endpoints) {
@@ -481,53 +277,36 @@ class ReactHookGenerator {
481
277
  // Group endpoints by resource/tag
482
278
  const groupedEndpoints = this.groupEndpointsByResource(endpoints);
483
279
  for (const [resource, resourceEndpoints] of Object.entries(groupedEndpoints)) {
280
+ // Sanitize resource name to be a valid JS identifier
281
+ const sanitizedResource = this.sanitizeIdentifier(resource);
282
+ console.log(`DEBUG: resource="${resource}" -> sanitizedResource="${sanitizedResource}"`);
484
283
  keys.push(` // ${resource} keys`);
485
- keys.push(` ${resource}: () => [...queryKeys.all, '${resource}'] as const,`);
486
- // Track generated key names to avoid duplicates
487
- const generatedKeys = new Map();
284
+ keys.push(` ${sanitizedResource}: () => [...queryKeys.all, '${sanitizedResource}'] as const,`);
285
+ const usedKeys = new Set([sanitizedResource]); // Track used keys to avoid duplicates
488
286
  for (const endpoint of resourceEndpoints) {
489
287
  if (endpoint.method !== 'get')
490
288
  continue;
491
289
  const operationName = this.getOperationName(endpoint);
492
290
  let keyName = this.camelCase(operationName.replace(/^get/, ''));
493
- // If we've already generated this key, add suffix to differentiate
494
- if (generatedKeys.has(keyName)) {
495
- const existingEndpoint = generatedKeys.get(keyName);
496
- // Determine which endpoint should get a suffix based on the path
497
- // The one with a single resource gets the plain name, the one with sub-resources gets a suffix
498
- const existingIsSimpler = existingEndpoint.path.split('/').filter(s => !s.startsWith('{')).length <
499
- endpoint.path.split('/').filter(s => !s.startsWith('{')).length;
500
- if (existingIsSimpler) {
501
- // Current endpoint needs a suffix
502
- const suffix = endpoint.path.includes('/{subcategory_id}') ? 'Single' : 'List';
503
- keyName = `${keyName}${suffix}`;
504
- }
505
- else {
506
- // Update the existing key with a suffix
507
- const existingSuffix = existingEndpoint.path.includes('/{subcategory_id}') ? 'Single' : 'List';
508
- const existingKeyName = keyName;
509
- const newExistingKeyName = `${existingKeyName}${existingSuffix}`;
510
- // Find and update the existing key in our output
511
- for (let i = keys.length - 1; i >= 0; i--) {
512
- if (keys[i].includes(`${existingKeyName}:`)) {
513
- // Replace both the function name and the cache key string
514
- keys[i] = keys[i]
515
- .replace(`${existingKeyName}:`, `${newExistingKeyName}:`)
516
- .replace(`'${existingKeyName}'`, `'${newExistingKeyName}'`);
517
- break;
518
- }
519
- }
520
- // Update our tracking map
521
- generatedKeys.delete(existingKeyName);
522
- generatedKeys.set(newExistingKeyName, existingEndpoint);
523
- }
291
+ // Skip if keyName matches sanitizedResource (base key already handles this)
292
+ if (keyName === sanitizedResource) {
293
+ continue;
294
+ }
295
+ // If keyName already used, add suffix to disambiguate
296
+ if (usedKeys.has(keyName)) {
297
+ const isList = !endpoint.path.includes('{');
298
+ keyName = isList ? `${keyName}List` : `${keyName}Detail`;
524
299
  }
525
- generatedKeys.set(keyName, endpoint);
300
+ // Still duplicate after suffix? Skip to avoid compilation error
301
+ if (usedKeys.has(keyName)) {
302
+ continue;
303
+ }
304
+ usedKeys.add(keyName);
526
305
  if (this.hasRequiredParams(endpoint)) {
527
- keys.push(` ${keyName}: (params: any) => [...queryKeys.${resource}(), '${keyName}', params] as const,`);
306
+ keys.push(` ${keyName}: (params: Record<string, unknown>) => [...queryKeys.${sanitizedResource}(), '${keyName}', params] as const,`);
528
307
  }
529
308
  else {
530
- keys.push(` ${keyName}: () => [...queryKeys.${resource}(), '${keyName}'] as const,`);
309
+ keys.push(` ${keyName}: () => [...queryKeys.${sanitizedResource}(), '${keyName}'] as const,`);
531
310
  }
532
311
  }
533
312
  keys.push('');
@@ -539,31 +318,20 @@ class ReactHookGenerator {
539
318
  const types = [];
540
319
  types.push(this.generateFileHeader('Hook Types'));
541
320
  types.push('');
542
- types.push('// Re-export bulk operation types for convenience');
543
- types.push("export type { BulkOperationRequest, BulkOperationResponse, BulkOperationProgress, BulkMutationOptions } from '../bulk-types'");
321
+ types.push("import { QueryOptions, MutationOptions, InfiniteQueryOptions } from '@tanstack/react-query'");
544
322
  types.push('');
545
- // Track generated type names to avoid duplicates
546
- const generatedTypes = new Set();
547
323
  // Generate parameter and response types for hooks
548
324
  for (const endpoint of endpoints) {
549
325
  const operationName = this.getOperationName(endpoint);
550
326
  if (this.hasRequiredParams(endpoint)) {
551
- const typeName = `${this.capitalize(operationName)}Params`;
552
- if (!generatedTypes.has(typeName)) {
553
- generatedTypes.add(typeName);
554
- const paramType = this.generateParamType(endpoint);
555
- types.push(`export interface ${typeName} ${paramType}`);
556
- types.push('');
557
- }
327
+ const paramType = this.generateParamType(endpoint);
328
+ types.push(`export interface ${this.capitalize(operationName)}Params ${paramType}`);
329
+ types.push('');
558
330
  }
559
331
  if (endpoint.method !== 'get') {
560
- const typeName = `${this.capitalize(operationName)}Data`;
561
- if (!generatedTypes.has(typeName)) {
562
- generatedTypes.add(typeName);
563
- const mutationType = this.generateMutationType(endpoint);
564
- types.push(`export interface ${typeName} ${mutationType}`);
565
- types.push('');
566
- }
332
+ const mutationType = this.generateMutationType(endpoint);
333
+ types.push(`export interface ${this.capitalize(operationName)}Data ${mutationType}`);
334
+ types.push('');
567
335
  }
568
336
  }
569
337
  return types.join('\n');
@@ -588,7 +356,7 @@ class ReactHookGenerator {
588
356
  generateFileHeader(title) {
589
357
  return `/**
590
358
  * ${title}
591
- *
359
+ *
592
360
  * Auto-generated React hooks from OpenAPI specification
593
361
  * Do not edit manually - regenerate using the hook generator
594
362
  */`;
@@ -599,14 +367,6 @@ class ReactHookGenerator {
599
367
  }
600
368
  generateMutationHookName(endpoint) {
601
369
  const operationName = this.getOperationName(endpoint);
602
- // Check if this is a bulk operation
603
- const resourceName = this.extractResourceName(endpoint.path);
604
- if (resourceName) {
605
- const bulkHookName = this.bulkHookGenerator.generateBulkMutationHookName(endpoint, resourceName);
606
- if (bulkHookName) {
607
- return bulkHookName;
608
- }
609
- }
610
370
  return `use${this.capitalize(operationName)}`;
611
371
  }
612
372
  getOperationName(endpoint) {
@@ -659,217 +419,168 @@ class ReactHookGenerator {
659
419
  cleanOperationId(operationId, endpoint) {
660
420
  // Parse the operation ID into components
661
421
  const parsed = this.parseOperationId(operationId, endpoint);
662
- // Generate name based on parsed components
663
- return this.generateNameFromParsed(parsed, endpoint);
422
+ // Check if it's a custom action endpoint
423
+ if (parsed.isCustomAction) {
424
+ return this.formatCustomActionName(parsed);
425
+ }
426
+ // Check if it's a special endpoint that should keep its verb
427
+ if (this.isSpecialEndpoint(parsed)) {
428
+ return this.formatSpecialEndpointName(parsed);
429
+ }
430
+ // Format standard CRUD operation
431
+ return this.formatStandardOperationName(parsed, endpoint);
664
432
  }
665
433
  /**
666
434
  * Parse operation ID into structured components
667
435
  */
668
436
  parseOperationId(operationId, endpoint) {
669
- // Clean common patterns
670
- const cleaned = operationId
671
- .replace(/_api_v\d+_/g, '_') // Remove API version
672
- .replace(/_(get|post|put|patch|delete)$/i, ''); // Remove HTTP suffix
673
- // Split into parts
674
- const parts = cleaned.split('_').filter(Boolean);
675
- // Extract prefix (action) if exists
676
- const prefix = this.extractPrefix(parts, { ...endpoint, operationId });
677
- // Extract resources from path
678
- const resources = this.extractResourcesFromPath(endpoint.path);
437
+ // Common patterns for operation IDs
438
+ const patterns = [
439
+ // Pattern: action_resource_api_v1_path_segments_method
440
+ /^(?<action>\w+?)_(?<resource>\w+?)_api_v\d+_(?<path>.+?)_(?<method>get|post|put|patch|delete)$/i,
441
+ // Pattern: action_api_v1_path_segments_method
442
+ /^(?<action>\w+?)_api_v\d+_(?<path>.+?)_(?<method>get|post|put|patch|delete)$/i,
443
+ // Pattern: action_resource_path_method
444
+ /^(?<action>\w+?)_(?<resource>\w+?)_(?<path>.+?)_(?<method>get|post|put|patch|delete)$/i,
445
+ // Pattern: simple action_action_method (e.g., ready_ready_get, health_health_get)
446
+ /^(?<action>\w+?)_\1_(?<method>get|post|put|patch|delete)$/i,
447
+ // Pattern: simple action_resource
448
+ /^(?<action>\w+?)_(?<resource>\w+?)$/i,
449
+ ];
450
+ let components = {};
451
+ for (const pattern of patterns) {
452
+ const match = operationId.match(pattern);
453
+ if (match && match.groups) {
454
+ components = match.groups;
455
+ break;
456
+ }
457
+ }
458
+ // Extract path information
459
+ const pathSegments = endpoint.path.split('/').filter(s => s && !s.startsWith('{'));
460
+ const lastPathSegment = pathSegments[pathSegments.length - 1]?.replace(/-/g, '_');
679
461
  // Check if it's a custom action
680
- const segments = endpoint.path.split('/');
681
- const lastSegment = segments[segments.length - 1];
682
- const isCustomAction = !lastSegment.startsWith('{') && segments[segments.length - 2]?.startsWith('{');
462
+ const urlSegments = endpoint.path.split('/');
463
+ const lastSegment = urlSegments[urlSegments.length - 1];
464
+ const prevSegment = urlSegments[urlSegments.length - 2];
465
+ // Custom action: either after a path param (e.g., /accounts/{id}/archive)
466
+ // or a known action verb on a collection (e.g., /activities/search)
467
+ const isResourceAction = !lastSegment.startsWith('{') && prevSegment?.startsWith('{');
468
+ const isCollectionAction = !lastSegment.startsWith('{') && !prevSegment?.startsWith('{') && COLLECTION_ACTIONS.includes(lastSegment);
469
+ const isCustomAction = isResourceAction || isCollectionAction;
470
+ // Extract or infer the action verb
471
+ const actionVerb = this.extractActionVerb(components.action || operationId, endpoint.method);
472
+ // Extract or infer the resource
473
+ const resource = components.resource ||
474
+ this.extractResourceFromPath(endpoint.path) ||
475
+ lastPathSegment ||
476
+ 'resource';
683
477
  return {
684
- action: prefix?.action || '',
685
- resource: resources.primary || '',
686
- subResource: resources.sub?.[0] || '',
478
+ action: actionVerb,
479
+ resource: isCollectionAction ? prevSegment : resource,
480
+ path: components.path || '',
687
481
  method: endpoint.method,
688
482
  isCustomAction,
689
483
  customActionName: isCustomAction ? lastSegment.replace(/-/g, '_') : undefined,
690
- originalId: operationId,
691
- prefix: prefix?.fullPrefix || '',
692
- resources
484
+ originalId: operationId
693
485
  };
694
486
  }
695
487
  /**
696
- * Extract meaningful prefix from operation ID parts
488
+ * Extract the primary action verb from the operation ID
697
489
  */
698
- extractPrefix(parts, endpoint) {
699
- if (parts.length === 0)
700
- return null;
701
- // For extracting the prefix, use the original operation ID
702
- const originalId = endpoint.operationId || '';
703
- // Check if first part is a known action
704
- const knownActions = [
705
- 'login', 'logout', 'register', 'signup', 'signin',
706
- 'assign', 'reassign', 'create', 'update', 'delete',
707
- 'get', 'list', 'health', 'status', 'verify', 'validate'
490
+ extractActionVerb(actionPart, method) {
491
+ // Common action verbs to recognize
492
+ const actionVerbs = [
493
+ ...AUTH_ACTIONS,
494
+ ...HEALTH_ENDPOINTS,
495
+ 'list', 'get', 'create', 'update', 'delete',
496
+ ...COLLECTION_ACTIONS,
497
+ 'approve', 'reject', 'archive', 'restore',
498
+ 'sync', 'refresh', 'reset', 'verify', 'validate'
708
499
  ];
709
- const firstPart = parts[0].toLowerCase();
710
- if (knownActions.includes(firstPart)) {
711
- // Special handling for compound names like "get_health_status" or "get_current_user_info"
712
- if (firstPart === 'get' && originalId.startsWith('get_')) {
713
- // Extract everything between get_ and _api_v
714
- const match = originalId.match(/^get_(.+?)_api_v\d+/);
715
- if (match) {
716
- const compoundName = match[1];
717
- if (compoundName === 'health_status' || compoundName === 'current_user_info') {
718
- return {
719
- action: firstPart,
720
- fullPrefix: `${firstPart}_${compoundName}`
721
- };
722
- }
723
- }
724
- }
725
- // Look for compound actions like "assign_roles" or "reassign_budget"
726
- if (parts.length > 1 && ['assign', 'reassign'].includes(firstPart)) {
727
- // Special case for reassign_transaction_budget
728
- if (firstPart === 'reassign' && parts[1] === 'transaction' && parts[2] === 'budget') {
729
- return {
730
- action: 'reassign',
731
- fullPrefix: 'reassign_budget'
732
- };
733
- }
734
- return {
735
- action: firstPart,
736
- fullPrefix: `${parts[0]}_${parts[1]}`
737
- };
500
+ // Check if the action part starts with any known verb
501
+ const lowerAction = actionPart.toLowerCase();
502
+ for (const verb of actionVerbs) {
503
+ if (lowerAction.startsWith(verb)) {
504
+ return verb;
738
505
  }
739
- return {
740
- action: firstPart,
741
- fullPrefix: parts[0]
742
- };
743
506
  }
744
- return null;
745
- }
746
- /**
747
- * Extract resources from API path
748
- */
749
- extractResourcesFromPath(path) {
750
- // Remove API version prefix
751
- const cleanPath = path.replace(/^\/api\/v\d+\//, '/');
752
- const segments = cleanPath.split('/').filter(Boolean);
753
- const resources = [];
754
- let lastHasParam = false;
755
- for (let i = 0; i < segments.length; i++) {
756
- if (!segments[i].startsWith('{')) {
757
- resources.push(segments[i].replace(/-/g, '_'));
758
- }
759
- else if (i === segments.length - 1) {
760
- lastHasParam = true;
761
- }
762
- }
763
- return {
764
- primary: resources[0] || '',
765
- sub: resources.slice(1),
766
- lastHasParam
507
+ // Default to HTTP method mapping
508
+ const methodMap = {
509
+ get: 'get',
510
+ post: 'create',
511
+ put: 'update',
512
+ patch: 'update',
513
+ delete: 'delete'
767
514
  };
515
+ return methodMap[method] || method;
768
516
  }
769
517
  /**
770
- * Generate clean hook name from parsed components
518
+ * Extract resource name from the API path
771
519
  */
772
- generateNameFromParsed(parsed, endpoint) {
773
- // Special cases first
774
- if (parsed.prefix === 'login')
775
- return 'login';
776
- if (parsed.prefix === 'logout')
777
- return 'logout';
778
- // Handle compound GET operations (e.g., get_health_status, get_current_user_info)
779
- if (parsed.prefix && parsed.prefix.startsWith('get_') && parsed.prefix.includes('_')) {
780
- const withoutGet = parsed.prefix.substring(4); // Remove "get_"
781
- // Return the compound name without "get_" prefix
782
- if (withoutGet === 'health_status' || withoutGet === 'current_user_info') {
783
- return withoutGet;
784
- }
785
- }
786
- // Handle custom actions with meaningful prefixes
787
- if (parsed.prefix && this.isMeaningfulPrefix(parsed.prefix, parsed.resources)) {
788
- return this.formatWithPrefix(parsed);
789
- }
790
- // Standard REST conventions
791
- return this.formatStandardOperation(parsed, endpoint);
520
+ extractResourceFromPath(path) {
521
+ const segments = path.split('/').filter(s => s && !s.startsWith('{'));
522
+ // Skip common prefixes
523
+ const filtered = segments.filter(s => !['api', 'v1', 'v2'].includes(s));
524
+ // Return the last meaningful segment
525
+ return filtered[filtered.length - 1]?.replace(/-/g, '_') || null;
792
526
  }
793
527
  /**
794
- * Check if prefix adds meaningful context
528
+ * Check if this is a special endpoint that should keep its action verb
795
529
  */
796
- isMeaningfulPrefix(prefix, resources) {
797
- const action = prefix.split('_')[0];
798
- // Not meaningful if it just repeats the resource
799
- if (prefix === resources.primary)
800
- return false;
801
- if (prefix === `get_${resources.primary}`)
802
- return false;
803
- if (prefix === `list_${resources.primary}`)
804
- return false;
805
- // Meaningful if it's a custom action
806
- return ['assign', 'reassign', 'verify', 'validate', 'sync', 'refresh'].includes(action);
530
+ isSpecialEndpoint(parsed) {
531
+ return SPECIAL_ACTIONS.includes(parsed.action);
807
532
  }
808
533
  /**
809
- * Format name with meaningful prefix
534
+ * Format name for custom action endpoints
810
535
  */
811
- formatWithPrefix(parsed) {
812
- const { prefix, resources, originalId, isCustomAction, customActionName } = parsed;
813
- const prefixParts = prefix.split('_');
814
- const action = prefixParts[0];
815
- const subject = prefixParts.slice(1).join('_');
816
- // Special handling for custom actions
817
- if (isCustomAction && customActionName) {
818
- // For reassign_budget on transactions
819
- if (action === 'reassign' && subject === 'budget') {
820
- return `reassign_${this.singularize(resources.primary)}_budget`;
536
+ formatCustomActionName(parsed) {
537
+ const { action, resource, customActionName } = parsed;
538
+ // For custom actions like "reassign-budget", format as "reassign_budget"
539
+ if (customActionName) {
540
+ // Check if the action is already part of the custom action name
541
+ if (customActionName.startsWith(action)) {
542
+ return customActionName;
821
543
  }
544
+ // Otherwise combine resource and action
545
+ return `${singularize(resource)}_${customActionName}`;
822
546
  }
823
- // Handle deduplication - don't repeat if subject matches sub-resource
824
- if (subject && resources.sub.length > 0) {
825
- const subResource = resources.sub[0];
826
- // If subject matches the sub-resource, don't duplicate
827
- if (subject === subResource || subject === this.pluralize(subResource)) {
828
- return `${action}_${this.singularize(resources.primary)}_${subResource}`;
829
- }
547
+ return `${action}_${singularize(resource)}`;
548
+ }
549
+ /**
550
+ * Format name for special endpoints (login, health, etc.)
551
+ */
552
+ formatSpecialEndpointName(parsed) {
553
+ const { action, resource } = parsed;
554
+ // For simple singleton endpoints, just return the action
555
+ const simpleActions = [...AUTH_ACTIONS.filter(a => ['login', 'logout'].includes(a)), ...HEALTH_ENDPOINTS.filter(h => ['health', 'ready', 'status'].includes(h)), ...USER_PROFILE_ENDPOINTS];
556
+ if (simpleActions.includes(action)) {
557
+ return action;
830
558
  }
831
- // Standard format: action_resource_subresource
832
- if (resources.sub.length > 0 && !isCustomAction) {
833
- return `${action}_${this.singularize(resources.primary)}_${resources.sub[0]}`;
559
+ // For other special endpoints, combine with resource if meaningful
560
+ if (resource && resource !== action) {
561
+ return `${action}_${resource}`;
834
562
  }
835
- return `${action}_${this.singularize(resources.primary)}`;
563
+ return action;
836
564
  }
837
565
  /**
838
- * Format standard REST operation
566
+ * Format name for standard CRUD operations
839
567
  */
840
- formatStandardOperation(parsed, endpoint) {
841
- const { resources, method } = parsed;
842
- const hasParams = endpoint.path.includes('{');
843
- const isListOperation = method === 'get' && !hasParams;
844
- // Handle sub-resources
845
- if (resources.sub.length > 0) {
846
- const primary = this.singularize(resources.primary);
847
- const sub = resources.sub[0];
848
- switch (method) {
849
- case 'get':
850
- return isListOperation ? `${primary}_${this.pluralize(sub)}` : `${primary}_${sub}`;
851
- case 'post':
852
- return `create_${primary}_${this.singularize(sub)}`;
853
- case 'put':
854
- case 'patch':
855
- return `set_${primary}_${sub}`;
856
- case 'delete':
857
- return `remove_${primary}_${sub}`;
858
- }
859
- }
860
- // Handle primary resource only
861
- switch (method) {
568
+ formatStandardOperationName(parsed, endpoint) {
569
+ const { resource } = parsed;
570
+ // Determine if it's a list or single resource operation
571
+ const isList = endpoint.method === 'get' && !endpoint.path.includes('{') && !this.isSingletonEndpoint(resource, endpoint.path);
572
+ switch (endpoint.method) {
862
573
  case 'get':
863
- return isListOperation ? this.pluralize(resources.primary) : this.singularize(resources.primary);
574
+ return isList ? pluralize(resource) : singularize(resource);
864
575
  case 'post':
865
- return `create_${this.singularize(resources.primary)}`;
576
+ return `create_${singularize(resource)}`;
866
577
  case 'put':
867
578
  case 'patch':
868
- return `update_${this.singularize(resources.primary)}`;
579
+ return `update_${singularize(resource)}`;
869
580
  case 'delete':
870
- return `delete_${this.singularize(resources.primary)}`;
581
+ return `delete_${singularize(resource)}`;
871
582
  default:
872
- return this.singularize(resources.primary);
583
+ return singularize(resource);
873
584
  }
874
585
  }
875
586
  generateCleanName(endpoint) {
@@ -880,7 +591,7 @@ class ReactHookGenerator {
880
591
  return endpoint.method;
881
592
  // Get the main resource (usually the last non-parameter segment)
882
593
  const resource = pathParts[pathParts.length - 1];
883
- const cleanResource = resource.replace(/-/g, '_');
594
+ let cleanResource = resource.replace(/-/g, '_');
884
595
  // Check if it's a custom action (last segment after a parameter)
885
596
  const segments = endpoint.path.split('/');
886
597
  const lastSegment = segments[segments.length - 1];
@@ -891,7 +602,7 @@ class ReactHookGenerator {
891
602
  const action = lastSegment.replace(/-/g, '_');
892
603
  return `${mainResource}_${action}`;
893
604
  }
894
- // For nested resources (e.g., /categories/{id}/subcategories),
605
+ // For nested resources (e.g., /categories/{id}/subcategories),
895
606
  // just use the last resource name
896
607
  if (pathParts.length > 2 && segments.some(s => s.startsWith('{'))) {
897
608
  // This is a nested resource, just use the last part
@@ -904,7 +615,7 @@ class ReactHookGenerator {
904
615
  const isList = !endpoint.path.includes('{');
905
616
  // Only pluralize if it's a list and not already plural
906
617
  if (isList && !cleanResource.endsWith('s') && !cleanResource.endsWith('ies')) {
907
- return this.pluralize(cleanResource);
618
+ return pluralize(cleanResource);
908
619
  }
909
620
  return cleanResource;
910
621
  case 'post':
@@ -918,38 +629,22 @@ class ReactHookGenerator {
918
629
  return `${endpoint.method}_${cleanResource}`;
919
630
  }
920
631
  }
921
- pluralize(word) {
922
- // Don't pluralize if already plural
923
- if (word.endsWith('s') && !word.endsWith('ss') && !word.endsWith('us') && !word.endsWith('is')) {
924
- return word;
925
- }
926
- // Simple pluralization rules
927
- if (word.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].includes(word.slice(-2))) {
928
- return word.slice(0, -1) + 'ies';
929
- }
930
- if (word.endsWith('ss') || word.endsWith('x') || word.endsWith('ch') || word.endsWith('sh')) {
931
- return word + 'es';
932
- }
933
- if (word.endsWith('us')) {
934
- return word.slice(0, -2) + 'i';
935
- }
936
- if (word.endsWith('is')) {
937
- return word.slice(0, -2) + 'es';
938
- }
939
- return word + 's';
940
- }
941
- singularize(word) {
942
- // Simple singularization rules
943
- if (word.endsWith('ies')) {
944
- return word.slice(0, -3) + 'y';
945
- }
946
- if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('ches') || word.endsWith('shes')) {
947
- return word.slice(0, -2);
632
+ isSingletonEndpoint(resource, path) {
633
+ // Check if resource matches singleton pattern
634
+ const lowerResource = resource.toLowerCase().replace(/_/g, '');
635
+ if (SINGLETON_RESOURCES.some(s => lowerResource === s || lowerResource.endsWith(s))) {
636
+ return true;
948
637
  }
949
- if (word.endsWith('s') && !word.endsWith('ss')) {
950
- return word.slice(0, -1);
638
+
639
+ // Check path patterns for common singletons
640
+ const pathSegments = path.split('/').filter(s => s && !s.startsWith('{'));
641
+ const lastSegment = pathSegments[pathSegments.length - 1]?.toLowerCase();
642
+
643
+ if (SINGLETON_RESOURCES.includes(lastSegment)) {
644
+ return true;
951
645
  }
952
- return word;
646
+
647
+ return false;
953
648
  }
954
649
  hasRequiredParams(endpoint) {
955
650
  return endpoint.parameters.some(p => p.required) ||
@@ -986,25 +681,26 @@ class ReactHookGenerator {
986
681
  }
987
682
  }
988
683
  isListEndpoint(endpoint) {
989
- // Only GET requests can be list endpoints
990
- if (endpoint.method.toLowerCase() !== 'get')
684
+ // Extract resource for singleton check
685
+ const pathSegments = endpoint.path.split('/').filter(s => s && !s.startsWith('{'));
686
+ const resource = pathSegments[pathSegments.length - 1]?.replace(/-/g, '_') || '';
687
+
688
+ // Singleton endpoints are never list endpoints
689
+ if (this.isSingletonEndpoint(resource, endpoint.path)) {
991
690
  return false;
992
- // If it has path parameters, it's likely fetching a single resource
993
- if (endpoint.path.includes('{'))
994
- return false;
995
- // Check if the response is an array
996
- if (endpoint.responses?.['200']?.schema?.type === 'array')
997
- return true;
998
- // Check if summary/description indicates it's a list
999
- const description = `${endpoint.summary || ''} ${endpoint.description || ''}`.toLowerCase();
1000
- return description.includes('list') ||
1001
- description.includes('get all') ||
1002
- description.includes('fetch all');
691
+ }
692
+
693
+ return endpoint.path.includes('list') ||
694
+ !endpoint.path.includes('{') ||
695
+ (endpoint.summary?.toLowerCase().includes('list') ?? false) ||
696
+ (endpoint.summary?.toLowerCase().includes('get all') ?? false);
1003
697
  }
1004
698
  groupEndpointsByResource(endpoints) {
1005
699
  const groups = {};
1006
700
  for (const endpoint of endpoints) {
1007
- const resource = endpoint.tags?.[0] || 'default';
701
+ const tag = endpoint.tags?.[0] || 'default';
702
+ // Sanitize tag to be a valid JS identifier (camelCase, no spaces)
703
+ const resource = this.sanitizeIdentifier(tag);
1008
704
  if (!groups[resource]) {
1009
705
  groups[resource] = [];
1010
706
  }
@@ -1012,8 +708,17 @@ class ReactHookGenerator {
1012
708
  }
1013
709
  return groups;
1014
710
  }
711
+ sanitizeIdentifier(str) {
712
+ // Convert to camelCase and remove invalid characters
713
+ return str
714
+ .replace(/[^a-zA-Z0-9\s]/g, '') // Remove special chars except spaces
715
+ .split(/\s+/) // Split on whitespace
716
+ .map((word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
717
+ .join('');
718
+ }
1015
719
  getRelatedQueryTags(endpoint) {
1016
- const resource = endpoint.tags?.[0] || 'default';
720
+ const tag = endpoint.tags?.[0] || 'default';
721
+ const resource = this.sanitizeIdentifier(tag);
1017
722
  return [resource, 'all'];
1018
723
  }
1019
724
  camelCase(str) {
@@ -1041,7 +746,9 @@ class ReactHookGenerator {
1041
746
  const result = [];
1042
747
  for (const part of parts) {
1043
748
  const lower = part.toLowerCase();
1044
- if (!seen.has(lower)) {
749
+ // Keep important words even if duplicated (like verbs)
750
+ const importantWords = ['create', 'update', 'delete', 'get', 'list', 'reassign', 'assign'];
751
+ if (!seen.has(lower) || importantWords.includes(lower)) {
1045
752
  seen.add(lower);
1046
753
  result.push(part);
1047
754
  }
@@ -1049,9 +756,8 @@ class ReactHookGenerator {
1049
756
  return result.join('_');
1050
757
  }
1051
758
  }
1052
- exports.ReactHookGenerator = ReactHookGenerator;
1053
759
  // Factory function
1054
- async function generateHooks(parsedAPI, options) {
760
+ export async function generateHooks(parsedAPI, options) {
1055
761
  const generator = new ReactHookGenerator(options);
1056
762
  return generator.generate(parsedAPI);
1057
763
  }