Skip to content

Conversation

@kyleamazza
Copy link
Collaborator

Summary

  • Implement queryOptions/mutationOptions export pattern for React Query v5 compatibility
  • Add type-safe query key builder with matchQueryKey function
  • Preserve full operation names in generated query options

Breaking Changes

  • Export Pattern: Changed from exporting hooks (useGetWidgets) to exporting query/mutation options (getWidgetsQueryOptions)
  • Method Naming: All operations now keep their full names (e.g., getWidgetsgetWidgetsQueryOptions instead of widgets)
  • Import Changes: Consumers need to use React Query hooks with the exported options

New Features

  • Type-safe matchQueryKey function for building query keys with full IntelliSense support
  • Support for infinite queries on paginated endpoints
  • Consistent query key structure: [serviceName, methodName, params || {}]

Test plan

  • Run npm test to verify all tests pass
  • Run npm run build to ensure clean compilation
  • Review generated snapshots in src/snapshot/v1/
  • Verify MIGRATION.md provides clear upgrade path

🤖 Generated with Claude Code

kyleamazza-fq and others added 10 commits June 14, 2025 21:12
- Transform hook-file.ts to export queryOptions instead of wrapper hooks
- Update query key structure to [service, method, params] pattern
- Create non-hook service getters in context-file.ts for queryOptions access
- Update naming conventions to remove 'use' prefix (e.g., widgetsQueryOptions)
- Regenerate snapshots with new export patterns
- Update tsconfig.json to exclude snapshot directory from build

This is the first step in migrating from wrapper hooks to queryOptions exports,
following the pattern recommended by the React Query team.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add comprehensive usage examples in README
- Update CLAUDE.md to reflect new queryOptions architecture
- Create MIGRATION.md guide for v0.1.x to v0.2.0 upgrade
- Document new query key structure and benefits

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- All tests passing with 80.53% code coverage
- Linting and formatting verified
- Build successful without errors
- Updated version to 0.2.0
- Created comprehensive CHANGELOG.md
- Removed outdated TODO comment
- Verified backward compatibility documentation
- Project ready for v0.2.0 release

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove the automatic stripping of "get" prefix from GET method names to maintain consistency across all generated query/mutation options. This change makes the API more predictable by keeping operation names exactly as defined in the service specification.

- Update getQueryOptionsName to preserve full method names
- Update documentation to reflect new naming convention
- Regenerate snapshots with new naming pattern

BREAKING CHANGE: Query options for GET methods now include the full operation name (e.g., getWidgetsQueryOptions instead of widgetsQueryOptions)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Create QueryKeyBuilderFile class to generate type-safe query keys
- Generate QueryKeyMap interface mapping services → operations → params
- Add type extraction helpers (ServiceKeys, OperationKeys, OperationParams)
- Implement matchQueryKey function with three overloads for partial matching
- Handle optional parameters with ParamsType | undefined pattern

Part of v0.2.0 release

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update matchQueryKey to properly handle 3-argument calls with undefined params
- Ensure query keys always have consistent structure [service, operation, params]
- Use empty object {} for undefined params when explicitly passed
- Add query-key-builder.ts to generated hooks directory
- Include comprehensive snapshot test for query key builder

This enhancement improves type safety and consistency in query key matching,
particularly for operations with optional parameters.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove redundant '-file' suffix from filename
- Update import in hook-generator.ts
- Preserve git history using git mv

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ntation

- Created 16 unit tests covering all aspects of query key builder functionality
- Updated snapshots to include generated query-key-builder.ts file
- Added usage examples to README demonstrating matchQueryKey function
- Updated CLAUDE.md with architecture details for type-safe query keys
- All tests passing with 99% coverage of query-key-builder.ts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add documentation for the new type-safe matchQueryKey function that enables
flexible query key construction with full IntelliSense support.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
These files were accidentally committed but should not be in the repository.
@kyleamazza kyleamazza mentioned this pull request Jun 15, 2025
@kyleamazza
Copy link
Collaborator Author

Addresses #14

@kyleamazza kyleamazza added the Breaking Breaking change that will require an major(est) version bump label Jun 15, 2025
@kyleamazza kyleamazza linked an issue Jun 15, 2025 that may be closed by this pull request
@kyleamazza kyleamazza self-assigned this Jun 15, 2025
@kyleamazza
Copy link
Collaborator Author

One design concern I have here is with the matchQueryKey helper. This is to provide a pretty simple and type-safe way of generating the queryKeys to use for invalidation or query filtering, but it's usage might be a bit confusing/unclear for general users.

For example, it's not intended to be used to provide custom query keys to queryOptions, but rather for non-queryOption-related functionality, like:

queryClient.invalidateQueries({
  // Invalidation via a full key with params
  queryKey: matchQueryKey('widgets', 'getWidgets', { status 'active' }),
});

queryClient.invalidateQueries({
  // Invalidation via a partial key at the operation level
  queryKey: matchQueryKey('widgets', 'getWidgets'),
});

queryClient.invalidateQueries({
  // Invalidation via a partial key at the service level
  queryKey: matchQueryKey('widgets'),
});

@kyleamazza kyleamazza changed the base branch from main to alpha June 15, 2025 17:46
@kyleamazza kyleamazza changed the base branch from alpha to main June 15, 2025 17:47
kyleamazza-fq and others added 5 commits June 15, 2025 15:00
- Replace generic ClientContext/ClientProvider with service-specific names (e.g., BasketryExampleContext, BasketryExampleProvider)
- Fix context reference bug where hooks were using old ClientContext name
- Update error messages to use service-specific provider names
- Remove unused buildHookName method from NameFactory
- Remove use prefix from query options exports (e.g., getWidgetFooQueryOptions)
- Keep service hooks (e.g., useWidgetService) for React Context integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove logic that stripped 'get' prefix from infinite query option names
- Use full method name (e.g., getWidgetsInfiniteQueryOptions instead of widgetsInfiniteQueryOptions)
- Remove unused getHttpMethodByName import from NameFactory
- Ensures consistency with regular query options naming

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add focused tests for relay-paginated methods that generate infinite query options
- Verify that full method names are preserved (including 'get' prefix)
- Ensure non-paginated methods don't generate infinite options
- Test that query keys include the infinite flag for proper cache isolation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Document service-specific naming changes for contexts and providers
- Add notes about preserving full method names in exports
- Include fixes for context reference bugs
- Note removal of 'get' prefix stripping logic
- Add test coverage additions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
kyleamazza-fq and others added 2 commits June 15, 2025 16:01
- Add explicit check for operation \!== undefined to avoid TypeScript non-null assertion
- Improves type safety in generated code by eliminating escape hatches
- Updates test to match new implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@kyleamazza kyleamazza marked this pull request as ready for review June 15, 2025 23:07
Comment on lines +79 to +84
expect(output).toContain('export interface QueryKeyMap {');
expect(output).toContain('widget: {');
expect(output).toContain('getWidgets: GetWidgetsParams | undefined;');
expect(output).toContain('getWidgetById: GetWidgetByIdParams;');
expect(output).toContain('gizmo: {');
expect(output).toContain('getGizmos: GetGizmosParams | undefined;');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine. I want to find a solution like this to unit testing generator outputs other than just snapshot tests. The only issue I see with this is testing indentation (not critical) and line order.

},
"include": ["src"],
"exclude": ["**/*.test?.*"]
"exclude": ["**/*.test?.*", "src/snapshot/**"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note, I do like including the snapshot folder so that we get compile-time type checks on the generated snapshots.

Copy link
Member

@skonves skonves left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks good! I left some notes.

kyleamazza and others added 17 commits June 27, 2025 08:03
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Task Group 1 Complete:
- Added hook name generation methods to NameFactory
- Set up ImportBuilder for React Query hooks
- Created deprecation message templates and helper method

This prepares the foundation for generating deprecated hook wrappers
alongside the new query options pattern.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Generate useXxx() and useSuspenseXxx() wrappers for query operations
- Generate useXxx() wrappers for mutation operations with query invalidation
- Generate useXxxInfinite() and useSuspenseXxxInfinite() for paginated queries
- Add comprehensive deprecation messages with migration examples
- Update snapshots with new deprecated exports
- All tests passing (19/19)

Part of Task Group 2: Generate Deprecated Hooks
- Add comprehensive unit tests for all deprecated hook types
- Fix deprecation message formatting with proper pluralization
- Update test snapshots to include both old and new patterns
- Ensure 98%+ test coverage maintained
- All 23 tests passing
- Update version to 0.2.0-alpha.1
- Add comprehensive migration guide to README
- Document backwards compatibility in CHANGELOG
- Include examples for migrating from hooks to queryOptions pattern

This release provides a smooth upgrade path for users with deprecated
hook wrappers that maintain full backwards compatibility while
encouraging adoption of the new queryOptions pattern.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add automated migration tool to help users upgrade from v0.1.x to v0.2.x:

- jscodeshift transform that converts deprecated hooks to queryOptions pattern
- Handles all hook types: query, mutation, infinite, and suspense
- Preserves TypeScript type parameters and existing imports
- Includes comprehensive test suite with fixtures
- Provides detailed documentation and helper script
- Safe by default with dry-run mode

Users can now run:
  ./codemod/run-migration.sh --apply

To automatically migrate their codebase from the old hook pattern to the
new queryOptions pattern, significantly reducing manual migration effort.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add instructions for using the automated migration tool in the README's
migration guide section, making it easier for users to discover and use
the jscodeshift codemod.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Include direct jscodeshift/npx commands in the automated migration
section for users who prefer running the codemod without the wrapper
script. Shows both dry-run and apply examples.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update .npmignore to include codemod in npm package
- Apply prettier formatting to codemod files
- Update CHANGELOG to mention jscodeshift codemod
- Ensure all files pass linting and formatting checks

The package is now ready for the v0.2.0-alpha.1 release with full
backwards compatibility and automated migration tooling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
The version bump should be handled by the GitHub Actions workflow,
not manually. Reverting to let the automated release process handle
the version increment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The old v0.1.x version stripped "Get" prefix from GET method hook names:
- getWidgets → useWidgets (not useGetWidgets)
- getWidgetById → useWidgetById (not useGetWidgetById)

This fix ensures true backwards compatibility by:
- Updating NameFactory to strip "Get" prefix for GET methods
- Passing HTTP verb info to name generation methods
- Updating codemod to handle both patterns (with and without Get)
- Fixing tests to expect the correct naming
- Regenerating snapshots with proper hook names

Now the deprecated hooks match the v0.1.x naming exactly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Apply code formatting to ensure consistent style across:
- name-factory.ts
- hook-file.ts
- hook-file.test.ts
- react-query-v0.2-migration.js

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Restored original hook implementation that accepts React Query options
- Query hooks now properly accept and spread options parameter
- Mutation hooks accept options and merge with mutation options
- Infinite query hooks work with original naming pattern (useWidgetsInfinite)
- All deprecated hooks maintain backwards compatibility while guiding to new pattern
- New query/mutation options exports remain as purely additive feature
- No breaking changes to existing hook patterns

The deprecated hooks now work exactly as in v0.1.0, accepting options parameters
while the new query options exports provide the migration path forward.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@kyleamazza
Copy link
Collaborator Author

Closing in favor of #20 which preserves backwards compatibility while this PR does not.

@kyleamazza kyleamazza closed this Jul 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking Breaking change that will require an major(est) version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export QueryOptions instead of hooks

4 participants