typed-settings 0.1.1 → 0.1.4

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 CHANGED
@@ -185,7 +185,7 @@ Provider details: [docs/vault.md](docs/vault.md).
185
185
  | `schema` | Standard Schema | required | zod / valibot / arktype object, or a legacy bridge. An async `validate()` forces `settingsAsync()` |
186
186
  | `sources` | array | `['.env', 'env']` | layers in priority order, later wins |
187
187
  | `prefix` | `string` | - | global prefix strip, case-insensitive, once |
188
- | `envMap` | object | - | keys used literally: no prefix strip, no `__` splitting |
188
+ | `envMap` | object | `{}` | maps an env key to the literal key it should occupy (used as-is: no strip/split) |
189
189
  | `env` | object | `process.env` | the env snapshot. Pass `{}` to isolate, or a map in tests/on Edge |
190
190
  | `expand` | `boolean` | `true` | `$VAR` expansion after merging. `false` leaves literals alone |
191
191
  | `allowUnresolved` | `boolean` | `false` | `true` keeps `${MISSING}` as-is instead of throwing |
@@ -279,7 +279,9 @@ Things people actually hit, in rough order of frequency:
279
279
 
280
280
  - **`USE_ASYNC: provider is async`.** One of your providers (or the schema itself) is async. Switch to `settingsAsync()`.
281
281
  - **`E_UNRESOLVED: FOO missing`.** A `$FOO` reference points at nothing. Set the variable, give it a default (`${FOO:-fallback}`), or pass `allowUnresolved: true` to leave it literal.
282
- - **Schema rejects everything.** Keys are lowercased during load, so schemas need lowercase keys. `port`, not `PORT`.
282
+ - **Schema rejects everything.** Keys are lowercased during load, so schemas need lowercase keys (`port`, not `PORT`). The failure mode is sneaky: `z.object({ PORT: z.coerce.number() })` never matches the lowercased input, so it validates `undefined`, and `z.coerce.number()` turns that into `NaN` — you get the confusing "expected number, received NaN".
283
+ - **Everything is silently empty or wrong.** Most often a global `prefix` ate your keys: matching applies to every source, so `prefix: 'APP_'` drops a YAML key like `port:` and a secrets-dir entry like `API_TOKEN` without a word. Scope it per source, or skip it and bridge env names via `envMap`.
284
+ - **Deno (JSR) refuses a fresh release.** Deno's minimum-dependency-age policy blocks versions published within ~24h. For local dev, run with `--minimum-dependency-age=0`.
283
285
  - **`E_NO_FS` under ESM.** Core can't reach `node:fs` on its own. Import `typed-settings/node` once, or stick to `{ text }` / `{ map }` sources.
284
286
  - **`E_UNKNOWN_KEY`.** Extra keys with `unknownKeys: 'reject'` (the CLI calls this `--strict`). Either drop the keys or switch to `strip` / `preserve`.
285
287
  - **`SCHEMA_LOAD_ERROR`.** The CLI can't find your schema. Export it under the name `schema`, or point at it with `--schema-export NAME`.
@@ -3,6 +3,8 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  /**
4
4
  * Zod helpers: `withPrefix` scopes any Standard Schema under an env prefix,
5
5
  * `coerceEnv` is the identity hook for composed pipelines.
6
+ *
7
+ * @module
6
8
  */
7
9
 
8
10
  /**
@@ -3,6 +3,8 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  /**
4
4
  * Zod helpers: `withPrefix` scopes any Standard Schema under an env prefix,
5
5
  * `coerceEnv` is the identity hook for composed pipelines.
6
+ *
7
+ * @module
6
8
  */
7
9
 
8
10
  /**
@@ -1,4 +1,4 @@
1
- import { settings } from './chunk-RCO6K3WQ.js';
1
+ import { settings } from './chunk-YVJEZPPZ.js';
2
2
  import { parseEnvText } from './chunk-F6DX3HXZ.js';
3
3
  import { readFileSync, readdirSync, statSync, watch } from 'fs';
4
4
  import { join, dirname, basename, resolve } from 'path';
@@ -253,12 +253,12 @@ function expandKeys(flat, opts) {
253
253
  const stripped = stripPrefix(rawKey);
254
254
  if (stripped === null) continue;
255
255
  const useKey = stripped;
256
- if (useKey.includes("__")) {
257
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
258
- const parts = useKey.split("__");
256
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
257
+ const parts = useKey.split("__");
258
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
259
+ if (cleanSplit) {
259
260
  for (const p of parts) {
260
261
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
261
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
262
262
  }
263
263
  const lowered = parts.map((p) => p.toLowerCase());
264
264
  const top = lowered[0];
@@ -290,7 +290,6 @@ function expandKeys(flat, opts) {
290
290
  setPath(out, lowered, value);
291
291
  } else {
292
292
  const k = useKey.toLowerCase();
293
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
294
293
  const kind = kindOf(k);
295
294
  if (kind.indexed) {
296
295
  throw new ConfigError(
@@ -791,7 +790,13 @@ function makeLookup(merged, envSnapshot) {
791
790
  function expandDeepStrings(obj, lookup, ctx, path = []) {
792
791
  if (typeof obj === "string") {
793
792
  const top = (path[0] ?? "<root>").toLowerCase();
794
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
793
+ const from = ctx.fromMap.get(top) ?? "config";
794
+ try {
795
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
796
+ } catch (e) {
797
+ if (from === "env") return obj;
798
+ throw e;
799
+ }
795
800
  }
796
801
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
797
802
  if (obj && typeof obj === "object") {
package/dist/cli/main.cjs CHANGED
@@ -485,12 +485,12 @@ function expandKeys(flat, opts) {
485
485
  const stripped = stripPrefix(rawKey);
486
486
  if (stripped === null) continue;
487
487
  const useKey = stripped;
488
- if (useKey.includes("__")) {
489
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
490
- const parts = useKey.split("__");
488
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
489
+ const parts = useKey.split("__");
490
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
491
+ if (cleanSplit) {
491
492
  for (const p of parts) {
492
493
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
493
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
494
494
  }
495
495
  const lowered = parts.map((p) => p.toLowerCase());
496
496
  const top = lowered[0];
@@ -522,7 +522,6 @@ function expandKeys(flat, opts) {
522
522
  setPath(out, lowered, value);
523
523
  } else {
524
524
  const k = useKey.toLowerCase();
525
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
526
525
  const kind = kindOf(k);
527
526
  if (kind.indexed) {
528
527
  throw new ConfigError(
@@ -1044,7 +1043,13 @@ function makeLookup(merged, envSnapshot) {
1044
1043
  function expandDeepStrings(obj, lookup, ctx, path = []) {
1045
1044
  if (typeof obj === "string") {
1046
1045
  const top = (path[0] ?? "<root>").toLowerCase();
1047
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
1046
+ const from = ctx.fromMap.get(top) ?? "config";
1047
+ try {
1048
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
1049
+ } catch (e) {
1050
+ if (from === "env") return obj;
1051
+ throw e;
1052
+ }
1048
1053
  }
1049
1054
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
1050
1055
  if (obj && typeof obj === "object") {
package/dist/cli/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { watchSettings } from '../chunk-VTIYOSG2.js';
3
- import { settings } from '../chunk-RCO6K3WQ.js';
2
+ import { watchSettings } from '../chunk-DEP5SD4J.js';
3
+ import { settings } from '../chunk-YVJEZPPZ.js';
4
4
  import '../chunk-33M5GIKQ.js';
5
5
  import '../chunk-OVFMKC2O.js';
6
6
  import '../chunk-UFUHHR2J.js';
@@ -440,12 +440,12 @@ function expandKeys(flat, opts) {
440
440
  const stripped = stripPrefix(rawKey);
441
441
  if (stripped === null) continue;
442
442
  const useKey = stripped;
443
- if (useKey.includes("__")) {
444
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
445
- const parts = useKey.split("__");
443
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
444
+ const parts = useKey.split("__");
445
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
446
+ if (cleanSplit) {
446
447
  for (const p of parts) {
447
448
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
448
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
449
449
  }
450
450
  const lowered = parts.map((p) => p.toLowerCase());
451
451
  const top = lowered[0];
@@ -477,7 +477,6 @@ function expandKeys(flat, opts) {
477
477
  setPath(out, lowered, value);
478
478
  } else {
479
479
  const k = useKey.toLowerCase();
480
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
481
480
  const kind = kindOf(k);
482
481
  if (kind.indexed) {
483
482
  throw new ConfigError(
@@ -949,7 +948,13 @@ function makeLookup(merged, envSnapshot) {
949
948
  function expandDeepStrings(obj, lookup, ctx, path = []) {
950
949
  if (typeof obj === "string") {
951
950
  const top = (path[0] ?? "<root>").toLowerCase();
952
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
951
+ const from = ctx.fromMap.get(top) ?? "config";
952
+ try {
953
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
954
+ } catch (e) {
955
+ if (from === "env") return obj;
956
+ throw e;
957
+ }
953
958
  }
954
959
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
955
960
  if (obj && typeof obj === "object") {
@@ -1,6 +1,6 @@
1
1
  export { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  export { pickPublic, settings, settingsAsync } from '../index.cjs';
3
- export { S as SettingsOptions } from '../types-CClP1lxq.cjs';
3
+ export { S as SettingsOptions } from '../types-B_Dc2L4T.cjs';
4
4
  export { C as ConfigError } from '../errors-itGaU4Q8.cjs';
5
5
 
6
6
  /**
@@ -12,6 +12,8 @@ export { C as ConfigError } from '../errors-itGaU4Q8.cjs';
12
12
  * outside a React Server Component, which would break plain Node/vitest usage).
13
13
  * // settings.ts (server)
14
14
  * import 'server-only';
15
+ *
16
+ * @module
15
17
  */
16
18
 
17
19
  /** Explicit public subset - no spread, keys enumerated so secrets can't leak via `...cfg`. */
@@ -1,6 +1,6 @@
1
1
  export { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  export { pickPublic, settings, settingsAsync } from '../index.js';
3
- export { S as SettingsOptions } from '../types-CClP1lxq.js';
3
+ export { S as SettingsOptions } from '../types-B_Dc2L4T.js';
4
4
  export { C as ConfigError } from '../errors-itGaU4Q8.js';
5
5
 
6
6
  /**
@@ -12,6 +12,8 @@ export { C as ConfigError } from '../errors-itGaU4Q8.js';
12
12
  * outside a React Server Component, which would break plain Node/vitest usage).
13
13
  * // settings.ts (server)
14
14
  * import 'server-only';
15
+ *
16
+ * @module
15
17
  */
16
18
 
17
19
  /** Explicit public subset - no spread, keys enumerated so secrets can't leak via `...cfg`. */
@@ -1,4 +1,4 @@
1
- export { pickPublic, settings, settingsAsync } from '../chunk-RCO6K3WQ.js';
1
+ export { pickPublic, settings, settingsAsync } from '../chunk-YVJEZPPZ.js';
2
2
  import '../chunk-33M5GIKQ.js';
3
3
  import '../chunk-OVFMKC2O.js';
4
4
  import '../chunk-UFUHHR2J.js';
@@ -440,12 +440,12 @@ function expandKeys(flat, opts) {
440
440
  const stripped = stripPrefix(rawKey);
441
441
  if (stripped === null) continue;
442
442
  const useKey = stripped;
443
- if (useKey.includes("__")) {
444
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
445
- const parts = useKey.split("__");
443
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
444
+ const parts = useKey.split("__");
445
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
446
+ if (cleanSplit) {
446
447
  for (const p of parts) {
447
448
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
448
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
449
449
  }
450
450
  const lowered = parts.map((p) => p.toLowerCase());
451
451
  const top = lowered[0];
@@ -477,7 +477,6 @@ function expandKeys(flat, opts) {
477
477
  setPath(out, lowered, value);
478
478
  } else {
479
479
  const k = useKey.toLowerCase();
480
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
481
480
  const kind = kindOf(k);
482
481
  if (kind.indexed) {
483
482
  throw new ConfigError(
@@ -949,7 +948,13 @@ function makeLookup(merged, envSnapshot) {
949
948
  function expandDeepStrings(obj, lookup, ctx, path = []) {
950
949
  if (typeof obj === "string") {
951
950
  const top = (path[0] ?? "<root>").toLowerCase();
952
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
951
+ const from = ctx.fromMap.get(top) ?? "config";
952
+ try {
953
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
954
+ } catch (e) {
955
+ if (from === "env") return obj;
956
+ throw e;
957
+ }
953
958
  }
954
959
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
955
960
  if (obj && typeof obj === "object") {
@@ -1,7 +1,7 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  export { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  export { pickPublic, settings, settingsAsync } from '../index.cjs';
4
- import { S as SettingsOptions, F as Flat } from '../types-CClP1lxq.cjs';
4
+ import { S as SettingsOptions, F as Flat } from '../types-B_Dc2L4T.cjs';
5
5
  export { C as ConfigError } from '../errors-itGaU4Q8.cjs';
6
6
 
7
7
  /**
@@ -10,6 +10,8 @@ export { C as ConfigError } from '../errors-itGaU4Q8.cjs';
10
10
  * Rules: never spread `import.meta.env` (it drags server secrets into the
11
11
  * client bundle). Pass it as a map; file preloading belongs in `vite.config.ts`
12
12
  * via `loadEnvFiles()` passed as `{ text }`.
13
+ *
14
+ * @module
13
15
  */
14
16
 
15
17
  /**
@@ -1,7 +1,7 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  export { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  export { pickPublic, settings, settingsAsync } from '../index.js';
4
- import { S as SettingsOptions, F as Flat } from '../types-CClP1lxq.js';
4
+ import { S as SettingsOptions, F as Flat } from '../types-B_Dc2L4T.js';
5
5
  export { C as ConfigError } from '../errors-itGaU4Q8.js';
6
6
 
7
7
  /**
@@ -10,6 +10,8 @@ export { C as ConfigError } from '../errors-itGaU4Q8.js';
10
10
  * Rules: never spread `import.meta.env` (it drags server secrets into the
11
11
  * client bundle). Pass it as a map; file preloading belongs in `vite.config.ts`
12
12
  * via `loadEnvFiles()` passed as `{ text }`.
13
+ *
14
+ * @module
13
15
  */
14
16
 
15
17
  /**
@@ -1,5 +1,5 @@
1
- import { settings } from '../chunk-RCO6K3WQ.js';
2
- export { pickPublic, settings, settingsAsync } from '../chunk-RCO6K3WQ.js';
1
+ import { settings } from '../chunk-YVJEZPPZ.js';
2
+ export { pickPublic, settings, settingsAsync } from '../chunk-YVJEZPPZ.js';
3
3
  import '../chunk-33M5GIKQ.js';
4
4
  import '../chunk-OVFMKC2O.js';
5
5
  import '../chunk-UFUHHR2J.js';
package/dist/index.cjs CHANGED
@@ -440,12 +440,12 @@ function expandKeys(flat, opts) {
440
440
  const stripped = stripPrefix(rawKey);
441
441
  if (stripped === null) continue;
442
442
  const useKey = stripped;
443
- if (useKey.includes("__")) {
444
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
445
- const parts = useKey.split("__");
443
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
444
+ const parts = useKey.split("__");
445
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
446
+ if (cleanSplit) {
446
447
  for (const p of parts) {
447
448
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
448
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
449
449
  }
450
450
  const lowered = parts.map((p) => p.toLowerCase());
451
451
  const top = lowered[0];
@@ -477,7 +477,6 @@ function expandKeys(flat, opts) {
477
477
  setPath(out, lowered, value);
478
478
  } else {
479
479
  const k = useKey.toLowerCase();
480
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
481
480
  const kind = kindOf(k);
482
481
  if (kind.indexed) {
483
482
  throw new ConfigError(
@@ -978,7 +977,13 @@ function makeLookup(merged, envSnapshot) {
978
977
  function expandDeepStrings(obj, lookup, ctx, path = []) {
979
978
  if (typeof obj === "string") {
980
979
  const top = (path[0] ?? "<root>").toLowerCase();
981
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
980
+ const from = ctx.fromMap.get(top) ?? "config";
981
+ try {
982
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
983
+ } catch (e) {
984
+ if (from === "env") return obj;
985
+ throw e;
986
+ }
982
987
  }
983
988
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
984
989
  if (obj && typeof obj === "object") {
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
- import { S as SettingsOptions, F as Flat } from './types-CClP1lxq.cjs';
3
- export { A as ArrayStrategy, a as SecretProvider, b as SourceInput, U as UnknownKeys } from './types-CClP1lxq.cjs';
2
+ import { S as SettingsOptions, F as Flat } from './types-B_Dc2L4T.cjs';
3
+ export { A as ArrayStrategy, a as SecretProvider, b as SourceInput, U as UnknownKeys } from './types-B_Dc2L4T.cjs';
4
4
  export { C as ConfigError } from './errors-itGaU4Q8.cjs';
5
5
 
6
6
  /**
@@ -10,6 +10,8 @@ export { C as ConfigError } from './errors-itGaU4Q8.cjs';
10
10
  * secrets-dir maps, and vault secrets, then merge, expand `$VAR` references,
11
11
  * coerce, validate against a Standard Schema, and return the result frozen.
12
12
  * Anything invalid throws `ConfigError` at boot with the key and source attached.
13
+ *
14
+ * @module
13
15
  */
14
16
 
15
17
  /**
@@ -31,9 +33,12 @@ declare function pickPublic<T extends object>(cfg: T, keys: (keyof T)[]): Partia
31
33
  declare function viteSettings<S extends StandardSchemaV1>(opts: SettingsOptions<S>, metaEnv: Flat): StandardSchemaV1.InferOutput<S>;
32
34
  /**
33
35
  * Loads, merges, expands, coerces, and validates config synchronously, returning
34
- * the inferred output type, frozen. Throws `ConfigError` on invalid input (issues
35
- * carry `path` + winning-source `from`, secrets redacted) and `USE_ASYNC` when a
36
- * provider or the schema itself is async — then use `settingsAsync()`.
36
+ * the inferred output type, frozen. Each issue names the failing `path` and the
37
+ * source that won it, with secrets redacted.
38
+ *
39
+ * @throws ConfigError on merge, parse, expansion, or validation failure.
40
+ * @throws ConfigError with `code: 'USE_ASYNC'` if a provider or the schema is
41
+ * async — switch to `settingsAsync()`.
37
42
  */
38
43
  declare function settings<S extends StandardSchemaV1>(opts: SettingsOptions<S>): StandardSchemaV1.InferOutput<S>;
39
44
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
- import { S as SettingsOptions, F as Flat } from './types-CClP1lxq.js';
3
- export { A as ArrayStrategy, a as SecretProvider, b as SourceInput, U as UnknownKeys } from './types-CClP1lxq.js';
2
+ import { S as SettingsOptions, F as Flat } from './types-B_Dc2L4T.js';
3
+ export { A as ArrayStrategy, a as SecretProvider, b as SourceInput, U as UnknownKeys } from './types-B_Dc2L4T.js';
4
4
  export { C as ConfigError } from './errors-itGaU4Q8.js';
5
5
 
6
6
  /**
@@ -10,6 +10,8 @@ export { C as ConfigError } from './errors-itGaU4Q8.js';
10
10
  * secrets-dir maps, and vault secrets, then merge, expand `$VAR` references,
11
11
  * coerce, validate against a Standard Schema, and return the result frozen.
12
12
  * Anything invalid throws `ConfigError` at boot with the key and source attached.
13
+ *
14
+ * @module
13
15
  */
14
16
 
15
17
  /**
@@ -31,9 +33,12 @@ declare function pickPublic<T extends object>(cfg: T, keys: (keyof T)[]): Partia
31
33
  declare function viteSettings<S extends StandardSchemaV1>(opts: SettingsOptions<S>, metaEnv: Flat): StandardSchemaV1.InferOutput<S>;
32
34
  /**
33
35
  * Loads, merges, expands, coerces, and validates config synchronously, returning
34
- * the inferred output type, frozen. Throws `ConfigError` on invalid input (issues
35
- * carry `path` + winning-source `from`, secrets redacted) and `USE_ASYNC` when a
36
- * provider or the schema itself is async — then use `settingsAsync()`.
36
+ * the inferred output type, frozen. Each issue names the failing `path` and the
37
+ * source that won it, with secrets redacted.
38
+ *
39
+ * @throws ConfigError on merge, parse, expansion, or validation failure.
40
+ * @throws ConfigError with `code: 'USE_ASYNC'` if a provider or the schema is
41
+ * async — switch to `settingsAsync()`.
37
42
  */
38
43
  declare function settings<S extends StandardSchemaV1>(opts: SettingsOptions<S>): StandardSchemaV1.InferOutput<S>;
39
44
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { pickPublic, settings, settingsAsync, viteSettings, withOverrides } from './chunk-RCO6K3WQ.js';
1
+ export { pickPublic, settings, settingsAsync, viteSettings, withOverrides } from './chunk-YVJEZPPZ.js';
2
2
  import './chunk-33M5GIKQ.js';
3
3
  import './chunk-OVFMKC2O.js';
4
4
  import './chunk-UFUHHR2J.js';
package/dist/node.cjs CHANGED
@@ -444,12 +444,12 @@ function expandKeys(flat, opts) {
444
444
  const stripped = stripPrefix(rawKey);
445
445
  if (stripped === null) continue;
446
446
  const useKey = stripped;
447
- if (useKey.includes("__")) {
448
- if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
449
- const parts = useKey.split("__");
447
+ if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
448
+ const parts = useKey.split("__");
449
+ const cleanSplit = parts.length > 1 && parts.every((p) => p !== "");
450
+ if (cleanSplit) {
450
451
  for (const p of parts) {
451
452
  if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
452
- if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
453
453
  }
454
454
  const lowered = parts.map((p) => p.toLowerCase());
455
455
  const top = lowered[0];
@@ -481,7 +481,6 @@ function expandKeys(flat, opts) {
481
481
  setPath(out, lowered, value);
482
482
  } else {
483
483
  const k = useKey.toLowerCase();
484
- if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
485
484
  const kind = kindOf(k);
486
485
  if (kind.indexed) {
487
486
  throw new ConfigError(
@@ -861,7 +860,13 @@ function makeLookup(merged, envSnapshot) {
861
860
  function expandDeepStrings(obj, lookup, ctx, path = []) {
862
861
  if (typeof obj === "string") {
863
862
  const top = (path[0] ?? "<root>").toLowerCase();
864
- return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
863
+ const from = ctx.fromMap.get(top) ?? "config";
864
+ try {
865
+ return expandLeaf(obj, lookup, { ...ctx, from }, path.map((p) => p.toLowerCase()));
866
+ } catch (e) {
867
+ if (from === "env") return obj;
868
+ throw e;
869
+ }
865
870
  }
866
871
  if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
867
872
  if (obj && typeof obj === "object") {
package/dist/node.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as Flat, S as SettingsOptions } from './types-CClP1lxq.cjs';
1
+ import { F as Flat, S as SettingsOptions } from './types-B_Dc2L4T.cjs';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { C as ConfigError } from './errors-itGaU4Q8.cjs';
4
4
 
package/dist/node.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as Flat, S as SettingsOptions } from './types-CClP1lxq.js';
1
+ import { F as Flat, S as SettingsOptions } from './types-B_Dc2L4T.js';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { C as ConfigError } from './errors-itGaU4Q8.js';
4
4
 
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
- export { loadEnvFiles, loadSecretsDir, parseEnvFile, watchSettings } from './chunk-VTIYOSG2.js';
2
- import './chunk-RCO6K3WQ.js';
1
+ export { loadEnvFiles, loadSecretsDir, parseEnvFile, watchSettings } from './chunk-DEP5SD4J.js';
2
+ import './chunk-YVJEZPPZ.js';
3
3
  import './chunk-33M5GIKQ.js';
4
4
  import './chunk-OVFMKC2O.js';
5
5
  import './chunk-UFUHHR2J.js';
@@ -10,8 +10,10 @@ interface SecretProvider {
10
10
  }
11
11
  /**
12
12
  * One config source. Strings `'env' | '.env' | '.env.local'` are built-ins,
13
- * any other string is a file path; objects cover inline text, files,
14
- * secrets dirs (resolved by the caller), maps, and providers.
13
+ * any other string is a file path (relative to cwd; `.json`/`.yaml`/`.yml`/
14
+ * `.toml` are parsed structurally, anything else as `.env` text); objects cover
15
+ * inline text, files, secrets dirs (resolved by the caller), maps, and
16
+ * providers.
15
17
  */
16
18
  type SourceInput = 'env' | '.env' | '.env.local' | {
17
19
  text: string;
@@ -10,8 +10,10 @@ interface SecretProvider {
10
10
  }
11
11
  /**
12
12
  * One config source. Strings `'env' | '.env' | '.env.local'` are built-ins,
13
- * any other string is a file path; objects cover inline text, files,
14
- * secrets dirs (resolved by the caller), maps, and providers.
13
+ * any other string is a file path (relative to cwd; `.json`/`.yaml`/`.yml`/
14
+ * `.toml` are parsed structurally, anything else as `.env` text); objects cover
15
+ * inline text, files, secrets dirs (resolved by the caller), maps, and
16
+ * providers.
15
17
  */
16
18
  type SourceInput = 'env' | '.env' | '.env.local' | {
17
19
  text: string;
@@ -1,10 +1,12 @@
1
- import { a as SecretProvider } from '../types-CClP1lxq.cjs';
1
+ import { a as SecretProvider } from '../types-B_Dc2L4T.cjs';
2
2
  import '@standard-schema/spec';
3
3
 
4
4
  /**
5
5
  * AWS provider: Secrets Manager ids plus SSM parameters (with decryption)
6
6
  * merged into one flat map. SDK clients are injected, never imported blindly,
7
7
  * so tests can pass fakes and Edge bundles stay lean.
8
+ *
9
+ * @module
8
10
  */
9
11
 
10
12
  /** Minimal Secrets Manager surface this provider needs (real SDK client or a fake). */
@@ -1,10 +1,12 @@
1
- import { a as SecretProvider } from '../types-CClP1lxq.js';
1
+ import { a as SecretProvider } from '../types-B_Dc2L4T.js';
2
2
  import '@standard-schema/spec';
3
3
 
4
4
  /**
5
5
  * AWS provider: Secrets Manager ids plus SSM parameters (with decryption)
6
6
  * merged into one flat map. SDK clients are injected, never imported blindly,
7
7
  * so tests can pass fakes and Edge bundles stay lean.
8
+ *
9
+ * @module
8
10
  */
9
11
 
10
12
  /** Minimal Secrets Manager surface this provider needs (real SDK client or a fake). */
@@ -1,4 +1,4 @@
1
- import { a as SecretProvider } from '../types-CClP1lxq.cjs';
1
+ import { a as SecretProvider } from '../types-B_Dc2L4T.cjs';
2
2
  import { F as FetchFn } from '../shared-CucO_XsG.cjs';
3
3
  import '@standard-schema/spec';
4
4
 
@@ -6,6 +6,8 @@ import '@standard-schema/spec';
6
6
  * HashiCorp Vault provider (KVv1 + KVv2): reads one secret path into a flat map.
7
7
  * Token comes from `auth.token` or `VAULT_TOKEN`; KVv2 envelopes unwrap only
8
8
  * when a `metadata` sibling is present. Fail-closed on 403.
9
+ *
10
+ * @module
9
11
  */
10
12
 
11
13
  /** Options for {@link hashicorpProvider}. */
@@ -1,4 +1,4 @@
1
- import { a as SecretProvider } from '../types-CClP1lxq.js';
1
+ import { a as SecretProvider } from '../types-B_Dc2L4T.js';
2
2
  import { F as FetchFn } from '../shared-CucO_XsG.js';
3
3
  import '@standard-schema/spec';
4
4
 
@@ -6,6 +6,8 @@ import '@standard-schema/spec';
6
6
  * HashiCorp Vault provider (KVv1 + KVv2): reads one secret path into a flat map.
7
7
  * Token comes from `auth.token` or `VAULT_TOKEN`; KVv2 envelopes unwrap only
8
8
  * when a `metadata` sibling is present. Fail-closed on 403.
9
+ *
10
+ * @module
9
11
  */
10
12
 
11
13
  /** Options for {@link hashicorpProvider}. */
@@ -1,10 +1,12 @@
1
- import { F as Flat, a as SecretProvider } from '../types-CClP1lxq.cjs';
1
+ import { F as Flat, a as SecretProvider } from '../types-B_Dc2L4T.cjs';
2
2
  import { F as FetchFn } from '../shared-CucO_XsG.cjs';
3
3
  import '@standard-schema/spec';
4
4
 
5
5
  /**
6
6
  * Generic HTTP secret provider: GETs a JSON endpoint and reads a dot-path
7
7
  * (`jsonPath`, default `'data'`) into a flat map. Auth rides in `headers`.
8
+ *
9
+ * @module
8
10
  */
9
11
 
10
12
  /** Options for {@link httpProvider}. */
@@ -1,10 +1,12 @@
1
- import { F as Flat, a as SecretProvider } from '../types-CClP1lxq.js';
1
+ import { F as Flat, a as SecretProvider } from '../types-B_Dc2L4T.js';
2
2
  import { F as FetchFn } from '../shared-CucO_XsG.js';
3
3
  import '@standard-schema/spec';
4
4
 
5
5
  /**
6
6
  * Generic HTTP secret provider: GETs a JSON endpoint and reads a dot-path
7
7
  * (`jsonPath`, default `'data'`) into a flat map. Auth rides in `headers`.
8
+ *
9
+ * @module
8
10
  */
9
11
 
10
12
  /** Options for {@link httpProvider}. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typed-settings",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "One-call typed config for TS: env + .env + yaml/toml/json + secrets-dir + vault -> frozen object. Standard Schema + legacy bridges.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -89,6 +89,14 @@
89
89
  "standard-schema"
90
90
  ],
91
91
  "license": "MIT",
92
+ "repository": {
93
+ "type": "git",
94
+ "url": "https://github.com/prosubodh/typed-settings"
95
+ },
96
+ "homepage": "https://github.com/prosubodh/typed-settings#readme",
97
+ "bugs": {
98
+ "url": "https://github.com/prosubodh/typed-settings/issues"
99
+ },
92
100
  "engines": {
93
101
  "node": ">=20"
94
102
  },