{"_id":"@arkida39/effective-query-kit","_rev":"2-9ef97516dd760896e0b4efc7e1a9cae8","name":"@arkida39/effective-query-kit","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@arkida39/effective-query-kit","version":"1.0.0","keywords":["tanstack","tanstack-query","query","query-keys"],"author":{"name":"Daniil Kim","email":"248118208+arkida39@users.noreply.github.com"},"license":"MIT","_id":"@arkida39/effective-query-kit@1.0.0","maintainers":[{"name":"arkida39","email":"arkida39+npmjs@gmail.com"}],"homepage":"https://github.com/arkida39/effective-query-kit#readme","bugs":{"url":"https://github.com/arkida39/effective-query-kit/issues"},"dist":{"shasum":"c9692fca69f1373c4a89f507caa4ca81c7e8e527","tarball":"https://registry.npmjs.org/@arkida39/effective-query-kit/-/effective-query-kit-1.0.0.tgz","fileCount":9,"integrity":"sha512-ixaWQp9sBfjtiimX6Qiyjgi3PtJHjKlb5M6mZEzQuXkRA0DReuwcRbSRiTxAWF7cUs+OnjgrCkDytP/Z4esNqg==","signatures":[{"sig":"MEUCIHcFGTp3OCgJ9ey4QS1koRgeza1Un2tjxxepGzoxuzNzAiEAoQnOtw5iIDLi50+xH7KinUsItdSuSRpleRc/yaj2F8s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arkida39%2feffective-query-kit@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":55081},"main":"./dist/index.cjs","type":"module","_from":"file:arkida39-effective-query-kit-1.0.0.tgz","types":"./dist/index.d.cts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest","build":"tsdown","check":"biome check --write .","release":"pnpm run build && changeset publish","test:ci":"vitest run --reporter=verbose","version":"changeset version","test:run":"vitest run","validate":"pnpm run check && pnpm run typecheck && pnpm run attwcheck && pnpm run test:run","attwcheck":"attw --pack .","changeset":"changeset","typecheck":"tsc --noEmit"},"_npmUser":{"name":"arkida39","email":"arkida39+npmjs@gmail.com"},"_resolved":"/tmp/8216827562820cbf5cecfd83c0a9c98c/arkida39-effective-query-kit-1.0.0.tgz","_integrity":"sha512-ixaWQp9sBfjtiimX6Qiyjgi3PtJHjKlb5M6mZEzQuXkRA0DReuwcRbSRiTxAWF7cUs+OnjgrCkDytP/Z4esNqg==","repository":{"url":"git+https://github.com/arkida39/effective-query-kit.git","type":"git"},"_npmVersion":"10.9.7","description":"A typesafe key schema builder for @tanstack/query. Inspired by TkDodo's \"Effective Query Keys\" pattern.","directories":{},"_nodeVersion":"22.22.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.21.7","vitest":"^4.1.2","publint":"^0.3.18","prettier":"3.8.2","typescript":"^6.0.2","@types/node":"^25.5.0","@biomejs/biome":"^2.4.12","@changesets/cli":"^2.31.0","@faker-js/faker":"^10.4.0","@tanstack/query-core":"^5.99.2","@arethetypeswrong/cli":"^0.18.2","@typescript/native-preview":"7.0.0-dev.20260328.1","@changesets/changelog-github":"^0.6.0"},"peerDependencies":{"@tanstack/query-core":">=5.0.0"},"peerDependenciesMeta":{"@tanstack/query-core":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/effective-query-kit_1.0.0_1777289115721_0.23794172147555792","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@arkida39/effective-query-kit","type":"module","version":"1.0.1","description":"A typesafe key schema builder for @tanstack/query. Inspired by TkDodo's \"Effective Query Keys\" pattern.","author":{"name":"Daniil Kim","email":"248118208+arkida39@users.noreply.github.com"},"license":"MIT","publishConfig":{"access":"public"},"keywords":["tanstack","tanstack-query","query","query-keys"],"homepage":"https://github.com/arkida39/effective-query-kit#readme","repository":{"type":"git","url":"git+https://github.com/arkida39/effective-query-kit.git"},"bugs":{"url":"https://github.com/arkida39/effective-query-kit/issues"},"peerDependencies":{"@tanstack/query-core":">=5.0.0"},"peerDependenciesMeta":{"@tanstack/query-core":{"optional":false}},"devDependencies":{"@arethetypeswrong/cli":"^0.18.2","@biomejs/biome":"^2.4.12","@changesets/changelog-github":"^0.6.0","@changesets/cli":"^2.31.0","@faker-js/faker":"^10.4.0","@tanstack/query-core":"^5.99.2","@types/node":"^25.5.0","@typescript/native-preview":"7.0.0-dev.20260328.1","prettier":"3.8.2","publint":"^0.3.18","tsdown":"^0.21.7","typescript":"^6.0.2","vitest":"^4.1.2"},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.cts","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"build":"tsdown","dev":"tsdown --watch","test":"vitest","test:run":"vitest run","test:ci":"vitest run --reporter=verbose","check":"biome check --write .","attwcheck":"attw --pack .","typecheck":"tsc --noEmit","changeset":"changeset","version":"changeset version","release":"pnpm run build && changeset publish","validate":"pnpm run check && pnpm run typecheck && pnpm run attwcheck && pnpm run test:run"},"_id":"@arkida39/effective-query-kit@1.0.1","_integrity":"sha512-9IujWSqKwBliw1ySmRWPdLW+4obpZ9F/tHRqzWIdBY3lu6seyzz89muVkgciMe4U7p0fb9xy60/kBARMZ+XaAw==","_resolved":"/tmp/d597df9d9ebd147f2702f395e53099c7/arkida39-effective-query-kit-1.0.1.tgz","_from":"file:arkida39-effective-query-kit-1.0.1.tgz","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-9IujWSqKwBliw1ySmRWPdLW+4obpZ9F/tHRqzWIdBY3lu6seyzz89muVkgciMe4U7p0fb9xy60/kBARMZ+XaAw==","shasum":"6d527e4e09b54dd6b2de6b9f593ddc6854eb2b34","tarball":"https://registry.npmjs.org/@arkida39/effective-query-kit/-/effective-query-kit-1.0.1.tgz","fileCount":9,"unpackedSize":55115,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arkida39%2feffective-query-kit@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFpeBQn2lyyNmtVHGX40IgryhZqG+ad6Q7UZdpnhKnwGAiAaaynEG0iyoLGH9HpVh3Byyz+Srxd+O85NkaTGYaHu8w=="}]},"_npmUser":{"name":"arkida39","email":"arkida39+npmjs@gmail.com"},"directories":{},"maintainers":[{"name":"arkida39","email":"arkida39+npmjs@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/effective-query-kit_1.0.1_1777367303658_0.4729837935677632"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-27T11:25:15.583Z","modified":"2026-04-28T09:08:24.357Z","1.0.0":"2026-04-27T11:25:15.871Z","1.0.1":"2026-04-28T09:08:23.798Z"},"bugs":{"url":"https://github.com/arkida39/effective-query-kit/issues"},"author":{"name":"Daniil Kim","email":"248118208+arkida39@users.noreply.github.com"},"license":"MIT","homepage":"https://github.com/arkida39/effective-query-kit#readme","keywords":["tanstack","tanstack-query","query","query-keys"],"repository":{"type":"git","url":"git+https://github.com/arkida39/effective-query-kit.git"},"description":"A typesafe key schema builder for @tanstack/query. Inspired by TkDodo's \"Effective Query Keys\" pattern.","maintainers":[{"name":"arkida39","email":"arkida39+npmjs@gmail.com"}],"readme":"<h1 align=\"center\">\n    Effective Query Kit\n</h1>\n\n<p align=\"center\">\nA typesafe key schema builder for <a href=\"https://tanstack.com/query\" target=\"\\_parent\">@tanstack/query</a>. Inspired by TkDodo's \"Effective Query Keys\" pattern.\n</p>\n\n## Install\n\n```bash\nnpm install @arkida39/effective-query-kit\n```\n\n## Quick Start\n\nStart by defining the schema:\n\n```ts\nimport { defineQuerySchema } from \"@arkida39/effective-query-kit\";\n\nexport const queries = defineQuerySchema((b) => ({\n  users: b.group({\n    me: b.leaf(),\n    profile: b.param((id: number) => ({ id })),\n  }),\n  todos: b.group(\n    { recent: b.leaf() },\n    b.param((id: number) => ({ id }), {\n      details: b.leaf(),\n      comments: b.param((page: number) => ({ page })),\n    }),\n  ),\n}));\n```\n\nUsage:\n\n```ts\n// Static keys\n\nq.users.me.$key\n// ↳ ['users', 'me']\n\nq.todos.$key\n// ↳ ['todos']\n\nq.todos.recent.$key\n// ↳ ['todos', 'recent']\n```\n\n```ts\n// Parameterized keys\n\nq.users.profile({ id: 1 }).$key\n// ↳ ['users', 'profile', { id: 1 }]\n```\n\n```ts\n// Collection keys\n\nq.todos.$entity({ id: 2 }).$key\n// ↳ ['todos', { id: 2 }]\n\nq.todos.$entity({ id: 2 }).details.$key \n// ↳ ['todos', { id: 2 }, 'details']\n\nq.todos.$entity({ id: 2 }).comments({ page: 2 }).$key  \n// ↳ ['todos', { id: 2 }, 'comments', { page: 2 }]\n```\n\n## DSL\n\nThe DSL has 3 constructors that are used to compose the schema.\n\n### `b.leaf()`\n \nA terminal static key.\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  health: b.leaf(),\n}))\n \nq.health.$key\n// ↳ ['health']\n```\n\n### `b.param(build, children?, options?)`\n \nA parameterized node. The `build` function declares the parameters shape via its return type.\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  user: b.param((id: number) => ({ id })),\n}))\n \nq.user({ id: 1 }).$key\n// ↳ ['user', { id: 1 }]\n```\n\nWith children, it becomes a scope - children are nested under the parameterized prefix:\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  post: b.param((id: number) => ({ id }), {\n    comments: b.param((page: number) => ({ page })),\n    details: b.leaf(),\n  }),\n}))\n \nq.post({ id: 1 }).details.$key\n// ↳ ['post', { id: 1 }, 'details']\nq.post({ id: 1 }).comments({ page: 2 }).$key\n// ↳ ['post', { id: 1 }, 'comments', { page: 2 }]\n```\n\n### `b.group(children, entity?)`\n \nA static grouping. Children share the group's prefix. Groups are just namespaces.\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  settings: b.group({\n    theme: b.leaf(),\n    notifications: b.leaf(),\n  }),\n}))\n \nq.settings.$key\n// ↳ ['settings']\nq.settings.theme.$key\n// ↳ ['settings', 'theme']\nq.settings.notifications.$key\n// ↳ ['settings', 'notifications']\n```\n \nPass a `b.param(...)` as the second argument to create a **collection** - a group that's both fetchable as a whole and has parameterized entity access via `$entity(...)`:\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  todos: b.group(\n    { recent: b.leaf() },\n    b.param((id: number) => ({ id }), {\n      details: b.leaf(),\n    }),\n  ),\n}))\n \nq.todos.$key\n// ↳ ['todos'] - whole collection\nq.todos.recent.$key\n// ↳ ['todos', 'recent'] - static child\nq.todos.$entity({ id: 1 }).$key\n// ↳ ['todos', { id: 1 }] - particular entity\nq.todos.$entity({ id: 1 }).details.$key\n// ↳ ['todos', { id: 1 }, 'details'] - dynamic child\n```\n\n## Object-wrapped key segments\n \nParameter values are wrapped as `{ key: value }` objects in the query key, not spread as bare values. This prevents ambiguity between path segments and parameter values:\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  todos: b.group(\n    { recent: b.leaf() },\n    b.param((board: string, id: number) => ({ board, id }), {\n      details: b.leaf(),\n    }),\n  ),\n}))\n\n\nq.todos.recent.$key\n// ↳ ['todos', 'recent'] - two path segments\nq.todos.$entity({ board: 'a', id: 1 }).$key\n// ↳ ['todos', { board: 'a' }, { id: 1 }] - path + param + param\nq.todos.$entity({ board: 'a', id: 1 }).details.$key\n// ↳ ['todos', { board: 'a' }, { id: 1 }, 'details'] - path + param + param + path\n```\n\n## Parameter extraction with `$params`\n \nEvery parameterized schema node has a `$params(ctx)` method that extracts the typed parameters object from a `QueryFunctionContext`:\n\n```ts\nconst q = defineQuerySchema((b) => ({\n  users: b.group({\n    profile: b.param((id: number) => ({ id })),\n  }),\n  todos: b.group(\n    { recent: b.leaf() },\n    b.param((board: string, id: number) => ({ board, id }), {\n      details: b.leaf(),\n    }),\n  ),\n}))\n\nqueryOptions({\n    // ...\n    queryFn: (ctx) => {\n        // On a param node - no need to call with parameters first\n        const parameters = q.users.profile.$params(ctx)\n        // ↳ { id: number }\n        // ...\n    }\n    // ...\n})\n\nqueryOptions({\n    // ...\n    queryFn: (ctx) => {\n        // On a collection - extracts entity parameters\n        const parameters = q.todos.$params(ctx)\n        // ↳ { board: string, id: number }\n        // ...\n    }\n    // ...\n})\n```\n\n## Query options with `$queryOptions`\n \nEvery fetchable entry (leaf, param, collection) exposes `$queryOptions(fetcher)`, which returns `{ queryKey, queryFn }` ready to be used in `useQuery`:\n \n```ts\n// Static entry - fetcher receives (QueryFunctionContext)\nuseQuery(\n  q.users.me.$queryOptions(async (context) => {\n    const res = await fetch('/api/me')\n    return res.json()\n  }),\n)\n \n// Dynamic entry - fetcher receives (QueryFunctionContext, params)\nuseQuery(\n  q.todos.$entity({ board: 'a', id: 1 }).$queryOptions(async (context, { board, id }) => {\n    const res = await fetch(`/api/todos/${board}-${id}`)\n    return res.json()\n  }),\n)\n \n// Spread with additional options\nuseQuery({\n  ...q.todos.$entity({ board: 'a', id: 1 }).$queryOptions(fetchTodo),\n  staleTime: 60_000,\n})\n```\n\n## Param propagation\n \nBy default, all parameters, except the ones that start with `_`, from a scope propagate into children's keys. Use `propagate` predicate to override this behavior:\n \n```ts\nconst q = defineQuerySchema((b) => ({\n  sessions: b.param(\n    (userId: string, token: string) => ({ userId, token }),\n    { activity: b.leaf() },\n    { propagate: (key) => key !== 'token' },\n  ),\n}))\n \nconst s = q.sessions({ userId: 'u_1', token: 't_abc' })\ns.$key\n// ↳ ['sessions', { userId: 'u_1' }, { token: 't_abc' }]\ns.activity.$key\n// ↳ ['sessions', { userId: 'u_1' }, 'activity'] - 'token' excluded from children\n```\n \nThe scope's own `$key` always contains every parameter. `propagate` only affects what children inherit.\n\n## Modular schemas\n \nDefine schemas per feature and merge with spread:\n \n```ts\n// features/users.ts\nexport const users = defineQuerySchema((b) => ({\n  users: b.group({\n    me: b.leaf(),\n    profile: b.param((id: number) => ({ id })),\n  }),\n}))\n \n// features/todos.ts\nexport const todos = defineQuerySchema((b) => ({\n  todos: b.group(\n    { recent: b.leaf() },\n    b.param((id: number) => ({ id })),\n  ),\n}))\n \n// queries.ts\nimport { users } from './features/users'\nimport { todos } from './features/todos'\n \nexport const q = { ...users, ...todos }\n```\n \nTypes merge via intersection. No special utility needed.\n\n## License\n\nLicensed under the [MIT license](https://github.com/arkida39/effective-query-kit/blob/main/LICENSE).","readmeFilename":"README.md"}