envapt 4.1.1 → 5.0.0
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/CHANGELOG.md +122 -0
- package/README.md +58 -946
- package/dist/Envapter-CBSM3v-5.cjs +3 -0
- package/dist/Envapter-CBSM3v-5.cjs.map +1 -0
- package/dist/Envapter-D8FEdzBR.mjs +3 -0
- package/dist/Envapter-D8FEdzBR.mjs.map +1 -0
- package/dist/config.cjs +2 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +1 -0
- package/dist/config.d.mts +1 -0
- package/dist/config.mjs +2 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.cjs +1 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +847 -401
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1127 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -909
- package/dist/index.mjs.map +1 -1
- package/package.json +50 -16
- package/dist/index.d.ts +0 -681
package/README.md
CHANGED
|
@@ -1,987 +1,99 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
<table border="0" cellspacing="0" cellpadding="0">
|
|
2
|
+
<tr>
|
|
3
|
+
<td width="160" valign="middle" align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/materwelonDhruv/envapt/main/.github/assets/logo.png" width="120" alt="envapt logo" />
|
|
5
|
+
</td>
|
|
6
|
+
<td valign="middle">
|
|
7
|
+
<h1>envapt</h1>
|
|
8
|
+
<p>
|
|
9
|
+
<strong>The apt way to handle environment variables.</strong><br/>
|
|
10
|
+
Read them as typed values, with zero runtime dependencies and the same API on Node, Bun, and Deno.
|
|
11
|
+
</p>
|
|
11
12
|
<a href="https://www.npmjs.com/package/envapt"><img alt="npm" src="https://img.shields.io/npm/v/envapt?logo=npm&logoColor=cb3838&label=%20&labelColor=103544&color=cb3838"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/envapt"><img alt="downloads" src="https://img.shields.io/npm/dm/envapt?style=flat&color=f7f6e8&labelColor=103544&label=downloads"></a>
|
|
12
14
|
<a href="https://jsr.io/@materwelon/envapt"><img alt="jsr" src="https://jsr.io/badges/@materwelon/envapt"></a>
|
|
13
|
-
<img alt="CI" src="https://img.shields.io/github/actions/workflow/status/materwelonDhruv/envapt/checks.yml?branch=main&label=tests&style=flat&logo=github&color=3fb950&labelColor=103544"
|
|
14
|
-
<a href="https://codecov.io/github/materwelonDhruv/envapt"><img alt="codecov" src="https://img.shields.io/codecov/c/github/materwelonDhruv/envapt/main?token=IQ4GC645LO&logo=codecov&color=f01f7a&labelColor=103544"/></a>
|
|
15
|
-
<br>
|
|
15
|
+
<img alt="CI" src="https://img.shields.io/github/actions/workflow/status/materwelonDhruv/envapt/checks.yml?branch=main&label=tests&style=flat&logo=github&color=3fb950&labelColor=103544">
|
|
16
16
|
<a href="LICENSE"><img alt="License" src="https://img.shields.io/npm/l/envapt?style=flat&color=e97826&logo=apache&label="></a>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<a href="https://nodejs.org/"><img alt="Node" src="https://img.shields.io/badge/node-%3E=22.0.0-339933?style=flat&logo=node.js&logoColor=white"></a>
|
|
21
|
-
<a href="https://bundlephobia.com/package/envapt"><img alt="Bundle Size" src="https://img.shields.io/bundlephobia/minzip/envapt?style=flat&color=212121"></a>
|
|
22
|
-
<a href="https://www.npmjs.com/package/envapt"><img alt="Downloads (total)" src="https://img.shields.io/npm/dt/envapt?style=flat&color=f7f6e8"></a>
|
|
23
|
-
<a href="https://github.com/materwelonDhruv/envapt/stargazers"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/materwelondhruv/envapt?style=flat&color=e3b341"></a>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## ✨ Features
|
|
29
|
-
|
|
30
|
-
- 🔧 **Automatic Type Detection** - Runtime types inferred from fallback values
|
|
31
|
-
- 🔗 **Template Variables** - `${VAR}` syntax with circular reference protection
|
|
32
|
-
- 🎯 **Class Properties** - Functional and Decorator-based configuration for class members _(Decorators: TypeScript only)_
|
|
33
|
-
- 🏷️ **Built-in & Custom Converters** - Ready-to-use converters for common patterns + custom transformations
|
|
34
|
-
- 🔖 **Tagged Template Resolver** - Tagged template literals with environment variable resolution
|
|
35
|
-
- 🌍 **Environment Detection** - Built-in development/staging/production handling
|
|
36
|
-
- 💪 **Edge Case Handling** - Robust validation and parsing for all scenarios
|
|
37
|
-
- 🔑 **Multi-Key Lookups** - Provide ordered lists of env keys and Envapt will use the first value it finds
|
|
38
|
-
- 🛡️ **Type Safety** - Full TypeScript support with proper type inference _(TypeScript optional)_
|
|
39
|
-
- 📂 **Multiple .env Files** - Load from multiple sources
|
|
40
|
-
- ⚡ **Lightweight** - Minimal overhead with [`dotenv`](https://www.npmjs.com/package/dotenv) bundled
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
</table>
|
|
41
20
|
|
|
42
21
|
---
|
|
43
22
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### ⚙️ Essentials
|
|
47
|
-
|
|
48
|
-
- [Requirements](#requirements)
|
|
49
|
-
- [Quick Start](#quick-start)
|
|
50
|
-
- [Installation](#installation)
|
|
51
|
-
- [Basic Usage](#basic-usage)
|
|
52
|
-
|
|
53
|
-
### 🧬 API Reference
|
|
54
|
-
|
|
55
|
-
- [API Reference](#api-reference)
|
|
56
|
-
- [Decorator API](#decorator-api)
|
|
57
|
-
- [Modern Syntax (Recommended)](#modern-syntax-recommended)
|
|
58
|
-
- [Classic Syntax](#classic-syntax)
|
|
59
|
-
- [Automatic Runtime Type Detection](#automatic-runtime-type-detection)
|
|
60
|
-
- [Primitive Converters](#primitive-converters)
|
|
61
|
-
- [Built-in Converters](#built-in-converters)
|
|
62
|
-
- [Custom Array Converters](#custom-array-converters)
|
|
63
|
-
- [Custom Converters](#custom-converters)
|
|
64
|
-
- [Handling Missing Values](#handling-missing-values)
|
|
65
|
-
- [Functional API](#functional-api)
|
|
66
|
-
- [Tagged Template Resolver](#tagged-template-resolver)
|
|
67
|
-
- [Converter Type Quick Reference](#converter-type-quick-reference)
|
|
68
|
-
|
|
69
|
-
### 🌍 Environment & Templates
|
|
70
|
-
|
|
71
|
-
- [Environment Detection](#environment-detection)
|
|
72
|
-
- [Environment Management](#environment-management)
|
|
73
|
-
- [Template Variables](#template-variables)
|
|
74
|
-
- [Circular Reference Protection](#circular-reference-protection)
|
|
75
|
-
|
|
76
|
-
### 🛠 Configuration & Errors
|
|
77
|
-
|
|
78
|
-
- [Configuration](#configuration)
|
|
79
|
-
- [Multiple .env Files](#multiple-env-files)
|
|
80
|
-
- [Dotenv Configuration](#dotenv-configuration)
|
|
81
|
-
- [Error Handling](#error-handling)
|
|
82
|
-
- [Error Code Reference](#error-code-reference)
|
|
83
|
-
|
|
84
|
-
### 🚀 Examples
|
|
85
|
-
|
|
86
|
-
- [Advanced Examples](#advanced-examples)
|
|
87
|
-
- [JavaScript](#javascript)
|
|
88
|
-
- [TypeScript](#typescript)
|
|
89
|
-
|
|
90
|
-
<div align="right">
|
|
91
|
-
|
|
92
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
93
|
-
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## Requirements
|
|
99
|
-
|
|
100
|
-
### TypeScript Users Only
|
|
101
|
-
|
|
102
|
-
- **TypeScript**: `>=5.8` _(Only required for decorator API)_
|
|
103
|
-
|
|
104
|
-
```jsonc
|
|
105
|
-
// tsconfig.json (required settings for decorators)
|
|
106
|
-
{
|
|
107
|
-
"experimentalDecorators": true,
|
|
108
|
-
"module": "esnext", // or "nodenext"
|
|
109
|
-
"moduleResolution": "bundler", // or "nodenext"
|
|
110
|
-
"target": "ESNext",
|
|
111
|
-
"lib": ["ESNext"]
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
> [!NOTE]
|
|
116
|
-
> **JavaScript users** can use all features except the `@Envapt` decorator API. The [Functional API](#functional-api), [Tagged Template Resolver](#tagged-template-resolver), and all converters work perfectly in plain JavaScript.
|
|
117
|
-
|
|
118
|
-
<div align="right">
|
|
119
|
-
|
|
120
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
121
|
-
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
## Quick Start
|
|
125
|
-
|
|
126
|
-
### Installation
|
|
127
|
-
|
|
128
|
-
| Package Manager | Command |
|
|
129
|
-
| --------------- | --------------------------------- |
|
|
130
|
-
| **pnpm** | `pnpm add envapt` |
|
|
131
|
-
| **yarn** | `yarn add envapt` |
|
|
132
|
-
| **npm** | `npm install envapt` |
|
|
133
|
-
| **deno** (jsr) | `deno add jsr:@materwelon/envapt` |
|
|
134
|
-
| **deno** (npm) | `deno add npm:envapt` |
|
|
135
|
-
| **bun** | `bun add envapt` |
|
|
136
|
-
|
|
137
|
-
### Basic Usage
|
|
138
|
-
|
|
139
|
-
**Step 1:** Create a `.env` file:
|
|
140
|
-
|
|
141
|
-
```env
|
|
142
|
-
APP_PORT=8443
|
|
143
|
-
APP_URL=http://localhost:${APP_PORT}
|
|
144
|
-
DATABASE_URL=postgres://localhost:5432/mydb
|
|
145
|
-
IS_PRODUCTION=false
|
|
146
|
-
MAX_CONNECTIONS=100
|
|
147
|
-
ALLOWED_ORIGINS=https://app.com,https://admin.com
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**JavaScript Example (Functional API):**
|
|
151
|
-
|
|
152
|
-
```js
|
|
153
|
-
import { Envapter, Converters } from 'envapt';
|
|
154
|
-
|
|
155
|
-
// Basic usage
|
|
156
|
-
const port = Envapter.getNumber('APP_PORT', 3000);
|
|
157
|
-
const url = Envapter.get('APP_URL', 'http://localhost:3000');
|
|
158
|
-
const isProduction = Envapter.isProduction;
|
|
159
|
-
|
|
160
|
-
console.log(`Server running on port ${port}`); // 8443
|
|
161
|
-
console.log(`URL: ${url}`); // "http://localhost:8443"
|
|
162
|
-
|
|
163
|
-
// Advanced converters
|
|
164
|
-
const corsOrigins = Envapter.getUsing('ALLOWED_ORIGINS', Converters.Array, []);
|
|
165
|
-
const dbConfig = Envapter.getUsing('DATABASE_CONFIG', Converters.Json, {});
|
|
166
|
-
|
|
167
|
-
// Multi-key lookup: try primary, then replica, then fall back
|
|
168
|
-
const dbUrl = Envapter.get(['PRIMARY_DB_URL', 'REPLICA_DB_URL'], 'sqlite://memory');
|
|
169
|
-
const httpPort = Envapter.getNumber(['APP_PORT', 'PORT'], 3000);
|
|
170
|
-
|
|
171
|
-
// Tagged template literals
|
|
172
|
-
const message = Envapter.resolve`Server ${'APP_URL'} is ready!`;
|
|
173
|
-
console.log(message); // "Server http://localhost:8443 is ready!"
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**TypeScript Example (Decorator API):**
|
|
23
|
+
`process.env` always hands you a `string | undefined`. envapt returns the type you asked for, with a
|
|
24
|
+
fallback that removes `undefined` from the return type.
|
|
177
25
|
|
|
178
26
|
```ts
|
|
179
|
-
import {
|
|
180
|
-
|
|
181
|
-
// Global app configuration (static properties)
|
|
182
|
-
class AppConfig extends Envapter {
|
|
183
|
-
@Envapt('APP_PORT', 3000)
|
|
184
|
-
static readonly port: number;
|
|
185
|
-
|
|
186
|
-
// The Classic Syntax only works for Primitive Converters. Converters.Url is a Built-in Converter.
|
|
187
|
-
@Envapt('APP_URL', { fallback: new URL('http://localhost:3000'), converter: Converters.Url })
|
|
188
|
-
static readonly url: URL;
|
|
189
|
-
|
|
190
|
-
// Prefer CLOUD_REDIS_URL but fall back to classic REDIS_URL when missing
|
|
191
|
-
@Envapt(['CLOUD_REDIS_URL', 'REDIS_URL'], 'redis://localhost:6379')
|
|
192
|
-
static readonly redisUrl: string;
|
|
193
|
-
|
|
194
|
-
@Envapt('ALLOWED_ORIGINS', {
|
|
195
|
-
fallback: ['http://localhost:3000'],
|
|
196
|
-
converter: Converters.Array
|
|
197
|
-
})
|
|
198
|
-
static readonly allowedOrigins: string[];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Service configuration (instance properties)
|
|
202
|
-
class DatabaseService {
|
|
203
|
-
@Envapt('DATABASE_URL', 'sqlite://memory')
|
|
204
|
-
declare readonly databaseUrl: string;
|
|
205
|
-
|
|
206
|
-
// Will detect that '10' is a number and set the runtime type accordingly
|
|
207
|
-
@Envapt('MAX_CONNECTIONS', 10)
|
|
208
|
-
declare readonly maxConnections: number;
|
|
209
|
-
|
|
210
|
-
@Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
|
|
211
|
-
declare readonly timeout: number; // Converts "5s" to 5000ms
|
|
212
|
-
|
|
213
|
-
async connect() {
|
|
214
|
-
console.log(`Connecting to ${this.databaseUrl}`);
|
|
215
|
-
// Connection logic here
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Usage
|
|
220
|
-
console.log(AppConfig.port); // 8443 (number)
|
|
221
|
-
console.log(AppConfig.url.href); // "http://localhost:8443"
|
|
222
|
-
|
|
223
|
-
const dbService = new DatabaseService();
|
|
224
|
-
await dbService.connect();
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
<div align="right">
|
|
228
|
-
|
|
229
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
230
|
-
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
## API Reference
|
|
234
|
-
|
|
235
|
-
### Decorator API
|
|
236
|
-
|
|
237
|
-
> [!IMPORTANT]
|
|
238
|
-
> **TypeScript Only**: The `@Envapt` decorator API requires TypeScript with `experimentalDecorators: true`. JavaScript users should use the [Functional API](#functional-api) instead.
|
|
239
|
-
|
|
240
|
-
The `@Envapt` decorator can be used on both **static** and **instance** class properties:
|
|
241
|
-
|
|
242
|
-
- **Static properties**: Can use for global configuration that's shared across your entire application (e.g., app port, global features, environment settings)
|
|
243
|
-
- **Instance properties**: Can use for service-specific configuration that may vary per service or when you want the configuration tied to a specific class instance (e.g., database connections, service endpoints, per-service settings)
|
|
244
|
-
|
|
245
|
-
**Important**: Instance properties must be declared with `declare` keyword or `!` assertion since they're populated by the decorator rather than set in a constructor.
|
|
246
|
-
|
|
247
|
-
#### Modern Syntax (Recommended)
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
@Envapt('ENV_VAR', { fallback?: T, converter?: EnvConverter<T> })
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
> [!NOTE]
|
|
254
|
-
> The first argument can be a string **or** an ordered array of strings:
|
|
255
|
-
> `@Envapt(['PRIMARY_URL', 'SECONDARY_URL'], { fallback: 'https://example.com' })`.
|
|
256
|
-
> Envapt will resolve the first key that exists.
|
|
257
|
-
>
|
|
258
|
-
> [!TIP]
|
|
259
|
-
> **Generic Typing for Better IntelliSense**
|
|
260
|
-
>
|
|
261
|
-
> You can specify explicit types using generics for better type safety and IntelliSense:
|
|
262
|
-
>
|
|
263
|
-
> ```ts
|
|
264
|
-
> // Explicit typing provides better IntelliSense for complex types
|
|
265
|
-
> @Envapt<DatabaseConfig>('DB_CONFIG', {
|
|
266
|
-
> fallback: { host: 'localhost', port: 5432, ssl: false },
|
|
267
|
-
> converter: Converters.Json
|
|
268
|
-
> })
|
|
269
|
-
> static readonly dbConfig: DatabaseConfig;
|
|
270
|
-
> ```
|
|
271
|
-
|
|
272
|
-
#### Classic Syntax
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
@Envapt('ENV_VAR', fallback?, converter?)
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Need to chain multiple keys with the classic API? Pass an array instead of a string:
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
@Envapt(['HOST_PRIMARY', 'HOST_SECONDARY'], 'localhost')
|
|
282
|
-
static readonly host: string;
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
#### Automatic Runtime Type Detection
|
|
286
|
-
|
|
287
|
-
Types are automatically inferred from fallback values.
|
|
288
|
-
|
|
289
|
-
```ts
|
|
290
|
-
class Config extends Envapter {
|
|
291
|
-
// Static properties for global settings
|
|
292
|
-
@Envapt('APP_NAME', 'MyApp') // string
|
|
293
|
-
static readonly appName: string;
|
|
294
|
-
|
|
295
|
-
@Envapt('APP_PORT', 3000) // number
|
|
296
|
-
static readonly port: number;
|
|
297
|
-
|
|
298
|
-
@Envapt('DEBUG_MODE', false) // boolean
|
|
299
|
-
static readonly debugMode: boolean;
|
|
300
|
-
|
|
301
|
-
// Instance properties for service-specific settings
|
|
302
|
-
@Envapt('SMTP_HOST', 'localhost') // string
|
|
303
|
-
declare readonly smtpHost: string;
|
|
304
|
-
|
|
305
|
-
@Envapt('SMTP_PORT', 587) // number
|
|
306
|
-
declare readonly smtpPort: number;
|
|
307
|
-
|
|
308
|
-
@Envapt('SMTP_SECURE', true) // boolean
|
|
309
|
-
declare readonly smtpSecure: boolean;
|
|
310
|
-
|
|
311
|
-
sendEmail(to: string, subject: string) {
|
|
312
|
-
console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
#### Primitive Converters
|
|
318
|
-
|
|
319
|
-
Envapt allows using the 5 "primitive" type-like converters. These **will** coerce values.
|
|
320
|
-
|
|
321
|
-
> [!NOTE]
|
|
322
|
-
> The runtime validator will ignore this usage, allowing type coercion for flexibility.
|
|
323
|
-
|
|
324
|
-
**Valid Primitive Types:** `String`, `Number`, `Boolean`, `Symbol`, and `BigInt`.
|
|
325
|
-
|
|
326
|
-
```ts
|
|
327
|
-
class Config extends Envapter {
|
|
328
|
-
@Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
|
|
329
|
-
static readonly portAsString: string;
|
|
330
|
-
|
|
331
|
-
@Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
|
|
332
|
-
static readonly debugMode: boolean;
|
|
333
|
-
|
|
334
|
-
@Envapt('USER_ID', { fallback: 12345, converter: Number })
|
|
335
|
-
static readonly userId: number;
|
|
336
|
-
|
|
337
|
-
@Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
|
|
338
|
-
static readonly maxSafeInt: bigint;
|
|
339
|
-
|
|
340
|
-
@Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
|
|
341
|
-
static readonly appInstance: symbol;
|
|
342
|
-
|
|
343
|
-
// Instance properties work the same way
|
|
344
|
-
@Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
|
|
345
|
-
declare readonly timeout: number;
|
|
27
|
+
import { Envapter } from 'envapt';
|
|
346
28
|
|
|
347
|
-
|
|
348
|
-
@Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
|
|
349
|
-
declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
|
|
350
|
-
}
|
|
29
|
+
const port = Envapter.getNumber('PORT', 3000); // number, not string | undefined
|
|
351
30
|
```
|
|
352
31
|
|
|
353
|
-
**
|
|
354
|
-
|
|
355
|
-
- When you need explicit type coercion between incompatible types
|
|
356
|
-
- When working with external systems that provide values in unexpected formats
|
|
357
|
-
|
|
358
|
-
#### Built-in Converters
|
|
359
|
-
|
|
360
|
-
Envapt provides many built-in converters for common patterns:
|
|
361
|
-
|
|
362
|
-
> [!IMPORTANT]
|
|
363
|
-
> **Use the `Converters` enum** instead of string literals. They look better, and provide better type inference:
|
|
364
|
-
>
|
|
365
|
-
> ```ts
|
|
366
|
-
> import { Converters } from 'envapt';
|
|
367
|
-
> // ✅ Recommended: Use enum
|
|
368
|
-
> @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
|
|
369
|
-
>
|
|
370
|
-
> // ❌ Discouraged: String literals (still supported for compatibility)
|
|
371
|
-
> @Envapt('PORT', { converter: 'number', fallback: 3000 })
|
|
372
|
-
> ```
|
|
373
|
-
>
|
|
374
|
-
> Built-in converters enforce **strict type validation** between the converter and fallback types. The converter's expected return type must match the fallback's type.
|
|
375
|
-
|
|
376
|
-
```ts
|
|
377
|
-
class Config extends Envapter {
|
|
378
|
-
// Basic types
|
|
379
|
-
@Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
|
|
380
|
-
static readonly appName: string;
|
|
381
|
-
|
|
382
|
-
@Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
|
|
383
|
-
static readonly port: number;
|
|
384
|
-
|
|
385
|
-
@Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
|
|
386
|
-
static readonly productionMode: boolean;
|
|
387
|
-
|
|
388
|
-
// Advanced types
|
|
389
|
-
@Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
|
|
390
|
-
static readonly corsOrigins: string[];
|
|
32
|
+
**[Read the docs →](https://envapt.materwelon.dev)**
|
|
391
33
|
|
|
392
|
-
|
|
393
|
-
static readonly config: object;
|
|
34
|
+
## What you get
|
|
394
35
|
|
|
395
|
-
|
|
396
|
-
|
|
36
|
+
- **Typed values.** A fallback removes `undefined` from the return type. Built-in converters cover
|
|
37
|
+
numbers, booleans, bigint, JSON, URLs, regular expressions, dates, durations, and arrays, or pass
|
|
38
|
+
your own function or a Standard Schema validator (zod, valibot, arktype).
|
|
39
|
+
- **Zero runtime dependencies.** envapt ships its own `.env` parser, so nothing is added to your
|
|
40
|
+
dependency tree.
|
|
41
|
+
- **The same API on Node, Bun, and Deno.** Node `>=20`, Bun `>=1.3`, Deno `>=2.5`; ESM and CJS.
|
|
42
|
+
- **`.env` loading built in.** A per-environment file cascade, `${VAR}` templates, and strict /
|
|
43
|
+
required checks.
|
|
397
44
|
|
|
398
|
-
|
|
399
|
-
static readonly timeout: number; // Converts "30s" to 30000ms
|
|
45
|
+
## Install
|
|
400
46
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
47
|
+
```sh
|
|
48
|
+
npm install envapt
|
|
49
|
+
pnpm add envapt
|
|
50
|
+
yarn add envapt
|
|
51
|
+
bun add envapt
|
|
52
|
+
deno add jsr:@materwelon/envapt
|
|
405
53
|
```
|
|
406
54
|
|
|
407
|
-
|
|
408
|
-
> These will throw runtime errors due to type mismatches:
|
|
409
|
-
>
|
|
410
|
-
> ```ts
|
|
411
|
-
> // ❌ String converter with number fallback
|
|
412
|
-
> @Envapt('VAR', { converter: Converters.String, fallback: 42 })
|
|
413
|
-
>
|
|
414
|
-
> // ❌ URL converter with string fallback
|
|
415
|
-
> @Envapt('VAR', { converter: Converters.Url, fallback: 'http://example.com' })
|
|
416
|
-
>
|
|
417
|
-
> // ✅ Use primitive constructors for type coercion instead
|
|
418
|
-
> @Envapt('VAR', { converter: String, fallback: 42 })
|
|
419
|
-
> ```
|
|
420
|
-
|
|
421
|
-
**Available Built-in Converters:**
|
|
55
|
+
## Quick start
|
|
422
56
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
| `Converters.String` | `'string'` | String values |
|
|
426
|
-
| `Converters.Number` | `'number'` | Numeric values (integers and floats) |
|
|
427
|
-
| `Converters.Integer` | `'integer'` | Integer values only |
|
|
428
|
-
| `Converters.Float` | `'float'` | Float values only |
|
|
429
|
-
| `Converters.Boolean` | `'boolean'` | Boolean values (`true`/`false`, `yes`/`no`, `on`/`off`, `1`/`0`) |
|
|
430
|
-
| `Converters.Bigint` | `'bigint'` | BigInt values for large integers |
|
|
431
|
-
| `Converters.Symbol` | `'symbol'` | Symbol values (creates symbols from string descriptions) |
|
|
432
|
-
| `Converters.Json` | `'json'` | JSON objects/arrays (safe parsing with fallback) |
|
|
433
|
-
| `Converters.Array` | `'array'` | Comma-separated string arrays |
|
|
434
|
-
| `Converters.Url` | `'url'` | URL objects |
|
|
435
|
-
| `Converters.Regexp` | `'regexp'` | Regular expressions (supports `/pattern/flags` syntax) |
|
|
436
|
-
| `Converters.Date` | `'date'` | Date objects (supports ISO strings and timestamps) |
|
|
437
|
-
| `Converters.Time` | `'time'` | Time values (e.g. `"5s"`, `"30m"`, `"2h"` converted to milliseconds) |
|
|
57
|
+
Read values functionally with `Envapter`, or bind them to class fields with the `@Envapt` decorator.
|
|
58
|
+
Both share the same parsing, converters, and cache.
|
|
438
59
|
|
|
439
|
-
|
|
60
|
+
### Functional
|
|
440
61
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
> [!IMPORTANT]
|
|
444
|
-
> Array converters validate that:
|
|
445
|
-
>
|
|
446
|
-
> 1. **Fallback must be an array** (if provided)
|
|
447
|
-
> 2. **All fallback elements have consistent types** (no mixed types like `['string', 42, true]`)
|
|
448
|
-
> 3. **Array converter `type` matches fallback element types** (if `type` is specified)
|
|
62
|
+
Read a value anywhere, in JavaScript or TypeScript. No build step.
|
|
449
63
|
|
|
450
64
|
```ts
|
|
451
|
-
class Config extends Envapter {
|
|
452
|
-
// Basic array (comma-separated strings)
|
|
453
|
-
@Envapt('TAGS', { converter: Converters.Array, fallback: [] })
|
|
454
|
-
static readonly tags: string[];
|
|
455
|
-
|
|
456
|
-
// Custom delimiter
|
|
457
|
-
@Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
|
|
458
|
-
declare readonly allowedMethods: string[];
|
|
459
|
-
|
|
460
|
-
// Custom delimiter with type conversion
|
|
461
|
-
@Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
|
|
462
|
-
declare readonly rateLimits: number[];
|
|
463
|
-
|
|
464
|
-
@Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
|
|
465
|
-
declare readonly featureFlags: boolean[];
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
> [!WARNING]
|
|
470
|
-
> These will throw runtime validation errors:
|
|
471
|
-
>
|
|
472
|
-
> ```ts
|
|
473
|
-
> // ❌ Mixed types in fallback array
|
|
474
|
-
> @Envapt('MIXED', { converter: Converters.Array, fallback: ['string', 42, true] })
|
|
475
|
-
>
|
|
476
|
-
> // ❌ Array converter type doesn't match fallback elements
|
|
477
|
-
> @Envapt('NUMS', { converter: { delimiter: ',', type: Converters.Number }, fallback: ['not', 'numbers'] })
|
|
478
|
-
>
|
|
479
|
-
> // ❌ Non-array fallback with array converter
|
|
480
|
-
> @Envapt('INVALID', { converter: Converters.Array, fallback: 'not-an-array' })
|
|
481
|
-
> ```
|
|
482
|
-
|
|
483
|
-
**ArrayConverter Interface:**
|
|
484
|
-
|
|
485
|
-
- `delimiter: string` - The string used to split array elements
|
|
486
|
-
- `type?: BuiltInConverter` - Optional type to convert each element to (excludes `Converters.Array`, `Converters.Json`, and `Converters.Regexp`)
|
|
487
|
-
|
|
488
|
-
#### Custom Converters
|
|
489
|
-
|
|
490
|
-
Transform environment values to any type:
|
|
491
|
-
|
|
492
|
-
```ts
|
|
493
|
-
class Config extends Envapter {
|
|
494
|
-
@Envapt('TAGS', {
|
|
495
|
-
fallback: new Set(['default']),
|
|
496
|
-
converter: (raw, fallback) => {
|
|
497
|
-
if (!raw) return fallback;
|
|
498
|
-
return new Set(raw.split(',').map((s) => s.trim()));
|
|
499
|
-
}
|
|
500
|
-
})
|
|
501
|
-
static readonly tags: Set<string>;
|
|
502
|
-
|
|
503
|
-
@Envapt('NOTIFICATION_CHANNELS', {
|
|
504
|
-
fallback: new Map([['email', 'enabled']]),
|
|
505
|
-
converter: (raw, fallback) => {
|
|
506
|
-
if (!raw) return fallback;
|
|
507
|
-
const map = new Map();
|
|
508
|
-
raw.split(',').forEach((pair) => {
|
|
509
|
-
const [key, value] = pair.split(':');
|
|
510
|
-
map.set(key?.trim(), value?.trim() || 'enabled');
|
|
511
|
-
});
|
|
512
|
-
return map;
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
declare readonly channels: Map<string, string>;
|
|
516
|
-
}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
> [!TIP]
|
|
520
|
-
> **Custom Validation with Error Throwing**
|
|
521
|
-
>
|
|
522
|
-
> Custom converters can throw errors for validation. The custom converter is called even when a variable is not found in the env file(s):
|
|
523
|
-
>
|
|
524
|
-
> ```ts
|
|
525
|
-
> @Envapt<string>('API_KEY', {
|
|
526
|
-
> converter(raw, _fallback) {
|
|
527
|
-
> if (typeof raw !== 'string' || raw === '') {
|
|
528
|
-
> throw new Error('API_KEY is required and cannot be empty');
|
|
529
|
-
> }
|
|
530
|
-
> return raw;
|
|
531
|
-
> }
|
|
532
|
-
> })
|
|
533
|
-
> static readonly apiKey: string;
|
|
534
|
-
> ```
|
|
535
|
-
>
|
|
536
|
-
> _No `fallback` needed here because the converter throws an error if a value is not what we want it to be_
|
|
537
|
-
|
|
538
|
-
#### Handling Missing Values
|
|
539
|
-
|
|
540
|
-
Control what happens when environment variables don't exist:
|
|
541
|
-
|
|
542
|
-
```ts
|
|
543
|
-
class Config extends Envapter {
|
|
544
|
-
// Returns undefined if not found
|
|
545
|
-
@Envapt('OPTIONAL_FEATURE', { fallback: undefined })
|
|
546
|
-
static readonly optionalFeature: string | undefined;
|
|
547
|
-
|
|
548
|
-
// Returns null if not found (no fallback provided)
|
|
549
|
-
@Envapt('MISSING_CONFIG', { converter: Converters.String })
|
|
550
|
-
static readonly missingConfig: string | null;
|
|
551
|
-
|
|
552
|
-
// Uses fallback if not found
|
|
553
|
-
@Envapt('DEFAULT_THEME', { fallback: 'light' })
|
|
554
|
-
static readonly defaultTheme: string;
|
|
555
|
-
|
|
556
|
-
// Instance properties work the same way
|
|
557
|
-
@Envapt('LOG_FILE_PATH', { fallback: undefined })
|
|
558
|
-
declare readonly logFilePath: string | undefined;
|
|
559
|
-
}
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
### Functional API
|
|
563
|
-
|
|
564
|
-
For functional-style environment variable access on primitive types:
|
|
565
|
-
|
|
566
|
-
```js
|
|
567
|
-
import { Envapter, Converters } from 'envapt';
|
|
568
|
-
|
|
569
|
-
// Basic type-specific getters
|
|
570
|
-
const str = Envapter.get('STRING_VAR', 'default');
|
|
571
|
-
const num = Envapter.getNumber('NUMBER_VAR', 42);
|
|
572
|
-
const bool = Envapter.getBoolean('BOOLEAN_VAR', false);
|
|
573
|
-
const bigint = Envapter.getBigInt('BIGINT_VAR', 100n);
|
|
574
|
-
const symbol = Envapter.getSymbol('SYMBOL_VAR', Symbol('default'));
|
|
575
|
-
|
|
576
|
-
// Advanced converter methods
|
|
577
|
-
const jsonData = Envapter.getUsing('CONFIG_JSON', Converters.Json);
|
|
578
|
-
const urlArray = Envapter.getUsing('API_URLS', { delimiter: ',', type: Converters.Url });
|
|
579
|
-
const customData = Envapter.getWith('RAW_DATA', (raw) => raw?.split('|').map((s) => s.trim()));
|
|
580
|
-
|
|
581
|
-
// Multi-key inputs work everywhere: Envapt will read left-to-right
|
|
582
|
-
const secretsHost = Envapter.get(['SECRETS_HOST', 'DEFAULT_HOST'], 'localhost');
|
|
583
|
-
|
|
584
|
-
// Instance methods (same API available)
|
|
585
|
-
const envapter = new Envapter();
|
|
586
|
-
const value = envapter.get('VAR', 'default');
|
|
587
|
-
const processed = envapter.getUsing('DATA', Converters.Array);
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
For functional-style environment variable access with converters:
|
|
591
|
-
|
|
592
|
-
```js
|
|
593
65
|
import { Envapter, Converters } from 'envapt';
|
|
594
66
|
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
const urls = Envapter.getUsing('SERVICE_URLS', { delimiter: '|', type: Converters.Url });
|
|
598
|
-
const pgUrl = Envapter.getUsing(['PRIMARY_PG_URL', 'SECONDARY_PG_URL'], Converters.Url);
|
|
599
|
-
|
|
600
|
-
// TypeScript: Use type override for better type inference
|
|
601
|
-
const typedConfig = Envapter.getUsing<{ host: string; port: number; ssl: boolean }>('DATABASE_CONFIG', Converters.Json);
|
|
602
|
-
// typedConfig is now typed as { host: string; port: number; ssl: boolean } instead of JsonValue | undefined
|
|
603
|
-
|
|
604
|
-
// Use custom converter functions
|
|
605
|
-
const processedData = Envapter.getWith(
|
|
606
|
-
'RAW_DATA',
|
|
607
|
-
(raw, fallback) => {
|
|
608
|
-
if (!raw) return fallback ?? [];
|
|
609
|
-
return raw.split(',').map((item) => ({ name: item.trim(), enabled: true }));
|
|
610
|
-
},
|
|
611
|
-
[]
|
|
612
|
-
);
|
|
613
|
-
|
|
614
|
-
// Instance methods work the same way
|
|
615
|
-
const envapter = new Envapter();
|
|
616
|
-
const result = envapter.getUsing('DATABASE_CONFIG', Converters.Json);
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
> [!TIP]
|
|
620
|
-
> **Type Override with `getUsing`**
|
|
621
|
-
>
|
|
622
|
-
> You can explicitly specify the return type for `getUsing` when TypeScript's inference isn't specific enough (especially useful with `Converters.Json`):
|
|
623
|
-
>
|
|
624
|
-
> ```ts
|
|
625
|
-
> // Default behavior
|
|
626
|
-
> const config = Envapter.getUsing('CONFIG', Converters.Json); // type: JsonValue | undefined (undefined because no fallback)
|
|
627
|
-
>
|
|
628
|
-
> // Override with specific interface
|
|
629
|
-
> interface DatabaseConfig {
|
|
630
|
-
> host: string;
|
|
631
|
-
> port: number;
|
|
632
|
-
> ssl: boolean;
|
|
633
|
-
> }
|
|
634
|
-
> const dbConfig = Envapter.getUsing<DatabaseConfig>('DB_CONFIG', Converters.Json);
|
|
635
|
-
> // dbConfig is now properly typed as DatabaseConfig
|
|
636
|
-
> ```
|
|
637
|
-
>
|
|
638
|
-
> _Make sure the fallback value matches the expected type, if you use a fallback. Otherwise you'll see a TypeScript error._\
|
|
639
|
-
> _This does NOT validate the type at runtime. You'll need to handle that yourself._
|
|
640
|
-
|
|
641
|
-
### Converter Type Quick Reference
|
|
642
|
-
|
|
643
|
-
| **Use Case** | **Converter Type** | **Example** |
|
|
644
|
-
| ----------------------- | ------------------------- | --------------------------------------------------------- |
|
|
645
|
-
| **Type coercion** | Primitive constructors | `converter: String` |
|
|
646
|
-
| **Strict validation** | Built-in converters | `converter: Converters.String` |
|
|
647
|
-
| **Array parsing** | Built-in Array converters | `converter: { delimiter: ',', type?: Converters.String }` |
|
|
648
|
-
| **Complex transforms** | Custom function | `converter: (raw, fallback) => ...` |
|
|
649
|
-
| **Functional built-in** | `getUsing()` method | `Envapter.getUsing('VAR', Converters.Json)` |
|
|
650
|
-
| **Type override** | `getUsing<T>()` method | `Envapter.getUsing<MyType>('VAR', Converters.Json)` |
|
|
651
|
-
| **Functional custom** | `getWith()` method | `Envapter.getWith('VAR', (raw) => transform(raw))` |
|
|
652
|
-
|
|
653
|
-
> [!TIP]
|
|
654
|
-
> **Use the `Converters` enum**. They look better. Start with built-in converters, use primitive constructors when you need coercion, and custom converters for complex transforms.
|
|
655
|
-
|
|
656
|
-
### Tagged Template Resolver
|
|
657
|
-
|
|
658
|
-
Envapt provides a convenient tagged template literal syntax for resolving environment variables directly in template strings:
|
|
659
|
-
|
|
660
|
-
```js
|
|
661
|
-
import { Envapter } from 'envapt';
|
|
662
|
-
|
|
663
|
-
// Given these environment variables:
|
|
664
|
-
// API_HOST=api.example.com
|
|
665
|
-
// API_PORT=8080
|
|
666
|
-
// API_URL=https://${API_HOST}:${API_PORT}
|
|
667
|
-
// SERVICE_NAME=UserService
|
|
668
|
-
|
|
669
|
-
// Use tagged template literals for string interpolation
|
|
670
|
-
const endpoint = Envapter.resolve`Connecting to ${'SERVICE_NAME'} at ${'API_URL'}`;
|
|
671
|
-
// Returns: "Connecting to UserService at https://api.example.com:8080"
|
|
672
|
-
|
|
673
|
-
const logMessage = Envapter.resolve`Starting ${'SERVICE_NAME'} on port ${'API_PORT'}`;
|
|
674
|
-
// Returns: "Starting UserService on port 8080"
|
|
675
|
-
|
|
676
|
-
// Works with instance methods too
|
|
677
|
-
const envapter = new Envapter();
|
|
678
|
-
const status = envapter.resolve`${'SERVICE_NAME'} is running`;
|
|
679
|
-
// Returns: "UserService is running"
|
|
67
|
+
const port = Envapter.getNumber('PORT', 3000);
|
|
68
|
+
const origins = Envapter.getUsing('ALLOWED_ORIGINS', Converters.array(), []);
|
|
680
69
|
```
|
|
681
70
|
|
|
682
|
-
|
|
71
|
+
### Decorator
|
|
683
72
|
|
|
684
|
-
|
|
685
|
-
# Your .env file
|
|
686
|
-
API_HOST=api.example.com
|
|
687
|
-
API_PORT=8080
|
|
688
|
-
API_URL=https://${API_HOST}:${API_PORT} # Template resolved first
|
|
689
|
-
SERVICE_NAME=UserService
|
|
690
|
-
```
|
|
73
|
+
Bind a value to a class field. TypeScript, with `experimentalDecorators` in your `tsconfig.json`.
|
|
691
74
|
|
|
692
75
|
```ts
|
|
693
|
-
|
|
694
|
-
// Returns: "Service UserService endpoint: https://api.example.com:8080"
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
> [!NOTE]
|
|
698
|
-
> Tagged template literals work with any environment variables, including those that use `${VAR}` template syntax in your `.env` file. The template resolution happens first, then the tagged template interpolation.
|
|
699
|
-
|
|
700
|
-
<div align="right">
|
|
701
|
-
|
|
702
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
703
|
-
|
|
704
|
-
</div>
|
|
705
|
-
|
|
706
|
-
## Environment Detection
|
|
707
|
-
|
|
708
|
-
Envapt automatically detects your environment from these variables (in order):
|
|
709
|
-
|
|
710
|
-
1. `ENVIRONMENT`
|
|
711
|
-
2. `ENV`
|
|
712
|
-
3. `NODE_ENV`
|
|
713
|
-
|
|
714
|
-
Supported values: `development`, `staging`, `production` (case-sensitive)
|
|
715
|
-
|
|
716
|
-
### Environment Management
|
|
717
|
-
|
|
718
|
-
```js
|
|
719
|
-
import { Envapter, EnvaptEnvironment } from 'envapt';
|
|
720
|
-
|
|
721
|
-
// Check current environment
|
|
722
|
-
console.log(Envapter.environment); // Environment.Development (default)
|
|
723
|
-
console.log(Envapter.isProduction); // false
|
|
724
|
-
console.log(Envapter.isDevelopment); // true
|
|
725
|
-
console.log(Envapter.isStaging); // false
|
|
726
|
-
|
|
727
|
-
// Set environment
|
|
728
|
-
Envapter.environment = EnvaptEnvironment.Production;
|
|
729
|
-
Envapter.environment = 'staging'; // string also works
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
<div align="right">
|
|
733
|
-
|
|
734
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
735
|
-
|
|
736
|
-
</div>
|
|
737
|
-
|
|
738
|
-
## Configuration
|
|
739
|
-
|
|
740
|
-
### Multiple .env Files
|
|
741
|
-
|
|
742
|
-
```js
|
|
743
|
-
import { resolve } from 'node:path';
|
|
744
|
-
import { Envapter } from 'envapt';
|
|
745
|
-
|
|
746
|
-
// Load from multiple files
|
|
747
|
-
Envapter.envPaths = [resolve(import.meta.dirname, '.env.local'), resolve(import.meta.dirname, '.env.production')];
|
|
748
|
-
|
|
749
|
-
// Or single file
|
|
750
|
-
Envapter.envPaths = resolve(import.meta.dirname, '.env.production');
|
|
751
|
-
|
|
752
|
-
// Or just don't set a path for it to default to .env at the root of your project
|
|
753
|
-
|
|
754
|
-
// Also, in CommonJS, use `__dirname` instead of `import.meta.dirname`:
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
### Dotenv Configuration
|
|
758
|
-
|
|
759
|
-
Envapt allows you to customize dotenv behavior by setting configuration options:
|
|
760
|
-
|
|
761
|
-
```js
|
|
762
|
-
import { Envapter } from 'envapt';
|
|
763
|
-
|
|
764
|
-
// Set dotenv configuration options
|
|
765
|
-
Envapter.dotenvConfig = {
|
|
766
|
-
encoding: 'latin1', // File encoding (default: 'utf8')
|
|
767
|
-
debug: true, // Enable debug logging
|
|
768
|
-
override: true, // Override existing environment variables
|
|
769
|
-
quiet: false, // Suppress non-error output (default: true)
|
|
770
|
-
DOTENV_KEY: 'key...' // Decryption key for .env.vault files
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
// Get current configuration
|
|
774
|
-
console.log(Envapter.dotenvConfig);
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
> [!NOTE]
|
|
778
|
-
> The `path` and `processEnv` options are managed internally by Envapter and cannot be set via `dotenvConfig`.
|
|
779
|
-
|
|
780
|
-
<div align="right">
|
|
781
|
-
|
|
782
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
783
|
-
|
|
784
|
-
</div>
|
|
785
|
-
|
|
786
|
-
## Template Variables
|
|
787
|
-
|
|
788
|
-
Envapt supports variable interpolation with `${VARIABLE}` syntax:
|
|
789
|
-
|
|
790
|
-
```env
|
|
791
|
-
DATABASE_HOST=localhost
|
|
792
|
-
DATABASE_PORT=5432
|
|
793
|
-
DATABASE_URL=postgres://${DATABASE_HOST}:${DATABASE_PORT}/mydb
|
|
794
|
-
|
|
795
|
-
API_VERSION=v1
|
|
796
|
-
API_BASE=https://api.example.com
|
|
797
|
-
API_ENDPOINT=${API_BASE}/${API_VERSION}/users
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Circular Reference Protection
|
|
801
|
-
|
|
802
|
-
```env
|
|
803
|
-
CIRCULAR_A=${CIRCULAR_B}
|
|
804
|
-
CIRCULAR_B=${CIRCULAR_A}
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
Circular references are detected and preserved as-is rather than causing infinite loops.
|
|
808
|
-
|
|
809
|
-
<div align="right">
|
|
810
|
-
|
|
811
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
812
|
-
|
|
813
|
-
</div>
|
|
814
|
-
|
|
815
|
-
## Error Handling
|
|
816
|
-
|
|
817
|
-
Envapt provides detailed error codes for better debugging and error handling:
|
|
818
|
-
|
|
819
|
-
```js
|
|
820
|
-
import { EnvaptError, EnvaptErrorCodes } from 'envapt';
|
|
76
|
+
import { Envapt, Converters } from 'envapt';
|
|
821
77
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
} catch (error) {
|
|
826
|
-
if (error instanceof EnvaptError) {
|
|
827
|
-
console.log('Error code:', error.code);
|
|
828
|
-
console.log('Error message:', error.message);
|
|
829
|
-
|
|
830
|
-
// Handle specific error types
|
|
831
|
-
switch (error.code) {
|
|
832
|
-
case EnvaptErrorCodes.InvalidUserDefinedConfig:
|
|
833
|
-
console.log('Invalid configuration provided');
|
|
834
|
-
break;
|
|
835
|
-
case EnvaptErrorCodes.EnvFileNotFound:
|
|
836
|
-
console.log('Environment file not found');
|
|
837
|
-
break;
|
|
838
|
-
default:
|
|
839
|
-
console.warn('Unhandled error code:', error.code);
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
### Error Code Reference
|
|
847
|
-
|
|
848
|
-
#### 🔧 Fallback Errors (1xx)
|
|
849
|
-
|
|
850
|
-
| **Error Code** | **Description** |
|
|
851
|
-
| ---------------------------------------- | --------------------------------------------------------- |
|
|
852
|
-
| `InvalidFallback` (101) | Invalid fallback value provided |
|
|
853
|
-
| `InvalidFallbackType` (102) | Fallback value type doesn't match expected converter type |
|
|
854
|
-
| `ArrayFallbackElementTypeMismatch` (103) | Array fallback contains elements of wrong type |
|
|
855
|
-
| `FallbackConverterTypeMismatch` (104) | Fallback type doesn't match the specified converter |
|
|
856
|
-
|
|
857
|
-
#### 🧪 Converter Errors (2xx)
|
|
858
|
-
|
|
859
|
-
| **Error Code** | **Description** |
|
|
860
|
-
| --------------------------------- | ---------------------------------------------- |
|
|
861
|
-
| `InvalidArrayConverterType` (201) | Invalid array converter configuration provided |
|
|
862
|
-
| `InvalidBuiltInConverter` (202) | Invalid built-in converter specified |
|
|
863
|
-
| `InvalidCustomConverter` (203) | Custom converter function is invalid |
|
|
864
|
-
| `InvalidConverterType` (204) | Converter type is not recognized |
|
|
865
|
-
| `PrimitiveCoercionFailed` (205) | Primitive type coercion failed |
|
|
866
|
-
|
|
867
|
-
#### 📂 Environment File & Config Errors (3xx)
|
|
868
|
-
|
|
869
|
-
| **Error Code** | **Description** |
|
|
870
|
-
| -------------------------------- | ---------------------------------------------- |
|
|
871
|
-
| `MissingDelimiter` (301) | Delimiter is missing in array converter config |
|
|
872
|
-
| `InvalidUserDefinedConfig` (302) | Invalid user-defined configuration provided |
|
|
873
|
-
| `EnvFilesNotFound` (303) | Specified environment file doesn't exist |
|
|
874
|
-
| `InvalidKeyInput` (304) | Invalid key input (not string or string array) |
|
|
875
|
-
|
|
876
|
-
<div align="right">
|
|
877
|
-
|
|
878
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
879
|
-
|
|
880
|
-
</div>
|
|
881
|
-
|
|
882
|
-
## Advanced Examples
|
|
883
|
-
|
|
884
|
-
### JavaScript
|
|
885
|
-
|
|
886
|
-
```js
|
|
887
|
-
import { Envapter, Converters } from 'envapt';
|
|
888
|
-
|
|
889
|
-
// Global configuration
|
|
890
|
-
const config = {
|
|
891
|
-
port: Envapter.getNumber('PORT', 3000),
|
|
892
|
-
requestTimeout: Envapter.getUsing('REQUEST_TIMEOUT', Converters.Time, 10000), // "5s" -> 5000ms
|
|
893
|
-
featureFlags: Envapter.getWith(
|
|
894
|
-
'FEATURE_FLAGS',
|
|
895
|
-
(raw, fallback) => {
|
|
896
|
-
if (!raw) return fallback;
|
|
897
|
-
return new Set(raw.split(',').map((s) => s.trim()));
|
|
898
|
-
},
|
|
899
|
-
new Set(['basic'])
|
|
900
|
-
)
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
// Service configuration
|
|
904
|
-
class DatabaseService {
|
|
905
|
-
constructor() {
|
|
906
|
-
this.databaseUrl = Envapter.get('DB_URL', 'sqlite://memory');
|
|
907
|
-
this.cacheTtl = Envapter.getUsing('CACHE_TTL', Converters.Time, 3600000); // "1h" -> 3600000ms
|
|
908
|
-
this.redisUrls = Envapter.getWith(
|
|
909
|
-
'REDIS_URLS',
|
|
910
|
-
(raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback),
|
|
911
|
-
[new URL('redis://localhost:6379')]
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
async initialize() {
|
|
916
|
-
console.log(`App running on port ${config.port}`);
|
|
917
|
-
console.log(`Database: ${this.databaseUrl}`);
|
|
918
|
-
console.log(`Cache TTL: ${this.cacheTtl}ms`);
|
|
919
|
-
}
|
|
78
|
+
class Config {
|
|
79
|
+
@Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
|
|
80
|
+
declare static readonly port: number;
|
|
920
81
|
}
|
|
921
82
|
```
|
|
922
83
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
```ts
|
|
926
|
-
import { Envapt, Envapter, Converters } from 'envapt';
|
|
927
|
-
|
|
928
|
-
class AppConfig extends Envapter {
|
|
929
|
-
// Global settings (static)
|
|
930
|
-
@Envapt('PORT', 3000)
|
|
931
|
-
static readonly port: number;
|
|
84
|
+
## Documentation
|
|
932
85
|
|
|
933
|
-
|
|
934
|
-
|
|
86
|
+
The guide, converter reference, validation, configuration, and the v4 to v5 migration live at
|
|
87
|
+
**[envapt.materwelon.dev](https://envapt.materwelon.dev)**.
|
|
935
88
|
|
|
936
|
-
|
|
937
|
-
fallback: new Set(['basic']),
|
|
938
|
-
converter: (raw, fallback) => {
|
|
939
|
-
if (!raw) return fallback;
|
|
940
|
-
return new Set(raw.split(',').map((s) => s.trim()));
|
|
941
|
-
}
|
|
942
|
-
})
|
|
943
|
-
static readonly featureFlags: Set<string>;
|
|
89
|
+
## Agent skill
|
|
944
90
|
|
|
945
|
-
|
|
946
|
-
@Envapt('DB_URL', 'sqlite://memory')
|
|
947
|
-
declare readonly databaseUrl: string;
|
|
91
|
+
Install the envapt agent skill so AI coding tools use the correct API:
|
|
948
92
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
@Envapt('REDIS_URLS', {
|
|
953
|
-
fallback: [new URL('redis://localhost:6379')],
|
|
954
|
-
converter: (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback)
|
|
955
|
-
})
|
|
956
|
-
declare readonly redisUrls: URL[];
|
|
957
|
-
|
|
958
|
-
async initialize() {
|
|
959
|
-
console.log(`App running on port ${AppConfig.port}`);
|
|
960
|
-
console.log(`Database: ${this.databaseUrl}`);
|
|
961
|
-
console.log(`Cache TTL: ${this.cacheTtl}ms`);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
93
|
+
```sh
|
|
94
|
+
npx skills add materwelonDhruv/envapt
|
|
964
95
|
```
|
|
965
96
|
|
|
966
|
-
<div align="right">
|
|
967
|
-
|
|
968
|
-
**[⬆️ Back to Top](#table-of-contents)**
|
|
969
|
-
|
|
970
|
-
</div>
|
|
971
|
-
|
|
972
97
|
---
|
|
973
98
|
|
|
974
|
-
<
|
|
975
|
-
|
|
976
|
-
<p align="center">
|
|
977
|
-
<a href="https://github.com/materwelondhruv/envapt">⭐️ Star it on GitHub</a> •
|
|
978
|
-
<a href="https://github.com/materwelondhruv/envapt/issues">🐛 Report a bug</a> •
|
|
979
|
-
<a href="https://github.com/materwelondhruv/envapt/issues/new?labels=enhancement">💡 Request a feature</a>
|
|
980
|
-
</p>
|
|
981
|
-
|
|
982
|
-
<p align="center">
|
|
983
|
-
<sub>
|
|
984
|
-
Built by <a href="https://github.com/materwelondhruv">@materwelonDhruv</a> • Licensed under
|
|
985
|
-
<a href="LICENSE">Apache 2.0</a>
|
|
986
|
-
</sub>
|
|
987
|
-
</p>
|
|
99
|
+
<p align="center"><sub>Built by <a href="https://github.com/materwelondhruv">@materwelonDhruv</a> · Apache 2.0</sub></p>
|