env-dictionary 1.0.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 ADDED
@@ -0,0 +1,315 @@
1
+ # Dictionary
2
+
3
+ A TypeScript library for type-safe environment variable handling with runtime validation.
4
+
5
+ ## Features
6
+
7
+ - **Type Safety**: Full TypeScript support with inferred types
8
+ - **Runtime Validation**: Validate environment variables against defined schemas
9
+ - **Multiple Type Support**: Built-in validators for string, number, boolean, object, array, and any types
10
+ - **Flexible API**: Choose between schema-based or descriptor-based approaches
11
+ - **Zero Dependencies**: Lightweight with no external runtime dependencies
12
+ - **Bun Optimized**: Built with Bun in mind, works with any TypeScript project
13
+
14
+ ## Installation
15
+
16
+ Install the package:
17
+
18
+ ```bash
19
+ bun install
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Using Dictionary (Schema-based)
25
+
26
+ ```typescript
27
+ import { Dictionary } from './lib/dictionary';
28
+ import { t } from './lib/t';
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([
55
+ { API_KEY: 'secret', type: t.string() },
56
+ { PORT: 3000, type: t.number() },
57
+ { DEBUG: true, type: t.boolean() },
58
+ ]);
59
+
60
+ console.log(env.env.API_KEY); // 'secret'
61
+ console.log(env.env.PORT); // 3000
62
+ console.log(env.env.DEBUG); // true
63
+ ```
64
+
65
+ ## API Reference
66
+
67
+ ### t
68
+
69
+ The `t` object provides type validators for schema definitions.
70
+
71
+ #### Available Types
72
+
73
+ | Type | Validator | Example |
74
+ |------|-----------|---------|
75
+ | `string` | `t.string()` | `'hello'` |
76
+ | `number` | `t.number()` | `42` |
77
+ | `boolean` | `t.boolean()` | `true` |
78
+ | `object` | `t.object()` | `{ key: 'value' }` |
79
+ | `array` | `t.array()` | `[1, 2, 3]` |
80
+ | `any` | `t.any()` | Any value |
81
+
82
+ ### Dictionary
83
+
84
+ A class that validates environment variables against a schema.
85
+
86
+ #### Constructor
87
+
88
+ ```typescript
89
+ new Dictionary<T>(options: DictionaryOptions)
90
+ ```
91
+
92
+ **Parameters:**
93
+
94
+ - `options.env: Record<string, unknown>` - The environment variables to validate
95
+ - `options.schema?: Record<string, SchemaType>` - Optional schema for validation
96
+
97
+ **Properties:**
98
+
99
+ - `values: T` - The validated and typed environment variables
100
+
101
+ #### Example
102
+
103
+ ```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
+ });
120
+ ```
121
+
122
+ #### Behavior
123
+
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
128
+
129
+ ### DictionaryEnv
130
+
131
+ A class that creates a typed environment object from an array of descriptors.
132
+
133
+ #### Constructor
134
+
135
+ ```typescript
136
+ new DictionaryEnv<TSchema>(schema: TSchema)
137
+ ```
138
+
139
+ **Parameters:**
140
+
141
+ - `schema: TSchema[]` - Array of descriptors defining environment variables
142
+
143
+ **Properties:**
144
+
145
+ - `env: TEnv` - The typed environment object
146
+
147
+ #### Example
148
+
149
+ ```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
+ ```
158
+
159
+ #### Descriptor Format
160
+
161
+ Each descriptor must have:
162
+ - A variable name (key)
163
+ - A value
164
+ - A `type` property with the validator
165
+
166
+ #### Behavior
167
+
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
172
+
173
+ ## Usage Patterns
174
+
175
+ ### Environment Variables
176
+
177
+ Commonly used for validating `.env` files:
178
+
179
+ ```typescript
180
+ import { Dictionary } from './lib/dictionary';
181
+ import { t } from './lib/t';
182
+
183
+ const env = new Dictionary({
184
+ env: process.env,
185
+ schema: {
186
+ DATABASE_URL: t.string(),
187
+ PORT: t.number(),
188
+ NODE_ENV: t.string(),
189
+ },
190
+ });
191
+ ```
192
+
193
+ ### Configuration Object
194
+
195
+ Can be used with any configuration object:
196
+
197
+ ```typescript
198
+ const config = {
199
+ server: {
200
+ host: 'localhost',
201
+ port: 8080,
202
+ },
203
+ features: {
204
+ auth: true,
205
+ logging: false,
206
+ },
207
+ };
208
+
209
+ const typedConfig = new Dictionary({
210
+ env: config,
211
+ schema: {
212
+ server: t.object(),
213
+ features: t.object(),
214
+ },
215
+ });
216
+ ```
217
+
218
+ ### Nested Configuration
219
+
220
+ For complex nested objects, use the `object` type:
221
+
222
+ ```typescript
223
+ const dbConfig = new Dictionary({
224
+ env: {
225
+ database: {
226
+ host: 'localhost',
227
+ port: 5432,
228
+ ssl: true,
229
+ },
230
+ },
231
+ schema: {
232
+ database: t.object(),
233
+ },
234
+ });
235
+ ```
236
+
237
+ ## Error Handling
238
+
239
+ Both `Dictionary` and `DictionaryEnv` throw errors when validation fails:
240
+
241
+ ```typescript
242
+ import { Dictionary } from './lib/dictionary';
243
+ import { t } from './lib/t';
244
+
245
+ try {
246
+ const dict = new Dictionary({
247
+ env: {
248
+ PORT: '3000', // Should be a number
249
+ },
250
+ schema: {
251
+ PORT: t.number(),
252
+ },
253
+ });
254
+ } catch (error) {
255
+ console.error(error.message);
256
+ // 'Invalid environment variable "PORT".'
257
+ }
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
+ ```
270
+
271
+ ## Type Safety
272
+
273
+ Both classes provide full TypeScript type inference:
274
+
275
+ ```typescript
276
+ const env = new DictionaryEnv([
277
+ { API_KEY: 'secret', type: t.string() },
278
+ { PORT: 3000, type: t.number() },
279
+ { DEBUG: true, type: t.boolean() },
280
+ ]);
281
+
282
+ // TypeScript knows these types:
283
+ env.env.API_KEY; // string
284
+ env.env.PORT; // number
285
+ env.env.DEBUG; // boolean
286
+ env.env.UNKNOWN; // TypeScript error
287
+ ```
288
+
289
+ ## Development
290
+
291
+ ### Install Dependencies
292
+
293
+ ```bash
294
+ bun install
295
+ ```
296
+
297
+ ### Build
298
+
299
+ ```bash
300
+ bun run build
301
+ ```
302
+
303
+ ### Run Tests
304
+
305
+ ```bash
306
+ bun test
307
+ ```
308
+
309
+ ## License
310
+
311
+ MIT
312
+
313
+ ## Contributing
314
+
315
+ Contributions are welcome! Please ensure all tests pass before submitting a pull request.
package/bun.lock ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "dictionary",
6
+ "devDependencies": {
7
+ "@types/bun": "latest",
8
+ },
9
+ "peerDependencies": {
10
+ "typescript": "^5",
11
+ },
12
+ },
13
+ },
14
+ "packages": {
15
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
16
+
17
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
18
+
19
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
20
+
21
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
22
+
23
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
24
+ }
25
+ }
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { t } from "./lib/t";
@@ -0,0 +1,59 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: . */
2
+ import type { DictionaryEnvDescriptor, DictionaryOptions, SchemaType } from "./types";
3
+
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<
29
+ TSchema extends DictionaryEnvDescriptor<string, any>[],
30
+ > {
31
+ public readonly env: {
32
+ [K in TSchema[number] as K extends { type: SchemaType }
33
+ ? keyof K & string
34
+ : never]: K extends { type: infer S }
35
+ ? S extends { validate: (input: unknown) => input is infer U }
36
+ ? U
37
+ : never
38
+ : never;
39
+ };
40
+
41
+ constructor(schema: TSchema) {
42
+ const result: any = {};
43
+ for (const desc of schema) {
44
+ const key = Object.keys(desc).find((k) => k !== "type");
45
+ if (!key) throw new Error("Descriptor must have a variable name");
46
+ const val = (desc as any)[key];
47
+ const type = (desc as any).type;
48
+ if (!type || typeof type.validate !== "function")
49
+ throw new Error("Descriptor must have a type");
50
+ if (!type.validate(val)) {
51
+ throw new Error(
52
+ `ENV value "${key}" is not valid for the provided type`,
53
+ );
54
+ }
55
+ result[key] = val;
56
+ }
57
+ this.env = result;
58
+ }
59
+ }
package/lib/t.ts ADDED
@@ -0,0 +1,47 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: . */
2
+ type SchemaType = "string" | "number" | "boolean" | "object" | "array" | "any";
3
+
4
+ interface Schema<T, S extends SchemaType> {
5
+ type: S;
6
+ validate: (input: unknown) => input is T;
7
+ }
8
+
9
+ const stringSchema: Schema<string, "string"> = {
10
+ type: "string",
11
+ validate: (input): input is string => typeof input === "string",
12
+ };
13
+
14
+ const numberSchema: Schema<number, "number"> = {
15
+ type: "number",
16
+ validate: (input): input is number => typeof input === "number",
17
+ };
18
+
19
+ const booleanSchema: Schema<boolean, "boolean"> = {
20
+ type: "boolean",
21
+ validate: (input): input is boolean => typeof input === "boolean",
22
+ };
23
+
24
+ const objectSchema: Schema<object, "object"> = {
25
+ type: "object",
26
+ validate: (input): input is object =>
27
+ typeof input === "object" && input !== null && !Array.isArray(input),
28
+ };
29
+
30
+ const arraySchema: Schema<any[], "array"> = {
31
+ type: "array",
32
+ validate: (input): input is any[] => Array.isArray(input),
33
+ };
34
+
35
+ const anySchema: Schema<any, "any"> = {
36
+ type: "any",
37
+ validate: (_input): _input is any => true,
38
+ };
39
+
40
+ export const t = {
41
+ string: () => stringSchema,
42
+ number: () => numberSchema,
43
+ boolean: () => booleanSchema,
44
+ object: () => objectSchema,
45
+ array: () => arraySchema,
46
+ any: () => anySchema,
47
+ };
package/lib/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { t } from "./t";
2
+
3
+ type SchemaType = ReturnType<
4
+ | typeof t.string
5
+ | typeof t.number
6
+ | typeof t.boolean
7
+ | typeof t.object
8
+ | typeof t.array
9
+ | typeof t.any
10
+ >;
11
+
12
+ type DictionaryOptions = {
13
+ env: Record<string, unknown>;
14
+ schema?: Record<string, SchemaType>;
15
+ };
16
+
17
+ type DictionaryEnvDescriptor<T extends string, V> = { [K in T]: V } & {
18
+ type: SchemaType;
19
+ };
20
+
21
+ export type { SchemaType, DictionaryOptions, DictionaryEnvDescriptor }
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "env-dictionary",
3
+ "version": "1.0.0",
4
+ "module": "dist/index.js",
5
+ "type": "module",
6
+ "devDependencies": {
7
+ "@types/bun": "latest"
8
+ },
9
+ "peerDependencies": {
10
+ "typescript": "^5"
11
+ }
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "Preserve",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "allowJs": true,
9
+
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "noEmit": true,
14
+
15
+ "strict": true,
16
+ "skipLibCheck": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedIndexedAccess": true,
19
+ "noImplicitOverride": true,
20
+
21
+ "noUnusedLocals": false,
22
+ "noUnusedParameters": false,
23
+ "noPropertyAccessFromIndexSignature": false
24
+ }
25
+ }