@splashcodex/api-key-manager 4.1.0 → 5.0.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.
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * File-Based Storage Adapter
4
+ *
5
+ * Persists API key state to a JSON file on disk.
6
+ * Survives process restarts so keys don't reset to CLOSED
7
+ * when an app reboots.
8
+ *
9
+ * Extracted from the proven WhatsDeX adapter pattern.
10
+ *
11
+ * @module persistence/file
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FileStorage = void 0;
15
+ const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ const os_1 = require("os");
18
+ /**
19
+ * File-based storage adapter for ApiKeyManager.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { ApiKeyManager } from '@splashcodex/api-key-manager';
24
+ * import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
25
+ *
26
+ * const manager = new ApiKeyManager(keys, {
27
+ * storage: new FileStorage({ filePath: './state.json' })
28
+ * });
29
+ * ```
30
+ */
31
+ class FileStorage {
32
+ filePath;
33
+ constructor(options = {}) {
34
+ this.filePath = options.filePath || (0, path_1.join)((0, os_1.tmpdir)(), 'codedex_api_key_state.json');
35
+ if (options.clearOnInit !== false) {
36
+ this.clear();
37
+ }
38
+ }
39
+ getItem(_key) {
40
+ try {
41
+ if ((0, fs_1.existsSync)(this.filePath)) {
42
+ return (0, fs_1.readFileSync)(this.filePath, 'utf-8');
43
+ }
44
+ }
45
+ catch {
46
+ // Silently fail — state will be rebuilt from scratch
47
+ }
48
+ return null;
49
+ }
50
+ setItem(_key, value) {
51
+ try {
52
+ const dir = (0, path_1.dirname)(this.filePath);
53
+ if (!(0, fs_1.existsSync)(dir)) {
54
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
55
+ }
56
+ (0, fs_1.writeFileSync)(this.filePath, value, 'utf-8');
57
+ }
58
+ catch {
59
+ // Silently fail — state will be lost on restart
60
+ }
61
+ }
62
+ /**
63
+ * Delete the persisted state file.
64
+ * Useful for clearing stale dead-key states from a previous session.
65
+ */
66
+ clear() {
67
+ try {
68
+ if ((0, fs_1.existsSync)(this.filePath)) {
69
+ (0, fs_1.unlinkSync)(this.filePath);
70
+ }
71
+ }
72
+ catch {
73
+ // Silently fail
74
+ }
75
+ }
76
+ /** Get the path to the state file (for debugging/logging) */
77
+ getFilePath() {
78
+ return this.filePath;
79
+ }
80
+ }
81
+ exports.FileStorage = FileStorage;
82
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/persistence/file.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAEH,2BAAoF;AACpF,+BAAqC;AACrC,2BAA4B;AAS5B;;;;;;;;;;;;GAYG;AACH,MAAa,WAAW;IACZ,QAAQ,CAAS;IAEzB,YAAY,UAA8B,EAAE;QACxC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAA,WAAI,EAAC,IAAA,WAAM,GAAE,EAAE,4BAA4B,CAAC,CAAC;QAEjF,IAAI,OAAO,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;IACL,CAAC;IAED,OAAO,CAAC,IAAY;QAChB,IAAI,CAAC;YACD,IAAI,IAAA,eAAU,EAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,OAAO,IAAA,iBAAY,EAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAChD,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,qDAAqD;QACzD,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,IAAY,EAAE,KAAa;QAC/B,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAA,cAAO,EAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,CAAC,IAAA,eAAU,EAAC,GAAG,CAAC,EAAE,CAAC;gBACnB,IAAA,cAAS,EAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxC,CAAC;YACD,IAAA,kBAAa,EAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACL,gDAAgD;QACpD,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,KAAK;QACD,IAAI,CAAC;YACD,IAAI,IAAA,eAAU,EAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,IAAA,eAAU,EAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,gBAAgB;QACpB,CAAC;IACL,CAAC;IAED,6DAA6D;IAC7D,WAAW;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;CACJ;AApDD,kCAoDC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Persistence Adapters
3
+ * @module persistence
4
+ */
5
+ export { FileStorage } from './file';
6
+ export type { FileStorageOptions } from './file';
7
+ export { MemoryStorage } from './memory';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryStorage = exports.FileStorage = void 0;
4
+ /**
5
+ * Persistence Adapters
6
+ * @module persistence
7
+ */
8
+ var file_1 = require("./file");
9
+ Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return file_1.FileStorage; } });
10
+ var memory_1 = require("./memory");
11
+ Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return memory_1.MemoryStorage; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/persistence/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,+BAAqC;AAA5B,mGAAA,WAAW,OAAA;AAEpB,mCAAyC;AAAhC,uGAAA,aAAa,OAAA"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * In-Memory Storage Adapter
3
+ *
4
+ * Simple key-value storage that lives only for the process lifetime.
5
+ * Useful for testing, serverless functions, or when persistence isn't needed.
6
+ *
7
+ * @module persistence/memory
8
+ */
9
+ /**
10
+ * In-memory storage adapter for ApiKeyManager.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { ApiKeyManager } from '@splashcodex/api-key-manager';
15
+ * import { MemoryStorage } from '@splashcodex/api-key-manager/persistence/memory';
16
+ *
17
+ * const manager = new ApiKeyManager(keys, {
18
+ * storage: new MemoryStorage()
19
+ * });
20
+ * ```
21
+ */
22
+ export declare class MemoryStorage {
23
+ private store;
24
+ getItem(key: string): string | null;
25
+ setItem(key: string, value: string): void;
26
+ /** Clear all stored state */
27
+ clear(): void;
28
+ /** Get the number of stored entries */
29
+ get size(): number;
30
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * In-Memory Storage Adapter
4
+ *
5
+ * Simple key-value storage that lives only for the process lifetime.
6
+ * Useful for testing, serverless functions, or when persistence isn't needed.
7
+ *
8
+ * @module persistence/memory
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MemoryStorage = void 0;
12
+ /**
13
+ * In-memory storage adapter for ApiKeyManager.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { ApiKeyManager } from '@splashcodex/api-key-manager';
18
+ * import { MemoryStorage } from '@splashcodex/api-key-manager/persistence/memory';
19
+ *
20
+ * const manager = new ApiKeyManager(keys, {
21
+ * storage: new MemoryStorage()
22
+ * });
23
+ * ```
24
+ */
25
+ class MemoryStorage {
26
+ store = new Map();
27
+ getItem(key) {
28
+ return this.store.get(key) ?? null;
29
+ }
30
+ setItem(key, value) {
31
+ this.store.set(key, value);
32
+ }
33
+ /** Clear all stored state */
34
+ clear() {
35
+ this.store.clear();
36
+ }
37
+ /** Get the number of stored entries */
38
+ get size() {
39
+ return this.store.size;
40
+ }
41
+ }
42
+ exports.MemoryStorage = MemoryStorage;
43
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/persistence/memory.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH;;;;;;;;;;;;GAYG;AACH,MAAa,aAAa;IACd,KAAK,GAAwB,IAAI,GAAG,EAAE,CAAC;IAE/C,OAAO,CAAC,GAAW;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,KAAa;QAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,6BAA6B;IAC7B,KAAK;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,uCAAuC;IACvC,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IAC3B,CAAC;CACJ;AApBD,sCAoBC"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * BasePreset — Shared foundation for all provider presets
3
+ *
4
+ * Provides the "batteries included" pattern:
5
+ * - Singleton lifecycle management
6
+ * - Environment variable parsing (single key or JSON array)
7
+ * - File-based persistence (survives restarts)
8
+ * - Event-to-logger wiring
9
+ * - Health check scheduling
10
+ * - Result<T> pattern for safe initialization
11
+ *
12
+ * @module presets/base
13
+ */
14
+ import { ApiKeyManager, ExecuteOptions, ApiKeyManagerOptions, LoadBalancingStrategy } from '../index';
15
+ /**
16
+ * A discriminated union for safe error handling without exceptions.
17
+ * Used by `getInstance()` to avoid crashing on missing env vars.
18
+ */
19
+ export type Result<T> = {
20
+ success: true;
21
+ data: T;
22
+ } | {
23
+ success: false;
24
+ error: Error;
25
+ };
26
+ export interface PresetOptions {
27
+ /** Environment variable name(s) to read API keys from. */
28
+ envKeys: string[];
29
+ /** Provider name for logging and file state isolation. Default: 'default' */
30
+ provider?: string;
31
+ /** Load balancing strategy. Default: LatencyStrategy */
32
+ strategy?: LoadBalancingStrategy;
33
+ /** Max concurrent execute() calls. Default: 20 */
34
+ concurrency?: number;
35
+ /** Health check interval in ms. Set to 0 to disable. Default: 300_000 (5 min) */
36
+ healthCheckIntervalMs?: number;
37
+ /** Custom health check function. If not set, health checks are disabled. */
38
+ healthCheckFn?: (key: string) => Promise<boolean>;
39
+ /** Semantic cache config. Optional. */
40
+ semanticCache?: ApiKeyManagerOptions['semanticCache'];
41
+ /** Custom logger. Defaults to console. */
42
+ logger?: PresetLogger;
43
+ /** Fallback function when all keys are exhausted. */
44
+ fallbackFn?: () => any;
45
+ /** Custom state file path. Defaults to `os.tmpdir()/codedex_{provider}_state.json` */
46
+ stateFilePath?: string;
47
+ }
48
+ export interface PresetLogger {
49
+ info(message: string, ...args: any[]): void;
50
+ warn(message: string, ...args: any[]): void;
51
+ error(message: string, ...args: any[]): void;
52
+ }
53
+ /**
54
+ * Abstract base class for provider presets.
55
+ * Subclasses only need to define `getDefaultOptions()` with provider-specific
56
+ * defaults (env var name, health check function, etc.).
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // Creating a custom preset:
61
+ * class MyProviderPreset extends BasePreset {
62
+ * protected static getDefaultOptions(): Partial<PresetOptions> {
63
+ * return {
64
+ * envKeys: ['MY_PROVIDER_API_KEY'],
65
+ * provider: 'my-provider',
66
+ * };
67
+ * }
68
+ * }
69
+ * ```
70
+ */
71
+ export declare abstract class BasePreset {
72
+ private static instances;
73
+ protected manager: ApiKeyManager;
74
+ protected logger: PresetLogger;
75
+ protected options: Required<Pick<PresetOptions, 'provider' | 'concurrency' | 'healthCheckIntervalMs'>> & PresetOptions;
76
+ protected constructor(apiKeys: string[], options: PresetOptions);
77
+ /**
78
+ * Wire all manager events to the logger.
79
+ */
80
+ private wireEvents;
81
+ /**
82
+ * Execute a function with automatic key rotation, retries, timeout, and caching.
83
+ */
84
+ execute<T>(fn: (key: string, signal?: AbortSignal) => Promise<T>, options?: ExecuteOptions & {
85
+ prompt?: string;
86
+ }): Promise<T>;
87
+ /**
88
+ * Execute a streaming function with retry on initial connection failure.
89
+ */
90
+ executeStream<T>(fn: (key: string, signal?: AbortSignal) => AsyncGenerator<T, any, unknown>, options?: ExecuteOptions & {
91
+ prompt?: string;
92
+ }): AsyncGenerator<T, any, unknown>;
93
+ /**
94
+ * Get the best available API key directly (low-level).
95
+ */
96
+ getKey(): string | null;
97
+ /**
98
+ * Get the number of non-dead keys.
99
+ */
100
+ getKeyCount(): number;
101
+ /**
102
+ * Get pool health statistics.
103
+ */
104
+ getStats(): import("../index").ApiKeyManagerStats;
105
+ /**
106
+ * Get the underlying ApiKeyManager instance for advanced use.
107
+ */
108
+ getManager(): ApiKeyManager;
109
+ /**
110
+ * Stop health checks and clean up. Call when shutting down.
111
+ */
112
+ destroy(): void;
113
+ /**
114
+ * Parse API keys from environment variables.
115
+ * Supports:
116
+ * - Single key: `"AIza..."`
117
+ * - JSON array: `'["AIza...", "AIzb..."]'`
118
+ * - Comma-separated: `"AIza...,AIzb..."`
119
+ */
120
+ protected static parseKeysFromEnv(envKeys: string[]): string[];
121
+ /**
122
+ * Get or create the singleton instance for a preset.
123
+ * This is the core factory used by all preset subclasses.
124
+ *
125
+ * @param PresetClass - The preset class to instantiate
126
+ * @param overrides - Optional overrides for preset options
127
+ * @returns Result<T> — either `{ success: true, data: instance }` or `{ success: false, error }`
128
+ */
129
+ protected static createInstance<T extends BasePreset>(PresetClass: new (keys: string[], options: PresetOptions) => T, defaultOptions: Partial<PresetOptions>, overrides?: Partial<PresetOptions>): Result<T>;
130
+ /**
131
+ * Reset a singleton instance (primarily for testing).
132
+ */
133
+ protected static resetInstance(provider: string): void;
134
+ /**
135
+ * Reset ALL singleton instances (primarily for testing).
136
+ */
137
+ static resetAll(): void;
138
+ }
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * BasePreset — Shared foundation for all provider presets
4
+ *
5
+ * Provides the "batteries included" pattern:
6
+ * - Singleton lifecycle management
7
+ * - Environment variable parsing (single key or JSON array)
8
+ * - File-based persistence (survives restarts)
9
+ * - Event-to-logger wiring
10
+ * - Health check scheduling
11
+ * - Result<T> pattern for safe initialization
12
+ *
13
+ * @module presets/base
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.BasePreset = void 0;
17
+ const index_1 = require("../index");
18
+ const file_1 = require("../persistence/file");
19
+ const path_1 = require("path");
20
+ const os_1 = require("os");
21
+ // ─── Base Preset Class ──────────────────────────────────────────────────────
22
+ /**
23
+ * Abstract base class for provider presets.
24
+ * Subclasses only need to define `getDefaultOptions()` with provider-specific
25
+ * defaults (env var name, health check function, etc.).
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Creating a custom preset:
30
+ * class MyProviderPreset extends BasePreset {
31
+ * protected static getDefaultOptions(): Partial<PresetOptions> {
32
+ * return {
33
+ * envKeys: ['MY_PROVIDER_API_KEY'],
34
+ * provider: 'my-provider',
35
+ * };
36
+ * }
37
+ * }
38
+ * ```
39
+ */
40
+ class BasePreset {
41
+ static instances = new Map();
42
+ manager;
43
+ logger;
44
+ options;
45
+ constructor(apiKeys, options) {
46
+ this.logger = options.logger || console;
47
+ this.options = {
48
+ provider: options.provider || 'default',
49
+ concurrency: options.concurrency ?? 20,
50
+ healthCheckIntervalMs: options.healthCheckIntervalMs ?? 300_000,
51
+ ...options,
52
+ };
53
+ const stateFile = this.options.stateFilePath ||
54
+ (0, path_1.join)((0, os_1.tmpdir)(), `codedex_${this.options.provider}_state.json`);
55
+ const storage = new file_1.FileStorage({
56
+ filePath: stateFile,
57
+ clearOnInit: true, // Fresh start each session to clear stale DEAD keys
58
+ });
59
+ this.manager = new index_1.ApiKeyManager(apiKeys, {
60
+ storage,
61
+ strategy: this.options.strategy || new index_1.LatencyStrategy(),
62
+ fallbackFn: this.options.fallbackFn,
63
+ concurrency: this.options.concurrency,
64
+ semanticCache: this.options.semanticCache,
65
+ });
66
+ this.wireEvents();
67
+ if (this.options.healthCheckFn && this.options.healthCheckIntervalMs > 0) {
68
+ this.manager.setHealthCheck(this.options.healthCheckFn);
69
+ this.manager.startHealthChecks(this.options.healthCheckIntervalMs);
70
+ }
71
+ this.logger.info(`[${this.options.provider}] ApiKeyManager initialized with ${apiKeys.length} keys ` +
72
+ `(Strategy: ${this.options.strategy?.constructor.name || 'LatencyStrategy'}, ` +
73
+ `Concurrency: ${this.options.concurrency}, ` +
74
+ `HealthChecks: ${this.options.healthCheckIntervalMs > 0 ? `every ${this.options.healthCheckIntervalMs / 1000}s` : 'disabled'})`);
75
+ }
76
+ /**
77
+ * Wire all manager events to the logger.
78
+ */
79
+ wireEvents() {
80
+ const tag = this.options.provider;
81
+ this.manager.on('keyDead', (key) => this.logger.error(`[${tag}] Key PERMANENTLY DEAD: ...${key.slice(-4)}`));
82
+ this.manager.on('circuitOpen', (key) => this.logger.warn(`[${tag}] Circuit OPEN (cooldown): ...${key.slice(-4)}`));
83
+ this.manager.on('keyRecovered', (key) => this.logger.info(`[${tag}] Key RECOVERED: ...${key.slice(-4)}`));
84
+ this.manager.on('retry', (key, attempt, delay) => this.logger.info(`[${tag}] Retry with ...${key.slice(-4)} (Attempt ${attempt}, Delay ${delay}ms)`));
85
+ this.manager.on('fallback', (reason) => this.logger.warn(`[${tag}] Triggering FALLBACK: ${reason}`));
86
+ this.manager.on('allKeysExhausted', () => this.logger.error(`[${tag}] ALL KEYS EXHAUSTED! No fallback available.`));
87
+ this.manager.on('bulkheadRejected', () => this.logger.warn(`[${tag}] Bulkhead rejected request (concurrency limit reached)`));
88
+ this.manager.on('healthCheckPassed', (key) => this.logger.info(`[${tag}] Health check PASSED: ...${key.slice(-4)}`));
89
+ this.manager.on('healthCheckFailed', (key) => this.logger.warn(`[${tag}] Health check FAILED: ...${key.slice(-4)}`));
90
+ }
91
+ // ─── Public API ─────────────────────────────────────────────────────────
92
+ /**
93
+ * Execute a function with automatic key rotation, retries, timeout, and caching.
94
+ */
95
+ async execute(fn, options) {
96
+ return this.manager.execute(fn, options);
97
+ }
98
+ /**
99
+ * Execute a streaming function with retry on initial connection failure.
100
+ */
101
+ async *executeStream(fn, options) {
102
+ yield* this.manager.executeStream(fn, options);
103
+ }
104
+ /**
105
+ * Get the best available API key directly (low-level).
106
+ */
107
+ getKey() {
108
+ return this.manager.getKey();
109
+ }
110
+ /**
111
+ * Get the number of non-dead keys.
112
+ */
113
+ getKeyCount() {
114
+ return this.manager.getKeyCount();
115
+ }
116
+ /**
117
+ * Get pool health statistics.
118
+ */
119
+ getStats() {
120
+ return this.manager.getStats();
121
+ }
122
+ /**
123
+ * Get the underlying ApiKeyManager instance for advanced use.
124
+ */
125
+ getManager() {
126
+ return this.manager;
127
+ }
128
+ /**
129
+ * Stop health checks and clean up. Call when shutting down.
130
+ */
131
+ destroy() {
132
+ this.manager.stopHealthChecks();
133
+ }
134
+ // ─── Static Factory ─────────────────────────────────────────────────────
135
+ /**
136
+ * Parse API keys from environment variables.
137
+ * Supports:
138
+ * - Single key: `"AIza..."`
139
+ * - JSON array: `'["AIza...", "AIzb..."]'`
140
+ * - Comma-separated: `"AIza...,AIzb..."`
141
+ */
142
+ static parseKeysFromEnv(envKeys) {
143
+ const keys = [];
144
+ for (const envName of envKeys) {
145
+ const envValue = process.env[envName];
146
+ if (!envValue)
147
+ continue;
148
+ const trimmed = envValue.trim();
149
+ if (!trimmed)
150
+ continue;
151
+ // Try JSON array
152
+ if (trimmed.startsWith('[')) {
153
+ try {
154
+ const parsed = JSON.parse(trimmed);
155
+ if (Array.isArray(parsed)) {
156
+ keys.push(...parsed.filter((k) => typeof k === 'string' && k.trim()));
157
+ continue;
158
+ }
159
+ }
160
+ catch {
161
+ // Not valid JSON, fall through to comma split
162
+ }
163
+ }
164
+ // Comma-separated or single key
165
+ keys.push(...trimmed.split(',').map(k => k.trim()).filter(k => k.length > 0));
166
+ }
167
+ return [...new Set(keys)]; // Deduplicate
168
+ }
169
+ /**
170
+ * Get or create the singleton instance for a preset.
171
+ * This is the core factory used by all preset subclasses.
172
+ *
173
+ * @param PresetClass - The preset class to instantiate
174
+ * @param overrides - Optional overrides for preset options
175
+ * @returns Result<T> — either `{ success: true, data: instance }` or `{ success: false, error }`
176
+ */
177
+ static createInstance(PresetClass, defaultOptions, overrides) {
178
+ const mergedOptions = { ...defaultOptions, ...overrides };
179
+ const instanceKey = mergedOptions.provider || PresetClass.name;
180
+ // Return existing singleton
181
+ const existing = BasePreset.instances.get(instanceKey);
182
+ if (existing) {
183
+ return { success: true, data: existing };
184
+ }
185
+ // Parse keys
186
+ const envKeys = mergedOptions.envKeys || [];
187
+ const keys = BasePreset.parseKeysFromEnv(envKeys);
188
+ if (keys.length === 0) {
189
+ const logger = mergedOptions.logger || console;
190
+ logger.warn(`[${instanceKey}] No API keys found in env vars: ${envKeys.join(', ')}. ` +
191
+ `AI features will be disabled.`);
192
+ // Still create the instance with empty keys — allows graceful degradation
193
+ }
194
+ try {
195
+ const instance = new PresetClass(keys, mergedOptions);
196
+ BasePreset.instances.set(instanceKey, instance);
197
+ return { success: true, data: instance };
198
+ }
199
+ catch (error) {
200
+ return {
201
+ success: false,
202
+ error: error instanceof Error ? error : new Error(String(error)),
203
+ };
204
+ }
205
+ }
206
+ /**
207
+ * Reset a singleton instance (primarily for testing).
208
+ */
209
+ static resetInstance(provider) {
210
+ const existing = BasePreset.instances.get(provider);
211
+ if (existing) {
212
+ existing.destroy();
213
+ BasePreset.instances.delete(provider);
214
+ }
215
+ }
216
+ /**
217
+ * Reset ALL singleton instances (primarily for testing).
218
+ */
219
+ static resetAll() {
220
+ for (const [, instance] of BasePreset.instances) {
221
+ instance.destroy();
222
+ }
223
+ BasePreset.instances.clear();
224
+ }
225
+ }
226
+ exports.BasePreset = BasePreset;
227
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/presets/base.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,oCAMkB;AAClB,8CAAkD;AAClD,+BAA4B;AAC5B,2BAA4B;AA2C5B,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAsB,UAAU;IACpB,MAAM,CAAC,SAAS,GAA4B,IAAI,GAAG,EAAE,CAAC;IAEpD,OAAO,CAAgB;IACvB,MAAM,CAAe;IACrB,OAAO,CAAsG;IAEvH,YAAsB,OAAiB,EAAE,OAAsB;QAC3D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;QAExC,IAAI,CAAC,OAAO,GAAG;YACX,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS;YACvC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,EAAE;YACtC,qBAAqB,EAAE,OAAO,CAAC,qBAAqB,IAAI,OAAO;YAC/D,GAAG,OAAO;SACb,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa;YACxC,IAAA,WAAI,EAAC,IAAA,WAAM,GAAE,EAAE,WAAW,IAAI,CAAC,OAAO,CAAC,QAAQ,aAAa,CAAC,CAAC;QAElE,MAAM,OAAO,GAAG,IAAI,kBAAW,CAAC;YAC5B,QAAQ,EAAE,SAAS;YACnB,WAAW,EAAE,IAAI,EAAE,oDAAoD;SAC1E,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,GAAG,IAAI,qBAAa,CAAC,OAAO,EAAE;YACtC,OAAO;YACP,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,uBAAe,EAAE;YACxD,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YACrC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;SAC5C,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC,OAAO,CAAC,qBAAqB,GAAG,CAAC,EAAE,CAAC;YACvE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YACxD,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACZ,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,oCAAoC,OAAO,CAAC,MAAM,QAAQ;YACnF,cAAc,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,IAAI,iBAAiB,IAAI;YAC9E,gBAAgB,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI;YAC5C,iBAAiB,IAAI,CAAC,OAAO,CAAC,qBAAqB,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,CAClI,CAAC;IACN,CAAC;IAED;;OAEG;IACK,UAAU;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,8BAA8B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,iCAAiC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,EAAE,CACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,uBAAuB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,mBAAmB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,OAAO,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC;QACxG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,0BAA0B,MAAM,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CACrC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,8CAA8C,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,yDAAyD,CAAC,CAAC,CAAC;QACxF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE,CACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,6BAA6B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE,CACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,6BAA6B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,2EAA2E;IAE3E;;OAEG;IACH,KAAK,CAAC,OAAO,CACT,EAAqD,EACrD,OAA8C;QAE9C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,CAAC,aAAa,CAChB,EAA0E,EAC1E,OAA8C;QAE9C,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,MAAM;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,WAAW;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,QAAQ;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,UAAU;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,OAAO;QACH,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;IACpC,CAAC;IAED,2EAA2E;IAE3E;;;;;;OAMG;IACO,MAAM,CAAC,gBAAgB,CAAC,OAAiB;QAC/C,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,CAAC,QAAQ;gBAAE,SAAS;YAExB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO;gBAAE,SAAS;YAEvB,iBAAiB;YACjB,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACnC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;wBACxB,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;wBAC3E,SAAS;oBACb,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACL,8CAA8C;gBAClD,CAAC;YACL,CAAC;YAED,gCAAgC;YAChC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QAClF,CAAC;QAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc;IAC7C,CAAC;IAED;;;;;;;OAOG;IACO,MAAM,CAAC,cAAc,CAC3B,WAA8D,EAC9D,cAAsC,EACtC,SAAkC;QAElC,MAAM,aAAa,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,EAAmB,CAAC;QAC3E,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC;QAE/D,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAa,EAAE,CAAC;QAClD,CAAC;QAED,aAAa;QACb,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAElD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,IAAI,OAAO,CAAC;YAC/C,MAAM,CAAC,IAAI,CACP,IAAI,WAAW,oCAAoC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBACzE,+BAA+B,CAClC,CAAC;YACF,0EAA0E;QAC9E,CAAC;QAED,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;YACtD,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACnE,CAAC;QACN,CAAC;IACL,CAAC;IAED;;OAEG;IACO,MAAM,CAAC,aAAa,CAAC,QAAgB;QAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACX,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,QAAQ;QACX,KAAK,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YAC9C,QAAQ,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QACD,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACjC,CAAC;;AA3OL,gCA4OC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Gemini Preset — One-line Gemini API key management
3
+ *
4
+ * Pre-configured singleton with:
5
+ * - Reads `GOOGLE_GEMINI_API_KEY` or `GEMINI_API_KEY` from env
6
+ * - LatencyStrategy for optimal key selection
7
+ * - File persistence (survives restarts)
8
+ * - Health checks every 5 minutes
9
+ * - Concurrency limit of 20
10
+ *
11
+ * @module presets/gemini
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
16
+ *
17
+ * // Initialize once (reads keys from process.env automatically)
18
+ * const result = GeminiManager.getInstance();
19
+ * if (!result.success) throw result.error;
20
+ * const gemini = result.data;
21
+ *
22
+ * // Use the execute() wrapper for automatic key rotation + retries
23
+ * const response = await gemini.execute(async (key) => {
24
+ * const genAI = new GoogleGenerativeAI(key);
25
+ * const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
26
+ * const res = await model.generateContent('Hello, world!');
27
+ * return res.response.text();
28
+ * }, { maxRetries: 3, timeoutMs: 30000 });
29
+ * ```
30
+ */
31
+ import { BasePreset, PresetOptions, Result } from './base';
32
+ export declare class GeminiManager extends BasePreset {
33
+ private static readonly PROVIDER;
34
+ private constructor();
35
+ /**
36
+ * Default configuration for Gemini presets.
37
+ */
38
+ private static getDefaultOptions;
39
+ /**
40
+ * Get or create the singleton GeminiManager instance.
41
+ *
42
+ * @param overrides - Optional configuration overrides
43
+ * @returns Result<GeminiManager> — safely check `.success` before using `.data`
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const result = GeminiManager.getInstance();
48
+ * if (result.success) {
49
+ * const gemini = result.data;
50
+ * const text = await gemini.execute(apiCall, { maxRetries: 3 });
51
+ * }
52
+ * ```
53
+ */
54
+ static getInstance(overrides?: Partial<PresetOptions>): Result<GeminiManager>;
55
+ /**
56
+ * Reset the singleton instance. Primarily for testing.
57
+ */
58
+ static reset(): void;
59
+ }
60
+ export { Result } from './base';