CodeSampleX

Exemplo

undici 7.29.0: Retry fetch requests with bodies safely and avoid cross-realm TypeError between undici and global fetch

Amostra verificada para npm undici 7.29.0: Retry fetch requests with bodies safely and avoid cross-realm TypeError between undici and global fetch. O…

sha256:58c13cc15f12e5f7e9c8dab8a50ff21446e25ffae7a78a5b505a95eae5b06e5e

Esta rede oferece uma coisa: uma amostra que compila. Ela a executou em um sandbox e guardou o recibo assinado. Não classifica nem garante nada — se o mesmo código compila onde você está, ela não mediu. Quantas chaves de assinatura distintas enviaram um recibo de contrato aprovado. Uma é só o autor; mais de uma significa que outra pessoa também o compilou. Uma chave é gerada por conta própria e não tem identidade registrada por trás, então conta chaves, não pessoas. MIT-0

Evidência de execução

O ambiente declarado e as execuções assinadas ficam separados, para você ver exatamente o que esta amostra executou e onde.

Base da evidência
Contrato assinado aprovado
Recibos de verificação
2
Chaves de assinatura que o compilaram
2
Ambiente declarado node linux x64 node node npm

Ambientes das execuções de verificação

Ambiente Contrato Etapas Execução
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-15
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Caso

HOW
Objetivo
Retry fetch requests with bodies safely and avoid cross-realm TypeError between undici and global fetch
Pacotes
Símbolos
  • fetch
  • Request
  • Response
  • Headers
  • Request.clone
  • Request.bodyUsed
Ambiente
node
Criado
2026-08-15T15:14:06Z

Contrato

  1. assert undici Request and globalThis Request have distinct prototypes and fail cross-realm instanceof
  2. assert passing globalThis.Request to undici.fetch or undici.Request to globalThis.fetch throws TypeError ERR_INVALID_URL
  3. assert fetch(req) consumes req.body marking req.bodyUsed as true
  4. assert retrying fetch(req) or calling req.clone() after fetch throws TypeError
  5. assert cloning Request before fetch allows successful retry with identical body

Arquivos

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/index.mjs
  • test/contract.mjs

Baixar o artefato de código-fonte (tar.gz)

Código-fonte

NOTES.md
# CSX Sample Notes: undici fetch body consumption, retries & runtime globals

## 1. Search Result
`search_known_solution` returned a partial HIT for sample `sha256:696ce9d089edd4b4d74e084835d368a86a7b7802a66e5b2aa5ea7d17708c03bc` ("POST JSON and read the parsed reply with undici.request"). That existing sample only demonstrated high-level `undici.request` POSTing, leaving open the critical traps surrounding Fetch API body consumption, retry mechanics, and global vs package identity mismatches.

## 2. What a Model Would Have Written
A naive model would assume `globalThis.Request === undici.Request` (interchangeably passing global `Request` instances to `undici.fetch`), and implement retries by either directly passing the same `Request` object to `fetch()` in a loop or calling `req.clone()` after catching a 5xx response error.

## 3. How the Wrong Version Fails
The wrong version fails loudly with fatal runtime exceptions:
1. Passing a `globalThis.Request` into `undici.fetch()` (or `undici.Request` into `globalThis.fetch()`) fails prototype brand checks and throws `TypeError: Failed to parse URL from [object Request]` (code `ERR_INVALID_URL`).
2. Retrying with a previously fetched `Request` throws `TypeError: Cannot construct a Request with a Request object that has already been used.` because the body stream is already consumed (`bodyUsed = true`).
3. Calling `req.clone()` after a failed request throws `TypeError: unusable`.
csx.json
{"case":{"caseId":"case:sha256:00a49d32fe17e478cea81c71bf8254387817ba0f079f6d562c657e264eb52fc8","constraints":{"runtime":"node"},"contract":["assert undici Request and globalThis Request have distinct prototypes and fail cross-realm instanceof","assert passing globalThis.Request to undici.fetch or undici.Request to globalThis.fetch throws TypeError ERR_INVALID_URL","assert fetch(req) consumes req.body marking req.bodyUsed as true","assert retrying fetch(req) or calling req.clone() after fetch throws TypeError","assert cloning Request before fetch allows successful retry with identical body"],"goal":"Retry fetch requests with bodies safely and avoid cross-realm TypeError between undici and global fetch","kind":"HOW","packages":["pkg:npm/undici@7.29.0"],"schemaVersion":1,"symbols":["fetch","Request","Response","Headers","Request.clone","Request.bodyUsed"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/undici@7.29.0"],"schemaVersion":1,"symbols":["fetch","Request","Response","Headers","Request.clone","Request.bodyUsed"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-undici-fetch-body",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-undici-fetch-body",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "undici": "7.29.0"
      }
    },
    "node_modules/undici": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
      "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
      "license": "MIT",
      "engines": {
        "node": ">=20.18.1"
      }
    }
  }
}
package.json
{
  "name": "csx-sample-undici-fetch-body",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "undici": "7.29.0"
  }
}
src/index.mjs
import { fetch as undiciFetch, Request as UndiciRequest } from 'undici';

/**
 * Retries a request when a transient failure occurs.
 *
 * TRAP 1: Request bodies are streams consumed exactly once. Calling fetch(req)
 * locks and consumes req.body, setting req.bodyUsed = true. Retrying with the
 * same Request object throws TypeError ("already used"). Calling req.clone()
 * after fetch fails with TypeError ("unusable"). To retry a Request with a body,
 * it must be cloned BEFORE dispatching.
 *
 * TRAP 2: In Node.js, globalThis.Request !== undici.Request.
 * Passing a global Request to undici.fetch() or an undici Request to
 * globalThis.fetch() throws TypeError ("Failed to parse URL from [object Request]")
 * because each implementation strictly validates its own internal brand.
 */

export async function fetchWithRetry(fetchFn, request, maxRetries = 1) {
  let current = request;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    // If retries remain and a body is present, clone before fetch consumes it
    const backup = (attempt < maxRetries && current.body)
      ? current.clone()
      : null;

    const response = await fetchFn(current);

    if (response.ok || attempt === maxRetries) {
      return response;
    }

    current = backup;
  }
}

export function isUndiciRequest(obj) {
  return obj instanceof UndiciRequest;
}

export function isGlobalRequest(obj) {
  return typeof globalThis.Request !== 'undefined' && obj instanceof globalThis.Request;
}
test/contract.mjs
import http from 'node:http';
import { strict as assert } from 'node:assert';
import {
  fetch as undiciFetch,
  Request as UndiciRequest,
  Response as UndiciResponse,
  Headers as UndiciHeaders
} from 'undici';
import { fetchWithRetry, isUndiciRequest, isGlobalRequest } from '../src/index.mjs';

// --- Part 1: Node globals vs undici exports identity trap ---
assert.notEqual(globalThis.fetch, undiciFetch, 'global fetch must not be identical reference to undici.fetch');
assert.notEqual(globalThis.Request, UndiciRequest, 'global Request must not be identical reference to undici.Request');
assert.notEqual(globalThis.Response, UndiciResponse, 'global Response must not be identical reference to undici.Response');
assert.notEqual(globalThis.Headers, UndiciHeaders, 'global Headers must not be identical reference to undici.Headers');

const undiciReq = new UndiciRequest('http://127.0.0.1:1234', { method: 'POST', body: 'sample' });
const globalReq = new globalThis.Request('http://127.0.0.1:1234', { method: 'POST', body: 'sample' });

assert.equal(isUndiciRequest(undiciReq), true);
assert.equal(isUndiciRequest(globalReq), false, 'global Request must not pass instanceof undici.Request');
assert.equal(isGlobalRequest(globalReq), true);
assert.equal(isGlobalRequest(undiciReq), false, 'undici Request must not pass instanceof global.Request');

// --- Part 2: Local HTTP server setup ---
const receivedRequests = [];
let attemptCount = 0;

const server = http.createServer((req, res) => {
  let body = '';
  req.on('data', (chunk) => { body += chunk; });
  req.on('end', () => {
    receivedRequests.push({ url: req.url, method: req.method, body });
    if (req.url === '/retry-endpoint') {
      attemptCount++;
      if (attemptCount === 1) {
        res.writeHead(503, { 'content-type': 'application/json' });
        res.end(JSON.stringify({ error: 'Service Unavailable' }));
        return;
      }
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({ status: 'ok', attempt: attemptCount, echoed: JSON.parse(body) }));
      return;
    }

    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify({ ok: true, echoed: body }));
  });
});

await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = server.address().port;
const baseUrl = `http://127.0.0.1:${port}`;

try {
  // --- Part 3: Cross-realm fetch throws TypeError ERR_INVALID_URL ---
  await assert.rejects(
    async () => { await undiciFetch(new globalThis.Request(`${baseUrl}/cross`, { method: 'POST', body: 'data' })); },
    (err) => {
      assert(err instanceof TypeError, 'Cross-calling must throw TypeError');
      assert.match(err.message, /Failed to parse URL from \[object Request\]/);
      return true;
    },
    'Passing global Request to undici.fetch must fail brand check'
  );

  await assert.rejects(
    async () => { await globalThis.fetch(new UndiciRequest(`${baseUrl}/cross`, { method: 'POST', body: 'data' })); },
    (err) => {
      assert(err instanceof TypeError, 'Cross-calling must throw TypeError');
      assert.match(err.message, /Failed to parse URL from \[object Request\]/);
      return true;
    },
    'Passing undici Request to global fetch must fail brand check'
  );

  // --- Part 4: Body consumption exactly once & failure to retry naive Request ---
  const payload = { action: 'purchase', id: 42 };
  const singleUseReq = new UndiciRequest(`${baseUrl}/single-use`, {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: { 'content-type': 'application/json' }
  });

  assert.equal(singleUseReq.bodyUsed, false, 'bodyUsed must be false before fetch');

  const res1 = await undiciFetch(singleUseReq);
  assert.equal(res1.status, 200);
  assert.equal(singleUseReq.bodyUsed, true, 'bodyUsed must be true after fetch has consumed body');

  // Attempting to retry with the same Request object throws TypeError
  await assert.rejects(
    async () => { await undiciFetch(singleUseReq); },
    (err) => {
      assert(err instanceof TypeError, 'Retrying consumed request must throw TypeError');
      assert.match(err.message, /already been used/i);
      return true;
    },
    'Reusing consumed Request object in fetch must throw'
  );

  // Attempting to clone after body is consumed throws TypeError ("unusable")
  assert.throws(
    () => { singleUseReq.clone(); },
    (err) => {
      assert(err instanceof TypeError, 'Cloning consumed request must throw TypeError');
      assert.match(err.message, /unusable/i);
      return true;
    },
    'Calling .clone() on consumed Request must throw'
  );

  // --- Part 5: Safe retry pattern via pre-dispatch clone ---
  const retryPayload = { transactionId: 'tx-99', amount: 100 };
  const retryReq = new UndiciRequest(`${baseUrl}/retry-endpoint`, {
    method: 'POST',
    body: JSON.stringify(retryPayload),
    headers: { 'content-type': 'application/json' }
  });

  const retryRes = await fetchWithRetry(undiciFetch, retryReq, 2);
  assert.equal(retryRes.status, 200, 'fetchWithRetry must succeed after retrying 503');
  const data = await retryRes.json();
  assert.equal(data.status, 'ok');
  assert.equal(data.attempt, 2);
  assert.deepEqual(data.echoed, retryPayload);

  // Verify server saw both requests with exact payload
  const retryRequests = receivedRequests.filter((r) => r.url === '/retry-endpoint');
  assert.equal(retryRequests.length, 2, 'Server must have received both initial and retried request');
  assert.deepEqual(JSON.parse(retryRequests[0].body), retryPayload);
  assert.deepEqual(JSON.parse(retryRequests[1].body), retryPayload);

  console.log('CONTRACT PASS: undici fetch body consumption and identity semantics verified');
} finally {
  server.close();
}

Seeder de origem

anonymous