{"_id":"@abstraks-dev/aws-helpers","name":"@abstraks-dev/aws-helpers","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@abstraks-dev/aws-helpers","version":"1.0.0","description":"AWS SDK helpers with caching and retry logic for Secrets Manager and SSM Parameter Store","type":"module","main":"src/index.js","exports":{".":"./src/index.js"},"scripts":{"test":"NODE_OPTIONS='--experimental-vm-modules' jest"},"keywords":["aws","secrets-manager","ssm","parameter-store","caching","retry"],"author":{"name":"Abstraks Dev"},"license":"MIT","peerDependencies":{"@aws-sdk/client-secrets-manager":"^3.0.0","@aws-sdk/client-ssm":"^3.0.0"},"peerDependenciesMeta":{"@aws-sdk/client-secrets-manager":{"optional":true},"@aws-sdk/client-ssm":{"optional":true}},"devDependencies":{"@jest/globals":"^29.7.0","jest":"^29.7.0"},"_id":"@abstraks-dev/aws-helpers@1.0.0","gitHead":"f629da8e3ebc2d8fdc6f83a147ea8e50b8bc66b5","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-OwgZxZJwd511sWaZXJ0aLoh93z0Hg3Rs7q5UcExE88s4n5kJoEubUxtL8pGhUhSBWY4D1BGIaFFSHYdQ+SNT0Q==","shasum":"dfb5b1173ba226856926de7a803fcc2671cb3533","tarball":"https://registry.npmjs.org/@abstraks-dev/aws-helpers/-/aws-helpers-1.0.0.tgz","fileCount":5,"unpackedSize":39693,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDRSPzVX0xCrz5bmm5BViaTVwHxdoJygQ6AkrIh/fIXzQIgdQ17MNnHLaWzltX9jnUETbh45NzpnUAZ7km07fuJ2r8="}]},"_npmUser":{"name":"abstraks-dev","email":"contactabstraks@gmail.com"},"directories":{},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/aws-helpers_1.0.0_1763675403889_0.5143017984001486"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-20T21:50:03.788Z","1.0.0":"2025-11-20T21:50:04.077Z","modified":"2025-11-20T21:50:04.436Z"},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"description":"AWS SDK helpers with caching and retry logic for Secrets Manager and SSM Parameter Store","keywords":["aws","secrets-manager","ssm","parameter-store","caching","retry"],"author":{"name":"Abstraks Dev"},"license":"MIT","readme":"# @abstraks-dev/aws-helpers\n\nAWS SDK helpers with caching and retry logic for Secrets Manager and SSM Parameter Store.\n\n## Features\n\n- 🔐 **Secrets Manager**: Retrieve secrets with automatic JSON parsing\n- 📦 **SSM Parameter Store**: Get single or multiple parameters\n- ⚡ **Caching**: 5-minute cache reduces AWS API calls and costs\n- 🔄 **Retry Logic**: Exponential backoff for transient errors\n- 🎯 **Lazy Loading**: AWS SDK loaded only when needed\n- 🛡️ **Error Handling**: Smart retry vs. immediate failure\n- 📊 **Cache Management**: Clear cache and get statistics\n- 🔌 **Drop-in Replacement**: Compatible with existing `getSecret()` patterns\n\n## Installation\n\n```bash\nnpm install @abstraks-dev/aws-helpers\n```\n\n### Optional Peer Dependencies\n\nInstall only what you need:\n\n```bash\n# For Secrets Manager\nnpm install @aws-sdk/client-secrets-manager\n\n# For SSM Parameter Store\nnpm install @aws-sdk/client-ssm\n```\n\n## Usage\n\n### Secrets Manager - Simple\n\n```javascript\nimport { getSecret } from '@abstraks-dev/aws-helpers';\n\n// Get entire JSON secret\nconst config = await getSecret('app-config');\nconsole.log(config.API_KEY, config.DB_URL);\n\n// Extract specific key from JSON secret\nconst apiKey = await getSecret('app-config', 'API_KEY');\n\n// Get plain text secret\nconst token = await getSecret('github-token');\n```\n\n### Secrets Manager - With Caching\n\n```javascript\nimport { createSecretsManagerHelper } from '@abstraks-dev/aws-helpers';\n\nconst secrets = createSecretsManagerHelper({\n\tregion: 'us-west-2',\n\tcacheTTL: 5 * 60 * 1000, // 5 minutes (default)\n\tmaxRetries: 3, // Default\n});\n\n// First call fetches from AWS\nconst key1 = await secrets.getSecret('app-config', 'API_KEY');\n\n// Second call uses cache (no AWS API call)\nconst key2 = await secrets.getSecret('app-config', 'API_KEY');\n\n// Clear cache when secrets rotate\nsecrets.clearCache('app-config');\n\n// Get cache statistics\nconst stats = secrets.getCacheStats();\nconsole.log(stats); // { size: 2, entries: [...] }\n```\n\n### SSM Parameter Store - Single Parameter\n\n```javascript\nimport { getParameter } from '@abstraks-dev/aws-helpers';\n\n// Get decrypted parameter\nconst dbUrl = await getParameter('/app/database-url');\n\n// Get without decryption\nconst value = await getParameter('/app/config', { withDecryption: false });\n```\n\n### SSM Parameter Store - Multiple Parameters\n\n```javascript\nimport { createSSMHelper } from '@abstraks-dev/aws-helpers';\n\nconst ssm = createSSMHelper({ region: 'us-west-2' });\n\n// Get multiple parameters efficiently\nconst params = await ssm.getParameters([\n\t'/app/database-url',\n\t'/app/api-key',\n\t'/app/redis-url',\n]);\n\nconsole.log(params['/app/database-url']);\nconsole.log(params['/app/api-key']);\n```\n\n### Integration with Existing Code\n\nDrop-in replacement for existing patterns:\n\n```javascript\n// Before: Custom getSecret helper\nimport { getSecret } from './helpers/secrets.js';\n\n// After: @abstraks-dev/aws-helpers\nimport { getSecret } from '@abstraks-dev/aws-helpers';\n\n// Same API!\nconst secret = await getSecret('app-config', 'JWT_SECRET');\n```\n\n## API Documentation\n\n### `createSecretsManagerHelper(options)`\n\nCreates a Secrets Manager helper with caching and retry logic.\n\n**Parameters:**\n\n- `options.region` (string, optional): AWS region (default: `AWS_REGION` env var or 'us-west-2')\n- `options.cacheTTL` (number, optional): Cache TTL in milliseconds (default: 300000 = 5 minutes)\n- `options.maxRetries` (number, optional): Maximum retry attempts (default: 3)\n- `options.retryDelay` (number, optional): Initial retry delay in ms (default: 100)\n\n**Returns:** Object with methods: `getSecret`, `clearCache`, `getCacheStats`\n\n**Example:**\n\n```javascript\nconst secrets = createSecretsManagerHelper({\n\tregion: 'us-west-2',\n\tcacheTTL: 10 * 60 * 1000, // 10 minutes\n\tmaxRetries: 5,\n\tretryDelay: 200,\n});\n```\n\n---\n\n### `helper.getSecret(secretName, key)`\n\nRetrieves a secret from AWS Secrets Manager with caching.\n\n**Parameters:**\n\n- `secretName` (string, required): Name or ARN of the secret\n- `key` (string, optional): Key to extract from JSON secret\n\n**Returns:** Promise<string | object>\n\n**Behavior:**\n\n- Returns full object if secret is JSON and no key provided\n- Returns specific value if key provided\n- Returns raw string for non-JSON secrets\n- Throws if trying to extract key from non-JSON secret\n\n**Example:**\n\n```javascript\n// JSON secret: {\"API_KEY\": \"abc123\", \"DB\": \"mongodb://...\"}\nconst fullConfig = await helper.getSecret('app-config');\n// Returns: { API_KEY: 'abc123', DB: 'mongodb://...' }\n\nconst apiKey = await helper.getSecret('app-config', 'API_KEY');\n// Returns: 'abc123'\n\n// Plain text secret\nconst token = await helper.getSecret('github-token');\n// Returns: 'ghp_abc123...'\n```\n\n---\n\n### `helper.clearCache(secretName)`\n\nClears cached secrets.\n\n**Parameters:**\n\n- `secretName` (string, optional): Secret name to clear (clears all if omitted)\n\n**Example:**\n\n```javascript\n// Clear specific secret (including all keys)\nhelper.clearCache('app-config');\n\n// Clear all cached secrets\nhelper.clearCache();\n```\n\n---\n\n### `helper.getCacheStats()`\n\nReturns cache statistics.\n\n**Returns:** Object with `size` (number) and `entries` (array of strings)\n\n**Example:**\n\n```javascript\nconst stats = helper.getCacheStats();\nconsole.log(stats.size); // 3\nconsole.log(stats.entries); // ['secret1:KEY1', 'secret2', 'secret3:KEY2']\n```\n\n---\n\n### `createSSMHelper(options)`\n\nCreates an SSM Parameter Store helper with caching and retry logic.\n\n**Parameters:**\n\n- `options.region` (string, optional): AWS region (default: `AWS_REGION` env var or 'us-west-2')\n- `options.cacheTTL` (number, optional): Cache TTL in milliseconds (default: 300000 = 5 minutes)\n- `options.maxRetries` (number, optional): Maximum retry attempts (default: 3)\n- `options.retryDelay` (number, optional): Initial retry delay in ms (default: 100)\n\n**Returns:** Object with methods: `getParameter`, `getParameters`, `clearCache`, `getCacheStats`\n\n---\n\n### `helper.getParameter(name, options)`\n\nRetrieves a parameter from SSM Parameter Store with caching.\n\n**Parameters:**\n\n- `name` (string, required): Parameter name or path\n- `options.withDecryption` (boolean, optional): Decrypt SecureString (default: true)\n\n**Returns:** Promise<string>\n\n**Example:**\n\n```javascript\nconst dbUrl = await helper.getParameter('/app/database-url');\n\nconst publicConfig = await helper.getParameter('/app/version', {\n\twithDecryption: false,\n});\n```\n\n---\n\n### `helper.getParameters(names, options)`\n\nRetrieves multiple parameters efficiently with caching.\n\n**Parameters:**\n\n- `names` (string[], required): Array of parameter names\n- `options.withDecryption` (boolean, optional): Decrypt SecureString (default: true)\n\n**Returns:** Promise<Object> - Map of parameter names to values\n\n**Behavior:**\n\n- Uses cache for already-fetched parameters\n- Fetches uncached parameters in single AWS API call\n- Caches all fetched parameters\n\n**Example:**\n\n```javascript\nconst params = await helper.getParameters([\n\t'/app/db-url',\n\t'/app/api-key',\n\t'/app/redis-url',\n]);\n\nconsole.log(params['/app/db-url']);\n```\n\n---\n\n### Simple Wrapper Functions\n\n#### `getSecret(secretName, key, options)`\n\nSimple wrapper for one-off secret retrieval (no persistent cache).\n\n**Example:**\n\n```javascript\nimport { getSecret } from '@abstraks-dev/aws-helpers';\n\nconst secret = await getSecret('app-config', 'API_KEY', {\n\tregion: 'us-west-2',\n});\n```\n\n#### `getParameter(name, options)`\n\nSimple wrapper for one-off parameter retrieval (no persistent cache).\n\n**Example:**\n\n```javascript\nimport { getParameter } from '@abstraks-dev/aws-helpers';\n\nconst value = await getParameter('/app/config');\n```\n\n## Caching Strategy\n\n### How Caching Works\n\n1. **First Request**: Fetches from AWS, stores in memory with timestamp\n2. **Subsequent Requests**: Returns cached value if within TTL\n3. **After TTL**: Fetches fresh value from AWS, updates cache\n4. **Per-Container**: Each Lambda container has independent cache\n\n### Cache Benefits\n\n```javascript\nconst secrets = createSecretsManagerHelper({ cacheTTL: 5 * 60 * 1000 });\n\nawait secrets.getSecret('config', 'KEY'); // AWS API call\nawait secrets.getSecret('config', 'KEY'); // Cache (0ms)\nawait secrets.getSecret('config', 'KEY'); // Cache (0ms)\n\n// After 5 minutes...\nawait secrets.getSecret('config', 'KEY'); // Fresh fetch from AWS\n```\n\n**Performance Impact:**\n\n- ✅ Reduces AWS API calls by ~90%+\n- ✅ Saves $0.05 per 10,000 requests (Secrets Manager pricing)\n- ✅ Improves Lambda response time (cache: <1ms vs AWS: 50-100ms)\n- ✅ Reduces throttling risk\n\n**Considerations:**\n\n- ⚠️ Secret rotation takes up to TTL to propagate\n- ⚠️ Cache is per-container (not shared across containers)\n- ⚠️ Cold starts always fetch fresh secrets\n- ⚠️ Call `clearCache()` after manual secret updates\n\n## Retry Logic\n\n### Automatic Retries\n\nThe package automatically retries transient errors with exponential backoff:\n\n```javascript\nconst helper = createSecretsManagerHelper({\n\tmaxRetries: 3,\n\tretryDelay: 100, // Initial delay\n});\n\n// Retry delays: 100ms, 200ms, 400ms\n// Total max wait: 700ms before giving up\n```\n\n### Errors That Are Retried\n\n- `ServiceUnavailableException`\n- `ThrottlingException`\n- `InternalServiceErrorException`\n- Network timeouts and connection errors\n\n### Errors That Are NOT Retried\n\n- `AccessDeniedException` (permission issue)\n- `ResourceNotFoundException` (secret/parameter doesn't exist)\n- `ValidationException` (bad request)\n\n**Why?** These errors won't be fixed by retrying.\n\n## Migration Guide\n\n### From Social/Media Service Pattern\n\n**Before:**\n\n```javascript\n// helpers/secrets.js\nimport {\n\tSecretsManagerClient,\n\tGetSecretValueCommand,\n} from '@aws-sdk/client-secrets-manager';\n\nconst client = new SecretsManagerClient({ region: 'us-west-2' });\n\nexport async function getSecret(secretName, key) {\n\ttry {\n\t\tconst command = new GetSecretValueCommand({\n\t\t\tSecretId: secretName,\n\t\t\tVersionStage: 'AWSCURRENT',\n\t\t});\n\t\tconst response = await client.send(command);\n\t\tconst secretString = response.SecretString;\n\n\t\ttry {\n\t\t\tconst secretObj = JSON.parse(secretString);\n\t\t\treturn key ? secretObj[key] : secretObj;\n\t\t} catch (e) {\n\t\t\treturn secretString;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error retrieving secret ${secretName}:`, error);\n\t\tthrow error;\n\t}\n}\n```\n\n**After:**\n\n```javascript\nimport { createSecretsManagerHelper } from '@abstraks-dev/aws-helpers';\n\nexport const secrets = createSecretsManagerHelper({ region: 'us-west-2' });\nexport const getSecret = secrets.getSecret.bind(secrets);\n```\n\n**Or just replace imports:**\n\n```javascript\n// Before\nimport { getSecret } from './helpers/secrets.js';\n\n// After\nimport { getSecret } from '@abstraks-dev/aws-helpers';\n```\n\n**Benefits:**\n\n- ✅ 5-minute caching (80-90% fewer AWS calls)\n- ✅ Automatic retry with exponential backoff\n- ✅ Lazy AWS SDK loading (faster cold starts)\n- ✅ Cache management and statistics\n- ✅ Same API, drop-in compatible\n\n## Best Practices\n\n### 1. Reuse Helper Instances\n\n```javascript\n// Good: Single instance with shared cache\nconst secrets = createSecretsManagerHelper();\nexport const getSecret = secrets.getSecret.bind(secrets);\n\n// Avoid: New instance every time (no cache benefit)\nfunction getSecret(name, key) {\n\tconst helper = createSecretsManagerHelper();\n\treturn helper.getSecret(name, key);\n}\n```\n\n### 2. Use Appropriate Cache TTL\n\n```javascript\n// Production secrets (rotate monthly)\nconst secrets = createSecretsManagerHelper({\n\tcacheTTL: 10 * 60 * 1000, // 10 minutes\n});\n\n// Development secrets (rotate frequently)\nconst secrets = createSecretsManagerHelper({\n\tcacheTTL: 60 * 1000, // 1 minute\n});\n\n// High-security (no caching)\nconst secrets = createSecretsManagerHelper({\n\tcacheTTL: 0, // Always fetch fresh\n});\n```\n\n### 3. Handle Secret Rotation\n\n```javascript\n// After rotating secret in AWS console\nsecrets.clearCache('app-config'); // Force fresh fetch\n```\n\n### 4. Batch Parameter Fetches\n\n```javascript\n// Good: Single API call\nconst params = await ssm.getParameters(['/app/db', '/app/key', '/app/url']);\n\n// Avoid: Multiple API calls\nconst db = await ssm.getParameter('/app/db');\nconst key = await ssm.getParameter('/app/key');\nconst url = await ssm.getParameter('/app/url');\n```\n\n### 5. Monitor Cache Effectiveness\n\n```javascript\nconst stats = secrets.getCacheStats();\nconsole.log(`Cache size: ${stats.size}`);\nconsole.log(`Cached entries: ${stats.entries.join(', ')}`);\n\n// Log in CloudWatch for monitoring\n```\n\n## Troubleshooting\n\n### \"AWS Secrets Manager SDK not installed\"\n\n**Cause:** Missing peer dependency.\n\n**Solution:**\n\n```bash\nnpm install @aws-sdk/client-secrets-manager\n```\n\n### \"Access denied\" / \"AccessDeniedException\"\n\n**Cause:** Lambda execution role lacks permissions.\n\n**Solution:** Add IAM policy:\n\n```json\n{\n\t\"Effect\": \"Allow\",\n\t\"Action\": [\"secretsmanager:GetSecretValue\"],\n\t\"Resource\": \"arn:aws:secretsmanager:REGION:ACCOUNT:secret:SECRET_NAME-*\"\n}\n```\n\n### \"Secret not found\" / \"ResourceNotFoundException\"\n\n**Cause:** Secret doesn't exist or wrong name/region.\n\n**Solution:**\n\n- Verify secret name (case-sensitive)\n- Check region matches\n- Confirm secret exists: `aws secretsmanager list-secrets`\n\n### \"Throttling\" Errors\n\n**Cause:** Too many AWS API calls.\n\n**Solution:** Increase cache TTL:\n\n```javascript\nconst secrets = createSecretsManagerHelper({\n\tcacheTTL: 10 * 60 * 1000, // Increase from 5 to 10 minutes\n});\n```\n\n### Secrets Not Updating After Rotation\n\n**Cause:** Cache still has old value.\n\n**Solution:**\n\n```javascript\n// Clear cache after rotation\nsecrets.clearCache('app-config');\n\n// Or reduce cache TTL for frequently-rotated secrets\nconst secrets = createSecretsManagerHelper({\n\tcacheTTL: 60 * 1000, // 1 minute\n});\n```\n\n## Performance Characteristics\n\n| Operation                | Without Cache | With Cache | Improvement        |\n| ------------------------ | ------------- | ---------- | ------------------ |\n| getSecret (first call)   | 50-100ms      | 50-100ms   | -                  |\n| getSecret (cached)       | 50-100ms      | <1ms       | **50-100x faster** |\n| API calls (100 requests) | 100           | ~2-5       | **20-50x fewer**   |\n| Cost (1M requests)       | $400          | $8-20      | **20-50x cheaper** |\n\n**Note:** Actual performance depends on AWS region, network latency, and secret size.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-a85fb967bf21210c0362d4b9d4e3b288"}