@geekmidas/envkit 0.0.8 → 0.1.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/README.md +228 -174
- package/dist/EnvironmentBuilder-DHfDXJUm.d.mts +131 -0
- package/dist/EnvironmentBuilder-DfmYRBm-.mjs +83 -0
- package/dist/EnvironmentBuilder-DfmYRBm-.mjs.map +1 -0
- package/dist/EnvironmentBuilder-W2wku49g.cjs +95 -0
- package/dist/EnvironmentBuilder-W2wku49g.cjs.map +1 -0
- package/dist/EnvironmentBuilder-Xuf2Dd9u.d.cts +131 -0
- package/dist/EnvironmentBuilder.cjs +4 -0
- package/dist/EnvironmentBuilder.d.cts +2 -0
- package/dist/EnvironmentBuilder.d.mts +2 -0
- package/dist/EnvironmentBuilder.mjs +3 -0
- package/dist/{EnvironmentParser-cnxuy7lw.cjs → EnvironmentParser-Bt246UeP.cjs} +1 -1
- package/dist/{EnvironmentParser-cnxuy7lw.cjs.map → EnvironmentParser-Bt246UeP.cjs.map} +1 -1
- package/dist/{EnvironmentParser-B8--woiB.d.cts → EnvironmentParser-CVWU1ooT.d.mts} +1 -1
- package/dist/{EnvironmentParser-STvN_RCc.mjs → EnvironmentParser-c06agx31.mjs} +1 -1
- package/dist/{EnvironmentParser-STvN_RCc.mjs.map → EnvironmentParser-c06agx31.mjs.map} +1 -1
- package/dist/{EnvironmentParser-C_9v2BDw.d.mts → EnvironmentParser-tV-JjCg7.d.cts} +1 -1
- package/dist/EnvironmentParser.cjs +1 -1
- package/dist/EnvironmentParser.d.cts +1 -1
- package/dist/EnvironmentParser.d.mts +1 -1
- package/dist/EnvironmentParser.mjs +1 -1
- package/dist/SnifferEnvironmentParser.cjs +1 -1
- package/dist/SnifferEnvironmentParser.cjs.map +1 -1
- package/dist/SnifferEnvironmentParser.d.cts +1 -1
- package/dist/SnifferEnvironmentParser.d.mts +1 -1
- package/dist/SnifferEnvironmentParser.mjs +1 -1
- package/dist/SnifferEnvironmentParser.mjs.map +1 -1
- package/dist/SstEnvironmentBuilder-BuFw1hCe.cjs +125 -0
- package/dist/SstEnvironmentBuilder-BuFw1hCe.cjs.map +1 -0
- package/dist/SstEnvironmentBuilder-CjURMGjW.d.mts +177 -0
- package/dist/SstEnvironmentBuilder-D4oSo_KX.d.cts +177 -0
- package/dist/SstEnvironmentBuilder-DEa3lTUB.mjs +108 -0
- package/dist/SstEnvironmentBuilder-DEa3lTUB.mjs.map +1 -0
- package/dist/SstEnvironmentBuilder.cjs +7 -0
- package/dist/SstEnvironmentBuilder.d.cts +3 -0
- package/dist/SstEnvironmentBuilder.d.mts +3 -0
- package/dist/SstEnvironmentBuilder.mjs +4 -0
- package/dist/index.cjs +5 -2
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/sst.cjs +13 -114
- package/dist/sst.cjs.map +1 -1
- package/dist/sst.d.cts +14 -93
- package/dist/sst.d.mts +14 -93
- package/dist/sst.mjs +10 -112
- package/dist/sst.mjs.map +1 -1
- package/docs/async-secrets-design.md +355 -0
- package/package.json +6 -2
- package/src/EnvironmentBuilder.ts +196 -0
- package/src/SnifferEnvironmentParser.ts +7 -5
- package/src/SstEnvironmentBuilder.ts +298 -0
- package/src/__tests__/EnvironmentBuilder.spec.ts +274 -0
- package/src/__tests__/SstEnvironmentBuilder.spec.ts +373 -0
- package/src/__tests__/sst.spec.ts +1 -1
- package/src/index.ts +12 -0
- package/src/sst.ts +45 -207
package/README.md
CHANGED
|
@@ -1,84 +1,51 @@
|
|
|
1
1
|
# @geekmidas/envkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe environment configuration utilities for parsing environment variables and building environment records from typed resources.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- **EnvironmentParser**: Parse and validate environment variables with Zod schemas
|
|
8
|
+
- **EnvironmentBuilder**: Build environment records from type-discriminated objects
|
|
9
|
+
- **SstEnvironmentBuilder**: SST-specific builder with built-in resolvers for AWS resources
|
|
10
|
+
- Full TypeScript support with automatic type inference
|
|
11
|
+
- Nested configuration support
|
|
12
|
+
- Error aggregation and reporting
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install @geekmidas/envkit zod
|
|
18
|
-
# or
|
|
19
|
-
yarn add @geekmidas/envkit zod
|
|
20
|
-
# or
|
|
21
17
|
pnpm add @geekmidas/envkit zod
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
##
|
|
20
|
+
## EnvironmentParser
|
|
21
|
+
|
|
22
|
+
Parse and validate environment variables using Zod schemas.
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
27
|
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
28
|
-
import { z } from 'zod';
|
|
29
28
|
|
|
30
|
-
// Create parser with your config object (e.g., process.env)
|
|
31
29
|
const parser = new EnvironmentParser(process.env);
|
|
32
30
|
|
|
33
|
-
// Define your configuration schema
|
|
34
31
|
const config = parser.create((get) => ({
|
|
35
32
|
port: get('PORT').string().transform(Number).default(3000),
|
|
36
33
|
database: {
|
|
37
34
|
host: get('DATABASE_HOST').string(),
|
|
38
35
|
port: get('DATABASE_PORT').string().transform(Number),
|
|
39
36
|
name: get('DATABASE_NAME').string(),
|
|
40
|
-
user: get('DATABASE_USER').string(),
|
|
41
|
-
password: get('DATABASE_PASSWORD').string()
|
|
42
37
|
},
|
|
43
38
|
api: {
|
|
44
39
|
key: get('API_KEY').string().min(32),
|
|
45
40
|
url: get('API_URL').url(),
|
|
46
|
-
timeout: get('API_TIMEOUT').string().transform(Number).default(5000)
|
|
47
41
|
}
|
|
48
42
|
}));
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const parsedConfig = config.parse();
|
|
53
|
-
console.log('Configuration loaded successfully:', parsedConfig);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('Configuration validation failed:', error);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Usage
|
|
61
|
-
|
|
62
|
-
### Basic Configuration
|
|
63
|
-
|
|
64
|
-
The simplest use case is parsing flat environment variables:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
const parser = new EnvironmentParser(process.env);
|
|
68
|
-
|
|
69
|
-
const config = parser.create((get) => ({
|
|
70
|
-
appName: get('APP_NAME').string(),
|
|
71
|
-
port: get('PORT').string().transform(Number),
|
|
72
|
-
isProduction: get('NODE_ENV').string().transform(env => env === 'production')
|
|
73
|
-
}));
|
|
74
|
-
|
|
75
|
-
const { appName, port, isProduction } = config.parse();
|
|
44
|
+
const parsedConfig = config.parse();
|
|
76
45
|
```
|
|
77
46
|
|
|
78
47
|
### Nested Configuration
|
|
79
48
|
|
|
80
|
-
Create deeply nested configuration structures:
|
|
81
|
-
|
|
82
49
|
```typescript
|
|
83
50
|
const config = parser.create((get) => ({
|
|
84
51
|
server: {
|
|
@@ -87,79 +54,25 @@ const config = parser.create((get) => ({
|
|
|
87
54
|
ssl: {
|
|
88
55
|
enabled: get('SSL_ENABLED').string().transform(v => v === 'true'),
|
|
89
56
|
certPath: get('SSL_CERT_PATH').string().optional(),
|
|
90
|
-
keyPath: get('SSL_KEY_PATH').string().optional()
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
features: {
|
|
94
|
-
authentication: get('FEATURE_AUTH').string().transform(v => v === 'true'),
|
|
95
|
-
rateLimit: get('FEATURE_RATE_LIMIT').string().transform(v => v === 'true'),
|
|
96
|
-
cache: {
|
|
97
|
-
enabled: get('CACHE_ENABLED').string().transform(v => v === 'true'),
|
|
98
|
-
ttl: get('CACHE_TTL').string().transform(Number).default(3600)
|
|
99
57
|
}
|
|
100
58
|
}
|
|
101
59
|
}));
|
|
102
60
|
```
|
|
103
61
|
|
|
104
|
-
### Using Different Config Sources
|
|
105
|
-
|
|
106
|
-
While `process.env` is the most common source, you can use any object:
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// From a JSON file
|
|
110
|
-
import configJson from './config.json';
|
|
111
|
-
const parser = new EnvironmentParser(configJson);
|
|
112
|
-
|
|
113
|
-
// From a custom object
|
|
114
|
-
const customConfig = {
|
|
115
|
-
API_URL: 'https://api.example.com',
|
|
116
|
-
API_KEY: 'secret-key-123'
|
|
117
|
-
};
|
|
118
|
-
const parser = new EnvironmentParser(customConfig);
|
|
119
|
-
|
|
120
|
-
// Combining multiple sources
|
|
121
|
-
const mergedConfig = {
|
|
122
|
-
...defaultConfig,
|
|
123
|
-
...process.env
|
|
124
|
-
};
|
|
125
|
-
const parser = new EnvironmentParser(mergedConfig);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Advanced Validation
|
|
129
|
-
|
|
130
|
-
Leverage Zod's full validation capabilities:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
const config = parser.create((get) => ({
|
|
134
|
-
email: get('ADMIN_EMAIL').string().email(),
|
|
135
|
-
webhook: get('WEBHOOK_URL').url(),
|
|
136
|
-
retries: get('MAX_RETRIES').string().transform(Number).int().min(0).max(10),
|
|
137
|
-
allowedOrigins: get('ALLOWED_ORIGINS')
|
|
138
|
-
.string()
|
|
139
|
-
.transform(origins => origins.split(','))
|
|
140
|
-
.refine(origins => origins.every(o => o.startsWith('http')), {
|
|
141
|
-
message: 'All origins must be valid URLs'
|
|
142
|
-
}),
|
|
143
|
-
logLevel: get('LOG_LEVEL')
|
|
144
|
-
.enum(['debug', 'info', 'warn', 'error'])
|
|
145
|
-
.default('info')
|
|
146
|
-
}));
|
|
147
|
-
```
|
|
148
|
-
|
|
149
62
|
### Error Handling
|
|
150
63
|
|
|
151
|
-
The parser aggregates all validation errors
|
|
64
|
+
The parser aggregates all validation errors:
|
|
152
65
|
|
|
153
66
|
```typescript
|
|
67
|
+
import { z } from 'zod';
|
|
68
|
+
|
|
154
69
|
try {
|
|
155
70
|
const config = parser.create((get) => ({
|
|
156
71
|
required1: get('MISSING_VAR_1').string(),
|
|
157
72
|
required2: get('MISSING_VAR_2').string(),
|
|
158
|
-
invalid: get('INVALID_NUMBER').string().transform(Number)
|
|
159
73
|
})).parse();
|
|
160
74
|
} catch (error) {
|
|
161
75
|
if (error instanceof z.ZodError) {
|
|
162
|
-
console.error('Configuration errors:');
|
|
163
76
|
error.errors.forEach(err => {
|
|
164
77
|
console.error(`- ${err.path.join('.')}: ${err.message}`);
|
|
165
78
|
});
|
|
@@ -167,129 +80,270 @@ try {
|
|
|
167
80
|
}
|
|
168
81
|
```
|
|
169
82
|
|
|
170
|
-
|
|
83
|
+
## EnvironmentBuilder
|
|
171
84
|
|
|
172
|
-
|
|
85
|
+
A generic builder for creating environment variables from objects with type-discriminated values.
|
|
86
|
+
|
|
87
|
+
### Basic Usage
|
|
173
88
|
|
|
174
89
|
```typescript
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
90
|
+
import { EnvironmentBuilder } from '@geekmidas/envkit';
|
|
91
|
+
|
|
92
|
+
const env = new EnvironmentBuilder(
|
|
93
|
+
{
|
|
94
|
+
apiKey: { type: 'secret', value: 'xyz' },
|
|
95
|
+
appName: 'my-app',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
// Resolver receives value without 'type' key
|
|
99
|
+
secret: (key, value) => ({ [key]: value.value }),
|
|
179
100
|
}
|
|
180
|
-
|
|
101
|
+
).build();
|
|
181
102
|
|
|
182
|
-
|
|
183
|
-
// TypeScript knows: parsed.port is number, parsed.features.auth is boolean
|
|
103
|
+
// Result: { API_KEY: 'xyz', APP_NAME: 'my-app' }
|
|
184
104
|
```
|
|
185
105
|
|
|
186
|
-
|
|
106
|
+
### How It Works
|
|
187
107
|
|
|
188
|
-
|
|
108
|
+
1. **Plain string values** are passed through with key transformation to `UPPER_SNAKE_CASE`
|
|
109
|
+
2. **Object values** with a `type` property are matched against resolvers
|
|
110
|
+
3. **Resolvers** receive values without the `type` key
|
|
111
|
+
4. **Root-level keys** from resolver output are transformed to `UPPER_SNAKE_CASE`
|
|
189
112
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
#### Constructor
|
|
113
|
+
### Multiple Resolvers
|
|
193
114
|
|
|
194
115
|
```typescript
|
|
195
|
-
new
|
|
116
|
+
const env = new EnvironmentBuilder(
|
|
117
|
+
{
|
|
118
|
+
secret: { type: 'secret', value: 'my-secret' },
|
|
119
|
+
database: { type: 'postgres', host: 'localhost', port: 5432 },
|
|
120
|
+
bucket: { type: 'bucket', name: 'my-bucket' },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
secret: (key, value) => ({ [key]: value.value }),
|
|
124
|
+
postgres: (key, value) => ({
|
|
125
|
+
[`${key}Host`]: value.host,
|
|
126
|
+
[`${key}Port`]: value.port,
|
|
127
|
+
}),
|
|
128
|
+
bucket: (key, value) => ({ [`${key}Name`]: value.name }),
|
|
129
|
+
}
|
|
130
|
+
).build();
|
|
131
|
+
|
|
132
|
+
// Result:
|
|
133
|
+
// {
|
|
134
|
+
// SECRET: 'my-secret',
|
|
135
|
+
// DATABASE_HOST: 'localhost',
|
|
136
|
+
// DATABASE_PORT: 5432,
|
|
137
|
+
// BUCKET_NAME: 'my-bucket',
|
|
138
|
+
// }
|
|
196
139
|
```
|
|
197
140
|
|
|
198
|
-
|
|
141
|
+
### Typed Resolvers
|
|
142
|
+
|
|
143
|
+
Resolver keys and values are type-checked based on the input record:
|
|
199
144
|
|
|
200
|
-
|
|
145
|
+
```typescript
|
|
146
|
+
const env = new EnvironmentBuilder(
|
|
147
|
+
{
|
|
148
|
+
auth: { type: 'auth0' as const, domain: 'example.auth0.com', clientId: 'abc' },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
// TypeScript enforces 'auth0' resolver exists and value has correct shape
|
|
152
|
+
auth0: (key, value) => ({
|
|
153
|
+
[`${key}Domain`]: value.domain,
|
|
154
|
+
[`${key}ClientId`]: value.clientId,
|
|
155
|
+
}),
|
|
156
|
+
}
|
|
157
|
+
).build();
|
|
158
|
+
|
|
159
|
+
// Result: { AUTH_DOMAIN: 'example.auth0.com', AUTH_CLIENT_ID: 'abc' }
|
|
160
|
+
```
|
|
201
161
|
|
|
202
|
-
|
|
162
|
+
### Handling Unmatched Values
|
|
203
163
|
|
|
204
|
-
|
|
164
|
+
```typescript
|
|
165
|
+
const env = new EnvironmentBuilder(
|
|
166
|
+
{ unknown: { type: 'unknown-type', data: 'test' } },
|
|
167
|
+
{},
|
|
168
|
+
{
|
|
169
|
+
onUnmatchedValue: (key, value) => {
|
|
170
|
+
console.warn(`No resolver for "${key}":`, value);
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
).build();
|
|
174
|
+
```
|
|
205
175
|
|
|
206
|
-
|
|
207
|
-
- Returns: A `ConfigParser` instance
|
|
176
|
+
## SstEnvironmentBuilder
|
|
208
177
|
|
|
209
|
-
|
|
178
|
+
SST-specific builder with built-in resolvers for AWS resources.
|
|
210
179
|
|
|
211
|
-
|
|
180
|
+
### Basic Usage
|
|
212
181
|
|
|
213
|
-
|
|
182
|
+
```typescript
|
|
183
|
+
import { SstEnvironmentBuilder, ResourceType } from '@geekmidas/envkit/sst';
|
|
214
184
|
|
|
215
|
-
|
|
185
|
+
const env = new SstEnvironmentBuilder({
|
|
186
|
+
database: {
|
|
187
|
+
type: ResourceType.Postgres,
|
|
188
|
+
host: 'db.example.com',
|
|
189
|
+
port: 5432,
|
|
190
|
+
database: 'myapp',
|
|
191
|
+
username: 'admin',
|
|
192
|
+
password: 'secret',
|
|
193
|
+
},
|
|
194
|
+
apiKey: {
|
|
195
|
+
type: ResourceType.Secret,
|
|
196
|
+
value: 'super-secret',
|
|
197
|
+
},
|
|
198
|
+
appName: 'my-app',
|
|
199
|
+
}).build();
|
|
200
|
+
|
|
201
|
+
// Result:
|
|
202
|
+
// {
|
|
203
|
+
// DATABASE_HOST: 'db.example.com',
|
|
204
|
+
// DATABASE_PORT: 5432,
|
|
205
|
+
// DATABASE_NAME: 'myapp',
|
|
206
|
+
// DATABASE_USERNAME: 'admin',
|
|
207
|
+
// DATABASE_PASSWORD: 'secret',
|
|
208
|
+
// API_KEY: 'super-secret',
|
|
209
|
+
// APP_NAME: 'my-app',
|
|
210
|
+
// }
|
|
211
|
+
```
|
|
216
212
|
|
|
217
|
-
|
|
213
|
+
### Supported Resource Types
|
|
218
214
|
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
| Resource Type | Properties | Output |
|
|
216
|
+
|--------------|------------|--------|
|
|
217
|
+
| `Secret` / `SSTSecret` | `value` | `{key}: value` |
|
|
218
|
+
| `Postgres` / `SSTPostgres` | `host`, `port`, `database`, `username`, `password` | `{key}Host`, `{key}Port`, `{key}Name`, `{key}Username`, `{key}Password` |
|
|
219
|
+
| `Bucket` / `SSTBucket` | `name` | `{key}Name` |
|
|
220
|
+
| `SnsTopic` | `arn` | `{key}Arn` |
|
|
221
|
+
| `ApiGatewayV2` | - | No output (noop) |
|
|
222
|
+
| `Function` | - | No output (noop) |
|
|
223
|
+
| `Vpc` | - | No output (noop) |
|
|
221
224
|
|
|
222
|
-
###
|
|
225
|
+
### Custom Resolvers
|
|
223
226
|
|
|
224
|
-
|
|
227
|
+
Add custom resolvers alongside built-in SST resolvers:
|
|
225
228
|
|
|
226
229
|
```typescript
|
|
227
|
-
|
|
230
|
+
const env = new SstEnvironmentBuilder(
|
|
231
|
+
{
|
|
232
|
+
database: { type: ResourceType.Postgres, /* ... */ },
|
|
233
|
+
custom: { type: 'my-custom' as const, data: 'custom-data' },
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
// Custom resolver merged with SST resolvers
|
|
237
|
+
'my-custom': (key, value) => ({ [`${key}Data`]: value.data }),
|
|
238
|
+
}
|
|
239
|
+
).build();
|
|
240
|
+
|
|
241
|
+
// Result includes both SST resources and custom type
|
|
228
242
|
```
|
|
229
243
|
|
|
230
|
-
|
|
231
|
-
|
|
244
|
+
### Resource Type Enum
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { ResourceType } from '@geekmidas/envkit/sst';
|
|
248
|
+
|
|
249
|
+
// Legacy format (dot notation)
|
|
250
|
+
ResourceType.Postgres // 'sst.aws.Postgres'
|
|
251
|
+
ResourceType.Secret // 'sst.sst.Secret'
|
|
252
|
+
ResourceType.Bucket // 'sst.aws.Bucket'
|
|
253
|
+
|
|
254
|
+
// Modern format (colon notation)
|
|
255
|
+
ResourceType.SSTPostgres // 'sst:aws:Postgres'
|
|
256
|
+
ResourceType.SSTSecret // 'sst:sst:Secret'
|
|
257
|
+
ResourceType.SSTBucket // 'sst:aws:Bucket'
|
|
258
|
+
ResourceType.SnsTopic // 'sst:aws:SnsTopic'
|
|
259
|
+
```
|
|
232
260
|
|
|
233
|
-
|
|
261
|
+
### Using with SST Resource
|
|
234
262
|
|
|
235
|
-
|
|
263
|
+
Combine `SstEnvironmentBuilder` with `EnvironmentParser` to create a type-safe configuration from SST resources:
|
|
236
264
|
|
|
237
265
|
```typescript
|
|
238
266
|
// config.ts
|
|
239
267
|
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
240
|
-
import {
|
|
268
|
+
import { SstEnvironmentBuilder } from '@geekmidas/envkit/sst';
|
|
269
|
+
import { Resource } from 'sst';
|
|
241
270
|
|
|
242
|
-
|
|
271
|
+
// Build environment variables from SST resources
|
|
272
|
+
const env = new SstEnvironmentBuilder(Resource as {}).build();
|
|
273
|
+
|
|
274
|
+
// Create parser with the normalized environment
|
|
275
|
+
export const envParser = new EnvironmentParser(env);
|
|
243
276
|
|
|
244
|
-
|
|
245
|
-
|
|
277
|
+
// Define your configuration schema
|
|
278
|
+
export const config = envParser.create((get) => ({
|
|
279
|
+
database: {
|
|
280
|
+
host: get('DATABASE_HOST').string(),
|
|
281
|
+
port: get('DATABASE_PORT').number(),
|
|
282
|
+
name: get('DATABASE_NAME').string(),
|
|
283
|
+
username: get('DATABASE_USERNAME').string(),
|
|
284
|
+
password: get('DATABASE_PASSWORD').string(),
|
|
285
|
+
},
|
|
286
|
+
apiKey: get('API_KEY').string(),
|
|
246
287
|
})).parse();
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
This pattern allows you to:
|
|
291
|
+
1. Normalize SST resources (Postgres, Secrets, Buckets, etc.) into flat environment variables
|
|
292
|
+
2. Parse and validate them with Zod schemas
|
|
293
|
+
3. Get full TypeScript type inference for your configuration
|
|
247
294
|
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
## API Reference
|
|
296
|
+
|
|
297
|
+
### EnvironmentParser
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
class EnvironmentParser {
|
|
301
|
+
constructor(config: Record<string, unknown>);
|
|
302
|
+
create<T>(schemaBuilder: (get: GetFunction) => T): ConfigParser<T>;
|
|
303
|
+
}
|
|
250
304
|
```
|
|
251
305
|
|
|
252
|
-
|
|
306
|
+
### EnvironmentBuilder
|
|
253
307
|
|
|
254
308
|
```typescript
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
309
|
+
class EnvironmentBuilder<TRecord, TResolvers> {
|
|
310
|
+
constructor(
|
|
311
|
+
record: TRecord,
|
|
312
|
+
resolvers: TResolvers,
|
|
313
|
+
options?: EnvironmentBuilderOptions
|
|
314
|
+
);
|
|
315
|
+
build(): EnvRecord;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
interface EnvironmentBuilderOptions {
|
|
319
|
+
onUnmatchedValue?: (key: string, value: unknown) => void;
|
|
320
|
+
}
|
|
259
321
|
```
|
|
260
322
|
|
|
261
|
-
|
|
323
|
+
### SstEnvironmentBuilder
|
|
262
324
|
|
|
263
325
|
```typescript
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
326
|
+
class SstEnvironmentBuilder<TRecord> {
|
|
327
|
+
constructor(
|
|
328
|
+
record: TRecord,
|
|
329
|
+
additionalResolvers?: CustomResolvers<TRecord>,
|
|
330
|
+
options?: EnvironmentBuilderOptions
|
|
331
|
+
);
|
|
332
|
+
build(): EnvRecord;
|
|
333
|
+
}
|
|
270
334
|
```
|
|
271
335
|
|
|
272
|
-
|
|
336
|
+
### environmentCase
|
|
273
337
|
|
|
274
338
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
.min(1)
|
|
282
|
-
.max(1000)
|
|
283
|
-
.default(100),
|
|
284
|
-
|
|
285
|
-
// Comma-separated list of allowed origins
|
|
286
|
-
allowedOrigins: get('ALLOWED_ORIGINS')
|
|
287
|
-
.string()
|
|
288
|
-
.transform(origins => origins.split(',').map(o => o.trim()))
|
|
289
|
-
.default(['http://localhost:3000'])
|
|
290
|
-
}));
|
|
339
|
+
function environmentCase(name: string): string;
|
|
340
|
+
|
|
341
|
+
// Examples:
|
|
342
|
+
environmentCase('myVariable') // 'MY_VARIABLE'
|
|
343
|
+
environmentCase('apiUrl') // 'API_URL'
|
|
344
|
+
environmentCase('databaseName') // 'DATABASE_NAME'
|
|
291
345
|
```
|
|
292
346
|
|
|
293
347
|
## License
|
|
294
348
|
|
|
295
|
-
MIT
|
|
349
|
+
MIT
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//#region src/EnvironmentBuilder.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts a string to environment variable case format (UPPER_SNAKE_CASE).
|
|
4
|
+
* Numbers following underscores are preserved without the underscore.
|
|
5
|
+
*
|
|
6
|
+
* @param name - The string to convert
|
|
7
|
+
* @returns The converted string in environment variable format
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* environmentCase('myVariable') // 'MY_VARIABLE'
|
|
11
|
+
* environmentCase('apiV2') // 'APIV2'
|
|
12
|
+
*/
|
|
13
|
+
declare function environmentCase(name: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* A record of environment variable names to their values.
|
|
16
|
+
* Values can be primitives or nested records.
|
|
17
|
+
*/
|
|
18
|
+
interface EnvRecord {
|
|
19
|
+
[key: string]: EnvValue;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Represents a value that can be stored in an environment record.
|
|
23
|
+
* Can be a primitive value or a nested record of environment values.
|
|
24
|
+
*/
|
|
25
|
+
type EnvValue = string | number | boolean | EnvRecord;
|
|
26
|
+
/**
|
|
27
|
+
* A resolver function that converts a typed value into environment variables.
|
|
28
|
+
*
|
|
29
|
+
* @template T - The type of value this resolver handles (without the `type` key)
|
|
30
|
+
* @param key - The key name from the input record
|
|
31
|
+
* @param value - The value to resolve (without the `type` key)
|
|
32
|
+
* @returns A record of environment variable names to their values
|
|
33
|
+
*/
|
|
34
|
+
type EnvironmentResolver<T = any> = (key: string, value: T) => EnvRecord;
|
|
35
|
+
/**
|
|
36
|
+
* A map of type discriminator strings to their resolver functions.
|
|
37
|
+
*/
|
|
38
|
+
type Resolvers = Record<string, EnvironmentResolver<any>>;
|
|
39
|
+
/**
|
|
40
|
+
* Options for configuring the EnvironmentBuilder.
|
|
41
|
+
*/
|
|
42
|
+
interface EnvironmentBuilderOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Handler called when a value's type doesn't match any registered resolver.
|
|
45
|
+
* Defaults to console.warn.
|
|
46
|
+
*/
|
|
47
|
+
onUnmatchedValue?: (key: string, value: unknown) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Input value type - either a string or an object with a `type` discriminator.
|
|
51
|
+
*/
|
|
52
|
+
type InputValue = string | {
|
|
53
|
+
type: string;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Base type for typed input values with a specific type discriminator.
|
|
58
|
+
*/
|
|
59
|
+
type TypedInputValue<TType extends string = string> = {
|
|
60
|
+
type: TType;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Extracts the `type` string value from an input value.
|
|
65
|
+
*/
|
|
66
|
+
type ExtractType<T> = T extends {
|
|
67
|
+
type: infer U extends string;
|
|
68
|
+
} ? U : never;
|
|
69
|
+
/**
|
|
70
|
+
* Removes the `type` key from an object type.
|
|
71
|
+
*/
|
|
72
|
+
type OmitType<T> = T extends {
|
|
73
|
+
type: string;
|
|
74
|
+
} ? Omit<T, 'type'> : never;
|
|
75
|
+
/**
|
|
76
|
+
* Extracts all unique `type` values from a record (excluding plain strings).
|
|
77
|
+
*/
|
|
78
|
+
type AllTypeValues<TRecord extends Record<string, InputValue>> = { [K in keyof TRecord]: ExtractType<TRecord[K]> }[keyof TRecord];
|
|
79
|
+
/**
|
|
80
|
+
* For a given type value, finds the corresponding value type (without `type` key).
|
|
81
|
+
*/
|
|
82
|
+
type ValueForType<TRecord extends Record<string, InputValue>, TType extends string> = { [K in keyof TRecord]: TRecord[K] extends {
|
|
83
|
+
type: TType;
|
|
84
|
+
} ? OmitType<TRecord[K]> : never }[keyof TRecord];
|
|
85
|
+
/**
|
|
86
|
+
* Generates typed resolvers based on the input record.
|
|
87
|
+
* Keys are the `type` values, values are resolver functions receiving the value without `type`.
|
|
88
|
+
*/
|
|
89
|
+
type TypedResolvers<TRecord extends Record<string, InputValue>> = { [TType in AllTypeValues<TRecord>]: EnvironmentResolver<ValueForType<TRecord, TType>> };
|
|
90
|
+
/**
|
|
91
|
+
* A generic, extensible class for building environment variables from
|
|
92
|
+
* objects with type-discriminated values.
|
|
93
|
+
*
|
|
94
|
+
* @template TRecord - The input record type for type inference
|
|
95
|
+
* @template TResolvers - The resolvers type (defaults to TypedResolvers<TRecord>)
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const env = new EnvironmentBuilder(
|
|
100
|
+
* {
|
|
101
|
+
* apiKey: { type: 'secret', value: 'xyz' },
|
|
102
|
+
* appName: 'my-app'
|
|
103
|
+
* },
|
|
104
|
+
* {
|
|
105
|
+
* // `value` is typed as { value: string } (without `type`)
|
|
106
|
+
* secret: (key, value) => ({ [key]: value.value }),
|
|
107
|
+
* }
|
|
108
|
+
* ).build();
|
|
109
|
+
* // { API_KEY: 'xyz', APP_NAME: 'my-app' }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare class EnvironmentBuilder<TRecord extends Record<string, InputValue> = Record<string, InputValue>, TResolvers extends Resolvers = TypedResolvers<TRecord>> {
|
|
113
|
+
private readonly record;
|
|
114
|
+
private readonly resolvers;
|
|
115
|
+
private readonly options;
|
|
116
|
+
constructor(record: TRecord, resolvers: TResolvers, options?: EnvironmentBuilderOptions);
|
|
117
|
+
/**
|
|
118
|
+
* Build environment variables from the input record.
|
|
119
|
+
*
|
|
120
|
+
* - Plain string values are passed through with key transformation
|
|
121
|
+
* - Object values with a `type` property are matched against resolvers
|
|
122
|
+
* - Resolvers receive values without the `type` key
|
|
123
|
+
* - Only root-level keys are transformed to UPPER_SNAKE_CASE
|
|
124
|
+
*
|
|
125
|
+
* @returns A record of environment variables
|
|
126
|
+
*/
|
|
127
|
+
build(): EnvRecord;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { EnvRecord, EnvValue, EnvironmentBuilder, EnvironmentBuilderOptions, EnvironmentResolver, InputValue, Resolvers, TypedInputValue, TypedResolvers, environmentCase };
|
|
131
|
+
//# sourceMappingURL=EnvironmentBuilder-DHfDXJUm.d.mts.map
|