Skip to content

fix: isolate tuple searchParams in init hooks#861

Merged
sindresorhus merged 1 commit into
sindresorhus:mainfrom
atharvasingh7007:codex/fix-init-searchparams-tuple-clone
Apr 21, 2026
Merged

fix: isolate tuple searchParams in init hooks#861
sindresorhus merged 1 commit into
sindresorhus:mainfrom
atharvasingh7007:codex/fix-init-searchparams-tuple-clone

Conversation

@atharvasingh7007

Copy link
Copy Markdown
Contributor

Summary

  • isolate searchParams tuple arrays before init hooks mutate them
  • prevent tuple edits from leaking into later requests on the same instance
  • add a regression test covering the supported [[key, value]] search params form

Root cause

cloneInitHookOptions() is supposed to give init hooks a request-local mutable copy of the options, but searchParams in tuple-array form only got a shallow outer array copy. Mutating an existing tuple like options.searchParams[0][1] = ... changed the shared default tuple, so the next request inherited the previous hook mutation.

Fix

  • add a cloneSearchParametersForInitHook() helper that deep-copies tuple arrays while preserving the other supported searchParams input forms
  • keep the existing cloning behavior for strings, objects, and URLSearchParams
  • add an end-to-end init-hook regression that proves tuple-array defaults no longer leak between requests

Testing

  • npm run build
  • npx ava test/hooks.ts --match "init hook*do not leak across requests"
  • npx ava test/hooks.ts --match "init hook tuple searchParams mutations do not leak across requests"

Notes

npm test in this Windows checkout still hits upstream xo project-service parsing errors for the repo's test files before it reaches AVA. The targeted hook tests and build above passed locally.

@sindresorhus sindresorhus merged commit 346f898 into sindresorhus:main Apr 21, 2026
3 checks passed
productdevbook added a commit to productdevbook/misina that referenced this pull request Apr 25, 2026
…sers

Inspired by ky [PR #849](sindresorhus/ky#849).
The parseJson option now receives an optional second argument with the
request and response, so a single shared parser can route on URL or
content-type:

\`\`\`ts
createMisina({
  parseJson: (text, ctx) => {
    if (ctx?.response.headers.get('x-format') === 'bigint') {
      return JSON.parse(text, bigintReviver)
    }
    return JSON.parse(text)
  },
})
\`\`\`

The previous one-arg signature still works — the second arg is optional
and the existing tests (which pass a one-arg callback) continue to pass.

### test/init-isolation.test.ts
Two new tests covering ky [PR #861](sindresorhus/ky#861
spirit: per-request init hook isolation.
- Concurrent requests don't share a counter through options.headers.
- A defaults.headers value mutated inside init doesn't compound across
  later requests.

Both pass — verifies that resolveOptions() builds a fresh per-request
options object each time, so default state isn't leaked or compounded.

### test/parsejson-context.test.ts
Two new tests for the new signature:
- ctx?.request.url and ctx?.response.headers.get(...) are observable.
- The original one-arg form keeps working.

117/117 tests pass; lint, typecheck, build, bundle budget all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants