@proventuslabs/nestjs-zod 1.0.0-rc.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 ADDED
@@ -0,0 +1,297 @@
1
+ # @proventuslabs/nestjs-zod
2
+
3
+ A collection of NestJS modules to integrate Zod into your application with enhanced configuration management and type safety.
4
+
5
+ ## Features
6
+
7
+ - 🔧 **Zod-powered Configuration**: Type-safe configuration management using Zod schemas
8
+ - 🌍 **Environment Variable Support**: Automatic parsing and validation of environment variables
9
+ - 📁 **Configuration Files**: Support for YAML configuration files
10
+ - 🏗️ **Configurable Modules**: Enhanced NestJS configurable modules with Zod validation
11
+ - 🎯 **Type Safety**: Full TypeScript support with automatic type inference
12
+ - 🔄 **Merge Strategy**: Environment variables override configuration file values
13
+ - 📝 **Descriptive Errors**: Enhanced error messages with schema descriptions
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @proventuslabs/nestjs-zod
19
+ ```
20
+
21
+ ## Peer Dependencies
22
+
23
+ This package requires the following peer dependencies:
24
+
25
+ - `@nestjs/common` ^10.0.0 || ^11.0.0
26
+ - `@nestjs/config` ^3.0.0 || ^4.0.0
27
+ - `zod` ^3.0.0
28
+
29
+ ## Quick Start
30
+
31
+ ### Configuration Management
32
+
33
+ ```typescript
34
+ import { Module } from '@nestjs/common';
35
+ import { ConfigModule } from '@nestjs/config';
36
+ import { registerConfig, type ConfigType } from '@proventuslabs/nestjs-zod';
37
+ import { z } from 'zod';
38
+
39
+ // Define your configuration schema
40
+ const appConfig = registerConfig(
41
+ 'app',
42
+ z.object({
43
+ port: z
44
+ .number()
45
+ .int()
46
+ .min(0)
47
+ .max(65535)
48
+ .default(3000)
49
+ .describe('The local HTTP port to bind the server to'),
50
+ host: z
51
+ .string()
52
+ .default('localhost')
53
+ .describe('The host to bind the server to'),
54
+ environment: z
55
+ .enum(['development', 'production', 'test'])
56
+ .default('development')
57
+ .describe('The application environment'),
58
+ }),
59
+ );
60
+
61
+ // Type inference
62
+ type AppConfig = ConfigType<typeof appConfig>;
63
+
64
+ @Module({
65
+ imports: [
66
+ ConfigModule.forRoot({
67
+ isGlobal: true,
68
+ load: [appConfig],
69
+ }),
70
+ ],
71
+ })
72
+ export class AppModule {}
73
+ ```
74
+
75
+ ### Using Configuration
76
+
77
+ ```typescript
78
+ import { Injectable } from '@nestjs/common';
79
+ import { ConfigService } from '@nestjs/config';
80
+ import { type NamespacedConfigType } from '@proventuslabs/nestjs-zod';
81
+
82
+ @Injectable()
83
+ export class AppService {
84
+ constructor(
85
+ private configService: ConfigService<NamespacedConfigType<typeof appConfig>, true>
86
+ ) {}
87
+
88
+ getPort(): number {
89
+ return this.configService.get('app.port', { infer: true });
90
+ }
91
+
92
+ getHost(): string {
93
+ return this.configService.get('app.host', { infer: true });
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Configuration Sources
99
+
100
+ The package supports multiple configuration sources with a clear precedence order:
101
+
102
+ 1. **Environment Variables** (highest priority)
103
+ 2. **Configuration Files** (YAML)
104
+ 3. **Schema Defaults** (lowest priority)
105
+
106
+ ### Environment Variables
107
+
108
+ Environment variables are automatically parsed and validated. Use the namespace prefix followed by underscores:
109
+
110
+ ```bash
111
+ # For the 'app' namespace
112
+ APP_PORT=8080
113
+ APP_HOST=0.0.0.0
114
+ APP_ENVIRONMENT=production
115
+
116
+ # Nested configuration using double underscores
117
+ APP_DATABASE__HOST=localhost
118
+ APP_DATABASE__PORT=5432
119
+ APP_DATABASE__NAME=myapp
120
+ ```
121
+
122
+ ### Configuration Files
123
+
124
+ Support for YAML configuration files via environment variables:
125
+
126
+ ```bash
127
+ # Specify a configuration file
128
+ CONFIG_FILE=./config/app.yaml
129
+
130
+ # Or provide inline YAML content
131
+ CONFIG_CONTENT="app:
132
+ port: 8080
133
+ host: 0.0.0.0
134
+ environment: production
135
+ database:
136
+ host: localhost
137
+ port: 5432
138
+ name: myapp"
139
+ ```
140
+
141
+ Example `config/app.yaml`:
142
+
143
+ ```yaml
144
+ app:
145
+ port: 8080
146
+ host: 0.0.0.0
147
+ environment: production
148
+ database:
149
+ host: localhost
150
+ port: 5432
151
+ name: myapp
152
+ redis:
153
+ host: localhost
154
+ port: 6379
155
+ ```
156
+
157
+ ## Configurable Modules
158
+
159
+ Create type-safe configurable modules with Zod validation:
160
+
161
+ ```typescript
162
+ import { Module } from '@nestjs/common';
163
+ import { ZodConfigurableModuleBuilder } from '@proventuslabs/nestjs-zod';
164
+ import { z } from 'zod';
165
+
166
+ const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ZodConfigurableModuleBuilder(
167
+ z.object({
168
+ apiKey: z.string().min(1),
169
+ baseUrl: z.string().url().default('https://api.example.com'),
170
+ timeout: z.number().positive().default(5000),
171
+ })
172
+ ).build();
173
+
174
+ @Module({
175
+ providers: [
176
+ {
177
+ provide: 'EXTERNAL_SERVICE',
178
+ useFactory: (options) => {
179
+ // options is fully typed and validated
180
+ return new ExternalService(options);
181
+ },
182
+ inject: [MODULE_OPTIONS_TOKEN],
183
+ },
184
+ ],
185
+ exports: ['EXTERNAL_SERVICE'],
186
+ })
187
+ export class ExternalServiceModule extends ConfigurableModuleClass {}
188
+ ```
189
+
190
+ ### Using Configurable Modules
191
+
192
+ ```typescript
193
+ // Static configuration
194
+ ExternalServiceModule.register({
195
+ apiKey: 'your-api-key',
196
+ baseUrl: 'https://custom-api.example.com',
197
+ timeout: 10000,
198
+ });
199
+
200
+ // Async configuration
201
+ ExternalServiceModule.registerAsync({
202
+ useFactory: (configService: ConfigService) => ({
203
+ apiKey: configService.get('EXTERNAL_API_KEY'),
204
+ baseUrl: configService.get('EXTERNAL_BASE_URL'),
205
+ timeout: configService.get('EXTERNAL_TIMEOUT'),
206
+ }),
207
+ inject: [ConfigService],
208
+ });
209
+ ```
210
+
211
+ ## API Reference
212
+
213
+ ### `registerConfig`
214
+
215
+ Registers a configuration with the `ConfigModule.forFeature` for partial configuration under the provided namespace.
216
+
217
+ ```typescript
218
+ function registerConfig<N extends string, C extends ConfigObject, I extends JsonValue>(
219
+ namespace: ConfigNamespace<N>,
220
+ configSchema: ZodType<C, ZodTypeDef, I>,
221
+ variables?: Record<string, string | undefined>
222
+ ): ReturnType<typeof registerAs<C>> & { NAMESPACE: N }
223
+ ```
224
+
225
+ **Parameters:**
226
+ - `namespace`: The namespace for the configuration (camelCase)
227
+ - `configSchema`: Zod schema for validation
228
+ - `variables`: Environment variables object (defaults to `process.env`)
229
+
230
+ **Returns:** A configuration provider that can be used with `ConfigModule.forRoot()`
231
+
232
+ ### `ConfigType<T>`
233
+
234
+ Extracts the inferred type from a configuration schema.
235
+
236
+ ```typescript
237
+ type ConfigType<T extends (...args: unknown[]) => unknown> = NestConfigType<T>;
238
+ ```
239
+
240
+ ### `NamespacedConfigType<T>`
241
+
242
+ Extracts the namespaced configuration type.
243
+
244
+ ```typescript
245
+ type NamespacedConfigType<R> = {
246
+ [K in R["NAMESPACE"]]: NestConfigType<R>;
247
+ };
248
+ ```
249
+
250
+ ### `ZodConfigurableModuleBuilder`
251
+
252
+ Builder class for creating configurable modules with Zod validation.
253
+
254
+ ```typescript
255
+ class ZodConfigurableModuleBuilder<
256
+ ModuleOptions,
257
+ ModuleOptionsInput = unknown,
258
+ StaticMethodKey extends string = string,
259
+ FactoryClassMethodKey extends string = string,
260
+ ExtraModuleDefinitionOptions = object
261
+ >
262
+ ```
263
+
264
+ ## Error Handling
265
+
266
+ The package provides enhanced error messages that include:
267
+
268
+ - Schema descriptions when available
269
+ - Original environment variable names
270
+ - Detailed validation error paths
271
+ - Helpful suggestions for fixing configuration issues
272
+
273
+ Example error output:
274
+
275
+ ```
276
+ TypeError: Configuration validation failed for 'app' namespace
277
+
278
+ • app.port: Expected number, received string
279
+ Description: The local HTTP port to bind the server to
280
+ Environment Variable: APP_PORT
281
+
282
+ • app.database.host: Required
283
+ Description: Database host address
284
+ Environment Variable: APP_DATABASE__HOST
285
+ ```
286
+
287
+ ## Examples
288
+
289
+ See the [examples](./examples) directory for complete working examples.
290
+
291
+ ## Contributing
292
+
293
+ Contributions are welcome! Please feel free to submit a Pull Request.
294
+
295
+ ## License
296
+
297
+ This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
@@ -0,0 +1,56 @@
1
+ import { type ConfigObject, type ConfigType as NestConfigType } from "@nestjs/config";
2
+ import type { CamelCase, JsonValue } from "type-fest";
3
+ import { type ZodType, type ZodTypeDef } from "zod";
4
+ /**
5
+ * Simple type alias for config namespace.
6
+ */
7
+ type ConfigNamespace<T extends string> = CamelCase<T>;
8
+ /**
9
+ * The flattened type of the config.
10
+ *
11
+ * @example
12
+ * const config = registerConfig('app', z.object({ port: z.number() }));
13
+ * type AppConfig = ConfigType<typeof config>;
14
+ * // ^? { port: number }
15
+ */
16
+ export type ConfigType<T extends (...args: unknown[]) => unknown> = NestConfigType<T>;
17
+ /**
18
+ * The type of the config for a given namespace.
19
+ *
20
+ * @example
21
+ * const config = registerConfig('app', z.object({ port: z.number() }));
22
+ * type AppConfig = NamespacedConfigType<typeof config>;
23
+ * // ^? { app: { port: number } }
24
+ */
25
+ export type NamespacedConfigType<R extends ReturnType<typeof registerConfig<string, any, any>>> = {
26
+ [K in R["NAMESPACE"]]: NestConfigType<R>;
27
+ };
28
+ /**
29
+ * Registers a config with the `ConfigModule.forFeature` for partial configuration under the provided namespace.
30
+ *
31
+ * This function handles configuration by validating and merging values from multiple sources:
32
+ * - **Environment Variables:** Reads environment variables as JSON values, using the namespace prefix and validating them against the provided schema.
33
+ * - **Configuration File:** Optionally reads configuration from a file or inline YAML content, specified by the `CONFIG_FILE` or `CONFIG_CONTENT` environment variables respectively.
34
+ *
35
+ * **Merge order is Config File < Enviornment Variables** thus enviornment variables _override_ whatever the config sets.
36
+ *
37
+ * @param namespace - The namespace of the config
38
+ * @param configSchema - The schema of the config
39
+ * @param variables - The environment variables - defaults to `process.env`
40
+ *
41
+ * @throws {TypeError} If the environment variables or configuration file content do not match the schema.
42
+ *
43
+ * @example
44
+ * const ConfigSchema = z.object({
45
+ * port: z.number().int().min(0).max(65535).default(9558).describe('The local HTTP port to bind the server to'),
46
+ * });
47
+ *
48
+ * export const appConfig = registerConfig('app', ConfigSchema);
49
+ *
50
+ * export type AppConfigNamespaced = NamespacedConfigType<typeof appConfig>;
51
+ * export type AppConfig = ConfigType<typeof appConfig>;
52
+ */
53
+ export declare function registerConfig<N extends string, C extends ConfigObject, I extends JsonValue>(namespace: ConfigNamespace<N>, configSchema: ZodType<C, ZodTypeDef, I>, variables?: Record<string, string | undefined>): import("@nestjs/config").ConfigFactory<C> & import("@nestjs/config").ConfigFactoryKeyHost<C | Promise<C>> & {
54
+ NAMESPACE: N;
55
+ };
56
+ export default registerConfig;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerConfig = registerConfig;
7
+ const node_process_1 = __importDefault(require("node:process"));
8
+ const config_1 = require("@nestjs/config");
9
+ const lodash_1 = require("lodash");
10
+ const zod_1 = require("zod");
11
+ const internal_1 = require("../internal");
12
+ /**
13
+ * Registers a config with the `ConfigModule.forFeature` for partial configuration under the provided namespace.
14
+ *
15
+ * This function handles configuration by validating and merging values from multiple sources:
16
+ * - **Environment Variables:** Reads environment variables as JSON values, using the namespace prefix and validating them against the provided schema.
17
+ * - **Configuration File:** Optionally reads configuration from a file or inline YAML content, specified by the `CONFIG_FILE` or `CONFIG_CONTENT` environment variables respectively.
18
+ *
19
+ * **Merge order is Config File < Enviornment Variables** thus enviornment variables _override_ whatever the config sets.
20
+ *
21
+ * @param namespace - The namespace of the config
22
+ * @param configSchema - The schema of the config
23
+ * @param variables - The environment variables - defaults to `process.env`
24
+ *
25
+ * @throws {TypeError} If the environment variables or configuration file content do not match the schema.
26
+ *
27
+ * @example
28
+ * const ConfigSchema = z.object({
29
+ * port: z.number().int().min(0).max(65535).default(9558).describe('The local HTTP port to bind the server to'),
30
+ * });
31
+ *
32
+ * export const appConfig = registerConfig('app', ConfigSchema);
33
+ *
34
+ * export type AppConfigNamespaced = NamespacedConfigType<typeof appConfig>;
35
+ * export type AppConfig = ConfigType<typeof appConfig>;
36
+ */
37
+ function registerConfig(namespace, configSchema, variables = node_process_1.default.env) {
38
+ const service = (0, config_1.registerAs)(namespace, async () => {
39
+ const [decodedEnv, envKeys] = (0, internal_1.decodeVariables)(variables, namespace);
40
+ const decodedConfig = (0, internal_1.decodeConfig)(variables.CONFIG_CONTENT, variables.CONFIG_FILE);
41
+ const namespacedSchema = zod_1.z.object({
42
+ [namespace]: configSchema,
43
+ });
44
+ const parsedConfig = await namespacedSchema.safeParseAsync((0, lodash_1.merge)({ [namespace]: {} }, decodedConfig, decodedEnv));
45
+ if (!parsedConfig.success)
46
+ throw (0, internal_1.zodErrorToTypeError)(parsedConfig.error, namespacedSchema, namespace, envKeys);
47
+ const config = parsedConfig.data[namespace];
48
+ return config;
49
+ });
50
+ // we add the namespace to the object itself for runtime access too
51
+ return Object.defineProperty(service, "NAMESPACE", {
52
+ value: namespace,
53
+ writable: false,
54
+ });
55
+ }
56
+ exports.default = registerConfig;
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":";;;;;AAiEA,wCA2BC;AA5FD,gEAAmC;AAEnC,2CAAkG;AAElG,mCAA+B;AAE/B,6BAAuD;AAEvD,0CAAiF;AAgCjF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAgB,cAAc,CAC7B,SAA6B,EAC7B,YAAuC,EACvC,YAAgD,sBAAO,CAAC,GAAG;IAE3D,MAAM,OAAO,GAAG,IAAA,mBAAU,EAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,IAAA,0BAAe,EAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACpE,MAAM,aAAa,GAAG,IAAA,uBAAY,EAAC,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QAEpF,MAAM,gBAAgB,GAAG,OAAC,CAAC,MAAM,CAAC;YACjC,CAAC,SAAS,CAAC,EAAE,YAAY;SACzB,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,cAAc,CACzD,IAAA,cAAK,EAAC,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,UAAU,CAAC,CACrD,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,OAAO;YACxB,MAAM,IAAA,8BAAmB,EAAC,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACrF,MAAM,MAAM,GAAM,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE/C,OAAO,MAAM,CAAC;IACf,CAAC,CAAwD,CAAC;IAE1D,mEAAmE;IACnE,OAAO,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE;QAClD,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;AACJ,CAAC;AAED,kBAAe,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_1 = require("@nestjs/config");
4
+ const testing_1 = require("@nestjs/testing");
5
+ const zod_1 = require("zod");
6
+ const config_2 = require("./config");
7
+ describe("config", () => {
8
+ it("should register config with ConfigModule.forFeature", async () => {
9
+ const config = (0, config_2.registerConfig)("app", zod_1.z.object({}), {});
10
+ const moduleRef = await testing_1.Test.createTestingModule({
11
+ imports: [config_1.ConfigModule.forFeature(config)],
12
+ }).compile();
13
+ const configService = moduleRef.get(config_1.ConfigService);
14
+ expect(configService).toBeDefined();
15
+ });
16
+ it("should return the correct config", async () => {
17
+ const config = (0, config_2.registerConfig)("app", zod_1.z.object({
18
+ port: zod_1.z.coerce
19
+ .number()
20
+ .positive()
21
+ .min(0)
22
+ .max(65535)
23
+ .default(9556)
24
+ .describe("The local HTTP port to bind the server to"),
25
+ }), {});
26
+ const moduleRef = await testing_1.Test.createTestingModule({
27
+ imports: [config_1.ConfigModule.forFeature(config)],
28
+ }).compile();
29
+ const configService = moduleRef.get(config_1.ConfigService);
30
+ expect(configService.get("app")).toEqual({
31
+ port: 9556,
32
+ });
33
+ });
34
+ it("should throw an error if the config is invalid", async () => {
35
+ const config = (0, config_2.registerConfig)("app", zod_1.z.object({
36
+ missing: zod_1.z.string().describe("This is a required field"),
37
+ }), {
38
+ APP_NOT_MISSING: "not missing",
39
+ });
40
+ const moduleRef = testing_1.Test.createTestingModule({
41
+ imports: [
42
+ config_1.ConfigModule.forRoot({ ignoreEnvFile: true, load: [] }),
43
+ config_1.ConfigModule.forFeature(config),
44
+ ],
45
+ }).compile();
46
+ await expect(moduleRef).rejects.toThrow();
47
+ });
48
+ });
49
+ //# sourceMappingURL=config.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.spec.js","sourceRoot":"","sources":["../../src/config/config.spec.ts"],"names":[],"mappings":";;AAAA,2CAA6D;AAC7D,6CAAuC;AAEvC,6BAAwB;AAExB,qCAA0C;AAE1C,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,MAAM,GAAG,IAAA,uBAAc,EAAC,KAAK,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAEvD,MAAM,SAAS,GAAG,MAAM,cAAI,CAAC,mBAAmB,CAAC;YAChD,OAAO,EAAE,CAAC,qBAAY,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;SAC1C,CAAC,CAAC,OAAO,EAAE,CAAC;QAEb,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,sBAAa,CAAC,CAAC;QAEnD,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,MAAM,GAAG,IAAA,uBAAc,EAC5B,KAAK,EACL,OAAC,CAAC,MAAM,CAAC;YACR,IAAI,EAAE,OAAC,CAAC,MAAM;iBACZ,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,KAAK,CAAC;iBACV,OAAO,CAAC,IAAI,CAAC;iBACb,QAAQ,CAAC,2CAA2C,CAAC;SACvD,CAAC,EACF,EAAE,CACF,CAAC;QAEF,MAAM,SAAS,GAAG,MAAM,cAAI,CAAC,mBAAmB,CAAC;YAChD,OAAO,EAAE,CAAC,qBAAY,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;SAC1C,CAAC,CAAC,OAAO,EAAE,CAAC;QAEb,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,sBAAa,CAAC,CAAC;QAEnD,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YACxC,IAAI,EAAE,IAAI;SACV,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,MAAM,GAAG,IAAA,uBAAc,EAC5B,KAAK,EACL,OAAC,CAAC,MAAM,CAAC;YACR,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;SACxD,CAAC,EACF;YACC,eAAe,EAAE,aAAa;SAC9B,CACD,CAAC;QAEF,MAAM,SAAS,GAAG,cAAI,CAAC,mBAAmB,CAAC;YAC1C,OAAO,EAAE;gBACR,qBAAY,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;gBACvD,qBAAY,CAAC,UAAU,CAAC,MAAM,CAAC;aAC/B;SACD,CAAC,CAAC,OAAO,EAAE,CAAC;QAEb,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./config/config";
2
+ export * from "./module/module";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./config/config"), exports);
18
+ __exportStar(require("./module/module"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,kDAAgC"}
@@ -0,0 +1,77 @@
1
+ import type { JsonValue } from "type-fest";
2
+ import { type ZodTypeAny, type z } from "zod";
3
+ /**
4
+ * Reads configuration from either a string content or a file path.
5
+ * String content takes precedence over file path to avoid unnecessary filesystem operations.
6
+ *
7
+ * @param content - Optional string content in YAML format
8
+ * @param file - Optional path to a configuration file
9
+ * @returns The parsed configuration as a record of string keys to JSON values
10
+ * @throws {Error} if neither content nor file contains a valid configuration object
11
+ */
12
+ export declare function decodeConfig(content?: string, file?: string): Record<string, JsonValue>;
13
+ /**
14
+ * Transforms environment variables from a flat structure with a specific namespace prefix
15
+ * into a nested object structure with camelCase keys, while preserving the original keys
16
+ * for error reporting.
17
+ *
18
+ * For example, transforms:
19
+ * {
20
+ * "MY_APP_SERVER__HOST": "localhost",
21
+ * "MY_APP_DATABASE__PORT": "5432",
22
+ * "UNRELATED_VAR": "value"
23
+ * }
24
+ *
25
+ * With namespace "myApp" into:
26
+ * {
27
+ * "my_app": {
28
+ * "server": {
29
+ * "host": "localhost"
30
+ * },
31
+ * "database": {
32
+ * "port": 5432 // Note: jsonify attempts to parse values
33
+ * }
34
+ * }
35
+ * }
36
+ *
37
+ * @param variables - Record of environment variables with string keys and string values
38
+ * @param namespace - The namespace prefix to filter variables (will be converted to SNAKE_CASE)
39
+ * @returns A tuple containing:
40
+ * 1. The transformed nested configuration object
41
+ * 2. A Map relating the transformed path keys to original environment variable names for error reporting
42
+ */
43
+ export declare function decodeVariables(variables: Record<string, string | undefined>, namespace: string): readonly [Record<string, JsonValue | undefined>, Map<string, string>];
44
+ /**
45
+ * Converts Zod validation errors into a more descriptive TypeError.
46
+ * Enhances error messages with schema descriptions when available.
47
+ *
48
+ * @param error - The Zod error containing validation issues
49
+ * @param schema - The namespaced schema that was used for validation
50
+ * @param namespace - The configuration namespace for context in error messages
51
+ * @param keys - Map of path strings to user-friendly key names for better error reporting
52
+ * @returns A TypeError with formatted error messages for each validation issue
53
+ */
54
+ export declare function zodErrorToTypeError(error: z.ZodError, schema: ZodTypeAny, namespace: string, keys: Map<string, string>, isSchemaNamespaced?: boolean): ZodConfigError;
55
+ /**
56
+ * Simple error wrapper for expressing an invalid config from a zod schema.
57
+ */
58
+ export declare class ZodConfigError extends TypeError {
59
+ readonly namespace: {
60
+ name: string;
61
+ description: string | undefined;
62
+ };
63
+ readonly configErrors: readonly {
64
+ path: string;
65
+ message: string;
66
+ description: string | undefined;
67
+ }[];
68
+ constructor(namespace: {
69
+ name: string;
70
+ description: string | undefined;
71
+ }, configErrors: readonly {
72
+ path: string;
73
+ message: string;
74
+ description: string | undefined;
75
+ }[], options?: ErrorOptions);
76
+ private static mapError;
77
+ }
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ZodConfigError = void 0;
7
+ exports.decodeConfig = decodeConfig;
8
+ exports.decodeVariables = decodeVariables;
9
+ exports.zodErrorToTypeError = zodErrorToTypeError;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const lodash_1 = require("lodash");
12
+ const yaml_1 = __importDefault(require("yaml"));
13
+ const zod_1 = require("zod");
14
+ /**
15
+ * Raw content of the config file as UTF-8.
16
+ */
17
+ let cachedConfigFileContent;
18
+ /**
19
+ * Parsed YAML file - unknown as we don't know what shape it will have until we validate it.
20
+ */
21
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
22
+ let parsedConfigFileContent;
23
+ /**
24
+ * Parses a string content into a JavaScript object using YAML parser.
25
+ * Uses a cached result if the content has been parsed before.
26
+ *
27
+ * @param content - The string content in YAML format to parse
28
+ * @returns The parsed JavaScript object representation of the YAML content
29
+ */
30
+ function parseConfig(content) {
31
+ if (!(0, lodash_1.isUndefined)(parsedConfigFileContent))
32
+ return parsedConfigFileContent;
33
+ parsedConfigFileContent = yaml_1.default.parse(content);
34
+ return parsedConfigFileContent;
35
+ }
36
+ /**
37
+ * Reads and parses a configuration file from the filesystem.
38
+ * Uses cached file content if available to prevent multiple filesystem reads.
39
+ *
40
+ * @param path - The path to the configuration file
41
+ * @returns The parsed JavaScript object representation of the file
42
+ * @throws {Error} if the file cannot be read or parsed
43
+ */
44
+ function parseConfigFile(path) {
45
+ try {
46
+ if (!(0, lodash_1.isString)(cachedConfigFileContent))
47
+ cachedConfigFileContent = node_fs_1.default.readFileSync(path).toString("utf8");
48
+ return parseConfig(cachedConfigFileContent);
49
+ }
50
+ catch (err) {
51
+ const message = err instanceof Error ? err.message : new String(err).toString();
52
+ throw new Error(`Unable to open the config file provided: ${message}`, {
53
+ cause: err,
54
+ });
55
+ }
56
+ }
57
+ /**
58
+ * Reads configuration from either a string content or a file path.
59
+ * String content takes precedence over file path to avoid unnecessary filesystem operations.
60
+ *
61
+ * @param content - Optional string content in YAML format
62
+ * @param file - Optional path to a configuration file
63
+ * @returns The parsed configuration as a record of string keys to JSON values
64
+ * @throws {Error} if neither content nor file contains a valid configuration object
65
+ */
66
+ function decodeConfig(content, file) {
67
+ let readConfig = {};
68
+ // NOTE: `content` takes precedence over a `file` (to avoid a file system read in case of both)
69
+ if ((0, lodash_1.isString)(content))
70
+ readConfig = parseConfig(content);
71
+ else if ((0, lodash_1.isString)(file))
72
+ readConfig = parseConfigFile(file);
73
+ if (!(0, lodash_1.isObjectLike)(readConfig))
74
+ throw new Error(`Config file provided must contain the JSON configuration object`);
75
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
76
+ return readConfig;
77
+ }
78
+ /**
79
+ * Transforms environment variable keys into nested object notation starting from the provided namespace.
80
+ * For example, transforms "APP_SERVER__HOST" into "app.server.host" using camelCase ("APP" was the env namespace).
81
+ *
82
+ * @param envKey - The environment variable key to transform
83
+ * @param envNamespace - The namespace prefix to replace with a dot notation
84
+ * @returns A string in dot notation following camelCase conventions
85
+ */
86
+ function nestedConventionNamespaced(envKey, envNamespace) {
87
+ return envKey
88
+ .replace(`${envNamespace}_`, `${envNamespace}.`)
89
+ .toLowerCase()
90
+ .replace(/__/g, ".")
91
+ .replace(/[a-z_]+/g, (word) => (0, lodash_1.camelCase)(word));
92
+ }
93
+ /**
94
+ * Attempts to parse a string value as JSON, falling back to the original string if parsing fails.
95
+ * Used for converting environment variables that might contain JSON values to mimic config file read from ENVs as well.
96
+ *
97
+ * @param value - The string value to parse as JSON
98
+ * @returns The parsed JSON value or the original string if parsing fails
99
+ */
100
+ function jsonify(value) {
101
+ try {
102
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
103
+ return JSON.parse(value ?? "");
104
+ }
105
+ catch {
106
+ return value;
107
+ }
108
+ }
109
+ /**
110
+ * Transforms environment variables from a flat structure with a specific namespace prefix
111
+ * into a nested object structure with camelCase keys, while preserving the original keys
112
+ * for error reporting.
113
+ *
114
+ * For example, transforms:
115
+ * {
116
+ * "MY_APP_SERVER__HOST": "localhost",
117
+ * "MY_APP_DATABASE__PORT": "5432",
118
+ * "UNRELATED_VAR": "value"
119
+ * }
120
+ *
121
+ * With namespace "myApp" into:
122
+ * {
123
+ * "my_app": {
124
+ * "server": {
125
+ * "host": "localhost"
126
+ * },
127
+ * "database": {
128
+ * "port": 5432 // Note: jsonify attempts to parse values
129
+ * }
130
+ * }
131
+ * }
132
+ *
133
+ * @param variables - Record of environment variables with string keys and string values
134
+ * @param namespace - The namespace prefix to filter variables (will be converted to SNAKE_CASE)
135
+ * @returns A tuple containing:
136
+ * 1. The transformed nested configuration object
137
+ * 2. A Map relating the transformed path keys to original environment variable names for error reporting
138
+ */
139
+ function decodeVariables(variables, namespace) {
140
+ const envKeys = new Map();
141
+ const envNamespace = (0, lodash_1.snakeCase)(namespace).toUpperCase();
142
+ const relevantEnv = (0, lodash_1.pickBy)(variables, (_value, key) => key.startsWith(envNamespace));
143
+ const decodedEnv = (0, lodash_1.reduce)(relevantEnv, (env, value, key) => {
144
+ const newKey = nestedConventionNamespaced(key, envNamespace);
145
+ envKeys.set(newKey, key);
146
+ const newValue = jsonify(value);
147
+ return (0, lodash_1.set)(env, newKey, newValue);
148
+ }, {});
149
+ return [decodedEnv, envKeys];
150
+ }
151
+ /**
152
+ * Recursively navigates through a Zod schema to find description metadata at a specific path.
153
+ * Handles various Zod schema types including effects, transformers, and objects.
154
+ *
155
+ * @param path - Array of string keys representing the path in the schema
156
+ * @param schema - The Zod schema to search through
157
+ * @returns The description string if found, otherwise undefined
158
+ */
159
+ function findDescriptionInSchemaByPath(path, schema) {
160
+ // we have to work with ZodTypeAny to accept anything, so we disable the unsafe argument check
161
+ if ((0, lodash_1.isEmpty)(path))
162
+ return schema?.description;
163
+ else if (schema instanceof zod_1.ZodEffects)
164
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
165
+ return findDescriptionInSchemaByPath(path, schema.innerType());
166
+ else if (schema instanceof zod_1.ZodTransformer)
167
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
168
+ return findDescriptionInSchemaByPath(path, schema.innerType());
169
+ else if (schema instanceof zod_1.ZodObject)
170
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
171
+ return findDescriptionInSchemaByPath((0, lodash_1.tail)(path), (0, lodash_1.get)(schema.shape, (0, lodash_1.head)(path) ?? ""));
172
+ else
173
+ return schema?.description;
174
+ }
175
+ /**
176
+ * Converts Zod validation errors into a more descriptive TypeError.
177
+ * Enhances error messages with schema descriptions when available.
178
+ *
179
+ * @param error - The Zod error containing validation issues
180
+ * @param schema - The namespaced schema that was used for validation
181
+ * @param namespace - The configuration namespace for context in error messages
182
+ * @param keys - Map of path strings to user-friendly key names for better error reporting
183
+ * @returns A TypeError with formatted error messages for each validation issue
184
+ */
185
+ function zodErrorToTypeError(error, schema, namespace, keys, isSchemaNamespaced = true) {
186
+ const errorMessages = error.issues.map((err) => {
187
+ const pathNotation = err.path.join(".");
188
+ const path = keys.get(pathNotation) ?? pathNotation;
189
+ const message = err.message;
190
+ const description = findDescriptionInSchemaByPath(err.path.map((v) => v.toString()), schema);
191
+ return { path, message, description };
192
+ });
193
+ return new ZodConfigError({
194
+ name: namespace,
195
+ description: findDescriptionInSchemaByPath(isSchemaNamespaced ? [namespace] : [], schema),
196
+ }, errorMessages);
197
+ }
198
+ /**
199
+ * Simple error wrapper for expressing an invalid config from a zod schema.
200
+ */
201
+ class ZodConfigError extends TypeError {
202
+ namespace;
203
+ configErrors;
204
+ constructor(namespace, configErrors, options) {
205
+ const spacing = " ";
206
+ const title = `Invalid config for "${namespace.name}":\n`;
207
+ const description = (0, lodash_1.isUndefined)(namespace.description)
208
+ ? ""
209
+ : `${spacing}${namespace.description}\n`;
210
+ const errors = configErrors.map((v) => ZodConfigError.mapError(v, spacing)).join("\n");
211
+ super(title + description + errors, options);
212
+ this.namespace = namespace;
213
+ this.configErrors = configErrors;
214
+ }
215
+ static mapError(error, spacing) {
216
+ let message = `${spacing}- ${error.path}: ${error.message}`;
217
+ message += (0, lodash_1.isUndefined)(error.description) ? "" : `\n${spacing}${error.description}`;
218
+ return message;
219
+ }
220
+ }
221
+ exports.ZodConfigError = ZodConfigError;
222
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":";;;;;;AA2EA,oCAWC;AAgED,0CAmBC;AAmCD,kDA0BC;AAtOD,sDAAyB;AAEzB,mCAagB;AAEhB,gDAAwB;AACxB,6BAAqF;AAErF;;GAEG;AACH,IAAI,uBAA2C,CAAC;AAChD;;GAEG;AACH,6EAA6E;AAC7E,IAAI,uBAA4C,CAAC;AAEjD;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,OAAe;IACnC,IAAI,CAAC,IAAA,oBAAW,EAAC,uBAAuB,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAE1E,uBAAuB,GAAG,cAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO,uBAAuB,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,IAAY;IACpC,IAAI,CAAC;QACJ,IAAI,CAAC,IAAA,iBAAQ,EAAC,uBAAuB,CAAC;YACrC,uBAAuB,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAElE,OAAO,WAAW,CAAC,uBAAuB,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,EAAE,EAAE;YACtE,KAAK,EAAE,GAAG;SACV,CAAC,CAAC;IACJ,CAAC;AACF,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,YAAY,CAAC,OAAgB,EAAE,IAAa;IAC3D,IAAI,UAAU,GAAY,EAAE,CAAC;IAC7B,+FAA+F;IAC/F,IAAI,IAAA,iBAAQ,EAAC,OAAO,CAAC;QAAE,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;SACpD,IAAI,IAAA,iBAAQ,EAAC,IAAI,CAAC;QAAE,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAE5D,IAAI,CAAC,IAAA,qBAAY,EAAC,UAAU,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IAEpF,uEAAuE;IACvE,OAAO,UAAuC,CAAC;AAChD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,0BAA0B,CAAC,MAAc,EAAE,YAAoB;IACvE,OAAO,MAAM;SACX,OAAO,CAAC,GAAG,YAAY,GAAG,EAAE,GAAG,YAAY,GAAG,CAAC;SAC/C,WAAW,EAAE;SACb,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,kBAAS,EAAC,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,KAAyB;IACzC,IAAI,CAAC;QACJ,uEAAuE;QACvE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAc,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,SAAgB,eAAe,CAC9B,SAA6C,EAC7C,SAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,YAAY,GAAG,IAAA,kBAAS,EAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,MAAM,WAAW,GAAG,IAAA,eAAM,EAAC,SAAS,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;IACrF,MAAM,UAAU,GAAG,IAAA,eAAM,EACxB,WAAW,EACX,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,0BAA0B,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,IAAA,YAAG,EAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC,EACD,EAA2C,CAC3C,CAAC;IAEF,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,6BAA6B,CAAC,IAAc,EAAE,MAAkB;IACxE,8FAA8F;IAC9F,IAAI,IAAA,gBAAO,EAAC,IAAI,CAAC;QAAE,OAAO,MAAM,EAAE,WAAW,CAAC;SACzC,IAAI,MAAM,YAAY,gBAAU;QACpC,iEAAiE;QACjE,OAAO,6BAA6B,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;SAC3D,IAAI,MAAM,YAAY,oBAAc;QACxC,iEAAiE;QACjE,OAAO,6BAA6B,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;SAC3D,IAAI,MAAM,YAAY,eAAS;QACnC,iEAAiE;QACjE,OAAO,6BAA6B,CAAC,IAAA,aAAI,EAAC,IAAI,CAAC,EAAE,IAAA,YAAG,EAAC,MAAM,CAAC,KAAK,EAAE,IAAA,aAAI,EAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;;QAClF,OAAO,MAAM,EAAE,WAAW,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,mBAAmB,CAClC,KAAiB,EACjB,MAAkB,EAClB,SAAiB,EACjB,IAAyB,EACzB,qBAA8B,IAAI;IAElC,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC9C,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC;QACpD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC5B,MAAM,WAAW,GAAG,6BAA6B,CAChD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,EACjC,MAAM,CACN,CAAC;QAEF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,cAAc,CACxB;QACC,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,6BAA6B,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;KACzF,EACD,aAAa,CACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAa,cAAe,SAAQ,SAAS;IAE3B;IAIA;IALjB,YACiB,SAGf,EACe,YAIb,EACH,OAAsB;QAEtB,MAAM,OAAO,GAAG,MAAM,CAAC;QACvB,MAAM,KAAK,GAAG,uBAAuB,SAAS,CAAC,IAAI,MAAM,CAAC;QAC1D,MAAM,WAAW,GAAG,IAAA,oBAAW,EAAC,SAAS,CAAC,WAAW,CAAC;YACrD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC,WAAW,IAAI,CAAC;QAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEvF,KAAK,CAAC,KAAK,GAAG,WAAW,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC;QAlB7B,cAAS,GAAT,SAAS,CAGxB;QACe,iBAAY,GAAZ,YAAY,CAIzB;IAWJ,CAAC;IAEO,MAAM,CAAC,QAAQ,CACtB,KAAyE,EACzE,OAAe;QAEf,IAAI,OAAO,GAAG,GAAG,OAAO,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QAC5D,OAAO,IAAI,IAAA,oBAAW,EAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QACpF,OAAO,OAAO,CAAC;IAChB,CAAC;CACD;AA/BD,wCA+BC"}
@@ -0,0 +1,75 @@
1
+ import { type ConfigurableModuleAsyncOptions, ConfigurableModuleBuilder, type ConfigurableModuleBuilderOptions, type DynamicModule } from "@nestjs/common";
2
+ import type { DEFAULT_FACTORY_CLASS_METHOD_KEY, DEFAULT_METHOD_KEY } from "@nestjs/common/module-utils/constants";
3
+ import { type ZodType, type ZodTypeDef } from "zod";
4
+ export declare class ZodConfigurableModuleBuilder<ModuleOptions, ModuleOptionsInput = unknown, StaticMethodKey extends string = typeof DEFAULT_METHOD_KEY, FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY, ExtraModuleDefinitionOptions = object> {
5
+ protected readonly schema: ZodType<ModuleOptions, ZodTypeDef, ModuleOptionsInput>;
6
+ protected readonly options: ConfigurableModuleBuilderOptions;
7
+ protected base: ConfigurableModuleBuilder<ModuleOptionsInput, StaticMethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
8
+ private transformSet;
9
+ constructor(schema: ZodType<ModuleOptions, ZodTypeDef, ModuleOptionsInput>, options?: ConfigurableModuleBuilderOptions, parentBuilder?: ConfigurableModuleBuilder<ModuleOptionsInput>);
10
+ setExtras<ExtraModuleDefinitionOptions>(extras: ExtraModuleDefinitionOptions, transformDefinition?: (definition: DynamicModule, extras: ExtraModuleDefinitionOptions) => DynamicModule): ZodConfigurableModuleBuilder<ModuleOptions, ModuleOptionsInput, StaticMethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
11
+ private patchTransform;
12
+ setClassMethodName<StaticMethodKey extends string>(key: StaticMethodKey): ZodConfigurableModuleBuilder<ModuleOptions, ModuleOptionsInput, StaticMethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
13
+ setFactoryMethodName<FactoryClassMethodKey extends string>(key: FactoryClassMethodKey): ZodConfigurableModuleBuilder<ModuleOptions, ModuleOptionsInput, StaticMethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
14
+ build(): ZodConfigurableModuleHost<ModuleOptions, ModuleOptionsInput, StaticMethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
15
+ private transformModuleDefinitionWithSchema;
16
+ }
17
+ type ZodConfigurableModuleCls<ModuleOptions, ModuleOptionsInput = unknown, MethodKey extends string = typeof DEFAULT_METHOD_KEY, FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY, ExtraModuleDefinitionOptions = object> = {
18
+ new (): any;
19
+ } & Record<`${MethodKey}`, (options: ModuleOptionsInput & Partial<ExtraModuleDefinitionOptions>) => DynamicModule> & Record<`${MethodKey}Async`, (options: ConfigurableModuleAsyncOptions<ModuleOptionsInput, FactoryClassMethodKey> & Partial<ExtraModuleDefinitionOptions>) => DynamicModule>;
20
+ interface ZodConfigurableModuleHost<ModuleOptions = Record<string, unknown>, ModuleOptionsInput = unknown, MethodKey extends string = string, FactoryClassMethodKey extends string = string, ExtraModuleDefinitionOptions = object> {
21
+ /**
22
+ * Class that represents a blueprint/prototype for a configurable Nest module.
23
+ * This class provides static methods for constructing dynamic modules. Their names
24
+ * can be controlled through the "MethodKey" type argument.
25
+ *
26
+ * Your module class should inherit from this class to make the static methods available.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * @Module({})
31
+ * class IntegrationModule extends ConfigurableModuleCls {
32
+ * // ...
33
+ * }
34
+ * ```
35
+ */
36
+ ConfigurableModuleClass: ZodConfigurableModuleCls<ModuleOptions, ModuleOptionsInput, MethodKey, FactoryClassMethodKey, ExtraModuleDefinitionOptions>;
37
+ /**
38
+ * Module options provider token. Can be used to inject the "options object" to
39
+ * providers registered within the host module.
40
+ */
41
+ MODULE_OPTIONS_TOKEN: string | symbol;
42
+ /**
43
+ * Can be used to auto-infer the compound "async module options" type.
44
+ * Note: this property is not supposed to be used as a value.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * @Module({})
49
+ * class IntegrationModule extends ConfigurableModuleCls {
50
+ * static module = initializer(IntegrationModule);
51
+ *
52
+ * static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
53
+ * return super.registerAsync(options);
54
+ * }
55
+ * ```
56
+ */
57
+ OPTIONS_TYPE: ModuleOptions & Partial<ExtraModuleDefinitionOptions>;
58
+ /**
59
+ * Can be used to auto-infer the compound "module options" type (options interface + extra module definition options).
60
+ * Note: this property is not supposed to be used as a value.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * @Module({})
65
+ * class IntegrationModule extends ConfigurableModuleCls {
66
+ * static module = initializer(IntegrationModule);
67
+ *
68
+ * static register(options: typeof OPTIONS_TYPE): DynamicModule {
69
+ * return super.register(options);
70
+ * }
71
+ * ```
72
+ */
73
+ ASYNC_OPTIONS_TYPE: ConfigurableModuleAsyncOptions<ModuleOptions, FactoryClassMethodKey> & Partial<ExtraModuleDefinitionOptions>;
74
+ }
75
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZodConfigurableModuleBuilder = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const lodash_1 = require("lodash");
6
+ const zod_1 = require("zod");
7
+ const internal_1 = require("../internal");
8
+ class ZodConfigurableModuleBuilder {
9
+ schema;
10
+ options;
11
+ base;
12
+ transformSet = false;
13
+ constructor(schema, options = {}, parentBuilder) {
14
+ this.schema = schema;
15
+ this.options = options;
16
+ this.base = new common_1.ConfigurableModuleBuilder(options, parentBuilder);
17
+ }
18
+ setExtras(extras, transformDefinition = (def) => def) {
19
+ const builder = new ZodConfigurableModuleBuilder(this.schema, this.options, this);
20
+ // this was called and we patched
21
+ this.transformSet = true;
22
+ builder.base = this.patchTransform(extras, transformDefinition);
23
+ return builder;
24
+ }
25
+ patchTransform(extras, transformDefinition = (def) => def) {
26
+ transformDefinition ??= (definition) => definition;
27
+ const existingTransform = transformDefinition;
28
+ transformDefinition = (definition, extraOptions) => {
29
+ const result = existingTransform(definition, extraOptions);
30
+ return this.transformModuleDefinitionWithSchema(result, extraOptions, extras);
31
+ };
32
+ return this.base.setExtras(extras, transformDefinition);
33
+ }
34
+ setClassMethodName(key) {
35
+ const builder = new ZodConfigurableModuleBuilder(this.schema, this.options, this);
36
+ builder.base = builder.base.setClassMethodName(key);
37
+ return builder;
38
+ }
39
+ setFactoryMethodName(key) {
40
+ const builder = new ZodConfigurableModuleBuilder(this.schema, this.options, this);
41
+ builder.base = builder.base.setFactoryMethodName(key);
42
+ return builder;
43
+ }
44
+ build() {
45
+ if (!this.transformSet)
46
+ this.base = this.patchTransform({});
47
+ return this.base.build();
48
+ }
49
+ transformModuleDefinitionWithSchema(definition, extraOptions, extras) {
50
+ const isOptionProvider = (provider) => "provide" in provider && provider.provide === this.options.optionsInjectionToken;
51
+ definition.providers = definition.providers?.map((provider) => {
52
+ if (isOptionProvider(provider)) {
53
+ if ("useFactory" in provider) {
54
+ const existingFactory = provider.useFactory;
55
+ provider.useFactory = async (...args) => {
56
+ try {
57
+ return await this.schema.parseAsync(existingFactory(...args));
58
+ }
59
+ catch (err) {
60
+ if (err instanceof zod_1.ZodError) {
61
+ throw (0, internal_1.zodErrorToTypeError)(err, this.schema, definition.module.name, new Map(), false);
62
+ }
63
+ throw err;
64
+ }
65
+ };
66
+ }
67
+ else {
68
+ return {
69
+ // biome-ignore lint/style/noNonNullAssertion: the token is guaranteed to be set
70
+ provide: this.options.optionsInjectionToken,
71
+ useFactory: async () => {
72
+ const finalOptions = (0, lodash_1.isObject)(extraOptions)
73
+ ? (0, lodash_1.omit)(extraOptions, (0, lodash_1.keys)(extras))
74
+ : extraOptions;
75
+ try {
76
+ return await this.schema.parseAsync(finalOptions);
77
+ }
78
+ catch (err) {
79
+ if (err instanceof zod_1.ZodError) {
80
+ throw (0, internal_1.zodErrorToTypeError)(err, this.schema, definition.module.name, new Map(), false);
81
+ }
82
+ throw err;
83
+ }
84
+ },
85
+ };
86
+ }
87
+ }
88
+ return provider;
89
+ });
90
+ return definition;
91
+ }
92
+ }
93
+ exports.ZodConfigurableModuleBuilder = ZodConfigurableModuleBuilder;
94
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/module/module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const common_1 = require("@nestjs/common");
38
+ const testing_1 = require("@nestjs/testing");
39
+ const zod_1 = require("zod");
40
+ const module_1 = require("./module");
41
+ describe("module", () => {
42
+ const MyOptionSchema = zod_1.z
43
+ .object({
44
+ useFlags: zod_1.z.boolean().describe("Whether the use of flag is considered when connecting"),
45
+ clientName: zod_1.z.string().min(5).describe("The client name used for connecting to the universe"),
46
+ })
47
+ .describe("The options for connecting using the dynamic module")
48
+ .transform((v) => ({ ...v, supportsTransforms: false }))
49
+ .transform(async (v) => ({ ...v, supportsTransforms: true }));
50
+ const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new module_1.ZodConfigurableModuleBuilder(MyOptionSchema)
51
+ .setExtras({
52
+ isGlobal: false,
53
+ }, (definition, extras) => ({
54
+ ...definition,
55
+ global: extras.isGlobal,
56
+ }))
57
+ .setClassMethodName("forRoot")
58
+ .build();
59
+ let DynModule = (() => {
60
+ let _classDecorators = [(0, common_1.Module)({})];
61
+ let _classDescriptor;
62
+ let _classExtraInitializers = [];
63
+ let _classThis;
64
+ let _classSuper = ConfigurableModuleClass;
65
+ var DynModule = class extends _classSuper {
66
+ static { _classThis = this; }
67
+ static {
68
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
69
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
70
+ DynModule = _classThis = _classDescriptor.value;
71
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
72
+ __runInitializers(_classThis, _classExtraInitializers);
73
+ }
74
+ };
75
+ return DynModule = _classThis;
76
+ })();
77
+ it("should register correct options with static synchronous method", async () => {
78
+ const moduleRef = await testing_1.Test.createTestingModule({
79
+ imports: [
80
+ DynModule.forRoot({
81
+ useFlags: true,
82
+ clientName: "some long enough client name",
83
+ }),
84
+ ],
85
+ }).compile();
86
+ const options = moduleRef.get(MODULE_OPTIONS_TOKEN);
87
+ expect(options).toMatchObject({
88
+ useFlags: true,
89
+ clientName: "some long enough client name",
90
+ supportsTransforms: true,
91
+ });
92
+ });
93
+ it("should register correct options with asynchronous method", async () => {
94
+ const DEFAULTS_TOKEN = "DEFAULT_OPTIONS";
95
+ const defaultsValue = {
96
+ useFlags: false,
97
+ };
98
+ const moduleRef = await testing_1.Test.createTestingModule({
99
+ imports: [
100
+ DynModule.forRootAsync({
101
+ provideInjectionTokensFrom: [{ provide: DEFAULTS_TOKEN, useValue: defaultsValue }],
102
+ useFactory: (defaults) => {
103
+ return {
104
+ ...defaults,
105
+ clientName: "some long enough client name",
106
+ };
107
+ },
108
+ inject: [{ token: DEFAULTS_TOKEN, optional: false }],
109
+ }),
110
+ ],
111
+ }).compile();
112
+ const options = moduleRef.get(MODULE_OPTIONS_TOKEN);
113
+ expect(options).toMatchObject({
114
+ useFlags: false,
115
+ clientName: "some long enough client name",
116
+ supportsTransforms: true,
117
+ });
118
+ });
119
+ it("should throw an error when clientName is too short", async () => {
120
+ await expect(testing_1.Test.createTestingModule({
121
+ imports: [
122
+ DynModule.forRoot({
123
+ useFlags: true,
124
+ clientName: "shor", // Less than 5 characters
125
+ }),
126
+ ],
127
+ }).compile()).rejects.toThrow();
128
+ });
129
+ it("should throw an error when required options are missing", async () => {
130
+ await expect(testing_1.Test.createTestingModule({
131
+ imports: [
132
+ // @ts-expect-error: we are passing what could be a complete wrong and unchecked object
133
+ DynModule.forRoot({
134
+ // Missing clientName
135
+ useFlags: true,
136
+ }),
137
+ ],
138
+ }).compile()).rejects.toThrow();
139
+ });
140
+ it("should throw an error when async factory returns invalid options", async () => {
141
+ const DEFAULTS_TOKEN = "INVALID_DEFAULTS";
142
+ const defaultsValue = {
143
+ useFlags: false,
144
+ };
145
+ await expect(testing_1.Test.createTestingModule({
146
+ imports: [
147
+ DynModule.forRootAsync({
148
+ provideInjectionTokensFrom: [{ provide: DEFAULTS_TOKEN, useValue: defaultsValue }],
149
+ useFactory: (defaults) => {
150
+ return {
151
+ ...defaults,
152
+ clientName: "bad", // Too short
153
+ };
154
+ },
155
+ inject: [{ token: DEFAULTS_TOKEN, optional: false }],
156
+ }),
157
+ ],
158
+ }).compile()).rejects.toThrow();
159
+ });
160
+ it("should throw an error when invalid option types are provided", async () => {
161
+ await expect(testing_1.Test.createTestingModule({
162
+ imports: [
163
+ DynModule.forRoot({
164
+ // @ts-expect-error: we are passing a wrong and unchecked field
165
+ useFlags: "not-a-boolean", // Wrong type
166
+ clientName: "some long enough client name",
167
+ }),
168
+ ],
169
+ }).compile()).rejects.toThrow();
170
+ });
171
+ it("should throw an error when injected token is not found and not optional", async () => {
172
+ const NON_EXISTENT_TOKEN = "NON_EXISTENT_TOKEN";
173
+ await expect(testing_1.Test.createTestingModule({
174
+ imports: [
175
+ DynModule.forRootAsync({
176
+ useFactory: () => ({
177
+ useFlags: true,
178
+ clientName: "some long enough client name",
179
+ }),
180
+ inject: [{ token: NON_EXISTENT_TOKEN, optional: false }],
181
+ }),
182
+ ],
183
+ }).compile()).rejects.toThrow();
184
+ });
185
+ });
186
+ //# sourceMappingURL=module.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.spec.js","sourceRoot":"","sources":["../../src/module/module.spec.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@proventuslabs/nestjs-zod",
3
+ "version": "1.0.0-rc.1",
4
+ "license": "MIT",
5
+ "description": "A collection of NestJS modules to integrate Zod into your application.",
6
+ "keywords": [
7
+ "nestjs",
8
+ "zod",
9
+ "config",
10
+ "module"
11
+ ],
12
+ "author": "ProventusLabs <info@proventuslabs.com>",
13
+ "homepage": "https://github.com/proventuslabs/nestjs/tree/main/packages/zod#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/proventuslabs/nestjs.git",
17
+ "directory": "packages/zod"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/proventuslabs/nestjs/issues"
21
+ },
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "prepublishOnly": "npm run build",
29
+ "build": "nest build",
30
+ "lint": "biome check .",
31
+ "lint:fix": "biome check . --write",
32
+ "format": "biome format .",
33
+ "format:fix": "biome format . --write",
34
+ "typecheck": "tsc --noEmit",
35
+ "test": "jest",
36
+ "test:watch": "jest --watch",
37
+ "test:coverage": "jest --coverage",
38
+ "test:coverage:watch": "jest --coverage --watch"
39
+ },
40
+ "dependencies": {
41
+ "lodash": "^4.17.21",
42
+ "yaml": "^2.8.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
46
+ "@nestjs/config": "^3.0.0 || ^4.0.0",
47
+ "zod": "^3.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@nestjs/cli": "^11.0.0",
51
+ "@nestjs/common": "^11.0.0",
52
+ "@nestjs/core": "^11.0.0",
53
+ "@nestjs/platform-express": "^11.0.0",
54
+ "@nestjs/testing": "^11.0.0",
55
+ "@types/jest": "^30.0.0",
56
+ "@types/lodash": "^4.17.18",
57
+ "@types/node": "^20.0.0",
58
+ "jest": "^30.0.0",
59
+ "reflect-metadata": "^0.2.2",
60
+ "rxjs": "^7.8.2",
61
+ "ts-jest": "^29.4.0",
62
+ "type-fest": "^4.41.0",
63
+ "typescript": "^5.8.3",
64
+ "zod": "^3.25.67"
65
+ }
66
+ }