@geekmidas/envkit 0.0.1

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 ADDED
@@ -0,0 +1,295 @@
1
+ # @geekmidas/envkit
2
+
3
+ A type-safe environment configuration parser that uses Zod schemas to validate and parse environment variables or configuration objects.
4
+
5
+ ## Features
6
+
7
+ - 🔒 **Type-safe**: Full TypeScript support with automatic type inference
8
+ - ✅ **Schema validation**: Uses Zod schemas for robust validation
9
+ - 🏗️ **Nested configuration**: Support for complex, nested configuration structures
10
+ - 🚨 **Error aggregation**: Collects and reports all validation errors at once
11
+ - 🔍 **Path-based access**: Uses lodash's `get` and `set` for deep object access
12
+ - 🎯 **Zero dependencies**: Only depends on Zod and minimal lodash utilities
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @geekmidas/envkit zod
18
+ # or
19
+ yarn add @geekmidas/envkit zod
20
+ # or
21
+ pnpm add @geekmidas/envkit zod
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { EnvironmentParser } from '@geekmidas/envkit';
28
+ import { z } from 'zod';
29
+
30
+ // Create parser with your config object (e.g., process.env)
31
+ const parser = new EnvironmentParser(process.env);
32
+
33
+ // Define your configuration schema
34
+ const config = parser.create((get) => ({
35
+ port: get('PORT').string().transform(Number).default(3000),
36
+ database: {
37
+ host: get('DATABASE_HOST').string(),
38
+ port: get('DATABASE_PORT').string().transform(Number),
39
+ name: get('DATABASE_NAME').string(),
40
+ user: get('DATABASE_USER').string(),
41
+ password: get('DATABASE_PASSWORD').string()
42
+ },
43
+ api: {
44
+ key: get('API_KEY').string().min(32),
45
+ url: get('API_URL').url(),
46
+ timeout: get('API_TIMEOUT').string().transform(Number).default(5000)
47
+ }
48
+ }));
49
+
50
+ // Parse and validate
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();
76
+ ```
77
+
78
+ ### Nested Configuration
79
+
80
+ Create deeply nested configuration structures:
81
+
82
+ ```typescript
83
+ const config = parser.create((get) => ({
84
+ server: {
85
+ host: get('SERVER_HOST').string().default('localhost'),
86
+ port: get('SERVER_PORT').string().transform(Number).default(3000),
87
+ ssl: {
88
+ enabled: get('SSL_ENABLED').string().transform(v => v === 'true'),
89
+ 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
+ }
100
+ }
101
+ }));
102
+ ```
103
+
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
+ ### Error Handling
150
+
151
+ The parser aggregates all validation errors and throws a single `ZodError`:
152
+
153
+ ```typescript
154
+ try {
155
+ const config = parser.create((get) => ({
156
+ required1: get('MISSING_VAR_1').string(),
157
+ required2: get('MISSING_VAR_2').string(),
158
+ invalid: get('INVALID_NUMBER').string().transform(Number)
159
+ })).parse();
160
+ } catch (error) {
161
+ if (error instanceof z.ZodError) {
162
+ console.error('Configuration errors:');
163
+ error.errors.forEach(err => {
164
+ console.error(`- ${err.path.join('.')}: ${err.message}`);
165
+ });
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### Type Safety
171
+
172
+ The parsed configuration is fully typed:
173
+
174
+ ```typescript
175
+ const config = parser.create((get) => ({
176
+ port: get('PORT').string().transform(Number),
177
+ features: {
178
+ auth: get('FEATURE_AUTH').string().transform(v => v === 'true')
179
+ }
180
+ }));
181
+
182
+ const parsed = config.parse();
183
+ // TypeScript knows: parsed.port is number, parsed.features.auth is boolean
184
+ ```
185
+
186
+ ## API Reference
187
+
188
+ ### `EnvironmentParser`
189
+
190
+ The main class for creating configuration parsers.
191
+
192
+ #### Constructor
193
+
194
+ ```typescript
195
+ new EnvironmentParser(config: Record<string, unknown>)
196
+ ```
197
+
198
+ - `config`: The configuration object to parse (typically `process.env`)
199
+
200
+ #### Methods
201
+
202
+ ##### `create<T>(schemaBuilder: (get: GetFunction) => T): ConfigParser<T>`
203
+
204
+ Creates a configuration parser with the specified schema.
205
+
206
+ - `schemaBuilder`: A function that receives a `get` function and returns the schema definition
207
+ - Returns: A `ConfigParser` instance
208
+
209
+ ### `ConfigParser`
210
+
211
+ The configuration parser returned by `EnvironmentParser.create()`.
212
+
213
+ #### Methods
214
+
215
+ ##### `parse(): T`
216
+
217
+ Parses and validates the configuration.
218
+
219
+ - Returns: The parsed configuration object
220
+ - Throws: `ZodError` if validation fails
221
+
222
+ ### `GetFunction`
223
+
224
+ The function passed to the schema builder for accessing configuration values.
225
+
226
+ ```typescript
227
+ type GetFunction = (path: string) => ZodTypeAny
228
+ ```
229
+
230
+ - `path`: The path to the configuration value (supports nested paths with dots)
231
+ - Returns: A Zod schema that will be used to validate the value at the specified path
232
+
233
+ ## Best Practices
234
+
235
+ 1. **Define configuration at startup**: Parse your configuration once at application startup and export the result:
236
+
237
+ ```typescript
238
+ // config.ts
239
+ import { EnvironmentParser } from '@geekmidas/envkit';
240
+ import { z } from 'zod';
241
+
242
+ const parser = new EnvironmentParser(process.env);
243
+
244
+ export const config = parser.create((get) => ({
245
+ // ... your schema
246
+ })).parse();
247
+
248
+ // Other files can import the typed config
249
+ import { config } from './config';
250
+ ```
251
+
252
+ 2. **Use meaningful defaults**: Provide sensible defaults for optional configuration:
253
+
254
+ ```typescript
255
+ const config = parser.create((get) => ({
256
+ port: get('PORT').string().transform(Number).default(3000),
257
+ logLevel: get('LOG_LEVEL').enum(['debug', 'info', 'warn', 'error']).default('info')
258
+ }));
259
+ ```
260
+
261
+ 3. **Group related configuration**: Organize your configuration into logical groups:
262
+
263
+ ```typescript
264
+ const config = parser.create((get) => ({
265
+ server: { /* server config */ },
266
+ database: { /* database config */ },
267
+ features: { /* feature flags */ },
268
+ thirdParty: { /* external service config */ }
269
+ }));
270
+ ```
271
+
272
+ 4. **Document your configuration**: Add comments to explain complex validations:
273
+
274
+ ```typescript
275
+ const config = parser.create((get) => ({
276
+ // Maximum number of concurrent connections
277
+ maxConnections: get('MAX_CONNECTIONS')
278
+ .string()
279
+ .transform(Number)
280
+ .int()
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
+ }));
291
+ ```
292
+
293
+ ## License
294
+
295
+ MIT
@@ -0,0 +1,99 @@
1
+ import get from "lodash.get";
2
+ import set from "lodash.set";
3
+ import { z } from "zod/v4";
4
+
5
+ //#region src/EnvironmentParser.ts
6
+ var ConfigParser = class {
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ /**
11
+ * Parses the config object and validates it against the Zod schemas
12
+ * @returns The parsed config object
13
+ */
14
+ parse() {
15
+ const errors = [];
16
+ const parseDeep = (config, path = []) => {
17
+ const result = {};
18
+ if (config && typeof config !== "object") return config;
19
+ for (const key in config) {
20
+ const schema = config[key];
21
+ const currentPath = [...path, key];
22
+ if (schema instanceof z.ZodType) {
23
+ const parsed = schema.safeParse(void 0);
24
+ if (parsed.success) set(result, key, parsed.data);
25
+ else errors.push(...parsed.error.issues.map((issue) => ({
26
+ ...issue,
27
+ path: [...currentPath, ...issue.path]
28
+ })));
29
+ } else if (schema) set(result, key, parseDeep(schema, currentPath));
30
+ }
31
+ return result;
32
+ };
33
+ const parsedConfig = parseDeep(this.config);
34
+ if (errors.length > 0) throw new z.ZodError(errors);
35
+ return parsedConfig;
36
+ }
37
+ };
38
+ var EnvironmentParser = class {
39
+ constructor(config) {
40
+ this.config = config;
41
+ }
42
+ getZodGetter = (name) => {
43
+ return new Proxy({ ...z }, { get: (target, prop) => {
44
+ const func = target[prop];
45
+ if (typeof func === "function") return (...args) => {
46
+ const schema = func(...args);
47
+ const originalParse = schema.parse;
48
+ const originalSafeParse = schema.safeParse;
49
+ schema.parse = () => {
50
+ const value = get(this.config, name);
51
+ try {
52
+ return originalParse.call(schema, value);
53
+ } catch (error) {
54
+ if (error instanceof z.ZodError) {
55
+ const modifiedIssues = error.issues.map((issue) => ({
56
+ ...issue,
57
+ message: `Environment variable "${name}": ${issue.message}`,
58
+ path: [name, ...issue.path]
59
+ }));
60
+ throw new z.ZodError(modifiedIssues);
61
+ }
62
+ throw error;
63
+ }
64
+ };
65
+ schema.safeParse = () => {
66
+ const value = get(this.config, name);
67
+ const result = originalSafeParse.call(schema, value);
68
+ if (!result.success) {
69
+ const modifiedIssues = result.error.issues.map((issue) => ({
70
+ ...issue,
71
+ message: `Environment variable "${name}": ${issue.message}`,
72
+ path: [name, ...issue.path]
73
+ }));
74
+ return {
75
+ success: false,
76
+ error: new z.ZodError(modifiedIssues)
77
+ };
78
+ }
79
+ return result;
80
+ };
81
+ return schema;
82
+ };
83
+ return func;
84
+ } });
85
+ };
86
+ /**
87
+ * Creates a new JordConfigParser object that can be used to parse the config object
88
+ *
89
+ * @param builder - A function that takes a getter function and returns a config object
90
+ * @returns A JordConfigParser object that can be used to parse the config object
91
+ */
92
+ create(builder) {
93
+ const config = builder(this.getZodGetter);
94
+ return new ConfigParser(config);
95
+ }
96
+ };
97
+
98
+ //#endregion
99
+ export { ConfigParser, EnvironmentParser };
@@ -0,0 +1,133 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const lodash_get = __toESM(require("lodash.get"));
25
+ const lodash_set = __toESM(require("lodash.set"));
26
+ const zod_v4 = __toESM(require("zod/v4"));
27
+
28
+ //#region src/EnvironmentParser.ts
29
+ var ConfigParser = class {
30
+ constructor(config) {
31
+ this.config = config;
32
+ }
33
+ /**
34
+ * Parses the config object and validates it against the Zod schemas
35
+ * @returns The parsed config object
36
+ */
37
+ parse() {
38
+ const errors = [];
39
+ const parseDeep = (config, path = []) => {
40
+ const result = {};
41
+ if (config && typeof config !== "object") return config;
42
+ for (const key in config) {
43
+ const schema = config[key];
44
+ const currentPath = [...path, key];
45
+ if (schema instanceof zod_v4.z.ZodType) {
46
+ const parsed = schema.safeParse(void 0);
47
+ if (parsed.success) (0, lodash_set.default)(result, key, parsed.data);
48
+ else errors.push(...parsed.error.issues.map((issue) => ({
49
+ ...issue,
50
+ path: [...currentPath, ...issue.path]
51
+ })));
52
+ } else if (schema) (0, lodash_set.default)(result, key, parseDeep(schema, currentPath));
53
+ }
54
+ return result;
55
+ };
56
+ const parsedConfig = parseDeep(this.config);
57
+ if (errors.length > 0) throw new zod_v4.z.ZodError(errors);
58
+ return parsedConfig;
59
+ }
60
+ };
61
+ var EnvironmentParser = class {
62
+ constructor(config) {
63
+ this.config = config;
64
+ }
65
+ getZodGetter = (name) => {
66
+ return new Proxy({ ...zod_v4.z }, { get: (target, prop) => {
67
+ const func = target[prop];
68
+ if (typeof func === "function") return (...args) => {
69
+ const schema = func(...args);
70
+ const originalParse = schema.parse;
71
+ const originalSafeParse = schema.safeParse;
72
+ schema.parse = () => {
73
+ const value = (0, lodash_get.default)(this.config, name);
74
+ try {
75
+ return originalParse.call(schema, value);
76
+ } catch (error) {
77
+ if (error instanceof zod_v4.z.ZodError) {
78
+ const modifiedIssues = error.issues.map((issue) => ({
79
+ ...issue,
80
+ message: `Environment variable "${name}": ${issue.message}`,
81
+ path: [name, ...issue.path]
82
+ }));
83
+ throw new zod_v4.z.ZodError(modifiedIssues);
84
+ }
85
+ throw error;
86
+ }
87
+ };
88
+ schema.safeParse = () => {
89
+ const value = (0, lodash_get.default)(this.config, name);
90
+ const result = originalSafeParse.call(schema, value);
91
+ if (!result.success) {
92
+ const modifiedIssues = result.error.issues.map((issue) => ({
93
+ ...issue,
94
+ message: `Environment variable "${name}": ${issue.message}`,
95
+ path: [name, ...issue.path]
96
+ }));
97
+ return {
98
+ success: false,
99
+ error: new zod_v4.z.ZodError(modifiedIssues)
100
+ };
101
+ }
102
+ return result;
103
+ };
104
+ return schema;
105
+ };
106
+ return func;
107
+ } });
108
+ };
109
+ /**
110
+ * Creates a new JordConfigParser object that can be used to parse the config object
111
+ *
112
+ * @param builder - A function that takes a getter function and returns a config object
113
+ * @returns A JordConfigParser object that can be used to parse the config object
114
+ */
115
+ create(builder) {
116
+ const config = builder(this.getZodGetter);
117
+ return new ConfigParser(config);
118
+ }
119
+ };
120
+
121
+ //#endregion
122
+ Object.defineProperty(exports, 'ConfigParser', {
123
+ enumerable: true,
124
+ get: function () {
125
+ return ConfigParser;
126
+ }
127
+ });
128
+ Object.defineProperty(exports, 'EnvironmentParser', {
129
+ enumerable: true,
130
+ get: function () {
131
+ return EnvironmentParser;
132
+ }
133
+ });
@@ -0,0 +1,4 @@
1
+ const require_EnvironmentParser = require('./EnvironmentParser-nZnZXM_Y.cjs');
2
+
3
+ exports.ConfigParser = require_EnvironmentParser.ConfigParser;
4
+ exports.EnvironmentParser = require_EnvironmentParser.EnvironmentParser;
@@ -0,0 +1,3 @@
1
+ import { ConfigParser, EnvironmentParser } from "./EnvironmentParser-Dd6TbwJC.mjs";
2
+
3
+ export { ConfigParser, EnvironmentParser };
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ const require_EnvironmentParser = require('./EnvironmentParser-nZnZXM_Y.cjs');
2
+
3
+ exports.EnvironmentParser = require_EnvironmentParser.EnvironmentParser;
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { EnvironmentParser } from "./EnvironmentParser-Dd6TbwJC.mjs";
2
+
3
+ export { EnvironmentParser };