Skip to content

feat(utils): export resetFetchContext and clearFetchCache#1421

Merged
ekremney merged 2 commits intomainfrom
feat/export-fetch-context-utils
Mar 10, 2026
Merged

feat(utils): export resetFetchContext and clearFetchCache#1421
ekremney merged 2 commits intomainfrom
feat/export-fetch-context-utils

Conversation

@ekremney
Copy link
Copy Markdown
Member

Summary

  • Export resetFetchContext and clearFetchCache from the @adobe/fetch h1/h2 context via @adobe/spacecat-shared-utils
  • The h1/h2 context has internal connection pooling (reset) and HTTP response caching (clearCache). Exposing these allows consumers — especially tests using nock with sinon.useFakeTimers — to properly isolate fetch state between test runs
  • Required by the upcoming vault-secrets and data-access connection reuse changes

Test plan

  • Verify existing utils tests still pass
  • Merge and confirm semantic-release publishes a new minor version

🤖 Generated with Claude Code

…/fetch context

The h1/h2 context created by @adobe/fetch has internal connection pooling
and response caching. Exposing reset and clearCache allows consumers
(especially tests using nock with fake timers) to properly isolate
fetch state between test runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ekremney ekremney requested a review from solaris007 March 10, 2026 11:27
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@ekremney ekremney merged commit 0e05bd8 into main Mar 10, 2026
7 checks passed
@ekremney ekremney deleted the feat/export-fetch-context-utils branch March 10, 2026 11:49
solaris007 pushed a commit that referenced this pull request Mar 10, 2026
## [@adobe/spacecat-shared-utils-v1.101.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.100.1...@adobe/spacecat-shared-utils-v1.101.0) (2026-03-10)

### Features

* **utils:** export resetFetchContext and clearFetchCache ([#1421](#1421)) ([0e05bd8](0e05bd8))
@solaris007
Copy link
Copy Markdown
Member

🎉 This PR is included in version @adobe/spacecat-shared-utils-v1.101.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

ekremney added a commit that referenced this pull request Mar 10, 2026
#1423)

## Summary

Resolves "fetch failed" errors across Lambda functions by enabling HTTP
connection reuse for the two highest-volume outbound callers:
PostgrestClient (every DB query) and VaultClient (secret reads, auth,
token renewal).

### The problem

**PostgrestClient** — `@supabase/postgrest-js` defaults to
`@supabase/node-fetch`, which explicitly sets the `Connection: close`
header on every request. This **overrides** the agent's `keepAlive`
setting and forces the TCP connection to close after each response.
Every DB query = new TCP handshake + close. With thousands of
queries/sec across all Lambdas, this creates massive ephemeral port
pressure on the NAT gateway.

> Note: Since Node 19+, `http.globalAgent.keepAlive` defaults to `true`,
and `@adobe/fetch` h1 mode also uses keepAlive by default. But none of
that matters when the fetch implementation itself sets `Connection:
close` — which is exactly what `@supabase/node-fetch` does.

**VaultClient** — Used bare `globalThis.fetch` (Node's built-in
undici-based fetch). While undici supports connection pooling, using
`@adobe/fetch` gives us a shared, configurable context with the same
pooling semantics used across the rest of the codebase.

Combined with the NACL port range issue ([P0 fix:
spacecat-infrastructure#383](adobe/spacecat-infrastructure#383)),
this meant Lambdas were:
- Opening thousands of short-lived connections per second
- Exhausting ephemeral ports on the NAT gateway
- Getting `fetch failed` when the OS ran out of source ports or
connections timed out in the queue

### How passing `@adobe/fetch` fixes it

By passing `@adobe/fetch`'s `fetch` to `new PostgrestClient(..., { fetch
})`, we **bypass `@supabase/node-fetch` entirely**. `@adobe/fetch` does
NOT set `Connection: close`, so the agent's `keepAlive` actually takes
effect and connections are reused.

```
BEFORE (per DB query — @supabase/node-fetch with Connection: close):
  DNS → TCP handshake → HTTP request → response → connection closed → port released

AFTER (pooled — @adobe/fetch with keepAlive):
  First query:  DNS → TCP handshake → HTTP request → response → connection stays open
  Next queries: HTTP request → response (reuses existing connection)
```

For a Lambda handling 100 requests, this goes from ~100 TCP connections
to PostgREST down to ~1-2 persistent connections. Multiply across all
Lambda instances and you dramatically reduce NAT gateway connection
pressure.

### Key design decisions (per review feedback)

**Dedicated fetch contexts** — Instead of using the shared
`@adobe/fetch` default context, each package creates its own
purpose-built context:
- **data-access**: `keepAliveNoCache()` — forces h1 with `keepAlive:
true` (for connection reuse to the plain HTTP ALB) and disables HTTP
response caching (prevents stale reads after writes)
- **vault-secrets**: `noCache()` — uses default protocol negotiation (h2
for HTTPS Vault targets) with caching disabled

**Why `keepAliveNoCache` for PostgREST**: The default `h2()` context
sets `keepAlive: false` for h1 on Node 19+ (to ensure cross-version
consistency). PostgREST is behind a plain HTTP ALB → always h1 →
`keepAlive: false` would defeat connection reuse. `keepAliveNoCache()`
explicitly enables `keepAlive: true`.

**Why `noCache` for everything**: `@adobe/fetch` has a built-in
100MB/500-entry LRU response cache enabled by default. PostgREST GET
responses (status 200) are cacheable by default, meaning reads after
writes could return stale data within a Lambda invocation.

**Headers compatibility wrapper** (`createFetchCompat`):
`@adobe/fetch`'s `Headers` class uses `instanceof` to detect Headers
objects, which fails for native WHATWG `Headers`. Since
`@supabase/postgrest-js` creates native `Headers`, all headers
(including `Content-Type: application/json`) were silently dropped. Fix:
convert native Headers to plain objects via `Object.fromEntries()`.

**Direct `@adobe/fetch` dependency for vault-secrets**: Instead of
pulling in `@adobe/spacecat-shared-utils` (which transitively brings
cheerio, aws-xray-sdk, zod, etc.), vault-secrets imports `@adobe/fetch`
directly — keeping the dependency footprint minimal for a
security-critical package.

### Changes

- **data-access**: use `keepAliveNoCache()` from `@adobe/fetch` for
PostgREST — connection reuse + no response caching
- **data-access**: add `createFetchCompat()` wrapper to convert native
WHATWG Headers to plain objects
- **data-access**: add `@adobe/fetch` as direct dependency
- **vault-secrets**: use `noCache()` from `@adobe/fetch` — h2 for HTTPS
+ no response caching
- **vault-secrets**: depend on `@adobe/fetch` directly instead of
`@adobe/spacecat-shared-utils` (lighter deps)
- **vault-secrets**: add `test/setup-env.js` for
`HELIX_FETCH_FORCE_HTTP1` (nock compatibility)
- **both**: `HELIX_FETCH_FORCE_HTTP1` fallback to `h1NoCache()` in tests
for nock compatibility

Depends on
[spacecat-shared-utils@1.101.0](#1421)
(merged).

## Test plan
- [x] data-access: 1533 unit tests passing, 100% coverage
- [x] data-access: 709 PostgREST IT tests passing
- [x] data-access: 359 legacy IT tests passing
- [x] vault-secrets: 75 tests passing, 100% coverage
- [ ] CI passes
- [ ] Deploy to dev and monitor PostgREST error rates and NAT gateway
connection metrics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants