env-dictionary 1.0.0 → 1.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 CHANGED
@@ -1,4 +1,4 @@
1
- # Dictionary
1
+ # env-dictionary
2
2
 
3
3
  A TypeScript library for type-safe environment variable handling with runtime validation.
4
4
 
@@ -21,37 +21,13 @@ bun install
21
21
 
22
22
  ## Quick Start
23
23
 
24
- ### Using Dictionary (Schema-based)
24
+ ### Using Dictionary (Create a typed env)
25
25
 
26
26
  ```typescript
27
- import { Dictionary } from './lib/dictionary';
28
- import { t } from './lib/t';
27
+ import { Dictionary } from 'env-dictionary';
28
+ import { t } from 'env-dictionary';
29
29
 
30
- const config = new Dictionary({
31
- env: {
32
- API_KEY: 'secret',
33
- PORT: 3000,
34
- DEBUG: true,
35
- },
36
- schema: {
37
- API_KEY: t.string(),
38
- PORT: t.number(),
39
- DEBUG: t.boolean(),
40
- },
41
- });
42
-
43
- console.log(config.values.API_KEY); // 'secret'
44
- console.log(config.values.PORT); // 3000
45
- console.log(config.values.DEBUG); // true
46
- ```
47
-
48
- ### Using DictionaryEnv (Descriptor-based)
49
-
50
- ```typescript
51
- import { DictionaryEnv } from './lib/dictionary';
52
- import { t } from './lib/t';
53
-
54
- const env = new DictionaryEnv([
30
+ const env = new Dictionary([
55
31
  { API_KEY: 'secret', type: t.string() },
56
32
  { PORT: 3000, type: t.number() },
57
33
  { DEBUG: true, type: t.boolean() },
@@ -62,6 +38,35 @@ console.log(env.env.PORT); // 3000
62
38
  console.log(env.env.DEBUG); // true
63
39
  ```
64
40
 
41
+ ### Using DictionaryEnv (Type pre-existing envs)
42
+
43
+ ```typescript
44
+ import { DictionaryEnv } from 'env-dictionary';
45
+
46
+ const typedEnv = new DictionaryEnv({ env: process.env })
47
+
48
+ console.log(typedEnv.env.PORT) // 8080
49
+ console.log(typedEnv.type.PORT) // Number
50
+ ```
51
+
52
+ You can also provide a descriptor-based schema:
53
+
54
+ ```typescript
55
+ import { DictionaryEnv } from 'env-dictionary';
56
+ import { t } from 'env-dictionary';
57
+
58
+ const typedEnv = new DictionaryEnv(
59
+ { env: process.env },
60
+ { schema: [
61
+ { var: 'PORT', type: t.number() },
62
+ { var: 'API_KEY', type: t.string() },
63
+ ]}
64
+ )
65
+
66
+ console.log(typedEnv.env.PORT) // 8080
67
+ console.log(typedEnv.type.PORT) // Number
68
+ ```
69
+
65
70
  ## API Reference
66
71
 
67
72
  ### t
@@ -81,106 +86,135 @@ The `t` object provides type validators for schema definitions.
81
86
 
82
87
  ### Dictionary
83
88
 
84
- A class that validates environment variables against a schema.
89
+ A class that creates a typed environment object from an array of descriptors.
85
90
 
86
91
  #### Constructor
87
92
 
88
93
  ```typescript
89
- new Dictionary<T>(options: DictionaryOptions)
94
+ new Dictionary<TSchema>(schema: TSchema)
90
95
  ```
91
96
 
92
97
  **Parameters:**
93
98
 
94
- - `options.env: Record<string, unknown>` - The environment variables to validate
95
- - `options.schema?: Record<string, SchemaType>` - Optional schema for validation
99
+ - `schema: TSchema[]` - Array of descriptors defining environment variables
96
100
 
97
101
  **Properties:**
98
102
 
99
- - `values: T` - The validated and typed environment variables
103
+ - `env: TEnv` - The typed environment object
100
104
 
101
105
  #### Example
102
106
 
103
107
  ```typescript
104
- const dict = new Dictionary({
105
- env: {
106
- API_KEY: 'secret',
107
- PORT: 3000,
108
- DEBUG: true,
109
- CONFIG: { url: 'https://api.example.com' },
110
- ITEMS: [1, 2, 3],
111
- },
112
- schema: {
113
- API_KEY: t.string(),
114
- PORT: t.number(),
115
- DEBUG: t.boolean(),
116
- CONFIG: t.object(),
117
- ITEMS: t.array(),
118
- },
119
- });
108
+ const env = new Dictionary([
109
+ { API_KEY: 'secret', type: t.string() },
110
+ { PORT: 3000, type: t.number() },
111
+ { DEBUG: true, type: t.boolean() },
112
+ { CONFIG: { url: 'https://api.example.com' }, type: t.object() },
113
+ { ITEMS: [1, 2, 3], type: t.array() },
114
+ ]);
120
115
  ```
121
116
 
117
+ #### Descriptor Format
118
+
119
+ Each descriptor must have:
120
+ - A variable name (key)
121
+ - A value
122
+ - A `type` property with the validator
123
+
122
124
  #### Behavior
123
125
 
124
- - If no schema is provided, all environment variables are included as-is
125
- - If a schema is provided, only keys defined in the schema are included
126
- - Throws an error if a variable fails validation
127
- - Values are automatically typed based on the schema
126
+ - Throws an error if a descriptor is missing a variable name
127
+ - Throws an error if a descriptor is missing a type
128
+ - Throws an error if a value fails validation for its type
129
+ - Types are automatically inferred from the descriptors
128
130
 
129
131
  ### DictionaryEnv
130
132
 
131
- A class that creates a typed environment object from an array of descriptors.
133
+ A class that validates environment variables against a schema.
132
134
 
133
135
  #### Constructor
134
136
 
135
137
  ```typescript
136
- new DictionaryEnv<TSchema>(schema: TSchema)
138
+ new DictionaryEnv<T>(options: DictionaryOptions, descriptorOptions?: { schema: Array<{ var: string; type: SchemaType }> })
137
139
  ```
138
140
 
139
141
  **Parameters:**
140
142
 
141
- - `schema: TSchema[]` - Array of descriptors defining environment variables
143
+ - `options.env: Record<string, unknown>` - The environment variables to validate
144
+ - `options.schema?: Record<string, SchemaType>` - Optional schema for validation (key-based)
145
+ - `descriptorOptions?: { schema: Array<{ var: string; type: SchemaType }> }` - Optional descriptor-based schema
142
146
 
143
147
  **Properties:**
144
148
 
145
- - `env: TEnv` - The typed environment object
149
+ - `env: T` - The validated and typed environment variables
150
+ - `type: { [K in keyof T]: TypeConstructor }` - Type constructors for each variable
146
151
 
147
- #### Example
152
+ #### Examples
153
+
154
+ **Key-based schema:**
148
155
 
149
156
  ```typescript
150
- const env = new DictionaryEnv([
151
- { API_KEY: 'secret', type: t.string() },
152
- { PORT: 3000, type: t.number() },
153
- { DEBUG: true, type: t.boolean() },
154
- { CONFIG: { url: 'https://api.example.com' }, type: t.object() },
155
- { ITEMS: [1, 2, 3], type: t.array() },
156
- ]);
157
+ const typedEnv = new DictionaryEnv({
158
+ env: {
159
+ API_KEY: 'secret',
160
+ PORT: 3000,
161
+ DEBUG: true,
162
+ CONFIG: { url: 'https://api.example.com' },
163
+ ITEMS: [1, 2, 3],
164
+ },
165
+ schema: {
166
+ API_KEY: t.string(),
167
+ PORT: t.number(),
168
+ DEBUG: t.boolean(),
169
+ CONFIG: t.object(),
170
+ ITEMS: t.array(),
171
+ },
172
+ });
157
173
  ```
158
174
 
159
- #### Descriptor Format
175
+ **Descriptor-based schema:**
160
176
 
161
- Each descriptor must have:
162
- - A variable name (key)
163
- - A value
164
- - A `type` property with the validator
177
+ ```typescript
178
+ const typedEnv = new DictionaryEnv(
179
+ { env: process.env },
180
+ { schema: [
181
+ { var: 'PORT', type: t.number() },
182
+ { var: 'API_KEY', type: t.string() },
183
+ ]}
184
+ );
185
+ ```
165
186
 
166
187
  #### Behavior
167
188
 
168
- - Throws an error if a descriptor is missing a variable name
169
- - Throws an error if a descriptor is missing a type
170
- - Throws an error if a value fails validation for its type
171
- - Types are automatically inferred from the descriptors
189
+ - If no schema is provided, all environment variables are included as-is with inferred types
190
+ - If a schema is provided (key-based or descriptor-based), only keys defined in the schema are included
191
+ - Throws an error if a variable fails validation
192
+ - Values are automatically typed based on the schema
172
193
 
173
194
  ## Usage Patterns
174
195
 
175
196
  ### Environment Variables
176
197
 
177
- Commonly used for validating `.env` files:
198
+ Use `Dictionary` to create a new typed environment:
178
199
 
179
200
  ```typescript
180
- import { Dictionary } from './lib/dictionary';
181
- import { t } from './lib/t';
201
+ import { Dictionary } from 'env-dictionary';
202
+ import { t } from 'env-dictionary';
203
+
204
+ const env = new Dictionary([
205
+ { DATABASE_URL: 'postgresql://localhost', type: t.string() },
206
+ { PORT: 5432, type: t.number() },
207
+ { NODE_ENV: 'development', type: t.string() },
208
+ ]);
209
+ ```
210
+
211
+ Use `DictionaryEnv` to type a pre-existing environment (key-based schema):
182
212
 
183
- const env = new Dictionary({
213
+ ```typescript
214
+ import { DictionaryEnv } from 'env-dictionary';
215
+ import { t } from 'env-dictionary';
216
+
217
+ const typedEnv = new DictionaryEnv({
184
218
  env: process.env,
185
219
  schema: {
186
220
  DATABASE_URL: t.string(),
@@ -190,6 +224,22 @@ const env = new Dictionary({
190
224
  });
191
225
  ```
192
226
 
227
+ Use `DictionaryEnv` with descriptor-based schema:
228
+
229
+ ```typescript
230
+ import { DictionaryEnv } from 'env-dictionary';
231
+ import { t } from 'env-dictionary';
232
+
233
+ const typedEnv = new DictionaryEnv(
234
+ { env: process.env },
235
+ { schema: [
236
+ { var: 'DATABASE_URL', type: t.string() },
237
+ { var: 'PORT', type: t.number() },
238
+ { var: 'NODE_ENV', type: t.string() },
239
+ ]}
240
+ );
241
+ ```
242
+
193
243
  ### Configuration Object
194
244
 
195
245
  Can be used with any configuration object:
@@ -206,7 +256,7 @@ const config = {
206
256
  },
207
257
  };
208
258
 
209
- const typedConfig = new Dictionary({
259
+ const typedConfig = new DictionaryEnv({
210
260
  env: config,
211
261
  schema: {
212
262
  server: t.object(),
@@ -220,7 +270,7 @@ const typedConfig = new Dictionary({
220
270
  For complex nested objects, use the `object` type:
221
271
 
222
272
  ```typescript
223
- const dbConfig = new Dictionary({
273
+ const dbConfig = new DictionaryEnv({
224
274
  env: {
225
275
  database: {
226
276
  host: 'localhost',
@@ -239,11 +289,23 @@ const dbConfig = new Dictionary({
239
289
  Both `Dictionary` and `DictionaryEnv` throw errors when validation fails:
240
290
 
241
291
  ```typescript
242
- import { Dictionary } from './lib/dictionary';
243
- import { t } from './lib/t';
292
+ import { Dictionary } from 'env-dictionary';
293
+ import { t } from 'env-dictionary';
294
+
295
+ try {
296
+ const env = new Dictionary([
297
+ { PORT: '3000', type: t.number() }, // Should be a number
298
+ ]);
299
+ } catch (error) {
300
+ console.error(error.message);
301
+ // 'ENV value "PORT" is not valid for the provided type'
302
+ }
303
+
304
+ import { DictionaryEnv } from 'env-dictionary';
305
+ import { t } from 'env-dictionary';
244
306
 
245
307
  try {
246
- const dict = new Dictionary({
308
+ const typedEnv = new DictionaryEnv({
247
309
  env: {
248
310
  PORT: '3000', // Should be a number
249
311
  },
@@ -255,17 +317,6 @@ try {
255
317
  console.error(error.message);
256
318
  // 'Invalid environment variable "PORT".'
257
319
  }
258
-
259
- import { DictionaryEnv } from './lib/dictionary';
260
-
261
- try {
262
- const env = new DictionaryEnv([
263
- { PORT: '3000', type: t.number() }, // Should be a number
264
- ]);
265
- } catch (error) {
266
- console.error(error.message);
267
- // 'ENV value "PORT" is not valid for the provided type'
268
- }
269
320
  ```
270
321
 
271
322
  ## Type Safety
@@ -273,7 +324,10 @@ try {
273
324
  Both classes provide full TypeScript type inference:
274
325
 
275
326
  ```typescript
276
- const env = new DictionaryEnv([
327
+ import { Dictionary } from 'env-dictionary';
328
+ import { t } from 'env-dictionary';
329
+
330
+ const env = new Dictionary([
277
331
  { API_KEY: 'secret', type: t.string() },
278
332
  { PORT: 3000, type: t.number() },
279
333
  { DEBUG: true, type: t.boolean() },
package/index.ts CHANGED
@@ -1 +1,2 @@
1
+ export { Dictionary, DictionaryEnv } from "./lib/dictionary";
1
2
  export { t } from "./lib/t";
package/lib/dictionary.ts CHANGED
@@ -1,31 +1,11 @@
1
1
  /** biome-ignore-all lint/suspicious/noExplicitAny: . */
2
- import type { DictionaryEnvDescriptor, DictionaryOptions, SchemaType } from "./types";
2
+ import type {
3
+ DictionaryEnvDescriptor,
4
+ DictionaryOptions,
5
+ SchemaType,
6
+ } from "./types";
3
7
 
4
- export class Dictionary<T extends Record<string, any> = Record<string, any>> {
5
- public readonly values: T;
6
- constructor(options: DictionaryOptions) {
7
- const { env, schema } = options;
8
-
9
- let typedEnv: any = {};
10
- if (schema) {
11
- for (const key in schema) {
12
- const s = schema[key];
13
- const value = env[key];
14
- if (s?.validate(value)) {
15
- typedEnv[key] = value;
16
- } else if (s) {
17
- throw new Error(`Invalid environment variable "${key}".`);
18
- }
19
- }
20
- } else {
21
- typedEnv = env;
22
- }
23
-
24
- this.values = typedEnv;
25
- }
26
- }
27
-
28
- export class DictionaryEnv<
8
+ export class Dictionary<
29
9
  TSchema extends DictionaryEnvDescriptor<string, any>[],
30
10
  > {
31
11
  public readonly env: {
@@ -57,3 +37,89 @@ export class DictionaryEnv<
57
37
  this.env = result;
58
38
  }
59
39
  }
40
+
41
+ export class DictionaryEnv<
42
+ T extends Record<string, any> = Record<string, any>,
43
+ > {
44
+ public readonly env: T;
45
+ public readonly type: {
46
+ [K in keyof T]: T[K] extends string
47
+ ? typeof String
48
+ : T[K] extends number
49
+ ? typeof Number
50
+ : T[K] extends boolean
51
+ ? typeof Boolean
52
+ : T[K] extends object
53
+ ? typeof Object
54
+ : typeof Array;
55
+ };
56
+ constructor(
57
+ options: DictionaryOptions,
58
+ descriptorOptions?: { schema: Array<{ var: string; type: SchemaType }> },
59
+ ) {
60
+ const { env, schema } = options;
61
+
62
+ const typeMap: any = {
63
+ string: String,
64
+ number: Number,
65
+ boolean: Boolean,
66
+ object: Object,
67
+ array: Array,
68
+ any: Object,
69
+ };
70
+
71
+ let typedEnv: any = {};
72
+ let resultTypeMap: any = {};
73
+
74
+ if (descriptorOptions?.schema) {
75
+ const { schema: descriptors } = descriptorOptions;
76
+ for (const desc of descriptors) {
77
+ const key = desc.var;
78
+ const s = desc.type;
79
+ const value = env[key];
80
+ if (s?.validate(value)) {
81
+ // Convert string numbers to actual numbers
82
+ if (s.type === "number" && typeof value === "string") {
83
+ typedEnv[key] = Number(value);
84
+ } else {
85
+ typedEnv[key] = value;
86
+ }
87
+ resultTypeMap[key] = typeMap[s.type];
88
+ } else if (s) {
89
+ throw new Error(`Invalid environment variable "${key}".`);
90
+ }
91
+ }
92
+ } else if (schema) {
93
+ for (const key in schema) {
94
+ const s = schema[key];
95
+ const value = env[key];
96
+ if (s?.validate(value)) {
97
+ // Convert string numbers to actual numbers
98
+ if (s.type === "number" && typeof value === "string") {
99
+ typedEnv[key] = Number(value);
100
+ } else {
101
+ typedEnv[key] = value;
102
+ }
103
+ resultTypeMap[key] = typeMap[s.type];
104
+ } else if (s) {
105
+ throw new Error(`Invalid environment variable "${key}".`);
106
+ }
107
+ }
108
+ } else {
109
+ typedEnv = env;
110
+ for (const key in env) {
111
+ const value = env[key];
112
+ if (typeof value === "string") resultTypeMap[key] = String;
113
+ else if (typeof value === "number") resultTypeMap[key] = Number;
114
+ else if (typeof value === "boolean") resultTypeMap[key] = Boolean;
115
+ else if (Array.isArray(value)) resultTypeMap[key] = Array;
116
+ else if (typeof value === "object" && value !== null)
117
+ resultTypeMap[key] = Object;
118
+ else resultTypeMap[key] = typeof value;
119
+ }
120
+ }
121
+
122
+ this.env = typedEnv;
123
+ this.type = resultTypeMap;
124
+ }
125
+ }
package/lib/t.ts CHANGED
@@ -13,7 +13,14 @@ const stringSchema: Schema<string, "string"> = {
13
13
 
14
14
  const numberSchema: Schema<number, "number"> = {
15
15
  type: "number",
16
- validate: (input): input is number => typeof input === "number",
16
+ validate: (input): input is number => {
17
+ if (typeof input === "number") return true;
18
+ if (typeof input === "string") {
19
+ const parsed = Number(input);
20
+ return !isNaN(parsed) && isFinite(parsed) && input.trim() !== "";
21
+ }
22
+ return false;
23
+ },
17
24
  };
18
25
 
19
26
  const booleanSchema: Schema<boolean, "boolean"> = {
package/lib/types.ts CHANGED
@@ -18,4 +18,4 @@ type DictionaryEnvDescriptor<T extends string, V> = { [K in T]: V } & {
18
18
  type: SchemaType;
19
19
  };
20
20
 
21
- export type { SchemaType, DictionaryOptions, DictionaryEnvDescriptor }
21
+ export type { SchemaType, DictionaryOptions, DictionaryEnvDescriptor };
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "env-dictionary",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "module": "dist/index.js",
5
5
  "type": "module",
6
+ "scripts": {
7
+ "build": "bun build index.ts --outdir dist --target node",
8
+ "typecheck": "tsc --noEmit"
9
+ },
6
10
  "devDependencies": {
7
11
  "@types/bun": "latest"
8
12
  },