Skip to content

Commit

Permalink
make remaining optional
Browse files Browse the repository at this point in the history
also make sorting fall back to limit, and document getRateLimits function in readme
  • Loading branch information
nfriedly committed Oct 27, 2023
1 parent 0332c1f commit 54670f0
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 14 deletions.
19 changes: 15 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ For more examples, take a look at the [`examples/`](examples/) folder.

Scans the input for ratelimit headers in a variety of formats and returns the
result in a consistent format, or undefined if it fails to find any rate-limit
headers. Returns an object with the following fields, or `undefined` if it does
not find any rate-limit headers.
headers. If multiple ratelimits are found, it chooses the one with the lowest
remaining value.

Returns an object with the following fields, or `undefined` if it does not find
any rate-limit headers.

```ts
type RateLimitInfo = {
limit: number
used: number
remaining: number
used: number | undefined
remaining: number | undefined
reset: Date | undefined
}
```
Expand All @@ -112,6 +115,14 @@ type Options = {
}
```
### `getRateLimits(responseOrHeaders, [options]) => object[]`
For APIs that may return multiple rate limits (e.g. per client & per end-user),
this will parse all of them.
Accepts the same inputs as `getRateLimit` and returns an array containing zero
or more of the same `RateLimitInfo` objects that `getRateLimit` returns.
## Issues and Contributing
If you encounter a bug or want to see something added/changed, please go ahead
Expand Down
39 changes: 33 additions & 6 deletions source/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import type {
RateLimitInfo,
ParserOptions,
} from './types'
import { secondsToDate, toInt, getHeader } from './utilities.js'
import {
secondsToDate,
toInt,
getHeader,
toIntOrUndefined,
} from './utilities.js'

/**
* The following links might be referred to in the below lines of code:
Expand Down Expand Up @@ -40,6 +45,30 @@ export const getRateLimit = (
return rateLimits.length === 0 ? undefined : rateLimits[0]
}

/**
* Function to sort an array of RateLimitInfo[] by remaining, then by limit, whith lower values coming first, and undefined remaining values coming after defined ones
* @param a {RateLimitInfo}
* @param b {RateLimitInfo}
* @returns number
*/
export function remainingSortFn(a: RateLimitInfo, b: RateLimitInfo): number {
const aDefined = a.remaining !== undefined
const bDefined = b.remaining !== undefined
if (a.remaining === b.remaining) {
return a.limit - b.limit
}

if (aDefined && !bDefined) {
return -1
}

if (!aDefined && bDefined) {
return 1
}

return a.remaining! - b.remaining!
}

/**
* Parses the passed response/headers object and returns rate limit information
* extracted from ALL rate limit headers the parser can find.
Expand Down Expand Up @@ -106,9 +135,7 @@ export const getRateLimits = (
) as RateLimitInfo[]

// Sort so that the limit with the lowest remaining value comes first
rateLimits.sort(
(a: RateLimitInfo, b: RateLimitInfo) => a.remaining - b.remaining,
)
rateLimits.sort(remainingSortFn)

return rateLimits
}
Expand Down Expand Up @@ -217,13 +244,13 @@ const reReset = /reset\s*=\s*(\d+)/i
*/
export const parseDraft7Header = (header: string): RateLimitInfo => {
const limit = toInt(reLimit.exec(header)?.[1])
const remaining = toInt(reRemaining.exec(header)?.[1])
const remaining = toIntOrUndefined(reRemaining.exec(header)?.[1]) // Optional per https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-07#name-ratelimit
const resetSeconds = toInt(reReset.exec(header)?.[1])
const reset = secondsToDate(resetSeconds)

return {
limit,
used: limit - remaining,
used: typeof remaining === 'number' ? limit - remaining : undefined,
remaining,
reset,
}
Expand Down
4 changes: 2 additions & 2 deletions source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export type RateLimitInfo = {
/**
* The number of requests already made to that endpoint.
*/
used: number
used?: number

/**
* The number of requests that can be made before reaching the rate limit.
*/
remaining: number
remaining?: number

/**
* The timestamp at which the window resets, and one's hit count is set to zero.
Expand Down
17 changes: 15 additions & 2 deletions source/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,27 @@ export const secondsToDate = (seconds: number): Date => {
*
* @param input {string | number | undefined} - The input to convert to a number.
*
* @return {number} - The parsed integer.
* @throws {Error} - Thrown if the string does not contain a valid number.
* @return {number} - The parsed integer. May be NaN for unparseable input.
*/
export const toInt = (input: string | number | undefined): number => {
if (typeof input === 'number') return input
return Number.parseInt(input ?? '', 10)
}

/**
* Converts a string/number to a number or undefined.
*
* @param input {string | number | undefined} - The input to convert to a number.
*
* @return {number | undefined} - The parsed integer.
*/
export const toIntOrUndefined = (
input: string | number | undefined,
): number | undefined => {
const number_ = toInt(input)
return Number.isNaN(number_) ? undefined : number_
}

/**
* Returns a header (or undefined if it's not present) from the passed
* node/fetch-style header object.
Expand Down
15 changes: 15 additions & 0 deletions test/sort-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, test, expect } from '@jest/globals'
import { remainingSortFn } from '../source/parser.js'

describe('remainingSortFn', () => {
test('Should short with lowest remaining values first and undefined values last', () => {
const unknown70 = { limit: 70 }
const unknown75 = { limit: 75 }
const five100 = { limit: 100, remaining: 5, used: 95 }
const five101 = { limit: 101, remaining: 5, used: 95 }
const ten99 = { limit: 99, remaining: 10, used: 190 }
const infos = [unknown75, five101, ten99, unknown70, five100]
infos.sort(remainingSortFn)
expect(infos).toMatchObject([five100, five101, ten99, unknown70, unknown75])
})
})

0 comments on commit 54670f0

Please sign in to comment.