bini-env 1.1.0 → 1.1.2

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.
package/README.md CHANGED
@@ -3,43 +3,31 @@
3
3
  ![npm](https://img.shields.io/npm/v/bini-env?color=cyan&style=flat-square)
4
4
  ![npm downloads](https://img.shields.io/npm/dm/bini-env?style=flat-square)
5
5
  ![license](https://img.shields.io/npm/l/bini-env?style=flat-square)
6
- ![vite](https://img.shields.io/badge/vite-%3E%3D8.0-646CFF?style=flat-square&logo=vite)
7
- ![hono](https://img.shields.io/badge/hono-%3E%3D4.0-E36002?style=flat-square&logo=hono)
8
- ![typescript](https://img.shields.io/badge/TypeScript-ready-3178C6?style=flat-square&logo=typescript)
6
+ ![vite](https://img.shields.io/badge/vite-8.x-646CFF?style=flat-square&logo=vite)
7
+ ![hono](https://img.shields.io/badge/hono-4.x-E36002?style=flat-square&logo=hono)
8
+ ![typescript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript)
9
9
  ![node](https://img.shields.io/badge/node-%3E%3D20.19-339933?style=flat-square&logo=node.js)
10
10
 
11
- **Zero-config environment variable system + Vite plugin for Bini.js**
12
- Powered by [Hono](https://hono.dev) — reads env vars from the request context so variables are always resolved from the correct runtime binding, whether you're on Node.js, Bun, Deno, Vercel Edge, Netlify Edge, or Cloudflare Workers.
11
+ **Environment variable system + Vite plugin for Bini.js**
13
12
 
14
- ---
15
-
16
- ## ⚠️ Before You Use This
17
-
18
- This library **does NOT magically make env vars safe**.
19
-
20
- - Anything exposed to the client (`import.meta.env`) is **public**.
21
- - Only server-side code (`getEnv`, `requireEnv`) can safely access secrets.
22
- - Misconfigured prefixes = **data leak**.
23
-
24
- If you don't understand this, stop and fix that first.
13
+ Powered by [Hono](https://hono.dev) — reads env vars from the Hono request context so variables are always resolved from the correct runtime binding, whether you're on Node.js, Bun, Deno, Vercel Edge, Netlify Edge, or Cloudflare Workers.
25
14
 
26
15
  ---
27
16
 
28
- ## ✨ Features
17
+ ## Features
29
18
 
30
- - **Hono-native API** — `getEnv(c, key)` / `requireEnv(c, key)` read from Hono's request context
31
- - **Universal runtime support** — CF Workers, Node, Bun, Deno, Vercel Edge, Netlify Edge
32
- - **Zero dotenv** — no `.env` file parsing at runtime; vars come from the host
33
- - **Build-time `process.env` fallback** — for Vite plugin hooks that run outside a request
34
- - **Auto server restart** — dev server restarts when `.env` files are created or changed
35
- - **Prefix control** — supports `BINI_`, `VITE_`, or custom
36
- - **Tree-shakeable** — no dead code in client bundles
37
- - **Typed** — full TypeScript support with exported `HonoContext` duck type
38
- - **Fast** — zero overhead; no file reads in production
19
+ - **Hono-native** — `getEnv(c, key)` / `requireEnv(c, key)` read directly from the Hono request context
20
+ - **Universal runtime** — CF Workers, Node, Bun, Deno, Vercel Edge, Netlify Edge — Hono's adapter handles all of it
21
+ - **Zero dotenv** — no `.env` parsing at runtime; vars come from the host platform
22
+ - **Vite-native dev** — `.env` loading, file watching, and server restarts are handled by Vite itself
23
+ - **Dev banner** — prints `ß Bini.js (dev/preview)` and lists detected `.env` files on server start
24
+ - **Prefix control** — `BINI_` and `VITE_` included out of the box, extend with your own
25
+ - **Tree-shakeable** — nothing lands in the client bundle that shouldn't
26
+ - **Typed** — full TypeScript support with an exported `HonoContext` type
39
27
 
40
28
  ---
41
29
 
42
- ## 📦 Installation
30
+ ## Installation
43
31
 
44
32
  ```bash
45
33
  pnpm add bini-env hono
@@ -53,7 +41,9 @@ yarn add bini-env hono
53
41
 
54
42
  ---
55
43
 
56
- ## 🚀 Quick Start
44
+ ## Quick Start
45
+
46
+ **1. Register the Vite plugin**
57
47
 
58
48
  ```ts
59
49
  // vite.config.ts
@@ -65,18 +55,34 @@ export default defineConfig({
65
55
  })
66
56
  ```
67
57
 
58
+ **2. Read env vars in your Hono handlers**
59
+
68
60
  ```ts
69
- // src/app/api/example.ts
61
+ // src/app/api/hello.ts
70
62
  import { Hono } from 'hono'
71
63
  import { getEnv, requireEnv } from 'bini-env'
72
64
 
73
65
  const app = new Hono()
74
66
 
75
- app.get('/hello', (c) => {
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- const ctx = c as any
78
- const name = getEnv(ctx, 'APP_NAME') ?? 'World'
79
- return c.json({ message: `Hello, ${name}!` })
67
+ app.post('/hello', async (c) => {
68
+ try {
69
+ const ctx = c as any
70
+
71
+ // requireEnv throws if the var is missing — fail fast on required config
72
+ const apiKey = requireEnv(ctx, 'MY_API_KEY')
73
+
74
+ // getEnv returns undefined if missing — use ?? to provide a default
75
+ const appName = getEnv(ctx, 'APP_NAME') ?? 'World'
76
+ const timeout = parseInt(getEnv(ctx, 'TIMEOUT_MS') ?? '5000')
77
+
78
+ return c.json({ message: `Hello, ${appName}!` })
79
+
80
+ } catch (error: any) {
81
+ if (error.message?.includes('[bini-env] Missing required')) {
82
+ return c.json({ error: error.message }, 500)
83
+ }
84
+ return c.json({ error: 'Something went wrong.' }, 500)
85
+ }
80
86
  })
81
87
 
82
88
  export default app
@@ -84,155 +90,233 @@ export default app
84
90
 
85
91
  ---
86
92
 
87
- ## 🔐 Environment Rules (Read This Twice)
93
+ ## Usage Pattern
88
94
 
89
- > ✅ Both `BINI_` and `VITE_` prefixes work out of the box — no extra config needed.
95
+ **Always pass `c` explicitly.** Cast it once at the top of the handler, then use `ctx` throughout.
90
96
 
91
- ### Client (PUBLIC)
92
-
93
- ```env
94
- BINI_PUBLIC_API_URL=https://api.example.com
95
- VITE_ANALYTICS_ID=UA-XXXX
97
+ ```ts
98
+ app.post('/example', async (c) => {
99
+ try {
100
+ const ctx = c as any
101
+
102
+ // Required vars — handler throws immediately if missing
103
+ const dbUrl = requireEnv(ctx, 'DATABASE_URL')
104
+ const apiKey = requireEnv(ctx, 'STRIPE_SECRET_KEY')
105
+
106
+ // Optional vars — fall back to sensible defaults
107
+ const model = getEnv(ctx, 'AI_MODEL') ?? 'gpt-4o'
108
+ const region = getEnv(ctx, 'AWS_REGION') ?? 'us-east-1'
109
+ const maxRetries = parseInt(getEnv(ctx, 'MAX_RETRIES') ?? '3')
110
+ const debug = getEnv(ctx, 'DEBUG_MODE') === 'true'
111
+
112
+ // ... rest of handler
113
+
114
+ } catch (error: any) {
115
+ if (error.message?.includes('[bini-env] Missing required')) {
116
+ return c.json({ error: error.message }, 500)
117
+ }
118
+ return c.json({ error: 'Something went wrong.' }, 500)
119
+ }
120
+ })
96
121
  ```
97
122
 
123
+ **The pattern in three steps:**
124
+
125
+ 1. `const ctx = c as any` — once, at the top of the handler
126
+ 2. `requireEnv(ctx, 'KEY')` — for vars the handler cannot run without
127
+ 3. `getEnv(ctx, 'KEY') ?? 'default'` — for optional vars with sensible defaults
128
+
129
+ ---
130
+
131
+ ## Environment Prefixes
132
+
133
+ ### The Truth About Prefixes
134
+
135
+ **Both `BINI_` and `VITE_` work exactly the same way.** Neither is special.
136
+
137
+ | Prefix | Exposed to Browser? | Default Behavior |
138
+ |--------|--------------------|------------------|
139
+ | `BINI_` | **Yes** | Included in `envPrefix` by default |
140
+ | `VITE_` | **Yes** | Vite's default prefix (always included) |
141
+ | Any prefix you add | **Yes** | If you add it to `envPrefix` |
142
+ | No prefix | **No** | Not exposed to browser |
143
+
144
+ ### What `biniEnv()` Actually Does
145
+
98
146
  ```ts
99
- import.meta.env.BINI_PUBLIC_API_URL
147
+ // biniEnv() sets envPrefix to:
148
+ envPrefix: ['BINI_', 'VITE_']
149
+
150
+ // This tells Vite: "Expose ALL variables starting with BINI_ or VITE_ to import.meta.env"
100
151
  ```
101
152
 
102
- 👉 **Never put secrets here. Ever.**
153
+ **This means:**
154
+ - ✅ `BINI_API_KEY` → available in browser via `import.meta.env.BINI_API_KEY`
155
+ - ✅ `VITE_API_URL` → available in browser via `import.meta.env.VITE_API_URL`
156
+ - ❌ `DATABASE_URL` → NOT available in browser (no prefix)
157
+
158
+ ### How to Keep Secrets Secret
103
159
 
104
- ### Server (PRIVATE)
160
+ **Use no prefix for secrets:**
105
161
 
106
162
  ```env
107
- SMTP_PASS=super_secret
163
+ # ✅ NOT exposed to browser
108
164
  DATABASE_URL=postgres://...
165
+ STRIPE_SECRET_KEY=sk_live_...
166
+ AWS_SECRET_KEY=...
109
167
  ```
110
168
 
169
+ **If you want `BINI_` to be private, remove it from `envPrefix`:**
170
+
111
171
  ```ts
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
- const ctx = c as any
114
- const pass = requireEnv(ctx, 'SMTP_PASS') // throws if missing
115
- const db = getEnv(ctx, 'DATABASE_URL') // returns undefined if missing
172
+ // vite.config.ts
173
+ biniEnv({ envPrefix: ['VITE_'] }) // Only VITE_ exposed
174
+ // BINI_ is no longer exposed to the browser
116
175
  ```
117
176
 
118
- 👉 If this leaks, it's your fault, not the library's.
177
+ ### Choosing the Right Prefix
119
178
 
120
- ---
179
+ Since `BINI_` and `VITE_` are both exposed by default:
121
180
 
122
- ## 🌍 Platform Support
181
+ ```env
182
+ # ✅ Use no prefix for secrets
183
+ DATABASE_URL=postgres://...
184
+ STRIPE_SECRET_KEY=sk_live_...
123
185
 
124
- `getEnv` and `requireEnv` resolve vars from Hono's `env(c)` adapter, which maps to the correct source on every supported platform automatically.
186
+ # ✅ Use any prefix for public config
187
+ BINI_API_URL=https://api.example.com
188
+ VITE_ANALYTICS_ID=UA-XXXX
189
+ PUBLIC_APP_NAME=MyApp # If you add PUBLIC_ to envPrefix
190
+ ```
125
191
 
126
- | Platform | Runtime | Where vars come from | How `env(c)` reads them |
127
- | -------------------- | ------------- | ----------------------------------- | ---------------------------- |
128
- | Node.js | Node | System env / platform dashboard | `process.env` ✅ |
129
- | Bun | Bun | System env / platform dashboard | `process.env` ✅ |
130
- | Vercel Edge | V8 isolate | Project settings → Environment Vars | `process.env` ✅ |
131
- | Netlify Edge | Deno | Site settings → Environment Vars | `Deno.env.get()` ✅ |
132
- | Cloudflare Workers | V8 isolate | `wrangler.toml [vars]` / dashboard | CF bindings via `c.env` ✅ |
133
- | Deno Deploy | Deno | Project settings → Environment Vars | `Deno.env.get()` ✅ |
192
+ ### Prefix Summary
134
193
 
135
- > **Cloudflare note:** Secrets added via `wrangler secret put` are only available inside the fetch handler via `c.env` — this is a Cloudflare Workers architecture constraint, not a bini-env limitation. `getEnv` reads them correctly when called with `c` inside a route handler.
194
+ | Prefix | Exposed to Browser? | Use For |
195
+ |--------|--------------------|---------|
196
+ | No prefix | ❌ Never | **SECRETS** — API keys, database URLs, tokens |
197
+ | `BINI_` | ✅ Yes (by default) | Public config (same as VITE_) |
198
+ | `VITE_` | ✅ Yes (always) | Public config (Vite's default) |
199
+ | Custom prefix | ✅ If in `envPrefix` | Public config you want to control |
136
200
 
137
201
  ---
138
202
 
139
- ## 🧠 How It Actually Works
203
+ ## Platform Support
140
204
 
141
- `getEnv(c, key)` resolves in this order:
205
+ `getEnv` and `requireEnv` delegate to Hono's `env(c)` adapter, which reads from the correct source on every supported platform automatically. Your code never changes regardless of where it deploys.
142
206
 
143
- 1. **`env(c)` from `hono/adapter`** — the Hono request context. This is the primary and correct source on every runtime. Never cached — request-scoped.
144
- 2. **Stored context** — if you called `setContext(c)` in a global middleware, `getEnv('KEY')` (no `c`) uses it.
145
- 3. **`process.env`** — build-time only, for Vite plugin hooks that run outside any request.
207
+ | Platform | Runtime | Env source | How Hono reads it |
208
+ |----------|---------|------------|-------------------|
209
+ | Node.js | Node | System env / platform dashboard | `process.env` ✅ |
210
+ | Bun | Bun | System env / platform dashboard | `process.env` ✅ |
211
+ | Vercel Edge | V8 isolate | Project settings → Env Vars | `process.env` ✅ |
212
+ | Netlify Edge | Deno | Site settings → Env Vars | `Deno.env.get()` ✅ |
213
+ | Cloudflare Workers | V8 isolate | `wrangler.toml` / dashboard | CF bindings via `c.env` ✅ |
214
+ | Deno Deploy | Deno | Project settings → Env Vars | `Deno.env.get()` ✅ |
146
215
 
147
- `dotenv` is not used anywhere. Variables must be set in your platform's environment config.
216
+ > **Cloudflare note:** Secrets set via `wrangler secret put` are only available inside the fetch handler via `c.env` — this is a Workers architecture constraint. `getEnv(ctx, key)` reads them correctly as long as you pass `c`.
148
217
 
149
218
  ---
150
219
 
151
- ## 🔄 Auto Server Restart
220
+ ## How It Works
152
221
 
153
- The dev server automatically restarts when any `.env*` file is created or changed in your project root.
222
+ ### The plugin — `biniEnv()`
154
223
 
155
- ```
156
- 12:00:01 [bini-env] .env.local changed — restarting server
224
+ Does two things: tells Vite which env prefixes to expose to `import.meta.env`, and prints the `ß Bini.js` banner with detected `.env` files when the dev or preview server starts.
225
+
226
+ ```ts
227
+ // what biniEnv() does internally
228
+ config() {
229
+ return { envPrefix: ['BINI_', 'VITE_', ...yourExtras] }
230
+ }
157
231
  ```
158
232
 
159
- Covered events:
160
- - Creating a new `.env`, `.env.local`, `.env.production`, etc.
161
- - Editing any existing `.env*` file
233
+ On server start you will see:
162
234
 
163
- Editor swap files (`.env.swp`, `.env~`, `.env.bak`) are ignored.
235
+ ```
236
+ ß Bini.js (dev)
237
+ ➜ Environments: .env.local, .env
238
+ ➜ Local: http://localhost:3000/
239
+ ➜ Network: http://192.168.1.7:3000/
240
+ ```
164
241
 
165
- ---
242
+ Vite handles everything else natively: loading `.env`, `.env.local`, `.env.[mode]` files during dev, watching and restarting on change, injecting prefixed vars into `import.meta.env` at build time, and HMR when env files change. bini-env does not reimplement any of that.
166
243
 
167
- ## ⚙️ Plugin Options
244
+ ### The env functions — `getEnv` / `requireEnv`
168
245
 
169
- ```ts
170
- biniEnv({
171
- enabled : true,
172
- clearViteHeader: true,
173
- logo : 'ß',
174
- envPrefix : ['BINI_', 'VITE_'],
175
- loadInPreview : true,
176
- })
177
- ```
246
+ Both functions read exclusively from `env(c)` via `hono/adapter` — the Hono request context. There are no `process.env` fallbacks. Every read is request-scoped and resolved by Hono's adapter for the current platform.
178
247
 
179
- > **Critical:** If you change `envPrefix`, you are changing what gets exposed to the browser. Break this → you leak secrets.
248
+ `dotenv` is never used. In production, vars are set in your hosting platform's environment config.
180
249
 
181
250
  ---
182
251
 
183
- ## 📚 API
252
+ ## API Reference
184
253
 
185
254
  ### `getEnv(c, key)`
186
255
 
187
- Returns `string | undefined`. Reads from Hono's request context.
256
+ Returns `string | undefined`. Reads from the Hono request context.
188
257
 
189
258
  ```ts
190
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- const ctx = c as any
192
- const debug = getEnv(ctx, 'DEBUG_MODE')
193
- ```
259
+ app.get('/config', async (c) => {
260
+ const ctx = c as any
194
261
 
195
- Also works without `c` outside a request (build-time / Vite hooks):
262
+ const region = getEnv(ctx, 'AWS_REGION') ?? 'us-east-1'
263
+ const logLevel = getEnv(ctx, 'LOG_LEVEL') ?? 'info'
264
+ const debug = getEnv(ctx, 'DEBUG_MODE') === 'true'
196
265
 
197
- ```ts
198
- const debug = getEnv('DEBUG_MODE')
266
+ return c.json({ region, logLevel, debug })
267
+ })
199
268
  ```
200
269
 
201
270
  ### `requireEnv(c, key)`
202
271
 
203
- Same as `getEnv` but throws if the variable is missing:
272
+ Returns `string`. Throws immediately if the variable is missing or empty. Logs a descriptive error to the terminal on failure.
204
273
 
205
- ```
206
- [bini-env] Missing required environment variable: "SMTP_PASS"
207
- → In development: set it in your platform's env config.
208
- → In production: set it in your hosting dashboard.
274
+ ```ts
275
+ app.post('/send-email', async (c) => {
276
+ try {
277
+ const ctx = c as any
278
+
279
+ const smtpHost = requireEnv(ctx, 'SMTP_HOST')
280
+ const smtpPass = requireEnv(ctx, 'SMTP_PASS')
281
+ const smtpPort = parseInt(getEnv(ctx, 'SMTP_PORT') ?? '587')
282
+
283
+ // ... send email
284
+
285
+ return c.json({ sent: true })
286
+
287
+ } catch (error: any) {
288
+ if (error.message?.includes('[bini-env] Missing required')) {
289
+ return c.json({ error: error.message }, 500)
290
+ }
291
+ return c.json({ error: 'Failed to send email.' }, 500)
292
+ }
293
+ })
209
294
  ```
210
295
 
211
- ```ts
212
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
- const ctx = c as any
214
- const dbUrl = requireEnv(ctx, 'DATABASE_URL')
296
+ On failure, the terminal will show:
297
+
298
+ ```
299
+ [bini-env] error Missing required environment variable: "SMTP_HOST"
300
+ -> Set it in your platform's env config or hosting dashboard.
215
301
  ```
216
302
 
217
- ### `setContext(c)` / `clearContext()`
303
+ ### `biniEnv(options?)`
218
304
 
219
- Store a Hono context module-level so `getEnv('KEY')` works without passing `c` at every call site. Register once in a global middleware:
305
+ Vite plugin. Registers `BINI_` and `VITE_` as env prefixes and optionally adds more.
220
306
 
221
307
  ```ts
222
- app.use('*', (c, next) => {
223
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
- setContext(c as any)
225
- return next()
226
- })
308
+ biniEnv()
309
+ // or with extra prefixes
310
+ biniEnv({ envPrefix: ['MY_PUBLIC_'] })
227
311
  ```
228
312
 
229
- ### `biniEnv(options)`
230
-
231
- Vite plugin. Handles terminal output and dev server watching.
313
+ | Option | Type | Default | Description |
314
+ |--------|------|---------|-------------|
315
+ | `envPrefix` | `string \| string[]` | `[]` | Extra prefixes to expose to `import.meta.env`, in addition to `BINI_` and `VITE_` |
232
316
 
233
317
  ### `biniLogger`
234
318
 
235
- Standalone logger that uses Vite-style terminal output. Available for use in your own Bini.js plugins.
319
+ Vite-style terminal logger. Use it in your own Bini.js plugins or server-side code.
236
320
 
237
321
  ```ts
238
322
  import { biniLogger } from 'bini-env'
@@ -244,102 +328,123 @@ biniLogger.error('Something broke', error)
244
328
 
245
329
  ### `HonoContext`
246
330
 
247
- Exported duck type satisfied by any Hono `Context`. Use it if you need to type a wrapper:
331
+ Exported type (`Context` from Hono). Use it to type helper functions that group env reads, so you only cast `c as any` once per entry point.
248
332
 
249
333
  ```ts
250
334
  import type { HonoContext } from 'bini-env'
251
335
 
252
- function readConfig(c: HonoContext) {
253
- return getEnv(c, 'CONFIG_KEY')
336
+ function readDbConfig(c: HonoContext) {
337
+ const ctx = c as any
338
+ return {
339
+ url: requireEnv(ctx, 'DATABASE_URL'),
340
+ poolSize: parseInt(getEnv(ctx, 'DB_POOL_SIZE') ?? '10'),
341
+ ssl: getEnv(ctx, 'DB_SSL') !== 'false',
342
+ }
254
343
  }
255
344
  ```
256
345
 
257
346
  ---
258
347
 
259
- ## 📂 Env File Resolution Order (Dev only)
348
+ ## Security Best Practices
260
349
 
261
- The Vite plugin watches these files and triggers a server restart on change:
350
+ ### Rule 1: Never Prefix Secrets
351
+ ```env
352
+ # ❌ BAD - This will be exposed to the browser!
353
+ BINI_DATABASE_URL=postgres://...
262
354
 
263
- 1. `.env.local`
264
- 2. `.env.[mode].local`
265
- 3. `.env.[mode]`
266
- 4. `.env`
355
+ # ✅ GOOD - Not exposed to browser
356
+ DATABASE_URL=postgres://...
357
+ ```
267
358
 
268
- In production, none of these are read. Variables must come from your platform.
359
+ ### Rule 2: Use No Prefix for Anything Secret
360
+ ```env
361
+ # ✅ Safe - Not exposed
362
+ DATABASE_URL=postgres://...
363
+ STRIPE_SECRET=sk_live_...
364
+ AWS_SECRET_KEY=...
365
+ ```
366
+
367
+ ### Rule 3: Use Any Prefix for Public Data
368
+ ```env
369
+ # ✅ Safe - Public data
370
+ BINI_API_URL=https://api.example.com
371
+ VITE_GA_ID=UA-XXXXX
372
+ PUBLIC_APP_NAME=MyApp
373
+ ```
374
+
375
+ ### Rule 4: Remove `BINI_` from envPrefix if You Want It Private
376
+ ```ts
377
+ // To make BINI_ private, remove it from envPrefix
378
+ biniEnv({ envPrefix: ['VITE_'] })
379
+ ```
269
380
 
270
381
  ---
271
382
 
272
- ## ⚡ Performance
383
+ ## Performance
273
384
 
274
- | Metric | Dev | Prod |
275
- | ------------- | -------- | ----------- |
276
- | File Reads | 0 | 0 |
277
- | Runtime Cost | ~0ms | 0 |
278
- | Bundle Impact | Minimal | Tree-shaken |
385
+ | Metric | Dev | Prod |
386
+ |--------|-----|------|
387
+ | File reads | 0 | 0 |
388
+ | Runtime cost | ~0ms | 0 |
389
+ | Bundle impact | Minimal | Tree-shaken |
279
390
 
280
- No dotenv. No disk reads. No overhead.
391
+ No dotenv. No disk reads. No caching layer. `getEnv` is a direct call to Hono's adapter on every invocation — request-scoped and correct.
281
392
 
282
393
  ---
283
394
 
284
- ## 🔥 Common Failure Modes
395
+ ## Troubleshooting
396
+
397
+ ### "My BINI_ var is exposed to the browser!"
398
+
399
+ Yes — by design. `BINI_` is included in `envPrefix` by default. Use no prefix for secrets.
285
400
 
286
- ### "Env is undefined in production"
287
- You relied on a `.env` file in production. Set vars in your hosting dashboard — bini-env does not read `.env` files at runtime.
401
+ ### "Env var is undefined in production"
288
402
 
289
- ### "Works in dev, broken in prod"
290
- Same as above. Production vars come from the host, not from files.
403
+ Vars set in `.env` files are only loaded by Vite during local development. In production, set your variables in your hosting platform's environment dashboard (Vercel, Netlify, Cloudflare, etc.).
291
404
 
292
- ### "Secrets leaked"
293
- You exposed them via `BINI_` or `VITE_` prefix. Those are public. Use unprefixed vars for secrets and only read them server-side via `getEnv(c, key)`.
405
+ ### "Works in dev, undefined in prod"
406
+
407
+ Same as above. Local dev works because Vite loads `.env` files automatically. Production requires platform-level configuration.
294
408
 
295
409
  ### "Cloudflare secret not found"
296
- Secrets added via `wrangler secret put` are only available via `c.env` inside your handler — which is exactly where `getEnv(c, key)` reads from. Make sure you're passing `c`.
410
+
411
+ Secrets set via `wrangler secret put` are only available via `c.env` inside a handler — which is exactly what `getEnv(ctx, key)` reads. Ensure you are passing `c` to the function.
297
412
 
298
413
  ### "TypeScript error: Context not assignable to HonoContext"
299
- Hono 4.12 added a `[GET_MATCH_RESULT]` symbol to `HonoRequest` that breaks structural assignability. Cast once per handler:
414
+
415
+ Hono 4.12+ added a symbol to `HonoRequest` that can break structural assignability in strict TypeScript projects. Cast once per handler:
300
416
 
301
417
  ```ts
302
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
418
  const ctx = c as any
304
- const key = requireEnv(ctx, 'MY_KEY')
305
419
  ```
306
420
 
307
421
  ### "Types not found"
308
- Add to your `tsconfig` or entry file:
422
+
423
+ Add to your `tsconfig.json` or entry file:
424
+
309
425
  ```ts
310
426
  /// <reference types="vite/client" />
311
427
  ```
312
428
 
313
429
  ---
314
430
 
315
- ## 🧪 Reality Check
316
-
317
- This library is intentionally simple.
431
+ ## Compatibility
318
432
 
319
- If you need:
320
- - Secret rotation
321
- - Encrypted envs
322
- - Runtime validation schemas
323
-
324
- That's **your job**, not this package.
433
+ | Tool | Version | Notes |
434
+ |------|---------|-------|
435
+ | Vite | 8.x | `envPrefix` hook is fully compatible; empty prefix array is never passed |
436
+ | Hono | 4.x | `hono/adapter` `env()` is stable across all 4.x releases |
437
+ | TypeScript | 5.x | Full type support via exported `HonoContext` |
438
+ | Node.js | ≥ 20.19 | Minimum required version |
325
439
 
326
440
  ---
327
441
 
328
- ## 🤝 Contributing
329
-
330
- PRs welcome — but:
331
- - No bloat
332
- - No magic
333
- - No runtime cost
442
+ ## Contributing
334
443
 
335
- If it slows startup or increases bundle size, it's getting rejected.
444
+ PRs are welcome. The goal of this library is to stay minimal and runtime-cost-free. Contributions that add unnecessary complexity, increase bundle size, or introduce startup overhead will not be accepted.
336
445
 
337
446
  ---
338
447
 
339
- ## 📄 License
448
+ ## License
340
449
 
341
450
  MIT © Bini.js Team
342
-
343
- ---
344
-
345
- **Ship fast. Leak nothing. Blame config, not tooling.**
package/dist/index.d.mts CHANGED
@@ -1,32 +1,34 @@
1
+ import { Context } from 'hono';
1
2
  import { Plugin } from 'vite';
2
3
  export { Plugin } from 'vite';
3
- export { Context } from 'hono';
4
4
 
5
- /** Minimal shape accepted by getEnv / requireEnv. Satisfied by any Hono Context. */
6
- type HonoContext = {
7
- env: unknown;
8
- } & Record<string, unknown>;
5
+ /**
6
+ * bini-env/src/index.ts
7
+ *
8
+ * Two responsibilities only:
9
+ * 1. Tell Vite which env prefixes to expose to import.meta.env (biniEnv plugin)
10
+ * 2. Read env vars from the Hono request context (getEnv / requireEnv)
11
+ *
12
+ * process.env is never used. All runtime env access goes through Hono's env() adapter,
13
+ * which handles Node, Bun, Cloudflare Workers, Deno, and Vercel Edge natively.
14
+ */
15
+
16
+ /**
17
+ * Accepts any Hono Context. Typed against Hono's own Context rather than a
18
+ * loose duck type so misuse is caught at the call site.
19
+ */
20
+ type HonoContext = Context;
9
21
  interface BiniEnvPluginOptions {
10
- readonly enabled?: boolean;
11
- readonly clearViteHeader?: boolean;
12
- readonly logo?: string;
22
+ /** Extra prefixes to expose to import.meta.env, in addition to BINI_ and VITE_. */
13
23
  readonly envPrefix?: string | string[];
14
- readonly loadInPreview?: boolean;
15
- }
16
- interface DetectedEnvFile {
17
- readonly name: string;
18
- readonly path: string;
19
24
  }
20
25
  declare const biniLogger: {
21
26
  readonly info: (msg: string) => void;
22
27
  readonly warn: (msg: string) => void;
23
28
  readonly error: (msg: string, err?: unknown) => void;
24
29
  };
25
- declare function setContext(c: HonoContext): void;
26
- declare function clearContext(): void;
27
- declare function getEnv(c: HonoContext | string, key?: string): string | undefined;
28
- declare function requireEnv(c: HonoContext | string, key?: string): string;
29
- declare function detectEnvFiles(projectRoot?: string): DetectedEnvFile[];
30
+ declare function getEnv(c: HonoContext, key: string): string | undefined;
31
+ declare function requireEnv(c: HonoContext, key: string): string;
30
32
  declare function biniEnv(options?: Readonly<BiniEnvPluginOptions>): Plugin;
31
33
 
32
- export { type BiniEnvPluginOptions, type DetectedEnvFile, type HonoContext, biniEnv, biniLogger, clearContext, detectEnvFiles, getEnv, requireEnv, setContext };
34
+ export { type BiniEnvPluginOptions, type HonoContext, biniEnv, biniLogger, getEnv, requireEnv };