← Files stark AI DeveloperARCHIVED FILE
skills/codex-spec-interviewer/assets/example-repo-refactor.spec.md
9.96 KB · Oct 2, 2026 · 00:31 UTC
---
title: "Replace legacy fetchJson wrapper with typed apiClient across the repo"
slug: "typed-api-client-repo-refactor"
artifact_path: "docs/specs/typed-api-client-repo-refactor-spec.md"
mode: "deep"
status: "draft"
owner: "platform-foundation"
repo: "monorepo"
created: "2026-05-21"
updated: "2026-05-21"
source_request: "Refactor the repo to use the new typed API client instead of the old fetch wrapper"
phases: ["phase-1-safe-adoption", "phase-2-full-cutover"]
---
# Replace legacy fetchJson wrapper with typed apiClient across the repo
## Goal
Standardize HTTP access on the new typed `apiClient` abstraction to improve type safety, error consistency, retry handling, and maintainability, while minimizing regression risk during the migration.
## Background
The repository currently uses a mixture of `fetchJson(...)`, ad hoc `fetch(...)`, and service-local wrappers. This creates inconsistent error handling, duplicated headers, and weak typing at call sites. The new `apiClient` already exists but is only partially adopted.
## Scope
### In scope
- migrate direct `fetchJson(...)` call sites to `apiClient`
- align common error handling on the `apiClient` response/error model
- update affected tests and mocks
- preserve current user-visible behavior
### Non-goals
- redesign backend APIs
- rename domain models unrelated to the migration
- perform unrelated cleanup in touched files
- replace transport behavior that is intentionally service-specific
## Repo context
- Relevant packages/services/modules:
- `packages/api-client/`
- `apps/web/`
- `apps/admin/`
- `packages/test-utils/`
- Current architecture and seams:
- shared HTTP utilities live in `packages/api-client`
- some apps still import `fetchJson` from legacy utility modules
- CI/build/test expectations:
- lint, typecheck, unit tests, and affected integration tests must pass
- Unknowns:
- full inventory of remaining `fetchJson` usage is unspecified before code search
## Requirements
### Functional requirements
- WHEN a module currently uses `fetchJson(...)`, THE SYSTEM SHALL migrate it to `apiClient` unless a documented exception applies.
- WHEN a migrated call previously returned typed domain data, THE SYSTEM SHALL preserve the same effective runtime behavior while improving compile-time typing.
- IF the old call path relied on custom headers, auth propagation, or retries, THEN THE SYSTEM SHALL preserve that behavior after migration.
- IF a call site cannot be safely migrated in this pass, THEN THE SYSTEM SHALL be left unchanged and documented as deferred.
### Non-functional requirements
- Performance: no materially worse request fan-out or duplicate calls.
- Reliability: preserve timeout/retry behavior where it already exists.
- Security/privacy: preserve auth headers, CSRF handling, and redaction rules.
- Observability: preserve or improve request error logging.
- Backward compatibility: no user-visible API contract changes.
## Design
### Proposed architecture
- `apiClient` becomes the default shared abstraction.
- Legacy `fetchJson` remains only as a temporary compatibility shim during the migration window.
- Shared test helpers and mocks are updated first so downstream migrations become cheaper.
- Service-specific adapters may wrap `apiClient` if a domain needs typed helper methods.
### Alternatives considered
- Big-bang removal of all legacy wrappers in one PR:
- rejected because review size and regression risk are too high.
- Permanent dual-stack support:
- rejected because it keeps error semantics inconsistent.
- Per-app independent migration strategies:
- rejected because shared consistency is the point of the refactor.
## Architectural decisions
- ADR required: yes, if `apiClient` is not already the accepted shared HTTP abstraction.
- Existing ADRs consulted: check for API-client or transport ADR before implementation.
- ADR draft or path: `docs/adrs/NNNN-use-typed-api-client.md` if no accepted ADR exists.
- Supersedes: any accepted ADR that requires permanent legacy `fetchJson` usage.
- Implementation blocked until ADR accepted: yes for repo-wide cutover; no for inventory-only work.
## Source challenge
- Repo evidence checked:
- inventory current `fetchJson(...)` call sites before deciding migration phases
- inspect `apiClient` behavior, shared mocks, and test utilities before replacing call sites
- ADRs/specs checked:
- check for any API-client or transport ADR before finalizing the migration contract
- External docs checked:
- check current framework/runtime fetch behavior only if `apiClient` depends on changed platform semantics
- Requirements revised:
- defer call sites with undocumented service-specific behavior
- Requirements preserved:
- preserve auth, headers, retry behavior, and user-visible runtime behavior
- Preceding ADR/spec work needed:
- add an ADR or migration note if `apiClient` is not already the accepted default abstraction
- ADR gate result:
- unresolved until existing ADRs and `apiClient` ownership are checked
- Skipped checks and why:
- package-specific external docs are unnecessary until the concrete `apiClient` implementation is inspected
## User verification
- Final checkpoint confirmed by: platform owner
- Confirmation date: 2026-05-21
- Verified scope/non-goals:
- migrate safe call sites, preserve runtime behavior, and defer service-specific exceptions
- Verified rollout/rollback assumptions:
- cutover proceeds in phases and implementation remains blocked where a required ADR is unresolved
- Non-blocking open questions accepted:
- exact call-site inventory is completed during phase 1 before code changes
## File and module plan
### Expected touched areas
- `packages/api-client/**`
- `packages/test-utils/**`
- `apps/web/**`
- `apps/admin/**`
### Expected new files
- migration inventory note if needed:
- `docs/adrs/` or `docs/migrations/typed-api-client.md`
- targeted helpers/tests only if required
### Explicitly protected areas
- backend services
- public API schema definitions
- unrelated UI state management
## Artifact plan
- Spec path: `docs/specs/typed-api-client-repo-refactor-spec.md`
- Destination basis: existing `docs/specs/` convention; ADR path requires confirmation if a new ADR is needed
- Explicit confirmation needed: yes, for ADR creation or ambiguous API-client ownership
- Spec persistence: saved
- Existing file overwrite needed: no
- ADR paths:
- `docs/adrs/NNNN-use-typed-api-client.md` if no existing ADR covers the default transport abstraction
- ADR persistence: blocked until existing ADR coverage and API-client ownership are confirmed
- ADR index updates needed: yes when a new ADR is saved
- Companion execution prompt path or embedding: embed with saved spec unless the repo already stores execution prompts separately
## Task breakdown
### Phase 1
- inventory all `fetchJson` call sites
- migrate shared mocks/helpers to support `apiClient`
- migrate low-risk leaf modules first
- add lint rule or grep-based guard to prevent new `fetchJson` usage
Validation gate:
- typecheck passes
- migrated leaf modules pass tests
- no net-new legacy call sites appear
### Phase 2
- migrate remaining app call sites module by module
- preserve service-specific behaviors with adapters where necessary
- leave deferred exceptions documented with reasons
- reduce legacy wrapper to compatibility-only or remove if zero usages remain
Validation gate:
- full test suite passes
- search confirms expected usage count
- diff review confirms no unrelated scope expansion
## Validation
```bash
pnpm lint
pnpm typecheck
pnpm test
rg "fetchJson\\(" .
```
### Manual verification
- Verify login/authenticated flows still send expected auth context.
- Verify representative success and error flows in web and admin apps.
- Verify test mocks and fixtures still match runtime request shape.
### Review focus
- silent error-shape drift
- auth/header propagation regressions
- duplicated requests or retry regressions
- accidental scope creep in touched files
## Rollout and rollback
- Rollout strategy:
- merge in phases, not one giant refactor
- Feature flag:
- not required unless runtime path selection is introduced
- Data migration/backfill:
- none expected
- Monitoring during rollout:
- watch request error rates and auth-related failures
- Rollback trigger:
- elevated request failures, auth regressions, or broad integration breakage
- Rollback procedure:
- revert the current migration slice, keep already-stable shared helper changes if isolated and safe
## Risks
| Risk | Why it matters | Mitigation |
| ---------------------------------------- | --------------------------------------------------------------- | --------------------------------------------- |
| hidden service-specific wrapper behavior | a naive replacement can break retries/headers/error translation | inventory and preserve behavior before swap |
| too-large PRs | review quality and regression risk collapse | phase the migration by package or module |
| mock drift | tests pass for the wrong reason or start failing everywhere | migrate shared test utilities first |
| partial cutover confusion | engineers may keep adding legacy calls | add a guardrail and document the default path |
## Done when
- [ ] All planned modules are migrated or explicitly deferred
- [ ] No unintended user-visible behavior changed
- [ ] Validation commands pass
- [ ] Remaining exceptions are documented
- [ ] Legacy abstraction is either compatibility-only or removed where safe
## Assumptions and open questions
- Assumption: `apiClient` already exists and is preferred by maintainers.
- Assumption: phase-based migration is acceptable.
- Open question: whether a repo-level lint guard already exists for legacy wrapper usage.
- Open question: whether any package intentionally depends on fetch-specific semantics that `apiClient` does not yet expose.
SHA-256: 4cbc5a2776256214c151834d1381bb9d8489fd0be434819dcb592aec3e313d46