# ============================================================================ # @bloomneo/appkit — environment variables # ============================================================================ # # Copy this file to `.env` and fill in what your app uses: # cp .env.example .env # # AppKit does NOT auto-load .env. Load it yourself, BEFORE any appkit import: # - CLI: node --import=dotenv/config ./server.mjs # - Code: import 'dotenv/config'; // must be the first import # # Only the MINIMUM block at the top is required to start. The rest is opt-in — # declare a var and the relevant module lights up; leave it blank and the # module uses a safe in-memory / local default. # ============================================================================ # ─── MINIMUM — set these first ────────────────────────────────────────────── # Node environment. Controls production-mode checks, log verbosity, error # stack traces, and memory caching behavior across modules. NODE_ENV=development # JWT signing secret. REQUIRED if you use the auth module. Must be at least # 32 random characters. Rotating this invalidates every existing token. # Generate one: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" BLOOM_AUTH_SECRET= # ─── Optional: app identity ───────────────────────────────────────────────── # Used by logger, queue, email defaults for service tagging. Falls back to # `npm_package_name` if unset. # BLOOM_SERVICE_NAME=my-api # BLOOM_SERVICE_VERSION=1.0.0 # HTTP server listen config (read by the config module; not used internally). # PORT=3000 # HOST=0.0.0.0 # ─── Auth (src/auth) ──────────────────────────────────────────────────────── # BLOOM_AUTH_SECRET is above in the minimum block. # Token lifetime. Accepts durations like '7d', '24h', '30m', or seconds. # BLOOM_AUTH_EXPIRES_IN=7d # Bcrypt cost factor for hashPassword(). 10 is the default; 12+ for production. # BLOOM_AUTH_BCRYPT_ROUNDS=10 # Custom role.level hierarchy. Default ships with user.basic → admin.system. # Format: comma-separated role.level pairs; left-to-right is low-to-high. # BLOOM_AUTH_ROLES=user.basic,user.pro,admin.tenant,admin.system # Custom permissions list. Comma-separated tokens. # BLOOM_AUTH_PERMISSIONS=manage:own,manage:team,manage:all # ─── Database (src/database) ──────────────────────────────────────────────── # Primary connection string. Provider is auto-detected from the URL scheme: # postgresql://… → pg # mysql://… → mysql2 # mongodb://… → mongoose # file:./… or sqlite://… → sqlite # DATABASE_URL=postgresql://user:pass@localhost:5432/myapp # Multi-tenant filtering. 'auto' injects WHERE tenant_id=? into every query. # Leave unset or set to 'false' for single-tenant apps. # # 5.0: when enabled, databaseClass.get() THROWS rather than returning an # unscoped client. Use database.tenant(req, fn) / database.bypass(reason, fn). # BLOOM_DB_TENANT=false # ─── Roles: matrix mode (src/auth) ────────────────────────────────────────── # OPT-IN. Set BOTH to switch role inheritance from a single ladder to the # product of two axes: a role satisfies a requirement only when its scope AND # its tier are both high enough. This is what stops moderator.system from # inheriting admin.tenant's delete. # # Setting only one throws — a half-configured lattice would silently give you # a different authorization model than you asked for. # Leave both unset for the classic 9-level ladder. # BLOOM_AUTH_SCOPES=client,tenant,org,system # BLOOM_AUTH_TIERS=user,moderator,admin # ─── MCP server (src/mcp) ─────────────────────────────────────────────────── # Signs the OAuth JWTs issued to MCP clients (min 32 chars). # Falls back to BLOOM_AUTH_SECRET when unset. # BLOOM_MCP_OAUTH_SECRET= # Reported by the MCP `initialize` handshake. # BLOOM_MCP_NAME=my-app # BLOOM_MCP_VERSION=1.0.0 # BLOOM_MCP_PROTOCOL_VERSION=2025-06-18 # Directory scanned by mcp.discover(). # BLOOM_MCP_FEATURES_DIR=features # ─── Cache (src/cache) ────────────────────────────────────────────────────── # Set REDIS_URL to switch from Memory to Redis. Unset = in-process Memory cache. # REDIS_URL=redis://localhost:6379 # REDIS_PASSWORD= # Default TTL in seconds (1hr prod, 5min dev). # BLOOM_CACHE_TTL=3600 # Namespace prefix. Falls back to BLOOM_SERVICE_NAME. # BLOOM_CACHE_NAMESPACE=app # ─── Queue (src/queue) ────────────────────────────────────────────────────── # Transport auto-detection: Memory → Redis (if REDIS_URL) → Database (if DATABASE_URL). # Override explicitly if needed: # BLOOM_QUEUE_TRANSPORT=redis # Concurrency + retry tuning. # BLOOM_QUEUE_CONCURRENCY=5 # BLOOM_QUEUE_MAX_ATTEMPTS=3 # ─── Email (src/email) ────────────────────────────────────────────────────── # Provider auto-detection: Console (dev) → Resend (if RESEND_API_KEY) → SMTP. # Option A: Resend (recommended for transactional email). # RESEND_API_KEY=re_... # Option B: SMTP. # SMTP_HOST=smtp.example.com # SMTP_PORT=587 # SMTP_USER=user@example.com # SMTP_PASS= # Required in production — the "From" identity. # BLOOM_EMAIL_FROM_EMAIL=noreply@yourdomain.com # BLOOM_EMAIL_FROM_NAME=YourApp # ─── Storage (src/storage) ────────────────────────────────────────────────── # Strategy auto-detection: Local → S3 (if AWS_S3_BUCKET) → R2 (if CLOUDFLARE_R2_BUCKET). # Local (default — where uploaded files are written). # BLOOM_STORAGE_DIR=./uploads # Option A: AWS S3. # AWS_S3_BUCKET=my-bucket # AWS_REGION=us-east-1 # AWS_ACCESS_KEY_ID= # AWS_SECRET_ACCESS_KEY= # Option B: Cloudflare R2. # CLOUDFLARE_R2_BUCKET=my-bucket # CLOUDFLARE_ACCOUNT_ID= # CLOUDFLARE_R2_ACCESS_KEY_ID= # CLOUDFLARE_R2_SECRET_ACCESS_KEY= # ─── Security (src/security) ──────────────────────────────────────────────── # CSRF token signing secret. Falls back to BLOOM_AUTH_SECRET if unset. # BLOOM_SECURITY_CSRF_SECRET= # AES-256-GCM key for security.encrypt() / security.decrypt(). Must be 32 bytes # hex-encoded (64 hex chars). Generate: openssl rand -hex 32 # BLOOM_SECURITY_ENCRYPTION_KEY= # Rate limit: requests per window. # BLOOM_SECURITY_RATE_LIMIT=100 # BLOOM_SECURITY_RATE_WINDOW=900000 # 15 minutes in ms # ─── Event (src/event) ────────────────────────────────────────────────────── # Strategy auto-detection: Memory → Redis (if REDIS_URL). # BLOOM_EVENT_NAMESPACE=default # ─── Logger (src/logger) ──────────────────────────────────────────────────── # Level: debug | info | warn | error. Default: info (prod) / debug (dev). # BLOOM_LOGGER_LEVEL=info # Scope: minimal | standard | full. Default: minimal in prod/CI, full in dev. # BLOOM_LOGGER_SCOPE=standard # Enable file / database / HTTP / webhook transports. # BLOOM_LOGGER_FILE=true # BLOOM_LOGGER_DATABASE=false # BLOOM_LOGGER_HTTP_URL= # BLOOM_LOGGER_WEBHOOK_URL= # ─── Error (src/error) ────────────────────────────────────────────────────── # Show stack traces in error responses. True in dev, false in prod. # BLOOM_ERROR_STACK=false # BLOOM_ERROR_LOG=true