@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.
@@ -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
@@ -0,0 +1,9 @@
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+
6
+ /// <reference path="../../sst-env.d.ts" />
7
+
8
+ import 'sst';
9
+ export {};
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({});