@pawells/config 2.3.1 → 3.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.
@@ -1,100 +1,88 @@
1
1
  import { ConfigManager } from './manager.js';
2
2
  import { IsMarkedSecret } from './secret.js';
3
- import { ConfigurationNotRegisteredError, ConfigurationNotSetError } from './errors.js';
4
- /**
5
- * Intelligently parse an environment variable string to a JSON-compatible value.
6
- * Attempts to parse as JSON first (handles objects, arrays, booleans, null, numbers).
7
- * Falls back to treating as a plain string if JSON parsing fails.
8
- *
9
- * @param envVarValue - Environment variable value (always a string from process.env)
10
- * @returns Parsed value (JSON-parsed if applicable, otherwise the original string)
11
- *
12
- * @example
13
- * parseEnvVarValue('true') → true (boolean)
14
- * parseEnvVarValue('123') → 123 (number)
15
- * parseEnvVarValue('["a","b"]') → ['a', 'b'] (array)
16
- * parseEnvVarValue('hello') → 'hello' (string, unchanged)
17
- */
18
- function ParseEnvVarValue(envVarValue) {
19
- try {
20
- return JSON.parse(envVarValue);
21
- }
22
- catch {
23
- // If JSON parsing fails, treat as a plain string
24
- return envVarValue;
25
- }
26
- }
3
+ import { ConfigNotRegisteredError, ConfigNotSetError } from './errors.js';
27
4
  /**
28
5
  * Safely extracts the default value from a Zod schema, handling both
29
6
  * lazy defaults (functions) and eager defaults (plain values).
7
+ * Traverses wrapper schemas (ZodDefault, ZodOptional, ZodNullable, etc.)
8
+ * to find the default value.
30
9
  *
31
10
  * @param schema - The field schema to extract the default from
32
11
  * @returns The evaluated default value, or undefined if not present
12
+ * @remarks Returns `undefined` in two distinct cases: (1) the schema has no `ZodDefault` wrapper, and (2) the `ZodDefault` wrapper explicitly wraps `undefined` as its default value. Callers cannot distinguish these cases from the return value alone.
33
13
  */
34
- function extractDefaultValue(schema) {
35
- const _def = schema._def;
36
- if (!_def || typeof _def !== 'object')
37
- return undefined;
38
- const dv = _def.defaultValue;
39
- if (dv === undefined)
40
- return undefined;
41
- // Handle both lazy (function) and eager (plain value) defaults
42
- return typeof dv === 'function' ? dv() : dv;
14
+ function ExtractDefaultValue(schema) {
15
+ let current = schema;
16
+ while (current != null) {
17
+ const def = current.def;
18
+ if (def != null && 'defaultValue' in def) {
19
+ return def.defaultValue;
20
+ }
21
+ const inner = current.unwrap;
22
+ if (typeof inner === 'function') {
23
+ current = inner.call(current);
24
+ }
25
+ else {
26
+ break;
27
+ }
28
+ }
29
+ return undefined;
43
30
  }
44
31
  /**
45
- * Creates a configuration schema object from a Zod schema and prefix.
32
+ * Registers a configuration schema with {@link ConfigManager} and returns a typed
33
+ * accessor object for the namespace.
34
+ *
35
+ * All schema fields are registered with {@link ConfigManager} immediately when this
36
+ * function is called. The name is used to derive the environment variable prefix:
37
+ * `name.toUpperCase() + '_'` (e.g. `'Keycloak'` → `KEYCLOAK_`).
46
38
  *
47
- * Generic factory that eliminates duplication between concrete config classes.
48
- * Returns an object with Register, Get, Set, Validate, and ParseENV methods
49
- * that manage config values for a specific namespace.
39
+ * **Important:** Call {@link ConfigManager.RegisterProvider} for all providers before
40
+ * importing any module that calls `RegisterConfigSchema` so that provider values are
41
+ * available when schemas are registered.
50
42
  *
51
- * @param schema - Zod schema defining the config shape and validation rules
52
- * @param prefix - Prefix for environment variable names (e.g., 'MONGODB_')
53
- * @returns ConfigSchemaObject with static-like methods for the config namespace
54
- * @template TSchema - The Zod schema shape
43
+ * @param name - Human-readable namespace name; used to derive the env var prefix
44
+ * @param schema - Zod object schema defining the config shape and validation rules
45
+ * @returns {@link IConfigSchemaObject} with typed accessor methods
46
+ * @throws {ConfigurationError} If any field's default value does not satisfy its schema
55
47
  *
56
48
  * @example
57
49
  * ```typescript
58
- * const MONGODB_SCHEMA = z.object({
59
- * HOST: z.string().default('localhost'),
60
- * PORT: z.number().default(27017),
50
+ * const KEYCLOAK_SCHEMA = z.object({
51
+ * AUTH_SERVER_URL: z.string().url().default('http://localhost:8080/auth'),
52
+ * REALM: z.string().min(1).default('master'),
61
53
  * });
62
54
  *
63
- * export const MongoDBConfig = createConfigSchema(MONGODB_SCHEMA, 'MONGODB_');
64
- * // Usage:
65
- * // MongoDBConfig.Register();
66
- * // const host = MongoDBConfig.Get('HOST');
67
- * // MongoDBConfig.Set('PORT', 27018);
68
- * // MongoDBConfig.ParseENV();
55
+ * export const KeycloakConfig = RegisterConfigSchema('Keycloak', KEYCLOAK_SCHEMA);
56
+ * // → prefix is 'KEYCLOAK_'; fields registered immediately
57
+ * // → KeycloakConfig.Get('AUTH_SERVER_URL') returns the resolved value
69
58
  * ```
70
59
  */
71
- export function CreateConfigSchema(schema, prefix) {
72
- // Build a map of key -> prefixed name
60
+ export function RegisterConfigSchema(name, schema) {
61
+ const prefix = `${name.toUpperCase()}_`;
62
+ ConfigManager.RegisterNamespace(name, prefix);
63
+ // Build a map of key -> prefixed name, a set of secret keys, and register all fields in a single pass
73
64
  const prefixedNames = {};
74
- for (const key in schema.shape) {
75
- if (Object.prototype.hasOwnProperty.call(schema.shape, key)) {
76
- prefixedNames[key] = `${prefix}${key}`;
65
+ const secretKeys = new Set();
66
+ for (const key of Object.keys(schema.shape)) {
67
+ const prefixedKey = prefix ? `${prefix}${key}` : key;
68
+ prefixedNames[key] = prefixedKey;
69
+ const fieldSchema = schema.shape[key];
70
+ if (IsMarkedSecret(fieldSchema)) {
71
+ secretKeys.add(key);
77
72
  }
73
+ // Register schema field with its default value
74
+ const defaultValue = ExtractDefaultValue(fieldSchema);
75
+ // Type: Register accepts ZodTypeAny; runtime enforcement via AssertConfigValueType
76
+ ConfigManager.Register(prefixedKey, fieldSchema, defaultValue);
78
77
  }
79
- // Build a set of secret keys by checking each field's metadata
80
- const secretKeys = new Set(Object.keys(schema.shape).filter((key) => IsMarkedSecret(schema.shape[key])));
81
78
  return {
82
- Register() {
83
- for (const key in schema.shape) {
84
- if (Object.prototype.hasOwnProperty.call(schema.shape, key)) {
85
- const fieldSchema = schema.shape[key];
86
- // Safely extract default value from Zod's internal structure
87
- const defaultValue = extractDefaultValue(fieldSchema);
88
- // @ts-expect-error Zod schema shape types don't align with ConfigManager.Register signature, but this is safe at runtime
89
- ConfigManager.Register(prefixedNames[key], fieldSchema, defaultValue);
90
- }
91
- }
92
- },
79
+ name,
93
80
  Get(key) {
94
81
  return ConfigManager.Get(prefixedNames[key]);
95
82
  },
96
83
  Set(key, value, source = 'OVERRIDE') {
97
- // @ts-expect-error TConfig may contain values that don't match TConfigValueTypes, but we trust the schema validation
84
+ // Type: TConfig[K] maps to TConfigValueTypes at runtime via Zod schema validation.
85
+ // The schema was validated at registration (line 190), so this cast is safe.
98
86
  ConfigManager.Set(prefixedNames[key], value, source);
99
87
  },
100
88
  Validate(key, value) {
@@ -107,27 +95,6 @@ export function CreateConfigSchema(schema, prefix) {
107
95
  return false;
108
96
  }
109
97
  },
110
- ParseENV(throwOnError = false) {
111
- for (const key in schema.shape) {
112
- if (Object.prototype.hasOwnProperty.call(schema.shape, key)) {
113
- const envVarName = prefixedNames[key];
114
- const envVarValue = process.env[envVarName];
115
- if (envVarValue !== undefined) {
116
- try {
117
- const fieldSchema = schema.shape[key];
118
- // Pre-process env var string: try JSON parsing first, fall back to plain string
119
- const preProcessedValue = ParseEnvVarValue(envVarValue);
120
- const parsedValue = fieldSchema.parse(preProcessedValue);
121
- this.Set(key, parsedValue, 'OVERRIDE');
122
- }
123
- catch (error) {
124
- if (throwOnError)
125
- throw error;
126
- }
127
- }
128
- }
129
- }
130
- },
131
98
  IsSecret(key) {
132
99
  return secretKeys.has(key);
133
100
  },
@@ -142,8 +109,8 @@ export function CreateConfigSchema(schema, prefix) {
142
109
  result[key] = secretKeys.has(key) ? '***' : value;
143
110
  }
144
111
  catch (error) {
145
- if (error instanceof ConfigurationNotSetError ||
146
- error instanceof ConfigurationNotRegisteredError) {
112
+ if (error instanceof ConfigNotSetError
113
+ || error instanceof ConfigNotRegisteredError) {
147
114
  // omit unregistered/unset keys
148
115
  continue;
149
116
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.factory.js","sourceRoot":"","sources":["../src/schema.factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.factory.js","sourceRoot":"","sources":["../src/schema.factory.ts"],"names":[],"mappings":"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"}
package/dist/secret.d.ts CHANGED
@@ -12,7 +12,7 @@ import { z } from 'zod/v4';
12
12
  *
13
13
  * @remarks
14
14
  * The inferred TypeScript type is unchanged and will not affect type inference
15
- * for values validated by this schema. CreateConfigSchema automatically detects
15
+ * for values validated by this schema. RegisterConfigSchema automatically detects
16
16
  * this marker via the IsMarkedSecret helper to handle sensitive fields specially.
17
17
  *
18
18
  * @example
@@ -28,11 +28,22 @@ import { z } from 'zod/v4';
28
28
  * ```
29
29
  */
30
30
  export declare function Secret<T extends z.ZodTypeAny>(schema: T): T;
31
+ /**
32
+ * Generator function that traverses a Zod schema chain to its base type.
33
+ *
34
+ * Yields each schema in the unwrap chain, starting with the input schema.
35
+ * Handles both .unwrap() method (for schemas that support it) and .def.innerType fallback.
36
+ *
37
+ * @param schema - The Zod schema to traverse
38
+ * @yields Each schema in the chain from outermost to innermost
39
+ * @internal
40
+ */
41
+ export declare function traverseSchemaToBase(schema: z.ZodTypeAny): Generator<z.ZodTypeAny>;
31
42
  /**
32
43
  * Internal helper function that traverses the Zod schema metadata chain
33
44
  * to determine if a schema is marked as secret.
34
45
  *
35
- * Walks through the innerType chain of wrapper schemas (e.g., ZodDefault,
46
+ * Walks through the unwrap chain of wrapper schemas (e.g., ZodDefault,
36
47
  * ZodOptional) and checks each level's metadata in the global registry.
37
48
  *
38
49
  * @param schema - The Zod schema to check for secret metadata
@@ -1 +1 @@
1
- {"version":3,"file":"secret.d.ts","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAG3D;AAED;;;;;;;;;;GAUG;AACH,iBAAS,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,OAAO,CAsBrD;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"secret.d.ts","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAM3D;AAED;;;;;;;;;GASG;AACH,wBAAiB,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAkBnF;AAED;;;;;;;;;;GAUG;AACH,iBAAS,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,OAAO,CAgBrD;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/secret.js CHANGED
@@ -12,7 +12,7 @@ import { z } from 'zod/v4';
12
12
  *
13
13
  * @remarks
14
14
  * The inferred TypeScript type is unchanged and will not affect type inference
15
- * for values validated by this schema. CreateConfigSchema automatically detects
15
+ * for values validated by this schema. RegisterConfigSchema automatically detects
16
16
  * this marker via the IsMarkedSecret helper to handle sensitive fields specially.
17
17
  *
18
18
  * @example
@@ -29,13 +29,41 @@ import { z } from 'zod/v4';
29
29
  */
30
30
  export function Secret(schema) {
31
31
  const existingMeta = z.globalRegistry.get(schema) ?? {};
32
+ if (existingMeta.secret === true) {
33
+ return schema; // Already marked as secret, no-op
34
+ }
32
35
  return schema.meta({ ...existingMeta, secret: true });
33
36
  }
37
+ /**
38
+ * Generator function that traverses a Zod schema chain to its base type.
39
+ *
40
+ * Yields each schema in the unwrap chain, starting with the input schema.
41
+ * Handles both .unwrap() method (for schemas that support it) and .def.innerType fallback.
42
+ *
43
+ * @param schema - The Zod schema to traverse
44
+ * @yields Each schema in the chain from outermost to innermost
45
+ * @internal
46
+ */
47
+ export function* traverseSchemaToBase(schema) {
48
+ let current = schema;
49
+ while (current != null) {
50
+ yield current;
51
+ // Traverse to the next level via unwrap or .def.innerType (present in wrapper schemas)
52
+ const unwrapFn = current.unwrap;
53
+ if (typeof unwrapFn === 'function') {
54
+ current = unwrapFn.call(current);
55
+ }
56
+ else {
57
+ const innerType = current.def?.innerType;
58
+ current = innerType;
59
+ }
60
+ }
61
+ }
34
62
  /**
35
63
  * Internal helper function that traverses the Zod schema metadata chain
36
64
  * to determine if a schema is marked as secret.
37
65
  *
38
- * Walks through the innerType chain of wrapper schemas (e.g., ZodDefault,
66
+ * Walks through the unwrap chain of wrapper schemas (e.g., ZodDefault,
39
67
  * ZodOptional) and checks each level's metadata in the global registry.
40
68
  *
41
69
  * @param schema - The Zod schema to check for secret metadata
@@ -43,8 +71,7 @@ export function Secret(schema) {
43
71
  * @internal
44
72
  */
45
73
  function IsMarkedSecret(schema) {
46
- let current = schema;
47
- while (current != null) {
74
+ for (const current of traverseSchemaToBase(schema)) {
48
75
  try {
49
76
  const meta = z.globalRegistry.get(current);
50
77
  if (meta?.secret === true) {
@@ -56,8 +83,6 @@ function IsMarkedSecret(schema) {
56
83
  // Treat as non-secret and stop traversal.
57
84
  break;
58
85
  }
59
- // Traverse to the next level via innerType (present in wrapper schemas)
60
- current = current._def?.innerType;
61
86
  }
62
87
  return false;
63
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"secret.js","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,MAAM,CAAyB,MAAS;IACvD,MAAM,YAAY,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAM,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,cAAc,CAAC,MAAoB;IAC3C,IAAI,OAAO,GAA6B,MAAM,CAAC;IAE/C,OAAO,OAAO,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC3B,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,8EAA8E;YAC9E,0CAA0C;YAC1C,MAAM;QACP,CAAC;QAED,wEAAwE;QACxE,OAAO,GAAI,OAAO,CAAC,IAA2C,EAAE,SAEpD,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC;AAED,wEAAwE;AACxE,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"secret.js","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,MAAM,CAAyB,MAAS;IACvD,MAAM,YAAY,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxD,IAAK,YAAqC,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC,CAAC,kCAAkC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAM,CAAC;AAC5D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,SAAS,CAAC,CAAC,oBAAoB,CAAC,MAAoB;IACzD,IAAI,OAAO,GAA6B,MAAM,CAAC;IAE/C,OAAO,OAAO,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,OAAO,CAAC;QAEd,uFAAuF;QACvF,MAAM,QAAQ,GAAI,OAA2C,CAAC,MAAM,CAAC;QACrE,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;YACpC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAiB,CAAC;QAClD,CAAC;aACI,CAAC;YACL,MAAM,SAAS,GAAI,OAA6C,CAAC,GAAG,EAAE,SAEzD,CAAC;YACd,OAAO,GAAG,SAAS,CAAC;QACrB,CAAC;IACF,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,cAAc,CAAC,MAAoB;IAC3C,KAAK,MAAM,OAAO,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC3B,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC;QACD,MAAM,CAAC;YACN,8EAA8E;YAC9E,0CAA0C;YAC1C,MAAM;QACP,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC;AAED,wEAAwE;AACxE,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@pawells/config",
3
3
  "displayName": "Config",
4
- "version": "2.3.1",
4
+ "version": "3.0.1",
5
5
  "description": "Type-safe configuration management with environment variable loading and validation",
6
6
  "keywords": [
7
7
  "config",
8
8
  "configuration",
9
- "environment",
9
+ "schema",
10
+ "validation",
11
+ "zod",
12
+ "secrets",
10
13
  "typescript",
11
14
  "nodejs"
12
15
  ],
@@ -15,9 +18,14 @@
15
18
  "name": "Phillip Aaron Wells",
16
19
  "email": "69355326+PhillipAWells@users.noreply.github.com"
17
20
  },
18
- "homepage": "https://github.com/PhillipAWells/common",
21
+ "homepage": "https://github.com/PhillipAWells/config",
22
+ "bugs": {
23
+ "url": "https://github.com/PhillipAWells/config/issues"
24
+ },
19
25
  "repository": {
20
- "url": "https://github.com/PhillipAWells/common.git"
26
+ "type": "git",
27
+ "url": "https://github.com/PhillipAWells/config.git",
28
+ "directory": "packages/config"
21
29
  },
22
30
  "funding": {
23
31
  "type": "github",
@@ -27,12 +35,11 @@
27
35
  "node": ">=22.0.0"
28
36
  },
29
37
  "type": "module",
30
- "main": "./dist/index.js",
31
- "module": "./dist/index.js",
32
38
  "types": "./dist/index.d.ts",
33
39
  "exports": {
34
40
  "./package.json": "./package.json",
35
41
  ".": {
42
+ "local": "./src/index.ts",
36
43
  "types": "./dist/index.d.ts",
37
44
  "import": "./dist/index.js",
38
45
  "default": "./dist/index.js"
@@ -40,12 +47,19 @@
40
47
  },
41
48
  "files": [
42
49
  "dist",
43
- "CHANGELOG.md",
44
50
  "!**/*.tsbuildinfo",
45
51
  "LICENSE"
46
52
  ],
53
+ "sideEffects": false,
54
+ "scripts": {
55
+ "test:coverage": "yarn nx test config -- --coverage"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
47
60
  "dependencies": {
61
+ "@pawells/typescript-common": "^3.0.5",
48
62
  "tslib": "^2.8.1",
49
63
  "zod": "^4.4.3"
50
64
  }
51
- }
65
+ }
package/CHANGELOG.md DELETED
@@ -1,19 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- [Unreleased]: https://github.com/PhillipAWells/common/compare/v2.3.0...HEAD
11
-
12
- ## [2.3.0] - 2026-06-01
13
-
14
- ### Added
15
-
16
- - **Secret** — `Secret<T>` wrapper type for marking config values as sensitive; `IsSecret()` type guard for runtime detection; `GetSecretKeys()` for retrieving all secret field names from a config object; `Redact()` for producing a copy of a config with all secret values masked
17
- - **ConfigManager.GenerateEnv** — new method for serializing a registered config to `.env` file format, with options to target specific keys and to unmask secret values
18
-
19
- [2.3.0]: https://github.com/PhillipAWells/common/releases/tag/v2.3.0