@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.
- package/CHANGELOG.md +69 -74
- package/README.md +17 -2
- package/cli/commands/generate-hooks.ts +15 -6
- package/cli/commands/scaffold.ts +1 -1
- package/cli/index.ts +1 -3
- package/cli/src/codegen/openapi/__tests__/naming-utils.test.js +367 -0
- package/cli/src/codegen/openapi/client-generator.js +90 -35
- package/cli/src/codegen/openapi/confidence-scorer.js +89 -200
- package/cli/src/codegen/openapi/hook-config.js +45 -63
- package/cli/src/codegen/openapi/hook-generator.js +253 -547
- package/cli/src/codegen/openapi/naming-constants.js +98 -0
- package/cli/src/codegen/openapi/naming-utils.js +149 -0
- package/cli/src/codegen/openapi/parser.js +9 -14
- package/cli/src/codegen/openapi/type-generator.js +6 -16
- package/dist/atoms/components/core/Avatar/Avatar.d.ts +6 -6
- package/dist/atoms/components/core/Avatar/Avatar.d.ts.map +1 -1
- package/dist/atoms/components/core/Avatar/index.d.ts +1 -1
- package/dist/atoms/components/core/Badge/Badge.d.ts +6 -6
- package/dist/atoms/components/core/Badge/Badge.d.ts.map +1 -1
- package/dist/atoms/components/core/Badge/index.d.ts +1 -1
- package/dist/atoms/components/core/Button/Button.d.ts +3 -3
- package/dist/atoms/components/core/Button/Button.d.ts.map +1 -1
- package/dist/atoms/components/core/Button/index.d.ts +2 -2
- package/dist/atoms/components/core/Card/Card.d.ts +2 -2
- package/dist/atoms/components/core/Card/Card.d.ts.map +1 -1
- package/dist/atoms/components/core/Card/index.d.ts +2 -2
- package/dist/atoms/components/core/Card/index.d.ts.map +1 -1
- package/dist/atoms/components/core/Checkbox/Checkbox.d.ts +4 -4
- package/dist/atoms/components/core/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/atoms/components/core/Checkbox/index.d.ts +2 -2
- package/dist/atoms/components/core/Input/Input.d.ts +2 -2
- package/dist/atoms/components/core/Input/Input.d.ts.map +1 -1
- package/dist/atoms/components/core/Input/index.d.ts +2 -2
- package/dist/atoms/components/core/Label/Label.d.ts +3 -4
- package/dist/atoms/components/core/Label/Label.d.ts.map +1 -1
- package/dist/atoms/components/core/Label/index.d.ts +2 -2
- package/dist/atoms/components/core/Select/Select.d.ts +5 -5
- package/dist/atoms/components/core/Select/Select.d.ts.map +1 -1
- package/dist/atoms/components/core/Select/index.d.ts +2 -2
- package/dist/atoms/components/core/Select/index.d.ts.map +1 -1
- package/dist/atoms/components/core/Spinner/Spinner.d.ts +4 -4
- package/dist/atoms/components/core/Spinner/Spinner.d.ts.map +1 -1
- package/dist/atoms/components/core/Spinner/index.d.ts +2 -2
- package/dist/atoms/components/core/Switch/Switch.d.ts +4 -4
- package/dist/atoms/components/core/Switch/Switch.d.ts.map +1 -1
- package/dist/atoms/components/core/Switch/index.d.ts +1 -1
- package/dist/atoms/components/core/index.d.ts +10 -10
- package/dist/atoms/components/core/index.d.ts.map +1 -1
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts +2 -2
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts.map +1 -1
- package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts +3 -3
- package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts.map +1 -1
- package/dist/atoms/components/data/ActivityFeed/index.d.ts +3 -3
- package/dist/atoms/components/data/ActivityFeed/index.d.ts.map +1 -1
- package/dist/atoms/components/data/ActivityFeed/types.d.ts +3 -3
- package/dist/atoms/components/data/ActivityFeed/types.d.ts.map +1 -1
- package/dist/atoms/components/data/ActivityFeed/utils.d.ts +2 -2
- package/dist/atoms/components/data/ActivityFeed/utils.d.ts.map +1 -1
- package/dist/atoms/components/data/Chart/Chart.d.ts +52 -5
- package/dist/atoms/components/data/Chart/Chart.d.ts.map +1 -1
- package/dist/atoms/components/data/Chart/index.d.ts +2 -2
- package/dist/atoms/components/data/Chart/index.d.ts.map +1 -1
- package/dist/atoms/components/data/DataBadge/DataBadge.d.ts +6 -6
- package/dist/atoms/components/data/DataBadge/DataBadge.d.ts.map +1 -1
- package/dist/atoms/components/data/DataBadge/index.d.ts +1 -1
- package/dist/atoms/components/data/DataTable/DataTable.d.ts +4 -4
- package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +4 -4
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
- package/dist/atoms/components/data/DataTable/TableCellWithTooltip.d.ts +1 -1
- package/dist/atoms/components/data/DataTable/index.d.ts +2 -2
- package/dist/atoms/components/data/DetailedCard/DetailedCard.d.ts +4 -4
- package/dist/atoms/components/data/DetailedCard/DetailedCard.d.ts.map +1 -1
- package/dist/atoms/components/data/DetailedCard/index.d.ts +2 -2
- package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts +3 -3
- package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts.map +1 -1
- package/dist/atoms/components/data/EntityIcon/index.d.ts +1 -1
- package/dist/atoms/components/data/IconBadge/IconBadge.d.ts +6 -6
- package/dist/atoms/components/data/IconBadge/IconBadge.d.ts.map +1 -1
- package/dist/atoms/components/data/IconBadge/index.d.ts +2 -2
- package/dist/atoms/components/data/ListCard/ListCard.d.ts +9 -9
- package/dist/atoms/components/data/ListCard/ListCard.d.ts.map +1 -1
- package/dist/atoms/components/data/ListCard/index.d.ts +1 -1
- package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts +5 -5
- package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/atoms/components/data/ProgressBar/index.d.ts +1 -1
- package/dist/atoms/components/data/StatCard/StatCard.d.ts +3 -3
- package/dist/atoms/components/data/StatCard/StatCard.d.ts.map +1 -1
- package/dist/atoms/components/data/StatCard/index.d.ts +1 -1
- package/dist/atoms/components/data/Table/Table.d.ts +2 -2
- package/dist/atoms/components/data/Table/Table.d.ts.map +1 -1
- package/dist/atoms/components/data/Table/index.d.ts +1 -1
- package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts +2 -2
- package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts.map +1 -1
- package/dist/atoms/components/data/TruncatedText/index.d.ts +1 -1
- package/dist/atoms/components/data/index.d.ts +12 -12
- package/dist/atoms/components/data/index.d.ts.map +1 -1
- package/dist/atoms/components/domain/SalesPanel/SalesPanel.d.ts +15 -15
- package/dist/atoms/components/domain/SalesPanel/SalesPanel.d.ts.map +1 -1
- package/dist/atoms/components/domain/SalesPanel/index.d.ts +1 -1
- package/dist/atoms/components/domain/SalesPanel/index.d.ts.map +1 -1
- package/dist/atoms/components/domain/index.d.ts +1 -1
- package/dist/atoms/components/feedback/Alert/Alert.d.ts +4 -4
- package/dist/atoms/components/feedback/Alert/Alert.d.ts.map +1 -1
- package/dist/atoms/components/feedback/Alert/index.d.ts +1 -1
- package/dist/atoms/components/feedback/EmptyState/EmptyState.d.ts +3 -3
- package/dist/atoms/components/feedback/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/atoms/components/feedback/EmptyState/index.d.ts +1 -1
- package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts +1 -1
- package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts.map +1 -1
- package/dist/atoms/components/feedback/ErrorBoundary/index.d.ts +1 -1
- package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts +30 -4
- package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/atoms/components/feedback/Skeleton/index.d.ts +1 -1
- package/dist/atoms/components/feedback/Toast/Toast.d.ts +4 -4
- package/dist/atoms/components/feedback/Toast/Toast.d.ts.map +1 -1
- package/dist/atoms/components/feedback/Toast/index.d.ts +1 -1
- package/dist/atoms/components/feedback/Toast/index.d.ts.map +1 -1
- package/dist/atoms/components/feedback/index.d.ts +5 -5
- package/dist/atoms/components/feedback/index.d.ts.map +1 -1
- package/dist/atoms/components/forms/DateTimePicker/DateTimePicker.d.ts +6 -6
- package/dist/atoms/components/forms/DateTimePicker/DateTimePicker.d.ts.map +1 -1
- package/dist/atoms/components/forms/DateTimePicker/index.d.ts +2 -2
- package/dist/atoms/components/forms/FileUpload/FileUpload.d.ts +3 -3
- package/dist/atoms/components/forms/FileUpload/FileUpload.d.ts.map +1 -1
- package/dist/atoms/components/forms/FileUpload/index.d.ts +2 -2
- package/dist/atoms/components/forms/FormField/FormField.d.ts +2 -2
- package/dist/atoms/components/forms/FormField/FormField.d.ts.map +1 -1
- package/dist/atoms/components/forms/FormField/index.d.ts +1 -1
- package/dist/atoms/components/forms/index.d.ts +3 -3
- package/dist/atoms/components/index.d.ts +9 -9
- package/dist/atoms/components/layout/Accordion/Accordion.d.ts +3 -3
- package/dist/atoms/components/layout/Accordion/Accordion.d.ts.map +1 -1
- package/dist/atoms/components/layout/Accordion/index.d.ts +1 -1
- package/dist/atoms/components/layout/Accordion/index.d.ts.map +1 -1
- package/dist/atoms/components/layout/Breadcrumb/Breadcrumb.d.ts +2 -3
- package/dist/atoms/components/layout/Breadcrumb/Breadcrumb.d.ts.map +1 -1
- package/dist/atoms/components/layout/Breadcrumb/index.d.ts +1 -1
- package/dist/atoms/components/layout/Breadcrumb/index.d.ts.map +1 -1
- package/dist/atoms/components/layout/Dialog/Dialog.d.ts +45 -0
- package/dist/atoms/components/layout/Dialog/Dialog.d.ts.map +1 -0
- package/dist/atoms/components/layout/Dialog/index.d.ts +2 -2
- package/dist/atoms/components/layout/Dialog/index.d.ts.map +1 -1
- package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts +9 -9
- package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/atoms/components/layout/Dropdown/index.d.ts +2 -2
- package/dist/atoms/components/layout/Dropdown/index.d.ts.map +1 -1
- package/dist/atoms/components/layout/Modal/Modal.d.ts +6 -6
- package/dist/atoms/components/layout/Modal/Modal.d.ts.map +1 -1
- package/dist/atoms/components/layout/Modal/index.d.ts +2 -2
- package/dist/atoms/components/layout/Tabs/Tabs.d.ts +46 -0
- package/dist/atoms/components/layout/Tabs/Tabs.d.ts.map +1 -0
- package/dist/atoms/components/layout/Tabs/index.d.ts +1 -1
- package/dist/atoms/components/layout/Tooltip/Tooltip.d.ts +6 -6
- package/dist/atoms/components/layout/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/atoms/components/layout/Tooltip/index.d.ts +1 -1
- package/dist/atoms/components/layout/index.d.ts +7 -7
- package/dist/atoms/components/layout/index.d.ts.map +1 -1
- package/dist/atoms/components/navigation/GlobalSearch/GlobalSearch.d.ts +2 -3
- package/dist/atoms/components/navigation/GlobalSearch/GlobalSearch.d.ts.map +1 -1
- package/dist/atoms/components/navigation/GlobalSearch/index.d.ts +1 -1
- package/dist/atoms/components/navigation/GlobalSearch/index.d.ts.map +1 -1
- package/dist/atoms/components/navigation/index.d.ts +1 -1
- package/dist/atoms/components/theme/ColorSwatch/ColorSwatch.d.ts +2 -2
- package/dist/atoms/components/theme/ColorSwatch/ColorSwatch.d.ts.map +1 -1
- package/dist/atoms/components/theme/ColorSwatch/index.d.ts +1 -1
- package/dist/atoms/components/theme/DarkModeToggle.d.ts.map +1 -1
- package/dist/atoms/components/theme/PaletteSwitcher.d.ts +1 -1
- package/dist/atoms/components/theme/PaletteSwitcher.d.ts.map +1 -1
- package/dist/atoms/components/theme/StyleGuide.d.ts +1 -1
- package/dist/atoms/components/theme/StyleGuide.d.ts.map +1 -1
- package/dist/atoms/components/theme/index.d.ts +4 -4
- package/dist/atoms/components/user/UserAvatar/UserAvatar.d.ts +2 -3
- package/dist/atoms/components/user/UserAvatar/UserAvatar.d.ts.map +1 -1
- package/dist/atoms/components/user/UserAvatar/index.d.ts +1 -1
- package/dist/atoms/components/user/UserAvatar/index.d.ts.map +1 -1
- package/dist/atoms/components/user/UserMenu/UserMenu.d.ts +2 -3
- package/dist/atoms/components/user/UserMenu/UserMenu.d.ts.map +1 -1
- package/dist/atoms/components/user/UserMenu/index.d.ts +1 -1
- package/dist/atoms/components/user/UserMenu/index.d.ts.map +1 -1
- package/dist/atoms/components/user/index.d.ts +2 -2
- package/dist/atoms/config/responsive.d.ts +16 -16
- package/dist/atoms/config/responsive.d.ts.map +1 -1
- package/dist/atoms/hooks/index.d.ts +4 -4
- package/dist/atoms/hooks/use-toast.d.ts +1 -1
- package/dist/atoms/hooks/use-toast.d.ts.map +1 -1
- package/dist/atoms/hooks/useApi.d.ts +2 -2
- package/dist/atoms/hooks/useApi.d.ts.map +1 -1
- package/dist/atoms/hooks/useResponsive.d.ts +1 -1
- package/dist/atoms/hooks/useResponsive.d.ts.map +1 -1
- package/dist/atoms/index.d.ts +6 -7
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/primitives/Badge.d.ts +6 -6
- package/dist/atoms/primitives/Badge.d.ts.map +1 -1
- package/dist/atoms/primitives/ErrorBoundary.d.ts +3 -3
- package/dist/atoms/primitives/ErrorBoundary.d.ts.map +1 -1
- package/dist/atoms/primitives/Select.d.ts +9 -7
- package/dist/atoms/primitives/Select.d.ts.map +1 -1
- package/dist/atoms/primitives/Switch.d.ts +4 -3
- package/dist/atoms/primitives/Switch.d.ts.map +1 -1
- package/dist/atoms/primitives/Tabs.d.ts +10 -10
- package/dist/atoms/primitives/Tabs.d.ts.map +1 -1
- package/dist/atoms/primitives/avatar.d.ts.map +1 -1
- package/dist/atoms/primitives/button.d.ts +2 -5
- package/dist/atoms/primitives/button.d.ts.map +1 -1
- package/dist/atoms/primitives/button.variants.d.ts +7 -0
- package/dist/atoms/primitives/button.variants.d.ts.map +1 -0
- package/dist/atoms/primitives/card.d.ts +3 -2
- package/dist/atoms/primitives/card.d.ts.map +1 -1
- package/dist/atoms/primitives/checkbox.d.ts +3 -3
- package/dist/atoms/primitives/checkbox.d.ts.map +1 -1
- package/dist/atoms/primitives/dialog.d.ts +4 -4
- package/dist/atoms/primitives/dialog.d.ts.map +1 -1
- package/dist/atoms/primitives/dropdown-menu.d.ts.map +1 -1
- package/dist/atoms/primitives/index.d.ts +16 -16
- package/dist/atoms/primitives/index.d.ts.map +1 -1
- package/dist/atoms/primitives/input.d.ts.map +1 -1
- package/dist/atoms/primitives/label.d.ts +4 -3
- package/dist/atoms/primitives/label.d.ts.map +1 -1
- package/dist/atoms/primitives/skeleton.d.ts.map +1 -1
- package/dist/atoms/primitives/spinner.d.ts +5 -5
- package/dist/atoms/primitives/spinner.d.ts.map +1 -1
- package/dist/atoms/primitives/table.d.ts.map +1 -1
- package/dist/atoms/services/api/client.d.ts +11 -2
- package/dist/atoms/services/api/client.d.ts.map +1 -1
- package/dist/atoms/services/auth-service.d.ts +1 -1
- package/dist/atoms/services/auth-service.d.ts.map +1 -1
- package/dist/atoms/services/health.d.ts +2 -2
- package/dist/atoms/services/index.d.ts +3 -3
- package/dist/atoms/shared/config/dashboard-sizes.d.ts +17 -17
- package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +1 -1
- package/dist/atoms/shared/config/environment.d.ts.map +1 -1
- package/dist/atoms/shared/index.d.ts +4 -4
- package/dist/atoms/shared/index.d.ts.map +1 -1
- package/dist/atoms/types/auth.d.ts +11 -1
- package/dist/atoms/types/auth.d.ts.map +1 -1
- package/dist/atoms/types/entity-config.d.ts +9 -9
- package/dist/atoms/types/entity-config.d.ts.map +1 -1
- package/dist/atoms/types/generated.d.ts.map +1 -1
- package/dist/atoms/types/index.d.ts +6 -6
- package/dist/atoms/types/loading.d.ts +1 -1
- package/dist/atoms/types/navigation.d.ts +1 -1
- package/dist/atoms/types/navigation.d.ts.map +1 -1
- package/dist/atoms/types/ui-config.d.ts +8 -8
- package/dist/atoms/types/ui-config.d.ts.map +1 -1
- package/dist/atoms/utils/animations.d.ts +7 -7
- package/dist/atoms/utils/animations.d.ts.map +1 -1
- package/dist/atoms/utils/color-manager.d.ts +1 -1
- package/dist/atoms/utils/debounce.d.ts +1 -1
- package/dist/atoms/utils/debounce.d.ts.map +1 -1
- package/dist/atoms/utils/field-detection.d.ts +2 -2
- package/dist/atoms/utils/field-detection.d.ts.map +1 -1
- package/dist/atoms/utils/icon-map.d.ts +68 -0
- package/dist/atoms/utils/icon-map.d.ts.map +1 -0
- package/dist/atoms/utils/icon-resolver.d.ts +7 -67
- package/dist/atoms/utils/icon-resolver.d.ts.map +1 -1
- package/dist/atoms/utils/index.d.ts +4 -4
- package/dist/atoms/utils/metric-engine.d.ts +2 -10
- package/dist/atoms/utils/metric-engine.d.ts.map +1 -1
- package/dist/atoms/utils/tooltip-helpers.d.ts +1 -1
- package/dist/atoms/utils/tooltip-helpers.d.ts.map +1 -1
- package/dist/atoms/utils/ui-mapping.d.ts +6 -4
- package/dist/atoms/utils/ui-mapping.d.ts.map +1 -1
- package/dist/atoms/utils/utils.d.ts +7 -6
- package/dist/atoms/utils/utils.d.ts.map +1 -1
- package/dist/codegen/openapi/bulk-types.d.ts +4 -4
- package/dist/codegen/openapi/bulk-types.d.ts.map +1 -1
- package/dist/features/auth/components/LoginForm.d.ts.map +1 -1
- package/dist/features/auth/components/LogoutButton.d.ts.map +1 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts +2 -2
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
- package/dist/features/auth/components/index.d.ts +3 -3
- package/dist/features/auth/hooks/auth-context.d.ts +3 -0
- package/dist/features/auth/hooks/auth-context.d.ts.map +1 -0
- package/dist/features/auth/hooks/index.d.ts +4 -3
- package/dist/features/auth/hooks/index.d.ts.map +1 -1
- package/dist/features/auth/hooks/use-auth.d.ts +3 -0
- package/dist/features/auth/hooks/use-auth.d.ts.map +1 -0
- package/dist/features/auth/hooks/useAuth.d.ts +3 -4
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
- package/dist/features/auth/hooks/useAuthContext.d.ts +1 -1
- package/dist/features/auth/hooks/useAuthContext.d.ts.map +1 -1
- package/dist/features/auth/hooks/usePermissions.d.ts +3 -3
- package/dist/features/auth/index.d.ts +3 -3
- package/dist/features/auth/providers/MockAuthProvider.d.ts +3 -4
- package/dist/features/auth/providers/MockAuthProvider.d.ts.map +1 -1
- package/dist/features/auth/providers/index.d.ts +2 -1
- package/dist/features/auth/providers/index.d.ts.map +1 -1
- package/dist/features/auth/providers/mock-auth-context.d.ts +3 -0
- package/dist/features/auth/providers/mock-auth-context.d.ts.map +1 -0
- package/dist/features/auth/providers/use-mock-auth.d.ts +3 -0
- package/dist/features/auth/providers/use-mock-auth.d.ts.map +1 -0
- package/dist/features/auth/services/mock-auth-service.d.ts +3 -3
- package/dist/features/auth/services/mock-auth-service.d.ts.map +1 -1
- package/dist/features/index.d.ts +1 -1
- package/dist/frontend-patterns.css +713 -576
- package/dist/index.d.ts +17 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +21400 -21788
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +21404 -21792
- package/dist/index.js.map +1 -1
- package/dist/molecules/forms/FormGroup.d.ts +2 -2
- package/dist/molecules/forms/FormGroup.d.ts.map +1 -1
- package/dist/molecules/forms/SearchInput.d.ts +1 -1
- package/dist/molecules/forms/SearchInput.d.ts.map +1 -1
- package/dist/molecules/forms/index.d.ts +2 -2
- package/dist/molecules/forms/index.d.ts.map +1 -1
- package/dist/molecules/index.d.ts +3 -3
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts +1 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
- package/dist/molecules/layout/AppHeader/index.d.ts +1 -1
- package/dist/molecules/layout/BulkSelectionBar.d.ts +2 -2
- package/dist/molecules/layout/BulkSelectionBar.d.ts.map +1 -1
- package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts +1 -1
- package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts +1 -1
- package/dist/molecules/layout/NavigationContext.d.ts +3 -9
- package/dist/molecules/layout/NavigationContext.d.ts.map +1 -1
- package/dist/molecules/layout/PageTemplate.d.ts +1 -1
- package/dist/molecules/layout/PageTemplate.d.ts.map +1 -1
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +4 -4
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +1 -1
- package/dist/molecules/layout/SectionHeader/index.d.ts +1 -1
- package/dist/molecules/layout/ShowcaseSection.d.ts +4 -4
- package/dist/molecules/layout/ShowcaseSection.d.ts.map +1 -1
- package/dist/molecules/layout/Sidebar.d.ts.map +1 -1
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +1 -1
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -1
- package/dist/molecules/layout/SidebarButton/index.d.ts +1 -1
- package/dist/molecules/layout/SidebarContext.d.ts +1 -8
- package/dist/molecules/layout/SidebarContext.d.ts.map +1 -1
- package/dist/molecules/layout/index.d.ts +14 -11
- package/dist/molecules/layout/index.d.ts.map +1 -1
- package/dist/molecules/layout/navigation-context.d.ts +9 -0
- package/dist/molecules/layout/navigation-context.d.ts.map +1 -0
- package/dist/molecules/layout/sidebar-context.d.ts +7 -0
- package/dist/molecules/layout/sidebar-context.d.ts.map +1 -0
- package/dist/molecules/layout/use-navigation.d.ts +2 -0
- package/dist/molecules/layout/use-navigation.d.ts.map +1 -0
- package/dist/molecules/layout/use-sidebar.d.ts +2 -0
- package/dist/molecules/layout/use-sidebar.d.ts.map +1 -0
- package/dist/molecules/navigation/NavMenu.d.ts +2 -2
- package/dist/molecules/navigation/NavMenu.d.ts.map +1 -1
- package/dist/molecules/navigation/Pagination.d.ts +2 -2
- package/dist/molecules/navigation/index.d.ts +2 -2
- package/dist/organisms/index.d.ts +1 -1
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts +1 -1
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +1 -1
- package/dist/templates/AuthTemplate.d.ts +4 -4
- package/dist/templates/AuthTemplate.d.ts.map +1 -1
- package/dist/templates/ComponentShowcaseTemplate.d.ts +7 -7
- package/dist/templates/ComponentShowcaseTemplate.d.ts.map +1 -1
- package/dist/templates/DashboardTemplate.d.ts +3 -3
- package/dist/templates/DashboardTemplate.d.ts.map +1 -1
- package/dist/templates/DataTemplate.d.ts +3 -3
- package/dist/templates/DataTemplate.d.ts.map +1 -1
- package/dist/templates/EnhancedDataTemplate.d.ts +11 -11
- package/dist/templates/EnhancedDataTemplate.d.ts.map +1 -1
- package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts +2 -2
- package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts.map +1 -1
- package/dist/templates/EnhancedDataTemplate.hooks.d.ts +4 -4
- package/dist/templates/EnhancedDataTemplate.hooks.d.ts.map +1 -1
- package/dist/templates/admin/AdminCRUDTemplate.d.ts +4 -4
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
- package/dist/templates/admin/AdminDashboardTemplate.d.ts +7 -7
- package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +1 -1
- package/dist/templates/admin/AdminDetailTemplate.d.ts +4 -4
- package/dist/templates/admin/AdminDetailTemplate.d.ts.map +1 -1
- package/dist/templates/admin/index.d.ts +3 -3
- package/dist/templates/admin/index.d.ts.map +1 -1
- package/dist/templates/api/APIDataTemplate.d.ts +14 -9
- package/dist/templates/api/APIDataTemplate.d.ts.map +1 -1
- package/dist/templates/api/create-api-data-template.d.ts +4 -0
- package/dist/templates/api/create-api-data-template.d.ts.map +1 -0
- package/dist/templates/api/index.d.ts +3 -2
- package/dist/templates/api/index.d.ts.map +1 -1
- package/dist/templates/factory.d.ts +3 -3
- package/dist/templates/factory.d.ts.map +1 -1
- package/dist/templates/index.d.ts +8 -8
- package/dist/templates/index.d.ts.map +1 -1
- package/package.json +10 -7
- package/LICENSE +0 -19
- package/cli/cli/commands/generate-hooks.js +0 -291
- package/cli/cli/commands/init.js +0 -25
- package/cli/cli/commands/scaffold.js +0 -201
- package/cli/cli/index.js +0 -113
- package/cli/commands/generate-hooks.js +0 -291
- package/cli/commands/init.js +0 -25
- package/cli/commands/scaffold.js +0 -201
- package/cli/index.js +0 -6665
- package/cli/src/codegen/openapi/bulk-hook-generator.js +0 -252
- package/cli/src/codegen/openapi/bulk-types.js +0 -89
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts +0 -38
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts.map +0 -1
- package/dist/codegen/index.d.ts +0 -7
- package/dist/codegen/index.d.ts.map +0 -1
- package/dist/codegen/openapi/bulk-hook-generator.d.ts +0 -40
- package/dist/codegen/openapi/bulk-hook-generator.d.ts.map +0 -1
- package/dist/codegen/openapi/client-generator.d.ts +0 -52
- package/dist/codegen/openapi/client-generator.d.ts.map +0 -1
- package/dist/codegen/openapi/confidence-scorer.d.ts +0 -30
- package/dist/codegen/openapi/confidence-scorer.d.ts.map +0 -1
- package/dist/codegen/openapi/hook-config.d.ts +0 -50
- package/dist/codegen/openapi/hook-config.d.ts.map +0 -1
- package/dist/codegen/openapi/hook-generator.d.ts +0 -108
- package/dist/codegen/openapi/hook-generator.d.ts.map +0 -1
- package/dist/codegen/openapi/index.d.ts +0 -27
- package/dist/codegen/openapi/index.d.ts.map +0 -1
- package/dist/codegen/openapi/parser.d.ts +0 -107
- package/dist/codegen/openapi/parser.d.ts.map +0 -1
- package/dist/codegen/openapi/type-generator.d.ts +0 -53
- package/dist/codegen/openapi/type-generator.d.ts.map +0 -1
- package/dist/generated/client/client.d.ts +0 -23
- package/dist/generated/client/client.d.ts.map +0 -1
- package/dist/generated/client/config.d.ts +0 -10
- package/dist/generated/client/config.d.ts.map +0 -1
- package/dist/generated/client/index.d.ts +0 -12
- package/dist/generated/client/index.d.ts.map +0 -1
- package/dist/generated/client/methods.d.ts +0 -591
- package/dist/generated/client/methods.d.ts.map +0 -1
- package/dist/generated/client/types.d.ts +0 -37
- package/dist/generated/client/types.d.ts.map +0 -1
- package/dist/generated/example.d.ts +0 -8
- package/dist/generated/example.d.ts.map +0 -1
- package/dist/generated/hooks/index.d.ts +0 -11
- package/dist/generated/hooks/index.d.ts.map +0 -1
- package/dist/generated/hooks/keys.d.ts +0 -59
- package/dist/generated/hooks/keys.d.ts.map +0 -1
- package/dist/generated/hooks/mutations.d.ts +0 -551
- package/dist/generated/hooks/mutations.d.ts.map +0 -1
- package/dist/generated/hooks/queries.d.ts +0 -426
- package/dist/generated/hooks/queries.d.ts.map +0 -1
- package/dist/generated/hooks/types.d.ts +0 -318
- package/dist/generated/hooks/types.d.ts.map +0 -1
- package/dist/generated/index.d.ts +0 -13
- package/dist/generated/index.d.ts.map +0 -1
- package/dist/generated/types/endpoints.d.ts +0 -1364
- package/dist/generated/types/endpoints.d.ts.map +0 -1
- package/dist/generated/types/index.d.ts +0 -11
- package/dist/generated/types/index.d.ts.map +0 -1
- package/dist/generated/types/parameters.d.ts +0 -8
- package/dist/generated/types/parameters.d.ts.map +0 -1
- package/dist/generated/types/responses.d.ts +0 -8
- package/dist/generated/types/responses.d.ts.map +0 -1
- package/dist/generated/types/schemas.d.ts +0 -652
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
32
|
-
this.confidenceScorer = new
|
|
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,
|
|
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
|
-
|
|
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?:
|
|
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.${
|
|
252
|
-
`queryKeys.${
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
308
|
-
const
|
|
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?:
|
|
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 (
|
|
205
|
+
return ` onMutate: async (newData) => {
|
|
371
206
|
// Cancel outgoing refetches
|
|
372
|
-
await queryClient.cancelQueries({ queryKey: queryKeys
|
|
373
|
-
|
|
207
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.all })
|
|
208
|
+
|
|
374
209
|
// Snapshot previous value
|
|
375
|
-
const previousData = queryClient.getQueryData(queryKeys
|
|
376
|
-
|
|
210
|
+
const previousData = queryClient.getQueryData(queryKeys.all)
|
|
211
|
+
|
|
377
212
|
// Optimistically update cache
|
|
378
|
-
queryClient.setQueryData(queryKeys
|
|
379
|
-
|
|
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
|
|
217
|
+
onError: (err, newData, context) => {
|
|
388
218
|
// Rollback on error
|
|
389
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
432
|
-
|
|
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 (
|
|
438
|
-
await queryClient.cancelQueries({ queryKey: queryKeys
|
|
439
|
-
|
|
440
|
-
const previousData = queryClient.getQueryData(queryKeys
|
|
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
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|
454
|
-
|
|
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}
|
|
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(` ${
|
|
486
|
-
// Track
|
|
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
|
-
//
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
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:
|
|
306
|
+
keys.push(` ${keyName}: (params: Record<string, unknown>) => [...queryKeys.${sanitizedResource}(), '${keyName}', params] as const,`);
|
|
528
307
|
}
|
|
529
308
|
else {
|
|
530
|
-
keys.push(` ${keyName}: () => [...queryKeys.${
|
|
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(
|
|
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
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
//
|
|
663
|
-
|
|
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
|
-
//
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
|
681
|
-
const lastSegment =
|
|
682
|
-
const
|
|
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:
|
|
685
|
-
resource:
|
|
686
|
-
|
|
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
|
|
488
|
+
* Extract the primary action verb from the operation ID
|
|
697
489
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
'
|
|
706
|
-
'
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
*
|
|
518
|
+
* Extract resource name from the API path
|
|
771
519
|
*/
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
528
|
+
* Check if this is a special endpoint that should keep its action verb
|
|
795
529
|
*/
|
|
796
|
-
|
|
797
|
-
|
|
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
|
|
534
|
+
* Format name for custom action endpoints
|
|
810
535
|
*/
|
|
811
|
-
|
|
812
|
-
const {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
//
|
|
832
|
-
if (
|
|
833
|
-
return `${action}_${
|
|
559
|
+
// For other special endpoints, combine with resource if meaningful
|
|
560
|
+
if (resource && resource !== action) {
|
|
561
|
+
return `${action}_${resource}`;
|
|
834
562
|
}
|
|
835
|
-
return
|
|
563
|
+
return action;
|
|
836
564
|
}
|
|
837
565
|
/**
|
|
838
|
-
* Format standard
|
|
566
|
+
* Format name for standard CRUD operations
|
|
839
567
|
*/
|
|
840
|
-
|
|
841
|
-
const {
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
|
|
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
|
|
574
|
+
return isList ? pluralize(resource) : singularize(resource);
|
|
864
575
|
case 'post':
|
|
865
|
-
return `create_${
|
|
576
|
+
return `create_${singularize(resource)}`;
|
|
866
577
|
case 'put':
|
|
867
578
|
case 'patch':
|
|
868
|
-
return `update_${
|
|
579
|
+
return `update_${singularize(resource)}`;
|
|
869
580
|
case 'delete':
|
|
870
|
-
return `delete_${
|
|
581
|
+
return `delete_${singularize(resource)}`;
|
|
871
582
|
default:
|
|
872
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
922
|
-
//
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
990
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
}
|