{"id":13469550,"url":"https://github.com/lukemorales/query-key-factory","last_synced_at":"2025-05-13T19:10:54.708Z","repository":{"id":58037888,"uuid":"527349399","full_name":"lukemorales/query-key-factory","owner":"lukemorales","description":"A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query","archived":false,"fork":false,"pushed_at":"2024-12-29T23:35:37.000Z","size":1044,"stargazers_count":1436,"open_issues_count":19,"forks_count":37,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-27T05:46:22.938Z","etag":null,"topics":["cache","query","query-keys","react","react-query","solid-query","svelte-query","tanstack","tanstack-react-query","vue-query"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@lukemorales/query-key-factory","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lukemorales.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-21T23:18:48.000Z","updated_at":"2025-04-25T08:39:12.000Z","dependencies_parsed_at":"2024-11-19T09:06:24.206Z","dependency_job_id":"6f5f6bb2-ddbe-4c60-854c-e19f22e13f98","html_url":"https://github.com/lukemorales/query-key-factory","commit_stats":{"total_commits":81,"total_committers":12,"mean_commits":6.75,"dds":0.2592592592592593,"last_synced_commit":"c0e80e86b9afe803f4e3456bc80ac3c536ab60f9"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukemorales%2Fquery-key-factory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukemorales%2Fquery-key-factory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukemorales%2Fquery-key-factory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukemorales%2Fquery-key-factory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukemorales","download_url":"https://codeload.github.com/lukemorales/query-key-factory/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251094601,"owners_count":21535325,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","query","query-keys","react","react-query","solid-query","svelte-query","tanstack","tanstack-react-query","vue-query"],"created_at":"2024-07-31T15:01:44.421Z","updated_at":"2025-04-27T05:46:27.510Z","avatar_url":"https://github.com/lukemorales.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","TanStack Libraries"],"sub_categories":["TanStack Query"],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://images.emojiterra.com/mozilla/512px/1f3ed.png\" alt=\"Factory emoji\" height=\"130\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eQuery Key Factory\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/lukemorales/query-key-factory/actions/workflows/tests.yml\" target=\"\\_parent\"\u003e\u003cimg src=\"https://github.com/lukemorales/query-key-factory/actions/workflows/tests.yml/badge.svg?branch=main\" alt=\"Latest build\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/npm/v/@lukemorales/query-key-factory\" alt=\"Latest published version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/@lukemorales/query-key-factory@latest\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/bundlephobia/minzip/@lukemorales/query-key-factory\" alt=\"Bundlephobia\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/@lukemorales/query-key-factory@latest\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/bundlephobia/tree-shaking/@lukemorales/query-key-factory\" alt=\"Tree shaking available\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/npm/types/@lukemorales/query-key-factory\" alt=\"Types included\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/npm/license/@lukemorales/query-key-factory\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://badgen.net/npm/dt/@lukemorales/query-key-factory\" alt=\"Number of downloads\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/lukemorales/query-key-factory\" target=\"\\_parent\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/lukemorales/query-key-factory.svg?style=social\u0026amp;label=Star\" alt=\"GitHub Stars\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eTypesafe query key management for \u003ca href=\"https://tanstack.com/query\" target=\"\\_parent\"\u003e@tanstack/query\u003c/a\u003e with auto-completion features.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Focus on writing and invalidating queries without the hassle of remembering\u003cbr/\u003e how you've set up a key for a specific query! This lib will take care of the rest.\n\u003c/p\u003e\n\n## 📦 Install\nQuery Key Factory is available as a package on NPM, install with your favorite package manager:\n\n```dircolors\nnpm install @lukemorales/query-key-factory\n```\n\n## ⚡ Quick start\nStart by defining the query keys for the features of your app:\n\n### Declare your store in a single file\n```ts\nimport { createQueryKeyStore } from \"@lukemorales/query-key-factory\";\n\n// if you prefer to declare everything in one file\nexport const queries = createQueryKeyStore({\n  users: {\n    all: null,\n    detail: (userId: string) =\u003e ({\n      queryKey: [userId],\n      queryFn: () =\u003e api.getUser(userId),\n    }),\n  },\n  todos: {\n    detail: (todoId: string) =\u003e [todoId],\n    list: (filters: TodoFilters) =\u003e ({\n      queryKey: [{ filters }],\n      queryFn: (ctx) =\u003e api.getTodos({ filters, page: ctx.pageParam }),\n      contextQueries: {\n        search: (query: string, limit = 15) =\u003e ({\n          queryKey: [query, limit],\n          queryFn: (ctx) =\u003e api.getSearchTodos({\n            page: ctx.pageParam,\n            filters,\n            limit,\n            query,\n          }),\n        }),\n      },\n    }),\n  },\n});\n```\n\n### Fine-grained declaration colocated by features\n```ts\nimport { createQueryKeys, mergeQueryKeys } from \"@lukemorales/query-key-factory\";\n\n// queries/users.ts\nexport const users = createQueryKeys('users', {\n  all: null,\n  detail: (userId: string) =\u003e ({\n    queryKey: [userId],\n    queryFn: () =\u003e api.getUser(userId),\n  }),\n});\n\n// queries/todos.ts\nexport const todos = createQueryKeys('todos', {\n  detail: (todoId: string) =\u003e [todoId],\n  list: (filters: TodoFilters) =\u003e ({\n    queryKey: [{ filters }],\n    queryFn: (ctx) =\u003e api.getTodos({ filters, page: ctx.pageParam }),\n    contextQueries: {\n      search: (query: string, limit = 15) =\u003e ({\n        queryKey: [query, limit],\n        queryFn: (ctx) =\u003e api.getSearchTodos({\n          page: ctx.pageParam,\n          filters,\n          limit,\n          query,\n        }),\n      }),\n    },\n  }),\n});\n\n// queries/index.ts\nexport const queries = mergeQueryKeys(users, todos);\n```\n\nUse throughout your codebase as the single source for writing the query keys, or even the complete queries for your cache management:\n\n```ts\nimport { queries } from '../queries';\n\nexport function useUsers() {\n  return useQuery({\n    ...queries.users.all,\n    queryFn: () =\u003e api.getUsers(),\n  });\n};\n\nexport function useUserDetail(id: string) {\n  return useQuery(queries.users.detail(id));\n};\n```\n\n```ts\nimport { queries } from '../queries';\n\nexport function useTodos(filters: TodoFilters) {\n  return useQuery(queries.todos.list(filters));\n};\n\nexport function useSearchTodos(filters: TodoFilters, query: string, limit = 15) {\n  return useQuery({\n    ...queries.todos.list(filters)._ctx.search(query, limit),\n    enabled: Boolean(query),\n  });\n};\n\nexport function useUpdateTodo() {\n  const queryClient = useQueryClient();\n\n  return useMutation(updateTodo, {\n    onSuccess(newTodo) {\n      queryClient.setQueryData(queries.todos.detail(newTodo.id).queryKey, newTodo);\n\n      // invalidate all the list queries\n      queryClient.invalidateQueries({\n        queryKey: queries.todos.list._def,\n        refetchActive: false,\n      });\n    },\n  });\n};\n```\n\u003cbr /\u003e\n\n## 📝 Features\n\n### Standardized keys\nAll keys generated follow the @tanstack/query convention of being an [array at top level](https://tanstack.com/query/v4/docs/framework/react/guides/query-keys), including [keys with serializable objects](https://tanstack.com/query/v4/docs/framework/react/guides/query-keys#array-keys-with-variables):\n\n```ts\nexport const todos = createQueryKeys('todos', {\n  detail: (todoId: string) =\u003e [todoId],\n  list: (filters: TodoFilters) =\u003e ({\n    queryKey: [{ filters }],\n  }),\n});\n\n// =\u003e createQueryKeys output:\n// {\n//   _def: ['todos'],\n//   detail: (todoId: string) =\u003e {\n//     queryKey: ['todos', 'detail', todoId],\n//   },\n//   list: (filters: TodoFilters) =\u003e {\n//     queryKey: ['todos', 'list', { filters }],\n//   },\n// }\n```\n\n`queryKey` can be optional when there's no need for a dynamic query:\n\n```ts\nexport const users = createQueryKeys('users', {\n  list: {\n    queryKey: null,\n    queryFn: () =\u003e api.getUsers(),\n  }\n});\n```\n\n### Generate the query options you need to run `useQuery`\nDeclare your `queryKey` and your `queryFn` together, and have easy access to everything you need to run a query:\n\n```ts\nexport const users = createQueryKeys('users', {\n  detail: (userId: string) =\u003e ({\n    queryKey: [userId],\n    queryFn: () =\u003e api.getUser(userId),\n  }),\n});\n\n// =\u003e createQueryKeys output:\n// {\n//   _def: ['users'],\n//   detail: (userId: string) =\u003e {\n//     queryKey: ['users', 'detail', userId],\n//     queryFn: (ctx: QueryFunctionContext) =\u003e api.getUser(userId),\n//   },\n// }\n\nexport function useUserDetail(id: string) {\n  return useQuery(users.detail(id));\n};\n```\n\n### Generate contextual queries\nDeclare queries that are dependent or related to a parent context (e.g.: all likes from a user):\n\n```ts\nexport const users = createQueryKeys('users', {\n  detail: (userId: string) =\u003e ({\n    queryKey: [userId],\n    queryFn: () =\u003e api.getUser(userId),\n    contextQueries: {\n      likes: {\n        queryKey: null,\n        queryFn: () =\u003e api.getUserLikes(userId),\n      },\n    },\n  }),\n});\n\n// =\u003e createQueryKeys output:\n// {\n//   _def: ['users'],\n//   detail: (userId: string) =\u003e {\n//     queryKey: ['users', 'detail', userId],\n//     queryFn: (ctx: QueryFunctionContext) =\u003e api.getUser(userId),\n//     _ctx: {\n//       likes: {\n//         queryKey: ['users', 'detail', userId, 'likes'],\n//         queryFn: (ctx: QueryFunctionContext) =\u003e api.getUserLikes(userId),\n//       },\n//     },\n//   },\n// }\n\nexport function useUserLikes(userId: string) {\n  return useQuery(users.detail(userId)._ctx.likes);\n};\n```\n\n### Access to serializable keys scope definition\nEasy way to access the serializable key scope and invalidate all cache for that context:\n\n```ts\nusers.detail(userId).queryKey; // =\u003e ['users', 'detail', userId]\nusers.detail._def; // =\u003e ['users', 'detail']\n```\n\n### Create a single point of access for all your query keys\n\n#### Declare your query keys store in a single file\nJust one place to edit and maintain your store:\n```ts\nexport const queries = createQueryKeyStore({\n  users: {\n    all: null,\n    detail: (userId: string) =\u003e ({\n      queryKey: [userId],\n      queryFn: () =\u003e api.getUser(userId),\n    }),\n  },\n  todos: {\n    detail: (todoId: string) =\u003e [todoId],\n    list: (filters: TodoFilters) =\u003e ({\n      queryKey: [{ filters }],\n      queryFn: (ctx) =\u003e api.getTodos({ filters, page: ctx.pageParam }),\n    }),\n  },\n});\n```\n\n#### Declare your query keys by feature\nHave fine-grained control over your features' keys and merge them into a single object to have access to all your query keys in your codebase:\n\n```ts\n// queries/users.ts\nexport const users = createQueryKeys('users', {\n  all: null,\n  detail: (userId: string) =\u003e ({\n    queryKey: [userId],\n    queryFn: () =\u003e api.getUser(userId),\n  }),\n});\n\n// queries/todos.ts\nexport const todos = createQueryKeys('todos', {\n  detail: (todoId: string) =\u003e [todoId],\n  list: (filters: TodoFilters) =\u003e ({\n    queryKey: [{ filters }],\n    queryFn: (ctx) =\u003e api.getTodos({ filters, page: ctx.pageParam }),\n  }),\n});\n\n// queries/index.ts\nexport const queries = mergeQueryKeys(users, todos);\n```\n\n### Type safety and smart autocomplete\nTypescript is a first class citizen of Query Key Factory, providing easy of use and autocomplete for all query keys available and their outputs. Don't remember if a key is serializable or the shape of a key? Just let your IDE show you all information you need.\n\n#### Infer the type of the store's query keys\n```ts\nimport { createQueryKeyStore, inferQueryKeyStore } from \"@lukemorales/query-key-factory\";\n\nexport const queries = createQueryKeyStore({\n  /* ... */\n});\n\nexport type QueryKeys = inferQueryKeyStore\u003ctypeof queries\u003e;\n```\n```ts\n// queries/index.ts\nimport { mergeQueryKeys, inferQueryKeyStore } from \"@lukemorales/query-key-factory\";\n\nimport { users } from './users';\nimport { todos } from './todos';\n\nexport const queries = mergeQueryKeys(users, todos);\n\nexport type QueryKeys = inferQueryKeyStore\u003ctypeof queries\u003e;\n```\n\n#### Infer the type of a feature's query keys\n```ts\nimport { createQueryKeys, inferQueryKeys } from \"@lukemorales/query-key-factory\";\n\nexport const todos = createQueryKeys('todos', {\n  detail: (todoId: string) =\u003e [todoId],\n  list: (filters: TodoFilters) =\u003e ({\n    queryKey: [{ filters }],\n    queryFn: (ctx) =\u003e api.getTodos({ filters, page: ctx.pageParam }),\n  }),\n});\n\nexport type TodosKeys = inferQueryKeys\u003ctypeof todos\u003e;\n```\n\n#### Type your QueryFunctionContext with ease\nGet accurate types of your query keys passed to the `queryFn` context:\n\n```ts\nimport type { QueryKeys } from \"../queries\";\n// import type { TodosKeys } from \"../queries/todos\";\n\ntype TodosList = QueryKeys['todos']['list'];\n// type TodosList = TodosKeys['list'];\n\nconst fetchTodos = async (ctx: QueryFunctionContext\u003cTodosList['queryKey']\u003e) =\u003e {\n  const [, , { filters }] = ctx.queryKey;\n\n  return api.getTodos({ filters, page: ctx.pageParam });\n}\n\nexport function useTodos(filters: TodoFilters) {\n  return useQuery({\n    ...queries.todos.list(filters),\n    queryFn: fetchTodos,\n  });\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukemorales%2Fquery-key-factory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukemorales%2Fquery-key-factory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukemorales%2Fquery-key-factory/lists"}