@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 +295 -0
- package/dist/EnvironmentParser-Dd6TbwJC.mjs +99 -0
- package/dist/EnvironmentParser-nZnZXM_Y.cjs +133 -0
- package/dist/EnvironmentParser.cjs +4 -0
- package/dist/EnvironmentParser.mjs +3 -0
- package/dist/index.cjs +3 -0
- package/dist/index.mjs +3 -0
- package/docs/api-reference.md +302 -0
- package/examples/basic-usage.ts +390 -0
- package/package.json +22 -0
- package/src/EnvironmentParser.ts +159 -0
- package/src/index.ts +1 -0
- package/sst-env.d.ts +9 -0
- package/tsdown.config.ts +3 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import get from 'lodash.get';
|
|
2
|
+
import set from 'lodash.set';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
|
|
5
|
+
export class ConfigParser<TResponse extends EmptyObject> {
|
|
6
|
+
constructor(private readonly config: TResponse) {}
|
|
7
|
+
/**
|
|
8
|
+
* Parses the config object and validates it against the Zod schemas
|
|
9
|
+
* @returns The parsed config object
|
|
10
|
+
*/
|
|
11
|
+
parse(): InferConfig<TResponse> {
|
|
12
|
+
const errors: z.core.$ZodIssue[] = [];
|
|
13
|
+
|
|
14
|
+
const parseDeep = <T>(config: T, path: string[] = []) => {
|
|
15
|
+
const result: EmptyObject = {};
|
|
16
|
+
|
|
17
|
+
if (config && typeof config !== 'object') {
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const key in config) {
|
|
22
|
+
const schema = config[key];
|
|
23
|
+
const currentPath = [...path, key];
|
|
24
|
+
|
|
25
|
+
if (schema instanceof z.ZodType) {
|
|
26
|
+
const parsed = schema.safeParse(undefined);
|
|
27
|
+
if (parsed.success) {
|
|
28
|
+
set(result, key, parsed.data);
|
|
29
|
+
} else {
|
|
30
|
+
// If the schema is invalid, assign the error
|
|
31
|
+
errors.push(
|
|
32
|
+
...parsed.error.issues.map((issue) => ({
|
|
33
|
+
...issue,
|
|
34
|
+
path: [...currentPath, ...(issue.path as string[])],
|
|
35
|
+
})),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
} else if (schema) {
|
|
39
|
+
set(result, key, parseDeep(schema as EmptyObject, currentPath));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const parsedConfig = parseDeep(
|
|
47
|
+
this.config,
|
|
48
|
+
) as unknown as InferConfig<TResponse>;
|
|
49
|
+
|
|
50
|
+
if (errors.length > 0) {
|
|
51
|
+
// If there are errors, throw them
|
|
52
|
+
throw new z.ZodError(errors);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsedConfig;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class EnvironmentParser<T extends EmptyObject> {
|
|
60
|
+
constructor(private readonly config: T) {}
|
|
61
|
+
|
|
62
|
+
private getZodGetter = (name: string) => {
|
|
63
|
+
// Return an object that has all Zod schemas but with our wrapper
|
|
64
|
+
return new Proxy(
|
|
65
|
+
{ ...z },
|
|
66
|
+
{
|
|
67
|
+
get: (target, prop) => {
|
|
68
|
+
// deno-lint-ignore ban-ts-comment
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
const func = target[prop];
|
|
71
|
+
|
|
72
|
+
if (typeof func === 'function') {
|
|
73
|
+
// Return a wrapper around each Zod schema creator
|
|
74
|
+
return (...args: any[]) => {
|
|
75
|
+
const schema = func(...args);
|
|
76
|
+
// Add a custom parse method that gets the value from config
|
|
77
|
+
const originalParse = schema.parse;
|
|
78
|
+
const originalSafeParse = schema.safeParse;
|
|
79
|
+
|
|
80
|
+
schema.parse = () => {
|
|
81
|
+
const value = get(this.config, name);
|
|
82
|
+
try {
|
|
83
|
+
return originalParse.call(schema, value);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof z.ZodError) {
|
|
86
|
+
// Modify the error to include the environment variable name
|
|
87
|
+
const modifiedIssues = error.issues.map((issue) => ({
|
|
88
|
+
...issue,
|
|
89
|
+
message: `Environment variable "${name}": ${issue.message}`,
|
|
90
|
+
path: [name, ...issue.path],
|
|
91
|
+
}));
|
|
92
|
+
throw new z.ZodError(modifiedIssues);
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
schema.safeParse = () => {
|
|
99
|
+
const value = get(this.config, name);
|
|
100
|
+
const result = originalSafeParse.call(schema, value);
|
|
101
|
+
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
// Modify the error to include the environment variable name
|
|
104
|
+
const modifiedIssues = result.error.issues.map(
|
|
105
|
+
(issue: z.core.$ZodIssue) => ({
|
|
106
|
+
...issue,
|
|
107
|
+
message: `Environment variable "${name}": ${issue.message}`,
|
|
108
|
+
path: [name, ...issue.path],
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
success: false as const,
|
|
113
|
+
error: new z.ZodError(modifiedIssues),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return schema;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return func;
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a new JordConfigParser object that can be used to parse the config object
|
|
131
|
+
*
|
|
132
|
+
* @param builder - A function that takes a getter function and returns a config object
|
|
133
|
+
* @returns A JordConfigParser object that can be used to parse the config object
|
|
134
|
+
*/
|
|
135
|
+
create<TReturn extends EmptyObject>(
|
|
136
|
+
builder: (get: EnvFetcher) => TReturn,
|
|
137
|
+
): ConfigParser<TReturn> {
|
|
138
|
+
const config = builder(this.getZodGetter);
|
|
139
|
+
return new ConfigParser(config);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type InferConfig<T extends EmptyObject> = {
|
|
144
|
+
[K in keyof T]: T[K] extends z.ZodSchema
|
|
145
|
+
? z.infer<T[K]>
|
|
146
|
+
: T[K] extends Record<string, unknown>
|
|
147
|
+
? InferConfig<T[K]>
|
|
148
|
+
: T[K];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type EnvFetcher<TPath extends string = string> = (
|
|
152
|
+
name: TPath,
|
|
153
|
+
) => typeof z;
|
|
154
|
+
|
|
155
|
+
export type EnvironmentBuilder<TResponse extends EmptyObject> = (
|
|
156
|
+
get: EnvFetcher,
|
|
157
|
+
) => TResponse;
|
|
158
|
+
|
|
159
|
+
export type EmptyObject = Record<string | number | symbol, unknown>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { EnvironmentParser } from './EnvironmentParser';
|
package/sst-env.d.ts
ADDED
package/tsdown.config.ts
ADDED