1import * as ConfigProvider from "effect/ConfigProvider";
2import * as Effect from "effect/Effect";
3
4/**
5 * The variable names an env-style ConfigProvider knows, for debug logging.
6 *
7 * `fromEnv`/`fromDotEnv` index variables as a trie split on `_`
8 * (`APP_DB_URL` lives at `["APP", "DB", "URL"]`), so the names are rebuilt
9 * by walking that trie and joining the segments again. A node carrying a
10 * value is a variable even when it also has children (`APP` and `APP_DB`).
11 * Values are never read.
12 */
13export const envKeys = (provider: ConfigProvider.ConfigProvider) => {
14 const walk = (
15 path: ReadonlyArray<string>,
16 ): Effect.Effect<ReadonlyArray<string>, ConfigProvider.SourceError> =>
17 Effect.gen(function* () {
18 const node = yield* provider.load(path);
19 if (node === undefined) return [];
20 const name = path.join("_");
21 if (node._tag === "Value") return [name];
22 if (node._tag !== "Record") return [];
23 const children = yield* Effect.forEach([...node.keys], (key) =>
24 walk([...path, key]),
25 );
26 const own = node.value === undefined ? [] : [name];
27 return [...own, ...children.flat()];
28 });
29 return walk([]).pipe(Effect.catch(() => Effect.succeed([])));
30};
31
32/** `Loaded 3 secrets from <source>: A, B, C` at debug level. Names are sorted. */
33export const logLoadedKeys = (source: string, keys: ReadonlyArray<string>) =>
34 Effect.logDebug(
35 keys.length === 0
36 ? `Loaded no secrets from ${source}`
37 : `Loaded ${keys.length} secrets from ${source}: ${[...keys].sort().join(", ")}`,
38 );