@splashcodex/api-key-manager 4.1.0 → 5.0.2
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/README.md +129 -201
- package/bin/cli.js +50 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/persistence/file.d.ts +43 -0
- package/dist/persistence/file.js +82 -0
- package/dist/persistence/file.js.map +1 -0
- package/dist/persistence/index.d.ts +7 -0
- package/dist/persistence/index.js +12 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory.d.ts +30 -0
- package/dist/persistence/memory.js +43 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/presets/base.d.ts +138 -0
- package/dist/presets/base.js +227 -0
- package/dist/presets/base.js.map +1 -0
- package/dist/presets/gemini.d.ts +60 -0
- package/dist/presets/gemini.js +77 -0
- package/dist/presets/gemini.js.map +1 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +16 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/multi.d.ts +120 -0
- package/dist/presets/multi.js +210 -0
- package/dist/presets/multi.js.map +1 -0
- package/dist/presets/openai.d.ts +45 -0
- package/dist/presets/openai.js +62 -0
- package/dist/presets/openai.js.map +1 -0
- package/package.json +88 -5
- package/src/index.ts +24 -3
- package/src/persistence/file.ts +89 -0
- package/src/persistence/index.ts +7 -0
- package/src/persistence/memory.ts +43 -0
- package/src/presets/base.ts +323 -0
- package/src/presets/gemini.ts +84 -0
- package/src/presets/index.ts +10 -0
- package/src/presets/multi.ts +280 -0
- package/src/presets/openai.ts +69 -0
package/README.md
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
# @splashcodex/ApiKeyManager
|
|
1
|
+
# @splashcodex/ApiKeyManager v5.0 — Ecosystem Edition
|
|
2
2
|
|
|
3
|
-
> Universal API Key
|
|
3
|
+
> Universal API Key Management Gateway with Provider Presets, Built-in Persistence, and Multi-Provider Vault.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@splashcodex/ApiKeyManager)
|
|
6
6
|
|
|
7
|
+
## New in v5.0 (Ecosystem Edition)
|
|
8
|
+
|
|
9
|
+
- **Provider Presets** — One-line setup for `GeminiManager`, `OpenAIManager`, and `MultiManager`.
|
|
10
|
+
- **Automatic Env Parsing** — Reads `GOOGLE_GEMINI_API_KEY`, `OPENAI_API_KEY`, etc. (supports JSON arrays and comma-separated strings).
|
|
11
|
+
- **Built-in Persistence** — `FileStorage` (survives restarts) and `MemoryStorage` included.
|
|
12
|
+
- **Singleton Pattern** — Thread-safe singletons with `getInstance()` and `Result<T>` pattern.
|
|
13
|
+
- **Multi-Provider Vault** — Manage multiple providers (`gemini`, `openai`, `anthropic`) from a single entry point.
|
|
14
|
+
- **Centralized API Gateway** — Built-in Fastify proxy server to centralize AI requests for multiple apps securely.
|
|
15
|
+
|
|
7
16
|
## Features
|
|
8
17
|
|
|
9
18
|
- **Circuit Breaker** — Keys transition through `CLOSED → OPEN → HALF_OPEN → DEAD`
|
|
@@ -12,16 +21,9 @@
|
|
|
12
21
|
- **`execute()` Wrapper** — Single method: get key → call → latency → retry → fallback
|
|
13
22
|
- **Event Emitter** — Typed lifecycle hooks for monitoring & alerting
|
|
14
23
|
- **Auto-Retry with Backoff** — Built-in retry loop with exponential backoff + jitter
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **Health Checks** — Periodic key validation and auto-recovery
|
|
19
|
-
- **Bulkhead / Concurrency** — Limits concurrent `execute()` calls
|
|
20
|
-
- **Semantic Cache** *(v4 NEW)* — Cosine-similarity cache with pluggable embeddings
|
|
21
|
-
- **Streaming Support** *(v4.1 NEW)* — `executeStream()` with initial retry + cache replay
|
|
22
|
-
- **Recursion Guard** *(v4 NEW)* — Prevents infinite loops when `getEmbedding` calls `execute()`
|
|
23
|
-
- **State Persistence** — Survives restarts via pluggable storage
|
|
24
|
-
- **100% Backward Compatible** — v1.x, v2.x, and v3.x code works without changes
|
|
24
|
+
- **Semantic Cache** — Cosine-similarity cache with pluggable embeddings
|
|
25
|
+
- **Streaming Support** — `executeStream()` with initial retry + cache replay
|
|
26
|
+
- **100% Backward Compatible** — v1.x through v4.x code works without changes
|
|
25
27
|
|
|
26
28
|
## Installation
|
|
27
29
|
|
|
@@ -29,249 +31,175 @@
|
|
|
29
31
|
npm install @splashcodex/api-key-manager
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Simple (v1/v2 compatible)
|
|
38
|
-
const manager = new ApiKeyManager(['key1', 'key2', 'key3']);
|
|
39
|
-
const key = manager.getKey();
|
|
40
|
-
manager.markSuccess(key!);
|
|
41
|
-
|
|
42
|
-
// v3+ — Full power
|
|
43
|
-
const result = await manager.execute(
|
|
44
|
-
(key) => fetch(`https://api.example.com?key=${key}`),
|
|
45
|
-
{ maxRetries: 3, timeoutMs: 5000 }
|
|
46
|
-
);
|
|
34
|
+
### 🚀 Auto-Scaffold (New!)
|
|
35
|
+
The fastest way to get started is to run the `init` command in your project directory:
|
|
36
|
+
```bash
|
|
37
|
+
npx @splashcodex/api-key-manager init
|
|
47
38
|
```
|
|
39
|
+
This will automatically create a `.env` template and a `demo.ts` file showing the Gemini Preset in action.
|
|
48
40
|
|
|
49
|
-
##
|
|
41
|
+
## Quick Start (v5 Presets)
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
The fastest way to get started in any CodeDex repository.
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
### Gemini Preset
|
|
46
|
+
Reads `GOOGLE_GEMINI_API_KEY` or `GEMINI_API_KEY` from environment.
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
threshold: 0.92, // 92% cosine similarity to match
|
|
59
|
-
ttlMs: 86400000, // 24-hour TTL (default)
|
|
60
|
-
getEmbedding: async (text) => {
|
|
61
|
-
// Your embedding function (e.g. OpenAI, Gemini, local model)
|
|
62
|
-
return await myEmbeddingModel.embed(text);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
48
|
+
```typescript
|
|
49
|
+
import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
const result = GeminiManager.getInstance();
|
|
52
|
+
if (!result.success) throw result.error;
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
const
|
|
54
|
+
const gemini = result.data;
|
|
55
|
+
const response = await gemini.execute(async (key) => {
|
|
56
|
+
// result.data is the underlying ApiKeyManager
|
|
57
|
+
return await callGemini(key, "Hello!");
|
|
58
|
+
});
|
|
72
59
|
```
|
|
73
60
|
|
|
74
|
-
###
|
|
61
|
+
### Centralized API Gateway (New!)
|
|
75
62
|
|
|
76
|
-
|
|
63
|
+
Run a standalone proxy server that acts as a single point of entry for all your microservices or repositories. It centralizes key management, circuit breaking, and rate limiting across applications.
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
1. Install global/local and export your keys:
|
|
66
|
+
```bash
|
|
67
|
+
export GOOGLE_GEMINI_API_KEY="AIzaSy...1,AIzaSy...2"
|
|
68
|
+
```
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
70
|
+
2. Start the gateway:
|
|
71
|
+
```bash
|
|
72
|
+
npm run gateway
|
|
73
|
+
# Server runs on http://localhost:9000
|
|
86
74
|
```
|
|
87
75
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
76
|
+
3. Call the proxy from ANY language without managing keys in the client app:
|
|
77
|
+
```bash
|
|
78
|
+
curl -X POST http://localhost:9000/v1/generate \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
-d '{"provider": "gemini", "prompt": "Hello!"}'
|
|
81
|
+
```
|
|
82
|
+
Route supports both `/v1/generate` (Standard JSON) and `/v1/stream` (Server-Sent Events). Monitoring available at `/v1/health` and `/v1/providers`.
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
> the cache automatically skips on nested calls to prevent infinite recursion.
|
|
84
|
+
---
|
|
94
85
|
|
|
95
|
-
|
|
86
|
+
### Multi-Provider Vault
|
|
96
87
|
|
|
97
|
-
|
|
88
|
+
Manage ALL your provider keys across your system from one pool.Perfect for gateways or complex bots handling multiple models.
|
|
98
89
|
|
|
99
90
|
```typescript
|
|
100
|
-
|
|
101
|
-
storage: localStorage,
|
|
102
|
-
strategy: new WeightedStrategy(),
|
|
103
|
-
fallbackFn: () => cachedResponse,
|
|
104
|
-
concurrency: 10
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const result = await manager.execute(
|
|
108
|
-
async (key, signal) => {
|
|
109
|
-
const res = await fetch(url, { headers: { 'x-api-key': key }, signal });
|
|
110
|
-
return res.json();
|
|
111
|
-
},
|
|
112
|
-
{ maxRetries: 3, timeoutMs: 10000 }
|
|
113
|
-
);
|
|
114
|
-
// Handles: key selection → cache → timeout → retry → fallback → latency tracking
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Event Emitter
|
|
91
|
+
import { MultiManager } from '@splashcodex/api-key-manager/presets/multi';
|
|
118
92
|
|
|
119
|
-
|
|
93
|
+
const vault = MultiManager.getInstance({
|
|
94
|
+
providers: {
|
|
95
|
+
gemini: { envKeys: ['GOOGLE_GEMINI_API_KEY'] },
|
|
96
|
+
openai: { envKeys: ['OPENAI_API_KEY'] }
|
|
97
|
+
}
|
|
98
|
+
}).data!;
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
manager.on('circuitOpen', (key) => metrics.increment('circuit_opens'));
|
|
124
|
-
manager.on('keyRecovered', (key) => log(`Key ${key} recovered`));
|
|
125
|
-
manager.on('retry', (key, attempt, delay) => log(`Retry #${attempt} in ${delay}ms`));
|
|
126
|
-
manager.on('fallback', (reason) => log(`Fallback triggered: ${reason}`));
|
|
127
|
-
manager.on('allKeysExhausted', () => alert('No healthy keys!'));
|
|
128
|
-
manager.on('bulkheadRejected', () => metrics.increment('rejected'));
|
|
129
|
-
manager.on('healthCheckPassed', (key) => log(`${key} healthy`));
|
|
130
|
-
manager.on('healthCheckFailed', (key, err) => log(`${key} unhealthy`));
|
|
100
|
+
// Route by provider
|
|
101
|
+
const res = await vault.execute(fn, { provider: 'gemini' });
|
|
131
102
|
```
|
|
132
103
|
|
|
133
|
-
|
|
104
|
+
---
|
|
134
105
|
|
|
135
|
-
|
|
106
|
+
## v5.0 — Architecture & Persistence
|
|
136
107
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const manager = new ApiKeyManager(
|
|
141
|
-
[
|
|
142
|
-
{ key: 'free-key-1', weight: 1.0 },
|
|
143
|
-
{ key: 'free-key-2', weight: 1.0 },
|
|
144
|
-
{ key: 'paid-backup', weight: 0.1 },
|
|
145
|
-
],
|
|
146
|
-
{ strategy: new WeightedStrategy() }
|
|
147
|
-
);
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Latency (Performance)
|
|
108
|
+
### Built-in Persistence
|
|
109
|
+
State (cooling down keys, dead keys) now survives application restarts by default.
|
|
151
110
|
|
|
152
111
|
```typescript
|
|
153
|
-
import {
|
|
112
|
+
import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
|
|
154
113
|
|
|
155
|
-
const manager = new ApiKeyManager(keys, {
|
|
156
|
-
|
|
114
|
+
const manager = new ApiKeyManager(keys, {
|
|
115
|
+
storage: new FileStorage({
|
|
116
|
+
filePath: './api_state.json'
|
|
117
|
+
})
|
|
118
|
+
});
|
|
157
119
|
```
|
|
158
120
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Route requests to specific providers:
|
|
121
|
+
### Subpath Imports
|
|
122
|
+
To keep your bundles small, you can import only what you need:
|
|
162
123
|
|
|
163
124
|
```typescript
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
{ key: 'sk-openai-2', weight: 1.0, provider: 'openai' },
|
|
167
|
-
{ key: 'AIza-gemini', weight: 0.5, provider: 'gemini' },
|
|
168
|
-
]);
|
|
169
|
-
|
|
170
|
-
const openaiKey = manager.getKeyByProvider('openai');
|
|
171
|
-
const geminiKey = manager.getKeyByProvider('gemini');
|
|
125
|
+
import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
|
|
126
|
+
import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
|
|
172
127
|
```
|
|
173
128
|
|
|
174
|
-
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## v4.0 — Semantic Cache
|
|
175
132
|
|
|
176
|
-
|
|
133
|
+
Automatically cache API responses by semantic similarity.
|
|
177
134
|
|
|
178
135
|
```typescript
|
|
179
|
-
manager
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
136
|
+
const manager = new ApiKeyManager(['key1', 'key2'], {
|
|
137
|
+
semanticCache: {
|
|
138
|
+
threshold: 0.92,
|
|
139
|
+
getEmbedding: async (text) => await myModel.embed(text)
|
|
140
|
+
}
|
|
184
141
|
});
|
|
185
142
|
|
|
186
|
-
|
|
187
|
-
|
|
143
|
+
// Cache HIT if prompt is semantically similar
|
|
144
|
+
const r1 = await manager.execute(apiFn, { prompt: 'What is the weather?' });
|
|
145
|
+
const r2 = await manager.execute(apiFn, { prompt: 'How is the weather today?' });
|
|
188
146
|
```
|
|
189
147
|
|
|
190
|
-
|
|
148
|
+
### v4.1 — Streaming Support
|
|
149
|
+
Real-time response handling with the same resilience as `execute()`.
|
|
191
150
|
|
|
192
151
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (classification.retryable) {
|
|
201
|
-
const delay = manager.calculateBackoff(attempt);
|
|
202
|
-
await sleep(delay);
|
|
203
|
-
}
|
|
152
|
+
const stream = await manager.executeStream(async (key) => {
|
|
153
|
+
return await gemini.generateContentStream({ prompt: "..." });
|
|
154
|
+
}, { prompt: "..." });
|
|
155
|
+
|
|
156
|
+
for await (const chunk of stream) {
|
|
157
|
+
console.log(chunk.text());
|
|
204
158
|
}
|
|
205
159
|
```
|
|
206
160
|
|
|
207
|
-
|
|
161
|
+
---
|
|
208
162
|
|
|
209
|
-
|
|
163
|
+
## execute() Wrapper
|
|
164
|
+
|
|
165
|
+
Wraps the entire lifecycle into one method:
|
|
210
166
|
|
|
211
167
|
```typescript
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
fallbackFn?, // () => any — called when all keys exhausted
|
|
220
|
-
concurrency?, // Max concurrent execute() calls
|
|
221
|
-
semanticCache?, // v4: { threshold, ttlMs, getEmbedding }
|
|
222
|
-
})
|
|
168
|
+
const result = await manager.execute(
|
|
169
|
+
async (key, signal) => {
|
|
170
|
+
const res = await fetch(url, { headers: { 'x-api-key': key }, signal });
|
|
171
|
+
return res.json();
|
|
172
|
+
},
|
|
173
|
+
{ maxRetries: 3, timeoutMs: 10000 }
|
|
174
|
+
);
|
|
223
175
|
```
|
|
224
176
|
|
|
225
|
-
|
|
177
|
+
## API Reference
|
|
178
|
+
|
|
179
|
+
### Presets
|
|
180
|
+
|
|
181
|
+
| Class | Env Vars | Description |
|
|
182
|
+
|-------|----------|-------------|
|
|
183
|
+
| `GeminiManager` | `GOOGLE_GEMINI_API_KEY`, `GEMINI_API_KEY` | Gemini-optimized singleton |
|
|
184
|
+
| `OpenAIManager` | `OPENAI_API_KEY` | OpenAI-optimized singleton |
|
|
185
|
+
| `MultiManager` | Custom | Vault for multiple provider pools |
|
|
186
|
+
|
|
187
|
+
### Persistence
|
|
188
|
+
|
|
189
|
+
| Class | Description |
|
|
190
|
+
|-------|-------------|
|
|
191
|
+
| `FileStorage` | Persists to a JSON file (recommended for servers) |
|
|
192
|
+
| `MemoryStorage` | In-memory only (best for serverless/short-lived) |
|
|
193
|
+
|
|
194
|
+
### Core Methods
|
|
226
195
|
|
|
227
196
|
| Method | Description |
|
|
228
197
|
|--------|-------------|
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `execute(fn, options?)` | Full lifecycle wrapper with retry/timeout |
|
|
235
|
-
| `executeStream(fn, options?)` | Streaming lifecycle wrapper |
|
|
236
|
-
| `calculateBackoff(attempt)` | Get backoff delay with jitter |
|
|
237
|
-
| `getStats()` | Get pool health statistics |
|
|
238
|
-
| `getKeyCount()` | Count of non-DEAD keys |
|
|
239
|
-
| `setHealthCheck(fn)` | Set health check function |
|
|
240
|
-
| `startHealthChecks(ms)` | Start periodic health checks |
|
|
241
|
-
| `stopHealthChecks()` | Stop health checks |
|
|
242
|
-
|
|
243
|
-
### Events
|
|
244
|
-
|
|
245
|
-
| Event | Payload | Trigger |
|
|
246
|
-
|-------|---------|---------|
|
|
247
|
-
| `keyDead` | `key: string` | Key marked as permanently dead |
|
|
248
|
-
| `circuitOpen` | `key: string` | Key circuit opened (cooldown) |
|
|
249
|
-
| `circuitHalfOpen` | `key: string` | Key entering test phase |
|
|
250
|
-
| `keyRecovered` | `key: string` | Key recovered from failure |
|
|
251
|
-
| `fallback` | `reason: string` | Fallback function invoked |
|
|
252
|
-
| `allKeysExhausted` | — | All keys dead, no fallback |
|
|
253
|
-
| `retry` | `key, attempt, delayMs` | Retry attempt starting |
|
|
254
|
-
| `executeSuccess` | `key, durationMs` | execute() completed successfully |
|
|
255
|
-
| `executeFailed` | `key, error` | execute() attempt failed |
|
|
256
|
-
| `bulkheadRejected` | — | Concurrency limit reached |
|
|
257
|
-
| `healthCheckPassed` | `key: string` | Health check succeeded |
|
|
258
|
-
| `healthCheckFailed` | `key, error` | Health check failed |
|
|
259
|
-
|
|
260
|
-
### Custom Errors
|
|
261
|
-
|
|
262
|
-
| Error | When |
|
|
263
|
-
|-------|------|
|
|
264
|
-
| `TimeoutError` | Request exceeded `timeoutMs` |
|
|
265
|
-
| `BulkheadRejectionError` | Concurrency limit exceeded |
|
|
266
|
-
| `AllKeysExhaustedError` | All keys dead, no fallback |
|
|
267
|
-
|
|
268
|
-
### Strategies
|
|
269
|
-
|
|
270
|
-
| Strategy | Algorithm | Best For |
|
|
271
|
-
|----------|-----------|----------|
|
|
272
|
-
| `StandardStrategy` | Least Failures → LRU | General use |
|
|
273
|
-
| `WeightedStrategy` | Probabilistic by weight | Cost optimization |
|
|
274
|
-
| `LatencyStrategy` | Lowest avg latency | Performance |
|
|
198
|
+
| `execute(fn, opts)` | Standard wrapper |
|
|
199
|
+
| `executeStream(fn, opts)` | Streaming wrapper |
|
|
200
|
+
| `getStats()` | Get pool health |
|
|
201
|
+
| `getKey()` | Manual key rotation |
|
|
202
|
+
| `markFailed(key, err)` | Manual failure reporting |
|
|
275
203
|
|
|
276
204
|
## License
|
|
277
205
|
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0];
|
|
7
|
+
|
|
8
|
+
function printDocs() {
|
|
9
|
+
console.log(`\x1b[92m🌟 @splashcodex/api-key-manager v5.0\x1b[0m`);
|
|
10
|
+
console.log(`\x1b[96m📚 Documentation: https://www.npmjs.com/package/@splashcodex/ApiKeyManager\x1b[0m\n`);
|
|
11
|
+
console.log(`\x1b[93m🚀 Commands:\x1b[0m`);
|
|
12
|
+
console.log(` npx @splashcodex/api-key-manager init \x1b[90m# Scaffold a demo project in the current directory\x1b[0m`);
|
|
13
|
+
console.log(`\n\x1b[95m💡 Tip: Need help? Run init to see it in action!\x1b[0m\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function initProject() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const envPath = path.join(cwd, '.env');
|
|
19
|
+
const demoPath = path.join(cwd, 'demo.ts');
|
|
20
|
+
|
|
21
|
+
console.log(`\n\x1b[96m🚀 Initializing @splashcodex/api-key-manager environment...\x1b[0m\n`);
|
|
22
|
+
|
|
23
|
+
// Create .env
|
|
24
|
+
if (!fs.existsSync(envPath)) {
|
|
25
|
+
fs.writeFileSync(envPath, `GOOGLE_GEMINI_API_KEY="your_api_key_1,your_api_key_2"\nOPENAI_API_KEY="sk-..."\n`);
|
|
26
|
+
console.log(`\x1b[92m[✔] Created .env file\x1b[0m`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(`\x1b[90m[-] .env already exists. Remember to add GOOGLE_GEMINI_API_KEY!\x1b[0m`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create demo.ts
|
|
32
|
+
if (!fs.existsSync(demoPath)) {
|
|
33
|
+
const tsCode = `import 'dotenv/config';\nimport { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';\n\nasync function main() {\n console.log("Initialize GeminiManager...");\n // Automatically parse GOOGLE_GEMINI_API_KEY from env\n const result = GeminiManager.getInstance();\n if (!result.success) {\n console.error("Failed to initialize:", result.error);\n return;\n }\n \n const gemini = result.data;\n \n console.log("Executing resilient request...");\n // Automatically handles key rotation and 429 quota limits\n try {\n const response = await gemini.execute(async (key) => {\n console.log(\`[Network] Sending request with key: \${key.substring(0, 8)}...\`);\n // Replace with actual fetch: await fetch(..., { headers: { 'x-goog-api-key': key } })\n return "Success: Simulated API Response!";\n });\n console.log("Result:", response);\n } catch (e) {\n console.error("All keys exhausted or network failed.", e.message);\n }\n}\n\nmain();\n`;
|
|
34
|
+
fs.writeFileSync(demoPath, tsCode);
|
|
35
|
+
console.log(`\x1b[92m[✔] Created demo.ts\x1b[0m`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`\x1b[90m[-] demo.ts already exists.\x1b[0m`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`\n\x1b[93m🎉 Setup Complete!\x1b[0m`);
|
|
41
|
+
console.log(`To run the demo:`);
|
|
42
|
+
console.log(`\x1b[36m npm install dotenv\x1b[0m`);
|
|
43
|
+
console.log(`\x1b[36m npx ts-node demo.ts\x1b[0m\n`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (command === 'init') {
|
|
47
|
+
initProject();
|
|
48
|
+
} else {
|
|
49
|
+
printDocs();
|
|
50
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Universal ApiKeyManager
|
|
2
|
+
* Universal ApiKeyManager v5.0 — Ecosystem Edition
|
|
3
3
|
* Implements: Rotation, Circuit Breaker, Persistence, Exponential Backoff, Strategies,
|
|
4
4
|
* Event Emitter, Fallback, execute(), Timeout, Auto-Retry, Provider Tags,
|
|
5
5
|
* Health Checks, Bulkhead/Concurrency
|
|
6
|
+
* NEW in v5.0: Provider Presets (GeminiManager, OpenAIManager, MultiManager),
|
|
7
|
+
* Built-in Persistence (FileStorage, MemoryStorage)
|
|
6
8
|
* Gemini-Specific: finishReason handling, Safety blocks, RECITATION detection
|
|
7
9
|
*/
|
|
8
10
|
import { EventEmitter } from 'events';
|
|
11
|
+
export { FileStorage } from './persistence/file';
|
|
12
|
+
export type { FileStorageOptions } from './persistence/file';
|
|
13
|
+
export { MemoryStorage } from './persistence/memory';
|
|
9
14
|
export interface KeyState {
|
|
10
15
|
key: string;
|
|
11
16
|
failCount: number;
|
|
@@ -103,7 +108,7 @@ export declare class WeightedStrategy implements LoadBalancingStrategy {
|
|
|
103
108
|
next(candidates: KeyState[]): KeyState | null;
|
|
104
109
|
}
|
|
105
110
|
/**
|
|
106
|
-
* Latency Strategy: Pick lowest average latency
|
|
111
|
+
* Latency Strategy: Pick lowest average latency with LRU tie-break
|
|
107
112
|
*/
|
|
108
113
|
export declare class LatencyStrategy implements LoadBalancingStrategy {
|
|
109
114
|
next(candidates: KeyState[]): KeyState | null;
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Universal ApiKeyManager
|
|
3
|
+
* Universal ApiKeyManager v5.0 — Ecosystem Edition
|
|
4
4
|
* Implements: Rotation, Circuit Breaker, Persistence, Exponential Backoff, Strategies,
|
|
5
5
|
* Event Emitter, Fallback, execute(), Timeout, Auto-Retry, Provider Tags,
|
|
6
6
|
* Health Checks, Bulkhead/Concurrency
|
|
7
|
+
* NEW in v5.0: Provider Presets (GeminiManager, OpenAIManager, MultiManager),
|
|
8
|
+
* Built-in Persistence (FileStorage, MemoryStorage)
|
|
7
9
|
* Gemini-Specific: finishReason handling, Safety blocks, RECITATION detection
|
|
8
10
|
*/
|
|
9
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ApiKeyManager = exports.SemanticCache = exports.LatencyStrategy = exports.WeightedStrategy = exports.StandardStrategy = exports.AllKeysExhaustedError = exports.BulkheadRejectionError = exports.TimeoutError = void 0;
|
|
12
|
+
exports.ApiKeyManager = exports.SemanticCache = exports.LatencyStrategy = exports.WeightedStrategy = exports.StandardStrategy = exports.AllKeysExhaustedError = exports.BulkheadRejectionError = exports.TimeoutError = exports.MemoryStorage = exports.FileStorage = void 0;
|
|
11
13
|
const events_1 = require("events");
|
|
14
|
+
// ─── Re-exports: Persistence ─────────────────────────────────────────────────
|
|
15
|
+
// Persistence adapters can be imported from root or via subpath
|
|
16
|
+
var file_1 = require("./persistence/file");
|
|
17
|
+
Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return file_1.FileStorage; } });
|
|
18
|
+
var memory_1 = require("./persistence/memory");
|
|
19
|
+
Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return memory_1.MemoryStorage; } });
|
|
12
20
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
13
21
|
const CONFIG = {
|
|
14
22
|
MAX_CONSECUTIVE_FAILURES: 5,
|
|
@@ -83,13 +91,17 @@ class WeightedStrategy {
|
|
|
83
91
|
}
|
|
84
92
|
exports.WeightedStrategy = WeightedStrategy;
|
|
85
93
|
/**
|
|
86
|
-
* Latency Strategy: Pick lowest average latency
|
|
94
|
+
* Latency Strategy: Pick lowest average latency with LRU tie-break
|
|
87
95
|
*/
|
|
88
96
|
class LatencyStrategy {
|
|
89
97
|
next(candidates) {
|
|
90
98
|
if (candidates.length === 0)
|
|
91
99
|
return null;
|
|
92
|
-
candidates.sort((a, b) =>
|
|
100
|
+
candidates.sort((a, b) => {
|
|
101
|
+
if (a.averageLatency !== b.averageLatency)
|
|
102
|
+
return a.averageLatency - b.averageLatency;
|
|
103
|
+
return a.lastUsed - b.lastUsed; // LRU tie-break
|
|
104
|
+
});
|
|
93
105
|
return candidates[0];
|
|
94
106
|
}
|
|
95
107
|
}
|