Skip to content

feat(node): add clone() support to ImpitResponse#419

Merged
barjin merged 6 commits intoapify:masterfrom
privatenumber:feat/response-clone
Mar 24, 2026
Merged

feat(node): add clone() support to ImpitResponse#419
barjin merged 6 commits intoapify:masterfrom
privatenumber:feat/response-clone

Conversation

@privatenumber
Copy link
Contributor

@privatenumber privatenumber commented Mar 22, 2026

Summary

Implements Response.clone() on ImpitResponse using ReadableStream.tee(), making impit compatible with libraries like ky that call response.clone() internally.

Approach

When .clone() is called:

  1. this.body.tee() splits the underlying ReadableStream into two independent streams (synchronous, no eager buffering)
  2. The original response's .body getter and body methods (text, json, arrayBuffer, bytes) are re-patched to read from one stream
  3. A standard Response is returned as the clone, backed by the other stream

Multiple clones are supported (matching the Fetch spec) — each call tees the current branch, so the original and all clones can be read independently.

Charset-aware text() decoding is preserved on the original via decodeBuffer. The clone uses standard Response.text() (UTF-8).

Throws TypeError on clone after body consumption, matching Fetch API semantics.

Changes

  • index.wrapper.js — add clone() in #wrapResponse, re-patch .body getter and body methods with configurable: true so subsequent clones work
  • dts-header.d.ts — add clone(): Response to ImpitResponse via declaration merging
  • test/basics.test.ts — tests covering: return type, url/header preservation, independent body reads, text() on both, multiple clones, body streaming after clone, clone-after-consume error, arrayBuffer on both, read ordering, non-200 status

@privatenumber privatenumber marked this pull request as ready for review March 22, 2026 18:02
Implement Response.clone() on ImpitResponse using ReadableStream.tee()
to split the body into two independent streams. The original response's
body methods are re-bound to one stream; the clone is a standard
Response backed by the other.

- Streaming preserved (no eager buffering)
- Charset-aware text() preserved on original via decodeBuffer
- Throws TypeError on double clone or clone after body consumed
- Clone returns standard Response (with url set via defineProperty)
- Add bodyConsumed flag: clone() after body consumption now throws
  a clear TypeError ("body has already been consumed") instead of a
  cryptic ReadableStream error
- Move cloned=true after successful tee() to avoid side effects on
  failure
- Change dts-header from class to interface: TypeScript does not
  merge duplicate class declarations, causing TS2300 when prepended
  to the auto-generated index.d.ts
- Separate error messages: "body has already been consumed" vs
  "Response has already been cloned"
- Add tests: arrayBuffer on both, read-original-first order, non-200
  status preservation, strict TypeError + message assertion
privatenumber and others added 4 commits March 23, 2026 23:02
- Override body getter after tee() so response.body returns the
  delegate's stream instead of the original locked stream
- Remove single-clone restriction (cloned flag) to match Fetch spec
- Add markConsumed + configurable to re-patched methods so consumption
  tracking and subsequent clones work correctly
- Add tests: multiple clones, body streaming after clone
Copy link
Member

@barjin barjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @privatenumber !

I just cleaned up some rough edges (e.g., moving the clone() declaration to the Rust code or making the text decoding work on the cloned response as well). Otherwise I am happy with the proposed changes.

Thanks again!

@barjin barjin merged commit bbbde99 into apify:master Mar 24, 2026
28 checks passed
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.

3 participants