@geekmidas/envkit 0.2.0 → 0.4.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/dist/{EnvironmentBuilder-DfmYRBm-.mjs → EnvironmentBuilder-BSuHZm0y.mjs} +2 -4
- package/dist/EnvironmentBuilder-BSuHZm0y.mjs.map +1 -0
- package/dist/EnvironmentBuilder-DHfDXJUm.d.mts.map +1 -0
- package/dist/{EnvironmentBuilder-W2wku49g.cjs → EnvironmentBuilder-Djr1VsWM.cjs} +2 -4
- package/dist/EnvironmentBuilder-Djr1VsWM.cjs.map +1 -0
- package/dist/EnvironmentBuilder-Xuf2Dd9u.d.cts.map +1 -0
- package/dist/EnvironmentBuilder.cjs +1 -1
- package/dist/EnvironmentBuilder.mjs +1 -1
- package/dist/EnvironmentParser-Bt246UeP.cjs.map +1 -1
- package/dist/{EnvironmentParser-CVWU1ooT.d.mts → EnvironmentParser-CY8TosTN.d.mts} +2 -1
- package/dist/EnvironmentParser-CY8TosTN.d.mts.map +1 -0
- package/dist/{EnvironmentParser-tV-JjCg7.d.cts → EnvironmentParser-DtOL86NU.d.cts} +2 -1
- package/dist/EnvironmentParser-DtOL86NU.d.cts.map +1 -0
- package/dist/EnvironmentParser-c06agx31.mjs.map +1 -1
- package/dist/EnvironmentParser.d.cts +1 -1
- package/dist/EnvironmentParser.d.mts +1 -1
- package/dist/SnifferEnvironmentParser.cjs.map +1 -1
- package/dist/SnifferEnvironmentParser.d.cts +3 -2
- package/dist/SnifferEnvironmentParser.d.cts.map +1 -0
- package/dist/SnifferEnvironmentParser.d.mts +3 -2
- package/dist/SnifferEnvironmentParser.d.mts.map +1 -0
- package/dist/SnifferEnvironmentParser.mjs.map +1 -1
- package/dist/{SstEnvironmentBuilder-DEa3lTUB.mjs → SstEnvironmentBuilder-BEBFSUYr.mjs} +2 -2
- package/dist/SstEnvironmentBuilder-BEBFSUYr.mjs.map +1 -0
- package/dist/SstEnvironmentBuilder-CjURMGjW.d.mts.map +1 -0
- package/dist/SstEnvironmentBuilder-D4oSo_KX.d.cts.map +1 -0
- package/dist/{SstEnvironmentBuilder-BuFw1hCe.cjs → SstEnvironmentBuilder-wFnN2M5O.cjs} +2 -2
- package/dist/SstEnvironmentBuilder-wFnN2M5O.cjs.map +1 -0
- package/dist/SstEnvironmentBuilder.cjs +2 -2
- package/dist/SstEnvironmentBuilder.mjs +2 -2
- package/dist/credentials.cjs +66 -0
- package/dist/credentials.cjs.map +1 -0
- package/dist/credentials.d.cts +31 -0
- package/dist/credentials.d.cts.map +1 -0
- package/dist/credentials.d.mts +31 -0
- package/dist/credentials.d.mts.map +1 -0
- package/dist/credentials.mjs +62 -0
- package/dist/credentials.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/sst.cjs +2 -2
- package/dist/sst.cjs.map +1 -1
- package/dist/sst.d.cts +1 -0
- package/dist/sst.d.cts.map +1 -0
- package/dist/sst.d.mts +1 -0
- package/dist/sst.d.mts.map +1 -0
- package/dist/sst.mjs +2 -2
- package/dist/sst.mjs.map +1 -1
- package/examples/basic-usage.ts +329 -333
- package/package.json +6 -1
- package/src/EnvironmentBuilder.ts +76 -80
- package/src/EnvironmentParser.ts +231 -231
- package/src/SnifferEnvironmentParser.ts +178 -178
- package/src/SstEnvironmentBuilder.ts +127 -127
- package/src/__tests__/ConfigParser.spec.ts +388 -388
- package/src/__tests__/EnvironmentBuilder.spec.ts +245 -265
- package/src/__tests__/EnvironmentParser.spec.ts +828 -828
- package/src/__tests__/SnifferEnvironmentParser.spec.ts +380 -326
- package/src/__tests__/SstEnvironmentBuilder.spec.ts +347 -367
- package/src/__tests__/credentials.integration.spec.ts +239 -0
- package/src/__tests__/credentials.spec.ts +136 -0
- package/src/__tests__/sst.spec.ts +390 -413
- package/src/credentials.ts +99 -0
- package/src/index.ts +11 -11
- package/src/sst.ts +24 -24
- package/sst-env.d.ts +0 -1
- package/tsconfig.json +9 -0
- package/dist/EnvironmentBuilder-DfmYRBm-.mjs.map +0 -1
- package/dist/EnvironmentBuilder-W2wku49g.cjs.map +0 -1
- package/dist/SstEnvironmentBuilder-BuFw1hCe.cjs.map +0 -1
- package/dist/SstEnvironmentBuilder-DEa3lTUB.mjs.map +0 -1
package/examples/basic-usage.ts
CHANGED
|
@@ -4,386 +4,382 @@ import { z } from 'zod';
|
|
|
4
4
|
const parser = new EnvironmentParser(process.env as {});
|
|
5
5
|
// Example 1: Basic configuration
|
|
6
6
|
export function basicExample() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const config = parser.create((get) => ({
|
|
8
|
+
appName: get('APP_NAME').string().default('My App'),
|
|
9
|
+
port: get('PORT').string().transform(Number).default(3000),
|
|
10
|
+
isDevelopment: get('NODE_ENV')
|
|
11
|
+
.string()
|
|
12
|
+
.transform((env) => env === 'development'),
|
|
13
|
+
}));
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const result = config.parse();
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
return result;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Example 2: Database configuration
|
|
21
21
|
export function databaseExample() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
const config = parser.create((get) => ({
|
|
23
|
+
database: {
|
|
24
|
+
host: get('DB_HOST').string().default('localhost'),
|
|
25
|
+
port: get('DB_PORT').string().transform(Number).default(5432),
|
|
26
|
+
name: get('DB_NAME').string(),
|
|
27
|
+
user: get('DB_USER').string(),
|
|
28
|
+
password: get('DB_PASSWORD').string(),
|
|
29
|
+
ssl: get('DB_SSL')
|
|
30
|
+
.string()
|
|
31
|
+
.transform((v) => v === 'true')
|
|
32
|
+
.default(false),
|
|
33
|
+
poolSize: get('DB_POOL_SIZE')
|
|
34
|
+
.string()
|
|
35
|
+
.transform(Number)
|
|
36
|
+
.int()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(100)
|
|
39
|
+
.default(10),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
return config.parse();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Example 3: API configuration with validation
|
|
47
47
|
export function apiConfigExample() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
const config = parser.create((get) => ({
|
|
49
|
+
api: {
|
|
50
|
+
baseUrl: get('API_BASE_URL').url(),
|
|
51
|
+
key: get('API_KEY').string().min(32),
|
|
52
|
+
secret: get('API_SECRET').string().min(64),
|
|
53
|
+
timeout: get('API_TIMEOUT').string().transform(Number).default(5000),
|
|
54
|
+
retries: get('API_RETRIES').string().transform(Number).default(3),
|
|
55
|
+
endpoints: {
|
|
56
|
+
users: get('API_ENDPOINT_USERS').string().default('/api/v1/users'),
|
|
57
|
+
auth: get('API_ENDPOINT_AUTH').string().default('/api/v1/auth'),
|
|
58
|
+
products: get('API_ENDPOINT_PRODUCTS')
|
|
59
|
+
.string()
|
|
60
|
+
.default('/api/v1/products'),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
return config.parse();
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Example 4: Feature flags and complex validation
|
|
69
69
|
export function featureFlagsExample() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
70
|
+
const config = parser.create((get) => ({
|
|
71
|
+
features: {
|
|
72
|
+
authentication: get('FEATURE_AUTH')
|
|
73
|
+
.string()
|
|
74
|
+
.transform((v) => v === 'true')
|
|
75
|
+
.default(true),
|
|
76
|
+
rateLimit: get('FEATURE_RATE_LIMIT')
|
|
77
|
+
.string()
|
|
78
|
+
.transform((v) => v === 'true')
|
|
79
|
+
.default(true),
|
|
80
|
+
cache: get('FEATURE_CACHE')
|
|
81
|
+
.string()
|
|
82
|
+
.transform((v) => v === 'true')
|
|
83
|
+
.default(false),
|
|
84
|
+
beta: {
|
|
85
|
+
enabled: get('FEATURE_BETA')
|
|
86
|
+
.string()
|
|
87
|
+
.transform((v) => v === 'true')
|
|
88
|
+
.default(false),
|
|
89
|
+
allowedUsers: get('FEATURE_BETA_USERS')
|
|
90
|
+
.string()
|
|
91
|
+
.transform((users) =>
|
|
92
|
+
users ? users.split(',').map((u) => u.trim()) : [],
|
|
93
|
+
)
|
|
94
|
+
.default([]),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
rateLimit: {
|
|
98
|
+
windowMs: get('RATE_LIMIT_WINDOW_MS')
|
|
99
|
+
.string()
|
|
100
|
+
.transform(Number)
|
|
101
|
+
.default(60000),
|
|
102
|
+
maxRequests: get('RATE_LIMIT_MAX_REQUESTS')
|
|
103
|
+
.string()
|
|
104
|
+
.transform(Number)
|
|
105
|
+
.default(100),
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return config.parse();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// Example 5: Email configuration with refinements
|
|
113
113
|
export function emailConfigExample() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
114
|
+
const config = parser.create((get) => ({
|
|
115
|
+
email: {
|
|
116
|
+
provider: get('EMAIL_PROVIDER').enum(['sendgrid', 'mailgun', 'ses']),
|
|
117
|
+
apiKey: get('EMAIL_API_KEY').string().min(20),
|
|
118
|
+
from: {
|
|
119
|
+
name: get('EMAIL_FROM_NAME').string().default('Support Team'),
|
|
120
|
+
address: get('EMAIL_FROM_ADDRESS').string().email(),
|
|
121
|
+
},
|
|
122
|
+
replyTo: get('EMAIL_REPLY_TO').string().email().optional(),
|
|
123
|
+
templates: {
|
|
124
|
+
welcome: get('EMAIL_TEMPLATE_WELCOME').string().uuid(),
|
|
125
|
+
resetPassword: get('EMAIL_TEMPLATE_RESET_PASSWORD').string().uuid(),
|
|
126
|
+
invoice: get('EMAIL_TEMPLATE_INVOICE').string().uuid().optional(),
|
|
127
|
+
},
|
|
128
|
+
smtp: {
|
|
129
|
+
host: get('SMTP_HOST').string().optional(),
|
|
130
|
+
port: get('SMTP_PORT').string().transform(Number).optional(),
|
|
131
|
+
secure: get('SMTP_SECURE')
|
|
132
|
+
.string()
|
|
133
|
+
.transform((v) => v === 'true')
|
|
134
|
+
.default(true),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
return config.parse();
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Example 6: Multi-environment configuration
|
|
143
143
|
export function multiEnvironmentExample() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
144
|
+
const env = process.env.NODE_ENV || 'development';
|
|
145
|
+
|
|
146
|
+
// Different config sources based on environment
|
|
147
|
+
const configSource = {
|
|
148
|
+
...process.env,
|
|
149
|
+
// Override with environment-specific values
|
|
150
|
+
...(env === 'production'
|
|
151
|
+
? {
|
|
152
|
+
LOG_LEVEL: 'error',
|
|
153
|
+
DEBUG: 'false',
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
LOG_LEVEL: 'debug',
|
|
157
|
+
DEBUG: 'true',
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const parser = new EnvironmentParser(configSource);
|
|
162
|
+
|
|
163
|
+
const config = parser.create((get) => ({
|
|
164
|
+
env: get('NODE_ENV')
|
|
165
|
+
.enum(['development', 'staging', 'production'])
|
|
166
|
+
.default('development'),
|
|
167
|
+
logging: {
|
|
168
|
+
level: get('LOG_LEVEL').enum([
|
|
169
|
+
'trace',
|
|
170
|
+
'debug',
|
|
171
|
+
'info',
|
|
172
|
+
'warn',
|
|
173
|
+
'error',
|
|
174
|
+
'fatal',
|
|
175
|
+
]),
|
|
176
|
+
pretty: get('LOG_PRETTY')
|
|
177
|
+
.string()
|
|
178
|
+
.transform((v) => v === 'true')
|
|
179
|
+
.default(env !== 'production'),
|
|
180
|
+
debug: get('DEBUG')
|
|
181
|
+
.string()
|
|
182
|
+
.transform((v) => v === 'true'),
|
|
183
|
+
},
|
|
184
|
+
server: {
|
|
185
|
+
host: get('HOST').string().default('0.0.0.0'),
|
|
186
|
+
port: get('PORT')
|
|
187
|
+
.string()
|
|
188
|
+
.transform(Number)
|
|
189
|
+
.default(env === 'production' ? 80 : 3000),
|
|
190
|
+
cors: {
|
|
191
|
+
enabled: get('CORS_ENABLED')
|
|
192
|
+
.string()
|
|
193
|
+
.transform((v) => v === 'true')
|
|
194
|
+
.default(true),
|
|
195
|
+
origins: get('CORS_ORIGINS')
|
|
196
|
+
.string()
|
|
197
|
+
.transform((origins) => origins.split(',').map((o) => o.trim()))
|
|
198
|
+
.refine((origins) => origins.every((o) => o.startsWith('http')), {
|
|
199
|
+
message: 'All CORS origins must be valid URLs',
|
|
200
|
+
})
|
|
201
|
+
.default(['http://localhost:3000']),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
return config.parse();
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// Example 7: Error handling
|
|
210
210
|
export function errorHandlingExample() {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
211
|
+
try {
|
|
212
|
+
const config = parser
|
|
213
|
+
.create((get) => ({
|
|
214
|
+
required: {
|
|
215
|
+
apiKey: get('API_KEY').string().min(32),
|
|
216
|
+
databaseUrl: get('DATABASE_URL').string().url(),
|
|
217
|
+
adminEmail: get('ADMIN_EMAIL').string().email(),
|
|
218
|
+
},
|
|
219
|
+
optional: {
|
|
220
|
+
sentryDsn: get('SENTRY_DSN').string().url().optional(),
|
|
221
|
+
slackWebhook: get('SLACK_WEBHOOK').string().url().optional(),
|
|
222
|
+
},
|
|
223
|
+
}))
|
|
224
|
+
.parse();
|
|
225
|
+
|
|
226
|
+
return config;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof z.ZodError) {
|
|
229
|
+
error.errors.forEach((err) => {
|
|
230
|
+
const _path = err.path.join('.');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// In a real app, you might want to exit
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
242
238
|
}
|
|
243
239
|
|
|
244
240
|
// Example 8: Using with dotenv
|
|
245
241
|
export function dotenvExample() {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
242
|
+
// Load .env file
|
|
243
|
+
require('dotenv').config();
|
|
244
|
+
|
|
245
|
+
const config = parser.create((get) => ({
|
|
246
|
+
app: {
|
|
247
|
+
name: get('APP_NAME').string(),
|
|
248
|
+
version: get('APP_VERSION')
|
|
249
|
+
.string()
|
|
250
|
+
.regex(/^\d+\.\d+\.\d+$/),
|
|
251
|
+
description: get('APP_DESCRIPTION').string().optional(),
|
|
252
|
+
},
|
|
253
|
+
secrets: {
|
|
254
|
+
jwtSecret: get('JWT_SECRET').string().min(64),
|
|
255
|
+
encryptionKey: get('ENCRYPTION_KEY').string().length(32),
|
|
256
|
+
apiKeys: get('API_KEYS')
|
|
257
|
+
.string()
|
|
258
|
+
.transform((keys) => keys.split(',').map((k) => k.trim()))
|
|
259
|
+
.pipe(z.array(z.string().min(32))),
|
|
260
|
+
},
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
return config.parse();
|
|
268
264
|
}
|
|
269
265
|
|
|
270
266
|
// Example 9: Custom transformations
|
|
271
267
|
export function customTransformationsExample() {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
268
|
+
const config = parser.create((get) => ({
|
|
269
|
+
// Parse JSON
|
|
270
|
+
features: get('FEATURES_JSON')
|
|
271
|
+
.string()
|
|
272
|
+
.transform((str) => JSON.parse(str))
|
|
273
|
+
.pipe(z.record(z.boolean())),
|
|
274
|
+
|
|
275
|
+
// Parse duration strings
|
|
276
|
+
timeouts: {
|
|
277
|
+
request: get('TIMEOUT_REQUEST')
|
|
278
|
+
.string()
|
|
279
|
+
.transform(parseDuration)
|
|
280
|
+
.default('30s'),
|
|
281
|
+
idle: get('TIMEOUT_IDLE').string().transform(parseDuration).default('5m'),
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// Parse memory sizes
|
|
285
|
+
limits: {
|
|
286
|
+
memory: get('MEMORY_LIMIT')
|
|
287
|
+
.string()
|
|
288
|
+
.transform(parseMemorySize)
|
|
289
|
+
.default('512MB'),
|
|
290
|
+
upload: get('UPLOAD_LIMIT')
|
|
291
|
+
.string()
|
|
292
|
+
.transform(parseMemorySize)
|
|
293
|
+
.default('10MB'),
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
// Complex array parsing
|
|
297
|
+
allowedDomains: get('ALLOWED_DOMAINS')
|
|
298
|
+
.string()
|
|
299
|
+
.transform((domains) =>
|
|
300
|
+
domains
|
|
301
|
+
.split(',')
|
|
302
|
+
.map((d) => d.trim())
|
|
303
|
+
.filter(Boolean),
|
|
304
|
+
)
|
|
305
|
+
.pipe(z.array(z.string().regex(/^[a-z0-9.-]+$/i))),
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
return config.parse();
|
|
313
309
|
}
|
|
314
310
|
|
|
315
311
|
// Helper functions for custom transformations
|
|
316
312
|
function parseDuration(duration: string): number {
|
|
317
|
-
|
|
318
|
-
|
|
313
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)$/);
|
|
314
|
+
if (!match) throw new Error(`Invalid duration: ${duration}`);
|
|
319
315
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
316
|
+
const [, value, unit] = match;
|
|
317
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000 };
|
|
318
|
+
return parseInt(value, 10) * multipliers[unit as keyof typeof multipliers];
|
|
323
319
|
}
|
|
324
320
|
|
|
325
321
|
function parseMemorySize(size: string): number {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
322
|
+
const match = size.match(/^(\d+)(B|KB|MB|GB)$/i);
|
|
323
|
+
if (!match) throw new Error(`Invalid memory size: ${size}`);
|
|
324
|
+
|
|
325
|
+
const [, value, unit] = match;
|
|
326
|
+
const multipliers = { B: 1, KB: 1024, MB: 1048576, GB: 1073741824 };
|
|
327
|
+
return (
|
|
328
|
+
parseInt(value, 10) *
|
|
329
|
+
multipliers[unit.toUpperCase() as keyof typeof multipliers]
|
|
330
|
+
);
|
|
335
331
|
}
|
|
336
332
|
|
|
337
333
|
// Example 10: Type-safe configuration module
|
|
338
334
|
// config.ts - This is how you'd typically use it in a real app
|
|
339
335
|
export const loadConfig = () => {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
336
|
+
const parser = new EnvironmentParser(process.env as any);
|
|
337
|
+
|
|
338
|
+
return parser
|
|
339
|
+
.create((get) => ({
|
|
340
|
+
app: {
|
|
341
|
+
name: get('APP_NAME').string().default('My Application'),
|
|
342
|
+
env: get('NODE_ENV')
|
|
343
|
+
.enum(['development', 'staging', 'production'])
|
|
344
|
+
.default('development'),
|
|
345
|
+
port: get('PORT').string().transform(Number).default(3000),
|
|
346
|
+
host: get('HOST').string().default('localhost'),
|
|
347
|
+
},
|
|
348
|
+
database: {
|
|
349
|
+
url: get('DATABASE_URL').string().url(),
|
|
350
|
+
maxConnections: get('DB_MAX_CONNECTIONS')
|
|
351
|
+
.string()
|
|
352
|
+
.transform(Number)
|
|
353
|
+
.default(10),
|
|
354
|
+
ssl: get('DB_SSL')
|
|
355
|
+
.string()
|
|
356
|
+
.transform((v) => v === 'true')
|
|
357
|
+
.default(false),
|
|
358
|
+
},
|
|
359
|
+
redis: {
|
|
360
|
+
url: get('REDIS_URL').string().url().optional(),
|
|
361
|
+
ttl: get('REDIS_TTL').string().transform(Number).default(3600),
|
|
362
|
+
},
|
|
363
|
+
auth: {
|
|
364
|
+
jwtSecret: get('JWT_SECRET').string().min(32),
|
|
365
|
+
jwtExpiry: get('JWT_EXPIRY').string().default('7d'),
|
|
366
|
+
bcryptRounds: get('BCRYPT_ROUNDS')
|
|
367
|
+
.string()
|
|
368
|
+
.transform(Number)
|
|
369
|
+
.default(10),
|
|
370
|
+
},
|
|
371
|
+
features: {
|
|
372
|
+
signups: get('FEATURE_SIGNUPS')
|
|
373
|
+
.string()
|
|
374
|
+
.transform((v) => v === 'true')
|
|
375
|
+
.default(true),
|
|
376
|
+
subscriptions: get('FEATURE_SUBSCRIPTIONS')
|
|
377
|
+
.string()
|
|
378
|
+
.transform((v) => v === 'true')
|
|
379
|
+
.default(false),
|
|
380
|
+
},
|
|
381
|
+
}))
|
|
382
|
+
.parse();
|
|
387
383
|
};
|
|
388
384
|
|
|
389
385
|
// Export the config for use throughout the app
|