Skip to content

feat(COMPT-40): implement createQuery factory#1

Closed
a-elkhiraooui-ciscode wants to merge 2 commits intodevelopfrom
feat/COMPT-40-create-query-factory
Closed

feat(COMPT-40): implement createQuery factory#1
a-elkhiraooui-ciscode wants to merge 2 commits intodevelopfrom
feat/COMPT-40-create-query-factory

Conversation

@a-elkhiraooui-ciscode
Copy link
Copy Markdown

  • Add createQuery(keyFn, fetcher) returning QueryDefinition<TParams, TData>
  • TData and TParams fully inferred from fetcher signature, zero manual annotation
  • queryKey returns stable readonly tuple via keyFn
  • useQuery shorthand hook wraps useTanstackQuery with typed params
  • Export from src/index.ts
  • Add @tanstack/react-query as peerDependency (>=5) and devDependency
  • Add pnpm cssstyle override to fix Node.js v22 + jsdom@28 ESM compat issue
  • Fix duplicate import in vitest.config.ts
  • 9 tests, 100% coverage on createQuery.ts, 88.6% overall

Closes COMPT-40

Summary

  • What does this PR change?

Why

  • Why is this change needed?

Checklist

  • Added/updated tests (if behavior changed)
  • npm run lint passes
  • npm run typecheck passes
  • npm test passes
  • npm run build passes
  • Added a changeset (npx changeset) if this affects consumers

Notes

  • Anything reviewers should pay attention to?

- Add createQuery(keyFn, fetcher) returning QueryDefinition<TParams, TData>
- TData and TParams fully inferred from fetcher signature, zero manual annotation
- queryKey returns stable readonly tuple via keyFn
- useQuery shorthand hook wraps useTanstackQuery with typed params
- Export from src/index.ts
- Add @tanstack/react-query as peerDependency (>=5) and devDependency
- Add pnpm cssstyle override to fix Node.js v22 + jsdom@28 ESM compat issue
- Fix duplicate import in vitest.config.ts
- 9 tests, 100% coverage on createQuery.ts, 88.6% overall

Closes COMPT-40
@a-elkhiraooui-ciscode a-elkhiraooui-ciscode requested a review from a team as a code owner April 6, 2026 10:36
Copilot AI review requested due to automatic review settings April 6, 2026 10:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a small abstraction layer over TanStack Query v5 by adding a createQuery(keyFn, fetcher) factory that returns a typed QueryDefinition (including a useQuery shorthand hook), and wires it into the library’s public exports and dependencies.

Changes:

  • Add createQuery factory + types and a useQuery shorthand hook wrapper.
  • Add unit tests for createQuery behavior.
  • Export the new query module from the package entrypoint and add @tanstack/react-query as a peer/dev dependency (plus a pnpm override for cssstyle).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
vitest.config.ts Removes duplicate import.
src/query/index.ts Adds barrel export for the query module.
src/query/createQuery.ts Implements createQuery and QueryDefinition types + useQuery wrapper.
src/query/createQuery.test.tsx Adds tests validating the query factory and hook behavior.
src/index.ts Re-exports the new query module from the public API.
package.json Adds TanStack Query peer/dev dependency and pnpm override/pin.

Comment on lines +4 to +27
export type QueryKeyFn<TParams> = (params: TParams) => readonly unknown[];
export type QueryFetcher<TParams, TData> = (params: TParams) => Promise<TData>;

type UseQueryShorthandOptions<TData> = Omit<UseQueryOptions<TData, Error>, 'queryKey' | 'queryFn'>;

export interface QueryDefinition<TParams, TData> {
queryKey: QueryKeyFn<TParams>;
queryFn: QueryFetcher<TParams, TData>;
useQuery: (
params: TParams,
options?: UseQueryShorthandOptions<TData>,
) => UseQueryResult<TData, Error>;
}

export function createQuery<TParams, TData>(
keyFn: QueryKeyFn<TParams>,
fetcher: QueryFetcher<TParams, TData>,
): QueryDefinition<TParams, TData> {
return {
queryKey: keyFn,
queryFn: fetcher,
useQuery(params, options) {

return useTanstackQuery<TData, Error>({
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

QueryKeyFn is typed to return readonly unknown[], which will widen away any tuple/as const key type and prevents consumers from getting a typed query key back from def.queryKey(...). Consider parameterizing the key type (e.g., TKey extends readonly unknown[]) and carrying it through QueryDefinition / useQuery so the returned key and UseQueryOptions are keyed correctly.

Suggested change
export type QueryKeyFn<TParams> = (params: TParams) => readonly unknown[];
export type QueryFetcher<TParams, TData> = (params: TParams) => Promise<TData>;
type UseQueryShorthandOptions<TData> = Omit<UseQueryOptions<TData, Error>, 'queryKey' | 'queryFn'>;
export interface QueryDefinition<TParams, TData> {
queryKey: QueryKeyFn<TParams>;
queryFn: QueryFetcher<TParams, TData>;
useQuery: (
params: TParams,
options?: UseQueryShorthandOptions<TData>,
) => UseQueryResult<TData, Error>;
}
export function createQuery<TParams, TData>(
keyFn: QueryKeyFn<TParams>,
fetcher: QueryFetcher<TParams, TData>,
): QueryDefinition<TParams, TData> {
return {
queryKey: keyFn,
queryFn: fetcher,
useQuery(params, options) {
return useTanstackQuery<TData, Error>({
export type QueryKeyFn<TParams, TKey extends readonly unknown[]> = (params: TParams) => TKey;
export type QueryFetcher<TParams, TData> = (params: TParams) => Promise<TData>;
type UseQueryShorthandOptions<TData, TKey extends readonly unknown[]> = Omit<
UseQueryOptions<TData, Error, TData, TKey>,
'queryKey' | 'queryFn'
>;
export interface QueryDefinition<TParams, TData, TKey extends readonly unknown[]> {
queryKey: QueryKeyFn<TParams, TKey>;
queryFn: QueryFetcher<TParams, TData>;
useQuery: (
params: TParams,
options?: UseQueryShorthandOptions<TData, TKey>,
) => UseQueryResult<TData, Error>;
}
export function createQuery<TParams, TData, TKey extends readonly unknown[]>(
keyFn: QueryKeyFn<TParams, TKey>,
fetcher: QueryFetcher<TParams, TData>,
): QueryDefinition<TParams, TData, TKey> {
return {
queryKey: keyFn,
queryFn: fetcher,
useQuery(params, options) {
return useTanstackQuery<TData, Error, TData, TKey>({

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +31
useQuery(params, options) {

return useTanstackQuery<TData, Error>({
queryKey: keyFn(params),
queryFn: () => fetcher(params),
...options,
});
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

useQuery wraps TanStack's queryFn as () => fetcher(params), which drops the QueryFunctionContext (notably signal for cancellation). If consumers need request aborting (e.g., fetch with AbortSignal), consider allowing the fetcher to receive the context (or at least the signal) and wiring it through.

Copilot uses AI. Check for mistakes.
queryKey: keyFn,
queryFn: fetcher,
useQuery(params, options) {

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

There’s an extra whitespace-only line inside useQuery (between the signature and the return) that will likely be reformatted/flagged by Prettier/ESLint. Please remove the stray indentation to keep formatting clean.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +46
it('queryKey produces a stable reference for the same params', () => {
const def = createQuery(
(id: number) => ['item', id] as const,
async (id: number) => id,
);

const first = def.queryKey(1);
const second = def.queryKey(1);
expect(first).toEqual(second);
});
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The test name says “stable reference for the same params”, but the assertion uses toEqual, which only checks deep equality and does not verify referential stability. Either update the expectation to check identity (e.g., toBe) or rename the test to reflect that only the key value is stable.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +99
it('useQuery hook shows loading state before data resolves', () => {
let resolve: (v: { value: number }) => void;
const fetcher = vi.fn(
() =>
new Promise<{ value: number }>((res) => {
resolve = res;
}),
);
const def = createQuery((n: number) => ['lazy', n] as const, fetcher);

const { result } = renderHook(() => def.useQuery(99), {
wrapper: makeWrapper(),
});

expect(result.current.isLoading).toBe(true);
// Resolve to avoid open handles
resolve!({ value: 99 });
});
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

In the loading-state test, the promise is resolved to avoid open handles, but the test doesn’t wait for the query to settle after calling resolve. That can leave async state updates running after the test completes (leading to flaky warnings or leaks). Consider making the test async and awaiting a settled state after resolving (or explicitly clearing the QueryClient).

Copilot uses AI. Check for mistakes.
"cssstyle": "6.0.1"
}
},
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd"
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

packageManager pins pnpm to 10.20.0, but repo docs currently state pnpm 9.15.0 (see .github/instructions/general.instructions.md:16). To avoid setup/CI confusion, either align the pinned version with the documented one or update the docs to match this new pin.

Suggested change
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd"
"packageManager": "pnpm@9.15.0"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants