or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-limiter

A generic rate limiter for the web and node.js, useful for API clients, web crawling, or other tasks that need to be throttled

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/limiter@3.0.x

To install, run

npx @tessl/cli install tessl/npm-limiter@3.0.0

0

# Limiter

1

2

Limiter provides a generic rate limiter for the web and Node.js. It's useful for API clients, web crawling, or other tasks that need to be throttled. The library implements two complementary classes: `RateLimiter` provides a high-level interface for enforcing rate limits with configurable tokens per interval, while `TokenBucket` offers a lower-level interface with configurable burst and drip rates.

3

4

## Package Information

5

6

- **Package Name**: limiter

7

- **Package Type**: npm

8

- **Language**: TypeScript

9

- **Installation**: `npm install limiter`

10

11

## Core Imports

12

13

```typescript

14

import { RateLimiter, TokenBucket } from "limiter";

15

```

16

17

For CommonJS:

18

19

```javascript

20

const { RateLimiter, TokenBucket } = require("limiter");

21

```

22

23

## Basic Usage

24

25

```typescript

26

import { RateLimiter } from "limiter";

27

28

// Allow 150 requests per hour (Twitter search limit example)

29

const limiter = new RateLimiter({ tokensPerInterval: 150, interval: "hour" });

30

31

async function sendRequest() {

32

// Remove a token, waiting if necessary

33

const remainingRequests = await limiter.removeTokens(1);

34

console.log(`${remainingRequests} requests remaining`);

35

// Make your API call here

36

}

37

38

// Synchronous token removal

39

if (limiter.tryRemoveTokens(1)) {

40

console.log('Token removed successfully');

41

} else {

42

console.log('No tokens available');

43

}

44

```

45

46

## Architecture

47

48

Limiter is built around two core components:

49

50

- **RateLimiter**: High-level rate limiter that combines a token bucket with interval-based restrictions to comply with common API limits like "150 requests per hour maximum"

51

- **TokenBucket**: Lower-level hierarchical token bucket implementation providing configurable burst rate and drip rate with optional parent-child relationships

52

- **Clock Utilities**: Internal high-resolution timing functions for precise rate limiting calculations

53

54

## Capabilities

55

56

### Rate Limiting

57

58

High-level rate limiting with configurable tokens per interval and optional immediate response mode.

59

60

```typescript { .api }

61

class RateLimiter {

62

/**

63

* Create a new rate limiter

64

* @param options Configuration options for the rate limiter

65

*/

66

constructor(options: RateLimiterOpts);

67

68

/**

69

* Remove the requested number of tokens. Waits if necessary until tokens are available.

70

* @param count The number of tokens to remove (must not exceed tokensPerInterval)

71

* @returns Promise resolving to the number of tokens remaining after removal

72

* @throws Error if count exceeds tokensPerInterval

73

*/

74

removeTokens(count: number): Promise<number>;

75

76

/**

77

* Attempt to remove tokens immediately without waiting

78

* @param count The number of tokens to remove

79

* @returns true if tokens were successfully removed, false otherwise

80

*/

81

tryRemoveTokens(count: number): boolean;

82

83

/**

84

* Get the current number of tokens remaining in the bucket

85

* @returns The number of tokens currently available

86

*/

87

getTokensRemaining(): number;

88

89

/** The underlying token bucket used for rate limiting */

90

tokenBucket: TokenBucket;

91

/** Timestamp marking the start of the current interval */

92

curIntervalStart: number;

93

/** Number of tokens consumed in the current interval */

94

tokensThisInterval: number;

95

/** Whether to fire immediately when rate limiting is in effect */

96

fireImmediately: boolean;

97

}

98

99

interface RateLimiterOpts {

100

/** Maximum number of tokens that can be removed per interval */

101

tokensPerInterval: number;

102

/** The interval length in milliseconds or as a string (e.g., 'second', 'minute', 'hour', 'day') */

103

interval: Interval;

104

/** Whether to return immediately with -1 when rate limiting is active (default: false) */

105

fireImmediately?: boolean;

106

}

107

```

108

109

**Usage Examples:**

110

111

```typescript

112

import { RateLimiter } from "limiter";

113

114

// Basic rate limiting - 10 requests per second

115

const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" });

116

117

// Async usage (waits for tokens to become available)

118

async function makeRequest() {

119

const remaining = await limiter.removeTokens(1);

120

console.log(`Request made, ${remaining} tokens remaining`);

121

}

122

123

// Immediate response mode for HTTP 429 handling

124

const apiLimiter = new RateLimiter({

125

tokensPerInterval: 150,

126

interval: "hour",

127

fireImmediately: true

128

});

129

130

async function handleApiRequest(request, response) {

131

const remaining = await apiLimiter.removeTokens(1);

132

if (remaining < 0) {

133

response.writeHead(429, {'Content-Type': 'text/plain'});

134

response.end('429 Too Many Requests - rate limited');

135

} else {

136

// Process request normally

137

}

138

}

139

140

// Check remaining tokens without removing any

141

console.log(`Current tokens: ${limiter.getTokensRemaining()}`);

142

```

143

144

### Token Bucket

145

146

Lower-level hierarchical token bucket for precise rate control with burst capabilities and parent-child relationships.

147

148

```typescript { .api }

149

class TokenBucket {

150

/**

151

* Create a new token bucket

152

* @param options Configuration options for the token bucket

153

*/

154

constructor(options: TokenBucketOpts);

155

156

/**

157

* Remove tokens from the bucket, waiting if necessary until enough tokens are available

158

* @param count The number of tokens to remove (must not exceed bucketSize)

159

* @returns Promise resolving to the number of tokens remaining after removal

160

* @throws Error if count exceeds bucketSize

161

*/

162

removeTokens(count: number): Promise<number>;

163

164

/**

165

* Attempt to remove tokens immediately without waiting

166

* @param count The number of tokens to remove

167

* @returns true if tokens were successfully removed, false otherwise

168

*/

169

tryRemoveTokens(count: number): boolean;

170

171

/**

172

* Add any new tokens to the bucket based on time elapsed since last drip

173

* @returns true if new tokens were added, false otherwise

174

*/

175

drip(): boolean;

176

177

/** Maximum number of tokens the bucket can hold (burst rate) */

178

bucketSize: number;

179

/** Number of tokens added to the bucket per interval */

180

tokensPerInterval: number;

181

/** The interval length in milliseconds */

182

interval: number;

183

/** Optional parent bucket for hierarchical rate limiting */

184

parentBucket?: TokenBucket;

185

/** Current number of tokens in the bucket */

186

content: number;

187

/** Timestamp of the last token drip operation */

188

lastDrip: number;

189

}

190

191

interface TokenBucketOpts {

192

/** Maximum number of tokens the bucket can hold (burst rate) */

193

bucketSize: number;

194

/** Number of tokens to add to the bucket per interval */

195

tokensPerInterval: number;

196

/** The interval length in milliseconds or as a string (e.g., 'second', 'minute', 'hour', 'day') */

197

interval: Interval;

198

/** Optional parent bucket for hierarchical rate limiting */

199

parentBucket?: TokenBucket;

200

}

201

```

202

203

**Usage Examples:**

204

205

```typescript

206

import { TokenBucket } from "limiter";

207

208

// Byte-level throttling at 50KB/sec sustained, 150KB/sec burst

209

const BURST_RATE = 1024 * 1024 * 150; // 150KB/sec burst

210

const FILL_RATE = 1024 * 1024 * 50; // 50KB/sec sustained

211

212

const bucket = new TokenBucket({

213

bucketSize: BURST_RATE,

214

tokensPerInterval: FILL_RATE,

215

interval: "second"

216

});

217

218

async function handleData(data) {

219

await bucket.removeTokens(data.byteLength);

220

sendData(data);

221

}

222

223

// Hierarchical token buckets

224

const parentBucket = new TokenBucket({

225

bucketSize: 1000,

226

tokensPerInterval: 100,

227

interval: "second"

228

});

229

230

const childBucket = new TokenBucket({

231

bucketSize: 100,

232

tokensPerInterval: 50,

233

interval: "second",

234

parentBucket: parentBucket

235

});

236

237

// Removing tokens from child also removes from parent

238

await childBucket.removeTokens(10);

239

240

// Manual token drip

241

if (bucket.drip()) {

242

console.log("New tokens were added to the bucket");

243

}

244

245

// Infinite bucket (bucketSize: 0)

246

const infiniteBucket = new TokenBucket({

247

bucketSize: 0,

248

tokensPerInterval: 10,

249

interval: "second"

250

});

251

```

252

253

## Types

254

255

```typescript { .api }

256

type Interval = number | "second" | "sec" | "minute" | "min" | "hour" | "hr" | "day";

257

```

258

259

The `Interval` type supports both numeric values (milliseconds) and convenient string shortcuts:

260

- `"second"` or `"sec"`: 1000ms

261

- `"minute"` or `"min"`: 60,000ms

262

- `"hour"` or `"hr"`: 3,600,000ms

263

- `"day"`: 86,400,000ms

264

265

## Error Handling

266

267

Both `RateLimiter` and `TokenBucket` throw errors in specific scenarios:

268

269

### RateLimiter Errors

270

- **`removeTokens(count)`**: Throws `Error` if `count` exceeds `tokensPerInterval`

271

- **Constructor**: Throws `Error` if `interval` is an invalid string

272

273

### TokenBucket Errors

274

- **`removeTokens(count)`**: Throws `Error` if `count` exceeds `bucketSize`

275

- **Constructor**: Throws `Error` if `interval` is an invalid string

276

277

### Error Examples

278

279

```typescript

280

try {

281

// Invalid interval string

282

const limiter = new RateLimiter({

283

tokensPerInterval: 10,

284

interval: "invalid" as any

285

});

286

} catch (error) {

287

console.error("Invalid interval:", error.message);

288

// Output: "Invalid interval invalid"

289

}

290

291

try {

292

// Requesting more tokens than allowed per interval

293

const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" });

294

await limiter.removeTokens(15); // More than tokensPerInterval

295

} catch (error) {

296

console.error("Too many tokens requested:", error.message);

297

// Output: "Requested tokens 15 exceeds maximum tokens per interval 10"

298

}

299

300

try {

301

// Requesting more tokens than bucket size

302

const bucket = new TokenBucket({

303

bucketSize: 5,

304

tokensPerInterval: 10,

305

interval: "second"

306

});

307

await bucket.removeTokens(10); // More than bucketSize

308

} catch (error) {

309

console.error("Bucket size exceeded:", error.message);

310

// Output: "Requested tokens 10 exceeds bucket size 5"

311

}

312

```

313

314

### Valid Interval Values

315

- **Numeric**: Any positive number (milliseconds)

316

- **String shortcuts**: `"second"`, `"sec"`, `"minute"`, `"min"`, `"hour"`, `"hr"`, `"day"`

317

318

## Additional Notes

319

320

- Both classes should be used with message queues or serialization to prevent race conditions with simultaneous `removeTokens()` calls

321

- Race conditions can lead to out-of-order messages or apparent "lost" messages under heavy load

322

- Use `tryRemoveTokens()` for non-blocking token removal when immediate response is needed

323

- The `RateLimiter` automatically manages interval resets and token bucket refills

324

- Token buckets support hierarchical relationships where child buckets also consume tokens from parent buckets

325

- Infinite buckets (bucketSize: 0) always allow token removal and return `Number.POSITIVE_INFINITY`