Created
September 24, 2025 14:47
-
-
Save cfluke-cb/3cbfc2684dc7df63185af32ff35da755 to your computer and use it in GitHub Desktop.
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
| /** | |
| * Copyright 2025-present Coinbase Global, Inc. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** | |
| * Rate Limiter Example: Request Transformers for Rate Limiting | |
| * | |
| * This example demonstrates how to add rate limiting to your Prime API client | |
| * using request transformers. Two approaches are shown: | |
| * 1. Simple token bucket rate limiter (self-made) | |
| * 2. Using the popular 'bottleneck' npm package | |
| * | |
| * Rate limiting is important for: | |
| * - Staying within API rate limits | |
| * - Being a good API citizen | |
| * - Preventing overwhelming the server | |
| * - Managing concurrent requests in high-load applications | |
| * | |
| * Usage: | |
| * npm install bottleneck # For approach 2 | |
| * node examples/advanced/rateLimiterExample.js | |
| */ | |
| require('dotenv').config(); | |
| const { CoinbasePrimeClient, PortfoliosService } = require('@coinbase-sample/prime'); | |
| /** | |
| * Approach 1: Simple Token Bucket Rate Limiter | |
| * | |
| * This is a basic implementation that limits requests per second. | |
| * It uses a token bucket algorithm where tokens are replenished at a fixed rate. | |
| */ | |
| class SimpleRateLimiter { | |
| constructor(tokensPerSecond = 10, maxTokens = 10) { | |
| this.tokensPerSecond = tokensPerSecond; | |
| this.maxTokens = maxTokens; | |
| this.tokens = maxTokens; | |
| this.lastRefill = Date.now(); | |
| this.waitingQueue = []; | |
| } | |
| /** | |
| * Request permission to make an API call | |
| * @returns {Promise<void>} Resolves when request can proceed | |
| */ | |
| async acquireToken() { | |
| this.refillTokens(); | |
| if (this.tokens >= 1) { | |
| this.tokens--; | |
| return Promise.resolve(); | |
| } | |
| // No tokens available, wait for next refill | |
| return new Promise((resolve) => { | |
| this.waitingQueue.push(resolve); | |
| this.scheduleNextRefill(); | |
| }); | |
| } | |
| refillTokens() { | |
| const now = Date.now(); | |
| const elapsed = (now - this.lastRefill) / 1000; | |
| const tokensToAdd = elapsed * this.tokensPerSecond; | |
| this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd); | |
| this.lastRefill = now; | |
| } | |
| scheduleNextRefill() { | |
| if (this.waitingQueue.length === 0) return; | |
| const timeToNextToken = (1 / this.tokensPerSecond) * 1000; | |
| setTimeout(() => { | |
| this.refillTokens(); | |
| if (this.tokens >= 1 && this.waitingQueue.length > 0) { | |
| this.tokens--; | |
| const resolve = this.waitingQueue.shift(); | |
| resolve(); | |
| } | |
| this.scheduleNextRefill(); | |
| }, timeToNextToken); | |
| } | |
| /** | |
| * Create a request transformer that applies rate limiting | |
| * @returns {Function} Request transformer function | |
| */ | |
| createRequestTransformer() { | |
| return async (config) => { | |
| const startTime = Date.now(); | |
| await this.acquireToken(); | |
| const waitTime = Date.now() - startTime; | |
| if (waitTime > 0) { | |
| console.log(`β±οΈ Rate limited: waited ${waitTime}ms for token`); | |
| } | |
| // Add rate limiting headers for debugging | |
| config.headers = config.headers || {}; | |
| config.headers['X-Rate-Limit-Wait'] = waitTime.toString(); | |
| return config; | |
| }; | |
| } | |
| } | |
| /** | |
| * Approach 2: Using Bottleneck Library | |
| * | |
| * Bottleneck is a popular, battle-tested rate limiting library | |
| * with many advanced features like clustering support, Redis backing, etc. | |
| * | |
| * Install with: npm install bottleneck | |
| */ | |
| function createBottleneckRateLimiter() { | |
| let Bottleneck; | |
| try { | |
| Bottleneck = require('bottleneck'); | |
| } catch (error) { | |
| console.log('β οΈ Bottleneck not installed. Run: npm install bottleneck'); | |
| return null; | |
| } | |
| // Create bottleneck limiter with 10 requests per second | |
| const limiter = new Bottleneck({ | |
| minTime: 100, // Minimum 100ms between requests (= 10 requests/second) | |
| maxConcurrent: 5, // Maximum 5 concurrent requests | |
| reservoir: 20, // Start with 20 requests available | |
| reservoirRefreshAmount: 10, // Add 10 requests every refresh | |
| reservoirRefreshInterval: 1000, // Refresh every 1000ms (1 second) | |
| }); | |
| // Optional: Add event listeners for monitoring | |
| limiter.on('ready', () => { | |
| console.log('π Rate limiter ready'); | |
| }); | |
| limiter.on('idle', () => { | |
| console.log('π€ Rate limiter idle (no pending requests)'); | |
| }); | |
| limiter.on('depleted', () => { | |
| console.log('πͺ£ Rate limiter depleted (waiting for tokens)'); | |
| }); | |
| /** | |
| * Create a request transformer using Bottleneck | |
| * @returns {Function} Request transformer function | |
| */ | |
| function createRequestTransformer() { | |
| return async (config) => { | |
| const startTime = Date.now(); | |
| // Wrap the request in bottleneck's schedule method | |
| await limiter.schedule(() => Promise.resolve()); | |
| const waitTime = Date.now() - startTime; | |
| if (waitTime > 10) { | |
| // Only log if we actually waited | |
| console.log(`β±οΈ Rate limited: waited ${waitTime}ms`); | |
| } | |
| // Add debugging headers | |
| config.headers = config.headers || {}; | |
| config.headers['X-Rate-Limit-Wait'] = waitTime.toString(); | |
| config.headers['X-Rate-Limit-Library'] = 'bottleneck'; | |
| return config; | |
| }; | |
| } | |
| return { limiter, createRequestTransformer }; | |
| } | |
| /** | |
| * Example 1: Simple Rate Limiter | |
| */ | |
| async function simpleRateLimiterExample() { | |
| console.log('\n=== Example 1: Simple Token Bucket Rate Limiter ==='); | |
| try { | |
| // Create client from environment variables | |
| const client = CoinbasePrimeClient.fromEnv(); | |
| // Create rate limiter: 5 requests per second, max 5 tokens | |
| const rateLimiter = new SimpleRateLimiter(5, 5); | |
| // Add the rate limiting transformer to the client | |
| client.addTransformRequest(rateLimiter.createRequestTransformer()); | |
| const portfolioService = new PortfoliosService(client); | |
| console.log( | |
| 'π Making 10 rapid requests (should be rate limited to 5/second)...' | |
| ); | |
| const startTime = Date.now(); | |
| const requests = []; | |
| // Make 10 concurrent requests | |
| for (let i = 0; i < 10; i++) { | |
| requests.push( | |
| portfolioService | |
| .listPortfolios() | |
| .then((result) => { | |
| const elapsed = Date.now() - startTime; | |
| console.log(`β Request ${i + 1} completed after ${elapsed}ms`); | |
| return result; | |
| }) | |
| .catch((error) => { | |
| console.error(`β Request ${i + 1} failed:`, error.message); | |
| }) | |
| ); | |
| } | |
| await Promise.all(requests); | |
| const totalTime = Date.now() - startTime; | |
| console.log(`π All requests completed in ${totalTime}ms`); | |
| console.log(`π Average: ${totalTime / 10}ms per request`); | |
| } catch (error) { | |
| console.error('Simple rate limiter example error:', error.message); | |
| } | |
| } | |
| /** | |
| * Example 2: Bottleneck Rate Limiter | |
| */ | |
| async function bottleneckRateLimiterExample() { | |
| console.log('\n=== Example 2: Bottleneck Rate Limiter ==='); | |
| const bottleneckConfig = createBottleneckRateLimiter(); | |
| if (!bottleneckConfig) { | |
| console.log('βοΈ Skipping bottleneck example (bottleneck not installed)'); | |
| return; | |
| } | |
| try { | |
| const client = CoinbasePrimeClient.fromEnv(); | |
| const { limiter, createRequestTransformer } = bottleneckConfig; | |
| // Add the bottleneck rate limiting transformer | |
| client.addTransformRequest(createRequestTransformer()); | |
| const portfolioService = new PortfoliosService(client); | |
| console.log('π Making 8 requests with Bottleneck rate limiting...'); | |
| const startTime = Date.now(); | |
| const requests = []; | |
| for (let i = 0; i < 8; i++) { | |
| requests.push( | |
| portfolioService | |
| .listPortfolios() | |
| .then((result) => { | |
| const elapsed = Date.now() - startTime; | |
| console.log(`β Request ${i + 1} completed after ${elapsed}ms`); | |
| return result; | |
| }) | |
| .catch((error) => { | |
| console.error(`β Request ${i + 1} failed:`, error.message); | |
| }) | |
| ); | |
| } | |
| await Promise.all(requests); | |
| const totalTime = Date.now() - startTime; | |
| console.log(`π All requests completed in ${totalTime}ms`); | |
| // Show bottleneck statistics | |
| console.log('π Bottleneck Statistics:'); | |
| console.log(` Running: ${limiter.running()}`); | |
| console.log(` Pending: ${limiter.pending()}`); | |
| console.log(` Reservoir: ${limiter.reservoir()}`); | |
| } catch (error) { | |
| console.error('Bottleneck rate limiter example error:', error.message); | |
| } | |
| } | |
| /** | |
| * Example 3: Per-Call Rate Limiting | |
| * | |
| * This shows how to apply rate limiting to individual calls | |
| * rather than the entire client. | |
| */ | |
| async function perCallRateLimitingExample() { | |
| console.log('\n=== Example 3: Per-Call Rate Limiting ==='); | |
| try { | |
| const client = CoinbasePrimeClient.fromEnv(); | |
| const portfolioService = new PortfoliosService(client); | |
| // Create a rate limiter for this specific operation | |
| const operationRateLimiter = new SimpleRateLimiter(2, 2); // 2 requests per second | |
| console.log('π Making 5 requests with per-call rate limiting...'); | |
| const startTime = Date.now(); | |
| for (let i = 0; i < 5; i++) { | |
| try { | |
| // Apply rate limiting using call options | |
| const result = await portfolioService.listPortfolios( | |
| {}, // query params | |
| { | |
| transformRequest: operationRateLimiter.createRequestTransformer(), | |
| } | |
| ); | |
| const elapsed = Date.now() - startTime; | |
| console.log(`β Request ${i + 1} completed after ${elapsed}ms`); | |
| } catch (error) { | |
| console.error(`β Request ${i + 1} failed:`, error.message); | |
| } | |
| } | |
| const totalTime = Date.now() - startTime; | |
| console.log(`π All sequential requests completed in ${totalTime}ms`); | |
| } catch (error) { | |
| console.error('Per-call rate limiting example error:', error.message); | |
| } | |
| } | |
| /** | |
| * Run all examples | |
| */ | |
| async function runAllExamples() { | |
| console.log('π¦ Rate Limiter Examples for Prime SDK'); | |
| console.log('====================================='); | |
| try { | |
| await simpleRateLimiterExample(); | |
| await bottleneckRateLimiterExample(); | |
| await perCallRateLimitingExample(); | |
| console.log('\nβ All rate limiting examples completed!'); | |
| console.log('\nπ‘ Key Takeaways:'); | |
| console.log('- Use request transformers for client-wide rate limiting'); | |
| console.log('- Use call options for per-operation rate limiting'); | |
| console.log('- Simple rate limiters work for basic use cases'); | |
| console.log('- Use libraries like Bottleneck for production applications'); | |
| console.log('- Always monitor your rate limiting effectiveness'); | |
| } catch (error) { | |
| console.error('β Examples failed:', error.message); | |
| if (error.message.includes('PRIME_CREDENTIALS')) { | |
| console.log('\nπ§ Setup Instructions:'); | |
| console.log('1. Set PRIME_CREDENTIALS environment variable:'); | |
| console.log( | |
| ' export PRIME_CREDENTIALS=\'{"AccessKey":"your-key","SecretKey":"your-secret","Passphrase":"your-passphrase"}\'' | |
| ); | |
| console.log('\n2. Or create a .env file with your credentials'); | |
| } | |
| } | |
| } | |
| // Only run if this file is executed directly | |
| if (require.main === module) { | |
| runAllExamples().catch(console.error); | |
| } | |
| module.exports = { | |
| SimpleRateLimiter, | |
| createBottleneckRateLimiter, | |
| simpleRateLimiterExample, | |
| bottleneckRateLimiterExample, | |
| perCallRateLimitingExample, | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment