feat(@pinia/colada): generate InfiniteQuery factory helpers for paginated operations#3846
feat(@pinia/colada): generate InfiniteQuery factory helpers for paginated operations#384650Bytes-dev wants to merge 5 commits intohey-api:mainfrom
Conversation
|
|
|
@50Bytes-dev is attempting to deploy a commit to the Hey API Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: 151ac76 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
TL;DR — Adds Key changes
Summary | 60 files | 5 commits | base: Infinite query factory generation
The generator detects pagination via
Deep-partial override for required-sibling query fields
For nested pagination paths (e.g.
Strict-mode type safety for generated helpers
Review feedback coverage tests
The tests use |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3846 +/- ##
==========================================
+ Coverage 39.58% 42.35% +2.76%
==========================================
Files 532 533 +1
Lines 19581 19670 +89
Branches 5835 5854 +19
==========================================
+ Hits 7751 8331 +580
+ Misses 9582 9175 -407
+ Partials 2248 2164 -84
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ated operations Closes hey-api#3377. Mirrors the TanStack v5 `infiniteQueryOptions` generator inside the `@pinia/colada` plugin so paginated OpenAPI operations emit ready-to-use Pinia Colada infinite-query helpers. Per paginated operation, the generator emits: - `{{name}}InfiniteQueryKey` — typed key factory built on the extended `createQueryKey('id', options, true)` discriminator. - `{{name}}InfiniteQuery` — Option-A factory `(options, init) => DefineInfiniteQueryOptionsTagged<...>`. `init` is a typed `Pick<DefineInfiniteQueryOptions<TData, TError, TPageParam, undefined>, 'initialPageParam' | 'getNextPageParam' | 'maxPages' | 'getPreviousPageParam'>` — required upstream fields are enforced at compile time, optional ones surface in IDE completion. No `@ts-ignore`. Shared helpers: - `createInfiniteParams<K extends Pick<QueryKey<Options>[0], 'body' | 'path' | 'query'>>` — deduped via `plugin.querySymbol`. No `headers` slot (Pinia `QueryKey` excludes headers by design). - Extended `createQueryKey` runtime helper with `infinite?: boolean` parameter and `_infinite?: boolean` slot on the QueryKey type alias. Plugin config: - `infiniteQueryOptions` — `{ enabled: true, name: '{{name}}InfiniteQuery', case, meta? }` (independent from `queryOptions.meta`). - `infiniteQueryKeys` — `{ enabled: true, name: '{{name}}InfiniteQueryKey', case, tags: false }`. Workspace bumps `@pinia/colada` from `0.x` to `^1.2.1` to pick up `defineInfiniteQueryOptions` (added in v1.2.0) and the `DefineInfiniteQueryOptions` type. `defineQueryOptions` shape is stable v0.17.6 → v1.2.1, so existing snapshot output is unchanged except for the additive `_infinite?` / `infinite?` additions. Use-site: ```ts const { data, loadNextPage } = useInfiniteQuery(() => getFooInfiniteQuery(options, { initialPageParam: 0, getNextPageParam: (last) => last.nextCursor, }), ); ```
2d5757d to
709c565
Compare
There was a problem hiding this comment.
Important
Two issues to address before merge: (1) planning docs committed to the repo, (2) version specifiers violate save-exact=true.
The core implementation is solid — it cleanly mirrors the TanStack pattern, the createInfiniteParams helper is well-structured, and the dedup via plugin.querySymbol is correct. The test coverage with both pagination and pagination-disabled scenarios is good. The _infinite discriminator on the shared QueryKey type (always emitted regardless of config) matches TanStack's existing approach for cache-collision prevention.
| @@ -0,0 +1,126 @@ | |||
| --- | |||
There was a problem hiding this comment.
This file and docs/plans/2026-05-02-001-feat-pinia-colada-infinite-query-options-plan.md are AI planning artifacts, not user-facing documentation. They create novel docs/brainstorms/ and docs/plans/ directories that don't exist anywhere else in the repo. Remove both files before merge — the PR description and changeset already capture the design decisions.
| "@opencode-ai/sdk": "1.3.13", | ||
| "@orpc/contract": "1.13.14", | ||
| "@pinia/colada": "0.19.1", | ||
| "@pinia/colada": "^1.2.1", |
There was a problem hiding this comment.
The repo's .npmrc has save-exact=true, so all versions should be exact. This applies to all three package.json files changed in this PR (dev/, examples/openapi-ts-pinia-colada/, packages/openapi-ts-tests/main/).
| "@pinia/colada": "^1.2.1", | |
| "@pinia/colada": "1.2.1", |
Resolves three classes of strict-tsc errors that surfaced in consumer projects after PR hey-api#3846 introduced @pinia/colada InfiniteQuery factories: - TS1016: when options is logically optional, emit `options: Type = {}` instead of `options?:`, so the required `init` parameter no longer follows an optional one. - TS2345: type the per-request page shape as `Partial<Pick<Options<TData>, 'body' | 'path' | 'query'>>` (operation data type instead of the broad QueryKey shape) and loosen the `createInfiniteParams` K bound to `Pick<Options, ...>`. Spread of `...params` into the SDK call now typechecks even when body/path are `never`, while Partial allows the wrap literal to omit fields already filled in by `options`. - TS2322: replace the `typeof pageParam === 'object'` narrowing with a structural guard (`'body' in pageParam || 'path' in pageParam || 'query' in pageParam`), and cast the leaf pageParam to the pagination schema's emitted type so unions like `Date | null` and `null` no longer leak into the page-object branch. Also fixes a runtime correctness bug for nested pagination params: a parameter like `foo` whose pagination keyword lives on its inner schema (`foo.page`) now generates `{ query: { foo: { page: pageParam } } }` instead of a literal dotted key `'foo.page'`. Extend `specs/3.1.x/pagination-ref.yaml` with `getAlbums` (optional integer cursor) and `getProducts` (optional date-time cursor) so the snapshot tests cover both flat and primitive-with-null pagination shapes alongside the pre-existing nested `foo.page` case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nfinite-query-options # Conflicts: # pnpm-lock.yaml
- Pin @pinia/colada to "1.2.1" (drop caret) in dev/, example, and test packages to match repo-wide save-exact policy in .npmrc - Regenerate examples/openapi-ts-pinia-colada client to keep examples:check in sync after the merge from main - Add unit tests covering @pinia/colada infiniteQueryOptions generation paths (numeric/string cursor, nested ref pagination, mixed ops, disabled flag) — lifts patch coverage from ~3% to ~91% Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hey-api/codegen-core
@hey-api/json-schema-ref-parser
@hey-api/nuxt
@hey-api/openapi-ts
@hey-api/shared
@hey-api/spec-types
@hey-api/types
@hey-api/vite-plugin
commit: |
…deep-partial inline literal
Generated InfiniteQuery helpers used `Partial<Pick<Options<TData>, 'body' | 'path' | 'query'>>`
as the page-override type, which only opt-out the top level. When `query` (or `body`/`path`)
carried a required sibling alongside the pagination param (e.g. `tenantId` next to `offset`,
`client_subscription_id` next to `offset`), TS rejected the override literal with TS2322
because the inner shape stayed strict. The previous fix worked around this with an
`as unknown as Partial<Pick<...>>` cast — semantically a lie that also failed to help users
returning partial overrides from `getNextPageParam` callbacks.
Switch to an inline deep-partial literal — `{ body?: Partial<O['body']>; path?: Partial<O['path']>;
query?: Partial<O['query']> }` — at all three use sites (typePageObjectParam, page const annotation,
DefineInfiniteQueryOptions TPageParam generic). The inline form keeps the override-type private to
each generated file (no new exported helper). `createInfiniteParams` gains a `TData extends Options`
generic and an explicit `Pick<TData, 'body' | 'path' | 'query'>` return type so its output stays
strict for the SDK call while inputs remain permissive. The cast survives only for nested
pagination paths (e.g. `foo.page` where `foo` is a required object) where deep-partial alone can't
reach the inner level.
Adds in-source regression test using tmpdir + fs.readFileSync to assert the deep-partial form is
present and no `as unknown as Partial<Pick<` / `@ts-ignore` slips into pinia output. Adds /orders
operation to specs/3.1.x/pagination-ref.yaml exercising the required-sibling case end-to-end via
the existing snapshot tests. Extends the existing changeset for this branch with a 6th bullet
covering the change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Closes #3377.
Summary
Mirrors the TanStack v5
infiniteQueryOptionsgenerator inside the@pinia/coladaplugin so paginated OpenAPI operations emit ready-to-use Pinia Colada infinite-query helpers. Closes the parity gap with the TanStack family of plugins.Per paginated operation the generator emits:
{{name}}InfiniteQueryKey— typed key factory built on the extendedcreateQueryKey('id', options, true)discriminator.{{name}}InfiniteQuery— Option-A factory(options, init) => DefineInfiniteQueryOptionsTagged<...>.initis a typedPick<DefineInfiniteQueryOptions<TData, TError, TPageParam, undefined>, 'initialPageParam' | 'getNextPageParam' | 'maxPages' | 'getPreviousPageParam'>— required upstream fields are enforced at compile time, optional ones surface in IDE completion. No@ts-ignoreanywhere in the emitted output.Shared once per output (deduped via
plugin.querySymbol):createInfiniteParams<K extends Pick<QueryKey<Options>[0], 'body' | 'path' | 'query'>>mergesbody/path/queryfrom the queryKey with the per-page object. Noheadersslot — PiniaQueryKeyexcludes headers by design (packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts).createQueryKeyruntime helper withinfinite?: booleanparameter and_infinite?: booleanslot on theQueryKeytype alias to prevent cache collisions between regular and infinite entries.Plugin config
Both blocks accept
boolean/string/functionshorthands matching the existingqueryOptions/queryKeysconventions. Defaults:enabled: true, naming{{name}}InfiniteQuery/{{name}}InfiniteQueryKey.Use-site
The two values OpenAPI cannot derive (
initialPageParam,getNextPageParam) are enforced at compile time via theinitPick. Auto-generation of these values from response schema is documented as a follow-up under "Future Enhancements" in the plan (industry prior-art: Speakeasyx-speakeasy-pagination, Stainlessx-stainless-pagination-property, APIMaticx-pagination).Workspace bumps
@pinia/colada0.x→^1.2.1in:dev/package.jsonexamples/openapi-ts-pinia-colada/package.jsonpackages/openapi-ts-tests/main/package.jsonRequired for
defineInfiniteQueryOptions(added in v1.2.0, April 17 2026) and theDefineInfiniteQueryOptionstype.defineQueryOptionsshape is stable v0.17.6 → v1.2.1, so existing snapshot output is unchanged except for the additive_infinite?/infinite?additions.Snapshot impact
Existing
@pinia/colada/fetchand@pinia/colada/asClasssnapshots gain:_infinite?: booleanoptional prop on theQueryKeytype alias.infinite?: booleanoptional parameter on thecreateQueryKeyruntime helper.full.yaml.Two new scenarios target the existing
specs/3.1.x/pagination-ref.yamlfixture:pagination— verifies factory shape, externals,createInfiniteParamsdedup, and absence of@ts-ignore.pagination-disabled— verifies thatinfiniteQueryOptions: falseomits all infinite output while regular query/mutation output is untouched.Test plan
pnpm installclean.pnpm ty -- @hey-api/openapi-tsclean.pnpm tt(vitest projects@hey-api/openapi-ts+@test/openapi-ts): 880 tests pass, 1 skipped.pnpm lintclean (oxfmt + eslint, husky lint-staged ran on commit).key, noheadersslot, no@ts-ignore, staticdefineInfiniteQueryOptions({...})call.Post-Deploy Monitoring & Validation
No additional operational monitoring required — this is build-time codegen only, no runtime/server impact. Generated code uses
throwOnError: trueto propagate SDK errors via Pinia Colada's normal error pipeline. Consumers on@pinia/colada< 1.2.0will hit a missing-export error at typecheck/build time; the changeset documents the version requirement andinfiniteQueryOptions: falseprovides an opt-out for users not yet ready to upgrade.Verification at user-side: monitor for issue reports referencing missing
defineInfiniteQueryOptionsimport (indicates user is on@pinia/colada< 1.2.0) — direct them to the changeset or the opt-out flag.