feat: add request deduplication for concurrent identical requests#15
Merged
AryanSharma48 merged 3 commits intoJun 18, 2026
Merged
Conversation
|
@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. |
There was a problem hiding this comment.
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, adddeduplicationconfig + key function typing, and wire it intocreateResilientFetch. - Python: introduce an async
RequestDeduplicator, addDeduplicationConfigtoResilientConfig, 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 () => { |
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 |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Checklist
Design & Parity
README.mdor general documentation where necessary.Quality & Testing
cd sandbox && node server.js) before running the tests.npm run build && npm testinsidepackages/smooth-api-ts) and all tests passed.pytest tests/insidepackages/smooth-api-py) and all tests passed.