Skip to content

feat: add request deduplication for concurrent identical requests#15

Merged
AryanSharma48 merged 3 commits into
AryanSharma48:mainfrom
sarthakroutray:fix/dedupe-in-flight-requests
Jun 18, 2026
Merged

feat: add request deduplication for concurrent identical requests#15
AryanSharma48 merged 3 commits into
AryanSharma48:mainfrom
sarthakroutray:fix/dedupe-in-flight-requests

Conversation

@sarthakroutray

Copy link
Copy Markdown
Contributor

Description

Please include a summary of the change and the related issue/motivation. Specify which package(s) or files are affected.

Fixes #13

Type of Change

Please check the option that applies:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update (changes to READMEs, docs, or inline comments)

Checklist

Design & Parity

  • If this introduces a new configuration option or public API, I have implemented equivalent options/behavior in both TypeScript and Python packages.
  • The library remains dependency-free (no new external runtime dependencies added).
  • I have updated the relevant package-specific README.md or general documentation where necessary.

Quality & Testing

  • I started the sandbox Express server (cd sandbox && node server.js) before running the tests.
  • TypeScript Package: I have built and run the TypeScript tests (npm run build && npm test inside packages/smooth-api-ts) and all tests passed.
  • Python Package: I have run the Python tests (pytest tests/ inside packages/smooth-api-py) and all tests passed.
  • I have added new tests to cover my changes.
  • I have commented my code, particularly in hard-to-understand areas, and updated JSDoc/docstrings.

@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

@sarthakroutray is attempting to deploy a commit to the aryansharma48's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in “request deduplication” feature to both the TypeScript and Python SmoothAPI packages, allowing concurrent identical in-flight requests to share a single execution, with tests and documentation updates to support the new capability.

Changes:

  • TypeScript: introduce a RequestDeduplicator, add deduplication config + key function typing, and wire it into createResilientFetch.
  • Python: introduce an async RequestDeduplicator, add DeduplicationConfig to ResilientConfig, and integrate it into the async decorator path.
  • Add new unit tests and documentation sections (plus benchmark-style tests) for deduplication behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/smooth-api-ts/tests/deduplication.test.ts Adds TS unit tests for dedup + includes a benchmark-style test
packages/smooth-api-ts/src/types.ts Adds TS public types/config for deduplication
packages/smooth-api-ts/src/index.ts Wires TS deduplicator into resilient fetch execution path
packages/smooth-api-ts/src/dedup.ts Implements TS in-flight request map + keying logic
packages/smooth-api-ts/README.md Documents TS deduplication feature and usage
packages/smooth-api-py/tests/test_deduplication.py Adds Python unit tests for dedup + includes a benchmark-style test
packages/smooth-api-py/smooth_api/dedup.py Implements Python async in-flight coalescing
packages/smooth-api-py/smooth_api/config.py Adds Python DeduplicationConfig and ResilientConfig.deduplication
packages/smooth-api-py/smooth_api/init.py Integrates Python deduplication into async wrapper + re-exports config
packages/smooth-api-py/README.md Documents Python deduplication feature and usage

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +12
* Default key derivation: stringified URL only.
* This intentionally ignores `options` (e.g. headers, body) so that
* concurrent GET /users/1 calls are always collapsed, even when the
* caller did not customise the key function.
*
* For mutation-safe deduplication (POST, PUT …) supply a custom
* `keyFn` via `DeduplicationConfig.keyFn`.
*/
const defaultKeyFn: DeduplicationKeyFn = (url) => url.toString();
Comment on lines +55 to +65
if (this.inflight.has(key)) {
return this.inflight.get(key)! as Promise<R>;
}

const promise = fetcher().finally(() => {
this.inflight.delete(key);
});

this.inflight.set(key, promise as unknown as Promise<Response>);
return promise;
}
Comment on lines +3 to +11
/**
* Optional function that derives a cache key from a request.
* Defaults to `url.toString()` when not provided.
* Return `null` to opt this specific request out of deduplication.
*/
export type DeduplicationKeyFn = (
url: string | URL,
options?: RequestInit
) => string | null;
Comment on lines +169 to +176
it('respects a custom keyFn — different keys are NOT deduplicated', async () => {
const counter = { calls: 0 };

const originalFetch = globalThis.fetch;
globalThis.fetch = async (): Promise<Response> => {
counter.calls++;
await new Promise(r => setTimeout(r, 20));
return new Response('{}', { status: 200 });
Comment on lines +80 to +98
loop = asyncio.get_event_loop()

if key in self._inflight:
# Another coroutine is already executing this call — share its Future.
return await self._inflight[key]

future: asyncio.Future[Any] = loop.create_future()
self._inflight[key] = future

try:
result = await thunk()
future.set_result(result)
return result
except Exception as exc:
future.set_exception(exc)
raise
finally:
# Always clean up so the next call (after settlement) runs fresh.
self._inflight.pop(key, None)

When multiple identical requests are made concurrently, SmoothAPI can execute only one network call and share the result with all callers. This reduces unnecessary load on downstream services.

**Enable with default key function** (deduplicates by URL):
});
```

* **Default Behavior**: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are coalesced.
Comment on lines +14 to +19
/**
* Custom function to compute the deduplication key.
* Receives the same (url, options) passed to resilientFetch.
* Defaults to the stringified URL (method-agnostic).
*/
keyFn?: DeduplicationKeyFn;
# Benchmark (informational — does not fail the suite)
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
// ---------------------------------------------------------------------------

describe('Request Deduplication Benchmark', () => {
it('reports overhead of deduplication map lookup vs plain fetch', async () => {

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comment on lines +4 to +12
* Default key derivation: stringified URL only.
* This intentionally ignores `options` (e.g. headers, body) so that
* concurrent GET /users/1 calls are always collapsed, even when the
* caller did not customise the key function.
*
* For mutation-safe deduplication (POST, PUT …) supply a custom
* `keyFn` via `DeduplicationConfig.keyFn`.
*/
const defaultKeyFn: DeduplicationKeyFn = (url) => url.toString();
Comment on lines +161 to +163
* **Default Behavior**: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are coalesced.
* **Error Propagation**: If the network call fails, all waiting callers receive the same error.
* **Settlement**: Once a request completes, the next call to the same URL triggers a fresh network request.
Comment on lines 3 to 5
import asyncio
import functools
from urllib.parse import urlparse
Comment thread packages/smooth-api-py/smooth_api/dedup.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@AryanSharma48 AryanSharma48 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Looks great!

@AryanSharma48
AryanSharma48 merged commit c339b6e into AryanSharma48:main Jun 18, 2026
4 of 5 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.

Request Deduplication

3 participants