unlimit-keys 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +164 -24
- package/dist/cli.js +12 -6
- package/dist/cli.js.map +1 -1
- package/dist/schema/getLeastUsedKey.js +2 -2
- package/dist/schema/getLeastUsedKey.js.map +1 -1
- package/dist/schema/redis.d.ts.map +1 -1
- package/dist/schema/redis.js +11 -3
- package/dist/schema/redis.js.map +1 -1
- package/dist/scripts/sync-api-keys.d.ts +2 -1
- package/dist/scripts/sync-api-keys.d.ts.map +1 -1
- package/dist/scripts/sync-api-keys.js +44 -33
- package/dist/scripts/sync-api-keys.js.map +1 -1
- package/package.json +10 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anirban Majumder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,64 +1,204 @@
|
|
|
1
|
-
# unlimit-keys
|
|
1
|
+
# 🔑 unlimit-keys
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## **Never get blocked by rate limits again!**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Tired of hitting "Rate Limit Exceeded" errors? Use this tool to automatically rotate between your API keys. It picks the best key every time you make a request - so you can use AI services (Google Gemini, Groq, OpenAI, and more) without worrying about limits!
|
|
6
6
|
|
|
7
|
+
## **What you get:**
|
|
8
|
+
- 🚀 Use APIs 3x more (with 3 keys), 10x more (with 10 keys), etc.
|
|
9
|
+
- 🔄 Automatic key rotation - no manual switching
|
|
10
|
+
- ✅ Works with ANY API (AI, weather, payment, search, etc.)
|
|
11
|
+
- ⚡ Super fast - handles thousands of requests per second
|
|
12
|
+
- 🔒 Works perfectly with serverless apps and high-traffic services
|
|
13
|
+
|
|
14
|
+
## Quick Start 🚀
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
**Using npm:**
|
|
7
19
|
```bash
|
|
8
20
|
npm install unlimit-keys
|
|
9
21
|
```
|
|
10
22
|
|
|
23
|
+
**Using pnpm:**
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add unlimit-keys
|
|
26
|
+
```
|
|
27
|
+
|
|
11
28
|
## Setup
|
|
12
29
|
|
|
13
|
-
### 1.
|
|
30
|
+
### 1. Get Redis (Free!) 🗄️
|
|
31
|
+
|
|
32
|
+
We use [Upstash Redis](https://upstash.com/) - it's free and perfect for this. Follow their setup guide to get your Redis URL and token.
|
|
14
33
|
|
|
15
|
-
Create a `.env.local` file with your
|
|
34
|
+
Create a `.env.local` file with your keys:
|
|
16
35
|
|
|
17
36
|
```env
|
|
18
|
-
# Upstash Redis
|
|
37
|
+
# Upstash Redis (get from https://upstash.com - it's free!)
|
|
19
38
|
REDIS_URL=https://your-redis-url.upstash.io
|
|
20
39
|
REDIS_TOKEN=your-token
|
|
21
40
|
|
|
22
|
-
#
|
|
23
|
-
|
|
41
|
+
# Your API keys (comma or newline separated)
|
|
42
|
+
API_KEYS="key1,key2,key3"
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
### 2.
|
|
45
|
+
### 2. Save Your Keys 📝
|
|
27
46
|
|
|
28
|
-
Run
|
|
47
|
+
Run this once to save all your keys to Redis:
|
|
29
48
|
|
|
49
|
+
**Using npm:**
|
|
30
50
|
```bash
|
|
31
51
|
npx unlimit-keys sync
|
|
32
52
|
```
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
**Using pnpm:**
|
|
55
|
+
```bash
|
|
56
|
+
pnpm dlx unlimit-keys sync
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## How to Use It
|
|
35
60
|
|
|
36
|
-
###
|
|
61
|
+
### Option 1: In Your Code 💻
|
|
37
62
|
|
|
38
63
|
```typescript
|
|
39
64
|
import { getLeastUsedKey } from 'unlimit-keys';
|
|
40
65
|
|
|
41
|
-
// Get the
|
|
66
|
+
// Get the best key to use right now
|
|
42
67
|
const apiKey = await getLeastUsedKey();
|
|
43
|
-
console.log(apiKey);
|
|
68
|
+
console.log("Using API Key:", apiKey);
|
|
69
|
+
|
|
70
|
+
// Use it with your AI service
|
|
71
|
+
const response = await fetch('https://api.example.com/chat', {
|
|
72
|
+
headers: { 'Authorization': \`Bearer \${apiKey}\` }
|
|
73
|
+
});
|
|
44
74
|
```
|
|
45
75
|
|
|
46
|
-
###
|
|
76
|
+
### Option 2: Command Line 📱
|
|
47
77
|
|
|
48
78
|
```bash
|
|
49
|
-
# Get the least recently used key via CLI
|
|
50
79
|
npx unlimit-keys get-key
|
|
51
80
|
```
|
|
52
81
|
|
|
53
|
-
##
|
|
82
|
+
## Real Examples 📚
|
|
83
|
+
|
|
84
|
+
### Using with Google Gemini 🤖
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { getLeastUsedKey } from 'unlimit-keys';
|
|
88
|
+
|
|
89
|
+
async function askGemini(question: string) {
|
|
90
|
+
const apiKey = await getLeastUsedKey();
|
|
91
|
+
|
|
92
|
+
const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent', {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'x-goog-api-key': apiKey
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
contents: [{ parts: [{ text: question }] }]
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return response.json();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use many times without rate limits!
|
|
107
|
+
await askGemini('What is AI?');
|
|
108
|
+
await askGemini('How do I code?');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Using with Groq ⚡
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { getLeastUsedKey } from 'unlimit-keys';
|
|
115
|
+
import Groq from 'groq-sdk';
|
|
116
|
+
|
|
117
|
+
async function askGroq(question: string) {
|
|
118
|
+
const apiKey = await getLeastUsedKey();
|
|
119
|
+
|
|
120
|
+
const client = new Groq({ apiKey });
|
|
121
|
+
const message = await client.messages.create({
|
|
122
|
+
model: 'mixtral-8x7b-32768',
|
|
123
|
+
max_tokens: 1024,
|
|
124
|
+
messages: [{ role: 'user', content: question }]
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return message;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await askGroq('Tell me a joke');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Using with Google AI SDK 🎯
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { getLeastUsedKey } from 'unlimit-keys';
|
|
137
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
138
|
+
|
|
139
|
+
async function askWithSDK(question: string) {
|
|
140
|
+
const apiKey = await getLeastUsedKey();
|
|
141
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
142
|
+
|
|
143
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-flash-latest' });
|
|
144
|
+
const result = await model.generateContent(question);
|
|
145
|
+
|
|
146
|
+
return result.response.text();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await askWithSDK('What is machine learning?');
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Using with Regular APIs 🌍
|
|
153
|
+
|
|
154
|
+
Not just for AI! Works with any API that uses keys. Here's an example with a weather API:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { getLeastUsedKey } from 'unlimit-keys';
|
|
158
|
+
|
|
159
|
+
async function getWeather(city: string) {
|
|
160
|
+
const apiKey = await getLeastUsedKey();
|
|
161
|
+
|
|
162
|
+
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`, {
|
|
163
|
+
method: 'GET'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return response.json();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Make lots of weather requests without hitting rate limits!
|
|
170
|
+
await getWeather('London');
|
|
171
|
+
await getWeather('New York');
|
|
172
|
+
await getWeather('Tokyo');
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Supported Services 🌐
|
|
176
|
+
|
|
177
|
+
**AI & ML Services:**
|
|
178
|
+
- 🤖 [Google Gemini](https://ai.google.dev/)
|
|
179
|
+
- ⚡ [Groq](https://www.groq.com/)
|
|
180
|
+
- 🧠 [OpenAI](https://openai.com/)
|
|
181
|
+
- 🤖 [Anthropic Claude](https://www.anthropic.com/)
|
|
182
|
+
|
|
183
|
+
**Other APIs:**
|
|
184
|
+
- Weather APIs
|
|
185
|
+
- Payment APIs
|
|
186
|
+
- Search APIs
|
|
187
|
+
- Translation APIs
|
|
188
|
+
- And literally any API that uses keys!
|
|
189
|
+
|
|
190
|
+
**Works with anything that has API rate limits!**
|
|
191
|
+
|
|
192
|
+
## How It Works Behind the Scenes 🔧
|
|
193
|
+
|
|
194
|
+
1. **You have multiple keys** - Like 3 API keys
|
|
195
|
+
2. **We pick the best one** - We use the key that's been used the least recently
|
|
196
|
+
3. **We use it** - You make your API call
|
|
197
|
+
4. **We update the score** - We mark that key as just-used so we don't pick it again
|
|
198
|
+
5. **Repeat!** - Next time you call us, we pick a different key
|
|
54
199
|
|
|
55
|
-
|
|
56
|
-
2. **LRU Rotation**: When you request a key, the library atomically:
|
|
57
|
-
* Finds the key with the lowest score (oldest usage timestamp).
|
|
58
|
-
* Updates that key's score to the current timestamp.
|
|
59
|
-
* Returns the key.
|
|
60
|
-
3. **Concurrency**: Atomic Lua scripts ensure no race conditions, even with high concurrency across serverless functions.
|
|
200
|
+
This spreads out your requests so you hit rate limits slower!
|
|
61
201
|
|
|
62
202
|
## License
|
|
63
203
|
|
|
64
|
-
|
|
204
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -12,11 +12,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
const commander_1 = require("commander");
|
|
14
14
|
const getLeastUsedKey_1 = require("./schema/getLeastUsedKey");
|
|
15
|
+
const sync_api_keys_1 = require("./scripts/sync-api-keys");
|
|
15
16
|
const program = new commander_1.Command();
|
|
16
17
|
program
|
|
17
18
|
.name('unlimit-keys')
|
|
18
19
|
.description('Distributed LRU API key rotation using Redis')
|
|
19
|
-
.version('
|
|
20
|
+
.version(require('../package.json').version);
|
|
20
21
|
program
|
|
21
22
|
.command('get-key')
|
|
22
23
|
.description('Get the least recently used API key')
|
|
@@ -32,10 +33,15 @@ program
|
|
|
32
33
|
}));
|
|
33
34
|
program
|
|
34
35
|
.command('sync')
|
|
35
|
-
.description('Sync API keys from
|
|
36
|
-
.action(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
36
|
+
.description('Sync API keys from API_KEYS env var to Redis')
|
|
37
|
+
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
try {
|
|
39
|
+
yield (0, sync_api_keys_1.syncApiKeys)();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
40
46
|
program.parse();
|
|
41
47
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,yCAAoC;AACpC,8DAA2D;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,yCAAoC;AACpC,8DAA2D;AAC3D,2DAAsD;AAEtD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,cAAc,CAAC;KACpB,WAAW,CAAC,8CAA8C,CAAC;KAC3D,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC;AAE/C,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,qCAAqC,CAAC;KAClD,MAAM,CAAC,GAAS,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,IAAA,iCAAe,GAAE,CAAC;QACpC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAA,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,8CAA8C,CAAC;KAC3D,MAAM,CAAC,GAAS,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAA,2BAAW,GAAE,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAA,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -11,7 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.getLeastUsedKey = getLeastUsedKey;
|
|
13
13
|
const redis_1 = require("./redis");
|
|
14
|
-
const KEY_SET = '
|
|
14
|
+
const KEY_SET = 'API_KEYS';
|
|
15
15
|
/**
|
|
16
16
|
* Fetches the least recently used API key from the global pool.
|
|
17
17
|
* Automatically updates the key's usage timestamp.
|
|
@@ -37,7 +37,7 @@ function getLeastUsedKey() {
|
|
|
37
37
|
`;
|
|
38
38
|
const key = yield redis_1.redis.eval(script, [KEY_SET], [currentTime]);
|
|
39
39
|
if (!key) {
|
|
40
|
-
throw new Error('No API keys found in Redis. Please check your
|
|
40
|
+
throw new Error('No API keys found in Redis. Please check your API_KEYS environment variable and run the sync script.');
|
|
41
41
|
}
|
|
42
42
|
return key;
|
|
43
43
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLeastUsedKey.js","sourceRoot":"","sources":["../../src/schema/getLeastUsedKey.ts"],"names":[],"mappings":";;;;;;;;;;;AAWA,0CAwBC;AAnCD,mCAAgC;AAEhC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"getLeastUsedKey.js","sourceRoot":"","sources":["../../src/schema/getLeastUsedKey.ts"],"names":[],"mappings":";;;;;;;;;;;AAWA,0CAwBC;AAnCD,mCAAgC;AAEhC,MAAM,OAAO,GAAG,UAAU,CAAC;AAE3B;;;;;;GAMG;AACH,SAAsB,eAAe;;QACjC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAEtC,qBAAqB;QACrB,6DAA6D;QAC7D,uDAAuD;QACvD,oBAAoB;QACpB,MAAM,MAAM,GAAG;;;;;;;;KAQd,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,aAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAkB,CAAC;QAEhF,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,sGAAsG,CAAC,CAAC;QAC5H,CAAC;QAED,OAAO,GAAG,CAAC;IACf,CAAC;CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/schema/redis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/schema/redis.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAYvC,eAAO,MAAM,KAAK,OAGhB,CAAC"}
|
package/dist/schema/redis.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.redis = void 0;
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
4
8
|
const redis_1 = require("@upstash/redis");
|
|
5
|
-
|
|
9
|
+
// Load env files silently - .env.local overrides .env
|
|
10
|
+
dotenv_1.default.config({ path: '.env', quiet: true });
|
|
11
|
+
dotenv_1.default.config({ path: '.env.local', quiet: true });
|
|
12
|
+
const { REDIS_URL, REDIS_TOKEN } = process.env;
|
|
13
|
+
if (!REDIS_URL || !REDIS_TOKEN) {
|
|
6
14
|
throw new Error('Missing Redis credentials. Please set REDIS_URL and REDIS_TOKEN.');
|
|
7
15
|
}
|
|
8
16
|
exports.redis = new redis_1.Redis({
|
|
9
|
-
url:
|
|
10
|
-
token:
|
|
17
|
+
url: REDIS_URL,
|
|
18
|
+
token: REDIS_TOKEN,
|
|
11
19
|
});
|
|
12
20
|
//# sourceMappingURL=redis.js.map
|
package/dist/schema/redis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/schema/redis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/schema/redis.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,0CAAuC;AAEvC,sDAAsD;AACtD,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAEnD,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAE/C,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC;AAEY,QAAA,KAAK,GAAG,IAAI,aAAK,CAAC;IAC7B,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,WAAW;CACnB,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-api-keys.d.ts","sourceRoot":"","sources":["../../src/scripts/sync-api-keys.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync-api-keys.d.ts","sourceRoot":"","sources":["../../src/scripts/sync-api-keys.ts"],"names":[],"mappings":"AAIA,iBAAe,WAAW,kBA0DzB;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -9,54 +9,65 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
|
|
13
|
-
const redis_1 = require("
|
|
14
|
-
|
|
15
|
-
if (!process.env.REDIS_URL || !process.env.REDIS_TOKEN) {
|
|
16
|
-
throw new Error('Missing Upstash Redis credentials in .env file');
|
|
17
|
-
}
|
|
18
|
-
const redis = new redis_1.Redis({
|
|
19
|
-
url: process.env.REDIS_URL,
|
|
20
|
-
token: process.env.REDIS_TOKEN,
|
|
21
|
-
});
|
|
22
|
-
const KEY_SET = 'ru_api_keys';
|
|
12
|
+
exports.syncApiKeys = syncApiKeys;
|
|
13
|
+
const redis_1 = require("../schema/redis");
|
|
14
|
+
const KEY_SET = 'API_KEYS';
|
|
23
15
|
function syncApiKeys() {
|
|
24
16
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
console.log('
|
|
26
|
-
const keysString = process.env.
|
|
17
|
+
console.log('🔄 Starting API key sync...\n');
|
|
18
|
+
const keysString = process.env.API_KEYS || '';
|
|
27
19
|
const keysFromEnv = keysString.split(/[\n,]/).map(k => k.trim()).filter(Boolean);
|
|
28
20
|
if (keysFromEnv.length === 0) {
|
|
29
|
-
console.warn('No
|
|
21
|
+
console.warn('⚠️ No API_KEYS found in environment variables.');
|
|
22
|
+
console.warn(' Make sure API_KEYS is set in your .env or .env.local file.\n');
|
|
30
23
|
return;
|
|
31
24
|
}
|
|
32
|
-
|
|
25
|
+
console.log(`📋 Found ${keysFromEnv.length} keys in environment`);
|
|
33
26
|
// Get existing keys
|
|
34
|
-
const keysInRedis = yield redis.zrange(KEY_SET, 0, -1);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
27
|
+
const keysInRedis = yield redis_1.redis.zrange(KEY_SET, 0, -1);
|
|
28
|
+
console.log(`🗄️ Found ${keysInRedis.length} keys in Redis\n`);
|
|
29
|
+
// Use Sets for O(1) lookup instead of O(n) array includes
|
|
30
|
+
const envKeySet = new Set(keysFromEnv);
|
|
31
|
+
const redisKeySet = new Set(keysInRedis);
|
|
32
|
+
const keysToAdd = keysFromEnv.filter(k => !redisKeySet.has(k));
|
|
33
|
+
const keysToRemove = keysInRedis.filter(k => !envKeySet.has(k));
|
|
34
|
+
if (keysToAdd.length === 0 && keysToRemove.length === 0) {
|
|
35
|
+
console.log('✅ Keys are already in sync. No changes needed.\n');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const pipeline = redis_1.redis.pipeline();
|
|
38
39
|
if (keysToAdd.length > 0) {
|
|
39
|
-
console.log(
|
|
40
|
+
console.log(`➕ Adding ${keysToAdd.length} new key(s):`);
|
|
40
41
|
for (const key of keysToAdd) {
|
|
41
|
-
|
|
42
|
+
console.log(` • ${key.slice(0, 10)}...${key.slice(-4)}`);
|
|
42
43
|
pipeline.zadd(KEY_SET, { score: 0, member: key });
|
|
43
44
|
}
|
|
45
|
+
console.log();
|
|
44
46
|
}
|
|
45
47
|
if (keysToRemove.length > 0) {
|
|
46
|
-
console.log(
|
|
48
|
+
console.log(`➖ Removing ${keysToRemove.length} stale key(s):`);
|
|
49
|
+
for (const key of keysToRemove) {
|
|
50
|
+
console.log(` • ${key.slice(0, 10)}...${key.slice(-4)}`);
|
|
51
|
+
}
|
|
47
52
|
pipeline.zrem(KEY_SET, ...keysToRemove);
|
|
53
|
+
console.log();
|
|
48
54
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(
|
|
55
|
-
|
|
55
|
+
yield pipeline.exec();
|
|
56
|
+
console.log('─'.repeat(40));
|
|
57
|
+
console.log(`✨ Sync complete!`);
|
|
58
|
+
console.log(` Total keys in pool: ${keysFromEnv.length}`);
|
|
59
|
+
if (keysToAdd.length > 0)
|
|
60
|
+
console.log(` Added: ${keysToAdd.length}`);
|
|
61
|
+
if (keysToRemove.length > 0)
|
|
62
|
+
console.log(` Removed: ${keysToRemove.length}`);
|
|
63
|
+
console.log();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Run directly if this is the main module
|
|
67
|
+
if (require.main === module) {
|
|
68
|
+
syncApiKeys().catch((err) => {
|
|
69
|
+
console.error('Error syncing keys:', err);
|
|
70
|
+
process.exit(1);
|
|
56
71
|
});
|
|
57
72
|
}
|
|
58
|
-
syncApiKeys().catch((err) => {
|
|
59
|
-
console.error('Error syncing keys:', err);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
});
|
|
62
73
|
//# sourceMappingURL=sync-api-keys.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-api-keys.js","sourceRoot":"","sources":["../../src/scripts/sync-api-keys.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"sync-api-keys.js","sourceRoot":"","sources":["../../src/scripts/sync-api-keys.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unlimit-keys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An API key rotation system using Redis",
|
|
5
5
|
"author": "Anirban Majumder <anirbanmajumder.404@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/Anirban-Majumder/unlimit-keys.git"
|
|
8
|
+
"url": "git+https://github.com/Anirban-Majumder/unlimit-keys.git"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/Anirban-Majumder/unlimit-keys/issues"
|
|
@@ -31,17 +31,18 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"bin": {
|
|
34
|
-
"unlimit-keys": "
|
|
34
|
+
"unlimit-keys": "dist/cli.js"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc",
|
|
41
|
-
"
|
|
42
|
-
"sync": "node dist/scripts/sync-api-keys.js",
|
|
41
|
+
"dev": "tsc --watch",
|
|
43
42
|
"test": "vitest run",
|
|
44
|
-
"
|
|
43
|
+
"sync": "node dist/scripts/sync-api-keys.js",
|
|
44
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
45
|
+
"publish": "npm publish --access public"
|
|
45
46
|
},
|
|
46
47
|
"type": "commonjs",
|
|
47
48
|
"engines": {
|
|
@@ -49,11 +50,11 @@
|
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@upstash/redis": ">=1.0.0",
|
|
52
|
-
"commander": "^14.0.0"
|
|
53
|
+
"commander": "^14.0.0",
|
|
54
|
+
"dotenv": "^17.2.3"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@types/node": "^24.0.15",
|
|
56
|
-
"ts-node": "^10.9.2",
|
|
57
58
|
"typescript": "^5.8.3",
|
|
58
59
|
"vitest": "^4.0.17"
|
|
59
60
|
},
|
|
@@ -65,4 +66,4 @@
|
|
|
65
66
|
"optional": true
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
}
|
|
69
|
+
}
|