- name
- migrate-to-tanstack
- description
- Migrate a data-fetching endpoint from the legacy ExtensionRegistryService + fetch-retry pattern to a TanStack Query hook. Only do this when the user explicitly asks — it is an opt-in, in-progress migration. Pairs with the tanstack-query-conventions skill.
# Migrating an endpoint to TanStack Query
**Only run this when the user explicitly asks to migrate an endpoint.** The migration is in progress and opt-in — never convert endpoints as a side effect of other work.
## The current picture
`src/extension-registry-service.ts` (`ExtensionRegistryService`, plus `service.admin.*`) holds every server call. Legacy, un-migrated consumers call these methods straight from a component, passing an `AbortController` and relying on `fetch-retry`'s 10-attempt backoff inside `sendRequest`. That drags along per-component `AbortController` refs, `useEffect` fetch-on-mount wiring, and hand-rolled loading/error state.
Migrated endpoints instead go through a `use*` hook wrapping `useQuery`/`useMutation`, retries move to the shared query client (`src/query-client.ts`), and error handling moves into the transport via `sendStrictRequest`. Roughly half the service is migrated — grep before assuming either state.
## Steps
1. **Find every consumer** of the method you're migrating: `grep -rn "service\.<method>\|\.<method>(" src`. List them — you'll migrate all of them or a named subset.
2. **Decide the retry scope — ask if unsure.** Ideally the service method flips from `sendRequest` (retriable) to `sendStrictRequest`, handing retries to TanStack. Only do that when **every** consumer is moving to a hook — a legacy consumer still calling the method directly would silently lose its retry *and* start seeing rejections where it used to get a resolved error result. If you're migrating just one of several consumers, either leave the method retriable (the query then double-retries, tolerated in the interim) or confirm scope with the user. When the request doesn't make the consumer scope clear, ask.
3. **Adjust the service method.**
- Switch it to `sendStrictRequest` and **drop `| ErrorResult` from its return type** (`Promise<Readonly<SuccessResult | ErrorResult>>` → `Promise<Readonly<SuccessResult>>`). The method now resolves with data or rejects; the hook needs no `isError` check, and consumers lose their `as SuccessResult` casts.
- Query methods: keep the `AbortController` param — the hook passes `controllerFromSignal(signal)`.
- Mutation methods: **drop the `AbortController` param** — we no longer abort writes.
4. **Create the hook** — shape and naming per the `tanstack-query-conventions` skill. Co-locate it in the feature's folder first; move to `src/hooks/` only when a second place needs it. The hook returns the react-query result object **as-is**, never just `data` or a picked subset.
5. **Update the consumers.** Replace the `AbortController` / `useEffect` / manual-state boilerplate with the hook, destructuring and renaming its result (`const { data: user, error: userError } = ...`; `const { mutateAsync, isPending } = ...`). Delete the dead boilerplate.
6. **Fix the tests that stubbed the old contract.** A spec stubbing the service method with `mockResolvedValue({ error: '…' })` was standing in for the old resolve-an-error-result behaviour — flip it to `mockRejectedValue({ error: '…' })`. `sendStrictRequest` itself is covered once, in `test/unit/server-request.spec.ts`; don't re-test it per endpoint.
7. **Finish per the `write-code` skill:** add or update tests (`write-tests`), add a changelog entry, and pass `yarn lint`.
## Don't
- Don't strip `fetch-retry` from `sendRequest` globally — that's the final cleanup once the whole migration is done, and un-migrated direct callers still depend on it. Keep the 429 / `Retry-After` block regardless.
- Don't migrate endpoints nobody asked you to.
Voir sur GitHub