npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aws-blocks/bb-secret

v0.2.0

Published

A single application secret backed by AWS Secrets Manager.

Readme

Secret

A single application secret backed by AWS Secrets Manager.

When to use: You need to store a high-value credential — a third-party API key, OAuth client secret, database connection string, webhook signing key, or encryption key. Each Secret instance maps to exactly one Secrets Manager secret.

When NOT to use: For non-sensitive configuration (feature flags, API URLs, thresholds) use AppSetting — it is free for standard values. For application state, use KVStore or DistributedTable.

Secret is the recommended home for production secrets. For any credential that protects a production app — a database connection string, API key, OAuth token, or private key — use Secret. It is purpose-built for secrets: a dedicated per-secret resource with its own access controls, cross-account sharing, version history, and (future) managed rotation. This mirrors AWS's own guidance on where credentials belong.

AppSetting with secret: true also encrypts the value at rest and is free, so it is a reasonable choice for a low-value secret in a prototype, or when you want to avoid the per-secret cost. But for anything protecting a production app, reach for Secret.

Design & mock/AWS parity details: DESIGN.md

API

const secret = new Secret(scope, id, options?)

| Method | Returns | Description | |--------|---------|-------------| | get(options?) | Promise<T \| null> | Read the value. null when unset/missing. options.version selects 'current' (default), 'previous', or a SecretVersion from listVersions(). | | put(value) | Promise<void> | Set or update the value at runtime. The prior value becomes the 'previous' version. | | listVersions() | Promise<SecretVersionInfo[]> | List retained versions (metadata only, newest first — never values). | | Secret.fromExisting(arn) | ExternalSecretRef | Reference an existing Secrets Manager secret (static). Pass to options.secret. |

Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | name | string | No | Explicit Secrets Manager secret name. When omitted, derived from the scope tree (unique per stack). Provide a well-known name when a team/CI pipeline sets or rotates the value out-of-band via the aws CLI. You own uniqueness across the account/region when set. Ignored for fromExisting. | | schema | StandardSchemaV1<T> | No | Runtime validation schema (Zod, Valibot, ArkType). Infers T, treats the stored value as JSON, and validates on get()/put(). Omit for an opaque string. | | secret | ExternalSecretRef | No | Wrap an existing Secrets Manager secret from Secret.fromExisting(arn) instead of creating one. | | generate | boolean | No | When true, seed a random value on first create (for a signing/HMAC key that only needs to exist). Otherwise the secret starts empty and get() returns null until set. Don't combine with schema. | | cacheTtlSeconds | number | No | How long the runtime reuses a cached value before re-fetching. Defaults to 300s (5 min), matching the AWS Secrets Manager agent — a bounded default so a long-running process can't serve a rotated value indefinitely. Raise it to cut fetches, lower it for fresher reads, or set 0 to disable caching. put() always clears the cache. Ignored by the mock. | | removalPolicy | 'destroy' \| 'retain' | No | Override the stack-wide removal default for the created secret. Ignored by the mock and when wrapping an existing secret. | | logger | ChildLogger | No | Optional logger for internal operations. Defaults to an error-level logger. |

Error handling

import { isBlocksError } from '@aws-blocks/core';
import { SecretErrors } from '@aws-blocks/bb-secret';

try {
  await secret.put(value);
} catch (e: unknown) {
  if (isBlocksError(e, SecretErrors.ValidationFailed)) {
    // schema validation failed
  }
  if (isBlocksError(e, SecretErrors.SecretNotFound)) {
    // the underlying Secrets Manager secret does not exist
  }
  throw e;
}

get() returns null for a missing value rather than throwing, so a missing secret is normal control flow, not an exception:

const key = await stripeKey.get();
if (key === null) throw new Error('Stripe key not configured');

Examples

Auto-generated signing key (the app creates the value)

Pass generate: true and never set a value — Secrets Manager creates a random one on first deploy (the mock seeds an equivalent value locally). Good for a JWT/session signing key or HMAC key, where the app generates the secret and nobody needs to read it.

const signingKey = new Secret(scope, 'session-signing-key', { generate: true });

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async issueToken(userId: string) {
    const key = await signingKey.get(); // random value, created on first deploy
    // sign(userId, key)...
  },
}));

Without generate: true, a BB-created secret starts empty: get() returns null until a value is set. Don't combine generate with schema — a random string isn't valid JSON.

Manually-configured credential (you set an externally-issued value)

A third-party key (Stripe, an OAuth client secret) is not auto-generated — you set it out-of-band after deploy. The declaration is the same; the difference is that someone provides the value.

const stripeKey = new Secret(scope, 'stripe-api-key');

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async charge(amount: number) {
    const key = await stripeKey.get();
    if (key === null) throw new Error('Stripe key not configured');
    // use key...
  },
}));

Set the value once — via the settings console route, a deploy script, or the AWS CLI (see "Managing secret values"). At runtime you can also update it:

await stripeKey.put('sk_live_...');

Typed JSON secret with schema validation

import { z } from 'zod';

const dbConfig = new Secret(scope, 'db-config', {
  schema: z.object({ host: z.string(), port: z.number() }),
});

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async connect() {
    const config = await dbConfig.get(); // { host: string; port: number } | null
    if (config === null) throw new Error('DB config not set');
    // connect(config.host, config.port)...
  },
}));

Wrap an existing secret

const legacy = new Secret(scope, 'legacy-key', {
  secret: Secret.fromExisting('arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret-AbCdEf'),
});
const value = await legacy.get();

AWS Blocks will not create, seed, or delete the referenced secret — it only reads and writes its value and grants the app access to it.

Managing secret values

A Secret declaration carries only the name — never the value. How you set the value depends on how centrally the secret is managed. There are three complementary approaches:

1. Auto-generated (random) — no value to set

Pass generate: true and never call put(): the secret is created with a random value on first deploy (the mock seeds an equivalent value locally). Use this for opaque values nobody needs to know — session signing keys, HMAC keys. Read it with get(); it is stable across redeploys. Without generate: true the secret starts empty and get() returns null until you set a value.

2. Set via the settings endpoint → console — the default for most apps

Every AWS Blocks app exposes a built-in /aws-blocks/settings route. Deployed, it redirects to the stack's settings resource group in the AWS console, where your Secret values appear alongside your AppSetting parameters — set or rotate them in the console UI. Locally, the same route opens .bb-data/settings.json, where secret values live next to settings (plaintext, dev-only). One route, one place, local and deployed.

3. Well-known name + the aws CLI — for CI/CD-driven workflows

If your deployment model is scripted, give the secret an explicit, stable name and set/rotate it with the standard AWS CLI. The value travels machine → Secrets Manager over the AWS API; it never enters source, git, or the CloudFormation template.

const stripeKey = new Secret(scope, 'stripe-key', { name: 'my-app/stripe-key' });
# Set / rotate — value over stdin, never argv or shell history.
printf '%s' "$STRIPE_KEY" \
  | aws secretsmanager put-secret-value \
      --secret-id my-app/stripe-key \
      --secret-string file:///dev/stdin \
      --region us-east-1

# Inspect (metadata only; the value requires explicit get-secret-value perms).
aws secretsmanager describe-secret --secret-id my-app/stripe-key

The Blocks app creates the secret (empty/random) when you deploy, so the value is set against a secret that already exists — run the CLI once the secret exists. The exact sequencing in your pipeline is up to you. Re-running put-secret-value overwrites (rotation); the next cold start reads the new value. Write to the same region the app deploys to, or reads see an unset secret.

Without an explicit name, the secret name is derived from the scope tree; find it in the settings resource group, the console, or aws secretsmanager list-secrets.

Rotating & removing

  • Rotate: set a new value (console or put-secret-value); a warm runtime that already cached the old value picks up the change on its next cold start — no redeploy needed. (A put() from within the app clears that instance's cache immediately.)
  • Remove: aws secretsmanager delete-secret --secret-id <name> (recoverable by default; --force-delete-without-recovery to skip the recovery window).

Which approach, when

| Scenario | Approach | |----------|----------| | Centrally-managed credential shared across many app installations | fromExisting(arn) — reference it; the app never owns or sets it | | A normal app, set the value once after deploy | Settings endpoint → console (approach 2) | | Scripted / CI/CD-driven provisioning | Well-known name + aws CLI (approach 3) | | An opaque value nobody needs to read | Auto-generated (approach 1) |

There is intentionally no way to seed a secret's value from source code or an environment variable through the constructor — a literal in source would land in git and the template, and env-seeding is kept out of the declaration (consistent with how hosting secret() separates declare from set). Set values out-of-band via the console or CLI.

Reading an earlier version (rotation grace window)

Each change keeps the prior value available as the previous version. This is useful during a key rollout: validate against both the new and the old value for a grace period.

const current  = await signingKey.get();                       // live value
const previous = await signingKey.get({ version: 'previous' }); // value before the last change, or null
// verify(token, current) || (previous !== null && verify(token, previous))

get({ version: 'previous' }) returns null until the secret has been changed at least once. To browse the retained versions (metadata only — never values), use listVersions() and pass a returned item straight back to get():

const versions = await signingKey.listVersions();
// [{ versionId, stages: ['AWSCURRENT'], createdDate }, { versionId, stages: ['AWSPREVIOUS'], createdDate }]
const value = await signingKey.get({ version: versions[1] });  // read a specific version

For anything deeper than the current/previous pair (full history, custom staging labels, reading by an arbitrary id), use the AWS SDK directly against the secret — the BB covers the common path and leaves the raw resource reachable. The secret's ARN is exposed on the CDK construct (secret.secretArn, available at synth) — pass it where you build the SDK client; the runtime/mock classes deliberately don't expose AWS primitives.

Scaling & cost (AWS)

  • Billing: $0.40 per secret per month (prorated hourly) + $0.05 per 10,000 API calls. No free tier.
  • Latency: low-tens-of-ms per API call; the runtime caches the fetched value in-process, so steady-state reads hit the cache, not the API. A cached value is held for cacheTtlSeconds — default 5 minutes (matching the AWS Secrets Manager agent) — so a rotated value is picked up within the TTL even on a long-running process; raise it to cut fetches, or set 0 to disable caching.
  • Throughput: GetSecretValue scales to thousands of requests/sec per account (default quotas, raisable). Reads are the hot path; put() is rare.
  • Value size limit: 64 KB per secret value.
  • Version history: listVersions() returns the labeled versions — current + previous. Secrets Manager retains additional unlabeled versions internally (until >100, never removing any created in the last 24h), but the BB surfaces only current/previous; reach for the AWS SDK for deeper history.
  • Encryption: KMS at rest, using the default aws/secretsmanager key.
  • IAM: the construct grants the shared execution role secretsmanager:GetSecretValue + PutSecretValue scoped to the single secret (not a wildcard), plus the KMS grants Secrets Manager attaches.
  • Deletion: recoverable by default (30-day recovery window); --force-delete-without-recovery to delete immediately.
  • Removal policy follows the stack-wide defaults (production retains, sandbox destroys); override per-instance with removalPolicy. BB-created secrets are tagged aws-blocks-stack=<stackName> (join the settings resource group); fromExisting secrets are not tagged.

Local development

The mock stores the current value as plaintext in the shared .bb-data/settings.json (the same file AppSetting uses, keyed by the secret's name or fullId) and logs a one-time warning. This is why the local /aws-blocks/settings route surfaces secrets alongside settings. The previous value (for get({ version: 'previous' }) / listVersions()) is kept in a small .bb-data/secret-versions.json sidecar — current + previous only; deeper history is an AWS-only capability. It is not secure — never use real credentials in local development. Wipe with rm -rf .bb-data.

Secret runs server-side only. Importing and instantiating it in browser/client code throws SecretErrors.NotSupported — a secret read in the browser would expose it to the client.