or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md
tile.json

index.mddocs/

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`