zenvx 0.1.0 → 0.1.3
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 +13 -6
- package/dist/index.cjs +65 -41
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +65 -41
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -26,6 +26,14 @@ Standard Zod is great, but environment variables are always strings. This leads
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- ✅ Type-safe environment variables
|
|
32
|
+
- ✅ Smart coercion for numbers, booleans, and ports
|
|
33
|
+
- ✅ Strict validation for strings, URLs, emails, JSON, enums
|
|
34
|
+
- ✅ Beautiful, human-readable error reporting
|
|
35
|
+
- ✅ Seamless TypeScript integration
|
|
36
|
+
|
|
29
37
|
## Installation
|
|
30
38
|
|
|
31
39
|
```bash
|
|
@@ -42,7 +50,7 @@ yarn add zenvx zod
|
|
|
42
50
|
|
|
43
51
|
Create a file (e.g., src/env.ts) and export your configuration:
|
|
44
52
|
|
|
45
|
-
```
|
|
53
|
+
```ts
|
|
46
54
|
import { defineEnv, tx } from "zenvx";
|
|
47
55
|
|
|
48
56
|
export const env = defineEnv({
|
|
@@ -61,7 +69,7 @@ export const env = defineEnv({
|
|
|
61
69
|
|
|
62
70
|
Now use it anywhere in your app:
|
|
63
71
|
|
|
64
|
-
```
|
|
72
|
+
```ts
|
|
65
73
|
import { env } from "./env";
|
|
66
74
|
|
|
67
75
|
console.log(`Server running on port ${env.PORT}`);
|
|
@@ -95,13 +103,12 @@ DATABASE_URL: Must be a valid URL
|
|
|
95
103
|
| `tx.email()` | Valid email address. | `"admin@app.com"` | `"admin@..."` |
|
|
96
104
|
| `tx.json()` | Parses a JSON string into an Object. | `{"foo":"bar"}` | `{foo: "bar"}` |
|
|
97
105
|
| `tx.enum([...])` | Strict allow-list. | `"PROD"` | `"PROD"` |
|
|
98
|
-
| `tx.ip()` | Validates IPv4 format. | `"192.168.1.1"` | `"192..."` |
|
|
99
106
|
|
|
100
107
|
## Customizing Error Messages
|
|
101
108
|
|
|
102
109
|
Every tx validator accepts an optional custom error message.
|
|
103
110
|
|
|
104
|
-
```
|
|
111
|
+
```ts
|
|
105
112
|
export const env = defineEnv({
|
|
106
113
|
API_KEY: tx.string("Please provide a REAL API Key, not just numbers!"),
|
|
107
114
|
PORT: tx.port("Port is invalid or out of range"),
|
|
@@ -114,7 +121,7 @@ Custom .env Path
|
|
|
114
121
|
|
|
115
122
|
By default, zenvx looks for .env in your project root. You can change this:
|
|
116
123
|
|
|
117
|
-
```
|
|
124
|
+
```ts
|
|
118
125
|
export const env = defineEnv(
|
|
119
126
|
{
|
|
120
127
|
PORT: tx.port(),
|
|
@@ -129,7 +136,7 @@ export const env = defineEnv(
|
|
|
129
136
|
|
|
130
137
|
You can mix tx helpers with standard Zod schemas if you need specific logic.
|
|
131
138
|
|
|
132
|
-
```
|
|
139
|
+
```ts
|
|
133
140
|
import { defineEnv, tx } from "zenvx";
|
|
134
141
|
import { z } from "zod";
|
|
135
142
|
|
package/dist/index.cjs
CHANGED
|
@@ -95,71 +95,95 @@ var tx = {
|
|
|
95
95
|
* Rejects purely numeric strings (e.g. "12345").
|
|
96
96
|
* Good for API Keys.
|
|
97
97
|
*/
|
|
98
|
-
string: (message) =>
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
string: (message) => {
|
|
99
|
+
return import_zod2.z.string().refine((val) => !/^\d+$/.test(val), {
|
|
100
|
+
error: message || "Value should be text, but looks like a number."
|
|
101
|
+
});
|
|
102
|
+
},
|
|
101
103
|
/**
|
|
102
104
|
* SMART NUMBER:
|
|
103
105
|
* Automatically converts "3000" -> 3000.
|
|
104
106
|
* Fails if the value is not a valid number (e.g. "abc").
|
|
105
107
|
*/
|
|
106
|
-
number: (message) =>
|
|
108
|
+
number: (message) => {
|
|
109
|
+
return import_zod2.z.coerce.number({ error: message || "Must be a number" });
|
|
110
|
+
},
|
|
107
111
|
/**
|
|
108
112
|
* PORT VALIDATOR:
|
|
109
113
|
* Coerces to number and ensures it is between 1 and 65535.
|
|
110
114
|
*/
|
|
111
|
-
port: (message) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}),
|
|
115
|
+
port: (message) => {
|
|
116
|
+
return import_zod2.z.coerce.number({
|
|
117
|
+
error: message || "Must be a valid port (1-65535)"
|
|
118
|
+
}).min(1, { error: message || "Port must be >= 1" }).max(65535, { error: message || "Port must be <= 65535" });
|
|
119
|
+
},
|
|
117
120
|
/**
|
|
118
121
|
* SMART BOOLEAN:
|
|
119
122
|
* Handles "true", "TRUE", "1" -> true
|
|
120
123
|
* Handles "false", "FALSE", "0" -> false
|
|
121
124
|
* Throws error on anything else.
|
|
122
125
|
*/
|
|
123
|
-
bool: (message) =>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
bool: (message) => {
|
|
127
|
+
return import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()]).transform((val, ctx) => {
|
|
128
|
+
if (typeof val === "boolean") return val;
|
|
129
|
+
const v = val.toLowerCase();
|
|
130
|
+
if (v === "true" || v === "1") return true;
|
|
131
|
+
if (v === "false" || v === "0") return false;
|
|
132
|
+
ctx.addIssue({
|
|
133
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
134
|
+
message: message || 'Must be a boolean ("true"/"false"/"1"/"0")'
|
|
135
|
+
});
|
|
136
|
+
return import_zod2.z.NEVER;
|
|
130
137
|
});
|
|
131
|
-
|
|
132
|
-
}),
|
|
138
|
+
},
|
|
133
139
|
/**
|
|
134
140
|
* URL:
|
|
135
141
|
* Strict URL checking.
|
|
136
142
|
*/
|
|
137
|
-
url: (message) =>
|
|
143
|
+
url: (message) => {
|
|
144
|
+
return import_zod2.z.url({
|
|
145
|
+
error: message || "Must be a valid URL (e.g. https://...)"
|
|
146
|
+
});
|
|
147
|
+
},
|
|
138
148
|
/**
|
|
139
149
|
* EMAIL:
|
|
140
150
|
* Strict Email checking.
|
|
141
151
|
*/
|
|
142
|
-
email: (message) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
email: (message) => {
|
|
153
|
+
return import_zod2.z.email({ error: message || "Must be a valid email address." });
|
|
154
|
+
},
|
|
155
|
+
positiveNumber: (message) => {
|
|
156
|
+
return import_zod2.z.coerce.number().gt(0, { message: message || "Must be > 0" });
|
|
157
|
+
},
|
|
158
|
+
nonEmptyString: (message) => {
|
|
159
|
+
return import_zod2.z.string().trim().min(1, { message: message || "Cannot be empty" });
|
|
160
|
+
},
|
|
161
|
+
semver: (message) => {
|
|
162
|
+
return import_zod2.z.string().refine((val) => /^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$/.test(val), {
|
|
163
|
+
error: message || "Must be valid semver"
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
path: (message) => {
|
|
167
|
+
return import_zod2.z.string().min(1, { error: message || "Must be a valid path" });
|
|
168
|
+
},
|
|
169
|
+
enum: (values, message) => {
|
|
170
|
+
return import_zod2.z.string().refine((val) => values.includes(val), {
|
|
171
|
+
error: message || `Must be one of: ${values.join(", ")}`
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
json: (message) => {
|
|
175
|
+
return import_zod2.z.string().transform((val, ctx) => {
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(val);
|
|
178
|
+
} catch {
|
|
179
|
+
ctx.addIssue({
|
|
180
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
181
|
+
message: message || "Must be valid JSON"
|
|
182
|
+
});
|
|
183
|
+
return import_zod2.z.NEVER;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
163
187
|
};
|
|
164
188
|
function defineEnv(shape, options) {
|
|
165
189
|
const fileEnv = loadDotEnv(options?.path);
|
package/dist/index.d.cts
CHANGED
|
@@ -20,14 +20,14 @@ declare const tx: {
|
|
|
20
20
|
* PORT VALIDATOR:
|
|
21
21
|
* Coerces to number and ensures it is between 1 and 65535.
|
|
22
22
|
*/
|
|
23
|
-
port: (message?: string) => z.
|
|
23
|
+
port: (message?: string) => z.ZodCoercedNumber<unknown>;
|
|
24
24
|
/**
|
|
25
25
|
* SMART BOOLEAN:
|
|
26
26
|
* Handles "true", "TRUE", "1" -> true
|
|
27
27
|
* Handles "false", "FALSE", "0" -> false
|
|
28
28
|
* Throws error on anything else.
|
|
29
29
|
*/
|
|
30
|
-
bool: (message?: string) => z.ZodPipe<z.ZodString, z.ZodTransform<boolean, string>>;
|
|
30
|
+
bool: (message?: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodBoolean]>, z.ZodTransform<boolean, string | boolean>>;
|
|
31
31
|
/**
|
|
32
32
|
* URL:
|
|
33
33
|
* Strict URL checking.
|
package/dist/index.d.ts
CHANGED
|
@@ -20,14 +20,14 @@ declare const tx: {
|
|
|
20
20
|
* PORT VALIDATOR:
|
|
21
21
|
* Coerces to number and ensures it is between 1 and 65535.
|
|
22
22
|
*/
|
|
23
|
-
port: (message?: string) => z.
|
|
23
|
+
port: (message?: string) => z.ZodCoercedNumber<unknown>;
|
|
24
24
|
/**
|
|
25
25
|
* SMART BOOLEAN:
|
|
26
26
|
* Handles "true", "TRUE", "1" -> true
|
|
27
27
|
* Handles "false", "FALSE", "0" -> false
|
|
28
28
|
* Throws error on anything else.
|
|
29
29
|
*/
|
|
30
|
-
bool: (message?: string) => z.ZodPipe<z.ZodString, z.ZodTransform<boolean, string>>;
|
|
30
|
+
bool: (message?: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodBoolean]>, z.ZodTransform<boolean, string | boolean>>;
|
|
31
31
|
/**
|
|
32
32
|
* URL:
|
|
33
33
|
* Strict URL checking.
|
package/dist/index.js
CHANGED
|
@@ -60,71 +60,95 @@ var tx = {
|
|
|
60
60
|
* Rejects purely numeric strings (e.g. "12345").
|
|
61
61
|
* Good for API Keys.
|
|
62
62
|
*/
|
|
63
|
-
string: (message) =>
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
string: (message) => {
|
|
64
|
+
return z2.string().refine((val) => !/^\d+$/.test(val), {
|
|
65
|
+
error: message || "Value should be text, but looks like a number."
|
|
66
|
+
});
|
|
67
|
+
},
|
|
66
68
|
/**
|
|
67
69
|
* SMART NUMBER:
|
|
68
70
|
* Automatically converts "3000" -> 3000.
|
|
69
71
|
* Fails if the value is not a valid number (e.g. "abc").
|
|
70
72
|
*/
|
|
71
|
-
number: (message) =>
|
|
73
|
+
number: (message) => {
|
|
74
|
+
return z2.coerce.number({ error: message || "Must be a number" });
|
|
75
|
+
},
|
|
72
76
|
/**
|
|
73
77
|
* PORT VALIDATOR:
|
|
74
78
|
* Coerces to number and ensures it is between 1 and 65535.
|
|
75
79
|
*/
|
|
76
|
-
port: (message) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}),
|
|
80
|
+
port: (message) => {
|
|
81
|
+
return z2.coerce.number({
|
|
82
|
+
error: message || "Must be a valid port (1-65535)"
|
|
83
|
+
}).min(1, { error: message || "Port must be >= 1" }).max(65535, { error: message || "Port must be <= 65535" });
|
|
84
|
+
},
|
|
82
85
|
/**
|
|
83
86
|
* SMART BOOLEAN:
|
|
84
87
|
* Handles "true", "TRUE", "1" -> true
|
|
85
88
|
* Handles "false", "FALSE", "0" -> false
|
|
86
89
|
* Throws error on anything else.
|
|
87
90
|
*/
|
|
88
|
-
bool: (message) =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
bool: (message) => {
|
|
92
|
+
return z2.union([z2.string(), z2.boolean()]).transform((val, ctx) => {
|
|
93
|
+
if (typeof val === "boolean") return val;
|
|
94
|
+
const v = val.toLowerCase();
|
|
95
|
+
if (v === "true" || v === "1") return true;
|
|
96
|
+
if (v === "false" || v === "0") return false;
|
|
97
|
+
ctx.addIssue({
|
|
98
|
+
code: z2.ZodIssueCode.custom,
|
|
99
|
+
message: message || 'Must be a boolean ("true"/"false"/"1"/"0")'
|
|
100
|
+
});
|
|
101
|
+
return z2.NEVER;
|
|
95
102
|
});
|
|
96
|
-
|
|
97
|
-
}),
|
|
103
|
+
},
|
|
98
104
|
/**
|
|
99
105
|
* URL:
|
|
100
106
|
* Strict URL checking.
|
|
101
107
|
*/
|
|
102
|
-
url: (message) =>
|
|
108
|
+
url: (message) => {
|
|
109
|
+
return z2.url({
|
|
110
|
+
error: message || "Must be a valid URL (e.g. https://...)"
|
|
111
|
+
});
|
|
112
|
+
},
|
|
103
113
|
/**
|
|
104
114
|
* EMAIL:
|
|
105
115
|
* Strict Email checking.
|
|
106
116
|
*/
|
|
107
|
-
email: (message) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
email: (message) => {
|
|
118
|
+
return z2.email({ error: message || "Must be a valid email address." });
|
|
119
|
+
},
|
|
120
|
+
positiveNumber: (message) => {
|
|
121
|
+
return z2.coerce.number().gt(0, { message: message || "Must be > 0" });
|
|
122
|
+
},
|
|
123
|
+
nonEmptyString: (message) => {
|
|
124
|
+
return z2.string().trim().min(1, { message: message || "Cannot be empty" });
|
|
125
|
+
},
|
|
126
|
+
semver: (message) => {
|
|
127
|
+
return z2.string().refine((val) => /^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$/.test(val), {
|
|
128
|
+
error: message || "Must be valid semver"
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
path: (message) => {
|
|
132
|
+
return z2.string().min(1, { error: message || "Must be a valid path" });
|
|
133
|
+
},
|
|
134
|
+
enum: (values, message) => {
|
|
135
|
+
return z2.string().refine((val) => values.includes(val), {
|
|
136
|
+
error: message || `Must be one of: ${values.join(", ")}`
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
json: (message) => {
|
|
140
|
+
return z2.string().transform((val, ctx) => {
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(val);
|
|
143
|
+
} catch {
|
|
144
|
+
ctx.addIssue({
|
|
145
|
+
code: z2.ZodIssueCode.custom,
|
|
146
|
+
message: message || "Must be valid JSON"
|
|
147
|
+
});
|
|
148
|
+
return z2.NEVER;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
128
152
|
};
|
|
129
153
|
function defineEnv(shape, options) {
|
|
130
154
|
const fileEnv = loadDotEnv(options?.path);
|
package/package.json
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zenvx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"env",
|
|
10
|
+
"environment-variables",
|
|
11
|
+
"configuration",
|
|
12
|
+
"config",
|
|
13
|
+
"dotenv",
|
|
14
|
+
"zod",
|
|
15
|
+
"typescript",
|
|
16
|
+
"validation",
|
|
17
|
+
"schema",
|
|
18
|
+
"type-safe",
|
|
19
|
+
"strict-env",
|
|
20
|
+
"parser",
|
|
21
|
+
"process.env"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
8
24
|
"author": {
|
|
9
25
|
"name": "Sean Wilfred T. Custodio",
|
|
10
26
|
"email": "custodiocsean@gmail.com"
|