@theholocron/env-utils 1.14.0 → 1.15.0

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
@@ -49,6 +49,23 @@ const env = createEnvLookup();
49
49
  const dsn = firstEnv(env, ["MYAPP_SENTRY_DSN", "SENTRY_DSN"], DEFAULT_DSN);
50
50
  ```
51
51
 
52
+ `withEnvPrefix(env, prefix)` wraps an `EnvLookup` so `get("KEY")` tries `<prefix>_KEY` first, then `KEY`. The prefix is matched with exact case, so pass it in upper case. Name the variable once and let the namespaced override win:
53
+
54
+ ```typescript
55
+ import { createEnvLookup, withEnvPrefix } from "@theholocron/env-utils";
56
+
57
+ const env = withEnvPrefix(createEnvLookup(), "MYAPP");
58
+ env.get("SENTRY_DSN"); // MYAPP_SENTRY_DSN, else SENTRY_DSN
59
+ ```
60
+
61
+ ### Importing without side effects
62
+
63
+ The package entry also exports `DotenvLoader`, whose module runs `import "dotenv/config"` and so loads `.env` into `process.env` the moment the package is imported. Libraries that only read variables should import from the `/lookup` subpath, which has `createEnvLookup`, `firstEnv` and `withEnvPrefix` and never touches dotenv:
64
+
65
+ ```typescript
66
+ import { createEnvLookup, firstEnv } from "@theholocron/env-utils/lookup";
67
+ ```
68
+
52
69
  ### Logging
53
70
 
54
71
  Debug output (key-loaded counts, missing-key warnings, deprecation notices) goes to `console.info`/`console.warn` by default. Pass an `EnvLogger` (any object shaped `{ info(message, meta?), warn(message, meta?) }`) to route it elsewhere:
package/dist/index.d.mts CHANGED
@@ -1,27 +1,5 @@
1
+ import { i as createEnvLookup, n as firstEnv, r as EnvLookup, t as withEnvPrefix } from "./with-env-prefix-BtxbH3HN.mjs";
1
2
  import "dotenv/config";
2
- //#region src/create-env-lookup.d.ts
3
- interface EnvLookup {
4
- /** Return the string value for `key`, or `undefined` if absent or empty. */
5
- get(key: string): string | undefined;
6
- }
7
- /**
8
- * Create a lightweight env lookup backed by a fixed source dict or, when
9
- * called with no arguments, by a lazy read of `process.env` on every call.
10
- *
11
- * Use the no-arg form for the application singleton — it always reflects the
12
- * current state of `process.env` so tests can mutate it freely:
13
- *
14
- * @example
15
- * // singleton — reads process.env lazily
16
- * export const env = createEnvLookup();
17
- *
18
- * // injectable — tests pass a fake dict
19
- * export function makeEnv(source?: Record<string, string | undefined>) {
20
- * return createEnvLookup(source);
21
- * }
22
- */
23
- export declare function createEnvLookup(source?: Record<string, string | undefined>): EnvLookup;
24
- //#endregion
25
3
  //#region src/types.d.ts
26
4
  type Primitive = string | number | boolean;
27
5
  type EnvObject = {
@@ -195,18 +173,6 @@ export declare const environment: {
195
173
  };
196
174
  type Environment = (typeof ENVIRONMENTS)[number];
197
175
  //#endregion
198
- //#region src/first-env.d.ts
199
- /**
200
- * Return the value of the first variable in `names` that is set and
201
- * non-empty, or `fallback` when none is. Use it for a precedence chain such
202
- * as a product-specific name, then the vendor-native name, then a default.
203
- *
204
- * @example
205
- * const dsn = firstEnv(env, ["HOLOCRON_SENTRY_DSN", "SENTRY_DSN"], DEFAULT_DSN);
206
- */
207
- export declare function firstEnv(env: EnvLookup, names: readonly string[], fallback: string): string;
208
- export declare function firstEnv(env: EnvLookup, names: readonly string[], fallback?: string): string | undefined;
209
- //#endregion
210
176
  //#region src/loader.d.ts
211
177
  /**
212
178
  * Default loader — calls dotenv/config so the .env file is
@@ -246,4 +212,4 @@ export declare function filterByNamespace(raw: Record<string, string | undefined
246
212
  */
247
213
  export declare function mergeNamespaces(raw: Record<string, string | undefined>, namespaces: string[]): Record<string, string | undefined>;
248
214
  //#endregion
249
- export type { EnvLoader, EnvLogger, EnvLookup, EnvObject, EnvParser, EnvParserOptions, Environment, Primitive };
215
+ export { type EnvLoader, type EnvLogger, type EnvLookup, type EnvObject, type EnvParser, type EnvParserOptions, type Environment, type Primitive, createEnvLookup, firstEnv, withEnvPrefix };
package/dist/index.mjs CHANGED
@@ -1,28 +1,5 @@
1
+ import { n as firstEnv, r as createEnvLookup, t as withEnvPrefix } from "./with-env-prefix-CC-WUj7Q.mjs";
1
2
  import "dotenv/config";
2
- //#region src/create-env-lookup.ts
3
- /**
4
- * Create a lightweight env lookup backed by a fixed source dict or, when
5
- * called with no arguments, by a lazy read of `process.env` on every call.
6
- *
7
- * Use the no-arg form for the application singleton — it always reflects the
8
- * current state of `process.env` so tests can mutate it freely:
9
- *
10
- * @example
11
- * // singleton — reads process.env lazily
12
- * export const env = createEnvLookup();
13
- *
14
- * // injectable — tests pass a fake dict
15
- * export function makeEnv(source?: Record<string, string | undefined>) {
16
- * return createEnvLookup(source);
17
- * }
18
- */
19
- function createEnvLookup(source) {
20
- return { get(key) {
21
- const val = (source ?? process.env)[key];
22
- return typeof val === "string" && val ? val : void 0;
23
- } };
24
- }
25
- //#endregion
26
3
  //#region src/environment.ts
27
4
  const ENVIRONMENTS = [
28
5
  "local",
@@ -315,13 +292,4 @@ function createEnvParser(options) {
315
292
  };
316
293
  }
317
294
  //#endregion
318
- //#region src/first-env.ts
319
- function firstEnv(env, names, fallback) {
320
- for (const name of names) {
321
- const value = env.get(name);
322
- if (value !== void 0) return value;
323
- }
324
- return fallback;
325
- }
326
- //#endregion
327
- export { DEFAULT_ENVIRONMENT, DEPLOYED_ENVIRONMENTS, DotenvLoader, ENVIRONMENTS, createEnvLookup, createEnvParser, environment, filterByNamespace, firstEnv, mergeNamespaces };
295
+ export { DEFAULT_ENVIRONMENT, DEPLOYED_ENVIRONMENTS, DotenvLoader, ENVIRONMENTS, createEnvLookup, createEnvParser, environment, filterByNamespace, firstEnv, mergeNamespaces, withEnvPrefix };
@@ -0,0 +1,2 @@
1
+ import { i as createEnvLookup, n as firstEnv, r as EnvLookup, t as withEnvPrefix } from "./with-env-prefix-BtxbH3HN.mjs";
2
+ export { type EnvLookup, createEnvLookup, firstEnv, withEnvPrefix };
@@ -0,0 +1,2 @@
1
+ import { n as firstEnv, r as createEnvLookup, t as withEnvPrefix } from "./with-env-prefix-CC-WUj7Q.mjs";
2
+ export { createEnvLookup, firstEnv, withEnvPrefix };
@@ -0,0 +1,57 @@
1
+ //#region src/create-env-lookup.d.ts
2
+ interface EnvLookup {
3
+ /** Return the string value for `key`, or `undefined` if absent or empty. */
4
+ get(key: string): string | undefined;
5
+ }
6
+ /**
7
+ * Create a lightweight env lookup backed by a fixed source dict or, when
8
+ * called with no arguments, by a lazy read of `process.env` on every call.
9
+ *
10
+ * Use the no-arg form for the application singleton — it always reflects the
11
+ * current state of `process.env` so tests can mutate it freely:
12
+ *
13
+ * @example
14
+ * // singleton — reads process.env lazily
15
+ * export const env = createEnvLookup();
16
+ *
17
+ * // injectable — tests pass a fake dict
18
+ * export function makeEnv(source?: Record<string, string | undefined>) {
19
+ * return createEnvLookup(source);
20
+ * }
21
+ */
22
+ declare function createEnvLookup(source?: Record<string, string | undefined>): EnvLookup;
23
+ //#endregion
24
+ //#region src/first-env.d.ts
25
+ /**
26
+ * Return the value of the first variable in `names` that is set and
27
+ * non-empty, or `fallback` when none is. Use it for a precedence chain such
28
+ * as a product-specific name, then the vendor-native name, then a default.
29
+ *
30
+ * @example
31
+ * const dsn = firstEnv(env, ["HOLOCRON_SENTRY_DSN", "SENTRY_DSN"], DEFAULT_DSN);
32
+ */
33
+ declare function firstEnv(env: EnvLookup, names: readonly string[], fallback: string): string;
34
+ declare function firstEnv(env: EnvLookup, names: readonly string[], fallback?: string): string | undefined;
35
+ //#endregion
36
+ //#region src/with-env-prefix.d.ts
37
+ /**
38
+ * Wrap an {@link EnvLookup} so every `get(key)` tries `<prefix>_<key>` first
39
+ * and falls back to the bare `key`. Callers name the variable once, and the
40
+ * namespaced override wins when it is set.
41
+ *
42
+ * Pass the prefix without a trailing underscore. Empty values are skipped,
43
+ * as in {@link EnvLookup.get}.
44
+ *
45
+ * The prefix is matched with exact case: `"holocron"` looks up
46
+ * `holocron_TOKEN`, not `HOLOCRON_TOKEN`. An {@link EnvLookup} only answers
47
+ * exact-name `get` calls, so unlike `filterByNamespace` (which scans a whole
48
+ * env record) this cannot match case-insensitively. Environment variable
49
+ * names are conventionally upper case, so pass an upper-case prefix.
50
+ *
51
+ * @example
52
+ * const env = withEnvPrefix(createEnvLookup(), "HOLOCRON");
53
+ * env.get("SENTRY_DSN"); // HOLOCRON_SENTRY_DSN, else SENTRY_DSN
54
+ */
55
+ declare function withEnvPrefix(env: EnvLookup, prefix: string): EnvLookup;
56
+ //#endregion
57
+ export { createEnvLookup as i, firstEnv as n, EnvLookup as r, withEnvPrefix as t };
@@ -0,0 +1,59 @@
1
+ //#region src/create-env-lookup.ts
2
+ /**
3
+ * Create a lightweight env lookup backed by a fixed source dict or, when
4
+ * called with no arguments, by a lazy read of `process.env` on every call.
5
+ *
6
+ * Use the no-arg form for the application singleton — it always reflects the
7
+ * current state of `process.env` so tests can mutate it freely:
8
+ *
9
+ * @example
10
+ * // singleton — reads process.env lazily
11
+ * export const env = createEnvLookup();
12
+ *
13
+ * // injectable — tests pass a fake dict
14
+ * export function makeEnv(source?: Record<string, string | undefined>) {
15
+ * return createEnvLookup(source);
16
+ * }
17
+ */
18
+ function createEnvLookup(source) {
19
+ return { get(key) {
20
+ const val = (source ?? process.env)[key];
21
+ return typeof val === "string" && val ? val : void 0;
22
+ } };
23
+ }
24
+ //#endregion
25
+ //#region src/first-env.ts
26
+ function firstEnv(env, names, fallback) {
27
+ for (const name of names) {
28
+ const value = env.get(name);
29
+ if (value !== void 0) return value;
30
+ }
31
+ return fallback;
32
+ }
33
+ //#endregion
34
+ //#region src/with-env-prefix.ts
35
+ /**
36
+ * Wrap an {@link EnvLookup} so every `get(key)` tries `<prefix>_<key>` first
37
+ * and falls back to the bare `key`. Callers name the variable once, and the
38
+ * namespaced override wins when it is set.
39
+ *
40
+ * Pass the prefix without a trailing underscore. Empty values are skipped,
41
+ * as in {@link EnvLookup.get}.
42
+ *
43
+ * The prefix is matched with exact case: `"holocron"` looks up
44
+ * `holocron_TOKEN`, not `HOLOCRON_TOKEN`. An {@link EnvLookup} only answers
45
+ * exact-name `get` calls, so unlike `filterByNamespace` (which scans a whole
46
+ * env record) this cannot match case-insensitively. Environment variable
47
+ * names are conventionally upper case, so pass an upper-case prefix.
48
+ *
49
+ * @example
50
+ * const env = withEnvPrefix(createEnvLookup(), "HOLOCRON");
51
+ * env.get("SENTRY_DSN"); // HOLOCRON_SENTRY_DSN, else SENTRY_DSN
52
+ */
53
+ function withEnvPrefix(env, prefix) {
54
+ return { get(key) {
55
+ return env.get(`${prefix}_${key}`) ?? env.get(key);
56
+ } };
57
+ }
58
+ //#endregion
59
+ export { firstEnv as n, createEnvLookup as r, withEnvPrefix as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/env-utils",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Runtime environment detection and typed env-var parsing.",
5
5
  "keywords": [
6
6
  "env",
@@ -24,6 +24,11 @@
24
24
  "types": "./dist/index.d.mts",
25
25
  "import": "./dist/index.mjs",
26
26
  "default": "./dist/index.mjs"
27
+ },
28
+ "./lookup": {
29
+ "types": "./dist/lookup.d.mts",
30
+ "import": "./dist/lookup.mjs",
31
+ "default": "./dist/lookup.mjs"
27
32
  }
28
33
  },
29
34
  "files": [