Skip to content

Commit 6aa83bd

Browse files
committed
feat: add request deduplication
fix #148
1 parent 53ac586 commit 6aa83bd

File tree

1 file changed

+26
-4
lines changed

1 file changed

+26
-4
lines changed

packages/client/src/client.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
3333
private readonly peerRouting: PeerRouting
3434
private readonly filterAddrs?: string[]
3535
private readonly filterProtocols?: string[]
36+
private readonly inFlightRequests: Map<string, Promise<Response>>
3637

3738
/**
3839
* Create a new DelegatedContentRouting instance
@@ -44,6 +45,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
4445
this.httpQueue = new PQueue({
4546
concurrency: init.concurrentRequests ?? defaultValues.concurrentRequests
4647
})
48+
this.inFlightRequests = new Map() // Tracks in-flight requests to avoid duplicate requests
4749
this.clientUrl = url instanceof URL ? url : new URL(url)
4850
this.timeout = init.timeout ?? defaultValues.timeout
4951
this.filterAddrs = init.filterAddrs
@@ -95,7 +97,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
9597
const url = new URL(`${this.clientUrl}routing/v1/providers/${cid.toString()}`)
9698
this.#addFilterParams(url, options.filterAddrs, options.filterProtocols)
9799
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
98-
const res = await fetch(url, getOptions)
100+
const res = await this.#makeRequest(url.toString(), getOptions)
99101

100102
if (res.status === 404) {
101103
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
@@ -162,7 +164,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
162164
this.#addFilterParams(url, options.filterAddrs, options.filterProtocols)
163165

164166
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
165-
const res = await fetch(url, getOptions)
167+
const res = await this.#makeRequest(url.toString(), getOptions)
166168

167169
if (res.status === 404) {
168170
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
@@ -228,7 +230,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
228230
await onStart.promise
229231

230232
const getOptions = { headers: { Accept: 'application/vnd.ipfs.ipns-record' }, signal }
231-
const res = await fetch(resource, getOptions)
233+
const res = await this.#makeRequest(resource, getOptions)
232234

233235
log('getIPNS GET %s %d', resource, res.status)
234236

@@ -290,7 +292,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
290292
const body = marshalIPNSRecord(record)
291293

292294
const getOptions = { method: 'PUT', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, body, signal }
293-
const res = await fetch(resource, getOptions)
295+
const res = await this.#makeRequest(resource, getOptions)
294296

295297
log('putIPNS PUT %s %d', resource, res.status)
296298

@@ -349,4 +351,24 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
349351
}
350352
}
351353
}
354+
355+
// Ensures that only one concurrent request is made for the same URL with the same method
356+
async #makeRequest (url: string, options: RequestInit): Promise<Response> {
357+
const key = `${options.method ?? 'GET'}-${url}`
358+
359+
// Check if there's already an in-flight request for this URL
360+
const existingRequest = this.inFlightRequests.get(key)
361+
if (existingRequest != null) {
362+
return existingRequest
363+
}
364+
365+
// Create new request and track it
366+
const requestPromise = fetch(url, options).finally(() => {
367+
// Clean up the tracked request when it completes
368+
this.inFlightRequests.delete(key)
369+
})
370+
371+
this.inFlightRequests.set(key, requestPromise)
372+
return requestPromise
373+
}
352374
}

0 commit comments

Comments
 (0)