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

@alvarorestrepo/envshare-sdk

v0.2.0

Published

EnvShare SDK — Runtime environment variable injection

Readme

@alvarorestrepo/envshare-sdk

Runtime environment variable injection for Node.js — no .env files on disk.

npm version license node


The Problem

Every team has been here:

  • .env files get committed by accident — one git add . and your production database credentials are in the repo history forever.
  • Sharing secrets over Slack or email — "hey can you send me the staging API key?" Copy-paste. Screenshot. Forwarded. Zero control.
  • No audit trail — who accessed the production secrets? When? From where? Nobody knows.
  • Variables drift out of sync — one developer updates a key locally, forgets to tell the team. Three hours of debugging later: "oh, the API key rotated."
  • No per-environment access control — the intern has the same production credentials as the lead engineer.

.env files were designed for local convenience. They were never meant to be a secrets management system.


The Solution — How EnvShare Works

EnvShare replaces .env files with a centralized, encrypted, access-controlled platform. The SDK fetches your variables at runtime and injects them directly into process.env — nothing is ever written to disk.

┌─────────────────────────────────────────────────────────┐
│                   EnvShare Platform                      │
│              envshared.vercel.app                         │
│                                                          │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                  │
│  │   Dev   │  │ Staging │  │  Prod   │  ← Environments  │
│  │ 12 vars │  │ 12 vars │  │ 12 vars │                  │
│  └────┬────┘  └────┬────┘  └────┬────┘                  │
│       │            │            │                         │
│  ┌────┴────────────┴────────────┴────┐                   │
│  │     AES-256-GCM Encryption        │                   │
│  │     Per-project derived keys      │                   │
│  └────────────────┬──────────────────┘                   │
│                   │                                       │
│  ┌────────────────┴──────────────────┐                   │
│  │        REST API + Auth            │                   │
│  │  API Key + IP Allowlist + Audit   │                   │
│  └────────────────┬──────────────────┘                   │
└───────────────────┼──────────────────────────────────────┘
                    │
          ┌─────────┴─────────┐
          │  @alvarorestrepo/  │
          │  envshare-sdk      │
          │                    │
          │  • init()          │
          │  • ETag caching    │
          │  • Auto-retry      │
          │  • Stale fallback  │
          └─────────┬─────────┘
                    │
                    ▼
            ┌──────────────┐
            │  Your App    │
            │              │
            │ process.env  │
            │ .DATABASE_URL│
            │ .API_SECRET  │
            │ .REDIS_URL   │
            └──────────────┘

Your app calls init() once at startup. The SDK authenticates with your API key, verifies your IP is in the allowlist, decrypts the variables server-side, and injects them into process.env. From that point on, your code reads process.env.DATABASE_URL exactly like it would with a .env file — except the secret was never on disk.


⚠️ Prerequisites — Read This First

Before your SDK calls will work, you MUST configure both an API key and the IP allowlist on the platform.

EnvShare uses a two-layer security model. Both layers must pass for the SDK to receive variables:

  1. API Key — validates you have access to the project
  2. IP Allowlist — validates your machine/server has access to the specific environment

Setup Steps

  1. Go to envshared.vercel.app and create an account
  2. Create a project (e.g., my-app)
  3. Add your environments (development, staging, production)
  4. Add your environment variables — they are encrypted with AES-256-GCM before storage
  5. Go to Settings → API Keys → Generate a key (copy it — it's shown only once)
  6. Go to Settings → IP Configuration → Add the IPs of your machines/servers:
    • Local development: your public IP — find it at ifconfig.me
    • CI/CD runners: your provider's IP ranges (e.g., GitHub Actions IP ranges)
    • Production servers: your server's static IP or load balancer IP
  7. Wait for IP approval — if your role is Developer, an Owner or Admin must approve your IP request
  8. Now your SDK calls will work

How the Two Layers Work

Your Machine (IP: 203.0.113.42)
       │
       ▼
  ┌──────────────┐
  │  API Key     │──→ "Do you have access to this PROJECT?"
  │  Validation  │     ✅ Valid key, not revoked
  └──────┬───────┘
         │
         ▼
  ┌──────────────┐
  │ IP Allowlist │──→ "Is your IP approved for this ENVIRONMENT?"
  │    Check     │     ✅ 203.0.113.42 is in the allowlist
  └──────┬───────┘
         │
         ▼
  ┌──────────────┐
  │  Decrypt &   │──→ Variables decrypted with AES-256-GCM
  │   Respond    │     Returned to your app
  └──────────────┘

Important: If the IP allowlist for an environment is empty, access is DENIED — not open. This is by design. You must explicitly add at least one IP to access any environment.

Common Errors During Setup

| Error | Meaning | Fix | |-------|---------|-----| | AuthError (401) | API key is invalid or revoked | Generate a new key in Settings → API Keys | | ForbiddenError (403) | Your IP is not in the allowlist | Add your IP in Settings → IP Configuration | | NotFoundError (404) | Project or environment doesn't exist | Check spelling — slugs are case-sensitive |


Quick Start

Installation

npm install @alvarorestrepo/envshare-sdk

Option 1: Programmatic (recommended)

import { init } from '@alvarorestrepo/envshare-sdk';

await init({
  apiKey: process.env.ENVSHARE_API_KEY!,
  project: 'my-app',
  environment: 'production',
});

// process.env now has all your variables
console.log(process.env.DATABASE_URL);
console.log(process.env.API_SECRET);

Option 2: Preload (zero code changes)

Set the required environment variables:

export ENVSHARE_API_KEY=es_live_xxxxx
export ENVSHARE_PROJECT=my-app
export ENVSHARE_ENVIRONMENT=production

Run your app with --import:

node --import @alvarorestrepo/envshare-sdk/preload app.js

Variables are fetched and injected into process.env before your application code runs.


Configuration Reference

| Option | Type | Default | Env Var Fallback | Description | |--------|------|---------|------------------|-------------| | apiKey | string | — | ENVSHARE_API_KEY | API key (starts with es_live_) | | project | string | — | ENVSHARE_PROJECT | Project slug | | environment | string | — | ENVSHARE_ENVIRONMENT | Environment name (development, staging, production) | | apiUrl | string | https://envshared.vercel.app | ENVSHARE_API_URL | Base URL of the EnvShare API | | cacheTtl | number | 300_000 (5 min) | — | Cache TTL in milliseconds | | maxStale | number | 3_600_000 (1 hr) | — | Stale cache window in milliseconds | | timeout | number | 10_000 (10s) | — | Request timeout in milliseconds | | maxRetries | number | 3 | — | Max retry attempts for retryable errors | | inject | boolean | true | — | Inject variables into process.env | | logger | Logger | noopLogger | — | Logger instance (silent by default) |

Full example:

import { init } from '@alvarorestrepo/envshare-sdk';

await init({
  apiKey: 'es_live_xxxxx',
  project: 'my-app',
  environment: 'production',
  apiUrl: 'https://envshared.vercel.app',
  cacheTtl: 300_000,
  maxStale: 3_600_000,
  timeout: 10_000,
  maxRetries: 3,
  inject: true,
});

API Reference

init(config): Promise<FetchResult>

Initialize the SDK, fetch variables from the EnvShare API, and optionally inject them into process.env.

const result = await init({
  apiKey: 'es_live_xxxxx',
  project: 'my-app',
  environment: 'production',
});

console.log(result.variables);  // { DATABASE_URL: '...', API_SECRET: '...' }
console.log(result.fromCache);  // false (first call is always fresh)
console.log(result.etag);       // "abc123" (used for cache revalidation)

Returns: { variables: Record<string, string>, fromCache: boolean, etag: string | null }

fetch(): Promise<FetchResult>

Fetch variables without reinitializing. Uses in-memory cache and ETag revalidation to minimize network requests.

const result = await fetch();
// If called within cacheTtl, returns cached data without hitting the network

clear(): void

Clear the in-memory cache, remove all injected variables from process.env, and reset the SDK state. Useful for testing or graceful shutdown.

clear();
// Cache purged, process.env cleaned, SDK reset

isInitialized(): boolean

Check whether the SDK has been initialized.

if (!isInitialized()) {
  await init({ ... });
}

Caching & Resilience

The SDK uses an in-memory cache with ETag revalidation and stale-while-revalidate semantics. This means your app stays resilient even if the EnvShare API is temporarily unavailable.

  • Zero dependencies — uses native fetch (Node 18+)
  • ETag revalidation — only downloads variables when they've actually changed (HTTP 304)
  • Stale fallback — if the API is down, the SDK returns the last known good values
  • Automatic retry — retryable errors (network, timeout, rate limit) are retried with exponential backoff
Request Flow:

  fetch() called
       │
       ▼
  ┌──────────┐  YES   ┌───────────┐
  │ Cache    ├───────→│ Return    │
  │ fresh?   │        │ cached    │
  └────┬─────┘        └───────────┘
       │ NO
       ▼
  ┌──────────┐  200   ┌───────────┐
  │ Call API ├───────→│ Update    │
  │ w/ ETag  │        │ cache     │
  └────┬─────┘        └───────────┘
       │ 304
       ▼
  ┌──────────┐        ┌───────────┐
  │ Not      ├───────→│ Refresh   │
  │ Modified │        │ TTL       │
  └────┬─────┘        └───────────┘
       │ ERROR
       ▼
  ┌──────────┐  YES   ┌───────────┐
  │ Stale    ├───────→│ Return    │
  │ cache?   │        │ stale     │
  └────┬─────┘        └───────────┘
       │ NO
       ▼
  ┌──────────┐
  │  THROW   │
  │  Error   │
  └──────────┘

Cache timeline:

├── cacheTtl (5 min default) ──┤── maxStale (1 hr default) ──┤
│        FRESH                 │         STALE                │  EXPIRED
│   Return instantly           │   Try API, fallback to cache │  Must fetch

Error Handling

All SDK errors extend EnvShareError, so you can catch all SDK errors in one block or handle specific types:

import {
  init,
  EnvShareError,
  AuthError,
  ForbiddenError,
  NetworkError,
  RateLimitError,
  NotFoundError,
  ConfigError,
  TimeoutError,
} from '@alvarorestrepo/envshare-sdk';

try {
  await init({
    apiKey: process.env.ENVSHARE_API_KEY!,
    project: 'my-app',
    environment: 'production',
  });
} catch (error) {
  if (error instanceof AuthError) {
    // 401 — API key is invalid, revoked, or expired
    console.error('Invalid API key. Generate a new one at envshared.vercel.app');
  } else if (error instanceof ForbiddenError) {
    // 403 — Your IP is not in the allowlist for this environment
    console.error('IP not allowed. Add your IP in Settings → IP Configuration');
  } else if (error instanceof NotFoundError) {
    // 404 — Project or environment doesn't exist
    console.error('Project or environment not found. Check the slug spelling.');
  } else if (error instanceof RateLimitError) {
    // 429 — Too many requests
    console.error(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof NetworkError) {
    // DNS failure, connection refused, etc.
    console.error('Cannot reach EnvShare API');
  } else if (error instanceof TimeoutError) {
    console.error('Request timed out');
  } else if (error instanceof ConfigError) {
    console.error('Invalid SDK configuration');
  }
}

Error Types

| Error | Code | HTTP | Retryable | Cause | |-------|------|------|-----------|-------| | ConfigError | CONFIG_ERROR | — | No | Invalid or missing configuration | | AuthError | AUTH_ERROR | 401 | No | Invalid, revoked, or expired API key | | ForbiddenError | FORBIDDEN_ERROR | 403 | No | IP not in allowlist for this environment | | NotFoundError | NOT_FOUND_ERROR | 404 | No | Project or environment not found | | RateLimitError | RATE_LIMIT_ERROR | 429 | Yes | Too many requests (has retryAfter property) | | NetworkError | NETWORK_ERROR | — | Yes | Connection/DNS failure | | TimeoutError | TIMEOUT_ERROR | — | Yes | Request exceeded timeout |

Retryable errors are automatically retried up to maxRetries times with exponential backoff before throwing.


CI/CD Examples

GitHub Actions

steps:
  - name: Install dependencies
    run: npm ci

  - name: Start app with EnvShare
    env:
      ENVSHARE_API_KEY: ${{ secrets.ENVSHARE_API_KEY }}
      ENVSHARE_PROJECT: my-app
      ENVSHARE_ENVIRONMENT: staging
    run: node --import @alvarorestrepo/envshare-sdk/preload app.js

Note: You must add your GitHub Actions runner IPs to the IP allowlist for the target environment. GitHub publishes their runner IP ranges in their documentation. Alternatively, use a self-hosted runner with a static IP.

Docker

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .

ENV ENVSHARE_PROJECT=my-app
ENV ENVSHARE_ENVIRONMENT=production

# Pass ENVSHARE_API_KEY at runtime, not build time:
# docker run -e ENVSHARE_API_KEY=es_live_xxxxx my-app
CMD ["node", "--import", "@alvarorestrepo/envshare-sdk/preload", "app.js"]

Note: Never bake ENVSHARE_API_KEY into a Docker image. Pass it at runtime via -e or your orchestrator's secrets management (ECS task definition, Kubernetes secret, etc.).

Programmatic in CI

If you prefer programmatic initialization over preload:

// bootstrap.ts — runs before your app
import { init } from '@alvarorestrepo/envshare-sdk';

await init({
  apiKey: process.env.ENVSHARE_API_KEY!,
  project: process.env.ENVSHARE_PROJECT!,
  environment: process.env.ENVSHARE_ENVIRONMENT!,
});

// Now start your app
await import('./app.js');

🔒 Security Model

EnvShare is built with defense-in-depth. Multiple security layers protect your secrets:

Encryption at Rest

All environment variables are encrypted with AES-256-GCM before being stored in the database. Each project uses a unique encryption key derived via HKDF (HMAC-based Key Derivation Function) from a master key. Even if the database is compromised, the variables are unreadable without the master key.

API Key Security

API keys are SHA-256 hashed before storage. The raw key (starting with es_live_) is shown exactly once when generated — it is never stored or retrievable again. If a key is compromised, it can be revoked instantly from the platform.

IP Allowlist

Every environment has an independent IP allowlist. A valid API key alone is not enough — the request must also originate from an approved IP address. This prevents stolen API keys from being used outside your infrastructure.

Role-Based Access Control

| Role | Can manage variables | Can manage API keys | Can approve IPs | Can invite members | |------|---------------------|--------------------|-----------------|--------------------| | Owner | Yes | Yes | Yes | Yes | | Admin | Yes | Yes | Yes | Yes | | Developer | Yes | No | No (must request) | No |

Developers can request IP access, but an Owner or Admin must approve it.

Audit Logging

Every API access, key generation, key revocation, IP approval, and variable change is recorded in an audit log with:

  • Who performed the action
  • When it happened
  • From which IP address
  • What was affected

Rate Limiting

The API enforces rate limits per IP address and per API key to prevent abuse and brute-force attacks.


All of this is managed through the EnvShare Platform. Sign up to get started.


Comparison: .env Files vs EnvShare SDK

| Feature | .env files | EnvShare SDK | |---------|-------------|--------------| | Secrets on disk | Yes (risk) | Never | | Accidental git commits | Common risk | Impossible | | Sharing secrets | Slack/email | Encrypted platform | | Audit trail | None | Full audit log | | Access control | None | Per-environment IP allowlist | | Auto-sync across team | Manual | Automatic on restart | | Encryption at rest | No | AES-256-GCM | | Revoke access | Change all passwords | Revoke key instantly | | Per-environment isolation | One .env per environment, manually managed | Built-in, platform-managed | | Zero dependencies | Requires dotenv | Native fetch (Node 18+) |


Features

  • Zero dependencies — uses native fetch (Node 18+)
  • In-memory cache with ETag revalidation
  • Stale-while-revalidate for resilience when the API is down
  • Automatic retry with exponential backoff for transient errors
  • TypeScript-first with full type definitions
  • ESM + CommonJS dual publish
  • Preload mode — zero-code setup with --import

EnvBridge — Runtime Next.js Support

The Problem: NEXT_PUBLIC_* and Build-Time Replacement

Next.js performs static text replacement on process.env.NEXT_PUBLIC_* references at build time. The reference itself is removed from your code:

// What you write
const url = process.env.NEXT_PUBLIC_API_URL;

// What Next.js outputs after build (the variable reference is GONE)
const url = "https://api.example.com";

The EnvShare SDK injects variables into process.env at runtime — but by the time your app runs on the client, those NEXT_PUBLIC_* references have already been replaced with whatever value was available at build time (or undefined if nothing was set).

BUILD TIME (next build)                    RUNTIME (node server.js)
─────────────────────────                  ────────────────────────
process.env.NEXT_PUBLIC_API_URL            EnvShare SDK injects
    → replaced with literal "..."              → process.env.NEXT_PUBLIC_API_URL = "https://..."
    → original reference is GONE               → but client code already has the literal

Result: Server Components work fine (they read process.env at runtime). Client Components get stale build-time values.

The Solution: EnvBridge + env()

The SDK provides three exports to solve this:

| Export | Type | Purpose | |--------|------|---------| | EnvBridge | React Server Component | Reads NEXT_PUBLIC_* from runtime process.env and injects them into window.__ENVSHARE via a <script> tag | | env(key) | Function | Universal accessor — reads from process.env on the server, from window.__ENVSHARE on the client | | envRequired(key) | Function | Same as env() but throws a descriptive error if the variable is not defined |

Setup

Step 1: Add EnvBridge to your root layout

// app/layout.tsx
import { EnvBridge } from '@alvarorestrepo/envshare-sdk';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <EnvBridge />
      </head>
      <body>{children}</body>
    </html>
  );
}

Step 2: Use env() instead of process.env in Client Components

'use client';
import { env } from '@alvarorestrepo/envshare-sdk';

export function ApiStatus() {
  const apiUrl = env('NEXT_PUBLIC_API_URL');
  return <p>API: {apiUrl}</p>;
}

That's it. EnvBridge renders at the server, env() reads the injected values on the client.

Usage Examples

Basic usage in a Client Component:

'use client';
import { env } from '@alvarorestrepo/envshare-sdk';

export function Analytics() {
  const trackingId = env('NEXT_PUBLIC_TRACKING_ID');
  const apiUrl = env('NEXT_PUBLIC_API_URL');

  // Both values come from EnvShare runtime injection,
  // not from build-time replacement
  return <script src={`https://analytics.example.com/${trackingId}`} />;
}

Using envRequired() for mandatory variables:

'use client';
import { envRequired } from '@alvarorestrepo/envshare-sdk';

export function PaymentForm() {
  // Throws with a helpful error if NEXT_PUBLIC_STRIPE_KEY is not set:
  // "[envshare] Required environment variable "NEXT_PUBLIC_STRIPE_KEY" is not defined.
  //  Make sure EnvShare SDK is loaded and EnvBridge is in your root layout."
  const stripeKey = envRequired('NEXT_PUBLIC_STRIPE_KEY');

  return <div data-stripe-key={stripeKey}>...</div>;
}

Server Components — just use process.env directly:

// app/dashboard/page.tsx (Server Component — no 'use client')
export default async function DashboardPage() {
  // Server Components read process.env at runtime — no EnvBridge needed
  const apiUrl = process.env.NEXT_PUBLIC_API_URL;
  const secret = process.env.DATABASE_URL; // non-public vars too

  const data = await fetch(apiUrl + '/stats');
  // ...
}

You can also use env() in Server Components — it reads process.env on the server, so it works the same way:

import { env } from '@alvarorestrepo/envshare-sdk';

export default async function DashboardPage() {
  const apiUrl = env('NEXT_PUBLIC_API_URL'); // reads process.env on server
  // ...
}

Custom prefix:

If your app uses a different prefix convention, pass it to EnvBridge:

<EnvBridge prefix="MY_APP_" />

This will collect all MY_APP_* variables from process.env and inject them into window.__ENVSHARE. The env() helper reads from window.__ENVSHARE for keys starting with NEXT_PUBLIC_ by default — for custom prefixes, access window.__ENVSHARE directly on the client or use env() on the server.

TypeScript usage:

The env() and envRequired() functions are fully typed:

import { env, envRequired } from '@alvarorestrepo/envshare-sdk';

// env() returns string | undefined
const apiUrl: string | undefined = env('NEXT_PUBLIC_API_URL');

// envRequired() returns string (throws if undefined)
const stripeKey: string = envRequired('NEXT_PUBLIC_STRIPE_KEY');

// Type-safe wrapper for your app's env vars
function getConfig() {
  return {
    apiUrl: envRequired('NEXT_PUBLIC_API_URL'),
    analyticsId: env('NEXT_PUBLIC_ANALYTICS_ID') ?? 'default',
    debug: env('NEXT_PUBLIC_DEBUG') === 'true',
  } as const;
}

How It Works

1. Server startup
   ┌────────────────────────────────┐
   │  EnvShare SDK init()           │
   │  → fetches vars from API      │
   │  → injects into process.env   │
   └──────────────┬─────────────────┘
                  │
2. SSR render (every request)
   ┌──────────────┴─────────────────┐
   │  <EnvBridge /> (Server Component)│
   │  → reads NEXT_PUBLIC_* from    │
   │    process.env at runtime      │
   │  → renders <script> tag with   │
   │    JSON-serialized values      │
   └──────────────┬─────────────────┘
                  │
3. Client hydration
   ┌──────────────┴─────────────────┐
   │  Browser executes <script>     │
   │  → window.__ENVSHARE = {...}   │
   └──────────────┬─────────────────┘
                  │
4. Client Components
   ┌──────────────┴─────────────────┐
   │  env('NEXT_PUBLIC_API_URL')    │
   │  → reads window.__ENVSHARE     │
   │  → returns runtime value       │
   └────────────────────────────────┘

Comparison

| Approach | Server Components | Client Components | No extra HTTP | Immediate | |----------|:-:|:-:|:-:|:-:| | process.env (build-time) | ✅ | ❌ runtime vars | ✅ | ✅ | | EnvBridge + env() | ✅ | ✅ | ✅ | ✅ | | API route | ✅ | ✅ | ❌ | ❌ |

Important Notes

  • Replace process.env.NEXT_PUBLIC_* with env('NEXT_PUBLIC_*') in Client Components. This is the only change needed — env() handles the server/client logic automatically.
  • Server Components can still use process.env directly. They run at request time and read the runtime values without any bridge.
  • EnvBridge must be in the root layout (app/layout.tsx). If it's only in a nested layout, pages outside that layout won't have access to the injected values.
  • React 18+ is required for EnvBridge (it's a React Server Component). React is an optional peer dependency of the SDK.
  • Don't put secrets in NEXT_PUBLIC_* variables. EnvBridge serializes values as JSON in the HTML — they are visible in the page source. This is the same behavior as Next.js build-time replacement. Only use NEXT_PUBLIC_* for values that are safe to expose to the browser.

Requirements

  • Node.js >= 18.0.0
  • An EnvShare account with:
    • At least one project and environment
    • An API key
    • Your IP(s) added to the allowlist

Links


License

MIT