@galaxus/chabis-application-config 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Digitec Galaxus AG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # @galaxus/chabis-application-config
2
+
3
+ Type-safe application configuration for Node and TypeScript. Describe your config
4
+ shape with a **zod** or **valibot** schema and get back one fully typed object,
5
+ merged from layered YAML/TOML files and cloud secrets and validated against that
6
+ schema
7
+
8
+ It implements the Chabis `application-config` contract: the same file layering,
9
+ merge semantics, and secret resolution as the Python `chabis-application-config`
10
+ library, so a service keeps one config layout across stacks
11
+
12
+ What you get:
13
+
14
+ - Layered `settings`, `settings.{env}`, and `.secrets` files in YAML or TOML,
15
+ deep-merged in a fixed priority order
16
+ - Secrets from Google Cloud Parameter Manager or Azure Key Vault, filling
17
+ placeholders left in those files
18
+ - GPM versions resolve to the latest automatically, no version pin to bump
19
+ - Environment-variable overrides derived from your schema, no magic prefix
20
+ - A single typed object validated against your schema, or a thrown error
21
+
22
+ ## Install
23
+
24
+ ```sh
25
+ pnpm add @galaxus/chabis-application-config
26
+ ```
27
+
28
+ Bring a schema library as a peer dependency, zod or valibot:
29
+
30
+ ```sh
31
+ pnpm add zod
32
+ # or
33
+ pnpm add valibot @valibot/to-json-schema
34
+ ```
35
+
36
+ Google Cloud Parameter Manager is included out of the box. Azure Key Vault is
37
+ optional, install its SDKs only if you use an `AzureKeyVault` provider instead:
38
+
39
+ ```sh
40
+ pnpm add @azure/identity @azure/keyvault-secrets
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```ts
46
+ import { z } from "zod";
47
+ import { loadConfig } from "@galaxus/chabis-application-config";
48
+
49
+ const Schema = z.object({
50
+ LogLevel:
51
+ z.string().default("INFO"),
52
+ Database:
53
+ z.object({ Url: z.string() }),
54
+ Auth:
55
+ z.object({ ClientId: z.string(), TenantId: z.string(), Secret: z.string() }),
56
+ });
57
+
58
+ export const config = await loadConfig(Schema, {
59
+ // directory holding the settings.*.{yaml,toml} files, defaults to process.cwd()
60
+ settingsDir: "./configs",
61
+ // selects settings.{env}.*, defaults to process.env.ENV ?? "dev"
62
+ env: process.env.ENV ?? "dev",
63
+ });
64
+ // config is fully typed: config.Auth.Secret, config.Database.Url
65
+ ```
66
+
67
+ Typesafe:
68
+
69
+ <img width="1268" height="635" alt="VSCode Typescript Tooltip" src="https://github.com/user-attachments/assets/0bc6b786-bd8c-48a3-aaf7-f9c31cda94a2" />
70
+
71
+
72
+ `loadConfig` is async (secret fetches are async) and memoized by
73
+ `(schema, settingsDir, env)`. Pass `{ fresh: true }` to bypass the cache, or call
74
+ `resetConfigCache()`
75
+
76
+ ## Options
77
+
78
+ `loadConfig(schema, opts)` takes an optional second argument:
79
+
80
+ | Field | Type | Default | Purpose |
81
+ | --- | --- | --- | --- |
82
+ | `settingsDir` | `string` | `process.cwd()` | Directory holding the `settings.*.{yaml,toml}` files |
83
+ | `env` | `string` | `process.env.ENV ?? "dev"` | Selects the `settings.{env}.*` layer |
84
+ | `overrides` | `Record<string, unknown>` | `{}` | Highest-priority layer, deep-merged on top of everything |
85
+ | `envNestingDelimiter` | `string` | `"__"` | Separator between schema leaf segments in env-var names |
86
+ | `fresh` | `boolean` | `false` | Bypass the memoized result and force a fresh load |
87
+
88
+ `overrides` and environment variables are two different layers (tiers 1 and 2)
89
+ with two different shapes:
90
+
91
+ - `overrides` is a **nested object** passed in code, `{ Database: { Url: "…" } }`.
92
+ It is the `process.env`-free way to force a value, e.g. in tests
93
+ - Env vars are **flat, `__`-delimited strings**, `Database__Url=…`. See
94
+ [Environment-variable overrides](#environment-variable-overrides)
95
+
96
+ ## Files
97
+
98
+ `loadConfig` reads, for a given `settingsDir` and `env`, any of these that exist:
99
+
100
+ | Base name | Purpose |
101
+ | --- | --- |
102
+ | `settings.{toml,yaml}` | Base configuration (lowest priority) |
103
+ | `settings.{env}.{toml,yaml}` | Environment-specific overrides |
104
+ | `.secrets.{toml,yaml}` | Local secrets, highest-priority file layer |
105
+
106
+ ## How it resolves a value (priority, highest to lowest)
107
+
108
+ The order is 1:1 with the Python `settings_customise_sources`:
109
+
110
+ ```
111
+ 1. opts.overrides
112
+ 2. env vars (schema leaf) Database__Url -> { Database: { Url } }
113
+ 3. Azure Key Vault secrets (when configured)
114
+ 4. Google Parameter Manager secrets
115
+ 5. .secrets.{toml,yaml}
116
+ 6. settings.{env}.{toml,yaml}
117
+ 7. settings.{toml,yaml}
118
+ ```
119
+
120
+ Within the file tiers every TOML file outranks every YAML file
121
+ (`*toml_sources` before `*yaml_sources`), and base-name order is
122
+ `.secrets` > `settings.{env}` > `settings`, so the full file priority, high to low:
123
+
124
+ ```
125
+ .secrets.toml > settings.{env}.toml > settings.toml >
126
+ .secrets.yaml > settings.{env}.yaml > settings.yaml
127
+ ```
128
+
129
+ The secret tiers (3 to 4) outrank every settings file (5 to 7), so a
130
+ `"<Configured-in-GoogleParameterManager>"` placeholder written in
131
+ `settings.dev.yaml` is replaced by the real secret payload
132
+
133
+ ### Merge semantics
134
+
135
+ Applied lowest to highest (pydantic-settings `deep_update`):
136
+
137
+ - Plain objects deep-merge recursively
138
+ - Every other value, **including arrays**, is replaced wholesale by the
139
+ higher-priority layer, no array concatenation
140
+
141
+ ## Environment-variable overrides
142
+
143
+ Selection is schema-driven: the loader walks your schema's leaf paths and looks up
144
+ the env var named by joining each path with the nesting delimiter (`__`). A
145
+ `Database.Url` leaf is overridden by `Database__Url`, mapped back to
146
+ `{ Database: { Url } }`. Matching is case-sensitive against your verbatim keys, so
147
+ there is no prefix and ambient vars like `PATH` never leak in
148
+
149
+ ```sh
150
+ Database__Url=postgres://… node app.js
151
+ ```
152
+
153
+ Values stay raw strings, coercion to number or bool is the schema's job
154
+ (`z.coerce.number()`). Change the delimiter per call:
155
+
156
+ ```ts
157
+ await loadConfig(Schema, { envNestingDelimiter: "." }); // Database.Url=…
158
+ ```
159
+
160
+ Only leaves declared in the schema are overridable, and only zod and valibot
161
+ schemas can be introspected. For any other vendor, env overrides quietly do not
162
+ apply rather than failing the load
163
+
164
+ `ENV` selects the environment and is **not** itself an override. To force a value
165
+ from code without touching `process.env`, pass a nested object to
166
+ [`overrides`](#options) instead (tier 1, outranks env vars)
167
+
168
+ ## The `Secrets` block
169
+
170
+ Any settings file may carry a top-level `Secrets` array. It is metadata, stripped
171
+ before validation, and drives the providers. If more than one file declares it the
172
+ last in execution order wins (TOML before YAML); in practice exactly one file does
173
+
174
+ ```yaml
175
+ Secrets:
176
+ - Provider: GoogleParameterManager
177
+ GCPProject: my-project-dev
178
+ GCPParameterName: my-app-config
179
+ # GCPParameterVersion: v5 # OPTIONAL, omit for auto-latest
180
+ ```
181
+
182
+ - Auth via Application Default Credentials
183
+ - Without `GCPParameterVersion` the provider lists all versions and picks the
184
+ newest `createTime`
185
+ - The payload is parsed JSON-first, YAML-fallback, deep-merged whole at tier 4
186
+ - Any failure (no versions, list/fetch error, unparseable payload) **throws**
187
+
188
+ Azure:
189
+
190
+ ```yaml
191
+ Secrets:
192
+ - Provider: AzureKeyVault
193
+ AzureKeyVault: my-vault
194
+ Mode: per-leaf # default, one GET per schema leaf, Auth.Secret -> Auth--Secret
195
+ # Mode: single-blob # one secret holding a JSON/YAML blob
196
+ # SecretName: config # single-blob secret name (default "config")
197
+ ```
198
+
199
+ - `per-leaf` (default): introspect the schema's leaf paths, GET one secret per leaf
200
+ with `.` → `--`, reassemble nested. Missing secrets are skipped (fall through to
201
+ a lower layer)
202
+ - `single-blob`: GET one secret whose payload is a JSON/YAML blob (any vendor)
203
+ - Auth via `DefaultAzureCredential`. SDKs are loaded only when Azure is configured
204
+
205
+ `SecretFiles` is reserved, declaring it throws `NotImplementedProviderError`
206
+
207
+ ## Validation
208
+
209
+ Validation runs through the [Standard Schema](https://standardschema.dev)
210
+ interface, so zod, valibot, and arktype all work. On issues a
211
+ `ConfigValidationError` lists each `path: message`, on success the typed
212
+ `result.value` is returned
213
+
214
+ Unknown-key handling is your schema's call: zod strips unknown keys unless you use
215
+ `z.looseObject({...})` or `.loose()`, and any key you want kept must be declared or
216
+ it is stripped
217
+
218
+ Azure `per-leaf` mode reads your schema's leaf paths to derive secret names, which
219
+ works on zod and valibot only (via their JSON Schema emitters). Any other vendor
220
+ must use `Mode: single-blob`
221
+
222
+ ## Errors
223
+
224
+ Everything surfaces fast as a thrown subclass of `ChabisConfigError`. The library
225
+ logs nothing
226
+
227
+ | Error | When |
228
+ | --- | --- |
229
+ | `SecretsConfigError` | Malformed `Secrets` block |
230
+ | `ProviderError` | GPM/Azure auth/list/fetch/parse failure (wraps the cause) |
231
+ | `IntrospectionError` | Azure `per-leaf` with a vendor that has no JSON-Schema emitter |
232
+ | `ConfigValidationError` | Standard Schema validation issues |
233
+ | `NotImplementedProviderError` | `SecretFiles` provider requested |
234
+ | `PlaceholderError` | `assertNoPlaceholders` found an unresolved placeholder |
235
+
236
+ `assertNoPlaceholders(config)` is an opt-in helper that throws if any leaf string
237
+ still starts with `"<Configured-in-"`, guarding against a provider key that never
238
+ matched a field
239
+
240
+ ## More
241
+
242
+ See [`examples/zod`](examples/zod) and [`examples/valibot`](examples/valibot) for
243
+ runnable setups
@@ -0,0 +1,297 @@
1
+ /** The Standard Typed interface. This is a base type extended by other specs. */
2
+ interface StandardTypedV1<Input = unknown, Output = Input> {
3
+ /** The Standard properties. */
4
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
5
+ }
6
+ declare namespace StandardTypedV1 {
7
+ /** The Standard Typed properties interface. */
8
+ interface Props<Input = unknown, Output = Input> {
9
+ /** The version number of the standard. */
10
+ readonly version: 1;
11
+ /** The vendor name of the schema library. */
12
+ readonly vendor: string;
13
+ /** Inferred types associated with the schema. */
14
+ readonly types?: Types<Input, Output> | undefined;
15
+ }
16
+ /** The Standard Typed types interface. */
17
+ interface Types<Input = unknown, Output = Input> {
18
+ /** The input type of the schema. */
19
+ readonly input: Input;
20
+ /** The output type of the schema. */
21
+ readonly output: Output;
22
+ }
23
+ /** Infers the input type of a Standard Typed. */
24
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
25
+ /** Infers the output type of a Standard Typed. */
26
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
27
+ }
28
+ /** The Standard Schema interface. */
29
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
30
+ /** The Standard Schema properties. */
31
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
32
+ }
33
+ declare namespace StandardSchemaV1 {
34
+ /** The Standard Schema properties interface. */
35
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
36
+ /** Validates unknown input values. */
37
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
38
+ }
39
+ /** The result interface of the validate function. */
40
+ type Result<Output> = SuccessResult<Output> | FailureResult;
41
+ /** The result interface if validation succeeds. */
42
+ interface SuccessResult<Output> {
43
+ /** The typed output value. */
44
+ readonly value: Output;
45
+ /** A falsy value for `issues` indicates success. */
46
+ readonly issues?: undefined;
47
+ }
48
+ interface Options {
49
+ /** Explicit support for additional vendor-specific parameters, if needed. */
50
+ readonly libraryOptions?: Record<string, unknown> | undefined;
51
+ }
52
+ /** The result interface if validation fails. */
53
+ interface FailureResult {
54
+ /** The issues of failed validation. */
55
+ readonly issues: ReadonlyArray<Issue>;
56
+ }
57
+ /** The issue interface of the failure output. */
58
+ interface Issue {
59
+ /** The error message of the issue. */
60
+ readonly message: string;
61
+ /** The path of the issue, if any. */
62
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
63
+ }
64
+ /** The path segment interface of the issue. */
65
+ interface PathSegment {
66
+ /** The key representing a path segment. */
67
+ readonly key: PropertyKey;
68
+ }
69
+ /** The Standard types interface. */
70
+ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {
71
+ }
72
+ /** Infers the input type of a Standard. */
73
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
74
+ /** Infers the output type of a Standard. */
75
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
76
+ }
77
+
78
+ /**
79
+ * Orchestration: gather every layer in priority order, deep-merge, validate
80
+ * against the Standard Schema, and memoize the result.
81
+ *
82
+ * Priority chain (HIGHEST to lowest), 1:1 with the Python source order:
83
+ * overrides, env vars, Azure secrets, GPM secrets, settings files (TOML > YAML).
84
+ * The secrets tier outranks every settings file, which is why
85
+ * `"<Configured-in-…>"` placeholders get replaced by the real secret payload
86
+ */
87
+
88
+ /**
89
+ * Recursively optional version of `T`: every nested object field may be
90
+ * omitted, so `overrides` can target a single leaf without restating its
91
+ * siblings. Arrays replace wholesale in the merge (decision #12), so their
92
+ * element type is kept intact rather than made partial
93
+ */
94
+ type DeepPartial<T> = T extends readonly unknown[] ? T : T extends object ? {
95
+ [K in keyof T]?: DeepPartial<T[K]>;
96
+ } : T;
97
+ interface LoadConfigOptions<S extends StandardSchemaV1 = StandardSchemaV1> {
98
+ /** Directory containing the `settings.*.{yaml,toml}` files. Default `process.cwd()` */
99
+ settingsDir?: string;
100
+ /** Environment selecting the `settings.{env}.*` layer. Default `process.env.ENV ?? "dev"` */
101
+ env?: string;
102
+ /**
103
+ * Highest-priority overrides, deep-merged on top of everything (parity with
104
+ * `init_settings`). Typed as a deep partial of the schema's input since
105
+ * overrides feed the merge before validation and may target any subset
106
+ */
107
+ overrides?: DeepPartial<StandardSchemaV1.InferInput<S>>;
108
+ /** Delimiter separating schema leaf segments in env-var names. Default `"__"` (`Database__Url`) */
109
+ envNestingDelimiter?: string;
110
+ /** Skip the memoized singleton and force a fresh load. Default `false` */
111
+ fresh?: boolean;
112
+ }
113
+ /**
114
+ * Load, merge, validate, and return the typed config. Throws on any provider
115
+ * error, validation failure, or missing-required field. The override-independent
116
+ * base is memoized by `(schema, settingsDir, env)` unless `opts.fresh`, and
117
+ * `overrides` are applied on top of that cached base on every call
118
+ */
119
+ declare function loadConfig<S extends StandardSchemaV1>(schema: S, opts?: LoadConfigOptions<S>): Promise<StandardSchemaV1.InferOutput<S>>;
120
+ /** Clear the singleton cache, mainly for tests */
121
+ declare function resetConfigCache(): void;
122
+
123
+ /**
124
+ * Secret-provider contract plus the shared JSON-first-then-YAML payload parser
125
+ * used by GPM and Azure single-blob mode (parity with the Python lib and the
126
+ * current `instrumentation.ts`)
127
+ */
128
+ /** A secret source that returns a nested object to deep-merge at the secrets tier */
129
+ interface SecretProvider {
130
+ /** Resolve secrets into a nested plain object, already shaped like the config */
131
+ load(): Promise<Record<string, unknown>>;
132
+ }
133
+
134
+ /**
135
+ * Parser for the top-level `Secrets` block. This is metadata, not part of the
136
+ * caller's config schema, so a tiny hand-written validator mirroring the Python
137
+ * `SecretsConfig` model handles it, never the consumer's schema
138
+ */
139
+ interface GpmSecretsConfig {
140
+ Provider: "GoogleParameterManager";
141
+ GCPProject: string;
142
+ GCPParameterName: string;
143
+ /** Omit for auto-latest resolution */
144
+ GCPParameterVersion?: string;
145
+ }
146
+ interface AzureSecretsConfig {
147
+ Provider: "AzureKeyVault";
148
+ AzureKeyVault: string;
149
+ /** Defaults to `per-leaf` (Python parity). `single-blob` works with any vendor */
150
+ Mode?: "per-leaf" | "single-blob";
151
+ /** Secret holding the whole config blob in `single-blob` mode, default `config` */
152
+ SecretName?: string;
153
+ }
154
+ interface SecretFilesConfig {
155
+ Provider: "SecretFiles";
156
+ SecretFilePath: string;
157
+ }
158
+ type SecretsConfig = GpmSecretsConfig | AzureSecretsConfig | SecretFilesConfig;
159
+
160
+ /**
161
+ * Google Parameter Manager provider, mirrors `DgGoogleParameterSecretsSource`.
162
+ *
163
+ * When `GCPParameterVersion` is omitted it lists all versions and picks the
164
+ * newest by `createTime`, replacing the hardcoded `/versions/v5` of the current
165
+ * `instrumentation.ts`. Any failure throws (decision #10), GPM errors are never
166
+ * swallowed
167
+ */
168
+
169
+ /** Minimal slice of the `@google-cloud/parametermanager` client we rely on */
170
+ interface ParameterVersion {
171
+ name?: string | null;
172
+ createTime?: {
173
+ seconds?: number | string | null;
174
+ nanos?: number | null;
175
+ } | null;
176
+ payload?: {
177
+ data?: Uint8Array | string | null;
178
+ } | null;
179
+ }
180
+ interface ParameterManagerClient {
181
+ parameterPath(project: string, location: string, parameter: string): string;
182
+ listParameterVersions(request: {
183
+ parent: string;
184
+ }): Promise<[ParameterVersion[], unknown, unknown]>;
185
+ getParameterVersion(request: {
186
+ name: string;
187
+ }): Promise<[ParameterVersion, unknown, unknown]>;
188
+ }
189
+ /** Factory for the SDK client, overridable so tests can inject a fake */
190
+ type GpmClientFactory = () => Promise<ParameterManagerClient>;
191
+ declare class GoogleParameterManagerProvider implements SecretProvider {
192
+ private readonly config;
193
+ private readonly clientFactory;
194
+ constructor(config: GpmSecretsConfig, clientFactory?: GpmClientFactory);
195
+ load(): Promise<Record<string, unknown>>;
196
+ private resolveLatestVersion;
197
+ }
198
+
199
+ /**
200
+ * Azure Key Vault provider, mirrors `DgAzureKeyvaultSecretsSource`.
201
+ *
202
+ * `per-leaf` (default, Python parity): introspect the schema to enumerate leaf
203
+ * paths, then one GET per leaf mapping `.` to `--` (`Auth.Secret` becomes
204
+ * `Auth--Secret`), reassembling a nested object. Missing secrets are skipped so
205
+ * the field falls through to a lower-priority layer.
206
+ *
207
+ * `single-blob`: fetch one secret whose payload is JSON/YAML (same shape as GPM)
208
+ * and deep-merge it. No introspection, works with any vendor.
209
+ *
210
+ * The Azure SDKs are dynamically imported so GPM-only consumers never load them
211
+ */
212
+
213
+ /** Minimal slice of `@azure/keyvault-secrets`' `SecretClient` */
214
+ interface AzureSecretClient {
215
+ getSecret(name: string): Promise<{
216
+ value?: string | null;
217
+ }>;
218
+ }
219
+ /** Factory for the vault client, overridable so tests can inject a fake */
220
+ type AzureClientFactory = (vaultName: string) => Promise<AzureSecretClient>;
221
+ declare class AzureKeyVaultProvider implements SecretProvider {
222
+ private readonly config;
223
+ private readonly schema;
224
+ private readonly clientFactory;
225
+ constructor(config: AzureSecretsConfig, schema: StandardSchemaV1, clientFactory?: AzureClientFactory);
226
+ load(): Promise<Record<string, unknown>>;
227
+ private buildClient;
228
+ private loadPerLeaf;
229
+ private loadSingleBlob;
230
+ /** GET one secret, return undefined when missing (parity: swallow + skip) */
231
+ private tryGetSecret;
232
+ }
233
+
234
+ /**
235
+ * Error hierarchy. Every failure mode throws (decision #10) so a misconfigured
236
+ * deploy fails fast at boot instead of running on placeholder strings where
237
+ * real secrets should be
238
+ */
239
+ /** Base class for every error this library throws */
240
+ declare class ChabisConfigError extends Error {
241
+ constructor(message: string, options?: {
242
+ cause?: unknown;
243
+ });
244
+ }
245
+ /** Malformed `Secrets` block: missing required provider fields, unknown provider */
246
+ declare class SecretsConfigError extends ChabisConfigError {
247
+ }
248
+ /** A secret provider (GPM/Azure) failed to authenticate, list, fetch, or parse */
249
+ declare class ProviderError extends ChabisConfigError {
250
+ }
251
+ /**
252
+ * Azure `per-leaf` mode requested with a schema whose vendor has no registered
253
+ * JSON-Schema introspector, so leaf paths can't be enumerated
254
+ */
255
+ declare class IntrospectionError extends ChabisConfigError {
256
+ }
257
+ /** The merged config failed Standard Schema validation */
258
+ declare class ConfigValidationError extends ChabisConfigError {
259
+ }
260
+ /** Provider declared in the spec but not yet implemented (`SecretFiles`) */
261
+ declare class NotImplementedProviderError extends ChabisConfigError {
262
+ }
263
+
264
+ /**
265
+ * Opt-in placeholder guard (SPEC §10). A GPM/Azure key that matched no field
266
+ * path leaves its `"<Configured-in-…>"` placeholder untouched. Not run
267
+ * automatically: the consumer calls it after `loadConfig` if they want the check
268
+ */
269
+
270
+ /** Thrown when an unresolved placeholder remains */
271
+ declare class PlaceholderError extends ChabisConfigError {
272
+ }
273
+ /** Throw if any leaf string in `obj` still starts with `marker`, meaning a secret never resolved */
274
+ declare function assertNoPlaceholders(obj: unknown, marker?: string): void;
275
+
276
+ /**
277
+ * Vendor-detected JSON-Schema introspection (decision #9 / SPEC §9a).
278
+ *
279
+ * Standard Schema's `validate` does not expose fields, but the schema library
280
+ * can: zod v4 and valibot each emit a JSON Schema whose `properties` we recurse
281
+ * to recover the same leaf paths the Python Azure provider gets from
282
+ * `model_cls.model_fields`. The right emitter is picked from
283
+ * `schema["~standard"].vendor`, not by feature-detecting a method, because
284
+ * `z.toJSONSchema(schema)` and valibot's `toJsonSchema(schema)` are module-level
285
+ * functions, not methods on the schema object
286
+ */
287
+
288
+ /** Vendors that can be introspected for Azure `per-leaf` mode */
289
+ declare function supportedIntrospectionVendors(): string[];
290
+ /**
291
+ * Enumerate every leaf path of a schema (`[["Auth","Secret"], ["Database","Url"], …]`)
292
+ * via its vendor's JSON-Schema emitter. Throws `IntrospectionError` when the
293
+ * vendor has no registered introspector
294
+ */
295
+ declare function introspectLeafPaths(schema: StandardSchemaV1): Promise<string[][]>;
296
+
297
+ export { AzureKeyVaultProvider, type AzureSecretsConfig, ChabisConfigError, ConfigValidationError, type DeepPartial, GoogleParameterManagerProvider, type GpmSecretsConfig, IntrospectionError, type LoadConfigOptions, NotImplementedProviderError, PlaceholderError, ProviderError, type SecretFilesConfig, type SecretProvider, type SecretsConfig, SecretsConfigError, assertNoPlaceholders, introspectLeafPaths, loadConfig, resetConfigCache, supportedIntrospectionVendors };